chunkedge_entity/
active_status_effects.rs

1use bevy_ecs::prelude::*;
2use chunkedge_protocol::status_effects::StatusEffect;
3use indexmap::IndexMap;
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: i32,
311    /// The initial duration of the effect in ticks.
312    /// If `None`, the effect is infinite.
313    /// (encoded as -1 if infinite)
314    ///
315    /// # Default Value
316    /// Some(600) (30 seconds)
317    initial_duration: Option<i32>,
318    /// The amount of ticks the effect has been active.
319    ///
320    /// # Default Value
321    /// 0
322    active_ticks: i32,
323    /// # Default Value
324    /// false
325    ambient: bool,
326    /// # Default Value
327    /// true
328    show_particles: bool,
329    /// # Default Value
330    /// true
331    show_icon: bool,
332}
333
334impl ActiveStatusEffect {
335    /// Creates a new [`ActiveStatusEffect`].
336    pub fn from_effect(effect: StatusEffect) -> Self {
337        Self {
338            effect,
339            amplifier: 0,
340            initial_duration: Some(600),
341            active_ticks: 0,
342            ambient: false,
343            show_particles: true,
344            show_icon: true,
345        }
346    }
347
348    /// Sets the amplifier of the [`ActiveStatusEffect`].
349    pub fn with_amplifier(mut self, amplifier: i32) -> Self {
350        self.amplifier = amplifier;
351        self
352    }
353
354    /// Sets the duration of the [`ActiveStatusEffect`] in ticks.
355    pub fn with_duration(mut self, duration: i32) -> Self {
356        self.initial_duration = Some(duration);
357        self
358    }
359
360    /// Sets the duration of the [`ActiveStatusEffect`] in seconds.
361    pub fn with_duration_seconds(mut self, duration: f32) -> Self {
362        self.initial_duration = Some((duration * 20.0).round() as i32);
363        self
364    }
365
366    /// Sets the duration of the [`ActiveStatusEffect`] to infinite.
367    pub fn with_infinite(mut self) -> Self {
368        self.initial_duration = None;
369        self
370    }
371
372    /// Sets whether the [`ActiveStatusEffect`] is ambient.
373    pub fn with_ambient(mut self, ambient: bool) -> Self {
374        self.ambient = ambient;
375        self
376    }
377
378    /// Sets whether the [`ActiveStatusEffect`] shows particles.
379    pub fn with_show_particles(mut self, show_particles: bool) -> Self {
380        self.show_particles = show_particles;
381        self
382    }
383
384    /// Sets whether the [`ActiveStatusEffect`] shows an icon.
385    pub fn with_show_icon(mut self, show_icon: bool) -> Self {
386        self.show_icon = show_icon;
387        self
388    }
389
390    /// Increments the active ticks of the [`ActiveStatusEffect`] by one.
391    pub fn increment_active_ticks(&mut self) {
392        self.active_ticks += 1;
393    }
394
395    /// Returns the [`StatusEffect`] of the [`ActiveStatusEffect`].
396    pub fn status_effect(&self) -> StatusEffect {
397        self.effect
398    }
399
400    /// Returns the amplifier of the [`ActiveStatusEffect`].
401    pub fn amplifier(&self) -> i32 {
402        self.amplifier
403    }
404
405    /// Returns the initial duration of the [`ActiveStatusEffect`] in ticks.
406    /// Returns `None` if the [`ActiveStatusEffect`] is infinite.
407    pub fn initial_duration(&self) -> Option<i32> {
408        self.initial_duration
409    }
410
411    /// Returns the remaining duration of the [`ActiveStatusEffect`] in ticks.
412    /// Returns `None` if the [`ActiveStatusEffect`] is infinite.
413    pub fn remaining_duration(&self) -> Option<i32> {
414        self.initial_duration
415            .map(|duration| duration - self.active_ticks)
416    }
417
418    /// Returns the active ticks of the [`ActiveStatusEffect`].
419    pub fn active_ticks(&self) -> i32 {
420        self.active_ticks
421    }
422
423    /// Returns true if the [`ActiveStatusEffect`] is ambient.
424    pub fn ambient(&self) -> bool {
425        self.ambient
426    }
427
428    /// Returns true if the [`ActiveStatusEffect`] shows particles.
429    pub fn show_particles(&self) -> bool {
430        self.show_particles
431    }
432
433    /// Returns true if the [`ActiveStatusEffect`] shows an icon.
434    pub fn show_icon(&self) -> bool {
435        self.show_icon
436    }
437
438    /// Returns true if the [`ActiveStatusEffect`] has expired or if it is
439    /// instant.
440    pub fn expired(&self) -> bool {
441        self.status_effect().instant()
442            || self
443                .remaining_duration()
444                .is_some_and(|duration| duration <= 0)
445    }
446}
447
448#[cfg(test)]
449mod test {
450    use super::*;
451
452    #[test]
453    fn test_apply_effect() {
454        let mut effects = ActiveStatusEffects::default();
455
456        let effect = ActiveStatusEffect::from_effect(StatusEffect::Speed).with_amplifier(1);
457        let effect2 = ActiveStatusEffect::from_effect(StatusEffect::Speed).with_amplifier(2);
458
459        let effect3 = ActiveStatusEffect::from_effect(StatusEffect::Strength).with_amplifier(1);
460        let effect4 = ActiveStatusEffect::from_effect(StatusEffect::Strength).with_amplifier(2);
461
462        effects.apply(effect.clone());
463        effects.apply_changes();
464        assert_eq!(
465            effects.get_all_effect(StatusEffect::Speed),
466            Some(&vec![effect.clone()])
467        );
468
469        effects.apply(effect2.clone());
470        effects.apply_changes();
471        assert_eq!(
472            effects.get_all_effect(StatusEffect::Speed),
473            Some(&vec![effect2.clone()])
474        );
475
476        effects.apply(effect3.clone());
477        effects.apply_changes();
478        assert_eq!(
479            effects.get_all_effect(StatusEffect::Strength),
480            Some(&vec![effect3.clone()])
481        );
482
483        effects.apply(effect4.clone());
484        effects.apply_changes();
485        assert_eq!(
486            effects.get_all_effect(StatusEffect::Strength),
487            Some(&vec![effect4.clone()])
488        );
489    }
490
491    #[test]
492    fn test_apply_effect_duration() {
493        let mut effects = ActiveStatusEffects::default();
494
495        let effect = ActiveStatusEffect::from_effect(StatusEffect::Speed)
496            .with_amplifier(1)
497            .with_duration(100);
498        let effect2 = ActiveStatusEffect::from_effect(StatusEffect::Speed)
499            .with_amplifier(1)
500            .with_duration(200);
501        let effect3 = ActiveStatusEffect::from_effect(StatusEffect::Speed)
502            .with_amplifier(0)
503            .with_duration(300);
504
505        effects.apply(effect.clone());
506        effects.apply_changes();
507        assert_eq!(
508            effects.get_all_effect(StatusEffect::Speed),
509            Some(&vec![effect.clone()])
510        );
511
512        effects.apply(effect2.clone());
513        effects.apply_changes();
514        assert_eq!(
515            effects.get_all_effect(StatusEffect::Speed),
516            Some(&vec![effect2.clone()])
517        );
518
519        effects.apply(effect3.clone());
520        effects.apply_changes();
521        assert_eq!(
522            effects.get_all_effect(StatusEffect::Speed),
523            Some(&vec![effect2.clone(), effect3.clone()])
524        );
525    }
526}