306 lines
9.7 KiB
Rust
Executable File
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,
|
|
}
|
|
}
|
|
}
|