ed_journals/modules/logs/content/log_event_content/
scan_event.rs1use serde::{Deserialize, Deserializer, Serialize};
2use serde_json::Value;
3
4use crate::galaxy::{Gravity, LocalDistance, PlanetComposition};
5use crate::modules::galaxy::{
6 Atmosphere, AtmosphereElement, AtmosphereType, OrbitInfo, PlanetClass, RingClass, StarClass,
7 StarLuminosity, TerraformState, Volcanism,
8};
9use crate::modules::materials::Material;
10
11#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
12#[serde(rename_all = "PascalCase")]
13pub struct ScanEvent {
14 pub scan_type: ScanEventScanType,
15 pub body_name: String,
16
17 #[serde(rename = "BodyID")]
18 pub body_id: u8,
19
20 #[serde(default)]
21 pub parents: Vec<ScanEventParent>,
22 pub star_system: String,
23 pub system_address: u64,
24
25 #[serde(rename = "DistanceFromArrivalLS")]
26 pub distance_from_arrival: LocalDistance,
27
28 #[serde(default)]
29 pub was_discovered: bool,
30
31 #[serde(default)]
32 pub was_mapped: bool,
33
34 #[serde(flatten)]
36 pub kind: ScanEventKind,
37}
38
39#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
40#[serde(rename_all = "PascalCase")]
41pub enum ScanEventScanType {
42 Basic,
43 NavBeaconDetail,
44 AutoScan,
45 Detailed,
46}
47
48impl ScanEventScanType {
49 pub fn is_basic(&self) -> bool {
50 matches!(self, ScanEventScanType::Basic)
51 }
52
53 pub fn is_nav_beacon_detail(&self) -> bool {
54 matches!(self, ScanEventScanType::NavBeaconDetail)
55 }
56
57 pub fn is_auto_scan(&self) -> bool {
58 matches!(self, ScanEventScanType::AutoScan)
59 }
60
61 pub fn is_detailed(&self) -> bool {
62 matches!(self, ScanEventScanType::Detailed)
63 }
64}
65
66#[derive(Debug, Serialize, Clone, PartialEq)]
67pub enum ScanEventKind {
68 Star(ScanEventStar),
69 Planet(ScanEventPlanet),
70 BeltCluster(ScanEventBeltCluster),
71}
72
73impl ScanEventKind {
74 pub fn is_star(&self) -> bool {
75 matches!(self, ScanEventKind::Star(_))
76 }
77
78 pub fn star(&self) -> Option<&ScanEventStar> {
79 if let ScanEventKind::Star(star) = self {
80 return Some(star);
81 }
82
83 None
84 }
85
86 pub fn is_planet(&self) -> bool {
87 matches!(self, ScanEventKind::Planet(_))
88 }
89
90 pub fn planet(&self) -> Option<&ScanEventPlanet> {
91 if let ScanEventKind::Planet(planet) = self {
92 return Some(planet);
93 }
94
95 None
96 }
97
98 pub fn is_belt_cluster(&self) -> bool {
99 matches!(self, ScanEventKind::BeltCluster(_))
100 }
101
102 pub fn belt_cluster(&self) -> Option<&ScanEventBeltCluster> {
103 if let ScanEventKind::BeltCluster(cluster) = self {
104 return Some(cluster);
105 }
106
107 None
108 }
109}
110
111impl<'de> Deserialize<'de> for ScanEventKind {
112 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
113 where
114 D: Deserializer<'de>,
115 {
116 let value = Value::deserialize(deserializer)?;
117
118 let Value::Object(map) = &value else {
119 return Err(serde::de::Error::custom("Failed to parse scan event kind"));
120 };
121
122 if map.get("StarType").is_some() {
125 return Ok(ScanEventKind::Star(
126 serde_json::from_value(value)
127 .map_err(|e| serde::de::Error::custom(format!("{e}")))?,
128 ));
129 }
130
131 if map.get("TidalLock").is_some() {
134 return Ok(ScanEventKind::Planet(
135 serde_json::from_value(value)
136 .map_err(|e| serde::de::Error::custom(format!("{e}")))?,
137 ));
138 }
139
140 Ok(ScanEventKind::BeltCluster(
142 serde_json::from_value(value).map_err(|e| serde::de::Error::custom(format!("{e}")))?,
143 ))
144 }
145}
146
147#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
148#[serde(rename_all = "PascalCase")]
149pub struct ScanEventStar {
150 pub star_type: StarClass,
151
152 #[serde(default)]
153 pub subclass: u8,
154 pub stellar_mass: f32,
155 pub radius: f32,
156 pub absolute_magnitude: f32,
157
158 #[serde(rename = "Age_MY")]
159 pub age_my: u32,
160 pub surface_temperature: f32,
161 pub luminosity: StarLuminosity,
162
163 pub orbit_info: Option<OrbitInfo>,
165 pub rotation_period: f32,
166 pub axial_tilt: f32,
167
168 #[serde(default)]
169 pub rings: Vec<ScanEventRing>,
170}
171
172#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
173#[serde(rename_all = "PascalCase")]
174pub struct ScanEventPlanet {
175 pub tidal_lock: bool,
176 pub terraform_state: TerraformState,
177 pub planet_class: PlanetClass,
178 pub atmosphere: Atmosphere,
179 pub atmosphere_type: Option<AtmosphereType>,
180
181 #[serde(default)]
182 pub atmosphere_composition: Vec<ScanEventPlanetAtmosphereComposition>,
183 pub volcanism: Volcanism,
184
185 #[serde(rename = "MassEM")]
186 pub mass_em: f32,
187
188 pub radius: f32,
190 pub surface_gravity: Gravity,
191 pub surface_temperature: f32,
192 pub surface_pressure: f32,
193 pub landable: bool,
194
195 #[serde(default)]
196 pub materials: Vec<ScanEventPlanetMaterial>,
197 pub composition: Option<PlanetComposition>,
198
199 #[serde(flatten)]
200 pub orbit_info: OrbitInfo,
201 pub rotation_period: f32,
202 pub axial_tilt: f32,
203
204 #[serde(default)]
205 pub rings: Vec<ScanEventRing>,
206}
207
208#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
209#[serde(rename_all = "PascalCase")]
210pub struct ScanEventPlanetAtmosphereComposition {
211 pub name: AtmosphereElement,
212 pub percent: f32,
213}
214
215#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
216#[serde(rename_all = "PascalCase")]
217pub struct ScanEventPlanetMaterial {
218 pub name: Material,
219 pub percent: f32,
220}
221
222#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
223#[serde(rename_all = "PascalCase")]
224pub enum ScanEventParent {
225 Null(u8),
226 Star(u8),
227 Ring(u8),
228 Planet(u8),
229}
230
231#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
232#[serde(rename_all = "PascalCase")]
233pub struct ScanEventRing {
234 pub name: String,
235 pub ring_class: RingClass,
236
237 #[serde(rename = "MassMT")]
238 pub mass_mt: f32,
239 pub inner_rad: f32,
240 pub outer_rad: f32,
241}
242
243#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
245pub struct ScanEventBeltCluster {}
246
247#[cfg(test)]
248mod tests {
249 use crate::galaxy::LocalDistance;
250 use crate::modules::logs::content::log_event_content::scan_event::ScanEvent;
251
252 #[test]
253 fn scan_event_is_parsed_correctly() {
254 let value = serde_json::from_str::<ScanEvent>(
255 r#"
256 {
257 "timestamp": "2022-10-11T19:59:10Z",
258 "event": "Scan",
259 "ScanType": "AutoScan",
260 "BodyName": "Etain A Belt Cluster 1",
261 "BodyID": 2,
262 "Parents": [
263 {
264 "Ring": 1
265 },
266 {
267 "Star": 0
268 }
269 ],
270 "StarSystem": "Etain",
271 "SystemAddress": 2869977884057,
272 "DistanceFromArrivalLS": 4.884683,
273 "WasDiscovered": true,
274 "WasMapped": false
275 }
276 "#,
277 );
278
279 assert!(value.is_ok());
280 }
281
282 #[test]
283 fn distance_is_converted_correctly() {
284 fn assert_roughly_eq(a: f32, b: f32) {
285 assert!((a - b).abs() < 0.0001);
286 }
287
288 let distance = LocalDistance(1000.0);
289 assert_roughly_eq(distance.as_au(), 2.0);
290
291 let distance = LocalDistance::from_au(2.0);
292 assert_roughly_eq(distance.as_ls(), 1000.0);
293 }
294}