wowlab_engine_combat/state/
health.rs1use wowlab_engine_domain::dbc::SpellSchoolMask;
2use wowlab_engine_ports::SpecRuntime;
3use wowlab_types::sim::{ActorId, AuraKey, SimTime};
4
5use super::CombatInstance;
6
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9#[non_exhaustive]
10pub enum AbsorbKind {
11 Damage,
12 Healing,
13 Overkill,
14}
15
16#[derive(Clone, Copy, Debug, PartialEq)]
18pub struct OverkillAbsorbEvent {
19 pub aura: AuraKey,
20 pub target: ActorId,
21 pub school: SpellSchoolMask,
22 pub incoming_damage: f64,
23 pub remaining_damage: f64,
24 pub current_health: f64,
25 pub maximum_health: f64,
26 pub at: SimTime,
27}
28
29#[derive(Clone, Copy, Debug, Default, PartialEq)]
31pub struct OverkillAbsorbDecision {
32 pub absorbed: f64,
33 pub death_fully_prevented: bool,
34}
35
36#[derive(Debug)]
38#[must_use]
39pub struct OverkillAbsorbScriptContext<'a> {
40 instance: &'a mut CombatInstance,
41 event: OverkillAbsorbEvent,
42 pool: usize,
43}
44
45impl<'a> OverkillAbsorbScriptContext<'a> {
46 pub(crate) fn new(
47 instance: &'a mut CombatInstance,
48 event: OverkillAbsorbEvent,
49 pool: usize,
50 ) -> Self {
51 Self {
52 instance,
53 event,
54 pool,
55 }
56 }
57
58 #[must_use]
60 pub const fn event(&self) -> OverkillAbsorbEvent {
61 self.event
62 }
63
64 #[must_use]
66 pub fn cooldown_ready(&self) -> bool {
67 self.instance
68 .runtime
69 .pools
70 .overkill_absorb_ready_at
71 .is_ready(self.pool, self.event.at)
72 }
73
74 pub fn start_cooldown(&mut self, duration: SimTime) -> SimTime {
76 self.instance
77 .runtime
78 .pools
79 .overkill_absorb_ready_at
80 .start_for(self.pool, self.event.at, duration)
81 }
82
83 #[must_use]
85 pub fn friendly_actor_health(&self, actor: ActorId) -> Option<f64> {
86 self.instance.friendly_actor_health(actor)
87 }
88
89 #[must_use]
91 pub fn spec_runtime<T>(&self) -> Option<&T>
92 where
93 T: SpecRuntime,
94 {
95 self.instance.spec_runtime::<T>()
96 }
97
98 pub fn spec_runtime_mut<T>(&mut self) -> Option<&mut T>
100 where
101 T: SpecRuntime,
102 {
103 self.instance.spec_runtime_mut::<T>()
104 }
105}
106
107pub(crate) type OverkillAbsorbScriptFn =
109 fn(&mut OverkillAbsorbScriptContext<'_>) -> OverkillAbsorbDecision;
110
111#[derive(Clone, Copy, Debug)]
113pub struct AbsorbEffect {
114 pub amount: f64,
115 pub effect_index: u8,
116 pub schools: SpellSchoolMask,
117 pub kind: AbsorbKind,
118 pub high_priority: bool,
119 pub overkill_script: Option<OverkillAbsorbScriptFn>,
120 pub priority: i32,
122}
123
124#[derive(Clone, Copy, Debug)]
126pub(crate) struct ActiveAbsorb {
127 pub key: AuraKey,
128 pub pool: usize,
129 pub kind: AbsorbKind,
130 pub schools: SpellSchoolMask,
131 pub remaining: f64,
132 pub high_priority: bool,
133 pub overkill_script: Option<OverkillAbsorbScriptFn>,
134 pub priority: i32,
135 pub application_order: u64,
136}
137
138#[derive(Clone, Copy, Debug, Eq, PartialEq)]
140pub struct IncomingImmunityWindow {
141 pub actor: ActorId,
142 pub schools: SpellSchoolMask,
143 pub mechanic_mask: u64,
144 pub until: SimTime,
145}
146
147#[derive(Clone, Copy, Debug, PartialEq)]
149pub(crate) struct HealthPool {
150 current: f64,
151 maximum: f64,
152 fraction_when_unknown: f64,
153}
154
155impl HealthPool {
156 #[must_use]
157 pub(crate) fn full(maximum: f64) -> Self {
158 let maximum = maximum.max(0.0);
159
160 Self {
161 current: maximum,
162 maximum,
163 fraction_when_unknown: 1.0,
164 }
165 }
166
167 #[must_use]
168 pub(crate) const fn current(self) -> f64 {
169 self.current
170 }
171
172 #[must_use]
173 pub(crate) const fn maximum(self) -> f64 {
174 self.maximum
175 }
176
177 #[must_use]
178 pub(crate) fn fraction(self) -> f64 {
179 if self.maximum > 0.0 {
180 self.current / self.maximum
181 } else {
182 self.fraction_when_unknown
183 }
184 }
185
186 #[must_use]
187 pub(crate) fn is_alive(self) -> bool {
188 self.maximum <= 0.0 || self.current > 0.0
189 }
190
191 pub(crate) fn reset(&mut self) {
192 self.current = self.maximum;
193 self.fraction_when_unknown = 1.0;
194 }
195
196 pub(crate) fn set_maximum_preserve_fraction(&mut self, maximum: f64) {
197 let fraction = self.fraction();
198
199 self.maximum = maximum.max(0.0);
200 self.current = self.maximum * fraction;
201 }
202
203 pub(crate) fn set_maximum_clamp_current(&mut self, maximum: f64) {
204 self.maximum = maximum.max(0.0);
205 self.current = self.current.min(self.maximum);
206 }
207
208 pub(crate) fn set_fraction(&mut self, fraction: f64) -> f64 {
209 let previous = self.fraction();
210
211 let fraction = fraction.clamp(0.0, 1.0);
212
213 self.current = self.maximum * fraction;
214 self.fraction_when_unknown = fraction;
215
216 previous
217 }
218
219 pub(crate) fn apply_damage(&mut self, amount: f64) -> f64 {
221 let amount = amount.max(0.0).min(self.current);
222
223 self.current -= amount;
224
225 amount
226 }
227
228 pub(crate) fn transition_to_dead(&mut self) {
229 self.current = 0.0;
230 self.fraction_when_unknown = 0.0;
231 }
232
233 pub(crate) fn apply_heal(&mut self, amount: f64) -> f64 {
235 let amount = amount.max(0.0).min((self.maximum - self.current).max(0.0));
236
237 self.current += amount;
238
239 amount
240 }
241}
242
243#[cfg(test)]
244mod tests;