PkmnLib_rs/src/dynamic_data/flow/turn_runner.rs

339 lines
14 KiB
Rust
Raw Normal View History

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<RwLock<Pokemon<'own, 'library>>>,
) -> 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(())
}
}