dnd_character/
classes.rs

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    // Wizard
12    // Ask the user to prepare spells at the start of the day
13    //
14    // TODO: Add slots and consume them instead of removing from prepared
15    // TODO: daily chosable spells = inteligence + level
16    KnowledgePrepared {
17        /// Indexes from https://www.dnd5eapi.co/api/spells/
18        spells_index: Vec<Vec<String>>,
19        /// Indexes from https://www.dnd5eapi.co/api/spells/
20        spells_prepared_index: Vec<Vec<String>>,
21        /// If the user has already prepared spells for the day
22        pending_preparation: bool,
23    },
24    // TEMP: Wizard
25    // Cleric, Paladin, Druid
26    // Ask the user to prepare spells at the start of the day
27    //
28    // TODO: Add slots and consume them instead of removing from prepared
29    // TODO: cleric/druid daily chosable spells = WISDOM + (level/2)
30    // TODO: paladin daily chosable spells = CHARISMA + (level/2)
31    AlreadyKnowPrepared {
32        /// Indexes from https://www.dnd5eapi.co/api/spells/
33        spells_prepared_index: Vec<Vec<String>>,
34        /// If the user has already prepared spells for the day
35        pending_preparation: bool,
36    },
37    // Bard, Ranger, Warlock, (Sorcerer?)
38    // No need to ask anything, at the start of the day
39    KnowledgeAlreadyPrepared {
40        /// Indexes from https://www.dnd5eapi.co/api/spells/
41        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    /// The level of the class
69    pub level: u8,
70    /// Index from https://www.dnd5eapi.co/api/subclasses/
71    pub subclass: Option<String>,
72    /// Indexes from https://www.dnd5eapi.co/api/spells/
73    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 abilities_modifiers: Abilities,
83}
84
85/// The key is the index of the class from https://www.dnd5eapi.co/api/classes
86#[derive(Debug)]
87#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
88#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
89#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
90pub struct Class(String, pub ClassProperties);
91
92impl Hash for Class {
93    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
94        self.0.hash(state);
95    }
96}
97
98impl PartialEq for Class {
99    fn eq(&self, other: &Self) -> bool {
100        self.0 == other.0
101    }
102}
103
104impl Eq for Class {}
105
106impl Class {
107    pub fn index(&self) -> &str {
108        &self.0
109    }
110
111    pub fn hit_dice(&self) -> u8 {
112        match self.index() {
113            "barbarian" => 12,
114            "bard" => 8,
115            "cleric" => 8,
116            "druid" => 8,
117            "fighter" => 10,
118            "monk" => 8,
119            "paladin" => 10,
120            "ranger" => 10,
121            "rogue" => 8,
122            "sorcerer" => 6,
123            "warlock" => 8,
124            "wizard" => 6,
125            // For unknown classes we will use the minimum hit dice
126            _ => 6,
127        }
128    }
129}
130
131#[derive(Default, Debug)]
132#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
133#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
134#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
135pub struct Classes(pub HashMap<String, Class>);
136
137impl Classes {
138    pub fn new(class_index: String) -> Self {
139        let mut classes = Self::default();
140
141        let spell_casting = match class_index.as_str() {
142            "cleric" | "paladin" | "druid" | "wizard " => {
143                Some(ClassSpellCasting::AlreadyKnowPrepared {
144                    spells_prepared_index: Vec::new(),
145                    pending_preparation: true,
146                })
147            }
148            "ranger" | "bard" | "warlock" => Some(ClassSpellCasting::KnowledgeAlreadyPrepared {
149                spells_index: Vec::new(),
150                usable_slots: UsableSlots::default(),
151            }),
152            _ => None,
153        };
154
155        let class_properties = ClassProperties {
156            spell_casting,
157            ..ClassProperties::default()
158        };
159
160        classes
161            .0
162            .insert(class_index.clone(), Class(class_index, class_properties));
163        classes
164    }
165}