wowlab_engine_combat/state/defs/
guardians.rs1use wowlab_types::sim::{ActorId, EnemyIdx, PetIdx};
4
5use crate::{
6 context::HookCtx,
7 state::{PetOwnerCoefficients, PetStatInheritance},
8};
9
10pub type GuardianActionFn = fn(&mut HookCtx<'_>, GuardianEvent);
11
12pub(crate) const MAX_GUARDIAN_ABILITIES: usize = 8;
13
14fn empty_guardian_action(_ctx: &mut HookCtx<'_>, _event: GuardianEvent) {}
15
16#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
18#[must_use]
19pub struct GuardianHandle(pub(crate) u32, pub(crate) u32);
20
21impl GuardianHandle {
22 pub const fn new(slot: u32, generation: u32) -> Self {
23 Self(slot, generation)
24 }
25
26 #[must_use]
27 pub const fn as_u32(&self) -> u32 {
28 self.0
29 }
30
31 #[must_use]
32 pub const fn generation(&self) -> u32 {
33 self.1
34 }
35
36 pub(crate) fn actor(self) -> Option<ActorId> {
37 PetIdx::from_guardian_slot(self.0 as usize).map(ActorId::Pet)
38 }
39}
40
41#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
43#[error("temporary guardian capacity of {capacity} actors is exhausted")]
44pub struct GuardianSummonError {
45 capacity: usize,
46}
47
48impl GuardianSummonError {
49 pub(crate) const fn saturated() -> Self {
50 Self {
51 capacity: PetIdx::MAX - 1,
52 }
53 }
54
55 #[must_use]
56 pub const fn capacity(&self) -> usize {
57 self.capacity
58 }
59}
60
61#[derive(Clone, Copy, Debug, Eq, PartialEq)]
63#[must_use]
64pub struct GuardianEvent {
65 pub handle: GuardianHandle,
66 pub tag: u32,
67 pub ability_index: usize,
68 pub action_index: u16,
69 pub spawned_at: wowlab_types::sim::SimTime,
70 pub expires_at: wowlab_types::sim::SimTime,
71}
72
73#[derive(Clone, Copy, Debug)]
75#[must_use]
76#[expect(
77 clippy::struct_excessive_bools,
78 reason = "guardian cadence, lifetime, and scheduler ownership are independent encounter capabilities"
79)]
80pub struct GuardianAbility {
81 pub(crate) first_action_delay_ms: u32,
82 pub(crate) action_interval_ms: u32,
83 pub(crate) hasted_first_action: bool,
84 pub(crate) hasted_action_interval: bool,
85 pub(crate) max_actions: u16,
86 pub(crate) ends_guardian: bool,
87 pub(crate) self_scheduled: bool,
88 pub(crate) action: GuardianActionFn,
89}
90
91impl GuardianAbility {
92 const EMPTY: Self = Self {
93 first_action_delay_ms: 0,
94 action_interval_ms: 0,
95 hasted_first_action: false,
96 hasted_action_interval: false,
97 max_actions: 0,
98 ends_guardian: false,
99 self_scheduled: false,
100 action: empty_guardian_action,
101 };
102
103 pub const fn new(action_interval_ms: u32, action: GuardianActionFn) -> Self {
104 Self {
105 first_action_delay_ms: 0,
106 action_interval_ms,
107 hasted_first_action: false,
108 hasted_action_interval: false,
109 max_actions: 0,
110 ends_guardian: false,
111 self_scheduled: true,
112 action,
113 }
114 }
115
116 pub const fn on_demand(action: GuardianActionFn) -> Self {
119 let mut ability = Self::new(0, action);
120
121 ability.self_scheduled = false;
122
123 ability
124 }
125
126 pub const fn first_action_delay(mut self, delay_ms: u32) -> Self {
127 self.first_action_delay_ms = delay_ms;
128
129 self
130 }
131
132 pub const fn hasted_actions(mut self) -> Self {
133 self.hasted_first_action = true;
134 self.hasted_action_interval = true;
135
136 self
137 }
138
139 pub const fn hasted_action_interval(mut self) -> Self {
141 self.hasted_action_interval = true;
142
143 self
144 }
145
146 pub const fn max_actions(mut self, count: u16) -> Self {
148 self.max_actions = count;
149
150 self
151 }
152
153 pub const fn ends_guardian(mut self) -> Self {
155 self.ends_guardian = true;
156
157 self
158 }
159}
160
161#[derive(Clone, Copy, Debug)]
163#[must_use]
164pub struct GuardianSpec {
165 pub(crate) tag: u32,
166 pub(crate) npc_id: Option<u32>,
167 pub(crate) stat_inheritance: PetStatInheritance,
168 pub(crate) duration_ms: u32,
169 abilities: [GuardianAbility; MAX_GUARDIAN_ABILITIES],
170 ability_count: u8,
171 pub(crate) on_demise: Option<GuardianActionFn>,
172 pub(crate) owner_coefficients: PetOwnerCoefficients,
173}
174
175impl GuardianSpec {
176 pub const fn new(tag: u32, duration_ms: u32) -> Self {
177 Self {
178 tag,
179 npc_id: None,
180 stat_inheritance: PetStatInheritance::IDENTITY,
181 duration_ms,
182 abilities: [GuardianAbility::EMPTY; MAX_GUARDIAN_ABILITIES],
183 ability_count: 0,
184 on_demise: None,
185 owner_coefficients: PetOwnerCoefficients::new(),
186 }
187 }
188
189 pub const fn new_inheriting_owner(tag: u32, duration_ms: u32) -> Self {
190 Self::new(tag, duration_ms).inherit_owner_stats()
191 }
192
193 pub const fn npc_id(mut self, npc_id: u32) -> Self {
195 self.npc_id = Some(npc_id);
196
197 self
198 }
199
200 pub const fn stat_inheritance(mut self, inheritance: PetStatInheritance) -> Self {
202 self.stat_inheritance = inheritance;
203
204 self
205 }
206
207 pub const fn ability(mut self, ability: GuardianAbility) -> Self {
213 assert!(
214 (self.ability_count as usize) < MAX_GUARDIAN_ABILITIES,
215 "guardian ability count exceeds the fixed runtime bound"
216 );
217 self.abilities[self.ability_count as usize] = ability;
219 self.ability_count += 1;
220
221 self
222 }
223
224 pub const fn on_demise(mut self, hook: GuardianActionFn) -> Self {
225 self.on_demise = Some(hook);
226
227 self
228 }
229
230 pub const fn owner_coefficients(mut self, coefficients: PetOwnerCoefficients) -> Self {
231 self.owner_coefficients = coefficients;
232
233 self
234 }
235
236 pub const fn inherit_owner_stats(self) -> Self {
237 self.owner_coefficients(PetOwnerCoefficients::owner_identity())
238 }
239
240 pub(crate) fn abilities(&self) -> &[GuardianAbility] {
241 self.abilities
242 .get(..usize::from(self.ability_count))
243 .expect("guardian ability count is bounded at construction")
244 }
245
246 pub(crate) const fn ability_count(&self) -> usize {
247 self.ability_count as usize
248 }
249}
250
251#[derive(Clone, Copy, Debug)]
252pub(crate) struct GuardianAbilityState {
253 pub(crate) next_action_at: Option<wowlab_types::sim::SimTime>,
254 pub(crate) actions_executed: u16,
255}
256
257#[derive(Clone, Debug)]
258pub(crate) struct GuardianInstance {
259 pub(crate) handle: GuardianHandle,
260 pub(crate) spec: GuardianSpec,
261 pub(crate) spawned_at: wowlab_types::sim::SimTime,
262 pub(crate) expires_at: wowlab_types::sim::SimTime,
263 pub(crate) abilities: [GuardianAbilityState; MAX_GUARDIAN_ABILITIES],
264 pub(crate) source: ActorId,
265 pub(crate) target: EnemyIdx,
266 pub(crate) stat_snapshot: super::PetStatSnapshot,
267}
268
269impl GuardianInstance {
270 pub(crate) fn ability_states(&self) -> &[GuardianAbilityState] {
271 self.abilities
272 .get(..self.spec.ability_count())
273 .expect("guardian ability state count matches its bounded specification")
274 }
275}