Skip to main content

wowlab_engine_domain/dbc/passives/
stats.rs

1use wowlab_types::data::SpellDataFlat;
2
3use super::{
4    AttributeMask, AuraSubtypeKind, RankedPassive, RatingMultiplierMask, aura_kind, pct_to_mult,
5};
6use crate::gear::{MatchingArmorPassive, matching_armor_specialization_passive};
7
8/// Player-wide sheet-stat adjustments from passive auras.
9#[derive(Clone, Copy, Debug)]
10pub struct PassiveStatMods {
11    pub strength_mult: f64,
12    pub agility_mult: f64,
13    pub stamina_mult: f64,
14    pub intellect_mult: f64,
15    pub crit_chance: f64,
16    pub mastery_points: f64,
17    pub haste_mult: f64,
18    pub crit_rating_mult: f64,
19    pub haste_rating_mult: f64,
20    pub mastery_rating_mult: f64,
21    pub versatility_rating_mult: f64,
22    pub versatility_pct: f64,
23    pub matching_armor: MatchingArmorPassive,
24}
25
26impl Default for PassiveStatMods {
27    fn default() -> Self {
28        Self {
29            strength_mult: 1.0,
30            agility_mult: 1.0,
31            stamina_mult: 1.0,
32            intellect_mult: 1.0,
33            crit_chance: 0.0,
34            mastery_points: 0.0,
35            haste_mult: 1.0,
36            crit_rating_mult: 1.0,
37            haste_rating_mult: 1.0,
38            mastery_rating_mult: 1.0,
39            versatility_rating_mult: 1.0,
40            versatility_pct: 0.0,
41            matching_armor: MatchingArmorPassive::default(),
42        }
43    }
44}
45
46/// Whether `spell` carries any player-wide sheet-stat effect this module models.
47#[must_use]
48pub fn is_stat_modifier_passive(spell: &SpellDataFlat) -> bool {
49    spell.is_passive
50        && spell.effects.iter().any(|effect| {
51            matches!(
52                aura_kind(effect),
53                Some(
54                    AuraSubtypeKind::ModSpellCritChance
55                        | AuraSubtypeKind::ModTotalStatPercentage
56                        | AuraSubtypeKind::HasteAll
57                        | AuraSubtypeKind::ModAllCritChance
58                        | AuraSubtypeKind::ModMasteryPercent
59                        | AuraSubtypeKind::ModRatingMultiplier
60                        | AuraSubtypeKind::ModVersatilityPercent
61                )
62            )
63        })
64}
65
66fn apply_stat_mask_mult(mods: &mut PassiveStatMods, stat_mask: i32, mult: f64) {
67    let mask = AttributeMask::from_dbc(stat_mask);
68
69    if mask.contains(AttributeMask::STRENGTH) {
70        mods.strength_mult *= mult;
71    }
72
73    if mask.contains(AttributeMask::AGILITY) {
74        mods.agility_mult *= mult;
75    }
76
77    if mask.contains(AttributeMask::STAMINA) {
78        mods.stamina_mult *= mult;
79    }
80
81    if mask.contains(AttributeMask::INTELLECT) {
82        mods.intellect_mult *= mult;
83    }
84}
85
86fn apply_rating_mask_mult(mods: &mut PassiveStatMods, rating_mask: i32, mult: f64) {
87    let mask = RatingMultiplierMask::from_dbc(rating_mask);
88
89    if mask.intersects(RatingMultiplierMask::CRIT) {
90        mods.crit_rating_mult *= mult;
91    }
92
93    if mask.intersects(RatingMultiplierMask::HASTE) {
94        mods.haste_rating_mult *= mult;
95    }
96
97    if mask.intersects(RatingMultiplierMask::MASTERY) {
98        mods.mastery_rating_mult *= mult;
99    }
100
101    if mask.intersects(RatingMultiplierMask::VERSATILITY) {
102        mods.versatility_rating_mult *= mult;
103    }
104}
105
106/// Folds passive sheet-stat effects into one [`PassiveStatMods`].
107#[must_use]
108pub fn passive_stat_mods(passives: &[RankedPassive]) -> PassiveStatMods {
109    let mut mods = PassiveStatMods::default();
110
111    for (aura, ranks) in passives {
112        let matching_armor = matching_armor_specialization_passive(aura);
113
114        if let Some(passive) = matching_armor {
115            mods.matching_armor.add_scaled(passive, *ranks);
116        }
117
118        for effect in &aura.effects {
119            let kind = aura_kind(effect);
120            let value = effect.base_points * ranks;
121
122            match kind {
123                Some(AuraSubtypeKind::ModSpellCritChance | AuraSubtypeKind::ModAllCritChance) => {
124                    mods.crit_chance += value;
125                }
126                Some(AuraSubtypeKind::ModMasteryPercent) => mods.mastery_points += value,
127                Some(AuraSubtypeKind::HasteAll) => mods.haste_mult *= pct_to_mult(value),
128                Some(AuraSubtypeKind::ModTotalStatPercentage) => {
129                    if matching_armor.is_none() {
130                        apply_stat_mask_mult(&mut mods, effect.misc_value_1, pct_to_mult(value));
131                    }
132                }
133                Some(AuraSubtypeKind::ModRatingMultiplier) => {
134                    apply_rating_mask_mult(&mut mods, effect.misc_value_0, pct_to_mult(value));
135                }
136                Some(AuraSubtypeKind::ModVersatilityPercent) => mods.versatility_pct += value,
137                _ => {}
138            }
139        }
140    }
141
142    mods
143}