wowlab_engine_content/hooks/devastation_evoker/
hooks.rs1use wowlab_engine_combat::DamageOps as _;
2use wowlab_engine_combat::CooldownOps as _;
3use wowlab_engine_combat::AuraOps as _;
4use wowlab_engine_combat::{DamageFlags, EmpowerReleaseAdjustment, HookCtx, MasteryCtx};
5use wowlab_engine_domain::dbc::ResolvedGameDataEffectExt as _;
6use wowlab_engine_rng::proc_chance;
7use wowlab_types::{constants::HUNDRED};
8
9use super::config::{DevastationConfig, config};
10use crate::generated::specs::devastation_evoker::{
11 AURA_BOMBARDMENTS, AURA_BURNOUT, AURA_CHARGED_BLAST, AURA_DEEP_BREATH_TRIGGER, AURA_DRAGONRAGE,
12 AURA_ESSENCE_BURST, AURA_IMMINENT_DESTRUCTION, AURA_IRIDESCENCE_BLUE, AURA_IRIDESCENCE_RED,
13 AURA_MASS_DISINTEGRATE, AURA_MELT_ARMOR_DOT, AURA_POWER_SWELL, AURA_RISING_FURY,
14 AURA_STRAFING_RUN, EFFECT, REPORTED_SPELL, SPELL, SPELL_ETERNITY_SURGE, SPELL_FIRE_BREATH,
15 TALENT,
16};
17
18pub(crate) fn living_flame_hook(ctx: &mut HookCtx<'_>) {
19 let cfg = config(ctx);
20
21 if cfg.ruby_eb_chance > 0.0
22 && (ctx.is_aura_active(AURA_DRAGONRAGE.raw()) || proc_chance(ctx.rng(), cfg.ruby_eb_chance))
23 {
24 ctx.apply_aura(AURA_ESSENCE_BURST.raw());
25 }
26}
27
28pub(crate) fn azure_strike_hook(ctx: &mut HookCtx<'_>) {
29 let cfg = config(ctx);
30
31 if cfg.azure_eb_chance > 0.0
32 && (ctx.is_aura_active(AURA_DRAGONRAGE.raw())
33 || proc_chance(ctx.rng(), cfg.azure_eb_chance))
34 {
35 ctx.apply_aura(AURA_ESSENCE_BURST.raw());
36 }
37}
38
39pub(crate) fn disintegrate_hook(ctx: &mut HookCtx<'_>) {
40 let cfg = config(ctx);
41
42 if ctx.is_aura_active(AURA_ESSENCE_BURST.raw()) {
43 ctx.consume_aura_stack(AURA_ESSENCE_BURST.raw());
44
45 if cfg.titanic_wrath_mult > 1.0 {
46 ctx.multiply_active_channel_damage(cfg.titanic_wrath_mult);
47 }
48 }
49
50 if cfg.mass_disintegrate && ctx.is_aura_active(AURA_MASS_DISINTEGRATE.raw()) {
51 if cfg.bombardments {
52 ctx.apply_aura(AURA_BOMBARDMENTS.raw());
53 }
54
55 ctx.consume_aura_stack(AURA_MASS_DISINTEGRATE.raw());
56 }
57}
58
59pub(crate) fn disintegrate_tick_hook(ctx: &mut HookCtx<'_>) {
60 let cfg = config(ctx);
61
62 ctx.apply_aura(AURA_CHARGED_BLAST.raw());
63
64 if cfg.causality_tick_cdr_ms > 0 {
65 ctx.reduce_cooldown(SPELL_FIRE_BREATH.raw(), cfg.causality_tick_cdr_ms);
66 ctx.reduce_cooldown(SPELL_ETERNITY_SURGE.raw(), cfg.causality_tick_cdr_ms);
67 }
68
69 if cfg.scintillation_chance > 0.0 && proc_chance(ctx.rng(), cfg.scintillation_chance) {
70 ctx.deal_damage_sp(
71 TALENT::SCINTILLATION,
72 cfg.scintillation_coef,
73 DamageFlags::empty(),
74 );
75
76 if cfg.shattering_star_coef > 0.0 {
77 ctx.deal_damage_sp(
78 SPELL::SHATTERING_STAR,
79 cfg.shattering_star_coef,
80 DamageFlags::empty(),
81 );
82 }
83 }
84}
85
86fn empower_release(ctx: &mut HookCtx<'_>, cfg: &DevastationConfig, iridescence_aura: u8) {
87 ctx.apply_aura(AURA_POWER_SWELL.raw());
88
89 if cfg.iridescence {
90 ctx.apply_aura(iridescence_aura);
91 }
92
93 if cfg.mass_disintegrate {
94 ctx.apply_aura(AURA_MASS_DISINTEGRATE.raw());
95 }
96
97 if cfg.animosity_extend_ms > 0 && ctx.is_aura_active(AURA_DRAGONRAGE.raw()) {
98 ctx.extend_aura(AURA_DRAGONRAGE.raw(), cfg.animosity_extend_ms);
99 }
100}
101
102pub(crate) fn eternity_surge_hook(ctx: &mut HookCtx<'_>) {
103 let cfg = config(ctx);
104
105 empower_release(ctx, &cfg, AURA_IRIDESCENCE_BLUE.raw());
106
107 if cfg.shattering_star_coef > 0.0 {
108 ctx.deal_damage_sp(
109 SPELL::SHATTERING_STAR,
110 cfg.shattering_star_coef,
111 DamageFlags::empty(),
112 );
113 }
114 if cfg.eye_of_infinity {
118 let crit = ctx.buf().player().crit / HUNDRED;
119
120 if proc_chance(ctx.rng(), crit) {
121 ctx.apply_aura(AURA_ESSENCE_BURST.raw());
122 }
123 }
124}
125
126pub(crate) fn fire_breath_hook(ctx: &mut HookCtx<'_>) {
127 let cfg = config(ctx);
128
129 empower_release(ctx, &cfg, AURA_IRIDESCENCE_RED.raw());
130
131 if cfg.burnout_chance > 0.0 && proc_chance(ctx.rng(), cfg.burnout_chance) {
132 ctx.apply_aura(AURA_BURNOUT.raw());
133 }
134}
135
136pub(crate) fn fire_breath_empower_release(ctx: &HookCtx<'_>, rank: u8) -> EmpowerReleaseAdjustment {
138 const BASE_TICKS: u16 = 10;
139 const TRANSFERRED_TICKS_PER_RANK: u16 = 3;
140
141 let cfg = config(ctx);
142 let total_ticks = BASE_TICKS + u16::from(cfg.blast_furnace_bonus_ticks);
143 let transferred_ticks = u16::from(rank.saturating_sub(1)) * TRANSFERRED_TICKS_PER_RANK;
144
145 EmpowerReleaseAdjustment::transfer_periodic_ticks(total_ticks, transferred_ticks)
146}
147
148pub(crate) fn pyre_hook(ctx: &mut HookCtx<'_>) {
149 if ctx.is_aura_active(AURA_ESSENCE_BURST.raw()) {
150 ctx.consume_aura_stack(AURA_ESSENCE_BURST.raw());
151 }
152
153 ctx.expire_aura(AURA_CHARGED_BLAST.raw());
154}
155
156pub(crate) fn dragonrage_hook(ctx: &mut HookCtx<'_>) {
157 let cfg = config(ctx);
158
159 if cfg.rising_fury {
160 ctx.apply_aura(AURA_RISING_FURY.raw());
161 }
162
163 let coef = ctx.game_data().effect_sp_coefficient(EFFECT::PYRE_DAMAGE);
164
165 ctx.deal_damage_sp(REPORTED_SPELL::PYRE_DAMAGE, coef, DamageFlags::empty());
166}
167
168pub(crate) fn dragonrage_expire_hook(ctx: &mut HookCtx<'_>) {
169 ctx.expire_aura(AURA_RISING_FURY.raw());
170}
171
172pub(crate) fn deep_breath_hook(ctx: &mut HookCtx<'_>) {
173 let cfg = config(ctx);
174 let targets = ctx.resolved_spell_effect_targets(
175 EFFECT::DEEP_BREATH_PATH,
176 EFFECT::DEEP_BREATH_PATH.0,
177 None,
178 );
179
180 for target in targets {
181 ctx.with_resolved_target(target, |ctx| {
182 ctx.apply_aura(AURA_DEEP_BREATH_TRIGGER.raw());
183 });
184 }
185
186 if cfg.melt_armor {
187 ctx.apply_aura(AURA_MELT_ARMOR_DOT.raw());
188 }
189
190 if cfg.imminent_destruction {
191 ctx.apply_aura(AURA_IMMINENT_DESTRUCTION.raw());
192 }
193
194 if cfg.strafing_run {
195 if ctx.is_aura_active(AURA_STRAFING_RUN.raw()) {
196 ctx.expire_aura(AURA_STRAFING_RUN.raw());
197 } else {
198 ctx.apply_aura(AURA_STRAFING_RUN.raw());
199 }
200 }
201}
202
203#[must_use]
205pub(crate) fn mastery(ctx: &MasteryCtx<'_>) -> f64 {
206 const GIANTKILLER_FLOOR: f64 = 0.3;
207 let health = ctx.target_health_pct().max(GIANTKILLER_FLOOR);
208
209 1.0 + ctx.points(1) / HUNDRED * health
210}