use std::collections::{HashMap};
use std::hash::Hash;
use serde::{Deserialize, Serialize};
use crate::abilities::Abilities;
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub enum ClassSpellCasting {
KnowledgePrepared {
spells_index: Vec<Vec<String>>,
spells_prepared_index: Vec<Vec<String>>,
pending_preparation: bool,
},
AlreadyKnowPrepared {
spells_prepared_index: Vec<Vec<String>>,
pending_preparation: bool,
},
KnowledgeAlreadyPrepared {
spells_index: Vec<Vec<String>>,
usable_slots: UsableSlots,
},
}
#[derive(Debug, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct UsableSlots {
pub level_1: u8,
pub level_2: u8,
pub level_3: u8,
pub level_4: u8,
pub level_5: u8,
pub level_6: u8,
pub level_7: u8,
pub level_8: u8,
pub level_9: u8,
}
#[derive(Debug, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct ClassProperties {
pub level: u8,
pub subclass: Option<String>,
pub spell_casting: Option<ClassSpellCasting>,
pub fighting_style: Option<String>,
pub additional_fighting_style: Option<String>,
pub abilities_modifiers: Abilities,
}
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct Class(pub String, pub ClassProperties);
impl Hash for Class {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl PartialEq for Class {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Eq for Class {}
impl Class {
pub fn index(&self) -> &str {
&self.0
}
pub fn hit_dice(&self) -> u8 {
match self.index() {
"barbarian" => 12,
"bard" => 8,
"cleric" => 8,
"druid" => 8,
"fighter" => 10,
"monk" => 8,
"paladin" => 10,
"ranger" => 10,
"rogue" => 8,
"sorcerer" => 6,
"warlock" => 8,
"wizard" => 6,
_ => 6,
}
}
}
#[derive(Default, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct Classes(
#[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_classes"))]
pub Vec<Class>
);
#[cfg(feature = "serde")]
fn deserialize_classes<'de, D>(deserializer: D) -> Result<Vec<Class>, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum ClassesFormat {
Vec(Vec<Class>),
Map(HashMap<String, Class>),
}
match ClassesFormat::deserialize(deserializer)? {
ClassesFormat::Vec(vec) => Ok(vec),
ClassesFormat::Map(map) => Ok(map.into_iter().map(|(_, class)| class).collect()),
}
}
impl Classes {
pub fn new(class_index: String) -> Self {
let mut classes = Self::default();
let spell_casting = match class_index.as_str() {
"cleric" | "paladin" | "druid" => {
Some(ClassSpellCasting::AlreadyKnowPrepared {
spells_prepared_index: Vec::new(),
pending_preparation: true,
})
}
_ => None
};
let class_properties = ClassProperties {
spell_casting,
..ClassProperties::default()
};
let class = Class(class_index.clone(), class_properties);
if !classes.0.iter().any(|c| c.0 == class_index) {
classes.0.push(class);
}
classes
}
}