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}