1use crate::dice::roll_formula;
2use crate::random_tables::RANDOM_TABLES;
3use crate::rules::ability_scores::{AbilityScore, AbilityScoreCollection};
4use crate::rules::classes::Class;
5use crate::rules::races::Race;
6use log::debug;
7use std::collections::HashMap;
8pub struct Npc {
11 pub alignment: Option<String>,
12 pub race: Option<&'static Race>,
13 pub class: Option<&'static Class>,
14 pub ability_scores: Option<AbilityScoreCollection>,
15 pub persona: Option<String>,
16}
17
18impl Npc {
19 pub fn new(
20 alignment: Option<String>,
21 race: Option<&'static Race>,
22 class: Option<&'static Class>,
23 ability_scores: Option<AbilityScoreCollection>,
24 persona: Option<String>,
25 ) -> Self {
26 Npc {
27 alignment,
28 race,
29 class,
30 ability_scores,
31 persona,
32 }
33 }
34
35 pub fn roll_henchman_ability_scores(&mut self) {
36 rand::thread_rng();
37 let mut ability_score_rolls: HashMap<AbilityScore, i32> = HashMap::new();
38
39 for &ability in &[
40 AbilityScore::Strength,
41 AbilityScore::Intelligence,
42 AbilityScore::Wisdom,
43 AbilityScore::Dexterity,
44 AbilityScore::Constitution,
45 AbilityScore::Charisma,
46 ] {
47 debug!("Generating score for {}", &ability.abbr());
48 let mut roll_result = roll_formula("3d6").unwrap();
50 debug!("Rolled {}", roll_result.total());
51 let class_ref = self.class.unwrap();
52 let race_ref = self.race.unwrap();
53
54 if class_ref.prime_requisites.contains(&ability) {
57 roll_result.increase_sides_below_max(6, 1);
58 }
59 debug!("Bumped prime requisites, now at {}", roll_result.total());
60
61 let mut total = roll_result.total() as i32;
63
64 total += class_ref
66 .npc_ability_score_modifiers
67 .get(&ability)
68 .copied()
69 .unwrap_or(0);
70 debug!("After adding NPC class modifiers, now at {}", total);
71
72 total += race_ref
74 .npc_ability_score_modifiers
75 .get(&ability)
76 .copied()
77 .unwrap_or(0);
78 debug!("After adding racial modifiers, now at {}", total);
79
80 let [min_score, max_score] = race_ref.ability_score_ranges.get(&ability).unwrap().male;
83 total = total.max(min_score as i32);
84 total = total.min(max_score as i32);
85
86 ability_score_rolls.insert(ability, total);
87 }
88
89 let mut score_collection = AbilityScoreCollection::new();
91 for (ability, value) in &ability_score_rolls {
92 score_collection.add_score(*ability, *value);
93 }
94
95 self.ability_scores = Some(score_collection);
96
97 }
101
102 pub fn randomize_persona(&mut self) {
103 let appearance = RANDOM_TABLES
104 .roll_table("npc_general_appearance")
105 .to_string();
106 let tendencies = RANDOM_TABLES
107 .roll_table("npc_general_tendencies")
108 .to_string();
109 let personality = RANDOM_TABLES.roll_table("npc_personality").to_string();
110 let disposition = RANDOM_TABLES.roll_table("npc_disposition").to_string();
111 let components = vec![appearance, tendencies, personality, disposition];
112 self.persona = Some(components.join(", "));
113 }
114
115 }
127
128#[cfg(test)]
145mod tests {
146 use super::*;
147 use crate::rules::classes::CLASSES;
148 use crate::rules::races::RACES;
149
150 #[test]
151 #[ignore]
152 #[should_panic(expected = "Ability score generation isn't testable yet.")]
153 fn test_roll_henchman_ability_scores() {
154 let class_ref = CLASSES.get("fighter").unwrap();
155 let race_ref = RACES.get("dwarf").unwrap();
156 let mut npc = Npc {
157 alignment: Some(String::from("Lawful Good")),
158 race: Some(race_ref),
159 class: Some(class_ref),
160 ability_scores: None,
161 persona: None,
162 };
163
164 npc.roll_henchman_ability_scores();
166
167 }
171}