Skip to main content

nms_core/
discovery.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::fmt;
4use std::str::FromStr;
5
6use crate::address::GalacticAddress;
7
8/// The kind of object that was discovered.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[non_exhaustive]
11pub enum Discovery {
12    Planet,
13    SolarSystem,
14    Sector,
15    Animal,
16    Flora,
17    Mineral,
18}
19
20impl fmt::Display for Discovery {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        match self {
23            Self::Planet => write!(f, "Planet"),
24            Self::SolarSystem => write!(f, "SolarSystem"),
25            Self::Sector => write!(f, "Sector"),
26            Self::Animal => write!(f, "Animal"),
27            Self::Flora => write!(f, "Flora"),
28            Self::Mineral => write!(f, "Mineral"),
29        }
30    }
31}
32
33/// Error returned when parsing a discovery type string fails.
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct DiscoveryParseError(pub String);
36
37impl fmt::Display for DiscoveryParseError {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        write!(f, "unknown discovery type: {}", self.0)
40    }
41}
42
43impl std::error::Error for DiscoveryParseError {}
44
45impl FromStr for Discovery {
46    type Err = DiscoveryParseError;
47
48    fn from_str(s: &str) -> Result<Self, Self::Err> {
49        match s.to_lowercase().as_str() {
50            "planet" => Ok(Self::Planet),
51            "solarsystem" | "solar_system" => Ok(Self::SolarSystem),
52            "sector" => Ok(Self::Sector),
53            "animal" => Ok(Self::Animal),
54            "flora" => Ok(Self::Flora),
55            "mineral" => Ok(Self::Mineral),
56            _ => Err(DiscoveryParseError(s.to_string())),
57        }
58    }
59}
60
61/// A single discovery event from the player's save file.
62///
63/// The `reality_index` is derived from the `universe_address` and kept in sync
64/// by the constructor. Both refer to the galaxy where the discovery was made.
65#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
66#[non_exhaustive]
67pub struct DiscoveryRecord {
68    pub discovery_type: Discovery,
69    pub universe_address: GalacticAddress,
70    pub timestamp: Option<DateTime<Utc>>,
71    pub name: Option<String>,
72    pub discoverer: Option<String>,
73    pub is_uploaded: bool,
74}
75
76impl DiscoveryRecord {
77    pub fn new(
78        discovery_type: Discovery,
79        universe_address: GalacticAddress,
80        timestamp: Option<DateTime<Utc>>,
81        name: Option<String>,
82        discoverer: Option<String>,
83        is_uploaded: bool,
84    ) -> Self {
85        Self {
86            discovery_type,
87            universe_address,
88            timestamp,
89            name,
90            discoverer,
91            is_uploaded,
92        }
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn discovery_display_fromstr_roundtrip() {
102        for d in [
103            Discovery::Planet,
104            Discovery::SolarSystem,
105            Discovery::Sector,
106            Discovery::Animal,
107            Discovery::Flora,
108            Discovery::Mineral,
109        ] {
110            let s = d.to_string();
111            let parsed: Discovery = s.parse().unwrap();
112            assert_eq!(d, parsed);
113        }
114    }
115
116    #[test]
117    fn discovery_solar_system_alternate() {
118        assert_eq!(
119            "solar_system".parse::<Discovery>().unwrap(),
120            Discovery::SolarSystem
121        );
122    }
123
124    #[test]
125    fn discovery_record_constructor() {
126        let addr = GalacticAddress::new(0, 0, 0, 0x123, 0, 5);
127        let rec = DiscoveryRecord::new(
128            Discovery::Planet,
129            addr,
130            None,
131            Some("TestPlanet".to_string()),
132            None,
133            false,
134        );
135        assert_eq!(rec.discovery_type, Discovery::Planet);
136        assert_eq!(rec.universe_address.reality_index, 5);
137        assert!(!rec.is_uploaded);
138    }
139}