346 lines
13 KiB
Rust
346 lines
13 KiB
Rust
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 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.clone(),
|
|
number_of_hits,
|
|
choice.user().clone(),
|
|
used_move.clone(),
|
|
move_data.clone(),
|
|
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<Pokemon>) -> 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(())
|
|
}
|
|
}
|