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 _, is_aura_active,
4 set_spell_gate,
5};
6use wowlab_engine_domain::pool::SpellGate;
7use wowlab_engine_rng::proc_chance;
8use wowlab_types::constants::MS_PER_SECOND;
9
10pub(crate) const EXECUTE_BASE_PCT: f64 = 20.0;
12pub(crate) const IMPROVED_ATTACK_CHARGES: u8 = 2;
14pub(crate) const FROTHING_CHANCE: f64 = 0.20;
16pub(crate) const PRD_C_15: f64 = 0.032_220_914;
18
19#[derive(Clone, Copy, Debug)]
20pub(crate) struct RageSpendConfig {
21 pub anger_management_div: f64,
22 pub frothing_chance: f64,
23 pub frothing_refund_pct: f64,
24 pub frothing_refund_source: (u32, u8),
25 pub avatar: LocalSpellIdx,
26 pub paired_cooldown: LocalSpellIdx,
27 pub tactician: Option<(f64, LocalSpellIdx)>,
28}
29
30#[derive(Clone, Copy, Debug)]
31pub(crate) struct SpecialAttackConfig {
32 pub rage: RageSpendConfig,
33 pub sudden_death_driver: u32,
34 pub sudden_death_aura: LocalAuraIdx,
35 pub execute: LocalSpellIdx,
36 pub slayers_dominance: bool,
37 pub slayers_dominance_driver: u32,
38 pub prd_c: f64,
39 pub slayers_strike: u32,
40 pub slayers_strike_coef: f64,
41 pub executioner: LocalAuraIdx,
42 pub imminent_demise_driver: u32,
43 pub imminent_demise_every: i32,
44}
45
46pub(crate) fn grant_sudden_death(
47 ctx: &mut HookCtx<'_>,
48 aura: LocalAuraIdx,
49 execute: LocalSpellIdx,
50) {
51 ctx.apply_aura(aura.raw());
52 let cooldown = ctx.spell_cooldown_ms(execute.raw());
53
54 ctx.reduce_cooldown(execute.raw(), cooldown);
55}
56
57pub(crate) fn on_rage_spent(ctx: &mut HookCtx<'_>, cfg: RageSpendConfig, rage: f64) {
58 if rage <= 0.0 {
59 return;
60 }
61
62 if let Some((chance, spell)) = cfg.tactician {
63 if chance > 0.0 && proc_chance(ctx.rng(), chance) {
64 ctx.reduce_cooldown(spell.raw(), ctx.spell_cooldown_ms(spell.raw()));
65 }
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.raw(), cdr_ms);
74 ctx.reduce_cooldown(cfg.paired_cooldown.raw(), 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(cfg.slayers_dominance_driver)
97 && ctx.roll_accumulating_proc(cfg.slayers_dominance_driver, cfg.prd_c)
98 {
99 ctx.start_proc_category_cooldown(cfg.slayers_dominance_driver);
100 ctx.deal_physical_damage_ap_if_positive(cfg.slayers_strike, cfg.slayers_strike_coef);
101 ctx.apply_aura(cfg.executioner.raw());
102
103 if ctx.advance_proc_counter(
104 cfg.imminent_demise_driver,
105 wowlab_types::numeric::i32_to_u32_nonnegative(cfg.imminent_demise_every),
106 ) {
107 grant_sudden_death(ctx, cfg.sudden_death_aura, cfg.execute);
108 }
109 }
110}
111
112pub(crate) fn consume_bladestorm_extension(
117 ctx: &mut HookCtx<'_>,
118 imminent_demise: LocalAuraIdx,
119 bladestorm: LocalAuraIdx,
120 base_duration_ms: u32,
121 tick_ms: u32,
122) -> Option<std::num::NonZeroU32> {
123 let stacks =
124 wowlab_types::numeric::i32_to_u32_nonnegative(ctx.aura_stacks(imminent_demise.raw()));
125 let extension_ms = tick_ms.saturating_mul(stacks);
126
127 if extension_ms > 0 {
128 ctx.extend_aura(bladestorm.raw(), extension_ms);
129 }
130
131 ctx.consume_aura(imminent_demise.raw());
132
133 std::num::NonZeroU32::new(base_duration_ms.saturating_add(extension_ms))
134}
135
136pub(crate) fn sync_execute_gate(
137 handler: &mut CombatHandler,
138 threshold: f64,
139 sudden_death: LocalAuraIdx,
140 execute_spell: LocalSpellIdx,
141 now: wowlab_types::sim::SimTime,
142) {
143 let (state, buf) = handler.state_and_buf_mut();
144 let sudden_death = is_aura_active(
145 &wowlab_engine_combat::CombatView::new(state, buf),
146 sudden_death,
147 wowlab_types::sim::ActorId::Player,
148 state.current_target(),
149 );
150 let health_fraction = state
151 .current_target()
152 .and_then(|target| state.enemy_health_fraction(target, now))
153 .unwrap_or(1.0);
154
155 set_spell_gate(
156 state,
157 buf,
158 execute_spell,
159 SpellGate::TargetHealth,
160 sudden_death || health_fraction < threshold,
161 );
162}