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, 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, 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, hit_number: u8, ) -> bool; } /// The implementation of a Damage Library for generation 7. #[derive(Debug)] pub struct Gen7DamageLibrary { /// Defines whether or not a random damage modifier is applied to damage (0.85 - 1.00). has_randomness: bool, } impl Gen7DamageLibrary { /// Creates a new generation 7 damage library. `has_randomness` defines whether a random damage /// modifier (0.85x - 1.00x) is applied to the calculated damage. pub fn new(has_randomness: bool) -> Self { Self { has_randomness } } /// Calculates the modifier applied to damage from the statistics of the relevant Pokemon. fn get_stat_modifier( &self, executing_move: &ExecutingMove, target: &Arc, hit_number: u8, hit_data: &HitData, ) -> f32 { let user = executing_move.user(); let offensive_stat; let defensive_stat; // Get the relevant stats based on the move category. if executing_move.use_move().category() == MoveCategory::Physical { offensive_stat = Statistic::Attack; defensive_stat = Statistic::Defense; } else { offensive_stat = Statistic::SpecialAttack; defensive_stat = Statistic::SpecialDefense; } // Check if we can bypass the defensive stat boost on the target. We default to this if the // move is critical, and the target has a defensive stat boost of > 0, but a script is // allowed to change this. let mut bypass_defensive_stat_boost = hit_data.is_critical() && target.stat_boost(defensive_stat) > 0; script_hook!( bypass_defensive_stat_boost, executing_move, executing_move, target, hit_number, &mut bypass_defensive_stat_boost ); // Check if we can bypass the offensive stat boost on the user. We default to this if the // move is critical, and the user has an offensive stat boost of < 0, but a script is // allowed to change this. let mut bypass_offensive_stat_boost = hit_data.is_critical() && user.stat_boost(offensive_stat) > 0; script_hook!( bypass_offensive_stat_boost, executing_move, executing_move, target, hit_number, &mut bypass_offensive_stat_boost ); // Get the unboosted stat if we bypass the stat boost, otherwise get the boosted stat. let mut defensive_stat = if bypass_defensive_stat_boost { target.flat_stats().get_stat(offensive_stat) } else { target.boosted_stats().get_stat(offensive_stat) }; let mut offensive_stat = if bypass_offensive_stat_boost { user.flat_stats().get_stat(offensive_stat) } else { user.boosted_stats().get_stat(offensive_stat) }; // Allow scripts to modify the raw values we use for the damage modifier. script_hook!( change_defensive_stat_value, executing_move, executing_move, target, hit_number, &mut defensive_stat ); script_hook!( change_offensive_stat_value, executing_move, executing_move, target, hit_number, &mut offensive_stat ); // Calculate the effective stat modifier. let mut stat_modifier = offensive_stat as f32 / defensive_stat as f32; script_hook!( change_damage_stat_modifier, executing_move, executing_move, target, hit_number, &mut stat_modifier ); stat_modifier } /// Gets the damage modifier. This is a value that defaults to 1.0, but can be modified by scripts /// to apply a raw modifier to the damage. fn get_damage_modifier( &self, executing_move: &ExecutingMove, target: &Arc, hit_number: u8, _hit_data: &HitData, ) -> f32 { 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, 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, 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, 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, } } }