Skip to main content

wowlab_engine_combat/handler/spec_handler/
encounter.rs

1use 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}