1use wowlab_engine_combat::{
2 AuraOps as _, DamageFlags, DamageOps as _, GuardianAbility, GuardianEvent, GuardianOps as _,
3 GuardianSpec, HookCtx, LocalAuraIdx,
4};
5use wowlab_types::constants::MS_PER_SECOND;
6
7use crate::generated::{
8 shared::warlock::diabolist::{
9 DAMAGE_EFFECT as DIABOLIC_DAMAGE_EFFECT, DAMAGE_REPORTED as DIABOLIC_DAMAGE_REPORTED,
10 },
11 specs::{
12 demonology_warlock::{
13 AURA_DEMONIC_ART_MOTHER as DEMO_ART_MOTHER,
14 AURA_DEMONIC_ART_OVERLORD as DEMO_ART_OVERLORD,
15 AURA_DEMONIC_ART_PIT_LORD as DEMO_ART_PIT_LORD, AURA_DEMONIC_OCULI as DEMO_OCULI,
16 AURA_DIABOLIC_OCULI_TALENT as DEMO_OCULI_TALENT,
17 AURA_INFERNAL_BOLT as DEMO_INFERNAL_BOLT,
18 AURA_LOOKS_THAT_KILL_TALENT as DEMO_LOOKS_THAT_KILL,
19 AURA_MINDS_EYES as DEMO_MINDS_EYES, AURA_MINDS_EYES_TALENT as DEMO_MINDS_EYES_TALENT,
20 AURA_RUINATION as DEMO_RUINATION,
21 },
22 destruction_warlock::{
23 AURA_DEMONIC_ART_MOTHER as DESTRO_ART_MOTHER,
24 AURA_DEMONIC_ART_OVERLORD as DESTRO_ART_OVERLORD,
25 AURA_DEMONIC_ART_PIT_LORD as DESTRO_ART_PIT_LORD, AURA_DEMONIC_OCULI as DESTRO_OCULI,
26 AURA_DIABOLIC_OCULI_TALENT as DESTRO_OCULI_TALENT,
27 AURA_INFERNAL_BOLT as DESTRO_INFERNAL_BOLT,
28 AURA_LOOKS_THAT_KILL_TALENT as DESTRO_LOOKS_THAT_KILL,
29 AURA_MINDS_EYES as DESTRO_MINDS_EYES,
30 AURA_MINDS_EYES_TALENT as DESTRO_MINDS_EYES_TALENT, AURA_RUINATION as DESTRO_RUINATION,
31 },
32 },
33};
34
35const DEMONOLOGY_TAG_BASE: u32 = 4_285_140;
37const DESTRUCTION_TAG_BASE: u32 = 4_285_150;
38const DEMONOLOGY_DAMAGE_MULT: f64 = 0.80;
39const DESTRUCTION_DAMAGE_MULT: f64 = 1.15;
40const GUARDIAN_LIFETIME_MS: u32 = 8_000;
41const OVERLORD_ACTION_DELAY_MS: u32 = 1_000;
42const OVERLORD_TAG_OFFSET: u32 = 1;
43const OVERLORD_MAX_ACTIONS: u16 = 1;
44const OVERLORD_AP_FROM_SP: f64 = 1.0;
45const MOTHER_TAG_OFFSET: u32 = 2;
46const PIT_LORD_TAG_OFFSET: u32 = 3;
47const PIT_LORD_ACTION_DELAY_MS: u32 = 500;
48const PIT_LORD_MAX_ACTIONS: u16 = 4;
49
50const RITUAL_VARIANT_COUNT: usize = 3;
51const RITUAL_FIRST_THRESHOLD: f64 = 1.0 / 3.0;
52const RITUAL_SECOND_THRESHOLD: f64 = 2.0 / 3.0;
53const OCULI_GAZE_ONE_STACK: i32 = 1;
54const OCULI_GAZE_TWO_STACKS: i32 = 2;
55const OCULI_GAZE_THREE_STACKS: i32 = 3;
56const OCULI_CONTROLLER_TAG_OFFSET: u32 = 4;
57
58#[derive(Clone, Copy, Debug, Eq, PartialEq)]
59#[non_exhaustive]
60pub(crate) enum WarlockSpec {
61 Demonology,
62 Destruction,
63}
64
65#[derive(Clone, Copy, Debug)]
66enum DiabolicDemon {
67 Overlord,
68 Mother,
69 PitLord,
70}
71
72#[derive(Clone, Copy, Debug)]
73pub(crate) struct DiabolicRitual {
74 pub talent: LocalAuraIdx,
75 pub art: [LocalAuraIdx; RITUAL_VARIANT_COUNT],
76 pub ritual: [LocalAuraIdx; RITUAL_VARIANT_COUNT],
77}
78
79impl DiabolicRitual {
80 #[must_use]
81 pub(crate) fn active(self, ctx: &HookCtx<'_>) -> bool {
82 self.ritual.into_iter().any(|aura| ctx.is_aura_active(aura))
83 }
84
85 #[must_use]
86 pub(crate) fn next_from_art(self, ctx: &HookCtx<'_>) -> Option<LocalAuraIdx> {
87 let [overlord_art, mother_art, pit_lord_art] = self.art;
88 let [overlord_ritual, mother_ritual, pit_lord_ritual] = self.ritual;
89
90 if ctx.is_aura_active(overlord_art) {
91 Some(mother_ritual)
92 } else if ctx.is_aura_active(mother_art) {
93 Some(pit_lord_ritual)
94 } else if ctx.is_aura_active(pit_lord_art) {
95 Some(overlord_ritual)
96 } else {
97 None
98 }
99 }
100
101 pub(crate) fn consume_art(self, ctx: &mut HookCtx<'_>) -> Option<LocalAuraIdx> {
102 let next = self.next_from_art(ctx);
103
104 for aura in self.art {
105 if ctx.consume_aura(aura) {
106 return next;
107 }
108 }
109
110 None
111 }
112
113 pub(crate) fn advance(
114 self,
115 ctx: &mut HookCtx<'_>,
116 next: Option<LocalAuraIdx>,
117 reduction_ms: u32,
118 ) {
119 if !ctx.is_aura_active(self.talent) {
120 return;
121 }
122
123 for aura in self.ritual {
124 if ctx.is_aura_active(aura) {
125 ctx.reduce_aura(aura, reduction_ms);
126
127 return;
128 }
129 }
130
131 let aura = next.unwrap_or_else(|| {
132 let roll = ctx.rng()();
133 let [overlord, mother, pit_lord] = self.ritual;
134
135 if roll < RITUAL_FIRST_THRESHOLD {
136 overlord
137 } else if roll < RITUAL_SECOND_THRESHOLD {
138 mother
139 } else {
140 pit_lord
141 }
142 });
143
144 ctx.apply_aura(aura);
145 ctx.reduce_aura(aura, reduction_ms);
146 }
147}
148
149const fn damage_mult(tag: u32) -> f64 {
150 if tag >= DESTRUCTION_TAG_BASE {
151 DESTRUCTION_DAMAGE_MULT
152 } else {
153 DEMONOLOGY_DAMAGE_MULT
154 }
155}
156
157fn overlord_action(ctx: &mut HookCtx<'_>, event: GuardianEvent) {
158 let coefficient = ctx.effect_ap_coefficient(DIABOLIC_DAMAGE_EFFECT::WICKED_CLEAVE_DAMAGE);
159
160 ctx.deal_damage_sp(
161 DIABOLIC_DAMAGE_REPORTED::WICKED_CLEAVE,
162 coefficient * OVERLORD_AP_FROM_SP * damage_mult(event.tag),
163 DamageFlags::PET | DamageFlags::IGNORE_CRIT_DAMAGE_BONUSES,
164 );
165}
166
167fn mother_action(ctx: &mut HookCtx<'_>, event: GuardianEvent) {
168 let coefficient = ctx.effect_sp_coefficient(DIABOLIC_DAMAGE_EFFECT::CHAOS_SALVO_DAMAGE);
169
170 ctx.deal_damage_sp(
171 DIABOLIC_DAMAGE_REPORTED::CHAOS_SALVO,
172 coefficient * damage_mult(event.tag),
173 DamageFlags::PET,
174 );
175}
176
177fn pit_lord_action(ctx: &mut HookCtx<'_>, event: GuardianEvent) {
178 let coefficient = ctx.effect_sp_coefficient(DIABOLIC_DAMAGE_EFFECT::FELSEEKER_DAMAGE);
179
180 ctx.deal_damage_sp(
181 DIABOLIC_DAMAGE_REPORTED::FELSEEKER,
182 coefficient * damage_mult(event.tag),
183 DamageFlags::PET,
184 );
185}
186
187const fn tag(base: u32, spec: WarlockSpec) -> u32 {
188 match spec {
189 WarlockSpec::Demonology => DEMONOLOGY_TAG_BASE + base,
190 WarlockSpec::Destruction => DESTRUCTION_TAG_BASE + base,
191 }
192}
193
194fn summon_overlord(ctx: &mut HookCtx<'_>, spec: WarlockSpec) {
195 let _ = ctx.summon_guardian(
196 GuardianSpec::new_inheriting_owner(tag(OVERLORD_TAG_OFFSET, spec), GUARDIAN_LIFETIME_MS)
197 .ability(
198 GuardianAbility::new(OVERLORD_ACTION_DELAY_MS, overlord_action)
199 .first_action_delay(OVERLORD_ACTION_DELAY_MS)
200 .max_actions(OVERLORD_MAX_ACTIONS)
201 .ends_guardian(),
202 ),
203 );
204}
205
206const CHAOS_SALVO_CHANNEL_MS: u32 = 4_000;
208const CHAOS_SALVO_TICK_MS: u32 = 200;
209
210fn summon_mother(ctx: &mut HookCtx<'_>, spec: WarlockSpec) {
211 let _ = ctx.summon_guardian(
212 GuardianSpec::new_inheriting_owner(
213 tag(MOTHER_TAG_OFFSET, spec),
214 CHAOS_SALVO_CHANNEL_MS + CHAOS_SALVO_TICK_MS,
215 )
216 .ability(
217 GuardianAbility::new(CHAOS_SALVO_TICK_MS, mother_action)
218 .first_action_delay(CHAOS_SALVO_TICK_MS)
219 .hasted_actions(),
220 ),
221 );
222}
223
224fn summon_pit_lord(ctx: &mut HookCtx<'_>, spec: WarlockSpec) {
225 let _ = ctx.summon_guardian(
226 GuardianSpec::new_inheriting_owner(tag(PIT_LORD_TAG_OFFSET, spec), GUARDIAN_LIFETIME_MS)
227 .ability(
228 GuardianAbility::new(PIT_LORD_ACTION_DELAY_MS, pit_lord_action)
229 .first_action_delay(PIT_LORD_ACTION_DELAY_MS)
230 .hasted_actions()
231 .max_actions(PIT_LORD_MAX_ACTIONS)
232 .ends_guardian(),
233 ),
234 );
235}
236
237const fn art_aura(spec: WarlockSpec, demon: DiabolicDemon) -> LocalAuraIdx {
238 match (spec, demon) {
239 (WarlockSpec::Demonology, DiabolicDemon::Overlord) => DEMO_ART_OVERLORD,
240 (WarlockSpec::Demonology, DiabolicDemon::Mother) => DEMO_ART_MOTHER,
241 (WarlockSpec::Demonology, DiabolicDemon::PitLord) => DEMO_ART_PIT_LORD,
242 (WarlockSpec::Destruction, DiabolicDemon::Overlord) => DESTRO_ART_OVERLORD,
243 (WarlockSpec::Destruction, DiabolicDemon::Mother) => DESTRO_ART_MOTHER,
244 (WarlockSpec::Destruction, DiabolicDemon::PitLord) => DESTRO_ART_PIT_LORD,
245 }
246}
247
248fn diabolic_ritual_expire(ctx: &mut HookCtx<'_>, spec: WarlockSpec, demon: DiabolicDemon) {
249 ctx.apply_aura(art_aura(spec, demon));
250}
251
252fn demonic_art_expire(ctx: &mut HookCtx<'_>, spec: WarlockSpec, demon: DiabolicDemon) {
253 match demon {
254 DiabolicDemon::Overlord => summon_overlord(ctx, spec),
255 DiabolicDemon::Mother => {
256 summon_mother(ctx, spec);
257 let aura = match spec {
258 WarlockSpec::Demonology => DEMO_INFERNAL_BOLT,
259 WarlockSpec::Destruction => DESTRO_INFERNAL_BOLT,
260 };
261
262 ctx.apply_aura(aura);
263 }
264 DiabolicDemon::PitLord => {
265 summon_pit_lord(ctx, spec);
266 let aura = match spec {
267 WarlockSpec::Demonology => DEMO_RUINATION,
268 WarlockSpec::Destruction => DESTRO_RUINATION,
269 };
270
271 ctx.apply_aura(aura);
272 }
273 }
274}
275
276pub(crate) fn demonology_diabolic_ritual_overlord_expire_hook(ctx: &mut HookCtx<'_>) {
277 diabolic_ritual_expire(ctx, WarlockSpec::Demonology, DiabolicDemon::Overlord);
278}
279
280pub(crate) fn demonology_diabolic_ritual_mother_expire_hook(ctx: &mut HookCtx<'_>) {
281 diabolic_ritual_expire(ctx, WarlockSpec::Demonology, DiabolicDemon::Mother);
282}
283
284pub(crate) fn demonology_diabolic_ritual_pit_lord_expire_hook(ctx: &mut HookCtx<'_>) {
285 diabolic_ritual_expire(ctx, WarlockSpec::Demonology, DiabolicDemon::PitLord);
286}
287
288pub(crate) fn demonology_demonic_art_overlord_expire_hook(ctx: &mut HookCtx<'_>) {
289 demonic_art_expire(ctx, WarlockSpec::Demonology, DiabolicDemon::Overlord);
290}
291
292pub(crate) fn demonology_demonic_art_mother_expire_hook(ctx: &mut HookCtx<'_>) {
293 demonic_art_expire(ctx, WarlockSpec::Demonology, DiabolicDemon::Mother);
294}
295
296pub(crate) fn demonology_demonic_art_pit_lord_expire_hook(ctx: &mut HookCtx<'_>) {
297 demonic_art_expire(ctx, WarlockSpec::Demonology, DiabolicDemon::PitLord);
298}
299
300pub(crate) fn destruction_diabolic_ritual_overlord_expire_hook(ctx: &mut HookCtx<'_>) {
301 diabolic_ritual_expire(ctx, WarlockSpec::Destruction, DiabolicDemon::Overlord);
302}
303
304pub(crate) fn destruction_diabolic_ritual_mother_expire_hook(ctx: &mut HookCtx<'_>) {
305 diabolic_ritual_expire(ctx, WarlockSpec::Destruction, DiabolicDemon::Mother);
306}
307
308pub(crate) fn destruction_diabolic_ritual_pit_lord_expire_hook(ctx: &mut HookCtx<'_>) {
309 diabolic_ritual_expire(ctx, WarlockSpec::Destruction, DiabolicDemon::PitLord);
310}
311
312pub(crate) fn destruction_demonic_art_overlord_expire_hook(ctx: &mut HookCtx<'_>) {
313 demonic_art_expire(ctx, WarlockSpec::Destruction, DiabolicDemon::Overlord);
314}
315
316pub(crate) fn destruction_demonic_art_mother_expire_hook(ctx: &mut HookCtx<'_>) {
317 demonic_art_expire(ctx, WarlockSpec::Destruction, DiabolicDemon::Mother);
318}
319
320pub(crate) fn destruction_demonic_art_pit_lord_expire_hook(ctx: &mut HookCtx<'_>) {
321 demonic_art_expire(ctx, WarlockSpec::Destruction, DiabolicDemon::PitLord);
322}
323
324#[derive(Clone, Copy)]
325struct OculiIds {
326 talent: LocalAuraIdx,
327 oculi: LocalAuraIdx,
328 looks_that_kill: LocalAuraIdx,
329 minds_eyes_talent: LocalAuraIdx,
330 minds_eyes: LocalAuraIdx,
331}
332
333const fn oculi_ids(spec: WarlockSpec) -> OculiIds {
334 match spec {
335 WarlockSpec::Demonology => OculiIds {
336 talent: DEMO_OCULI_TALENT,
337 oculi: DEMO_OCULI,
338 looks_that_kill: DEMO_LOOKS_THAT_KILL,
339 minds_eyes_talent: DEMO_MINDS_EYES_TALENT,
340 minds_eyes: DEMO_MINDS_EYES,
341 },
342 WarlockSpec::Destruction => OculiIds {
343 talent: DESTRO_OCULI_TALENT,
344 oculi: DESTRO_OCULI,
345 looks_that_kill: DESTRO_LOOKS_THAT_KILL,
346 minds_eyes_talent: DESTRO_MINDS_EYES_TALENT,
347 minds_eyes: DESTRO_MINDS_EYES,
348 },
349 }
350}
351
352fn oculi_tick(ctx: &mut HookCtx<'_>, _event: GuardianEvent, spec: WarlockSpec) {
353 let ids = oculi_ids(spec);
354
355 if !ctx.is_aura_active(ids.oculi) || !ctx.is_aura_active(ids.looks_that_kill) {
356 return;
357 }
358
359 let (spell_id, effect) = match ctx.aura_stacks(ids.oculi) {
360 OCULI_GAZE_ONE_STACK => (
361 DIABOLIC_DAMAGE_REPORTED::DIABOLIC_GAZE_1,
362 DIABOLIC_DAMAGE_EFFECT::DIABOLIC_GAZE_1_DAMAGE,
363 ),
364 OCULI_GAZE_TWO_STACKS => (
365 DIABOLIC_DAMAGE_REPORTED::DIABOLIC_GAZE_2,
366 DIABOLIC_DAMAGE_EFFECT::DIABOLIC_GAZE_2_DAMAGE,
367 ),
368 OCULI_GAZE_THREE_STACKS => (
369 DIABOLIC_DAMAGE_REPORTED::DIABOLIC_GAZE_3,
370 DIABOLIC_DAMAGE_EFFECT::DIABOLIC_GAZE_3_DAMAGE,
371 ),
372 _ => return,
373 };
374 let coefficient = ctx.effect_sp_coefficient(effect);
375
376 ctx.deal_damage_sp(spell_id, coefficient, DamageFlags::empty());
377}
378
379fn demonology_oculi_tick(ctx: &mut HookCtx<'_>, event: GuardianEvent) {
380 oculi_tick(ctx, event, WarlockSpec::Demonology);
381}
382
383fn destruction_oculi_tick(ctx: &mut HookCtx<'_>, event: GuardianEvent) {
384 oculi_tick(ctx, event, WarlockSpec::Destruction);
385}
386
387pub(crate) fn trigger_oculi(ctx: &mut HookCtx<'_>, spec: WarlockSpec) {
388 let ids = oculi_ids(spec);
389
390 if !ctx.is_aura_active(ids.talent) {
391 return;
392 }
393
394 ctx.apply_aura(ids.oculi);
395 let controller_tag = tag(OCULI_CONTROLLER_TAG_OFFSET, spec);
396
397 if ctx.active_guardian_count(controller_tag) > 0 {
398 return;
399 }
400
401 let fight_end_ms = wowlab_types::numeric::f64_to_u32_saturating_trunc(
402 ctx.program().encounter().fight_duration_secs() * MS_PER_SECOND,
403 );
404 let duration_ms = fight_end_ms
405 .saturating_sub(ctx.now().as_millis())
406 .saturating_add(1);
407 let action = match spec {
408 WarlockSpec::Demonology => demonology_oculi_tick,
409 WarlockSpec::Destruction => destruction_oculi_tick,
410 };
411 let oculi_action = match spec {
412 WarlockSpec::Demonology => {
413 crate::generated::specs::demonology_warlock::DAMAGE_AURA::DEMONIC_OCULI
414 }
415 WarlockSpec::Destruction => {
416 crate::generated::specs::destruction_warlock::DAMAGE_AURA::DEMONIC_OCULI
417 }
418 };
419 let tick_ms = ctx.action_aura_tick_ms(oculi_action);
420 let _ = ctx.summon_guardian(
421 GuardianSpec::new_inheriting_owner(controller_tag, duration_ms)
422 .ability(GuardianAbility::new(tick_ms, action).first_action_delay(tick_ms)),
423 );
424}
425
426pub(crate) fn explode_oculi(ctx: &mut HookCtx<'_>, spec: WarlockSpec) {
427 let ids = oculi_ids(spec);
428 let stacks = ctx.aura_stacks(ids.oculi);
429
430 if stacks <= 0 {
431 return;
432 }
433
434 if ctx.is_aura_active(ids.minds_eyes_talent) {
435 ctx.apply_aura_n(ids.minds_eyes, stacks);
436 }
437
438 let coefficient = ctx.effect_sp_coefficient(DIABOLIC_DAMAGE_EFFECT::EYE_EXPLOSION_DAMAGE);
439
440 ctx.deal_damage_sp(
441 DIABOLIC_DAMAGE_REPORTED::EYE_EXPLOSION,
442 coefficient * f64::from(stacks),
443 DamageFlags::empty(),
444 );
445 ctx.expire_aura(ids.oculi);
446}