1use chrono::DateTime;
4
5use crate::model::*;
6
7impl RawDiscoveryRecord {
8 pub fn to_core_record(&self) -> Option<nms_core::DiscoveryRecord> {
11 let discovery_type = match self.dd.dt.as_str() {
12 "Planet" => nms_core::Discovery::Planet,
13 "SolarSystem" => nms_core::Discovery::SolarSystem,
14 "Sector" => nms_core::Discovery::Sector,
15 "Animal" => nms_core::Discovery::Animal,
16 "Flora" => nms_core::Discovery::Flora,
17 "Mineral" => nms_core::Discovery::Mineral,
18 _ => return None,
19 };
20
21 let timestamp = if self.ows.ts > 0 {
22 DateTime::from_timestamp(self.ows.ts as i64, 0)
23 } else {
24 None
25 };
26
27 let discoverer = if self.ows.usn.is_empty() {
28 None
29 } else {
30 Some(self.ows.usn.clone())
31 };
32
33 let is_uploaded = self.fl.uploaded.unwrap_or(0) > 0;
34
35 Some(nms_core::DiscoveryRecord::new(
36 discovery_type,
37 self.dd.ua.to_galactic_address(0),
38 timestamp,
39 None, discoverer,
41 is_uploaded,
42 ))
43 }
44}
45
46impl PersistentPlayerBase {
47 pub fn to_core_base(&self) -> nms_core::PlayerBase {
49 let base_type = match self.base_type.persistent_base_types.as_str() {
50 "HomePlanetBase" => nms_core::BaseType::HomePlanetBase,
51 "FreighterBase" => nms_core::BaseType::FreighterBase,
52 _ => nms_core::BaseType::ExternalPlanetBase,
53 };
54
55 nms_core::PlayerBase::new(
56 self.name.clone(),
57 base_type,
58 self.galactic_address.to_galactic_address(0),
59 self.position,
60 if self.owner.uid.is_empty() {
61 None
62 } else {
63 Some(self.owner.uid.clone())
64 },
65 )
66 }
67}
68
69impl SaveRoot {
70 pub fn active_player_state(&self) -> &PlayerStateData {
72 match self.active_context.as_str() {
73 "Expedition" => &self.expedition_context.player_state_data,
74 _ => &self.base_context.player_state_data,
75 }
76 }
77
78 pub fn to_core_player_state(&self) -> nms_core::PlayerState {
80 let ps = self.active_player_state();
81 let ua = &ps.universe_address;
82 let current_address = ua.galactic_address.to_galactic_address(ua.reality_index);
83
84 let prev_ua = &ps.previous_universe_address;
85 let previous_address = if prev_ua.galactic_address.voxel_x == 0
86 && prev_ua.galactic_address.voxel_y == 0
87 && prev_ua.galactic_address.voxel_z == 0
88 && prev_ua.galactic_address.solar_system_index == 0
89 {
90 None
91 } else {
92 Some(
93 prev_ua
94 .galactic_address
95 .to_galactic_address(prev_ua.reality_index),
96 )
97 };
98
99 nms_core::PlayerState::new(
100 current_address,
101 ua.reality_index,
102 previous_address,
103 None, ps.units as u64,
105 ps.nanites as u64,
106 ps.specials as u64,
107 )
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn discovery_record_to_core() {
117 let raw = RawDiscoveryRecord {
118 dd: DiscoveryData {
119 ua: PackedGalacticAddress(0x513300F79B1D82),
120 dt: "Flora".into(),
121 vp: vec![],
122 },
123 dm: serde_json::Value::Object(serde_json::Map::new()),
124 ows: OwnershipData {
125 lid: String::new(),
126 uid: "12345".into(),
127 usn: "TestUser".into(),
128 ptk: "ST".into(),
129 ts: 1700000000,
130 },
131 fl: DiscoveryFlags {
132 created: Some(1),
133 uploaded: Some(1),
134 },
135 rid: Some("abc".into()),
136 };
137
138 let core = raw.to_core_record().unwrap();
139 assert_eq!(core.discovery_type, nms_core::Discovery::Flora);
140 assert_eq!(core.discoverer.as_deref(), Some("TestUser"));
141 assert!(core.is_uploaded);
142 assert!(core.timestamp.is_some());
143 }
144
145 #[test]
146 fn unknown_discovery_type_returns_none() {
147 let raw = RawDiscoveryRecord {
148 dd: DiscoveryData {
149 ua: PackedGalacticAddress(0),
150 dt: "UnknownType".into(),
151 vp: vec![],
152 },
153 dm: serde_json::Value::Null,
154 ows: OwnershipData::default(),
155 fl: DiscoveryFlags::default(),
156 rid: None,
157 };
158 assert!(raw.to_core_record().is_none());
159 }
160
161 #[test]
162 fn base_to_core() {
163 let base = PersistentPlayerBase {
164 base_version: 8,
165 galactic_address: PackedGalacticAddress(0x40050003AB8C07),
166 position: [100.0, 200.0, 300.0],
167 forward: [1.0, 0.0, 0.0],
168 last_update_timestamp: 1700000000,
169 objects: vec![],
170 rid: String::new(),
171 owner: OwnershipData {
172 lid: String::new(),
173 uid: "76561198025707979".into(),
174 usn: String::new(),
175 ptk: "ST".into(),
176 ts: 0,
177 },
178 name: "My Base".into(),
179 base_type: BaseTypeWrapper {
180 persistent_base_types: "HomePlanetBase".into(),
181 },
182 last_edited_by_id: String::new(),
183 last_edited_by_username: String::new(),
184 game_mode: None,
185 };
186
187 let core = base.to_core_base();
188 assert_eq!(core.name, "My Base");
189 assert_eq!(core.base_type, nms_core::BaseType::HomePlanetBase);
190 assert_eq!(core.owner_uid.as_deref(), Some("76561198025707979"));
191 }
192
193 #[test]
194 fn galactic_address_object_to_core() {
195 let obj = GalacticAddressObject {
196 voxel_x: 1699,
197 voxel_y: -2,
198 voxel_z: 165,
199 solar_system_index: 369,
200 planet_index: 0,
201 };
202 let addr = obj.to_galactic_address(0);
203 assert_eq!(addr.voxel_x(), 1699);
204 assert_eq!(addr.voxel_y(), -2);
205 assert_eq!(addr.voxel_z(), 165);
206 assert_eq!(addr.solar_system_index(), 369);
207 assert_eq!(addr.planet_index(), 0);
208 assert_eq!(addr.reality_index, 0);
209 }
210
211 #[test]
212 fn active_context_main() {
213 let json = r#"{
214 "Version": 4720,
215 "Platform": "Mac|Final",
216 "ActiveContext": "Main",
217 "CommonStateData": {"SaveName": "test"},
218 "BaseContext": {
219 "GameMode": 1,
220 "PlayerStateData": {"Units": 999}
221 },
222 "ExpeditionContext": {
223 "GameMode": 6,
224 "PlayerStateData": {"Units": 111}
225 },
226 "DiscoveryManagerData": {"DiscoveryData-v1": {"Store": {"Record": []}}}
227 }"#;
228 let save: SaveRoot = serde_json::from_str(json).unwrap();
229 assert_eq!(save.active_player_state().units, 999);
230 }
231
232 #[test]
233 fn active_context_expedition() {
234 let json = r#"{
235 "Version": 4720,
236 "Platform": "Mac|Final",
237 "ActiveContext": "Expedition",
238 "CommonStateData": {"SaveName": "test"},
239 "BaseContext": {
240 "GameMode": 1,
241 "PlayerStateData": {"Units": 999}
242 },
243 "ExpeditionContext": {
244 "GameMode": 6,
245 "PlayerStateData": {"Units": 111}
246 },
247 "DiscoveryManagerData": {"DiscoveryData-v1": {"Store": {"Record": []}}}
248 }"#;
249 let save: SaveRoot = serde_json::from_str(json).unwrap();
250 assert_eq!(save.active_player_state().units, 111);
251 }
252
253 #[test]
254 fn to_core_player_state_with_previous() {
255 let json = r#"{
256 "Version": 4720,
257 "Platform": "Mac|Final",
258 "ActiveContext": "Main",
259 "CommonStateData": {"SaveName": "test"},
260 "BaseContext": {
261 "GameMode": 1,
262 "PlayerStateData": {
263 "UniverseAddress": {
264 "RealityIndex": 0,
265 "GalacticAddress": {"VoxelX": 100, "VoxelY": 10, "VoxelZ": 200, "SolarSystemIndex": 369, "PlanetIndex": 2}
266 },
267 "PreviousUniverseAddress": {
268 "RealityIndex": 0,
269 "GalacticAddress": {"VoxelX": 50, "VoxelY": 5, "VoxelZ": 100, "SolarSystemIndex": 505, "PlanetIndex": 0}
270 },
271 "Units": 1000000,
272 "Nanites": 5000,
273 "Specials": 200
274 }
275 },
276 "ExpeditionContext": {"GameMode": 6, "PlayerStateData": {}},
277 "DiscoveryManagerData": {"DiscoveryData-v1": {"Store": {"Record": []}}}
278 }"#;
279 let save: SaveRoot = serde_json::from_str(json).unwrap();
280 let state = save.to_core_player_state();
281 assert_eq!(state.units, 1000000);
282 assert_eq!(state.nanites, 5000);
283 assert_eq!(state.quicksilver, 200);
284 assert_eq!(state.current_address.voxel_x(), 100);
285 assert!(state.previous_address.is_some());
286 assert_eq!(state.previous_address.unwrap().voxel_x(), 50);
287 }
288
289 #[test]
290 fn to_core_player_state_zero_previous_is_none() {
291 let json = r#"{
292 "Version": 4720,
293 "Platform": "Mac|Final",
294 "ActiveContext": "Main",
295 "CommonStateData": {},
296 "BaseContext": {
297 "GameMode": 1,
298 "PlayerStateData": {
299 "UniverseAddress": {
300 "RealityIndex": 0,
301 "GalacticAddress": {"VoxelX": 100, "VoxelY": 10, "VoxelZ": 200, "SolarSystemIndex": 369, "PlanetIndex": 0}
302 },
303 "PreviousUniverseAddress": {
304 "RealityIndex": 0,
305 "GalacticAddress": {"VoxelX": 0, "VoxelY": 0, "VoxelZ": 0, "SolarSystemIndex": 0, "PlanetIndex": 0}
306 },
307 "Units": 500
308 }
309 },
310 "ExpeditionContext": {"GameMode": 6, "PlayerStateData": {}},
311 "DiscoveryManagerData": {"DiscoveryData-v1": {"Store": {"Record": []}}}
312 }"#;
313 let save: SaveRoot = serde_json::from_str(json).unwrap();
314 let state = save.to_core_player_state();
315 assert!(state.previous_address.is_none());
316 }
317}