1use wowlab_engine_domain::{dbc::ModifierOperation, rotation::DenseBuffer};
2use wowlab_types::{combat::DamageSchool, constants::HUNDRED};
3
4use super::{
5 AbsorbEffect, ActiveSpellGate, DamageReplication, DamageSource, DamageTransfer,
6 HealthThresholdDirection,
7};
8
9fn stacked_percent_multiplier(percent: f64, stacks: f64) -> f64 {
10 ((1.0 + percent / HUNDRED).powf(stacks) - 1.0) * HUNDRED
11}
12
13pub const MAX_AURA_BUFF_EFFECTS: usize = 16;
14
15#[derive(Clone, Copy, Debug)]
17pub struct BuffEffectsFull;
18
19pub fn try_push_effect(
25 effects: &mut [Option<BuffEffect>; MAX_AURA_BUFF_EFFECTS],
26 effect: BuffEffect,
27) -> Result<(), BuffEffectsFull> {
28 for slot in effects.iter_mut() {
29 if slot.is_none() {
30 *slot = Some(effect);
31
32 return Ok(());
33 }
34 }
35
36 Err(BuffEffectsFull)
37}
38
39#[derive(Clone, Copy, Debug)]
41#[non_exhaustive]
42pub enum BuffEffect {
43 Haste(f64),
44 HasteMult(f64),
46 HasteMultFromEffect {
48 percent: f64,
49 source_spell_id: u32,
50 effect_index: u8,
51 },
52 SpellCastSpeed(f64),
54 AttackSpeed(f64),
56 AutoAttackDamage(f64),
58 AutoAttackDamageTaken(f64),
60 AutoAttackCritChance(f64),
62 CritChanceVersusTargetHealth {
64 percent: f64,
65 threshold_pct: f64,
66 },
67 AutoAttackReplacement(u32),
69 HealthThresholdTrigger {
71 threshold_pct: f64,
72 direction: HealthThresholdDirection,
73 spell_id: u32,
74 },
75 ArmorBypassFromCaster(f64),
77 SpellSchoolFromEffect {
79 school: DamageSchool,
80 source_spell_id: u32,
81 effect_index: u8,
82 },
83 AuraTimeRateFromEffect {
85 percent: f64,
86 source_spell_id: u32,
87 effect_index: u8,
88 },
89 SpellReflection(wowlab_engine_domain::dbc::SpellSchoolMask),
90 SpellDeflectChance(f64),
91 ResourceGainMult(f64),
93 RageGainMult(f64),
95 ResourceRegenPerFiveSeconds(f64, wowlab_types::combat::ResourceType),
97 IncomingDamagePercent {
99 percent: f64,
100 schools: wowlab_engine_domain::dbc::SpellSchoolMask,
101 },
102 IncomingAoeDamagePercent {
104 percent: f64,
105 schools: wowlab_engine_domain::dbc::SpellSchoolMask,
106 },
107 HealingDoneFlat {
109 amount: f64,
110 schools: wowlab_engine_domain::dbc::SpellSchoolMask,
111 },
112 HealingDonePercent(f64),
114 HealingReceivedPercent(f64),
116 HealingReceivedFromCasterPercent(f64),
118 PeriodicHealingPercent(f64),
120 LeechPercent(f64),
122 AbsorbDonePercent(f64),
124 AbsorbTakenPercent(f64),
126 Absorb(AbsorbEffect),
128 DamageTransfer(DamageTransfer),
130 SchoolImmunity(wowlab_engine_domain::dbc::SpellSchoolMask),
132 MechanicImmunity(u64),
134 InterruptRegen,
136 InterruptedRegenPercent(f64),
138 PreventResourceRegen(u64),
140 ResourceCostPercent(f64, wowlab_types::combat::ResourceType),
142 Crit(f64),
143 CritFromEffect {
145 percent: f64,
146 source_spell_id: u32,
147 effect_index: u8,
148 },
149 CritActive(f64),
151 Mastery(f64),
152 Versatility(f64),
153 PrimaryStat(f64),
154 PrimaryStatPercent(f64),
155 BonusStatPercent {
157 percent: f64,
158 stat: wowlab_engine_domain::dbc::StatModifierKind,
159 },
160 OverrideSpellPowerByAttackPowerPercent(f64),
162 OverrideAttackPowerBySpellPowerPercent(f64),
164 AttackPowerPercent(f64),
165 DamageMult(f64),
166 PetDamageMult(f64),
167 DynamicDamageMult(
168 fn(&super::CombatState, &DenseBuffer, Option<wowlab_types::sim::EnemyIdx>) -> f64,
169 ),
170 DynamicDamageMultSpells(
171 fn(
172 &super::CombatState,
173 &DenseBuffer,
174 Option<wowlab_types::sim::EnemyIdx>,
175 wowlab_types::sim::SimTime,
176 ) -> f64,
177 &'static [u32],
178 ),
179 DynamicRegenPercent(fn(&super::CombatState, &DenseBuffer) -> f64),
180 DamageMultSchool(f64, DamageSchool),
181 DamageMultSpells(f64, &'static [u32]),
182 DamageMultSpellsActive(f64, &'static [u32]),
184 DamageToTargetAuraMechanicPercent {
186 percent: f64,
187 mechanic_mask: wowlab_engine_domain::dbc::MechanicMask,
188 },
189 MechanicDamageTakenPercent {
191 percent: f64,
192 mechanic_mask: wowlab_engine_domain::dbc::MechanicMask,
193 },
194 CreatureAoeDamageAvoidance {
196 percent: f64,
197 schools: wowlab_engine_domain::dbc::SpellSchoolMask,
198 },
199 SpellDamageFromEffect {
200 percent: f64,
201 source_spell_id: u32,
202 effect_index: u8,
203 periodic: bool,
204 },
205 SpellModifierFromEffect {
207 value: f64,
208 source_spell_id: u32,
209 effect_index: u8,
210 property: wowlab_engine_domain::dbc::ModifierPropertyKind,
211 operation: ModifierOperation,
212 },
213 SpellCritChanceFromEffect {
214 percent: f64,
215 source_spell_id: u32,
216 effect_index: u8,
217 },
218 SpellCritDamageFromEffect {
219 percent: f64,
220 source_spell_id: u32,
221 effect_index: u8,
222 },
223 SpellGateFromEffect {
224 source_spell_id: u32,
225 effect_index: u8,
226 gate: ActiveSpellGate,
227 },
228 SpellStanceMaskFromEffect {
230 source_spell_id: u32,
231 effect_index: u8,
232 },
233 DamageTakenFromSource(f64, DamageSource),
234 DamageTakenFromSourceSpells {
235 percent: f64,
236 source_spell_id: u32,
237 effect_index: u8,
238 source: DamageSource,
239 },
240 DamageMultSpellsLinear(f64, &'static [u32]),
241 CritChanceSpells(f64, &'static [u32]),
243 CritDamageSpells(f64, &'static [u32]),
245 SpellCostPercent(f64, &'static [u32], super::ResourcePool),
246 SpellCostFlat(f64, &'static [u32], super::ResourcePool),
247 SpellCostFromEffect {
248 value: f64,
249 source_spell_id: u32,
250 effect_index: u8,
251 cost_entry: u8,
252 operation: ModifierOperation,
253 },
254 AdditionalPowerCostFromEffect {
256 value: f64,
257 source_spell_id: u32,
258 effect_index: u8,
259 resource_type: i32,
260 },
261 RechargeRatePercent(f64, &'static [u32]),
262 CooldownRechargeRateFromEffect {
264 percent: f64,
265 source_spell_id: u32,
266 effect_index: u8,
267 },
268 ChargeRechargeRateCategory {
270 percent: f64,
271 category: i32,
272 source_spell_id: u32,
273 effect_index: u8,
274 },
275 MaxChargesCategory(f64, i32),
277 IgnoreChargeCooldownCategory(i32),
279 CooldownDurationPercent {
280 percent: f64,
281 source_spell_id: u32,
282 effect_index: u8,
283 },
284 SpellCastTimeFromEffect {
285 value: f64,
286 source_spell_id: u32,
287 effect_index: u8,
288 operation: ModifierOperation,
289 },
290 SpellGcdFromEffect {
291 value: f64,
292 source_spell_id: u32,
293 effect_index: u8,
294 operation: ModifierOperation,
295 },
296 SpellDurationFromEffect {
297 value: f64,
298 source_spell_id: u32,
299 effect_index: u8,
300 operation: ModifierOperation,
301 },
302 SpellTickTimeFromEffect {
303 value: f64,
304 source_spell_id: u32,
305 effect_index: u8,
306 operation: ModifierOperation,
307 },
308 CastWhileMovingFromEffect {
309 source_spell_id: u32,
310 effect_index: u8,
311 },
312 ResistPushbackFromEffect {
313 value: f64,
314 source_spell_id: u32,
315 effect_index: u8,
316 operation: ModifierOperation,
317 },
318 SpellTargetingFromEffect {
319 value: f64,
320 source_spell_id: u32,
321 effect_index: u8,
322 property: wowlab_engine_domain::dbc::ModifierPropertyKind,
323 operation: ModifierOperation,
324 },
325 CastTimePercent(f64, &'static [u32]),
327 CastTimePercentPerStack(f64, &'static [u32]),
328 GcdPercent(f64, &'static [u32]),
330 DamageReplication(DamageReplication),
331 DamageMultStacking {
332 initial: f64,
333 per_stack: f64,
334 },
335 RegenPercent(f64),
336 MaxResourceFlat(f64),
337}
338
339impl BuffEffect {
340 #[inline]
344 #[must_use]
345 pub fn dbc_source(&self) -> Option<(u32, u8)> {
346 match self {
347 Self::SpellSchoolFromEffect {
348 source_spell_id,
349 effect_index,
350 ..
351 }
352 | Self::AuraTimeRateFromEffect {
353 source_spell_id,
354 effect_index,
355 ..
356 }
357 | Self::HasteMultFromEffect {
358 source_spell_id,
359 effect_index,
360 ..
361 }
362 | Self::CritFromEffect {
363 source_spell_id,
364 effect_index,
365 ..
366 }
367 | Self::SpellDamageFromEffect {
368 source_spell_id,
369 effect_index,
370 ..
371 }
372 | Self::SpellModifierFromEffect {
373 source_spell_id,
374 effect_index,
375 ..
376 }
377 | Self::SpellCritChanceFromEffect {
378 source_spell_id,
379 effect_index,
380 ..
381 }
382 | Self::SpellCritDamageFromEffect {
383 source_spell_id,
384 effect_index,
385 ..
386 }
387 | Self::SpellGateFromEffect {
388 source_spell_id,
389 effect_index,
390 ..
391 }
392 | Self::SpellStanceMaskFromEffect {
393 source_spell_id,
394 effect_index,
395 ..
396 }
397 | Self::DamageTakenFromSourceSpells {
398 source_spell_id,
399 effect_index,
400 ..
401 }
402 | Self::SpellCostFromEffect {
403 source_spell_id,
404 effect_index,
405 ..
406 }
407 | Self::AdditionalPowerCostFromEffect {
408 source_spell_id,
409 effect_index,
410 ..
411 }
412 | Self::CooldownRechargeRateFromEffect {
413 source_spell_id,
414 effect_index,
415 ..
416 }
417 | Self::ChargeRechargeRateCategory {
418 source_spell_id,
419 effect_index,
420 ..
421 }
422 | Self::CooldownDurationPercent {
423 source_spell_id,
424 effect_index,
425 ..
426 }
427 | Self::SpellCastTimeFromEffect {
428 source_spell_id,
429 effect_index,
430 ..
431 }
432 | Self::SpellGcdFromEffect {
433 source_spell_id,
434 effect_index,
435 ..
436 }
437 | Self::SpellDurationFromEffect {
438 source_spell_id,
439 effect_index,
440 ..
441 }
442 | Self::SpellTickTimeFromEffect {
443 source_spell_id,
444 effect_index,
445 ..
446 }
447 | Self::CastWhileMovingFromEffect {
448 source_spell_id,
449 effect_index,
450 ..
451 }
452 | Self::ResistPushbackFromEffect {
453 source_spell_id,
454 effect_index,
455 ..
456 }
457 | Self::SpellTargetingFromEffect {
458 source_spell_id,
459 effect_index,
460 ..
461 } => Some((*source_spell_id, *effect_index)),
462 _ => None,
463 }
464 }
465
466 #[inline]
473 #[must_use]
476 pub fn scaled(self, stacks: f64) -> Self {
477 if let Some(scaled) = self.unscaled_dbc_effect() {
478 return scaled;
479 }
480 match self {
481 Self::Haste(v) => Self::Haste(v * stacks),
482 Self::HasteMult(v) => Self::HasteMult(v * stacks),
483 Self::HasteMultFromEffect {
484 percent,
485 source_spell_id,
486 effect_index,
487 } => Self::HasteMultFromEffect {
488 percent: percent * stacks,
489 source_spell_id,
490 effect_index,
491 },
492 Self::SpellCastSpeed(v) => Self::SpellCastSpeed(stacked_percent_multiplier(v, stacks)),
493 Self::AttackSpeed(v) => Self::AttackSpeed(v * stacks),
494 Self::AutoAttackDamage(v) => Self::AutoAttackDamage(v * stacks),
495 Self::AutoAttackDamageTaken(v) => Self::AutoAttackDamageTaken(v * stacks),
496 Self::AutoAttackCritChance(v) => Self::AutoAttackCritChance(v * stacks),
497 Self::CritChanceVersusTargetHealth {
498 percent,
499 threshold_pct,
500 } => Self::CritChanceVersusTargetHealth {
501 percent: percent * stacks,
502 threshold_pct,
503 },
504 Self::ArmorBypassFromCaster(v) => Self::ArmorBypassFromCaster(v * stacks),
505 Self::ResourceGainMult(v) => Self::ResourceGainMult(v * stacks),
506 Self::RageGainMult(v) => Self::RageGainMult(v * stacks),
507 Self::ResourceRegenPerFiveSeconds(value, resource_type) => {
508 Self::ResourceRegenPerFiveSeconds(value * stacks, resource_type)
509 }
510 Self::IncomingDamagePercent { percent, schools } => Self::IncomingDamagePercent {
511 percent: percent * stacks,
512 schools,
513 },
514 Self::IncomingAoeDamagePercent { percent, schools } => Self::IncomingAoeDamagePercent {
515 percent: percent * stacks,
516 schools,
517 },
518 Self::HealingDoneFlat { amount, schools } => Self::HealingDoneFlat {
519 amount: amount * stacks,
520 schools,
521 },
522 Self::HealingDonePercent(v) => Self::HealingDonePercent(v * stacks),
523 Self::HealingReceivedPercent(v) => Self::HealingReceivedPercent(v * stacks),
524 Self::HealingReceivedFromCasterPercent(v) => {
525 Self::HealingReceivedFromCasterPercent(v * stacks)
526 }
527 Self::PeriodicHealingPercent(v) => Self::PeriodicHealingPercent(v * stacks),
528 Self::LeechPercent(v) => Self::LeechPercent(v * stacks),
529 Self::AbsorbDonePercent(v) => Self::AbsorbDonePercent(v * stacks),
530 Self::AbsorbTakenPercent(v) => Self::AbsorbTakenPercent(v * stacks),
531 Self::Absorb(effect) => Self::Absorb(AbsorbEffect {
532 amount: effect.amount * stacks,
533 ..effect
534 }),
535 Self::DamageTransfer(transfer) => Self::DamageTransfer(transfer),
536 Self::SchoolImmunity(schools) => Self::SchoolImmunity(schools),
537 Self::MechanicImmunity(mask) => Self::MechanicImmunity(mask),
538 Self::InterruptRegen => Self::InterruptRegen,
539 Self::InterruptedRegenPercent(percent) => {
540 Self::InterruptedRegenPercent(percent * stacks)
541 }
542 Self::PreventResourceRegen(mask) => Self::PreventResourceRegen(mask),
543 Self::ResourceCostPercent(value, resource_type) => {
544 Self::ResourceCostPercent(value * stacks, resource_type)
545 }
546 Self::Crit(v) => Self::Crit(v * stacks),
547 Self::CritFromEffect {
548 percent,
549 source_spell_id,
550 effect_index,
551 } => Self::CritFromEffect {
552 percent: percent * stacks,
553 source_spell_id,
554 effect_index,
555 },
556 Self::CritActive(v) => Self::CritActive(v),
557 Self::Mastery(v) => Self::Mastery(v * stacks),
558 Self::Versatility(v) => Self::Versatility(v * stacks),
559 Self::PrimaryStat(v) => Self::PrimaryStat(v * stacks),
560 Self::PrimaryStatPercent(v) => Self::PrimaryStatPercent(v * stacks),
561 Self::BonusStatPercent { percent, stat } => Self::BonusStatPercent {
562 percent: percent * stacks,
563 stat,
564 },
565 Self::OverrideSpellPowerByAttackPowerPercent(v) => {
566 Self::OverrideSpellPowerByAttackPowerPercent(v * stacks)
567 }
568 Self::OverrideAttackPowerBySpellPowerPercent(v) => {
569 Self::OverrideAttackPowerBySpellPowerPercent(v * stacks)
570 }
571 Self::AttackPowerPercent(v) => Self::AttackPowerPercent(v * stacks),
572 Self::DamageMult(v) => Self::DamageMult(v.powf(stacks)),
573 Self::PetDamageMult(v) => Self::PetDamageMult(v.powf(stacks)),
574 Self::DynamicDamageMult(resolve) => Self::DynamicDamageMult(resolve),
575 Self::DynamicDamageMultSpells(r, s) => Self::DynamicDamageMultSpells(r, s),
576 Self::DynamicRegenPercent(resolve) => Self::DynamicRegenPercent(resolve),
577 Self::DamageMultSchool(v, school) => Self::DamageMultSchool(v.powf(stacks), school),
578 Self::DamageMultSpells(v, spells) => Self::DamageMultSpells(v.powf(stacks), spells),
579 Self::DamageMultSpellsActive(v, spells) => Self::DamageMultSpellsActive(v, spells),
580 Self::DamageToTargetAuraMechanicPercent {
581 percent,
582 mechanic_mask,
583 } => Self::DamageToTargetAuraMechanicPercent {
584 percent: percent * stacks,
585 mechanic_mask,
586 },
587 Self::MechanicDamageTakenPercent {
588 percent,
589 mechanic_mask,
590 } => Self::MechanicDamageTakenPercent {
591 percent: percent * stacks,
592 mechanic_mask,
593 },
594 Self::CreatureAoeDamageAvoidance { percent, schools } => {
595 Self::CreatureAoeDamageAvoidance {
596 percent: percent * stacks,
597 schools,
598 }
599 }
600 Self::DamageTakenFromSource(v, source) => {
601 Self::DamageTakenFromSource(v * stacks, source)
602 }
603 Self::DamageMultSpellsLinear(v, spells) => {
604 Self::DamageMultSpellsLinear(v * stacks, spells)
605 }
606 Self::CritChanceSpells(v, spells) => Self::CritChanceSpells(v * stacks, spells),
607 Self::CritDamageSpells(v, spells) => Self::CritDamageSpells(v * stacks, spells),
608 Self::SpellCostPercent(v, spells, pool) => {
609 Self::SpellCostPercent(v * stacks, spells, pool)
610 }
611 Self::SpellCostFlat(v, spells, pool) => Self::SpellCostFlat(v * stacks, spells, pool),
612 Self::SpellCostFromEffect {
613 value,
614 source_spell_id,
615 effect_index,
616 cost_entry,
617 operation,
618 } => Self::SpellCostFromEffect {
619 value,
620 source_spell_id,
621 effect_index,
622 cost_entry,
623 operation,
624 },
625 Self::AdditionalPowerCostFromEffect {
626 value,
627 source_spell_id,
628 effect_index,
629 resource_type,
630 } => Self::AdditionalPowerCostFromEffect {
631 value,
632 source_spell_id,
633 effect_index,
634 resource_type,
635 },
636 Self::RechargeRatePercent(v, spells) => Self::RechargeRatePercent(v * stacks, spells),
637 Self::CooldownRechargeRateFromEffect {
638 percent,
639 source_spell_id,
640 effect_index,
641 } => Self::CooldownRechargeRateFromEffect {
642 percent,
643 source_spell_id,
644 effect_index,
645 },
646 Self::ChargeRechargeRateCategory {
647 percent,
648 category,
649 source_spell_id,
650 effect_index,
651 } => Self::ChargeRechargeRateCategory {
652 percent,
653 category,
654 source_spell_id,
655 effect_index,
656 },
657 Self::MaxChargesCategory(v, category) => Self::MaxChargesCategory(v * stacks, category),
658 Self::IgnoreChargeCooldownCategory(category) => {
659 Self::IgnoreChargeCooldownCategory(category)
660 }
661 Self::CooldownDurationPercent {
662 percent,
663 source_spell_id,
664 effect_index,
665 } => Self::CooldownDurationPercent {
666 percent,
667 source_spell_id,
668 effect_index,
669 },
670 Self::SpellCastTimeFromEffect { .. }
671 | Self::SpellGcdFromEffect { .. }
672 | Self::SpellDurationFromEffect { .. }
673 | Self::SpellTickTimeFromEffect { .. }
674 | Self::SpellModifierFromEffect { .. }
675 | Self::SpellTargetingFromEffect { .. }
676 | Self::SpellSchoolFromEffect { .. }
677 | Self::AuraTimeRateFromEffect { .. }
678 | Self::SpellReflection(..)
679 | Self::AutoAttackReplacement(..)
680 | Self::HealthThresholdTrigger { .. }
681 | Self::SpellDamageFromEffect { .. }
682 | Self::SpellCritChanceFromEffect { .. }
683 | Self::SpellCritDamageFromEffect { .. }
684 | Self::DamageTakenFromSourceSpells { .. }
685 | Self::SpellGateFromEffect { .. }
686 | Self::SpellStanceMaskFromEffect { .. }
687 | Self::CastWhileMovingFromEffect { .. }
688 | Self::ResistPushbackFromEffect { .. } => unreachable!("handled above"),
689 Self::CastTimePercent(v, spells) => Self::CastTimePercent(v, spells),
690 Self::CastTimePercentPerStack(v, spells) => {
691 Self::CastTimePercentPerStack(v * stacks, spells)
692 }
693 Self::GcdPercent(v, spells) => Self::GcdPercent(v, spells),
694 Self::DamageReplication(replication) => Self::DamageReplication(replication),
695 Self::DamageMultStacking { initial, per_stack } => {
696 Self::DamageMult(1.0 + initial + per_stack * (stacks - 1.0))
697 }
698 Self::RegenPercent(v) => Self::RegenPercent(v * stacks),
699 Self::MaxResourceFlat(v) => Self::MaxResourceFlat(v * stacks),
700 Self::SpellDeflectChance(v) => Self::SpellDeflectChance(v * stacks),
701 }
702 }
703 fn unscaled_dbc_effect(self) -> Option<Self> {
705 let scaled = match self {
706 Self::SpellCastTimeFromEffect {
707 value,
708 source_spell_id,
709 effect_index,
710 operation,
711 } => Self::SpellCastTimeFromEffect {
712 value,
713 source_spell_id,
714 effect_index,
715 operation,
716 },
717 Self::SpellGcdFromEffect {
718 value,
719 source_spell_id,
720 effect_index,
721 operation,
722 } => Self::SpellGcdFromEffect {
723 value,
724 source_spell_id,
725 effect_index,
726 operation,
727 },
728 Self::SpellDurationFromEffect {
729 value,
730 source_spell_id,
731 effect_index,
732 operation,
733 } => Self::SpellDurationFromEffect {
734 value,
735 source_spell_id,
736 effect_index,
737 operation,
738 },
739 Self::SpellTickTimeFromEffect {
740 value,
741 source_spell_id,
742 effect_index,
743 operation,
744 } => Self::SpellTickTimeFromEffect {
745 value,
746 source_spell_id,
747 effect_index,
748 operation,
749 },
750 Self::CastWhileMovingFromEffect {
751 source_spell_id,
752 effect_index,
753 } => Self::CastWhileMovingFromEffect {
754 source_spell_id,
755 effect_index,
756 },
757 Self::ResistPushbackFromEffect {
758 value,
759 source_spell_id,
760 effect_index,
761 operation,
762 } => Self::ResistPushbackFromEffect {
763 value,
764 source_spell_id,
765 effect_index,
766 operation,
767 },
768 Self::SpellGateFromEffect {
769 source_spell_id,
770 effect_index,
771 gate,
772 } => Self::SpellGateFromEffect {
773 source_spell_id,
774 effect_index,
775 gate,
776 },
777 Self::SpellStanceMaskFromEffect {
778 source_spell_id,
779 effect_index,
780 } => Self::SpellStanceMaskFromEffect {
781 source_spell_id,
782 effect_index,
783 },
784 Self::SpellModifierFromEffect {
785 value,
786 source_spell_id,
787 effect_index,
788 property,
789 operation,
790 } => Self::SpellModifierFromEffect {
791 value,
792 source_spell_id,
793 effect_index,
794 property,
795 operation,
796 },
797 Self::SpellTargetingFromEffect {
798 value,
799 source_spell_id,
800 effect_index,
801 property,
802 operation,
803 } => Self::SpellTargetingFromEffect {
804 value,
805 source_spell_id,
806 effect_index,
807 property,
808 operation,
809 },
810 Self::SpellSchoolFromEffect {
811 school,
812 source_spell_id,
813 effect_index,
814 } => Self::SpellSchoolFromEffect {
815 school,
816 source_spell_id,
817 effect_index,
818 },
819 Self::AuraTimeRateFromEffect {
820 percent,
821 source_spell_id,
822 effect_index,
823 } => Self::AuraTimeRateFromEffect {
824 percent,
825 source_spell_id,
826 effect_index,
827 },
828 Self::SpellReflection(schools) => Self::SpellReflection(schools),
829 Self::AutoAttackReplacement(spell_id) => Self::AutoAttackReplacement(spell_id),
830 Self::HealthThresholdTrigger {
831 threshold_pct,
832 direction,
833 spell_id,
834 } => Self::HealthThresholdTrigger {
835 threshold_pct,
836 direction,
837 spell_id,
838 },
839 effect @ (Self::SpellDamageFromEffect { .. }
840 | Self::SpellCritChanceFromEffect { .. }
841 | Self::SpellCritDamageFromEffect { .. }
842 | Self::DamageTakenFromSourceSpells { .. }) => effect,
843 _ => return None,
844 };
845 Some(scaled)
846 }
847 }
849
850pub(crate) const DAMAGE_SCHOOL_COUNT: usize = DamageSchool::COUNT;
852const BONUS_STAT_COUNT: usize = 4;
853
854#[derive(Clone, Copy, Debug)]
856pub struct BuffTotals {
857 pub haste_pct: f64,
858 pub haste_mult: f64,
859 pub spell_cast_speed_mult: f64,
860 pub attack_speed_mult: f64,
861 pub auto_attack_damage_mult: f64,
862 pub auto_attack_crit_pct: f64,
863 pub resource_gain_mult: f64,
864 pub rage_gain_mult: f64,
865 pub crit_pct: f64,
866 pub mastery_pct: f64,
867 pub vers_pct: f64,
868 pub primary_stat: f64,
869 pub primary_stat_pct: f64,
870 pub bonus_primary_stat_pct: f64,
871 pub bonus_all_stat_pct: f64,
872 pub bonus_stat_pct: [f64; BONUS_STAT_COUNT],
873 pub override_spell_power_by_attack_power_pct: Option<f64>,
874 pub override_attack_power_by_spell_power_pct: Option<f64>,
875 pub attack_power_pct: f64,
876 pub damage_mult: f64,
877 pub pet_damage_mult: f64,
878 pub school_damage_mult: [f64; DAMAGE_SCHOOL_COUNT],
879 pub regen_pct: f64,
880 pub max_resource_flat: f64,
881}
882
883impl BuffTotals {
884 #[expect(
886 clippy::match_same_arms,
887 reason = "separate no-op groups document which subsystem consumes each effect category"
888 )]
889 pub fn add(&mut self, effect: BuffEffect) {
890 match effect {
891 BuffEffect::Haste(v) => self.haste_pct += v,
892 BuffEffect::HasteMult(v) => self.haste_mult *= 1.0 + v / HUNDRED,
893 BuffEffect::HasteMultFromEffect { percent, .. } => {
894 self.haste_mult *= 1.0 + percent / HUNDRED;
895 }
896 BuffEffect::SpellCastSpeed(v) => {
897 self.spell_cast_speed_mult *= 1.0 + v / HUNDRED;
898 }
899 BuffEffect::AttackSpeed(v) => self.attack_speed_mult *= 1.0 + v / HUNDRED,
900 BuffEffect::AutoAttackDamage(v) | BuffEffect::AutoAttackDamageTaken(v) => {
901 self.auto_attack_damage_mult *= 1.0 + v / HUNDRED;
902 }
903 BuffEffect::AutoAttackCritChance(v) => self.auto_attack_crit_pct += v,
904 BuffEffect::CritChanceVersusTargetHealth { .. } => {}
905 BuffEffect::AutoAttackReplacement(..) => {}
906 BuffEffect::HealthThresholdTrigger { .. } => {}
907 BuffEffect::ArmorBypassFromCaster(..) => {}
908 BuffEffect::SpellSchoolFromEffect { .. } => {}
909 BuffEffect::AuraTimeRateFromEffect { .. } => {}
910 BuffEffect::SpellReflection(..) | BuffEffect::SpellDeflectChance(..) => {}
911 BuffEffect::ResourceGainMult(v) => self.resource_gain_mult *= 1.0 + v / HUNDRED,
912 BuffEffect::RageGainMult(v) => self.rage_gain_mult *= 1.0 + v / HUNDRED,
913 BuffEffect::Crit(v) => self.crit_pct += v,
914 BuffEffect::CritFromEffect { percent, .. } => self.crit_pct += percent,
915 BuffEffect::CritActive(v) => self.crit_pct += v,
916 BuffEffect::Mastery(v) => self.mastery_pct += v,
917 BuffEffect::Versatility(v) => self.vers_pct += v,
918 BuffEffect::PrimaryStat(v) => self.primary_stat += v,
919 BuffEffect::PrimaryStatPercent(v) => self.primary_stat_pct += v,
920 BuffEffect::BonusStatPercent { percent, stat } => match stat {
921 wowlab_engine_domain::dbc::StatModifierKind::Primary => {
922 self.bonus_primary_stat_pct += percent;
923 }
924 wowlab_engine_domain::dbc::StatModifierKind::All => {
925 self.bonus_all_stat_pct += percent;
926 }
927 _ => {
928 if let Some(index) = stat.stat_index()
929 && let Some(total) = self.bonus_stat_pct.get_mut(index)
930 {
931 *total += percent;
932 }
933 }
934 },
935 BuffEffect::OverrideSpellPowerByAttackPowerPercent(v) => {
936 self.override_spell_power_by_attack_power_pct = Some(v);
937 }
938 BuffEffect::OverrideAttackPowerBySpellPowerPercent(v) => {
939 self.override_attack_power_by_spell_power_pct = Some(v);
940 }
941 BuffEffect::AttackPowerPercent(v) => self.attack_power_pct += v,
942 BuffEffect::DamageMult(v) => self.damage_mult *= v,
943 BuffEffect::PetDamageMult(v) => self.pet_damage_mult *= v,
944 BuffEffect::DynamicDamageMult(..)
946 | BuffEffect::DynamicDamageMultSpells(..)
947 | BuffEffect::DynamicRegenPercent(..) => {}
948 BuffEffect::DamageMultSchool(v, school) => {
949 self.school_damage_mult[school as usize] *= v;
951 }
952 BuffEffect::DamageMultSpells(..)
954 | BuffEffect::DamageMultSpellsActive(..)
955 | BuffEffect::SpellDamageFromEffect { .. }
956 | BuffEffect::SpellModifierFromEffect { .. }
957 | BuffEffect::SpellCritChanceFromEffect { .. }
958 | BuffEffect::SpellCritDamageFromEffect { .. }
959 | BuffEffect::SpellGateFromEffect { .. }
960 | BuffEffect::SpellStanceMaskFromEffect { .. }
961 | BuffEffect::DamageTakenFromSource(..)
962 | BuffEffect::DamageTakenFromSourceSpells { .. }
963 | BuffEffect::DamageToTargetAuraMechanicPercent { .. }
964 | BuffEffect::MechanicDamageTakenPercent { .. }
965 | BuffEffect::CreatureAoeDamageAvoidance { .. }
966 | BuffEffect::DamageMultSpellsLinear(..)
967 | BuffEffect::CritChanceSpells(..)
968 | BuffEffect::CritDamageSpells(..) => {}
969 BuffEffect::SpellCostPercent(..)
971 | BuffEffect::SpellCostFlat(..)
972 | BuffEffect::SpellCostFromEffect { .. }
973 | BuffEffect::ResourceCostPercent(..)
974 | BuffEffect::AdditionalPowerCostFromEffect { .. }
975 | BuffEffect::RechargeRatePercent(..)
976 | BuffEffect::CooldownRechargeRateFromEffect { .. }
977 | BuffEffect::ChargeRechargeRateCategory { .. }
978 | BuffEffect::MaxChargesCategory(..)
979 | BuffEffect::IgnoreChargeCooldownCategory(..)
980 | BuffEffect::CooldownDurationPercent { .. }
981 | BuffEffect::SpellCastTimeFromEffect { .. }
982 | BuffEffect::SpellGcdFromEffect { .. }
983 | BuffEffect::SpellDurationFromEffect { .. }
984 | BuffEffect::SpellTickTimeFromEffect { .. }
985 | BuffEffect::CastWhileMovingFromEffect { .. }
986 | BuffEffect::ResistPushbackFromEffect { .. }
987 | BuffEffect::SpellTargetingFromEffect { .. }
988 | BuffEffect::CastTimePercent(..)
989 | BuffEffect::CastTimePercentPerStack(..)
990 | BuffEffect::GcdPercent(..) => {}
991 BuffEffect::DamageReplication(..) => {}
993 BuffEffect::DamageMultStacking { .. } => {}
995 BuffEffect::RegenPercent(v) => self.regen_pct += v,
996 BuffEffect::ResourceRegenPerFiveSeconds(..) => {}
997 BuffEffect::IncomingDamagePercent { .. }
998 | BuffEffect::IncomingAoeDamagePercent { .. }
999 | BuffEffect::SchoolImmunity(..)
1000 | BuffEffect::MechanicImmunity(..)
1001 | BuffEffect::InterruptRegen
1002 | BuffEffect::InterruptedRegenPercent(..)
1003 | BuffEffect::PreventResourceRegen(..)
1004 | BuffEffect::HealingDoneFlat { .. }
1005 | BuffEffect::HealingDonePercent(..)
1006 | BuffEffect::HealingReceivedPercent(..)
1007 | BuffEffect::HealingReceivedFromCasterPercent(..)
1008 | BuffEffect::PeriodicHealingPercent(..)
1009 | BuffEffect::LeechPercent(..)
1010 | BuffEffect::AbsorbDonePercent(..)
1011 | BuffEffect::AbsorbTakenPercent(..)
1012 | BuffEffect::Absorb(..)
1013 | BuffEffect::DamageTransfer(..) => {}
1014 BuffEffect::MaxResourceFlat(v) => self.max_resource_flat += v,
1015 }
1016 }
1017}
1018
1019impl Default for BuffTotals {
1020 fn default() -> Self {
1021 Self {
1022 haste_pct: 0.0,
1023 haste_mult: 1.0,
1024 spell_cast_speed_mult: 1.0,
1025 attack_speed_mult: 1.0,
1026 auto_attack_damage_mult: 1.0,
1027 auto_attack_crit_pct: 0.0,
1028 resource_gain_mult: 1.0,
1029 rage_gain_mult: 1.0,
1030 crit_pct: 0.0,
1031 mastery_pct: 0.0,
1032 vers_pct: 0.0,
1033 primary_stat: 0.0,
1034 primary_stat_pct: 0.0,
1035 bonus_primary_stat_pct: 0.0,
1036 bonus_all_stat_pct: 0.0,
1037 bonus_stat_pct: [0.0; BONUS_STAT_COUNT],
1038 override_spell_power_by_attack_power_pct: None,
1039 override_attack_power_by_spell_power_pct: None,
1040 attack_power_pct: 0.0,
1041 damage_mult: 1.0,
1042 pet_damage_mult: 1.0,
1043 school_damage_mult: [1.0; DAMAGE_SCHOOL_COUNT],
1044 regen_pct: 0.0,
1045 max_resource_flat: 0.0,
1046 }
1047 }
1048}
1049
1050#[cfg(test)]
1051#[path = "buff_effect/tests.rs"]
1052mod tests;