dnd_character/
classes.rs

1use std::collections::{HashMap};
2use std::hash::Hash;
3use serde::{Deserialize, Serialize};
4use crate::abilities::Abilities;
5
6#[derive(Debug)]
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    // Wizard
12    // Ask the user to prepare spells at the start of the day
13    KnowledgePrepared {
14        /// Indexes from https://www.dnd5eapi.co/api/spells/
15        spells_index: Vec<Vec<String>>,
16        /// Indexes from https://www.dnd5eapi.co/api/spells/
17        spells_prepared_index: Vec<Vec<String>>,
18        /// If the user has already prepared spells for the day
19        pending_preparation: bool,
20    },
21    // Cleric, Paladin, Druid
22    // Ask the user to prepare spells at the start of the day
23    AlreadyKnowPrepared {
24        /// Indexes from https://www.dnd5eapi.co/api/spells/
25        spells_prepared_index: Vec<Vec<String>>,
26        /// If the user has already prepared spells for the day
27        pending_preparation: bool,
28    },
29    // Bard, Ranger, Warlock
30    // No need to ask anything, at the start of the day
31    KnowledgeAlreadyPrepared {
32        /// Indexes from https://www.dnd5eapi.co/api/spells/
33        spells_index: Vec<Vec<String>>,
34        usable_slots: UsableSlots,
35    },
36}
37
38#[derive(Debug, Default)]
39#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
40#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
41#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
42pub struct UsableSlots {
43    pub level_1: u8,
44    pub level_2: u8,
45    pub level_3: u8,
46    pub level_4: u8,
47    pub level_5: u8,
48    pub level_6: u8,
49    pub level_7: u8,
50    pub level_8: u8,
51    pub level_9: u8,
52}
53
54#[derive(Debug, Default)]
55#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
56#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
57#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
58pub struct ClassProperties {
59    /// The level of the class
60    pub level: u8,
61    /// Index from https://www.dnd5eapi.co/api/subclasses/
62    pub subclass: Option<String>,
63    /// Indexes from https://www.dnd5eapi.co/api/spells/
64    pub spell_casting: Option<ClassSpellCasting>,
65    pub fighting_style: Option<String>,
66    pub additional_fighting_style: Option<String>,
67    pub abilities_modifiers: Abilities,
68}
69
70/// The key is the index of the class from https://www.dnd5eapi.co/api/classes
71#[derive(Debug)]
72#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
73#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
74#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
75pub struct Class(String, pub ClassProperties);
76
77impl Hash for Class {
78    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
79        self.0.hash(state);
80    }
81}
82
83impl PartialEq for Class {
84    fn eq(&self, other: &Self) -> bool {
85        self.0 == other.0
86    }
87}
88
89impl Eq for Class {}
90
91impl Class {
92    pub fn index(&self) -> &str {
93        &self.0
94    }
95
96    pub fn hit_dice(&self) -> u8 {
97        match self.index() {
98            "barbarian" => 12,
99            "bard" => 8,
100            "cleric" => 8,
101            "druid" => 8,
102            "fighter" => 10,
103            "monk" => 8,
104            "paladin" => 10,
105            "ranger" => 10,
106            "rogue" => 8,
107            "sorcerer" => 6,
108            "warlock" => 8,
109            "wizard" => 6,
110            // For unknown classes we will use the minimum hit dice
111            _ => 6,
112        }
113    }
114}
115
116#[derive(Default, Debug)]
117#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
118#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
119#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
120pub struct Classes(pub HashMap<String, Class>);
121
122
123impl Classes {
124    pub fn new(class_index: String) -> Self {
125        let mut classes = Self::default();
126
127        let spell_casting = match class_index.as_str() {
128            "cleric" | "paladin" | "druid" => {
129                Some(ClassSpellCasting::AlreadyKnowPrepared {
130                    spells_prepared_index: Vec::new(),
131                    pending_preparation: true,
132                })
133            }
134            _ => {
135                None
136            }
137        };
138
139        let class_properties = ClassProperties {
140            spell_casting,
141            ..ClassProperties::default()
142        };
143
144        classes.0.insert(class_index.clone(), Class(class_index, class_properties));
145        classes
146    }
147}