use bevy::{ecs::prelude::Component, reflect::Reflect};
use std::{fmt::Display, marker::PhantomData};
use crate::{Abilitylike, CannotUseAbility};
use std::collections::HashMap;
#[derive(Component, Clone, PartialEq, Eq, Debug, Reflect)]
pub struct ChargeState<A: Abilitylike> {
charges_map: HashMap<A, Charges>,
#[reflect(ignore)]
_phantom: PhantomData<A>,
}
impl<A: Abilitylike> Default for ChargeState<A> {
fn default() -> Self {
ChargeState {
charges_map: HashMap::new(),
_phantom: PhantomData,
}
}
}
#[derive(Clone, Default, PartialEq, Eq, Debug, Reflect)]
pub struct Charges {
current: u8,
max: u8,
pub replenish_strat: ReplenishStrategy,
pub cooldown_strat: CooldownStrategy,
}
impl Display for Charges {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}/{}", self.current, self.max)
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Reflect)]
pub enum ReplenishStrategy {
#[default]
OneAtATime,
AllAtOnce,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Reflect)]
pub enum CooldownStrategy {
Ignore,
#[default]
ConstantlyRefresh,
RefreshWhenEmpty,
}
impl<A: Abilitylike> ChargeState<A> {
#[must_use]
pub fn new(action_chargestate_pairs: impl IntoIterator<Item = (A, Charges)>) -> Self {
let mut charge_state = ChargeState::default();
for (action, charges) in action_chargestate_pairs.into_iter() {
charge_state.set(action, charges);
}
charge_state
}
#[inline]
#[must_use]
pub fn available(&self, action: &A) -> bool {
if let Some(charges) = self.get(action) {
charges.available()
} else {
true
}
}
#[inline]
pub fn expend(&mut self, action: &A) -> Result<(), CannotUseAbility> {
if let Some(charges) = self.get_mut(action) {
charges.expend()
} else {
Ok(())
}
}
#[inline]
pub fn replenish(&mut self, action: &A) {
if let Some(charges) = self.get_mut(action) {
charges.replenish();
}
}
#[inline]
#[must_use]
pub fn get(&self, action: &A) -> Option<&Charges> {
self.charges_map.get(action)
}
#[inline]
#[must_use]
pub fn get_mut(&mut self, action: &A) -> Option<&mut Charges> {
self.charges_map.get_mut(action)
}
#[inline]
pub fn set(&mut self, action: A, charges: Charges) -> &mut Self {
self.charges_map.insert(action, charges);
self
}
#[inline]
#[must_use]
pub fn build(&mut self) -> Self {
self.clone()
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &Charges> {
self.charges_map.values()
}
#[inline]
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Charges> {
self.charges_map.values_mut()
}
}
impl Charges {
#[inline]
#[must_use]
pub fn new(
max_charges: u8,
replenish_strat: ReplenishStrategy,
cooldown_strat: CooldownStrategy,
) -> Charges {
Charges {
current: max_charges,
max: max_charges,
replenish_strat,
cooldown_strat,
}
}
pub fn simple(max_charges: u8) -> Charges {
Charges {
current: max_charges,
max: max_charges,
replenish_strat: ReplenishStrategy::OneAtATime,
cooldown_strat: CooldownStrategy::Ignore,
}
}
pub fn ammo(max_charges: u8) -> Charges {
Charges {
current: max_charges,
max: max_charges,
replenish_strat: ReplenishStrategy::AllAtOnce,
cooldown_strat: CooldownStrategy::Ignore,
}
}
pub fn replenish_one(max_charges: u8) -> Charges {
Charges {
current: max_charges,
max: max_charges,
replenish_strat: ReplenishStrategy::OneAtATime,
cooldown_strat: CooldownStrategy::ConstantlyRefresh,
}
}
pub fn replenish_all(max_charges: u8) -> Charges {
Charges {
current: max_charges,
max: max_charges,
replenish_strat: ReplenishStrategy::AllAtOnce,
cooldown_strat: CooldownStrategy::RefreshWhenEmpty,
}
}
#[inline]
#[must_use]
pub fn charges(&self) -> u8 {
self.current
}
#[inline]
#[must_use]
pub fn max_charges(&self) -> u8 {
self.max
}
#[inline]
#[must_use]
pub fn add_charges(&mut self, charges: u8) -> u8 {
let new_total = self.current.saturating_add(charges);
let excess = new_total.saturating_sub(self.max);
self.current = new_total.min(self.max);
excess
}
#[inline]
pub fn set_charges(&mut self, charges: u8) -> u8 {
let excess = charges.saturating_sub(self.max);
self.current = charges.min(self.max);
excess
}
#[inline]
pub fn set_max_charges(&mut self, max_charges: u8) {
self.max = max_charges;
self.current = self.current.min(self.max);
}
#[inline]
#[must_use]
pub fn available(&self) -> bool {
self.current > 0
}
#[inline]
pub fn expend(&mut self) -> Result<(), CannotUseAbility> {
if self.current == 0 {
return Err(CannotUseAbility::NoCharges);
}
self.current = self.current.saturating_sub(1);
Ok(())
}
#[inline]
pub fn replenish(&mut self) {
let charges_to_add = match self.replenish_strat {
ReplenishStrategy::OneAtATime => 1,
ReplenishStrategy::AllAtOnce => self.max,
};
let _ = self.add_charges(charges_to_add);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn charges_start_full() {
let charges = Charges::simple(3);
assert_eq!(charges.charges(), 3);
assert_eq!(charges.max_charges(), 3);
}
#[test]
fn charges_available() {
let mut charges = Charges::simple(3);
assert!(charges.available());
charges.set_charges(1);
assert!(charges.available());
charges.set_charges(0);
assert!(!charges.available());
}
#[test]
fn charges_deplete() {
let mut charges = Charges::simple(2);
charges.expend().unwrap();
assert_eq!(charges.charges(), 1);
charges.expend().unwrap();
assert_eq!(charges.charges(), 0);
assert_eq!(charges.expend(), Err(CannotUseAbility::NoCharges));
assert_eq!(charges.charges(), 0);
}
#[test]
fn charges_replenish_one_at_a_time() {
let mut charges = Charges::replenish_one(3);
charges.set_charges(0);
assert_eq!(charges.charges(), 0);
charges.replenish();
assert_eq!(charges.charges(), 1);
charges.replenish();
assert_eq!(charges.charges(), 2);
charges.replenish();
assert_eq!(charges.charges(), 3);
charges.replenish();
assert_eq!(charges.charges(), 3);
}
#[test]
fn charges_replenish_all_at_once() {
let mut charges = Charges::replenish_all(3);
charges.set_charges(0);
assert_eq!(charges.charges(), 0);
charges.replenish();
assert_eq!(charges.charges(), 3);
}
}