use crate::dice::roll_formula; use crate::random_tables::RANDOM_TABLES; use crate::rules::ability_scores::{AbilityScore, AbilityScoreCollection}; use crate::rules::classes::Class; use crate::rules::races::Race; use log::debug; use std::collections::HashMap; // use std::fmt; pub struct Npc { pub alignment: Option, pub race: Option<&'static Race>, pub class: Option<&'static Class>, pub ability_scores: Option, pub persona: Option, } impl Npc { pub fn new( alignment: Option, race: Option<&'static Race>, class: Option<&'static Class>, ability_scores: Option, persona: Option, ) -> Self { Npc { alignment, race, class, ability_scores, persona, } } pub fn roll_henchman_ability_scores(&mut self) { rand::thread_rng(); let mut ability_score_rolls: HashMap = HashMap::new(); for &ability in &[ AbilityScore::Strength, AbilityScore::Intelligence, AbilityScore::Wisdom, AbilityScore::Dexterity, AbilityScore::Constitution, AbilityScore::Charisma, ] { debug!("Generating score for {}", &ability.abbr()); // Roll 3d6 down the line. let mut roll_result = roll_formula("3d6").unwrap(); debug!("Rolled {}", roll_result.total()); let class_ref = self.class.unwrap(); let race_ref = self.race.unwrap(); // For ability scores which are prime requisites of the class, // increase all dice by 1 which do not already show a 6. if class_ref.prime_requisites.contains(&ability) { roll_result.increase_sides_below_max(6, 1); } debug!("Bumped prime requisites, now at {}", roll_result.total()); // At this point we don't need the individual dice anymore. let mut total = roll_result.total() as i32; // Add NPC-specific class ability score modifiers. total += class_ref .npc_ability_score_modifiers .get(&ability) .copied() .unwrap_or(0); debug!("After adding NPC class modifiers, now at {}", total); // Add racial ability score modifiers. total += race_ref .npc_ability_score_modifiers .get(&ability) .copied() .unwrap_or(0); debug!("After adding racial modifiers, now at {}", total); // Ensure racial ability score limits are imposed. // TODO: Use u8 for all of these, so no conversion from u32 will be needed. let [min_score, max_score] = race_ref.ability_score_ranges.get(&ability).unwrap().male; total = total.max(min_score as i32); total = total.min(max_score as i32); ability_score_rolls.insert(ability, total); } // Create an AbilityScoreCollection for the Npc, using the above results. let mut score_collection = AbilityScoreCollection::new(); for (ability, value) in &ability_score_rolls { score_collection.add_score(*ability, *value); } self.ability_scores = Some(score_collection); // TODO: Verify legality of class based on alignment. // TODO: Verify legality of class based on race. // TODO: Verify legality of class based on ability scores. } pub fn randomize_persona(&mut self) { let appearance = RANDOM_TABLES .roll_table("npc_general_appearance") .to_string(); let tendencies = RANDOM_TABLES .roll_table("npc_general_tendencies") .to_string(); let personality = RANDOM_TABLES.roll_table("npc_personality").to_string(); let disposition = RANDOM_TABLES.roll_table("npc_disposition").to_string(); let components = vec![appearance, tendencies, personality, disposition]; self.persona = Some(components.join(", ")); } // TODO: Probably break this out later like this. // fn increase_prime_requisites(&mut self, roll_result: &mut RollResult) { // let class_ref = self.class.unwrap(); // // // For ability scores which are prime requisites of the class, // // increase all dice by 1 which do not already show a 6. // if class_ref.prime_requisites.contains(&ability) { // roll_result.increase_sides_below_max(6, 1); // } // roll_result.increase_sides_below_max(6, 1); // } } // impl fmt::Display for Npc { // fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // let values: Vec<&str> = vec![ // self.alignment.as_deref().unwrap_or(""), // self.race.as_deref().unwrap_or(""), // self.class.as_ref().map_or("", |class| &class.name), // ] // .into_iter() // .filter(|&s| !s.is_empty()) // .collect(); // // let formatted_string = values.join(" "); // write!(f, "{}", formatted_string) // } // } #[cfg(test)] mod tests { use super::*; use crate::rules::classes::CLASSES; use crate::rules::races::RACES; #[test] #[ignore] #[should_panic(expected = "Ability score generation isn't testable yet.")] fn test_roll_henchman_ability_scores() { let class_ref = CLASSES.get("fighter").unwrap(); let race_ref = RACES.get("dwarf").unwrap(); let mut npc = Npc { alignment: Some(String::from("Lawful Good")), race: Some(race_ref), class: Some(class_ref), ability_scores: None, }; // Roll ability scores for the Npc. npc.roll_henchman_ability_scores(); // TODO: Need to actually test this process. // Check if ability scores are modified correctly based on class requirements and modifiers. // let ability_scores = npc.ability_scores.unwrap(); } }