Skip to main content

wowlab_engine_content/hooks/fury_warrior/
config.rs

1use wowlab_engine_combat::{BuffEffect, ImpactProc, PassiveDriverEffect, ResourcePool};
2use wowlab_engine_domain::dbc::ResolvedGameDataEffectExt as _;
3use wowlab_types::{
4    constants::{HUNDRED, MS_PER_SECOND},
5    sim::SpellIdx,
6};
7
8use crate::{
9    generated::specs::fury_warrior::{
10        AURA_BERSERKER_STANCE, AURA_RECKLESSNESS, EFFECT, REPORTED_SPELL, SET_BONUS, SPELL,
11        SPELL_EXECUTE, SPELL_RAGING_BLOW, SPELL_RECKLESSNESS, TALENT,
12    },
13    hooks::{
14        gated, gated_or,
15        shared::warrior::{EXECUTE_BASE_PCT, FROTHING_CHANCE, IMPROVED_ATTACK_CHARGES, PRD_C_15},
16    },
17};
18
19/// Rampage cast spell affected by the Rampaging Berserker reck-window cost delta.
20static RAMPAGE_COST_SPELLS: [u32; 1] = [SPELL::RAMPAGE];
21/// Raw e1 value divided by ten for rage display scaling.
22const RAMPAGING_BERSERKER_COST_DIVISOR: f64 = 10.0;
23use super::hooks::{
24    bloodbath_dot_impact, bloodbath_impact_filter, bloodthirst_family_impact_filter,
25    cold_steel_hot_blood_impact,
26};
27
28/// Rage power-type display divisor (DBC stores rage *10).
29pub(super) const RAGE_DISPLAY_DIV: f64 = 10.0;
30
31const FRENZY_TALENT: u32 = 335_077;
32const FOCUS_IN_CHAOS_TALENT: u32 = 383_486;
33
34static MID2_RAGING_BLOW_DAMAGE_SPELLS: [u32; 4] = [
35    REPORTED_SPELL::RAGING_BLOW_MAIN_HAND,
36    REPORTED_SPELL::RAGING_BLOW_OFF_HAND,
37    REPORTED_SPELL::CRUSHING_BLOW_MAIN_HAND,
38    REPORTED_SPELL::CRUSHING_BLOW_OFF_HAND,
39];
40static MID2_BLOODTHIRST_DAMAGE_SPELLS: [u32; 2] = [SPELL::BLOODTHIRST, SPELL::BLOODBATH];
41
42#[derive(Clone, Copy, Debug, Default)]
43pub(super) struct FuryRuntime {
44    pub(super) mid2_recklessness_extensions: u8,
45}
46
47crate::hooks::define_spec_config! {
48/// Talent-gated Fury hook parameters.
49pub(super) struct FuryConfig {
50    bloodborne: bool { provenance: "Bloodborne 385703 grants its periodic-damage aura from Bloodthirst and Bloodbath.", gate: params.talent_picked(TALENT::BLOODBORNE), source: true, transform: |value| value },
51    frenzy: bool { provenance: "Frenzy 335077 applies its asynchronous haste aura from Rampage.", gate: params.talent_picked(FRENZY_TALENT), source: true, transform: |value| value },
52    wild_strikes: bool { provenance: "Wild Strikes selection gates the auto-crit speed buff.", gate: params.talent_picked(TALENT::WILD_STRIKES), source: true, transform: |value| value },
53    surge_of_adrenaline: bool { provenance: "Surge of Adrenaline selection gates reset-triggered buffs.", gate: params.talent_picked(TALENT::SURGE_OF_ADRENALINE), source: true, transform: |value| value },
54    rb_reset_chance: f64 { provenance: "Raging Blow 85288 e1 reset chance.", gate: params.talent_picked(TALENT::IMPROVED_RAGING_BLOW), source: base(EFFECT::RB_RESET_CHANCE), transform: |value| value / HUNDRED },
55    wnf_reset_chance: f64 { provenance: "Wrath and Fury 392936 e1 extra reset chance while enraged.", gate: params.talent_picked(TALENT::IMPROVED_RAGING_BLOW) && params.talent_picked(TALENT::WRATH_AND_FURY), source: base(EFFECT::WNF_RESET_CHANCE), transform: |value| value / HUNDRED },
56    execute_threshold: f64 { provenance: "Massacre 206315 e2 threshold, otherwise the 20% baseline.", gate: true, source: gated_or(params.talent_picked(TALENT::MASSACRE), EXECUTE_BASE_PCT, || base(EFFECT::MASSACRE_PCT)), transform: |value| value / HUNDRED },
57    deep_wounds: bool { provenance: "Deep Wounds 1261060 applies from Execute main-hand impacts.", gate: params.talent_picked(TALENT::DEEP_WOUNDS), source: true, transform: |value| value },
58    slayers_dominance: bool { provenance: "Slayer's Dominance 444767 selection.", gate: params.talent_picked(TALENT::SLAYERS_DOMINANCE), source: true, transform: |value| value },
59    slayers_strike_coef: f64 { provenance: "Slayer's Strike AP coefficient.", gate: true, source: ap(EFFECT::SLAYERS_STRIKE_DMG), transform: |value| value },
60    prd_c: f64 { provenance: "PRD constant for the 15% nominal rate.", gate: true, source: PRD_C_15, transform: |value| value },
61    imminent_demise_every: i32 { provenance: "Imminent Demise 444769 e1 strike interval.", gate: imminent_demise, source: base(EFFECT::IMMINENT_DEMISE_EVERY), transform: wowlab_types::numeric::f64_to_i32_saturating_trunc },
62    reap_chance: f64 { provenance: "Imminent Demise 444769 e2 Reap chance.", gate: reap, source: base(EFFECT::IMMINENT_DEMISE_REAP_CHANCE), transform: |value| value / HUNDRED },
63    reap_coef: f64 { provenance: "Reap the Storm 446005 e1 AP coefficient.", gate: true, source: ap(EFFECT::REAP_DMG), transform: |value| value },
64    uo_cdr_ms_per_stack: u32 { provenance: "Unrelenting Onslaught 444780 e1 seconds converted to milliseconds.", gate: params.talent_picked(TALENT::UNRELENTING_ONSLAUGHT), source: base(EFFECT::UNRELENTING_CDR_S), transform: |value| wowlab_types::numeric::f64_to_u32_saturating_trunc(value * MS_PER_SECOND) },
65    uo_overwhelmed_per_stack: i32 { provenance: "Unrelenting Onslaught 444780 e2 Overwhelmed stacks.", gate: true, source: base(EFFECT::UNRELENTING_OVERWHELMED), transform: wowlab_types::numeric::f64_to_i32_saturating_trunc },
66    unhinged: bool { provenance: "Unhinged 386628 casts Bloodthirst on odd Bladestorm ticks.", gate: params.talent_picked(TALENT::UNHINGED), source: true, transform: |value| value },
67    executioners_wrath: bool { provenance: "Executioner's Wrath selection gates the Rampage buff.", gate: params.talent_picked(TALENT::EXECUTIONERS_WRATH), source: true, transform: |value| value },
68    mid2_2pc_extension_ms: u32 { provenance: "MID2 Fury two-piece Recklessness extension per Raging Blow.", gate: params.set_bonus_auras.contains(&SET_BONUS::MID2_FURY_2PC), source: base(EFFECT::MID2_RECKLESSNESS_EXTENSION_MS), transform: wowlab_types::numeric::f64_to_u32_saturating_trunc },
69    mid2_2pc_extension_cap: u8 { provenance: "MID2 Fury two-piece maximum Raging Blow extensions per Recklessness cast.", gate: params.set_bonus_auras.contains(&SET_BONUS::MID2_FURY_2PC), source: base(EFFECT::MID2_RECKLESSNESS_EXTENSION_CAP), transform: wowlab_types::numeric::f64_to_u8_saturating_trunc },
70    mid2_4pc: bool { provenance: "MID2 Fury four-piece stacks critical strike during Recklessness from Bloodthirst.", gate: params.set_bonus_auras.contains(&SET_BONUS::MID2_FURY_4PC), source: true, transform: |value| value },
71    anger_management_div: f64 { provenance: "Anger Management 152278 e3 rage per second of CDR.", gate: params.talent_picked(TALENT::ANGER_MANAGEMENT), source: base(EFFECT::ANGER_MANAGEMENT_DIV), transform: |value| value },
72    frothing_chance: f64 { provenance: "Frothing Berserker `SpellAuraOptions` proc chance.", gate: params.talent_picked(TALENT::FROTHING_BERSERKER), source: FROTHING_CHANCE, transform: |value| value },
73    frothing_refund_pct: f64 { provenance: "Frothing Berserker 392792 e1 refund fraction.", gate: true, source: base(EFFECT::FROTHING_REFUND), transform: |value| value / HUNDRED },
74    bladestorm_duration_ms: u32 { provenance: "Bladestorm's resolved aura lifetime drives its finite mechanic callbacks.", gate: params.talent_picked(TALENT::UNHINGED), source: data.require_aura_duration_ms(SpellIdx::from_raw(SPELL::BLADESTORM))?, transform: |value| value },
75    bladestorm_period_ms: u32 { provenance: "Bladestorm 227847 e1 provides the Unhinged callback cadence and Imminent Demise extension quantum.", gate: params.talent_picked(TALENT::UNHINGED), source: data.effect_lookup(EFFECT::BLADESTORM_PERIOD).period(), transform: wowlab_types::numeric::f64_to_u32_saturating_round },
76    unhinged_bloodthirst_coef: f64 { provenance: "Unhinged's Bloodthirst callbacks use Bloodthirst 23881 e1's AP coefficient.", gate: params.talent_picked(TALENT::UNHINGED), source: ap(EFFECT::BLOODTHIRST_DAMAGE), transform: |value| value },
77    unhinged_bloodthirst_rage: f64 { provenance: "Unhinged's Bloodthirst callbacks energize from Bloodthirst 23881 e2 with rage display scaling.", gate: params.talent_picked(TALENT::UNHINGED), source: base(EFFECT::BLOODTHIRST_RAGE), transform: |value| value / RAGE_DISPLAY_DIV },
78    unhinged_bloodbath_coef: f64 { provenance: "Reckless Abandon makes Unhinged use Bloodbath 335096 e1 while Recklessness is active.", gate: params.talent_picked(TALENT::UNHINGED), source: ap(EFFECT::BLOODBATH_DAMAGE), transform: |value| value },
79    unhinged_bloodbath_rage: f64 { provenance: "Unhinged Bloodbath callbacks energize from Bloodbath 335096 e2 with rage display scaling.", gate: params.talent_picked(TALENT::UNHINGED), source: base(EFFECT::BLOODBATH_RAGE), transform: |value| value / RAGE_DISPLAY_DIV },
80}
81accessor pub(super) fn fury_config(ctx);
82register pub(super) fn register_fury(built, params) -> Result<FuryConfig, wowlab_engine_ports::EngineError>;
83prepare {
84    let data = &params.game_data;
85    let base = |effect| data.effect_base_points(effect);
86    let ap = |effect| data.effect_ap_coefficient(effect);
87
88    let imminent_demise =
89        params.talent_picked(TALENT::IMMINENT_DEMISE) && params.talent_picked(TALENT::SUDDEN_DEATH);
90    let reap = params.talent_picked(TALENT::REAP_THE_STORM) && imminent_demise;
91}
92auras {
93    { provenance: "Rampaging Berserker rank two: 1269309 e1 / 10 changes Rampage's rage cost while Recklessness is active.", gate: params.talent_picked(TALENT::RAMPAGING_BERSERKER_2), aura: AURA_RECKLESSNESS, effect: BuffEffect::SpellCostFlat(base(EFFECT::RAMPAGING_BERSERKER_COST) / RAMPAGING_BERSERKER_COST_DIVISOR, &RAMPAGE_COST_SPELLS, ResourcePool::Primary) },
94    { provenance: "Rampaging Berserker rank two: 1269309 e2 increases Rampage damage while Recklessness is active.", gate: params.talent_picked(TALENT::RAMPAGING_BERSERKER_2), aura: AURA_RECKLESSNESS, effect: BuffEffect::DamageMultSpells(1.0 + base(EFFECT::RAMPAGING_BERSERKER_DMG) / HUNDRED, &RAMPAGE_COST_SPELLS) },
95}
96wiring {
97    { provenance: "MID2 Fury two-piece increases Raging Blow-family damage.", gate: params.set_bonus_auras.contains(&SET_BONUS::MID2_FURY_2PC), apply: {
98        built.register_passive_driver_effect(PassiveDriverEffect::new(
99            SET_BONUS::MID2_FURY_2PC,
100            BuffEffect::DamageMultSpells(1.0 + base(EFFECT::MID2_RAGING_BLOW_DAMAGE) / HUNDRED, &MID2_RAGING_BLOW_DAMAGE_SPELLS),
101        ));
102    } },
103    { provenance: "MID2 Fury four-piece increases Bloodthirst-family damage.", gate: params.set_bonus_auras.contains(&SET_BONUS::MID2_FURY_4PC), apply: {
104        built.register_passive_driver_effect(PassiveDriverEffect::new(
105            SET_BONUS::MID2_FURY_4PC,
106            BuffEffect::DamageMultSpells(1.0 + base(EFFECT::MID2_BLOODTHIRST_DAMAGE) / HUNDRED, &MID2_BLOODTHIRST_DAMAGE_SPELLS),
107        ));
108    } },
109    { provenance: "Sudden Death uses its DBC RPPM driver and proc-category recovery.", gate: params.talent_picked(TALENT::SUDDEN_DEATH), apply: {
110        built.bind_system_rppm(wowlab_engine_combat::generated_identities::system_rppm::SUDDEN_DEATH, TALENT::SUDDEN_DEATH)?;
111    } },
112    { provenance: "Focus in Chaos 383486 removes the dual-wield white-miss penalty while Enrage is active.", gate: params.talent_picked(FOCUS_IN_CHAOS_TALENT), apply: {
113        built.patch_auto_attacks(|attacks| for attack in attacks {
114            if !attack.is_pet {
115                attack.miss_chance_hook = Some(super::hooks::focus_in_chaos_miss_chance);
116            }
117        });
118    } },
119    { provenance: "Bloodbath applies its 113344 bleed on direct impact.", gate: true, apply: {
120        built.register_impact_proc(ImpactProc::new(bloodbath_dot_impact).with_spell_filter(bloodbath_impact_filter).skip_periodic());
121    } },
122    { provenance: "Cold Steel, Hot Blood applies Gushing Wound and energizes on Bloodthirst-family critical impacts.", gate: params.talent_picked(TALENT::COLD_STEEL_HOT_BLOOD), apply: {
123        built.register_impact_proc(ImpactProc::new(cold_steel_hot_blood_impact).with_spell_filter(bloodthirst_family_impact_filter).skip_periodic().crit_only());
124    } },
125    { provenance: "Improved Raging Blow charge adjustment.", gate: params.talent_picked(TALENT::IMPROVED_RAGING_BLOW), apply: {
126        built.patch_spell(SPELL_RAGING_BLOW, |spell| {
127            spell.cooldown.max_charges = IMPROVED_ATTACK_CHARGES;
128            if spell.cooldown.recharge_ms == 0 { spell.cooldown.recharge_ms = spell.cooldown.cooldown_duration_ms; }
129        });
130    } },
131    { provenance: "Improved Execute cost replacement and rage gain.", gate: params.talent_picked(TALENT::IMPROVED_EXECUTE), apply: {
132        let gain = base(EFFECT::IMPROVED_EXECUTE_RAGE) / RAGE_DISPLAY_DIV
133            + gated(params.talent_picked(TALENT::EXECUTIONERS_WRATH), || base(EFFECT::EXECUTIONERS_WRATH_RAGE) / RAGE_DISPLAY_DIV);
134        built.patch_spell(SPELL_EXECUTE, |spell| {
135            spell.cost.resource_cost = 0.0;
136            spell.cost.resource_gain = gain;
137        });
138    } },
139    { provenance: "Massacre Execute cooldown reduction.", gate: params.talent_picked(TALENT::MASSACRE), apply: {
140        let cdr = wowlab_types::numeric::f64_to_u32_saturating_trunc(base(EFFECT::MASSACRE_EXECUTE_CDR_MS));
141        built.patch_spell(SPELL_EXECUTE, |spell| {
142            spell.cooldown.cooldown_duration_ms = spell.cooldown.cooldown_duration_ms.saturating_sub(cdr);
143        });
144    } },
145    { provenance: "Reckless Abandon Recklessness rage gain.", gate: params.talent_picked(TALENT::RECKLESS_ABANDON), apply: {
146        built.patch_spell(SPELL_RECKLESSNESS, |spell| {
147            spell.cost.resource_gain = base(EFFECT::RECKLESS_ABANDON_RAGE) / RAGE_DISPLAY_DIV;
148        });
149    } },
150    { provenance: "Berserker Stance precombat aura.", gate: params.talent_picked(TALENT::BERSERKER_STANCE), apply: {
151        built.push_precombat_aura(AURA_BERSERKER_STANCE);
152    } },
153}
154finish |cfg| {
155    built.set_spec_config(cfg);
156    built.set_spec_runtime(FuryRuntime::default());
157    Ok(cfg)
158}
159}