wowlab_engine_content/hooks/destruction_warlock/
hooks.rs1use wowlab_engine_combat::AuraOps as _;
2use wowlab_engine_combat::DamageOps as _;
3use wowlab_engine_combat::GuardianOps as _;
4use wowlab_engine_combat::ResourceOps as _;
5use wowlab_engine_combat::SchedulingOps as _;
6use wowlab_engine_combat::{
7 DamageFlags, GuardianAbility, GuardianEvent, GuardianSpec, HookCtx, MasteryCtx,
8};
9use wowlab_engine_domain::damage::pet_swing_power_coefficient;
10use wowlab_engine_rng::proc_chance;
11use wowlab_types::{combat::DamageSchool, constants::HUNDRED};
12
13use crate::generated::specs::destruction_warlock::{
14 AURA, AURA_AVATAR_OF_DESTRUCTION_TALENT, AURA_BACKDRAFT, AURA_CHAOTIC_INFERNO,
15 AURA_CHAOTIC_INFERNO_TALENT, AURA_CONFLAGRATION_OF_CHAOS_TALENT, AURA_DARK_TITANS_MARK,
16 AURA_DEMONIC_ART_MOTHER, AURA_DEMONIC_ART_OVERLORD, AURA_DEMONIC_ART_PIT_LORD,
17 AURA_DIABOLIC_RITUAL_MOTHER, AURA_DIABOLIC_RITUAL_OVERLORD, AURA_DIABOLIC_RITUAL_PIT_LORD,
18 AURA_DIABOLIC_RITUAL_TALENT, AURA_EMBERS_OF_NIHILAM_1_TALENT, AURA_EMBERS_OF_NIHILAM_2_TALENT,
19 AURA_EMBERS_OF_NIHILAM_3_TALENT, AURA_FIENDISH_CRUELTY, AURA_FIENDISH_CRUELTY_TALENT,
20 AURA_INFERNAL_EMBERS, AURA_VISION_OF_NIHILAM, REPORTED_SPELL, SPELL,
21};
22use crate::hooks::shared::warlock::{DiabolicRitual, WarlockSpec};
23
24const DIABOLIC_RITUAL: DiabolicRitual = DiabolicRitual {
25 talent: AURA_DIABOLIC_RITUAL_TALENT,
26 art: [
27 AURA_DEMONIC_ART_OVERLORD,
28 AURA_DEMONIC_ART_MOTHER,
29 AURA_DEMONIC_ART_PIT_LORD,
30 ],
31 ritual: [
32 AURA_DIABOLIC_RITUAL_OVERLORD,
33 AURA_DIABOLIC_RITUAL_MOTHER,
34 AURA_DIABOLIC_RITUAL_PIT_LORD,
35 ],
36};
37
38const CHAOTIC_ENERGIES_MEAN_ROLL: f64 = 0.75;
40
41const INFERNAL_COEF_FROM_SP: f64 = 2.2275;
43const INFERNAL_DURATION_MS: u32 = 30_000;
44const INFERNAL_ACTION_MS: u32 = 2_000;
45const DIABOLIC_RITUAL_REDUCTION_MS: u32 = 1_000;
46const OVERFIEND_DURATION_MS: u32 = 8_000;
47const OVERFIEND_CAST_MS: u32 = 3_000;
48const OVERFIEND_CHAOS_BOLT_EFFICIENCY: f64 = 0.60;
49const OVERFIEND_SHARDS: f64 = 0.8;
50pub(super) const SAYAAD_ACTION_MS: u32 = 6_000;
51const SAYAAD_DAMAGE_MULT: f64 = 1.15;
52const CHAOTIC_INFERNO_CHANCE: f64 = 0.25;
53const CHAOS_BOLT_RITUAL_SHARDS: u32 = 2;
54const FIENDISH_CRUELTY_BASE_CHANCE: f64 = 0.10;
55const OVERFIEND_BOLTS_PER_ACTION: f64 = 2.0;
56const OVERFIEND_MAX_ACTIONS: u16 = 3;
57const CONFLAGRATION_OF_CHAOS_MULT: f64 = 2.0;
58
59const fn pet_inherits_chaotic_energies(is_pet: bool, spell_id: u32) -> bool {
60 !is_pet || spell_id == SPELL::CHAOS_BOLT
61}
62
63pub(super) fn sayaad_action(ctx: &mut HookCtx<'_>, _event: GuardianEvent) {
64 let coefficient = ctx.effect_sp_coefficient(
65 crate::generated::specs::destruction_warlock::DAMAGE_EFFECT::LASH_OF_PAIN_DAMAGE,
66 );
67
68 ctx.deal_damage_sp(
69 crate::generated::specs::destruction_warlock::DAMAGE_REPORTED::LASH_OF_PAIN,
70 coefficient * SAYAAD_DAMAGE_MULT,
71 DamageFlags::PET,
72 );
73}
74
75fn trigger_echo_of_sargeras(
76 ctx: &mut HookCtx<'_>,
77 report_action: wowlab_engine_combat::DamageActionId,
78) {
79 let coefficient = ctx.effect_sp_coefficient(
80 crate::generated::specs::destruction_warlock::DAMAGE_EFFECT::ECHO_OF_SARGERAS_DAMAGE,
81 );
82
83 ctx.deal_profiled_damage_sp(
84 report_action,
85 crate::generated::specs::destruction_warlock::DAMAGE_REPORTED::ECHO_OF_SARGERAS,
86 coefficient,
87 DamageFlags::empty(),
88 );
89
90 if ctx.is_aura_active(AURA_EMBERS_OF_NIHILAM_2_TALENT) {
91 ctx.apply_aura(AURA_VISION_OF_NIHILAM);
92 }
93
94 if ctx
95 .program()
96 .spec_config::<super::handler::DestructionConfig>()
97 .is_some_and(|cfg| cfg.mid2_4pc)
98 {
99 ctx.apply_aura(AURA_DARK_TITANS_MARK);
100 }
101}
102
103fn maybe_trigger_base_echo(ctx: &mut HookCtx<'_>) {
104 let chance = ctx
105 .program()
106 .spec_config::<super::handler::DestructionConfig>()
107 .map_or(super::handler::BASE_ECHO_CHANCE, |cfg| cfg.echo_chance);
108
109 if ctx.is_aura_active(AURA_EMBERS_OF_NIHILAM_1_TALENT) && proc_chance(ctx.rng(), chance) {
110 trigger_echo_of_sargeras(
111 ctx,
112 crate::generated::specs::destruction_warlock::DAMAGE_REPORTED::ECHO_OF_SARGERAS,
113 );
114 }
115}
116
117fn diabolist_spender(ctx: &mut HookCtx<'_>, reduction_ms: u32) {
118 let ritual_was_active = DIABOLIC_RITUAL.active(ctx);
119 let next_ritual = DIABOLIC_RITUAL.consume_art(ctx);
120
121 if next_ritual.is_some() {
122 crate::hooks::shared::warlock::explode_oculi(ctx, WarlockSpec::Destruction);
123 }
124
125 DIABOLIC_RITUAL.advance(ctx, next_ritual, reduction_ms);
126
127 if ritual_was_active {
128 crate::hooks::shared::warlock::trigger_oculi(ctx, WarlockSpec::Destruction);
129 }
130}
131
132fn maybe_trigger_fiendish_cruelty(ctx: &mut HookCtx<'_>, chance: f64) {
133 if ctx.is_aura_active(AURA_FIENDISH_CRUELTY_TALENT) && proc_chance(ctx.rng(), chance) {
134 ctx.apply_aura(AURA_FIENDISH_CRUELTY);
135 }
136}
137
138pub(crate) fn chaos_bolt_hook(ctx: &mut HookCtx<'_>) {
139 ctx.consume_aura_stack(AURA_BACKDRAFT);
140
141 if ctx.is_aura_active(AURA_EMBERS_OF_NIHILAM_3_TALENT) {
142 trigger_echo_of_sargeras(
143 ctx,
144 crate::generated::specs::destruction_warlock::DAMAGE_REPORTED::ECHO_OF_SARGERAS_CB,
145 );
146 }
147
148 if ctx.is_aura_active(AURA_CHAOTIC_INFERNO_TALENT)
149 && proc_chance(ctx.rng(), CHAOTIC_INFERNO_CHANCE)
150 {
151 ctx.apply_aura(AURA_CHAOTIC_INFERNO);
152 }
153
154 diabolist_spender(ctx, DIABOLIC_RITUAL_REDUCTION_MS * CHAOS_BOLT_RITUAL_SHARDS);
155 maybe_trigger_fiendish_cruelty(ctx, FIENDISH_CRUELTY_BASE_CHANCE);
156}
157
158pub(crate) fn conflagrate_hook(ctx: &mut HookCtx<'_>) {
159 ctx.apply_aura(AURA_BACKDRAFT);
160 maybe_trigger_fiendish_cruelty(ctx, FIENDISH_CRUELTY_BASE_CHANCE);
161}
162
163pub(crate) fn incinerate_hook(ctx: &mut HookCtx<'_>) {
164 let chaotic_inferno = ctx.is_aura_active(AURA_CHAOTIC_INFERNO);
165
166 if !chaotic_inferno {
167 ctx.consume_aura_stack(AURA_BACKDRAFT);
168 }
169
170 ctx.consume_aura(AURA_CHAOTIC_INFERNO);
171 maybe_trigger_base_echo(ctx);
172 let buffs = ctx.buff_totals();
173 let crit_chance = ((ctx.buf().player().crit + buffs.crit_pct) / HUNDRED).clamp(0.0, 1.0);
174
175 maybe_trigger_fiendish_cruelty(ctx, FIENDISH_CRUELTY_BASE_CHANCE * crit_chance);
176}
177
178fn overfiend_action(ctx: &mut HookCtx<'_>, _event: GuardianEvent) {
179 let coefficient = ctx.effect_sp_coefficient(
180 crate::generated::specs::destruction_warlock::DAMAGE_EFFECT::CHAOS_BOLT_DAMAGE,
181 );
182
183 ctx.deal_damage_sp(
184 crate::generated::specs::destruction_warlock::DAMAGE_SPELL::CHAOS_BOLT,
185 coefficient * OVERFIEND_CHAOS_BOLT_EFFICIENCY * OVERFIEND_BOLTS_PER_ACTION,
186 DamageFlags::PET | DamageFlags::NO_CRIT,
187 );
188}
189
190pub(crate) fn soul_fire_hook(ctx: &mut HookCtx<'_>) {
191 ctx.consume_aura_stack(AURA_BACKDRAFT);
192
193 if !ctx.is_aura_active(AURA_AVATAR_OF_DESTRUCTION_TALENT) {
194 return;
195 }
196
197 ctx.gain_resource(OVERFIEND_SHARDS);
198 let _ = ctx.summon_guardian(
199 GuardianSpec::new_inheriting_owner(AURA::OVERFIEND, OVERFIEND_DURATION_MS).ability(
200 GuardianAbility::new(OVERFIEND_CAST_MS, overfiend_action)
201 .first_action_delay(OVERFIEND_CAST_MS)
202 .hasted_actions()
203 .max_actions(OVERFIEND_MAX_ACTIONS)
204 .ends_guardian(),
205 ),
206 );
207}
208
209pub(crate) fn infernal_bolt_hook(ctx: &mut HookCtx<'_>) {
210 ctx.consume_aura_stack(AURA_BACKDRAFT);
211 ctx.consume_aura(AURA_CHAOTIC_INFERNO);
212 maybe_trigger_base_echo(ctx);
213}
214
215pub(crate) fn rain_of_fire_hook(ctx: &mut HookCtx<'_>) {
216 if let Some(cfg) = ctx
217 .program()
218 .spec_config::<super::handler::DestructionConfig>()
219 && let Some(effect) = cfg.rain_of_fire_ground
220 {
221 ctx.schedule_ground_effect(effect);
222 }
223
224 diabolist_spender(ctx, DIABOLIC_RITUAL_REDUCTION_MS);
225}
226
227pub(crate) fn shadowburn_hook(ctx: &mut HookCtx<'_>) {
228 if ctx.is_aura_active(AURA_EMBERS_OF_NIHILAM_3_TALENT) {
229 trigger_echo_of_sargeras(
230 ctx,
231 crate::generated::specs::destruction_warlock::DAMAGE_REPORTED::ECHO_OF_SARGERAS_SB,
232 );
233 }
234
235 diabolist_spender(ctx, DIABOLIC_RITUAL_REDUCTION_MS);
236 ctx.consume_aura(AURA_FIENDISH_CRUELTY);
237}
238
239pub(crate) fn ruination_hook(ctx: &mut HookCtx<'_>) {
240 if DIABOLIC_RITUAL.consume_art(ctx).is_some() {
241 crate::hooks::shared::warlock::explode_oculi(ctx, WarlockSpec::Destruction);
242 }
243}
244
245fn infernal_action(ctx: &mut HookCtx<'_>, _event: GuardianEvent) {
246 let swing = INFERNAL_COEF_FROM_SP * pet_swing_power_coefficient(1.0, INFERNAL_ACTION_MS, 1.0);
247
248 ctx.deal_damage_sp(
249 crate::generated::specs::destruction_warlock::DAMAGE_REPORTED::INFERNAL_MELEE,
250 swing,
251 DamageFlags::PHYSICAL | DamageFlags::PET,
252 );
253 let immolation = ctx.effect_sp_coefficient(
254 crate::generated::specs::destruction_warlock::DAMAGE_EFFECT::IMMOLATION_DAMAGE,
255 ) * INFERNAL_COEF_FROM_SP;
256
257 ctx.deal_damage_sp(
258 crate::generated::specs::destruction_warlock::DAMAGE_REPORTED::IMMOLATION,
259 immolation,
260 DamageFlags::PET,
261 );
262}
263
264pub(crate) fn summon_infernal_hook(ctx: &mut HookCtx<'_>) {
266 let _ = ctx.summon_guardian(
267 GuardianSpec::new_inheriting_owner(REPORTED_SPELL::INFERNAL_MELEE, INFERNAL_DURATION_MS)
268 .ability(
269 GuardianAbility::new(INFERNAL_ACTION_MS, infernal_action)
270 .first_action_delay(INFERNAL_ACTION_MS)
271 .hasted_actions(),
272 ),
273 );
274
275 ctx.apply_aura(AURA_INFERNAL_EMBERS);
276}
277
278#[must_use]
280pub(crate) fn mastery(ctx: &MasteryCtx<'_>) -> f64 {
281 if !pet_inherits_chaotic_energies(ctx.is_pet, ctx.spell_id)
284 || matches!(
285 ctx.spell_id,
286 REPORTED_SPELL::DIABOLIC_GAZE_1
287 | REPORTED_SPELL::DIABOLIC_GAZE_2
288 | REPORTED_SPELL::DIABOLIC_GAZE_3
289 )
290 {
291 return 1.0;
292 }
293
294 let mastery = match ctx.school {
295 DamageSchool::Fire | DamageSchool::Chaos => {
296 1.0 + CHAOTIC_ENERGIES_MEAN_ROLL * ctx.points(1) / HUNDRED
297 }
298 _ => 1.0,
299 };
300
301 if ctx.spell_id == SPELL::CHAOS_BOLT {
302 let buffs = wowlab_engine_combat::buff_totals(ctx.program, ctx.instance, ctx.target);
303 let crit_fraction = (ctx.instance.player_crit() + buffs.crit_pct) / HUNDRED;
304
305 mastery * (1.0 + crit_fraction)
306 } else if matches!(ctx.spell_id, SPELL::CONFLAGRATE | SPELL::SHADOWBURN)
307 && ctx.is_aura_active(AURA_CONFLAGRATION_OF_CHAOS_TALENT)
308 {
309 mastery * CONFLAGRATION_OF_CHAOS_MULT
310 } else {
311 mastery
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use googletest::prelude::*;
318
319 use super::*;
320
321 #[gtest]
322 fn only_explicit_pet_chaos_bolts_inherit_chaotic_energies() -> Result<()> {
323 verify_false!(pet_inherits_chaotic_energies(
324 true,
325 REPORTED_SPELL::CHAOS_SALVO
326 ))?;
327 verify_false!(pet_inherits_chaotic_energies(
328 true,
329 REPORTED_SPELL::IMMOLATION
330 ))?;
331 verify_true!(pet_inherits_chaotic_energies(true, SPELL::CHAOS_BOLT))?;
332
333 verify_true!(pet_inherits_chaotic_energies(false, SPELL::CONFLAGRATE))
334 }
335}