Skip to main content

wowlab_engine_combat/state/defs/
guardians.rs

1//! Temporary-guardian definitions and runtime state.
2
3use wowlab_types::sim::{ActorId, EnemyIdx, PetIdx};
4
5use crate::{context::HookCtx, state::PetOwnerCoefficients};
6
7pub type GuardianActionFn = fn(&mut HookCtx<'_>, GuardianEvent);
8
9/// Stable handle for one temporary guardian instance during an iteration.
10#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
11#[must_use]
12pub struct GuardianHandle(pub(crate) u32, pub(crate) u32);
13
14impl GuardianHandle {
15    pub const fn new(slot: u32, generation: u32) -> Self {
16        Self(slot, generation)
17    }
18
19    #[must_use]
20    pub const fn as_u32(&self) -> u32 {
21        self.0
22    }
23
24    #[must_use]
25    pub const fn generation(&self) -> u32 {
26        self.1
27    }
28
29    pub(crate) fn actor(self) -> Option<ActorId> {
30        PetIdx::from_guardian_slot(self.0 as usize).map(ActorId::Pet)
31    }
32}
33
34/// Context supplied to a guardian action callback.
35#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36#[must_use]
37pub struct GuardianEvent {
38    pub handle: GuardianHandle,
39    pub tag: u32,
40    pub ability_index: usize,
41    pub action_index: u16,
42    pub spawned_at: wowlab_types::sim::SimTime,
43    pub expires_at: wowlab_types::sim::SimTime,
44}
45
46/// One independently scheduled ability owned by a temporary guardian.
47#[derive(Clone, Copy, Debug)]
48#[must_use]
49#[expect(
50    clippy::struct_excessive_bools,
51    reason = "guardian cadence, lifetime, and scheduler ownership are independent encounter capabilities"
52)]
53pub struct GuardianAbility {
54    pub(crate) first_action_delay_ms: u32,
55    pub(crate) action_interval_ms: u32,
56    pub(crate) hasted_first_action: bool,
57    pub(crate) hasted_action_interval: bool,
58    pub(crate) max_actions: u16,
59    pub(crate) ends_guardian: bool,
60    pub(crate) self_scheduled: bool,
61    pub(crate) action: GuardianActionFn,
62}
63
64impl GuardianAbility {
65    pub const fn new(action_interval_ms: u32, action: GuardianActionFn) -> Self {
66        Self {
67            first_action_delay_ms: 0,
68            action_interval_ms,
69            hasted_first_action: false,
70            hasted_action_interval: false,
71            max_actions: 0,
72            ends_guardian: false,
73            self_scheduled: true,
74            action,
75        }
76    }
77
78    /// An owner-driven ability that never self-schedules.
79    /// Dispatch it with [`crate::GuardianOps::command_guardians`].
80    pub const fn on_demand(action: GuardianActionFn) -> Self {
81        let mut ability = Self::new(0, action);
82
83        ability.self_scheduled = false;
84
85        ability
86    }
87
88    pub const fn first_action_delay(mut self, delay_ms: u32) -> Self {
89        self.first_action_delay_ms = delay_ms;
90
91        self
92    }
93
94    pub const fn hasted_actions(mut self) -> Self {
95        self.hasted_first_action = true;
96        self.hasted_action_interval = true;
97
98        self
99    }
100
101    /// Scale repeating action intervals with live haste while preserving a fixed initial delay.
102    pub const fn hasted_action_interval(mut self) -> Self {
103        self.hasted_action_interval = true;
104
105        self
106    }
107
108    /// Stop this ability after exactly `count` actions; zero means lifetime-only.
109    pub const fn max_actions(mut self, count: u16) -> Self {
110        self.max_actions = count;
111
112        self
113    }
114
115    /// Despawn the owning guardian when this bounded ability completes.
116    pub const fn ends_guardian(mut self) -> Self {
117        self.ends_guardian = true;
118
119        self
120    }
121}
122
123/// Definition used when a cast hook summons a temporary guardian.
124#[derive(Clone, Debug)]
125#[must_use]
126pub struct GuardianSpec {
127    pub(crate) tag: u32,
128    pub(crate) npc_id: Option<u32>,
129    pub(crate) duration_ms: u32,
130    pub(crate) abilities: Vec<GuardianAbility>,
131    pub(crate) on_demise: Option<GuardianActionFn>,
132    pub(crate) owner_coefficients: PetOwnerCoefficients,
133}
134
135impl GuardianSpec {
136    pub const fn new(tag: u32, duration_ms: u32) -> Self {
137        Self {
138            tag,
139            npc_id: None,
140            duration_ms,
141            abilities: Vec::new(),
142            on_demise: None,
143            owner_coefficients: PetOwnerCoefficients::new(),
144        }
145    }
146
147    pub const fn new_inheriting_owner(tag: u32, duration_ms: u32) -> Self {
148        Self::new(tag, duration_ms).inherit_owner_stats()
149    }
150
151    /// Assign the creature identity used by NPC-filtered companion modifiers.
152    pub const fn npc_id(mut self, npc_id: u32) -> Self {
153        self.npc_id = Some(npc_id);
154
155        self
156    }
157
158    pub fn ability(mut self, ability: GuardianAbility) -> Self {
159        self.abilities.push(ability);
160
161        self
162    }
163
164    pub const fn on_demise(mut self, hook: GuardianActionFn) -> Self {
165        self.on_demise = Some(hook);
166
167        self
168    }
169
170    pub const fn owner_coefficients(mut self, coefficients: PetOwnerCoefficients) -> Self {
171        self.owner_coefficients = coefficients;
172
173        self
174    }
175
176    pub const fn inherit_owner_stats(self) -> Self {
177        self.owner_coefficients(PetOwnerCoefficients::owner_identity())
178    }
179}
180
181#[derive(Clone, Copy, Debug)]
182pub(crate) struct GuardianAbilityState {
183    pub(crate) next_action_at: Option<wowlab_types::sim::SimTime>,
184    pub(crate) actions_executed: u16,
185}
186
187#[derive(Clone, Debug)]
188pub(crate) struct GuardianInstance {
189    pub(crate) handle: GuardianHandle,
190    pub(crate) spec: GuardianSpec,
191    pub(crate) spawned_at: wowlab_types::sim::SimTime,
192    pub(crate) expires_at: wowlab_types::sim::SimTime,
193    pub(crate) abilities: Vec<GuardianAbilityState>,
194    pub(crate) source: ActorId,
195    pub(crate) target: EnemyIdx,
196    pub(crate) stat_snapshot: super::PetStatSnapshot,
197}