Skip to main content

wowlab_engine_combat/systems/buffs/
traversal.rs

1use super::{
2    ActorId, ActorView, BuffEffect, CombatState, DenseBuffer, EnemyIdx, SpellEffectAttributes,
3};
4
5pub(crate) fn dbc_effect_points(
6    state: &CombatState,
7    source_spell_id: u32,
8    effect_index: u8,
9    stacks: f64,
10) -> f64 {
11    let attributes = SpellEffectAttributes::from_dbc(state.config.game_data.effect_attributes(
12        wowlab_types::sim::SpellIdx::from_raw(source_spell_id),
13        effect_index,
14    ));
15
16    if attributes.contains(SpellEffectAttributes::SUPPRESS_POINTS_STACKING) {
17        1.0
18    } else {
19        stacks
20    }
21}
22
23pub(crate) fn for_each_active_aura_effect(
24    state: &CombatState,
25    buf: &DenseBuffer,
26    mut f: impl FnMut(f64, &BuffEffect),
27) {
28    for_each_active_actor_aura_effect(ActorView::new(state, buf, ActorId::Player), &mut f);
29}
30
31pub(crate) fn for_each_active_actor_aura_effect(
32    view: ActorView<'_>,
33    mut f: impl FnMut(f64, &BuffEffect),
34) {
35    let ActorView { state, buf, actor } = view;
36
37    if actor == ActorId::Player {
38        for driver in &state.defs.passive_driver_effects {
39            // #t(rust_log_in_loop) Each passive driver has a distinct identity needed in trace diagnostics.
40            tracing::trace!(
41                driver_spell_id = driver.driver_spell_id,
42                ?driver.effect,
43                "passive driver effect applied"
44            );
45            f(1.0, &driver.effect);
46        }
47    }
48
49    for key in buf.aura_keys() {
50        if key.affected() != actor {
51            continue;
52        }
53
54        let Some(slot) = buf.aura(key).filter(|slot| slot.is_occupied()) else {
55            continue;
56        };
57        let Some(local) = state.aura_local_for_key(key) else {
58            continue;
59        };
60        // BOUNDS: local comes from the definition index built with this table.
61        let aura = &state.defs.auras[local.as_usize()];
62        let stacks_f = f64::from(slot.stacks);
63
64        for effect in aura.effects.iter().flatten() {
65            trace_content_damage_mult(aura.aura_id, stacks_f, effect);
66            f(stacks_f, effect);
67        }
68    }
69}
70
71/// Traces content-declared damage multipliers together with the aura that owns them.
72///   The spell-modifier trace records only the generic DBC rows and never names an aura, so an
73///   aura id present in both traces is a content row duplicating an effect the generic live-aura
74///   path already applies.
75fn trace_content_damage_mult(aura_id: u32, stacks: f64, effect: &BuffEffect) {
76    if !matches!(
77        effect,
78        BuffEffect::DamageMult(..)
79            | BuffEffect::DamageMultStacking { .. }
80            | BuffEffect::DamageMultSpells(..)
81            | BuffEffect::DamageMultSpellsActive(..)
82            | BuffEffect::DamageMultSpellsLinear(..)
83    ) {
84        return;
85    }
86
87    tracing::trace!(
88        aura_id,
89        stacks,
90        scaled = ?effect.scaled(stacks),
91        "content aura damage multiplier applied"
92    );
93}
94
95pub(crate) fn for_each_active_target_aura_effect(
96    state: &CombatState,
97    buf: &DenseBuffer,
98    target: EnemyIdx,
99    mut f: impl FnMut(f64, &BuffEffect),
100) {
101    for key in buf.aura_keys() {
102        if key.affected() != ActorId::Enemy(target) {
103            continue;
104        }
105
106        let Some(slot) = buf.aura(key).filter(|slot| slot.is_occupied()) else {
107            continue;
108        };
109        let Some(local) = state.aura_local_for_key(key) else {
110            continue;
111        };
112        // BOUNDS: local comes from the definition index built with this table.
113        let aura = &state.defs.auras[local.as_usize()];
114        let stacks_f = f64::from(slot.stacks);
115
116        for effect in aura.effects.iter().flatten() {
117            f(stacks_f, effect);
118        }
119    }
120}