Skip to main content

nms_query/
show.rs

1//! Detail view queries for systems, planets, and bases.
2
3use nms_core::player::PlayerBase;
4use nms_core::system::System;
5use nms_graph::spatial::SystemId;
6use nms_graph::{GalaxyModel, GraphError};
7
8/// What to show detail for.
9#[derive(Debug, Clone)]
10pub enum ShowQuery {
11    /// Show a system by name or packed address.
12    System(String),
13    /// Show a base by name.
14    Base(String),
15}
16
17/// Result of a show query.
18#[derive(Debug, Clone)]
19pub enum ShowResult {
20    System(ShowSystemResult),
21    Base(ShowBaseResult),
22}
23
24#[derive(Debug, Clone)]
25pub struct ShowSystemResult {
26    pub system: System,
27    pub portal_hex: String,
28    pub galaxy_name: String,
29    pub distance_from_player: Option<f64>,
30}
31
32#[derive(Debug, Clone)]
33pub struct ShowBaseResult {
34    pub base: PlayerBase,
35    pub portal_hex: String,
36    pub galaxy_name: String,
37    pub system: Option<System>,
38    pub distance_from_player: Option<f64>,
39}
40
41/// Execute a show query.
42pub fn execute_show(model: &GalaxyModel, query: &ShowQuery) -> Result<ShowResult, GraphError> {
43    match query {
44        ShowQuery::System(name_or_id) => show_system(model, name_or_id),
45        ShowQuery::Base(name) => show_base(model, name),
46    }
47}
48
49fn show_system(model: &GalaxyModel, name_or_id: &str) -> Result<ShowResult, GraphError> {
50    // Try name lookup first, then try as packed hex address
51    let system = if let Some((_id, sys)) = model.system_by_name(name_or_id) {
52        sys
53    } else {
54        let hex = name_or_id
55            .strip_prefix("0x")
56            .or_else(|| name_or_id.strip_prefix("0X"))
57            .unwrap_or(name_or_id);
58        let packed = u64::from_str_radix(hex, 16)
59            .map_err(|_| GraphError::SystemNotFound(name_or_id.to_string()))?;
60        let id = SystemId(packed & 0x0FFF_FFFF_FFFF);
61        model
62            .system(&id)
63            .ok_or_else(|| GraphError::SystemNotFound(name_or_id.to_string()))?
64    };
65
66    let portal_hex = format!("{:012X}", system.address.packed());
67    let galaxy = nms_core::galaxy::Galaxy::by_index(system.address.reality_index);
68
69    let distance_from_player = model
70        .player_position()
71        .map(|pos| pos.distance_ly(&system.address));
72
73    Ok(ShowResult::System(ShowSystemResult {
74        system: system.clone(),
75        portal_hex,
76        galaxy_name: galaxy.name.to_string(),
77        distance_from_player,
78    }))
79}
80
81fn show_base(model: &GalaxyModel, name: &str) -> Result<ShowResult, GraphError> {
82    let base = model
83        .base(name)
84        .ok_or_else(|| GraphError::BaseNotFound(name.to_string()))?;
85
86    let portal_hex = format!("{:012X}", base.address.packed());
87    let galaxy = nms_core::galaxy::Galaxy::by_index(base.address.reality_index);
88
89    // Try to find the system this base is in
90    let sys_id = SystemId::from_address(&base.address);
91    let system = model.system(&sys_id).cloned();
92
93    let distance_from_player = model
94        .player_position()
95        .map(|pos| pos.distance_ly(&base.address));
96
97    Ok(ShowResult::Base(ShowBaseResult {
98        base: base.clone(),
99        portal_hex,
100        galaxy_name: galaxy.name.to_string(),
101        system,
102        distance_from_player,
103    }))
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    fn test_model() -> GalaxyModel {
111        let json = r#"{
112            "Version": 4720, "Platform": "Mac|Final", "ActiveContext": "Main",
113            "CommonStateData": {"SaveName": "Test", "TotalPlayTime": 100},
114            "BaseContext": {"GameMode": 1, "PlayerStateData": {"UniverseAddress": {"RealityIndex": 0, "GalacticAddress": {"VoxelX": 0, "VoxelY": 0, "VoxelZ": 0, "SolarSystemIndex": 1, "PlanetIndex": 0}}, "Units": 0, "Nanites": 0, "Specials": 0, "PersistentPlayerBases": [{"BaseVersion": 8, "GalacticAddress": "0x001000000064", "Position": [0.0,0.0,0.0], "Forward": [1.0,0.0,0.0], "LastUpdateTimestamp": 0, "Objects": [], "RID": "", "Owner": {"LID":"","UID":"1","USN":"","PTK":"ST","TS":0}, "Name": "Alpha Base", "BaseType": {"PersistentBaseTypes": "HomePlanetBase"}, "LastEditedById": "", "LastEditedByUsername": ""}]}},
115            "ExpeditionContext": {"GameMode": 6, "PlayerStateData": {"UniverseAddress": {"RealityIndex": 0, "GalacticAddress": {"VoxelX": 0, "VoxelY": 0, "VoxelZ": 0, "SolarSystemIndex": 0, "PlanetIndex": 0}}, "Units": 0, "Nanites": 0, "Specials": 0, "PersistentPlayerBases": []}},
116            "DiscoveryManagerData": {"DiscoveryData-v1": {"ReserveStore": 0, "ReserveManaged": 0, "Store": {"Record": [
117                {"DD": {"UA": "0x001000000064", "DT": "SolarSystem", "VP": []}, "DM": {}, "OWS": {"LID": "", "UID": "1", "USN": "Explorer", "PTK": "ST", "TS": 1700000000}, "FL": {"U": 1}}
118            ]}}}
119        }"#;
120        nms_save::parse_save(json.as_bytes())
121            .map(|save| GalaxyModel::from_save(&save))
122            .unwrap()
123    }
124
125    #[test]
126    fn test_show_base_by_name() {
127        let model = test_model();
128        let result = execute_show(&model, &ShowQuery::Base("Alpha Base".into())).unwrap();
129        match result {
130            ShowResult::Base(b) => {
131                assert_eq!(b.base.name, "Alpha Base");
132                assert_eq!(b.galaxy_name, "Euclid");
133                assert_eq!(b.portal_hex.len(), 12);
134            }
135            _ => panic!("Expected Base result"),
136        }
137    }
138
139    #[test]
140    fn test_show_base_case_insensitive() {
141        let model = test_model();
142        assert!(execute_show(&model, &ShowQuery::Base("alpha base".into())).is_ok());
143    }
144
145    #[test]
146    fn test_show_base_not_found() {
147        let model = test_model();
148        assert!(execute_show(&model, &ShowQuery::Base("No Base".into())).is_err());
149    }
150
151    #[test]
152    fn test_show_system_not_found() {
153        let model = test_model();
154        assert!(execute_show(&model, &ShowQuery::System("No System".into())).is_err());
155    }
156}