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}