wowlab_engine_content/hooks/shared/
warrior.rs1use wowlab_engine_combat::{
2 AuraOps as _, CombatHandler, CooldownOps as _, DamageOps as _, HookCtx, LocalAuraIdx,
3 LocalSpellIdx, ProcOps as _, ResourceGainSource, ResourceOps as _,
4};
5use wowlab_engine_domain::pool::SpellGate;
6use wowlab_engine_rng::proc_chance;
7use wowlab_types::constants::MS_PER_SECOND;
8
9pub(crate) const EXECUTE_BASE_PCT: f64 = 20.0;
11pub(crate) const IMPROVED_ATTACK_CHARGES: u8 = 2;
13pub(crate) const FROTHING_CHANCE: f64 = 0.20;
15pub(crate) const PRD_C_15: f64 = 0.032_220_914;
17
18#[derive(Clone, Copy, Debug)]
19pub(crate) struct RageSpendConfig {
20 pub anger_management_div: f64,
21 pub frothing_chance: f64,
22 pub frothing_refund_pct: f64,
23 pub frothing_refund_source: (u32, u8),
24 pub avatar: LocalSpellIdx,
25 pub paired_cooldown: LocalSpellIdx,
26 pub tactician: Option<(f64, LocalSpellIdx)>,
27}
28
29#[derive(Clone, Copy, Debug)]
30pub(crate) struct SpecialAttackConfig {
31 pub rage: RageSpendConfig,
32 pub sudden_death_driver: wowlab_engine_combat::SystemRppmBinding,
33 pub sudden_death_aura: LocalAuraIdx,
34 pub execute: LocalSpellIdx,
35 pub slayers_dominance: bool,
36 pub slayers_dominance_driver: wowlab_engine_combat::ProcCounterBinding,
37 pub prd_c: f64,
38 pub slayers_strike: wowlab_engine_combat::DamageActionId,
39 pub slayers_strike_coef: f64,
40 pub executioner: LocalAuraIdx,
41 pub imminent_demise_driver: wowlab_engine_combat::ProcCounterBinding,
42 pub imminent_demise_every: i32,
43}
44
45pub(crate) fn grant_sudden_death(
46 ctx: &mut HookCtx<'_>,
47 aura: LocalAuraIdx,
48 execute: LocalSpellIdx,
49) {
50 ctx.apply_aura(aura);
51 let cooldown = ctx.spell_cooldown_ms(execute);
52
53 ctx.reduce_cooldown(execute, cooldown);
54}
55
56pub(crate) fn on_rage_spent(ctx: &mut HookCtx<'_>, cfg: RageSpendConfig, rage: f64) {
57 if rage <= 0.0 {
58 return;
59 }
60
61 if let Some((chance, spell)) = cfg.tactician
62 && chance > 0.0
63 && proc_chance(ctx.rng(), chance)
64 {
65 ctx.reduce_cooldown(spell, ctx.spell_cooldown_ms(spell));
66 }
67
68 if cfg.anger_management_div > 0.0 {
69 let cdr_ms = wowlab_types::numeric::f64_to_u32_saturating_trunc(
70 rage / cfg.anger_management_div * MS_PER_SECOND,
71 );
72
73 ctx.reduce_cooldown(cfg.avatar, cdr_ms);
74 ctx.reduce_cooldown(cfg.paired_cooldown, cdr_ms);
75 }
76
77 if cfg.frothing_chance > 0.0 && proc_chance(ctx.rng(), cfg.frothing_chance) {
78 ctx.gain_unmodified_resource_from(
79 rage * cfg.frothing_refund_pct,
80 ResourceGainSource::SpellEffect {
81 spell_id: cfg.frothing_refund_source.0,
82 effect_index: cfg.frothing_refund_source.1,
83 },
84 );
85 }
86}
87
88pub(crate) fn on_special_attack(ctx: &mut HookCtx<'_>, cfg: SpecialAttackConfig, rage_spent: f64) {
89 on_rage_spent(ctx, cfg.rage, rage_spent);
90
91 if ctx.roll_system_rppm(cfg.sudden_death_driver) {
92 grant_sudden_death(ctx, cfg.sudden_death_aura, cfg.execute);
93 }
94
95 if cfg.slayers_dominance
96 && ctx.proc_category_ready(
97 wowlab_engine_combat::generated_identities::proc_category::SLAYERS_DOMINANCE,
98 )
99 && ctx.roll_accumulating_proc(cfg.slayers_dominance_driver, cfg.prd_c)
100 {
101 ctx.start_proc_category_cooldown(
102 wowlab_engine_combat::generated_identities::proc_category::SLAYERS_DOMINANCE,
103 );
104 ctx.deal_physical_damage_ap_if_positive(cfg.slayers_strike, cfg.slayers_strike_coef);
105 ctx.apply_aura(cfg.executioner);
106
107 if ctx.advance_proc_counter(
108 cfg.imminent_demise_driver,
109 wowlab_types::numeric::i32_to_u32_nonnegative(cfg.imminent_demise_every),
110 ) {
111 grant_sudden_death(ctx, cfg.sudden_death_aura, cfg.execute);
112 }
113 }
114}
115
116pub(crate) fn consume_bladestorm_extension(
121 ctx: &mut HookCtx<'_>,
122 imminent_demise: LocalAuraIdx,
123 bladestorm: LocalAuraIdx,
124 base_duration_ms: u32,
125 tick_ms: u32,
126) -> Option<std::num::NonZeroU32> {
127 let stacks = wowlab_types::numeric::i32_to_u32_nonnegative(ctx.aura_stacks(imminent_demise));
128 let extension_ms = tick_ms.saturating_mul(stacks);
129
130 if extension_ms > 0 {
131 ctx.extend_aura(bladestorm, extension_ms);
132 }
133
134 ctx.consume_aura(imminent_demise);
135
136 std::num::NonZeroU32::new(base_duration_ms.saturating_add(extension_ms))
137}
138
139pub(crate) fn sync_execute_gate(
140 handler: &mut CombatHandler,
141 threshold: f64,
142 sudden_death: LocalAuraIdx,
143 execute_spell: LocalSpellIdx,
144 ctx: &mut wowlab_engine_ports::SimContext<'_>,
145) {
146 let now = ctx.state.current_time;
147 let request =
148 wowlab_engine_combat::HookCtxRequest::for_optional_target(now, handler.current_target());
149
150 handler.with_hook_ctx(ctx.telemetry, request, |hook| {
151 let sudden_death = hook.is_aura_active(sudden_death);
152 let health_fraction = hook.target_health_fraction();
153
154 hook.set_spell_gate(
155 execute_spell,
156 SpellGate::TargetHealth,
157 sudden_death || health_fraction < threshold,
158 );
159 });
160}