wowlab_engine_content/hooks/havoc_demon_hunter/
config.rs1use wowlab_engine_combat::DamageOps as _;
2use wowlab_engine_combat::AuraOps as _;
3use wowlab_engine_combat::{
4 HookCtx, ImpactEvent, ImpactProc, PeriodicKind,
5};
6use wowlab_types::constants::HUNDRED;
7
8use crate::generated::specs::havoc_demon_hunter::{
9 AURA_BURNING_BLADES, AURA_SOULSCAR, AURA_THE_HUNT_DOT, EFFECT, HERO, REPORTED_SPELL,
10 SPELL_THE_HUNT, TALENT,
11};
12
13#[rustfmt::skip]
15static BURNING_BLADES_TRIGGERS: &[u32] = &[
16 REPORTED_SPELL::AUTO_ATTACK,
17 REPORTED_SPELL::CHAOS_STRIKE_DAMAGE_2,
18 REPORTED_SPELL::ANNIHILATION_DAMAGE_2,
19 REPORTED_SPELL::CHAOS_STRIKE_DAMAGE_1,
20 REPORTED_SPELL::ANNIHILATION_DAMAGE_1,
21 REPORTED_SPELL::THROW_GLAIVE_DAMAGE,
22 REPORTED_SPELL::BLADE_DANCE_FIRST_BLOOD,
23 REPORTED_SPELL::BLADE_DANCE_FIRST_BLOOD_4,
24 REPORTED_SPELL::THROW_GLAIVE_FURIOUS_THROWS,
25 REPORTED_SPELL::DEATH_SWEEP_FIRST_BLOOD_4,
26 REPORTED_SPELL::DEATH_SWEEP_FIRST_BLOOD,
27];
28
29fn burning_blades_trigger_filter(spell_id: u32) -> bool {
30 BURNING_BLADES_TRIGGERS.contains(&spell_id)
31}
32
33fn burning_blades_impact(
34 ctx: &mut HookCtx<'_>,
35 impact: ImpactEvent,
36) {
37 let fraction = havoc_config(ctx).burning_blades_fraction;
38
39 ctx.add_residual_damage(AURA_BURNING_BLADES.raw(), impact.amount * fraction);
40}
41
42#[rustfmt::skip]
43static SOULSCAR_TRIGGERS: &[u32] = &[
44 REPORTED_SPELL::THROW_GLAIVE_DAMAGE,
45 REPORTED_SPELL::THROW_GLAIVE_FURIOUS_THROWS,
46];
47
48fn soulscar_trigger_filter(spell_id: u32) -> bool {
49 SOULSCAR_TRIGGERS.contains(&spell_id)
50}
51
52fn soulscar_impact(ctx: &mut HookCtx<'_>, impact: ImpactEvent) {
53 let fraction = havoc_config(ctx).soulscar_fraction;
54 let mastery_bonus = 1.0 + ctx.effect_points(EFFECT::MASTERY_PERIODIC_BONUS) / HUNDRED;
58
59 ctx.add_residual_damage(AURA_SOULSCAR.raw(), impact.amount * fraction * mastery_bonus);
60}
61
62const FURY_POWER_DIVISOR: f64 = 10.0;
64const CHANNEL_TICKS_PER_SECOND: f64 = 5.0;
66
67bitflags::bitflags! {
68 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
69 pub(crate) struct DemonsurgeAbilities: u8 {
70 const ABYSSAL_GAZE = 1 << 0;
71 const ANNIHILATION = 1 << 1;
72 const DEATH_SWEEP = 1 << 2;
73 const CONSUMING_FIRE = 1 << 3;
74
75 const ALL_HARDCAST = Self::ABYSSAL_GAZE.bits()
76 | Self::ANNIHILATION.bits()
77 | Self::DEATH_SWEEP.bits()
78 | Self::CONSUMING_FIRE.bits();
79 const DEMONIC = Self::ANNIHILATION.bits()
80 | Self::DEATH_SWEEP.bits();
81 }
82}
83
84#[derive(Clone, Copy, Debug, Default)]
85pub(crate) struct HavocRuntime {
86 pub(crate) demonsurge_available: DemonsurgeAbilities,
87 pub(crate) demonsurge_stacks: u8,
88}
89
90crate::hooks::define_spec_config! {
91pub(super) struct HavocConfig {
92 first_blood: bool { provenance: "First Blood 206416 selection.", gate: params.talent_ranks(TALENT::FIRST_BLOOD) > 0, source: true, transform: |value| value },
93 burning_blades_fraction: f64 { provenance: "Burning Blades 452408 e1 residual fraction with Havoc aura scaling.", gate: burning_blades, source: params.effect_base_points(EFFECT::BURNING_BLADES_FRACTION) * (1.0 + params.effect_base_points(EFFECT::HAVOC_AURA_DIRECT) / HUNDRED), transform: |value| value / HUNDRED },
94 serrated_glaive: bool { provenance: "Serrated Glaive 390154 selection.", gate: serrated, source: true, transform: |value| value },
95 furious_throws: bool { provenance: "Furious Throws 393029 selection.", gate: params.talent_ranks(TALENT::FURIOUS_THROWS) > 0, source: true, transform: |value| value },
96 screaming_brutality: bool { provenance: "Screaming Brutality 1220506 selection.", gate: screaming, source: true, transform: |value| value },
97 sb_slash_pct: f64 { provenance: "Screaming Brutality 1220506 e1 slash multiplier.", gate: screaming, source: params.effect_base_points(EFFECT::SB_SLASH_PCT), transform: |value| value / HUNDRED },
98 sb_slash_chance: f64 { provenance: "Screaming Brutality 1220506 e2 slash chance.", gate: screaming, source: params.effect_base_points(EFFECT::SB_SLASH_CHANCE), transform: |value| value / HUNDRED },
99 sb_full_pct: f64 { provenance: "Screaming Brutality 1220506 e3 full multiplier.", gate: screaming, source: params.effect_base_points(EFFECT::SB_FULL_PCT), transform: |value| value / HUNDRED },
100 soulscar_fraction: f64 { provenance: "Soulscar 388106 e1 residual fraction.", gate: soulscar, source: params.effect_base_points(EFFECT::SOULSCAR_FRACTION), transform: |value| value / HUNDRED },
101 demonic: bool { provenance: "Demonic 213410 selection.", gate: demonic, source: true, transform: |value| value },
102 demonic_extension_ms: u32 { provenance: "Demonic 213410 e1 extension milliseconds.", gate: demonic, source: params.effect_base_points(EFFECT::DEMONIC_EXTENSION), transform: wowlab_types::numeric::f64_to_u32_saturating_trunc },
103 chaotic_transformation: bool { provenance: "Chaotic Transformation 388112 selection.", gate: params.talent_ranks(TALENT::CHAOTIC_TRANSFORMATION) > 0, source: true, transform: |value| value },
104 cycle_of_hatred: bool { provenance: "Cycle of Hatred 258887 selection.", gate: cycle, source: true, transform: |value| value },
105 cycle_of_hatred_cdr_ms: u32 { provenance: "Cycle of Hatred 258887 e1 CDR milliseconds.", gate: cycle, source: params.effect_base_points(EFFECT::CYCLE_OF_HATRED_CDR), transform: wowlab_types::numeric::f64_to_u32_saturating_trunc },
106 blind_fury_per_tick: f64 { provenance: "Blind Fury 203550 e3 fury divided by power scale and channel ticks.", gate: params.talent_ranks(TALENT::BLIND_FURY) > 0, source: params.effect_base_points(EFFECT::BLIND_FURY_FURY), transform: |value| value / FURY_POWER_DIVISOR / CHANNEL_TICKS_PER_SECOND },
107 furious_gaze: bool { provenance: "Furious Gaze 343311 selection.", gate: params.talent_ranks(TALENT::FURIOUS_GAZE) > 0, source: true, transform: |value| value },
108 student_of_suffering: bool { provenance: "Student of Suffering 452412 selection.", gate: params.talent_ranks(HERO::FEL_SCARRED::SPELL::STUDENT_OF_SUFFERING) > 0, source: true, transform: |value| value },
109 demonic_intensity: bool { provenance: "Demonic Intensity 452415 selection.", gate: params.talent_ranks(HERO::FEL_SCARRED::SPELL::DEMONIC_INTENSITY) > 0, source: true, transform: |value| value },
110 eternal_hunt: bool { provenance: "Eternal Hunt 1270898 selection.", gate: params.talent_ranks(TALENT::ETERNAL_HUNT) > 0, source: true, transform: |value| value },
111 demonsurge: bool { provenance: "Demonsurge 452402 selection.", gate: demonsurge, source: true, transform: |value| value },
112 demonsurge_stack_pct: f64 { provenance: "Demonsurge 452416 e2 per-stack increase.", gate: demonsurge, source: params.effect_base_points(EFFECT::DEMONSURGE_STACK_PCT), transform: |value| value / HUNDRED },
113}
114accessor pub(crate) fn havoc_config(ctx);
115register pub(crate) fn register_havoc(built, params) -> HavocConfig;
116prepare {
117 let serrated = params.talent_ranks(TALENT::SERRATED_GLAIVE) > 0;
118 let screaming = params.talent_ranks(TALENT::SCREAMING_BRUTALITY) > 0;
119 let demonic = params.talent_ranks(TALENT::DEMONIC) > 0;
120 let cycle = params.talent_ranks(TALENT::CYCLE_OF_HATRED) > 0;
121 let demonsurge = params.talent_ranks(HERO::FEL_SCARRED::SPELL::DEMONSURGE) > 0;
122 let soulscar = params.talent_ranks(TALENT::SOULSCAR) > 0;
123 let burning_blades = params.talent_ranks(HERO::FEL_SCARRED::SPELL::BURNING_BLADES) > 0;
124}
125auras {}
126wiring {
127 { provenance: "Eternal Hunt direct, periodic, and cooldown modifiers.", gate: params.talent_ranks(TALENT::ETERNAL_HUNT_RANK_2) > 0, apply: {
128 let direct_mult = 1.0 + params.effect_base_points(EFFECT::ETERNAL_HUNT_DAMAGE_PCT) / HUNDRED;
129 let periodic_mult = 1.0 + params.effect_base_points(EFFECT::ETERNAL_HUNT_PERIODIC_PCT) / HUNDRED;
130 built.patch_spell(SPELL_THE_HUNT, |spell| {
131 spell.damage = spell.damage.scaled(direct_mult);
132 let cooldown_delta = params.effect_base_points(EFFECT::ETERNAL_HUNT_COOLDOWN_MS);
134 let adjusted = (f64::from(spell.cooldown.cooldown_duration_ms) + cooldown_delta).max(0.0);
135 spell.cooldown.cooldown_duration_ms = wowlab_types::numeric::f64_to_u32_saturating_trunc(adjusted);
136 });
137 built.patch_aura(AURA_THE_HUNT_DOT, |aura| {
138 if let Some(periodic) = aura.periodic.as_mut() {
139 periodic.effect = match periodic.effect {
140 PeriodicKind::DamageAp { coef, is_physical } => PeriodicKind::DamageAp {
141 coef: coef * periodic_mult,
142 is_physical,
143 },
144 other => other,
145 };
146 }
147 });
148 } },
149 { provenance: "Soulscar Throw Glaive residual proc.", gate: soulscar, apply: {
150 built.register_impact_proc(
151 ImpactProc::new(soulscar_impact)
152 .with_spell_filter(soulscar_trigger_filter)
153 .skip_periodic(),
154 );
155 } },
156 { provenance: "Burning Blades residual proc.", gate: burning_blades, apply: {
157 built.register_impact_proc(
158 ImpactProc::new(burning_blades_impact)
159 .with_spell_filter(burning_blades_trigger_filter)
160 .skip_periodic(),
161 );
162 } },
163}
164finish |cfg| {
165 built.state.set_spec_config(cfg);
166 built.state.set_spec_runtime(HavocRuntime::default());
167 cfg
168}
169}