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