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;
pub struct Npc {
pub alignment: Option<String>,
pub race: Option<&'static Race>,
pub class: Option<&'static Class>,
pub ability_scores: Option<AbilityScoreCollection>,
pub persona: Option<String>,
}
impl Npc {
pub fn new(
alignment: Option<String>,
race: Option<&'static Race>,
class: Option<&'static Class>,
ability_scores: Option<AbilityScoreCollection>,
persona: Option<String>,
) -> 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<AbilityScore, i32> = HashMap::new();
for &ability in &[
AbilityScore::Strength,
AbilityScore::Intelligence,
AbilityScore::Wisdom,
AbilityScore::Dexterity,
AbilityScore::Constitution,
AbilityScore::Charisma,
] {
debug!("Generating score for {}", &ability.abbr());
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();
if class_ref.prime_requisites.contains(&ability) {
roll_result.increase_sides_below_max(6, 1);
}
debug!("Bumped prime requisites, now at {}", roll_result.total());
let mut total = roll_result.total() as i32;
total += class_ref
.npc_ability_score_modifiers
.get(&ability)
.copied()
.unwrap_or(0);
debug!("After adding NPC class modifiers, now at {}", total);
total += race_ref
.npc_ability_score_modifiers
.get(&ability)
.copied()
.unwrap_or(0);
debug!("After adding racial modifiers, now at {}", total);
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);
}
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);
}
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(", "));
}
}
#[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,
persona: None,
};
npc.roll_henchman_ability_scores();
}
}