initiative_core/world/place/
mod.rs

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    // pub architecture: Option<String>,
31    // pub floors: Field<u8>,
32    // pub owner: Field<Vec<Uuid>>,
33    // pub staff: Field<Vec<Uuid>>,
34    // pub occupants: Field<Vec<Uuid>>,
35    // pub services: Option<String>,
36    // pub worship: Field<String>,
37    // pub quality: something
38    // pub price: something
39}
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        /*
507        expect_words_emoji
508            .iter()
509            .zip(words_emoji.iter())
510            .for_each(|(expect, word)| assert_eq!(expect, word));
511        */
512
513        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}