1pub use view::{DescriptionView, DetailsView, NameView, SummaryView};
2
3mod building;
4mod location;
5mod region;
6mod view;
7
8use super::{Demographics, Field, Generate};
9use initiative_macros::WordList;
10use rand::prelude::*;
11use serde::{Deserialize, Serialize};
12use std::fmt;
13use uuid::Uuid;
14
15#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
16pub struct Place {
17 pub uuid: Uuid,
18
19 #[serde(flatten)]
20 pub data: PlaceData,
21}
22
23#[derive(Clone, Debug, Deserialize, Default, Eq, PartialEq, Serialize)]
24pub struct PlaceData {
25 pub location_uuid: Field<Uuid>,
26 pub subtype: Field<PlaceType>,
27
28 pub name: Field<String>,
29 pub description: Field<String>,
30 }
40
41#[derive(Debug, Default)]
42pub struct PlaceRelations {
43 pub location: Option<(Place, Option<Place>)>,
44}
45
46#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, WordList)]
47#[serde(into = "&'static str", try_from = "&str")]
48pub enum PlaceType {
49 #[term = "place"]
50 Any,
51
52 Building(building::BuildingType),
53 Location(location::LocationType),
54 Region(region::RegionType),
55}
56
57impl Place {
58 pub fn display_name(&self) -> NameView {
59 self.data.display_name()
60 }
61
62 pub fn display_summary(&self) -> SummaryView {
63 self.data.display_summary()
64 }
65
66 pub fn display_description(&self) -> DescriptionView {
67 self.data.display_description()
68 }
69
70 pub fn display_details(&self, relations: PlaceRelations) -> DetailsView {
71 self.data.display_details(self.uuid, relations)
72 }
73
74 pub fn get_words() -> &'static [&'static str] {
75 &["place"][..]
76 }
77
78 pub fn lock_all(&mut self) {
79 self.data.lock_all()
80 }
81
82 pub fn apply_diff(&mut self, diff: &mut PlaceData) {
83 self.data.apply_diff(diff)
84 }
85}
86
87impl PlaceData {
88 pub fn display_name(&self) -> NameView {
89 NameView::new(self)
90 }
91
92 pub fn display_summary(&self) -> SummaryView {
93 SummaryView::new(self)
94 }
95
96 pub fn display_description(&self) -> DescriptionView {
97 DescriptionView::new(self)
98 }
99
100 pub fn display_details(&self, uuid: Uuid, relations: PlaceRelations) -> DetailsView {
101 DetailsView::new(self, uuid, relations)
102 }
103
104 pub fn lock_all(&mut self) {
105 let Self {
106 location_uuid,
107 subtype,
108 name,
109 description,
110 } = self;
111
112 location_uuid.lock();
113 subtype.lock();
114 name.lock();
115 description.lock();
116 }
117
118 pub fn apply_diff(&mut self, diff: &mut Self) {
119 let Self {
120 location_uuid,
121 subtype,
122 name,
123 description,
124 } = self;
125
126 location_uuid.apply_diff(&mut diff.location_uuid);
127 subtype.apply_diff(&mut diff.subtype);
128 name.apply_diff(&mut diff.name);
129 description.apply_diff(&mut diff.description);
130 }
131}
132
133impl Generate for PlaceData {
134 fn regenerate(&mut self, rng: &mut impl Rng, demographics: &Demographics) {
135 if !self.name.is_locked() || self.subtype.is_none() {
136 self.subtype
137 .replace_with(|_| PlaceType::generate(rng, demographics));
138 }
139
140 if let Some(value) = self.subtype.value() {
141 match value {
142 PlaceType::Building(_) => building::generate(self, rng, demographics),
143 PlaceType::Location(_) => location::generate(self, rng, demographics),
144 _ => {}
145 }
146 }
147 }
148}
149
150impl PlaceType {
151 pub const fn get_emoji(&self) -> &'static str {
152 if let Some(emoji) = match self {
153 Self::Any => None,
154 Self::Building(subtype) => subtype.get_emoji(),
155 Self::Location(subtype) => subtype.get_emoji(),
156 Self::Region(subtype) => subtype.get_emoji(),
157 } {
158 emoji
159 } else {
160 "ð"
161 }
162 }
163}
164
165impl Default for PlaceType {
166 fn default() -> Self {
167 Self::Any
168 }
169}
170
171impl Generate for PlaceType {
172 fn regenerate(&mut self, rng: &mut impl Rng, _demographics: &Demographics) {
173 *self = Self::get_words()
174 .nth(rng.gen_range(0..Self::word_count()))
175 .unwrap()
176 .parse()
177 .unwrap();
178 }
179}
180
181impl fmt::Display for PlaceType {
182 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
183 write!(f, "{}", self.as_str())
184 }
185}
186
187#[cfg(test)]
188mod test {
189 use super::*;
190
191 #[test]
192 fn generate_test() {
193 let demographics = Demographics::default();
194
195 let mut rng = SmallRng::seed_from_u64(1);
196 assert_ne!(
197 PlaceData::generate(&mut rng, &demographics).subtype,
198 PlaceData::generate(&mut rng, &demographics).subtype,
199 );
200
201 let mut rng1 = SmallRng::seed_from_u64(0);
202 let mut rng2 = SmallRng::seed_from_u64(0);
203 assert_eq!(
204 PlaceData::generate(&mut rng1, &demographics).subtype,
205 PlaceData::generate(&mut rng2, &demographics).subtype,
206 );
207 }
208
209 #[test]
210 fn place_type_default_test() {
211 assert_eq!(PlaceType::Any, PlaceType::default());
212 }
213
214 #[test]
215 fn place_type_serialize_deserialize_test() {
216 {
217 let inn: PlaceType = "inn".parse().unwrap();
218 assert_eq!(r#""inn""#, serde_json::to_string(&inn).unwrap());
219 assert_eq!(inn, serde_json::from_str::<PlaceType>(r#""inn""#).unwrap());
220 }
221
222 {
223 let business: PlaceType = "business".parse().unwrap();
224 assert_eq!(r#""business""#, serde_json::to_string(&business).unwrap());
225 assert_eq!(
226 business,
227 serde_json::from_str::<PlaceType>(r#""business""#).unwrap()
228 );
229 }
230
231 {
232 let building: PlaceType = "building".parse().unwrap();
233 assert_eq!(r#""building""#, serde_json::to_string(&building).unwrap());
234 assert_eq!(
235 building,
236 serde_json::from_str::<PlaceType>(r#""building""#).unwrap(),
237 );
238 }
239
240 {
241 let place: PlaceType = "place".parse().unwrap();
242 assert_eq!(r#""place""#, serde_json::to_string(&place).unwrap());
243 assert_eq!(
244 place,
245 serde_json::from_str::<PlaceType>(r#""place""#).unwrap(),
246 );
247 }
248 }
249
250 #[test]
251 fn place_serialize_deserialize_test() {
252 let place = oaken_mermaid_inn();
253
254 assert_eq!(
255 r#"{"uuid":"00000000-0000-0000-0000-000000000000","location_uuid":"00000000-0000-0000-0000-000000000000","subtype":"inn","name":"Oaken Mermaid Inn","description":"I am Mordenkainen"}"#,
256 serde_json::to_string(&place).unwrap(),
257 );
258
259 let value: Place = serde_json::from_str(r#"{"uuid":"00000000-0000-0000-0000-000000000000","location_uuid":"00000000-0000-0000-0000-000000000000","subtype":"inn","name":"Oaken Mermaid Inn","description":"I am Mordenkainen"}"#).unwrap();
260
261 assert_eq!(place, value);
262 }
263
264 #[test]
265 fn apply_diff_test_no_change() {
266 let mut place = oaken_mermaid_inn();
267 let mut diff = PlaceData::default();
268
269 place.data.apply_diff(&mut diff);
270
271 assert_eq!(oaken_mermaid_inn(), place);
272 assert_eq!(PlaceData::default(), diff);
273 }
274
275 #[test]
276 fn apply_diff_test_from_empty() {
277 let oaken_mermaid_inn = oaken_mermaid_inn();
278
279 let mut place = PlaceData::default();
280 let mut diff = oaken_mermaid_inn.data.clone();
281
282 place.apply_diff(&mut diff);
283
284 assert_eq!(oaken_mermaid_inn.data, place);
285
286 let mut empty_locked = PlaceData::default();
287 empty_locked.lock_all();
288 assert_eq!(empty_locked, diff);
289 }
290
291 #[test]
292 fn lock_all_test() {
293 let mut place = PlaceData::default();
294 place.lock_all();
295
296 assert_eq!(
297 PlaceData {
298 location_uuid: Field::Locked(None),
299 subtype: Field::Locked(None),
300 name: Field::Locked(None),
301 description: Field::Locked(None),
302 },
303 place,
304 );
305 }
306
307 #[test]
308 fn get_emoji_test() {
309 let mut words_emoji: Vec<(String, String)> = PlaceType::get_words()
310 .map(|word| {
311 (
312 word.to_string(),
313 PlaceType::parse_cs(word).unwrap().get_emoji().to_string(),
314 )
315 })
316 .collect();
317 words_emoji.sort();
318
319 let expect_words_emoji: Vec<(String, String)> = [
320 ("abbey", "ð"),
321 ("academy", "ð"),
322 ("archipelago", "ð"),
323 ("arena", "ð"),
324 ("armorer", "ðĄ"),
325 ("bakery", "ð"),
326 ("bank", "ðĶ"),
327 ("bar", "ðŧ"),
328 ("barony", "ð"),
329 ("barracks", "â"),
330 ("barrens", "ð"),
331 ("base", "â"),
332 ("bathhouse", "ð"),
333 ("beach", "ð"),
334 ("blacksmith", "ðĄ"),
335 ("brewery", "ðŧ"),
336 ("bridge", "ð"),
337 ("building", "ð"),
338 ("business", "ðŠ"),
339 ("camp", "ð"),
340 ("campsite", "ð"),
341 ("canyon", "ð"),
342 ("capital", "ð"),
343 ("caravansary", "ðĻ"),
344 ("casino", "ð"),
345 ("castle", "ð°"),
346 ("cave", "ð"),
347 ("cavern", "ð"),
348 ("cemetery", "ðŠĶ"),
349 ("chasm", "ð"),
350 ("church", "ð"),
351 ("citadel", "ð°"),
352 ("city", "ð"),
353 ("city-state", "ð"),
354 ("club", ""),
355 ("coastline", "ð"),
356 ("college", "ð"),
357 ("confederation", "ð"),
358 ("continent", "ð"),
359 ("country", "ð"),
360 ("county", "ð"),
361 ("court", "ð°"),
362 ("crypt", "ðŠĶ"),
363 ("desert", "ð"),
364 ("distillery", "ðĨ"),
365 ("district", "ð"),
366 ("domain", "ð"),
367 ("duchy", "ð"),
368 ("duty-house", "ðŠ"),
369 ("embassy", "ðĐ"),
370 ("empire", "ð"),
371 ("farm", "ðą"),
372 ("ferry", "âī"),
373 ("fighting-pit", "â"),
374 ("food-counter", "ðē"),
375 ("forest", "ðģ"),
376 ("forge", "ðĨ"),
377 ("fort", "ð°"),
378 ("fortress", "ð°"),
379 ("fountain", "âē"),
380 ("furniture-shop", "ðŠ"),
381 ("furrier", "ðĶ"),
382 ("gambling-hall", "ð"),
383 ("garden", "ðą"),
384 ("gate", "ðŠ"),
385 ("general-store", "ðŠ"),
386 ("glacier", "ð"),
387 ("gorge", "ð"),
388 ("graveyard", "ðŠĶ"),
389 ("grove", "ðģ"),
390 ("guardhouse", "ðĄ"),
391 ("guild-hall", "ðŠ"),
392 ("hamlet", "ð"),
393 ("harbor", "âĩ"),
394 ("hermitage", "ð"),
395 ("hill", "â°"),
396 ("hotel", "ðĻ"),
397 ("house", "ð "),
398 ("imports-shop", "ðŠ"),
399 ("inn", "ðĻ"),
400 ("island", "ð"),
401 ("jail", "ðĄ"),
402 ("jeweller", "ð"),
403 ("jungle", "ðģ"),
404 ("keep", "ð°"),
405 ("kingdom", "ð"),
406 ("lake", "ð"),
407 ("library", "ð"),
408 ("lighthouse", "âĩ"),
409 ("location", "ð"),
410 ("lodge", "ðĻ"),
411 ("lumberyard", "ðŠĩ"),
412 ("magic-shop", "ðŠ"),
413 ("manor", "ð "),
414 ("mansion", "ð "),
415 ("market", "ðŠ"),
416 ("marsh", "ð"),
417 ("mausoleum", "ðŠĶ"),
418 ("mesa", "ð"),
419 ("metropolis", "ð"),
420 ("mill", "ðū"),
421 ("mine", "â"),
422 ("monastery", "ð"),
423 ("monolith", "ðŋ"),
424 ("monument", "ð―"),
425 ("moor", "ð"),
426 ("mosque", "ð"),
427 ("mountain", "â°"),
428 ("nation", "ð"),
429 ("necropolis", "ðŠĶ"),
430 ("neighborhood", "ð"),
431 ("nightclub", "ðŧ"),
432 ("nunnery", "ð"),
433 ("oasis", "ðī"),
434 ("ocean", "ð"),
435 ("outpost", "ðĐ"),
436 ("palace", "ð°"),
437 ("parish", "ð"),
438 ("pass", "â°"),
439 ("peninsula", "ð"),
440 ("pet-store", "ðķ"),
441 ("pier", "âĩ"),
442 ("place", "ð"),
443 ("plain", "ð"),
444 ("plateau", "ð"),
445 ("portal", "ð"),
446 ("principality", "ð"),
447 ("prison", "ðĄ"),
448 ("province", "ð"),
449 ("pub", "ðŧ"),
450 ("quarter", "ð"),
451 ("realm", "ð"),
452 ("reef", "ð"),
453 ("region", "ð"),
454 ("region", "ð"),
455 ("residence", "ð "),
456 ("restaurant", "ð―"),
457 ("ridge", "â°"),
458 ("rift", "ð"),
459 ("river", "ð"),
460 ("ruin", "ð"),
461 ("school", "ð"),
462 ("sea", "ð"),
463 ("shipyard", "âĩ"),
464 ("shop", "ðŠ"),
465 ("shrine", "ð"),
466 ("smithy", "ðĄ"),
467 ("specialty-shop", "ðŠ"),
468 ("spirits-shop", "ðĨ"),
469 ("stable", "ð"),
470 ("statue", "ð―"),
471 ("store", "ðŠ"),
472 ("street", "ð"),
473 ("stronghold", "ð°"),
474 ("swamp", "ð"),
475 ("synagogue", "ð"),
476 ("tavern", "ðĻ"),
477 ("temple", "ð"),
478 ("territory", "ð"),
479 ("textiles-shop", "ðŠ"),
480 ("theater", "ð"),
481 ("tomb", "ðŠĶ"),
482 ("tower", "ð°"),
483 ("town", "ð"),
484 ("trading-post", "ðŠ"),
485 ("tree", "ðģ"),
486 ("tundra", "â"),
487 ("university", "ð"),
488 ("vale", "ð"),
489 ("valley", "ð"),
490 ("vault", "ðĶ"),
491 ("village", "ð"),
492 ("wainwright", "ðŠ"),
493 ("wall", "ð§ą"),
494 ("ward", "ð"),
495 ("warehouse", "ðĶ"),
496 ("wasteland", "ð"),
497 ("watch-house", "ðĄ"),
498 ("weaponsmith", "ðĄ"),
499 ("woodshop", "ðŠ"),
500 ("world", "ð"),
501 ]
502 .iter()
503 .map(|(a, b)| (a.to_string(), b.to_string()))
504 .collect();
505
506 assert_eq!(expect_words_emoji, words_emoji);
514 }
515
516 fn oaken_mermaid_inn() -> Place {
517 Place {
518 uuid: uuid::Uuid::nil(),
519 data: PlaceData {
520 location_uuid: uuid::Uuid::nil().into(),
521 subtype: "inn".parse::<PlaceType>().ok().into(),
522
523 name: "Oaken Mermaid Inn".into(),
524 description: "I am Mordenkainen".into(),
525 },
526 }
527 }
528}