A lot more documentation, some initial work on the script resolver.
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
25e2a0dda1
commit
03f5e3bb5a
|
@ -224,10 +224,10 @@ impl<'own, 'library> Battle<'own, 'library> {
|
||||||
);
|
);
|
||||||
|
|
||||||
if !block_critical {
|
if !block_critical {
|
||||||
let is_critical = self
|
let is_critical =
|
||||||
.library()
|
self.library()
|
||||||
.misc_library()
|
.damage_calculator()
|
||||||
.is_critical(self, executing_move, target, hit_index);
|
.is_critical(self, executing_move, target, hit_index);
|
||||||
hit_data.set_critical(is_critical);
|
hit_data.set_critical(is_critical);
|
||||||
}
|
}
|
||||||
let base_power = self.library().damage_calculator().get_base_power(
|
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);
|
hit_data.set_damage(damage);
|
||||||
}
|
}
|
||||||
if damage > 0 {
|
if damage > 0 {
|
||||||
target.damage(damage, DamageSource::AttackDamage);
|
target.damage(damage, DamageSource::MoveDamage);
|
||||||
if !target.is_fainted() {
|
if !target.is_fainted() {
|
||||||
script_hook!(on_incoming_hit, target, executing_move, target, hit_index);
|
script_hook!(on_incoming_hit, target, executing_move, target, hit_index);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
/// The history holder holds all specific history events that happened in the battle.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct HistoryHolder {}
|
|
|
@ -1,13 +1,15 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::dynamic_data::script_handling::ScriptSource;
|
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::dynamic_data::{ExecutingMove, HitData};
|
||||||
use crate::script_hook;
|
use crate::script_hook;
|
||||||
use crate::static_data::{MoveCategory, Statistic};
|
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 {
|
pub trait DamageLibrary: std::fmt::Debug {
|
||||||
fn has_randomness(&self) -> bool;
|
/// Calculate the damage for a given hit on a Pokemon.
|
||||||
fn get_damage(
|
fn get_damage(
|
||||||
&self,
|
&self,
|
||||||
executing_move: &ExecutingMove,
|
executing_move: &ExecutingMove,
|
||||||
|
@ -16,6 +18,7 @@ pub trait DamageLibrary: std::fmt::Debug {
|
||||||
hit_data: &HitData,
|
hit_data: &HitData,
|
||||||
) -> u32;
|
) -> u32;
|
||||||
|
|
||||||
|
/// Calculate the base power for a given hit on a Pokemon.
|
||||||
fn get_base_power(
|
fn get_base_power(
|
||||||
&self,
|
&self,
|
||||||
executing_move: &ExecutingMove,
|
executing_move: &ExecutingMove,
|
||||||
|
@ -24,39 +27,142 @@ pub trait DamageLibrary: std::fmt::Debug {
|
||||||
hit_data: &HitData,
|
hit_data: &HitData,
|
||||||
) -> u8;
|
) -> 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(
|
fn get_stat_modifier(
|
||||||
&self,
|
&self,
|
||||||
executing_move: &ExecutingMove,
|
executing_move: &ExecutingMove,
|
||||||
target: &Arc<Pokemon>,
|
target: &Arc<Pokemon>,
|
||||||
hit_number: u8,
|
hit_number: u8,
|
||||||
hit_data: &HitData,
|
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(
|
fn get_damage_modifier(
|
||||||
&self,
|
&self,
|
||||||
executing_move: &ExecutingMove,
|
executing_move: &ExecutingMove,
|
||||||
target: &Arc<Pokemon>,
|
target: &Arc<Pokemon>,
|
||||||
hit_number: u8,
|
hit_number: u8,
|
||||||
hit_data: &HitData,
|
_hit_data: &HitData,
|
||||||
) -> f32;
|
) -> f32 {
|
||||||
}
|
let mut modifier = 1.0;
|
||||||
|
script_hook!(
|
||||||
#[derive(Debug)]
|
change_damage_modifier,
|
||||||
pub struct Gen7DamageLibrary {
|
executing_move,
|
||||||
has_randomness: bool,
|
executing_move,
|
||||||
}
|
target,
|
||||||
|
hit_number,
|
||||||
impl Gen7DamageLibrary {
|
&mut modifier
|
||||||
pub fn new(has_randomness: bool) -> Self {
|
);
|
||||||
Self { has_randomness }
|
modifier
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DamageLibrary for Gen7DamageLibrary {
|
impl DamageLibrary for Gen7DamageLibrary {
|
||||||
fn has_randomness(&self) -> bool {
|
|
||||||
self.has_randomness
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_damage(
|
fn get_damage(
|
||||||
&self,
|
&self,
|
||||||
executing_move: &ExecutingMove,
|
executing_move: &ExecutingMove,
|
||||||
|
@ -166,108 +272,34 @@ impl DamageLibrary for Gen7DamageLibrary {
|
||||||
base_power
|
base_power
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_stat_modifier(
|
fn is_critical(
|
||||||
&self,
|
&self,
|
||||||
|
battle: &Battle,
|
||||||
executing_move: &ExecutingMove,
|
executing_move: &ExecutingMove,
|
||||||
target: &Arc<Pokemon>,
|
target: &Arc<Pokemon>,
|
||||||
hit_number: u8,
|
hit_number: u8,
|
||||||
hit_data: &HitData,
|
) -> bool {
|
||||||
) -> f32 {
|
// Status moves can't be critical.
|
||||||
let mut user = executing_move.user().clone();
|
if executing_move.use_move().category() == MoveCategory::Status {
|
||||||
script_hook!(
|
return false;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
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!(
|
script_hook!(
|
||||||
bypass_defensive_stat_boost,
|
change_critical_stage,
|
||||||
executing_move,
|
executing_move,
|
||||||
executing_move,
|
executing_move,
|
||||||
target,
|
target,
|
||||||
hit_number,
|
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;
|
// Crit stage is an unsigned byte, so we only care about values of 0 or higher.
|
||||||
script_hook!(
|
// For a critical stage of 3+ we always return true.
|
||||||
bypass_offensive_stat_boost,
|
match crit_stage {
|
||||||
executing_move,
|
0 => battle.random().get_max(24) == 0,
|
||||||
executing_move,
|
1 => battle.random().get_max(8) == 0,
|
||||||
target,
|
2 => battle.random().get_max(2) == 0,
|
||||||
hit_number,
|
_ => true,
|
||||||
&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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use std::ffi::c_void;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::Arc;
|
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::damage_library::DamageLibrary;
|
||||||
use crate::dynamic_data::libraries::misc_library::MiscLibrary;
|
use crate::dynamic_data::libraries::misc_library::MiscLibrary;
|
||||||
use crate::dynamic_data::libraries::script_resolver::ScriptCategory;
|
use crate::dynamic_data::libraries::script_resolver::ScriptCategory;
|
||||||
use crate::dynamic_data::ItemScript;
|
|
||||||
use crate::dynamic_data::Script;
|
use crate::dynamic_data::Script;
|
||||||
|
use crate::dynamic_data::{ItemScript, ScriptResolver};
|
||||||
use crate::static_data::Item;
|
use crate::static_data::Item;
|
||||||
use crate::static_data::StaticData;
|
use crate::static_data::StaticData;
|
||||||
use crate::{PkmnResult, StringKey};
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct DynamicLibrary {
|
pub struct DynamicLibrary {
|
||||||
|
/// The static data is the immutable storage data for this library.
|
||||||
static_data: StaticData,
|
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>,
|
stat_calculator: Box<dyn BattleStatCalculator>,
|
||||||
|
/// The damage calculator deals with the calculation of things relating to damage.
|
||||||
damage_calculator: Box<dyn DamageLibrary>,
|
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>>,
|
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 Sync for DynamicLibrary {}
|
||||||
|
|
||||||
unsafe impl Send for DynamicLibrary {}
|
unsafe impl Send for DynamicLibrary {}
|
||||||
|
|
||||||
impl DynamicLibrary {
|
impl DynamicLibrary {
|
||||||
|
/// Instantiates a new DynamicLibrary with given parameters.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
static_data: StaticData,
|
static_data: StaticData,
|
||||||
stat_calculator: Box<dyn BattleStatCalculator>,
|
stat_calculator: Box<dyn BattleStatCalculator>,
|
||||||
damage_calculator: Box<dyn DamageLibrary>,
|
damage_calculator: Box<dyn DamageLibrary>,
|
||||||
misc_library: Box<dyn MiscLibrary<'static>>,
|
misc_library: Box<dyn MiscLibrary<'static>>,
|
||||||
|
script_resolver: Box<dyn ScriptResolver>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
static_data,
|
static_data,
|
||||||
stat_calculator,
|
stat_calculator,
|
||||||
damage_calculator,
|
damage_calculator,
|
||||||
misc_library,
|
misc_library,
|
||||||
|
script_resolver,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The static data is the immutable storage data for this library.
|
||||||
pub fn static_data(&self) -> &StaticData {
|
pub fn static_data(&self) -> &StaticData {
|
||||||
&self.static_data
|
&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 {
|
pub fn stat_calculator(&self) -> &dyn BattleStatCalculator {
|
||||||
self.stat_calculator.deref()
|
self.stat_calculator.deref()
|
||||||
}
|
}
|
||||||
|
/// The damage calculator deals with the calculation of things relating to damage.
|
||||||
pub fn damage_calculator(&self) -> &dyn DamageLibrary {
|
pub fn damage_calculator(&self) -> &dyn DamageLibrary {
|
||||||
self.damage_calculator.deref()
|
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> {
|
pub fn misc_library(&self) -> &dyn MiscLibrary<'static> {
|
||||||
self.misc_library.deref()
|
self.misc_library.deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_script(&self, _category: ScriptCategory, _key: &StringKey) -> PkmnResult<Option<Arc<dyn Script>>> {
|
/// Loads a standard script with a given unique combination of category and key. If no script
|
||||||
todo!()
|
/// 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!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,6 +95,7 @@ pub mod test {
|
||||||
use crate::dynamic_data::libraries::damage_library::Gen7DamageLibrary;
|
use crate::dynamic_data::libraries::damage_library::Gen7DamageLibrary;
|
||||||
use crate::dynamic_data::libraries::dynamic_library::DynamicLibrary;
|
use crate::dynamic_data::libraries::dynamic_library::DynamicLibrary;
|
||||||
use crate::dynamic_data::libraries::misc_library::Gen7MiscLibrary;
|
use crate::dynamic_data::libraries::misc_library::Gen7MiscLibrary;
|
||||||
|
use crate::dynamic_data::EmptyScriptResolver;
|
||||||
|
|
||||||
pub fn build() -> DynamicLibrary {
|
pub fn build() -> DynamicLibrary {
|
||||||
DynamicLibrary {
|
DynamicLibrary {
|
||||||
|
@ -72,6 +103,7 @@ pub mod test {
|
||||||
stat_calculator: Box::new(Gen7BattleStatCalculator {}),
|
stat_calculator: Box::new(Gen7BattleStatCalculator {}),
|
||||||
damage_calculator: Box::new(Gen7DamageLibrary::new(false)),
|
damage_calculator: Box::new(Gen7DamageLibrary::new(false)),
|
||||||
misc_library: Box::new(Gen7MiscLibrary::new()),
|
misc_library: Box::new(Gen7MiscLibrary::new()),
|
||||||
|
script_resolver: Box::new(EmptyScriptResolver {}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,24 +3,17 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
|
|
||||||
use crate::dynamic_data::choices::{MoveChoice, SwitchChoice, TurnChoice};
|
use crate::dynamic_data::choices::{MoveChoice, TurnChoice};
|
||||||
use crate::dynamic_data::script_handling::ScriptSource;
|
|
||||||
use crate::dynamic_data::Battle;
|
|
||||||
use crate::dynamic_data::ExecutingMove;
|
|
||||||
use crate::dynamic_data::Pokemon;
|
use crate::dynamic_data::Pokemon;
|
||||||
use crate::dynamic_data::{LearnedMove, MoveLearnMethod};
|
use crate::dynamic_data::{LearnedMove, MoveLearnMethod};
|
||||||
use crate::static_data::{MoveCategory, MoveData, MoveTarget, SecondaryEffect};
|
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 {
|
pub trait MiscLibrary<'library>: Debug {
|
||||||
fn is_critical(
|
/// Returns whether or not a Pokemon is allowed to flee or switch out.
|
||||||
&self,
|
fn can_flee(&self, choice: &TurnChoice) -> bool;
|
||||||
battle: &Battle,
|
/// Returns the move we need to use if we can't use another move. Typically Struggle.
|
||||||
executing_move: &ExecutingMove,
|
|
||||||
target: &Arc<Pokemon>,
|
|
||||||
hit_number: u8,
|
|
||||||
) -> bool;
|
|
||||||
fn can_flee(&self, choice: &SwitchChoice) -> bool;
|
|
||||||
fn replacement_move<'func>(
|
fn replacement_move<'func>(
|
||||||
&'func self,
|
&'func self,
|
||||||
user: &Arc<Pokemon<'func, 'library>>,
|
user: &Arc<Pokemon<'func, 'library>>,
|
||||||
|
@ -31,13 +24,19 @@ pub trait MiscLibrary<'library>: Debug {
|
||||||
// TODO: get time
|
// TODO: get time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A gen 7 implementation for the MiscLibrary.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Gen7MiscLibrary<'library> {
|
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,
|
struggle_data: *const MoveData,
|
||||||
|
/// The learned move data for struggle.
|
||||||
struggle_learned_move: Arc<LearnedMove<'library>>,
|
struggle_learned_move: Arc<LearnedMove<'library>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'library> Gen7MiscLibrary<'library> {
|
impl<'library> Gen7MiscLibrary<'library> {
|
||||||
|
/// Instantiates a new MiscLibrary.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let struggle_data = Box::new(MoveData::new(
|
let struggle_data = Box::new(MoveData::new(
|
||||||
&StringKey::new("struggle"),
|
&StringKey::new("struggle"),
|
||||||
|
@ -75,36 +74,7 @@ impl<'library> Drop for Gen7MiscLibrary<'library> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'library> MiscLibrary<'library> for Gen7MiscLibrary<'library> {
|
impl<'library> MiscLibrary<'library> for Gen7MiscLibrary<'library> {
|
||||||
fn is_critical(
|
fn can_flee(&self, _choice: &TurnChoice) -> bool {
|
||||||
&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 {
|
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,6 +84,7 @@ impl<'library> MiscLibrary<'library> for Gen7MiscLibrary<'library> {
|
||||||
target_side: u8,
|
target_side: u8,
|
||||||
target_index: u8,
|
target_index: u8,
|
||||||
) -> TurnChoice<'func, 'library> {
|
) -> TurnChoice<'func, 'library> {
|
||||||
|
self.struggle_learned_move.restore_all_uses();
|
||||||
TurnChoice::Move(MoveChoice::new(
|
TurnChoice::Move(MoveChoice::new(
|
||||||
user.clone(),
|
user.clone(),
|
||||||
self.struggle_learned_move.clone(),
|
self.struggle_learned_move.clone(),
|
||||||
|
|
|
@ -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)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ScriptCategory {
|
pub enum ScriptCategory {
|
||||||
Move,
|
/// A script that belongs to a move. This generally is only the script that is attached to a
|
||||||
Ability,
|
/// [`MoveChoice`](crate::dynamic_data::MoveChoice) and [`ExecutingMove`](crate::dynamic_data::ExecutingMove)
|
||||||
Status,
|
Move = 0,
|
||||||
Pokemon,
|
/// An ability script. Scripts in this category are always abilities, and therefore always
|
||||||
Battle,
|
/// attached to a Pokemon.
|
||||||
Side,
|
Ability = 1,
|
||||||
ItemBattleTrigger,
|
/// 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,6 @@ pub use event_hooks::*;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use flow::*;
|
pub use flow::*;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use history::*;
|
|
||||||
#[doc(inline)]
|
|
||||||
pub use libraries::*;
|
pub use libraries::*;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use models::*;
|
pub use models::*;
|
||||||
|
@ -16,7 +14,6 @@ pub use script_handling::*;
|
||||||
mod choices;
|
mod choices;
|
||||||
mod event_hooks;
|
mod event_hooks;
|
||||||
mod flow;
|
mod flow;
|
||||||
mod history;
|
|
||||||
pub(crate) mod libraries;
|
pub(crate) mod libraries;
|
||||||
mod models;
|
mod models;
|
||||||
mod script_handling;
|
mod script_handling;
|
||||||
|
|
|
@ -7,7 +7,6 @@ use parking_lot::RwLock;
|
||||||
|
|
||||||
use crate::dynamic_data::choices::TurnChoice;
|
use crate::dynamic_data::choices::TurnChoice;
|
||||||
use crate::dynamic_data::event_hooks::{Event, EventHook};
|
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::is_valid_target;
|
||||||
use crate::dynamic_data::models::battle_party::BattleParty;
|
use crate::dynamic_data::models::battle_party::BattleParty;
|
||||||
use crate::dynamic_data::models::battle_random::BattleRandom;
|
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::dynamic_data::{ScriptCategory, ScriptSource, ScriptSourceData, ScriptWrapper};
|
||||||
use crate::{script_hook, PkmnResult, StringKey};
|
use crate::{script_hook, PkmnResult, StringKey};
|
||||||
|
|
||||||
|
/// A pokemon battle, with any amount of sides and pokemon per side.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Battle<'own, 'library> {
|
pub struct Battle<'own, 'library> {
|
||||||
|
/// The library the battle uses for handling.
|
||||||
library: &'own DynamicLibrary,
|
library: &'own DynamicLibrary,
|
||||||
|
/// A list of all different parties in the battle.
|
||||||
parties: Vec<BattleParty<'own, 'library>>,
|
parties: Vec<BattleParty<'own, 'library>>,
|
||||||
|
/// Whether or not Pokemon can flee from the battle.
|
||||||
can_flee: bool,
|
can_flee: bool,
|
||||||
|
/// The number of sides in the battle. Typically 2.
|
||||||
number_of_sides: u8,
|
number_of_sides: u8,
|
||||||
|
/// The number of Pokemon that can be on each side.
|
||||||
pokemon_per_side: u8,
|
pokemon_per_side: u8,
|
||||||
|
/// A list of all sides in the battle.
|
||||||
sides: Vec<BattleSide<'own, 'library>>,
|
sides: Vec<BattleSide<'own, 'library>>,
|
||||||
|
/// The RNG used for the battle.
|
||||||
random: BattleRandom,
|
random: BattleRandom,
|
||||||
|
/// A queue of the yet to be executed choices in a turn.
|
||||||
current_turn_queue: RwLock<Option<ChoiceQueue<'own, 'library>>>,
|
current_turn_queue: RwLock<Option<ChoiceQueue<'own, 'library>>>,
|
||||||
|
/// Whether or not the battle has ended.
|
||||||
has_ended: AtomicBool,
|
has_ended: AtomicBool,
|
||||||
|
/// The eventual result of the battle. Inconclusive until the battle is ended.
|
||||||
result: Atomic<BattleResult>,
|
result: Atomic<BattleResult>,
|
||||||
|
/// The handler to send all events to.
|
||||||
event_hook: EventHook,
|
event_hook: EventHook,
|
||||||
history_holder: Box<HistoryHolder>,
|
/// The index of the current turn. 0 until all choices
|
||||||
current_turn: AtomicU32,
|
current_turn: AtomicU32,
|
||||||
|
/// All the volatile scripts attached to a Pokemon
|
||||||
volatile_scripts: Arc<ScriptSet>,
|
volatile_scripts: Arc<ScriptSet>,
|
||||||
|
/// The time the last turn took to run. Defaults to 0.
|
||||||
last_turn_time: Atomic<chrono::Duration>,
|
last_turn_time: Atomic<chrono::Duration>,
|
||||||
|
/// Data required for this script to be a script source.
|
||||||
script_source_data: RwLock<ScriptSourceData>,
|
script_source_data: RwLock<ScriptSourceData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'own, 'library> Battle<'own, 'library> {
|
impl<'own, 'library> Battle<'own, 'library> {
|
||||||
|
/// Initializes a new battle.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
library: &'own DynamicLibrary,
|
library: &'own DynamicLibrary,
|
||||||
parties: Vec<BattleParty<'own, 'library>>,
|
parties: Vec<BattleParty<'own, 'library>>,
|
||||||
|
@ -52,6 +66,8 @@ impl<'own, 'library> Battle<'own, 'library> {
|
||||||
pokemon_per_side: u8,
|
pokemon_per_side: u8,
|
||||||
random_seed: Option<u128>,
|
random_seed: Option<u128>,
|
||||||
) -> Self {
|
) -> 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 {
|
let random = if let Some(seed) = random_seed {
|
||||||
BattleRandom::new_with_seed(seed)
|
BattleRandom::new_with_seed(seed)
|
||||||
} else {
|
} else {
|
||||||
|
@ -74,7 +90,6 @@ impl<'own, 'library> Battle<'own, 'library> {
|
||||||
has_ended: AtomicBool::new(false),
|
has_ended: AtomicBool::new(false),
|
||||||
result: Atomic::new(BattleResult::Inconclusive),
|
result: Atomic::new(BattleResult::Inconclusive),
|
||||||
event_hook: Default::default(),
|
event_hook: Default::default(),
|
||||||
history_holder: Box::new(HistoryHolder {}),
|
|
||||||
current_turn: AtomicU32::new(0),
|
current_turn: AtomicU32::new(0),
|
||||||
volatile_scripts: Default::default(),
|
volatile_scripts: Default::default(),
|
||||||
last_turn_time: Atomic::new(chrono::Duration::zero()),
|
last_turn_time: Atomic::new(chrono::Duration::zero()),
|
||||||
|
@ -89,53 +104,64 @@ impl<'own, 'library> Battle<'own, 'library> {
|
||||||
battle
|
battle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The library the battle uses for handling.
|
||||||
pub fn library(&self) -> &'own DynamicLibrary {
|
pub fn library(&self) -> &'own DynamicLibrary {
|
||||||
self.library
|
self.library
|
||||||
}
|
}
|
||||||
|
/// A list of all different parties in the battle.
|
||||||
pub fn parties(&self) -> &Vec<BattleParty<'own, 'library>> {
|
pub fn parties(&self) -> &Vec<BattleParty<'own, 'library>> {
|
||||||
&self.parties
|
&self.parties
|
||||||
}
|
}
|
||||||
|
/// Whether or not Pokemon can flee from the battle.
|
||||||
pub fn can_flee(&self) -> bool {
|
pub fn can_flee(&self) -> bool {
|
||||||
self.can_flee
|
self.can_flee
|
||||||
}
|
}
|
||||||
|
/// The number of sides in the battle. Typically 2.
|
||||||
pub fn number_of_sides(&self) -> u8 {
|
pub fn number_of_sides(&self) -> u8 {
|
||||||
self.number_of_sides
|
self.number_of_sides
|
||||||
}
|
}
|
||||||
|
/// The number of Pokemon that can be on each side.
|
||||||
pub fn pokemon_per_side(&self) -> u8 {
|
pub fn pokemon_per_side(&self) -> u8 {
|
||||||
self.pokemon_per_side
|
self.pokemon_per_side
|
||||||
}
|
}
|
||||||
|
/// A list of all sides in the battle.
|
||||||
pub fn sides(&self) -> &Vec<BattleSide<'own, 'library>> {
|
pub fn sides(&self) -> &Vec<BattleSide<'own, 'library>> {
|
||||||
&self.sides
|
&self.sides
|
||||||
}
|
}
|
||||||
|
/// A mutable list of all sides in the battle.
|
||||||
pub fn sides_mut(&mut self) -> &mut Vec<BattleSide<'own, 'library>> {
|
pub fn sides_mut(&mut self) -> &mut Vec<BattleSide<'own, 'library>> {
|
||||||
&mut self.sides
|
&mut self.sides
|
||||||
}
|
}
|
||||||
|
/// The RNG used for the battle.
|
||||||
pub fn random(&self) -> &BattleRandom {
|
pub fn random(&self) -> &BattleRandom {
|
||||||
&self.random
|
&self.random
|
||||||
}
|
}
|
||||||
|
/// Whether or not the battle has ended.
|
||||||
pub fn has_ended(&self) -> bool {
|
pub fn has_ended(&self) -> bool {
|
||||||
self.has_ended.load(Ordering::Relaxed)
|
self.has_ended.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
/// The eventual result of the battle. Inconclusive until the battle is ended.
|
||||||
pub fn result(&self) -> BattleResult {
|
pub fn result(&self) -> BattleResult {
|
||||||
self.result.load(Ordering::Relaxed)
|
self.result.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
/// The handler to send all events to.
|
||||||
pub fn event_hook(&self) -> &EventHook {
|
pub fn event_hook(&self) -> &EventHook {
|
||||||
&self.event_hook
|
&self.event_hook
|
||||||
}
|
}
|
||||||
pub fn history_holder(&self) -> &HistoryHolder {
|
/// The index of the current turn. 0 until all choices
|
||||||
self.history_holder.deref()
|
|
||||||
}
|
|
||||||
pub fn current_turn(&self) -> u32 {
|
pub fn current_turn(&self) -> u32 {
|
||||||
self.current_turn.load(Ordering::Relaxed)
|
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 {
|
pub fn last_turn_time(&self) -> chrono::Duration {
|
||||||
self.last_turn_time.load(Ordering::Relaxed)
|
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<Option<ChoiceQueue<'own, 'library>>> {
|
pub fn current_turn_queue(&self) -> &RwLock<Option<ChoiceQueue<'own, 'library>>> {
|
||||||
&self.current_turn_queue
|
&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<Arc<Pokemon<'own, 'library>>> {
|
pub fn get_pokemon(&self, side: u8, index: u8) -> Option<Arc<Pokemon<'own, 'library>>> {
|
||||||
let side = self.sides.get(side as usize);
|
let side = self.sides.get(side as usize);
|
||||||
side?;
|
side?;
|
||||||
|
@ -145,6 +171,9 @@ impl<'own, 'library> Battle<'own, 'library> {
|
||||||
pokemon.unwrap().clone()
|
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 {
|
pub fn can_slot_be_filled(&self, side: u8, index: u8) -> bool {
|
||||||
for party in &self.parties {
|
for party in &self.parties {
|
||||||
if party.is_responsible_for_index(side, index) && party.has_pokemon_not_in_field() {
|
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
|
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) {
|
pub fn validate_battle_state(&self) {
|
||||||
|
// If we've already ended, we dont need to run this.
|
||||||
if self.has_ended() {
|
if self.has_ended() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -189,6 +221,7 @@ impl<'own, 'library> Battle<'own, 'library> {
|
||||||
self.has_ended.store(true, Ordering::SeqCst);
|
self.has_ended.store(true, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks whether a choice is actually possible.
|
||||||
pub fn can_use(&self, choice: &TurnChoice) -> bool {
|
pub fn can_use(&self, choice: &TurnChoice) -> bool {
|
||||||
// If the user is not usable, we obviously can;t use the choice.
|
// If the user is not usable, we obviously can;t use the choice.
|
||||||
if !choice.user().is_usable() {
|
if !choice.user().is_usable() {
|
||||||
|
@ -211,6 +244,7 @@ impl<'own, 'library> Battle<'own, 'library> {
|
||||||
true
|
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<bool> {
|
pub fn try_set_choice(&mut self, choice: TurnChoice<'own, 'library>) -> PkmnResult<bool> {
|
||||||
if !self.can_use(&choice) {
|
if !self.can_use(&choice) {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
|
@ -227,6 +261,8 @@ impl<'own, 'library> Battle<'own, 'library> {
|
||||||
Ok(true)
|
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<()> {
|
fn check_choices_set_and_run(&self) -> PkmnResult<()> {
|
||||||
for side in &self.sides {
|
for side in &self.sides {
|
||||||
if !side.all_choices_set() {
|
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<Option<Arc<dyn Script>>> {
|
fn load_volatile_script(&self, key: &StringKey) -> PkmnResult<Option<Arc<dyn Script>>> {
|
||||||
self.library.load_script(ScriptCategory::Battle, key)
|
self.library
|
||||||
|
.load_script((self as *const Self).cast(), ScriptCategory::Battle, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,29 @@
|
||||||
use crate::dynamic_data::models::pokemon::Pokemon;
|
|
||||||
use crate::dynamic_data::models::pokemon_party::PokemonParty;
|
|
||||||
use std::sync::Arc;
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct BattleParty<'own, 'library> {
|
pub struct BattleParty<'own, 'library> {
|
||||||
|
/// The party the BattleParty is holding.
|
||||||
party: Arc<PokemonParty<'own, 'library>>,
|
party: Arc<PokemonParty<'own, 'library>>,
|
||||||
|
/// The indices for which the party is responsible, in the format (side, index)
|
||||||
responsible_indices: Vec<(u8, u8)>,
|
responsible_indices: Vec<(u8, u8)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'own, 'library> BattleParty<'own, 'library> {
|
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<PokemonParty<'own, 'library>>, responsible_indices: Vec<(u8, u8)>) -> Self {
|
pub fn new(party: Arc<PokemonParty<'own, 'library>>, responsible_indices: Vec<(u8, u8)>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
party,
|
party,
|
||||||
responsible_indices,
|
responsible_indices,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks whether the party is responsible for the given index.
|
||||||
pub fn is_responsible_for_index(&self, side: u8, index: u8) -> bool {
|
pub fn is_responsible_for_index(&self, side: u8, index: u8) -> bool {
|
||||||
for responsible_index in &self.responsible_indices {
|
for responsible_index in &self.responsible_indices {
|
||||||
if responsible_index.0 == side && responsible_index.1 == index {
|
if responsible_index.0 == side && responsible_index.1 == index {
|
||||||
|
@ -24,6 +33,7 @@ impl<'own, 'library> BattleParty<'own, 'library> {
|
||||||
false
|
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 {
|
pub fn has_pokemon_not_in_field(&self) -> bool {
|
||||||
for pokemon in self.party.pokemon().iter().flatten() {
|
for pokemon in self.party.pokemon().iter().flatten() {
|
||||||
if pokemon.is_usable() && !pokemon.is_on_battlefield() {
|
if pokemon.is_usable() && !pokemon.is_on_battlefield() {
|
||||||
|
@ -33,6 +43,7 @@ impl<'own, 'library> BattleParty<'own, 'library> {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets a Pokemon at an index.
|
||||||
pub fn get_pokemon(&self, index: usize) -> &Option<Arc<Pokemon<'own, 'library>>> {
|
pub fn get_pokemon(&self, index: usize) -> &Option<Arc<Pokemon<'own, 'library>>> {
|
||||||
self.party.at(index)
|
self.party.at(index)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,32 +7,43 @@ use crate::dynamic_data::script_handling::ScriptSource;
|
||||||
use crate::script_hook;
|
use crate::script_hook;
|
||||||
use crate::utils::Random;
|
use crate::utils::Random;
|
||||||
|
|
||||||
|
/// The RNG for a battle.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct BattleRandom {
|
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<Random>,
|
random: Mutex<Random>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BattleRandom {
|
impl BattleRandom {
|
||||||
|
/// Initializes a new RNG with a given seed.
|
||||||
pub fn new_with_seed(seed: u128) -> Self {
|
pub fn new_with_seed(seed: u128) -> Self {
|
||||||
BattleRandom {
|
BattleRandom {
|
||||||
random: Mutex::new(Random::new(seed)),
|
random: Mutex::new(Random::new(seed)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the underlying random number generator.
|
||||||
pub fn get_rng(&self) -> &Mutex<Random> {
|
pub fn get_rng(&self) -> &Mutex<Random> {
|
||||||
&self.random
|
&self.random
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a random 32 bit integer. Can be any value between min int and max int.
|
||||||
pub fn get(&self) -> i32 {
|
pub fn get(&self) -> i32 {
|
||||||
return self.get_rng().lock().unwrap().get();
|
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 {
|
pub fn get_max(&self, max: i32) -> i32 {
|
||||||
return self.get_rng().lock().unwrap().get_max(max);
|
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 {
|
pub fn get_between(&self, min: i32, max: i32) -> i32 {
|
||||||
return self.get_rng().lock().unwrap().get_between(min, max);
|
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(
|
pub fn effect_chance(
|
||||||
&self,
|
&self,
|
||||||
mut chance: f32,
|
mut chance: f32,
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
|
/// The result of a battle.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub enum BattleResult {
|
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,
|
Inconclusive,
|
||||||
|
/// The battle has a winner, with the inner value being the index of the side that has won.
|
||||||
Conclusive(u8),
|
Conclusive(u8),
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,22 +16,35 @@ use crate::dynamic_data::ScriptSet;
|
||||||
use crate::dynamic_data::VolatileScripts;
|
use crate::dynamic_data::VolatileScripts;
|
||||||
use crate::{script_hook, PkmnResult, StringKey};
|
use crate::{script_hook, PkmnResult, StringKey};
|
||||||
|
|
||||||
|
/// A side on a battle.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BattleSide<'own, 'library> {
|
pub struct BattleSide<'own, 'library> {
|
||||||
|
/// The index of the side on the battle.
|
||||||
index: u8,
|
index: u8,
|
||||||
|
/// The number of Pokemon that can be on the side.
|
||||||
pokemon_per_side: u8,
|
pokemon_per_side: u8,
|
||||||
|
/// A list of pokemon currently on the battlefield.
|
||||||
pokemon: RwLock<Vec<Option<Arc<Pokemon<'own, 'library>>>>>,
|
pokemon: RwLock<Vec<Option<Arc<Pokemon<'own, 'library>>>>>,
|
||||||
|
/// The currently set choices for all Pokemon on the battlefield. Cleared when the turn starts.
|
||||||
choices: RwLock<Vec<Option<TurnChoice<'own, 'library>>>>,
|
choices: RwLock<Vec<Option<TurnChoice<'own, 'library>>>>,
|
||||||
|
/// 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<AtomicBool>,
|
fillable_slots: Vec<AtomicBool>,
|
||||||
|
/// The number of choices that are set.
|
||||||
choices_set: AtomicU8,
|
choices_set: AtomicU8,
|
||||||
|
/// A reference to the battle we're part of.
|
||||||
battle: *mut Battle<'own, 'library>,
|
battle: *mut Battle<'own, 'library>,
|
||||||
|
/// Whether or not this side has fled.
|
||||||
has_fled_battle: bool,
|
has_fled_battle: bool,
|
||||||
|
/// The volatile scripts that are attached to the side.
|
||||||
volatile_scripts: Arc<ScriptSet>,
|
volatile_scripts: Arc<ScriptSet>,
|
||||||
|
|
||||||
|
/// Data required for this to be a script source.
|
||||||
script_source_data: RwLock<ScriptSourceData>,
|
script_source_data: RwLock<ScriptSourceData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'own, 'library> BattleSide<'own, 'library> {
|
impl<'own, 'library> BattleSide<'own, 'library> {
|
||||||
|
/// Instantiates a battle side.
|
||||||
pub fn new(index: u8, pokemon_per_side: u8) -> Self {
|
pub fn new(index: u8, pokemon_per_side: u8) -> Self {
|
||||||
let mut pokemon = Vec::with_capacity(pokemon_per_side as usize);
|
let mut pokemon = Vec::with_capacity(pokemon_per_side as usize);
|
||||||
let mut choices = 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>) {
|
pub(crate) fn set_battle(&mut self, battle: *mut Battle<'own, 'library>) {
|
||||||
self.battle = battle;
|
self.battle = battle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The index of the side on the battle.
|
||||||
pub fn index(&self) -> u8 {
|
pub fn index(&self) -> u8 {
|
||||||
self.index
|
self.index
|
||||||
}
|
}
|
||||||
|
/// The number of Pokemon that can be on the side.
|
||||||
pub fn pokemon_per_side(&self) -> u8 {
|
pub fn pokemon_per_side(&self) -> u8 {
|
||||||
self.pokemon_per_side
|
self.pokemon_per_side
|
||||||
}
|
}
|
||||||
|
/// A list of pokemon currently on the battlefield.
|
||||||
pub fn pokemon(&self) -> RwLockReadGuard<'_, RawRwLock, Vec<Option<Arc<Pokemon<'own, 'library>>>>> {
|
pub fn pokemon(&self) -> RwLockReadGuard<'_, RawRwLock, Vec<Option<Arc<Pokemon<'own, 'library>>>>> {
|
||||||
self.pokemon.read()
|
self.pokemon.read()
|
||||||
}
|
}
|
||||||
|
/// The currently set choices for all Pokemon on the battlefield. Cleared when the turn starts.
|
||||||
pub fn choices(&self) -> &RwLock<Vec<Option<TurnChoice<'own, 'library>>>> {
|
pub fn choices(&self) -> &RwLock<Vec<Option<TurnChoice<'own, 'library>>>> {
|
||||||
&self.choices
|
&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<AtomicBool> {
|
pub fn fillable_slots(&self) -> &Vec<AtomicBool> {
|
||||||
&self.fillable_slots
|
&self.fillable_slots
|
||||||
}
|
}
|
||||||
|
/// The number of choices that are set.
|
||||||
pub fn choices_set(&self) -> u8 {
|
pub fn choices_set(&self) -> u8 {
|
||||||
self.choices_set.load(Ordering::SeqCst)
|
self.choices_set.load(Ordering::SeqCst)
|
||||||
}
|
}
|
||||||
|
/// A reference to the battle we're part of.
|
||||||
pub fn battle(&self) -> &Battle<'own, 'library> {
|
pub fn battle(&self) -> &Battle<'own, 'library> {
|
||||||
unsafe { self.battle.as_ref().unwrap() }
|
unsafe { self.battle.as_ref().unwrap() }
|
||||||
}
|
}
|
||||||
|
/// Whether or not this side has fled.
|
||||||
pub fn has_fled_battle(&self) -> bool {
|
pub fn has_fled_battle(&self) -> bool {
|
||||||
self.has_fled_battle
|
self.has_fled_battle
|
||||||
}
|
}
|
||||||
|
/// The volatile scripts that are attached to the side.
|
||||||
pub fn volatile_scripts(&self) -> &Arc<ScriptSet> {
|
pub fn volatile_scripts(&self) -> &Arc<ScriptSet> {
|
||||||
&self.volatile_scripts
|
&self.volatile_scripts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether every Pokemon on this side has its choices
|
||||||
pub fn all_choices_set(&self) -> bool {
|
pub fn all_choices_set(&self) -> bool {
|
||||||
self.choices_set() == self.pokemon_per_side
|
self.choices_set() == self.pokemon_per_side
|
||||||
}
|
}
|
||||||
|
@ -110,7 +134,8 @@ impl<'own, 'library> BattleSide<'own, 'library> {
|
||||||
true
|
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() {
|
for (index, pokemon_slot) in self.pokemon.read().iter().enumerate() {
|
||||||
if let Some(pokemon) = pokemon_slot {
|
if let Some(pokemon) = pokemon_slot {
|
||||||
if std::ptr::eq(pokemon.deref(), choice.user().deref()) {
|
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) {
|
pub fn reset_choices(&self) {
|
||||||
let len = self.choices.read().len();
|
let len = self.choices.read().len();
|
||||||
for i in 0..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) {
|
pub fn force_clear_pokemon(&mut self, index: u8) {
|
||||||
self.pokemon.write()[index as usize] = None;
|
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<Arc<Pokemon<'own, 'library>>>) {
|
pub fn set_pokemon(&self, index: u8, pokemon: Option<Arc<Pokemon<'own, 'library>>>) {
|
||||||
{
|
{
|
||||||
let old = &self.pokemon.read()[index as usize];
|
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<Pokemon<'own, 'library>>) -> bool {
|
pub fn is_pokemon_on_side(&self, pokemon: Arc<Pokemon<'own, 'library>>) -> bool {
|
||||||
for p in self.pokemon.read().iter().flatten() {
|
for p in self.pokemon.read().iter().flatten() {
|
||||||
if std::ptr::eq(p.deref().deref(), pokemon.deref()) {
|
if std::ptr::eq(p.deref().deref(), pokemon.deref()) {
|
||||||
|
@ -182,10 +211,13 @@ impl<'own, 'library> BattleSide<'own, 'library> {
|
||||||
false
|
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);
|
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<Pokemon<'own, 'library>>) -> bool {
|
pub fn is_slot_unfillable(&self, pokemon: Arc<Pokemon<'own, 'library>>) -> bool {
|
||||||
for (i, slot) in self.pokemon.read().iter().enumerate() {
|
for (i, slot) in self.pokemon.read().iter().enumerate() {
|
||||||
if let Some(p) = slot {
|
if let Some(p) = slot {
|
||||||
|
@ -197,6 +229,7 @@ impl<'own, 'library> BattleSide<'own, 'library> {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks whether the side has been defeated.
|
||||||
pub fn is_defeated(&self) -> bool {
|
pub fn is_defeated(&self) -> bool {
|
||||||
for fillable_slot in &self.fillable_slots {
|
for fillable_slot in &self.fillable_slots {
|
||||||
if fillable_slot.load(Ordering::Relaxed) {
|
if fillable_slot.load(Ordering::Relaxed) {
|
||||||
|
@ -206,19 +239,23 @@ impl<'own, 'library> BattleSide<'own, 'library> {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks whether the side has fled.
|
||||||
pub fn has_fled(&self) -> bool {
|
pub fn has_fled(&self) -> bool {
|
||||||
self.has_fled_battle
|
self.has_fled_battle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mark the side as fled.
|
||||||
pub fn mark_as_fled(&mut self) {
|
pub fn mark_as_fled(&mut self) {
|
||||||
self.has_fled_battle = true;
|
self.has_fled_battle = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets a random Pokemon on the given side.
|
||||||
pub fn get_random_creature_index(&self) -> u8 {
|
pub fn get_random_creature_index(&self) -> u8 {
|
||||||
// TODO: Consider adding parameter to only get index for available creatures.
|
// TODO: Consider adding parameter to only get index for available creatures.
|
||||||
self.battle().random().get_max(self.pokemon_per_side as i32) as u8
|
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 {
|
pub fn swap_positions(&mut self, a: u8, b: u8) -> bool {
|
||||||
// If out of range, don't allow swapping.
|
// If out of range, don't allow swapping.
|
||||||
if a >= self.pokemon_per_side || b >= self.pokemon_per_side {
|
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<Option<Arc<dyn Script>>> {
|
fn load_volatile_script(&self, key: &StringKey) -> PkmnResult<Option<Arc<dyn Script>>> {
|
||||||
self.battle().library().load_script(crate::ScriptCategory::Side, key)
|
self.battle()
|
||||||
|
.library()
|
||||||
|
.load_script((self as *const Self).cast(), crate::ScriptCategory::Side, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
/// A source of damage. This should be as unique as possible.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum DamageSource {
|
pub enum DamageSource {
|
||||||
AttackDamage = 0,
|
/// The damage is done by a move.
|
||||||
|
MoveDamage = 0,
|
||||||
|
/// The damage is done by something else.
|
||||||
Misc = 1,
|
Misc = 1,
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,69 +13,99 @@ use crate::dynamic_data::TargetList;
|
||||||
use crate::static_data::MoveData;
|
use crate::static_data::MoveData;
|
||||||
use crate::{PkmnResult, PokemonError};
|
use crate::{PkmnResult, PokemonError};
|
||||||
|
|
||||||
|
/// A hit data is the data for a single hit, on a single target.
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct HitData {
|
pub struct HitData {
|
||||||
|
/// Whether or not the hit is critical.
|
||||||
critical: AtomicBool,
|
critical: AtomicBool,
|
||||||
|
/// The base power of the hit.
|
||||||
base_power: AtomicU8,
|
base_power: AtomicU8,
|
||||||
|
/// The type effectiveness of the hit.
|
||||||
effectiveness: Atomic<f32>,
|
effectiveness: Atomic<f32>,
|
||||||
|
/// The actual damage of the hit.
|
||||||
damage: AtomicU32,
|
damage: AtomicU32,
|
||||||
|
/// The type id of the type used for the hit.
|
||||||
move_type: AtomicU8,
|
move_type: AtomicU8,
|
||||||
|
/// Whether or not the hit has failed.
|
||||||
has_failed: AtomicBool,
|
has_failed: AtomicBool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HitData {
|
impl HitData {
|
||||||
|
/// Whether or not the hit is critical.
|
||||||
pub fn is_critical(&self) -> bool {
|
pub fn is_critical(&self) -> bool {
|
||||||
self.critical.load(Ordering::Relaxed)
|
self.critical.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
/// The base power of the hit.
|
||||||
pub fn base_power(&self) -> u8 {
|
pub fn base_power(&self) -> u8 {
|
||||||
self.base_power.load(Ordering::Relaxed)
|
self.base_power.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
/// The type effectiveness of the hit.
|
||||||
pub fn effectiveness(&self) -> f32 {
|
pub fn effectiveness(&self) -> f32 {
|
||||||
self.effectiveness.load(Ordering::Relaxed)
|
self.effectiveness.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
/// The actual damage of the hit.
|
||||||
pub fn damage(&self) -> u32 {
|
pub fn damage(&self) -> u32 {
|
||||||
self.damage.load(Ordering::Relaxed)
|
self.damage.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
/// The type id of the type used for the hit.
|
||||||
pub fn move_type(&self) -> u8 {
|
pub fn move_type(&self) -> u8 {
|
||||||
self.move_type.load(Ordering::Relaxed)
|
self.move_type.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
/// Whether or not the hit has failed.
|
||||||
pub fn has_failed(&self) -> bool {
|
pub fn has_failed(&self) -> bool {
|
||||||
self.has_failed.load(Ordering::Relaxed)
|
self.has_failed.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets whether or not the hit is critical.
|
||||||
pub fn set_critical(&self, value: bool) {
|
pub fn set_critical(&self, value: bool) {
|
||||||
self.critical.store(value, Ordering::SeqCst);
|
self.critical.store(value, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
/// Sets the base power of the hit.
|
||||||
pub fn set_base_power(&self, value: u8) {
|
pub fn set_base_power(&self, value: u8) {
|
||||||
self.base_power.store(value, Ordering::SeqCst);
|
self.base_power.store(value, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
/// Sets the type effectiveness of the hit.
|
||||||
pub fn set_effectiveness(&self, value: f32) {
|
pub fn set_effectiveness(&self, value: f32) {
|
||||||
self.effectiveness.store(value, Ordering::SeqCst);
|
self.effectiveness.store(value, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
/// Sets the actual damage of the hit.
|
||||||
pub fn set_damage(&self, value: u32) {
|
pub fn set_damage(&self, value: u32) {
|
||||||
self.damage.store(value, Ordering::SeqCst);
|
self.damage.store(value, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
/// Sets the move type id of the hit.
|
||||||
pub fn set_move_type(&self, value: u8) {
|
pub fn set_move_type(&self, value: u8) {
|
||||||
self.move_type.store(value, Ordering::SeqCst);
|
self.move_type.store(value, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
/// Marks the hit as failed.
|
||||||
pub fn fail(&self) {
|
pub fn fail(&self) {
|
||||||
self.has_failed.store(true, Ordering::SeqCst);
|
self.has_failed.store(true, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An executing move is the data of the move for while it is executing.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ExecutingMove<'own, 'battle, 'library> {
|
pub struct ExecutingMove<'own, 'battle, 'library> {
|
||||||
|
/// The number of hits this move has.
|
||||||
number_of_hits: u8,
|
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<HitData>,
|
hits: Vec<HitData>,
|
||||||
|
/// The user of the move.
|
||||||
user: Arc<Pokemon<'battle, 'library>>,
|
user: Arc<Pokemon<'battle, 'library>>,
|
||||||
|
/// The move the user has actually chosen to do.
|
||||||
chosen_move: Arc<LearnedMove<'library>>,
|
chosen_move: Arc<LearnedMove<'library>>,
|
||||||
|
/// The move that the user is actually going to do.
|
||||||
use_move: &'own MoveData,
|
use_move: &'own MoveData,
|
||||||
|
/// The script of the move.
|
||||||
script: ScriptContainer,
|
script: ScriptContainer,
|
||||||
|
/// The targets for this move.
|
||||||
targets: &'own TargetList<'battle, 'library>,
|
targets: &'own TargetList<'battle, 'library>,
|
||||||
|
/// Data required for this to be a script source.
|
||||||
script_source_data: RwLock<ScriptSourceData>,
|
script_source_data: RwLock<ScriptSourceData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'own, 'battle, 'library> ExecutingMove<'own, 'battle, 'library> {
|
impl<'own, 'battle, 'library> ExecutingMove<'own, 'battle, 'library> {
|
||||||
|
/// Instantiates an executing move.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
targets: &'own TargetList<'battle, 'library>,
|
targets: &'own TargetList<'battle, 'library>,
|
||||||
number_of_hits: u8,
|
number_of_hits: u8,
|
||||||
|
@ -100,26 +130,33 @@ impl<'own, 'battle, 'library> ExecutingMove<'own, 'battle, 'library> {
|
||||||
script_source_data: Default::default(),
|
script_source_data: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The number of targets this move has.
|
||||||
pub fn target_count(&self) -> usize {
|
pub fn target_count(&self) -> usize {
|
||||||
self.targets.len()
|
self.targets.len()
|
||||||
}
|
}
|
||||||
|
/// The number of hits this move has per target.
|
||||||
pub fn number_of_hits(&self) -> u8 {
|
pub fn number_of_hits(&self) -> u8 {
|
||||||
self.number_of_hits
|
self.number_of_hits
|
||||||
}
|
}
|
||||||
|
/// The user of the move.
|
||||||
pub fn user(&self) -> &Arc<Pokemon<'battle, 'library>> {
|
pub fn user(&self) -> &Arc<Pokemon<'battle, 'library>> {
|
||||||
&self.user
|
&self.user
|
||||||
}
|
}
|
||||||
|
/// The move the user has actually chosen to do.
|
||||||
pub fn chosen_move(&self) -> &Arc<LearnedMove<'library>> {
|
pub fn chosen_move(&self) -> &Arc<LearnedMove<'library>> {
|
||||||
&self.chosen_move
|
&self.chosen_move
|
||||||
}
|
}
|
||||||
|
/// The move that the user is actually going to do.
|
||||||
pub fn use_move(&self) -> &'own MoveData {
|
pub fn use_move(&self) -> &'own MoveData {
|
||||||
self.use_move
|
self.use_move
|
||||||
}
|
}
|
||||||
|
/// The script of the move.
|
||||||
pub fn script(&self) -> &ScriptContainer {
|
pub fn script(&self) -> &ScriptContainer {
|
||||||
&self.script
|
&self.script
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets a hit data for a target, with a specific index.
|
||||||
pub fn get_hit_data<'func>(
|
pub fn get_hit_data<'func>(
|
||||||
&'func self,
|
&'func self,
|
||||||
for_target: &'func Arc<Pokemon<'battle, 'library>>,
|
for_target: &'func Arc<Pokemon<'battle, 'library>>,
|
||||||
|
@ -136,6 +173,7 @@ impl<'own, 'battle, 'library> ExecutingMove<'own, 'battle, 'library> {
|
||||||
Err(PokemonError::InvalidTargetRequested)
|
Err(PokemonError::InvalidTargetRequested)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks whether a Pokemon is a target for this move.
|
||||||
pub fn is_pokemon_target(&self, pokemon: &Arc<Pokemon<'battle, 'library>>) -> bool {
|
pub fn is_pokemon_target(&self, pokemon: &Arc<Pokemon<'battle, 'library>>) -> bool {
|
||||||
for target in self.targets.iter().flatten() {
|
for target in self.targets.iter().flatten() {
|
||||||
if std::ptr::eq(target.deref().deref(), pokemon.deref().deref()) {
|
if std::ptr::eq(target.deref().deref(), pokemon.deref().deref()) {
|
||||||
|
@ -145,6 +183,7 @@ impl<'own, 'battle, 'library> ExecutingMove<'own, 'battle, 'library> {
|
||||||
false
|
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<Pokemon<'battle, 'library>>) -> PkmnResult<usize> {
|
pub(crate) fn get_index_of_target(&self, for_target: &Arc<Pokemon<'battle, 'library>>) -> PkmnResult<usize> {
|
||||||
for (index, target) in self.targets.iter().enumerate() {
|
for (index, target) in self.targets.iter().enumerate() {
|
||||||
if let Some(target) = target {
|
if let Some(target) = target {
|
||||||
|
@ -157,6 +196,7 @@ impl<'own, 'battle, 'library> ExecutingMove<'own, 'battle, 'library> {
|
||||||
Err(PokemonError::InvalidTargetRequested)
|
Err(PokemonError::InvalidTargetRequested)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets a hit based on its raw index.
|
||||||
pub(crate) fn get_hit_from_raw_index(&self, index: usize) -> &HitData {
|
pub(crate) fn get_hit_from_raw_index(&self, index: usize) -> &HitData {
|
||||||
&self.hits[index]
|
&self.hits[index]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::static_data::MoveData;
|
|
||||||
use std::sync::atomic::{AtomicU8, Ordering};
|
use std::sync::atomic::{AtomicU8, Ordering};
|
||||||
|
|
||||||
|
use crate::static_data::MoveData;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LearnedMove<'library> {
|
pub struct LearnedMove<'library> {
|
||||||
move_data: &'library MoveData,
|
move_data: &'library MoveData,
|
||||||
|
@ -46,4 +47,15 @@ impl<'a> LearnedMove<'a> {
|
||||||
self.remaining_pp.fetch_sub(amount, Ordering::SeqCst);
|
self.remaining_pp.fetch_sub(amount, Ordering::SeqCst);
|
||||||
true
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -439,7 +439,11 @@ impl<'own, 'library> Pokemon<'own, 'library> {
|
||||||
|
|
||||||
let ability_script = self
|
let ability_script = self
|
||||||
.library
|
.library
|
||||||
.load_script(ScriptCategory::Ability, self.active_ability().name())
|
.load_script(
|
||||||
|
(self as *const Self).cast(),
|
||||||
|
ScriptCategory::Ability,
|
||||||
|
self.active_ability().name(),
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
if let Some(ability_script) = ability_script {
|
if let Some(ability_script) = ability_script {
|
||||||
self.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<Option<Arc<dyn Script>>> {
|
fn load_volatile_script(&self, key: &StringKey) -> PkmnResult<Option<Arc<dyn Script>>> {
|
||||||
self.library.load_script(ScriptCategory::Pokemon, key)
|
self.library
|
||||||
|
.load_script((self as *const Self).cast(), ScriptCategory::Pokemon, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod test {
|
pub mod test {
|
||||||
use crate::dynamic_data::models::pokemon::Pokemon;
|
use crate::dynamic_data::models::pokemon::Pokemon;
|
||||||
use crate::dynamic_data::DynamicLibrary;
|
|
||||||
use crate::static_data::AbilityIndex;
|
use crate::static_data::AbilityIndex;
|
||||||
use crate::static_data::DataLibrary;
|
use crate::static_data::DataLibrary;
|
||||||
use crate::static_data::Gender;
|
use crate::static_data::Gender;
|
||||||
|
|
|
@ -134,16 +134,6 @@ pub trait Script: Send + Sync {
|
||||||
|
|
||||||
/// This function allows a script to change the effective base power of a move hit.
|
/// This function allows a script to change the effective base power of a move hit.
|
||||||
fn change_base_power(&self, _move: &ExecutingMove, _target: &Arc<Pokemon>, _hit: u8, _base_power: &mut u8) {}
|
fn change_base_power(&self, _move: &ExecutingMove, _target: &Arc<Pokemon>, _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<Pokemon>,
|
|
||||||
_hit: u8,
|
|
||||||
_stats_user: &mut Arc<Pokemon>,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
/// This function allows a script to bypass defensive stat boosts for a move hit.
|
/// This function allows a script to bypass defensive stat boosts for a move hit.
|
||||||
fn bypass_defensive_stat_boost(&self, _move: &ExecutingMove, _target: &Arc<Pokemon>, _hit: u8, _bypass: &mut bool) {
|
fn bypass_defensive_stat_boost(&self, _move: &ExecutingMove, _target: &Arc<Pokemon>, _hit: u8, _bypass: &mut bool) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,10 @@ use project_root::get_project_root;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use pkmn_lib::defines::LevelInt;
|
use pkmn_lib::defines::LevelInt;
|
||||||
use pkmn_lib::dynamic_data::DynamicLibrary;
|
|
||||||
use pkmn_lib::dynamic_data::Gen7BattleStatCalculator;
|
use pkmn_lib::dynamic_data::Gen7BattleStatCalculator;
|
||||||
use pkmn_lib::dynamic_data::Gen7DamageLibrary;
|
use pkmn_lib::dynamic_data::Gen7DamageLibrary;
|
||||||
use pkmn_lib::dynamic_data::Gen7MiscLibrary;
|
use pkmn_lib::dynamic_data::Gen7MiscLibrary;
|
||||||
|
use pkmn_lib::dynamic_data::{DynamicLibrary, EmptyScriptResolver};
|
||||||
use pkmn_lib::static_data::{
|
use pkmn_lib::static_data::{
|
||||||
Ability, AbilityLibrary, BattleItemCategory, DataLibrary, EffectParameter, Form, GrowthRateLibrary, Item,
|
Ability, AbilityLibrary, BattleItemCategory, DataLibrary, EffectParameter, Form, GrowthRateLibrary, Item,
|
||||||
ItemLibrary, LearnableMoves, LibrarySettings, LookupGrowthRate, MoveData, MoveLibrary, Nature, NatureLibrary,
|
ItemLibrary, LearnableMoves, LibrarySettings, LookupGrowthRate, MoveData, MoveLibrary, Nature, NatureLibrary,
|
||||||
|
@ -37,6 +37,7 @@ pub fn load_library() -> DynamicLibrary {
|
||||||
Box::new(Gen7BattleStatCalculator {}),
|
Box::new(Gen7BattleStatCalculator {}),
|
||||||
Box::new(Gen7DamageLibrary::new(false)),
|
Box::new(Gen7DamageLibrary::new(false)),
|
||||||
Box::new(Gen7MiscLibrary::new()),
|
Box::new(Gen7MiscLibrary::new()),
|
||||||
|
Box::new(EmptyScriptResolver {}),
|
||||||
);
|
);
|
||||||
dynamic
|
dynamic
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue