Skip to main content

wowlab_engine_combat/handler/orchestration/
raid_events.rs

1use wowlab_engine_ports::{Event, PullLifecycleGeneration, SimContext};
2use wowlab_types::{
3    constants::MS_PER_SECOND,
4    sim::{ActorId, EnemyIdx, SimTime},
5};
6
7use super::super::CombatHandler;
8use crate::context::HookCtxRequest;
9
10impl CombatHandler {
11    pub(in crate::handler) fn handle_raid_event(&mut self, index: usize, ctx: &mut SimContext) {
12        let Some(config) = self.program.config.raid_events.get(index).copied() else {
13            return;
14        };
15        let now = ctx.state.current_time;
16        let duration = SimTime::from_secs_f64(config.schedule.duration.max(0.0));
17
18        match config.event {
19            wowlab_engine_ports::RaidEventKind::Movement => {
20                self.instance.schedule_movement_window(
21                    &self.program,
22                    ActorId::Player,
23                    now,
24                    duration,
25                );
26            }
27            wowlab_engine_ports::RaidEventKind::Stun => {
28                let _ = self.instance.apply_crowd_control(
29                    ActorId::Player,
30                    crate::state::CrowdControlKind::Stun,
31                    crate::state::DiminishingGroup::None,
32                    duration,
33                    now,
34                );
35            }
36            wowlab_engine_ports::RaidEventKind::PlayerDamage { amount } => {
37                let _ = crate::systems::deal_incoming_damage(
38                    &self.program,
39                    &mut self.instance,
40                    crate::systems::IncomingDamage {
41                        source: ActorId::External,
42                        target: ActorId::Player,
43                        amount,
44                        school: wowlab_types::combat::DamageSchool::Physical,
45                        mechanic_mask: 0,
46                        is_aoe: true,
47                        is_periodic: false,
48                        at: now,
49                    },
50                );
51            }
52            wowlab_engine_ports::RaidEventKind::Invulnerable { target } => {
53                if let Ok(target) = u16::try_from(target) {
54                    self.instance.grant_enemy_invulnerability(
55                        EnemyIdx(target),
56                        now.saturating_add(duration),
57                    );
58                }
59            }
60            wowlab_engine_ports::RaidEventKind::Adds { target } => {
61                self.handle_raid_add_event(target, duration, ctx);
62            }
63            wowlab_engine_ports::RaidEventKind::EnemyCasting {
64                target,
65                spell_id,
66                school_mask,
67            } => {
68                if let Ok(target) = u16::try_from(target) {
69                    self.instance.begin_actor_cast(
70                        ActorId::Enemy(EnemyIdx(target)),
71                        crate::state::ActiveCast::new(
72                            wowlab_types::sim::SpellIdx::from_raw(spell_id),
73                            wowlab_engine_domain::dbc::SpellSchoolMask::from_dbc(school_mask),
74                            now.saturating_add(duration),
75                        ),
76                    );
77                }
78            }
79            wowlab_engine_ports::RaidEventKind::Interrupt { lockout_ms } => {
80                let _ = crate::systems::interrupt_cast(
81                    &self.program,
82                    &mut self.instance,
83                    ActorId::Player,
84                    now,
85                    lockout_ms,
86                );
87            }
88            _ => {}
89        }
90    }
91
92    pub(in crate::handler) fn handle_external_buff(&mut self, index: usize, ctx: &mut SimContext) {
93        let Some(config) = self.program.config.external_buffs.get(index).copied() else {
94            return;
95        };
96        let Some(local) = self
97            .program
98            .external_buff_auras
99            .get(index)
100            .copied()
101            .flatten()
102        else {
103            return;
104        };
105        let duration_ms = wowlab_types::numeric::f64_to_u32_saturating_round(
106            config.schedule.duration.max(0.0) * MS_PER_SECOND,
107        );
108
109        self.with_hook_ctx(
110            &mut *ctx.telemetry,
111            HookCtxRequest::targetless(ctx.state.current_time).with_source(ActorId::External),
112            |hook| crate::systems::apply_aura_with_duration(hook, local, Some(duration_ms)),
113        );
114    }
115
116    pub(in crate::handler) fn handle_raid_add_despawn(
117        &mut self,
118        target: EnemyIdx,
119        activation_generation: u64,
120        encounter_generation: PullLifecycleGeneration,
121        ctx: &mut SimContext,
122    ) {
123        if !self
124            .instance
125            .runtime
126            .encounter
127            .is_current_generation(encounter_generation)
128            || !self.instance.enemy(target).is_some_and(|enemy| {
129                enemy.is_active() && enemy.activation_generation() == activation_generation
130            })
131        {
132            return;
133        }
134
135        let _ = self.on_despawn_transition(target, ctx);
136    }
137
138    pub(super) fn handle_raid_add_event(
139        &mut self,
140        raw_target: usize,
141        duration: SimTime,
142        ctx: &mut SimContext,
143    ) {
144        let Ok(target) = u16::try_from(raw_target) else {
145            return;
146        };
147
148        let target = EnemyIdx(target);
149
150        let previous_generation = self
151            .instance
152            .enemy(target)
153            .map(wowlab_engine_domain::encounter::EnemyState::activation_generation);
154
155        self.handle_enemy_activation(
156            self.instance.runtime.encounter.active_pull(),
157            self.instance.runtime.encounter.pull_epoch(),
158            target,
159            ctx,
160        );
161
162        if duration <= SimTime::ZERO {
163            return;
164        }
165
166        let Some(activation_generation) = self
167            .instance
168            .enemy(target)
169            .filter(|enemy| enemy.is_active())
170            .map(wowlab_engine_domain::encounter::EnemyState::activation_generation)
171            .filter(|generation| Some(*generation) != previous_generation)
172        else {
173            return;
174        };
175
176        self.instance.schedule(Event::RaidAddDespawn {
177            t: ctx.state.current_time.saturating_add(duration),
178            add: self.program.runtime_events.raid_add(
179                target,
180                activation_generation,
181                self.instance.runtime.encounter.generation(),
182            ),
183        });
184    }
185}