use std::any::Any; use std::fmt::{Debug, Formatter}; use std::ops::Deref; use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering}; use std::sync::Arc; use std::thread::JoinHandle; use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard}; use crate::dynamic_data::choices::TurnChoice; use crate::dynamic_data::ExecutingMove; use crate::dynamic_data::Pokemon; use crate::dynamic_data::{Battle, DynamicLibrary}; use crate::dynamic_data::{BattleSide, DamageSource}; use crate::static_data::{EffectParameter, TypeIdentifier}; use crate::static_data::{Item, Statistic}; use crate::StringKey; /// The script trait is used to make changes to how a battle executes, without requiring hardcoded /// changes. This allows for easily defining generational differences, and add effects that the /// developer might require. pub trait Script: Send + Sync { /// The name of a script is its unique identifier. This should generally be set on load, and be /// the same as the key that was used to load it. fn name(&self) -> &StringKey; /// Returns an atomic bool for internal marking of deletion. This is currently only specifically /// used for deletion of a script while we are holding a reference to it (i.e. executing a script ///hook on it). fn get_marked_for_deletion(&self) -> &AtomicBool; /// This marks the script for deletion, which will dispose of it as soon as possible. fn mark_for_deletion(&self) { self.get_marked_for_deletion().store(true, Ordering::SeqCst); } /// Helper function to get the value of the marked for deletion bool. fn is_marked_for_deletion(&self) -> bool { self.get_marked_for_deletion().load(Ordering::SeqCst) } /// A script can be suppressed by other scripts. If a script is suppressed by at least one script /// we will not execute its methods. This should return the number of suppressions on the script. fn get_suppressed_count(&self) -> &AtomicUsize; /// Helper function to check if there is at least one suppression on the script fn is_suppressed(&self) -> bool { self.get_suppressed_count().load(Ordering::SeqCst) > 0 } /// Adds a suppression. This makes the script not run anymore. Note that adding this should also /// remove the suppression later. /// /// A common pattern for this is to run this in the [`Self::on_initialize`] function, and run the /// remove in the [`Self::on_remove`] function. fn add_suppression(&self) { self.get_suppressed_count().fetch_add(1, Ordering::SeqCst); } /// Removes a suppression. This allows the script to run again (provided other scripts are not /// suppressing it). Note that running this should only occur if an add was run before. fn remove_suppression(&self) { self.get_suppressed_count().fetch_sub(1, Ordering::SeqCst); } /// This function is ran when a volatile effect is added while that volatile effect already is /// in place. Instead of adding the volatile effect twice, it will execute this function instead. fn stack(&self) {} /// This function is ran when this script stops being in effect, and is removed from its owner. fn on_remove(&self) {} /// This function is ran when this script starts being in effect. fn on_initialize(&self, _library: &DynamicLibrary, _pars: &[EffectParameter]) {} /// This function is ran just before the start of the turn. Everyone has made its choices here, /// and the turn is about to start. This is a great place to initialize data if you need to know /// something has happened during a turn. fn on_before_turn(&self, _choice: &TurnChoice) {} /// This function allows you to modify the effective speed of the Pokemon. This is ran before /// turn ordering, so overriding here will allow you to put certain Pokemon before others. fn change_speed(&self, _choice: &TurnChoice, _speed: &mut u32) {} /// This function allows you to modify the effective priority of the Pokemon. This is ran before /// turn ordering, so overriding here will allow you to put certain Pokemon before others. Note /// that this is only relevant on move choices, as other turn choice types do not have a priority. fn change_priority(&self, _choice: &TurnChoice, _priority: &mut i8) {} /// This function allows you to change the move that is used during execution. This is useful for /// moves such as metronome, where the move chosen actually differs from the move used. fn change_move(&self, _choice: &TurnChoice, _move_name: &mut StringKey) {} /// This function allows you to change a move into a multi-hit move. The number of hits set here /// gets used as the number of hits. If set to 0, this will behave as if the move missed on its /// first hit. fn change_number_of_hits(&self, _choice: &TurnChoice, _number_of_hits: &mut u8) {} /// This function allows you to prevent a move from running. If this gets set to true, the move /// ends execution here. No PP will be decreased in this case. fn prevent_move(&self, _move: &ExecutingMove, _prevent: &mut bool) {} /// This function makes the move fail. If the fail field gets set to true, the move ends execution, /// and fail events get triggered. fn fail_move(&self, _move: &ExecutingMove, _fail: &mut bool) {} /// Similar to [`Self::prevent_move`]. This function will also stop execution, but PP will be /// decreased. fn stop_before_move(&self, _move: &ExecutingMove, _stop: &mut bool) {} /// This function runs just before the move starts its execution. fn on_before_move(&self, _move: &ExecutingMove) {} /// This function allows a script to prevent a move that is targeted at its owner. If set to true /// the move fails, and fail events get triggered. fn fail_incoming_move(&self, _move: &ExecutingMove, _target: &Arc, _fail: &mut bool) {} /// This function allows a script to make its owner invulnerable to an incoming move. fn is_invulnerable(&self, _move: &ExecutingMove, _target: &Arc, _invulnerable: &mut bool) {} /// This function occurs when a move gets missed. This runs on the scripts belonging to the executing /// move, which include the scripts that are attached to the owner of the script. fn on_move_miss(&self, _move: &ExecutingMove, _target: &Arc) {} /// This function allows the script to change the actual type that is used for the move on a target. fn change_move_type( &self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _move_type: &mut TypeIdentifier, ) { } /// This function allows the script to change how effective a move is on a target. fn change_effectiveness(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _effectiveness: &mut f32) {} /// This function allows a script to block an outgoing move from being critical. fn block_critical(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _block_critical: &mut bool) {} /// This function allows a script to block an incoming move from being critical. fn block_incoming_critical( &self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _block_critical: &mut bool, ) { } /// This function allows a script to modify the accuracy of a move used. This value represents /// the percentage accuracy, so anything above 100% will make it always hit. fn change_accuracy(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _accuracy: &mut u8) {} /// This function allows a script to change the critical stage of the move used. fn change_critical_stage(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _stage: &mut u8) {} /// This function allows a script to change the damage modifier of a critical hit. This will only /// run when a hit is critical. fn change_critical_modifier(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _modifier: &mut f32) {} /// This function allows a script to change the damage modifier of a Same Type Attack Bonus, which /// occurs when the user has the move type as one of its own types. fn change_stab_modifier(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _modifier: &mut f32) {} /// This function allows a script to change the effective base power of a move hit. fn change_base_power(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _base_power: &mut u8) {} /// This function allows a script to bypass defensive stat boosts for a move hit. fn bypass_defensive_stat_boost(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _bypass: &mut bool) { } /// This function allows a script to bypass offensive stat boosts for a move hit. fn bypass_offensive_stat_boost(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _bypass: &mut bool) { } /// This function allows a script to change the actual offensive stat values used when calculating damage fn change_offensive_stat_value(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _amount: &mut u32) {} /// This function allows a script to change the actual defensive stat values used when calculating damage. fn change_defensive_stat_value(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _amount: &mut u32) {} /// This function allows a script to change the raw modifier we retrieved from the stats of the /// defender and attacker. fn change_damage_stat_modifier( &self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _modifier: &mut f32, ) { } /// This function allows a script to apply a raw multiplier to the damage done by a move. fn change_damage_modifier(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _modifier: &mut f32) {} /// This function allows a script to modify the outgoing damage done by a move. fn change_damage(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _damage: &mut u32) {} /// This function allows a script to modify the incoming damage done by a move. fn change_incoming_damage(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _damage: &mut u32) {} /// This function triggers when an incoming hit happens. This triggers after the damage is done, /// but before the secondary effect of the move happens. fn on_incoming_hit(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8) {} /// This function triggers when an opponent on the field faints. fn on_opponent_faints(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8) {} /// This function allows a script attached to a Pokemon or its parents to prevent stat boost /// changes on that Pokemon. fn prevent_stat_boost_change( &self, _target: &Pokemon, _stat: Statistic, _amount: i8, _self_inflicted: bool, _prevent: &mut bool, ) { } /// This function allows a script attached to a Pokemon or its parents to modify the amount by /// which the stat boost will change. If the stat boost is done by the user itself, self /// inflicted will be true, otherwise it will be false. fn change_stat_boost_change(&self, _target: &Pokemon, _stat: Statistic, _self_inflicted: bool, _amount: &mut i8) {} /// This function allows a script attached to a Pokemon or its parents to prevent an incoming /// secondary effect. This means the move will still hit and do damage, but not trigger its /// secondary effect. Note that this function is not called for status moves. fn prevent_secondary_effect(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _prevent: &mut bool) {} /// This function allows a script attached to a move or its parents to change the chance the /// secondary effect of a move will trigger. The chance is depicted in percentage here, so /// changing this to above or equal to 100 will make it always hit, while setting it to equal or /// below 0 will make it never hit. fn change_effect_chance(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _chance: &mut f32) {} /// This function allows a script attached to a Pokemon or its parents to change the chance the /// secondary effect of an incoming move will trigger. The chance is depicted in percentage here, /// so changing this to above or equal to 100 will make it always hit, while setting it to equal /// or below 0 will make it never hit. fn change_incoming_effect_chance( &self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _chance: &mut f32, ) { } /// This function triggers when the move uses its secondary effect. Moves should implement their /// secondary effects here. Status moves should implement their actual functionality in this /// function as well, as status moves effects are defined as secondary effects for simplicity. fn on_secondary_effect(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8) {} /// This function triggers on a move or its parents when all hits on a target are finished. fn on_after_hits(&self, _move: &ExecutingMove, _target: &Arc) {} /// This function prevents the Pokemon it is attached to from being able to switch out. fn prevent_self_switch(&self, _choice: &TurnChoice, _prevent: &mut bool) {} /// This function allows the prevention of switching for any opponent. fn prevent_opponent_switch(&self, _choice: &TurnChoice, _prevent: &mut bool) {} /// This function is called on a move and its parents when the move fails. fn on_fail(&self, _target: &Pokemon) {} /// This function is called on a script when an opponent fails. fn on_opponent_fail(&self, _target: &Pokemon) {} /// This function allows preventing the running away of the Pokemon its attached to fn prevent_self_run_away(&self, _choice: &TurnChoice, _prevent: &mut bool) {} /// This function prevents a Pokemon on another side than where its attached to from running away. fn prevent_opponent_run_away(&self, _choice: &TurnChoice, _prevent: &mut bool) {} /// This function id triggered on all scripts active in the battle after all choices have finished /// running. Note that choices are not active anymore here, so their scripts do not call this /// function. fn on_end_turn(&self) {} /// This function is triggered on a Pokemon and its parents when the given Pokemon takes damage. fn on_damage(&self, _pokemon: &Pokemon, _source: DamageSource, _old_health: u32, _new_health: u32) {} /// This function is triggered on a Pokemon and its parents when the given Pokemon faints. fn on_faint(&self, _pokemon: &Pokemon, _source: DamageSource) {} /// This function is triggered on a Pokemon and its parents when the given Pokemon is switched into /// the battlefield. fn on_switch_in(&self, _pokemon: &Pokemon) {} /// This function is triggered on a Pokemon and its parents when the given Pokemon consumes the /// held item it had. fn on_after_held_item_consume(&self, _pokemon: &Pokemon, _item: &Item) {} /// This function is triggered on a Pokemon and its parents when the given Pokemon gains experience, /// and allows for changing this amount of experience. fn change_experience_gained(&self, _fainted_mon: &Pokemon, _winning_mon: &Pokemon, _amount: &mut u32) {} /// This function is triggered on a Pokemon and its parents when the given Pokemon gains experience, /// and allows for making the experience be shared across multiple Pokemon. fn share_experience(&self, _fainted_mon: &Pokemon, _winning_mon: &Pokemon, _shares: &mut bool) {} /// This function is triggered on a battle and its parents when something attempts to change the /// weather, and allows for blocking the weather change. fn block_weather(&self, _battle: &Battle, _blocked: &mut bool) {} /// This function is called when a Pokeball is thrown at a Pokemon, and allows modifying the catch /// rate of this attempt. Pokeball modifier effects should be implemented here, as well as for /// example status effects that change capture rates. fn change_capture_rate_bonus(&self, _target: &Pokemon, _pokeball: &Item, _modifier: &mut u8) {} /// Helper function to turn the self into an Any for downcasting. fn as_any(&self) -> &dyn Any; /// Helper function to turn the self into an Any for downcasting. fn as_any_mut(&mut self) -> &mut dyn Any; } impl Debug for dyn Script { fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result { Ok(()) } } /// A script holder defines the underlying type of how we store individual scripts on a script source. /// The outer [`Arc`] here is so we can take a weak reference to it with dynamic lifetimes, as well as /// copy the value for use during threading. /// The [`RwLock`] is so we cannot modify the internal script while we're reading from it. /// The inner [`Arc`] is the default of Scripts, where there can be multiple owners, and where it can be /// often copied. type ScriptHolder = Arc>>>; /// A script container is used to store a exclusive script. This is for example used for non-volatile /// Pokemon status, weather, ability, etc. It can only hold one script, and that script can be replaced /// if needed. #[derive(Default, Debug)] pub struct ScriptContainer { /// The actual place where the script is stored. script: ScriptHolder, /// The join_handle is a very specific piece of data used in one single specific case; the replacing /// of a script while the script is actively being read from. The RwLock blocks the write, and /// as such, this is delayed to a thread. This join handle is this thread. In some very specific /// cases (mostly test cases), it can be required to make sure that this has been completed before /// continuing. pub(crate) join_handle: RwLock>>, } impl ScriptContainer { /// Initialize a script container with a specific script. To initialize with an empty script, use /// default() instead. pub fn new(script: Arc) -> ScriptContainer { Self { script: Arc::new(RwLock::new(Some(script))), join_handle: RwLock::new(None), } } /// Get the internal script. Note that this can only be None if the script was marked for deletion. pub fn get(&self) -> Option<&ScriptHolder> { if self.script.read().is_some_and(|a| a.is_marked_for_deletion()) { if !self.script.is_locked() { self.script.write().take(); } None } else { Some(&self.script) } } /// Changes the internal script. This has several side effects: primarily that it calls the /// [`Script::on_remove`] function on itself. Other special cases are that the script is currently read /// from, and as such can not be replaced yet. In this case, the script will be replaced on a /// separate thread, and be replaced immediately after the script stops being active. pub fn set(&self, script: Arc) -> Arc { if let Some(v) = self.script.read().deref() { v.on_remove(); v.mark_for_deletion(); } // If we have a lock on the script, we can't replace it now. Wait until we can get the lock, // and only replace it then. Don't block in the meantime. if self.script.is_locked() { let holder = self.script.clone(); let new = script.clone(); let handle = std::thread::spawn(move || { holder.write().replace(new); }); self.join_handle.write().replace(handle); } else { self.script.write().replace(script.clone()); }; script } /// Removes the internal script. This calls the [`Script::on_remove`] function on itself. If no /// locks are held to the script, we also immediately set the internal value to None, otherwise /// we just mark it as deleted. If we do this we remove the script later on. pub fn clear(&self) { if let Some(v) = self.script.read().deref() { v.on_remove(); v.mark_for_deletion(); } if !self.script.is_locked() { self.script.write().take(); } } /// Gets the underlying reference counter to the script. pub fn arc(&self) -> &ScriptHolder { &self.script } /// Get the underlying script as the downcasted value. pub fn get_as(&self) -> MappedRwLockReadGuard { RwLockReadGuard::map(self.script.read(), |a| unsafe { let ptr = a.as_ref().as_ref().unwrap().as_ref() as *const dyn Script; let any = ptr.as_ref().unwrap().as_any(); any.downcast_ref::().unwrap() }) } } impl From for ScriptContainer { fn from(a: ScriptHolder) -> Self { Self { script: a, join_handle: RwLock::new(None), } } } impl Clone for ScriptContainer { fn clone(&self) -> Self { Self { script: self.script.clone(), join_handle: RwLock::new(None), } } } #[cfg(test)] mod tests { use std::sync::atomic::{AtomicBool, AtomicPtr}; use super::*; pub struct TestScript { name: StringKey, container: AtomicPtr, suppressed_count: AtomicUsize, marked_for_deletion: AtomicBool, } impl TestScript { fn new() -> Self { Self { name: StringKey::new("test".into()), container: AtomicPtr::::default(), suppressed_count: AtomicUsize::new(0), marked_for_deletion: Default::default(), } } } unsafe impl Sync for TestScript {} unsafe impl Send for TestScript {} impl Script for TestScript { fn name(&self) -> &StringKey { &self.name } fn get_marked_for_deletion(&self) -> &AtomicBool { &self.marked_for_deletion } fn get_suppressed_count(&self) -> &AtomicUsize { &self.suppressed_count } fn stack(&self) { unsafe { self.container.load(Ordering::Relaxed).as_ref().unwrap().clear() } } fn as_any(&self) -> &dyn Any { self } fn as_any_mut(&mut self) -> &mut dyn Any { self } } // Removing yourself while active should be completely valid for a script. Consider for example // a status effect such as sleep clearing itself in a script hook. As the script is actively // being read from at that time, we should wait until the script hook is finished reading, and // then dispose of the script. #[test] fn clear_self_while_active() { let script = Arc::new(TestScript::new()); let mut container = ScriptContainer::new(script); let c = &mut container as *mut ScriptContainer; let w = container.script.read(); let script: &TestScript = w.as_ref().unwrap().as_any().downcast_ref().unwrap(); script.container.store(c, Ordering::SeqCst); drop(w); // Initialize with the script being taken as read lock. This prevents the script from actually // removing itself, as it's still doing things. container.script.read().as_ref().unwrap().stack(); // If we now try and get the script, it will be none the first time. This has the side effect // of actually disposing of the script. assert!(container.get().is_none()); // As we've properly disposed of the script now, the next fetch will return something. assert!(container.get().is_some()); // But the value it returns shows that it is empty. assert!(container.get().unwrap().read().is_none()); } pub struct ReplaceTestScript { name: StringKey, container: AtomicPtr, suppressed_count: AtomicUsize, marked_for_deletion: AtomicBool, } impl ReplaceTestScript { fn new(name: StringKey) -> Self { Self { name, container: AtomicPtr::::default(), suppressed_count: AtomicUsize::new(0), marked_for_deletion: Default::default(), } } fn test(&self, script: Arc) { unsafe { self.container.load(Ordering::Relaxed).as_ref().unwrap().set(script); } } } unsafe impl Sync for ReplaceTestScript {} unsafe impl Send for ReplaceTestScript {} impl Script for ReplaceTestScript { fn name(&self) -> &StringKey { &self.name } fn get_marked_for_deletion(&self) -> &AtomicBool { &self.marked_for_deletion } fn get_suppressed_count(&self) -> &AtomicUsize { &self.suppressed_count } fn as_any(&self) -> &dyn Any { self } fn as_any_mut(&mut self) -> &mut dyn Any { self } } #[test] fn replace_self_while_active() { let script = Arc::new(ReplaceTestScript::new("script1".into())); let mut container = ScriptContainer::new(script); let c = &mut container as *mut ScriptContainer; let w = container.script.read(); let script: &ReplaceTestScript = w.as_ref().unwrap().as_any().downcast_ref().unwrap(); script.container.store(c, Ordering::SeqCst); drop(w); let script2 = Arc::new(ReplaceTestScript::new("script2".into())); container .script .read() .as_ref() .unwrap() .as_any() .downcast_ref::() .unwrap() .test(script2); let mut jh = container.join_handle.write(); if jh.is_some() { let h = jh.take().unwrap(); h.join().unwrap(); } assert_eq!( container.script.read().as_ref().unwrap().name(), &StringKey::new("script2".into()) ); } } /// Data to store references to their owning objects on scripts. pub enum ScriptOwnerData { /// A script attached to a Pokemon has a reference to that Pokemon. Pokemon(AtomicPtr), /// A script attached to a Battle Side has a reference to that Battle Side. BattleSide(AtomicPtr), /// A script attached to a Battle has a reference to that Battle. Battle(AtomicPtr), /// A script also can have no owner. None, } impl Into for &Pokemon { fn into(self) -> ScriptOwnerData { ScriptOwnerData::Pokemon(AtomicPtr::new(self as *const Pokemon as *mut Pokemon)) } } impl Into for &BattleSide { fn into(self) -> ScriptOwnerData { ScriptOwnerData::BattleSide(AtomicPtr::new(self as *const BattleSide as *mut BattleSide)) } } impl Into for &Battle { fn into(self) -> ScriptOwnerData { ScriptOwnerData::Battle(AtomicPtr::new(self as *const Battle as *mut Battle)) } }