Skip to main content

wowlab_engine_combat/handler/can_cast/
candidate.rs

1//! One-time cast identity and override resolution for the predicate pipeline.
2
3use wowlab_types::sim::EnemyIdx;
4
5use crate::state::{
6    CastProfile, CombatInstance, CombatProgram, LocalAuraIdx, LocalSpellIdx, RotationSpellBinding,
7    SpellData,
8};
9
10/// Requested and effective cast identities resolved once for one eligibility decision.
11#[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}