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