1use wowlab_engine_combat::AuraOps as _;
2use wowlab_engine_combat::CooldownOps as _;
3use wowlab_engine_combat::DamageOps as _;
4use wowlab_engine_combat::GuardianOps as _;
5use wowlab_engine_combat::ProcOps as _;
6use wowlab_engine_combat::ResourceOps as _;
7use wowlab_engine_combat::SchedulingOps as _;
8use wowlab_engine_combat::{
9 BuffEffect, CombatSystemDraft, DamageFlags, HookCtx, ImpactEvent, ImpactProc, MasteryCtx,
10 PassiveDriverEffect, RuntimeDamageDef, try_push_effect,
11};
12use wowlab_engine_domain::dbc::ResolvedGameDataEffectExt as _;
13use wowlab_engine_ports::HandlerParams;
14use wowlab_engine_rng::proc_chance;
15use wowlab_types::{constants::HUNDRED, sim::SpellIdx};
16
17use crate::generated::specs::elemental_shaman::{
18 AURA_ANCESTRAL_SWIFTNESS, AURA_ASCENDANCE, AURA_ELEMENTAL_BLAST_CRIT,
19 AURA_ELEMENTAL_BLAST_HASTE, AURA_ELEMENTAL_BLAST_MASTERY, AURA_FIRE_ELEMENTAL,
20 AURA_FLAME_SHOCK, AURA_FLOWING_ELEMENTS, AURA_LAVA_SURGE, AURA_LIGHTNING_ROD,
21 AURA_LIGHTNING_SHIELD, AURA_MASTER_OF_THE_ELEMENTS, AURA_OVERCHARGE_TIER,
22 AURA_POWER_OF_THE_MAELSTROM, AURA_STORM_ELEMENTAL, AURA_STORMKEEPER, AURA_STORMS_EYE,
23 AURA_TEMPEST, AURA_UNLIMITED_POWER, AURA_WIND_GUST, EFFECT, SET_BONUS, SPELL,
24 SPELL_ELEMENTAL_BLAST, SPELL_LAVA_BURST, TALENT,
25};
26
27const LAVA_BURST_MAELSTROM: f64 = 8.0;
29const LIGHTNING_BOLT_OVERLOAD_MAELSTROM: f64 = 2.0;
30const CHAIN_LIGHTNING_OVERLOAD_MAELSTROM: f64 = 1.0;
31const LAVA_BURST_OVERLOAD_MAELSTROM: f64 = 3.0;
32
33const CHAIN_OVERLOAD_DIVISOR: f64 = 3.0;
34
35const STORMKEEPER_CAST_CHARGES: u32 = 2;
37const ARC_DISCHARGE_CHARGES: u32 = 1;
39const MID2_EMPOWERED_BUILDER_CHARGES: i32 = 2;
40const MID2_OVERLAP_EMPOWERED_BUILDER_CHARGES: i32 = 4;
41
42const LAVA_SURGE_CEILING: f64 = 0.6;
44const LAVA_SURGE_DECAY_BASE: f64 = 1.16;
45const LAVA_SURGE_MIDPOINT: f64 = 5.0;
46const LAVA_SURGE_EXPONENT_SCALE: f64 = -2.0;
47
48#[rustfmt::skip]
50static EU_FIRE_PLAYER_SPELLS: [u32; 3] = [
51 SPELL::ELEMENTAL_BLAST,
52 EFFECT::ELEMENTAL_BLAST_OVERLOAD.0,
53 SPELL::FLAME_SHOCK,
54];
55static EU_FIRE_PET_SPELLS: [u32; 3] = [SPELL::METEOR, SPELL::IMMOLATE, SPELL::FIRE_BLAST];
57#[rustfmt::skip]
59static FLAG_126_PLAYER_SPELLS: [u32; 11] = [
60 SPELL::LIGHTNING_BOLT,
61 SPELL::CHAIN_LIGHTNING,
62 SPELL::ELEMENTAL_BLAST,
63 SPELL::TEMPEST,
64 SPELL::EARTH_SHOCK,
65 SPELL::FLAME_SHOCK,
66 SPELL::STORMS_EYE,
67 EFFECT::LIGHTNING_BOLT_OVERLOAD.0,
68 EFFECT::CHAIN_LIGHTNING_OVERLOAD.0,
69 EFFECT::ELEMENTAL_BLAST_OVERLOAD.0,
70 EFFECT::TEMPEST_OVERLOAD.0,
71];
72static EU_STORM_PET_SPELLS: [u32; 3] = [SPELL::WIND_GUST, SPELL::CALL_LIGHTNING, SPELL::STORMFURY];
74static EU_FIRE_MASKED_EFFECTS: [(u32, u8); 2] = [
76 EFFECT::FIRE_ELEMENTAL_UNITY,
77 EFFECT::FIRE_ELEMENTAL_UNITY_PERIODIC,
78];
79static EU_STORM_MASKED_EFFECTS: [(u32, u8); 2] = [
80 EFFECT::STORM_ELEMENTAL_UNITY,
81 EFFECT::STORM_ELEMENTAL_UNITY_PET,
82];
83#[rustfmt::skip]
85static IFW_SPELLS: [u32; 5] = [
86 SPELL::FLAME_SHOCK,
87 SPELL::LAVA_BURST,
88 SPELL::ELEMENTAL_BLAST,
89 EFFECT::LAVA_BURST_OVERLOAD.0,
90 EFFECT::ELEMENTAL_BLAST_OVERLOAD.0,
91];
92
93pub(crate) fn earthquake_hook(ctx: &mut HookCtx<'_>) {
94 if let Some(effect) = elemental_config(ctx).earthquake_ground {
95 ctx.schedule_ground_effect(effect);
96 }
97
98 ctx.consume_aura(AURA_OVERCHARGE_TIER);
99}
100
101pub(crate) fn earth_shock_hook(ctx: &mut HookCtx<'_>) {
102 ctx.consume_aura(AURA_OVERCHARGE_TIER);
103}
104
105const STORMS_EYE_CHARGES: i32 = 2;
106
107const RANDOM_STAT_ROLL_THIRD: f64 = 1.0 / 3.0;
108const RANDOM_STAT_ROLL_TWO_THIRDS: f64 = 2.0 / 3.0;
109
110use super::{
111 config::{ElementalRuntime, elemental_config, register_elemental_config},
112 elementals::{STORM_ELEMENTAL_GUARDIAN_TAG, summon_fire_elemental, summon_storm_elemental},
113};
114
115fn trigger_elemental_blast_stat(ctx: &mut HookCtx<'_>) {
116 let aura = match ctx.rng()() {
117 roll if roll < RANDOM_STAT_ROLL_THIRD => AURA_ELEMENTAL_BLAST_CRIT,
118 roll if roll < RANDOM_STAT_ROLL_TWO_THIRDS => AURA_ELEMENTAL_BLAST_HASTE,
119 _ => AURA_ELEMENTAL_BLAST_MASTERY,
120 };
121
122 ctx.apply_aura(aura);
123}
124
125fn overload_chance(ctx: &HookCtx<'_>) -> f64 {
127 ctx.effect_points(crate::generated::specs::elemental_shaman::DAMAGE_EFFECT::OVERLOAD_CHANCE)
128 / HUNDRED
129}
130
131fn overload_penalty(ctx: &HookCtx<'_>) -> f64 {
133 ctx.effect_base_points(
134 crate::generated::specs::elemental_shaman::DAMAGE_EFFECT::OVERLOAD_PENALTY,
135 ) / HUNDRED
136}
137
138fn fire_overload(
139 ctx: &mut HookCtx<'_>,
140 effect: wowlab_engine_combat::DamageEffectId,
141 maelstrom: f64,
142) {
143 fire_overload_scaled(ctx, effect, maelstrom, 1.0);
144 let feedback_chance = elemental_config(ctx).feedback_loop_chance;
145
146 if feedback_chance > 0.0 && proc_chance(ctx.rng(), feedback_chance) {
147 fire_overload_scaled(ctx, effect, maelstrom, 1.0);
148 }
149}
150
151fn fire_overload_scaled(
152 ctx: &mut HookCtx<'_>,
153 effect: wowlab_engine_combat::DamageEffectId,
154 maelstrom: f64,
155 damage_mult: f64,
156) {
157 let coef = ctx.effect_sp_coefficient(effect) * overload_penalty(ctx) * damage_mult;
158
159 ctx.deal_damage_sp(effect.action(), coef, DamageFlags::empty());
160
161 if maelstrom > 0.0 {
162 ctx.gain_resource(maelstrom);
163 }
164}
165
166fn fire_elemental_blast_overloads(ctx: &mut HookCtx<'_>, damage_mult: f64) {
168 let cfg = elemental_config(ctx);
169 let fire = |ctx: &mut HookCtx<'_>| {
170 trigger_elemental_blast_stat(ctx);
171 fire_overload_scaled(
172 ctx,
173 crate::generated::specs::elemental_shaman::DAMAGE_EFFECT::ELEMENTAL_BLAST_OVERLOAD,
174 0.0,
175 damage_mult,
176 );
177
178 if cfg.feedback_loop_chance > 0.0 && proc_chance(ctx.rng(), cfg.feedback_loop_chance) {
179 trigger_elemental_blast_stat(ctx);
180 fire_overload_scaled(
181 ctx,
182 crate::generated::specs::elemental_shaman::DAMAGE_EFFECT::ELEMENTAL_BLAST_OVERLOAD,
183 0.0,
184 damage_mult,
185 );
186 }
187 };
188
189 let chance = overload_chance(ctx);
190
191 if proc_chance(ctx.rng(), chance) {
192 fire(ctx);
193 }
194
195 if ctx.is_aura_active(AURA_ASCENDANCE) {
196 fire(ctx);
197 }
198}
199
200fn fire_elemental_blast(ctx: &mut HookCtx<'_>, damage_mult: f64) {
201 trigger_elemental_blast_stat(ctx);
202 let coefficient = ctx.effect_sp_coefficient(
203 crate::generated::specs::elemental_shaman::DAMAGE_EFFECT::ELEMENTAL_BLAST_DAMAGE,
204 ) * damage_mult;
205
206 ctx.deal_damage_sp(
207 crate::generated::specs::elemental_shaman::DAMAGE_SPELL::ELEMENTAL_BLAST,
208 coefficient,
209 DamageFlags::empty(),
210 );
211 let cfg = elemental_config(ctx);
212
213 if cfg.eb_overload {
214 fire_elemental_blast_overloads(ctx, cfg.eb_overload_mult * damage_mult);
215 }
216}
217
218fn spend_stormkeeper_charge(ctx: &mut HookCtx<'_>) {
219 if ctx.consume_proc_counter(
220 wowlab_engine_combat::generated_identities::proc_counter::STORMKEEPER,
221 1,
222 ) == 0
223 {
224 ctx.expire_aura(AURA_STORMKEEPER);
225 }
226}
227
228fn consume_flowing_elements(ctx: &mut HookCtx<'_>) {
229 if ctx.consume_aura_stack(AURA_FLOWING_ELEMENTS) {
230 ctx.apply_aura(AURA_OVERCHARGE_TIER);
231 }
232}
233
234#[derive(Clone, Copy)]
235struct EmpoweredLightningOptions {
236 chance_divisor: f64,
237 stormkeeper_overload: bool,
238 consume_stormkeeper: bool,
239}
240
241const LIGHTNING_BOLT_OPTIONS: EmpoweredLightningOptions = EmpoweredLightningOptions {
242 chance_divisor: 1.0,
243 stormkeeper_overload: true,
244 consume_stormkeeper: true,
245};
246const TEMPEST_OPTIONS: EmpoweredLightningOptions = EmpoweredLightningOptions {
247 chance_divisor: 1.0,
248 stormkeeper_overload: false,
249 consume_stormkeeper: false,
250};
251const CHAIN_LIGHTNING_OPTIONS: EmpoweredLightningOptions = EmpoweredLightningOptions {
252 chance_divisor: CHAIN_OVERLOAD_DIVISOR,
253 stormkeeper_overload: true,
254 consume_stormkeeper: true,
255};
256
257fn empowered_lightning_cast(
258 ctx: &mut HookCtx<'_>,
259 overload: wowlab_engine_combat::DamageEffectId,
260 overload_maelstrom: f64,
261 options: EmpoweredLightningOptions,
262) {
263 let stormkeeper = ctx.is_aura_active(AURA_STORMKEEPER);
264 let cfg = elemental_config(ctx);
265
266 let chance = overload_chance(ctx) / options.chance_divisor;
267
268 if (stormkeeper && options.stormkeeper_overload) || proc_chance(ctx.rng(), chance) {
269 fire_overload(ctx, overload, overload_maelstrom);
270 }
271
272 if stormkeeper && options.consume_stormkeeper {
273 spend_stormkeeper_charge(ctx);
274 }
275
276 if ctx.consume_aura(AURA_POWER_OF_THE_MAELSTROM) {
277 fire_overload(ctx, overload, overload_maelstrom);
278
279 if cfg.fusion_of_elements_mult > 0.0 {
280 fire_elemental_blast(ctx, cfg.fusion_of_elements_mult);
281 }
282 }
283
284 if cfg.supercharge_chance > 0.0 && proc_chance(ctx.rng(), cfg.supercharge_chance) {
285 fire_overload(ctx, overload, overload_maelstrom);
286 }
287 if ctx.is_aura_active(AURA_ASCENDANCE) {
290 fire_overload(ctx, overload, overload_maelstrom);
291 }
292
293 if ctx.active_guardian_count(STORM_ELEMENTAL_GUARDIAN_TAG) > 0 {
294 ctx.apply_aura(AURA_WIND_GUST);
295 }
296
297 ctx.consume_aura(AURA_MASTER_OF_THE_ELEMENTS);
298}
299
300pub(crate) fn lightning_bolt_hook(ctx: &mut HookCtx<'_>) {
301 empowered_lightning_cast(
302 ctx,
303 crate::generated::specs::elemental_shaman::DAMAGE_EFFECT::LIGHTNING_BOLT_OVERLOAD,
304 LIGHTNING_BOLT_OVERLOAD_MAELSTROM,
305 LIGHTNING_BOLT_OPTIONS,
306 );
307 ctx.consume_aura(AURA_ANCESTRAL_SWIFTNESS);
308 consume_flowing_elements(ctx);
309}
310
311pub(crate) fn tempest_hook(ctx: &mut HookCtx<'_>) {
312 let stormwell_maelstrom = elemental_config(ctx).stormwell_tempest_maelstrom;
313
314 if stormwell_maelstrom > 0.0
315 && elemental_config(ctx).stormwell_tempest_bug
316 && !ctx.is_aura_active(AURA_STORMKEEPER)
317 {
318 ctx.gain_resource(stormwell_maelstrom);
319 }
320
321 ctx.consume_aura(AURA_TEMPEST);
324
325 if elemental_config(ctx).tww3_stormbringer_4pc && ctx.consume_aura_stack(AURA_STORMS_EYE) {
326 let storms_eye = ctx.effect_sp_coefficient(
327 crate::generated::specs::elemental_shaman::DAMAGE_EFFECT::STORMS_EYE_DAMAGE,
328 );
329
330 ctx.deal_damage_sp(
331 crate::generated::specs::elemental_shaman::DAMAGE_SPELL::STORMS_EYE,
332 storms_eye,
333 DamageFlags::empty(),
334 );
335 }
336
337 empowered_lightning_cast(
338 ctx,
339 crate::generated::specs::elemental_shaman::DAMAGE_EFFECT::TEMPEST_OVERLOAD,
340 0.0,
341 TEMPEST_OPTIONS,
342 );
343
344 if elemental_config(ctx).arc_discharge {
345 ctx.apply_aura(AURA_STORMKEEPER);
346 ctx.add_proc_counter(
347 wowlab_engine_combat::generated_identities::proc_counter::STORMKEEPER,
348 ARC_DISCHARGE_CHARGES,
349 );
350 }
351}
352
353pub(crate) fn chain_lightning_hook(ctx: &mut HookCtx<'_>) {
354 empowered_lightning_cast(
355 ctx,
356 crate::generated::specs::elemental_shaman::DAMAGE_EFFECT::CHAIN_LIGHTNING_OVERLOAD,
357 CHAIN_LIGHTNING_OVERLOAD_MAELSTROM,
358 CHAIN_LIGHTNING_OPTIONS,
359 );
360 ctx.consume_aura(AURA_ANCESTRAL_SWIFTNESS);
361 consume_flowing_elements(ctx);
362}
363
364pub(crate) fn lava_burst_hook(ctx: &mut HookCtx<'_>) {
365 let cfg = elemental_config(ctx);
366
367 if cfg.master_of_the_elements {
368 ctx.apply_aura(AURA_MASTER_OF_THE_ELEMENTS);
369 }
370
371 if cfg.potm_chance > 0.0 && proc_chance(ctx.rng(), cfg.potm_chance) {
372 ctx.apply_aura(AURA_POWER_OF_THE_MAELSTROM);
373 }
374
375 ctx.gain_resource(LAVA_BURST_MAELSTROM);
376 ctx.consume_aura(AURA_LAVA_SURGE);
377
378 let chance = overload_chance(ctx);
379
380 if proc_chance(ctx.rng(), chance) {
381 fire_overload(
382 ctx,
383 crate::generated::specs::elemental_shaman::DAMAGE_EFFECT::LAVA_BURST_OVERLOAD,
384 LAVA_BURST_OVERLOAD_MAELSTROM,
385 );
386 }
387
388 if ctx.is_aura_active(AURA_ASCENDANCE) {
389 fire_overload(
390 ctx,
391 crate::generated::specs::elemental_shaman::DAMAGE_EFFECT::LAVA_BURST_OVERLOAD,
392 LAVA_BURST_OVERLOAD_MAELSTROM,
393 );
394 }
395
396 ctx.consume_aura(AURA_ANCESTRAL_SWIFTNESS);
397 consume_flowing_elements(ctx);
398}
399
400pub(crate) fn elemental_blast_hook(ctx: &mut HookCtx<'_>) {
401 let maelstrom_spent = ctx.last_primary_spent();
402
403 fire_elemental_blast(ctx, 1.0);
404
405 if elemental_config(ctx).unlimited_power {
406 ctx.apply_aura(AURA_UNLIMITED_POWER);
407 }
408
409 ctx.apply_aura(AURA_LIGHTNING_ROD);
410 ctx.consume_aura(AURA_MASTER_OF_THE_ELEMENTS);
411 let cfg = elemental_config(ctx);
412
413 if cfg.aftershock_chance > 0.0 && proc_chance(ctx.rng(), cfg.aftershock_chance) {
414 ctx.gain_resource(maelstrom_spent);
415 }
416
417 if maelstrom_spent > 0.0
418 && cfg.tempest_chance_on_blast > 0.0
419 && proc_chance(ctx.rng(), cfg.tempest_chance_on_blast)
420 {
421 ctx.apply_aura(AURA_TEMPEST);
422 }
423
424 ctx.consume_aura(AURA_OVERCHARGE_TIER);
425 ctx.consume_aura(AURA_ANCESTRAL_SWIFTNESS);
426}
427
428pub(crate) fn stormkeeper_hook(ctx: &mut HookCtx<'_>) {
429 let cfg = elemental_config(ctx);
430
431 ctx.reset_proc_counter(wowlab_engine_combat::generated_identities::proc_counter::STORMKEEPER);
432 ctx.add_proc_counter(
433 wowlab_engine_combat::generated_identities::proc_counter::STORMKEEPER,
434 STORMKEEPER_CAST_CHARGES,
435 );
436
437 if cfg.summon_storm_elemental {
438 summon_storm_elemental(ctx);
439 }
440}
441
442pub(crate) fn stormkeeper_expire_hook(ctx: &mut HookCtx<'_>) {
443 if !elemental_config(ctx).mid2_4pc {
444 return;
445 }
446
447 if ctx.is_aura_active(AURA_ASCENDANCE) {
448 if let Some(runtime) = ctx.spec_runtime_mut::<ElementalRuntime>() {
449 runtime.stormkeeper_faded_during_ascendance = true;
450 }
451 } else {
452 ctx.apply_aura_n(AURA_FLOWING_ELEMENTS, MID2_EMPOWERED_BUILDER_CHARGES);
453 }
454}
455
456pub(crate) fn ascendance_expire_hook(ctx: &mut HookCtx<'_>) {
457 if !elemental_config(ctx).mid2_4pc {
458 return;
459 }
460
461 let stormkeeper_buffered = ctx
462 .spec_runtime_mut::<ElementalRuntime>()
463 .is_some_and(|runtime| std::mem::take(&mut runtime.stormkeeper_faded_during_ascendance));
464 let stacks = if stormkeeper_buffered {
465 MID2_OVERLAP_EMPOWERED_BUILDER_CHARGES
466 } else {
467 MID2_EMPOWERED_BUILDER_CHARGES
468 };
469
470 ctx.apply_aura_n(AURA_FLOWING_ELEMENTS, stacks);
471}
472
473pub(crate) fn ascendance_hook(ctx: &mut HookCtx<'_>) {
474 let cfg = elemental_config(ctx);
475
476 if cfg.tww3_stormbringer_2pc {
477 ctx.apply_aura(AURA_TEMPEST);
478 }
479
480 if cfg.tww3_stormbringer_4pc {
481 ctx.apply_aura_n(AURA_STORMS_EYE, STORMS_EYE_CHARGES);
482 }
483
484 ctx.reset_cooldown(SPELL_LAVA_BURST);
485 ctx.apply_aura(AURA_FLAME_SHOCK);
486 let lava_burst = ctx.effect_sp_coefficient(
487 crate::generated::specs::elemental_shaman::DAMAGE_EFFECT::LAVA_BURST_DAMAGE,
488 );
489
490 ctx.deal_damage_sp(
491 crate::generated::specs::elemental_shaman::DAMAGE_SPELL::LAVA_BURST,
492 lava_burst,
493 DamageFlags::empty(),
494 );
495 fire_overload(
496 ctx,
497 crate::generated::specs::elemental_shaman::DAMAGE_EFFECT::LAVA_BURST_OVERLOAD,
498 LAVA_BURST_OVERLOAD_MAELSTROM,
499 );
500
501 let chance = overload_chance(ctx);
502
503 if proc_chance(ctx.rng(), chance) {
504 fire_overload(
505 ctx,
506 crate::generated::specs::elemental_shaman::DAMAGE_EFFECT::LAVA_BURST_OVERLOAD,
507 LAVA_BURST_OVERLOAD_MAELSTROM,
508 );
509 }
510
511 if cfg.summon_fire_elemental {
512 summon_fire_elemental(ctx);
513 }
514}
515
516#[must_use]
518pub(crate) fn mastery(ctx: &MasteryCtx<'_>) -> f64 {
519 if ctx.is_pet {
520 return ctx.bonus(EFFECT::MASTERY_PET_DAMAGE.1);
521 }
522
523 let effect = if ctx.is_periodic {
524 EFFECT::MASTERY_PERIODIC_DAMAGE.1
525 } else {
526 EFFECT::MASTERY_DIRECT_DAMAGE.1
527 };
528 let mut mult = if ctx.affects_spell(effect) {
529 ctx.bonus(effect)
530 } else {
531 1.0
532 };
533
534 if matches!(ctx.spell_id, SPELL::LAVA_BURST | SPELL::LAVA_BURST_OVERLOAD)
535 && ctx.is_aura_active(AURA_FLAME_SHOCK)
536 {
537 mult *= 1.0 + ctx.instance.player_crit() / HUNDRED;
538 }
539
540 mult
541}
542
543const fn flame_shock_filter(spell_id: u32) -> bool {
544 spell_id == SPELL::FLAME_SHOCK
545}
546
547const fn lightning_rod_filter(spell_id: u32) -> bool {
548 matches!(
549 spell_id,
550 SPELL::LIGHTNING_BOLT
551 | SPELL::CHAIN_LIGHTNING
552 | SPELL::TEMPEST
553 | SPELL::LIGHTNING_BOLT_OVERLOAD
554 | SPELL::CHAIN_LIGHTNING_OVERLOAD
555 | SPELL::TEMPEST_OVERLOAD
556 )
557}
558
559fn lightning_rod_fire(ctx: &mut HookCtx<'_>, impact: ImpactEvent) {
560 let cfg = elemental_config(ctx);
561
562 if cfg.lightning_rod_mult > 0.0 && ctx.is_aura_active(AURA_LIGHTNING_ROD) {
563 ctx.deal_resolved_damage(
564 crate::generated::specs::elemental_shaman::DAMAGE_SPELL::LIGHTNING_ROD_DAMAGE,
565 impact.amount * cfg.lightning_rod_mult,
566 );
567 }
568}
569
570fn lava_surge_fire(ctx: &mut HookCtx<'_>, _impact: ImpactEvent) {
571 if ctx.roll_attempt_proc(
572 wowlab_engine_combat::generated_identities::proc_counter::LAVA_SURGE_ATTEMPT,
573 |attempt| {
574 let exponent = LAVA_SURGE_EXPONENT_SCALE * (f64::from(attempt) - LAVA_SURGE_MIDPOINT);
575
576 (LAVA_SURGE_CEILING - LAVA_SURGE_DECAY_BASE.powf(exponent)).max(0.0)
577 },
578 ) {
579 ctx.reset_cooldown(SPELL_LAVA_BURST);
580 ctx.apply_aura(AURA_LAVA_SURGE);
581 }
582}
583
584static STORMKEEPER_OVERLOAD_SPELLS: [u32; 1] = [EFFECT::LIGHTNING_BOLT_OVERLOAD.0];
586
587static MID2_SPENDER_DAMAGE_SPELLS: [u32; 3] = [
588 SPELL::EARTH_SHOCK,
589 SPELL::ELEMENTAL_BLAST,
590 EFFECT::EARTHQUAKE_DAMAGE.0,
591];
592
593fn register_conditional_multipliers(built: &mut CombatSystemDraft, params: &HandlerParams<'_>) {
594 let data = ¶ms.game_data;
595 let mult_from = |effect: (u32, u8)| {
596 1.0 + data.base_points(SpellIdx::from_raw(effect.0), effect.1) / HUNDRED
597 };
598
599 for effect in EU_FIRE_MASKED_EFFECTS {
605 built.mask_aura_dbc_effect(AURA_FIRE_ELEMENTAL, effect.0, effect.1);
606 }
607
608 for effect in EU_STORM_MASKED_EFFECTS {
609 built.mask_aura_dbc_effect(AURA_STORM_ELEMENTAL, effect.0, effect.1);
610 }
611
612 if params.talent_picked(TALENT::ELEMENTAL_UNITY) {
613 let pe = params.talent_picked(TALENT::PRIMAL_ELEMENTALIST);
614 let fire = if pe {
615 EFFECT::ELEMENTAL_UNITY_FIRE
616 } else {
617 EFFECT::FIRE_ELEMENTAL_UNITY
618 };
619 let storm = if pe {
620 EFFECT::ELEMENTAL_UNITY_STORM
621 } else {
622 EFFECT::STORM_ELEMENTAL_UNITY_PET
623 };
624
625 let fire_mult = mult_from(fire);
626 let storm_mult = mult_from(storm);
627
628 for spells in [
629 EU_FIRE_PLAYER_SPELLS.as_slice(),
630 EU_FIRE_PET_SPELLS.as_slice(),
631 ] {
632 built.set_aura_effect(
633 AURA_FIRE_ELEMENTAL,
634 BuffEffect::DamageMultSpells(fire_mult, spells),
635 );
636 }
637
638 for spells in [
639 FLAG_126_PLAYER_SPELLS.as_slice(),
640 EU_STORM_PET_SPELLS.as_slice(),
641 ] {
642 built.set_aura_effect(
643 AURA_STORM_ELEMENTAL,
644 BuffEffect::DamageMultSpells(storm_mult, spells),
645 );
646 }
647 }
648
649 if params.talent_picked(TALENT::LIGHTNING_CAPACITOR) {
650 built.set_aura_effect(
651 AURA_LIGHTNING_SHIELD,
652 BuffEffect::DamageMultSpells(
653 mult_from(EFFECT::LIGHTNING_CAPACITOR_DAMAGE),
654 &FLAG_126_PLAYER_SPELLS,
655 ),
656 );
657 }
658
659 if params.talent_picked(TALENT::FLAMETONGUE_WEAPON) {
660 let effect = EFFECT::IMPROVED_FLAMETONGUE_DAMAGE;
661 let pct = data.base_points(SpellIdx::from_raw(effect.0), effect.1);
662
663 built.register_passive_driver_effect(PassiveDriverEffect::new(
664 TALENT::FLAMETONGUE_WEAPON,
665 BuffEffect::DamageMultSpells(1.0 + pct / HUNDRED, &IFW_SPELLS),
666 ));
667 }
668}
669
670fn register_stormkeeper_damage(built: &mut CombatSystemDraft, params: &HandlerParams<'_>) {
671 let stormkeeper_mult = 1.0
672 + params
673 .game_data
674 .effect_base_points(EFFECT::STORMKEEPER_DAMAGE)
675 / HUNDRED;
676
677 built.patch_aura(AURA_STORMKEEPER, |aura| {
678 try_push_effect(
679 &mut aura.effects,
680 BuffEffect::DamageMultSpells(stormkeeper_mult, &STORMKEEPER_OVERLOAD_SPELLS),
681 )
682 .expect("Stormkeeper aura has room for its overload damage modifier");
683 });
684}
685
686pub(super) fn wire_elemental(
687 built: &mut CombatSystemDraft,
688 params: &HandlerParams<'_>,
689) -> Result<(), wowlab_engine_ports::EngineError> {
690 if params.talent_picked(TALENT::ELEMENTAL_BLAST) {
691 built.disable_spell(SPELL::EARTH_SHOCK);
692
693 built.patch_spell(SPELL_ELEMENTAL_BLAST, |spell| {
694 spell.damage = RuntimeDamageDef::None;
695 });
696 }
697
698 register_stormkeeper_damage(built, params);
699 register_conditional_multipliers(built, params);
700
701 if params
702 .set_bonus_auras
703 .contains(&SET_BONUS::MID2_ELEMENTAL_2PC)
704 {
705 let bonus = params
706 .game_data
707 .effect_base_points(EFFECT::MID2_SPENDER_DAMAGE);
708
709 built.register_passive_driver_effect(PassiveDriverEffect::new(
710 SET_BONUS::MID2_ELEMENTAL_2PC,
711 BuffEffect::DamageMultSpells(1.0 + bonus / HUNDRED, &MID2_SPENDER_DAMAGE_SPELLS),
712 ));
713 }
714
715 if params.talent_picked(TALENT::FLAMETONGUE_WEAPON) {
716 built.register_weapon_imbue(SPELL::FLAMETONGUE_WEAPON, "flametongue_weapon", true);
717 }
718
719 built.register_impact_proc(
720 ImpactProc::new(lava_surge_fire)
721 .with_spell_filter(flame_shock_filter)
722 .periodic_only(),
723 );
724
725 built.register_impact_proc(
726 ImpactProc::new(lightning_rod_fire)
727 .with_spell_filter(lightning_rod_filter)
728 .skip_periodic(),
729 );
730
731 register_elemental_config(built, params)?;
732
733 Ok(())
734}