use bevy::ecs::component::Mutable;
use bevy::{ecs::prelude::*, reflect::Reflect};
use core::ops::{Add, AddAssign, Sub, SubAssign};
use core::time::Duration;
use std::{collections::HashMap, marker::PhantomData};
use thiserror::Error;
use crate::{Abilitylike, CannotUseAbility};
pub trait Pool: Sized + Component<Mutability = Mutable> {
type Quantity: Add<Output = Self::Quantity>
+ Sub<Output = Self::Quantity>
+ AddAssign
+ SubAssign
+ PartialEq
+ PartialOrd
+ Clone
+ Copy
+ Send
+ Sync
+ 'static;
const MIN: Self::Quantity;
fn current(&self) -> Self::Quantity;
fn available(&self, amount: Self::Quantity) -> Result<(), CannotUseAbility> {
if self.current() >= amount {
Ok(())
} else {
Err(CannotUseAbility::PoolInsufficient)
}
}
fn set_current(&mut self, new_quantity: Self::Quantity) -> Self::Quantity;
fn max(&self) -> Self::Quantity;
fn set_max(&mut self, new_max: Self::Quantity) -> Result<(), MaxPoolLessThanMin>;
#[inline]
#[must_use]
fn is_full(&self) -> bool {
self.current() == self.max()
}
#[inline]
#[must_use]
fn is_empty(&self) -> bool {
self.current() == Self::MIN
}
fn expend(&mut self, amount: Self::Quantity) -> Result<(), CannotUseAbility> {
self.available(amount)?;
let new_current = self.current() - amount;
self.set_current(new_current);
Ok(())
}
fn replenish(&mut self, amount: Self::Quantity) {
let new_current = self.current() + amount;
self.set_current(new_current);
}
}
pub trait RegeneratingPool: Pool {
fn regen_per_second(&self) -> Self::Quantity;
fn set_regen_per_second(&mut self, new_regen_per_second: Self::Quantity);
fn regenerate(&mut self, delta_time: Duration);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
#[error(
"The maximum quantity that can be stored in a pool must be greater than the minimum value."
)]
pub struct MaxPoolLessThanMin;
#[derive(Component, Debug, Reflect)]
pub struct AbilityCosts<A: Abilitylike, P: Pool> {
cost_map: HashMap<A, P::Quantity>,
_phantom: PhantomData<A>,
}
impl<A: Abilitylike, P: Pool> Clone for AbilityCosts<A, P> {
fn clone(&self) -> Self {
AbilityCosts {
cost_map: self.cost_map.clone(),
_phantom: PhantomData,
}
}
}
impl<A: Abilitylike, P: Pool> Default for AbilityCosts<A, P> {
fn default() -> Self {
AbilityCosts {
cost_map: HashMap::new(),
_phantom: PhantomData,
}
}
}
impl<A: Abilitylike, P: Pool> AbilityCosts<A, P> {
#[must_use]
pub fn new(action_cost_pairs: impl IntoIterator<Item = (A, P::Quantity)>) -> Self {
let mut ability_costs = AbilityCosts::default();
for (action, cost) in action_cost_pairs.into_iter() {
ability_costs.set(action, cost);
}
ability_costs
}
#[inline]
#[must_use]
pub fn available(&self, action: &A, pool: &P) -> bool {
if let Some(cost) = self.get(action) {
pool.available(*cost).is_ok()
} else {
true
}
}
#[inline]
pub fn pay_cost(&mut self, action: &A, pool: &mut P) -> Result<(), CannotUseAbility> {
if let Some(cost) = self.get(action) {
pool.expend(*cost)
} else {
Ok(())
}
}
#[inline]
#[must_use]
pub fn get(&self, action: &A) -> Option<&P::Quantity> {
self.cost_map.get(action)
}
#[inline]
#[must_use]
pub fn get_mut(&mut self, action: &A) -> Option<&mut P::Quantity> {
self.cost_map.get_mut(action)
}
#[inline]
pub fn set(&mut self, action: A, cost: P::Quantity) -> &mut Self {
self.cost_map.insert(action, cost);
self
}
#[inline]
#[must_use]
pub fn build(&mut self) -> Self {
self.clone()
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &P::Quantity> {
self.cost_map.values()
}
#[inline]
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut P::Quantity> {
self.cost_map.values_mut()
}
}
#[derive(Bundle, Reflect)]
pub struct PoolBundle<A: Abilitylike, P: Pool + Component> {
pub pool: P,
pub ability_costs: AbilityCosts<A, P>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::premade_pools::mana::{Mana, ManaPool};
#[test]
fn set_pool_cannot_exceed_min() {
let mut mana_pool = ManaPool::new(Mana(0.), Mana(10.), Mana(0.));
mana_pool.set_current(Mana(-3.));
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(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(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_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(MaxPoolLessThanMin))
}
#[test]
fn expending_depletes_pool() {
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();
assert_eq!(mana_pool.current(), Mana(1.));
assert_eq!(
mana_pool.expend(Mana(5.)),
Err(CannotUseAbility::PoolInsufficient)
);
}
#[test]
fn pool_can_regenerate() {
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);
assert!((mana_pool.current() - expected).0.abs() < f32::EPSILON);
}
}