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