1use crate::{FromArma, FromArmaError, IntoArma, Value};
4
5mod assigned;
6mod container;
7mod extended;
8mod inventory_item;
9mod magazine;
10mod weapon;
11
12pub use assigned::AssignedItems;
13pub use container::Container;
14pub use extended::CBAExtended;
15pub use inventory_item::InventoryItem;
16pub use magazine::Magazine;
17pub use weapon::Weapon;
18
19#[derive(Debug, Default, Clone, PartialEq)]
20pub struct Loadout(
22 Weapon,
23 Weapon,
24 Weapon,
25 Container,
26 Container,
27 Container,
28 String,
29 String,
30 Weapon,
31 AssignedItems,
32 CBAExtended,
33);
34
35impl Loadout {
36 #[must_use]
38 pub const fn primary(&self) -> &Weapon {
39 &self.0
40 }
41
42 pub fn primary_mut(&mut self) -> &mut Weapon {
44 &mut self.0
45 }
46
47 pub fn set_primary(&mut self, primary: Weapon) {
49 self.0 = primary;
50 }
51
52 #[must_use]
54 pub const fn secondary(&self) -> &Weapon {
55 &self.1
56 }
57
58 pub fn secondary_mut(&mut self) -> &mut Weapon {
60 &mut self.1
61 }
62
63 pub fn set_secondary(&mut self, secondary: Weapon) {
65 self.1 = secondary;
66 }
67
68 #[must_use]
70 pub const fn handgun(&self) -> &Weapon {
71 &self.2
72 }
73
74 pub fn handgun_mut(&mut self) -> &mut Weapon {
76 &mut self.2
77 }
78
79 pub fn set_handgun(&mut self, handgun: Weapon) {
81 self.2 = handgun;
82 }
83
84 #[must_use]
86 pub const fn uniform(&self) -> &Container {
87 &self.3
88 }
89
90 pub fn uniform_mut(&mut self) -> &mut Container {
92 &mut self.3
93 }
94
95 pub fn set_uniform(&mut self, uniform: Container) {
97 self.3 = uniform;
98 }
99
100 #[must_use]
102 pub const fn vest(&self) -> &Container {
103 &self.4
104 }
105
106 pub fn vest_mut(&mut self) -> &mut Container {
108 &mut self.4
109 }
110
111 pub fn set_vest(&mut self, vest: Container) {
113 self.4 = vest;
114 }
115
116 #[must_use]
118 pub const fn backpack(&self) -> &Container {
119 &self.5
120 }
121
122 pub fn backpack_mut(&mut self) -> &mut Container {
124 &mut self.5
125 }
126
127 pub fn set_backpack(&mut self, backpack: Container) {
129 self.5 = backpack;
130 }
131
132 #[must_use]
134 pub fn headgear(&self) -> &str {
135 &self.6
136 }
137
138 pub fn set_headgear(&mut self, headgear: String) {
140 self.6 = headgear;
141 }
142
143 #[must_use]
145 pub fn goggles(&self) -> &str {
146 &self.7
147 }
148
149 pub fn set_goggles(&mut self, goggles: String) {
151 self.7 = goggles;
152 }
153
154 #[must_use]
156 pub const fn binoculars(&self) -> &Weapon {
157 &self.8
158 }
159
160 pub fn binoculars_mut(&mut self) -> &mut Weapon {
162 &mut self.8
163 }
164
165 pub fn set_binoculars(&mut self, binoculars: Weapon) {
167 self.8 = binoculars;
168 }
169
170 #[must_use]
172 pub const fn assigned_items(&self) -> &AssignedItems {
173 &self.9
174 }
175
176 pub fn assigned_items_mut(&mut self) -> &mut AssignedItems {
178 &mut self.9
179 }
180
181 pub fn set_assigned_items(&mut self, assigned_items: AssignedItems) {
183 self.9 = assigned_items;
184 }
185
186 pub fn cba_extended(&self) -> &CBAExtended {
188 &self.10
189 }
190
191 pub fn classes(&self) -> std::collections::HashMap<String, u32> {
193 let mut items = std::collections::HashMap::new();
194 self.0.classes().iter().for_each(|c| {
195 *items.entry(c.to_string()).or_insert(0) += 1;
196 });
197 self.1.classes().iter().for_each(|c| {
198 *items.entry(c.to_string()).or_insert(0) += 1;
199 });
200 self.2.classes().iter().for_each(|c| {
201 *items.entry(c.to_string()).or_insert(0) += 1;
202 });
203 self.3.classes().iter().for_each(|(c, q)| {
204 *items.entry(c.clone()).or_insert(0) += q;
205 });
206 self.4.classes().iter().for_each(|(c, q)| {
207 *items.entry(c.clone()).or_insert(0) += q;
208 });
209 self.5.classes().iter().for_each(|(c, q)| {
210 *items.entry(c.clone()).or_insert(0) += q;
211 });
212 *items.entry(self.6.clone()).or_insert(0) += 1;
213 *items.entry(self.7.clone()).or_insert(0) += 1;
214 self.8.classes().iter().for_each(|c| {
215 *items.entry(c.to_string()).or_insert(0) += 1;
216 });
217 self.9.classes().iter().for_each(|c| {
218 *items.entry(c.to_string()).or_insert(0) += 1;
219 });
220 items.remove("");
221 items
222 }
223}
224
225impl FromArma for Loadout {
226 fn from_arma(s: String) -> Result<Self, FromArmaError> {
227 let vanilla = <(
228 Weapon,
229 Weapon,
230 Weapon,
231 Container,
232 Container,
233 Container,
234 String,
235 String,
236 Weapon,
237 AssignedItems,
238 )>::from_arma(s.clone())
239 .map(
240 |(
241 primary,
242 secondary,
243 handgun,
244 uniform,
245 vest,
246 backpack,
247 headgear,
248 goggles,
249 binoculars,
250 linked_items,
251 )| {
252 Self(
253 primary,
254 secondary,
255 handgun,
256 uniform,
257 vest,
258 backpack,
259 headgear,
260 goggles,
261 binoculars,
262 linked_items,
263 CBAExtended::default(),
264 )
265 },
266 );
267 if vanilla.is_err() {
268 return <(
269 (
270 Weapon,
271 Weapon,
272 Weapon,
273 Container,
274 Container,
275 Container,
276 String,
277 String,
278 Weapon,
279 AssignedItems,
280 ),
281 CBAExtended,
282 )>::from_arma(s)
283 .map(
284 |(
285 (
286 primary,
287 secondary,
288 handgun,
289 uniform,
290 vest,
291 backpack,
292 headgear,
293 goggles,
294 binoculars,
295 linked_items,
296 ),
297 extended,
298 )| {
299 Self(
300 primary,
301 secondary,
302 handgun,
303 uniform,
304 vest,
305 backpack,
306 headgear,
307 goggles,
308 binoculars,
309 linked_items,
310 extended,
311 )
312 },
313 );
314 }
315 vanilla
316 }
317}
318impl IntoArma for Loadout {
319 fn to_arma(&self) -> Value {
320 Value::Array(vec![
321 self.primary().to_arma(),
322 self.secondary().to_arma(),
323 self.handgun().to_arma(),
324 self.uniform().to_arma(),
325 self.vest().to_arma(),
326 self.backpack().to_arma(),
327 Value::String(self.headgear().to_owned()),
328 Value::String(self.goggles().to_owned()),
329 self.binoculars().to_arma(),
330 self.assigned_items().to_arma(),
331 ])
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338
339 #[test]
340 fn b_soldier_at_f() {
341 let loadout = r#"[["arifle_MXC_Holo_pointer_F", "", "acc_pointer_IR", "optic_Holosight", ["30Rnd_65x39_caseless_mag", 30], [], ""],
342 ["launch_B_Titan_short_F", "", "", "", ["Titan_AT", 1], [], ""],
343 ["hgun_P07_F", "", "", "", ["16Rnd_9x21_Mag", 16], [], ""],
344 ["U_B_CombatUniform_mcam", [["FirstAidKit", 1], ["30Rnd_65x39_caseless_mag", 2, 30], ["Chemlight_green", 1, 1]]],
345 ["V_PlateCarrier1_rgr", [["30Rnd_65x39_caseless_mag", 3, 30], ["16Rnd_9x21_Mag", 2, 16], ["SmokeShell", 1 ,1], ["SmokeShellGreen", 1, 1], ["Chemlight_green", 1, 1]]],
346 ["B_AssaultPack_mcamo_AT",[["Titan_AT", 2, 1]]],
347 "H_HelmetB_light_desert", "G_Bandanna_tan",[],
348 ["ItemMap", "", "ItemRadio", "ItemCompass", "ItemWatch", "NVGoggles"]]"#;
349 Loadout::from_arma(loadout.to_string()).unwrap();
350 let loadout = r#"[["arifle_SPAR_02_blk_F","","","optic_Holosight_blk_F",["30Rnd_556x45_Stanag",30],[],""],[],["hgun_ACPC2_F","","","",["9Rnd_45ACP_Mag",8],[],""],["tacs_Uniform_Polo_TP_LS_TP_TB_NoLogo",[]],["V_PlateCarrier1_rgr_noflag_F",[]],[],"H_Cap_headphones","G_Shades_Black",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]]"#;
351 let mut loadout = Loadout::from_arma(loadout.to_string()).unwrap();
352 loadout.set_secondary({
353 let mut weapon = Weapon::new("launch_B_Titan_short_F".to_string());
354 weapon.set_primary_magazine(Magazine::new("Titan_AT".to_string(), 1));
355 weapon
356 });
357 Loadout::from_arma(loadout.to_arma().to_string()).unwrap();
358 }
359
360 #[test]
361 fn marshal() {
362 let loadout = r#"[[],[],[],["U_Marshal",[]],[],[],"H_Cap_headphones","G_Aviator",[],["ItemMap","ItemGPS","","ItemCompass","ItemWatch",""]]"#;
363 let mut loadout = Loadout::from_arma(loadout.to_string()).unwrap();
364 loadout.set_secondary({
365 let mut weapon = Weapon::new("launch_B_Titan_short_F".to_string());
366 weapon.set_primary_magazine(Magazine::new("Titan_AT".to_string(), 1));
367 weapon
368 });
369 loadout.set_primary({
370 let mut weapon = Weapon::new("arifle_MXC_F".to_string());
371 weapon.set_optic("optic_Holosight".to_string());
372 weapon
373 });
374 let uniform = loadout.uniform_mut();
375 uniform.set_class("U_B_CombatUniform_mcam".to_string());
376 let uniform_items = uniform.items_mut().unwrap();
377 uniform_items.push(InventoryItem::new_item("FirstAidKit".to_string(), 3));
378 uniform_items.push(InventoryItem::new_magazine(
379 "30Rnd_65x39_caseless_mag".to_string(),
380 5,
381 30,
382 ));
383 }
384
385 #[test]
386 fn extended_empty() {
387 let loadout = r#"[[["arifle_XMS_Shot_lxWS","","tacgt_ANPEQ_15_Low_Light_Black","CUP_optic_Elcan_SpecterDR_black_PIP",["tacgt_30Rnd_556x45_Ball_Tracer_PMAG",30],["6Rnd_12Gauge_Pellets",6],""],["CUP_launch_M136_Loaded","","","",[],[],""],[],["tacs_Uniform_Floral_JP_RS_LP_BB",[["kat_guedel",1],["ACE_EntrenchingTool",1],["ACE_EarPlugs",1],["ACE_CableTie",2],["ACE_quikclot",1],["ACE_packingBandage",2],["ACE_elasticBandage",1],["SmokeShell",2,1],["Chemlight_yellow",1,1],["Chemlight_red",3,1],["ACE_Chemlight_IR",2,1],["ACE_Chemlight_HiBlue",1,1]]],["milgp_v_mmac_marksman_belt_CB",[["ACE_tourniquet",4],["ACE_splint",1],["tacgt_30Rnd_556x45_EPR_PMAG",15,30],["tacgt_30Rnd_556x45_AP_PMAG",2,30],["6rnd_Smoke_Mag_lxWS",1,6]]],["milgp_bp_Pointman_cb",[["ACE_SpraypaintGreen",1],["ACE_splint",1],["synixe_painkillers",2],["ACE_microDAGR",1],["ACE_MapTools",1],["ACE_bodyBag",1],["ACE_packingBandage",10],["ACE_elasticBandage",9],["ACE_quikclot",5],["ACE_EarPlugs",2],["ACRE_PRC152",1],["ACRE_PRC152",1],["6Rnd_12Gauge_Pellets",2,6],["6Rnd_12Gauge_Slug",1,6],["tacgt_30Rnd_556x45_Ball_Tracer_PMAG",1,30]]],"synixe_contractors_Hat_Beret_Black","",["ACE_VectorDay","","","",[],[],""],["ItemMap","ItemGPS","","ItemCompass","ItemWatch",""]],[]]"#;
388 let loadout = Loadout::from_arma(loadout.to_string()).unwrap();
389 assert!(loadout.cba_extended().is_empty());
390 }
391
392 #[test]
393 fn extended_crash_1_9_0() {
394 let loadout = r#"[[[],[],[],["synixe_contractors_Uniform_Contractor_Shirt",[]],[],[],"","",[],["","","","","",""]],[]]"#;
395 let loadout = Loadout::from_arma(loadout.to_string()).unwrap();
396 assert!(loadout.cba_extended().is_empty());
397 }
398
399 #[test]
400 fn extended_items() {
401 let loadout = r#"[[["CUP_arifle_M4A1_SOMMOD_Grip_tan","","","CUP_optic_Eotech553_Black",["tacgt_30Rnd_556x45_EPR_PMAG_Tan",30],[],""],[],["ACE_VMM3","","","",[],[],""],["casual_plaid_gray_khaki_uniform",[["ACE_packingBandage",10],["ACE_elasticBandage",10],["ACE_CableTie",2],["kat_guedel",1],["ACE_tourniquet",2],["ACE_splint",1],["synixe_painkillers",2]]],["milgp_v_mmac_assaulter_belt_AOR2",[["ACRE_PRC152",1],["SmokeShell",2,1],["HandGrenade",2,1],["tacgt_30Rnd_556x45_EPR_PMAG_Tan",10,30]]],["B_MU_TacticalPack_cbr",[["ACE_bodyBag",1],["ToolKit",1],["ACE_SpraypaintGreen",1],["synixe_axe",1],["ACE_wirecutter",1],["ACE_EntrenchingTool",1],["ACE_rope3",2],["DemoCharge_Remote_Mag",2,1]]],"synixe_contractors_Cap_Headphones_GreenLogo","CUP_G_Tan_Scarf_Shades",["Binocular","","","",[],[],""],["ItemMap","ItemGPS","","ItemCompass","ItemWatch",""]],[["grad_slingHelmet","CUP_H_OpsCore_Grey"]]]"#;
402 let loadout = Loadout::from_arma(loadout.to_string()).unwrap();
403 assert!(!loadout.cba_extended().is_empty());
404 assert_eq!(
405 loadout.primary().class(),
406 Some("CUP_arifle_M4A1_SOMMOD_Grip_tan")
407 );
408 assert_eq!(
409 loadout.cba_extended().get("grad_slingHelmet"),
410 Some(&Value::String("CUP_H_OpsCore_Grey".to_string()))
411 );
412 }
413
414 #[test]
415 fn classes() {
416 let loadout = r#"[[["CUP_arifle_M4A1_SOMMOD_Grip_tan","","","CUP_optic_Eotech553_Black",["tacgt_30Rnd_556x45_EPR_PMAG_Tan",30],[],""],[],["ACE_VMM3","","","",[],[],""],["casual_plaid_gray_khaki_uniform",[["ACE_packingBandage",10],["ACE_elasticBandage",10],["ACE_CableTie",2],["kat_guedel",1],["ACE_tourniquet",2],["ACE_splint",1],["synixe_painkillers",2]]],["milgp_v_mmac_assaulter_belt_AOR2",[["ACRE_PRC152",1],["SmokeShell",2,1],["HandGrenade",2,1],["tacgt_30Rnd_556x45_EPR_PMAG_Tan",10,30]]],["B_MU_TacticalPack_cbr",[["ACE_bodyBag",1],["ToolKit",1],["ACE_SpraypaintGreen",1],["synixe_axe",1],["ACE_wirecutter",1],["ACE_EntrenchingTool",1],["ACE_rope3",2],["DemoCharge_Remote_Mag",2,1]]],"synixe_contractors_Cap_Headphones_GreenLogo","CUP_G_Tan_Scarf_Shades",["Binocular","","","",[],[],""],["ItemMap","ItemGPS","","ItemCompass","ItemWatch",""]],[["grad_slingHelmet","CUP_H_OpsCore_Grey"]]]"#;
417 let loadout = Loadout::from_arma(loadout.to_string()).unwrap();
418 assert_eq!(loadout.classes(), {
419 let mut classes = std::collections::HashMap::new();
420 classes.insert("CUP_arifle_M4A1_SOMMOD_Grip_tan".to_string(), 1);
421 classes.insert("CUP_optic_Eotech553_Black".to_string(), 1);
422 classes.insert("tacgt_30Rnd_556x45_EPR_PMAG_Tan".to_string(), 11);
423 classes.insert("ACE_VMM3".to_string(), 1);
424 classes.insert("casual_plaid_gray_khaki_uniform".to_string(), 1);
425 classes.insert("ACE_packingBandage".to_string(), 10);
426 classes.insert("ACE_elasticBandage".to_string(), 10);
427 classes.insert("ACE_CableTie".to_string(), 2);
428 classes.insert("kat_guedel".to_string(), 1);
429 classes.insert("ACE_tourniquet".to_string(), 2);
430 classes.insert("ACE_splint".to_string(), 1);
431 classes.insert("synixe_painkillers".to_string(), 2);
432 classes.insert("milgp_v_mmac_assaulter_belt_AOR2".to_string(), 1);
433 classes.insert("ACRE_PRC152".to_string(), 1);
434 classes.insert("SmokeShell".to_string(), 2);
435 classes.insert("HandGrenade".to_string(), 2);
436 classes.insert("B_MU_TacticalPack_cbr".to_string(), 1);
437 classes.insert("ACE_bodyBag".to_string(), 1);
438 classes.insert("ToolKit".to_string(), 1);
439 classes.insert("ACE_SpraypaintGreen".to_string(), 1);
440 classes.insert("synixe_axe".to_string(), 1);
441 classes.insert("ACE_wirecutter".to_string(), 1);
442 classes.insert("ACE_EntrenchingTool".to_string(), 1);
443 classes.insert("ACE_rope3".to_string(), 2);
444 classes.insert("DemoCharge_Remote_Mag".to_string(), 2);
445 classes.insert("synixe_contractors_Cap_Headphones_GreenLogo".to_string(), 1);
446 classes.insert("CUP_G_Tan_Scarf_Shades".to_string(), 1);
447 classes.insert("Binocular".to_string(), 1);
448 classes.insert("ItemMap".to_string(), 1);
449 classes.insert("ItemGPS".to_string(), 1);
450 classes.insert("ItemCompass".to_string(), 1);
451 classes.insert("ItemWatch".to_string(), 1);
452 classes
453 });
454 }
455}