Skip to main content

nms_core/
galaxy.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3use std::str::FromStr;
4
5/// The four galaxy type classifications that affect planet generation.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7#[non_exhaustive]
8pub enum GalaxyType {
9    Norm,
10    Lush,
11    Harsh,
12    Empty,
13}
14
15impl fmt::Display for GalaxyType {
16    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17        match self {
18            Self::Norm => write!(f, "Normal"),
19            Self::Lush => write!(f, "Lush"),
20            Self::Harsh => write!(f, "Harsh"),
21            Self::Empty => write!(f, "Empty"),
22        }
23    }
24}
25
26/// Error returned when parsing a galaxy type string fails.
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct GalaxyTypeParseError(pub String);
29
30impl fmt::Display for GalaxyTypeParseError {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        write!(f, "unknown galaxy type: {}", self.0)
33    }
34}
35
36impl std::error::Error for GalaxyTypeParseError {}
37
38impl FromStr for GalaxyType {
39    type Err = GalaxyTypeParseError;
40
41    fn from_str(s: &str) -> Result<Self, Self::Err> {
42        match s.to_lowercase().as_str() {
43            "norm" | "normal" => Ok(Self::Norm),
44            "lush" => Ok(Self::Lush),
45            "harsh" => Ok(Self::Harsh),
46            "empty" => Ok(Self::Empty),
47            _ => Err(GalaxyTypeParseError(s.to_string())),
48        }
49    }
50}
51
52/// One of the 256 galaxies in No Man's Sky.
53#[derive(Debug, Clone, PartialEq, Eq, Hash)]
54#[non_exhaustive]
55pub struct Galaxy {
56    pub index: u8,
57    pub name: &'static str,
58    pub galaxy_type: GalaxyType,
59}
60
61impl Galaxy {
62    /// Lookup galaxy by index (0-255). Always returns a valid Galaxy.
63    pub fn by_index(index: u8) -> Self {
64        let (name, galaxy_type) = GALAXIES[index as usize];
65        Self {
66            index,
67            name,
68            galaxy_type,
69        }
70    }
71}
72
73impl fmt::Display for Galaxy {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        write!(f, "{}", self.name)
76    }
77}
78
79use GalaxyType::*;
80
81/// All 256 galaxies: (name, type).
82///
83/// Type pattern (0-indexed, derived from NMS wiki):
84/// - Harsh (26): 2, 14, 22, 34, 42, 54, 62, 74, 82, 94, 102, 114, 122, 134,
85///   142, 154, 162, 174, 182, 194, 202, 214, 222, 234, 242, 254
86/// - Empty (26): 6, 11, 26, 31, 46, 51, 66, 71, 86, 91, 106, 111, 126, 131,
87///   146, 151, 166, 171, 186, 191, 206, 211, 226, 231, 246, 251
88/// - Lush (25): 9, 18, 29, 38, 49, 58, 69, 78, 89, 98, 109, 118, 129, 138,
89///   149, 158, 169, 178, 189, 198, 209, 218, 229, 238, 249
90/// - Norm (179): everything else
91static GALAXIES: [(&str, GalaxyType); 256] = [
92    ("Euclid", Norm),              // 0
93    ("Hilbert Dimension", Norm),   // 1
94    ("Calypso", Harsh),            // 2
95    ("Hesperius Dimension", Norm), // 3
96    ("Hyades", Norm),              // 4
97    ("Ickjamatew", Norm),          // 5
98    ("Budullangr", Empty),         // 6
99    ("Kikolgallr", Norm),          // 7
100    ("Eltiensleen", Norm),         // 8
101    ("Eissentam", Lush),           // 9
102    ("Elkupalos", Norm),           // 10
103    ("Aptarkaba", Empty),          // 11
104    ("Ontiniangp", Norm),          // 12
105    ("Odiwagiri", Norm),           // 13
106    ("Ogtialabi", Harsh),          // 14
107    ("Muhacksonto", Norm),         // 15
108    ("Hitonskyer", Norm),          // 16
109    ("Rerasmutul", Norm),          // 17
110    ("Isdoraijung", Lush),         // 18
111    ("Doctinawyra", Norm),         // 19
112    ("Loychazinq", Norm),          // 20
113    ("Zukasizawa", Norm),          // 21
114    ("Ekwathore", Harsh),          // 22
115    ("Yeberhahne", Norm),          // 23
116    ("Twerbetek", Norm),           // 24
117    ("Sivarates", Norm),           // 25
118    ("Eajerandal", Empty),         // 26
119    ("Aldukesci", Norm),           // 27
120    ("Wotyarogii", Norm),          // 28
121    ("Sudzerbal", Lush),           // 29
122    ("Maupenzhay", Norm),          // 30
123    ("Sugueziume", Empty),         // 31
124    ("Brogoweldian", Norm),        // 32
125    ("Ehbogdenbu", Norm),          // 33
126    ("Ijsenufryos", Harsh),        // 34
127    ("Nipikulha", Norm),           // 35
128    ("Autsurabin", Norm),          // 36
129    ("Lusontrygiamh", Norm),       // 37
130    ("Rewmanawa", Lush),           // 38
131    ("Ethiophodhe", Norm),         // 39
132    ("Urastrykle", Norm),          // 40
133    ("Xobeurindj", Norm),          // 41
134    ("Oniijialdu", Harsh),         // 42
135    ("Wucetosucc", Norm),          // 43
136    ("Ebyeloof", Norm),            // 44
137    ("Odyavanta", Norm),           // 45
138    ("Milekistri", Empty),         // 46
139    ("Waferganh", Norm),           // 47
140    ("Agnusopwit", Norm),          // 48
141    ("Teyaypilny", Lush),          // 49
142    ("Zalienkosm", Norm),          // 50
143    ("Ladgudiraf", Empty),         // 51
144    ("Mushonponte", Norm),         // 52
145    ("Amsentisz", Norm),           // 53
146    ("Fladiselm", Harsh),          // 54
147    ("Laanawemb", Norm),           // 55
148    ("Ilkerloor", Norm),           // 56
149    ("Davanossi", Norm),           // 57
150    ("Ploehrliou", Lush),          // 58
151    ("Corpinyaya", Norm),          // 59
152    ("Leckandmeram", Norm),        // 60
153    ("Quulngais", Norm),           // 61
154    ("Nokokipsechl", Harsh),       // 62
155    ("Rinblodesa", Norm),          // 63
156    ("Loydporpen", Norm),          // 64
157    ("Ibtrevskip", Norm),          // 65
158    ("Elkowaldb", Empty),          // 66
159    ("Heholhofsko", Norm),         // 67
160    ("Yebrilowisod", Norm),        // 68
161    ("Husalvangewi", Lush),        // 69
162    ("Ovna'uesed", Norm),          // 70
163    ("Bahibusey", Empty),          // 71
164    ("Nuybeliaure", Norm),         // 72
165    ("Doshawchuc", Norm),          // 73
166    ("Ruckinarkh", Harsh),         // 74
167    ("Thorettac", Norm),           // 75
168    ("Nuponoparau", Norm),         // 76
169    ("Moglaschil", Norm),          // 77
170    ("Uiweupose", Lush),           // 78
171    ("Nasmilete", Norm),           // 79
172    ("Ekdaluskin", Norm),          // 80
173    ("Hakapanasy", Norm),          // 81
174    ("Dimonimba", Harsh),          // 82
175    ("Cajaccari", Norm),           // 83
176    ("Olonerovo", Norm),           // 84
177    ("Umlanswick", Norm),          // 85
178    ("Henayliszm", Empty),         // 86
179    ("Utzenmate", Norm),           // 87
180    ("Umirpaiya", Norm),           // 88
181    ("Paholiang", Lush),           // 89
182    ("Iaereznika", Norm),          // 90
183    ("Yudukagath", Empty),         // 91
184    ("Boealalosnj", Norm),         // 92
185    ("Yaevarcko", Norm),           // 93
186    ("Coellosipp", Harsh),         // 94
187    ("Wayndohalou", Norm),         // 95
188    ("Smoduraykl", Norm),          // 96
189    ("Apmaneessu", Norm),          // 97
190    ("Hicanpaav", Lush),           // 98
191    ("Akvasanta", Norm),           // 99
192    ("Tuychelisaor", Norm),        // 100
193    ("Rivskimbe", Norm),           // 101
194    ("Daksanquix", Harsh),         // 102
195    ("Kissonlin", Norm),           // 103
196    ("Aediabiel", Norm),           // 104
197    ("Ulosaginyik", Norm),         // 105
198    ("Roclaytonycar", Empty),      // 106
199    ("Kichiaroa", Norm),           // 107
200    ("Irceauffey", Norm),          // 108
201    ("Nudquathsenfe", Lush),       // 109
202    ("Getaizakaal", Norm),         // 110
203    ("Hansolmien", Empty),         // 111
204    ("Bloytisagra", Norm),         // 112
205    ("Ladsenlay", Norm),           // 113
206    ("Luyugoslasr", Harsh),        // 114
207    ("Ubredhatk", Norm),           // 115
208    ("Cidoniana", Norm),           // 116
209    ("Jasinessa", Norm),           // 117
210    ("Torweierf", Lush),           // 118
211    ("Saffneckm", Norm),           // 119
212    ("Thnistner", Norm),           // 120
213    ("Dotusingg", Norm),           // 121
214    ("Luleukous", Harsh),          // 122
215    ("Jelmandan", Norm),           // 123
216    ("Otimanaso", Norm),           // 124
217    ("Enjaxusanto", Norm),         // 125
218    ("Sezviktorew", Empty),        // 126
219    ("Zikehpm", Norm),             // 127
220    ("Bephembah", Norm),           // 128
221    ("Broomerrai", Lush),          // 129
222    ("Meximicka", Norm),           // 130
223    ("Venessika", Empty),          // 131
224    ("Gaiteseling", Norm),         // 132
225    ("Zosakasiro", Norm),          // 133
226    ("Drajayanes", Harsh),         // 134
227    ("Ooibekuar", Norm),           // 135
228    ("Urckiansi", Norm),           // 136
229    ("Dozivadido", Norm),          // 137
230    ("Emiekereks", Lush),          // 138
231    ("Meykinunukur", Norm),        // 139
232    ("Kimycuristh", Norm),         // 140
233    ("Roansfien", Norm),           // 141
234    ("Isgarmeso", Harsh),          // 142
235    ("Daitibeli", Norm),           // 143
236    ("Gucuttarik", Norm),          // 144
237    ("Enlaythie", Norm),           // 145
238    ("Drewweste", Empty),          // 146
239    ("Akbulkabi", Norm),           // 147
240    ("Homskiw", Norm),             // 148
241    ("Zavainlani", Lush),          // 149
242    ("Jewijkmas", Norm),           // 150
243    ("Itlhotagra", Empty),         // 151
244    ("Podalicess", Norm),          // 152
245    ("Hiviusauer", Norm),          // 153
246    ("Halsebenk", Harsh),          // 154
247    ("Puikitoac", Norm),           // 155
248    ("Gaybakuaria", Norm),         // 156
249    ("Grbodubhe", Norm),           // 157
250    ("Rycempler", Lush),           // 158
251    ("Indjalala", Norm),           // 159
252    ("Fontenikk", Norm),           // 160
253    ("Pasycihelwhee", Norm),       // 161
254    ("Ikbaksmit", Harsh),          // 162
255    ("Telicianses", Norm),         // 163
256    ("Oyleyzhan", Norm),           // 164
257    ("Uagerosat", Norm),           // 165
258    ("Impoxectin", Empty),         // 166
259    ("Twoodmand", Norm),           // 167
260    ("Hilfsesorbs", Norm),         // 168
261    ("Ezdaranit", Lush),           // 169
262    ("Wiensanshe", Norm),          // 170
263    ("Ewheelonc", Empty),          // 171
264    ("Litzmantufa", Norm),         // 172
265    ("Emarmatosi", Norm),          // 173
266    ("Mufimbomacvi", Harsh),       // 174
267    ("Wongquarum", Norm),          // 175
268    ("Hapirajua", Norm),           // 176
269    ("Igbinduina", Norm),          // 177
270    ("Wepaitvas", Lush),           // 178
271    ("Sthatigudi", Norm),          // 179
272    ("Yekathsebehn", Norm),        // 180
273    ("Ebedeagurst", Norm),         // 181
274    ("Nolisonia", Harsh),          // 182
275    ("Ulexovitab", Norm),          // 183
276    ("Iodhinxois", Norm),          // 184
277    ("Irroswitzs", Norm),          // 185
278    ("Bifredait", Empty),          // 186
279    ("Beiraghedwe", Norm),         // 187
280    ("Yeonatlak", Norm),           // 188
281    ("Cugnatachh", Lush),          // 189
282    ("Nozoryenki", Norm),          // 190
283    ("Ebralduri", Empty),          // 191
284    ("Evcickcandj", Norm),         // 192
285    ("Ziybosswin", Norm),          // 193
286    ("Heperclait", Harsh),         // 194
287    ("Sugiuniam", Norm),           // 195
288    ("Aaseertush", Norm),          // 196
289    ("Uglyestemaa", Norm),         // 197
290    ("Horeroedsh", Lush),          // 198
291    ("Drundemiso", Norm),          // 199
292    ("Ityanianat", Norm),          // 200
293    ("Purneyrine", Norm),          // 201
294    ("Dokiessmat", Harsh),         // 202
295    ("Nupiacheh", Norm),           // 203
296    ("Dihewsonj", Norm),           // 204
297    ("Rudrailhik", Norm),          // 205
298    ("Tweretnort", Empty),         // 206
299    ("Snatreetze", Norm),          // 207
300    ("Iwundaracos", Norm),         // 208
301    ("Digarlewena", Lush),         // 209
302    ("Erquagsta", Norm),           // 210
303    ("Logovoloin", Empty),         // 211
304    ("Boyaghosganh", Norm),        // 212
305    ("Kuolungau", Norm),           // 213
306    ("Pehneldept", Harsh),         // 214
307    ("Yevettiiqidcon", Norm),      // 215
308    ("Sahliacabru", Norm),         // 216
309    ("Noggalterpor", Norm),        // 217
310    ("Chmageaki", Lush),           // 218
311    ("Veticueca", Norm),           // 219
312    ("Vittesbursul", Norm),        // 220
313    ("Nootanore", Norm),           // 221
314    ("Innebdjerah", Harsh),        // 222
315    ("Kisvarcini", Norm),          // 223
316    ("Cuzcogipper", Norm),         // 224
317    ("Pamanhermonsu", Norm),       // 225
318    ("Brotoghek", Empty),          // 226
319    ("Mibittara", Norm),           // 227
320    ("Huruahili", Norm),           // 228
321    ("Raldwicarn", Lush),          // 229
322    ("Ezdartlic", Norm),           // 230
323    ("Badesclema", Empty),         // 231
324    ("Isenkeyan", Norm),           // 232
325    ("Iadoitesu", Norm),           // 233
326    ("Yagrovoisi", Harsh),         // 234
327    ("Ewcomechio", Norm),          // 235
328    ("Inunnunnoda", Norm),         // 236
329    ("Dischiutun", Norm),          // 237
330    ("Yuwarugha", Lush),           // 238
331    ("Ialmendra", Norm),           // 239
332    ("Reponudrle", Norm),          // 240
333    ("Rinjanagrbo", Norm),         // 241
334    ("Zeziceloh", Harsh),          // 242
335    ("Oeileutasc", Norm),          // 243
336    ("Zicniijinis", Norm),         // 244
337    ("Dugnowarilda", Norm),        // 245
338    ("Neuxoisan", Empty),          // 246
339    ("Ilmenhorn", Norm),           // 247
340    ("Rukwatsuku", Norm),          // 248
341    ("Nepitzaspru", Lush),         // 249
342    ("Chcehoemig", Norm),          // 250
343    ("Haffneyrin", Empty),         // 251
344    ("Uliciawai", Norm),           // 252
345    ("Tuhgrespod", Norm),          // 253
346    ("Iousongola", Harsh),         // 254
347    ("Odyalutai", Norm),           // 255
348];
349
350#[cfg(test)]
351mod tests {
352    use super::*;
353
354    #[test]
355    fn euclid_is_index_zero() {
356        let g = Galaxy::by_index(0);
357        assert_eq!(g.index, 0);
358        assert_eq!(g.name, "Euclid");
359        assert_eq!(g.galaxy_type, GalaxyType::Norm);
360    }
361
362    #[test]
363    fn calypso_is_harsh() {
364        let g = Galaxy::by_index(2);
365        assert_eq!(g.name, "Calypso");
366        assert_eq!(g.galaxy_type, GalaxyType::Harsh);
367    }
368
369    #[test]
370    fn eissentam_is_lush() {
371        let g = Galaxy::by_index(9);
372        assert_eq!(g.name, "Eissentam");
373        assert_eq!(g.galaxy_type, GalaxyType::Lush);
374    }
375
376    #[test]
377    fn budullangr_is_empty() {
378        let g = Galaxy::by_index(6);
379        assert_eq!(g.name, "Budullangr");
380        assert_eq!(g.galaxy_type, GalaxyType::Empty);
381    }
382
383    #[test]
384    fn all_256_valid() {
385        for i in 0..=255u8 {
386            let g = Galaxy::by_index(i);
387            assert_eq!(g.index, i);
388            assert!(!g.name.is_empty());
389        }
390    }
391
392    #[test]
393    fn type_counts() {
394        let mut norm = 0u16;
395        let mut lush = 0u16;
396        let mut harsh = 0u16;
397        let mut empty = 0u16;
398        for i in 0..=255u8 {
399            match Galaxy::by_index(i).galaxy_type {
400                GalaxyType::Norm => norm += 1,
401                GalaxyType::Lush => lush += 1,
402                GalaxyType::Harsh => harsh += 1,
403                GalaxyType::Empty => empty += 1,
404            }
405        }
406        assert_eq!(lush, 25);
407        assert_eq!(harsh, 26);
408        assert_eq!(empty, 26);
409        assert_eq!(norm, 179);
410    }
411
412    #[test]
413    fn last_galaxy() {
414        let g = Galaxy::by_index(255);
415        assert_eq!(g.name, "Odyalutai");
416    }
417
418    #[test]
419    fn galaxy_display() {
420        let g = Galaxy::by_index(0);
421        assert_eq!(format!("{g}"), "Euclid");
422    }
423
424    #[test]
425    fn galaxy_type_display_fromstr_roundtrip() {
426        for gt in [
427            GalaxyType::Norm,
428            GalaxyType::Lush,
429            GalaxyType::Harsh,
430            GalaxyType::Empty,
431        ] {
432            let s = gt.to_string();
433            let parsed: GalaxyType = s.parse().unwrap();
434            assert_eq!(gt, parsed);
435        }
436    }
437
438    #[test]
439    fn galaxy_type_alternate_names() {
440        assert_eq!("norm".parse::<GalaxyType>().unwrap(), GalaxyType::Norm);
441        assert_eq!("normal".parse::<GalaxyType>().unwrap(), GalaxyType::Norm);
442    }
443}