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 action: Option<crate::state::program::LocalDamageActionIdx>,
8    pub school: SpellSchoolMask,
9    pub damage_pushback: bool,
10    pub ends_at: SimTime,
11    pub interruptible: bool,
12    pub pushback_count: u8,
13    pub empower_rank: u8,
14    pub target: Option<wowlab_types::sim::EnemyIdx>,
15}
16
17impl ActiveCast {
18    pub(crate) const fn new(spell_id: SpellIdx, school: SpellSchoolMask, ends_at: SimTime) -> Self {
19        Self {
20            spell_id,
21            action: None,
22            school,
23            damage_pushback: false,
24            ends_at,
25            interruptible: true,
26            pushback_count: 0,
27            empower_rank: 0,
28            target: None,
29        }
30    }
31
32    pub(crate) const fn with_action(
33        mut self,
34        action: crate::state::program::LocalDamageActionIdx,
35    ) -> Self {
36        self.action = Some(action);
37
38        self
39    }
40
41    pub(crate) const fn with_damage_pushback(mut self, enabled: bool) -> Self {
42        self.damage_pushback = enabled;
43
44        self
45    }
46
47    pub(crate) const fn with_empower(
48        mut self,
49        empower_rank: u8,
50        target: wowlab_types::sim::EnemyIdx,
51    ) -> Self {
52        self.empower_rank = empower_rank;
53        self.target = Some(target);
54
55        self
56    }
57}
58
59#[derive(Clone, Copy, Debug, Eq, PartialEq)]
60pub(crate) struct SchoolLockout {
61    pub schools: SpellSchoolMask,
62    pub expires_at: SimTime,
63}
64
65#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
66#[non_exhaustive]
67pub enum MovementState {
68    #[default]
69    Stationary,
70    Moving,
71}
72
73impl super::CombatInstance {
74    pub fn schedule_movement_window(
75        &mut self,
76        program: &super::CombatProgram,
77        actor: ActorId,
78        starts_at: SimTime,
79        duration: SimTime,
80    ) {
81        let generation = self.runtime.control.movement_generation.value_mut(actor);
82
83        *generation = generation.wrapping_add(1).max(1);
84        let generation = *generation;
85
86        self.schedule(wowlab_engine_ports::Event::ActorMovement {
87            t: starts_at,
88            movement: program.runtime_events.actor_movement(actor, generation),
89            moving: true,
90        });
91        self.schedule(wowlab_engine_ports::Event::ActorMovement {
92            t: starts_at.saturating_add(duration),
93            movement: program.runtime_events.actor_movement(actor, generation),
94            moving: false,
95        });
96    }
97
98    #[must_use]
99    pub fn movement_state(&self, actor: ActorId) -> MovementState {
100        self.runtime.control.movement.value(actor)
101    }
102
103    #[must_use]
104    pub fn school_locked(&self, actor: ActorId, schools: SpellSchoolMask, now: SimTime) -> bool {
105        self.school_lock_ready_at(actor, schools, now).is_some()
106    }
107
108    #[must_use]
109    pub(crate) fn school_lock_ready_at(
110        &self,
111        actor: ActorId,
112        schools: SpellSchoolMask,
113        now: SimTime,
114    ) -> Option<SimTime> {
115        self.runtime
116            .control
117            .school_lockouts
118            .get(actor)
119            .filter(|lockout| lockout.expires_at > now && lockout.schools.intersects(schools))
120            .map(|lockout| lockout.expires_at)
121    }
122
123    pub(crate) fn begin_actor_cast(&mut self, actor: ActorId, active: ActiveCast) {
124        self.runtime.casting.active_casts.insert(actor, active);
125    }
126}