initiative_core/world/
thing.rs

1use super::{Demographics, Field, Generate};
2use crate::storage::ThingType;
3use crate::world::command::ParsedThing;
4use crate::world::npc::{DetailsView as NpcDetailsView, Gender, Npc, NpcData, NpcRelations};
5use crate::world::place::{DetailsView as PlaceDetailsView, Place, PlaceData, PlaceRelations};
6use initiative_macros::From;
7use rand::Rng;
8use serde::{Deserialize, Serialize};
9use std::cmp::Ordering;
10use std::fmt;
11use std::str::FromStr;
12use uuid::Uuid;
13
14#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
15pub struct Thing {
16    pub uuid: Uuid,
17
18    #[serde(flatten)]
19    pub data: ThingData,
20}
21
22#[derive(Clone, Debug, Deserialize, Eq, From, PartialEq, Serialize)]
23#[serde(tag = "type")]
24pub enum ThingData {
25    Npc(NpcData),
26    Place(PlaceData),
27}
28
29#[derive(Debug, Default, From)]
30pub enum ThingRelations {
31    #[default]
32    None,
33    Npc(NpcRelations),
34    Place(PlaceRelations),
35}
36
37pub struct SummaryView<'a>(&'a ThingData);
38
39pub struct DescriptionView<'a>(&'a ThingData);
40
41pub enum DetailsView<'a> {
42    Npc(NpcDetailsView<'a>),
43    Place(PlaceDetailsView<'a>),
44}
45
46impl Thing {
47    pub fn name(&self) -> &Field<String> {
48        self.data.name()
49    }
50
51    pub fn as_str(&self) -> &'static str {
52        self.data.as_str()
53    }
54
55    pub fn regenerate(&mut self, rng: &mut impl Rng, demographics: &Demographics) {
56        self.data.regenerate(rng, demographics)
57    }
58
59    pub fn gender(&self) -> Gender {
60        self.data.gender()
61    }
62
63    pub fn display_summary(&self) -> SummaryView {
64        self.data.display_summary()
65    }
66
67    pub fn display_description(&self) -> DescriptionView {
68        self.data.display_description()
69    }
70
71    pub fn display_details(&self, relations: ThingRelations) -> DetailsView {
72        self.data.display_details(self.uuid, relations)
73    }
74
75    pub fn lock_all(&mut self) {
76        self.data.lock_all()
77    }
78
79    pub fn is_type(&self, thing_type: ThingType) -> bool {
80        self.data.is_type(thing_type)
81    }
82
83    #[expect(clippy::result_unit_err)]
84    pub fn try_apply_diff(&mut self, diff: &mut ThingData) -> Result<(), ()> {
85        self.data.try_apply_diff(diff)
86    }
87}
88
89impl ThingData {
90    pub fn is_type(&self, thing_type: ThingType) -> bool {
91        matches!(
92            (self, thing_type),
93            (_, ThingType::Any)
94                | (ThingData::Npc(_), ThingType::Npc)
95                | (ThingData::Place(_), ThingType::Place)
96        )
97    }
98
99    pub fn name(&self) -> &Field<String> {
100        match &self {
101            ThingData::Place(place) => &place.name,
102            ThingData::Npc(npc) => &npc.name,
103        }
104    }
105
106    pub fn as_str(&self) -> &'static str {
107        match self {
108            ThingData::Place(..) => "place",
109            ThingData::Npc(..) => "character",
110        }
111    }
112
113    pub fn regenerate(&mut self, rng: &mut impl Rng, demographics: &Demographics) {
114        match self {
115            ThingData::Place(place) => place.regenerate(rng, demographics),
116            ThingData::Npc(npc) => npc.regenerate(rng, demographics),
117        }
118    }
119    pub fn gender(&self) -> Gender {
120        if let Self::Npc(npc) = self {
121            npc.gender()
122        } else {
123            Gender::Neuter
124        }
125    }
126
127    pub fn place_data(&self) -> Option<&PlaceData> {
128        if let Self::Place(place) = self {
129            Some(place)
130        } else {
131            None
132        }
133    }
134
135    pub fn npc_data(&self) -> Option<&NpcData> {
136        if let Self::Npc(npc) = self {
137            Some(npc)
138        } else {
139            None
140        }
141    }
142
143    pub fn display_summary(&self) -> SummaryView {
144        SummaryView(self)
145    }
146
147    pub fn display_description(&self) -> DescriptionView {
148        DescriptionView(self)
149    }
150
151    pub fn display_details(&self, uuid: Uuid, relations: ThingRelations) -> DetailsView {
152        match self {
153            Self::Npc(npc) => DetailsView::Npc(npc.display_details(uuid, relations.into())),
154            Self::Place(place) => DetailsView::Place(place.display_details(uuid, relations.into())),
155        }
156    }
157
158    pub fn lock_all(&mut self) {
159        match self {
160            Self::Npc(npc) => npc.lock_all(),
161            Self::Place(place) => place.lock_all(),
162        }
163    }
164
165    pub fn try_apply_diff(&mut self, diff: &mut Self) -> Result<(), ()> {
166        match (self, diff) {
167            (Self::Npc(npc), Self::Npc(diff_npc)) => npc.apply_diff(diff_npc),
168            (Self::Place(place), Self::Place(diff_place)) => place.apply_diff(diff_place),
169            _ => return Err(()),
170        }
171
172        Ok(())
173    }
174}
175
176impl From<Npc> for Thing {
177    fn from(npc: Npc) -> Self {
178        Thing {
179            uuid: npc.uuid,
180            data: npc.data.into(),
181        }
182    }
183}
184
185impl From<Place> for Thing {
186    fn from(place: Place) -> Self {
187        Thing {
188            uuid: place.uuid,
189            data: place.data.into(),
190        }
191    }
192}
193
194impl TryFrom<Thing> for Npc {
195    type Error = Thing;
196
197    fn try_from(thing: Thing) -> Result<Self, Self::Error> {
198        if let ThingData::Npc(npc) = thing.data {
199            Ok(Npc {
200                uuid: thing.uuid,
201                data: npc,
202            })
203        } else {
204            Err(thing)
205        }
206    }
207}
208
209impl TryFrom<Thing> for Place {
210    type Error = Thing;
211
212    fn try_from(thing: Thing) -> Result<Self, Self::Error> {
213        if let ThingData::Place(place) = thing.data {
214            Ok(Place {
215                uuid: thing.uuid,
216                data: place,
217            })
218        } else {
219            Err(thing)
220        }
221    }
222}
223
224impl TryFrom<ThingData> for NpcData {
225    type Error = ThingData;
226
227    fn try_from(thing_data: ThingData) -> Result<Self, Self::Error> {
228        if let ThingData::Npc(npc) = thing_data {
229            Ok(npc)
230        } else {
231            Err(thing_data)
232        }
233    }
234}
235
236impl TryFrom<ThingData> for PlaceData {
237    type Error = ThingData;
238
239    fn try_from(thing_data: ThingData) -> Result<Self, Self::Error> {
240        if let ThingData::Place(place) = thing_data {
241            Ok(place)
242        } else {
243            Err(thing_data)
244        }
245    }
246}
247
248impl From<ThingRelations> for NpcRelations {
249    fn from(input: ThingRelations) -> Self {
250        if let ThingRelations::Npc(npc) = input {
251            npc
252        } else {
253            NpcRelations::default()
254        }
255    }
256}
257
258impl From<ThingRelations> for PlaceRelations {
259    fn from(input: ThingRelations) -> Self {
260        if let ThingRelations::Place(place) = input {
261            place
262        } else {
263            PlaceRelations::default()
264        }
265    }
266}
267
268impl FromStr for ParsedThing<ThingData> {
269    type Err = ();
270
271    fn from_str(raw: &str) -> Result<Self, Self::Err> {
272        match (
273            raw.parse::<ParsedThing<NpcData>>(),
274            raw.parse::<ParsedThing<PlaceData>>(),
275        ) {
276            (Ok(parsed_npc), Ok(parsed_place)) => match parsed_npc
277                .unknown_words
278                .len()
279                .cmp(&parsed_place.unknown_words.len())
280            {
281                Ordering::Less => Ok(parsed_npc.into_thing_data()),
282                Ordering::Equal => Err(()),
283                Ordering::Greater => Ok(parsed_place.into_thing_data()),
284            },
285            (Ok(parsed_npc), Err(())) => Ok(parsed_npc.into_thing_data()),
286            (Err(()), Ok(parsed_place)) => Ok(parsed_place.into_thing_data()),
287            (Err(()), Err(())) => Err(()),
288        }
289    }
290}
291
292impl fmt::Display for SummaryView<'_> {
293    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
294        match self.0 {
295            ThingData::Place(l) => write!(f, "{}", l.display_summary()),
296            ThingData::Npc(n) => write!(f, "{}", n.display_summary()),
297        }
298    }
299}
300
301impl fmt::Display for DescriptionView<'_> {
302    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
303        match self.0 {
304            ThingData::Place(l) => write!(f, "{}", l.display_description()),
305            ThingData::Npc(n) => write!(f, "{}", n.display_description()),
306        }
307    }
308}
309
310impl fmt::Display for DetailsView<'_> {
311    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
312        match self {
313            DetailsView::Npc(view) => write!(f, "{}", view),
314            DetailsView::Place(view) => write!(f, "{}", view),
315        }
316    }
317}
318
319#[cfg(test)]
320mod test {
321    use super::*;
322
323    #[test]
324    fn name_test() {
325        {
326            let mut place = PlaceData::default();
327            place.name.replace("The Prancing Pony".to_string());
328            assert_eq!(
329                Some(&"The Prancing Pony".to_string()),
330                ThingData::from(place).name().value()
331            );
332        }
333
334        {
335            let mut npc = NpcData::default();
336            npc.name.replace("Frodo Underhill".to_string());
337            assert_eq!(
338                Some(&"Frodo Underhill".to_string()),
339                ThingData::from(npc).name().value()
340            );
341        }
342    }
343
344    #[test]
345    fn into_test() {
346        assert!(matches!(PlaceData::default().into(), ThingData::Place(_)));
347        assert!(matches!(NpcData::default().into(), ThingData::Npc(_)));
348    }
349
350    #[test]
351    fn serialize_deserialize_test_place() {
352        let thing = place();
353        assert_eq!(
354            r#"{"uuid":"00000000-0000-0000-0000-000000000000","type":"Place","location_uuid":null,"subtype":null,"name":null,"description":null}"#,
355            serde_json::to_string(&thing).unwrap(),
356        );
357    }
358
359    #[test]
360    fn serialize_deserialize_test_npc() {
361        let thing = npc();
362        assert_eq!(
363            r#"{"uuid":"00000000-0000-0000-0000-000000000000","type":"Npc","name":null,"gender":null,"age":null,"age_years":null,"size":null,"species":null,"ethnicity":null,"location_uuid":null}"#,
364            serde_json::to_string(&thing).unwrap(),
365        );
366    }
367
368    #[test]
369    fn place_npc_test() {
370        {
371            let thing = place();
372            assert!(thing.data.place_data().is_some());
373            assert!(thing.data.npc_data().is_none());
374            assert!(PlaceData::try_from(thing.data.clone()).is_ok());
375            assert!(NpcData::try_from(thing.data.clone()).is_err());
376            assert!(Place::try_from(thing.clone()).is_ok());
377            assert!(Npc::try_from(thing).is_err());
378        }
379
380        {
381            let thing = npc();
382            assert!(thing.data.npc_data().is_some());
383            assert!(thing.data.place_data().is_none());
384            assert!(NpcData::try_from(thing.data.clone()).is_ok());
385            assert!(PlaceData::try_from(thing.data.clone()).is_err());
386            assert!(Npc::try_from(thing.clone()).is_ok());
387            assert!(Place::try_from(thing).is_err());
388        }
389    }
390
391    #[test]
392    fn gender_test() {
393        assert_eq!(Gender::Neuter, place().gender());
394        assert_eq!(Gender::NonBinaryThey, npc().gender());
395
396        let npc = ThingData::Npc(NpcData {
397            gender: Gender::Feminine.into(),
398            ..Default::default()
399        });
400
401        assert_eq!(Gender::Feminine, npc.gender());
402    }
403
404    #[test]
405    fn lock_all_test_npc() {
406        let mut npc = NpcData::default();
407        npc.lock_all();
408        let mut thing = ThingData::Npc(NpcData::default());
409        thing.lock_all();
410        assert_eq!(ThingData::Npc(npc), thing);
411    }
412
413    #[test]
414    fn lock_all_test_place() {
415        let mut place = PlaceData::default();
416        place.lock_all();
417        let mut thing = ThingData::Place(PlaceData::default());
418        thing.lock_all();
419        assert_eq!(ThingData::Place(place), thing);
420    }
421
422    fn place() -> Thing {
423        Thing {
424            uuid: Uuid::nil(),
425            data: ThingData::Place(PlaceData::default()),
426        }
427    }
428
429    fn npc() -> Thing {
430        Thing {
431            uuid: Uuid::nil(),
432            data: ThingData::Npc(NpcData::default()),
433        }
434    }
435}