valence_entity/
active_status_effects.rs

1use bevy_ecs::prelude::*;
2use indexmap::IndexMap;
3use valence_protocol::status_effects::StatusEffect;
4
5/// Represents a change in the [`ActiveStatusEffects`] of an [`Entity`].
6#[derive(Debug)]
7enum StatusEffectChange {
8    Apply(ActiveStatusEffect),
9    Replace(ActiveStatusEffect),
10    Remove(StatusEffect),
11    RemoveAll,
12    /// **For internal use only.**
13    #[doc(hidden)]
14    Expire(StatusEffect),
15}
16
17/// The result of a duration calculation for a status effect.
18pub enum DurationResult {
19    /// There are no effects of the given type.
20    NoEffects,
21    /// The effect has an infinite duration.
22    Infinite,
23    /// The effect has a finite duration, represented as an integer number of
24    /// ticks.
25    Finite(i32),
26}
27
28/// [`Component`] that stores the [`ActiveStatusEffect`]s of an [`Entity`].
29#[derive(Component, Default, Debug)]
30pub struct ActiveStatusEffects {
31    /// vec is always sorted in descending order of amplifier and ascending
32    /// order of duration.
33    current_effects: IndexMap<StatusEffect, Vec<ActiveStatusEffect>>,
34    changes: Vec<StatusEffectChange>,
35}
36
37// public API
38impl ActiveStatusEffects {
39    /// Applies a new [`ActiveStatusEffect`].
40    ///
41    /// If the [`ActiveStatusEffect`] is already active:
42    /// 1. if the new effect is the same as the old one and its duration is
43    ///    longer, it replaces the old effect. Otherwise, it does nothing.
44    /// 2. if the new effect is stronger than the old one:
45    ///    - if the new effect's duration is longer, it replaces the old effect.
46    ///    - if the new effect's duration is shorter, it overrides the old
47    /// 3. if the new effect is weaker than the old one and if the new effect's
48    ///    duration is longer, it will be overridden by the old effect until the
49    ///    old effect's duration is over.
50    pub fn apply(&mut self, effect: ActiveStatusEffect) {
51        self.changes.push(StatusEffectChange::Apply(effect));
52    }
53
54    /// Replace an existing [`ActiveStatusEffect`].
55    pub fn replace(&mut self, effect: ActiveStatusEffect) {
56        self.changes.push(StatusEffectChange::Replace(effect));
57    }
58
59    /// Removes an [`ActiveStatusEffect`].
60    pub fn remove(&mut self, effect: StatusEffect) {
61        self.changes.push(StatusEffectChange::Remove(effect));
62    }
63
64    /// Removes all [`ActiveStatusEffect`]s.
65    pub fn remove_all(&mut self) {
66        self.changes.push(StatusEffectChange::RemoveAll);
67    }
68
69    /// Returns true if there are no effects of the given type.
70    pub fn no_effect(&self, effect: StatusEffect) -> bool {
71        self.current_effects
72            .get(&effect)
73            .is_none_or(|effects| effects.is_empty())
74    }
75
76    /// Returns true if there is an effect of the given type.
77    pub fn has_effect(&self, effect: StatusEffect) -> bool {
78        self.current_effects
79            .get(&effect)
80            .is_some_and(|effects| !effects.is_empty())
81    }
82
83    /// Returns true if there are no effects.
84    pub fn no_effects(&self) -> bool {
85        self.current_effects.is_empty()
86    }
87
88    /// Returns true if there are any effects.
89    pub fn has_effects(&self) -> bool {
90        !self.current_effects.is_empty()
91    }
92
93    /// Returns the maximum duration of the given effect.
94    pub fn max_duration(&self, effect: StatusEffect) -> DurationResult {
95        let effects = self.current_effects.get(&effect);
96
97        match effects {
98            None => DurationResult::NoEffects,
99            Some(effects) => {
100                if let Some(effect) = effects.last() {
101                    match effect.remaining_duration() {
102                        None => DurationResult::Infinite,
103                        Some(duration) => DurationResult::Finite(duration),
104                    }
105                } else {
106                    DurationResult::NoEffects
107                }
108            }
109        }
110    }
111
112    /// Gets the current effect of the given type.
113    pub fn get_current_effect(&self, effect: StatusEffect) -> Option<&ActiveStatusEffect> {
114        self.current_effects
115            .get(&effect)
116            .and_then(|effects| effects.first())
117    }
118
119    /// Gets all the effects of the given type.
120    pub fn get_all_effect(&self, effect: StatusEffect) -> Option<&Vec<ActiveStatusEffect>> {
121        self.current_effects.get(&effect)
122    }
123
124    /// Gets all the current effects.
125    pub fn get_current_effects(&self) -> Vec<&ActiveStatusEffect> {
126        self.current_effects
127            .values()
128            .filter_map(|effects| effects.first())
129            .collect()
130    }
131
132    /// Gets all the effects.
133    pub fn get_all_effects(&self) -> &IndexMap<StatusEffect, Vec<ActiveStatusEffect>> {
134        &self.current_effects
135    }
136}
137
138// internal methods
139impl ActiveStatusEffects {
140    /// Applies an effect.
141    ///
142    /// The vec must always be sorted in descending order of amplifier and
143    /// ascending order of duration.
144    ///
145    /// Returns true if the effect was applied.
146    fn apply_effect(&mut self, effect: ActiveStatusEffect) -> bool {
147        let effects = self
148            .current_effects
149            .entry(effect.status_effect())
150            .or_default();
151
152        let duration = effect.remaining_duration();
153        let amplifier = effect.amplifier();
154
155        if let Some(index) = effects.iter().position(|e| e.amplifier() <= amplifier) {
156            // Found an effect with the same or a lower amplifier.
157
158            let active_status_effect = &effects[index];
159
160            if active_status_effect.remaining_duration() < duration
161                || active_status_effect.amplifier() < amplifier
162            {
163                // if its duration is shorter or its amplifier is lower, override it.
164                effects[index] = effect;
165
166                // Remove effects after the current one that have a lower
167                // duration.
168                let mut remaining_effects = effects.split_off(index + 1);
169                remaining_effects.retain(|e| e.remaining_duration() >= duration);
170                effects.append(&mut remaining_effects);
171                true
172            } else if active_status_effect.remaining_duration() > duration
173                && active_status_effect.amplifier() < amplifier
174            {
175                // if its duration is longer and its amplifier is lower, insert
176                // the new effect before it.
177                effects.insert(index, effect);
178                true
179            } else {
180                // if its duration is longer and its amplifier is higher, do
181                // nothing.
182                false
183            }
184        } else {
185            // Found no effect with an equal or lower amplifier.
186            // This means all effects have a higher amplifier or the vec is
187            // empty.
188
189            if let Some(last) = effects.last() {
190                // There is at least one effect with a higher amplifier.
191                if last.remaining_duration() < effect.remaining_duration() {
192                    // if its duration is shorter, we can insert it at the end.
193                    effects.push(effect);
194                    true
195                } else {
196                    // if its duration is longer, do nothing.
197                    false
198                }
199            } else {
200                // The vec is empty.
201                effects.push(effect);
202                true
203            }
204        }
205    }
206
207    /// Replaces an effect.
208    fn replace_effect(&mut self, effect: ActiveStatusEffect) {
209        self.current_effects
210            .insert(effect.status_effect(), vec![effect]);
211    }
212
213    /// Removes an effect.
214    fn remove_effect(&mut self, effect: StatusEffect) {
215        self.current_effects.swap_remove(&effect);
216    }
217
218    /// Removes all effects.
219    fn remove_all_effects(&mut self) {
220        self.current_effects.clear();
221    }
222
223    /// Removes the strongest effect of the given type, i.e., the first effect
224    fn remove_strongest_effect(&mut self, effect: StatusEffect) {
225        if let Some(effects) = self.current_effects.get_mut(&effect) {
226            effects.remove(0);
227        }
228    }
229
230    /// **For internal use only.**
231    ///
232    /// Increments the active tick of all effects by a tick.
233    #[doc(hidden)]
234    pub fn increment_active_ticks(&mut self) {
235        for effects in self.current_effects.values_mut() {
236            for effect in effects.iter_mut() {
237                effect.increment_active_ticks();
238
239                if effect.expired() {
240                    self.changes
241                        .push(StatusEffectChange::Expire(effect.status_effect()));
242                }
243            }
244        }
245    }
246
247    /// **For internal use only.**
248    ///
249    /// Applies all the changes.
250    ///
251    /// Returns a [`IndexMap`] of [`StatusEffect`]s that were updated or removed
252    /// and their previous values.
253    #[doc(hidden)]
254    pub fn apply_changes(&mut self) -> IndexMap<StatusEffect, Option<ActiveStatusEffect>> {
255        let current = self.current_effects.clone();
256        let find_current = |effect: StatusEffect| {
257            current
258                .iter()
259                .find(|e| *e.0 == effect)
260                .map(|e| e.1.first().cloned())?
261        };
262        let mut updated_effects = IndexMap::new();
263
264        for change in std::mem::take(&mut self.changes) {
265            match change {
266                StatusEffectChange::Apply(effect) => {
267                    let value = effect.status_effect();
268                    if self.apply_effect(effect) {
269                        updated_effects
270                            .entry(value)
271                            .or_insert_with(|| find_current(value));
272                    }
273                }
274                StatusEffectChange::Replace(effect) => {
275                    let value = effect.status_effect();
276                    updated_effects
277                        .entry(value)
278                        .or_insert_with(|| find_current(value));
279                    self.replace_effect(effect);
280                }
281                StatusEffectChange::Remove(effect) => {
282                    self.remove_effect(effect);
283                    updated_effects.insert(effect, find_current(effect));
284                }
285                StatusEffectChange::RemoveAll => {
286                    self.remove_all_effects();
287                    for (status, effects) in &current {
288                        if let Some(effect) = effects.first() {
289                            updated_effects.insert(*status, Some(effect.clone()));
290                        }
291                    }
292                }
293                StatusEffectChange::Expire(effect) => {
294                    self.remove_strongest_effect(effect);
295                    updated_effects.insert(effect, find_current(effect));
296                }
297            }
298        }
299
300        updated_effects
301    }
302}
303
304/// Represents an active status effect.
305#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
306pub struct ActiveStatusEffect {
307    effect: StatusEffect,
308    /// # Default Value
309    /// 0
310    amplifier: u8,
311    /// The initial duration of the effect in ticks.
312    /// If `None`, the effect is infinite.
313    ///
314    /// # Default Value
315    /// Some(600) (30 seconds)
316    initial_duration: Option<i32>,
317    /// The amount of ticks the effect has been active.
318    ///
319    /// # Default Value
320    /// 0
321    active_ticks: i32,
322    /// # Default Value
323    /// false
324    ambient: bool,
325    /// # Default Value
326    /// true
327    show_particles: bool,
328    /// # Default Value
329    /// true
330    show_icon: bool,
331}
332
333impl ActiveStatusEffect {
334    /// Creates a new [`ActiveStatusEffect`].
335    pub fn from_effect(effect: StatusEffect) -> Self {
336        Self {
337            effect,
338            amplifier: 0,
339            initial_duration: Some(600),
340            active_ticks: 0,
341            ambient: false,
342            show_particles: true,
343            show_icon: true,
344        }
345    }
346
347    /// Sets the amplifier of the [`ActiveStatusEffect`].
348    pub fn with_amplifier(mut self, amplifier: u8) -> Self {
349        self.amplifier = amplifier;
350        self
351    }
352
353    /// Sets the duration of the [`ActiveStatusEffect`] in ticks.
354    pub fn with_duration(mut self, duration: i32) -> Self {
355        self.initial_duration = Some(duration);
356        self
357    }
358
359    /// Sets the duration of the [`ActiveStatusEffect`] in seconds.
360    pub fn with_duration_seconds(mut self, duration: f32) -> Self {
361        self.initial_duration = Some((duration * 20.0).round() as i32);
362        self
363    }
364
365    /// Sets the duration of the [`ActiveStatusEffect`] to infinite.
366    pub fn with_infinite(mut self) -> Self {
367        self.initial_duration = None;
368        self
369    }
370
371    /// Sets whether the [`ActiveStatusEffect`] is ambient.
372    pub fn with_ambient(mut self, ambient: bool) -> Self {
373        self.ambient = ambient;
374        self
375    }
376
377    /// Sets whether the [`ActiveStatusEffect`] shows particles.
378    pub fn with_show_particles(mut self, show_particles: bool) -> Self {
379        self.show_particles = show_particles;
380        self
381    }
382
383    /// Sets whether the [`ActiveStatusEffect`] shows an icon.
384    pub fn with_show_icon(mut self, show_icon: bool) -> Self {
385        self.show_icon = show_icon;
386        self
387    }
388
389    /// Increments the active ticks of the [`ActiveStatusEffect`] by one.
390    pub fn increment_active_ticks(&mut self) {
391        self.active_ticks += 1;
392    }
393
394    /// Returns the [`StatusEffect`] of the [`ActiveStatusEffect`].
395    pub fn status_effect(&self) -> StatusEffect {
396        self.effect
397    }
398
399    /// Returns the amplifier of the [`ActiveStatusEffect`].
400    pub fn amplifier(&self) -> u8 {
401        self.amplifier
402    }
403
404    /// Returns the initial duration of the [`ActiveStatusEffect`] in ticks.
405    /// Returns `None` if the [`ActiveStatusEffect`] is infinite.
406    pub fn initial_duration(&self) -> Option<i32> {
407        self.initial_duration
408    }
409
410    /// Returns the remaining duration of the [`ActiveStatusEffect`] in ticks.
411    /// Returns `None` if the [`ActiveStatusEffect`] is infinite.
412    pub fn remaining_duration(&self) -> Option<i32> {
413        self.initial_duration
414            .map(|duration| duration - self.active_ticks)
415    }
416
417    /// Returns the active ticks of the [`ActiveStatusEffect`].
418    pub fn active_ticks(&self) -> i32 {
419        self.active_ticks
420    }
421
422    /// Returns true if the [`ActiveStatusEffect`] is ambient.
423    pub fn ambient(&self) -> bool {
424        self.ambient
425    }
426
427    /// Returns true if the [`ActiveStatusEffect`] shows particles.
428    pub fn show_particles(&self) -> bool {
429        self.show_particles
430    }
431
432    /// Returns true if the [`ActiveStatusEffect`] shows an icon.
433    pub fn show_icon(&self) -> bool {
434        self.show_icon
435    }
436
437    /// Returns true if the [`ActiveStatusEffect`] has expired or if it is
438    /// instant.
439    pub fn expired(&self) -> bool {
440        self.status_effect().instant()
441            || self
442                .remaining_duration()
443                .is_some_and(|duration| duration <= 0)
444    }
445}
446
447#[cfg(test)]
448mod test {
449    use super::*;
450
451    #[test]
452    fn test_apply_effect() {
453        let mut effects = ActiveStatusEffects::default();
454
455        let effect = ActiveStatusEffect::from_effect(StatusEffect::Speed).with_amplifier(1);
456        let effect2 = ActiveStatusEffect::from_effect(StatusEffect::Speed).with_amplifier(2);
457
458        let effect3 = ActiveStatusEffect::from_effect(StatusEffect::Strength).with_amplifier(1);
459        let effect4 = ActiveStatusEffect::from_effect(StatusEffect::Strength).with_amplifier(2);
460
461        effects.apply(effect.clone());
462        effects.apply_changes();
463        assert_eq!(
464            effects.get_all_effect(StatusEffect::Speed),
465            Some(&vec![effect.clone()])
466        );
467
468        effects.apply(effect2.clone());
469        effects.apply_changes();
470        assert_eq!(
471            effects.get_all_effect(StatusEffect::Speed),
472            Some(&vec![effect2.clone()])
473        );
474
475        effects.apply(effect3.clone());
476        effects.apply_changes();
477        assert_eq!(
478            effects.get_all_effect(StatusEffect::Strength),
479            Some(&vec![effect3.clone()])
480        );
481
482        effects.apply(effect4.clone());
483        effects.apply_changes();
484        assert_eq!(
485            effects.get_all_effect(StatusEffect::Strength),
486            Some(&vec![effect4.clone()])
487        );
488    }
489
490    #[test]
491    fn test_apply_effect_duration() {
492        let mut effects = ActiveStatusEffects::default();
493
494        let effect = ActiveStatusEffect::from_effect(StatusEffect::Speed)
495            .with_amplifier(1)
496            .with_duration(100);
497        let effect2 = ActiveStatusEffect::from_effect(StatusEffect::Speed)
498            .with_amplifier(1)
499            .with_duration(200);
500        let effect3 = ActiveStatusEffect::from_effect(StatusEffect::Speed)
501            .with_amplifier(0)
502            .with_duration(300);
503
504        effects.apply(effect.clone());
505        effects.apply_changes();
506        assert_eq!(
507            effects.get_all_effect(StatusEffect::Speed),
508            Some(&vec![effect.clone()])
509        );
510
511        effects.apply(effect2.clone());
512        effects.apply_changes();
513        assert_eq!(
514            effects.get_all_effect(StatusEffect::Speed),
515            Some(&vec![effect2.clone()])
516        );
517
518        effects.apply(effect3.clone());
519        effects.apply_changes();
520        assert_eq!(
521            effects.get_all_effect(StatusEffect::Speed),
522            Some(&vec![effect2.clone(), effect3.clone()])
523        );
524    }
525}