Skip to main content

wowlab_engine_content/hooks/retribution_paladin/
templar.rs

1use wowlab_engine_combat::DamageOps as _;
2use wowlab_engine_combat::SchedulingOps as _;
3use wowlab_engine_combat::AuraOps as _;
4use wowlab_engine_combat::{DamageFlags, HookCtx};
5use wowlab_engine_domain::dbc::ResolvedGameDataEffectExt as _;
6use wowlab_types::sim::SpellIdx;
7
8use super::{
9    config::retribution_config,
10    hooks::{
11        apply_judgment_debuff, crusade_hammer_of_light_stacks, crusade_spender_stacks,
12        divine_purpose_cycle, righteous_cause_roll,
13    },
14};
15use crate::generated::specs::retribution_paladin::{
16    AURA_DIVINE_HAMMER, AURA_HAMMER_OF_LIGHT, AURA_HAMMER_OF_LIGHT_FREE, AURA_JUDGMENT_DEBUFF,
17    AURA_LIGHTS_DELIVERANCE, AURA_SHAKE_THE_HEAVENS, AURA_UNDISPUTED_RULING, EFFECT, SPELL,
18};
19
20const HAMMERFALL_STH_COUNT: u32 = 2;
21
22/// Fires one Empyrean Hammer at live stats.
23pub(super) fn empyrean_hammer_fire_hook(ctx: &mut HookCtx<'_>) {
24    let cfg = retribution_config(ctx);
25    let coef = ctx
26        .game_data()
27        .effect_ap_coefficient(EFFECT::EMPYREAN_HAMMER_DAMAGE);
28
29    ctx.deal_damage_ap(EFFECT::EMPYREAN_HAMMER_DAMAGE.0, coef, DamageFlags::empty());
30
31    if cfg.lights_deliverance {
32        gain_lights_deliverance(ctx);
33    }
34}
35
36pub(crate) fn queue_empyrean_hammers(ctx: &mut HookCtx<'_>, count: u32, initial_delay_ms: u32) {
37    let stagger_ms = retribution_config(ctx).empyrean_hammer_stagger_ms;
38
39    for i in 0..count {
40        ctx.schedule_hook(initial_delay_ms + i * stagger_ms, empyrean_hammer_fire_hook);
41    }
42}
43
44pub(crate) fn gain_lights_deliverance(ctx: &mut HookCtx<'_>) {
45    ctx.apply_aura(AURA_LIGHTS_DELIVERANCE.raw());
46    lights_deliverance_try_convert(ctx);
47}
48
49pub(crate) fn lights_deliverance_try_convert(ctx: &mut HookCtx<'_>) {
50    if !retribution_config(ctx).lights_deliverance {
51        return;
52    }
53
54    let cfg = retribution_config(ctx);
55
56    if !lights_deliverance_threshold_reached(
57        ctx.aura_stacks(AURA_LIGHTS_DELIVERANCE.raw()),
58        cfg.lights_deliverance_max_stacks,
59    ) {
60        return;
61    }
62
63    let now = ctx.now().as_secs_f64();
64    let wake_ready = ctx
65        .buf()
66        .cooldown(SpellIdx::from_raw(SPELL::WAKE_OF_ASHES))
67        .is_some_and(|cd| cd.is_ready(now));
68
69    if wake_ready {
70        return;
71    }
72
73    if ctx.is_aura_active(AURA_HAMMER_OF_LIGHT.raw()) {
74        return;
75    }
76
77    ctx.apply_aura(AURA_HAMMER_OF_LIGHT_FREE.raw());
78    ctx.apply_aura(AURA_HAMMER_OF_LIGHT.raw());
79    ctx.expire_aura(AURA_LIGHTS_DELIVERANCE.raw());
80}
81
82const fn lights_deliverance_threshold_reached(stacks: i32, max_stacks: i32) -> bool {
83    max_stacks > 0 && stacks >= max_stacks
84}
85
86pub(crate) fn hammerfall(ctx: &mut HookCtx<'_>) {
87    let cfg = retribution_config(ctx);
88
89    if !cfg.hammerfall {
90        return;
91    }
92
93    let count = if ctx.is_aura_active(AURA_SHAKE_THE_HEAVENS.raw()) {
94        HAMMERFALL_STH_COUNT
95    } else {
96        1
97    };
98
99    queue_empyrean_hammers(ctx, count, 0);
100}
101
102pub(crate) fn higher_calling_extend(ctx: &mut HookCtx<'_>, ms: u32) {
103    if ms > 0 && ctx.is_aura_active(AURA_SHAKE_THE_HEAVENS.raw()) {
104        ctx.extend_aura(AURA_SHAKE_THE_HEAVENS.raw(), ms);
105    }
106}
107
108/// Handles Hammer of Light and its Templar riders.
109pub(crate) fn hammer_of_light_hook(ctx: &mut HookCtx<'_>) {
110    let cfg = retribution_config(ctx);
111
112    crusade_hammer_of_light_stacks(ctx);
113
114    if cfg.undisputed_ruling && cfg.greater_judgment {
115        apply_judgment_debuff(ctx);
116    }
117
118    let coef = ctx
119        .game_data()
120        .effect_ap_coefficient(EFFECT::HAMMER_OF_LIGHT_DAMAGE);
121
122    ctx.deal_damage_ap(SPELL::HAMMER_OF_LIGHT, coef, DamageFlags::empty());
123    ctx.consume_aura_stack(AURA_JUDGMENT_DEBUFF.raw());
124    righteous_cause_roll(ctx);
125    ctx.expire_aura(AURA_HAMMER_OF_LIGHT_FREE.raw());
126    ctx.expire_aura(AURA_HAMMER_OF_LIGHT.raw());
127    queue_empyrean_hammers(ctx, cfg.zealous_vindication_count, 0);
128    queue_empyrean_hammers(ctx, cfg.lights_guidance_count, cfg.lights_guidance_delay_ms);
129
130    if cfg.shake_the_heavens {
131        // Pandemic refresh preserves the live tick chain instead of restarting it.
132        ctx.apply_aura(AURA_SHAKE_THE_HEAVENS.raw());
133    }
134
135    if cfg.undisputed_ruling {
136        ctx.apply_aura(AURA_UNDISPUTED_RULING.raw());
137    }
138
139    divine_purpose_cycle(ctx);
140}
141
142pub(crate) fn sth_pulse_hook(ctx: &mut HookCtx<'_>) {
143    queue_empyrean_hammers(ctx, 1, 0);
144}
145
146/// Rechecks Light's Deliverance when Hammer of Light readiness expires.
147pub(crate) fn lights_deliverance_check_hook(ctx: &mut HookCtx<'_>) {
148    lights_deliverance_try_convert(ctx);
149}
150
151/// Shared Holy Power spender tail: Divine Purpose roll + Hammerfall.
152pub(crate) fn holy_power_spender(ctx: &mut HookCtx<'_>) {
153    crusade_spender_stacks(ctx);
154    righteous_cause_roll(ctx);
155    divine_purpose_cycle(ctx);
156    hammerfall(ctx);
157}
158
159/// Readies Hammer of Light and leaves Seething Flames to Wake's DBC trigger program.
160pub(crate) fn wake_of_ashes_hook(ctx: &mut HookCtx<'_>) {
161    let cfg = retribution_config(ctx);
162
163    if cfg.templar {
164        ctx.apply_aura(AURA_HAMMER_OF_LIGHT.raw());
165    }
166}
167
168pub(crate) fn start_divine_hammer(ctx: &mut HookCtx<'_>) {
169    if !retribution_config(ctx).divine_hammer {
170        return;
171    }
172
173    ctx.apply_aura(AURA_DIVINE_HAMMER.raw());
174}
175
176/// Keeps the custom schedule while the DBC program deals child spell 198137.
177pub(crate) fn divine_hammer_pulse_hook(_ctx: &mut HookCtx<'_>) {}
178
179#[cfg(test)]
180mod tests {
181    use googletest::prelude::*;
182
183    use super::lights_deliverance_threshold_reached;
184
185    #[gtest]
186    fn lights_deliverance_converts_at_passively_modified_cap() -> Result<()> {
187        verify_false!(lights_deliverance_threshold_reached(49, 50))?;
188        verify_true!(lights_deliverance_threshold_reached(50, 50))?;
189        verify_true!(lights_deliverance_threshold_reached(51, 50))?;
190
191        verify_false!(lights_deliverance_threshold_reached(60, 0))
192    }
193}