ed_journals/modules/ship/models/
ship_slot.rs

1use std::fmt::{Display, Formatter};
2use std::num::ParseIntError;
3use std::str::FromStr;
4
5use lazy_static::lazy_static;
6use regex::Regex;
7use serde::Serialize;
8use thiserror::Error;
9
10use crate::from_str_deserialize_impl;
11use crate::modules::ship::models::ship_slot::core_slot::CoreSlot;
12use crate::modules::ship::{HardpointSize, HardpointSizeError};
13
14pub mod core_slot;
15
16#[derive(Debug, Serialize, Clone, PartialEq)]
17pub struct ShipSlot {
18    pub slot_nr: u8,
19    pub kind: ShipSlotKind,
20}
21
22// TODO kinda want to refactor this to use untagged variants
23#[derive(Debug, Serialize, Clone, PartialEq)]
24pub enum ShipSlotKind {
25    ShipCockpit,
26    CargoHatch,
27    UtilityMount,
28    Hardpoint(HardpointSize),
29    OptionalInternal(u8),
30    Military,
31    CoreInternal(CoreSlot),
32    DataLinkScanner,
33    CodexScanner,
34    DiscoveryScanner,
35
36    // Cosmetic
37    PaintJob,
38    Decal,
39    VesselVoice,
40    Nameplate,
41    IDPlate,
42    Bobble,
43    StringLights,
44    EngineColor,
45    WeaponColor,
46    ShipKitSpoiler,
47    ShipKitWings,
48    ShipKitTail,
49    ShipKitBumper,
50}
51
52#[derive(Debug, Error)]
53pub enum ShipSlotError {
54    #[error("Failed to parse slot number in: '{0}'")]
55    FailedToParseSlotNr(String),
56
57    #[error(transparent)]
58    HardpointSizeParseError(#[from] HardpointSizeError),
59
60    #[error("Failed to parse optional internal size: {0}")]
61    OptionalInternalSizeParseError(#[source] ParseIntError),
62
63    #[error("Failed to parse ship slot: '{0}'")]
64    FailedToParse(String),
65}
66
67lazy_static! {
68    static ref UTILITY_HARDPOINT_REGEX: Regex = Regex::new(r#"^TinyHardpoint(\d+)$"#).unwrap();
69    static ref HARDPOINT_REGEX: Regex =
70        Regex::new(r#"^(Small|Medium|Large|Huge)Hardpoint(\d+)$"#).unwrap();
71    static ref OPTIONAL_INTERNAL_REGEX: Regex = Regex::new(r#"^Slot(\d+)_Size(\d+)$"#).unwrap();
72    static ref MILITARY_REGEX: Regex = Regex::new(r#"^Military(\d+)$"#).unwrap();
73    static ref DECAL_REGEX: Regex = Regex::new(r#"^Decal(\d+)$"#).unwrap();
74    static ref NAMEPLATE_REGEX: Regex = Regex::new(r#"^ShipName(\d+)$"#).unwrap();
75    static ref ID_PLATE_REGEX: Regex = Regex::new(r#"^ShipID(\d+)$"#).unwrap();
76    static ref BOBBLE_REGEX: Regex = Regex::new(r#"^Bobble(\d+)$"#).unwrap();
77}
78
79impl FromStr for ShipSlot {
80    type Err = ShipSlotError;
81
82    fn from_str(s: &str) -> Result<Self, Self::Err> {
83        let specific = match s {
84            "ShipCockpit" => Some(ShipSlotKind::ShipCockpit),
85            "CargoHatch" => Some(ShipSlotKind::CargoHatch),
86            "PaintJob" => Some(ShipSlotKind::PaintJob),
87            "VesselVoice" => Some(ShipSlotKind::VesselVoice),
88            "DataLinkScanner" => Some(ShipSlotKind::DataLinkScanner),
89            "CodexScanner" => Some(ShipSlotKind::CodexScanner),
90            "DiscoveryScanner" => Some(ShipSlotKind::DiscoveryScanner),
91            "EngineColour" => Some(ShipSlotKind::EngineColor),
92            "WeaponColour" => Some(ShipSlotKind::WeaponColor),
93            "StringLights" => Some(ShipSlotKind::StringLights),
94            "ShipKitSpoiler" => Some(ShipSlotKind::ShipKitSpoiler),
95            "ShipKitWings" => Some(ShipSlotKind::ShipKitWings),
96            "ShipKitTail" => Some(ShipSlotKind::ShipKitTail),
97            "ShipKitBumper" => Some(ShipSlotKind::ShipKitBumper),
98            _ => None,
99        };
100
101        if let Some(kind) = specific {
102            return Ok(ShipSlot { slot_nr: 0, kind });
103        }
104
105        if let Some(captures) = UTILITY_HARDPOINT_REGEX.captures(s) {
106            let slot_nr = captures
107                .get(1)
108                .expect("Should have been captured already")
109                .as_str()
110                .parse()
111                .map_err(|_| ShipSlotError::FailedToParseSlotNr(s.to_string()))?;
112
113            return Ok(ShipSlot {
114                slot_nr,
115                kind: ShipSlotKind::UtilityMount,
116            });
117        }
118
119        if let Some(captures) = HARDPOINT_REGEX.captures(s) {
120            let size = captures
121                .get(1)
122                .expect("Should have been captured already")
123                .as_str()
124                .parse()?;
125
126            let slot_nr = captures
127                .get(2)
128                .expect("Should have been captured already")
129                .as_str()
130                .parse()
131                .map_err(|_| ShipSlotError::FailedToParseSlotNr(s.to_string()))?;
132
133            return Ok(ShipSlot {
134                slot_nr,
135                kind: ShipSlotKind::Hardpoint(size),
136            });
137        }
138
139        if let Some(captures) = OPTIONAL_INTERNAL_REGEX.captures(s) {
140            let slot_nr = captures
141                .get(1)
142                .expect("Should have been captured already")
143                .as_str()
144                .parse()
145                .map_err(|_| ShipSlotError::FailedToParseSlotNr(s.to_string()))?;
146
147            let size = captures
148                .get(2)
149                .expect("Should have been captured already")
150                .as_str()
151                .parse()
152                .map_err(ShipSlotError::OptionalInternalSizeParseError)?;
153
154            return Ok(ShipSlot {
155                slot_nr,
156                kind: ShipSlotKind::OptionalInternal(size),
157            });
158        }
159
160        if let Some(captures) = MILITARY_REGEX.captures(s) {
161            let slot_nr = captures
162                .get(1)
163                .expect("Should have been captured already")
164                .as_str()
165                .parse()
166                .map_err(|_| ShipSlotError::FailedToParseSlotNr(s.to_string()))?;
167
168            return Ok(ShipSlot {
169                slot_nr,
170                kind: ShipSlotKind::Military,
171            });
172        }
173
174        if let Some(captures) = DECAL_REGEX.captures(s) {
175            let slot_nr = captures
176                .get(1)
177                .expect("Should have been captured already")
178                .as_str()
179                .parse()
180                .map_err(|_| ShipSlotError::FailedToParseSlotNr(s.to_string()))?;
181
182            return Ok(ShipSlot {
183                slot_nr,
184                kind: ShipSlotKind::Decal,
185            });
186        }
187
188        if let Some(captures) = NAMEPLATE_REGEX.captures(s) {
189            let slot_nr = captures
190                .get(1)
191                .expect("Should have been captured already")
192                .as_str()
193                .parse()
194                .map_err(|_| ShipSlotError::FailedToParseSlotNr(s.to_string()))?;
195
196            return Ok(ShipSlot {
197                slot_nr,
198                kind: ShipSlotKind::Nameplate,
199            });
200        }
201
202        if let Some(captures) = ID_PLATE_REGEX.captures(s) {
203            let slot_nr = captures
204                .get(1)
205                .expect("Should have been captured already")
206                .as_str()
207                .parse()
208                .map_err(|_| ShipSlotError::FailedToParseSlotNr(s.to_string()))?;
209
210            return Ok(ShipSlot {
211                slot_nr,
212                kind: ShipSlotKind::IDPlate,
213            });
214        }
215
216        if let Some(captures) = BOBBLE_REGEX.captures(s) {
217            let slot_nr = captures
218                .get(1)
219                .expect("Should have been captured already")
220                .as_str()
221                .parse()
222                .map_err(|_| ShipSlotError::FailedToParseSlotNr(s.to_string()))?;
223
224            return Ok(ShipSlot {
225                slot_nr,
226                kind: ShipSlotKind::Bobble,
227            });
228        }
229
230        if let Ok(core_slot) = s.parse() {
231            return Ok(ShipSlot {
232                slot_nr: 1,
233                kind: ShipSlotKind::CoreInternal(core_slot),
234            });
235        }
236
237        Err(ShipSlotError::FailedToParse(s.to_string()))
238    }
239}
240
241from_str_deserialize_impl!(ShipSlot);
242
243impl Display for ShipSlot {
244    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
245        match &self.kind {
246            ShipSlotKind::ShipCockpit => write!(f, "Ship Cockpit"),
247            ShipSlotKind::CargoHatch => write!(f, "Cargo Hatch"),
248            ShipSlotKind::UtilityMount => write!(f, "Utility Mount"),
249            ShipSlotKind::Hardpoint(size) => write!(f, "{} Hardpoint", size.size_str()),
250            ShipSlotKind::OptionalInternal(size) => write!(f, "Size {size} Optional Internal"),
251            ShipSlotKind::Military => write!(f, "Military Slot"),
252            ShipSlotKind::CoreInternal(core_slot) => write!(f, "{core_slot} Core Internal"),
253            ShipSlotKind::DataLinkScanner => write!(f, "Data Link Scanner"),
254            ShipSlotKind::CodexScanner => write!(f, "Codex Scanner"),
255            ShipSlotKind::DiscoveryScanner => write!(f, "Discovery Scanner"),
256
257            // Cosmetic
258            ShipSlotKind::PaintJob => write!(f, "Paint job"),
259            ShipSlotKind::Decal => write!(f, "Decal"),
260            ShipSlotKind::VesselVoice => write!(f, "COVAS Voice"),
261            ShipSlotKind::Nameplate => write!(f, "Nameplate"),
262            ShipSlotKind::IDPlate => write!(f, "ID-Plate"),
263            ShipSlotKind::Bobble => write!(f, "Bobble"),
264            ShipSlotKind::StringLights => write!(f, "String Lights"),
265            ShipSlotKind::EngineColor => write!(f, "Engine Colour"),
266            ShipSlotKind::WeaponColor => write!(f, "Weapon Colour"),
267            ShipSlotKind::ShipKitSpoiler => write!(f, "Ship Kit Spoiler"),
268            ShipSlotKind::ShipKitWings => write!(f, "Ship Kit Wing"),
269            ShipSlotKind::ShipKitTail => write!(f, "Ship Kit Tail"),
270            ShipSlotKind::ShipKitBumper => write!(f, "Ship Kit Bumper"),
271        }
272    }
273}