Skip to main content

ed_journals/modules/galaxy/models/
planet_class.rs

1use crate::from_str_deserialize_impl;
2use serde::Serialize;
3use std::fmt::{Display, Formatter};
4use std::str::FromStr;
5use thiserror::Error;
6
7#[derive(Debug, Serialize, Clone, PartialEq, Eq, Hash)]
8pub enum PlanetClass {
9    MetalRichBody,
10    HighMetalContentBody,
11    RockyBody,
12    IcyBody,
13    RockyIceBody,
14    EarthlikeBody,
15    WaterWorld,
16    AmmoniaWorld,
17    WaterGiant,
18    WaterGiantWithLife,
19    GasGiantWithWaterBasedLife,
20    GasGiantWithAmmoniaBasedLife,
21    SudarskyClassIGasGiant,
22    SudarskyClassIIGasGiant,
23    SudarskyClassIIIGasGiant,
24    SudarskyClassIVGasGiant,
25    SudarskyClassVGasGiant,
26    HeliumRichGasGiant,
27    HeliumGasGiant,
28
29    #[cfg(feature = "allow-unknown")]
30    #[cfg_attr(docsrs, doc(cfg(feature = "allow-unknown")))]
31    Unknown(String),
32}
33
34#[derive(Debug, Error)]
35pub enum PlanetClassError {
36    #[error("Failed to parse planet class: '{0}'")]
37    FailedToParse(String),
38
39    #[error("Unknown planet class: '{0}'")]
40    UnknownPlanetClass(String),
41}
42
43impl FromStr for PlanetClass {
44    type Err = PlanetClassError;
45
46    fn from_str(s: &str) -> Result<Self, Self::Err> {
47        let string = s.to_ascii_lowercase().replace('_', " ");
48
49        let s: &str = string
50            .trim_end_matches('s')
51            .trim_end_matches(" body")
52            .trim_end_matches(" world");
53
54        Ok(match s {
55            "metal rich" => PlanetClass::MetalRichBody,
56            "high metal content" => PlanetClass::HighMetalContentBody,
57            "rocky" => PlanetClass::RockyBody,
58            "icy" | "ice" => PlanetClass::IcyBody,
59            "rocky ice" => PlanetClass::RockyIceBody,
60            "earthlike" | "earth like" | "earth-like" => PlanetClass::EarthlikeBody,
61            "water" => PlanetClass::WaterWorld,
62            "ammonia" => PlanetClass::AmmoniaWorld,
63            "water giant" => PlanetClass::WaterGiant,
64            "water giant with life" => PlanetClass::WaterGiantWithLife,
65            "gas giant with water based life" | "giant with water life" => {
66                PlanetClass::GasGiantWithWaterBasedLife
67            }
68            "gas giant with ammonia based life" | "giant with ammonia life" => {
69                PlanetClass::GasGiantWithAmmoniaBasedLife
70            }
71            "sudarsky class i gas giant" | "sudarsky class i" => {
72                PlanetClass::SudarskyClassIGasGiant
73            }
74            "sudarsky class ii gas giant" | "sudarsky class ii" => {
75                PlanetClass::SudarskyClassIIGasGiant
76            }
77            "sudarsky class iii gas giant" | "sudarsky class iii" => {
78                PlanetClass::SudarskyClassIIIGasGiant
79            }
80            "sudarsky class iv gas giant" | "sudarsky class iv" => {
81                PlanetClass::SudarskyClassIVGasGiant
82            }
83            "sudarsky class v gas giant" | "sudarsky class v" => {
84                PlanetClass::SudarskyClassVGasGiant
85            }
86            "helium rich gas giant" => PlanetClass::HeliumRichGasGiant,
87            "helium gas giant" => PlanetClass::HeliumGasGiant,
88
89            #[cfg(feature = "allow-unknown")]
90            _ => PlanetClass::Unknown(s.to_string()),
91
92            #[cfg(not(feature = "allow-unknown"))]
93            _ => return Err(PlanetClassError::UnknownPlanetClass(s.to_string())),
94        })
95    }
96}
97
98from_str_deserialize_impl!(PlanetClass);
99
100impl PlanetClass {
101    /// Returns the base exploration value of the star planet class.
102    pub fn base_value(&self) -> u64 {
103        match self {
104            PlanetClass::MetalRichBody => 21_790,
105            PlanetClass::AmmoniaWorld => 96_932,
106            PlanetClass::SudarskyClassIGasGiant => 1_656,
107            PlanetClass::SudarskyClassIIGasGiant => 9_654,
108            PlanetClass::HighMetalContentBody => 9_654,
109            PlanetClass::WaterWorld => 64_831,
110            PlanetClass::EarthlikeBody => 64_831,
111            _ => 300,
112        }
113    }
114
115    /// Returns the bonus exploration value if the planet is terraformable.
116    pub fn terraformable_bonus(&self) -> u64 {
117        match self {
118            PlanetClass::MetalRichBody => 65_631,
119            PlanetClass::AmmoniaWorld => 0,
120            PlanetClass::SudarskyClassIGasGiant => 0,
121            PlanetClass::SudarskyClassIIGasGiant => 100_677,
122            PlanetClass::HighMetalContentBody => 100_677,
123            PlanetClass::WaterWorld => 116_295,
124            PlanetClass::EarthlikeBody => 116_295,
125            _ => 93_328,
126        }
127    }
128}
129
130impl Display for PlanetClass {
131    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
132        write!(
133            f,
134            "{}",
135            match self {
136                PlanetClass::MetalRichBody => "Metal Rich Body",
137                PlanetClass::HighMetalContentBody => "High Metal Content Body",
138                PlanetClass::RockyBody => "Rocky Body",
139                PlanetClass::IcyBody => "Icy Body",
140                PlanetClass::RockyIceBody => "Rocky Ice Body",
141                PlanetClass::EarthlikeBody => "Earth-like Body",
142                PlanetClass::WaterWorld => "Water World",
143                PlanetClass::AmmoniaWorld => "Ammonia World",
144                PlanetClass::WaterGiant => "Water Giant",
145                PlanetClass::WaterGiantWithLife => "Water Giant with life",
146                PlanetClass::GasGiantWithWaterBasedLife => "Gas Giant with water based life",
147                PlanetClass::GasGiantWithAmmoniaBasedLife => "Gas Giant with ammonia based life",
148                PlanetClass::SudarskyClassIGasGiant => "Sudarsky Class I Gas Giant",
149                PlanetClass::SudarskyClassIIGasGiant => "Sudarsky Class II Gas Giant",
150                PlanetClass::SudarskyClassIIIGasGiant => "Sudarsky Class III Gas Giant",
151                PlanetClass::SudarskyClassIVGasGiant => "Sudarsky Class IV Gas Giant",
152                PlanetClass::SudarskyClassVGasGiant => "Sudarsky Class V Gas Giant",
153                PlanetClass::HeliumRichGasGiant => "Helium Rich Gas Giant",
154                PlanetClass::HeliumGasGiant => "Helium Gas Giant",
155
156                #[cfg(feature = "allow-unknown")]
157                PlanetClass::Unknown(unknown) =>
158                    return write!(f, "Unknown planet class: '{unknown}'"),
159            }
160        )
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    /// Ensure that deserializing and Display are consistent
169    #[test]
170    fn conversion_soundness() {
171        let planets = [
172            PlanetClass::MetalRichBody,
173            PlanetClass::HighMetalContentBody,
174            PlanetClass::RockyBody,
175            PlanetClass::IcyBody,
176            PlanetClass::RockyIceBody,
177            PlanetClass::EarthlikeBody,
178            PlanetClass::WaterWorld,
179            PlanetClass::AmmoniaWorld,
180            PlanetClass::WaterGiant,
181            PlanetClass::WaterGiantWithLife,
182            PlanetClass::GasGiantWithWaterBasedLife,
183            PlanetClass::GasGiantWithAmmoniaBasedLife,
184            PlanetClass::SudarskyClassIGasGiant,
185            PlanetClass::SudarskyClassIIGasGiant,
186            PlanetClass::SudarskyClassIIIGasGiant,
187            PlanetClass::SudarskyClassIVGasGiant,
188            PlanetClass::SudarskyClassVGasGiant,
189            PlanetClass::HeliumRichGasGiant,
190            PlanetClass::HeliumGasGiant,
191        ];
192
193        for planet in planets {
194            let planet_string = planet.to_string();
195            let planet_json_string = format!("\"{planet_string}\"");
196            let planet_deserialized = serde_json::de::from_str::<PlanetClass>(&planet_json_string);
197            assert!(
198                planet_deserialized.is_ok(),
199                "Failed to deserialize {planet_string}: {planet_deserialized:?}"
200            );
201            assert_eq!(planet, planet_deserialized.unwrap());
202        }
203    }
204}