use crate::ReflectComponent; use crate::registry::EffectMergeRegistry; use bevy_app::{App, Plugin, PreUpdate}; use bevy_ecs::component::Mutable; use bevy_ecs::prelude::{Commands, Component, Entity, EntityRef, Query, Res}; use bevy_ecs::schedule::IntoScheduleConfigs; use bevy_ecs::world::EntityWorldMut; use bevy_reflect::Reflect; use bevy_time::{Time, Timer, TimerMode}; use std::fmt::Debug; use std::time::Duration; pub(crate) struct TimerPlugin; impl Plugin for TimerPlugin { fn build(&self, app: &mut App) { app.add_systems(PreUpdate, (despawn_finished_lifetimes, tick_delay).chain()); app.world_mut() .get_resource_or_init::() .register::(merge_effect_timer::) .register::(merge_effect_timer::); } } /// A [merge function](crate::EffectMergeFn) for [`EffectTimer`] components ([`Lifetime`] and [`Delay`]). pub fn merge_effect_timer>( mut existing: EntityWorldMut, incoming: EntityRef, ) { let incoming = incoming.get::().unwrap(); existing.get_mut::().unwrap().merge(incoming); } /// A [timer](Timer) which is used for status effects and includes a [`TimerMergeMode`]. pub trait EffectTimer: Clone { /// Creates a new timer from a duration. fn new(duration: Duration) -> Self; /// Creates a new time from a duration, in seconds. fn from_seconds(seconds: f32) -> Self { Self::new(Duration::from_secs_f32(seconds)) } /// A builder that overwrites the current merge mode with a new value. fn with_mode(self, mode: TimerMergeMode) -> Self; /// Returns reference to the internal timer. fn get_timer(&self) -> &Timer; /// Returns mutable reference to the internal timer. fn get_timer_mut(&mut self) -> &mut Timer; /// Returns reference to the timer's merge mode. fn get_mode(&self) -> &TimerMergeMode; /// Returns mutable reference to the timer's merge mode. fn get_mode_mut(&mut self) -> &mut TimerMergeMode; /// Merges an old timer (self) with the new one (incoming). /// Behaviour depends on the current [`TimerMergeMode`]. fn merge(&mut self, incoming: &Self) { match self.get_mode() { TimerMergeMode::Replace => self.clone_from(incoming), TimerMergeMode::Keep => {} TimerMergeMode::Fraction => { let fraction = self.get_timer().fraction(); let duration = incoming.get_timer().duration().as_secs_f32(); self.clone_from(incoming); self.get_timer_mut() .set_elapsed(Duration::from_secs_f32(fraction * duration)); } TimerMergeMode::Max => { let old = self.get_timer().remaining_secs(); let new = incoming.get_timer().remaining_secs(); if new > old { self.clone_from(incoming); } } TimerMergeMode::Sum => { let duration = self.get_timer().duration() + incoming.get_timer().duration(); self.clone_from(incoming); self.get_timer_mut().set_duration(duration); } } } } macro_rules! impl_effect_timer { ($ident:ident, $timer_mode:expr) => { impl EffectTimer for $ident { fn new(duration: Duration) -> Self { Self { timer: Timer::new(duration, $timer_mode), ..Self::default() } } fn with_mode(mut self, mode: TimerMergeMode) -> Self { self.mode = mode; self } fn get_timer(&self) -> &Timer { &self.timer } fn get_timer_mut(&mut self) -> &mut Timer { &mut self.timer } fn get_mode(&self) -> &TimerMergeMode { &self.mode } fn get_mode_mut(&mut self) -> &mut TimerMergeMode { &mut self.mode } } }; } /// A timer that despawns the effect when the timer finishes. #[doc(alias = "Duration")] #[derive(Component, Reflect, Eq, PartialEq, Debug, Clone)] #[reflect(Component, PartialEq, Debug, Clone)] pub struct Lifetime { /// Tracks the elapsed time. Once the timer is finished, the entity will be despawned. pub timer: Timer, /// Controls the merge behaviour when an effect is [merged](crate::EffectMode::Merge). pub mode: TimerMergeMode, } impl_effect_timer!(Lifetime, TimerMode::Once); impl Default for Lifetime { fn default() -> Self { Self { timer: Timer::default(), mode: TimerMergeMode::Max, } } } /// A repeating timer used for the delay between effect applications. #[derive(Component, Reflect, Eq, PartialEq, Debug, Clone)] #[reflect(Component, PartialEq, Debug, Clone)] pub struct Delay { /// Tracks the elapsed time. pub timer: Timer, /// Controls the merge behaviour when an effect is [merged](crate::EffectMode::Merge). pub mode: TimerMergeMode, } impl_effect_timer!(Delay, TimerMode::Repeating); impl Delay { /// Makes the timer [almost finished](Timer::almost_finish), leaving 1ns of remaining time. /// This allows effects to trigger immediately when applied. #[doc(alias = "trigger_on_start", alias = "almost_finish")] pub fn trigger_immediately(mut self) -> Self { self.timer.almost_finish(); self } } impl Default for Delay { fn default() -> Self { Self { timer: Timer::default(), mode: TimerMergeMode::Fraction, } } } /// Controls the merge behaviour of a timer when its effect is [merged](crate::EffectMode::Merge). #[derive(Reflect, Eq, PartialEq, Debug, Copy, Clone)] #[reflect(PartialEq, Debug, Clone)] pub enum TimerMergeMode { /// The new effect's time will be used, ignoring the old one. /// Results in same behaviour as [`EffectMode::Insert`](crate::EffectMode::Insert), but on a per-timer basis. Replace, /// The old effect's time will be used, ignoring the new one. Keep, /// The new timer is used, but with the same fraction of the old timer's elapsed time. Fraction, /// The timer with the larger time remaining will be used. Max, /// The timers' durations will be added together. Sum, } pub(super) fn despawn_finished_lifetimes( mut commands: Commands, time: Res