Skip to main content

wowlab_engine_content/hooks/affliction_warlock/
handler.rs

1use wowlab_engine_combat::{
2    CombatHandler, DriverSpellModifier, DriverSpellModifierResult, ResourceGainProc,
3};
4use wowlab_engine_ports::{EngineError, HandlerParams};
5
6use crate::generated::specs::affliction_warlock::{AURA_HAUNT, SET_BONUS, SPELL, TALENT};
7
8static MID2_CORRUPTION_SPELLS: [u32; 3] = [SPELL::CORRUPTION, 146_739, 445_474];
9static MID2_AGONY_SPELLS: [u32; 1] = [SPELL::AGONY];
10static IMPETUOUS_WRATH_SPELLS: [u32; 3] =
11    [SPELL::SHADOW_BOLT, SPELL::DRAIN_SOUL, SPELL::MALEFIC_GRASP];
12static IMPETUOUS_WRATH_DARK_HARVEST: [u32; 1] = [SPELL::DARK_HARVEST];
13
14use super::{
15    config::{affliction_config, AfflictionRuntime},
16    hooks::succulent_soul_proc,
17};
18
19const SUCCULENT_SOUL_RATE: f64 = 0.225;
20/// Affliction handler that installs gain-driven Soul Harvester mechanics.
21#[derive(Debug)]
22pub(crate) struct AfflictionHandler {
23    inner: CombatHandler,
24}
25
26impl AfflictionHandler {
27    /// Finishes the specialization handler from the shared-content composition stage.
28    pub(crate) fn try_new(
29        parts: crate::composition::ContentHandlerParts,
30        params: &HandlerParams<'_>,
31    ) -> Result<Self, EngineError> {
32        let mut parts = parts;
33        let built = parts.draft_mut();
34        let config = affliction_config(params);
35
36        if config.mid2_2pc {
37            built.register_driver_spell_modifier(DriverSpellModifier::new(
38                SET_BONUS::MID2_AFFLICTION_2PC,
39                &MID2_CORRUPTION_SPELLS,
40                mid2_corruption_modifier,
41            ));
42            built.register_driver_spell_modifier(DriverSpellModifier::new(
43                SET_BONUS::MID2_AFFLICTION_2PC,
44                &MID2_AGONY_SPELLS,
45                mid2_agony_modifier,
46            ));
47        }
48
49        if params.talent_picked(TALENT::IMPETUOUS_WRATH) {
50            built.register_driver_spell_modifier(DriverSpellModifier::new(
51                TALENT::IMPETUOUS_WRATH,
52                &IMPETUOUS_WRATH_SPELLS,
53                impetuous_wrath_modifier,
54            ));
55            built.register_driver_spell_modifier(DriverSpellModifier::new(
56                TALENT::IMPETUOUS_WRATH,
57                &IMPETUOUS_WRATH_DARK_HARVEST,
58                impetuous_wrath_dark_harvest_modifier,
59            ));
60        }
61
62        if params.talent_picked(TALENT::DEMONIC_SOUL) {
63            built.register_resource_gain_proc(
64                ResourceGainProc::prd(SUCCULENT_SOUL_RATE, succulent_soul_proc).per_whole_unit(),
65            );
66        }
67
68        if params.talent_picked(TALENT::SHADOW_OF_NATHREZA_3) {
69            built.bind_system_rppm(
70                wowlab_engine_combat::generated_identities::system_rppm::SHADOW_OF_NATHREZA,
71                TALENT::SHADOW_OF_NATHREZA_3,
72            )?;
73        }
74
75        if let Some(local) = built.spell_local(SPELL::CORRUPTION) {
76            built.patch_spell(local, |spell| {
77                // Metamorphosis is unavailable to Affliction; its legacy exclusion is inert.
78                spell.requirements.exclude_caster_aura_spell = 0;
79            });
80        }
81
82        built.set_spec_config(config);
83        built.set_spec_runtime(AfflictionRuntime::default());
84
85        let built = parts.finalize()?;
86
87        Ok(Self {
88            inner: CombatHandler::from_built(built),
89        })
90    }
91}
92
93fn mid2_corruption_modifier(view: wowlab_engine_combat::HookView<'_>) -> DriverSpellModifierResult {
94    DriverSpellModifierResult::damage_mult(
95        view.program()
96            .spec_config::<super::config::AfflictionConfig>()
97            .map_or(1.0, |cfg| cfg.mid2_corruption_mult),
98    )
99}
100
101fn mid2_agony_modifier(view: wowlab_engine_combat::HookView<'_>) -> DriverSpellModifierResult {
102    DriverSpellModifierResult::damage_mult(
103        view.program()
104            .spec_config::<super::config::AfflictionConfig>()
105            .map_or(1.0, |cfg| cfg.mid2_agony_mult),
106    )
107}
108
109fn impetuous_wrath_modifier(view: wowlab_engine_combat::HookView<'_>) -> DriverSpellModifierResult {
110    let Some(cfg) = view
111        .program()
112        .spec_config::<super::config::AfflictionConfig>()
113    else {
114        return DriverSpellModifierResult::default();
115    };
116    let haunted = wowlab_engine_combat::is_aura_active(
117        &view.combat(),
118        AURA_HAUNT,
119        wowlab_types::sim::ActorId::Player,
120        view.target,
121    );
122
123    DriverSpellModifierResult::damage_mult(if haunted {
124        cfg.impetuous_haunt_mult
125    } else {
126        cfg.impetuous_normal_mult
127    })
128}
129
130fn impetuous_wrath_dark_harvest_modifier(
131    view: wowlab_engine_combat::HookView<'_>,
132) -> DriverSpellModifierResult {
133    let Some(cfg) = view
134        .program()
135        .spec_config::<super::config::AfflictionConfig>()
136    else {
137        return DriverSpellModifierResult::default();
138    };
139    let haunted = wowlab_engine_combat::is_aura_active(
140        &view.combat(),
141        AURA_HAUNT,
142        wowlab_types::sim::ActorId::Player,
143        view.target,
144    );
145
146    DriverSpellModifierResult::damage_mult(if haunted {
147        cfg.impetuous_dark_harvest_haunt_mult
148    } else {
149        cfg.impetuous_dark_harvest_mult
150    })
151}
152
153crate::hooks::define_spec_handler! {
154impl AfflictionHandler(self, inner) {
155    fn on_sim_start(&mut self, ctx: &mut wowlab_engine_ports::SimContext) {
156        self.inner.on_sim_start(ctx);
157    }
158}
159}