use crate::dynamic_data::choices::TurnChoice; use crate::dynamic_data::event_hooks::event_hook::Event; use crate::dynamic_data::flow::target_resolver::resolve_targets; use crate::dynamic_data::models::battle::Battle; use crate::dynamic_data::models::damage_source::DamageSource; use crate::dynamic_data::models::executing_move::ExecutingMove; use crate::dynamic_data::models::pokemon::Pokemon; use crate::dynamic_data::script_handling::{ScriptSource, ScriptWrapper}; use crate::static_data::{DataLibrary, MoveCategory}; use crate::{run_scripts, script_hook, script_hook_on_lock, PkmnResult}; use parking_lot::RwLock; use std::ops::{Deref, DerefMut}; use std::sync::Arc; impl<'own, 'library> Battle<'own, 'library> { pub(crate) fn run_turn(&mut self) -> PkmnResult<()> { let mut choice_queue = self.current_turn_queue().as_ref().unwrap(); // We are now at the very beginning of a turn. We have assigned speeds and priorities to all // choices, and put them in the correct order. // The first thing to do is to run the on_before_turn script hook on every choice. This script hook // is primarily intended to be used to reset variables on a script (for example scripts that need // to check whether a pokemon was hit this turn. By resetting here, and setting a variable to true // they can then know this later on.) for choice in choice_queue.get_queue().iter().flatten() { script_hook!(on_before_turn, choice, choice); } // Now we can properly begin executing choices. // One by one dequeue the turns, and run them. If the battle has ended we do not want to // continue running however. while choice_queue.has_next() && !self.has_ended() { let choice = self.current_turn_queue_mut().as_mut().unwrap().dequeue(); self.execute_choice(&choice)?; choice_queue = self.current_turn_queue().as_ref().unwrap(); } // If the battle is not ended, we have arrived at the normal end of a turn. and thus want // to run the end turn scripts. // As we want all scripts to run exactly once, including those on the sides and battles, // we can't just use the default script hook macro on each pokemon. Instead manually call // the script functions on every script. if !self.has_ended() { let mut scripts; for side in self.sides() { for pokemon in side.pokemon().iter().flatten() { scripts = Vec::new(); pokemon.read().get_own_scripts(&mut scripts); run_scripts!(on_end_turn, scripts,); } scripts = Vec::new(); side.get_own_scripts(&mut scripts); run_scripts!(on_end_turn, scripts,); } scripts = Vec::new(); self.get_own_scripts(&mut scripts); run_scripts!(on_end_turn, scripts,); } Ok(()) } fn execute_choice(&self, choice: &TurnChoice<'own, 'library>) -> PkmnResult<()> { if let TurnChoice::Pass(..) = choice { return Ok(()); } if self.has_ended() { return Ok(()); } { let user = choice.user().read(); if !user.is_usable() { return Ok(()); } if !user.is_on_battlefield() { return Ok(()); } } if !self.can_use(&choice) { return Ok(()); } match choice { TurnChoice::Move(..) => self.execute_move_choice(&choice)?, TurnChoice::Item(_) => {} TurnChoice::Switch(_) => {} TurnChoice::Flee(_) => {} TurnChoice::Pass(_) => {} } Ok(()) } fn execute_move_choice<'func>(&'func self, choice: &'func TurnChoice<'own, 'library>) -> PkmnResult<()> { let choice = choice.get_move_turn_data(); let used_move = choice.used_move(); let move_data = { let move_data_lock = used_move; let move_data = move_data_lock.move_data(); let mut move_name = move_data.name().clone(); script_hook!(change_move, choice, choice, &mut move_name); self.library().static_data().moves().get(&move_name).unwrap() }; // FIXME: also change the script on the choice if changed; let target_type = move_data.target(); let targets = resolve_targets(choice.target_side(), choice.target_index(), target_type, self); let mut number_of_hits: u8 = 1; script_hook!(change_number_of_hits, choice, choice, &mut number_of_hits); if number_of_hits == 0 { return Ok(()); } let mut executing_move = ExecutingMove::new( &targets, number_of_hits, choice.user().clone(), used_move.clone(), move_data, choice.script().clone(), ); let mut prevented = false; script_hook!(prevent_move, executing_move, &executing_move, &mut prevented); if prevented { return Ok(()); } if !executing_move.chosen_move().try_use(1) { return Ok(()); } self.event_hook().trigger(Event::MoveUse { executing_move: &executing_move, }); let mut fail = false; script_hook!(fail_move, executing_move, &executing_move, &mut fail); if fail { // TODO: Add fail handling return Ok(()); } let mut stop = false; script_hook!(stop_before_move, executing_move, &executing_move, &mut stop); if stop { return Ok(()); } script_hook!(on_before_move, executing_move, &executing_move); for target in targets.iter().flatten() { self.handle_move_for_target(&mut executing_move, target)?; } Ok(()) } fn handle_move_for_target( &self, executing_move: &mut ExecutingMove<'_, 'own, 'library>, target: &Arc>>, ) -> PkmnResult<()> { { let mut fail = false; script_hook_on_lock!(fail_incoming_move, target, executing_move, target, &mut fail); if fail { // TODO: Add fail handling return Ok(()); } } { let mut invulnerable = false; script_hook_on_lock!(is_invulnerable, target, executing_move, target, &mut invulnerable); if invulnerable { // TODO: Add fail handling return Ok(()); } } let number_of_hits = executing_move.number_of_hits(); if number_of_hits == 0 { script_hook_on_lock!(on_move_miss, target, executing_move, target); self.event_hook().trigger(Event::Miss { user: executing_move.user().read().deref(), }); return Ok(()); } let target_hit_stat = executing_move.get_index_of_target(target)?; for hit_index in 0..number_of_hits { if self.has_ended() { return Ok(()); } if executing_move.user().read().is_fainted() { break; } if target.read().is_fainted() { break; } { let mut hit_type = executing_move.use_move().move_type(); script_hook!( change_move_type, executing_move, executing_move, target, hit_index, &mut hit_type ); executing_move .get_hit_from_raw_index_mut(target_hit_stat + hit_index as usize) .set_move_type(hit_type); let mut effectiveness = self .library() .static_data() .types() .get_effectiveness(hit_type, target.read().types()); script_hook!( change_effectiveness, executing_move, executing_move, target, hit_index, &mut effectiveness ); executing_move .get_hit_from_raw_index_mut(target_hit_stat + hit_index as usize) .set_effectiveness(effectiveness); let mut block_critical = false; script_hook!( block_critical, executing_move, executing_move, target, hit_index, &mut block_critical ); script_hook_on_lock!( block_incoming_critical, target, executing_move, target, hit_index, &mut block_critical ); if !block_critical { let is_critical = self.library() .misc_library() .is_critical(self, executing_move, target, hit_index); executing_move .get_hit_from_raw_index_mut(target_hit_stat + hit_index as usize) .set_critical(is_critical); } let base_power = self.library().damage_calculator().get_base_power( executing_move, target, hit_index, executing_move.get_hit_data(target, hit_index)?, ); executing_move .get_hit_from_raw_index_mut(target_hit_stat + hit_index as usize) .set_base_power(base_power); let damage = self.library().damage_calculator().get_damage( executing_move, target, hit_index, executing_move.get_hit_data(target, hit_index)?, ); executing_move .get_hit_from_raw_index_mut(target_hit_stat + hit_index as usize) .set_damage(damage); if executing_move.use_move().category() == MoveCategory::Status { if executing_move.use_move().has_secondary_effect() { let secondary_effect_chance = executing_move.use_move().secondary_effect().chance(); if secondary_effect_chance == -1.0 || self .random() .effect_chance(secondary_effect_chance, executing_move, target, hit_index) { script_hook!(on_secondary_effect, executing_move, executing_move, target, hit_index); // TODO: on fail } } } else { let mut damage = executing_move .get_hit_from_raw_index(target_hit_stat + hit_index as usize) .damage(); let current_health = target.read().current_health(); if damage > current_health { damage = current_health; executing_move .get_hit_from_raw_index_mut(target_hit_stat + hit_index as usize) .set_damage(damage); } if damage > 0 { target.write().damage(damage, DamageSource::AttackDamage); if !target.read().is_fainted() { script_hook_on_lock!(on_incoming_hit, target, executing_move, target, hit_index); } else { script_hook!(on_opponent_faints, executing_move, executing_move, target, hit_index); } if executing_move.use_move().has_secondary_effect() && !target.read().is_fainted() { let mut prevent_secondary = false; script_hook_on_lock!( prevent_secondary_effect, target, executing_move, target, hit_index, &mut prevent_secondary ); if !prevent_secondary { let secondary_effect_chance = executing_move.use_move().secondary_effect().chance(); if secondary_effect_chance == -1.0 || self.random().effect_chance( secondary_effect_chance, executing_move, target, hit_index, ) { script_hook!( on_secondary_effect, executing_move, executing_move, target, hit_index ); // TODO: on fail } } } } } } } if !executing_move.user().read().is_fainted() { script_hook!(on_after_hits, executing_move, executing_move, target); } Ok(()) } }