A lot more documentation, some initial work on the script resolver.
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2022-06-30 17:34:57 +02:00
parent 25e2a0dda1
commit 03f5e3bb5a
18 changed files with 450 additions and 210 deletions

View File

@@ -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<Pokemon>,
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<Pokemon>,
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<Pokemon>,
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<Pokemon>,
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<Pokemon>,
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,
}
}
}

View File

@@ -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<dyn BattleStatCalculator>,
/// The damage calculator deals with the calculation of things relating to damage.
damage_calculator: Box<dyn DamageLibrary>,
/// The Misc Library holds minor functions that do not fall in any of the other libraries and
/// calculators.
misc_library: Box<dyn MiscLibrary<'static>>,
/// The script resolver deals with how to resolve the scripts from specific unique key combinations.
script_resolver: Box<dyn ScriptResolver>,
}
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<dyn BattleStatCalculator>,
damage_calculator: Box<dyn DamageLibrary>,
misc_library: Box<dyn MiscLibrary<'static>>,
script_resolver: Box<dyn ScriptResolver>,
) -> 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<Option<Arc<dyn Script>>> {
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<Option<Arc<dyn Script>>> {
self.script_resolver.load_script(owner, _category, _key)
}
pub fn load_item_script(&self, _key: &Item) -> PkmnResult<Option<Box<dyn ItemScript>>> {
/// 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<Option<Arc<dyn ItemScript>>> {
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 {}),
}
}
}

View File

@@ -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<Pokemon>,
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<Pokemon<'func, 'library>>,
@@ -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<LearnedMove<'library>>,
}
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<Pokemon>,
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(),

View File

@@ -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<Option<Arc<dyn Script>>>;
/// 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<Option<Arc<dyn ItemScript>>>;
}
/// 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<Option<Arc<dyn Script>>> {
Ok(None)
}
fn load_item_script(&self, _key: &Item) -> PkmnResult<Option<Arc<dyn ItemScript>>> {
Ok(None)
}
}