PkmnLib_rs/src/dynamic_data/libraries/damage_library.rs

306 lines
9.7 KiB
Rust
Executable File

use std::sync::Arc;
use crate::dynamic_data::script_handling::ScriptSource;
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 {
/// Calculate the damage for a given hit on a Pokemon.
fn get_damage(
&self,
executing_move: &ExecutingMove,
target: &Arc<Pokemon>,
hit_number: u8,
hit_data: &HitData,
) -> u32;
/// Calculate the base power for a given hit on a Pokemon.
fn get_base_power(
&self,
executing_move: &ExecutingMove,
target: &Arc<Pokemon>,
hit_number: u8,
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 {
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 {
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 get_damage(
&self,
executing_move: &ExecutingMove,
target: &Arc<Pokemon>,
hit_number: u8,
hit_data: &HitData,
) -> u32 {
if executing_move.use_move().category() == MoveCategory::Status {
return 0;
}
let level_modifier = ((2.0 * executing_move.user().level() as f32) / 5.0).floor() + 2.0;
let base_power = hit_data.base_power();
let stat_modifier = self.get_stat_modifier(executing_move, target, hit_number, hit_data);
let damage_modifier = self.get_damage_modifier(executing_move, target, hit_number, hit_data);
let mut float_damage = (level_modifier * base_power as f32).floor();
float_damage = (float_damage * stat_modifier).floor();
float_damage = (float_damage / 50.0).floor() + 2.0;
float_damage = (float_damage * damage_modifier).floor();
if executing_move.target_count() > 1 {
float_damage = (float_damage * 0.75).floor();
}
if hit_data.is_critical() {
let mut crit_modifier = 1.5;
script_hook!(
change_critical_modifier,
executing_move,
executing_move,
target,
hit_number,
&mut crit_modifier
);
float_damage = (float_damage * crit_modifier).floor();
}
if self.has_randomness {
let random_percentage = 85 + executing_move.user().get_battle().unwrap().random().get_between(0, 16);
float_damage = (float_damage * (random_percentage as f32 / 100.0)).floor();
}
if executing_move.user().types().contains(&hit_data.move_type()) {
let mut stab_modifier = 1.5;
script_hook!(
change_stab_modifier,
executing_move,
executing_move,
target,
hit_number,
&mut stab_modifier
);
float_damage = (float_damage * stab_modifier).floor();
}
float_damage = (float_damage * hit_data.effectiveness()).floor();
let mut damage = if float_damage <= 0.0 {
if hit_data.effectiveness() == 0.0 {
0
} else {
1
}
} else if float_damage >= u32::MAX as f32 {
u32::MAX
} else {
float_damage as u32
};
script_hook!(
change_damage,
executing_move,
executing_move,
target,
hit_number,
&mut damage
);
script_hook!(
change_incoming_damage,
target,
executing_move,
target,
hit_number,
&mut damage
);
damage
}
fn get_base_power(
&self,
executing_move: &ExecutingMove,
target: &Arc<Pokemon>,
hit_number: u8,
_hit_data: &HitData,
) -> u8 {
if executing_move.use_move().category() == MoveCategory::Status {
return 0;
}
let mut base_power = executing_move.use_move().base_power();
script_hook!(
change_base_power,
executing_move,
executing_move,
target,
hit_number,
&mut base_power
);
base_power
}
fn is_critical(
&self,
battle: &Battle,
executing_move: &ExecutingMove,
target: &Arc<Pokemon>,
hit_number: u8,
) -> bool {
// Status moves can't be critical.
if executing_move.use_move().category() == MoveCategory::Status {
return false;
}
// Get the critical stage from scripts.
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,
}
}
}