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;