wowlab_engine_combat/handler/spec_handler/
encounter.rs1use super::{
2 ActorId, CombatHandler, EncounterDespawnKind, EncounterTelemetryEventKind,
3 EncounterTerminationReason, EnemyDespawnOutcome, EnemyIdx, FIRST_ENEMY_SWING_DIVISOR, GroupId,
4 HookCtxRequest, PositionedActorRef, PullScope, SimContext, SimTime, SpatialTransform, WaveId,
5};
6
7impl CombatHandler {
8 pub(super) fn on_death_impl(
9 &mut self,
10 target: EnemyIdx,
11 scope: wowlab_engine_ports::DeathEventScope,
12 ctx: &mut SimContext,
13 ) {
14 let now = ctx.state.current_time;
15
16 self.handle_encounter_death(target, scope, now, &mut *ctx.telemetry);
17 }
18
19 pub(super) fn on_despawn_impl(
20 &mut self,
21 target: EnemyIdx,
22 ctx: &mut SimContext,
23 ) -> EnemyDespawnOutcome {
24 let now = ctx.state.current_time;
25 let outcome = self.instance.transition_enemy_despawn(target, now);
26
27 if outcome == EnemyDespawnOutcome::Despawned {
28 self.emit_enemy_lifecycle(
29 &mut *ctx.telemetry,
30 EncounterTelemetryEventKind::Despawn,
31 target,
32 now,
33 self.instance
34 .enemy(target)
35 .map(wowlab_engine_domain::encounter::EnemyState::current_transform),
36 );
37 self.cleanup_inactive_enemy(target, now, &mut *ctx.telemetry);
38 }
39
40 outcome
41 }
42
43 pub(super) fn on_encounter_despawn_impl(
44 &mut self,
45 scope: PullScope,
46 target: EnemyIdx,
47 kind: EncounterDespawnKind,
48 ctx: &mut SimContext,
49 ) {
50 let PullScope { pull, epoch } = scope;
51
52 self.handle_encounter_despawn(pull, epoch, target, kind, ctx);
53 }
54
55 pub(super) fn on_group_complete_impl(
56 &mut self,
57 scope: PullScope,
58 group: GroupId,
59 ctx: &mut SimContext,
60 ) {
61 let PullScope { pull, epoch } = scope;
62
63 self.handle_group_completion(pull, epoch, group, ctx.state.current_time);
64 }
65
66 pub(super) fn on_pull_complete_impl(
67 &mut self,
68 scope: PullScope,
69 finalize: bool,
70 ctx: &mut SimContext,
71 ) {
72 let PullScope { pull, epoch } = scope;
73
74 self.handle_pull_completion(pull, epoch, finalize, ctx);
75 }
76
77 pub(super) fn on_encounter_terminate_impl(
78 &mut self,
79 _reason: EncounterTerminationReason,
80 ctx: &mut SimContext,
81 ) {
82 self.mark_encounter_terminated(ctx.state.current_time);
83 }
84
85 pub(super) fn on_wave_activate_impl(
86 &mut self,
87 scope: PullScope,
88 wave: WaveId,
89 ctx: &mut SimContext,
90 ) {
91 let PullScope { pull, epoch } = scope;
92
93 self.handle_wave_activation(pull, epoch, wave, ctx.state.current_time);
94 }
95
96 pub(super) fn on_enemy_activate_impl(
97 &mut self,
98 scope: PullScope,
99 target: EnemyIdx,
100 ctx: &mut SimContext,
101 ) {
102 let PullScope { pull, epoch } = scope;
103
104 self.handle_enemy_activation(pull, epoch, target, ctx);
105
106 if self.instance.is_valid_target(target)
107 && let Some(swing_ms) = self.program.enemy_definition(target).and_then(
108 wowlab_engine_domain::encounter::EnemyActorDefinition::auto_attack_swing_ms,
109 )
110 {
111 self.instance.schedule_enemy_auto_attack(
112 target,
113 ActorId::Player,
114 ctx.state
115 .current_time
116 .saturating_add(SimTime::from_millis(swing_ms / FIRST_ENEMY_SWING_DIVISOR)),
117 );
118 }
119 }
120
121 pub(super) fn on_retarget_impl(&mut self, scope: PullScope, ctx: &mut SimContext) {
122 let PullScope { pull, epoch } = scope;
123
124 self.handle_retarget(pull, epoch, ctx);
125 }
126
127 pub(super) fn on_encounter_move_impl(
128 &mut self,
129 scope: PullScope,
130 actor: PositionedActorRef,
131 transform: SpatialTransform,
132 ctx: &mut SimContext,
133 ) {
134 let PullScope { pull, epoch } = scope;
135
136 self.handle_movement(pull, epoch, actor, transform, ctx);
137 }
138
139 pub(super) fn on_raid_event_impl(
140 &mut self,
141 event: wowlab_engine_ports::RaidEventBinding,
142 ctx: &mut SimContext,
143 ) {
144 let Some(index) = self
145 .program
146 .runtime_events
147 .validate_raid_event(event)
148 .map(usize::from)
149 .filter(|index| *index < self.program.config.raid_events.len())
150 else {
151 return;
152 };
153
154 self.handle_raid_event(index, ctx);
155 }
156
157 pub(super) fn on_raid_add_despawn_impl(
158 &mut self,
159 target: EnemyIdx,
160 activation_generation: u64,
161 encounter_generation: wowlab_engine_ports::PullLifecycleGeneration,
162 ctx: &mut SimContext,
163 ) {
164 self.handle_raid_add_despawn(target, activation_generation, encounter_generation, ctx);
165 }
166 pub(crate) fn cleanup_inactive_enemy_effects(
167 &mut self,
168 target: EnemyIdx,
169 now: SimTime,
170 telemetry: &mut wowlab_engine_telemetry::TelemetrySink,
171 ) {
172 self.terminate_channel_targeting(target, now);
173 self.with_hook_ctx(telemetry, HookCtxRequest::for_target(now, target), |ctx| {
174 crate::systems::expire_auras_on_actor(ctx, ActorId::Enemy(target));
175 });
176 }
177
178 pub(super) fn cleanup_inactive_enemy(
179 &mut self,
180 target: EnemyIdx,
181 now: SimTime,
182 telemetry: &mut wowlab_engine_telemetry::TelemetrySink,
183 ) {
184 self.cleanup_inactive_enemy_effects(target, now, telemetry);
185 self.instance.retarget(&self.program, now);
186 }
187
188 fn terminate_channel_targeting(&mut self, target: EnemyIdx, now: SimTime) {
189 if self.instance.runtime.casting.active_channel_target != Some(target) {
190 return;
191 }
192
193 crate::systems::end_channel(&self.program, &mut self.instance, now);
194 }
195}