wowlab_engine_content/hooks/arms_warrior/
hooks.rs1use wowlab_engine_combat::AuraOps as _;
2use wowlab_engine_combat::CooldownOps as _;
3use wowlab_engine_combat::DamageOps as _;
4use wowlab_engine_combat::ProcOps as _;
5use wowlab_engine_combat::ResourceOps as _;
6use wowlab_engine_combat::SchedulingOps as _;
7use wowlab_types::constants::HUNDRED;
8
9use wowlab_engine_combat::{HookCtx, MasteryCtx, ResourceGainSource, SwingEvent};
10use wowlab_engine_rng::proc_chance;
11
12use crate::generated::specs::arms_warrior::{
13 AURA_BLADESTORM, AURA_DEEP_WOUNDS, AURA_EXECUTIONER, AURA_EXECUTIONERS_PRECISION,
14 AURA_FATAL_MARK, AURA_FIERCE_FOLLOWTHROUGH, AURA_IMMINENT_DEMISE, AURA_MARTIAL_PROWESS,
15 AURA_MASTER_OF_WARFARE, AURA_MASTER_OF_WARFARE_DAMAGE, AURA_OVERWHELMED, AURA_SUDDEN_DEATH,
16 AURA_WINDING_UP, EFFECT, SPELL_AVATAR, SPELL_BLADESTORM, SPELL_COLOSSUS_SMASH, SPELL_EXECUTE,
17 SPELL_HEROIC_STRIKE, SPELL_MORTAL_STRIKE, SPELL_OVERPOWER, SPELL_REND, SPELL_SLAM,
18 SPELL_THUNDER_CLAP,
19};
20use crate::hooks::shared::warrior::{
21 RageSpendConfig, SpecialAttackConfig, consume_bladestorm_extension,
22 on_special_attack as on_warrior_special_attack,
23};
24
25use super::{ArmsConfig, config::arms_config};
26
27const EXECUTE_DMG_SCALE: f64 = 2.0;
29
30#[must_use]
32pub(crate) fn mastery(ctx: &MasteryCtx<'_>) -> f64 {
33 ctx.bonus(1)
34}
35
36fn sheet_crit(ctx: &HookCtx<'_>) -> f64 {
38 ctx.program().base_stats().stats.crit_chance / HUNDRED
39}
40
41fn on_special_attack(ctx: &mut HookCtx<'_>, cfg: &ArmsConfig, rage_spent: f64, mow_eligible: bool) {
42 on_warrior_special_attack(
43 ctx,
44 SpecialAttackConfig {
45 rage: RageSpendConfig {
46 anger_management_div: cfg.anger_management_div,
47 frothing_chance: cfg.frothing_chance,
48 frothing_refund_pct: cfg.frothing_refund_pct,
49 frothing_refund_source: EFFECT::FROTHING_REFUND,
50 avatar: SPELL_AVATAR,
51 paired_cooldown: SPELL_COLOSSUS_SMASH,
52 tactician: Some((cfg.tactician_chance, SPELL_OVERPOWER)),
53 },
54 sudden_death_driver:
55 wowlab_engine_combat::generated_identities::system_rppm::SUDDEN_DEATH,
56 sudden_death_aura: AURA_SUDDEN_DEATH,
57 execute: SPELL_EXECUTE,
58 slayers_dominance: cfg.slayers_dominance,
59 slayers_dominance_driver:
60 wowlab_engine_combat::generated_identities::proc_counter::SLAYERS_DOMINANCE_ATTEMPT,
61 prd_c: cfg.prd_c,
62 slayers_strike: crate::generated::specs::arms_warrior::DAMAGE_SPELL::SLAYERS_STRIKE,
63 slayers_strike_coef: cfg.slayers_strike_coef,
64 executioner: AURA_EXECUTIONER,
65 imminent_demise_driver:
66 wowlab_engine_combat::generated_identities::proc_counter::IMMINENT_DEMISE,
67 imminent_demise_every: cfg.imminent_demise_every,
68 },
69 rage_spent,
70 );
71
72 if mow_eligible
73 && cfg.master_of_warfare
74 && !ctx.is_aura_active(AURA_MASTER_OF_WARFARE)
75 && ctx.roll_accumulating_proc(
76 wowlab_engine_combat::generated_identities::proc_counter::MASTER_OF_WARFARE_ATTEMPT,
77 cfg.prd_c,
78 )
79 {
80 ctx.apply_aura(AURA_MASTER_OF_WARFARE);
81 }
82}
83
84pub(crate) fn mortal_strike_hook(ctx: &mut HookCtx<'_>) {
85 let cfg = arms_config(ctx);
86
87 ctx.expire_aura(AURA_MARTIAL_PROWESS);
88 ctx.expire_aura(AURA_EXECUTIONERS_PRECISION);
89 ctx.expire_aura(AURA_FIERCE_FOLLOWTHROUGH);
90
91 if cfg.fatality_chance > 0.0 && proc_chance(ctx.rng(), cfg.fatality_chance) {
92 ctx.apply_aura(AURA_FATAL_MARK);
93 }
94
95 let crit_chance = sheet_crit(ctx);
96 let crit_roll = proc_chance(ctx.rng(), crit_chance);
97
98 if cfg.mortal_wounds && crit_roll {
99 ctx.apply_aura(AURA_DEEP_WOUNDS);
100 }
101
102 if cfg.fierce_followthrough && crit_roll {
103 ctx.apply_aura(AURA_FIERCE_FOLLOWTHROUGH);
104 }
105
106 if cfg.mid2_4pc {
107 ctx.apply_aura(AURA_WINDING_UP);
108 }
109
110 let cost = ctx.spell_resource_cost(SPELL_MORTAL_STRIKE);
111
112 on_special_attack(ctx, &cfg, cost, true);
113}
114
115pub(crate) fn overpower_hook(ctx: &mut HookCtx<'_>) {
116 let cfg = arms_config(ctx);
117
118 if cfg.martial_prowess {
119 ctx.apply_aura(AURA_MARTIAL_PROWESS);
120 }
121
122 if cfg.mid2_4pc {
123 ctx.apply_aura(AURA_WINDING_UP);
124 }
125
126 ctx.deal_physical_damage_ap_if_positive(
127 crate::generated::specs::arms_warrior::DAMAGE_SPELL::DREADNAUGHT,
128 cfg.dreadnaught_coef,
129 );
130
131 if cfg.op_finish_pct > 0.0 && ctx.target_health_fraction() < cfg.op_finish_below {
132 ctx.deal_physical_damage_ap_if_positive(
133 crate::generated::specs::arms_warrior::DAMAGE_SPELL::OVERPOWER,
134 cfg.overpower_coef * cfg.op_finish_pct,
135 );
136 }
137
138 on_special_attack(ctx, &cfg, 0.0, true);
139}
140
141pub(crate) fn execute_hook(ctx: &mut HookCtx<'_>) {
142 let cfg = arms_config(ctx);
143
144 let sudden_death = ctx.is_aura_active(AURA_SUDDEN_DEATH);
145 let mult;
146 let mut spent = 0.0;
147
148 if sudden_death {
149 ctx.consume_aura(AURA_SUDDEN_DEATH);
150 mult = EXECUTE_DMG_SCALE;
151 let executioner = ctx.aura_stacks(AURA_EXECUTIONER);
152
153 if cfg.uo_cdr_ms_per_stack > 0 && executioner > 0 {
154 ctx.reduce_cooldown(
155 SPELL_BLADESTORM,
156 cfg.uo_cdr_ms_per_stack
157 * wowlab_types::numeric::i32_to_u32_nonnegative(executioner),
158 );
159 ctx.apply_aura_n(AURA_OVERWHELMED, cfg.uo_overwhelmed_per_stack * executioner);
160 }
161
162 if cfg.imminent_demise_every > 0 {
163 ctx.apply_aura(AURA_IMMINENT_DEMISE);
164 }
165
166 if cfg.reap_the_storm
167 && ctx.proc_category_ready(
168 wowlab_engine_combat::generated_identities::proc_category::REAP_THE_STORM,
169 )
170 {
171 ctx.start_proc_category_cooldown(
172 wowlab_engine_combat::generated_identities::proc_category::REAP_THE_STORM,
173 );
174 ctx.deal_physical_damage_ap_if_positive(
175 crate::generated::specs::arms_warrior::DAMAGE_SPELL::REAP_THE_STORM_EXECUTE,
176 cfg.reap_coef,
177 );
178 }
179 } else {
180 spent = ctx.last_primary_spent();
181 mult = EXECUTE_DMG_SCALE * spent.min(cfg.execute_max_rage) / cfg.execute_max_rage;
182 }
183
184 ctx.deal_physical_damage_ap_if_positive(
185 crate::generated::specs::arms_warrior::DAMAGE_SPELL::EXECUTE_DAMAGE,
186 cfg.execute_coef * mult,
187 );
188
189 if spent > 0.0 && cfg.execute_refund_pct > 0.0 {
190 ctx.gain_resource_from(
191 spent * cfg.execute_refund_pct,
192 ResourceGainSource::SpellEffect {
193 spell_id: EFFECT::EXECUTE_REFUND.0,
194 effect_index: EFFECT::EXECUTE_REFUND.1,
195 },
196 );
197 }
198
199 if cfg.deep_wounds {
200 ctx.apply_aura(AURA_DEEP_WOUNDS);
201 }
202
203 if cfg.executioners_precision {
204 ctx.apply_aura(AURA_EXECUTIONERS_PRECISION);
205 }
206
207 let marks = ctx.aura_stacks(AURA_FATAL_MARK);
208
209 if cfg.fatality_chance > 0.0 && marks > 0 {
210 ctx.deal_physical_damage_ap_if_positive(
211 crate::generated::specs::arms_warrior::DAMAGE_SPELL::FATALITY,
212 cfg.fatality_coef * f64::from(marks),
213 );
214 ctx.expire_aura(AURA_FATAL_MARK);
215 }
216
217 on_special_attack(ctx, &cfg, spent, true);
218}
219
220pub(crate) fn slam_hook(ctx: &mut HookCtx<'_>) {
222 let cfg = arms_config(ctx);
223 let heroic_strike = ctx.is_aura_active(AURA_MASTER_OF_WARFARE);
226
227 if heroic_strike {
228 ctx.apply_aura(AURA_MASTER_OF_WARFARE_DAMAGE);
229 }
230
231 if cfg.martial_prowess {
232 ctx.apply_aura(AURA_MARTIAL_PROWESS);
233 }
234
235 if cfg.mid2_2pc {
236 let primary_target = ctx.target();
237 let effect =
238 crate::generated::specs::arms_warrior::DAMAGE_EFFECT::MID2_CONCUSSIVE_SLAM_DAMAGE;
239
240 ctx.with_resolved_spell_effect_targets(
241 effect,
242 crate::generated::specs::arms_warrior::DAMAGE_REPORTED::CONCUSSIVE_SLAM,
243 None,
244 |ctx, targets| {
245 let secondary_count = targets
246 .iter()
247 .filter(|target| Some(**target) != primary_target)
248 .count();
249 let reduced_aoe = if cfg.mid2_concussive_slam_reduced_targets > 0
250 && secondary_count > cfg.mid2_concussive_slam_reduced_targets
251 {
252 (wowlab_types::numeric::usize_to_f64(
253 cfg.mid2_concussive_slam_reduced_targets,
254 ) / wowlab_types::numeric::usize_to_f64(secondary_count))
255 .sqrt()
256 } else {
257 1.0
258 };
259
260 for target in targets
261 .iter()
262 .copied()
263 .filter(|target| Some(*target) != primary_target)
264 {
265 ctx.with_resolved_target(target, |target_ctx| {
266 target_ctx.deal_damage_ap_to_target(
267 target,
268 crate::generated::specs::arms_warrior::DAMAGE_REPORTED::CONCUSSIVE_SLAM,
269 cfg.mid2_concussive_slam_coef * reduced_aoe,
270 wowlab_engine_combat::DamageFlags::AOE
271 | wowlab_engine_combat::DamageFlags::PHYSICAL,
272 );
273 });
274 }
275 },
276 );
277 }
278
279 ctx.consume_aura(AURA_WINDING_UP);
280
281 let crit_chance = sheet_crit(ctx);
282
283 if cfg.mortal_wounds && proc_chance(ctx.rng(), crit_chance) {
284 ctx.apply_aura(AURA_DEEP_WOUNDS);
285 }
286
287 let resolved_spell = if heroic_strike {
288 SPELL_HEROIC_STRIKE
289 } else {
290 SPELL_SLAM
291 };
292 let resource_cost = ctx.spell_resource_cost(resolved_spell);
293
294 on_special_attack(ctx, &cfg, resource_cost, true);
295}
296
297pub(crate) fn colossus_smash_hook(ctx: &mut HookCtx<'_>) {
298 let cfg = arms_config(ctx);
299
300 on_special_attack(ctx, &cfg, 0.0, false);
301}
302
303pub(crate) fn bladestorm_hook(ctx: &mut HookCtx<'_>) {
304 let cfg = arms_config(ctx);
305 let duration = consume_bladestorm_extension(
306 ctx,
307 AURA_IMMINENT_DEMISE,
308 AURA_BLADESTORM,
309 cfg.bladestorm_duration_ms,
310 cfg.bladestorm_period_ms,
311 );
312
313 if cfg.overwhelming_blades
314 && let Some(duration) = duration
315 {
316 let _ = ctx.trigger_periodic_driver_for(
317 wowlab_engine_combat::generated_identities::periodic_driver::OVERWHELMING_BLADES,
318 duration.get(),
319 );
320 }
321
322 if cfg.unhinged
323 && let Some(duration) = duration
324 {
325 let _ = ctx.trigger_periodic_driver_for(
326 wowlab_engine_combat::generated_identities::periodic_driver::UNHINGED,
327 duration.get(),
328 );
329 }
330
331 on_special_attack(ctx, &cfg, 0.0, false);
332}
333
334pub(super) fn overwhelming_blades_tick(ctx: &mut HookCtx<'_>) {
335 ctx.apply_aura(AURA_OVERWHELMED);
336}
337
338pub(super) fn unhinged_tick(ctx: &mut HookCtx<'_>) {
339 let cfg = arms_config(ctx);
340
341 ctx.deal_physical_damage_ap_if_positive(
342 crate::generated::specs::arms_warrior::DAMAGE_TALENT::UNHINGED,
343 cfg.unhinged_coef,
344 );
345}
346
347pub(crate) fn rend_hook(ctx: &mut HookCtx<'_>) {
348 let cfg = arms_config(ctx);
349 let cost = ctx.spell_resource_cost(SPELL_REND);
350
351 on_special_attack(ctx, &cfg, cost, false);
352}
353
354pub(crate) fn thunder_clap_hook(ctx: &mut HookCtx<'_>) {
355 let cfg = arms_config(ctx);
356 let cost = ctx.spell_resource_cost(SPELL_THUNDER_CLAP);
357
358 on_special_attack(ctx, &cfg, cost, false);
359}
360
361pub(crate) fn swing_hook(ctx: &mut HookCtx<'_>, event: SwingEvent) {
362 let is_crit = event.is_crit;
363
364 if !is_crit {
365 return;
366 }
367
368 let cfg = arms_config(ctx);
369
370 if cfg.crit_swing_extra_rage > 0.0 {
371 ctx.gain_resource_from(cfg.crit_swing_extra_rage, ResourceGainSource::AutoAttack);
372 }
373}