1use crate::abilities::Abilities;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::hash::Hash;
5
6#[derive(Debug, Clone)]
7#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
8#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
9#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
10pub enum ClassSpellCasting {
11 KnowledgePrepared {
17 spells_index: Vec<Vec<String>>,
19 spells_prepared_index: Vec<Vec<String>>,
21 pending_preparation: bool,
23 },
24 AlreadyKnowPrepared {
32 spells_prepared_index: Vec<Vec<String>>,
34 pending_preparation: bool,
36 },
37 KnowledgeAlreadyPrepared {
40 spells_index: Vec<Vec<String>>,
42 usable_slots: UsableSlots,
43 },
44}
45
46#[derive(Debug, Default, Clone)]
47#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
48#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
49#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
50pub struct UsableSlots {
51 pub cantrip_slots: u8,
52 pub level_1: u8,
53 pub level_2: u8,
54 pub level_3: u8,
55 pub level_4: u8,
56 pub level_5: u8,
57 pub level_6: u8,
58 pub level_7: u8,
59 pub level_8: u8,
60 pub level_9: u8,
61}
62
63#[derive(Debug, Default, Clone)]
64#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
65#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
66#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
67pub struct ClassProperties {
68 pub level: u8,
70 pub subclass: Option<String>,
72 pub spell_casting: Option<ClassSpellCasting>,
74 pub fighting_style: Option<String>,
75 pub hunters_prey: Option<String>,
76 pub defensive_tactics: Option<String>,
77 pub additional_fighting_style: Option<String>,
78 pub multiattack: Option<String>,
79 pub superior_hunters_defense: Option<String>,
80 pub natural_explorer_terrain_type: Option<Vec<String>>,
81 pub ranger_favored_enemy_type: Option<Vec<String>>,
82 pub sorcerer_metamagic: Option<Vec<String>>,
83 pub warlock_eldritch_invocation: Option<Vec<String>>,
84 pub sorcerer_dragon_ancestor: Option<String>,
85 pub abilities_modifiers: Abilities,
86}
87
88#[derive(Debug)]
90#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
91#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
92#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
93pub struct Class(String, pub ClassProperties);
94
95impl Hash for Class {
96 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
97 self.0.hash(state);
98 }
99}
100
101impl PartialEq for Class {
102 fn eq(&self, other: &Self) -> bool {
103 self.0 == other.0
104 }
105}
106
107impl Eq for Class {}
108
109impl Class {
110 pub fn index(&self) -> &str {
111 &self.0
112 }
113
114 pub fn hit_dice(&self) -> u8 {
115 match self.index() {
116 "barbarian" => 12,
117 "bard" => 8,
118 "cleric" => 8,
119 "druid" => 8,
120 "fighter" => 10,
121 "monk" => 8,
122 "paladin" => 10,
123 "ranger" => 10,
124 "rogue" => 8,
125 "sorcerer" => 6,
126 "warlock" => 8,
127 "wizard" => 6,
128 _ => 6,
130 }
131 }
132}
133
134#[derive(Default, Debug)]
135#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
136#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
137#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
138pub struct Classes(pub HashMap<String, Class>);
139
140impl Classes {
141 pub fn new(class_index: String) -> Self {
142 let mut classes = Self::default();
143
144 let spell_casting = match class_index.as_str() {
145 "cleric" | "paladin" | "druid" | "wizard" => {
146 Some(ClassSpellCasting::AlreadyKnowPrepared {
147 spells_prepared_index: Vec::new(),
148 pending_preparation: true,
149 })
150 }
151 "ranger" | "bard" | "warlock" | "sorcerer" => {
152 Some(ClassSpellCasting::KnowledgeAlreadyPrepared {
153 spells_index: Vec::new(),
154 usable_slots: UsableSlots::default(),
155 })
156 }
157 _ => None,
158 };
159
160 let class_properties = ClassProperties {
161 spell_casting,
162 ..ClassProperties::default()
163 };
164
165 classes
166 .0
167 .insert(class_index.clone(), Class(class_index, class_properties));
168 classes
169 }
170}