Skip to main content

wowlab_types/types/data/
scaling.rs

1use serde::{Deserialize, Serialize};
2use wowlab_engine_macros::CopyInsert;
3
4use crate::{
5    data::gametable::{
6        CombatRatingsFlat, CombatRatingsMultByIlvlFlat, HpPerStaFlat, SpellScalingFlat,
7    },
8    sim::IntMap,
9};
10
11#[derive(Clone, Debug, Deserialize, Serialize)]
12#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
13#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
14pub struct AppliedBonus {
15    pub bonus_list_id: i32,
16    /// Bonus type (1=ILEVEL, 2=MOD, 11=SCALING, etc.).
17    pub bonus_type: i32,
18    pub description: String,
19}
20
21#[derive(Clone, Debug, Default, Deserialize, Serialize)]
22#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
23#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
24#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
25// docref:start items-scaling-data-struct
26pub struct ItemScalingData {
27    /// Grouped by `parent_item_bonus_list_id`.
28    pub bonuses: IntMap<i32, Vec<ItemBonusFlat>>,
29    pub curves: IntMap<i32, CurveFlat>,
30    /// Grouped by `curve_id`, sorted by `order_index`.
31    pub curve_points: IntMap<i32, Vec<CurvePointFlat>>,
32    /// Keyed by item level.
33    pub rand_prop_points: IntMap<i32, RandPropPointsFlat>,
34    pub item_scaling_configs: IntMap<i32, ItemScalingConfigFlat>,
35    pub item_offset_curves: IntMap<i32, ItemOffsetCurveFlat>,
36    pub item_squish_eras: IntMap<i32, ItemSquishEraFlat>,
37    /// `GameTable` rows keyed by character level.
38    #[serde(default)]
39    pub combat_ratings: IntMap<i32, CombatRatingsFlat>,
40    /// `GameTable` rows keyed by character level.
41    #[serde(default)]
42    pub hp_per_sta: IntMap<i32, HpPerStaFlat>,
43    /// `GameTable` rows keyed by character level.
44    #[serde(default)]
45    pub spell_scaling: IntMap<i32, SpellScalingFlat>,
46    /// `GameTable` rows keyed by item level.
47    #[serde(default)]
48    pub combat_ratings_mult_by_ilvl: IntMap<i32, CombatRatingsMultByIlvlFlat>,
49    /// Keyed by `GemProperties.ID` (referenced from `ItemDataFlat::gem_properties`).
50    #[serde(default)]
51    pub gem_properties: IntMap<i32, GemPropertiesFlat>,
52}
53// docref:end items-scaling-data-struct
54
55impl ItemScalingData {
56    // docref:start items-scaling-from-flat
57    // #t(fn: rust_large_fn_params) data-plumbing constructor mirrors all flat scaling collections verbatim
58    #[expect(
59        clippy::too_many_arguments,
60        reason = "constructor mirrors the source game table"
61    )]
62    #[must_use]
63    pub fn from_flat(
64        item_bonuses: Vec<ItemBonusFlat>,
65        curves: Vec<CurveFlat>,
66        curve_points: Vec<CurvePointFlat>,
67        rand_prop_points: Vec<RandPropPointsFlat>,
68        item_scaling_configs: Vec<ItemScalingConfigFlat>,
69        item_offset_curves: Vec<ItemOffsetCurveFlat>,
70        item_squish_eras: Vec<ItemSquishEraFlat>,
71        combat_ratings: Vec<CombatRatingsFlat>,
72        hp_per_sta: Vec<HpPerStaFlat>,
73        spell_scaling: Vec<SpellScalingFlat>,
74        combat_ratings_mult_by_ilvl: Vec<CombatRatingsMultByIlvlFlat>,
75        gem_properties: Vec<GemPropertiesFlat>,
76    ) -> Self {
77        // docref:end items-scaling-from-flat
78        let mut bonuses_by_list: IntMap<i32, Vec<ItemBonusFlat>> = IntMap::default();
79
80        for bonus in item_bonuses {
81            bonuses_by_list
82                .entry(bonus.parent_item_bonus_list_id)
83                .or_default()
84                .push(bonus);
85        }
86
87        let curves_by_id: IntMap<i32, CurveFlat> = curves.into_iter().map(|c| (c.id, c)).collect();
88
89        let mut points_by_curve: IntMap<i32, Vec<CurvePointFlat>> = IntMap::default();
90
91        for point in curve_points {
92            points_by_curve
93                .entry(point.curve_id)
94                .or_default()
95                .push(point);
96        }
97
98        for points in points_by_curve.values_mut() {
99            points.sort_by_key(|p| p.order_index);
100        }
101
102        let rand_prop_by_id: IntMap<i32, RandPropPointsFlat> =
103            rand_prop_points.into_iter().map(|r| (r.id, r)).collect();
104
105        let scaling_configs_by_id: IntMap<i32, ItemScalingConfigFlat> = item_scaling_configs
106            .into_iter()
107            .map(|r| (r.id, r))
108            .collect();
109
110        let offset_curves_by_id: IntMap<i32, ItemOffsetCurveFlat> =
111            item_offset_curves.into_iter().map(|r| (r.id, r)).collect();
112
113        let squish_eras_by_id: IntMap<i32, ItemSquishEraFlat> =
114            item_squish_eras.into_iter().map(|r| (r.id, r)).collect();
115
116        let combat_ratings_by_level: IntMap<i32, CombatRatingsFlat> =
117            combat_ratings.into_iter().map(|r| (r.level, r)).collect();
118
119        let hp_per_sta_by_level: IntMap<i32, HpPerStaFlat> =
120            hp_per_sta.into_iter().map(|r| (r.level, r)).collect();
121
122        let spell_scaling_by_level: IntMap<i32, SpellScalingFlat> =
123            spell_scaling.into_iter().map(|r| (r.level, r)).collect();
124
125        let combat_ratings_mult_by_ilvl: IntMap<i32, CombatRatingsMultByIlvlFlat> =
126            combat_ratings_mult_by_ilvl
127                .into_iter()
128                .map(|r| (r.item_level, r))
129                .collect();
130
131        let gem_properties_by_id: IntMap<i32, GemPropertiesFlat> =
132            gem_properties.into_iter().map(|r| (r.id, r)).collect();
133
134        Self {
135            bonuses: bonuses_by_list,
136            curves: curves_by_id,
137            curve_points: points_by_curve,
138            rand_prop_points: rand_prop_by_id,
139            item_scaling_configs: scaling_configs_by_id,
140            item_offset_curves: offset_curves_by_id,
141            item_squish_eras: squish_eras_by_id,
142            combat_ratings: combat_ratings_by_level,
143            hp_per_sta: hp_per_sta_by_level,
144            spell_scaling: spell_scaling_by_level,
145            combat_ratings_mult_by_ilvl,
146            gem_properties: gem_properties_by_id,
147        }
148    }
149}
150
151#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
152#[repr(i32)]
153#[non_exhaustive]
154pub enum ItemQuality {
155    Poor = 0,
156    Common = 1,
157    Uncommon = 2,
158    Rare = 3,
159    Epic = 4,
160    Legendary = 5,
161    Artifact = 6,
162    Heirloom = 7,
163}
164
165/// Consumer policy for selecting a `RandPropPoints` quality column.
166#[derive(Clone, Copy, Debug, Eq, PartialEq)]
167#[non_exhaustive]
168pub enum RandPropPointsUse {
169    /// Scale the allocated stats stored directly on an item.
170    StatAllocation,
171    /// Scale a coefficient-valued effect contributed by an item.
172    ItemEffect,
173}
174
175/// Quality-column family in `RandPropPoints`.
176#[derive(Clone, Copy, Debug, Eq, PartialEq)]
177#[non_exhaustive]
178pub enum RandPropPointsBucket {
179    Good,
180    Superior,
181    Epic,
182}
183
184impl ItemQuality {
185    /// Decode `ItemSparse.OverallQualityID`.
186    #[must_use]
187    pub const fn from_dbc(raw: i32) -> Option<Self> {
188        match raw {
189            0 => Some(Self::Poor),
190            1 => Some(Self::Common),
191            2 => Some(Self::Uncommon),
192            3 => Some(Self::Rare),
193            4 => Some(Self::Epic),
194            5 => Some(Self::Legendary),
195            6 => Some(Self::Artifact),
196            7 => Some(Self::Heirloom),
197            _ => None,
198        }
199    }
200
201    /// Select the `RandPropPoints` column family for the consuming calculation.
202    #[must_use]
203    pub const fn rand_prop_points_bucket(
204        self,
205        usage: RandPropPointsUse,
206    ) -> Option<RandPropPointsBucket> {
207        match usage {
208            RandPropPointsUse::StatAllocation => match self {
209                Self::Poor | Self::Common => None,
210                Self::Uncommon => Some(RandPropPointsBucket::Good),
211                Self::Rare | Self::Heirloom => Some(RandPropPointsBucket::Superior),
212                Self::Epic | Self::Legendary | Self::Artifact => Some(RandPropPointsBucket::Epic),
213            },
214            RandPropPointsUse::ItemEffect => Some(match self {
215                Self::Poor | Self::Common | Self::Uncommon => RandPropPointsBucket::Good,
216                Self::Rare => RandPropPointsBucket::Superior,
217                Self::Epic | Self::Legendary | Self::Artifact | Self::Heirloom => {
218                    RandPropPointsBucket::Epic
219                }
220            }),
221        }
222    }
223}
224
225#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
226#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
227#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
228#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
229pub struct ItemBonusFlat {
230    pub id: i32,
231    pub value_0: i32,
232    pub value_1: i32,
233    pub value_2: i32,
234    pub value_3: i32,
235    pub parent_item_bonus_list_id: i32,
236    #[serde(rename = "type")]
237    #[copy(rename = "type")]
238    pub bonus_type: i32,
239    pub order_index: i32,
240}
241
242#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
243#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
244#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
245#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
246pub struct CurveFlat {
247    pub id: i32,
248    #[serde(rename = "type")]
249    #[copy(rename = "type")]
250    pub curve_type: i32,
251    pub flags: i32,
252}
253
254#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
255#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
256#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
257#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
258pub struct CurvePointFlat {
259    pub id: i32,
260    pub curve_id: i32,
261    pub order_index: i32,
262    pub pos_0: f64,
263    pub pos_1: f64,
264    pub pos_pre_squish_0: f64,
265    pub pos_pre_squish_1: f64,
266}
267
268/// Flat stat budgets per item level and quality from `WoW`'s `RandPropPoints` DBC table.
269#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
270#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
271#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
272#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
273pub struct RandPropPointsFlat {
274    pub id: i32,
275    pub damage_replace_stat_f: f64,
276    pub damage_secondary_f: f64,
277    pub damage_replace_stat: i32,
278    pub damage_secondary: i32,
279    pub epic_f_0: f64,
280    pub epic_f_1: f64,
281    pub epic_f_2: f64,
282    pub epic_f_3: f64,
283    pub epic_f_4: f64,
284    pub superior_f_0: f64,
285    pub superior_f_1: f64,
286    pub superior_f_2: f64,
287    pub superior_f_3: f64,
288    pub superior_f_4: f64,
289    pub good_f_0: f64,
290    pub good_f_1: f64,
291    pub good_f_2: f64,
292    pub good_f_3: f64,
293    pub good_f_4: f64,
294    pub epic_0: i32,
295    pub epic_1: i32,
296    pub epic_2: i32,
297    pub epic_3: i32,
298    pub epic_4: i32,
299    pub superior_0: i32,
300    pub superior_1: i32,
301    pub superior_2: i32,
302    pub superior_3: i32,
303    pub superior_4: i32,
304    pub good_0: i32,
305    pub good_1: i32,
306    pub good_2: i32,
307    pub good_3: i32,
308    pub good_4: i32,
309}
310
311impl RandPropPointsFlat {
312    /// Read a floating-point stat budget from a quality bucket and slot group.
313    #[must_use]
314    pub const fn budget(&self, bucket: RandPropPointsBucket, slot_index: usize) -> f64 {
315        match (bucket, slot_index) {
316            (RandPropPointsBucket::Epic, 0) => self.epic_f_0,
317            (RandPropPointsBucket::Epic, 1) => self.epic_f_1,
318            (RandPropPointsBucket::Epic, 2) => self.epic_f_2,
319            (RandPropPointsBucket::Epic, 3) => self.epic_f_3,
320            (RandPropPointsBucket::Epic, _) => self.epic_f_4,
321            (RandPropPointsBucket::Superior, 0) => self.superior_f_0,
322            (RandPropPointsBucket::Superior, 1) => self.superior_f_1,
323            (RandPropPointsBucket::Superior, 2) => self.superior_f_2,
324            (RandPropPointsBucket::Superior, 3) => self.superior_f_3,
325            (RandPropPointsBucket::Superior, _) => self.superior_f_4,
326            (RandPropPointsBucket::Good, 0) => self.good_f_0,
327            (RandPropPointsBucket::Good, 1) => self.good_f_1,
328            (RandPropPointsBucket::Good, 2) => self.good_f_2,
329            (RandPropPointsBucket::Good, 3) => self.good_f_3,
330            (RandPropPointsBucket::Good, _) => self.good_f_4,
331        }
332    }
333}
334
335/// Flat Midnight item-level scaling config from `ItemScalingConfig` DBC table.
336#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
337#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
338#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
339#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
340pub struct ItemScalingConfigFlat {
341    pub id: i32,
342    pub item_offset_curve_id: i32,
343    pub item_level: i32,
344    pub required_level: i32,
345    pub item_squish_era_id: i32,
346    pub flags: i32,
347}
348
349/// Flat Midnight offset-curve entry from `ItemOffsetCurve` DBC table.
350#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
351#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
352#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
353#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
354pub struct ItemOffsetCurveFlat {
355    pub id: i32,
356    pub curve_id: i32,
357    /// DB column is `curve_offset` (`offset` is a SQL reserved word); the Rust field stays `offset`.
358    #[serde(rename = "curve_offset")]
359    #[copy(rename = "curve_offset")]
360    pub offset: i32,
361}
362
363/// Flat per-expansion item-squish era from `ItemSquishEra` DBC table.
364#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
365#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
366#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
367#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
368pub struct ItemSquishEraFlat {
369    pub id: i32,
370    pub patch: i32,
371    pub curve_id: i32,
372    pub flags: i32,
373}
374
375/// Flat enchantment data from `SpellItemEnchantment` DBC table.
376#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
377pub struct EnchantmentFlat {
378    pub id: i32,
379    pub name: String,
380    pub duration: i32,
381    pub flags: i32,
382    pub icon_file_data_id: i32,
383    pub item_level_min: i32,
384    pub item_level_max: i32,
385    pub item_level: i32,
386    pub charges: i32,
387    pub scaling_class: i32,
388    pub scaling_class_restricted: i32,
389    pub condition_id: i32,
390    pub min_level: i32,
391    pub max_level: i32,
392    pub required_skill_id: i32,
393    pub required_skill_rank: i32,
394    pub transmog_use_condition_id: i32,
395    pub transmog_cost: i32,
396    pub item_visual: i32,
397    pub effect_0: i32,
398    pub effect_1: i32,
399    pub effect_2: i32,
400    pub effect_arg_0: i32,
401    pub effect_arg_1: i32,
402    pub effect_arg_2: i32,
403    pub effect_points_min_0: f32,
404    pub effect_points_min_1: f32,
405    pub effect_points_min_2: f32,
406    pub effect_scaling_points_0: f32,
407    pub effect_scaling_points_1: f32,
408    pub effect_scaling_points_2: f32,
409}
410
411/// SimC-compatible permanent-enchant index entry derived from profession recipe data.
412#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
413pub struct PermanentEnchantFlat {
414    pub enchant_id: i32,
415    pub rank: i32,
416    pub item_class: i32,
417    pub inventory_type_mask: i32,
418    pub subclass_mask: i32,
419    pub tokenized_name: String,
420}
421
422#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
423#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
424#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
425#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
426pub struct GemPropertiesFlat {
427    pub id: i32,
428    pub enchant_id: i32,
429    #[serde(rename = "type")]
430    #[copy(rename = "type")]
431    pub gem_type: i32,
432}
433
434/// Flat item damage scaling data (merged from OneHand/TwoHand/Ammo tables).
435#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
436pub struct ItemDamageScalingFlat {
437    pub id: i32,
438    pub weapon_type: String,
439    pub item_level: i32,
440    pub quality_0: f32,
441    pub quality_1: f32,
442    pub quality_2: f32,
443    pub quality_3: f32,
444    pub quality_4: f32,
445    pub quality_5: f32,
446    pub quality_6: f32,
447}
448
449impl ItemDamageScalingFlat {
450    #[must_use]
451    pub fn quality(&self, quality: i32) -> Option<f64> {
452        let value = match quality {
453            0 => self.quality_0,
454            1 => self.quality_1,
455            2 => self.quality_2,
456            3 => self.quality_3,
457            4 => self.quality_4,
458            5 => self.quality_5,
459            6 => self.quality_6,
460            _ => return None,
461        };
462
463        Some(f64::from(value))
464    }
465}
466
467/// Flat cooldown set spell entry (stored as jsonb within `CooldownSetFlat`).
468#[derive(Clone, Debug, Default, Deserialize, Serialize)]
469#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
470pub struct CooldownSetSpellEntry {
471    pub spell_id: i32,
472    pub category: i32,
473    pub player_condition_id: i32,
474    pub order_index: i32,
475    pub flags: i32,
476}
477
478/// Flat cooldown set data (`CooldownSet` + `CooldownSetSpell` joined).
479#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
480pub struct CooldownSetFlat {
481    pub id: i32,
482    pub chr_specialization: i32,
483    #[copy(json)]
484    pub spells: Vec<CooldownSetSpellEntry>,
485}
486
487#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
488pub struct ExpectedStatFlat {
489    pub id: i32,
490    pub expansion_id: i32,
491    pub lvl: i32,
492    pub creature_health: f32,
493    pub player_health: f32,
494    pub creature_auto_attack_dps: f32,
495    pub creature_armor: f32,
496    pub player_mana: f32,
497    pub player_primary_stat: f32,
498    pub player_secondary_stat: f32,
499    pub armor_constant: f32,
500    pub creature_spell_damage: f32,
501    pub content_set_id: i32,
502}
503
504#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
505pub struct ExpectedStatModFlat {
506    pub id: i32,
507    pub creature_health_mod: f32,
508    pub player_health_mod: f32,
509    pub creature_auto_attack_dps_mod: f32,
510    pub creature_armor_mod: f32,
511    pub player_mana_mod: f32,
512    pub player_primary_stat_mod: f32,
513    pub player_secondary_stat_mod: f32,
514    pub armor_constant_mod: f32,
515    pub creature_spell_damage_mod: f32,
516}
517
518#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
519#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
520pub struct SpecializationSpellFlat {
521    pub id: i32,
522    pub spec_id: i32,
523    pub spell_id: i32,
524    pub overrides_spell_id: i32,
525}
526
527/// One racial ability granted by a racial skill line, expanded per race.
528#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
529#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
530pub struct RacialSpellFlat {
531    /// `ChrRaces.ID` the ability is available to.
532    pub race_id: i32,
533    pub spell_id: i32,
534    /// Bitmask of classes that may learn it; `0` means every class.
535    pub class_mask: i32,
536}
537
538#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
539#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
540pub struct PowerTypeFlat {
541    pub id: i32,
542    pub name_global_string_tag: String,
543    pub cost_global_string_tag: String,
544    pub power_type_enum: i32,
545    pub min_power: i32,
546    pub max_base_power: i32,
547    pub center_power: i32,
548    pub default_power: i32,
549    pub display_modifier: f32,
550    pub regen_interrupt_time_ms: i32,
551    pub regen_peace: f32,
552    pub regen_combat: f32,
553    pub flags: i32,
554}
555
556#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
557pub struct ItemArmorQualityFlat {
558    pub id: i32,
559    pub qualitymod_0: f32,
560    pub qualitymod_1: f32,
561    pub qualitymod_2: f32,
562    pub qualitymod_3: f32,
563    pub qualitymod_4: f32,
564    pub qualitymod_5: f32,
565    pub qualitymod_6: f32,
566}
567
568#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
569// #t(rust_similar_structs) shield armor and merged weapon damage are distinct canonical table outputs
570pub struct ItemArmorShieldFlat {
571    pub id: i32,
572    pub item_level: i32,
573    pub quality_0: f32,
574    pub quality_1: f32,
575    pub quality_2: f32,
576    pub quality_3: f32,
577    pub quality_4: f32,
578    pub quality_5: f32,
579    pub quality_6: f32,
580}
581
582#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
583pub struct ItemArmorTotalFlat {
584    pub id: i32,
585    pub item_level: i32,
586    pub cloth: f32,
587    pub leather: f32,
588    pub mail: f32,
589    pub plate: f32,
590}
591
592#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
593pub struct ArmorLocationFlat {
594    pub id: i32,
595    pub cloth_modifier: f32,
596    pub leather_modifier: f32,
597    pub chain_modifier: f32,
598    pub plate_modifier: f32,
599    pub modifier: f32,
600}
601
602#[cfg(test)]
603#[path = "scaling/tests.rs"]
604mod tests;