wowlab_engine_combat/handler/
reset.rs1use wowlab_engine_domain::rotation::DenseBuffer;
2use wowlab_types::sim::{ActorId, SimTime};
3
4use crate::{
5 builder::buffer_init::project_encounter,
6 keys::auto_attack_key,
7 state::{CombatState, LocalAuraIdx, LocalSpellIdx},
8 systems::{aura_keys_for_application, reset_cooldown, sync_spell_costs},
9};
10
11pub(super) fn reset_combat(
12 state: &mut CombatState,
13 buf: &mut DenseBuffer,
14 last_sync: &mut SimTime,
15) {
16 state.runtime.total_damage = 0.0;
17 state.runtime.resources.last_primary_spent = 0.0;
18 state.runtime.resources.last_optional_primary_spent = 0.0;
19 state.runtime.resources.last_secondary_spent = 0.0;
20 state.runtime.casting.instant_cast_pending_at = None;
21 state.runtime.casting.instant_off_gcd_casts_at = None;
22 state.runtime.casting.instant_off_gcd_spell_ids.clear();
23 state.runtime.casting.active_channel_target = None;
24 state.runtime.casting.channel_generation = 0;
25 state.runtime.casting.active_channel_damage_mult = 1.0;
26 state.runtime.casting.active_channel_spell_id = None;
27 state.runtime.casting.active_channel_last_tick = SimTime::ZERO;
28 state.runtime.casting.active_channel_tick_interval_ms = 0;
29 state.runtime.casting.active_channel_ticks_remaining = 0;
30 state.runtime.casting.active_channel_on_last_tick = false;
31 state.runtime.casting.active_channel_pushback_count = 0;
32 state.runtime.casting.free_action_dispatch_depth = 0;
33 state.runtime.casting.channel_chain_spell_id = None;
34 state.runtime.casting.active_casts.clear();
35 state.runtime.control.school_lockouts.clear();
36 state.runtime.control.movement.clear();
37 state.runtime.control.movement_generation.clear();
38 state.runtime.control.crowd_control.clear();
39 state.runtime.control.diminishing_returns.clear();
40 state.runtime.health.player_health.reset();
41
42 for health in state.runtime.health.pet_health.iter_mut().flatten() {
43 health.reset();
44 }
45
46 state.runtime.health.immunity_windows.clear();
47 state.runtime.health.active_absorbs.clear();
48 state.runtime.health.next_absorb_application_order = 0;
49 state.take_runtime_error();
50 *last_sync = SimTime::ZERO;
51 state.runtime.pending_events.clear();
52 state.runtime.procs.proc_heartbeat_scheduled_at = None;
53 state.runtime.deferred_work.clear();
54 state.runtime.procs.impact_effect_ready_at.fill(0.0);
55
56 for target_cooldowns in &mut state.runtime.procs.impact_effect_target_ready_at {
57 target_cooldowns.clear();
58 }
59
60 for (remaining, proc) in state
61 .runtime
62 .procs
63 .impact_effect_charges
64 .iter_mut()
65 .zip(&state.defs.impact_effect_procs)
66 {
67 *remaining = if proc.uses_stacks_for_charges {
68 0
69 } else {
70 proc.charges
71 };
72 }
73
74 for rng in &mut state.runtime.procs.impact_effect_rng {
75 rng.reset();
76 }
77
78 state.runtime.procs.proc_category_ready_at.clear();
79 state.runtime.procs.proc_attempts.clear();
80 state.runtime.procs.proc_counters.clear();
81 state.runtime.mask_reasons.clear();
82 state.runtime.recharge_rate_mults.fill(1.0);
83 state.runtime.pools.residual_damage_pools.clear();
84 state.runtime.pools.accumulated_damage_pools.clear();
85 state.runtime.pools.rolling_tick_mult.clear();
86 state.runtime.pools.async_stack_expiries.clear();
87 state.runtime.pools.overkill_absorb_ready_at.clear();
88 state.runtime.pools.cooldown_ready_at.clear();
89 state.runtime.pools.start_recovery_ready_at.clear();
90 state.runtime.pools.cooldown_category_ready_at.clear();
91 state.runtime.pools.charge_category_ready_at.clear();
92 crate::systems::reset_spell_gates(state, buf);
93 state.runtime.pending.pending_hook_timers.clear();
94 state.runtime.pending.pending_spell_impacts.clear();
95 let guardian_sources = state
96 .runtime
97 .companions
98 .guardians
99 .iter_mut()
100 .filter_map(Option::take)
101 .map(|guardian| guardian.source)
102 .collect::<Vec<_>>();
103
104 for source in guardian_sources {
105 let _ = state.deactivate_pet_health(source);
106 }
107
108 state.runtime.companions.guardians.clear();
109 state.runtime.companions.guardian_generations.clear();
110 state.runtime.companions.pet_actions.clear();
111 state.runtime.reset_spec_runtime();
112 state.reset_enemies();
113 buf.player_mut().gcd_end = 0.0;
114 buf.player_mut().cast_end = 0.0;
115 buf.player_mut().channel_end = 0.0;
116
117 for tracker in &mut state.defs.rppm_trackers {
118 tracker.last_attempt_time = 0.0;
119 tracker.last_proc_time = 0.0;
120 tracker.accumulated_blp = 0.0;
121 }
122
123 for tracker in &mut state.defs.threshold_trackers {
124 tracker.accumulated = f64::NAN;
126 }
127
128 for proc in &mut state.defs.accumulating_impact_procs {
129 proc.accumulator = f64::NAN;
130 }
131
132 for proc in &mut state.defs.resource_gain_procs {
133 proc.attempts = 0;
134 }
135
136 let base_haste = state.config.base_stats.stats.haste;
137
138 reset_resources(state, buf);
139 reset_cooldowns(state, buf);
140 reset_channels(state, buf);
141 reset_auras(buf);
142 sync_spell_costs(state, buf);
143 reset_auto_attacks(state, buf);
144 buf.clear_history();
145 buf.player_mut().set_haste(base_haste);
146
147 project_encounter(state, buf, SimTime::ZERO);
148}
149
150pub(super) fn reset_resources(state: &mut CombatState, buf: &mut DenseBuffer) {
151 let resource_type = state.config.base_stats.resource_type;
152 let resource_start = state.config.base_stats.resource_start;
153 let base_regen = state.config.base_stats.base_regen;
154 let secondary_resource_type = state.config.base_stats.secondary_resource_type;
155
156 state.runtime.resources.primary_base_regen_override = None;
157
158 if let Some(rt) = resource_type {
159 if let Some(res) = buf.resource_mut(rt) {
160 if resource_start >= 0.0 {
161 res.current = resource_start;
162 } else {
163 res.current = res.max;
164 }
165
166 res.regen_per_sec = base_regen;
167
168 if rt == wowlab_types::combat::ResourceType::Runes {
169 state.runtime.resources.runes.reset_to(
170 wowlab_types::numeric::f64_to_u32_saturating_round(res.current) as usize,
171 SimTime::ZERO,
172 );
173 }
174 }
175 }
176
177 if let Some(rt) = secondary_resource_type {
178 if let Some(res) = buf.resource_mut(rt) {
179 res.current = 0.0;
180 }
181 }
182}
183
184fn reset_channels(state: &CombatState, buf: &mut DenseBuffer) {
185 for spell in &state.defs.spells {
186 if !spell.channel.behavior.is_channel {
187 continue;
188 }
189
190 if let Some(slot) = buf.spell_mut(wowlab_types::sim::SpellIdx::from_raw(spell.spell_id)) {
191 slot.is_channeling = 0;
192 slot.channel_end = 0.0;
193 }
194 }
195}
196
197pub(super) fn reset_cooldowns(state: &mut CombatState, buf: &mut DenseBuffer) {
198 for i in 0..state.defs.spells.len() {
199 let Some(spell) = state.defs.spells.get(i).copied() else {
200 continue;
201 };
202 if !spell.cooldown.has_cooldown && !spell.is_charged() {
205 continue;
206 }
207
208 let local = LocalSpellIdx::new(u8::try_from(i).expect("spell count validated below 256"));
209
210 reset_cooldown(state, buf, local);
211 }
212}
213
214fn reset_auras(buf: &mut DenseBuffer) {
215 let keys: Vec<_> = buf.aura_keys().collect();
216
217 for key in keys {
218 if let Some(a) = buf.aura_mut(key) {
219 a.remove();
220 }
221 }
222}
223
224pub(super) fn reset_player_auras(buf: &mut DenseBuffer) {
225 let keys: Vec<_> = buf
226 .aura_keys()
227 .filter(|key| key.affected() == ActorId::Player)
228 .collect();
229
230 for key in keys {
231 if let Some(aura) = buf.aura_mut(key) {
232 aura.remove();
233 }
234 }
235}
236
237pub(super) fn reset_guardians_between_pulls(state: &mut CombatState) {
238 let guardian_sources = state
239 .runtime
240 .companions
241 .guardians
242 .iter_mut()
243 .filter_map(Option::take)
244 .map(|guardian| guardian.source)
245 .collect::<Vec<_>>();
246
247 for source in guardian_sources {
248 let _ = state.deactivate_pet_health(source);
249 }
250}
251
252fn reset_auto_attacks(state: &CombatState, buf: &mut DenseBuffer) {
253 for (i, attack) in state.defs.auto_attacks.iter().enumerate() {
254 let key = auto_attack_key(i);
255
256 if let Some(sw) = buf.swing_mut(&key) {
257 sw.next_swing_at = if attack.starts_active {
258 0.0
259 } else {
260 f64::INFINITY
261 };
262 }
263 }
264}
265
266pub(super) fn set_precombat_aura_stacks(
267 state: &CombatState,
268 buf: &mut DenseBuffer,
269 local_idx: LocalAuraIdx,
270 stacks: u8,
271 target: Option<wowlab_types::sim::EnemyIdx>,
272) {
273 let max_stacks = i32::from(state.aura(local_idx).max_stacks);
275
276 for key in aura_keys_for_application(state, local_idx, ActorId::Player, target)
277 .into_iter()
278 .flatten()
279 {
280 buf.ensure_aura_slot(key);
281
282 if let Some(aura) = buf.aura_mut(key) {
283 aura.stacks = i32::from(stacks).min(max_stacks);
284 }
285 }
286
287 crate::systems::refresh_aura_projections(buf, target);
288 sync_spell_costs(state, buf);
289}