diff --git a/RELEASES.md b/RELEASES.md index c78cea2..39470d7 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -2,12 +2,27 @@ ## Version 0.6 +### Documentation + +- fixed several typos (`@striezel`) +- improved the documentation for `Pool::replenish` + ### Usability +- removed the required `new` method from the `Pool` trait: this method was overly restrictive, and prevented the construction of more complex pools with custom initialization parameters + - `LifePool::new` and `ManaPool::new` methods have been added to the premade pools: do similarly for your own `Pool` types +- the `Pool::ZERO` associated constant has been renamed to the clearer `Pool::MIN`. + - the `MaxPoolLessThanZero` error type has been renamed to `MaxPoolLessThanMin` to match. +- the `Pool` trait has been split in two, with the regeneration-specific mechanics handled in `RegeneratingPool`, to make the construction of non-regenerating pools much more intuitive +- added the `Pool::is_empty` and `Pool::is_full` helper methods to the `Pool` trait +- added `Add`, `Sub`, `AddAssign` and `SubAssign` implementations to the premade `Life` and `Mana` types and their corresponding pools +- added the `Display` trait to `Life`, `Mana`, `LifePool` and `ManaPool` - removed the useless `AbilityPlugin::server()` plugin creation method ## Version 0.5 +### Dependencies + - now supports Bevy 0.11 ## Version 0.4 diff --git a/src/ability_state.rs b/src/ability_state.rs index 27211e6..fa976ff 100644 --- a/src/ability_state.rs +++ b/src/ability_state.rs @@ -4,7 +4,7 @@ use crate::{ charges::ChargeState, cooldown::CooldownState, - pool::{AbilityCosts, MaxPoolLessThanZero, Pool}, + pool::{AbilityCosts, MaxPoolLessThanMin, Pool}, Abilitylike, CannotUseAbility, }; // Required due to poor macro hygiene in `WorldQuery` macro @@ -219,37 +219,22 @@ mod tests { pub struct NullPool; impl Pool for NullPool { - // So easy, type Quantity = f32; - const ZERO: f32 = 0.0; - - fn new( - _current: Self::Quantity, - _max: Self::Quantity, - _regen_per_second: Self::Quantity, - ) -> Self { - panic!("This type cannot be constructed."); - } + const MIN: f32 = 0.0; fn current(&self) -> Self::Quantity { - Self::ZERO + Self::MIN } fn set_current(&mut self, _new_quantity: Self::Quantity) -> Self::Quantity { - Self::ZERO + Self::MIN } fn max(&self) -> Self::Quantity { - Self::ZERO + Self::MIN } - fn set_max(&mut self, _new_max: Self::Quantity) -> Result<(), MaxPoolLessThanZero> { + fn set_max(&mut self, _new_max: Self::Quantity) -> Result<(), MaxPoolLessThanMin> { Ok(()) } - - fn regen_per_second(&self) -> Self::Quantity { - Self::ZERO - } - - fn set_regen_per_second(&mut self, _new_regen_per_second: Self::Quantity) {} } diff --git a/src/lib.rs b/src/lib.rs index ae75594..8983178 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -168,7 +168,7 @@ pub fn ability_ready( } // The pool does not exist, but the cost does } else if let Some(cost) = cost { - if cost > P::ZERO { + if cost > P::MIN { Err(CannotUseAbility::PoolInsufficient) } else { Ok(()) diff --git a/src/pool.rs b/src/pool.rs index c099ff6..bc77ee3 100644 --- a/src/pool.rs +++ b/src/pool.rs @@ -3,7 +3,7 @@ //! Unlike charges, pools are typically shared across abilities. //! //! Life, mana, energy and rage might all be modelled effectively as pools. -//! Pools have a maximum value, have a minimum value of zero, can regenerate over time, and can be spent to pay for abilities. +//! Pools have a maximum value and a minimum value (almost always zero), can regenerate over time, and can be spent to pay for abilities. //! //! The [`regenerate_resource_pool`](crate::systems::regenerate_resource_pool) system will regenerate resource pools of a given type if manually added. @@ -41,38 +41,13 @@ pub trait Pool: Sized { /// The minimum value of the pool type. /// /// At this point, no resources remain to be spent. - const ZERO: Self::Quantity; - - /// Creates a new pool with the specified settings. - /// - /// # Panics - /// - /// Panics if `max` is less than [`Pool::ZERO`]. - fn new(current: Self::Quantity, max: Self::Quantity, regen_per_second: Self::Quantity) -> Self; - - /// Creates a new pool, with zero initial resources. - /// - /// # Panics - /// - /// Panics if `max` is less than [`Pool::ZERO`]. - fn new_empty(max: Self::Quantity, regen_per_second: Self::Quantity) -> Self { - Pool::new(Self::ZERO, max, regen_per_second) - } - - /// Creates a new pool with current value set at the specified maximum. - /// - /// # Panics - /// - /// Panics if `max` is less than [`Pool::ZERO`]. - fn new_full(max: Self::Quantity, regen_per_second: Self::Quantity) -> Self { - Pool::new(max, max, regen_per_second) - } + const MIN: Self::Quantity; /// The current quantity of resources in the pool. /// /// # Panics /// - /// Panics if `max` is less than [`Pool::ZERO`]. + /// Panics if `max` is less than [`Pool::MIN`]. fn current(&self) -> Self::Quantity; /// Check if the given cost can be paid by this pool. @@ -97,9 +72,25 @@ pub trait Pool: Sized { /// /// The current value will be reduced to the new max if necessary. /// - /// Has no effect if `new_max < Pool::ZERO`. - /// Returns a [`PoolMaxLessThanZero`] error if this occurs. - fn set_max(&mut self, new_max: Self::Quantity) -> Result<(), MaxPoolLessThanZero>; + /// Has no effect if `new_max < Pool::MIN`. + /// Returns a [`MaxPoolLessThanMin`] error if this occurs. + fn set_max(&mut self, new_max: Self::Quantity) -> Result<(), MaxPoolLessThanMin>; + + /// Is the pool currently full? + #[inline] + #[must_use] + fn is_full(&self) -> bool { + self.current() == self.max() + } + + /// Is the pool currently empty? + /// + /// Note that this compares the current value to [`Pool::MIN`], not `0`. + #[inline] + #[must_use] + fn is_empty(&self) -> bool { + self.current() == Self::MIN + } /// Spend the specified amount from the pool, if there is that much available. /// @@ -115,11 +106,18 @@ pub trait Pool: Sized { /// Replenish the pool by the specified amount. /// /// This cannot cause the pool to exceed maximum value that can be stored in the pool. + /// This is the sign-flipped counterpart to [`Self::expend`], + /// however, unlike [`Self::expend`], this method will not return an error if the pool is empty. fn replenish(&mut self, amount: Self::Quantity) { let new_current = self.current() + amount; self.set_current(new_current); } +} +/// A resource pool that regenerates (or decays) over time. +/// +/// Set the regeneration rate to a positive value to regenerate, or a negative value to decay. +pub trait RegeneratingPool: Pool { /// The quantity recovered by the pool in one second. /// /// This value may be negative, in the case of automatically decaying pools (like rage). @@ -140,10 +138,12 @@ pub trait Pool: Sized { } } -/// The maximum value for a [`Pool`] was set to be less than [`Pool::ZERO`]. +/// The maximum value for a [`Pool`] was set to be less than [`Pool::MIN`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, Error)] -#[error("The maximum quantity that can be stored in a pool must be greater than zero.")] -pub struct MaxPoolLessThanZero; +#[error( + "The maximum quantity that can be stored in a pool must be greater than the minimum value." +)] +pub struct MaxPoolLessThanMin; /// Stores the cost (in terms of the [`Pool::Quantity`] of ability) associated with each ability of type `A`. #[derive(Component, Debug)] @@ -292,38 +292,38 @@ mod tests { #[test] fn set_pool_cannot_exceed_min() { - let mut mana_pool = ManaPool::new_empty(Mana(10.), Mana(0.)); + let mut mana_pool = ManaPool::new(Mana(0.), Mana(10.), Mana(0.)); mana_pool.set_current(Mana(-3.)); - assert_eq!(mana_pool.current(), ManaPool::ZERO); + assert_eq!(mana_pool.current(), ManaPool::MIN); } #[test] fn set_pool_cannot_exceed_max() { let max_mana = Mana(10.); - let mut mana_pool = ManaPool::new_full(max_mana, Mana(0.)); + let mut mana_pool = ManaPool::new(max_mana, max_mana, Mana(0.)); mana_pool.set_current(Mana(100.0)); assert_eq!(mana_pool.current(), max_mana); } #[test] fn reducing_max_decreases_current() { - let mut mana_pool = ManaPool::new_full(Mana(10.), Mana(0.)); + let mut mana_pool = ManaPool::new(Mana(10.), Mana(10.), Mana(0.)); assert_eq!(mana_pool.current(), Mana(10.)); mana_pool.set_max(Mana(5.)).unwrap(); assert_eq!(mana_pool.current(), Mana(5.)); } #[test] - fn setting_max_below_zero_fails() { - let mut mana_pool = ManaPool::new_full(Mana(10.), Mana(0.)); + fn setting_max_below_min_fails() { + let mut mana_pool = ManaPool::new(Mana(10.), Mana(10.), Mana(0.)); let result = mana_pool.set_max(Mana(-7.)); assert_eq!(mana_pool.max(), Mana(10.)); - assert_eq!(result, Err(MaxPoolLessThanZero)) + assert_eq!(result, Err(MaxPoolLessThanMin)) } #[test] fn expending_depletes_pool() { - let mut mana_pool = ManaPool::new_full(Mana(11.), Mana(0.)); + let mut mana_pool = ManaPool::new(Mana(11.), Mana(11.), Mana(0.)); mana_pool.expend(Mana(5.)).unwrap(); assert_eq!(mana_pool.current(), Mana(6.)); mana_pool.expend(Mana(5.)).unwrap(); @@ -336,7 +336,7 @@ mod tests { #[test] fn pool_can_regenerate() { - let mut mana_pool = ManaPool::new_empty(Mana(10.), Mana(1.3)); + let mut mana_pool = ManaPool::new(Mana(0.), Mana(10.), Mana(1.3)); mana_pool.regenerate(Duration::from_secs(1)); let expected = Mana(1.3); diff --git a/src/premade_pools.rs b/src/premade_pools.rs index 1d23877..08a954f 100644 --- a/src/premade_pools.rs +++ b/src/premade_pools.rs @@ -3,13 +3,16 @@ //! These can be annoying due to orphan rules that prevent you from implementing your own methods, //! so feel free to copy-paste them (without attribution) into your own source to make new variants. -use crate::pool::{MaxPoolLessThanZero, Pool}; +use crate::pool::{MaxPoolLessThanMin, Pool}; use bevy::prelude::{Component, Resource}; -use core::ops::{Div, Mul}; +use core::fmt::{Display, Formatter}; +use core::ops::{Add, AddAssign, Div, Mul, Sub, SubAssign}; use derive_more::{Add, AddAssign, Sub, SubAssign}; /// A premade resource pool for life (aka health, hit points or HP). pub mod life { + use crate::pool::RegeneratingPool; + use super::*; /// The amount of life available to a unit. @@ -26,6 +29,25 @@ pub mod life { pub regen_per_second: Life, } + impl LifePool { + /// Creates a new [`LifePool`] with the supplied settings. + /// + /// # Panics + /// Panics if `current` is greater than `max`. + /// Panics if `current` or max is negative. + + pub fn new(current: Life, max: Life, regen_per_second: Life) -> Self { + assert!(current <= max); + assert!(current >= LifePool::MIN); + assert!(max >= LifePool::MIN); + Self { + current, + max, + regen_per_second, + } + } + } + /// A quantity of life, used to modify a [`LifePool`]. /// /// This can be used for damage computations, life regeneration, healing and so on. @@ -58,21 +80,17 @@ pub mod life { } } + impl Div for Life { + type Output = f32; + + fn div(self, rhs: Life) -> f32 { + self.0 / rhs.0 + } + } + impl Pool for LifePool { type Quantity = Life; - const ZERO: Life = Life(0.); - - fn new( - current: Self::Quantity, - max: Self::Quantity, - regen_per_second: Self::Quantity, - ) -> Self { - LifePool { - current, - max, - regen_per_second, - } - } + const MIN: Life = Life(0.); fn current(&self) -> Self::Quantity { self.current @@ -88,16 +106,18 @@ pub mod life { self.max } - fn set_max(&mut self, new_max: Self::Quantity) -> Result<(), MaxPoolLessThanZero> { - if new_max < Self::ZERO { - Err(MaxPoolLessThanZero) + fn set_max(&mut self, new_max: Self::Quantity) -> Result<(), MaxPoolLessThanMin> { + if new_max < Self::MIN { + Err(MaxPoolLessThanMin) } else { self.max = new_max; self.set_current(self.current); Ok(()) } } + } + impl RegeneratingPool for LifePool { fn regen_per_second(&self) -> Self::Quantity { self.regen_per_second } @@ -106,10 +126,54 @@ pub mod life { self.regen_per_second = new_regen_per_second; } } + + impl Add for LifePool { + type Output = Self; + + fn add(mut self, rhs: Life) -> Self::Output { + self.set_current(self.current + rhs); + self + } + } + + impl Sub for LifePool { + type Output = Self; + + fn sub(mut self, rhs: Life) -> Self::Output { + self.set_current(self.current - rhs); + self + } + } + + impl AddAssign for LifePool { + fn add_assign(&mut self, rhs: Life) { + self.set_current(self.current + rhs); + } + } + + impl SubAssign for LifePool { + fn sub_assign(&mut self, rhs: Life) { + self.set_current(self.current - rhs); + } + } + + impl Display for Life { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.0) + } + } + + impl Display for LifePool { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "{}/{}", self.current, self.max) + } + } } /// A premade resource pool for mana (aka MP). pub mod mana { + use crate::pool::RegeneratingPool; + use super::*; /// The amount of mana available to a unit. @@ -126,6 +190,24 @@ pub mod mana { pub regen_per_second: Mana, } + impl ManaPool { + /// Creates a new [`ManaPool`] with the supplied settings. + /// + /// # Panics + /// Panics if `current` is greater than `max`. + /// Panics if `current` or `max` is negative. + pub fn new(current: Mana, max: Mana, regen_per_second: Mana) -> Self { + assert!(current <= max); + assert!(current >= ManaPool::MIN); + assert!(max >= ManaPool::MIN); + Self { + current, + max, + regen_per_second, + } + } + } + /// A quantity of mana, used to modify a [`ManaPool`]. /// /// This can be used for ability costs, mana regeneration and so on. @@ -158,21 +240,17 @@ pub mod mana { } } + impl Div for Mana { + type Output = f32; + + fn div(self, rhs: Mana) -> f32 { + self.0 / rhs.0 + } + } + impl Pool for ManaPool { type Quantity = Mana; - const ZERO: Mana = Mana(0.); - - fn new( - current: Self::Quantity, - max: Self::Quantity, - regen_per_second: Self::Quantity, - ) -> Self { - ManaPool { - current, - max, - regen_per_second, - } - } + const MIN: Mana = Mana(0.); fn current(&self) -> Self::Quantity { self.current @@ -188,16 +266,18 @@ pub mod mana { self.max } - fn set_max(&mut self, new_max: Self::Quantity) -> Result<(), MaxPoolLessThanZero> { - if new_max < Self::ZERO { - Err(MaxPoolLessThanZero) + fn set_max(&mut self, new_max: Self::Quantity) -> Result<(), MaxPoolLessThanMin> { + if new_max < Self::MIN { + Err(MaxPoolLessThanMin) } else { self.max = new_max; self.set_current(self.current); Ok(()) } } + } + impl RegeneratingPool for ManaPool { fn regen_per_second(&self) -> Self::Quantity { self.regen_per_second } @@ -206,4 +286,46 @@ pub mod mana { self.regen_per_second = new_regen_per_second; } } + + impl Add for ManaPool { + type Output = Self; + + fn add(mut self, rhs: Mana) -> Self::Output { + self.set_current(self.current + rhs); + self + } + } + + impl Sub for ManaPool { + type Output = Self; + + fn sub(mut self, rhs: Mana) -> Self::Output { + self.set_current(self.current - rhs); + self + } + } + + impl AddAssign for ManaPool { + fn add_assign(&mut self, rhs: Mana) { + self.set_current(self.current + rhs); + } + } + + impl SubAssign for ManaPool { + fn sub_assign(&mut self, rhs: Mana) { + self.set_current(self.current - rhs); + } + } + + impl Display for Mana { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.0) + } + } + + impl Display for ManaPool { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "{}/{}", self.current, self.max) + } + } } diff --git a/src/systems.rs b/src/systems.rs index 94ec088..3a1e682 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -1,6 +1,6 @@ //! The systems that power each [`InputManagerPlugin`](crate::plugin::InputManagerPlugin). -use crate::pool::Pool; +use crate::pool::RegeneratingPool; use crate::{charges::ChargeState, cooldown::CooldownState, Abilitylike}; use bevy::ecs::prelude::*; @@ -37,7 +37,7 @@ pub fn tick_cooldowns( } /// Regenerates the resource of the [`Pool`] type `P` based on the elapsed [`Time`]. -pub fn regenerate_resource_pool( +pub fn regenerate_resource_pool( mut query: Query<&mut P>, pool_res: Option>, time: Res