wowlab_engine_content/hooks/arms_warrior/
hooks.rs1use 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::AuraOps 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_COLOSSUS_SMASH, AURA_DEEP_WOUNDS, AURA_EXECUTIONER,
14 AURA_EXECUTIONERS_PRECISION, AURA_FATAL_MARK, AURA_FIERCE_FOLLOWTHROUGH, AURA_IMMINENT_DEMISE,
15 AURA_MARTIAL_PROWESS, AURA_MASTER_OF_WARFARE, AURA_MASTER_OF_WARFARE_DAMAGE, AURA_OVERWHELMED,
16 AURA_SUDDEN_DEATH, EFFECT, HERO, SPELL, SPELL_AVATAR, SPELL_BLADESTORM, SPELL_COLOSSUS_SMASH,
17 SPELL_EXECUTE, SPELL_HEROIC_STRIKE, SPELL_MORTAL_STRIKE, SPELL_OVERPOWER, SPELL_REND,
18 SPELL_SLAM, SPELL_THUNDER_CLAP, TALENT,
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.state().config.base_stats.stats.crit_chance / HUNDRED
39}
40
41fn on_special_attack(
42 ctx: &mut HookCtx<'_>,
43 cfg: &ArmsConfig,
44 rage_spent: f64,
45 mow_eligible: bool,
46) {
47 on_warrior_special_attack(
48 ctx,
49 SpecialAttackConfig {
50 rage: RageSpendConfig {
51 anger_management_div: cfg.anger_management_div,
52 frothing_chance: cfg.frothing_chance,
53 frothing_refund_pct: cfg.frothing_refund_pct,
54 frothing_refund_source: EFFECT::FROTHING_REFUND,
55 avatar: SPELL_AVATAR,
56 paired_cooldown: SPELL_COLOSSUS_SMASH,
57 tactician: Some((cfg.tactician_chance, SPELL_OVERPOWER)),
58 },
59 sudden_death_driver: TALENT::SUDDEN_DEATH,
60 sudden_death_aura: AURA_SUDDEN_DEATH,
61 execute: SPELL_EXECUTE,
62 slayers_dominance: cfg.slayers_dominance,
63 slayers_dominance_driver: TALENT::SLAYERS_DOMINANCE,
64 prd_c: cfg.prd_c,
65 slayers_strike: HERO::SLAYER::SPELL::SLAYERS_STRIKE,
66 slayers_strike_coef: cfg.slayers_strike_coef,
67 executioner: AURA_EXECUTIONER,
68 imminent_demise_driver: TALENT::IMMINENT_DEMISE,
69 imminent_demise_every: cfg.imminent_demise_every,
70 },
71 rage_spent,
72 );
73
74 if mow_eligible
75 && cfg.master_of_warfare
76 && !ctx.is_aura_active(AURA_MASTER_OF_WARFARE.raw())
77 && ctx.roll_accumulating_proc(TALENT::MASTER_OF_WARFARE, cfg.prd_c)
78 {
79 ctx.apply_aura(AURA_MASTER_OF_WARFARE.raw());
80 }
81}
82
83pub(crate) fn mortal_strike_hook(ctx: &mut HookCtx<'_>) {
84 let cfg = arms_config(ctx);
85
86 ctx.expire_aura(AURA_MARTIAL_PROWESS.raw());
87 ctx.expire_aura(AURA_EXECUTIONERS_PRECISION.raw());
88 ctx.expire_aura(AURA_FIERCE_FOLLOWTHROUGH.raw());
89
90 if cfg.fatality_chance > 0.0 && proc_chance(ctx.rng(), cfg.fatality_chance) {
91 ctx.apply_aura(AURA_FATAL_MARK.raw());
92 }
93
94 let crit_chance = sheet_crit(ctx);
95 let crit_roll = proc_chance(ctx.rng(), crit_chance);
96
97 if cfg.mortal_wounds && crit_roll {
98 ctx.apply_aura(AURA_DEEP_WOUNDS.raw());
99 }
100
101 if cfg.fierce_followthrough && crit_roll {
102 ctx.apply_aura(AURA_FIERCE_FOLLOWTHROUGH.raw());
103 }
104
105 if cfg.set4pc_cs_extend_ms > 0 && ctx.is_aura_active(AURA_COLOSSUS_SMASH.raw()) {
106 ctx.extend_aura(AURA_COLOSSUS_SMASH.raw(), cfg.set4pc_cs_extend_ms);
107 }
108
109 let cost = ctx.spell_resource_cost(SPELL_MORTAL_STRIKE.raw());
110
111 on_special_attack(ctx, &cfg, cost, true);
112}
113
114pub(crate) fn overpower_hook(ctx: &mut HookCtx<'_>) {
115 let cfg = arms_config(ctx);
116
117 if cfg.martial_prowess {
118 ctx.apply_aura(AURA_MARTIAL_PROWESS.raw());
119 }
120
121 ctx.deal_physical_damage_ap_if_positive(SPELL::DREADNAUGHT, cfg.dreadnaught_coef);
122
123 if cfg.op_finish_pct > 0.0 && ctx.target_health_fraction() < cfg.op_finish_below {
124 ctx.deal_physical_damage_ap_if_positive(
125 SPELL::OVERPOWER,
126 cfg.overpower_coef * cfg.op_finish_pct,
127 );
128 }
129
130 on_special_attack(ctx, &cfg, 0.0, true);
131}
132
133pub(crate) fn execute_hook(ctx: &mut HookCtx<'_>) {
134 let cfg = arms_config(ctx);
135
136 let sudden_death = ctx.is_aura_active(AURA_SUDDEN_DEATH.raw());
137 let mult;
138 let mut spent = 0.0;
139
140 if sudden_death {
141 ctx.consume_aura(AURA_SUDDEN_DEATH.raw());
142 mult = EXECUTE_DMG_SCALE;
143 let executioner = ctx.aura_stacks(AURA_EXECUTIONER.raw());
144
145 if cfg.uo_cdr_ms_per_stack > 0 && executioner > 0 {
146 ctx.reduce_cooldown(
147 SPELL_BLADESTORM.raw(),
148 cfg.uo_cdr_ms_per_stack
149 * wowlab_types::numeric::i32_to_u32_nonnegative(executioner),
150 );
151 ctx.apply_aura_n(
152 AURA_OVERWHELMED.raw(),
153 cfg.uo_overwhelmed_per_stack * executioner,
154 );
155 }
156
157 if cfg.imminent_demise_every > 0 {
158 ctx.apply_aura(AURA_IMMINENT_DEMISE.raw());
159 }
160
161 if cfg.reap_the_storm && ctx.proc_category_ready(TALENT::REAP_THE_STORM) {
162 ctx.start_proc_category_cooldown(TALENT::REAP_THE_STORM);
163 ctx.deal_physical_damage_ap_if_positive(
164 HERO::SLAYER::SPELL::REAP_THE_STORM_EXECUTE,
165 cfg.reap_coef,
166 );
167 }
168 } else {
169 spent = ctx.last_primary_spent();
170 mult = EXECUTE_DMG_SCALE * spent.min(cfg.execute_max_rage) / cfg.execute_max_rage;
171 }
172
173 ctx.deal_physical_damage_ap_if_positive(SPELL::EXECUTE_DAMAGE, cfg.execute_coef * mult);
174
175 if spent > 0.0 && cfg.execute_refund_pct > 0.0 {
176 ctx.gain_resource_from(
177 spent * cfg.execute_refund_pct,
178 ResourceGainSource::SpellEffect {
179 spell_id: EFFECT::EXECUTE_REFUND.0,
180 effect_index: EFFECT::EXECUTE_REFUND.1,
181 },
182 );
183 }
184
185 if cfg.deep_wounds {
186 ctx.apply_aura(AURA_DEEP_WOUNDS.raw());
187 }
188
189 if cfg.executioners_precision {
190 ctx.apply_aura(AURA_EXECUTIONERS_PRECISION.raw());
191 }
192
193 let marks = ctx.aura_stacks(AURA_FATAL_MARK.raw());
194
195 if cfg.fatality_chance > 0.0 && marks > 0 {
196 ctx.deal_physical_damage_ap_if_positive(
197 SPELL::FATALITY,
198 cfg.fatality_coef * f64::from(marks),
199 );
200 ctx.expire_aura(AURA_FATAL_MARK.raw());
201 }
202
203 on_special_attack(ctx, &cfg, spent, true);
204}
205
206pub(crate) fn slam_hook(ctx: &mut HookCtx<'_>) {
208 let cfg = arms_config(ctx);
209 let heroic_strike = ctx.is_aura_active(AURA_MASTER_OF_WARFARE.raw());
212
213 if heroic_strike {
214 ctx.apply_aura(AURA_MASTER_OF_WARFARE_DAMAGE.raw());
215 }
216
217 if cfg.martial_prowess {
218 ctx.apply_aura(AURA_MARTIAL_PROWESS.raw());
219 }
220
221 let crit_chance = sheet_crit(ctx);
222
223 if cfg.mortal_wounds && proc_chance(ctx.rng(), crit_chance) {
224 ctx.apply_aura(AURA_DEEP_WOUNDS.raw());
225 }
226
227 let resolved_spell = if heroic_strike {
228 SPELL_HEROIC_STRIKE
229 } else {
230 SPELL_SLAM
231 };
232 let resource_cost = ctx.spell_resource_cost(resolved_spell.raw());
233
234 on_special_attack(ctx, &cfg, resource_cost, true);
235}
236
237pub(crate) fn colossus_smash_hook(ctx: &mut HookCtx<'_>) {
238 let cfg = arms_config(ctx);
239
240 on_special_attack(ctx, &cfg, 0.0, false);
241}
242
243pub(crate) fn bladestorm_hook(ctx: &mut HookCtx<'_>) {
244 let cfg = arms_config(ctx);
245 let duration = consume_bladestorm_extension(
246 ctx,
247 AURA_IMMINENT_DEMISE,
248 AURA_BLADESTORM,
249 cfg.bladestorm_duration_ms,
250 cfg.bladestorm_period_ms,
251 );
252
253 if cfg.overwhelming_blades
254 && let Some(duration) = duration
255 {
256 let _ = ctx.trigger_periodic_driver_for(TALENT::OVERWHELMING_BLADES, duration.get());
257 }
258
259 if cfg.unhinged
260 && let Some(duration) = duration
261 {
262 let _ = ctx.trigger_periodic_driver_for(TALENT::UNHINGED, duration.get());
263 }
264
265 on_special_attack(ctx, &cfg, 0.0, false);
266}
267
268pub(super) fn overwhelming_blades_tick(ctx: &mut HookCtx<'_>) {
269 ctx.apply_aura(AURA_OVERWHELMED.raw());
270}
271
272pub(super) fn unhinged_tick(ctx: &mut HookCtx<'_>) {
273 let cfg = arms_config(ctx);
274
275 ctx.deal_physical_damage_ap_if_positive(TALENT::UNHINGED, cfg.unhinged_coef);
276}
277
278pub(crate) fn rend_hook(ctx: &mut HookCtx<'_>) {
279 let cfg = arms_config(ctx);
280 let cost = ctx.spell_resource_cost(SPELL_REND.raw());
281
282 on_special_attack(ctx, &cfg, cost, false);
283}
284
285pub(crate) fn thunder_clap_hook(ctx: &mut HookCtx<'_>) {
286 let cfg = arms_config(ctx);
287 let cost = ctx.spell_resource_cost(SPELL_THUNDER_CLAP.raw());
288
289 on_special_attack(ctx, &cfg, cost, false);
290}
291
292pub(crate) fn swing_hook(
293 ctx: &mut HookCtx<'_>,
294 event: SwingEvent,
295) {
296 let is_crit = event.is_crit;
297
298 if !is_crit {
299 return;
300 }
301
302 let cfg = arms_config(ctx);
303
304 if cfg.crit_swing_extra_rage > 0.0 {
305 ctx.gain_resource_from(
306 cfg.crit_swing_extra_rage,
307 ResourceGainSource::AutoAttack,
308 );
309 }
310}