use crate::abilities::Abilities;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::hash::Hash;
use std::sync::{Arc, Mutex};
#[cfg(feature = "serde")]
fn default_abilities_modifiers() -> Arc<Mutex<Abilities>> {
Arc::new(Mutex::new(Abilities::default()))
}
#[derive(Debug, Clone)]
#[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, Clone)]
#[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 cantrip_slots: u8,
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, Clone)]
#[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 hunters_prey: Option<String>,
pub defensive_tactics: Option<String>,
pub additional_fighting_style: Option<String>,
pub multiattack: Option<String>,
pub superior_hunters_defense: Option<String>,
pub natural_explorer_terrain_type: Option<Vec<String>>,
pub ranger_favored_enemy_type: Option<Vec<String>>,
pub sorcerer_metamagic: Option<Vec<String>>,
pub warlock_eldritch_invocation: Option<Vec<String>>,
pub sorcerer_dragon_ancestor: Option<String>,
#[cfg_attr(
feature = "serde",
serde(
skip_serializing,
skip_deserializing,
default = "default_abilities_modifiers"
)
)]
#[cfg_attr(feature = "utoipa", schema(ignore))]
pub abilities: Arc<Mutex<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(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))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct Classes(pub HashMap<String, Class>);
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Classes {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let map = HashMap::<String, Class>::deserialize(deserializer)?;
Ok(Classes(map))
}
}
impl Classes {
#[cfg(feature = "serde")]
pub fn deserialize_with_abilities(
value: serde_json::Value,
shared_abilities: Arc<Mutex<Abilities>>,
) -> Result<Self, serde_json::Error> {
let mut result = Classes::default();
let class_map = value
.as_object()
.ok_or_else(|| serde::de::Error::custom("Expected object for Classes"))?;
for (key, value) in class_map {
let mut class_properties: ClassProperties =
serde_json::from_value(value.get(1).cloned().unwrap_or(serde_json::Value::Null))?;
class_properties.abilities = shared_abilities.clone();
let index = key.clone();
let class = Class(index, class_properties);
result.0.insert(key.clone(), class);
}
Ok(result)
}
pub fn new(class_index: String) -> Self {
let mut classes = Self::default();
let spell_casting = match class_index.as_str() {
"cleric" | "paladin" | "druid" | "wizard" => {
Some(ClassSpellCasting::AlreadyKnowPrepared {
spells_prepared_index: Vec::new(),
pending_preparation: true,
})
}
"ranger" | "bard" | "warlock" | "sorcerer" => {
Some(ClassSpellCasting::KnowledgeAlreadyPrepared {
spells_index: Vec::new(),
usable_slots: UsableSlots::default(),
})
}
_ => None,
};
let class_properties = ClassProperties {
spell_casting,
..ClassProperties::default()
};
classes
.0
.insert(class_index.clone(), Class(class_index, class_properties));
classes
}
}