Skip to main content

wowlab_engine_content/hooks/unholy_death_knight/
handler.rs

1use wowlab_engine_combat::DamageOps as _;
2use wowlab_engine_combat::AuraOps as _;
3use wowlab_engine_combat::{
4    ImpactActorFilter, ImpactProc,
5    CombatHandler,
6};
7use wowlab_engine_ports::{HandlerParams, SimContext, SpecAction};
8
9
10use super::{
11    config::{RiderRuntime, UnholyConfig, UnholyRuntime, unholy_config},
12    hooks::{
13        forbidden_knowledge_fire, sudden_doom_fire, PERMANENT_GHOUL_ACTION_BAR,
14        PERMANENT_GHOUL_NPC_ID,
15    },
16    riders::resource_spend,
17};
18use crate::generated::specs::unholy_death_knight::{
19    AURA, AURA_APOCALYPSE_DEATH, AURA_APOCALYPSE_FAMINE, AURA_APOCALYPSE_PESTILENCE,
20    AURA_APOCALYPSE_WAR, AURA_COIL_OF_DEVASTATION, EFFECT, HERO,
21};
22
23const fn dread_plague_filter(spell_id: u32) -> bool {
24    spell_id == AURA::DREAD_PLAGUE
25}
26
27const fn coil_of_devastation_filter(spell_id: u32) -> bool {
28    spell_id == EFFECT::DC_DAMAGE.0
29        || matches!(
30            spell_id,
31            HERO::UNHOLY_PETS::SPELL::NECROTIC_COIL
32                | HERO::UNHOLY_PETS::SPELL::NECROTIC_COIL_SHADOWSTRIKE
33        )
34}
35
36fn trigger_coil_of_devastation(
37    ctx: &mut wowlab_engine_combat::HookCtx<'_>,
38    event: wowlab_engine_combat::ImpactEvent,
39) {
40    let fraction = ctx.effect_percent(EFFECT::COIL_OF_DEVASTATION_PCT);
41
42    ctx.add_residual_damage(AURA_COIL_OF_DEVASTATION.raw(), event.amount * fraction);
43}
44
45const fn apocalypse_impact_filter(spell_id: u32) -> bool {
46    matches!(
47        spell_id,
48        HERO::UNHOLY_PETS::SPELL::CLAW
49            | HERO::UNHOLY_PETS::SPELL::SWEEPING_CLAWS
50            | HERO::UNHOLY_PETS::SPELL::LESSER_CLAW
51            | HERO::UNHOLY_PETS::SPELL::LESSER_SWEEPING_CLAWS
52            | HERO::UNHOLY_PETS::SPELL::RUPTURED_VISCERA
53            | HERO::UNHOLY_PETS::SPELL::PUTREFY_AOE
54    )
55}
56
57fn trigger_rune_of_the_apocalypse(
58    ctx: &mut wowlab_engine_combat::HookCtx<'_>,
59    _event: wowlab_engine_combat::ImpactEvent,
60) {
61    const DEATH_THRESHOLD: f64 = 0.25;
62    const FAMINE_THRESHOLD: f64 = 0.5;
63    const PESTILENCE_THRESHOLD: f64 = 0.75;
64
65    if !ctx
66        .spec_config::<UnholyConfig>()
67        .is_some_and(|config| config.apocalypse_runeforge)
68    {
69        return;
70    }
71
72    let roll = ctx.rng()();
73
74    if roll < DEATH_THRESHOLD {
75        ctx.apply_owner_aura(AURA_APOCALYPSE_DEATH.raw());
76    } else if roll < FAMINE_THRESHOLD {
77        ctx.apply_owner_aura(AURA_APOCALYPSE_FAMINE.raw());
78    } else if roll < PESTILENCE_THRESHOLD {
79        ctx.apply_owner_aura(AURA_APOCALYPSE_PESTILENCE.raw());
80    } else {
81        ctx.apply_owner_aura(AURA_APOCALYPSE_WAR.raw());
82    }
83}
84
85#[derive(Debug)]
86pub(crate) struct UnholyDkHandler {
87    inner: CombatHandler,
88}
89
90impl UnholyDkHandler {
91    pub(crate) fn try_new(
92        parts: crate::composition::ContentHandlerParts,
93        params: &HandlerParams<'_>,
94    ) -> Result<Self, wowlab_engine_ports::EngineError> {
95        let mut built = parts.into_built();
96
97        if !built.set_pet_action_bar(PERMANENT_GHOUL_NPC_ID, PERMANENT_GHOUL_ACTION_BAR) {
98            return Err(wowlab_engine_ports::EngineError::spec_construction(
99                "unholy permanent-ghoul auto attack is missing",
100            ));
101        }
102
103        built.register_player_cast_hook(resource_spend);
104
105        built.register_impact_proc(
106            ImpactProc::new(sudden_doom_fire)
107                .with_spell_filter(dread_plague_filter)
108                .periodic_only(),
109        );
110        built.register_impact_proc(
111            ImpactProc::new(forbidden_knowledge_fire)
112                .with_spell_filter(dread_plague_filter)
113                .periodic_only(),
114        );
115        built.register_impact_proc(
116            ImpactProc::new(trigger_coil_of_devastation)
117                .with_spell_filter(coil_of_devastation_filter)
118                .skip_periodic(),
119        );
120        built.register_impact_proc(
121            ImpactProc::new(trigger_rune_of_the_apocalypse)
122                .with_actor_filter(ImpactActorFilter::Any)
123                .with_spell_filter(apocalypse_impact_filter)
124                .skip_periodic(),
125        );
126        let config = unholy_config(params)?;
127        let runtime = UnholyRuntime {
128            riders: config.riders.map(|_| RiderRuntime::default()),
129        };
130
131        built.state.set_spec_config(config);
132        built.state.set_spec_runtime(runtime);
133
134        Ok(Self {
135            inner: CombatHandler::from_built(built),
136        })
137    }
138}
139
140crate::hooks::define_spec_handler! {
141impl UnholyDkHandler(self, inner) {
142    fn on_sim_start(&mut self, ctx: &mut SimContext) {
143        self.inner.on_sim_start(ctx);
144    }
145
146    fn on_player_ready(&mut self, ctx: &mut SimContext) -> Option<SpecAction> {
147        self.inner.on_player_ready(ctx)
148    }
149
150    fn on_cast_complete(&mut self, event: wowlab_engine_ports::Event, ctx: &mut SimContext) {
151        self.inner.on_cast_complete(event, ctx);
152    }
153
154    fn on_spell_impact(&mut self, impact_id: u32, ctx: &mut SimContext) {
155        self.inner.on_spell_impact(impact_id, ctx);
156    }
157
158}
159delegate {
160            fn on_aura_tick(&mut self, event: wowlab_engine_ports::AuraEventRef, ctx: &mut SimContext);
161            fn on_aura_expire(&mut self, event: wowlab_engine_ports::AuraEventRef, ctx: &mut SimContext);
162            fn on_auto_attack(&mut self, source: wowlab_types::sim::ActorId, target: wowlab_types::sim::EnemyIdx, ctx: &mut SimContext);
163            fn on_cooldown_ready(&mut self, cooldown_key: wowlab_types::sim::SpellIdx, ctx: &mut SimContext);
164            fn flush_scheduled(&mut self, push: &mut dyn FnMut(wowlab_engine_ports::Event));
165            fn reset(&mut self);
166            fn total_damage(&self) -> f64;
167            fn cast_time_ms(&self, spell_id: wowlab_types::sim::SpellIdx, empower_rank: u8) -> u32;
168            fn introspect(&self) -> wowlab_types::game::SpecIntrospection;
169            fn paperdoll(&self) -> Option<wowlab_engine_ports::Paperdoll>;
170            fn attach_decision_trace(
171                &mut self,
172                sink: std::sync::Arc<dyn wowlab_engine_ports::DecisionTraceSink>,
173            );
174}
175}