wowlab_engine_content/hooks/assassination_rogue/
hooks.rs1use wowlab_engine_combat::AuraOps as _;
2use wowlab_engine_combat::DamageOps as _;
3use wowlab_engine_combat::ResourceOps as _;
4use wowlab_engine_combat::SchedulingOps as _;
5use wowlab_engine_combat::{DamageFlags, HookCtx, SwingEvent};
6use wowlab_engine_domain::constants::OFF_HAND_DAMAGE_MULT;
7use wowlab_engine_rng::proc_chance;
8
9use crate::generated::specs::assassination_rogue::{
10 AURA_ALACRITY, AURA_AMPLIFYING_POISON_DEBUFF, AURA_BLINDSIDE, AURA_ENVENOM,
11 AURA_FINISH_THE_JOB, AURA_GARROTE, AURA_IMPLACABLE_BONUS, AURA_IMPLACABLE_REGEN,
12 AURA_IMPLACABLE_TRACKER, AURA_IMPROVED_GARROTE_WINDOW, AURA_INSPIRING_STRIKE,
13 AURA_INTERNAL_BLEEDING, AURA_KINGSBANE_RAMP, AURA_REGICIDES_REWARD, AURA_RUPTURE,
14 AURA_SLICE_AND_DICE,
15};
16#[cfg(test)]
17use crate::generated::specs::assassination_rogue::{EFFECT, SPELL};
18use crate::hooks::shared::rogue::{bound_edge_case, bound_hand_of_fate, cut_to_the_chase};
19
20use super::{
21 config::sin_config,
22 fatebound::{overflowing_purse, BINDING as FATEBOUND},
23 poisons::{proc_poisons, seal_fate},
24};
25
26const MS_PER_CP_ENVENOM: u32 = 1000;
27const MS_PER_CP_RUPTURE: u32 = 4000;
28const IMPLACABLE_STRIKE_TICKS: u32 = 5;
30const IMPLACABLE_MAX_BONUS_STACKS: i32 = 4;
31
32pub(crate) const fn hook_owned_damage_trigger_disabled(_ctx: &HookCtx<'_>) -> bool {
34 false
35}
36
37pub(crate) fn swing_hook(ctx: &mut HookCtx<'_>, _event: SwingEvent) {
38 proc_poisons(ctx);
39}
40
41pub(crate) fn mutilate_hook(ctx: &mut HookCtx<'_>) {
42 let cfg = sin_config(ctx);
43 let mh = ctx.effect_ap_coefficient(
44 crate::generated::specs::assassination_rogue::DAMAGE_EFFECT::MUTILATE_MH_DMG,
45 );
46 let oh = ctx.effect_ap_coefficient(
47 crate::generated::specs::assassination_rogue::DAMAGE_EFFECT::MUTILATE_OH_DMG,
48 ) * OFF_HAND_DAMAGE_MULT;
49
50 for (action, coef) in [
51 (
52 crate::generated::specs::assassination_rogue::DAMAGE_SPELL::MUTILATE_MH,
53 mh,
54 ),
55 (
56 crate::generated::specs::assassination_rogue::DAMAGE_SPELL::MUTILATE_OH,
57 oh,
58 ),
59 ] {
60 ctx.deal_damage_ap(action, coef, DamageFlags::PHYSICAL);
61
62 if cfg.doomblade_pct > 0.0 {
63 ctx.deal_damage_ap(
65 crate::generated::specs::assassination_rogue::DAMAGE_SPELL::MUTILATED_FLESH,
66 coef * cfg.doomblade_pct,
67 DamageFlags::PHYSICAL,
68 );
69 }
70
71 seal_fate(ctx);
72 proc_poisons(ctx);
73 }
74
75 trigger_blindside(ctx);
76}
77
78pub(crate) fn ambush_hook(ctx: &mut HookCtx<'_>) {
79 let cfg = sin_config(ctx);
80
81 ctx.consume_aura(AURA_BLINDSIDE);
82
83 let mut coef = ctx.effect_ap_coefficient(
84 crate::generated::specs::assassination_rogue::DAMAGE_EFFECT::AMBUSH_DMG,
85 );
86
87 if ctx.is_aura_active(AURA_GARROTE) {
88 coef *= cfg.systemic_failure_mult;
89 }
90
91 ctx.deal_damage_ap(
92 crate::generated::specs::assassination_rogue::DAMAGE_SPELL::AMBUSH,
93 coef,
94 DamageFlags::PHYSICAL,
95 );
96
97 seal_fate(ctx);
98 proc_poisons(ctx);
99 trigger_blindside(ctx);
100}
101
102pub(crate) fn garrote_hook(ctx: &mut HookCtx<'_>) {
103 if ctx.is_aura_active(AURA_IMPROVED_GARROTE_WINDOW) {
104 ctx.refresh_aura_snapshot_multiplier(
105 AURA_GARROTE,
106 1.0 + ctx.effect_percent(crate::generated::specs::assassination_rogue::DAMAGE_EFFECT::IMPROVED_GARROTE_DAMAGE),
107 );
108 }
109}
110
111pub(crate) fn rupture_hook(ctx: &mut HookCtx<'_>) {
112 let cfg = sin_config(ctx);
113 let cp =
114 wowlab_types::numeric::f64_to_u32_saturating_trunc(ctx.last_secondary_spent().max(0.0));
115
116 if cp > 0 {
117 ctx.extend_aura(AURA_RUPTURE, cp * MS_PER_CP_RUPTURE);
118 }
119
120 if cfg.has_internal_bleeding && cp > 0 {
121 ctx.apply_aura(AURA_INTERNAL_BLEEDING);
122 ctx.refresh_aura_snapshot_multiplier(AURA_INTERNAL_BLEEDING, f64::from(cp));
123 }
124
125 proc_poisons(ctx);
126 alacrity(ctx);
127 bound_hand_of_fate(ctx, false, FATEBOUND);
128}
129
130pub(crate) fn envenom_hook(ctx: &mut HookCtx<'_>) {
131 let cfg = sin_config(ctx);
132 let cp = ctx.last_secondary_spent().max(1.0);
133 let envenom_was_up = ctx.is_aura_active(AURA_ENVENOM);
134
135 if envenom_was_up && cfg.has_implacable_regen {
136 ctx.apply_aura(AURA_IMPLACABLE_TRACKER);
137 }
138
139 let mut coef = ctx.effect_ap_coefficient(
140 crate::generated::specs::assassination_rogue::DAMAGE_EFFECT::ENVENOM_DMG,
141 ) * cp;
142
143 if cp >= cfg.delivered_doom_cp {
144 coef *= cfg.delivered_doom_mult;
145 }
146
147 if envenom_was_up {
148 coef *= cfg.rapid_injection_mult;
149 }
150
151 if cfg.amplifying_consume_stacks > 0
152 && ctx.aura_stacks(AURA_AMPLIFYING_POISON_DEBUFF) >= cfg.amplifying_consume_stacks
153 {
154 ctx.consume_aura_stacks(AURA_AMPLIFYING_POISON_DEBUFF, cfg.amplifying_consume_stacks);
155 coef *= cfg.amplifying_bonus_mult;
156 }
157
158 ctx.apply_aura(AURA_ENVENOM);
161
162 if cp > 1.0 {
163 ctx.extend_aura(
164 AURA_ENVENOM,
165 (wowlab_types::numeric::f64_to_u32_saturating_trunc(cp) - 1) * MS_PER_CP_ENVENOM,
166 );
167 }
168
169 if envenom_was_up && cfg.inspiring_strike_mult > 1.0 {
170 ctx.apply_aura_with_duration(
171 AURA_INSPIRING_STRIKE,
172 wowlab_types::numeric::f64_to_u32_saturating_trunc(cp) * MS_PER_CP_ENVENOM,
173 );
174 }
175
176 ctx.deal_damage_ap(
177 crate::generated::specs::assassination_rogue::DAMAGE_SPELL::ENVENOM,
178 coef,
179 DamageFlags::empty(),
180 );
181 cut_to_the_chase(ctx, AURA_SLICE_AND_DICE, cp);
182 proc_poisons(ctx);
183 alacrity(ctx);
184 bound_hand_of_fate(ctx, true, FATEBOUND);
185 overflowing_purse(ctx);
186}
187
188pub(crate) fn envenom_expire_hook(ctx: &mut HookCtx<'_>) {
189 let cfg = sin_config(ctx);
190
191 if !cfg.has_implacable_regen {
192 return;
193 }
194
195 let bonus_stacks = ctx
196 .aura_stacks(AURA_IMPLACABLE_TRACKER)
197 .clamp(0, IMPLACABLE_MAX_BONUS_STACKS);
198
199 ctx.expire_aura(AURA_IMPLACABLE_TRACKER);
200 ctx.apply_aura(AURA_IMPLACABLE_REGEN);
201 ctx.apply_aura_n(AURA_IMPLACABLE_BONUS, bonus_stacks);
202}
203
204pub(crate) fn kingsbane_hook(ctx: &mut HookCtx<'_>) {
205 let cfg = sin_config(ctx);
206
207 if !cfg.has_implacable_strikes {
208 return;
209 }
210
211 let nature = ctx.effect_ap_coefficient(
212 crate::generated::specs::assassination_rogue::DAMAGE_EFFECT::IMPLACABLE_STRIKE_NATURE_DMG,
213 );
214 let physical = ctx.effect_ap_coefficient(
215 crate::generated::specs::assassination_rogue::DAMAGE_EFFECT::IMPLACABLE_STRIKE_PHYSICAL_DMG,
216 );
217
218 for _ in 0..IMPLACABLE_STRIKE_TICKS {
219 ctx.deal_damage_ap(
220 crate::generated::specs::assassination_rogue::DAMAGE_SPELL::IMPLACABLE_STRIKES_NATURE,
221 nature,
222 DamageFlags::empty(),
223 );
224 ctx.deal_damage_ap(
225 crate::generated::specs::assassination_rogue::DAMAGE_SPELL::IMPLACABLE_STRIKES_PHYSICAL,
226 physical,
227 DamageFlags::PHYSICAL,
228 );
229 ctx.gain_secondary_resource(cfg.implacable_strike_cp);
230 proc_poisons(ctx);
231 }
232}
233
234pub(crate) fn kingsbane_expire_hook(ctx: &mut HookCtx<'_>) {
235 let cfg = sin_config(ctx);
236 let ramp_stacks = ctx.aura_stacks(AURA_KINGSBANE_RAMP);
237
238 if cfg.regicides_reward_divisor > 0 {
239 ctx.apply_aura_n(
240 AURA_REGICIDES_REWARD,
241 ramp_stacks / cfg.regicides_reward_divisor,
242 );
243 }
244
245 ctx.consume_aura(AURA_KINGSBANE_RAMP);
246}
247
248pub(crate) fn deathmark_hook(ctx: &mut HookCtx<'_>) {
249 let cfg = sin_config(ctx);
250
251 let _ = ctx.trigger_periodic_driver(
252 wowlab_engine_combat::generated_identities::periodic_driver::DEATHMARK,
253 );
254
255 if cfg.has_finish_the_job {
256 ctx.apply_aura(AURA_FINISH_THE_JOB);
257 }
258
259 bound_edge_case(ctx, FATEBOUND);
260}
261
262pub(super) fn deathmark_energize_tick(ctx: &mut HookCtx<'_>) {
263 ctx.gain_resource(sin_config(ctx).deathmark_energy_per_tick);
264}
265
266pub(crate) fn vanish_hook(ctx: &mut HookCtx<'_>) {
267 if ctx.is_aura_active(crate::generated::specs::assassination_rogue::AURA_IMPROVED_GARROTE) {
268 ctx.apply_aura(AURA_IMPROVED_GARROTE_WINDOW);
269 }
270}
271
272fn alacrity(ctx: &mut HookCtx<'_>) {
273 let cfg = sin_config(ctx);
274
275 if cfg.alacrity_chance_per_cp <= 0.0 {
276 return;
277 }
278
279 let cp = ctx.last_secondary_spent().max(0.0);
280
281 if proc_chance(ctx.rng(), cfg.alacrity_chance_per_cp * cp) {
282 ctx.apply_aura(AURA_ALACRITY);
283 }
284}
285
286fn trigger_blindside(ctx: &mut HookCtx<'_>) {
287 let cfg = sin_config(ctx);
288 let chance = if ctx.target_health_fraction() < cfg.blindside_execute_threshold {
289 cfg.blindside_execute_chance
290 } else {
291 cfg.blindside_chance
292 };
293
294 if chance > 0.0 && proc_chance(ctx.rng(), chance) {
295 ctx.apply_aura(AURA_BLINDSIDE);
296 }
297}
298
299#[cfg(test)]
300#[path = "hooks/tests.rs"]
301mod tests;