wowlab_engine_content/hooks/retribution_paladin/
templar.rs1use 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
22pub(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
108pub(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 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
146pub(crate) fn lights_deliverance_check_hook(ctx: &mut HookCtx<'_>) {
148 lights_deliverance_try_convert(ctx);
149}
150
151pub(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
159pub(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
176pub(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}