Skip to main content

ed_journals/modules/ship/models/ship_module/
ship_hardpoint_module.rs

1use std::fmt::{Display, Formatter};
2use std::num::ParseIntError;
3use std::str::FromStr;
4
5use lazy_static::lazy_static;
6use regex::{Captures, Regex};
7use serde::Serialize;
8use thiserror::Error;
9
10use crate::from_str_deserialize_impl;
11use crate::modules::ship::{
12    HardpointModule, HardpointMounting, HardpointSize, HardpointSizeError, HardpointType,
13    ModuleClass, ModuleClassError,
14};
15
16pub mod hardpoint_module;
17pub mod hardpoint_mounting;
18pub mod hardpoint_size;
19pub mod hardpoint_type;
20
21/// Contains all the data related to hardpoints. This model is used for both full-sized hardpoint
22/// modules and utility modules. Utility modules use [HardpointSize::Tiny] and full-sized modules
23/// use the other sizes.
24#[derive(Debug, Serialize, Clone, PartialEq)]
25pub struct ShipHardpointModule {
26    /// The kind internal module, which can both be full-sized hardpoints and utility modules.
27    pub module: HardpointModule,
28
29    /// The mounting type the module used which dictate how aiming works. Utility modules always
30    /// use [HardpointMounting::Turreted].
31    pub mounting: HardpointMounting,
32
33    /// The size of the module.
34    pub size: HardpointSize,
35
36    /// The class of the module.
37    pub class: ModuleClass,
38}
39
40#[derive(Debug, Error)]
41pub enum ShipHardpointModuleError {
42    #[error("Failed to parse hardpoint module: {0}")]
43    FailedToParseHardpointModule(#[source] serde_json::Error),
44
45    #[error("Failed to parse hardpoint mounting: {0}")]
46    FailedToParseHardpointMounting(#[source] serde_json::Error),
47
48    #[error("Failed to parse hardpoint size: {0}")]
49    FailedToParseHardpointSize(#[from] HardpointSizeError),
50
51    #[error("Failed to parse class number: {0}")]
52    FailedToParseClassNumber(#[source] ParseIntError),
53
54    #[error("Mounting type cannot be missing when the module size is not 'tiny'")]
55    MissingMounting,
56
57    #[error(transparent)]
58    ModuleClassError(#[from] ModuleClassError),
59
60    #[error("Failed to parse ship hardpoint module: '{0}'")]
61    FailedToParse(String),
62}
63
64lazy_static! {
65    static ref GRADED_HARDPOINT_MODULE_REGEX: Regex = Regex::new(r#"^\$?[hH]pt_(\w+?)_[sS]ize(\d+)_[cC]lass(\d+)(_[nN]ame;)?$"#).unwrap();
66    static ref HARDPOINT_MODULE_REGEX: Regex = Regex::new(r#"^\$?[hH]pt_(\w+?)(_([gG]imbal|[fF]ixed|[tT]urret))?_([tT]iny|[sS]mall|[mM]edium|[lL]arge|[hH]uge)(_[a-zA-Z0-9_]+?)?(_[nN]ame;)?$"#).unwrap();
67}
68
69impl FromStr for ShipHardpointModule {
70    type Err = ShipHardpointModuleError;
71
72    fn from_str(s: &str) -> Result<Self, Self::Err> {
73        if let Some(captures) = GRADED_HARDPOINT_MODULE_REGEX.captures(s) {
74            return Self::from_graded_hardpoint_captures(captures);
75        }
76
77        if let Some(captures) = HARDPOINT_MODULE_REGEX.captures(s) {
78            return Self::from_hardpoint_captures(captures);
79        };
80
81        Err(ShipHardpointModuleError::FailedToParse(s.to_string()))
82    }
83}
84
85impl ShipHardpointModule {
86    /// Returns whether the module is a full-sized hardpoint or a utility module.
87    pub fn hardpoint_type(&self) -> HardpointType {
88        self.module.hardpoint_type()
89    }
90
91    /// Whether the module is a full-sized hardpoint module.
92    pub fn is_full_sized_module(&self) -> bool {
93        self.module.is_full_sized()
94    }
95
96    /// Whether the module is a utility module.
97    pub fn is_utility_module(&self) -> bool {
98        self.module.is_utility()
99    }
100
101    /// Whether the module is a module that is unlocked through powerplay.
102    pub fn is_powerplay_module(&self) -> bool {
103        self.module.is_powerplay_module()
104    }
105
106    /// Whether the module is a module that is unlocked using guardian parts at a guardian
107    /// technology broker.
108    pub fn is_guardian_module(&self) -> bool {
109        self.module.is_guardian_module()
110    }
111
112    fn from_graded_hardpoint_captures(
113        captures: Captures,
114    ) -> Result<ShipHardpointModule, ShipHardpointModuleError> {
115        let module = captures
116            .get(1)
117            .expect("Should have been captured already")
118            .as_str()
119            .parse()
120            .map_err(ShipHardpointModuleError::FailedToParseHardpointModule)?;
121
122        let class = captures
123            .get(3)
124            .expect("Should have been captured already")
125            .as_str()
126            .parse::<u8>()
127            .map_err(ShipHardpointModuleError::FailedToParseClassNumber)?
128            .try_into()?;
129
130        Ok(ShipHardpointModule {
131            module,
132            mounting: HardpointMounting::Turreted,
133            size: HardpointSize::Tiny,
134            class,
135        })
136    }
137
138    fn from_hardpoint_captures(
139        captures: Captures,
140    ) -> Result<ShipHardpointModule, ShipHardpointModuleError> {
141        let module = captures
142            .get(1)
143            .expect("Should have been captured already")
144            .as_str();
145
146        let module_suffix = captures
147            .get(5)
148            .map(|capture| capture.as_str())
149            .unwrap_or_default();
150
151        let mut module = format!("{module}{module_suffix}")
152            .parse()
153            .map_err(ShipHardpointModuleError::FailedToParseHardpointModule)?;
154
155        let size = captures
156            .get(4)
157            .expect("Should have been captured already")
158            .as_str()
159            .parse()?;
160
161        // Sometimes the mounting is missing when the size is 'tiny'
162        let mounting = match captures.get(3) {
163            Some(capture) => capture
164                .as_str()
165                .parse()
166                .map_err(ShipHardpointModuleError::FailedToParseHardpointMounting),
167            None => {
168                if matches!(size, HardpointSize::Tiny) {
169                    Ok(HardpointMounting::Turreted)
170                } else {
171                    Err(ShipHardpointModuleError::MissingMounting)
172                }
173            }
174        }?;
175
176        // I once again ask: Why Frontier?!
177        if module == HardpointModule::AXMultiCannon && mounting == HardpointMounting::Gimballed {
178            module = HardpointModule::EnhancedAXMultiCannon;
179        }
180
181        let class = Self::get_module_class(&module, &mounting, &size);
182
183        Ok(ShipHardpointModule {
184            module,
185            mounting,
186            class,
187            size,
188        })
189    }
190
191    #[rustfmt::skip]
192    fn get_module_class(
193        module: &HardpointModule,
194        mounting: &HardpointMounting,
195        size: &HardpointSize,
196    ) -> ModuleClass {
197        match (&module, &mounting, &size) {
198            // Pulse lasers
199            (HardpointModule::PulseLaser, HardpointMounting::Fixed, HardpointSize::Small) => ModuleClass::F,
200            (HardpointModule::PulseLaser, HardpointMounting::Gimballed, HardpointSize::Small) => ModuleClass::G,
201            (HardpointModule::PulseLaser, HardpointMounting::Turreted, HardpointSize::Small) => ModuleClass::G,
202            (HardpointModule::PulseLaser, HardpointMounting::Fixed, HardpointSize::Medium) => ModuleClass::E,
203            (HardpointModule::PulseLaser, HardpointMounting::Gimballed, HardpointSize::Medium) => ModuleClass::F,
204            (HardpointModule::PulseLaser, HardpointMounting::Turreted, HardpointSize::Medium) => ModuleClass::F,
205            (HardpointModule::PulseDisruptorLaser, _, _) => ModuleClass::E,
206            (HardpointModule::PulseLaser, HardpointMounting::Fixed, HardpointSize::Large) => ModuleClass::D,
207            (HardpointModule::PulseLaser, HardpointMounting::Gimballed, HardpointSize::Large) => ModuleClass::E,
208            (HardpointModule::PulseLaser, HardpointMounting::Turreted, HardpointSize::Large) => ModuleClass::F,
209            (HardpointModule::PulseLaser, _, HardpointSize::Huge) => ModuleClass::A,
210
211            // Burst lasers
212            (HardpointModule::BurstLaser, HardpointMounting::Fixed, HardpointSize::Small) => ModuleClass::F,
213            (HardpointModule::BurstLaser, HardpointMounting::Gimballed, HardpointSize::Small) => ModuleClass::G,
214            (HardpointModule::BurstLaser, HardpointMounting::Turreted, HardpointSize::Small) => ModuleClass::G,
215            (HardpointModule::CytoscramblerBurstLaser, _, _) => ModuleClass::F,
216            (HardpointModule::BurstLaser, HardpointMounting::Fixed, HardpointSize::Medium) => ModuleClass::E,
217            (HardpointModule::BurstLaser, HardpointMounting::Gimballed, HardpointSize::Medium) => ModuleClass::F,
218            (HardpointModule::BurstLaser, HardpointMounting::Turreted, HardpointSize::Medium) => ModuleClass::F,
219            (HardpointModule::BurstLaser, HardpointMounting::Fixed, HardpointSize::Large) => ModuleClass::D,
220            (HardpointModule::BurstLaser, HardpointMounting::Gimballed, HardpointSize::Large) => ModuleClass::E,
221            (HardpointModule::BurstLaser, HardpointMounting::Turreted, HardpointSize::Large) => ModuleClass::E,
222            (HardpointModule::BurstLaser, _, HardpointSize::Huge) => ModuleClass::E,
223
224            // Cannons
225            (HardpointModule::Cannon, HardpointMounting::Fixed, HardpointSize::Small) => ModuleClass::D,
226            (HardpointModule::Cannon, HardpointMounting::Gimballed, HardpointSize::Small) => ModuleClass::E,
227            (HardpointModule::Cannon, HardpointMounting::Turreted, HardpointSize::Small) => ModuleClass::F,
228            (HardpointModule::Cannon, HardpointMounting::Fixed, HardpointSize::Medium) => ModuleClass::D,
229            (HardpointModule::Cannon, HardpointMounting::Gimballed, HardpointSize::Medium) => ModuleClass::D,
230            (HardpointModule::Cannon, HardpointMounting::Turreted, HardpointSize::Medium) => ModuleClass::E,
231            (HardpointModule::Cannon, HardpointMounting::Fixed, HardpointSize::Large) => ModuleClass::C,
232            (HardpointModule::Cannon, HardpointMounting::Gimballed, HardpointSize::Large) => ModuleClass::C,
233            (HardpointModule::Cannon, HardpointMounting::Turreted, HardpointSize::Large) => ModuleClass::D,
234            (HardpointModule::Cannon, _, HardpointSize::Huge) => ModuleClass::B,
235
236            // Experimental
237            (HardpointModule::RemoteReleaseFlakLauncher, _, HardpointSize::Medium) => ModuleClass::B,
238            (HardpointModule::RemoteReleaseFlechetteLauncher, _, HardpointSize::Medium) => ModuleClass::B,
239            (HardpointModule::EnzymeMissileRack, _, HardpointSize::Medium) => ModuleClass::B,
240
241            // AX missile racks
242            (HardpointModule::AXMissileRack, HardpointMounting::Fixed, HardpointSize::Medium) => ModuleClass::E,
243            (HardpointModule::AXMissileRack, HardpointMounting::Turreted, HardpointSize::Medium) => ModuleClass::F,
244            (HardpointModule::EnhancedAXMissileRack, HardpointMounting::Fixed, HardpointSize::Medium) => ModuleClass::D,
245            (HardpointModule::EnhancedAXMissileRack, HardpointMounting::Turreted, HardpointSize::Medium) => ModuleClass::E,
246            (HardpointModule::AXMissileRack, HardpointMounting::Fixed, HardpointSize::Large) => ModuleClass::C,
247            (HardpointModule::AXMissileRack, HardpointMounting::Turreted, HardpointSize::Large) => ModuleClass::E,
248            (HardpointModule::EnhancedAXMissileRack, HardpointMounting::Fixed, HardpointSize::Large) => ModuleClass::B,
249            (HardpointModule::EnhancedAXMissileRack, HardpointMounting::Turreted, HardpointSize::Large) => ModuleClass::D,
250
251            // AX multi-cannons
252            (HardpointModule::AXMultiCannon, HardpointMounting::Fixed, HardpointSize::Medium) => ModuleClass::E,
253            (HardpointModule::AXMultiCannon, HardpointMounting::Turreted, HardpointSize::Medium) => ModuleClass::F,
254            (HardpointModule::EnhancedAXMultiCannon, HardpointMounting::Fixed, HardpointSize::Medium) => ModuleClass::D,
255            (HardpointModule::EnhancedAXMultiCannon, HardpointMounting::Gimballed, HardpointSize::Medium) => ModuleClass::E,
256            (HardpointModule::EnhancedAXMultiCannon, HardpointMounting::Turreted, HardpointSize::Medium) => ModuleClass::E,
257            (HardpointModule::AXMultiCannon, HardpointMounting::Fixed, HardpointSize::Large) => ModuleClass::C,
258            (HardpointModule::AXMultiCannon, HardpointMounting::Turreted, HardpointSize::Large) => ModuleClass::E,
259            (HardpointModule::EnhancedAXMultiCannon, HardpointMounting::Fixed, HardpointSize::Large) => ModuleClass::B,
260            (HardpointModule::EnhancedAXMultiCannon, HardpointMounting::Gimballed, HardpointSize::Large) => ModuleClass::C,
261            (HardpointModule::EnhancedAXMultiCannon, HardpointMounting::Turreted, HardpointSize::Large) => ModuleClass::D,
262
263            // Guardian gauss cannons
264            (HardpointModule::GuardianGaussCannon, _, HardpointSize::Small) => ModuleClass::D,
265            (HardpointModule::GuardianGaussCannon, _, HardpointSize::Medium) => ModuleClass::B,
266
267            // Guardian plasma chargers
268            (HardpointModule::GuardianPlasmaCharger, HardpointMounting::Fixed, HardpointSize::Small) => ModuleClass::D,
269            (HardpointModule::GuardianPlasmaCharger, HardpointMounting::Turreted, HardpointSize::Small) => ModuleClass::F,
270            (HardpointModule::GuardianPlasmaCharger, HardpointMounting::Fixed, HardpointSize::Medium) => ModuleClass::B,
271            (HardpointModule::GuardianPlasmaCharger, HardpointMounting::Turreted, HardpointSize::Medium) => ModuleClass::E,
272            (HardpointModule::GuardianPlasmaCharger, HardpointMounting::Fixed, HardpointSize::Large) => ModuleClass::C,
273            (HardpointModule::GuardianPlasmaCharger, HardpointMounting::Turreted, HardpointSize::Large) => ModuleClass::D,
274
275            // Guardian shard cannons
276            (HardpointModule::GuardianShardCannon, HardpointMounting::Fixed, HardpointSize::Small) => ModuleClass::D,
277            (HardpointModule::GuardianShardCannon, HardpointMounting::Turreted, HardpointSize::Small) => ModuleClass::F,
278            (HardpointModule::GuardianShardCannon, HardpointMounting::Fixed, HardpointSize::Medium) => ModuleClass::A,
279            (HardpointModule::GuardianShardCannon, HardpointMounting::Turreted, HardpointSize::Medium) => ModuleClass::A,
280            (HardpointModule::GuardianShardCannon, HardpointMounting::Fixed, HardpointSize::Large) => ModuleClass::C,
281            (HardpointModule::GuardianShardCannon, HardpointMounting::Turreted, HardpointSize::Large) => ModuleClass::D,
282
283            // Nanite torpedoes
284            (HardpointModule::NaniteTorpedoPylon, _, HardpointSize::Medium) => ModuleClass::I,
285            (HardpointModule::NaniteTorpedoPylon, _, HardpointSize::Large) => ModuleClass::I,
286
287            // Shock cannons
288            (HardpointModule::ShockCannon, HardpointMounting::Fixed, HardpointSize::Small) => ModuleClass::D,
289            (HardpointModule::ShockCannon, HardpointMounting::Gimballed, HardpointSize::Small) => ModuleClass::E,
290            (HardpointModule::ShockCannon, HardpointMounting::Turreted, HardpointSize::Small) => ModuleClass::F,
291            (HardpointModule::ShockCannon, HardpointMounting::Fixed, HardpointSize::Medium) => ModuleClass::D,
292            (HardpointModule::ShockCannon, HardpointMounting::Gimballed, HardpointSize::Medium) => ModuleClass::D,
293            (HardpointModule::ShockCannon, HardpointMounting::Turreted, HardpointSize::Medium) => ModuleClass::E,
294            (HardpointModule::ShockCannon, HardpointMounting::Fixed, HardpointSize::Large) => ModuleClass::C,
295            (HardpointModule::ShockCannon, HardpointMounting::Gimballed, HardpointSize::Large) => ModuleClass::C,
296            (HardpointModule::ShockCannon, HardpointMounting::Turreted, HardpointSize::Large) => ModuleClass::D,
297
298            //  Fragment cannons
299            (HardpointModule::FragmentCannon, _, HardpointSize::Small) => ModuleClass::E,
300            (HardpointModule::FragmentCannon, HardpointMounting::Fixed, HardpointSize::Medium) => ModuleClass::A,
301            (HardpointModule::FragmentCannon, HardpointMounting::Gimballed, HardpointSize::Medium) => ModuleClass::D,
302            (HardpointModule::FragmentCannon, HardpointMounting::Turreted, HardpointSize::Medium) => ModuleClass::D,
303            (HardpointModule::FragmentCannon, _, HardpointSize::Large) => ModuleClass::C,
304            (HardpointModule::PacifierFragCannon, _, _) => ModuleClass::C,
305
306            // Mines
307            (HardpointModule::MineLauncher, _, HardpointSize::Small) => ModuleClass::I,
308            (HardpointModule::ShockMineLauncher, _, HardpointSize::Small) => ModuleClass::I,
309            (HardpointModule::MineLauncher, _, HardpointSize::Medium) => ModuleClass::I,
310
311            // Mining lasers
312            (HardpointModule::MiningLaser, HardpointMounting::Fixed, HardpointSize::Small) => ModuleClass::D,
313            (HardpointModule::MiningLaser, HardpointMounting::Turreted, HardpointSize::Small) => ModuleClass::D,
314            (HardpointModule::AbrasionBlaster, HardpointMounting::Fixed, HardpointSize::Small) => ModuleClass::D,
315            (HardpointModule::AbrasionBlaster, HardpointMounting::Turreted, HardpointSize::Small) => ModuleClass::D,
316            (HardpointModule::MiningLanceBeamLaser, _, _) => ModuleClass::D,
317            (HardpointModule::MiningLaser, HardpointMounting::Fixed, HardpointSize::Medium) => ModuleClass::D,
318            (HardpointModule::MiningLaser, HardpointMounting::Turreted, HardpointSize::Medium) => ModuleClass::D,
319
320            // Mining launchers
321            (HardpointModule::DisplacementMissile, HardpointMounting::Fixed, HardpointSize::Small) => ModuleClass::B,
322            (HardpointModule::DisplacementMissile, HardpointMounting::Turreted, HardpointSize::Small) => ModuleClass::B,
323            (HardpointModule::DisplacementMissile, HardpointMounting::Fixed, HardpointSize::Medium) => ModuleClass::B,
324            (HardpointModule::DisplacementMissile, HardpointMounting::Turreted, HardpointSize::Medium) => ModuleClass::B,
325            (HardpointModule::SeismicCharge, HardpointMounting::Fixed, HardpointSize::Medium) => ModuleClass::B,
326            (HardpointModule::SeismicCharge, HardpointMounting::Turreted, HardpointSize::Medium) => ModuleClass::B,
327            (HardpointModule::SubSurfaceExtractionMissile, _, _) => ModuleClass::B,
328
329            // Missiles
330            (HardpointModule::MissileRack, _, HardpointSize::Small) => ModuleClass::B,
331            (HardpointModule::SeekerMissileRack, _, HardpointSize::Small) => ModuleClass::B,
332            (HardpointModule::AdvancedMissileRack, _, HardpointSize::Small) => ModuleClass::B,
333            (HardpointModule::MissileRack, _, HardpointSize::Medium) => ModuleClass::B,
334            (HardpointModule::SeekerMissileRack, _, HardpointSize::Medium) => ModuleClass::B,
335            (HardpointModule::AdvancedMissileRack, _, HardpointSize::Medium) => ModuleClass::B,
336            (HardpointModule::PackHoundMissileRack, _, _) => ModuleClass::B,
337            (HardpointModule::RocketPropelledFSDDisruptor, _, _) => ModuleClass::B,
338            (HardpointModule::MissileRack, _, HardpointSize::Large) => ModuleClass::A,
339            (HardpointModule::SeekerMissileRack, _, HardpointSize::Large) => ModuleClass::A,
340
341            // Multi-cannons
342            (HardpointModule::MultiCannon, HardpointMounting::Fixed, HardpointSize::Small) => ModuleClass::F,
343            (HardpointModule::MultiCannon, HardpointMounting::Gimballed, HardpointSize::Small) => ModuleClass::G,
344            (HardpointModule::MultiCannon, HardpointMounting::Turreted, HardpointSize::Small) => ModuleClass::G,
345            (HardpointModule::AdvancedMultiCannon, _, HardpointSize::Small) => ModuleClass::F,
346            (HardpointModule::EnforcerCannon, _, _) => ModuleClass::F,
347            (HardpointModule::MultiCannon, HardpointMounting::Fixed, HardpointSize::Medium) => ModuleClass::E,
348            (HardpointModule::MultiCannon, HardpointMounting::Gimballed, HardpointSize::Medium) => ModuleClass::F,
349            (HardpointModule::MultiCannon, HardpointMounting::Turreted, HardpointSize::Medium) => ModuleClass::F,
350            (HardpointModule::AdvancedMultiCannon, _, HardpointSize::Medium) => ModuleClass::E,
351            (HardpointModule::MultiCannon, HardpointMounting::Fixed, HardpointSize::Large) => ModuleClass::C,
352            (HardpointModule::MultiCannon, HardpointMounting::Gimballed, HardpointSize::Large) => ModuleClass::C,
353            (HardpointModule::MultiCannon, HardpointMounting::Turreted, HardpointSize::Large) => ModuleClass::E,
354            (HardpointModule::MultiCannon, _, _) => ModuleClass::A,
355
356            // Plasma accelerators
357            (HardpointModule::PlasmaAccelerator, _, HardpointSize::Medium) => ModuleClass::C,
358            (HardpointModule::PlasmaAccelerator, _, HardpointSize::Large) => ModuleClass::B,
359            (HardpointModule::AdvancedPlasmaAccelerator, _, _) => ModuleClass::B,
360            (HardpointModule::PlasmaAccelerator, _, HardpointSize::Huge) => ModuleClass::A,
361            (HardpointModule::MkIIPlasmaShockAccelerator, _, HardpointSize::Large) => ModuleClass::B,
362
363            // Beam lasers
364            (HardpointModule::BeamLaser, HardpointMounting::Fixed | HardpointMounting::Gimballed, HardpointSize::Small) => ModuleClass::E,
365            (HardpointModule::BeamLaser, HardpointMounting::Turreted, HardpointSize::Small) => ModuleClass::F,
366            (HardpointModule::RetributorBeamLaser, _, _) => ModuleClass::E,
367            (HardpointModule::BeamLaser, HardpointMounting::Fixed, HardpointSize::Medium) => ModuleClass::D,
368            (HardpointModule::BeamLaser, HardpointMounting::Gimballed, HardpointSize::Medium) => ModuleClass::D,
369            (HardpointModule::BeamLaser, HardpointMounting::Turreted, HardpointSize::Medium) => ModuleClass::E,
370            (HardpointModule::BeamLaser, HardpointMounting::Fixed, HardpointSize::Large) => ModuleClass::C,
371            (HardpointModule::BeamLaser, HardpointMounting::Gimballed, HardpointSize::Large) => ModuleClass::C,
372            (HardpointModule::BeamLaser, HardpointMounting::Turreted, HardpointSize::Large) => ModuleClass::D,
373            (HardpointModule::BeamLaser, _, HardpointSize::Huge) => ModuleClass::A,
374
375            // Rail guns
376            (HardpointModule::RailGun, _, HardpointSize::Small) => ModuleClass::D,
377            (HardpointModule::RailGun, _, HardpointSize::Medium) => ModuleClass::B,
378            (HardpointModule::ImperialHammerRailGun, _, _) => ModuleClass::B,
379
380            // Torpedoes
381            (HardpointModule::TorpedoPylon, _, HardpointSize::Small) => ModuleClass::I,
382            (HardpointModule::TorpedoPylon, _, HardpointSize::Medium) => ModuleClass::I,
383            (HardpointModule::TorpedoPylon, _, HardpointSize::Large) => ModuleClass::I,
384
385            // Chaff launchers
386            (HardpointModule::ChaffLauncher, _, _) => ModuleClass::I,
387
388            // ECMS
389            (HardpointModule::ElectronicCountermeasures, _, _) => ModuleClass::F,
390
391            // Experimental utilities
392            (HardpointModule::EnhancedXenoScanner, _, _) => ModuleClass::C,
393            (HardpointModule::PulseWaveXenoScanner, _, _) => ModuleClass::C,
394            (HardpointModule::ShutdownFieldNeutralizer, _, _) => ModuleClass::F,
395            (HardpointModule::ThargoidPulseNeutralizer, _, _) => ModuleClass::E,
396            (HardpointModule::XenoScanner, _, _) => ModuleClass::E,
397
398            // Heat sink launchers
399            (HardpointModule::CausticSinkLauncher, _, _) => ModuleClass::I,
400            (HardpointModule::HeatsinkLauncher, _, _) => ModuleClass::I,
401
402            // Point defence
403            (HardpointModule::PointDefenceTurret, _, _) => ModuleClass::I,
404
405            _ => ModuleClass::E,
406        }
407    }
408}
409
410from_str_deserialize_impl!(ShipHardpointModule);
411
412impl Display for ShipHardpointModule {
413    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
414        if let HardpointSize::Tiny = self.size {
415            return write!(f, "{}{} {}", self.size.size_nr(), self.class, self.module);
416        }
417
418        write!(
419            f,
420            "{}{}/{} {}",
421            self.size.size_nr(),
422            self.class,
423            self.mounting,
424            self.module
425        )
426    }
427}
428
429#[cfg(test)]
430mod tests {
431    use std::str::FromStr;
432
433    use crate::modules::ship::{
434        HardpointModule, HardpointMounting, HardpointSize, ModuleClass, ShipHardpointModule,
435    };
436
437    #[test]
438    fn ship_hardpoint_module_test_cases_all_pass() {
439        let test_cases = [
440            (
441                "$hpt_beamlaser_gimbal_medium_name;",
442                ShipHardpointModule {
443                    module: HardpointModule::BeamLaser,
444                    mounting: HardpointMounting::Gimballed,
445                    size: HardpointSize::Medium,
446                    class: ModuleClass::D,
447                },
448            ),
449            (
450                "$hpt_heatsinklauncher_turret_tiny_name;",
451                ShipHardpointModule {
452                    module: HardpointModule::HeatsinkLauncher,
453                    mounting: HardpointMounting::Turreted,
454                    size: HardpointSize::Tiny,
455                    class: ModuleClass::I,
456                },
457            ),
458            (
459                "Hpt_CausticSinkLauncher_Turret_Tiny",
460                ShipHardpointModule {
461                    module: HardpointModule::CausticSinkLauncher,
462                    mounting: HardpointMounting::Turreted,
463                    size: HardpointSize::Tiny,
464                    class: ModuleClass::I,
465                },
466            ),
467            (
468                "$hpt_chafflauncher_tiny_name;",
469                ShipHardpointModule {
470                    module: HardpointModule::ChaffLauncher,
471                    mounting: HardpointMounting::Turreted,
472                    size: HardpointSize::Tiny,
473                    class: ModuleClass::I,
474                },
475            ),
476            (
477                "$hpt_shieldbooster_size0_class5_name;",
478                ShipHardpointModule {
479                    module: HardpointModule::ShieldBooster,
480                    mounting: HardpointMounting::Turreted,
481                    size: HardpointSize::Tiny,
482                    class: ModuleClass::A,
483                },
484            ),
485            (
486                "Hpt_Guardian_GaussCannon_Fixed_Medium",
487                ShipHardpointModule {
488                    module: HardpointModule::GuardianGaussCannon,
489                    mounting: HardpointMounting::Fixed,
490                    size: HardpointSize::Medium,
491                    class: ModuleClass::B,
492                },
493            ),
494            (
495                "Hpt_CargoScanner_Size0_Class1",
496                ShipHardpointModule {
497                    module: HardpointModule::ManifestScanner,
498                    mounting: HardpointMounting::Turreted,
499                    size: HardpointSize::Tiny,
500                    class: ModuleClass::E,
501                },
502            ),
503            (
504                "Hpt_PlasmaAccelerator_Fixed_Large_Advanced",
505                ShipHardpointModule {
506                    module: HardpointModule::AdvancedPlasmaAccelerator,
507                    mounting: HardpointMounting::Fixed,
508                    size: HardpointSize::Large,
509                    class: ModuleClass::B,
510                },
511            ),
512            (
513                "Hpt_MkIIPlasmaShockAutocannon_Fixed_Large",
514                ShipHardpointModule {
515                    module: HardpointModule::MkIIPlasmaShockAccelerator,
516                    mounting: HardpointMounting::Fixed,
517                    size: HardpointSize::Large,
518                    class: ModuleClass::B,
519                },
520            ),
521        ];
522
523        for (case, expected) in test_cases {
524            let result = ShipHardpointModule::from_str(case);
525
526            dbg!(&result);
527            assert!(result.is_ok());
528            assert_eq!(result.unwrap(), expected);
529        }
530    }
531
532    #[test]
533    fn incorrect_combination_of_missing_mounting_type_and_size_is_rejected() {
534        let result = ShipHardpointModule::from_str("$hpt_chafflauncher_mediun_name;");
535        assert!(result.is_err());
536    }
537}