use std::sync::atomic::Ordering; use atomig::impls::{PrimitiveAtom, PrimitiveAtomInteger}; use atomig::{Atom, AtomInteger, Atomic}; use num_traits::{clamp, NumCast, PrimInt}; use super::statistics::Statistic; /// A collection of every individual stat. This set can hold any value that is valid for its integer /// type, and can be modified at will. /// /// As all data in this type is atomic, threaded access to this struct is completely legal. #[derive(Default, Debug)] #[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))] pub struct StatisticSet where T: PrimitiveAtom, T: Atom, T: PrimitiveAtomInteger, ::Repr: PrimitiveAtomInteger, T: AtomInteger, { /// The health point stat value. hp: Atomic, /// The physical attack stat value. attack: Atomic, /// The physical defense stat value. defense: Atomic, /// The special attack stat value. special_attack: Atomic, /// The special defense stat value. special_defense: Atomic, /// The speed stat value. speed: Atomic, } impl StatisticSet where T: PrimitiveAtom, T: Atom, T: PrimitiveAtomInteger, ::Repr: PrimitiveAtomInteger, T: AtomInteger, { /// Creates a new statistic set with given stats. pub fn new(hp: T, attack: T, defense: T, special_attack: T, special_defense: T, speed: T) -> Self { Self { hp: Atomic::::new(hp), attack: Atomic::::new(attack), defense: Atomic::::new(defense), special_attack: Atomic::::new(special_attack), special_defense: Atomic::::new(special_defense), speed: Atomic::::new(speed), } } /// The health point stat value. pub fn hp(&self) -> T { self.hp.load(Ordering::Relaxed) } /// The physical attack stat value. pub fn attack(&self) -> T { self.attack.load(Ordering::Relaxed) } /// The physical defense stat value. pub fn defense(&self) -> T { self.defense.load(Ordering::Relaxed) } /// The special attack stat value. pub fn special_attack(&self) -> T { self.special_attack.load(Ordering::Relaxed) } /// The special defense stat value. pub fn special_defense(&self) -> T { self.special_defense.load(Ordering::Relaxed) } /// The speed stat value. pub fn speed(&self) -> T { self.speed.load(Ordering::Relaxed) } /// Get the value of a specific stat pub fn get_stat(&self, stat: Statistic) -> T { match stat { Statistic::HP => self.hp.load(Ordering::Relaxed), Statistic::Attack => self.attack.load(Ordering::Relaxed), Statistic::Defense => self.defense.load(Ordering::Relaxed), Statistic::SpecialAttack => self.special_attack.load(Ordering::Relaxed), Statistic::SpecialDefense => self.special_defense.load(Ordering::Relaxed), Statistic::Speed => self.speed.load(Ordering::Relaxed), } } /// Modify the value of a specific stat. pub fn set_stat(&self, stat: Statistic, value: T) { match stat { Statistic::HP => self.hp.store(value, Ordering::SeqCst), Statistic::Attack => self.attack.store(value, Ordering::SeqCst), Statistic::Defense => self.defense.store(value, Ordering::SeqCst), Statistic::SpecialAttack => self.special_attack.store(value, Ordering::SeqCst), Statistic::SpecialDefense => self.special_defense.store(value, Ordering::SeqCst), Statistic::Speed => self.speed.store(value, Ordering::SeqCst), } } /// Increase the value of a given stat by a value. pub fn increase_stat(&self, stat: Statistic, value: T) { match stat { Statistic::HP => self.hp.fetch_add(value, Ordering::SeqCst), Statistic::Attack => self.attack.fetch_add(value, Ordering::SeqCst), Statistic::Defense => self.defense.fetch_add(value, Ordering::SeqCst), Statistic::SpecialAttack => self.special_attack.fetch_add(value, Ordering::SeqCst), Statistic::SpecialDefense => self.special_defense.fetch_add(value, Ordering::SeqCst), Statistic::Speed => self.speed.fetch_add(value, Ordering::SeqCst), }; } /// Decrease the value of a given stat by a value. pub fn decrease_stat(&self, stat: Statistic, value: T) { match stat { Statistic::HP => self.hp.fetch_sub(value, Ordering::SeqCst), Statistic::Attack => self.attack.fetch_sub(value, Ordering::SeqCst), Statistic::Defense => self.defense.fetch_sub(value, Ordering::SeqCst), Statistic::SpecialAttack => self.special_attack.fetch_sub(value, Ordering::SeqCst), Statistic::SpecialDefense => self.special_defense.fetch_sub(value, Ordering::SeqCst), Statistic::Speed => self.speed.fetch_sub(value, Ordering::SeqCst), }; } } /// A collection of statistics that can not be modified after creation. /// /// As no modifications happen, this struct does not use atomics. #[derive(Default, Eq, PartialEq, Clone, Debug)] pub struct StaticStatisticSet where T: PrimInt, { /// The health point stat value. hp: T, /// The physical attack stat value. attack: T, /// The physical defense stat value. defense: T, /// The special attack stat value. special_attack: T, /// The special defense stat value. special_defense: T, /// The speed stat value. speed: T, } impl StaticStatisticSet where T: PrimInt, { /// Create a new static statistic set. pub const fn new(hp: T, attack: T, defense: T, special_attack: T, special_defense: T, speed: T) -> Self { Self { hp, attack, defense, special_attack, special_defense, speed, } } /// The health point stat value. pub const fn hp(&self) -> T { self.hp } /// The physical attack stat value. pub const fn attack(&self) -> T { self.attack } /// The physical defense stat value. pub const fn defense(&self) -> T { self.defense } /// The special attack stat value. pub const fn special_attack(&self) -> T { self.special_attack } /// The special defense stat value. pub const fn special_defense(&self) -> T { self.special_defense } /// The speed stat value. pub const fn speed(&self) -> T { self.speed } /// Get the value of a specific stat pub const fn get_stat(&self, stat: Statistic) -> T { match stat { Statistic::HP => self.hp, Statistic::Attack => self.attack, Statistic::Defense => self.defense, Statistic::SpecialAttack => self.special_attack, Statistic::SpecialDefense => self.special_defense, Statistic::Speed => self.speed, } } } /// A clamped statistic set holds the 6 normal stats for a Pokemon, but ensures it always remains /// between two values (inclusive on the two values). #[derive(Default, Debug)] #[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))] pub struct ClampedStatisticSet where T: PrimitiveAtom, T: Atom, T: PrimitiveAtomInteger, ::Repr: PrimitiveAtomInteger, T: AtomInteger, T: NumCast, T: PrimInt, { /// The health point stat value. hp: Atomic, /// The physical attack stat value. attack: Atomic, /// The physical defense stat value. defense: Atomic, /// The special attack stat value. special_attack: Atomic, /// The special defense stat value. special_defense: Atomic, /// The speed stat value. speed: Atomic, } /// A clamped statistic set is a collection of each statistics that can be modified, but that will /// always remain between two compile time constant values (Min, Max). Values here are stored as /// atomics to ensure thread safety. impl ClampedStatisticSet where T: PrimitiveAtom, T: Atom, T: PrimitiveAtomInteger, ::Repr: PrimitiveAtomInteger, T: AtomInteger, T: NumCast, T: PrimInt, { /// The lowest value a value on the set can have. pub fn min() -> T { ::from(MIN).unwrap() } /// The highest value a value on the set can have. pub fn max() -> T { ::from(MAX).unwrap() } /// Takes the underlying primary value, clamp it between the two values, and give it back as /// atomic. fn clamped_cast(v: T) -> Atomic { let v = clamp(v, Self::min(), Self::max()); Atomic::::new(v) } /// Instantiates a new clamped statistic set. pub fn new(hp: T, attack: T, defense: T, special_attack: T, special_defense: T, speed: T) -> Self { Self { hp: Self::clamped_cast(hp), attack: Self::clamped_cast(attack), defense: Self::clamped_cast(defense), special_attack: Self::clamped_cast(special_attack), special_defense: Self::clamped_cast(special_defense), speed: Self::clamped_cast(speed), } } /// The health point stat value. pub fn hp(&self) -> T { self.hp.load(Ordering::Relaxed) } /// The physical attack stat value. pub fn attack(&self) -> T { self.attack.load(Ordering::Relaxed) } /// The physical defense stat value. pub fn defense(&self) -> T { self.defense.load(Ordering::Relaxed) } /// The special attack stat value. pub fn special_attack(&self) -> T { self.special_attack.load(Ordering::Relaxed) } /// The special defense stat value. pub fn special_defense(&self) -> T { self.special_defense.load(Ordering::Relaxed) } /// The speed stat value. pub fn speed(&self) -> T { self.speed.load(Ordering::Relaxed) } /// Gets a specific stat. pub fn get_stat(&self, stat: Statistic) -> T { match stat { Statistic::HP => self.hp.load(Ordering::Relaxed), Statistic::Attack => self.attack.load(Ordering::Relaxed), Statistic::Defense => self.defense.load(Ordering::Relaxed), Statistic::SpecialAttack => self.special_attack.load(Ordering::Relaxed), Statistic::SpecialDefense => self.special_defense.load(Ordering::Relaxed), Statistic::Speed => self.speed.load(Ordering::Relaxed), } } /// Modifies a specific stat. This will ensure the value remains within the min/max range. pub fn set_stat(&self, stat: Statistic, mut value: T) { if value < Self::min() { value = Self::min(); } else if value > Self::max() { value = Self::max(); } match stat { Statistic::HP => self.hp.store(value, Ordering::SeqCst), Statistic::Attack => self.attack.store(value, Ordering::SeqCst), Statistic::Defense => self.defense.store(value, Ordering::SeqCst), Statistic::SpecialAttack => self.special_attack.store(value, Ordering::SeqCst), Statistic::SpecialDefense => self.special_defense.store(value, Ordering::SeqCst), Statistic::Speed => self.speed.store(value, Ordering::SeqCst), } } /// Changes a value to a certain amount. Returns false if the value already has that value. True /// if the value was changed. fn change_stat(v: &Atomic, f: F) -> bool where F: Fn(T) -> T, { let res = v.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |original_value| { let mut new_value = f(original_value); if new_value < Self::min() { new_value = Self::min(); } else if new_value > Self::max() { new_value = Self::max(); } if original_value == new_value { None } else { Some(new_value) } }); res.is_ok() } /// Increases a stat by a certain amount. Returns false if the stat was not changed. pub fn increase_stat(&self, stat: Statistic, value: T) -> bool where ::Repr: PrimInt, { match stat { Statistic::HP => Self::change_stat(&self.hp, |a| a + value), Statistic::Attack => Self::change_stat(&self.attack, |a| a + value), Statistic::Defense => Self::change_stat(&self.defense, |a| a + value), Statistic::SpecialAttack => Self::change_stat(&self.special_attack, |a| a + value), Statistic::SpecialDefense => Self::change_stat(&self.special_defense, |a| a + value), Statistic::Speed => Self::change_stat(&self.speed, |a| a + value), } } /// Decreases a stat by a certain amount. Returns false if the stat was not changed. pub fn decrease_stat(&self, stat: Statistic, value: T) -> bool { match stat { Statistic::HP => Self::change_stat(&self.hp, |a| a - value), Statistic::Attack => Self::change_stat(&self.attack, |a| a - value), Statistic::Defense => Self::change_stat(&self.defense, |a| a - value), Statistic::SpecialAttack => Self::change_stat(&self.special_attack, |a| a - value), Statistic::SpecialDefense => Self::change_stat(&self.special_defense, |a| a - value), Statistic::Speed => Self::change_stat(&self.speed, |a| a - value), } } } #[cfg(test)] mod tests { use super::*; #[test] fn create_get_values() { let set = StatisticSet::::new(1, 2, 3, 4, 5, 6); assert_eq!(set.hp(), 1); assert_eq!(set.get_stat(Statistic::HP), 1); assert_eq!(set.attack(), 2); assert_eq!(set.get_stat(Statistic::Attack), 2); assert_eq!(set.defense(), 3); assert_eq!(set.get_stat(Statistic::Defense), 3); assert_eq!(set.special_attack(), 4); assert_eq!(set.get_stat(Statistic::SpecialAttack), 4); assert_eq!(set.special_defense(), 5); assert_eq!(set.get_stat(Statistic::SpecialDefense), 5); assert_eq!(set.speed(), 6); assert_eq!(set.get_stat(Statistic::Speed), 6); } #[test] fn create_set_value() { let set = StatisticSet::::new(1, 2, 3, 4, 5, 6); set.set_stat(Statistic::HP, 20); assert_eq!(set.hp(), 20); set.set_stat(Statistic::Attack, 30); assert_eq!(set.attack(), 30); set.set_stat(Statistic::Defense, 40); assert_eq!(set.defense(), 40); set.set_stat(Statistic::SpecialAttack, 50); assert_eq!(set.special_attack(), 50); set.set_stat(Statistic::SpecialDefense, 60); assert_eq!(set.special_defense(), 60); set.set_stat(Statistic::Speed, 70); assert_eq!(set.speed(), 70); } #[test] fn create_increase_value() { let set = StatisticSet::::new(1, 2, 3, 4, 5, 6); set.increase_stat(Statistic::SpecialAttack, 5); assert_eq!(set.special_attack(), 9); } #[test] fn create_decrease_value() { let set = StatisticSet::::new(1, 2, 3, 4, 5, 6); set.decrease_stat(Statistic::SpecialAttack, 5); assert_eq!(set.special_attack(), -1); } #[test] fn create_clamped_get_values() { let set = ClampedStatisticSet::::new(-5, 2, 3, 4, 5, 6); assert_eq!(set.hp(), -2); assert_eq!(set.get_stat(Statistic::HP), -2); assert_eq!(set.attack(), 2); assert_eq!(set.get_stat(Statistic::Attack), 2); assert_eq!(set.defense(), 3); assert_eq!(set.get_stat(Statistic::Defense), 3); assert_eq!(set.special_attack(), 4); assert_eq!(set.get_stat(Statistic::SpecialAttack), 4); assert_eq!(set.special_defense(), 4); assert_eq!(set.get_stat(Statistic::SpecialDefense), 4); assert_eq!(set.speed(), 4); assert_eq!(set.get_stat(Statistic::Speed), 4); } #[test] fn create_clamped_set_value() { let set = ClampedStatisticSet::::new(1, 2, 3, 4, 5, 6); set.set_stat(Statistic::SpecialAttack, 20); assert_eq!(set.special_attack(), 4); set.set_stat(Statistic::SpecialAttack, -10); assert_eq!(set.special_attack(), -2); } #[test] fn create_clamped_increase_value() { let set = ClampedStatisticSet::::new(1, 2, 3, 2, 2, 6); let mut has_changed = set.increase_stat(Statistic::SpecialAttack, 20); assert!(has_changed); assert_eq!(set.special_attack(), 4); has_changed = set.increase_stat(Statistic::SpecialAttack, 2); assert!(!has_changed); assert_eq!(set.special_attack(), 4); } #[test] fn create_clamped_decrease_value() { let set = ClampedStatisticSet::::new(1, 2, 3, 2, 2, 6); let mut has_changed = set.decrease_stat(Statistic::SpecialAttack, 20); assert!(has_changed); assert_eq!(set.special_attack(), -2); has_changed = set.decrease_stat(Statistic::SpecialAttack, 2); assert!(!has_changed); assert_eq!(set.special_attack(), -2); } }