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