1use bevy_ecs::prelude::*;
2use indexmap::IndexMap;
3use valence_protocol::status_effects::StatusEffect;
45/// 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)]
14Expire(StatusEffect),
15}
1617/// The result of a duration calculation for a status effect.
18pub enum DurationResult {
19/// There are no effects of the given type.
20NoEffects,
21/// The effect has an infinite duration.
22Infinite,
23/// The effect has a finite duration, represented as an integer number of
24 /// ticks.
25Finite(i32),
26}
2728/// [`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.
33current_effects: IndexMap<StatusEffect, Vec<ActiveStatusEffect>>,
34 changes: Vec<StatusEffectChange>,
35}
3637// 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.
50pub fn apply(&mut self, effect: ActiveStatusEffect) {
51self.changes.push(StatusEffectChange::Apply(effect));
52 }
5354/// Replace an existing [`ActiveStatusEffect`].
55pub fn replace(&mut self, effect: ActiveStatusEffect) {
56self.changes.push(StatusEffectChange::Replace(effect));
57 }
5859/// Removes an [`ActiveStatusEffect`].
60pub fn remove(&mut self, effect: StatusEffect) {
61self.changes.push(StatusEffectChange::Remove(effect));
62 }
6364/// Removes all [`ActiveStatusEffect`]s.
65pub fn remove_all(&mut self) {
66self.changes.push(StatusEffectChange::RemoveAll);
67 }
6869/// Returns true if there are no effects of the given type.
70pub fn no_effect(&self, effect: StatusEffect) -> bool {
71self.current_effects
72 .get(&effect)
73 .is_none_or(|effects| effects.is_empty())
74 }
7576/// Returns true if there is an effect of the given type.
77pub fn has_effect(&self, effect: StatusEffect) -> bool {
78self.current_effects
79 .get(&effect)
80 .is_some_and(|effects| !effects.is_empty())
81 }
8283/// Returns true if there are no effects.
84pub fn no_effects(&self) -> bool {
85self.current_effects.is_empty()
86 }
8788/// Returns true if there are any effects.
89pub fn has_effects(&self) -> bool {
90 !self.current_effects.is_empty()
91 }
9293/// Returns the maximum duration of the given effect.
94pub fn max_duration(&self, effect: StatusEffect) -> DurationResult {
95let effects = self.current_effects.get(&effect);
9697match effects {
98None => DurationResult::NoEffects,
99Some(effects) => {
100if let Some(effect) = effects.last() {
101match effect.remaining_duration() {
102None => DurationResult::Infinite,
103Some(duration) => DurationResult::Finite(duration),
104 }
105 } else {
106 DurationResult::NoEffects
107 }
108 }
109 }
110 }
111112/// Gets the current effect of the given type.
113pub fn get_current_effect(&self, effect: StatusEffect) -> Option<&ActiveStatusEffect> {
114self.current_effects
115 .get(&effect)
116 .and_then(|effects| effects.first())
117 }
118119/// Gets all the effects of the given type.
120pub fn get_all_effect(&self, effect: StatusEffect) -> Option<&Vec<ActiveStatusEffect>> {
121self.current_effects.get(&effect)
122 }
123124/// Gets all the current effects.
125pub fn get_current_effects(&self) -> Vec<&ActiveStatusEffect> {
126self.current_effects
127 .values()
128 .filter_map(|effects| effects.first())
129 .collect()
130 }
131132/// Gets all the effects.
133pub fn get_all_effects(&self) -> &IndexMap<StatusEffect, Vec<ActiveStatusEffect>> {
134&self.current_effects
135 }
136}
137138// 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.
146fn apply_effect(&mut self, effect: ActiveStatusEffect) -> bool {
147let effects = self
148.current_effects
149 .entry(effect.status_effect())
150 .or_default();
151152let duration = effect.remaining_duration();
153let amplifier = effect.amplifier();
154155if let Some(index) = effects.iter().position(|e| e.amplifier() <= amplifier) {
156// Found an effect with the same or a lower amplifier.
157158let active_status_effect = &effects[index];
159160if 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.
164effects[index] = effect;
165166// Remove effects after the current one that have a lower
167 // duration.
168let mut remaining_effects = effects.split_off(index + 1);
169 remaining_effects.retain(|e| e.remaining_duration() >= duration);
170 effects.append(&mut remaining_effects);
171true
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.
177effects.insert(index, effect);
178true
179} else {
180// if its duration is longer and its amplifier is higher, do
181 // nothing.
182false
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.
188189if let Some(last) = effects.last() {
190// There is at least one effect with a higher amplifier.
191if last.remaining_duration() < effect.remaining_duration() {
192// if its duration is shorter, we can insert it at the end.
193effects.push(effect);
194true
195} else {
196// if its duration is longer, do nothing.
197false
198}
199 } else {
200// The vec is empty.
201effects.push(effect);
202true
203}
204 }
205 }
206207/// Replaces an effect.
208fn replace_effect(&mut self, effect: ActiveStatusEffect) {
209self.current_effects
210 .insert(effect.status_effect(), vec![effect]);
211 }
212213/// Removes an effect.
214fn remove_effect(&mut self, effect: StatusEffect) {
215self.current_effects.swap_remove(&effect);
216 }
217218/// Removes all effects.
219fn remove_all_effects(&mut self) {
220self.current_effects.clear();
221 }
222223/// Removes the strongest effect of the given type, i.e., the first effect
224fn remove_strongest_effect(&mut self, effect: StatusEffect) {
225if let Some(effects) = self.current_effects.get_mut(&effect) {
226 effects.remove(0);
227 }
228 }
229230/// **For internal use only.**
231 ///
232 /// Increments the active tick of all effects by a tick.
233#[doc(hidden)]
234pub fn increment_active_ticks(&mut self) {
235for effects in self.current_effects.values_mut() {
236for effect in effects.iter_mut() {
237 effect.increment_active_ticks();
238239if effect.expired() {
240self.changes
241 .push(StatusEffectChange::Expire(effect.status_effect()));
242 }
243 }
244 }
245 }
246247/// **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)]
254pub fn apply_changes(&mut self) -> IndexMap<StatusEffect, Option<ActiveStatusEffect>> {
255let current = self.current_effects.clone();
256let find_current = |effect: StatusEffect| {
257 current
258 .iter()
259 .find(|e| *e.0 == effect)
260 .map(|e| e.1.first().cloned())?
261};
262let mut updated_effects = IndexMap::new();
263264for change in std::mem::take(&mut self.changes) {
265match change {
266 StatusEffectChange::Apply(effect) => {
267let value = effect.status_effect();
268if self.apply_effect(effect) {
269 updated_effects
270 .entry(value)
271 .or_insert_with(|| find_current(value));
272 }
273 }
274 StatusEffectChange::Replace(effect) => {
275let value = effect.status_effect();
276 updated_effects
277 .entry(value)
278 .or_insert_with(|| find_current(value));
279self.replace_effect(effect);
280 }
281 StatusEffectChange::Remove(effect) => {
282self.remove_effect(effect);
283 updated_effects.insert(effect, find_current(effect));
284 }
285 StatusEffectChange::RemoveAll => {
286self.remove_all_effects();
287for (status, effects) in ¤t {
288if let Some(effect) = effects.first() {
289 updated_effects.insert(*status, Some(effect.clone()));
290 }
291 }
292 }
293 StatusEffectChange::Expire(effect) => {
294self.remove_strongest_effect(effect);
295 updated_effects.insert(effect, find_current(effect));
296 }
297 }
298 }
299300 updated_effects
301 }
302}
303304/// 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
310amplifier: 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)
316initial_duration: Option<i32>,
317/// The amount of ticks the effect has been active.
318 ///
319 /// # Default Value
320 /// 0
321active_ticks: i32,
322/// # Default Value
323 /// false
324ambient: bool,
325/// # Default Value
326 /// true
327show_particles: bool,
328/// # Default Value
329 /// true
330show_icon: bool,
331}
332333impl ActiveStatusEffect {
334/// Creates a new [`ActiveStatusEffect`].
335pub fn from_effect(effect: StatusEffect) -> Self {
336Self {
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 }
346347/// Sets the amplifier of the [`ActiveStatusEffect`].
348pub fn with_amplifier(mut self, amplifier: u8) -> Self {
349self.amplifier = amplifier;
350self
351}
352353/// Sets the duration of the [`ActiveStatusEffect`] in ticks.
354pub fn with_duration(mut self, duration: i32) -> Self {
355self.initial_duration = Some(duration);
356self
357}
358359/// Sets the duration of the [`ActiveStatusEffect`] in seconds.
360pub fn with_duration_seconds(mut self, duration: f32) -> Self {
361self.initial_duration = Some((duration * 20.0).round() as i32);
362self
363}
364365/// Sets the duration of the [`ActiveStatusEffect`] to infinite.
366pub fn with_infinite(mut self) -> Self {
367self.initial_duration = None;
368self
369}
370371/// Sets whether the [`ActiveStatusEffect`] is ambient.
372pub fn with_ambient(mut self, ambient: bool) -> Self {
373self.ambient = ambient;
374self
375}
376377/// Sets whether the [`ActiveStatusEffect`] shows particles.
378pub fn with_show_particles(mut self, show_particles: bool) -> Self {
379self.show_particles = show_particles;
380self
381}
382383/// Sets whether the [`ActiveStatusEffect`] shows an icon.
384pub fn with_show_icon(mut self, show_icon: bool) -> Self {
385self.show_icon = show_icon;
386self
387}
388389/// Increments the active ticks of the [`ActiveStatusEffect`] by one.
390pub fn increment_active_ticks(&mut self) {
391self.active_ticks += 1;
392 }
393394/// Returns the [`StatusEffect`] of the [`ActiveStatusEffect`].
395pub fn status_effect(&self) -> StatusEffect {
396self.effect
397 }
398399/// Returns the amplifier of the [`ActiveStatusEffect`].
400pub fn amplifier(&self) -> u8 {
401self.amplifier
402 }
403404/// Returns the initial duration of the [`ActiveStatusEffect`] in ticks.
405 /// Returns `None` if the [`ActiveStatusEffect`] is infinite.
406pub fn initial_duration(&self) -> Option<i32> {
407self.initial_duration
408 }
409410/// Returns the remaining duration of the [`ActiveStatusEffect`] in ticks.
411 /// Returns `None` if the [`ActiveStatusEffect`] is infinite.
412pub fn remaining_duration(&self) -> Option<i32> {
413self.initial_duration
414 .map(|duration| duration - self.active_ticks)
415 }
416417/// Returns the active ticks of the [`ActiveStatusEffect`].
418pub fn active_ticks(&self) -> i32 {
419self.active_ticks
420 }
421422/// Returns true if the [`ActiveStatusEffect`] is ambient.
423pub fn ambient(&self) -> bool {
424self.ambient
425 }
426427/// Returns true if the [`ActiveStatusEffect`] shows particles.
428pub fn show_particles(&self) -> bool {
429self.show_particles
430 }
431432/// Returns true if the [`ActiveStatusEffect`] shows an icon.
433pub fn show_icon(&self) -> bool {
434self.show_icon
435 }
436437/// Returns true if the [`ActiveStatusEffect`] has expired or if it is
438 /// instant.
439pub fn expired(&self) -> bool {
440self.status_effect().instant()
441 || self
442.remaining_duration()
443 .is_some_and(|duration| duration <= 0)
444 }
445}
446447#[cfg(test)]
448mod test {
449use super::*;
450451#[test]
452fn test_apply_effect() {
453let mut effects = ActiveStatusEffects::default();
454455let effect = ActiveStatusEffect::from_effect(StatusEffect::Speed).with_amplifier(1);
456let effect2 = ActiveStatusEffect::from_effect(StatusEffect::Speed).with_amplifier(2);
457458let effect3 = ActiveStatusEffect::from_effect(StatusEffect::Strength).with_amplifier(1);
459let effect4 = ActiveStatusEffect::from_effect(StatusEffect::Strength).with_amplifier(2);
460461 effects.apply(effect.clone());
462 effects.apply_changes();
463assert_eq!(
464 effects.get_all_effect(StatusEffect::Speed),
465Some(&vec![effect.clone()])
466 );
467468 effects.apply(effect2.clone());
469 effects.apply_changes();
470assert_eq!(
471 effects.get_all_effect(StatusEffect::Speed),
472Some(&vec![effect2.clone()])
473 );
474475 effects.apply(effect3.clone());
476 effects.apply_changes();
477assert_eq!(
478 effects.get_all_effect(StatusEffect::Strength),
479Some(&vec![effect3.clone()])
480 );
481482 effects.apply(effect4.clone());
483 effects.apply_changes();
484assert_eq!(
485 effects.get_all_effect(StatusEffect::Strength),
486Some(&vec![effect4.clone()])
487 );
488 }
489490#[test]
491fn test_apply_effect_duration() {
492let mut effects = ActiveStatusEffects::default();
493494let effect = ActiveStatusEffect::from_effect(StatusEffect::Speed)
495 .with_amplifier(1)
496 .with_duration(100);
497let effect2 = ActiveStatusEffect::from_effect(StatusEffect::Speed)
498 .with_amplifier(1)
499 .with_duration(200);
500let effect3 = ActiveStatusEffect::from_effect(StatusEffect::Speed)
501 .with_amplifier(0)
502 .with_duration(300);
503504 effects.apply(effect.clone());
505 effects.apply_changes();
506assert_eq!(
507 effects.get_all_effect(StatusEffect::Speed),
508Some(&vec![effect.clone()])
509 );
510511 effects.apply(effect2.clone());
512 effects.apply_changes();
513assert_eq!(
514 effects.get_all_effect(StatusEffect::Speed),
515Some(&vec![effect2.clone()])
516 );
517518 effects.apply(effect3.clone());
519 effects.apply_changes();
520assert_eq!(
521 effects.get_all_effect(StatusEffect::Speed),
522Some(&vec![effect2.clone(), effect3.clone()])
523 );
524 }
525}