initiative_core/world/npc/
mod.rs1pub 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 }
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}