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(crate::generated::specs::unholy_death_knight::DAMAGE_EFFECT::COIL_OF_DEVASTATION_PCT);
41
42    ctx.add_residual_damage(AURA_COIL_OF_DEVASTATION, 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);
76    } else if roll < FAMINE_THRESHOLD {
77        ctx.apply_owner_aura(AURA_APOCALYPSE_FAMINE);
78    } else if roll < PESTILENCE_THRESHOLD {
79        ctx.apply_owner_aura(AURA_APOCALYPSE_PESTILENCE);
80    } else {
81        ctx.apply_owner_aura(AURA_APOCALYPSE_WAR);
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 parts = parts;
96        let built = parts.draft_mut();
97
98        if !built.set_pet_action_bar(PERMANENT_GHOUL_NPC_ID, PERMANENT_GHOUL_ACTION_BAR) {
99            return Err(wowlab_engine_ports::EngineError::spec_construction(
100                "unholy permanent-ghoul auto attack is missing",
101            ));
102        }
103
104        built.register_player_cast_hook(resource_spend);
105
106        built.register_impact_proc(
107            ImpactProc::new(sudden_doom_fire)
108                .with_spell_filter(dread_plague_filter)
109                .periodic_only(),
110        );
111        built.register_impact_proc(
112            ImpactProc::new(forbidden_knowledge_fire)
113                .with_spell_filter(dread_plague_filter)
114                .periodic_only(),
115        );
116        built.register_impact_proc(
117            ImpactProc::new(trigger_coil_of_devastation)
118                .with_spell_filter(coil_of_devastation_filter)
119                .skip_periodic(),
120        );
121        built.register_impact_proc(
122            ImpactProc::new(trigger_rune_of_the_apocalypse)
123                .with_actor_filter(ImpactActorFilter::Any)
124                .with_spell_filter(apocalypse_impact_filter)
125                .skip_periodic(),
126        );
127        let config = unholy_config(params)?;
128        let runtime = UnholyRuntime {
129            riders: config.riders.map(|_| RiderRuntime::default()),
130            active_army_ghouls: 0,
131        };
132
133        built.set_spec_config(config);
134        built.set_spec_runtime(runtime);
135
136        let built = parts.finalize()?;
137
138        Ok(Self {
139            inner: CombatHandler::from_built(built),
140        })
141    }
142}
143
144crate::hooks::define_spec_handler! {
145impl UnholyDkHandler(self, inner) {
146    fn on_sim_start(&mut self, ctx: &mut SimContext) {
147        self.inner.on_sim_start(ctx);
148    }
149
150    fn on_player_ready(&mut self, ctx: &mut SimContext) -> Option<SpecAction> {
151        self.inner.on_player_ready(ctx)
152    }
153
154    fn on_cast_complete(&mut self, event: wowlab_engine_ports::Event, ctx: &mut SimContext) {
155        self.inner.on_cast_complete(event, ctx);
156    }
157
158    fn on_spell_impact(&mut self, impact_id: wowlab_engine_ports::PendingImpactBinding, ctx: &mut SimContext) {
159        self.inner.on_spell_impact(impact_id, ctx);
160    }
161
162}
163delegate {
164            fn on_aura_tick(&mut self, event: wowlab_engine_ports::AuraEventRef, ctx: &mut SimContext);
165            fn on_aura_expire(&mut self, event: wowlab_engine_ports::AuraEventRef, ctx: &mut SimContext);
166            fn on_auto_attack(&mut self, source: wowlab_types::sim::ActorId, target: wowlab_types::sim::EnemyIdx, ctx: &mut SimContext);
167            fn on_cooldown_ready(&mut self, spell: wowlab_engine_ports::ScheduledSpellBinding, ctx: &mut SimContext);
168            fn flush_scheduled(&mut self, push: &mut dyn FnMut(wowlab_engine_ports::Event));
169            fn reset(&mut self);
170            fn total_damage(&self) -> f64;
171            fn cast_time_ms(&self, spell: wowlab_engine_ports::ScheduledSpellBinding, empower_rank: u8) -> u32;
172            fn introspect(&self) -> wowlab_types::game::SpecIntrospection;
173            fn paperdoll(&self) -> Option<wowlab_engine_ports::Paperdoll>;
174            fn attach_decision_trace(
175                &mut self,
176                sink: std::sync::Arc<dyn wowlab_engine_ports::DecisionTraceSink>,
177            );
178}
179}