wowlab_engine_content/hooks/affliction_warlock/
hooks.rs1use wowlab_engine_combat::AuraOps as _;
2use wowlab_engine_combat::DamageOps as _;
3use wowlab_engine_combat::GuardianOps as _;
4use wowlab_engine_combat::ProcOps as _;
5use wowlab_engine_combat::ResourceOps as _;
6use wowlab_engine_combat::{
7 DamageFlags, GuardianAbility, GuardianEvent, GuardianSpec, HookCtx, MasteryCtx,
8 ResourceGainEvent,
9};
10use wowlab_engine_rng::{prd_constant, proc_chance};
11use wowlab_types::constants::DEFAULT_GCD_MS;
12
13use super::config::{AfflictionConfig, AfflictionRuntime};
14use crate::generated::specs::affliction_warlock::{
15 AURA_AGONY, AURA_CORRUPTION, AURA_SOUL_ANATHEMA, AURA_SUCCULENT_SOUL, AURA_UNSTABLE_AFFLICTION,
16 AURA_UNSTABLE_EMPOWERMENT, AURA_WITHER, EFFECT, TALENT,
17};
18
19const DARKGLARE_DURATION_MS: u32 = 20_000;
20const MANIFESTED_AVARICE_RATE: f64 = 0.10;
21const SOUL_SWIPE_INTERVAL_MS: u32 = 1_000;
22const DESPERATE_SOUL_DURATION_MS: u32 = 1_000;
23const DESPERATE_SOUL_ACTION_DELAY_MS: u32 = 1;
24const MANIFESTED_SOUL_DURATION_MS: u32 = 14_000;
25
26fn darkglare_action(ctx: &mut HookCtx<'_>, _event: GuardianEvent) {
27 let dots = [
28 AURA_AGONY,
29 AURA_CORRUPTION,
30 AURA_UNSTABLE_AFFLICTION,
31 AURA_WITHER,
32 ]
33 .iter()
34 .filter(|a| ctx.is_aura_active(**a))
35 .count();
36 let coefficient = ctx.effect_sp_coefficient(
37 crate::generated::specs::affliction_warlock::DAMAGE_EFFECT::EYE_BEAM_DAMAGE,
38 );
39 let per_dot = ctx.effect_percent(
40 crate::generated::specs::affliction_warlock::DAMAGE_EFFECT::EYE_BEAM_PER_DOT,
41 );
42 let mult = 1.0 + per_dot * wowlab_types::numeric::usize_to_f64(dots);
43
44 ctx.deal_damage_sp(
45 crate::generated::specs::affliction_warlock::DAMAGE_REPORTED::EYE_BEAM,
46 coefficient * mult,
47 DamageFlags::PET,
48 );
49}
50
51pub(crate) fn summon_darkglare_hook(ctx: &mut HookCtx<'_>) {
53 let resolved_action_ms =
54 ctx.action_gcd_ms(crate::generated::specs::affliction_warlock::DAMAGE_REPORTED::EYE_BEAM);
55 let action_ms = if resolved_action_ms == 0 {
56 DEFAULT_GCD_MS
57 } else {
58 resolved_action_ms
59 };
60 let _ = ctx.summon_guardian(
61 GuardianSpec::new_inheriting_owner(TALENT::SUMMON_DARKGLARE, DARKGLARE_DURATION_MS)
62 .ability(
63 GuardianAbility::new(action_ms, darkglare_action)
64 .first_action_delay(action_ms)
65 .hasted_actions(),
66 ),
67 );
68}
69
70pub(super) fn succulent_soul_proc(ctx: &mut HookCtx<'_>, _event: ResourceGainEvent) {
71 ctx.apply_aura(AURA_SUCCULENT_SOUL);
72}
73
74fn manifested_soul_action(ctx: &mut HookCtx<'_>, _event: GuardianEvent) {
75 ctx.deal_damage_sp(
76 crate::generated::specs::affliction_warlock::DAMAGE_REPORTED::SOUL_SWIPE,
77 ctx.effect_sp_coefficient(
78 crate::generated::specs::affliction_warlock::DAMAGE_EFFECT::SOUL_SWIPE_DAMAGE,
79 ),
80 DamageFlags::PET,
81 );
82 ctx.deal_effect_damage_sp_with_geometry(
83 crate::generated::specs::affliction_warlock::DAMAGE_EFFECT::SOUL_SWIPE_AOE_DAMAGE,
84 crate::generated::specs::affliction_warlock::DAMAGE_EFFECT::SOUL_SWIPE_AOE_DAMAGE,
85 1.0,
86 DamageFlags::PET,
87 );
88}
89
90fn wrath_of_nathreza_action(ctx: &mut HookCtx<'_>, _event: GuardianEvent) {
91 ctx.deal_effect_damage_sp_with_geometry(
92 crate::generated::specs::affliction_warlock::DAMAGE_EFFECT::WRATH_OF_NATHREZA_DAMAGE,
93 crate::generated::specs::affliction_warlock::DAMAGE_EFFECT::WRATH_OF_NATHREZA_DAMAGE,
94 1.0,
95 DamageFlags::PET,
96 );
97}
98
99pub(crate) fn haunt_tick_hook(ctx: &mut HookCtx<'_>) {
101 const SHADOW_OF_NATHREZA_MAX_RANK: u8 = 3;
102
103 let config = ctx
104 .spec_config::<AfflictionConfig>()
105 .copied()
106 .unwrap_or_default();
107
108 if config.shadow_of_nathreza_rank == 0 {
109 return;
110 }
111
112 ctx.deal_damage_sp(
113 crate::generated::specs::affliction_warlock::DAMAGE_REPORTED::SHADOW_OF_NATHREZA,
114 ctx.effect_sp_coefficient(
115 crate::generated::specs::affliction_warlock::DAMAGE_EFFECT::SHADOW_OF_NATHREZA_DAMAGE,
116 ),
117 DamageFlags::empty(),
118 );
119
120 if config.shadow_of_nathreza_rank < SHADOW_OF_NATHREZA_MAX_RANK {
121 return;
122 }
123
124 if ctx.roll_system_rppm(
125 wowlab_engine_combat::generated_identities::system_rppm::SHADOW_OF_NATHREZA,
126 ) {
127 let _ = ctx.summon_guardian(
128 GuardianSpec::new_inheriting_owner(
129 TALENT::SHADOW_OF_NATHREZA_3,
130 DESPERATE_SOUL_DURATION_MS,
131 )
132 .ability(
133 GuardianAbility::new(DESPERATE_SOUL_ACTION_DELAY_MS, wrath_of_nathreza_action)
134 .max_actions(1)
135 .ends_guardian(),
136 ),
137 );
138 }
139}
140
141fn trigger_manifested_soul(ctx: &mut HookCtx<'_>) {
142 let Some(config) = ctx.spec_config::<AfflictionConfig>() else {
143 return;
144 };
145
146 tracing::trace!(
147 enabled = config.manifested_avarice,
148 attempts = ctx
149 .spec_runtime::<AfflictionRuntime>()
150 .map_or(0, |runtime| runtime.manifested_avarice_attempts),
151 "MANIFESTED_AVARICE_CHECK"
152 );
153
154 if !config.manifested_avarice {
155 return;
156 }
157
158 let attempts = ctx
159 .spec_runtime::<AfflictionRuntime>()
160 .map_or(1, |runtime| {
161 runtime.manifested_avarice_attempts.saturating_add(1)
162 });
163 let success = proc_chance(
164 ctx.rng(),
165 prd_constant(MANIFESTED_AVARICE_RATE) * f64::from(attempts),
166 );
167
168 if let Some(runtime) = ctx.spec_runtime_mut::<AfflictionRuntime>() {
169 runtime.manifested_avarice_attempts = if success { 0 } else { attempts };
170 }
171
172 if !success {
173 return;
174 }
175
176 let resolved_duration_ms = ctx.action_aura_duration_ms(
177 crate::generated::specs::affliction_warlock::DAMAGE_AURA::MANIFESTED_DEMONIC_SOUL,
178 );
179 let duration_ms = if resolved_duration_ms == 0 {
180 MANIFESTED_SOUL_DURATION_MS
181 } else {
182 resolved_duration_ms
183 };
184 let _ = ctx.summon_guardian(
185 GuardianSpec::new_inheriting_owner(TALENT::MANIFESTED_AVARICE, duration_ms).ability(
186 GuardianAbility::new(SOUL_SWIPE_INTERVAL_MS, manifested_soul_action)
187 .first_action_delay(SOUL_SWIPE_INTERVAL_MS),
188 ),
189 );
190}
191
192pub(crate) fn unstable_affliction_hook(ctx: &mut HookCtx<'_>) {
194 if ctx
195 .spec_config::<AfflictionConfig>()
196 .is_some_and(|cfg| cfg.mid2_4pc)
197 {
198 ctx.apply_aura(AURA_UNSTABLE_EMPOWERMENT);
199 }
200
201 if !ctx.consume_aura_stack(AURA_SUCCULENT_SOUL) {
202 return;
203 }
204
205 let config = ctx
206 .spec_config::<AfflictionConfig>()
207 .copied()
208 .unwrap_or_default();
209 let fervor = if config.demoniacs_fervor && ctx.is_aura_active(AURA_UNSTABLE_AFFLICTION) {
210 1.0 + ctx.effect_percent(
211 crate::generated::specs::affliction_warlock::DAMAGE_EFFECT::DEMONIACS_FERVOR_DAMAGE,
212 )
213 } else {
214 1.0
215 };
216
217 ctx.deal_damage_sp(
218 crate::generated::specs::affliction_warlock::DAMAGE_REPORTED::DEMONIC_SOUL,
219 ctx.effect_sp_coefficient(
220 crate::generated::specs::affliction_warlock::DAMAGE_EFFECT::DEMONIC_SOUL_DAMAGE,
221 ) * fervor,
222 DamageFlags::empty(),
223 );
224
225 if config.soul_anathema {
226 ctx.apply_aura(AURA_SOUL_ANATHEMA);
227 }
228
229 trigger_manifested_soul(ctx);
230}
231
232pub(crate) fn unstable_affliction_expire_hook(ctx: &mut HookCtx<'_>) {
233 ctx.expire_aura(AURA_UNSTABLE_EMPOWERMENT);
234}
235
236pub(crate) fn seed_of_corruption_hook(ctx: &mut HookCtx<'_>) {
237 let effectiveness = ctx
238 .spec_config::<AfflictionConfig>()
239 .map_or(0.0, |cfg| cfg.mid2_seed_effectiveness);
240
241 if effectiveness <= 0.0 {
242 return;
243 }
244
245 ctx.apply_aura(AURA_UNSTABLE_AFFLICTION);
246 ctx.refresh_aura_snapshot_multiplier(AURA_UNSTABLE_AFFLICTION, effectiveness);
247 ctx.apply_aura(AURA_UNSTABLE_EMPOWERMENT);
248}
249
250pub(crate) fn dark_harvest_hook(ctx: &mut HookCtx<'_>) {
252 if let Some(runtime) = ctx.spec_runtime_mut::<AfflictionRuntime>() {
253 runtime.dark_harvest_ticks = 0;
254 }
255}
256
257pub(crate) fn dark_harvest_tick_hook(ctx: &mut HookCtx<'_>) {
259 let enabled = ctx
260 .spec_config::<AfflictionConfig>()
261 .is_some_and(|config| config.shadow_of_death);
262 let tick = ctx
263 .spec_runtime_mut::<AfflictionRuntime>()
264 .map_or(0, |runtime| {
265 runtime.dark_harvest_ticks = runtime.dark_harvest_ticks.saturating_add(1);
266
267 runtime.dark_harvest_ticks
268 });
269
270 if !enabled || tick <= 1 {
271 return;
272 }
273
274 let shards = ctx.effect_base_points(
275 crate::generated::specs::affliction_warlock::DAMAGE_EFFECT::SHADOW_OF_DEATH_SHARDS,
276 );
277
278 ctx.gain_resource(shards);
279 ctx.apply_aura_n(
280 AURA_SUCCULENT_SOUL,
281 wowlab_types::numeric::f64_to_i32_saturating_round(shards.max(0.0)),
282 );
283}
284
285#[must_use]
289pub(crate) fn mastery(ctx: &MasteryCtx<'_>) -> f64 {
290 ctx.direct_or_periodic_affected_bonus_any(
291 &[EFFECT::POTENT_AFFLICTIONS_DIRECT.1],
292 &[
293 EFFECT::POTENT_AFFLICTIONS_DAMAGE.1,
294 EFFECT::POTENT_AFFLICTIONS_LABEL_DAMAGE.1,
295 ],
296 )
297}