use crate::constants; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SpellDef { pub id: String, pub code: String, pub name: String, pub caster_class: String, pub spell_level: i32, pub spell_points: i32, pub range: String, pub duration: String, pub description: String, pub combat_effect: String, pub combat_damage_min: i32, pub combat_damage_max: i32, pub heal_min: i32, pub heal_max: i32, } const SPELLS_JSON: &str = include_str!("../../../../skara-brae-common/data/spells/spells.json"); pub fn load_spells() -> Vec { serde_json::from_str(SPELLS_JSON).expect("failed to parse spells.json") } pub fn class_for_spells(class: &str) -> &str { if class == "mage" { "magician" } else { class } } pub fn spells_for_class(class: &str, spell_level: i32) -> Vec<&'static SpellDef> { let spell_class = class_for_spells(class); ALL_SPELLS .iter() .filter(|s| s.caster_class == spell_class && s.spell_level <= spell_level) .collect() } pub fn find_spell(id: &str) -> Option<&'static SpellDef> { ALL_SPELLS.iter().find(|s| s.id == id) } pub fn spell_learn_cost(spell_level: i32) -> i32 { if (constants::MIN_SPELL_LEVEL..=constants::MAX_SPELL_LEVEL).contains(&spell_level) { constants::SPELL_LEARN_COST_BY_LEVEL[(spell_level - 1) as usize] } else { constants::SPELL_LEARN_COST_UNKNOWN_LEVEL } } pub fn caster_spell_level(character_level: i32) -> i32 { (character_level / constants::CASTER_LEVELS_PER_SPELL_LEVEL) + 1 } pub fn max_spell_points(class: &str, level: i32) -> i32 { let spell_class = class_for_spells(class); let spell_level = match spell_class { "conjurer" | "magician" | "sorcerer" | "wizard" => caster_spell_level(level), _ => 0, }; spell_level * constants::SPELL_POINTS_PER_SPELL_LEVEL } pub fn is_caster_class(class: &str) -> bool { matches!( class_for_spells(class), "conjurer" | "magician" | "sorcerer" | "wizard" ) } pub fn caster_classes() -> &'static [&'static str] { &["conjurer", "magician", "sorcerer", "wizard"] } use std::sync::LazyLock; static ALL_SPELLS: LazyLock> = LazyLock::new(load_spells); #[cfg(test)] mod tests { use super::*; #[test] fn caster_spell_level_spot_values() { assert_eq!(caster_spell_level(1), 1); assert_eq!(caster_spell_level(2), 1); assert_eq!(caster_spell_level(3), 2); assert_eq!(caster_spell_level(6), 3); assert_eq!(caster_spell_level(100), 34); } #[test] fn spell_learn_cost_guard_spot_checks() { assert_eq!( spell_learn_cost(0), constants::SPELL_LEARN_COST_UNKNOWN_LEVEL ); assert_eq!( spell_learn_cost(-1), constants::SPELL_LEARN_COST_UNKNOWN_LEVEL ); assert_eq!( spell_learn_cost(8), constants::SPELL_LEARN_COST_UNKNOWN_LEVEL ); for level in constants::MIN_SPELL_LEVEL..=constants::MAX_SPELL_LEVEL { assert_eq!( spell_learn_cost(level), constants::SPELL_LEARN_COST_BY_LEVEL[(level - 1) as usize] ); } } #[test] fn spells_json_parses() { assert!(!load_spells().is_empty()); } }