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 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}