1use wowlab_engine_domain::dbc::ModifierOperation;
4use wowlab_types::{combat::DamageSchool, constants::HUNDRED};
5
6use super::{
7 AbsorbEffect, ActiveSpellGate, DamageReplication, DamageSource, DamageTransfer,
8 HealthThresholdDirection,
9};
10
11fn stacked_percent_multiplier(percent: f64, stacks: f64) -> f64 {
12 ((1.0 + percent / HUNDRED).powf(stacks) - 1.0) * HUNDRED
13}
14
15fn modifier_property_family(
17 property: wowlab_engine_domain::dbc::ModifierPropertyKind,
18) -> EffectFamily {
19 use wowlab_engine_domain::dbc::ModifierPropertyKind as Property;
20
21 match property {
22 Property::GenericDamage => EffectFamily::MODIFIER_GENERIC_DAMAGE,
23 Property::Duration => EffectFamily::MODIFIER_DURATION,
24 Property::Threat => EffectFamily::MODIFIER_THREAT,
25 Property::Effect1 => EffectFamily::MODIFIER_EFFECT_1,
26 Property::ProcCharges => EffectFamily::MODIFIER_PROC_CHARGES,
27 Property::Range => EffectFamily::MODIFIER_RANGE,
28 Property::Radius => EffectFamily::MODIFIER_RADIUS,
29 Property::CritChance => EffectFamily::MODIFIER_CRITICAL_CHANCE,
30 Property::Points => EffectFamily::MODIFIER_POINTS,
31 Property::ResistPushback => EffectFamily::MODIFIER_PUSHBACK_RESISTANCE,
32 Property::CastTime => EffectFamily::MODIFIER_CAST_TIME,
33 Property::Cooldown => EffectFamily::MODIFIER_COOLDOWN,
34 Property::Effect2 => EffectFamily::MODIFIER_EFFECT_2,
35 Property::TargetResistance => EffectFamily::MODIFIER_TARGET_RESISTANCE,
36 Property::ResourceCost1 => EffectFamily::MODIFIER_RESOURCE_COST_1,
37 Property::CritDamage => EffectFamily::MODIFIER_CRITICAL_DAMAGE,
38 Property::HitChance => EffectFamily::MODIFIER_HIT_CHANCE,
39 Property::ChainTargets => EffectFamily::MODIFIER_CHAIN_TARGETS,
40 Property::ProcChance => EffectFamily::MODIFIER_PROC_CHANCE,
41 Property::TickTime => EffectFamily::MODIFIER_TICK_TIME,
42 Property::ChainMultiplier => EffectFamily::MODIFIER_CHAIN_MULTIPLIER,
43 Property::GlobalCooldown => EffectFamily::MODIFIER_GLOBAL_COOLDOWN,
44 Property::PeriodicAmount => EffectFamily::MODIFIER_PERIODIC_AMOUNT,
45 Property::Effect3 => EffectFamily::MODIFIER_EFFECT_3,
46 Property::Coefficient => EffectFamily::MODIFIER_COEFFICIENT,
47 Property::ProcFrequency => EffectFamily::MODIFIER_PROC_FREQUENCY,
48 Property::Amplitude => EffectFamily::MODIFIER_AMPLITUDE,
49 Property::DispelResistance => EffectFamily::MODIFIER_DISPEL_RESISTANCE,
50 Property::Doses => EffectFamily::MODIFIER_DOSES,
51 Property::Effect4 => EffectFamily::MODIFIER_EFFECT_4,
52 Property::Effect5 => EffectFamily::MODIFIER_EFFECT_5,
53 Property::ResourceCost2 => EffectFamily::MODIFIER_RESOURCE_COST_2,
54 Property::ChainTargetRange => EffectFamily::MODIFIER_CHAIN_TARGET_RANGE,
55 Property::MaxStacks => EffectFamily::MODIFIER_MAX_STACKS,
56 Property::ProcCooldown => EffectFamily::MODIFIER_PROC_COOLDOWN,
57 Property::ResourceCost3 => EffectFamily::MODIFIER_RESOURCE_COST_3,
58 Property::MaxTargets => EffectFamily::MODIFIER_MAX_TARGETS,
59 _ => EffectFamily::empty(),
60 }
61}
62
63fn targeting_property_family(
64 property: wowlab_engine_domain::dbc::ModifierPropertyKind,
65) -> EffectFamily {
66 use wowlab_engine_domain::dbc::ModifierPropertyKind as Property;
67
68 match property {
69 Property::Range => EffectFamily::TARGETING_RANGE,
70 Property::Radius => EffectFamily::TARGETING_RADIUS,
71 Property::ChainTargetRange => EffectFamily::TARGETING_CHAIN_RANGE,
72 Property::MaxTargets => EffectFamily::TARGETING_MAX_TARGETS,
73 _ => EffectFamily::empty(),
74 }
75}
76
77fn resource_cost_property_family(cost_entry: u8) -> EffectFamily {
78 match cost_entry {
79 0 => EffectFamily::MODIFIER_RESOURCE_COST_1,
80 1 => EffectFamily::MODIFIER_RESOURCE_COST_2,
81 2 => EffectFamily::MODIFIER_RESOURCE_COST_3,
82 _ => EffectFamily::empty(),
83 }
84}
85
86pub const MAX_AURA_BUFF_EFFECTS: usize = 16;
87
88bitflags::bitflags! {
89 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
91 pub(crate) struct EffectFamily: u128 {
92 const AUTO_ATTACK_REPLACEMENT = 1 << 0;
93 const GENERAL_TOTALS = 1 << 1;
94 const STATIC_TOTALS = 1 << 2;
95 const DYNAMIC_TOTALS = 1 << 3;
96 const SPELL_MODIFIER = 1 << 4;
97 const SCHOOL_OVERRIDE = 1 << 5;
98 const STANCE_EXCEPTION = 1 << 6;
99 const TARGETING = 1 << 7;
100 const TARGETING_RANGE = 1 << 8;
101 const TARGETING_RADIUS = 1 << 9;
102 const TARGETING_CHAIN_RANGE = 1 << 10;
103 const TARGETING_MAX_TARGETS = 1 << 11;
104 const COOLDOWN_RECHARGE_RATE = 1 << 12;
105 const COOLDOWN_DURATION = 1 << 13;
106 const MAXIMUM_CHARGES = 1 << 14;
107 const IGNORED_CHARGE_COOLDOWN = 1 << 15;
108 const RESOURCE_COST = 1 << 16;
109 const RESOURCE_REGENERATION = 1 << 17;
110 const RESOURCE_PROJECTION = 1 << 18;
111 const AURA_TIME_RATE = 1 << 19;
112 const HEALTH_DEPENDENT_CRITICAL_CHANCE = 1 << 20;
113 const SPELL_GATE = 1 << 21;
114 const DAMAGE_REPLICATION = 1 << 22;
115 const HEALTH_THRESHOLD_TRIGGER = 1 << 23;
116 const CAST_TIME = 1 << 24;
117 const GLOBAL_COOLDOWN = 1 << 25;
118 const AURA_DURATION = 1 << 26;
119 const PERIODIC_INTERVAL = 1 << 27;
120 const PUSHBACK_RESISTANCE = 1 << 28;
121 const ARMOR_BYPASS = 1 << 29;
122 const REFLECTION = 1 << 30;
123 const DEFLECTION = 1 << 31;
124 const PLAYER_ATTACK_SPEED = 1 << 32;
125 const PET_GUARDIAN_SPEED = 1 << 33;
126 const AUTO_ATTACK_DAMAGE = 1 << 34;
127 const AUTO_ATTACK_CRITICAL_CHANCE = 1 << 35;
128 const RESOURCE_GAIN = 1 << 36;
129 const STATS = 1 << 37;
130 const OUTGOING_DAMAGE = 1 << 38;
131 const INCOMING_DAMAGE = 1 << 39;
132 const HEALING = 1 << 40;
133 const ABSORB = 1 << 41;
134 const DAMAGE_TRANSFER = 1 << 42;
135 const IMMUNITY = 1 << 43;
136 const LEECH = 1 << 44;
137
138 const MODIFIER_GENERIC_DAMAGE = 1 << 48;
139 const MODIFIER_DURATION = 1 << 49;
140 const MODIFIER_THREAT = 1 << 50;
141 const MODIFIER_EFFECT_1 = 1 << 51;
142 const MODIFIER_PROC_CHARGES = 1 << 52;
143 const MODIFIER_RANGE = 1 << 53;
144 const MODIFIER_RADIUS = 1 << 54;
145 const MODIFIER_CRITICAL_CHANCE = 1 << 55;
146 const MODIFIER_POINTS = 1 << 56;
147 const MODIFIER_PUSHBACK_RESISTANCE = 1 << 57;
148 const MODIFIER_CAST_TIME = 1 << 58;
149 const MODIFIER_COOLDOWN = 1 << 59;
150 const MODIFIER_EFFECT_2 = 1 << 60;
151 const MODIFIER_TARGET_RESISTANCE = 1 << 61;
152 const MODIFIER_RESOURCE_COST_1 = 1 << 62;
153 const MODIFIER_CRITICAL_DAMAGE = 1 << 63;
154 const MODIFIER_HIT_CHANCE = 1 << 64;
155 const MODIFIER_CHAIN_TARGETS = 1 << 65;
156 const MODIFIER_PROC_CHANCE = 1 << 66;
157 const MODIFIER_TICK_TIME = 1 << 67;
158 const MODIFIER_CHAIN_MULTIPLIER = 1 << 68;
159 const MODIFIER_GLOBAL_COOLDOWN = 1 << 69;
160 const MODIFIER_PERIODIC_AMOUNT = 1 << 70;
161 const MODIFIER_EFFECT_3 = 1 << 71;
162 const MODIFIER_COEFFICIENT = 1 << 72;
163 const MODIFIER_PROC_FREQUENCY = 1 << 73;
164 const MODIFIER_AMPLITUDE = 1 << 74;
165 const MODIFIER_DISPEL_RESISTANCE = 1 << 75;
166 const MODIFIER_DOSES = 1 << 76;
167 const MODIFIER_EFFECT_4 = 1 << 77;
168 const MODIFIER_EFFECT_5 = 1 << 78;
169 const MODIFIER_RESOURCE_COST_2 = 1 << 79;
170 const MODIFIER_CHAIN_TARGET_RANGE = 1 << 80;
171 const MODIFIER_MAX_STACKS = 1 << 81;
172 const MODIFIER_PROC_COOLDOWN = 1 << 82;
173 const MODIFIER_RESOURCE_COST_3 = 1 << 83;
174 const MODIFIER_MAX_TARGETS = 1 << 84;
175 }
176}
177
178bitflags::bitflags! {
179 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
180 pub(crate) struct EffectConsumer: u16 {
181 const GENERAL_TOTALS = 1 << 0;
182 const DYNAMIC_TOTALS = 1 << 1;
183 const SPELL_MODIFIERS = 1 << 2;
184 const COSTS = 1 << 3;
185 const TIMING = 1 << 4;
186 const TARGETING = 1 << 5;
187 const GATES = 1 << 6;
188 const RECHARGE_CHARGES = 1 << 7;
189 const STATS = 1 << 8;
190 const SPEED_PROJECTION = 1 << 9;
191 const SNAPSHOT_TOTALS = 1 << 10;
192 }
193}
194
195#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
197pub(crate) struct EffectClassification {
198 families: EffectFamily,
199 consumers: EffectConsumer,
200}
201
202impl EffectClassification {
203 const fn new(families: EffectFamily, consumers: EffectConsumer) -> Self {
204 Self {
205 families,
206 consumers,
207 }
208 }
209
210 pub(crate) const fn families(self) -> EffectFamily {
211 self.families
212 }
213
214 pub(crate) const fn consumers(self) -> EffectConsumer {
215 self.consumers
216 }
217}
218
219#[derive(Clone, Copy, Debug)]
221pub struct BuffEffectsFull;
222
223pub fn try_push_effect(
229 effects: &mut [Option<BuffEffect>; MAX_AURA_BUFF_EFFECTS],
230 effect: BuffEffect,
231) -> Result<(), BuffEffectsFull> {
232 for slot in effects.iter_mut() {
233 if slot.is_none() {
234 *slot = Some(effect);
235
236 return Ok(());
237 }
238 }
239
240 Err(BuffEffectsFull)
241}
242
243#[derive(Clone, Copy, Debug)]
245#[non_exhaustive]
246pub enum BuffEffect {
247 Haste(f64),
248 HasteMult(f64),
250 HasteMultFromEffect {
252 percent: f64,
253 source_spell_id: u32,
254 effect_index: u8,
255 },
256 SpellCastSpeed(f64),
258 AttackSpeed(f64),
260 AutoAttackDamage(f64),
262 AutoAttackDamageTaken(f64),
264 AutoAttackCritChance(f64),
266 CritChanceVersusTargetHealth {
268 percent: f64,
269 threshold_pct: f64,
270 },
271 AutoAttackReplacement(u32),
273 HealthThresholdTrigger {
275 threshold_pct: f64,
276 direction: HealthThresholdDirection,
277 spell_id: u32,
278 scheduled: Option<wowlab_engine_ports::ScheduledSpellBinding>,
279 },
280 ArmorBypassFromCaster(f64),
282 SpellSchoolFromEffect {
284 school: DamageSchool,
285 source_spell_id: u32,
286 effect_index: u8,
287 },
288 AuraTimeRateFromEffect {
290 percent: f64,
291 source_spell_id: u32,
292 effect_index: u8,
293 },
294 SpellReflection(wowlab_engine_domain::dbc::SpellSchoolMask),
295 SpellDeflectChance(f64),
296 ResourceGainMult(f64),
298 RageGainMult(f64),
300 ResourceRegenPerFiveSeconds(f64, wowlab_types::combat::ResourceType),
302 IncomingDamagePercent {
304 percent: f64,
305 schools: wowlab_engine_domain::dbc::SpellSchoolMask,
306 },
307 IncomingAoeDamagePercent {
309 percent: f64,
310 schools: wowlab_engine_domain::dbc::SpellSchoolMask,
311 },
312 HealingDoneFlat {
314 amount: f64,
315 schools: wowlab_engine_domain::dbc::SpellSchoolMask,
316 },
317 HealingDonePercent(f64),
319 HealingReceivedPercent(f64),
321 HealingReceivedFromCasterPercent(f64),
323 PeriodicHealingPercent(f64),
325 LeechPercent(f64),
327 AbsorbDonePercent(f64),
329 AbsorbTakenPercent(f64),
331 Absorb(AbsorbEffect),
333 DamageTransfer(DamageTransfer),
335 SchoolImmunity(wowlab_engine_domain::dbc::SpellSchoolMask),
337 MechanicImmunity(u64),
339 InterruptRegen,
341 InterruptedRegenPercent(f64),
343 PreventResourceRegen(u64),
345 ResourceCostPercent(f64, wowlab_types::combat::ResourceType),
347 Crit(f64),
348 CritFromEffect {
350 percent: f64,
351 source_spell_id: u32,
352 effect_index: u8,
353 },
354 CritActive(f64),
356 Mastery(f64),
357 Versatility(f64),
358 PrimaryStat(f64),
359 PrimaryStatPercent(f64),
360 BonusStatPercent {
362 percent: f64,
363 stat: wowlab_engine_domain::dbc::StatModifierKind,
364 },
365 OverrideSpellPowerByAttackPowerPercent(f64),
367 OverrideAttackPowerBySpellPowerPercent(f64),
369 AttackPowerPercent(f64),
370 DamageMult(f64),
371 PetDamageMult(f64),
372 DynamicDamageMult(
373 fn(
374 &super::CombatProgram,
375 &super::CombatInstance,
376 Option<wowlab_types::sim::EnemyIdx>,
377 wowlab_types::sim::SimTime,
378 ) -> f64,
379 ),
380 DynamicDamageMultSpells(
381 fn(
382 &super::CombatProgram,
383 &super::CombatInstance,
384 Option<wowlab_types::sim::EnemyIdx>,
385 wowlab_types::sim::SimTime,
386 ) -> f64,
387 &'static [u32],
388 ),
389 DynamicRegenPercent(fn(&super::CombatProgram, &super::CombatInstance) -> f64),
390 DamageMultSchool(f64, DamageSchool),
391 DamageMultSpells(f64, &'static [u32]),
392 DamageMultSpellsActive(f64, &'static [u32]),
394 DamageToTargetAuraMechanicPercent {
396 percent: f64,
397 mechanic_mask: wowlab_engine_domain::dbc::MechanicMask,
398 },
399 MechanicDamageTakenPercent {
401 percent: f64,
402 mechanic_mask: wowlab_engine_domain::dbc::MechanicMask,
403 },
404 CreatureAoeDamageAvoidance {
406 percent: f64,
407 schools: wowlab_engine_domain::dbc::SpellSchoolMask,
408 },
409 SpellDamageFromEffect {
410 percent: f64,
411 source_spell_id: u32,
412 effect_index: u8,
413 periodic: bool,
414 },
415 SpellModifierFromEffect {
417 value: f64,
418 source_spell_id: u32,
419 effect_index: u8,
420 property: wowlab_engine_domain::dbc::ModifierPropertyKind,
421 operation: ModifierOperation,
422 },
423 SpellCritChanceFromEffect {
424 percent: f64,
425 source_spell_id: u32,
426 effect_index: u8,
427 },
428 SpellCritDamageFromEffect {
429 percent: f64,
430 source_spell_id: u32,
431 effect_index: u8,
432 },
433 SpellGateFromEffect {
434 source_spell_id: u32,
435 effect_index: u8,
436 gate: ActiveSpellGate,
437 },
438 SpellStanceMaskFromEffect {
440 source_spell_id: u32,
441 effect_index: u8,
442 },
443 DamageTakenFromSource(f64, DamageSource),
444 DamageTakenFromSourceSpells {
445 percent: f64,
446 source_spell_id: u32,
447 effect_index: u8,
448 source: DamageSource,
449 },
450 DamageMultSpellsLinear(f64, &'static [u32]),
451 CritChanceSpells(f64, &'static [u32]),
453 CritDamageSpells(f64, &'static [u32]),
455 SpellCostPercent(f64, &'static [u32], super::ResourcePool),
456 SpellCostFlat(f64, &'static [u32], super::ResourcePool),
457 SpellCostFromEffect {
458 value: f64,
459 source_spell_id: u32,
460 effect_index: u8,
461 cost_entry: u8,
462 operation: ModifierOperation,
463 },
464 AdditionalPowerCostFromEffect {
466 value: f64,
467 source_spell_id: u32,
468 effect_index: u8,
469 resource_type: i32,
470 },
471 RechargeRatePercent(f64, &'static [u32]),
472 CooldownRechargeRateFromEffect {
474 percent: f64,
475 source_spell_id: u32,
476 effect_index: u8,
477 },
478 ChargeRechargeRateCategory {
480 percent: f64,
481 category: i32,
482 source_spell_id: u32,
483 effect_index: u8,
484 },
485 MaxChargesCategory(f64, i32),
487 IgnoreChargeCooldownCategory(i32),
489 CooldownDurationPercent {
490 percent: f64,
491 source_spell_id: u32,
492 effect_index: u8,
493 },
494 SpellCastTimeFromEffect {
495 value: f64,
496 source_spell_id: u32,
497 effect_index: u8,
498 operation: ModifierOperation,
499 },
500 SpellGcdFromEffect {
501 value: f64,
502 source_spell_id: u32,
503 effect_index: u8,
504 operation: ModifierOperation,
505 },
506 SpellDurationFromEffect {
507 value: f64,
508 source_spell_id: u32,
509 effect_index: u8,
510 operation: ModifierOperation,
511 },
512 SpellTickTimeFromEffect {
513 value: f64,
514 source_spell_id: u32,
515 effect_index: u8,
516 operation: ModifierOperation,
517 },
518 CastWhileMovingFromEffect {
519 source_spell_id: u32,
520 effect_index: u8,
521 },
522 ResistPushbackFromEffect {
523 value: f64,
524 source_spell_id: u32,
525 effect_index: u8,
526 operation: ModifierOperation,
527 },
528 SpellTargetingFromEffect {
529 value: f64,
530 source_spell_id: u32,
531 effect_index: u8,
532 property: wowlab_engine_domain::dbc::ModifierPropertyKind,
533 operation: ModifierOperation,
534 },
535 CastTimePercent(f64, &'static [u32]),
537 CastTimePercentPerStack(f64, &'static [u32]),
538 GcdPercent(f64, &'static [u32]),
540 DamageReplication(DamageReplication),
541 DamageMultStacking {
542 initial: f64,
543 per_stack: f64,
544 },
545 RegenPercent(f64),
546 MaxResourceFlat(f64),
547}
548
549impl BuffEffect {
550 #[inline]
554 #[must_use]
555 pub fn dbc_source(&self) -> Option<(u32, u8)> {
556 match self {
557 Self::SpellSchoolFromEffect {
558 source_spell_id,
559 effect_index,
560 ..
561 }
562 | Self::AuraTimeRateFromEffect {
563 source_spell_id,
564 effect_index,
565 ..
566 }
567 | Self::HasteMultFromEffect {
568 source_spell_id,
569 effect_index,
570 ..
571 }
572 | Self::CritFromEffect {
573 source_spell_id,
574 effect_index,
575 ..
576 }
577 | Self::SpellDamageFromEffect {
578 source_spell_id,
579 effect_index,
580 ..
581 }
582 | Self::SpellModifierFromEffect {
583 source_spell_id,
584 effect_index,
585 ..
586 }
587 | Self::SpellCritChanceFromEffect {
588 source_spell_id,
589 effect_index,
590 ..
591 }
592 | Self::SpellCritDamageFromEffect {
593 source_spell_id,
594 effect_index,
595 ..
596 }
597 | Self::SpellGateFromEffect {
598 source_spell_id,
599 effect_index,
600 ..
601 }
602 | Self::SpellStanceMaskFromEffect {
603 source_spell_id,
604 effect_index,
605 ..
606 }
607 | Self::DamageTakenFromSourceSpells {
608 source_spell_id,
609 effect_index,
610 ..
611 }
612 | Self::SpellCostFromEffect {
613 source_spell_id,
614 effect_index,
615 ..
616 }
617 | Self::AdditionalPowerCostFromEffect {
618 source_spell_id,
619 effect_index,
620 ..
621 }
622 | Self::CooldownRechargeRateFromEffect {
623 source_spell_id,
624 effect_index,
625 ..
626 }
627 | Self::ChargeRechargeRateCategory {
628 source_spell_id,
629 effect_index,
630 ..
631 }
632 | Self::CooldownDurationPercent {
633 source_spell_id,
634 effect_index,
635 ..
636 }
637 | Self::SpellCastTimeFromEffect {
638 source_spell_id,
639 effect_index,
640 ..
641 }
642 | Self::SpellGcdFromEffect {
643 source_spell_id,
644 effect_index,
645 ..
646 }
647 | Self::SpellDurationFromEffect {
648 source_spell_id,
649 effect_index,
650 ..
651 }
652 | Self::SpellTickTimeFromEffect {
653 source_spell_id,
654 effect_index,
655 ..
656 }
657 | Self::CastWhileMovingFromEffect {
658 source_spell_id,
659 effect_index,
660 ..
661 }
662 | Self::ResistPushbackFromEffect {
663 source_spell_id,
664 effect_index,
665 ..
666 }
667 | Self::SpellTargetingFromEffect {
668 source_spell_id,
669 effect_index,
670 ..
671 } => Some((*source_spell_id, *effect_index)),
672 _ => None,
673 }
674 }
675
676 #[inline]
683 #[must_use]
686 pub fn scaled(self, stacks: f64) -> Self {
687 if let Some(scaled) = self.unscaled_dbc_effect() {
688 return scaled;
689 }
690 match self {
691 Self::Haste(v) => Self::Haste(v * stacks),
692 Self::HasteMult(v) => Self::HasteMult(v * stacks),
693 Self::HasteMultFromEffect {
694 percent,
695 source_spell_id,
696 effect_index,
697 } => Self::HasteMultFromEffect {
698 percent: percent * stacks,
699 source_spell_id,
700 effect_index,
701 },
702 Self::SpellCastSpeed(v) => Self::SpellCastSpeed(stacked_percent_multiplier(v, stacks)),
703 Self::AttackSpeed(v) => Self::AttackSpeed(v * stacks),
704 Self::AutoAttackDamage(v) => Self::AutoAttackDamage(v * stacks),
705 Self::AutoAttackDamageTaken(v) => Self::AutoAttackDamageTaken(v * stacks),
706 Self::AutoAttackCritChance(v) => Self::AutoAttackCritChance(v * stacks),
707 Self::CritChanceVersusTargetHealth {
708 percent,
709 threshold_pct,
710 } => Self::CritChanceVersusTargetHealth {
711 percent: percent * stacks,
712 threshold_pct,
713 },
714 Self::ArmorBypassFromCaster(v) => Self::ArmorBypassFromCaster(v * stacks),
715 Self::ResourceGainMult(v) => Self::ResourceGainMult(v * stacks),
716 Self::RageGainMult(v) => Self::RageGainMult(v * stacks),
717 Self::ResourceRegenPerFiveSeconds(value, resource_type) => {
718 Self::ResourceRegenPerFiveSeconds(value * stacks, resource_type)
719 }
720 Self::IncomingDamagePercent { percent, schools } => Self::IncomingDamagePercent {
721 percent: percent * stacks,
722 schools,
723 },
724 Self::IncomingAoeDamagePercent { percent, schools } => Self::IncomingAoeDamagePercent {
725 percent: percent * stacks,
726 schools,
727 },
728 Self::HealingDoneFlat { amount, schools } => Self::HealingDoneFlat {
729 amount: amount * stacks,
730 schools,
731 },
732 Self::HealingDonePercent(v) => Self::HealingDonePercent(v * stacks),
733 Self::HealingReceivedPercent(v) => Self::HealingReceivedPercent(v * stacks),
734 Self::HealingReceivedFromCasterPercent(v) => {
735 Self::HealingReceivedFromCasterPercent(v * stacks)
736 }
737 Self::PeriodicHealingPercent(v) => Self::PeriodicHealingPercent(v * stacks),
738 Self::LeechPercent(v) => Self::LeechPercent(v * stacks),
739 Self::AbsorbDonePercent(v) => Self::AbsorbDonePercent(v * stacks),
740 Self::AbsorbTakenPercent(v) => Self::AbsorbTakenPercent(v * stacks),
741 Self::Absorb(effect) => Self::Absorb(AbsorbEffect {
742 amount: effect.amount * stacks,
743 ..effect
744 }),
745 Self::DamageTransfer(transfer) => Self::DamageTransfer(transfer),
746 Self::SchoolImmunity(schools) => Self::SchoolImmunity(schools),
747 Self::MechanicImmunity(mask) => Self::MechanicImmunity(mask),
748 Self::InterruptRegen => Self::InterruptRegen,
749 Self::InterruptedRegenPercent(percent) => {
750 Self::InterruptedRegenPercent(percent * stacks)
751 }
752 Self::PreventResourceRegen(mask) => Self::PreventResourceRegen(mask),
753 Self::ResourceCostPercent(value, resource_type) => {
754 Self::ResourceCostPercent(value * stacks, resource_type)
755 }
756 Self::Crit(v) => Self::Crit(v * stacks),
757 Self::CritFromEffect {
758 percent,
759 source_spell_id,
760 effect_index,
761 } => Self::CritFromEffect {
762 percent: percent * stacks,
763 source_spell_id,
764 effect_index,
765 },
766 Self::CritActive(v) => Self::CritActive(v),
767 Self::Mastery(v) => Self::Mastery(v * stacks),
768 Self::Versatility(v) => Self::Versatility(v * stacks),
769 Self::PrimaryStat(v) => Self::PrimaryStat(v * stacks),
770 Self::PrimaryStatPercent(v) => Self::PrimaryStatPercent(v * stacks),
771 Self::BonusStatPercent { percent, stat } => Self::BonusStatPercent {
772 percent: percent * stacks,
773 stat,
774 },
775 Self::OverrideSpellPowerByAttackPowerPercent(v) => {
776 Self::OverrideSpellPowerByAttackPowerPercent(v * stacks)
777 }
778 Self::OverrideAttackPowerBySpellPowerPercent(v) => {
779 Self::OverrideAttackPowerBySpellPowerPercent(v * stacks)
780 }
781 Self::AttackPowerPercent(v) => Self::AttackPowerPercent(v * stacks),
782 Self::DamageMult(v) => Self::DamageMult(v.powf(stacks)),
783 Self::PetDamageMult(v) => Self::PetDamageMult(v.powf(stacks)),
784 Self::DynamicDamageMult(resolve) => Self::DynamicDamageMult(resolve),
785 Self::DynamicDamageMultSpells(r, s) => Self::DynamicDamageMultSpells(r, s),
786 Self::DynamicRegenPercent(resolve) => Self::DynamicRegenPercent(resolve),
787 Self::DamageMultSchool(v, school) => Self::DamageMultSchool(v.powf(stacks), school),
788 Self::DamageMultSpells(v, spells) => Self::DamageMultSpells(v.powf(stacks), spells),
789 Self::DamageMultSpellsActive(v, spells) => Self::DamageMultSpellsActive(v, spells),
790 Self::DamageToTargetAuraMechanicPercent {
791 percent,
792 mechanic_mask,
793 } => Self::DamageToTargetAuraMechanicPercent {
794 percent: percent * stacks,
795 mechanic_mask,
796 },
797 Self::MechanicDamageTakenPercent {
798 percent,
799 mechanic_mask,
800 } => Self::MechanicDamageTakenPercent {
801 percent: percent * stacks,
802 mechanic_mask,
803 },
804 Self::CreatureAoeDamageAvoidance { percent, schools } => {
805 Self::CreatureAoeDamageAvoidance {
806 percent: percent * stacks,
807 schools,
808 }
809 }
810 Self::DamageTakenFromSource(v, source) => {
811 Self::DamageTakenFromSource(v * stacks, source)
812 }
813 Self::DamageMultSpellsLinear(v, spells) => {
814 Self::DamageMultSpellsLinear(v * stacks, spells)
815 }
816 Self::CritChanceSpells(v, spells) => Self::CritChanceSpells(v * stacks, spells),
817 Self::CritDamageSpells(v, spells) => Self::CritDamageSpells(v * stacks, spells),
818 Self::SpellCostPercent(v, spells, pool) => {
819 Self::SpellCostPercent(v * stacks, spells, pool)
820 }
821 Self::SpellCostFlat(v, spells, pool) => Self::SpellCostFlat(v * stacks, spells, pool),
822 Self::SpellCostFromEffect {
823 value,
824 source_spell_id,
825 effect_index,
826 cost_entry,
827 operation,
828 } => Self::SpellCostFromEffect {
829 value,
830 source_spell_id,
831 effect_index,
832 cost_entry,
833 operation,
834 },
835 Self::AdditionalPowerCostFromEffect {
836 value,
837 source_spell_id,
838 effect_index,
839 resource_type,
840 } => Self::AdditionalPowerCostFromEffect {
841 value,
842 source_spell_id,
843 effect_index,
844 resource_type,
845 },
846 Self::RechargeRatePercent(v, spells) => Self::RechargeRatePercent(v * stacks, spells),
847 Self::CooldownRechargeRateFromEffect {
848 percent,
849 source_spell_id,
850 effect_index,
851 } => Self::CooldownRechargeRateFromEffect {
852 percent,
853 source_spell_id,
854 effect_index,
855 },
856 Self::ChargeRechargeRateCategory {
857 percent,
858 category,
859 source_spell_id,
860 effect_index,
861 } => Self::ChargeRechargeRateCategory {
862 percent,
863 category,
864 source_spell_id,
865 effect_index,
866 },
867 Self::MaxChargesCategory(v, category) => Self::MaxChargesCategory(v * stacks, category),
868 Self::IgnoreChargeCooldownCategory(category) => {
869 Self::IgnoreChargeCooldownCategory(category)
870 }
871 Self::CooldownDurationPercent {
872 percent,
873 source_spell_id,
874 effect_index,
875 } => Self::CooldownDurationPercent {
876 percent,
877 source_spell_id,
878 effect_index,
879 },
880 Self::SpellCastTimeFromEffect { .. }
881 | Self::SpellGcdFromEffect { .. }
882 | Self::SpellDurationFromEffect { .. }
883 | Self::SpellTickTimeFromEffect { .. }
884 | Self::SpellModifierFromEffect { .. }
885 | Self::SpellTargetingFromEffect { .. }
886 | Self::SpellSchoolFromEffect { .. }
887 | Self::AuraTimeRateFromEffect { .. }
888 | Self::SpellReflection(..)
889 | Self::AutoAttackReplacement(..)
890 | Self::HealthThresholdTrigger { .. }
891 | Self::SpellDamageFromEffect { .. }
892 | Self::SpellCritChanceFromEffect { .. }
893 | Self::SpellCritDamageFromEffect { .. }
894 | Self::DamageTakenFromSourceSpells { .. }
895 | Self::SpellGateFromEffect { .. }
896 | Self::SpellStanceMaskFromEffect { .. }
897 | Self::CastWhileMovingFromEffect { .. }
898 | Self::ResistPushbackFromEffect { .. } => unreachable!("handled above"),
899 Self::CastTimePercent(v, spells) => Self::CastTimePercent(v, spells),
900 Self::CastTimePercentPerStack(v, spells) => {
901 Self::CastTimePercentPerStack(v * stacks, spells)
902 }
903 Self::GcdPercent(v, spells) => Self::GcdPercent(v, spells),
904 Self::DamageReplication(replication) => Self::DamageReplication(replication),
905 Self::DamageMultStacking { initial, per_stack } => {
906 Self::DamageMult(1.0 + initial + per_stack * (stacks - 1.0))
907 }
908 Self::RegenPercent(v) => Self::RegenPercent(v * stacks),
909 Self::MaxResourceFlat(v) => Self::MaxResourceFlat(v * stacks),
910 Self::SpellDeflectChance(v) => Self::SpellDeflectChance(v * stacks),
911 }
912 }
913 #[must_use]
917 pub(crate) const fn listed_spell_ids(&self) -> Option<&'static [u32]> {
918 match self {
919 Self::DynamicDamageMultSpells(_, spells)
920 | Self::DamageMultSpells(_, spells)
921 | Self::DamageMultSpellsActive(_, spells)
922 | Self::DamageMultSpellsLinear(_, spells)
923 | Self::CritChanceSpells(_, spells)
924 | Self::CritDamageSpells(_, spells)
925 | Self::SpellCostPercent(_, spells, _)
926 | Self::SpellCostFlat(_, spells, _)
927 | Self::RechargeRatePercent(_, spells)
928 | Self::CastTimePercent(_, spells)
929 | Self::CastTimePercentPerStack(_, spells)
930 | Self::GcdPercent(_, spells) => Some(spells),
931 _ => None,
932 }
933 }
934 #[must_use]
939 pub(crate) fn classification(&self) -> EffectClassification {
940 use EffectConsumer as Consumer;
941 use EffectFamily as Family;
942
943 let static_total = Consumer::GENERAL_TOTALS;
944 let stat_total = static_total | Consumer::STATS;
945 let speed_total = stat_total | Consumer::SPEED_PROJECTION;
946 let spell_modifier = Consumer::SPELL_MODIFIERS;
947 let timing = Consumer::TIMING;
948 let recharge = Consumer::RECHARGE_CHARGES;
949
950 match self {
951 Self::Haste(..) | Self::HasteMult(..) | Self::HasteMultFromEffect { .. } => {
952 EffectClassification::new(
953 Family::GENERAL_TOTALS
954 | Family::STATIC_TOTALS
955 | Family::STATS
956 | Family::PLAYER_ATTACK_SPEED
957 | Family::PET_GUARDIAN_SPEED,
958 speed_total,
959 )
960 }
961 Self::SpellCastSpeed(..) => EffectClassification::new(
962 Family::GENERAL_TOTALS
963 | Family::STATIC_TOTALS
964 | Family::STATS
965 | Family::PET_GUARDIAN_SPEED,
966 speed_total,
967 ),
968 Self::AttackSpeed(..) => EffectClassification::new(
969 Family::GENERAL_TOTALS
970 | Family::STATIC_TOTALS
971 | Family::PLAYER_ATTACK_SPEED
972 | Family::PET_GUARDIAN_SPEED,
973 static_total | Consumer::SPEED_PROJECTION,
974 ),
975 Self::AutoAttackDamage(..) => EffectClassification::new(
976 Family::GENERAL_TOTALS | Family::STATIC_TOTALS | Family::AUTO_ATTACK_DAMAGE,
977 static_total,
978 ),
979 Self::AutoAttackCritChance(..) => EffectClassification::new(
980 Family::GENERAL_TOTALS
981 | Family::STATIC_TOTALS
982 | Family::AUTO_ATTACK_CRITICAL_CHANCE,
983 static_total,
984 ),
985 Self::CritChanceVersusTargetHealth { .. } => EffectClassification::new(
986 Family::HEALTH_DEPENDENT_CRITICAL_CHANCE | Family::SPELL_MODIFIER,
987 spell_modifier,
988 ),
989 Self::AutoAttackReplacement(..) => {
990 EffectClassification::new(Family::AUTO_ATTACK_REPLACEMENT, EffectConsumer::empty())
991 }
992 Self::HealthThresholdTrigger { .. } => {
993 EffectClassification::new(Family::HEALTH_THRESHOLD_TRIGGER, EffectConsumer::empty())
994 }
995 Self::ArmorBypassFromCaster(..) => EffectClassification::new(
996 Family::ARMOR_BYPASS | Family::SPELL_MODIFIER,
997 spell_modifier,
998 ),
999 Self::SpellSchoolFromEffect { .. } => EffectClassification::new(
1000 Family::SCHOOL_OVERRIDE | Family::SPELL_MODIFIER,
1001 spell_modifier,
1002 ),
1003 Self::AuraTimeRateFromEffect { .. } => {
1004 EffectClassification::new(Family::AURA_TIME_RATE, timing)
1005 }
1006 Self::SpellReflection(..) => {
1007 EffectClassification::new(Family::REFLECTION, EffectConsumer::empty())
1008 }
1009 Self::SpellDeflectChance(..) => {
1010 EffectClassification::new(Family::DEFLECTION, EffectConsumer::empty())
1011 }
1012 Self::ResourceGainMult(..) | Self::RageGainMult(..) => EffectClassification::new(
1013 Family::GENERAL_TOTALS | Family::STATIC_TOTALS | Family::RESOURCE_GAIN,
1014 static_total,
1015 ),
1016 Self::ResourceRegenPerFiveSeconds(..)
1017 | Self::InterruptedRegenPercent(..)
1018 | Self::PreventResourceRegen(..)
1019 | Self::InterruptRegen => {
1020 EffectClassification::new(Family::RESOURCE_REGENERATION, EffectConsumer::empty())
1021 }
1022 Self::AutoAttackDamageTaken(..) => EffectClassification::new(
1023 Family::GENERAL_TOTALS
1024 | Family::STATIC_TOTALS
1025 | Family::AUTO_ATTACK_DAMAGE
1026 | Family::INCOMING_DAMAGE,
1027 static_total,
1028 ),
1029 Self::IncomingDamagePercent { .. }
1030 | Self::IncomingAoeDamagePercent { .. }
1031 | Self::DamageTakenFromSource(..)
1032 | Self::DamageTakenFromSourceSpells { .. }
1033 | Self::MechanicDamageTakenPercent { .. }
1034 | Self::CreatureAoeDamageAvoidance { .. } => EffectClassification::new(
1035 Family::INCOMING_DAMAGE | Family::SPELL_MODIFIER,
1036 spell_modifier,
1037 ),
1038 Self::HealingDoneFlat { .. }
1039 | Self::HealingDonePercent(..)
1040 | Self::HealingReceivedPercent(..)
1041 | Self::HealingReceivedFromCasterPercent(..)
1042 | Self::PeriodicHealingPercent(..) => {
1043 EffectClassification::new(Family::HEALING, EffectConsumer::empty())
1044 }
1045 Self::LeechPercent(..) => {
1046 EffectClassification::new(Family::LEECH, EffectConsumer::empty())
1047 }
1048 Self::AbsorbDonePercent(..) | Self::AbsorbTakenPercent(..) | Self::Absorb(..) => {
1049 EffectClassification::new(Family::ABSORB, EffectConsumer::empty())
1050 }
1051 Self::DamageTransfer(..) => {
1052 EffectClassification::new(Family::DAMAGE_TRANSFER, EffectConsumer::empty())
1053 }
1054 Self::SchoolImmunity(..) | Self::MechanicImmunity(..) => {
1055 EffectClassification::new(Family::IMMUNITY, EffectConsumer::empty())
1056 }
1057 Self::ResourceCostPercent(..) => {
1058 EffectClassification::new(Family::RESOURCE_COST, Consumer::COSTS)
1059 }
1060 Self::Crit(..)
1061 | Self::CritFromEffect { .. }
1062 | Self::CritActive(..)
1063 | Self::Mastery(..)
1064 | Self::Versatility(..)
1065 | Self::PrimaryStat(..)
1066 | Self::PrimaryStatPercent(..)
1067 | Self::BonusStatPercent { .. }
1068 | Self::OverrideSpellPowerByAttackPowerPercent(..)
1069 | Self::OverrideAttackPowerBySpellPowerPercent(..)
1070 | Self::AttackPowerPercent(..) => EffectClassification::new(
1071 Family::GENERAL_TOTALS | Family::STATIC_TOTALS | Family::STATS,
1072 stat_total,
1073 ),
1074 Self::DamageMult(..) | Self::PetDamageMult(..) | Self::DamageMultSchool(..) => {
1075 EffectClassification::new(
1076 Family::GENERAL_TOTALS | Family::STATIC_TOTALS | Family::OUTGOING_DAMAGE,
1077 static_total,
1078 )
1079 }
1080 Self::DynamicDamageMult(..) | Self::DynamicRegenPercent(..) => {
1081 EffectClassification::new(
1082 Family::GENERAL_TOTALS | Family::DYNAMIC_TOTALS | Family::OUTGOING_DAMAGE,
1083 Consumer::GENERAL_TOTALS | Consumer::DYNAMIC_TOTALS | Consumer::SNAPSHOT_TOTALS,
1084 )
1085 }
1086 Self::DynamicDamageMultSpells(..)
1087 | Self::DamageMultSpells(..)
1088 | Self::DamageMultSpellsActive(..)
1089 | Self::DamageToTargetAuraMechanicPercent { .. }
1090 | Self::DamageMultSpellsLinear(..)
1091 | Self::CritChanceSpells(..)
1092 | Self::CritDamageSpells(..) => EffectClassification::new(
1093 Family::SPELL_MODIFIER | Family::OUTGOING_DAMAGE,
1094 spell_modifier,
1095 ),
1096 Self::SpellDamageFromEffect { periodic, .. } => EffectClassification::new(
1097 Family::SPELL_MODIFIER
1098 | Family::OUTGOING_DAMAGE
1099 | if *periodic {
1100 Family::MODIFIER_PERIODIC_AMOUNT
1101 } else {
1102 Family::MODIFIER_GENERIC_DAMAGE
1103 },
1104 spell_modifier,
1105 ),
1106 Self::SpellModifierFromEffect { property, .. } => EffectClassification::new(
1107 Family::SPELL_MODIFIER | modifier_property_family(*property),
1108 spell_modifier,
1109 ),
1110 Self::SpellCritChanceFromEffect { .. } => EffectClassification::new(
1111 Family::SPELL_MODIFIER | Family::MODIFIER_CRITICAL_CHANCE,
1112 spell_modifier,
1113 ),
1114 Self::SpellCritDamageFromEffect { .. } => EffectClassification::new(
1115 Family::SPELL_MODIFIER | Family::MODIFIER_CRITICAL_DAMAGE,
1116 spell_modifier,
1117 ),
1118 Self::SpellGateFromEffect { .. } => {
1119 EffectClassification::new(Family::SPELL_GATE, Consumer::GATES)
1120 }
1121 Self::SpellStanceMaskFromEffect { .. } => EffectClassification::new(
1122 Family::STANCE_EXCEPTION | Family::SPELL_GATE,
1123 Consumer::GATES,
1124 ),
1125 Self::SpellCostPercent(..)
1126 | Self::SpellCostFlat(..)
1127 | Self::AdditionalPowerCostFromEffect { .. } => EffectClassification::new(
1128 Family::RESOURCE_COST | Family::SPELL_MODIFIER,
1129 Consumer::COSTS,
1130 ),
1131 Self::SpellCostFromEffect { cost_entry, .. } => EffectClassification::new(
1132 Family::RESOURCE_COST
1133 | Family::SPELL_MODIFIER
1134 | resource_cost_property_family(*cost_entry),
1135 Consumer::COSTS,
1136 ),
1137 Self::RechargeRatePercent(..)
1138 | Self::CooldownRechargeRateFromEffect { .. }
1139 | Self::ChargeRechargeRateCategory { .. } => {
1140 EffectClassification::new(Family::COOLDOWN_RECHARGE_RATE, recharge)
1141 }
1142 Self::MaxChargesCategory(..) => {
1143 EffectClassification::new(Family::MAXIMUM_CHARGES, recharge)
1144 }
1145 Self::IgnoreChargeCooldownCategory(..) => {
1146 EffectClassification::new(Family::IGNORED_CHARGE_COOLDOWN, recharge)
1147 }
1148 Self::CooldownDurationPercent { .. } => EffectClassification::new(
1149 Family::COOLDOWN_DURATION | Family::MODIFIER_COOLDOWN,
1150 timing | recharge,
1151 ),
1152 Self::SpellCastTimeFromEffect { .. } => EffectClassification::new(
1153 Family::SPELL_MODIFIER | Family::CAST_TIME | Family::MODIFIER_CAST_TIME,
1154 timing | spell_modifier,
1155 ),
1156 Self::SpellGcdFromEffect { .. } => EffectClassification::new(
1157 Family::SPELL_MODIFIER | Family::GLOBAL_COOLDOWN | Family::MODIFIER_GLOBAL_COOLDOWN,
1158 timing | spell_modifier,
1159 ),
1160 Self::SpellDurationFromEffect { .. } => EffectClassification::new(
1161 Family::SPELL_MODIFIER | Family::AURA_DURATION | Family::MODIFIER_DURATION,
1162 timing | spell_modifier,
1163 ),
1164 Self::SpellTickTimeFromEffect { .. } => EffectClassification::new(
1165 Family::SPELL_MODIFIER | Family::PERIODIC_INTERVAL | Family::MODIFIER_TICK_TIME,
1166 timing | spell_modifier,
1167 ),
1168 Self::CastWhileMovingFromEffect { .. } => EffectClassification::new(
1169 Family::SPELL_GATE | Family::SPELL_MODIFIER,
1170 Consumer::GATES | spell_modifier,
1171 ),
1172 Self::ResistPushbackFromEffect { .. } => EffectClassification::new(
1173 Family::SPELL_MODIFIER
1174 | Family::PUSHBACK_RESISTANCE
1175 | Family::MODIFIER_PUSHBACK_RESISTANCE,
1176 timing | spell_modifier,
1177 ),
1178 Self::SpellTargetingFromEffect { property, .. } => EffectClassification::new(
1179 Family::SPELL_MODIFIER
1180 | Family::TARGETING
1181 | modifier_property_family(*property)
1182 | targeting_property_family(*property),
1183 Consumer::TARGETING | spell_modifier,
1184 ),
1185 Self::CastTimePercent(..) | Self::CastTimePercentPerStack(..) => {
1186 EffectClassification::new(Family::CAST_TIME, timing)
1187 }
1188 Self::GcdPercent(..) => EffectClassification::new(Family::GLOBAL_COOLDOWN, timing),
1189 Self::DamageReplication(..) => {
1190 EffectClassification::new(Family::DAMAGE_REPLICATION, EffectConsumer::empty())
1191 }
1192 Self::DamageMultStacking { .. } => EffectClassification::new(
1193 Family::GENERAL_TOTALS | Family::STATIC_TOTALS | Family::OUTGOING_DAMAGE,
1194 static_total,
1195 ),
1196 Self::RegenPercent(..) => EffectClassification::new(
1197 Family::GENERAL_TOTALS | Family::STATIC_TOTALS | Family::RESOURCE_REGENERATION,
1198 static_total,
1199 ),
1200 Self::MaxResourceFlat(..) => EffectClassification::new(
1201 Family::GENERAL_TOTALS | Family::STATIC_TOTALS | Family::RESOURCE_PROJECTION,
1202 static_total,
1203 ),
1204 }
1205 }
1206
1207 fn unscaled_dbc_effect(self) -> Option<Self> {
1209 let scaled = match self {
1210 Self::SpellCastTimeFromEffect {
1211 value,
1212 source_spell_id,
1213 effect_index,
1214 operation,
1215 } => Self::SpellCastTimeFromEffect {
1216 value,
1217 source_spell_id,
1218 effect_index,
1219 operation,
1220 },
1221 Self::SpellGcdFromEffect {
1222 value,
1223 source_spell_id,
1224 effect_index,
1225 operation,
1226 } => Self::SpellGcdFromEffect {
1227 value,
1228 source_spell_id,
1229 effect_index,
1230 operation,
1231 },
1232 Self::SpellDurationFromEffect {
1233 value,
1234 source_spell_id,
1235 effect_index,
1236 operation,
1237 } => Self::SpellDurationFromEffect {
1238 value,
1239 source_spell_id,
1240 effect_index,
1241 operation,
1242 },
1243 Self::SpellTickTimeFromEffect {
1244 value,
1245 source_spell_id,
1246 effect_index,
1247 operation,
1248 } => Self::SpellTickTimeFromEffect {
1249 value,
1250 source_spell_id,
1251 effect_index,
1252 operation,
1253 },
1254 Self::CastWhileMovingFromEffect {
1255 source_spell_id,
1256 effect_index,
1257 } => Self::CastWhileMovingFromEffect {
1258 source_spell_id,
1259 effect_index,
1260 },
1261 Self::ResistPushbackFromEffect {
1262 value,
1263 source_spell_id,
1264 effect_index,
1265 operation,
1266 } => Self::ResistPushbackFromEffect {
1267 value,
1268 source_spell_id,
1269 effect_index,
1270 operation,
1271 },
1272 Self::SpellGateFromEffect {
1273 source_spell_id,
1274 effect_index,
1275 gate,
1276 } => Self::SpellGateFromEffect {
1277 source_spell_id,
1278 effect_index,
1279 gate,
1280 },
1281 Self::SpellStanceMaskFromEffect {
1282 source_spell_id,
1283 effect_index,
1284 } => Self::SpellStanceMaskFromEffect {
1285 source_spell_id,
1286 effect_index,
1287 },
1288 Self::SpellModifierFromEffect {
1289 value,
1290 source_spell_id,
1291 effect_index,
1292 property,
1293 operation,
1294 } => Self::SpellModifierFromEffect {
1295 value,
1296 source_spell_id,
1297 effect_index,
1298 property,
1299 operation,
1300 },
1301 Self::SpellTargetingFromEffect {
1302 value,
1303 source_spell_id,
1304 effect_index,
1305 property,
1306 operation,
1307 } => Self::SpellTargetingFromEffect {
1308 value,
1309 source_spell_id,
1310 effect_index,
1311 property,
1312 operation,
1313 },
1314 Self::SpellSchoolFromEffect {
1315 school,
1316 source_spell_id,
1317 effect_index,
1318 } => Self::SpellSchoolFromEffect {
1319 school,
1320 source_spell_id,
1321 effect_index,
1322 },
1323 Self::AuraTimeRateFromEffect {
1324 percent,
1325 source_spell_id,
1326 effect_index,
1327 } => Self::AuraTimeRateFromEffect {
1328 percent,
1329 source_spell_id,
1330 effect_index,
1331 },
1332 Self::SpellReflection(schools) => Self::SpellReflection(schools),
1333 Self::AutoAttackReplacement(spell_id) => Self::AutoAttackReplacement(spell_id),
1334 Self::HealthThresholdTrigger {
1335 threshold_pct,
1336 direction,
1337 spell_id,
1338 scheduled,
1339 } => Self::HealthThresholdTrigger {
1340 threshold_pct,
1341 direction,
1342 spell_id,
1343 scheduled,
1344 },
1345 effect @ (Self::SpellDamageFromEffect { .. }
1346 | Self::SpellCritChanceFromEffect { .. }
1347 | Self::SpellCritDamageFromEffect { .. }
1348 | Self::DamageTakenFromSourceSpells { .. }) => effect,
1349 _ => return None,
1350 };
1351
1352 Some(scaled)
1353 }
1354}
1355
1356#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1357#[cfg(test)]
1358enum ReferenceTotalsRelevance {
1359 None,
1360 Static,
1361 Snapshot,
1362}
1363
1364#[cfg(test)]
1366fn reference_recompute_totals_relevance(effect: &BuffEffect) -> ReferenceTotalsRelevance {
1367 use ReferenceTotalsRelevance::{None, Snapshot, Static};
1368
1369 match effect {
1370 BuffEffect::Haste(..)
1371 | BuffEffect::HasteMult(..)
1372 | BuffEffect::HasteMultFromEffect { .. }
1373 | BuffEffect::SpellCastSpeed(..)
1374 | BuffEffect::AttackSpeed(..)
1375 | BuffEffect::AutoAttackDamage(..)
1376 | BuffEffect::AutoAttackDamageTaken(..)
1377 | BuffEffect::AutoAttackCritChance(..)
1378 | BuffEffect::ResourceGainMult(..)
1379 | BuffEffect::RageGainMult(..)
1380 | BuffEffect::Crit(..)
1381 | BuffEffect::CritFromEffect { .. }
1382 | BuffEffect::CritActive(..)
1383 | BuffEffect::Mastery(..)
1384 | BuffEffect::Versatility(..)
1385 | BuffEffect::PrimaryStat(..)
1386 | BuffEffect::PrimaryStatPercent(..)
1387 | BuffEffect::BonusStatPercent { .. }
1388 | BuffEffect::OverrideSpellPowerByAttackPowerPercent(..)
1389 | BuffEffect::OverrideAttackPowerBySpellPowerPercent(..)
1390 | BuffEffect::AttackPowerPercent(..)
1391 | BuffEffect::DamageMult(..)
1392 | BuffEffect::PetDamageMult(..)
1393 | BuffEffect::DamageMultSchool(..)
1394 | BuffEffect::DamageMultStacking { .. }
1395 | BuffEffect::RegenPercent(..)
1396 | BuffEffect::MaxResourceFlat(..) => Static,
1397 BuffEffect::DynamicDamageMult(..) | BuffEffect::DynamicRegenPercent(..) => Snapshot,
1398 BuffEffect::CritChanceVersusTargetHealth { .. }
1399 | BuffEffect::AutoAttackReplacement(..)
1400 | BuffEffect::HealthThresholdTrigger { .. }
1401 | BuffEffect::ArmorBypassFromCaster(..)
1402 | BuffEffect::SpellSchoolFromEffect { .. }
1403 | BuffEffect::AuraTimeRateFromEffect { .. }
1404 | BuffEffect::SpellReflection(..)
1405 | BuffEffect::SpellDeflectChance(..)
1406 | BuffEffect::ResourceRegenPerFiveSeconds(..)
1407 | BuffEffect::IncomingDamagePercent { .. }
1408 | BuffEffect::IncomingAoeDamagePercent { .. }
1409 | BuffEffect::HealingDoneFlat { .. }
1410 | BuffEffect::HealingDonePercent(..)
1411 | BuffEffect::HealingReceivedPercent(..)
1412 | BuffEffect::HealingReceivedFromCasterPercent(..)
1413 | BuffEffect::PeriodicHealingPercent(..)
1414 | BuffEffect::LeechPercent(..)
1415 | BuffEffect::AbsorbDonePercent(..)
1416 | BuffEffect::AbsorbTakenPercent(..)
1417 | BuffEffect::Absorb(..)
1418 | BuffEffect::DamageTransfer(..)
1419 | BuffEffect::SchoolImmunity(..)
1420 | BuffEffect::MechanicImmunity(..)
1421 | BuffEffect::InterruptRegen
1422 | BuffEffect::InterruptedRegenPercent(..)
1423 | BuffEffect::PreventResourceRegen(..)
1424 | BuffEffect::ResourceCostPercent(..)
1425 | BuffEffect::DynamicDamageMultSpells(..)
1426 | BuffEffect::DamageMultSpells(..)
1427 | BuffEffect::DamageMultSpellsActive(..)
1428 | BuffEffect::DamageToTargetAuraMechanicPercent { .. }
1429 | BuffEffect::MechanicDamageTakenPercent { .. }
1430 | BuffEffect::CreatureAoeDamageAvoidance { .. }
1431 | BuffEffect::SpellDamageFromEffect { .. }
1432 | BuffEffect::SpellModifierFromEffect { .. }
1433 | BuffEffect::SpellCritChanceFromEffect { .. }
1434 | BuffEffect::SpellCritDamageFromEffect { .. }
1435 | BuffEffect::SpellGateFromEffect { .. }
1436 | BuffEffect::SpellStanceMaskFromEffect { .. }
1437 | BuffEffect::DamageTakenFromSource(..)
1438 | BuffEffect::DamageTakenFromSourceSpells { .. }
1439 | BuffEffect::DamageMultSpellsLinear(..)
1440 | BuffEffect::CritChanceSpells(..)
1441 | BuffEffect::CritDamageSpells(..)
1442 | BuffEffect::SpellCostPercent(..)
1443 | BuffEffect::SpellCostFlat(..)
1444 | BuffEffect::SpellCostFromEffect { .. }
1445 | BuffEffect::AdditionalPowerCostFromEffect { .. }
1446 | BuffEffect::RechargeRatePercent(..)
1447 | BuffEffect::CooldownRechargeRateFromEffect { .. }
1448 | BuffEffect::ChargeRechargeRateCategory { .. }
1449 | BuffEffect::MaxChargesCategory(..)
1450 | BuffEffect::IgnoreChargeCooldownCategory(..)
1451 | BuffEffect::CooldownDurationPercent { .. }
1452 | BuffEffect::SpellCastTimeFromEffect { .. }
1453 | BuffEffect::SpellGcdFromEffect { .. }
1454 | BuffEffect::SpellDurationFromEffect { .. }
1455 | BuffEffect::SpellTickTimeFromEffect { .. }
1456 | BuffEffect::CastWhileMovingFromEffect { .. }
1457 | BuffEffect::ResistPushbackFromEffect { .. }
1458 | BuffEffect::SpellTargetingFromEffect { .. }
1459 | BuffEffect::CastTimePercent(..)
1460 | BuffEffect::CastTimePercentPerStack(..)
1461 | BuffEffect::GcdPercent(..)
1462 | BuffEffect::DamageReplication(..) => None,
1463 }
1464}
1465
1466pub(crate) const DAMAGE_SCHOOL_COUNT: usize = DamageSchool::COUNT;
1468const BONUS_STAT_COUNT: usize = 4;
1469
1470#[derive(Clone, Copy, Debug, PartialEq)]
1472pub struct BuffTotals {
1473 pub haste_pct: f64,
1474 pub haste_mult: f64,
1475 pub spell_cast_speed_mult: f64,
1476 pub attack_speed_mult: f64,
1477 pub auto_attack_damage_mult: f64,
1478 pub auto_attack_crit_pct: f64,
1479 pub resource_gain_mult: f64,
1480 pub rage_gain_mult: f64,
1481 pub crit_pct: f64,
1482 pub mastery_pct: f64,
1483 pub vers_pct: f64,
1484 pub primary_stat: f64,
1485 pub primary_stat_pct: f64,
1486 pub bonus_primary_stat_pct: f64,
1487 pub bonus_all_stat_pct: f64,
1488 pub bonus_stat_pct: [f64; BONUS_STAT_COUNT],
1489 pub override_spell_power_by_attack_power_pct: Option<f64>,
1490 pub override_attack_power_by_spell_power_pct: Option<f64>,
1491 pub attack_power_pct: f64,
1492 pub damage_mult: f64,
1493 pub pet_damage_mult: f64,
1494 pub school_damage_mult: [f64; DAMAGE_SCHOOL_COUNT],
1495 pub regen_pct: f64,
1496 pub max_resource_flat: f64,
1497}
1498
1499#[derive(Clone, Copy, Debug, Default, PartialEq)]
1500pub(crate) struct PlayerScalingBuffs {
1501 pub(crate) haste_pct: f64,
1502 pub(crate) haste_mult: f64,
1503 pub(crate) spell_cast_speed_mult: f64,
1504 pub(crate) attack_speed_mult: f64,
1505 pub(crate) crit_pct: f64,
1506}
1507
1508impl From<BuffTotals> for PlayerScalingBuffs {
1509 fn from(totals: BuffTotals) -> Self {
1510 Self {
1511 haste_pct: totals.haste_pct,
1512 haste_mult: totals.haste_mult,
1513 spell_cast_speed_mult: totals.spell_cast_speed_mult,
1514 attack_speed_mult: totals.attack_speed_mult,
1515 crit_pct: totals.crit_pct,
1516 }
1517 }
1518}
1519
1520impl BuffTotals {
1521 #[expect(
1523 clippy::match_same_arms,
1524 reason = "separate no-op groups document which subsystem consumes each effect category"
1525 )]
1526 pub fn add(&mut self, effect: BuffEffect) {
1527 match effect {
1528 BuffEffect::Haste(v) => self.haste_pct += v,
1529 BuffEffect::HasteMult(v) => self.haste_mult *= 1.0 + v / HUNDRED,
1530 BuffEffect::HasteMultFromEffect { percent, .. } => {
1531 self.haste_mult *= 1.0 + percent / HUNDRED;
1532 }
1533 BuffEffect::SpellCastSpeed(v) => {
1534 self.spell_cast_speed_mult *= 1.0 + v / HUNDRED;
1535 }
1536 BuffEffect::AttackSpeed(v) => self.attack_speed_mult *= 1.0 + v / HUNDRED,
1537 BuffEffect::AutoAttackDamage(v) | BuffEffect::AutoAttackDamageTaken(v) => {
1538 self.auto_attack_damage_mult *= 1.0 + v / HUNDRED;
1539 }
1540 BuffEffect::AutoAttackCritChance(v) => self.auto_attack_crit_pct += v,
1541 BuffEffect::CritChanceVersusTargetHealth { .. } => {}
1542 BuffEffect::AutoAttackReplacement(..) => {}
1543 BuffEffect::HealthThresholdTrigger { .. } => {}
1544 BuffEffect::ArmorBypassFromCaster(..) => {}
1545 BuffEffect::SpellSchoolFromEffect { .. } => {}
1546 BuffEffect::AuraTimeRateFromEffect { .. } => {}
1547 BuffEffect::SpellReflection(..) | BuffEffect::SpellDeflectChance(..) => {}
1548 BuffEffect::ResourceGainMult(v) => self.resource_gain_mult *= 1.0 + v / HUNDRED,
1549 BuffEffect::RageGainMult(v) => self.rage_gain_mult *= 1.0 + v / HUNDRED,
1550 BuffEffect::Crit(v) => self.crit_pct += v,
1551 BuffEffect::CritFromEffect { percent, .. } => self.crit_pct += percent,
1552 BuffEffect::CritActive(v) => self.crit_pct += v,
1553 BuffEffect::Mastery(v) => self.mastery_pct += v,
1554 BuffEffect::Versatility(v) => self.vers_pct += v,
1555 BuffEffect::PrimaryStat(v) => self.primary_stat += v,
1556 BuffEffect::PrimaryStatPercent(v) => self.primary_stat_pct += v,
1557 BuffEffect::BonusStatPercent { percent, stat } => match stat {
1558 wowlab_engine_domain::dbc::StatModifierKind::Primary => {
1559 self.bonus_primary_stat_pct += percent;
1560 }
1561 wowlab_engine_domain::dbc::StatModifierKind::All => {
1562 self.bonus_all_stat_pct += percent;
1563 }
1564 _ => {
1565 if let Some(index) = stat.stat_index()
1566 && let Some(total) = self.bonus_stat_pct.get_mut(index)
1567 {
1568 *total += percent;
1569 }
1570 }
1571 },
1572 BuffEffect::OverrideSpellPowerByAttackPowerPercent(v) => {
1573 self.override_spell_power_by_attack_power_pct = Some(v);
1574 }
1575 BuffEffect::OverrideAttackPowerBySpellPowerPercent(v) => {
1576 self.override_attack_power_by_spell_power_pct = Some(v);
1577 }
1578 BuffEffect::AttackPowerPercent(v) => self.attack_power_pct += v,
1579 BuffEffect::DamageMult(v) => self.damage_mult *= v,
1580 BuffEffect::PetDamageMult(v) => self.pet_damage_mult *= v,
1581 BuffEffect::DynamicDamageMult(..)
1583 | BuffEffect::DynamicDamageMultSpells(..)
1584 | BuffEffect::DynamicRegenPercent(..) => {}
1585 BuffEffect::DamageMultSchool(v, school) => {
1586 self.school_damage_mult[school as usize] *= v;
1588 }
1589 BuffEffect::DamageMultSpells(..)
1591 | BuffEffect::DamageMultSpellsActive(..)
1592 | BuffEffect::SpellDamageFromEffect { .. }
1593 | BuffEffect::SpellModifierFromEffect { .. }
1594 | BuffEffect::SpellCritChanceFromEffect { .. }
1595 | BuffEffect::SpellCritDamageFromEffect { .. }
1596 | BuffEffect::SpellGateFromEffect { .. }
1597 | BuffEffect::SpellStanceMaskFromEffect { .. }
1598 | BuffEffect::DamageTakenFromSource(..)
1599 | BuffEffect::DamageTakenFromSourceSpells { .. }
1600 | BuffEffect::DamageToTargetAuraMechanicPercent { .. }
1601 | BuffEffect::MechanicDamageTakenPercent { .. }
1602 | BuffEffect::CreatureAoeDamageAvoidance { .. }
1603 | BuffEffect::DamageMultSpellsLinear(..)
1604 | BuffEffect::CritChanceSpells(..)
1605 | BuffEffect::CritDamageSpells(..) => {}
1606 BuffEffect::SpellCostPercent(..)
1608 | BuffEffect::SpellCostFlat(..)
1609 | BuffEffect::SpellCostFromEffect { .. }
1610 | BuffEffect::ResourceCostPercent(..)
1611 | BuffEffect::AdditionalPowerCostFromEffect { .. }
1612 | BuffEffect::RechargeRatePercent(..)
1613 | BuffEffect::CooldownRechargeRateFromEffect { .. }
1614 | BuffEffect::ChargeRechargeRateCategory { .. }
1615 | BuffEffect::MaxChargesCategory(..)
1616 | BuffEffect::IgnoreChargeCooldownCategory(..)
1617 | BuffEffect::CooldownDurationPercent { .. }
1618 | BuffEffect::SpellCastTimeFromEffect { .. }
1619 | BuffEffect::SpellGcdFromEffect { .. }
1620 | BuffEffect::SpellDurationFromEffect { .. }
1621 | BuffEffect::SpellTickTimeFromEffect { .. }
1622 | BuffEffect::CastWhileMovingFromEffect { .. }
1623 | BuffEffect::ResistPushbackFromEffect { .. }
1624 | BuffEffect::SpellTargetingFromEffect { .. }
1625 | BuffEffect::CastTimePercent(..)
1626 | BuffEffect::CastTimePercentPerStack(..)
1627 | BuffEffect::GcdPercent(..) => {}
1628 BuffEffect::DamageReplication(..) => {}
1630 BuffEffect::DamageMultStacking { .. } => {}
1632 BuffEffect::RegenPercent(v) => self.regen_pct += v,
1633 BuffEffect::ResourceRegenPerFiveSeconds(..) => {}
1634 BuffEffect::IncomingDamagePercent { .. }
1635 | BuffEffect::IncomingAoeDamagePercent { .. }
1636 | BuffEffect::SchoolImmunity(..)
1637 | BuffEffect::MechanicImmunity(..)
1638 | BuffEffect::InterruptRegen
1639 | BuffEffect::InterruptedRegenPercent(..)
1640 | BuffEffect::PreventResourceRegen(..)
1641 | BuffEffect::HealingDoneFlat { .. }
1642 | BuffEffect::HealingDonePercent(..)
1643 | BuffEffect::HealingReceivedPercent(..)
1644 | BuffEffect::HealingReceivedFromCasterPercent(..)
1645 | BuffEffect::PeriodicHealingPercent(..)
1646 | BuffEffect::LeechPercent(..)
1647 | BuffEffect::AbsorbDonePercent(..)
1648 | BuffEffect::AbsorbTakenPercent(..)
1649 | BuffEffect::Absorb(..)
1650 | BuffEffect::DamageTransfer(..) => {}
1651 BuffEffect::MaxResourceFlat(v) => self.max_resource_flat += v,
1652 }
1653 }
1654
1655 pub(crate) fn add_scaled(&mut self, effect: &BuffEffect, stacks: f64) {
1658 match effect {
1659 BuffEffect::Haste(value) => self.haste_pct += value * stacks,
1660 BuffEffect::HasteMult(value) => {
1661 self.haste_mult *= 1.0 + value * stacks / HUNDRED;
1662 }
1663 BuffEffect::HasteMultFromEffect { percent, .. } => {
1664 self.haste_mult *= 1.0 + percent * stacks / HUNDRED;
1665 }
1666 BuffEffect::SpellCastSpeed(value) => {
1667 self.spell_cast_speed_mult *= (1.0 + value / HUNDRED).powf(stacks);
1668 }
1669 BuffEffect::AttackSpeed(value) => {
1670 self.attack_speed_mult *= 1.0 + value * stacks / HUNDRED;
1671 }
1672 BuffEffect::AutoAttackDamage(value) | BuffEffect::AutoAttackDamageTaken(value) => {
1673 self.auto_attack_damage_mult *= 1.0 + value * stacks / HUNDRED;
1674 }
1675 BuffEffect::AutoAttackCritChance(value) => {
1676 self.auto_attack_crit_pct += value * stacks;
1677 }
1678 BuffEffect::ResourceGainMult(value) => {
1679 self.resource_gain_mult *= 1.0 + value * stacks / HUNDRED;
1680 }
1681 BuffEffect::RageGainMult(value) => {
1682 self.rage_gain_mult *= 1.0 + value * stacks / HUNDRED;
1683 }
1684 BuffEffect::Crit(value) => self.crit_pct += value * stacks,
1685 BuffEffect::CritFromEffect { percent, .. } => self.crit_pct += percent * stacks,
1686 BuffEffect::CritActive(value) => self.crit_pct += value,
1687 BuffEffect::Mastery(value) => self.mastery_pct += value * stacks,
1688 BuffEffect::Versatility(value) => self.vers_pct += value * stacks,
1689 BuffEffect::PrimaryStat(value) => self.primary_stat += value * stacks,
1690 BuffEffect::PrimaryStatPercent(value) => self.primary_stat_pct += value * stacks,
1691 BuffEffect::BonusStatPercent { percent, stat } => {
1692 let scaled = percent * stacks;
1693
1694 match stat {
1695 wowlab_engine_domain::dbc::StatModifierKind::Primary => {
1696 self.bonus_primary_stat_pct += scaled;
1697 }
1698 wowlab_engine_domain::dbc::StatModifierKind::All => {
1699 self.bonus_all_stat_pct += scaled;
1700 }
1701 _ => {
1702 if let Some(index) = stat.stat_index()
1703 && let Some(total) = self.bonus_stat_pct.get_mut(index)
1704 {
1705 *total += scaled;
1706 }
1707 }
1708 }
1709 }
1710 BuffEffect::OverrideSpellPowerByAttackPowerPercent(value) => {
1711 self.override_spell_power_by_attack_power_pct = Some(value * stacks);
1712 }
1713 BuffEffect::OverrideAttackPowerBySpellPowerPercent(value) => {
1714 self.override_attack_power_by_spell_power_pct = Some(value * stacks);
1715 }
1716 BuffEffect::AttackPowerPercent(value) => self.attack_power_pct += value * stacks,
1717 BuffEffect::DamageMult(value) => self.damage_mult *= value.powf(stacks),
1718 BuffEffect::PetDamageMult(value) => self.pet_damage_mult *= value.powf(stacks),
1719 BuffEffect::DamageMultSchool(value, school) => {
1720 self.school_damage_mult[*school as usize] *= value.powf(stacks);
1722 }
1723 BuffEffect::DamageMultStacking { initial, per_stack } => {
1724 self.damage_mult *= 1.0 + initial + per_stack * (stacks - 1.0);
1725 }
1726 BuffEffect::RegenPercent(value) => self.regen_pct += value * stacks,
1727 BuffEffect::MaxResourceFlat(value) => self.max_resource_flat += value * stacks,
1728 _ => {}
1729 }
1730 }
1731}
1732
1733impl Default for BuffTotals {
1734 fn default() -> Self {
1735 Self {
1736 haste_pct: 0.0,
1737 haste_mult: 1.0,
1738 spell_cast_speed_mult: 1.0,
1739 attack_speed_mult: 1.0,
1740 auto_attack_damage_mult: 1.0,
1741 auto_attack_crit_pct: 0.0,
1742 resource_gain_mult: 1.0,
1743 rage_gain_mult: 1.0,
1744 crit_pct: 0.0,
1745 mastery_pct: 0.0,
1746 vers_pct: 0.0,
1747 primary_stat: 0.0,
1748 primary_stat_pct: 0.0,
1749 bonus_primary_stat_pct: 0.0,
1750 bonus_all_stat_pct: 0.0,
1751 bonus_stat_pct: [0.0; BONUS_STAT_COUNT],
1752 override_spell_power_by_attack_power_pct: None,
1753 override_attack_power_by_spell_power_pct: None,
1754 attack_power_pct: 0.0,
1755 damage_mult: 1.0,
1756 pet_damage_mult: 1.0,
1757 school_damage_mult: [1.0; DAMAGE_SCHOOL_COUNT],
1758 regen_pct: 0.0,
1759 max_resource_flat: 0.0,
1760 }
1761 }
1762}
1763
1764#[cfg(test)]
1765#[path = "buff_effect/tests.rs"]
1766mod tests;