1use super::{
2 AuraSubtypeKind, CLASS_FAMILY_FLAGS, HUNDRED, ModifierFilter, ModifierFold, ModifierOperation,
3 ModifierPropertyKind, PassiveQuery, RankedPassive, SpellAttributeKind, SpellDataFlat,
4 aura_kind, aura_subtype_semantic, effect_targets_spell, fold_flat_pct_modifiers, masks_overlap,
5 modified_effect_base_points, modified_effect_value, modifier_property_kind, pct_to_mult,
6 spell_attribute_is,
7};
8
9#[derive(Clone, Copy, Debug)]
11pub struct PassiveDamageMods {
12 pub direct_flat: f64,
13 pub direct_flat_mastery_coefficient: f64,
14 pub direct: f64,
15 pub periodic: f64,
16 pub crit_chance: f64,
17 pub crit_chance_mult: f64,
18 pub crit_damage_bonus: f64,
19}
20
21#[derive(Clone, Copy, Debug, PartialEq)]
23pub struct PassiveProcChanceMods {
24 pub flat_pct: f64,
25 pub multiplier: f64,
26}
27
28#[must_use]
30pub fn passive_proc_chance_mods(
31 passives: &[RankedPassive],
32 spell: &SpellDataFlat,
33) -> PassiveProcChanceMods {
34 let mut mods = PassiveProcChanceMods {
35 flat_pct: 0.0,
36 multiplier: 1.0,
37 };
38
39 for (source, ranks) in passives {
40 for modifier in &source.effects {
41 if modifier_property_kind(modifier) != Some(ModifierPropertyKind::ProcChance)
42 || !effect_targets_spell(modifier, source, spell)
43 {
44 continue;
45 }
46
47 let amount = modified_effect_base_points(
48 PassiveQuery {
49 passives,
50 spell: source,
51 },
52 modifier.index,
53 modifier.base_points,
54 ) * ranks;
55
56 match aura_subtype_semantic(modifier.aura)
57 .and_then(|semantic| semantic.modifier)
58 .map(|(operation, _)| operation)
59 {
60 Some(ModifierOperation::Flat) => mods.flat_pct += amount,
61 Some(ModifierOperation::Percent) => mods.multiplier *= pct_to_mult(amount),
62 _ => {}
63 }
64 }
65 }
66
67 mods
68}
69
70#[must_use]
72pub fn is_damage_modifier_passive(spell: &SpellDataFlat) -> bool {
73 spell.is_passive
74 && spell.effects.iter().any(|effect| match aura_kind(effect) {
75 Some(
76 AuraSubtypeKind::AddPercentModifier
77 | AuraSubtypeKind::AddPercentLabelModifier
78 | AuraSubtypeKind::ModAutoAttackDamage
79 | AuraSubtypeKind::ModPetDamageDone,
80 ) => true,
81 Some(AuraSubtypeKind::AddFlatModifier | AuraSubtypeKind::AddFlatLabelModifier) => {
82 matches!(
83 modifier_property_kind(effect),
84 Some(
85 ModifierPropertyKind::CritChance
86 | ModifierPropertyKind::CritDamage
87 | ModifierPropertyKind::GenericDamage
88 | ModifierPropertyKind::ChainTargets
89 )
90 )
91 }
92 _ => false,
93 })
94}
95
96#[must_use]
98pub fn modified_effect_coefficient(
99 query: PassiveQuery<'_>,
100 effect_index: i32,
101 coefficient: f64,
102) -> f64 {
103 modified_effect_value(query, effect_index, coefficient, false)
104}
105
106#[must_use]
108pub fn modified_effect_amplitude(query: PassiveQuery<'_>, initial: f64) -> f64 {
109 let PassiveQuery {
110 passives,
111 spell: target,
112 } = query;
113
114 fold_flat_pct_modifiers(
115 passives,
116 initial,
117 ModifierFold::FlatAndPercent,
118 |source, ranks, modifier| {
119 if modifier_property_kind(modifier) != Some(ModifierPropertyKind::Amplitude)
120 || !effect_targets_spell(modifier, source, target)
121 {
122 return None;
123 }
124
125 Some(
126 modified_effect_base_points(
127 PassiveQuery {
128 passives,
129 spell: source,
130 },
131 modifier.index,
132 modifier.base_points,
133 ) * ranks,
134 )
135 },
136 )
137}
138
139#[derive(Clone, Copy, Debug, PartialEq)]
141pub struct ModifiedPowerCoefficients {
142 pub spell_power: f64,
143 pub attack_power: f64,
144 pub mastery_add: f64,
146}
147
148#[must_use]
150pub fn modified_power_coefficients(
151 query: PassiveQuery<'_>,
152 effect_index: i32,
153 spell_power: f64,
154 attack_power: f64,
155) -> ModifiedPowerCoefficients {
156 let PassiveQuery {
157 passives,
158 spell: target,
159 } = query;
160 let spell_power = modified_effect_coefficient(query, effect_index, spell_power);
161 let attack_power = modified_effect_coefficient(query, effect_index, attack_power);
162 let (initial, modifies_spell_power) = if spell_power.abs() > f64::EPSILON {
163 (spell_power, true)
164 } else if attack_power.abs() > f64::EPSILON {
165 (attack_power, false)
166 } else {
167 return ModifiedPowerCoefficients {
168 spell_power,
169 attack_power,
170 mastery_add: 0.0,
171 };
172 };
173 let mut flat = 0.0;
174 let mut flat_mastery = 0.0;
175 let mut percent = 1.0;
176
177 for (source, ranks) in passives {
178 for modifier in &source.effects {
179 if modifier_property_kind(modifier) != Some(ModifierPropertyKind::Coefficient)
180 || !effect_targets_spell(modifier, source, target)
181 {
182 continue;
183 }
184
185 let amount = modified_effect_base_points(
186 PassiveQuery {
187 passives,
188 spell: source,
189 },
190 modifier.index,
191 modifier.base_points,
192 ) * ranks;
193
194 match aura_subtype_semantic(modifier.aura)
195 .and_then(|semantic| semantic.modifier)
196 .map(|(operation, _)| operation)
197 {
198 Some(ModifierOperation::Flat) => {
199 flat += amount / HUNDRED;
200
201 if spell_attribute_is(
202 &source.attributes,
203 SpellAttributeKind::MasteryAffectsPoints,
204 ) {
205 flat_mastery += modified_effect_coefficient(
206 PassiveQuery {
207 passives,
208 spell: source,
209 },
210 modifier.index,
211 modifier.bonus_coefficient,
212 ) * ranks
213 / HUNDRED;
214 }
215 }
216 Some(ModifierOperation::Percent) => percent *= pct_to_mult(amount),
217 None => {}
218 }
219 }
220 }
221
222 let modified = (initial + flat) * percent;
223
224 ModifiedPowerCoefficients {
225 spell_power: if modifies_spell_power {
226 modified
227 } else {
228 spell_power
229 },
230 attack_power: if modifies_spell_power {
231 attack_power
232 } else {
233 modified
234 },
235 mastery_add: flat_mastery * percent,
236 }
237}
238
239#[must_use]
241pub fn passive_damage_mods(
242 passives: &[RankedPassive],
243 target_class_set: i32,
244 target_class_masks: [i32; CLASS_FAMILY_FLAGS],
245 target_labels: &[i32],
246) -> PassiveDamageMods {
247 let mut crit_damage_flat = 0.0;
248 let mut crit_damage_mult = 1.0;
249 let mut mods = PassiveDamageMods {
250 direct_flat: 0.0,
251 direct_flat_mastery_coefficient: 0.0,
252 direct: 1.0,
253 periodic: 1.0,
254 crit_chance: 0.0,
255 crit_chance_mult: 1.0,
256 crit_damage_bonus: 0.0,
257 };
258
259 for (aura, ranks) in passives {
260 for effect in &aura.effects {
261 let base_points = modified_effect_base_points(
262 PassiveQuery {
263 passives,
264 spell: aura,
265 },
266 effect.index,
267 effect.base_points,
268 );
269 let matched = match aura_subtype_semantic(effect.aura)
270 .and_then(|semantic| semantic.modifier)
271 .map(|(_, filter)| filter)
272 {
273 Some(ModifierFilter::ClassFamilyMask) => {
274 aura.spell_class_set == target_class_set
275 && masks_overlap(effect, target_class_masks)
276 }
277 Some(ModifierFilter::Label) => target_labels.contains(&effect.misc_value_1),
278 _ => false,
279 };
280
281 if !matched {
282 continue;
283 }
284
285 let operation = aura_subtype_semantic(effect.aura)
286 .and_then(|semantic| semantic.modifier)
287 .map(|(operation, _)| operation);
288
289 match (operation, modifier_property_kind(effect)) {
290 (Some(ModifierOperation::Flat), Some(ModifierPropertyKind::GenericDamage)) => {
291 mods.direct_flat += base_points * ranks;
292
293 if spell_attribute_is(
294 &aura.attributes,
295 SpellAttributeKind::MasteryAffectsPoints,
296 ) {
297 mods.direct_flat_mastery_coefficient += modified_effect_coefficient(
298 PassiveQuery {
299 passives,
300 spell: aura,
301 },
302 effect.index,
303 effect.bonus_coefficient,
304 ) * ranks;
305 }
306 }
307 (Some(ModifierOperation::Percent), Some(ModifierPropertyKind::GenericDamage)) => {
308 mods.direct *= pct_to_mult(base_points * ranks);
309 tracing::trace!(
311 source_spell_id = aura.id,
312 source_spell_name = aura.name.as_str(),
313 effect_index = effect.index,
314 percent = base_points * ranks,
315 running_direct = mods.direct,
316 "static passive direct-damage contribution"
317 );
318 }
319 (Some(ModifierOperation::Percent), Some(ModifierPropertyKind::PeriodicAmount)) => {
320 mods.periodic *= pct_to_mult(base_points * ranks);
321 tracing::trace!(
323 source_spell_id = aura.id,
324 source_spell_name = aura.name.as_str(),
325 effect_index = effect.index,
326 percent = base_points * ranks,
327 running_periodic = mods.periodic,
328 "static passive periodic-damage contribution"
329 );
330 }
331 (Some(ModifierOperation::Percent), Some(ModifierPropertyKind::CritDamage)) => {
332 crit_damage_mult *= pct_to_mult(base_points * ranks);
333 }
334 (Some(ModifierOperation::Flat), Some(ModifierPropertyKind::CritDamage)) => {
335 crit_damage_flat += base_points * ranks / HUNDRED;
336 }
337 (Some(ModifierOperation::Flat), Some(ModifierPropertyKind::CritChance)) => {
338 mods.crit_chance += base_points * ranks;
339 }
340 (Some(ModifierOperation::Percent), Some(ModifierPropertyKind::CritChance)) => {
341 mods.crit_chance_mult *= pct_to_mult(base_points * ranks);
342 }
343 _ => {}
344 }
345 }
346 }
347
348 mods.crit_damage_bonus = ((1.0 + crit_damage_flat) * crit_damage_mult - 1.0) * HUNDRED;
349
350 mods
351}
352
353#[must_use]
360pub fn is_target_damage_taken_debuff(spell: &SpellDataFlat) -> bool {
361 spell.effects.iter().any(|effect| {
362 aura_kind(effect) == Some(AuraSubtypeKind::ModDamageTakenPercent)
363 && effect_targets_current_enemy(effect)
364 })
365}
366
367fn effect_targets_current_enemy(effect: &wowlab_types::data::SpellEffect) -> bool {
368 crate::dbc::implicit_target_semantic(effect.implicit_target_a).is_some_and(|semantic| {
369 semantic.object == crate::dbc::ImplicitTargetObject::Unit
370 && semantic.reference == crate::dbc::ImplicitTargetReference::Target
371 && semantic.check == crate::dbc::ImplicitTargetCheck::Enemy
372 })
373}