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