From 03f5e3bb5a94c8040021b68b400f1e25b0984118 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Thu, 30 Jun 2022 17:34:57 +0200 Subject: [PATCH] A lot more documentation, some initial work on the script resolver. --- src/dynamic_data/flow/turn_runner.rs | 10 +- src/dynamic_data/history/mod.rs | 3 - src/dynamic_data/libraries/damage_library.rs | 254 ++++++++++-------- src/dynamic_data/libraries/dynamic_library.rs | 42 ++- src/dynamic_data/libraries/misc_library.rs | 57 +--- src/dynamic_data/libraries/script_resolver.rs | 75 +++++- src/dynamic_data/mod.rs | 3 - src/dynamic_data/models/battle.rs | 55 +++- src/dynamic_data/models/battle_party.rs | 15 +- src/dynamic_data/models/battle_random.rs | 11 + src/dynamic_data/models/battle_result.rs | 4 + src/dynamic_data/models/battle_side.rs | 47 +++- src/dynamic_data/models/damage_source.rs | 5 +- src/dynamic_data/models/executing_move.rs | 42 ++- src/dynamic_data/models/learned_move.rs | 14 +- src/dynamic_data/models/pokemon.rs | 10 +- src/dynamic_data/script_handling/script.rs | 10 - tests/common/library_loader.rs | 3 +- 18 files changed, 450 insertions(+), 210 deletions(-) delete mode 100644 src/dynamic_data/history/mod.rs diff --git a/src/dynamic_data/flow/turn_runner.rs b/src/dynamic_data/flow/turn_runner.rs index 10e9682..c11285c 100644 --- a/src/dynamic_data/flow/turn_runner.rs +++ b/src/dynamic_data/flow/turn_runner.rs @@ -224,10 +224,10 @@ impl<'own, 'library> Battle<'own, 'library> { ); if !block_critical { - let is_critical = self - .library() - .misc_library() - .is_critical(self, executing_move, target, hit_index); + let is_critical = + self.library() + .damage_calculator() + .is_critical(self, executing_move, target, hit_index); hit_data.set_critical(is_critical); } let base_power = self.library().damage_calculator().get_base_power( @@ -282,7 +282,7 @@ impl<'own, 'library> Battle<'own, 'library> { hit_data.set_damage(damage); } if damage > 0 { - target.damage(damage, DamageSource::AttackDamage); + target.damage(damage, DamageSource::MoveDamage); if !target.is_fainted() { script_hook!(on_incoming_hit, target, executing_move, target, hit_index); } else { diff --git a/src/dynamic_data/history/mod.rs b/src/dynamic_data/history/mod.rs deleted file mode 100644 index 7ef0549..0000000 --- a/src/dynamic_data/history/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -/// The history holder holds all specific history events that happened in the battle. -#[derive(Debug)] -pub struct HistoryHolder {} diff --git a/src/dynamic_data/libraries/damage_library.rs b/src/dynamic_data/libraries/damage_library.rs index a8c6295..dc672bd 100644 --- a/src/dynamic_data/libraries/damage_library.rs +++ b/src/dynamic_data/libraries/damage_library.rs @@ -1,13 +1,15 @@ use std::sync::Arc; use crate::dynamic_data::script_handling::ScriptSource; -use crate::dynamic_data::Pokemon; +use crate::dynamic_data::{Battle, Pokemon}; use crate::dynamic_data::{ExecutingMove, HitData}; use crate::script_hook; use crate::static_data::{MoveCategory, Statistic}; +/// A damage library holds the functions related to the calculation of damage. As this can change in +/// different generations and implementations, this is handled through a trait. pub trait DamageLibrary: std::fmt::Debug { - fn has_randomness(&self) -> bool; + /// Calculate the damage for a given hit on a Pokemon. fn get_damage( &self, executing_move: &ExecutingMove, @@ -16,6 +18,7 @@ pub trait DamageLibrary: std::fmt::Debug { hit_data: &HitData, ) -> u32; + /// Calculate the base power for a given hit on a Pokemon. fn get_base_power( &self, executing_move: &ExecutingMove, @@ -24,39 +27,142 @@ pub trait DamageLibrary: std::fmt::Debug { hit_data: &HitData, ) -> u8; + /// Returns whether a specified hit should be critical or not. + fn is_critical( + &self, + battle: &Battle, + executing_move: &ExecutingMove, + target: &Arc, + hit_number: u8, + ) -> bool; +} + +/// The implementation of a Damage Library for generation 7. +#[derive(Debug)] +pub struct Gen7DamageLibrary { + /// Defines whether or not a random damage modifier is applied to damage (0.85 - 1.00). + has_randomness: bool, +} + +impl Gen7DamageLibrary { + /// Creates a new generation 7 damage library. `has_randomness` defines whether a random damage + /// modifier (0.85x - 1.00x) is applied to the calculated damage. + pub fn new(has_randomness: bool) -> Self { + Self { has_randomness } + } + + /// Calculates the modifier applied to damage from the statistics of the relevant Pokemon. fn get_stat_modifier( &self, executing_move: &ExecutingMove, target: &Arc, hit_number: u8, hit_data: &HitData, - ) -> f32; + ) -> f32 { + let user = executing_move.user(); + let offensive_stat; + let defensive_stat; + // Get the relevant stats based on the move category. + if executing_move.use_move().category() == MoveCategory::Physical { + offensive_stat = Statistic::Attack; + defensive_stat = Statistic::Defense; + } else { + offensive_stat = Statistic::SpecialAttack; + defensive_stat = Statistic::SpecialDefense; + } + // Check if we can bypass the defensive stat boost on the target. We default to this if the + // move is critical, and the target has a defensive stat boost of > 0, but a script is + // allowed to change this. + let mut bypass_defensive_stat_boost = hit_data.is_critical() && target.stat_boost(defensive_stat) > 0; + script_hook!( + bypass_defensive_stat_boost, + executing_move, + executing_move, + target, + hit_number, + &mut bypass_defensive_stat_boost + ); + // Check if we can bypass the offensive stat boost on the user. We default to this if the + // move is critical, and the user has an offensive stat boost of < 0, but a script is + // allowed to change this. + let mut bypass_offensive_stat_boost = hit_data.is_critical() && user.stat_boost(offensive_stat) > 0; + script_hook!( + bypass_offensive_stat_boost, + executing_move, + executing_move, + target, + hit_number, + &mut bypass_offensive_stat_boost + ); + // Get the unboosted stat if we bypass the stat boost, otherwise get the boosted stat. + let mut defensive_stat = if bypass_defensive_stat_boost { + target.flat_stats().get_stat(offensive_stat) + } else { + target.boosted_stats().get_stat(offensive_stat) + }; + + let mut offensive_stat = if bypass_offensive_stat_boost { + user.flat_stats().get_stat(offensive_stat) + } else { + user.boosted_stats().get_stat(offensive_stat) + }; + + // Allow scripts to modify the raw values we use for the damage modifier. + script_hook!( + change_defensive_stat_value, + executing_move, + executing_move, + target, + hit_number, + &mut defensive_stat + ); + script_hook!( + change_offensive_stat_value, + executing_move, + executing_move, + target, + hit_number, + &mut offensive_stat + ); + + // Calculate the effective stat modifier. + let mut stat_modifier = offensive_stat as f32 / defensive_stat as f32; + script_hook!( + change_damage_stat_modifier, + executing_move, + executing_move, + target, + hit_number, + &mut stat_modifier + ); + + stat_modifier + } + + /// Gets the damage modifier. This is a value that defaults to 1.0, but can be modified by scripts + /// to apply a raw modifier to the damage. fn get_damage_modifier( &self, executing_move: &ExecutingMove, target: &Arc, hit_number: u8, - hit_data: &HitData, - ) -> f32; -} - -#[derive(Debug)] -pub struct Gen7DamageLibrary { - has_randomness: bool, -} - -impl Gen7DamageLibrary { - pub fn new(has_randomness: bool) -> Self { - Self { has_randomness } + _hit_data: &HitData, + ) -> f32 { + let mut modifier = 1.0; + script_hook!( + change_damage_modifier, + executing_move, + executing_move, + target, + hit_number, + &mut modifier + ); + modifier } } impl DamageLibrary for Gen7DamageLibrary { - fn has_randomness(&self) -> bool { - self.has_randomness - } - fn get_damage( &self, executing_move: &ExecutingMove, @@ -166,108 +272,34 @@ impl DamageLibrary for Gen7DamageLibrary { base_power } - fn get_stat_modifier( + fn is_critical( &self, + battle: &Battle, executing_move: &ExecutingMove, target: &Arc, hit_number: u8, - hit_data: &HitData, - ) -> f32 { - let mut user = executing_move.user().clone(); - script_hook!( - change_damage_stats_user, - executing_move, - executing_move, - target, - hit_number, - &mut user - ); - let offensive_stat; - let defensive_stat; - if executing_move.use_move().category() == MoveCategory::Physical { - offensive_stat = Statistic::Attack; - defensive_stat = Statistic::Defense; - } else { - offensive_stat = Statistic::SpecialAttack; - defensive_stat = Statistic::SpecialDefense; + ) -> bool { + // Status moves can't be critical. + if executing_move.use_move().category() == MoveCategory::Status { + return false; } - let mut bypass_defensive_stat_boost = hit_data.is_critical() && target.stat_boost(defensive_stat) > 0; + // Get the critical stage from scripts. + let mut crit_stage = 0; script_hook!( - bypass_defensive_stat_boost, + change_critical_stage, executing_move, executing_move, target, hit_number, - &mut bypass_defensive_stat_boost + &mut crit_stage ); - let mut bypass_offensive_stat_boost = hit_data.is_critical() && user.stat_boost(offensive_stat) > 0; - script_hook!( - bypass_offensive_stat_boost, - executing_move, - executing_move, - target, - hit_number, - &mut bypass_offensive_stat_boost - ); - - let mut defensive_stat = if bypass_defensive_stat_boost { - target.flat_stats().get_stat(offensive_stat) - } else { - target.boosted_stats().get_stat(offensive_stat) - }; - - let mut offensive_stat = if bypass_offensive_stat_boost { - user.flat_stats().get_stat(offensive_stat) - } else { - user.boosted_stats().get_stat(offensive_stat) - }; - - script_hook!( - change_defensive_stat_value, - executing_move, - executing_move, - target, - hit_number, - &mut defensive_stat - ); - script_hook!( - change_offensive_stat_value, - executing_move, - executing_move, - target, - hit_number, - &mut offensive_stat - ); - - let mut stat_modifier = offensive_stat as f32 / defensive_stat as f32; - script_hook!( - change_damage_stat_modifier, - executing_move, - executing_move, - target, - hit_number, - &mut stat_modifier - ); - - stat_modifier - } - - fn get_damage_modifier( - &self, - executing_move: &ExecutingMove, - target: &Arc, - hit_number: u8, - _hit_data: &HitData, - ) -> f32 { - let mut modifier = 1.0; - script_hook!( - change_damage_modifier, - executing_move, - executing_move, - target, - hit_number, - &mut modifier - ); - modifier + // Crit stage is an unsigned byte, so we only care about values of 0 or higher. + // For a critical stage of 3+ we always return true. + match crit_stage { + 0 => battle.random().get_max(24) == 0, + 1 => battle.random().get_max(8) == 0, + 2 => battle.random().get_max(2) == 0, + _ => true, + } } } diff --git a/src/dynamic_data/libraries/dynamic_library.rs b/src/dynamic_data/libraries/dynamic_library.rs index 62706ba..3fd9d48 100644 --- a/src/dynamic_data/libraries/dynamic_library.rs +++ b/src/dynamic_data/libraries/dynamic_library.rs @@ -1,3 +1,4 @@ +use std::ffi::c_void; use std::ops::Deref; use std::sync::Arc; @@ -5,56 +6,85 @@ use crate::dynamic_data::libraries::battle_stat_calculator::BattleStatCalculator use crate::dynamic_data::libraries::damage_library::DamageLibrary; use crate::dynamic_data::libraries::misc_library::MiscLibrary; use crate::dynamic_data::libraries::script_resolver::ScriptCategory; -use crate::dynamic_data::ItemScript; use crate::dynamic_data::Script; +use crate::dynamic_data::{ItemScript, ScriptResolver}; use crate::static_data::Item; use crate::static_data::StaticData; use crate::{PkmnResult, StringKey}; +/// The dynamic library stores a static data library, as well as holding different libraries and +/// calculators that might be customized between different generations and implementations. #[derive(Debug)] pub struct DynamicLibrary { + /// The static data is the immutable storage data for this library. static_data: StaticData, + /// The stat calculator deals with the calculation of flat and boosted stats, based on the + /// Pokemons attributes. stat_calculator: Box, + /// The damage calculator deals with the calculation of things relating to damage. damage_calculator: Box, + /// The Misc Library holds minor functions that do not fall in any of the other libraries and + /// calculators. misc_library: Box>, + + /// The script resolver deals with how to resolve the scripts from specific unique key combinations. + script_resolver: Box, } unsafe impl Sync for DynamicLibrary {} - unsafe impl Send for DynamicLibrary {} impl DynamicLibrary { + /// Instantiates a new DynamicLibrary with given parameters. pub fn new( static_data: StaticData, stat_calculator: Box, damage_calculator: Box, misc_library: Box>, + script_resolver: Box, ) -> Self { Self { static_data, stat_calculator, damage_calculator, misc_library, + script_resolver, } } + /// The static data is the immutable storage data for this library. pub fn static_data(&self) -> &StaticData { &self.static_data } + /// The stat calculator deals with the calculation of flat and boosted stats, based on the + /// Pokemons attributes. pub fn stat_calculator(&self) -> &dyn BattleStatCalculator { self.stat_calculator.deref() } + /// The damage calculator deals with the calculation of things relating to damage. pub fn damage_calculator(&self) -> &dyn DamageLibrary { self.damage_calculator.deref() } + /// The Misc Library holds minor functions that do not fall in any of the other libraries and + /// calculators. pub fn misc_library(&self) -> &dyn MiscLibrary<'static> { self.misc_library.deref() } - pub fn load_script(&self, _category: ScriptCategory, _key: &StringKey) -> PkmnResult>> { - todo!() + /// Loads a standard script with a given unique combination of category and key. If no script + /// can be created with this combination, returns None. + pub fn load_script( + &self, + owner: *const c_void, + _category: ScriptCategory, + _key: &StringKey, + ) -> PkmnResult>> { + self.script_resolver.load_script(owner, _category, _key) } - pub fn load_item_script(&self, _key: &Item) -> PkmnResult>> { + /// Loads an item script with the given unique key. If no script can be created with this + /// combinations, returns None. Note that ItemScripts are immutable, as their script should be + /// shared between all different usages. + pub fn load_item_script(&self, _key: &Item) -> PkmnResult>> { todo!() } } @@ -65,6 +95,7 @@ pub mod test { use crate::dynamic_data::libraries::damage_library::Gen7DamageLibrary; use crate::dynamic_data::libraries::dynamic_library::DynamicLibrary; use crate::dynamic_data::libraries::misc_library::Gen7MiscLibrary; + use crate::dynamic_data::EmptyScriptResolver; pub fn build() -> DynamicLibrary { DynamicLibrary { @@ -72,6 +103,7 @@ pub mod test { stat_calculator: Box::new(Gen7BattleStatCalculator {}), damage_calculator: Box::new(Gen7DamageLibrary::new(false)), misc_library: Box::new(Gen7MiscLibrary::new()), + script_resolver: Box::new(EmptyScriptResolver {}), } } } diff --git a/src/dynamic_data/libraries/misc_library.rs b/src/dynamic_data/libraries/misc_library.rs index 91d14be..1dcfa7f 100644 --- a/src/dynamic_data/libraries/misc_library.rs +++ b/src/dynamic_data/libraries/misc_library.rs @@ -3,24 +3,17 @@ use std::sync::Arc; use hashbrown::HashSet; -use crate::dynamic_data::choices::{MoveChoice, SwitchChoice, TurnChoice}; -use crate::dynamic_data::script_handling::ScriptSource; -use crate::dynamic_data::Battle; -use crate::dynamic_data::ExecutingMove; +use crate::dynamic_data::choices::{MoveChoice, TurnChoice}; use crate::dynamic_data::Pokemon; use crate::dynamic_data::{LearnedMove, MoveLearnMethod}; use crate::static_data::{MoveCategory, MoveData, MoveTarget, SecondaryEffect}; -use crate::{script_hook, StringKey}; +use crate::StringKey; +/// The misc library holds several misc functions required for the battle to run. pub trait MiscLibrary<'library>: Debug { - fn is_critical( - &self, - battle: &Battle, - executing_move: &ExecutingMove, - target: &Arc, - hit_number: u8, - ) -> bool; - fn can_flee(&self, choice: &SwitchChoice) -> bool; + /// Returns whether or not a Pokemon is allowed to flee or switch out. + fn can_flee(&self, choice: &TurnChoice) -> bool; + /// Returns the move we need to use if we can't use another move. Typically Struggle. fn replacement_move<'func>( &'func self, user: &Arc>, @@ -31,13 +24,19 @@ pub trait MiscLibrary<'library>: Debug { // TODO: get time } +/// A gen 7 implementation for the MiscLibrary. #[derive(Debug)] pub struct Gen7MiscLibrary<'library> { + /// The move data for struggle. This is a pointer due to lifetime issues; we know that the + /// learned move based on this has the same lifetime as the move data, but the compiler does not. + /// If possible in a sane manner, we should get rid of this pointer. struggle_data: *const MoveData, + /// The learned move data for struggle. struggle_learned_move: Arc>, } impl<'library> Gen7MiscLibrary<'library> { + /// Instantiates a new MiscLibrary. pub fn new() -> Self { let struggle_data = Box::new(MoveData::new( &StringKey::new("struggle"), @@ -75,36 +74,7 @@ impl<'library> Drop for Gen7MiscLibrary<'library> { } impl<'library> MiscLibrary<'library> for Gen7MiscLibrary<'library> { - fn is_critical( - &self, - battle: &Battle, - executing_move: &ExecutingMove, - target: &Arc, - hit_number: u8, - ) -> bool { - if executing_move.use_move().category() == MoveCategory::Status { - return false; - } - let mut crit_stage = 0; - script_hook!( - change_critical_stage, - executing_move, - executing_move, - target, - hit_number, - &mut crit_stage - ); - // Crit stage is an unsigned byte, so we only care about values of 0 or higher. - // For a critical stage of 3+ we always return true. - match crit_stage { - 0 => battle.random().get_max(24) == 0, - 1 => battle.random().get_max(8) == 0, - 2 => battle.random().get_max(2) == 0, - _ => true, - } - } - - fn can_flee(&self, _choice: &SwitchChoice) -> bool { + fn can_flee(&self, _choice: &TurnChoice) -> bool { todo!() } @@ -114,6 +84,7 @@ impl<'library> MiscLibrary<'library> for Gen7MiscLibrary<'library> { target_side: u8, target_index: u8, ) -> TurnChoice<'func, 'library> { + self.struggle_learned_move.restore_all_uses(); TurnChoice::Move(MoveChoice::new( user.clone(), self.struggle_learned_move.clone(), diff --git a/src/dynamic_data/libraries/script_resolver.rs b/src/dynamic_data/libraries/script_resolver.rs index 51b3480..6e8f31e 100644 --- a/src/dynamic_data/libraries/script_resolver.rs +++ b/src/dynamic_data/libraries/script_resolver.rs @@ -1,12 +1,71 @@ -pub trait ScriptResolver {} +use std::ffi::c_void; +use std::fmt::Debug; +use std::sync::Arc; +use crate::dynamic_data::{ItemScript, Script}; +use crate::static_data::Item; +use crate::{PkmnResult, StringKey}; + +/// A script resolver deals with the resolving of scripts. These scripts are non-hardcoded +/// implementations of different effects in Pokemon. This allows for things such as generational +/// differences, and custom implementations. +pub trait ScriptResolver: Debug { + /// Loads a standard script with a given unique combination of category and key. If no script + /// can be created with this combination, returns None. + fn load_script( + &self, + owner: *const c_void, + category: ScriptCategory, + script_key: &StringKey, + ) -> PkmnResult>>; + + /// Loads an item script with the given unique key. If no script can be created with this + /// combinations, returns None. Note that ItemScripts are immutable, as their script should be + /// shared between all different usages. + fn load_item_script(&self, _key: &Item) -> PkmnResult>>; +} + +/// A script category defines a sub-group of scripts. This can be used to have multiple scripts with +/// the same name, but a different script. It should be completely valid for a move to have the same +/// name as an ability, or more commonly: for a script attached to a Pokemon to have the same name as +/// a move that placed it there. #[derive(Debug, Clone)] pub enum ScriptCategory { - Move, - Ability, - Status, - Pokemon, - Battle, - Side, - ItemBattleTrigger, + /// A script that belongs to a move. This generally is only the script that is attached to a + /// [`MoveChoice`](crate::dynamic_data::MoveChoice) and [`ExecutingMove`](crate::dynamic_data::ExecutingMove) + Move = 0, + /// An ability script. Scripts in this category are always abilities, and therefore always + /// attached to a Pokemon. + Ability = 1, + /// A non volatile status script. Scripts in this category are always non volatile statuses, and + /// therefore always attached to a Pokemon. + Status = 2, + /// A volatile status script. Scripts in this category are always volatile status effects, and + /// therefore always attached to a Pokemon. + Pokemon = 3, + /// A script that can be attached to an entire side. + Side = 4, + /// A script that can be attached to the entire battle. + Battle = 5, + /// A special script for held items. As they're part of a held item, they're attached to a Pokemon. + ItemBattleTrigger = 6, +} + +/// A basic empty script resolver, that always returns None. +#[derive(Debug)] +pub struct EmptyScriptResolver {} + +impl ScriptResolver for EmptyScriptResolver { + fn load_script( + &self, + _owner: *const c_void, + _category: ScriptCategory, + _script_key: &StringKey, + ) -> PkmnResult>> { + Ok(None) + } + + fn load_item_script(&self, _key: &Item) -> PkmnResult>> { + Ok(None) + } } diff --git a/src/dynamic_data/mod.rs b/src/dynamic_data/mod.rs index 4ee7666..8191a60 100644 --- a/src/dynamic_data/mod.rs +++ b/src/dynamic_data/mod.rs @@ -5,8 +5,6 @@ pub use event_hooks::*; #[doc(inline)] pub use flow::*; #[doc(inline)] -pub use history::*; -#[doc(inline)] pub use libraries::*; #[doc(inline)] pub use models::*; @@ -16,7 +14,6 @@ pub use script_handling::*; mod choices; mod event_hooks; mod flow; -mod history; pub(crate) mod libraries; mod models; mod script_handling; diff --git a/src/dynamic_data/models/battle.rs b/src/dynamic_data/models/battle.rs index 6eb0097..5afd0ed 100644 --- a/src/dynamic_data/models/battle.rs +++ b/src/dynamic_data/models/battle.rs @@ -7,7 +7,6 @@ use parking_lot::RwLock; use crate::dynamic_data::choices::TurnChoice; use crate::dynamic_data::event_hooks::{Event, EventHook}; -use crate::dynamic_data::history::HistoryHolder; use crate::dynamic_data::is_valid_target; use crate::dynamic_data::models::battle_party::BattleParty; use crate::dynamic_data::models::battle_random::BattleRandom; @@ -22,28 +21,43 @@ use crate::dynamic_data::VolatileScripts; use crate::dynamic_data::{ScriptCategory, ScriptSource, ScriptSourceData, ScriptWrapper}; use crate::{script_hook, PkmnResult, StringKey}; +/// A pokemon battle, with any amount of sides and pokemon per side. #[derive(Debug)] pub struct Battle<'own, 'library> { + /// The library the battle uses for handling. library: &'own DynamicLibrary, + /// A list of all different parties in the battle. parties: Vec>, + /// Whether or not Pokemon can flee from the battle. can_flee: bool, + /// The number of sides in the battle. Typically 2. number_of_sides: u8, + /// The number of Pokemon that can be on each side. pokemon_per_side: u8, + /// A list of all sides in the battle. sides: Vec>, + /// The RNG used for the battle. random: BattleRandom, + /// A queue of the yet to be executed choices in a turn. current_turn_queue: RwLock>>, + /// Whether or not the battle has ended. has_ended: AtomicBool, + /// The eventual result of the battle. Inconclusive until the battle is ended. result: Atomic, + /// The handler to send all events to. event_hook: EventHook, - history_holder: Box, + /// The index of the current turn. 0 until all choices current_turn: AtomicU32, + /// All the volatile scripts attached to a Pokemon volatile_scripts: Arc, + /// The time the last turn took to run. Defaults to 0. last_turn_time: Atomic, - + /// Data required for this script to be a script source. script_source_data: RwLock, } impl<'own, 'library> Battle<'own, 'library> { + /// Initializes a new battle. pub fn new( library: &'own DynamicLibrary, parties: Vec>, @@ -52,6 +66,8 @@ impl<'own, 'library> Battle<'own, 'library> { pokemon_per_side: u8, random_seed: Option, ) -> Self { + // If no seed was passed, we use the current time as seed for the RNG, otherwise we use the + // seed. let random = if let Some(seed) = random_seed { BattleRandom::new_with_seed(seed) } else { @@ -74,7 +90,6 @@ impl<'own, 'library> Battle<'own, 'library> { has_ended: AtomicBool::new(false), result: Atomic::new(BattleResult::Inconclusive), event_hook: Default::default(), - history_holder: Box::new(HistoryHolder {}), current_turn: AtomicU32::new(0), volatile_scripts: Default::default(), last_turn_time: Atomic::new(chrono::Duration::zero()), @@ -89,53 +104,64 @@ impl<'own, 'library> Battle<'own, 'library> { battle } + /// The library the battle uses for handling. pub fn library(&self) -> &'own DynamicLibrary { self.library } + /// A list of all different parties in the battle. pub fn parties(&self) -> &Vec> { &self.parties } + /// Whether or not Pokemon can flee from the battle. pub fn can_flee(&self) -> bool { self.can_flee } + /// The number of sides in the battle. Typically 2. pub fn number_of_sides(&self) -> u8 { self.number_of_sides } + /// The number of Pokemon that can be on each side. pub fn pokemon_per_side(&self) -> u8 { self.pokemon_per_side } + /// A list of all sides in the battle. pub fn sides(&self) -> &Vec> { &self.sides } + /// A mutable list of all sides in the battle. pub fn sides_mut(&mut self) -> &mut Vec> { &mut self.sides } - + /// The RNG used for the battle. pub fn random(&self) -> &BattleRandom { &self.random } + /// Whether or not the battle has ended. pub fn has_ended(&self) -> bool { self.has_ended.load(Ordering::Relaxed) } + /// The eventual result of the battle. Inconclusive until the battle is ended. pub fn result(&self) -> BattleResult { self.result.load(Ordering::Relaxed) } + /// The handler to send all events to. pub fn event_hook(&self) -> &EventHook { &self.event_hook } - pub fn history_holder(&self) -> &HistoryHolder { - self.history_holder.deref() - } + /// The index of the current turn. 0 until all choices pub fn current_turn(&self) -> u32 { self.current_turn.load(Ordering::Relaxed) } + /// The time the last turn took to run. Defaults to 0. pub fn last_turn_time(&self) -> chrono::Duration { self.last_turn_time.load(Ordering::Relaxed) } + /// A queue of the yet to be executed choices in a turn. pub fn current_turn_queue(&self) -> &RwLock>> { &self.current_turn_queue } + /// Get a Pokemon on the battlefield, on a specific side and an index on that side. pub fn get_pokemon(&self, side: u8, index: u8) -> Option>> { let side = self.sides.get(side as usize); side?; @@ -145,6 +171,9 @@ impl<'own, 'library> Battle<'own, 'library> { pokemon.unwrap().clone() } + /// Returns whether a slot on the battlefield can still be filled. If no party is responsible + /// for that slot, or a party is responsible, but has no remaining Pokemon to throw out anymore, + /// this returns false. pub fn can_slot_be_filled(&self, side: u8, index: u8) -> bool { for party in &self.parties { if party.is_responsible_for_index(side, index) && party.has_pokemon_not_in_field() { @@ -154,7 +183,10 @@ impl<'own, 'library> Battle<'own, 'library> { false } + /// Validates whether the battle is still in a non-ended state. If the battle has ended, this + /// properly sets who has won etc. pub fn validate_battle_state(&self) { + // If we've already ended, we dont need to run this. if self.has_ended() { return; } @@ -189,6 +221,7 @@ impl<'own, 'library> Battle<'own, 'library> { self.has_ended.store(true, Ordering::SeqCst); } + /// Checks whether a choice is actually possible. pub fn can_use(&self, choice: &TurnChoice) -> bool { // If the user is not usable, we obviously can;t use the choice. if !choice.user().is_usable() { @@ -211,6 +244,7 @@ impl<'own, 'library> Battle<'own, 'library> { true } + /// Try and set the choice for the battle. If the choice is not valid, this returns false. pub fn try_set_choice(&mut self, choice: TurnChoice<'own, 'library>) -> PkmnResult { if !self.can_use(&choice) { return Ok(false); @@ -227,6 +261,8 @@ impl<'own, 'library> Battle<'own, 'library> { Ok(true) } + /// Checks to see whether all Pokemon on the field have set their choices. If so, we then run + /// the turn. fn check_choices_set_and_run(&self) -> PkmnResult<()> { for side in &self.sides { if !side.all_choices_set() { @@ -292,7 +328,8 @@ impl<'own, 'library> VolatileScripts<'own> for Battle<'own, 'library> { } fn load_volatile_script(&self, key: &StringKey) -> PkmnResult>> { - self.library.load_script(ScriptCategory::Battle, key) + self.library + .load_script((self as *const Self).cast(), ScriptCategory::Battle, key) } } diff --git a/src/dynamic_data/models/battle_party.rs b/src/dynamic_data/models/battle_party.rs index 87c1aaa..e7275c7 100644 --- a/src/dynamic_data/models/battle_party.rs +++ b/src/dynamic_data/models/battle_party.rs @@ -1,20 +1,29 @@ -use crate::dynamic_data::models::pokemon::Pokemon; -use crate::dynamic_data::models::pokemon_party::PokemonParty; use std::sync::Arc; +use crate::dynamic_data::models::pokemon::Pokemon; +use crate::dynamic_data::models::pokemon_party::PokemonParty; + +/// A battle party is a wrapper around a party, with the indices for which the party is responsible +/// on the field attached. #[derive(Debug)] pub struct BattleParty<'own, 'library> { + /// The party the BattleParty is holding. party: Arc>, + /// The indices for which the party is responsible, in the format (side, index) responsible_indices: Vec<(u8, u8)>, } impl<'own, 'library> BattleParty<'own, 'library> { + /// Initializes a battle party with the underlying party, and the indices the party is responsible + /// for. pub fn new(party: Arc>, responsible_indices: Vec<(u8, u8)>) -> Self { Self { party, responsible_indices, } } + + /// Checks whether the party is responsible for the given index. pub fn is_responsible_for_index(&self, side: u8, index: u8) -> bool { for responsible_index in &self.responsible_indices { if responsible_index.0 == side && responsible_index.1 == index { @@ -24,6 +33,7 @@ impl<'own, 'library> BattleParty<'own, 'library> { false } + /// Whether or not the party has non fainted Pokemon that could be thrown out into the field. pub fn has_pokemon_not_in_field(&self) -> bool { for pokemon in self.party.pokemon().iter().flatten() { if pokemon.is_usable() && !pokemon.is_on_battlefield() { @@ -33,6 +43,7 @@ impl<'own, 'library> BattleParty<'own, 'library> { false } + /// Gets a Pokemon at an index. pub fn get_pokemon(&self, index: usize) -> &Option>> { self.party.at(index) } diff --git a/src/dynamic_data/models/battle_random.rs b/src/dynamic_data/models/battle_random.rs index e4e0097..0873f39 100644 --- a/src/dynamic_data/models/battle_random.rs +++ b/src/dynamic_data/models/battle_random.rs @@ -7,32 +7,43 @@ use crate::dynamic_data::script_handling::ScriptSource; use crate::script_hook; use crate::utils::Random; +/// The RNG for a battle. #[derive(Default)] pub struct BattleRandom { + /// 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 { 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) -> i32 { return self.get_rng().lock().unwrap().get(); } + /// Get a random 32 bit integer between 0 and max. pub fn get_max(&self, max: i32) -> i32 { return self.get_rng().lock().unwrap().get_max(max); } + /// Get a random 32 bit integer between min and max. pub fn get_between(&self, min: i32, max: i32) -> i32 { return self.get_rng().lock().unwrap().get_between(min, max); } + /// 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, diff --git a/src/dynamic_data/models/battle_result.rs b/src/dynamic_data/models/battle_result.rs index 6f56783..9f0655b 100644 --- a/src/dynamic_data/models/battle_result.rs +++ b/src/dynamic_data/models/battle_result.rs @@ -1,5 +1,9 @@ +/// The result of a battle. #[derive(Debug, Copy, Clone)] pub enum BattleResult { + /// The battle has no winner. Either the battle has not ended, or everyone is dead, or one of + /// the parties has ran away. Inconclusive, + /// The battle has a winner, with the inner value being the index of the side that has won. Conclusive(u8), } diff --git a/src/dynamic_data/models/battle_side.rs b/src/dynamic_data/models/battle_side.rs index 0f2f347..a2413e9 100644 --- a/src/dynamic_data/models/battle_side.rs +++ b/src/dynamic_data/models/battle_side.rs @@ -16,22 +16,35 @@ use crate::dynamic_data::ScriptSet; use crate::dynamic_data::VolatileScripts; use crate::{script_hook, PkmnResult, StringKey}; +/// A side on a battle. #[derive(Debug)] pub struct BattleSide<'own, 'library> { + /// The index of the side on the battle. index: u8, + /// The number of Pokemon that can be on the side. pokemon_per_side: u8, + /// A list of pokemon currently on the battlefield. pokemon: RwLock>>>>, + /// The currently set choices for all Pokemon on the battlefield. Cleared when the turn starts. choices: RwLock>>>, + /// The slots on the side that can still be filled. Once all slots are set to false, this side + /// has lost the battle. fillable_slots: Vec, + /// The number of choices that are set. choices_set: AtomicU8, + /// A reference to the battle we're part of. battle: *mut Battle<'own, 'library>, + /// Whether or not this side has fled. has_fled_battle: bool, + /// The volatile scripts that are attached to the side. volatile_scripts: Arc, + /// Data required for this to be a script source. script_source_data: RwLock, } impl<'own, 'library> BattleSide<'own, 'library> { + /// Instantiates a battle side. pub fn new(index: u8, pokemon_per_side: u8) -> Self { let mut pokemon = Vec::with_capacity(pokemon_per_side as usize); let mut choices = Vec::with_capacity(pokemon_per_side as usize); @@ -59,39 +72,50 @@ impl<'own, 'library> BattleSide<'own, 'library> { } } + /// Set the battle this side belongs to. pub(crate) fn set_battle(&mut self, battle: *mut Battle<'own, 'library>) { self.battle = battle; } + /// The index of the side on the battle. pub fn index(&self) -> u8 { self.index } + /// The number of Pokemon that can be on the side. pub fn pokemon_per_side(&self) -> u8 { self.pokemon_per_side } + /// A list of pokemon currently on the battlefield. pub fn pokemon(&self) -> RwLockReadGuard<'_, RawRwLock, Vec>>>> { self.pokemon.read() } + /// The currently set choices for all Pokemon on the battlefield. Cleared when the turn starts. pub fn choices(&self) -> &RwLock>>> { &self.choices } - + /// The slots on the side that can still be filled. Once all slots are set to false, this side + /// has lost the battle. pub fn fillable_slots(&self) -> &Vec { &self.fillable_slots } + /// The number of choices that are set. pub fn choices_set(&self) -> u8 { self.choices_set.load(Ordering::SeqCst) } + /// A reference to the battle we're part of. pub fn battle(&self) -> &Battle<'own, 'library> { unsafe { self.battle.as_ref().unwrap() } } + /// Whether or not this side has fled. pub fn has_fled_battle(&self) -> bool { self.has_fled_battle } + /// The volatile scripts that are attached to the side. pub fn volatile_scripts(&self) -> &Arc { &self.volatile_scripts } + /// Whether every Pokemon on this side has its choices pub fn all_choices_set(&self) -> bool { self.choices_set() == self.pokemon_per_side } @@ -110,7 +134,8 @@ impl<'own, 'library> BattleSide<'own, 'library> { true } - pub fn set_choice(&self, choice: TurnChoice<'own, 'library>) { + /// Sets a choice for a Pokemon on this side. + pub(crate) fn set_choice(&self, choice: TurnChoice<'own, 'library>) { for (index, pokemon_slot) in self.pokemon.read().iter().enumerate() { if let Some(pokemon) = pokemon_slot { if std::ptr::eq(pokemon.deref(), choice.user().deref()) { @@ -122,6 +147,7 @@ impl<'own, 'library> BattleSide<'own, 'library> { } } + /// Resets all choices on this side. pub fn reset_choices(&self) { let len = self.choices.read().len(); for i in 0..len { @@ -129,10 +155,12 @@ impl<'own, 'library> BattleSide<'own, 'library> { } } + /// Forcibly removes a Pokemon from the field. pub fn force_clear_pokemon(&mut self, index: u8) { self.pokemon.write()[index as usize] = None; } + /// Switches out a spot on the field for a different Pokemon. pub fn set_pokemon(&self, index: u8, pokemon: Option>>) { { let old = &self.pokemon.read()[index as usize]; @@ -173,6 +201,7 @@ impl<'own, 'library> BattleSide<'own, 'library> { } } + /// Checks whether a Pokemon is on the field in this side. pub fn is_pokemon_on_side(&self, pokemon: Arc>) -> bool { for p in self.pokemon.read().iter().flatten() { if std::ptr::eq(p.deref().deref(), pokemon.deref()) { @@ -182,10 +211,13 @@ impl<'own, 'library> BattleSide<'own, 'library> { false } - pub fn mark_slot_as_unfillable(&self, index: u8) { + /// Marks a slot as unfillable. This happens when no parties are able to fill the slot anymore. + /// If this happens, the slot can not be used again. + pub(crate) fn mark_slot_as_unfillable(&self, index: u8) { self.fillable_slots[index as usize].store(false, Ordering::SeqCst); } + /// Checks whether a slot is unfillable or not. pub fn is_slot_unfillable(&self, pokemon: Arc>) -> bool { for (i, slot) in self.pokemon.read().iter().enumerate() { if let Some(p) = slot { @@ -197,6 +229,7 @@ impl<'own, 'library> BattleSide<'own, 'library> { false } + /// Checks whether the side has been defeated. pub fn is_defeated(&self) -> bool { for fillable_slot in &self.fillable_slots { if fillable_slot.load(Ordering::Relaxed) { @@ -206,19 +239,23 @@ impl<'own, 'library> BattleSide<'own, 'library> { true } + /// Checks whether the side has fled. pub fn has_fled(&self) -> bool { self.has_fled_battle } + /// Mark the side as fled. pub fn mark_as_fled(&mut self) { self.has_fled_battle = true; } + /// Gets a random Pokemon on the given side. pub fn get_random_creature_index(&self) -> u8 { // TODO: Consider adding parameter to only get index for available creatures. self.battle().random().get_max(self.pokemon_per_side as i32) as u8 } + /// Swap two Pokemon on a single side around. pub fn swap_positions(&mut self, a: u8, b: u8) -> bool { // If out of range, don't allow swapping. if a >= self.pokemon_per_side || b >= self.pokemon_per_side { @@ -265,7 +302,9 @@ impl<'own, 'library> VolatileScripts<'own> for BattleSide<'own, 'library> { } fn load_volatile_script(&self, key: &StringKey) -> PkmnResult>> { - self.battle().library().load_script(crate::ScriptCategory::Side, key) + self.battle() + .library() + .load_script((self as *const Self).cast(), crate::ScriptCategory::Side, key) } } diff --git a/src/dynamic_data/models/damage_source.rs b/src/dynamic_data/models/damage_source.rs index e5cbd91..80984d8 100644 --- a/src/dynamic_data/models/damage_source.rs +++ b/src/dynamic_data/models/damage_source.rs @@ -1,5 +1,8 @@ +/// A source of damage. This should be as unique as possible. #[derive(Debug, Clone, Copy)] pub enum DamageSource { - AttackDamage = 0, + /// The damage is done by a move. + MoveDamage = 0, + /// The damage is done by something else. Misc = 1, } diff --git a/src/dynamic_data/models/executing_move.rs b/src/dynamic_data/models/executing_move.rs index 4ae7aaf..84115cd 100644 --- a/src/dynamic_data/models/executing_move.rs +++ b/src/dynamic_data/models/executing_move.rs @@ -13,69 +13,99 @@ use crate::dynamic_data::TargetList; use crate::static_data::MoveData; use crate::{PkmnResult, PokemonError}; +/// A hit data is the data for a single hit, on a single target. #[derive(Default, Debug)] pub struct HitData { + /// Whether or not the hit is critical. critical: AtomicBool, + /// The base power of the hit. base_power: AtomicU8, + /// The type effectiveness of the hit. effectiveness: Atomic, + /// The actual damage of the hit. damage: AtomicU32, + /// The type id of the type used for the hit. move_type: AtomicU8, + /// Whether or not the hit has failed. has_failed: AtomicBool, } impl HitData { + /// Whether or not the hit is critical. pub fn is_critical(&self) -> bool { self.critical.load(Ordering::Relaxed) } + /// The base power of the hit. pub fn base_power(&self) -> u8 { self.base_power.load(Ordering::Relaxed) } + /// The type effectiveness of the hit. pub fn effectiveness(&self) -> f32 { self.effectiveness.load(Ordering::Relaxed) } + /// The actual damage of the hit. pub fn damage(&self) -> u32 { self.damage.load(Ordering::Relaxed) } + /// The type id of the type used for the hit. pub fn move_type(&self) -> u8 { self.move_type.load(Ordering::Relaxed) } + /// Whether or not the hit has failed. pub fn has_failed(&self) -> bool { self.has_failed.load(Ordering::Relaxed) } + /// Sets whether or not the hit is critical. pub fn set_critical(&self, value: bool) { self.critical.store(value, Ordering::SeqCst); } + /// Sets the base power of the hit. pub fn set_base_power(&self, value: u8) { self.base_power.store(value, Ordering::SeqCst); } + /// Sets the type effectiveness of the hit. pub fn set_effectiveness(&self, value: f32) { self.effectiveness.store(value, Ordering::SeqCst); } + /// Sets the actual damage of the hit. pub fn set_damage(&self, value: u32) { self.damage.store(value, Ordering::SeqCst); } + /// Sets the move type id of the hit. pub fn set_move_type(&self, value: u8) { self.move_type.store(value, Ordering::SeqCst); } + /// Marks the hit as failed. pub fn fail(&self) { self.has_failed.store(true, Ordering::SeqCst); } } +/// An executing move is the data of the move for while it is executing. #[derive(Debug)] pub struct ExecutingMove<'own, 'battle, 'library> { + /// The number of hits this move has. number_of_hits: u8, + /// A list of hits for this move. For multi target multi hit moves, this stores the hits linearly, + /// for example: (target1, hit1), (target1, hit2), (target2, hit1), (target2, hit2), etc. hits: Vec, + /// The user of the move. user: Arc>, + /// The move the user has actually chosen to do. chosen_move: Arc>, + /// The move that the user is actually going to do. use_move: &'own MoveData, + /// The script of the move. script: ScriptContainer, + /// The targets for this move. targets: &'own TargetList<'battle, 'library>, + /// Data required for this to be a script source. script_source_data: RwLock, } impl<'own, 'battle, 'library> ExecutingMove<'own, 'battle, 'library> { + /// Instantiates an executing move. pub fn new( targets: &'own TargetList<'battle, 'library>, number_of_hits: u8, @@ -100,26 +130,33 @@ impl<'own, 'battle, 'library> ExecutingMove<'own, 'battle, 'library> { script_source_data: Default::default(), } } + + /// The number of targets this move has. pub fn target_count(&self) -> usize { self.targets.len() } + /// The number of hits this move has per target. pub fn number_of_hits(&self) -> u8 { self.number_of_hits } + /// The user of the move. pub fn user(&self) -> &Arc> { &self.user } + /// The move the user has actually chosen to do. pub fn chosen_move(&self) -> &Arc> { &self.chosen_move } - + /// The move that the user is actually going to do. pub fn use_move(&self) -> &'own MoveData { self.use_move } + /// The script of the move. pub fn script(&self) -> &ScriptContainer { &self.script } + /// Gets a hit data for a target, with a specific index. pub fn get_hit_data<'func>( &'func self, for_target: &'func Arc>, @@ -136,6 +173,7 @@ impl<'own, 'battle, 'library> ExecutingMove<'own, 'battle, 'library> { Err(PokemonError::InvalidTargetRequested) } + /// Checks whether a Pokemon is a target for this move. pub fn is_pokemon_target(&self, pokemon: &Arc>) -> bool { for target in self.targets.iter().flatten() { if std::ptr::eq(target.deref().deref(), pokemon.deref().deref()) { @@ -145,6 +183,7 @@ impl<'own, 'battle, 'library> ExecutingMove<'own, 'battle, 'library> { false } + /// Gets the index of the hits in this move where the hits for a specific target start. pub(crate) fn get_index_of_target(&self, for_target: &Arc>) -> PkmnResult { for (index, target) in self.targets.iter().enumerate() { if let Some(target) = target { @@ -157,6 +196,7 @@ impl<'own, 'battle, 'library> ExecutingMove<'own, 'battle, 'library> { Err(PokemonError::InvalidTargetRequested) } + /// Gets a hit based on its raw index. pub(crate) fn get_hit_from_raw_index(&self, index: usize) -> &HitData { &self.hits[index] } diff --git a/src/dynamic_data/models/learned_move.rs b/src/dynamic_data/models/learned_move.rs index d2938a0..790a5cd 100644 --- a/src/dynamic_data/models/learned_move.rs +++ b/src/dynamic_data/models/learned_move.rs @@ -1,6 +1,7 @@ -use crate::static_data::MoveData; use std::sync::atomic::{AtomicU8, Ordering}; +use crate::static_data::MoveData; + #[derive(Debug)] pub struct LearnedMove<'library> { move_data: &'library MoveData, @@ -46,4 +47,15 @@ impl<'a> LearnedMove<'a> { self.remaining_pp.fetch_sub(amount, Ordering::SeqCst); true } + + pub fn restore_all_uses(&self) { + self.remaining_pp.store(self.max_pp, Ordering::SeqCst); + } + + pub fn restore_uses(&self, mut uses: u8) { + if self.remaining_pp() + uses > self.max_pp { + uses = self.remaining_pp() - uses; + } + self.remaining_pp.fetch_add(uses, Ordering::SeqCst); + } } diff --git a/src/dynamic_data/models/pokemon.rs b/src/dynamic_data/models/pokemon.rs index 1d9b2d2..598197d 100644 --- a/src/dynamic_data/models/pokemon.rs +++ b/src/dynamic_data/models/pokemon.rs @@ -439,7 +439,11 @@ impl<'own, 'library> Pokemon<'own, 'library> { let ability_script = self .library - .load_script(ScriptCategory::Ability, self.active_ability().name()) + .load_script( + (self as *const Self).cast(), + ScriptCategory::Ability, + self.active_ability().name(), + ) .unwrap(); if let Some(ability_script) = ability_script { self.ability_script @@ -626,14 +630,14 @@ impl<'own, 'library> VolatileScripts<'own> for Pokemon<'own, 'library> { } fn load_volatile_script(&self, key: &StringKey) -> PkmnResult>> { - self.library.load_script(ScriptCategory::Pokemon, key) + self.library + .load_script((self as *const Self).cast(), ScriptCategory::Pokemon, key) } } #[cfg(test)] pub mod test { use crate::dynamic_data::models::pokemon::Pokemon; - use crate::dynamic_data::DynamicLibrary; use crate::static_data::AbilityIndex; use crate::static_data::DataLibrary; use crate::static_data::Gender; diff --git a/src/dynamic_data/script_handling/script.rs b/src/dynamic_data/script_handling/script.rs index 289d734..ce8ecb8 100644 --- a/src/dynamic_data/script_handling/script.rs +++ b/src/dynamic_data/script_handling/script.rs @@ -134,16 +134,6 @@ pub trait Script: Send + Sync { /// 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 change which Pokemons stats are used for the offensive side when - /// calculating damage. - fn change_damage_stats_user( - &self, - _move: &ExecutingMove, - _target: &Arc, - _hit: u8, - _stats_user: &mut Arc, - ) { - } /// 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) { } diff --git a/tests/common/library_loader.rs b/tests/common/library_loader.rs index 5309512..46aefbe 100644 --- a/tests/common/library_loader.rs +++ b/tests/common/library_loader.rs @@ -9,10 +9,10 @@ use project_root::get_project_root; use serde_json::Value; use pkmn_lib::defines::LevelInt; -use pkmn_lib::dynamic_data::DynamicLibrary; use pkmn_lib::dynamic_data::Gen7BattleStatCalculator; use pkmn_lib::dynamic_data::Gen7DamageLibrary; use pkmn_lib::dynamic_data::Gen7MiscLibrary; +use pkmn_lib::dynamic_data::{DynamicLibrary, EmptyScriptResolver}; use pkmn_lib::static_data::{ Ability, AbilityLibrary, BattleItemCategory, DataLibrary, EffectParameter, Form, GrowthRateLibrary, Item, ItemLibrary, LearnableMoves, LibrarySettings, LookupGrowthRate, MoveData, MoveLibrary, Nature, NatureLibrary, @@ -37,6 +37,7 @@ pub fn load_library() -> DynamicLibrary { Box::new(Gen7BattleStatCalculator {}), Box::new(Gen7DamageLibrary::new(false)), Box::new(Gen7MiscLibrary::new()), + Box::new(EmptyScriptResolver {}), ); dynamic }