wowlab_engine_combat/handler/orchestration/
raid_events.rs1use 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}