1use wowlab_engine_combat::DamageOps as _;
2use wowlab_engine_combat::CooldownOps as _;
3use wowlab_engine_combat::ResourceOps as _;
4use wowlab_engine_combat::ProcOps as _;
5use wowlab_engine_combat::SchedulingOps as _;
6use wowlab_engine_combat::GuardianOps as _;
7use wowlab_engine_combat::AuraOps as _;
8use wowlab_engine_combat::{
9 BuffEffect, BuiltCombatSystem, 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, 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_LAVA_SURGE, AURA_LIGHTNING_ROD, AURA_LIGHTNING_SHIELD,
21 AURA_MASTER_OF_THE_ELEMENTS, AURA_POWER_OF_THE_MAELSTROM, AURA_STORM_ELEMENTAL,
22 AURA_STORMKEEPER, AURA_STORMS_EYE, AURA_TEMPEST, AURA_THUNDEROUS_VELOCITY,
23 AURA_UNLIMITED_POWER, AURA_WIND_GUST, EFFECT, SET_BONUS, SPELL, SPELL_ELEMENTAL_BLAST,
24 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;
39
40const LAVA_SURGE_CEILING: f64 = 0.6;
42const LAVA_SURGE_DECAY_BASE: f64 = 1.16;
43const LAVA_SURGE_MIDPOINT: f64 = 5.0;
44const LAVA_SURGE_EXPONENT_SCALE: f64 = -2.0;
45
46#[rustfmt::skip]
48static EU_FIRE_PLAYER_SPELLS: [u32; 3] = [
49 SPELL::ELEMENTAL_BLAST,
50 EFFECT::ELEMENTAL_BLAST_OVERLOAD.0,
51 SPELL::FLAME_SHOCK,
52];
53static EU_FIRE_PET_SPELLS: [u32; 3] = [SPELL::METEOR, SPELL::IMMOLATE, SPELL::FIRE_BLAST];
55#[rustfmt::skip]
57static FLAG_126_PLAYER_SPELLS: [u32; 11] = [
58 SPELL::LIGHTNING_BOLT,
59 SPELL::CHAIN_LIGHTNING,
60 SPELL::ELEMENTAL_BLAST,
61 SPELL::TEMPEST,
62 SPELL::EARTH_SHOCK,
63 SPELL::FLAME_SHOCK,
64 SPELL::STORMS_EYE,
65 EFFECT::LIGHTNING_BOLT_OVERLOAD.0,
66 EFFECT::CHAIN_LIGHTNING_OVERLOAD.0,
67 EFFECT::ELEMENTAL_BLAST_OVERLOAD.0,
68 EFFECT::TEMPEST_OVERLOAD.0,
69];
70static EU_STORM_PET_SPELLS: [u32; 3] = [SPELL::WIND_GUST, SPELL::CALL_LIGHTNING, SPELL::STORMFURY];
72static EU_FIRE_MASKED_EFFECTS: [(u32, u8); 2] = [
74 EFFECT::FIRE_ELEMENTAL_UNITY,
75 EFFECT::FIRE_ELEMENTAL_UNITY_PERIODIC,
76];
77static EU_STORM_MASKED_EFFECTS: [(u32, u8); 2] = [
78 EFFECT::STORM_ELEMENTAL_UNITY,
79 EFFECT::STORM_ELEMENTAL_UNITY_PET,
80];
81#[rustfmt::skip]
83static IFW_SPELLS: [u32; 5] = [
84 SPELL::FLAME_SHOCK,
85 SPELL::LAVA_BURST,
86 SPELL::ELEMENTAL_BLAST,
87 EFFECT::LAVA_BURST_OVERLOAD.0,
88 EFFECT::ELEMENTAL_BLAST_OVERLOAD.0,
89];
90
91pub(crate) fn earthquake_hook(ctx: &mut HookCtx<'_>) {
92 ctx.schedule_ground_effect_from_data(
93 SPELL::EARTHQUAKE,
94 EFFECT::EARTHQUAKE_TIMING.1,
95 EFFECT::EARTHQUAKE_DAMAGE,
96 EFFECT::EARTHQUAKE_DAMAGE,
97 DamageFlags::PERIODIC,
98 );
99}
100
101const STORMS_EYE_CHARGES: i32 = 2;
102
103const RANDOM_STAT_ROLL_THIRD: f64 = 1.0 / 3.0;
104const RANDOM_STAT_ROLL_TWO_THIRDS: f64 = 2.0 / 3.0;
105
106use super::{
107 bugs::BUG_STORMWELL_TEMPEST_MAELSTROM,
108 config::{ELEMENTAL_BLAST_COST, elemental_config, register_elemental_config},
109 elementals::{STORM_ELEMENTAL_GUARDIAN_TAG, summon_fire_elemental, summon_storm_elemental},
110};
111
112fn trigger_elemental_blast_stat(ctx: &mut HookCtx<'_>) {
113 let aura = match ctx.rng()() {
114 roll if roll < RANDOM_STAT_ROLL_THIRD => AURA_ELEMENTAL_BLAST_CRIT,
115 roll if roll < RANDOM_STAT_ROLL_TWO_THIRDS => AURA_ELEMENTAL_BLAST_HASTE,
116 _ => AURA_ELEMENTAL_BLAST_MASTERY,
117 };
118
119 ctx.apply_aura(aura.raw());
120}
121
122fn overload_chance(ctx: &HookCtx<'_>) -> f64 {
124 ctx.effect_points(EFFECT::OVERLOAD_CHANCE) / HUNDRED
125}
126
127fn overload_penalty(ctx: &HookCtx<'_>) -> f64 {
129 ctx.game_data().effect_base_points(EFFECT::OVERLOAD_PENALTY) / HUNDRED
130}
131
132fn fire_overload(ctx: &mut HookCtx<'_>, effect: (u32, u8), maelstrom: f64) {
133 fire_overload_scaled(ctx, effect, maelstrom, 1.0);
134 let feedback_chance = elemental_config(ctx).feedback_loop_chance;
135
136 if feedback_chance > 0.0 && proc_chance(ctx.rng(), feedback_chance) {
137 fire_overload_scaled(ctx, effect, maelstrom, 1.0);
138 }
139}
140
141fn fire_overload_scaled(
142 ctx: &mut HookCtx<'_>,
143 effect: (u32, u8),
144 maelstrom: f64,
145 damage_mult: f64,
146) {
147 let coef = ctx
148 .game_data()
149 .sp_coef(SpellIdx::from_raw(effect.0), effect.1)
150 * overload_penalty(ctx)
151 * damage_mult;
152
153 ctx.deal_damage_sp(effect.0, coef, DamageFlags::empty());
154
155 if maelstrom > 0.0 {
156 ctx.gain_resource(maelstrom);
157 }
158}
159
160fn fire_elemental_blast_overloads(ctx: &mut HookCtx<'_>, damage_mult: f64) {
162 let cfg = elemental_config(ctx);
163 let fire = |ctx: &mut HookCtx<'_>| {
164 trigger_elemental_blast_stat(ctx);
165 fire_overload_scaled(ctx, EFFECT::ELEMENTAL_BLAST_OVERLOAD, 0.0, damage_mult);
166
167 if cfg.feedback_loop_chance > 0.0 && proc_chance(ctx.rng(), cfg.feedback_loop_chance) {
168 trigger_elemental_blast_stat(ctx);
169 fire_overload_scaled(ctx, EFFECT::ELEMENTAL_BLAST_OVERLOAD, 0.0, damage_mult);
170 }
171 };
172
173 let chance = overload_chance(ctx);
174
175 if proc_chance(ctx.rng(), chance) {
176 fire(ctx);
177 }
178
179 if ctx.is_aura_active(AURA_ASCENDANCE.raw()) {
180 fire(ctx);
181 }
182}
183
184fn fire_elemental_blast(ctx: &mut HookCtx<'_>, damage_mult: f64) {
185 trigger_elemental_blast_stat(ctx);
186 let coefficient = ctx
187 .game_data()
188 .effect_sp_coefficient(EFFECT::ELEMENTAL_BLAST_DAMAGE)
189 * damage_mult;
190
191 ctx.deal_damage_sp(SPELL::ELEMENTAL_BLAST, coefficient, DamageFlags::empty());
192 let cfg = elemental_config(ctx);
193
194 if cfg.eb_overload {
195 fire_elemental_blast_overloads(ctx, cfg.eb_overload_mult * damage_mult);
196 }
197}
198
199fn spend_stormkeeper_charge(ctx: &mut HookCtx<'_>) {
200 if ctx.consume_proc_counter(SPELL::STORMKEEPER, 1) == 0 {
201 ctx.expire_aura(AURA_STORMKEEPER.raw());
202 }
203}
204
205#[derive(Clone, Copy)]
206struct EmpoweredLightningOptions {
207 chance_divisor: f64,
208 stormkeeper_overload: bool,
209 consume_stormkeeper: bool,
210}
211
212const LIGHTNING_BOLT_OPTIONS: EmpoweredLightningOptions = EmpoweredLightningOptions {
213 chance_divisor: 1.0,
214 stormkeeper_overload: true,
215 consume_stormkeeper: true,
216};
217const TEMPEST_OPTIONS: EmpoweredLightningOptions = EmpoweredLightningOptions {
218 chance_divisor: 1.0,
219 stormkeeper_overload: false,
220 consume_stormkeeper: false,
221};
222const CHAIN_LIGHTNING_OPTIONS: EmpoweredLightningOptions = EmpoweredLightningOptions {
223 chance_divisor: CHAIN_OVERLOAD_DIVISOR,
224 stormkeeper_overload: true,
225 consume_stormkeeper: true,
226};
227
228fn empowered_lightning_cast(
229 ctx: &mut HookCtx<'_>,
230 overload: (u32, u8),
231 overload_maelstrom: f64,
232 options: EmpoweredLightningOptions,
233) {
234 let stormkeeper = ctx.is_aura_active(AURA_STORMKEEPER.raw());
235 let cfg = elemental_config(ctx);
236
237 let chance = overload_chance(ctx) / options.chance_divisor;
238
239 if (stormkeeper && options.stormkeeper_overload) || proc_chance(ctx.rng(), chance) {
240 fire_overload(ctx, overload, overload_maelstrom);
241 }
242
243 if stormkeeper && options.consume_stormkeeper {
244 spend_stormkeeper_charge(ctx);
245 }
246
247 if ctx.consume_aura(AURA_POWER_OF_THE_MAELSTROM.raw()) {
248 fire_overload(ctx, overload, overload_maelstrom);
249
250 if cfg.fusion_of_elements_mult > 0.0 {
251 fire_elemental_blast(ctx, cfg.fusion_of_elements_mult);
252 }
253 }
254
255 if cfg.supercharge_chance > 0.0 && proc_chance(ctx.rng(), cfg.supercharge_chance) {
256 fire_overload(ctx, overload, overload_maelstrom);
257 }
258 if ctx.is_aura_active(AURA_ASCENDANCE.raw()) {
261 fire_overload(ctx, overload, overload_maelstrom);
262 }
263
264 if ctx.active_guardian_count(STORM_ELEMENTAL_GUARDIAN_TAG) > 0 {
265 ctx.apply_aura(AURA_WIND_GUST.raw());
266 }
267
268 ctx.consume_aura(AURA_MASTER_OF_THE_ELEMENTS.raw());
269}
270
271pub(crate) fn lightning_bolt_hook(ctx: &mut HookCtx<'_>) {
272 empowered_lightning_cast(
273 ctx,
274 EFFECT::LIGHTNING_BOLT_OVERLOAD,
275 LIGHTNING_BOLT_OVERLOAD_MAELSTROM,
276 LIGHTNING_BOLT_OPTIONS,
277 );
278 ctx.consume_aura(AURA_ANCESTRAL_SWIFTNESS.raw());
279}
280
281pub(crate) fn tempest_hook(ctx: &mut HookCtx<'_>) {
282 let stormwell_maelstrom = elemental_config(ctx).stormwell_tempest_maelstrom;
283
284 if stormwell_maelstrom > 0.0
285 && ctx.state().bug_enabled(&BUG_STORMWELL_TEMPEST_MAELSTROM)
286 && !ctx.is_aura_active(AURA_STORMKEEPER.raw())
287 {
288 ctx.gain_resource(stormwell_maelstrom);
289 }
290
291 ctx.consume_aura(AURA_TEMPEST.raw());
294
295 if elemental_config(ctx).tww3_stormbringer_4pc && ctx.consume_aura_stack(AURA_STORMS_EYE.raw())
296 {
297 let storms_eye = ctx
298 .game_data()
299 .effect_sp_coefficient(EFFECT::STORMS_EYE_DAMAGE);
300
301 ctx.deal_damage_sp(SPELL::STORMS_EYE, storms_eye, DamageFlags::empty());
302 }
303
304 empowered_lightning_cast(ctx, EFFECT::TEMPEST_OVERLOAD, 0.0, TEMPEST_OPTIONS);
305
306 if elemental_config(ctx).arc_discharge {
307 ctx.apply_aura(AURA_STORMKEEPER.raw());
308 ctx.add_proc_counter(SPELL::STORMKEEPER, ARC_DISCHARGE_CHARGES);
309 }
310}
311
312pub(crate) fn chain_lightning_hook(ctx: &mut HookCtx<'_>) {
313 empowered_lightning_cast(
314 ctx,
315 EFFECT::CHAIN_LIGHTNING_OVERLOAD,
316 CHAIN_LIGHTNING_OVERLOAD_MAELSTROM,
317 CHAIN_LIGHTNING_OPTIONS,
318 );
319 ctx.consume_aura(AURA_ANCESTRAL_SWIFTNESS.raw());
320}
321
322pub(crate) fn lava_burst_hook(ctx: &mut HookCtx<'_>) {
323 let cfg = elemental_config(ctx);
324
325 if cfg.master_of_the_elements {
326 ctx.apply_aura(AURA_MASTER_OF_THE_ELEMENTS.raw());
327 }
328
329 if cfg.potm_chance > 0.0 && proc_chance(ctx.rng(), cfg.potm_chance) {
330 ctx.apply_aura(AURA_POWER_OF_THE_MAELSTROM.raw());
331 }
332
333 ctx.gain_resource(LAVA_BURST_MAELSTROM);
334 ctx.consume_aura(AURA_LAVA_SURGE.raw());
335
336 let chance = overload_chance(ctx);
337
338 if proc_chance(ctx.rng(), chance) {
339 fire_overload(
340 ctx,
341 EFFECT::LAVA_BURST_OVERLOAD,
342 LAVA_BURST_OVERLOAD_MAELSTROM,
343 );
344 }
345
346 if ctx.is_aura_active(AURA_ASCENDANCE.raw()) {
347 fire_overload(
348 ctx,
349 EFFECT::LAVA_BURST_OVERLOAD,
350 LAVA_BURST_OVERLOAD_MAELSTROM,
351 );
352 }
353
354 ctx.consume_aura(AURA_ANCESTRAL_SWIFTNESS.raw());
355}
356
357pub(crate) fn elemental_blast_hook(ctx: &mut HookCtx<'_>) {
358 fire_elemental_blast(ctx, 1.0);
359
360 if elemental_config(ctx).unlimited_power {
361 ctx.apply_aura(AURA_UNLIMITED_POWER.raw());
362 }
363
364 ctx.apply_aura(AURA_LIGHTNING_ROD.raw());
365 ctx.consume_aura(AURA_MASTER_OF_THE_ELEMENTS.raw());
366 let cfg = elemental_config(ctx);
367
368 if cfg.aftershock_chance > 0.0 && proc_chance(ctx.rng(), cfg.aftershock_chance) {
369 ctx.gain_resource(ELEMENTAL_BLAST_COST);
370 }
371
372 if cfg.tempest_chance_on_blast > 0.0 && proc_chance(ctx.rng(), cfg.tempest_chance_on_blast) {
373 ctx.apply_aura(AURA_TEMPEST.raw());
374 }
375
376 ctx.consume_aura(AURA_ANCESTRAL_SWIFTNESS.raw());
377}
378
379pub(crate) fn stormkeeper_hook(ctx: &mut HookCtx<'_>) {
380 let cfg = elemental_config(ctx);
381
382 ctx.reset_proc_counter(SPELL::STORMKEEPER);
383 let charges = STORMKEEPER_CAST_CHARGES + u32::from(cfg.mid1_4pc);
384
385 ctx.add_proc_counter(SPELL::STORMKEEPER, charges);
386
387 if cfg.mid1_2pc {
388 ctx.apply_aura(AURA_THUNDEROUS_VELOCITY.raw());
389 }
390
391 if cfg.summon_storm_elemental {
392 summon_storm_elemental(ctx);
393 }
394}
395
396pub(crate) fn ascendance_hook(ctx: &mut HookCtx<'_>) {
397 let cfg = elemental_config(ctx);
398
399 if cfg.tww3_stormbringer_2pc {
400 ctx.apply_aura(AURA_TEMPEST.raw());
401 }
402
403 if cfg.tww3_stormbringer_4pc {
404 ctx.apply_aura_n(AURA_STORMS_EYE.raw(), STORMS_EYE_CHARGES);
405 }
406
407 ctx.reset_cooldown(SPELL_LAVA_BURST.raw());
408 ctx.apply_aura(AURA_FLAME_SHOCK.raw());
409 let lava_burst = ctx
410 .game_data()
411 .effect_sp_coefficient(EFFECT::LAVA_BURST_DAMAGE);
412
413 ctx.deal_damage_sp(SPELL::LAVA_BURST, lava_burst, DamageFlags::empty());
414 fire_overload(
415 ctx,
416 EFFECT::LAVA_BURST_OVERLOAD,
417 LAVA_BURST_OVERLOAD_MAELSTROM,
418 );
419
420 let chance = overload_chance(ctx);
421
422 if proc_chance(ctx.rng(), chance) {
423 fire_overload(
424 ctx,
425 EFFECT::LAVA_BURST_OVERLOAD,
426 LAVA_BURST_OVERLOAD_MAELSTROM,
427 );
428 }
429
430 if cfg.summon_fire_elemental {
431 summon_fire_elemental(ctx);
432 }
433}
434
435#[must_use]
437pub(crate) fn mastery(ctx: &MasteryCtx<'_>) -> f64 {
438 if ctx.is_pet {
439 return ctx.bonus(EFFECT::MASTERY_PET_DAMAGE.1);
440 }
441
442 let effect = if ctx.is_periodic {
443 EFFECT::MASTERY_PERIODIC_DAMAGE.1
444 } else {
445 EFFECT::MASTERY_DIRECT_DAMAGE.1
446 };
447 let mut mult = if ctx.affects_spell(effect) {
448 ctx.bonus(effect)
449 } else {
450 1.0
451 };
452
453 if matches!(ctx.spell_id, SPELL::LAVA_BURST | SPELL::LAVA_BURST_OVERLOAD)
454 && ctx.is_aura_active_id(AURA::FLAME_SHOCK)
455 {
456 mult *= 1.0 + ctx.buf.player().crit / HUNDRED;
457 }
458
459 mult
460}
461
462const fn flame_shock_filter(spell_id: u32) -> bool {
463 spell_id == SPELL::FLAME_SHOCK
464}
465
466const fn lightning_rod_filter(spell_id: u32) -> bool {
467 matches!(
468 spell_id,
469 SPELL::LIGHTNING_BOLT
470 | SPELL::CHAIN_LIGHTNING
471 | SPELL::TEMPEST
472 | SPELL::LIGHTNING_BOLT_OVERLOAD
473 | SPELL::CHAIN_LIGHTNING_OVERLOAD
474 | SPELL::TEMPEST_OVERLOAD
475 )
476}
477
478fn lightning_rod_fire(ctx: &mut HookCtx<'_>, impact: ImpactEvent) {
479 let cfg = elemental_config(ctx);
480
481 if cfg.lightning_rod_mult > 0.0 && ctx.is_aura_active(AURA_LIGHTNING_ROD.raw()) {
482 ctx.deal_resolved_damage(
483 SPELL::LIGHTNING_ROD_DAMAGE,
484 impact.amount * cfg.lightning_rod_mult,
485 );
486 }
487}
488
489fn lava_surge_fire(ctx: &mut HookCtx<'_>, impact: ImpactEvent) {
490 if ctx.roll_attempt_proc(impact.spell_id, |attempt| {
491 let exponent = LAVA_SURGE_EXPONENT_SCALE * (f64::from(attempt) - LAVA_SURGE_MIDPOINT);
492
493 (LAVA_SURGE_CEILING - LAVA_SURGE_DECAY_BASE.powf(exponent)).max(0.0)
494 }) {
495 ctx.reset_cooldown(SPELL_LAVA_BURST.raw());
496 ctx.apply_aura(AURA_LAVA_SURGE.raw());
497 }
498}
499
500static STORMKEEPER_OVERLOAD_SPELLS: [u32; 1] = [EFFECT::LIGHTNING_BOLT_OVERLOAD.0];
502
503static MID1_STORMKEEPER_DAMAGE_SPELLS: [u32; 2] = [SPELL::LIGHTNING_BOLT, SPELL::CHAIN_LIGHTNING];
505
506fn register_conditional_multipliers(built: &mut BuiltCombatSystem, params: &HandlerParams<'_>) {
507 let data = ¶ms.game_data;
508 let mult_from = |effect: (u32, u8)| {
509 1.0 + data.base_points(SpellIdx::from_raw(effect.0), effect.1) / HUNDRED
510 };
511
512 for effect in EU_FIRE_MASKED_EFFECTS {
518 built.mask_aura_dbc_effect(AURA_FIRE_ELEMENTAL, effect.0, effect.1);
519 }
520
521 for effect in EU_STORM_MASKED_EFFECTS {
522 built.mask_aura_dbc_effect(AURA_STORM_ELEMENTAL, effect.0, effect.1);
523 }
524
525 if params.talent_picked(TALENT::ELEMENTAL_UNITY) {
526 let pe = params.talent_picked(TALENT::PRIMAL_ELEMENTALIST);
527 let fire = if pe {
528 EFFECT::ELEMENTAL_UNITY_FIRE
529 } else {
530 EFFECT::FIRE_ELEMENTAL_UNITY
531 };
532 let storm = if pe {
533 EFFECT::ELEMENTAL_UNITY_STORM
534 } else {
535 EFFECT::STORM_ELEMENTAL_UNITY_PET
536 };
537
538 let fire_mult = mult_from(fire);
539 let storm_mult = mult_from(storm);
540
541 for spells in [
542 EU_FIRE_PLAYER_SPELLS.as_slice(),
543 EU_FIRE_PET_SPELLS.as_slice(),
544 ] {
545 built.set_aura_effect(
546 AURA_FIRE_ELEMENTAL,
547 BuffEffect::DamageMultSpells(fire_mult, spells),
548 );
549 }
550
551 for spells in [
552 FLAG_126_PLAYER_SPELLS.as_slice(),
553 EU_STORM_PET_SPELLS.as_slice(),
554 ] {
555 built.set_aura_effect(
556 AURA_STORM_ELEMENTAL,
557 BuffEffect::DamageMultSpells(storm_mult, spells),
558 );
559 }
560 }
561
562 if params.talent_picked(TALENT::LIGHTNING_CAPACITOR) {
563 built.set_aura_effect(
564 AURA_LIGHTNING_SHIELD,
565 BuffEffect::DamageMultSpells(
566 mult_from(EFFECT::LIGHTNING_CAPACITOR_DAMAGE),
567 &FLAG_126_PLAYER_SPELLS,
568 ),
569 );
570 }
571
572 if params.talent_picked(TALENT::FLAMETONGUE_WEAPON) {
573 let effect = EFFECT::IMPROVED_FLAMETONGUE_DAMAGE;
574 let pct = data.base_points(SpellIdx::from_raw(effect.0), effect.1);
575
576 built.register_passive_driver_effect(PassiveDriverEffect::new(
577 TALENT::FLAMETONGUE_WEAPON,
578 BuffEffect::DamageMultSpells(1.0 + pct / HUNDRED, &IFW_SPELLS),
579 ));
580 }
581}
582
583fn register_stormkeeper_damage(built: &mut BuiltCombatSystem, params: &HandlerParams<'_>) {
585 let data = ¶ms.game_data;
586 let stormkeeper_mult = 1.0 + data.effect_base_points(EFFECT::STORMKEEPER_DAMAGE) / HUNDRED;
587
588 built.patch_aura(AURA_STORMKEEPER, |aura| {
589 for slot in &mut aura.effects {
590 let is_tier_row = matches!(
591 slot,
592 Some(BuffEffect::SpellDamageFromEffect {
593 source_spell_id,
594 effect_index,
595 ..
596 }) if *source_spell_id == EFFECT::STORMKEEPER_TIER_DAMAGE.0
597 && *effect_index == EFFECT::STORMKEEPER_TIER_DAMAGE.1
598 );
599
600 if is_tier_row {
601 *slot = None;
602 }
603 }
604
605 try_push_effect(
606 &mut aura.effects,
607 BuffEffect::DamageMultSpells(stormkeeper_mult, &STORMKEEPER_OVERLOAD_SPELLS),
608 )
609 .expect("Stormkeeper aura has room for its overload damage modifier");
610
611 if params
612 .set_bonus_auras
613 .contains(&SET_BONUS::MID1_ELEMENTAL_4PC)
614 {
615 let tier_bonus = data.effect_base_points(EFFECT::MID1_STORMKEEPER_BONUS) / HUNDRED;
616 let tier_mult = 1.0 + tier_bonus;
618
619 try_push_effect(
620 &mut aura.effects,
621 BuffEffect::DamageMultSpells(tier_mult, &MID1_STORMKEEPER_DAMAGE_SPELLS),
622 )
623 .expect("Stormkeeper aura has room for the MID1 4pc damage modifier");
624 }
625 });
626}
627
628pub(super) fn wire_elemental(built: &mut BuiltCombatSystem, params: &HandlerParams<'_>) {
629 if params.talent_picked(TALENT::ELEMENTAL_BLAST) {
630 built.disable_spell(SPELL::EARTH_SHOCK);
631
632 built.patch_spell(SPELL_ELEMENTAL_BLAST, |spell| {
633 spell.damage = RuntimeDamageDef::None;
634 });
635 }
636
637 register_stormkeeper_damage(built, params);
638 register_conditional_multipliers(built, params);
639
640 if params.talent_picked(TALENT::FLAMETONGUE_WEAPON) {
641 built.register_weapon_imbue(SPELL::FLAMETONGUE_WEAPON, "flametongue_weapon", true);
642 }
643
644 built.register_impact_proc(
645 ImpactProc::new(lava_surge_fire)
646 .with_spell_filter(flame_shock_filter)
647 .periodic_only(),
648 );
649
650 built.register_impact_proc(
651 ImpactProc::new(lightning_rod_fire)
652 .with_spell_filter(lightning_rod_filter)
653 .skip_periodic(),
654 );
655
656 register_elemental_config(built, params);
657}