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,
26 CargoHatch,
27 UtilityMount,
28 Hardpoint(HardpointSize),
29 OptionalInternal(u8),
30 Military,
31 CoreInternal(CoreSlot),
32 DataLinkScanner,
33 CodexScanner,
34 DiscoveryScanner,
35
36 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 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}