Skip to main content

wowlab_engine_combat/handler/
spec_handler.rs

1use wowlab_engine_ports::{
2    AuraEventRef, EncounterDespawnKind, EncounterTerminationReason, EnemyDespawnOutcome, Event,
3    PullScope, SimContext, SpecAction, SpecHandler, SpecRuntimeError,
4};
5use wowlab_engine_telemetry::{EncounterTelemetryEvent, EncounterTelemetryEventKind};
6use wowlab_types::{
7    combat::DamageSchool,
8    constants::DEFAULT_GCD_MS,
9    sim::{ActorId, EnemyIdx, GroupId, PositionedActorRef, SimTime, SpatialTransform, WaveId},
10};
11
12use super::{
13    CombatHandler,
14    introspection::build_introspection,
15    reset::{reset_combat, set_precombat_aura_stacks},
16};
17use crate::{
18    context::{CombatCtx, CombatCtxRequest, HookCtxRequest},
19    state::GuardianHandle,
20    systems::{
21        apply_aura, apply_auto_attack_action_lifecycle, check_recharge, drain_deferred_work,
22        earliest_auto_attack_time, initialize_pet_actions, phase_auto_attacks_for,
23        process_channel_tick_for, process_pet_action, process_single_aura_tick_for, sync_resource,
24    },
25};
26
27mod auras;
28mod casting;
29mod combat_events;
30mod encounter;
31mod introspect;
32mod lifecycle;
33mod timers;
34
35const FIRST_ENEMY_SWING_DIVISOR: u32 = 2;
36
37impl SpecHandler for CombatHandler {
38    fn install_target_metadata(&self, sink: &mut wowlab_engine_telemetry::TelemetrySink) {
39        sink.install_targets(self.program.telemetry_targets());
40    }
41    fn on_sim_start(&mut self, ctx: &mut SimContext) {
42        self.on_sim_start_impl(ctx);
43    }
44    fn on_player_ready(&mut self, ctx: &mut SimContext) -> Option<SpecAction> {
45        let action = self.on_player_ready_impl(ctx);
46
47        #[cfg(feature = "rotation-optimizations")]
48        self.instance.runtime.rotation_mutations.clear();
49
50        action
51    }
52    fn on_cast_complete(&mut self, event: Event, ctx: &mut SimContext) {
53        self.on_cast_complete_impl(event, ctx);
54    }
55    fn on_triggered_spell(
56        &mut self,
57        spell: wowlab_engine_ports::ScheduledSpellBinding,
58        source: ActorId,
59        target: ActorId,
60        ctx: &mut SimContext,
61    ) {
62        self.on_triggered_spell_impl(spell, source, target, ctx);
63    }
64    fn on_spell_impact(
65        &mut self,
66        impact: wowlab_engine_ports::PendingImpactBinding,
67        ctx: &mut SimContext,
68    ) {
69        self.on_spell_impact_impl(impact, ctx);
70    }
71    fn on_spell_launch(
72        &mut self,
73        impact: wowlab_engine_ports::PendingImpactBinding,
74        ctx: &mut SimContext,
75    ) {
76        self.on_spell_launch_impl(impact, ctx);
77    }
78    fn on_guardian_action(
79        &mut self,
80        guardian: wowlab_engine_ports::GuardianEventBinding,
81        ctx: &mut SimContext,
82    ) {
83        self.on_guardian_action_impl(guardian, ctx);
84    }
85    fn on_guardian_expire(
86        &mut self,
87        guardian: wowlab_engine_ports::GuardianEventBinding,
88        ctx: &mut SimContext,
89    ) {
90        self.on_guardian_expire_impl(guardian, ctx);
91    }
92    fn on_pet_action(
93        &mut self,
94        action: wowlab_engine_ports::PetActionBinding,
95        ctx: &mut SimContext,
96    ) {
97        self.on_pet_action_impl(action, ctx);
98    }
99    fn on_aura_tick(&mut self, event: AuraEventRef, ctx: &mut SimContext) {
100        self.on_aura_tick_impl(event, ctx);
101    }
102    fn on_channel_tick(
103        &mut self,
104        spell: wowlab_engine_ports::ScheduledSpellBinding,
105        source: ActorId,
106        target: EnemyIdx,
107        generation: u64,
108        ctx: &mut SimContext,
109    ) {
110        self.on_channel_tick_impl(spell, source, target, generation, ctx);
111    }
112    fn on_aura_expire(&mut self, event: AuraEventRef, ctx: &mut SimContext) {
113        self.on_aura_expire_impl(event, ctx);
114    }
115    fn on_proc_heartbeat(&mut self, ctx: &mut SimContext) {
116        self.on_proc_heartbeat_impl(ctx);
117    }
118    fn on_death(
119        &mut self,
120        target: EnemyIdx,
121        scope: wowlab_engine_ports::DeathEventScope,
122        ctx: &mut SimContext,
123    ) {
124        self.on_death_impl(target, scope, ctx);
125    }
126    fn on_despawn(&mut self, target: EnemyIdx, ctx: &mut SimContext) -> EnemyDespawnOutcome {
127        self.on_despawn_impl(target, ctx)
128    }
129    fn on_encounter_despawn(
130        &mut self,
131        scope: PullScope,
132        target: EnemyIdx,
133        kind: EncounterDespawnKind,
134        ctx: &mut SimContext,
135    ) {
136        self.on_encounter_despawn_impl(scope, target, kind, ctx);
137    }
138    fn on_group_complete(&mut self, scope: PullScope, group: GroupId, ctx: &mut SimContext) {
139        self.on_group_complete_impl(scope, group, ctx);
140    }
141    fn on_pull_complete(&mut self, scope: PullScope, finalize: bool, ctx: &mut SimContext) {
142        self.on_pull_complete_impl(scope, finalize, ctx);
143    }
144    fn on_encounter_terminate(&mut self, reason: EncounterTerminationReason, ctx: &mut SimContext) {
145        self.on_encounter_terminate_impl(reason, ctx);
146    }
147    fn on_wave_activate(&mut self, scope: PullScope, wave: WaveId, ctx: &mut SimContext) {
148        self.on_wave_activate_impl(scope, wave, ctx);
149    }
150    fn on_enemy_activate(&mut self, scope: PullScope, target: EnemyIdx, ctx: &mut SimContext) {
151        self.on_enemy_activate_impl(scope, target, ctx);
152    }
153    fn on_retarget(&mut self, scope: PullScope, ctx: &mut SimContext) {
154        self.on_retarget_impl(scope, ctx);
155    }
156    fn on_encounter_move(
157        &mut self,
158        scope: PullScope,
159        actor: PositionedActorRef,
160        transform: SpatialTransform,
161        ctx: &mut SimContext,
162    ) {
163        self.on_encounter_move_impl(scope, actor, transform, ctx);
164    }
165    fn on_event_queue_empty(&mut self, ctx: &mut SimContext) {
166        self.on_event_queue_empty_impl(ctx);
167    }
168    fn encounter_termination_time(&self) -> Option<SimTime> {
169        self.encounter_termination_time_impl()
170    }
171    fn on_hook_timer(
172        &mut self,
173        timer: wowlab_engine_ports::HookTimerBinding,
174        source: ActorId,
175        target: Option<EnemyIdx>,
176        ctx: &mut SimContext,
177    ) {
178        self.on_hook_timer_impl(timer, source, target, ctx);
179    }
180    fn on_auto_attack(&mut self, source: ActorId, target: EnemyIdx, ctx: &mut SimContext) {
181        self.on_auto_attack_impl(source, target, ctx);
182    }
183    fn on_enemy_auto_attack(&mut self, source: EnemyIdx, target: ActorId, ctx: &mut SimContext) {
184        self.on_enemy_auto_attack_impl(source, target, ctx);
185    }
186    fn on_actor_movement(
187        &mut self,
188        movement: wowlab_engine_ports::ActorMovementBinding,
189        moving: bool,
190        ctx: &mut SimContext,
191    ) {
192        let Some((actor, generation)) = self
193            .program
194            .runtime_events
195            .validate_actor_movement(movement)
196        else {
197            return;
198        };
199
200        self.on_actor_movement_impl(actor, moving, generation, ctx);
201    }
202    fn on_raid_event(
203        &mut self,
204        event: wowlab_engine_ports::RaidEventBinding,
205        ctx: &mut SimContext,
206    ) {
207        self.on_raid_event_impl(event, ctx);
208    }
209    fn on_raid_add_despawn(
210        &mut self,
211        add: wowlab_engine_ports::RaidAddBinding,
212        ctx: &mut SimContext,
213    ) {
214        let Some((target, activation_generation, encounter_generation)) =
215            self.program.runtime_events.validate_raid_add(add)
216        else {
217            return;
218        };
219
220        self.on_raid_add_despawn_impl(target, activation_generation, encounter_generation, ctx);
221    }
222    fn on_external_buff(
223        &mut self,
224        buff: wowlab_engine_ports::ExternalBuffBinding,
225        ctx: &mut SimContext,
226    ) {
227        self.on_external_buff_impl(buff, ctx);
228    }
229    fn on_cooldown_ready(
230        &mut self,
231        spell: wowlab_engine_ports::ScheduledSpellBinding,
232        ctx: &mut SimContext,
233    ) {
234        self.on_cooldown_ready_impl(spell, ctx);
235    }
236    fn flush_scheduled(&mut self, push: &mut dyn FnMut(Event)) {
237        self.flush_scheduled_impl(push);
238    }
239    fn take_runtime_error(&mut self) -> Option<SpecRuntimeError> {
240        self.take_runtime_error_impl()
241    }
242    fn reset(&mut self) {
243        self.reset_impl();
244    }
245    fn total_damage(&self) -> f64 {
246        self.total_damage_impl()
247    }
248    fn cast_time_ms(
249        &self,
250        spell: wowlab_engine_ports::ScheduledSpellBinding,
251        empower_rank: u8,
252    ) -> u32 {
253        self.cast_time_ms_impl(spell, empower_rank)
254    }
255    fn introspect(&self) -> wowlab_types::game::SpecIntrospection {
256        self.introspect_impl()
257    }
258    fn attach_decision_trace(
259        &mut self,
260        sink: std::sync::Arc<dyn wowlab_engine_ports::DecisionTraceSink>,
261    ) {
262        self.attach_decision_trace_impl(sink);
263    }
264    fn paperdoll(&self) -> Option<wowlab_engine_ports::Paperdoll> {
265        Some(self.paperdoll_impl())
266    }
267}
268
269#[cfg(test)]
270mod tests;