use anyhow::Result; use anyhow_ext::anyhow; use std::fmt::{Debug, Formatter}; use std::sync::{Arc, Mutex}; use crate::dynamic_data::models::executing_move::ExecutingMove; use crate::dynamic_data::models::pokemon::Pokemon; use crate::dynamic_data::script_handling::ScriptSource; use crate::utils::Random; use crate::{script_hook, ValueIdentifiable, ValueIdentifier}; /// The RNG for a battle. #[derive(Default)] pub struct BattleRandom { /// A unique identifier so we know what value this is. identifier: ValueIdentifier, /// The actual underlying RNG. This is in a mutex, so it is thread safe, and can be ran /// predictably, with guaranteed the same outputs. random: Mutex, } impl BattleRandom { /// Initializes a new RNG with a given seed. pub fn new_with_seed(seed: u128) -> Self { BattleRandom { identifier: Default::default(), random: Mutex::new(Random::new(seed)), } } /// Returns the underlying random number generator. pub fn get_rng(&self) -> &Mutex { &self.random } /// Get a random 32 bit integer. Can be any value between min int and max int. pub fn get(&self) -> Result { match self.get_rng().lock() { Ok(mut l) => Ok(l.get()), Err(_) => Err(anyhow!("Failed to get a RNG lock")), } } /// Get a random 32 bit integer between 0 and max. pub fn get_max(&self, max: i32) -> Result { match self.get_rng().lock() { Ok(mut l) => Ok(l.get_max(max)), Err(_) => Err(anyhow!("Failed to get a RNG lock")), } } /// Get a random 32 bit integer between min and max. pub fn get_between(&self, min: i32, max: i32) -> Result { match self.get_rng().lock() { Ok(mut l) => Ok(l.get_between(min, max)), Err(_) => Err(anyhow!("Failed to get a RNG lock")), } } /// Gets whether or not a move triggers its secondary effect. This takes its chance, and /// rolls whether it triggers. As a side effect this run scripts to allow modifying this random /// chance. pub fn effect_chance( &self, mut chance: f32, executing_move: &ExecutingMove, target: &Arc, hit_number: u8, ) -> Result { script_hook!( change_effect_chance, executing_move, executing_move, target, hit_number, &mut chance ); script_hook!( change_incoming_effect_chance, target, executing_move, target, hit_number, &mut chance ); if chance < 100.0 { if chance > 0.0 { match self.get_rng().lock() { Ok(mut l) => Ok(l.get_float() < (chance / 100.0)), Err(_) => Err(anyhow!("Failed to get a RNG lock")), } } else { Ok(false) } } else { Ok(true) } } } impl Debug for BattleRandom { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { f.debug_struct("BattleRandom").finish() } } impl Clone for BattleRandom { fn clone(&self) -> Self { Self { identifier: Default::default(), // As cloning when we can't get a lock on the randomness is completely impossible, we // should unwrap here. #[allow(clippy::unwrap_used)] random: Mutex::new(self.random.lock().unwrap().clone()), } } } impl ValueIdentifiable for BattleRandom { fn value_identifier(&self) -> ValueIdentifier { self.identifier } }