Skip to main content

wowlab_engine_combat/handler/spec_handler/
combat_events.rs

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