Skip to main content

nms_core/
biome.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3use std::str::FromStr;
4
5/// Planet biome classification matching GcBiomeType from game data.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7#[cfg_attr(
8    feature = "archive",
9    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
10)]
11#[non_exhaustive]
12pub enum Biome {
13    Lush,
14    Toxic,
15    Scorched,
16    Radioactive,
17    Frozen,
18    Barren,
19    Dead,
20    Weird,
21    Red,
22    Green,
23    Blue,
24    Swamp,
25    Lava,
26    Waterworld,
27    GasGiant,
28}
29
30impl fmt::Display for Biome {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        let s = match self {
33            Self::Lush => "Lush",
34            Self::Toxic => "Toxic",
35            Self::Scorched => "Scorched",
36            Self::Radioactive => "Radioactive",
37            Self::Frozen => "Frozen",
38            Self::Barren => "Barren",
39            Self::Dead => "Dead",
40            Self::Weird => "Weird",
41            Self::Red => "Red",
42            Self::Green => "Green",
43            Self::Blue => "Blue",
44            Self::Swamp => "Swamp",
45            Self::Lava => "Lava",
46            Self::Waterworld => "Waterworld",
47            Self::GasGiant => "GasGiant",
48        };
49        write!(f, "{s}")
50    }
51}
52
53/// Error returned when parsing a biome or biome subtype string fails.
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct BiomeParseError(pub String);
56
57impl fmt::Display for BiomeParseError {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        write!(f, "unknown biome: {}", self.0)
60    }
61}
62
63impl std::error::Error for BiomeParseError {}
64
65impl FromStr for Biome {
66    type Err = BiomeParseError;
67
68    fn from_str(s: &str) -> Result<Self, Self::Err> {
69        match s.to_lowercase().as_str() {
70            "lush" => Ok(Self::Lush),
71            "toxic" => Ok(Self::Toxic),
72            "scorched" => Ok(Self::Scorched),
73            "radioactive" => Ok(Self::Radioactive),
74            "frozen" => Ok(Self::Frozen),
75            "barren" => Ok(Self::Barren),
76            "dead" => Ok(Self::Dead),
77            "weird" => Ok(Self::Weird),
78            "red" => Ok(Self::Red),
79            "green" => Ok(Self::Green),
80            "blue" => Ok(Self::Blue),
81            "swamp" => Ok(Self::Swamp),
82            "lava" => Ok(Self::Lava),
83            "waterworld" => Ok(Self::Waterworld),
84            "gasgiant" | "gas_giant" => Ok(Self::GasGiant),
85            _ => Err(BiomeParseError(s.to_string())),
86        }
87    }
88}
89
90/// Planet biome subtype matching GcBiomeSubType from game data (31 variants).
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
92#[cfg_attr(
93    feature = "archive",
94    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
95)]
96#[non_exhaustive]
97pub enum BiomeSubType {
98    LushRoomTemp,
99    LushHumid,
100    LushInactive,
101    ToxicTentacles,
102    ToxicFungus,
103    ToxicEggs,
104    ScorchedSinged,
105    ScorchedCharred,
106    ScorchedBlasted,
107    RadioactiveFungal,
108    RadioactiveContaminated,
109    RadioactiveIrradiated,
110    FrozenIce,
111    FrozenSnow,
112    FrozenGlacial,
113    BarrenDusty,
114    BarrenRocky,
115    BarrenMountainous,
116    DeadEmpty,
117    DeadCorroded,
118    DeadVoid,
119    WeirdHexagonal,
120    WeirdCabled,
121    WeirdBubbling,
122    WeirdFractured,
123    WeirdShattered,
124    WeirdContorted,
125    WeirdWireCell,
126    SwampMurky,
127    LavaVolcanic,
128    WaterworldOcean,
129}
130
131impl fmt::Display for BiomeSubType {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        write!(f, "{self:?}")
134    }
135}
136
137impl FromStr for BiomeSubType {
138    type Err = BiomeParseError;
139
140    fn from_str(s: &str) -> Result<Self, Self::Err> {
141        match s.to_lowercase().as_str() {
142            "lushroomtemp" => Ok(Self::LushRoomTemp),
143            "lushhumid" => Ok(Self::LushHumid),
144            "lushinactive" => Ok(Self::LushInactive),
145            "toxictentacles" => Ok(Self::ToxicTentacles),
146            "toxicfungus" => Ok(Self::ToxicFungus),
147            "toxiceggs" => Ok(Self::ToxicEggs),
148            "scorchedsinged" => Ok(Self::ScorchedSinged),
149            "scorchedcharred" => Ok(Self::ScorchedCharred),
150            "scorchedblasted" => Ok(Self::ScorchedBlasted),
151            "radioactivefungal" => Ok(Self::RadioactiveFungal),
152            "radioactivecontaminated" => Ok(Self::RadioactiveContaminated),
153            "radioactiveirradiated" => Ok(Self::RadioactiveIrradiated),
154            "frozenice" => Ok(Self::FrozenIce),
155            "frozensnow" => Ok(Self::FrozenSnow),
156            "frozenglacial" => Ok(Self::FrozenGlacial),
157            "barrendusty" => Ok(Self::BarrenDusty),
158            "barrenrocky" => Ok(Self::BarrenRocky),
159            "barrenmountainous" => Ok(Self::BarrenMountainous),
160            "deadempty" => Ok(Self::DeadEmpty),
161            "deadcorroded" => Ok(Self::DeadCorroded),
162            "deadvoid" => Ok(Self::DeadVoid),
163            "weirdhexagonal" => Ok(Self::WeirdHexagonal),
164            "weirdcabled" => Ok(Self::WeirdCabled),
165            "weirdbubbling" => Ok(Self::WeirdBubbling),
166            "weirdfractured" => Ok(Self::WeirdFractured),
167            "weirdshattered" => Ok(Self::WeirdShattered),
168            "weirdcontorted" => Ok(Self::WeirdContorted),
169            "weirdwirecell" => Ok(Self::WeirdWireCell),
170            "swampmurky" => Ok(Self::SwampMurky),
171            "lavavolcanic" => Ok(Self::LavaVolcanic),
172            "waterworldocean" => Ok(Self::WaterworldOcean),
173            _ => Err(BiomeParseError(s.to_string())),
174        }
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    const BIOME_SUB_VARIANTS: [BiomeSubType; 31] = [
183        BiomeSubType::LushRoomTemp,
184        BiomeSubType::LushHumid,
185        BiomeSubType::LushInactive,
186        BiomeSubType::ToxicTentacles,
187        BiomeSubType::ToxicFungus,
188        BiomeSubType::ToxicEggs,
189        BiomeSubType::ScorchedSinged,
190        BiomeSubType::ScorchedCharred,
191        BiomeSubType::ScorchedBlasted,
192        BiomeSubType::RadioactiveFungal,
193        BiomeSubType::RadioactiveContaminated,
194        BiomeSubType::RadioactiveIrradiated,
195        BiomeSubType::FrozenIce,
196        BiomeSubType::FrozenSnow,
197        BiomeSubType::FrozenGlacial,
198        BiomeSubType::BarrenDusty,
199        BiomeSubType::BarrenRocky,
200        BiomeSubType::BarrenMountainous,
201        BiomeSubType::DeadEmpty,
202        BiomeSubType::DeadCorroded,
203        BiomeSubType::DeadVoid,
204        BiomeSubType::WeirdHexagonal,
205        BiomeSubType::WeirdCabled,
206        BiomeSubType::WeirdBubbling,
207        BiomeSubType::WeirdFractured,
208        BiomeSubType::WeirdShattered,
209        BiomeSubType::WeirdContorted,
210        BiomeSubType::WeirdWireCell,
211        BiomeSubType::SwampMurky,
212        BiomeSubType::LavaVolcanic,
213        BiomeSubType::WaterworldOcean,
214    ];
215
216    #[test]
217    fn display_fromstr_roundtrip() {
218        for biome in [
219            Biome::Lush,
220            Biome::Toxic,
221            Biome::Scorched,
222            Biome::Radioactive,
223            Biome::Frozen,
224            Biome::Barren,
225            Biome::Dead,
226            Biome::Weird,
227            Biome::Red,
228            Biome::Green,
229            Biome::Blue,
230            Biome::Swamp,
231            Biome::Lava,
232            Biome::Waterworld,
233            Biome::GasGiant,
234        ] {
235            let s = biome.to_string();
236            let parsed: Biome = s.parse().unwrap();
237            assert_eq!(biome, parsed);
238        }
239    }
240
241    #[test]
242    fn case_insensitive_parse() {
243        assert_eq!("lush".parse::<Biome>().unwrap(), Biome::Lush);
244        assert_eq!("LUSH".parse::<Biome>().unwrap(), Biome::Lush);
245        assert_eq!("Lush".parse::<Biome>().unwrap(), Biome::Lush);
246    }
247
248    #[test]
249    fn gas_giant_alternate_name() {
250        assert_eq!("gas_giant".parse::<Biome>().unwrap(), Biome::GasGiant);
251    }
252
253    #[test]
254    fn unknown_biome_error() {
255        assert!("NotABiome".parse::<Biome>().is_err());
256    }
257
258    #[test]
259    fn subtype_display_fromstr_roundtrip() {
260        for v in BIOME_SUB_VARIANTS {
261            let s = v.to_string();
262            let parsed: BiomeSubType = s.parse().unwrap();
263            assert_eq!(v, parsed);
264        }
265    }
266}