Skip to main content

wowlab_engine_combat/state/
casting.rs

1use wowlab_engine_domain::dbc::SpellSchoolMask;
2use wowlab_types::sim::{ActorId, SimTime, SpellIdx};
3
4#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5pub(crate) struct ActiveCast {
6    pub spell_id: SpellIdx,
7    pub school: SpellSchoolMask,
8    pub ends_at: SimTime,
9    pub interruptible: bool,
10    pub pushback_count: u8,
11    pub empower_rank: u8,
12    pub target: Option<wowlab_types::sim::EnemyIdx>,
13}
14
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16pub(crate) struct SchoolLockout {
17    pub schools: SpellSchoolMask,
18    pub expires_at: SimTime,
19}
20
21#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
22#[non_exhaustive]
23pub enum MovementState {
24    #[default]
25    Stationary,
26    Moving,
27}
28
29impl super::CombatState {
30    pub fn begin_actor_cast(
31        &mut self,
32        actor: ActorId,
33        spell_id: SpellIdx,
34        schools: SpellSchoolMask,
35        ends_at: SimTime,
36        interruptible: bool,
37    ) {
38        self.runtime.casting.active_casts.insert(
39            actor,
40            ActiveCast {
41                spell_id,
42                school: schools,
43                ends_at,
44                interruptible,
45                pushback_count: 0,
46                empower_rank: 0,
47                target: None,
48            },
49        );
50    }
51
52    pub fn schedule_movement_window(
53        &mut self,
54        actor: ActorId,
55        starts_at: SimTime,
56        duration: SimTime,
57    ) {
58        let generation = self
59            .runtime
60            .control
61            .movement_generation
62            .entry(actor)
63            .and_modify(|value| *value = value.wrapping_add(1))
64            .or_insert(1);
65        let generation = *generation;
66
67        self.schedule(wowlab_engine_ports::Event::ActorMovement {
68            t: starts_at,
69            actor,
70            moving: true,
71            generation,
72        });
73        self.schedule(wowlab_engine_ports::Event::ActorMovement {
74            t: starts_at.saturating_add(duration),
75            actor,
76            moving: false,
77            generation,
78        });
79    }
80
81    #[must_use]
82    pub fn movement_state(&self, actor: ActorId) -> MovementState {
83        self.runtime
84            .control
85            .movement
86            .get(&actor)
87            .copied()
88            .unwrap_or_default()
89    }
90
91    #[must_use]
92    pub fn school_locked(&self, actor: ActorId, schools: SpellSchoolMask, now: SimTime) -> bool {
93        self.runtime
94            .control
95            .school_lockouts
96            .get(&actor)
97            .is_some_and(|lockout| lockout.expires_at > now && lockout.schools.intersects(schools))
98    }
99
100    pub(crate) fn begin_player_cast(
101        &mut self,
102        spell_id: SpellIdx,
103        schools: SpellSchoolMask,
104        ends_at: SimTime,
105        empower_rank: u8,
106        target: wowlab_types::sim::EnemyIdx,
107    ) {
108        self.begin_actor_cast(ActorId::Player, spell_id, schools, ends_at, true);
109
110        if let Some(active) = self.runtime.casting.active_casts.get_mut(&ActorId::Player) {
111            active.empower_rank = empower_rank;
112            active.target = Some(target);
113        }
114    }
115}