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::CombatState;
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 state: &'a mut CombatState,
41 event: OverkillAbsorbEvent,
42}
43
44impl<'a> OverkillAbsorbScriptContext<'a> {
45 pub(crate) fn new(state: &'a mut CombatState, event: OverkillAbsorbEvent) -> Self {
46 Self { state, event }
47 }
48
49 #[must_use]
51 pub const fn event(&self) -> OverkillAbsorbEvent {
52 self.event
53 }
54
55 #[must_use]
57 pub fn cooldown_ready(&self) -> bool {
58 self.state
59 .runtime
60 .pools
61 .overkill_absorb_ready_at
62 .is_ready(&self.event.aura, self.event.at)
63 }
64
65 pub fn start_cooldown(&mut self, duration: SimTime) -> SimTime {
67 self.state.runtime.pools.overkill_absorb_ready_at.start_for(
68 self.event.aura,
69 self.event.at,
70 duration,
71 )
72 }
73
74 #[must_use]
76 pub fn friendly_actor_health(&self, actor: ActorId) -> Option<f64> {
77 self.state.friendly_actor_health(actor)
78 }
79
80 #[must_use]
82 pub fn spec_runtime<T>(&self) -> Option<&T>
83 where
84 T: SpecRuntime,
85 {
86 self.state.spec_runtime::<T>()
87 }
88
89 pub fn spec_runtime_mut<T>(&mut self) -> Option<&mut T>
91 where
92 T: SpecRuntime,
93 {
94 self.state.spec_runtime_mut::<T>()
95 }
96}
97
98pub(crate) type OverkillAbsorbScriptFn =
100 fn(&mut OverkillAbsorbScriptContext<'_>) -> OverkillAbsorbDecision;
101
102#[derive(Clone, Copy, Debug)]
104pub struct AbsorbEffect {
105 pub amount: f64,
106 pub effect_index: u8,
107 pub schools: SpellSchoolMask,
108 pub kind: AbsorbKind,
109 pub high_priority: bool,
110 pub overkill_script: Option<OverkillAbsorbScriptFn>,
111 pub priority: i32,
113}
114
115#[derive(Clone, Copy, Debug)]
117pub(crate) struct ActiveAbsorb {
118 pub key: AuraKey,
119 pub kind: AbsorbKind,
120 pub schools: SpellSchoolMask,
121 pub remaining: f64,
122 pub high_priority: bool,
123 pub overkill_script: Option<OverkillAbsorbScriptFn>,
124 pub priority: i32,
125 pub application_order: u64,
126}
127
128#[derive(Clone, Copy, Debug, Eq, PartialEq)]
130pub struct IncomingImmunityWindow {
131 pub actor: ActorId,
132 pub schools: SpellSchoolMask,
133 pub mechanic_mask: u64,
134 pub until: SimTime,
135}
136
137#[derive(Clone, Copy, Debug, PartialEq)]
139pub(crate) struct HealthPool {
140 current: f64,
141 maximum: f64,
142 fraction_when_unknown: f64,
143}
144
145impl HealthPool {
146 #[must_use]
147 pub(crate) fn full(maximum: f64) -> Self {
148 let maximum = maximum.max(0.0);
149
150 Self {
151 current: maximum,
152 maximum,
153 fraction_when_unknown: 1.0,
154 }
155 }
156
157 #[must_use]
158 pub(crate) const fn current(self) -> f64 {
159 self.current
160 }
161
162 #[must_use]
163 pub(crate) const fn maximum(self) -> f64 {
164 self.maximum
165 }
166
167 #[must_use]
168 pub(crate) fn fraction(self) -> f64 {
169 if self.maximum > 0.0 {
170 self.current / self.maximum
171 } else {
172 self.fraction_when_unknown
173 }
174 }
175
176 #[must_use]
177 pub(crate) fn is_alive(self) -> bool {
178 self.maximum <= 0.0 || self.current > 0.0
179 }
180
181 pub(crate) fn reset(&mut self) {
182 self.current = self.maximum;
183 self.fraction_when_unknown = 1.0;
184 }
185
186 pub(crate) fn set_maximum_preserve_fraction(&mut self, maximum: f64) {
187 let fraction = self.fraction();
188
189 self.maximum = maximum.max(0.0);
190 self.current = self.maximum * fraction;
191 }
192
193 pub(crate) fn set_maximum_clamp_current(&mut self, maximum: f64) {
194 self.maximum = maximum.max(0.0);
195 self.current = self.current.min(self.maximum);
196 }
197
198 pub(crate) fn set_fraction(&mut self, fraction: f64) -> f64 {
199 let previous = self.fraction();
200
201 let fraction = fraction.clamp(0.0, 1.0);
202
203 self.current = self.maximum * fraction;
204 self.fraction_when_unknown = fraction;
205
206 previous
207 }
208
209 pub(crate) fn apply_damage(&mut self, amount: f64) -> f64 {
211 let amount = amount.max(0.0).min(self.current);
212
213 self.current -= amount;
214
215 amount
216 }
217
218 pub(crate) fn transition_to_dead(&mut self) {
219 self.current = 0.0;
220 self.fraction_when_unknown = 0.0;
221 }
222
223 pub(crate) fn apply_heal(&mut self, amount: f64) -> f64 {
225 let amount = amount.max(0.0).min((self.maximum - self.current).max(0.0));
226
227 self.current += amount;
228
229 amount
230 }
231}
232
233#[cfg(test)]
234mod tests;