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::{
10 ActorId, EnemyIdx, GroupId, PositionedActorRef, SimTime, SpatialTransform, SpellIdx, WaveId,
11 },
12};
13
14use super::{
15 CombatHandler,
16 introspection::build_introspection,
17 reset::{reset_combat, set_precombat_aura_stacks},
18};
19use crate::{
20 builder::buffer_init::project_encounter,
21 context::{CombatCtx, CombatCtxRequest, HookCtxRequest},
22 state::{GuardianHandle, LocalAuraIdx, LocalSpellIdx},
23 systems::{
24 apply_aura, apply_auto_attack_action_lifecycle, check_recharge, drain_deferred_work,
25 earliest_auto_attack_time, initialize_pet_actions, phase_auto_attacks_for,
26 process_channel_tick_for, process_pet_action, process_single_aura_tick_for, set_gcd,
27 sync_resource,
28 },
29};
30
31mod auras;
32mod casting;
33mod combat_events;
34mod encounter;
35mod introspect;
36mod lifecycle;
37mod timers;
38
39const FIRST_ENEMY_SWING_DIVISOR: u32 = 2;
40
41impl SpecHandler for CombatHandler {
42 fn on_sim_start(&mut self, ctx: &mut SimContext) {
43 self.on_sim_start_impl(ctx);
44 }
45 fn on_player_ready(&mut self, ctx: &mut SimContext) -> Option<SpecAction> {
46 self.on_player_ready_impl(ctx)
47 }
48 fn on_cast_complete(&mut self, event: Event, ctx: &mut SimContext) {
49 self.on_cast_complete_impl(event, ctx);
50 }
51 fn on_triggered_spell(
52 &mut self,
53 spell_id: SpellIdx,
54 source: ActorId,
55 target: ActorId,
56 ctx: &mut SimContext,
57 ) {
58 self.on_triggered_spell_impl(spell_id, source, target, ctx);
59 }
60 fn on_spell_impact(&mut self, impact_id: u32, ctx: &mut SimContext) {
61 self.on_spell_impact_impl(impact_id, ctx);
62 }
63 fn on_spell_launch(&mut self, impact_id: u32, ctx: &mut SimContext) {
64 self.on_spell_launch_impl(impact_id, ctx);
65 }
66 fn on_guardian_action(
67 &mut self,
68 guardian_id: u32,
69 guardian_generation: u32,
70 ability_index: usize,
71 ctx: &mut SimContext,
72 ) {
73 self.on_guardian_action_impl(guardian_id, guardian_generation, ability_index, ctx);
74 }
75 fn on_guardian_expire(
76 &mut self,
77 guardian_id: u32,
78 guardian_generation: u32,
79 ctx: &mut SimContext,
80 ) {
81 self.on_guardian_expire_impl(guardian_id, guardian_generation, ctx);
82 }
83 fn on_pet_action(&mut self, auto_attack_index: usize, ctx: &mut SimContext) {
84 self.on_pet_action_impl(auto_attack_index, ctx);
85 }
86 fn on_aura_tick(&mut self, event: AuraEventRef, ctx: &mut SimContext) {
87 self.on_aura_tick_impl(event, ctx);
88 }
89 fn on_channel_tick(
90 &mut self,
91 spell_id: SpellIdx,
92 source: ActorId,
93 target: EnemyIdx,
94 generation: u64,
95 ctx: &mut SimContext,
96 ) {
97 self.on_channel_tick_impl(spell_id, source, target, generation, ctx);
98 }
99 fn on_aura_expire(&mut self, event: AuraEventRef, ctx: &mut SimContext) {
100 self.on_aura_expire_impl(event, ctx);
101 }
102 fn on_proc_heartbeat(&mut self, ctx: &mut SimContext) {
103 self.on_proc_heartbeat_impl(ctx);
104 }
105 fn on_death(
106 &mut self,
107 target: EnemyIdx,
108 scope: wowlab_engine_ports::DeathEventScope,
109 ctx: &mut SimContext,
110 ) {
111 self.on_death_impl(target, scope, ctx);
112 }
113 fn on_despawn(&mut self, target: EnemyIdx, ctx: &mut SimContext) -> EnemyDespawnOutcome {
114 self.on_despawn_impl(target, ctx)
115 }
116 fn on_encounter_despawn(
117 &mut self,
118 scope: PullScope,
119 target: EnemyIdx,
120 kind: EncounterDespawnKind,
121 ctx: &mut SimContext,
122 ) {
123 self.on_encounter_despawn_impl(scope, target, kind, ctx);
124 }
125 fn on_group_complete(&mut self, scope: PullScope, group: GroupId, ctx: &mut SimContext) {
126 self.on_group_complete_impl(scope, group, ctx);
127 }
128 fn on_pull_complete(&mut self, scope: PullScope, finalize: bool, ctx: &mut SimContext) {
129 self.on_pull_complete_impl(scope, finalize, ctx);
130 }
131 fn on_encounter_terminate(&mut self, reason: EncounterTerminationReason, ctx: &mut SimContext) {
132 self.on_encounter_terminate_impl(reason, ctx);
133 }
134 fn on_wave_activate(&mut self, scope: PullScope, wave: WaveId, ctx: &mut SimContext) {
135 self.on_wave_activate_impl(scope, wave, ctx);
136 }
137 fn on_enemy_activate(&mut self, scope: PullScope, target: EnemyIdx, ctx: &mut SimContext) {
138 self.on_enemy_activate_impl(scope, target, ctx);
139 }
140 fn on_retarget(&mut self, scope: PullScope, ctx: &mut SimContext) {
141 self.on_retarget_impl(scope, ctx);
142 }
143 fn on_encounter_move(
144 &mut self,
145 scope: PullScope,
146 actor: PositionedActorRef,
147 transform: SpatialTransform,
148 ctx: &mut SimContext,
149 ) {
150 self.on_encounter_move_impl(scope, actor, transform, ctx);
151 }
152 fn on_event_queue_empty(&mut self, ctx: &mut SimContext) {
153 self.on_event_queue_empty_impl(ctx);
154 }
155 fn encounter_termination_time(&self) -> Option<SimTime> {
156 self.encounter_termination_time_impl()
157 }
158 fn on_hook_timer(
159 &mut self,
160 timer_id: u32,
161 source: ActorId,
162 target: Option<EnemyIdx>,
163 ctx: &mut SimContext,
164 ) {
165 self.on_hook_timer_impl(timer_id, source, target, ctx);
166 }
167 fn on_auto_attack(&mut self, source: ActorId, target: EnemyIdx, ctx: &mut SimContext) {
168 self.on_auto_attack_impl(source, target, ctx);
169 }
170 fn on_enemy_auto_attack(&mut self, source: EnemyIdx, target: ActorId, ctx: &mut SimContext) {
171 self.on_enemy_auto_attack_impl(source, target, ctx);
172 }
173 fn on_actor_movement(
174 &mut self,
175 actor: ActorId,
176 moving: bool,
177 generation: u64,
178 ctx: &mut SimContext,
179 ) {
180 self.on_actor_movement_impl(actor, moving, generation, ctx);
181 }
182 fn on_raid_event(&mut self, index: usize, ctx: &mut SimContext) {
183 self.on_raid_event_impl(index, ctx);
184 }
185 fn on_raid_add_despawn(
186 &mut self,
187 target: EnemyIdx,
188 activation_generation: u64,
189 encounter_generation: wowlab_engine_ports::PullLifecycleGeneration,
190 ctx: &mut SimContext,
191 ) {
192 self.on_raid_add_despawn_impl(target, activation_generation, encounter_generation, ctx);
193 }
194 fn on_external_buff(&mut self, index: usize, ctx: &mut SimContext) {
195 self.on_external_buff_impl(index, ctx);
196 }
197 fn on_cooldown_ready(&mut self, cooldown_key: SpellIdx, ctx: &mut SimContext) {
198 self.on_cooldown_ready_impl(cooldown_key, ctx);
199 }
200 fn flush_scheduled(&mut self, push: &mut dyn FnMut(Event)) {
201 self.flush_scheduled_impl(push);
202 }
203 fn take_runtime_error(&mut self) -> Option<SpecRuntimeError> {
204 self.take_runtime_error_impl()
205 }
206 fn reset(&mut self) {
207 self.reset_impl();
208 }
209 fn total_damage(&self) -> f64 {
210 self.total_damage_impl()
211 }
212 fn cast_time_ms(&self, spell_id: SpellIdx, empower_rank: u8) -> u32 {
213 self.cast_time_ms_impl(spell_id, empower_rank)
214 }
215 fn introspect(&self) -> wowlab_types::game::SpecIntrospection {
216 self.introspect_impl()
217 }
218 fn attach_decision_trace(
219 &mut self,
220 sink: std::sync::Arc<dyn wowlab_engine_ports::DecisionTraceSink>,
221 ) {
222 self.attach_decision_trace_impl(sink);
223 }
224 fn paperdoll(&self) -> Option<wowlab_engine_ports::Paperdoll> {
225 Some(self.paperdoll_impl())
226 }
227}
228
229#[cfg(test)]
230mod tests;