wowlab_engine_combat/state/program/
definitions.rs1#[cfg(all(test, feature = "bench-metrics"))]
2use wowlab_engine_domain::rotation::SpellSlot;
3use wowlab_engine_domain::{
4 encounter::EnemyActorDefinition,
5 rotation::{
6 AuraSlot, CooldownSlot, CooldownSlotHandle, ResourceSlot, ResourceSlotHandle, SwingSlot,
7 TalentSlot, UnitSlot, WeaponImbueSlot,
8 },
9};
10#[cfg(test)]
11use wowlab_types::sim::SpellIdx;
12use wowlab_types::{combat::ResourceType, sim::AuraKey};
13
14use super::{CombatProgram, ConcreteAuraBinding};
15use crate::state::{AuraData, CombatInstance, LocalAuraIdx, LocalSpellIdx, SpellData};
16
17impl CombatProgram {
18 #[must_use]
19 pub fn base_stats(&self) -> &crate::state::BaseStats {
20 &self.config.base_stats
21 }
22
23 #[must_use]
24 pub fn game_data(&self) -> &wowlab_engine_gamedata::ResolvedGameData {
25 &self.config.game_data
26 }
27
28 #[must_use]
29 pub fn encounter(&self) -> &wowlab_engine_ports::ResolvedEncounter {
30 &self.config.encounter
31 }
32
33 #[must_use]
34 pub fn spec_config<T>(&self) -> Option<&T>
35 where
36 T: wowlab_engine_ports::SpecConfig,
37 {
38 self.config
39 .spec_config
40 .as_ref()
41 .as_any()
42 .downcast_ref::<T>()
43 }
44
45 #[cfg(feature = "test-support")]
47 #[doc(hidden)]
48 #[must_use]
49 pub fn diagnostic_spell_data(&self, spell_id: u32) -> Option<(LocalSpellIdx, &SpellData)> {
50 self.lookup_spell_data(spell_id)
51 }
52
53 pub fn spell(&self, local: LocalSpellIdx) -> &SpellData {
60 let local = self
61 .resolve_spell_identity(local)
62 .expect("foreign specialization spell binding");
63
64 &self.spells[local.as_usize()]
65 }
66
67 pub fn spells(&self) -> &[SpellData] {
68 &self.spells
69 }
70
71 pub fn aura(&self, local: LocalAuraIdx) -> &AuraData {
78 let local = self
79 .resolve_aura_identity(local)
80 .expect("foreign specialization aura binding");
81
82 &self.auras[local.as_usize()]
83 }
84
85 pub fn auras(&self) -> &[AuraData] {
86 &self.auras
87 }
88
89 pub fn precombat_auras(&self) -> &[LocalAuraIdx] {
90 &self.precombat_auras
91 }
92
93 pub fn talent_aura_stacks(&self) -> &[(LocalAuraIdx, u8)] {
94 &self.talent_aura_stacks
95 }
96
97 #[must_use]
98 pub fn talent_rank(&self, talent: crate::DamageActionId) -> u8 {
99 self.damage
100 .generated_action(talent)
101 .and_then(|local| self.talent_ranks.get(local.index()).copied())
102 .unwrap_or(0)
103 }
104
105 #[cfg(any(test, feature = "test-support"))]
107 #[must_use]
108 pub fn diagnostic_talent_rank(&self, spell_id: u32) -> Option<u8> {
109 wowlab_types::bench_metrics::record_keyed_lookup();
110 let ordinal = self.damage.diagnostic_action_ordinal(spell_id)?;
111
112 self.talent_ranks.get(ordinal).copied()
113 }
114
115 #[must_use]
116 pub fn impact_effect_proc_count(&self) -> usize {
117 self.impact_effect_procs.len()
118 }
119
120 #[must_use]
121 pub fn impact_proc_count(&self) -> usize {
122 self.impact_procs.len()
123 }
124
125 #[must_use]
126 pub fn impact_proc(&self, index: usize) -> Option<&crate::ImpactProc> {
127 self.impact_procs.get(index)
128 }
129
130 #[must_use]
131 pub fn player_cast_hook_count(&self) -> usize {
132 self.player_cast_hooks.len()
133 }
134
135 #[must_use]
136 pub fn impact_effect_proc(&self, index: usize) -> Option<&crate::state::ImpactEffectProc> {
137 self.impact_effect_procs.get(index)
138 }
139
140 #[must_use]
141 pub fn spell_effects(&self) -> &[crate::SpellEffectData] {
142 &self.spell_effects
143 }
144
145 #[must_use]
146 pub fn enemy_definition(
147 &self,
148 enemy: wowlab_types::sim::EnemyIdx,
149 ) -> Option<&EnemyActorDefinition> {
150 self.enemies
151 .get(enemy.as_usize())
152 .filter(|definition| definition.id() == enemy)
153 }
154
155 #[must_use]
156 pub fn enemy_definitions(&self) -> impl ExactSizeIterator<Item = &EnemyActorDefinition> {
157 self.enemies.iter()
158 }
159
160 #[cfg_attr(not(test), expect(dead_code, reason = "test-support diagnostic"))]
161 pub(crate) fn spell_data(&self, spell_id: u32) -> Option<(LocalSpellIdx, &SpellData)> {
162 self.lookup_spell_data(spell_id)
163 }
164
165 pub(crate) fn scheduled_spell(
166 &self,
167 local: LocalSpellIdx,
168 ) -> wowlab_engine_ports::ScheduledSpellBinding {
169 self.scheduled_spells
170 .bind(u16::from(local.raw()))
171 .expect("finalized local spell has a scheduled binding")
172 }
173
174 pub(crate) fn validate_scheduled_spell(
175 &self,
176 binding: wowlab_engine_ports::ScheduledSpellBinding,
177 ) -> Option<LocalSpellIdx> {
178 match self.scheduled_spell_execution(binding)? {
179 super::ScheduledSpellExecution::Registered(local) => Some(local),
180 super::ScheduledSpellExecution::TriggerOnly(_)
181 | super::ScheduledSpellExecution::TriggerOnlyEmpty => None,
182 }
183 }
184
185 pub(crate) fn scheduled_spell_execution(
186 &self,
187 binding: wowlab_engine_ports::ScheduledSpellBinding,
188 ) -> Option<super::ScheduledSpellExecution> {
189 let local = usize::from(self.scheduled_spells.validate(binding)?);
190
191 self.scheduled_spell_executions.get(local).copied()
192 }
193
194 #[cfg(all(test, feature = "bench-metrics"))]
196 pub(crate) fn reference_buffer_spell<'a>(
197 &self,
198 instance: &'a CombatInstance,
199 spell: SpellIdx,
200 ) -> Option<&'a SpellSlot> {
201 let local = self.spell_local(spell.as_u32())?;
202
203 Some(instance.buffer.spell_at(self.buffer_bindings.spell(local)))
204 }
205
206 #[cfg(test)]
207 pub(crate) fn active_channel_identity(
208 &self,
209 spell: SpellIdx,
210 ) -> Option<crate::state::ActiveChannelIdentity> {
211 let &local = self.spell_by_id.get(&spell.as_u32())?;
212
213 Some(crate::state::ActiveChannelIdentity {
214 local,
215 spell,
216 handle: self.buffer_bindings.spell(local),
217 tick_cost_aura: self.rotation.cast_profile(local).channel_tick_cost_aura,
218 tick_damage_aura: self.rotation.cast_profile(local).channel_tick_damage_aura,
219 })
220 }
221
222 #[expect(
223 clippy::unnecessary_wraps,
224 reason = "cooldown access stays optional at subsystem boundaries that handle absent cooldown state"
225 )]
226 pub(crate) fn buffer_cooldown<'a>(
227 &self,
228 instance: &'a CombatInstance,
229 local: LocalSpellIdx,
230 ) -> Option<&'a CooldownSlot> {
231 Some(
232 instance
233 .buffer
234 .cooldown_at(self.buffer_bindings.cooldown(local)),
235 )
236 }
237
238 #[expect(
239 clippy::unnecessary_wraps,
240 reason = "cooldown access stays optional at subsystem boundaries that handle absent cooldown state"
241 )]
242 pub(crate) fn buffer_cooldown_mut<'a>(
243 &self,
244 instance: &'a mut CombatInstance,
245 local: LocalSpellIdx,
246 ) -> Option<&'a mut CooldownSlot> {
247 Some(
248 instance
249 .buffer
250 .cooldown_mut_at(self.buffer_bindings.cooldown(local)),
251 )
252 }
253
254 #[expect(
255 clippy::unnecessary_wraps,
256 reason = "cooldown binding lookup shares the optional subsystem-boundary contract"
257 )]
258 pub(crate) fn cooldown_binding(&self, local: LocalSpellIdx) -> Option<CooldownSlotHandle> {
259 Some(self.buffer_bindings.cooldown(local))
260 }
261
262 pub(crate) fn aura_binding(
263 &self,
264 local: LocalAuraIdx,
265 key: AuraKey,
266 ) -> Option<ConcreteAuraBinding> {
267 self.buffer_bindings.aura(local, key)
268 }
269
270 pub(crate) fn aura_pool_index(&self, local: LocalAuraIdx, key: AuraKey) -> Option<usize> {
271 Some(self.aura_binding(local, key)?.index().as_usize())
272 }
273
274 pub(crate) fn aura_event(
275 &self,
276 local: LocalAuraIdx,
277 key: AuraKey,
278 target: Option<wowlab_types::sim::EnemyIdx>,
279 ) -> Option<wowlab_engine_ports::AuraEventBinding> {
280 let concrete = u32::try_from(self.aura_binding(local, key)?.index().as_usize()).ok()?;
281
282 Some(self.runtime_events.aura(local.raw(), concrete, key, target))
283 }
284
285 pub(crate) fn validate_aura_event(
286 &self,
287 event: wowlab_engine_ports::AuraEventBinding,
288 ) -> Option<(LocalAuraIdx, AuraKey, Option<wowlab_types::sim::EnemyIdx>)> {
289 let (local, concrete, key, target) = self.runtime_events.validate_aura(event)?;
290 let local = LocalAuraIdx::new(local);
291
292 (self.aura_binding(local, key)?.index().as_usize() == concrete as usize)
293 .then_some((local, key, target))
294 }
295
296 pub(crate) fn buffer_aura<'a>(
297 &self,
298 instance: &'a CombatInstance,
299 local: LocalAuraIdx,
300 key: AuraKey,
301 ) -> Option<&'a AuraSlot> {
302 self.aura_binding(local, key)
303 .map(|binding| instance.buffer.aura_at(binding.handle))
304 }
305
306 pub(crate) fn buffer_resource<'a>(
307 &self,
308 instance: &'a CombatInstance,
309 resource: ResourceType,
310 ) -> Option<&'a ResourceSlot> {
311 self.buffer_bindings
312 .resource(resource)
313 .map(|handle| instance.buffer.resource_at(handle))
314 }
315
316 pub(crate) fn buffer_resource_mut<'a>(
317 &self,
318 instance: &'a mut CombatInstance,
319 resource: ResourceType,
320 ) -> Option<&'a mut ResourceSlot> {
321 self.buffer_bindings
322 .resource(resource)
323 .map(|handle| instance.buffer.resource_mut_at(handle))
324 }
325
326 pub(crate) fn resource_binding(&self, resource: ResourceType) -> Option<ResourceSlotHandle> {
327 self.buffer_bindings.resource(resource)
328 }
329
330 pub(crate) fn buffer_talent_mut<'a>(
331 &self,
332 instance: &'a mut CombatInstance,
333 key: &str,
334 ) -> Option<&'a mut TalentSlot> {
335 self.buffer_bindings
336 .talent(key)
337 .map(|handle| instance.buffer.talent_mut_at(handle))
338 }
339
340 pub(crate) fn buffer_swing<'a>(
341 &self,
342 instance: &'a CombatInstance,
343 index: usize,
344 ) -> Option<&'a SwingSlot> {
345 self.buffer_bindings
346 .swing(index)
347 .map(|handle| instance.buffer.swing_at(handle))
348 }
349
350 pub(crate) fn buffer_swing_mut<'a>(
351 &self,
352 instance: &'a mut CombatInstance,
353 index: usize,
354 ) -> Option<&'a mut SwingSlot> {
355 self.buffer_bindings
356 .swing(index)
357 .map(|handle| instance.buffer.swing_mut_at(handle))
358 }
359
360 pub(crate) fn target_unit_mut<'a>(&self, instance: &'a mut CombatInstance) -> &'a mut UnitSlot {
361 instance
362 .buffer
363 .unit_mut_at(self.buffer_bindings.target_unit())
364 }
365
366 #[cfg(test)]
367 pub(crate) fn target_unit<'a>(&self, instance: &'a CombatInstance) -> &'a UnitSlot {
368 instance.buffer.unit_at(self.buffer_bindings.target_unit())
369 }
370
371 pub(crate) fn buffer_weapon_imbue_mut<'a>(
372 &self,
373 instance: &'a mut CombatInstance,
374 key: &str,
375 ) -> Option<&'a mut WeaponImbueSlot> {
376 self.buffer_bindings
377 .weapon_imbue(key)
378 .map(|handle| instance.buffer.weapon_imbue_mut_at(handle))
379 }
380
381 fn lookup_spell_data(&self, spell_id: u32) -> Option<(LocalSpellIdx, &SpellData)> {
382 let local = self.spell_local(spell_id)?;
383
384 Some((local, &self.spells[local.as_usize()]))
387 }
388}