Skip to main content

wowlab_engine_combat/systems/buffs/
haste.rs

1use super::*;
2
3pub(crate) fn current_haste_mult(view: &CombatView<'_>) -> f64 {
4    let totals = view.buff_totals();
5
6    (1.0 + (view.buf.player().haste + totals.haste_pct) / HUNDRED)
7        * totals.haste_mult
8        * totals.spell_cast_speed_mult
9}
10
11pub(crate) fn current_stat_haste_mult(view: &CombatView<'_>) -> f64 {
12    let totals = view.buff_totals();
13
14    (1.0 + (view.buf.player().haste + totals.haste_pct) / HUNDRED) * totals.haste_mult
15}
16
17/// Live player scaling shared by every RPPM entry point.
18pub(crate) fn current_rppm_scaling(view: &CombatView<'_>) -> wowlab_engine_rng::RppmScaling {
19    let totals = view.buff_totals();
20    let inherited_haste =
21        (1.0 + (view.buf.player().haste + totals.haste_pct) / HUNDRED) * totals.haste_mult;
22
23    wowlab_engine_rng::RppmScaling {
24        haste_pct: (inherited_haste - 1.0) * HUNDRED,
25        crit_pct: view.buf.player().crit + totals.crit_pct,
26        auto_attack_speed_multiplier: inherited_haste * totals.attack_speed_mult,
27    }
28}
29
30/// SimC-compatible wall-clock factor for an aura on `actor`.
31pub(crate) fn aura_time_rate_multiplier(view: ActorView<'_>, aura_spell_id: u32) -> f64 {
32    let ActorView { state, .. } = view;
33    let mut multiplier = 1.0;
34    let target = wowlab_types::sim::SpellIdx::from_raw(aura_spell_id);
35
36    for_each_active_actor_aura_effect(view, |stacks, effect| {
37        let BuffEffect::AuraTimeRateFromEffect {
38            percent,
39            source_spell_id,
40            effect_index,
41        } = effect
42        else {
43            return;
44        };
45
46        if state.config.game_data.effect_affects_spell(
47            wowlab_types::sim::SpellIdx::from_raw(*source_spell_id),
48            *effect_index,
49            target,
50        ) {
51            multiplier *= time_rate_factor(*percent).powf(stacks);
52        }
53    });
54
55    multiplier
56}
57
58#[must_use]
59pub(crate) fn time_rate_factor(percent: f64) -> f64 {
60    1.0 / (1.0 + (percent / HUNDRED).max(MIN_TIME_RATE_DELTA))
61}
62
63pub(crate) fn current_mastery_points(view: &CombatView<'_>) -> f64 {
64    view.buf.player().mastery + view.buff_totals().mastery_pct
65}
66
67pub(crate) fn current_haste_mult_for(view: ActorView<'_>) -> f64 {
68    let ActorView { buf, actor, .. } = view;
69    let combat = view.combat();
70    let totals = combat.buff_totals();
71    let haste_pct = buf.player().haste + totals.haste_pct;
72    let inherited_haste = (1.0 + haste_pct / HUNDRED) * totals.haste_mult;
73
74    if actor == ActorId::Player {
75        return inherited_haste * totals.spell_cast_speed_mult;
76    }
77
78    let actor_totals = accumulate_actor_only_buffs(view, None);
79
80    inherited_haste
81        * totals.spell_cast_speed_mult
82        * (1.0 + actor_totals.haste_pct / HUNDRED)
83        * actor_totals.haste_mult
84        * actor_totals.spell_cast_speed_mult
85}
86
87pub(crate) fn current_attack_haste_mult(view: &CombatView<'_>) -> f64 {
88    current_stat_haste_mult(view)
89}
90
91/// Returns the swing-frequency multiplier without affecting haste-scaled paths.
92#[cfg(test)]
93pub(crate) fn current_auto_attack_mult(state: &CombatState, buf: &DenseBuffer) -> f64 {
94    current_auto_attack_mult_for(ActorView::new(state, buf, ActorId::Player))
95}
96
97pub(crate) fn current_auto_attack_mult_for(view: ActorView<'_>) -> f64 {
98    let ActorView { buf, actor, .. } = view;
99    let combat = view.combat();
100    let owner = combat.buff_totals();
101    let haste_pct = buf.player().haste + owner.haste_pct;
102    let inherited_haste = (1.0 + haste_pct / HUNDRED) * owner.haste_mult;
103
104    if actor == ActorId::Player {
105        return inherited_haste * owner.attack_speed_mult;
106    }
107
108    let actor_buffs = accumulate_actor_only_buffs(view, None);
109
110    inherited_haste * actor_buffs.attack_speed_mult
111}