wowlab_engine_combat/handler/spec_handler/
combat_events.rs1use super::{
2 ActorId, CombatCtxRequest, CombatHandler, DamageSchool, EnemyIdx, Event, GuardianHandle,
3 SimContext, SimTime, earliest_auto_attack_time, phase_auto_attacks_for, process_pet_action,
4 project_encounter,
5};
6
7impl CombatHandler {
8 pub(super) fn on_guardian_action_impl(
9 &mut self,
10 guardian_id: u32,
11 guardian_generation: u32,
12 ability_index: usize,
13 ctx: &mut SimContext,
14 ) {
15 let now = ctx.state.current_time;
16 let handle = GuardianHandle::new(guardian_id, guardian_generation);
17 let Some((source, target)) =
18 crate::systems::guardian_combat_coordinates(&self.state, handle)
19 else {
20 return;
21 };
22
23 self.with_combat_ctx(
24 &mut *ctx.telemetry,
25 CombatCtxRequest::new(now, source, target),
26 |combat| crate::systems::process_guardian_action(combat, handle, ability_index),
27 );
28 }
29
30 pub(super) fn on_guardian_expire_impl(
31 &mut self,
32 guardian_id: u32,
33 guardian_generation: u32,
34 ctx: &mut SimContext,
35 ) {
36 let now = ctx.state.current_time;
37 let handle = GuardianHandle::new(guardian_id, guardian_generation);
38 let Some((source, target)) =
39 crate::systems::guardian_combat_coordinates(&self.state, handle)
40 else {
41 return;
42 };
43
44 self.with_combat_ctx(
45 &mut *ctx.telemetry,
46 CombatCtxRequest::new(now, source, target),
47 |combat| crate::systems::process_guardian_expire(combat, handle),
48 );
49 }
50
51 pub(super) fn on_pet_action_impl(&mut self, auto_attack_index: usize, ctx: &mut SimContext) {
52 let now = ctx.state.current_time;
53 let Some(target) = self.state.current_target().or_else(|| {
54 self.state.retarget();
55 project_encounter(&self.state, &mut self.buf, now);
56
57 self.state.current_target()
58 }) else {
59 return;
60 };
61
62 self.with_combat_ctx(
63 &mut *ctx.telemetry,
64 CombatCtxRequest::new(
65 now,
66 ActorId::Pet(wowlab_types::sim::PetIdx::PRIMARY),
67 target,
68 ),
69 |combat| process_pet_action(combat, auto_attack_index),
70 );
71 }
72
73 pub(super) fn on_proc_heartbeat_impl(&mut self, ctx: &mut SimContext) {
74 let now = ctx.state.current_time;
75
76 if !crate::systems::begin_proc_heartbeat(&mut self.state, now) {
77 return;
78 }
79
80 crate::systems::refresh_pet_stat_snapshots(&mut self.state, &self.buf);
81
82 if let Some(target) = self.state.current_target() {
83 self.with_combat_ctx(
84 &mut *ctx.telemetry,
85 CombatCtxRequest::new(now, ActorId::Player, target),
86 crate::systems::fire_heartbeat_procs,
87 );
88 }
89
90 crate::systems::ensure_recurring_proc_heartbeat(&mut self.state, now, &mut self.rng);
91 }
92
93 pub(super) fn on_auto_attack_impl(
94 &mut self,
95 source: ActorId,
96 target: EnemyIdx,
97 ctx: &mut SimContext,
98 ) {
99 let now = ctx.state.current_time;
100
101 if !self.state.is_valid_target(target) {
102 self.state.retarget();
103 project_encounter(&self.state, &mut self.buf, now);
104
105 if let Some(target) = self.state.current_target() {
106 self.state.schedule(Event::AutoAttack {
107 t: now,
108 source,
109 target,
110 });
111 }
112
113 return;
114 }
115
116 let swing_was_due =
117 earliest_auto_attack_time(&self.state, &self.buf).is_some_and(|t| t <= now);
118
119 self.with_combat_ctx(
120 &mut *ctx.telemetry,
121 CombatCtxRequest::new(now, source, target),
122 phase_auto_attacks_for,
123 );
124
125 if swing_was_due {
126 if let Some(next_t) = earliest_auto_attack_time(&self.state, &self.buf) {
127 if next_t > now {
128 if let Some(target) = self.state.current_target() {
129 self.state.schedule(Event::AutoAttack {
130 t: next_t,
131 source,
132 target,
133 });
134 }
135 }
136 }
137 }
138 }
139
140 pub(super) fn on_enemy_auto_attack_impl(
141 &mut self,
142 source: EnemyIdx,
143 target: ActorId,
144 ctx: &mut SimContext,
145 ) {
146 if !self
147 .state
148 .enemy_auto_attack_is_current(source, ctx.state.current_time)
149 || !self.state.is_valid_target(source)
150 || !self.state.is_friendly_actor_alive(target)
151 {
152 return;
153 }
154
155 let Some((damage, swing_ms)) = self
156 .state
157 .enemy_definition(source)
158 .and_then(|enemy| Some((enemy.auto_attack_damage()?, enemy.auto_attack_swing_ms()?)))
159 else {
160 return;
161 };
162
163 let _ = crate::systems::deal_incoming_damage(
164 &mut self.state,
165 &mut self.buf,
166 crate::systems::IncomingDamage {
167 source: ActorId::Enemy(source),
168 target,
169 amount: damage,
170 school: DamageSchool::Physical,
171 mechanic_mask: 0,
172 is_aoe: false,
173 is_periodic: false,
174 at: ctx.state.current_time,
175 },
176 );
177
178 if self.state.is_valid_target(source) && self.state.is_friendly_actor_alive(target) {
179 self.state.schedule_enemy_auto_attack(
180 source,
181 target,
182 ctx.state
183 .current_time
184 .saturating_add(SimTime::from_millis(swing_ms)),
185 );
186 }
187 }
188}