Skip to main content

wowlab_engine_combat/systems/buffs/
totals.rs

1use super::{
2    ActorId, ActorView, BuffEffect, BuffTotals, CombatState, DenseBuffer, EnemyIdx,
3    for_each_active_actor_aura_effect,
4};
5
6/// Sum all active aura buff contributions, scaled by stack count.
7#[must_use]
8pub fn accumulate_buffs(
9    state: &CombatState,
10    buf: &DenseBuffer,
11    target: Option<EnemyIdx>,
12) -> BuffTotals {
13    accumulate_actor_buffs(ActorView::new(state, buf, ActorId::Player), target)
14}
15
16pub(crate) fn accumulate_actor_buffs(view: ActorView<'_>, target: Option<EnemyIdx>) -> BuffTotals {
17    let ActorView { state, buf, .. } = view;
18    let mut totals = BuffTotals::default();
19
20    for_each_active_actor_aura_effect(view, |stacks, effect| {
21        if let BuffEffect::DynamicDamageMult(resolve) = effect {
22            totals.damage_mult *= resolve(state, buf, target).powf(stacks);
23        } else if let BuffEffect::DynamicRegenPercent(resolve) = effect {
24            totals.regen_pct += resolve(state, buf) * stacks;
25        } else {
26            totals.add(effect.scaled(stacks));
27        }
28    });
29
30    totals
31}
32
33pub(crate) fn accumulate_actor_only_buffs(
34    view: ActorView<'_>,
35    target: Option<EnemyIdx>,
36) -> BuffTotals {
37    let ActorView { state, buf, actor } = view;
38    let mut totals = BuffTotals::default();
39
40    for key in buf.aura_keys() {
41        if key.affected() != actor
42            || buf.aura(key).is_none_or(|slot| !slot.is_occupied())
43            || buf.aura_keys().any(|owner_key| {
44                owner_key.aura() == key.aura()
45                    && owner_key.affected() == ActorId::Player
46                    && buf
47                        .aura(owner_key)
48                        .is_some_and(wowlab_engine_domain::rotation::AuraSlot::is_occupied)
49            })
50        {
51            continue;
52        }
53
54        let Some(local) = state.aura_local_for_key(key) else {
55            continue;
56        };
57        // BOUNDS: local comes from the definition index built with this table.
58        let aura = &state.defs.auras[local.as_usize()];
59        let stacks = f64::from(buf.aura(key).map_or(0, |slot| slot.stacks));
60
61        for effect in aura.effects.iter().flatten() {
62            if let BuffEffect::DynamicDamageMult(resolve) = effect {
63                totals.damage_mult *= resolve(state, buf, target).powf(stacks);
64            } else if let BuffEffect::DynamicRegenPercent(resolve) = effect {
65                totals.regen_pct += resolve(state, buf) * stacks;
66            } else {
67                totals.add(effect.scaled(stacks));
68            }
69        }
70    }
71
72    totals
73}