ed_journals/modules/ship/models/
ship_slot.rs1use 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#[derive(Debug, Serialize, Clone, PartialEq)]
24pub enum ShipSlotKind {
25 ShipCockpit,
27
28 CargoHatch,
30
31 UtilityMount,
33
34 Hardpoint(HardpointSize),
36
37 MiningHardPoint(HardpointSize),
39
40 OptionalInternal(u8),
42
43 LimpetController,
45
46 FighterBay,
48
49 Military,
51
52 CoreInternal(CoreSlot),
54
55 DataLinkScanner,
57
58 CodexScanner,
60
61 DiscoveryScanner,
63
64 PaintJob,
66 Decal,
67 VesselVoice,
68 Nameplate,
69 IDPlate,
70 Bobble,
71 StringLights,
72 EngineColor,
73 WeaponColor,
74 ShipKitSpoiler,
75 ShipKitWings,
76 ShipKitTail,
77 ShipKitBumper,
78}
79
80#[derive(Debug, Error)]
81pub enum ShipSlotError {
82 #[error("Failed to parse slot number in: '{0}'")]
83 FailedToParseSlotNr(String),
84
85 #[error(transparent)]
86 HardpointSizeParseError(#[from] HardpointSizeError),
87
88 #[error("Failed to parse optional internal size: {0}")]
89 OptionalInternalSizeParseError(#[source] ParseIntError),
90
91 #[error("Failed to parse ship slot: '{0}'")]
92 FailedToParse(String),
93}
94
95lazy_static! {
96 static ref UTILITY_HARDPOINT_REGEX: Regex = Regex::new(r#"^TinyHardpoint(\d+)$"#).unwrap();
97 static ref HARDPOINT_REGEX: Regex =
98 Regex::new(r#"^(Small|Medium|Large|Huge)Hardpoint(\d+)$"#).unwrap();
99 static ref MINING_HARDPOINT_REGEX: Regex =
100 Regex::new(r#"^(Small|Medium|Large|Huge)MiningHardpoint(\d+)$"#).unwrap();
101 static ref OPTIONAL_INTERNAL_REGEX: Regex = Regex::new(r#"^Slot(\d+)_Size(\d+)$"#).unwrap();
102 static ref MILITARY_REGEX: Regex = Regex::new(r#"^Military(\d+)$"#).unwrap();
103 static ref LIMPET_CONTROLLER_REGEX: Regex = Regex::new(r#"^LimpetController(\d+)$"#).unwrap();
104 static ref FIGHTER_BAY_REGEX: Regex = Regex::new(r#"^FighterBay(\d+)$"#).unwrap();
105 static ref DECAL_REGEX: Regex = Regex::new(r#"^Decal(\d+)$"#).unwrap();
106 static ref NAMEPLATE_REGEX: Regex = Regex::new(r#"^ShipName(\d+)$"#).unwrap();
107 static ref ID_PLATE_REGEX: Regex = Regex::new(r#"^ShipID(\d+)$"#).unwrap();
108 static ref BOBBLE_REGEX: Regex = Regex::new(r#"^Bobble(\d+)$"#).unwrap();
109}
110
111impl FromStr for ShipSlot {
112 type Err = ShipSlotError;
113
114 fn from_str(s: &str) -> Result<Self, Self::Err> {
115 let specific = match s {
116 "ShipCockpit" => Some(ShipSlotKind::ShipCockpit),
117 "CargoHatch" => Some(ShipSlotKind::CargoHatch),
118 "PaintJob" => Some(ShipSlotKind::PaintJob),
119 "VesselVoice" => Some(ShipSlotKind::VesselVoice),
120 "DataLinkScanner" => Some(ShipSlotKind::DataLinkScanner),
121 "CodexScanner" => Some(ShipSlotKind::CodexScanner),
122 "DiscoveryScanner" => Some(ShipSlotKind::DiscoveryScanner),
123 "EngineColour" => Some(ShipSlotKind::EngineColor),
124 "WeaponColour" => Some(ShipSlotKind::WeaponColor),
125 "StringLights" => Some(ShipSlotKind::StringLights),
126 "ShipKitSpoiler" => Some(ShipSlotKind::ShipKitSpoiler),
127 "ShipKitWings" => Some(ShipSlotKind::ShipKitWings),
128 "ShipKitTail" => Some(ShipSlotKind::ShipKitTail),
129 "ShipKitBumper" => Some(ShipSlotKind::ShipKitBumper),
130 _ => None,
131 };
132
133 if let Some(kind) = specific {
134 return Ok(ShipSlot { slot_nr: 0, kind });
135 }
136
137 if let Some(captures) = UTILITY_HARDPOINT_REGEX.captures(s) {
138 let slot_nr = captures
139 .get(1)
140 .expect("Should have been captured already")
141 .as_str()
142 .parse()
143 .map_err(|_| ShipSlotError::FailedToParseSlotNr(s.to_string()))?;
144
145 return Ok(ShipSlot {
146 slot_nr,
147 kind: ShipSlotKind::UtilityMount,
148 });
149 }
150
151 if let Some(captures) = HARDPOINT_REGEX.captures(s) {
152 let size = captures
153 .get(1)
154 .expect("Should have been captured already")
155 .as_str()
156 .parse()?;
157
158 let slot_nr = captures
159 .get(2)
160 .expect("Should have been captured already")
161 .as_str()
162 .parse()
163 .map_err(|_| ShipSlotError::FailedToParseSlotNr(s.to_string()))?;
164
165 return Ok(ShipSlot {
166 slot_nr,
167 kind: ShipSlotKind::Hardpoint(size),
168 });
169 }
170
171 if let Some(captures) = MINING_HARDPOINT_REGEX.captures(s) {
172 let size = captures
173 .get(1)
174 .expect("Should have been captured already")
175 .as_str()
176 .parse()?;
177
178 let slot_nr = captures
179 .get(2)
180 .expect("Should have been captured already")
181 .as_str()
182 .parse()
183 .map_err(|_| ShipSlotError::FailedToParseSlotNr(s.to_string()))?;
184
185 return Ok(ShipSlot {
186 slot_nr,
187 kind: ShipSlotKind::MiningHardPoint(size),
188 });
189 }
190
191 if let Some(captures) = OPTIONAL_INTERNAL_REGEX.captures(s) {
192 let slot_nr = captures
193 .get(1)
194 .expect("Should have been captured already")
195 .as_str()
196 .parse()
197 .map_err(|_| ShipSlotError::FailedToParseSlotNr(s.to_string()))?;
198
199 let size = captures
200 .get(2)
201 .expect("Should have been captured already")
202 .as_str()
203 .parse()
204 .map_err(ShipSlotError::OptionalInternalSizeParseError)?;
205
206 return Ok(ShipSlot {
207 slot_nr,
208 kind: ShipSlotKind::OptionalInternal(size),
209 });
210 }
211
212 if let Some(captures) = MILITARY_REGEX.captures(s) {
213 let slot_nr = captures
214 .get(1)
215 .expect("Should have been captured already")
216 .as_str()
217 .parse()
218 .map_err(|_| ShipSlotError::FailedToParseSlotNr(s.to_string()))?;
219
220 return Ok(ShipSlot {
221 slot_nr,
222 kind: ShipSlotKind::Military,
223 });
224 }
225
226 if let Some(captures) = LIMPET_CONTROLLER_REGEX.captures(s) {
227 let slot_nr = captures
228 .get(1)
229 .expect("Should have been captured already")
230 .as_str()
231 .parse()
232 .map_err(|_| ShipSlotError::FailedToParseSlotNr(s.to_string()))?;
233
234 return Ok(ShipSlot {
235 slot_nr,
236 kind: ShipSlotKind::LimpetController,
237 });
238 }
239
240 if let Some(captures) = FIGHTER_BAY_REGEX.captures(s) {
241 let slot_nr = captures
242 .get(1)
243 .expect("Should have been captured already")
244 .as_str()
245 .parse()
246 .map_err(|_| ShipSlotError::FailedToParseSlotNr(s.to_string()))?;
247
248 return Ok(ShipSlot {
249 slot_nr,
250 kind: ShipSlotKind::FighterBay,
251 });
252 }
253
254 if let Some(captures) = DECAL_REGEX.captures(s) {
255 let slot_nr = captures
256 .get(1)
257 .expect("Should have been captured already")
258 .as_str()
259 .parse()
260 .map_err(|_| ShipSlotError::FailedToParseSlotNr(s.to_string()))?;
261
262 return Ok(ShipSlot {
263 slot_nr,
264 kind: ShipSlotKind::Decal,
265 });
266 }
267
268 if let Some(captures) = NAMEPLATE_REGEX.captures(s) {
269 let slot_nr = captures
270 .get(1)
271 .expect("Should have been captured already")
272 .as_str()
273 .parse()
274 .map_err(|_| ShipSlotError::FailedToParseSlotNr(s.to_string()))?;
275
276 return Ok(ShipSlot {
277 slot_nr,
278 kind: ShipSlotKind::Nameplate,
279 });
280 }
281
282 if let Some(captures) = ID_PLATE_REGEX.captures(s) {
283 let slot_nr = captures
284 .get(1)
285 .expect("Should have been captured already")
286 .as_str()
287 .parse()
288 .map_err(|_| ShipSlotError::FailedToParseSlotNr(s.to_string()))?;
289
290 return Ok(ShipSlot {
291 slot_nr,
292 kind: ShipSlotKind::IDPlate,
293 });
294 }
295
296 if let Some(captures) = BOBBLE_REGEX.captures(s) {
297 let slot_nr = captures
298 .get(1)
299 .expect("Should have been captured already")
300 .as_str()
301 .parse()
302 .map_err(|_| ShipSlotError::FailedToParseSlotNr(s.to_string()))?;
303
304 return Ok(ShipSlot {
305 slot_nr,
306 kind: ShipSlotKind::Bobble,
307 });
308 }
309
310 if let Ok(core_slot) = s.parse() {
311 return Ok(ShipSlot {
312 slot_nr: 1,
313 kind: ShipSlotKind::CoreInternal(core_slot),
314 });
315 }
316
317 Err(ShipSlotError::FailedToParse(s.to_string()))
318 }
319}
320
321from_str_deserialize_impl!(ShipSlot);
322
323impl Display for ShipSlot {
324 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
325 match &self.kind {
326 ShipSlotKind::ShipCockpit => write!(f, "Ship Cockpit"),
327 ShipSlotKind::CargoHatch => write!(f, "Cargo Hatch"),
328 ShipSlotKind::UtilityMount => write!(f, "Utility Mount"),
329 ShipSlotKind::Hardpoint(size) => write!(f, "{} Hardpoint", size.size_str()),
330 ShipSlotKind::MiningHardPoint(size) => {
331 write!(f, "{} Mining Hardpoint", size.size_str())
332 }
333 ShipSlotKind::OptionalInternal(size) => write!(f, "Size {size} Optional Internal"),
334 ShipSlotKind::Military => write!(f, "Military Slot"),
335 ShipSlotKind::LimpetController => write!(f, "Limpet Controller Slot"),
336 ShipSlotKind::FighterBay => write!(f, "Fighter Bay Slot"),
337 ShipSlotKind::CoreInternal(core_slot) => write!(f, "{core_slot} Core Internal"),
338 ShipSlotKind::DataLinkScanner => write!(f, "Data Link Scanner"),
339 ShipSlotKind::CodexScanner => write!(f, "Codex Scanner"),
340 ShipSlotKind::DiscoveryScanner => write!(f, "Discovery Scanner"),
341
342 ShipSlotKind::PaintJob => write!(f, "Paint job"),
344 ShipSlotKind::Decal => write!(f, "Decal"),
345 ShipSlotKind::VesselVoice => write!(f, "COVAS Voice"),
346 ShipSlotKind::Nameplate => write!(f, "Nameplate"),
347 ShipSlotKind::IDPlate => write!(f, "ID-Plate"),
348 ShipSlotKind::Bobble => write!(f, "Bobble"),
349 ShipSlotKind::StringLights => write!(f, "String Lights"),
350 ShipSlotKind::EngineColor => write!(f, "Engine Colour"),
351 ShipSlotKind::WeaponColor => write!(f, "Weapon Colour"),
352 ShipSlotKind::ShipKitSpoiler => write!(f, "Ship Kit Spoiler"),
353 ShipSlotKind::ShipKitWings => write!(f, "Ship Kit Wing"),
354 ShipSlotKind::ShipKitTail => write!(f, "Ship Kit Tail"),
355 ShipSlotKind::ShipKitBumper => write!(f, "Ship Kit Bumper"),
356 }
357 }
358}