wowlab_engine_combat/handler/can_cast/
candidate.rs1use wowlab_types::sim::EnemyIdx;
4
5use crate::state::{
6 CastProfile, CombatInstance, CombatProgram, LocalAuraIdx, LocalSpellIdx, RotationSpellBinding,
7 SpellData,
8};
9
10#[derive(Clone, Copy, Debug)]
12pub(crate) struct CastCandidate {
13 pub(crate) requested_local: LocalSpellIdx,
14 pub(crate) effective_local: LocalSpellIdx,
15 pub(crate) cooldown_local: LocalSpellIdx,
16 pub(crate) override_aura_to_consume: Option<LocalAuraIdx>,
17 pub(crate) target: EnemyIdx,
18 resolved_cast_time_ms: Option<u32>,
19}
20
21const _: () = assert!(
22 size_of::<CastCandidate>() <= 64,
23 "cast candidates must remain compact hot-path transaction values"
24);
25
26impl CastCandidate {
27 pub(crate) fn resolve_planned(
28 instance: &CombatInstance,
29 binding: RotationSpellBinding,
30 target: EnemyIdx,
31 ) -> Self {
32 let active_override = binding.override_binding.filter(|override_binding| {
33 let aura = instance.buffer.aura_at(override_binding.aura_handle);
34
35 aura.is_occupied() && aura.stacks >= i32::from(override_binding.aura_min_stacks.max(1))
36 });
37 let (effective_local, shares_base_cooldown, override_aura_to_consume) = active_override
38 .map_or((binding.local, false, None), |override_binding| {
39 (
40 override_binding.effective_local,
41 override_binding.shares_base_cooldown,
42 override_binding.aura_to_consume,
43 )
44 });
45 let cooldown_local = if shares_base_cooldown {
46 binding.local
47 } else {
48 effective_local
49 };
50
51 Self {
52 requested_local: binding.local,
53 effective_local,
54 cooldown_local,
55 override_aura_to_consume,
56 target,
57 resolved_cast_time_ms: None,
58 }
59 }
60
61 pub(crate) fn requested(self, program: &CombatProgram) -> &SpellData {
62 program.spell(self.requested_local)
63 }
64
65 pub(crate) fn effective(self, program: &CombatProgram) -> &SpellData {
66 program.spell(self.effective_local)
67 }
68
69 pub(crate) fn requested_profile(self, program: &CombatProgram) -> &CastProfile {
70 program.rotation.cast_profile_ref(self.requested_local)
71 }
72
73 pub(crate) fn effective_profile(self, program: &CombatProgram) -> &CastProfile {
74 program.rotation.cast_profile_ref(self.effective_local)
75 }
76
77 pub(crate) fn requested_spell_id(self, program: &CombatProgram) -> u32 {
78 self.requested(program).spell_id
79 }
80
81 pub(crate) fn requested_slot(
82 self,
83 program: &CombatProgram,
84 ) -> wowlab_engine_domain::rotation::SpellSlotHandle {
85 program.buffer_bindings.spell(self.requested_local)
86 }
87
88 pub(crate) fn effective_slot(
89 self,
90 program: &CombatProgram,
91 ) -> wowlab_engine_domain::rotation::SpellSlotHandle {
92 program.buffer_bindings.spell(self.effective_local)
93 }
94
95 pub(crate) fn effective_history(
96 self,
97 program: &CombatProgram,
98 ) -> wowlab_engine_domain::rotation::HistorySlotHandle {
99 program.buffer_bindings.history(self.effective_local)
100 }
101
102 pub(crate) fn cooldown_slot(
103 self,
104 program: &CombatProgram,
105 ) -> wowlab_engine_domain::rotation::CooldownSlotHandle {
106 program.buffer_bindings.cooldown(self.cooldown_local)
107 }
108
109 pub(crate) const fn resolved_cast_time_ms(self) -> Option<u32> {
110 self.resolved_cast_time_ms
111 }
112
113 pub(crate) const fn with_resolved_cast_time_ms(mut self, cast_time_ms: u32) -> Self {
114 self.resolved_cast_time_ms = Some(cast_time_ms);
115
116 self
117 }
118}