use std::ops::Deref; use std::sync::Arc; use crate::dynamic_data::choices::TurnChoice; use crate::dynamic_data::event_hooks::Event; use crate::dynamic_data::flow::target_resolver::resolve_targets; use crate::dynamic_data::script_handling::{ScriptSource, ScriptWrapper}; use crate::dynamic_data::Battle; use crate::dynamic_data::DamageSource; use crate::dynamic_data::ExecutingMove; use crate::dynamic_data::Pokemon; use crate::static_data::{DataLibrary, MoveCategory}; use crate::{run_scripts, script_hook, PkmnResult}; impl Battle { /// Execute the entire turn after the choices are all set. pub(crate) fn run_turn(&self) -> PkmnResult<()> { let choice_queue = self.current_turn_queue(); // 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.read().as_ref().unwrap().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.read().as_ref().unwrap().has_next() && !self.has_ended() { let choice = choice_queue.write().as_mut().unwrap().dequeue(); self.execute_choice(&choice)?; } // 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.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(()) } /// Executes a single choice. fn execute_choice(&self, choice: &TurnChoice) -> PkmnResult<()> { // A pass turn choice means the user does not intend to do anything. As such, return. if let TurnChoice::Pass(..) = choice { return Ok(()); } if self.has_ended() { return Ok(()); } let user = choice.user(); 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(()) } /// Executes a move choice. fn execute_move_choice<'func>(&'func self, choice: &'func TurnChoice) -> PkmnResult<()> { let move_choice = choice.get_move_turn_data(); let used_move = 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(move_choice.target_side(), move_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.clone(), number_of_hits, choice.user().clone(), used_move.clone(), move_data.clone(), move_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(()) } /// Executes a move turn choice on a single target. fn handle_move_for_target(&self, executing_move: &mut ExecutingMove, target: &Arc) -> PkmnResult<()> { { let mut fail = false; script_hook!(fail_incoming_move, target, executing_move, target, &mut fail); if fail { // TODO: Add fail handling return Ok(()); } } { let mut invulnerable = false; script_hook!(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(); 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().is_fainted() { break; } if target.is_fainted() { break; } let used_move = executing_move.use_move(); let mut hit_type = used_move.move_type(); script_hook!( change_move_type, executing_move, executing_move, target, hit_index, &mut hit_type ); let hit_data = executing_move.get_hit_from_raw_index(target_hit_stat + hit_index as usize); hit_data.set_move_type(hit_type); let mut effectiveness = self .library() .static_data() .types() .get_effectiveness(hit_type, target.types()); script_hook!( change_effectiveness, executing_move, executing_move, target, hit_index, &mut effectiveness ); hit_data.set_effectiveness(effectiveness); let mut block_critical = false; script_hook!( block_critical, executing_move, executing_move, target, hit_index, &mut block_critical ); script_hook!( block_incoming_critical, target, executing_move, target, hit_index, &mut block_critical ); if !block_critical { let is_critical = self.library() .damage_calculator() .is_critical(self, executing_move, target, hit_index); hit_data.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)?, ); hit_data.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)?, ); hit_data.set_damage(damage); let mut accuracy = executing_move.use_move().accuracy(); // If the accuracy is 255, the move should always hit, and as such we should not allow // modifying it. if accuracy != 255 { script_hook!( change_accuracy, executing_move, executing_move, target, hit_index, &mut accuracy ); } if accuracy < 100 && self.random().get_max(100) as u8 >= accuracy { script_hook!(on_move_miss, target, executing_move, target); self.event_hook().trigger(Event::Miss { user: executing_move.user().deref(), }); break; } if used_move.category() == MoveCategory::Status { if let Some(secondary_effect) = used_move.secondary_effect() { let secondary_effect_chance = 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 = hit_data.damage(); let current_health = target.current_health(); if damage > current_health { damage = current_health; hit_data.set_damage(damage); } if damage > 0 { target.damage(damage, DamageSource::MoveDamage); if !target.is_fainted() { script_hook!(on_incoming_hit, target, executing_move, target, hit_index); } else { script_hook!(on_opponent_faints, executing_move, executing_move, target, hit_index); } if !target.is_fainted() { if let Some(secondary_effect) = used_move.secondary_effect() { let mut prevent_secondary = false; script_hook!( prevent_secondary_effect, target, executing_move, target, hit_index, &mut prevent_secondary ); if !prevent_secondary { let secondary_effect_chance = 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 number_of_hits == 0 { script_hook!(on_move_miss, target, executing_move, target); self.event_hook().trigger(Event::Miss { user: executing_move.user().deref(), }); } if !executing_move.user().is_fainted() { script_hook!(on_after_hits, executing_move, executing_move, target); } Ok(()) } }