Skip to main content

wowlab_engine_combat/state/combat_state/
health.rs

1use super::{ActorId, CombatState, HealthPool, IncomingImmunityWindow};
2
3impl CombatState {
4    /// Current absolute health for a friendly actor.
5    #[must_use]
6    pub fn friendly_actor_health(&self, actor: ActorId) -> Option<f64> {
7        match actor {
8            ActorId::External | ActorId::Enemy(_) => None,
9            ActorId::Player => Some(self.runtime.health.player_health.current()),
10            ActorId::Pet(pet) => self
11                .runtime
12                .health
13                .pet_health
14                .get(pet.as_usize())
15                .and_then(Option::as_ref)
16                .map(|health| health.current()),
17        }
18    }
19
20    /// Maximum absolute health for a friendly actor.
21    #[must_use]
22    pub fn friendly_actor_max_health(&self, actor: ActorId) -> Option<f64> {
23        match actor {
24            ActorId::External | ActorId::Enemy(_) => None,
25            ActorId::Player => Some(self.runtime.health.player_health.maximum()),
26            ActorId::Pet(pet) => self
27                .runtime
28                .health
29                .pet_health
30                .get(pet.as_usize())
31                .and_then(Option::as_ref)
32                .map(|health| health.maximum()),
33        }
34    }
35
36    #[must_use]
37    pub fn is_friendly_actor_alive(&self, actor: ActorId) -> bool {
38        match actor {
39            ActorId::External | ActorId::Enemy(_) => false,
40            ActorId::Player => self.runtime.health.player_health.is_alive(),
41            ActorId::Pet(pet) => self
42                .runtime
43                .health
44                .pet_health
45                .get(pet.as_usize())
46                .and_then(Option::as_ref)
47                .is_some_and(|health| health.is_alive()),
48        }
49    }
50
51    /// Adds a bounded friendly-actor immunity for encounter/raid-event orchestration.
52    pub fn grant_friendly_immunity_window(&mut self, window: IncomingImmunityWindow) {
53        if !matches!(window.actor, ActorId::Enemy(_)) {
54            self.runtime.health.immunity_windows.push(window);
55        }
56    }
57
58    pub(crate) fn set_friendly_actor_health_fraction(
59        &mut self,
60        actor: ActorId,
61        fraction: f64,
62    ) -> Option<f64> {
63        let fraction = fraction.clamp(0.0, 1.0);
64
65        match actor {
66            ActorId::External | ActorId::Enemy(_) => None,
67            ActorId::Player => Some(self.runtime.health.player_health.set_fraction(fraction)),
68            ActorId::Pet(pet) => self
69                .runtime
70                .health
71                .pet_health
72                .get_mut(pet.as_usize())
73                .and_then(Option::as_mut)
74                .map(|health| health.set_fraction(fraction)),
75        }
76    }
77
78    pub(crate) fn apply_friendly_actor_damage(
79        &mut self,
80        actor: ActorId,
81        amount: f64,
82    ) -> Option<f64> {
83        match actor {
84            ActorId::External | ActorId::Enemy(_) => None,
85            ActorId::Player => Some(self.runtime.health.player_health.apply_damage(amount)),
86            ActorId::Pet(pet) => self
87                .runtime
88                .health
89                .pet_health
90                .get_mut(pet.as_usize())
91                .and_then(Option::as_mut)
92                .map(|health| health.apply_damage(amount)),
93        }
94    }
95
96    pub(crate) fn transition_friendly_actor_to_dead(&mut self, actor: ActorId) -> bool {
97        let health = match actor {
98            ActorId::External | ActorId::Enemy(_) => return false,
99            ActorId::Player => &mut self.runtime.health.player_health,
100            ActorId::Pet(pet) => {
101                let Some(health) = self
102                    .runtime
103                    .health
104                    .pet_health
105                    .get_mut(pet.as_usize())
106                    .and_then(Option::as_mut)
107                else {
108                    return false;
109                };
110
111                health
112            }
113        };
114
115        health.transition_to_dead();
116
117        true
118    }
119
120    pub(crate) fn apply_friendly_actor_heal(&mut self, actor: ActorId, amount: f64) -> Option<f64> {
121        match actor {
122            ActorId::External | ActorId::Enemy(_) => None,
123            ActorId::Player => Some(self.runtime.health.player_health.apply_heal(amount)),
124            ActorId::Pet(pet) => self
125                .runtime
126                .health
127                .pet_health
128                .get_mut(pet.as_usize())
129                .and_then(Option::as_mut)
130                .map(|health| health.apply_heal(amount)),
131        }
132    }
133
134    pub(crate) fn activate_pet_health(&mut self, actor: ActorId, maximum: f64) -> bool {
135        let ActorId::Pet(pet) = actor else {
136            return false;
137        };
138        let index = pet.as_usize();
139
140        if self.runtime.health.pet_health.len() <= index {
141            self.runtime
142                .health
143                .pet_health
144                .resize_with(index.saturating_add(1), || None);
145        }
146
147        let Some(slot) = self.runtime.health.pet_health.get_mut(index) else {
148            return false;
149        };
150
151        if let Some(health) = slot {
152            health.set_maximum_preserve_fraction(maximum);
153        } else {
154            *slot = Some(HealthPool::full(maximum));
155        }
156
157        true
158    }
159
160    pub(crate) fn deactivate_pet_health(&mut self, actor: ActorId) -> bool {
161        let ActorId::Pet(pet) = actor else {
162            return false;
163        };
164
165        self.runtime
166            .health
167            .pet_health
168            .get_mut(pet.as_usize())
169            .is_some_and(|health| health.take().is_some())
170    }
171}