1use crate::abilities::Abilities;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::hash::Hash;
5use std::sync::{Arc, Mutex};
6
7#[cfg(feature = "serde")]
9fn default_abilities_modifiers() -> Arc<Mutex<Abilities>> {
10 Arc::new(Mutex::new(Abilities::default()))
11}
12
13#[derive(Debug, Clone)]
14#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
15#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
16#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
17pub enum ClassSpellCasting {
18 KnowledgePrepared {
24 spells_index: Vec<Vec<String>>,
26 spells_prepared_index: Vec<Vec<String>>,
28 pending_preparation: bool,
30 },
31 AlreadyKnowPrepared {
39 spells_prepared_index: Vec<Vec<String>>,
41 pending_preparation: bool,
43 },
44 KnowledgeAlreadyPrepared {
47 spells_index: Vec<Vec<String>>,
49 usable_slots: UsableSlots,
50 },
51}
52
53#[derive(Debug, Default, Clone)]
54#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
55#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
56#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
57pub struct UsableSlots {
58 pub cantrip_slots: u8,
59 pub level_1: u8,
60 pub level_2: u8,
61 pub level_3: u8,
62 pub level_4: u8,
63 pub level_5: u8,
64 pub level_6: u8,
65 pub level_7: u8,
66 pub level_8: u8,
67 pub level_9: u8,
68}
69
70#[derive(Debug, Default, Clone)]
71#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
72#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
73#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
74pub struct ClassProperties {
75 pub level: u8,
77 pub subclass: Option<String>,
79 pub spell_casting: Option<ClassSpellCasting>,
81 pub fighting_style: Option<String>,
82 pub hunters_prey: Option<String>,
83 pub defensive_tactics: Option<String>,
84 pub additional_fighting_style: Option<String>,
85 pub multiattack: Option<String>,
86 pub superior_hunters_defense: Option<String>,
87 pub natural_explorer_terrain_type: Option<Vec<String>>,
88 pub ranger_favored_enemy_type: Option<Vec<String>>,
89 pub sorcerer_metamagic: Option<Vec<String>>,
90 pub warlock_eldritch_invocation: Option<Vec<String>>,
91 pub sorcerer_dragon_ancestor: Option<String>,
92 #[deprecated]
93 pub abilities_modifiers: Abilities,
94 #[cfg_attr(
95 feature = "serde",
96 serde(
97 skip_serializing,
98 skip_deserializing,
99 default = "default_abilities_modifiers"
100 )
101 )]
102 #[cfg_attr(feature = "utoipa", schema(ignore))]
103 pub abilities: Arc<Mutex<Abilities>>,
104}
105
106#[derive(Debug)]
108#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
109#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
110#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
111pub struct Class(String, pub ClassProperties);
112
113impl Hash for Class {
114 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
115 self.0.hash(state);
116 }
117}
118
119impl PartialEq for Class {
120 fn eq(&self, other: &Self) -> bool {
121 self.0 == other.0
122 }
123}
124
125impl Eq for Class {}
126
127impl Class {
128 pub fn index(&self) -> &str {
129 &self.0
130 }
131
132 pub fn hit_dice(&self) -> u8 {
133 match self.index() {
134 "barbarian" => 12,
135 "bard" => 8,
136 "cleric" => 8,
137 "druid" => 8,
138 "fighter" => 10,
139 "monk" => 8,
140 "paladin" => 10,
141 "ranger" => 10,
142 "rogue" => 8,
143 "sorcerer" => 6,
144 "warlock" => 8,
145 "wizard" => 6,
146 _ => 6,
148 }
149 }
150}
151
152#[derive(Default, Debug)]
153#[cfg_attr(feature = "serde", derive(Serialize))]
154#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
155#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
156pub struct Classes(pub HashMap<String, Class>);
157
158#[cfg(feature = "serde")]
159impl<'de> Deserialize<'de> for Classes {
160 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
161 where
162 D: serde::Deserializer<'de>,
163 {
164 let map = HashMap::<String, Class>::deserialize(deserializer)?;
167 Ok(Classes(map))
168 }
169}
170
171impl Classes {
172 #[cfg(feature = "serde")]
173 pub fn deserialize_with_abilities(
174 value: serde_json::Value,
175 shared_abilities: Arc<Mutex<Abilities>>,
176 ) -> Result<Self, serde_json::Error> {
177 let mut result = Classes::default();
178
179 let class_map = value
181 .as_object()
182 .ok_or_else(|| serde::de::Error::custom("Expected object for Classes"))?;
183
184 for (key, value) in class_map {
185 let mut class_properties: ClassProperties =
187 serde_json::from_value(value.get(1).cloned().unwrap_or(serde_json::Value::Null))?;
188
189 class_properties.abilities = shared_abilities.clone();
191
192 let index = key.clone();
194 let class = Class(index, class_properties);
195
196 result.0.insert(key.clone(), class);
198 }
199
200 Ok(result)
201 }
202
203 pub fn new(class_index: String) -> Self {
204 let mut classes = Self::default();
205
206 let spell_casting = match class_index.as_str() {
207 "cleric" | "paladin" | "druid" | "wizard" => {
208 Some(ClassSpellCasting::AlreadyKnowPrepared {
209 spells_prepared_index: Vec::new(),
210 pending_preparation: true,
211 })
212 }
213 "ranger" | "bard" | "warlock" | "sorcerer" => {
214 Some(ClassSpellCasting::KnowledgeAlreadyPrepared {
215 spells_index: Vec::new(),
216 usable_slots: UsableSlots::default(),
217 })
218 }
219 _ => None,
220 };
221
222 let class_properties = ClassProperties {
223 spell_casting,
224 ..ClassProperties::default()
225 };
226
227 classes
230 .0
231 .insert(class_index.clone(), Class(class_index, class_properties));
232 classes
233 }
234}