wowlab_engine_domain/dbc/passives/
stats.rs1use 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#[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#[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#[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}