initiative_core/world/npc/
mod.rs

1pub use age::Age;
2pub use ethnicity::Ethnicity;
3pub use gender::Gender;
4pub use size::Size;
5pub use species::Species;
6pub use view::{DescriptionView, DetailsView, SummaryView};
7
8mod age;
9mod ethnicity;
10mod gender;
11mod size;
12mod species;
13mod view;
14
15use crate::world::place::Place;
16use crate::world::{Demographics, Field, Generate};
17use rand::Rng;
18use serde::{Deserialize, Serialize};
19use uuid::Uuid;
20
21#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
22pub struct Npc {
23    pub uuid: Uuid,
24
25    #[serde(flatten)]
26    pub data: NpcData,
27}
28
29#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
30pub struct NpcData {
31    pub name: Field<String>,
32    pub gender: Field<Gender>,
33    pub age: Field<Age>,
34    pub age_years: Field<u16>,
35    pub size: Field<Size>,
36    pub species: Field<Species>,
37    pub ethnicity: Field<Ethnicity>,
38    pub location_uuid: Field<Uuid>,
39    // pub home: Field<Uuid>,
40    // pub occupation: Field<Role>,
41    // pub languages: Field<Vec<String>>,
42    // pub parents: Field<Vec<Uuid>>,
43    // pub spouses: Field<Vec<Uuid>>,
44    // pub siblings: Field<Vec<Uuid>>,
45    // pub children: Field<Vec<Uuid>>,
46}
47
48#[derive(Debug, Default)]
49pub struct NpcRelations {
50    pub location: Option<(Place, Option<Place>)>,
51}
52
53impl Npc {
54    pub fn display_summary(&self) -> SummaryView {
55        self.data.display_summary()
56    }
57
58    pub fn display_description(&self) -> DescriptionView {
59        self.data.display_description()
60    }
61
62    pub fn display_details(&self, relations: NpcRelations) -> DetailsView {
63        self.data.display_details(self.uuid, relations)
64    }
65
66    pub fn gender(&self) -> Gender {
67        self.data.gender()
68    }
69
70    pub fn get_words() -> &'static [&'static str] {
71        NpcData::get_words()
72    }
73
74    pub fn lock_all(&mut self) {
75        self.data.lock_all()
76    }
77
78    pub fn apply_diff(&mut self, diff: &mut NpcData) {
79        self.data.apply_diff(diff)
80    }
81}
82
83impl NpcData {
84    pub fn display_summary(&self) -> SummaryView {
85        SummaryView::new(self)
86    }
87
88    pub fn display_description(&self) -> DescriptionView {
89        DescriptionView::new(self)
90    }
91
92    pub fn display_details(&self, uuid: Uuid, relations: NpcRelations) -> DetailsView {
93        DetailsView::new(self, uuid, relations)
94    }
95
96    pub fn gender(&self) -> Gender {
97        self.gender
98            .value()
99            .copied()
100            .unwrap_or(Gender::NonBinaryThey)
101    }
102
103    pub fn get_words() -> &'static [&'static str] {
104        &["character", "npc"][..]
105    }
106
107    pub fn lock_all(&mut self) {
108        let NpcData {
109            name,
110            gender,
111            age,
112            age_years,
113            size,
114            species,
115            ethnicity,
116            location_uuid,
117        } = self;
118
119        name.lock();
120        gender.lock();
121        age.lock();
122        age_years.lock();
123        size.lock();
124        species.lock();
125        ethnicity.lock();
126        location_uuid.lock();
127    }
128
129    pub fn apply_diff(&mut self, diff: &mut Self) {
130        let NpcData {
131            name,
132            gender,
133            age,
134            age_years,
135            size,
136            species,
137            ethnicity,
138            location_uuid,
139        } = self;
140
141        name.apply_diff(&mut diff.name);
142        gender.apply_diff(&mut diff.gender);
143        age.apply_diff(&mut diff.age);
144        age_years.apply_diff(&mut diff.age_years);
145        size.apply_diff(&mut diff.size);
146        species.apply_diff(&mut diff.species);
147        ethnicity.apply_diff(&mut diff.ethnicity);
148        location_uuid.apply_diff(&mut diff.location_uuid);
149    }
150}
151
152impl Generate for NpcData {
153    fn regenerate(&mut self, rng: &mut impl Rng, demographics: &Demographics) {
154        match (self.species.is_locked(), self.ethnicity.is_locked()) {
155            (false, false) => {
156                let (species, ethnicity) = demographics.gen_species_ethnicity(rng);
157                self.ethnicity.replace(ethnicity);
158                self.species.replace(species);
159            }
160            (false, true) => {
161                self.species.replace(
162                    demographics
163                        .only_ethnicity(self.ethnicity.value().unwrap())
164                        .gen_species_ethnicity(rng)
165                        .0,
166                );
167            }
168            (true, false) => {
169                self.ethnicity.replace(
170                    demographics
171                        .only_species(self.species.value().unwrap())
172                        .gen_species_ethnicity(rng)
173                        .1,
174                );
175            }
176            (true, true) => {}
177        }
178
179        species::regenerate(rng, self);
180        ethnicity::regenerate(rng, self);
181    }
182}
183
184#[cfg(test)]
185mod test {
186    use super::*;
187    use crate::test_utils as test;
188    use rand::prelude::*;
189
190    #[test]
191    fn regenerate_test() {
192        let mut rng = SmallRng::seed_from_u64(0);
193        let demographics = Demographics::default();
194
195        let npc = NpcData::generate(&mut rng, &demographics);
196
197        assert!(npc.species.is_some());
198        assert!(npc.name.is_some());
199    }
200
201    #[test]
202    fn gender_test() {
203        let mut npc = NpcData::default();
204        assert_eq!(Gender::NonBinaryThey, npc.gender());
205
206        npc.gender.replace(Gender::Feminine);
207        assert_eq!(Gender::Feminine, npc.gender());
208    }
209
210    #[test]
211    fn serialize_deserialize_test() {
212        assert_eq!(
213            r#"{"uuid":"00000000-0000-0000-0000-000000000011","name":"Odysseus","gender":"masculine","age":"middle-aged","age_years":50,"size":{"type":"Medium","height":72,"weight":180},"species":"human","ethnicity":"human","location_uuid":"00000000-0000-0000-0000-000000000003"}"#,
214            serde_json::to_string(&test::npc::odysseus()).unwrap()
215        );
216
217        let value: Npc = serde_json::from_str(r#"{"uuid":"00000000-0000-0000-0000-000000000011","name":"Odysseus","gender":"masculine","age":"middle-aged","age_years":50,"size":{"type":"Medium","height":72,"weight":180},"species":"human","ethnicity":"human","location_uuid":"00000000-0000-0000-0000-000000000003"}"#).unwrap();
218
219        assert_eq!(test::npc::odysseus(), value);
220    }
221
222    #[test]
223    fn apply_diff_test_no_change() {
224        let mut npc = test::npc::odysseus();
225        let mut diff = NpcData::default();
226
227        npc.data.apply_diff(&mut diff);
228
229        assert_eq!(test::npc::odysseus(), npc);
230        assert_eq!(NpcData::default(), diff);
231    }
232
233    #[test]
234    fn apply_diff_test_from_empty() {
235        let mut npc_data = NpcData::default();
236        let mut diff = test::npc::odysseus().data.clone();
237
238        npc_data.apply_diff(&mut diff);
239
240        assert_eq!(test::npc::odysseus().data, npc_data);
241
242        let mut empty_locked = NpcData::default();
243        empty_locked.lock_all();
244        assert_eq!(empty_locked, diff);
245    }
246
247    #[test]
248    fn lock_all_test() {
249        let mut npc = NpcData::default();
250        npc.lock_all();
251
252        assert_eq!(
253            NpcData {
254                name: Field::Locked(None),
255                gender: Field::Locked(None),
256                age: Field::Locked(None),
257                age_years: Field::Locked(None),
258                size: Field::Locked(None),
259                species: Field::Locked(None),
260                ethnicity: Field::Locked(None),
261                location_uuid: Field::Locked(None),
262            },
263            npc,
264        );
265    }
266}