wowlab_engine_combat/state/
config.rs1use wowlab_engine_ports::CombatStats;
2use wowlab_types::sim::ActorId;
3
4use super::{
5 AccumulatingImpactProc, CastHookFn, DamageTransfer, ImpactEffectProc, ImpactProc,
6 LandedImpactProc, ResourceGainProc, SpellEffectRange,
7};
8
9#[derive(Clone, Copy, Debug)]
10pub(crate) struct EffectProgramFrame {
11 pub(crate) range: SpellEffectRange,
12 pub(crate) profile_spell_id: u32,
13 pub(crate) profile_action: Option<crate::state::program::LocalDamageActionIdx>,
14 pub(crate) flags: crate::DamageFlags,
15 pub(crate) next: usize,
16 pub(crate) end: usize,
17 pub(crate) reflected: bool,
18}
19
20impl Default for EffectProgramFrame {
21 fn default() -> Self {
22 Self {
23 range: SpellEffectRange::default(),
24 profile_spell_id: 0,
25 profile_action: None,
26 flags: crate::DamageFlags::empty(),
27 next: 0,
28 end: 0,
29 reflected: false,
30 }
31 }
32}
33
34#[derive(Debug)]
35pub(crate) struct TargetWorkspace {
36 candidates: Vec<wowlab_types::sim::EnemyIdx>,
37 candidate_count: usize,
38 targets: Vec<wowlab_types::sim::EnemyIdx>,
39 target_count: usize,
40}
41
42impl TargetWorkspace {
43 pub(crate) fn new(target_capacity: usize) -> Self {
44 Self {
45 candidates: vec![wowlab_types::sim::EnemyIdx::PRIMARY; target_capacity],
46 candidate_count: 0,
47 targets: vec![wowlab_types::sim::EnemyIdx::PRIMARY; target_capacity],
48 target_count: 0,
49 }
50 }
51
52 pub(crate) fn clear(&mut self) {
53 self.candidate_count = 0;
54 self.target_count = 0;
55 }
56
57 pub(crate) fn candidate_storage(&mut self) -> &mut [wowlab_types::sim::EnemyIdx] {
58 &mut self.candidates
59 }
60
61 pub(crate) fn set_candidate_count(&mut self, count: usize) {
62 assert!(
63 count <= self.candidates.len(),
64 "candidate count {count} exceeds finalized target capacity {}",
65 self.candidates.len()
66 );
67 self.candidate_count = count;
68 }
69
70 pub(crate) fn candidates(&self) -> &[wowlab_types::sim::EnemyIdx] {
71 &self.candidates[..self.candidate_count]
73 }
74
75 pub(crate) const fn candidate_count(&self) -> usize {
76 self.candidate_count
77 }
78
79 pub(crate) fn candidate_at(&self, index: usize) -> Option<wowlab_types::sim::EnemyIdx> {
80 self.candidates().get(index).copied()
81 }
82
83 pub(crate) fn retain_candidates(
84 &mut self,
85 mut keep: impl FnMut(wowlab_types::sim::EnemyIdx) -> bool,
86 ) {
87 let mut retained = 0;
88
89 for index in 0..self.candidate_count {
90 let candidate = self.candidates[index];
92
93 if keep(candidate) {
94 self.candidates[retained] = candidate;
96 retained += 1;
97 }
98 }
99
100 self.candidate_count = retained;
101 }
102
103 pub(crate) fn retain_candidates_excluding_targets(
104 &mut self,
105 mut keep: impl FnMut(wowlab_types::sim::EnemyIdx) -> bool,
106 ) {
107 let mut retained = 0;
108
109 for index in 0..self.candidate_count {
110 let candidate = self.candidates[index];
112
113 if keep(candidate) && !self.targets().contains(&candidate) {
114 self.candidates[retained] = candidate;
116 retained += 1;
117 }
118 }
119
120 self.candidate_count = retained;
121 }
122
123 pub(crate) fn push_target(&mut self, target: wowlab_types::sim::EnemyIdx) {
124 assert!(
125 self.target_count < self.targets.len(),
126 "target count {} exhausts finalized capacity {}",
127 self.target_count,
128 self.targets.len()
129 );
130 self.targets[self.target_count] = target;
132 self.target_count += 1;
133 }
134
135 pub(crate) fn contains_target(&self, target: wowlab_types::sim::EnemyIdx) -> bool {
136 self.targets().contains(&target)
137 }
138
139 pub(crate) fn targets(&self) -> &[wowlab_types::sim::EnemyIdx] {
140 &self.targets[..self.target_count]
142 }
143
144 pub(crate) fn truncate_targets(&mut self, count: usize) {
145 self.target_count = self.target_count.min(count);
146 }
147}
148
149#[derive(Clone, Debug)]
151pub struct BaseStats {
152 pub stats: CombatStats,
153 pub primary_attribute: Option<wowlab_types::game::Attribute>,
154 pub base_regen: f64,
155 pub resource_max: f64,
156 pub resource_start: f64,
157 pub resource_name: String,
158 pub has_pet: bool,
159 pub secondary_resource_name: Option<String>,
160 pub secondary_resource_max: f64,
161 pub resource_type: Option<wowlab_types::combat::ResourceType>,
162 pub secondary_resource_type: Option<wowlab_types::combat::ResourceType>,
163 pub swing_resource_gain_mult: f64,
164}
165
166#[derive(Debug, Default)]
168pub(crate) struct ScratchBuffers {
169 pub(crate) absorb_indices: crate::state::BoundedVec<usize>,
170 pub(crate) active_damage_transfers: Vec<(ActorId, DamageTransfer)>,
171 pub hooks: Vec<CastHookFn>,
172 pub impacts: Vec<ImpactProc>,
173 pub landed_impacts: Vec<LandedImpactProc>,
174 pub(crate) landed_impact_events: Vec<crate::state::ImpactEvent>,
175 pub accumulating_impacts: Vec<AccumulatingImpactProc>,
176 pub impact_effects: Vec<ImpactEffectProc>,
177 pub resource_gains: Vec<ResourceGainProc>,
178 pub(crate) rotation_masks: Vec<(wowlab_engine_domain::rotation::SpellSlotHandle, i32)>,
179 pub(crate) locked_action_masks: Vec<(wowlab_engine_domain::rotation::SpellSlotHandle, i32)>,
180 pub(crate) rotation_candidates: Vec<crate::handler::can_cast::CastCandidate>,
181 pub(crate) rotation_trace_reasons: Option<Vec<(u32, crate::handler::can_cast::CastReject)>>,
182 pub(crate) effect_program_frames: Vec<EffectProgramFrame>,
183 pub(crate) effect_program_frame_count: usize,
184 pub(crate) target_workspaces: Vec<TargetWorkspace>,
185 pub(crate) health_threshold_triggers: Vec<wowlab_engine_ports::ScheduledSpellBinding>,
186}
187
188impl ScratchBuffers {
189 pub(crate) fn size_absorb_scratch(&mut self, capacity: usize) {
190 self.absorb_indices = crate::state::BoundedVec::new(capacity);
191 }
192
193 pub(crate) fn size_damage_transfer_scratch(&mut self, capacity: usize) {
194 self.active_damage_transfers = Vec::with_capacity(capacity);
195 }
196
197 pub(crate) fn size_rotation_masks(&mut self, availability_count: usize) {
198 self.rotation_masks = Vec::with_capacity(availability_count);
199 self.locked_action_masks = Vec::with_capacity(availability_count);
200 self.rotation_candidates = Vec::with_capacity(availability_count);
201 }
202
203 pub(crate) fn size_execution_scratch(
204 &mut self,
205 effect_count: usize,
206 target_program_count: usize,
207 target_capacity: usize,
208 ) {
209 self.effect_program_frames =
210 vec![EffectProgramFrame::default(); effect_count.saturating_add(1)];
211 self.effect_program_frame_count = 0;
212 let target_resolution_depth = effect_count
213 .saturating_add(target_program_count)
214 .saturating_add(1);
215
216 self.target_workspaces = (0..target_resolution_depth)
217 .map(|_| TargetWorkspace::new(target_capacity))
218 .collect();
219 }
220
221 pub(crate) fn push_effect_program_frame(&mut self, frame: EffectProgramFrame) {
222 assert!(
223 self.effect_program_frame_count < self.effect_program_frames.len(),
224 "effect execution depth {} exhausts finalized frame bound {}",
225 self.effect_program_frame_count,
226 self.effect_program_frames.len()
227 );
228 self.effect_program_frames[self.effect_program_frame_count] = frame;
230 self.effect_program_frame_count += 1;
231 }
232
233 pub(crate) fn pop_effect_program_frame_after(
234 &mut self,
235 base: usize,
236 ) -> Option<EffectProgramFrame> {
237 if self.effect_program_frame_count <= base {
238 return None;
239 }
240
241 self.effect_program_frame_count -= 1;
242
243 Some(self.effect_program_frames[self.effect_program_frame_count])
246 }
247}
248
249pub(crate) fn with_scratch<T>(
250 source: &[T],
251 mut scratch: Vec<T>,
252 use_entries: impl FnOnce(&mut Vec<T>),
253) -> Vec<T>
254where
255 T: Copy,
256{
257 scratch.clear();
258 debug_assert!(
259 scratch.capacity() >= source.len(),
260 "finalized scratch capacity must cover its immutable source plan"
261 );
262 scratch.extend(source.iter().copied());
263 use_entries(&mut scratch);
264
265 scratch
266}