wowlab_engine_combat/state/
casting.rs1use 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}