diff --git a/src/common/mod.rs b/src/common/mod.rs index 69c98f4..e39960c 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -5,6 +5,7 @@ use smash::lua2cpp::L2CFighterCommon; pub use crate::common::consts::MENU; use crate::common::consts::*; +use crate::training::character_specific::ptrainer; pub mod button_config; pub mod consts; @@ -155,15 +156,18 @@ pub unsafe fn is_ptrainer(module_accessor: &mut app::BattleObjectModuleAccessor) pub unsafe fn is_dead(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { let status_kind = StatusModule::status_kind(module_accessor); let prev_status_kind = StatusModule::prev_status_kind(module_accessor, 0); - // Pokemon trainer enters FIGHTER_STATUS_KIND_WAIT for one frame during their respawn animation - // And the previous status is FIGHTER_STATUS_NONE + let is_dead_status = + [*FIGHTER_STATUS_KIND_DEAD, *FIGHTER_STATUS_KIND_STANDBY].contains(&status_kind); + let mut ptrainer_switch_dead = false; + // There's one frame during switching that we can't detect as alive early, where the Pokemon is in Wait with no previous status. + // To prevent this matching the situation after a L + R + A reset, we check the status of the PTrainer to see if we're switching. if is_ptrainer(module_accessor) { - [*FIGHTER_STATUS_KIND_DEAD, *FIGHTER_STATUS_KIND_STANDBY].contains(&status_kind) - || (status_kind == FIGHTER_STATUS_KIND_WAIT - && prev_status_kind == FIGHTER_STATUS_KIND_NONE) - } else { - [*FIGHTER_STATUS_KIND_DEAD, *FIGHTER_STATUS_KIND_STANDBY].contains(&status_kind) + ptrainer_switch_dead = (status_kind == FIGHTER_STATUS_KIND_WAIT + && prev_status_kind == FIGHTER_STATUS_KIND_NONE) + && (StatusModule::status_kind(ptrainer::get_ptrainer_module_accessor(module_accessor)) + == *WEAPON_PTRAINER_PTRAINER_STATUS_KIND_RESTART_CHANGE); } + is_dead_status || ptrainer_switch_dead } pub unsafe fn is_in_clatter(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { diff --git a/src/training/character_specific/mod.rs b/src/training/character_specific/mod.rs index 0546682..3a7859d 100644 --- a/src/training/character_specific/mod.rs +++ b/src/training/character_specific/mod.rs @@ -3,6 +3,7 @@ use smash::app::{self}; mod bowser; pub mod items; pub mod pikmin; +pub mod ptrainer; pub mod steve; /** diff --git a/src/training/character_specific/ptrainer.rs b/src/training/character_specific/ptrainer.rs new file mode 100644 index 0000000..20f2f1e --- /dev/null +++ b/src/training/character_specific/ptrainer.rs @@ -0,0 +1,160 @@ +use crate::training::frame_counter; +use crate::training::save_states; +use once_cell::sync::Lazy; +use skyline::hooks::InlineCtx; +use smash::app::{self, lua_bind::*, smashball::is_training_mode}; +use smash::hash40; +use smash::lib::lua_const::*; +use smash::phx::Hash40; + +static SWITCH_DELAY_COUNTER: Lazy = + Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame)); + +pub unsafe fn is_switched(ptrainer_module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { + let status_kind = StatusModule::status_kind(ptrainer_module_accessor); + let situ_kind = StatusModule::situation_kind(ptrainer_module_accessor); + if status_kind == *WEAPON_PTRAINER_PTRAINER_STATUS_KIND_RUN { + MotionModule::set_rate(ptrainer_module_accessor, 1.0); + } + if frame_counter::should_delay(5_u32, *SWITCH_DELAY_COUNTER) { + // Need to wait to make sure we stop the flash effect + return false; + } + // If you're trying to fix PT getting locked up, maybe try + // to run FUN_71000106c0 in lua2cpp_ptrainer to get her wandering correct + // Also worth trying is figuring out how to prevent PT from entering WEAPON_PTRAINER_PTRAINER_STATUS_KIND_RUN_STOP + // instead of run below after that status change + if situ_kind == SITUATION_KIND_AIR { + StatusModule::set_situation_kind( + ptrainer_module_accessor, + app::SituationKind(*SITUATION_KIND_GROUND), + true, + ); + StatusModule::change_status_force( + ptrainer_module_accessor, + *WEAPON_PTRAINER_PTRAINER_STATUS_KIND_RUN, + false, + ); + } + true +} + +pub unsafe fn change_motion( + ptrainer_module_accessor: &mut app::BattleObjectModuleAccessor, + motion_kind: u64, +) { + if app::utility::get_kind(ptrainer_module_accessor) == *WEAPON_KIND_PTRAINER_PTRAINER + && hash40("restart") == motion_kind + && save_states::is_loading() + { + MotionModule::set_rate(ptrainer_module_accessor, 1000.0); + } +} + +pub unsafe fn get_ptrainer_mball_module_accessor( + ptrainer_module_accessor: &mut app::BattleObjectModuleAccessor, +) -> Option<&mut app::BattleObjectModuleAccessor> { + if ArticleModule::is_exist( + ptrainer_module_accessor, + *WEAPON_PTRAINER_PTRAINER_GENERATE_ARTICLE_MBALL, + ) { + let ptrainer_masterball: *mut app::Article = ArticleModule::get_article( + ptrainer_module_accessor, + *WEAPON_PTRAINER_PTRAINER_GENERATE_ARTICLE_MBALL, + ); + let ptrainer_masterball_id = Article::get_battle_object_id(ptrainer_masterball); + return Some(&mut *app::sv_battle_object::module_accessor( + ptrainer_masterball_id as u32, + )); + } + None +} + +pub unsafe fn get_ptrainer_module_accessor( + module_accessor: &mut app::BattleObjectModuleAccessor, +) -> &mut app::BattleObjectModuleAccessor { + let ptrainer_object_id = + LinkModule::get_parent_object_id(module_accessor, *FIGHTER_POKEMON_LINK_NO_PTRAINER); + &mut *app::sv_battle_object::module_accessor(ptrainer_object_id as u32) +} + +pub unsafe fn get_pokemon_module_accessor( + ptrainer_module_accessor: *mut app::BattleObjectModuleAccessor, +) -> *mut app::BattleObjectModuleAccessor { + let pokemon_object_id = LinkModule::get_node_object_id( + ptrainer_module_accessor, + *WEAPON_PTRAINER_PTRAINER_LINK_NO_POKEMON, + ); + &mut *app::sv_battle_object::module_accessor(pokemon_object_id as u32) +} + +pub unsafe fn handle_pokemon_effect( + module_accessor: &mut app::BattleObjectModuleAccessor, + hash: Hash40, + size: f32, +) -> f32 { + let kind = app::utility::get_kind(module_accessor); + if ![ + *FIGHTER_KIND_PZENIGAME, + *FIGHTER_KIND_PFUSHIGISOU, + *FIGHTER_KIND_PLIZARDON, + *WEAPON_KIND_PTRAINER_PTRAINER, + *WEAPON_KIND_PTRAINER_MBALL, + -1, + ] + .contains(&kind) + { + return size; + } + + let is_ptrainer_switch_hash = [ + Hash40::new("sys_flying_plate"), // for Req + Hash40::new("ptrainer_change_light"), // for ReqOnJoint + ] + .contains(&hash) + || hash.hash == 0x10e3fac8d9; + + // We never want the flying plate, and otherwise we allow outside of savestates + if (is_ptrainer_switch_hash && save_states::is_loading()) + || Hash40::new("sys_flying_plate") == hash + { + // Making the size 0 prevents these effects from being displayed. Fixes Pokemon Trainer Angel Platform Effect. + return 0.0; + } + size +} + +pub unsafe fn handle_pokemon_sound_effect(hash: Hash40) -> Hash40 { + let is_ptrainer_switch_sound_hash = [ + Hash40::new("se_ptrainer_change_appear"), + Hash40::new("se_ptrainer_ball_open"), + Hash40::new("se_ptrainer_ball_swing"), + ] + .contains(&hash); + if is_ptrainer_switch_sound_hash && save_states::is_loading() { + return Hash40::new("se_silent"); + } + hash +} + +// Choose which pokemon to switch to! +static POKEMON_DECIDE_OFFSET: usize = 0x34cdc64; + +#[skyline::hook(offset = POKEMON_DECIDE_OFFSET, inline)] +unsafe fn handle_pokemon_decide(ctx: &mut InlineCtx) { + if !is_training_mode() || !save_states::is_loading() { + return; + } + let x20 = ctx.registers[20].x.as_mut(); + let fighter = *x20 as *mut u64 as *mut app::Fighter; + let module_accessor = (*fighter).battle_object.module_accessor; + let pokemon_value = save_states::get_state_pokemon(module_accessor); + if pokemon_value <= 2 { + let w8 = ctx.registers[8].w.as_mut(); + *w8 = pokemon_value; + } +} + +pub fn init() { + skyline::install_hooks!(handle_pokemon_decide,); +} diff --git a/src/training/debug.rs b/src/training/debug.rs new file mode 100644 index 0000000..baaaeef --- /dev/null +++ b/src/training/debug.rs @@ -0,0 +1,174 @@ +#![allow(dead_code)] // For Debug +#![allow(unused_imports)] +#![cfg(debug_assertions)] +use crate::common::is_operation_cpu; +use smash::app::{self, lua_bind::*, smashball::is_training_mode, utility}; +use smash::lib::lua_const::*; + +#[skyline::from_offset(0x1655400)] +fn is_visible_backshield(module_accessor: *mut app::BattleObjectModuleAccessor) -> bool; + +#[repr(C)] +pub struct WorkModule2 { + vtable: u64, + owner: &'static mut app::BattleObjectModuleAccessor, +} + +static ON_FLAG_OFFSET: usize = 0x4e4910; +#[skyline::hook(offset = ON_FLAG_OFFSET)] +pub unsafe fn handle_on_flag(work_module: &mut WorkModule2, address: i32) { + if address == *WEAPON_PTRAINER_PTRAINER_INSTANCE_WORK_ID_FLAG_OUTFIELD_INVISIBLE + && app::utility::get_kind(work_module.owner) != *FIGHTER_KIND_SHEIK + { + is_visible_backshield(work_module.owner); + } + original!()(work_module, address); +} + +static SET_INT_OFFSET: usize = 0x4e4600; +#[skyline::hook(offset = SET_INT_OFFSET)] +pub unsafe fn handle_set_int(work_module: &mut WorkModule2, value: u32, address: i32) { + if !is_training_mode() { + original!()(work_module, value, address); + } + if address == *WEAPON_PTRAINER_MBALL_INSTANCE_WORK_ID_INT_PLATE_EFF_ID + && app::utility::get_kind(work_module.owner) == *WEAPON_KIND_PTRAINER_MBALL + { + is_visible_backshield(work_module.owner); + } + original!()(work_module, value, address); +} + +static SET_INT64_OFFSET: usize = 0x4e4680; +#[skyline::hook(offset = SET_INT64_OFFSET)] +pub unsafe fn handle_set_int_64(work_module: &mut WorkModule2, value: u64, address: i32) { + if !is_training_mode() { + original!()(work_module, value, address); + } + original!()(work_module, value, address); +} + +static SET_FLOAT_OFFSET: usize = 0x4e4420; +#[skyline::hook(offset = SET_FLOAT_OFFSET)] +pub unsafe fn handle_set_float(work_module: &mut WorkModule2, value: f32, address: i32) { + if !is_training_mode() { + original!()(work_module, value, address); + } + original!()(work_module, value, address); +} + +static IS_FLAG_OFFSET: usize = 0x4e48e0; +#[skyline::hook(offset = IS_FLAG_OFFSET)] +pub unsafe fn handle_is_flag(work_module: &mut WorkModule2, address: i32) -> bool { + if !is_training_mode() { + original!()(work_module, address); + } + if address == *WEAPON_PTRAINER_PTRAINER_INSTANCE_WORK_ID_FLAG_ENABLE_CHANGE_POKEMON //*FIGHTER_KIRBY_INSTANCE_WORK_ID_FLAG_COPY_ON_START + && app::utility::get_kind(work_module.owner) != *FIGHTER_KIND_SHEIK + && original!()(work_module, address) + { + is_visible_backshield(work_module.owner); + } + original!()(work_module, address) +} + +static GET_INT_OFFSET: usize = 0x4e45e0; +#[skyline::hook(offset = GET_INT_OFFSET)] +pub unsafe fn handle_get_int(work_module: &mut WorkModule2, address: i32) { + if !is_training_mode() { + original!()(work_module, address); + } + original!()(work_module, address); +} + +pub fn init() { + skyline::install_hooks!( + //handle_on_flag, + //handle_set_int, + // handle_set_int_64, + // handle_set_float, + // handle_get_int, + //handle_is_flag, + ); +} + +// Example Call: + +// print_fighter_info( +// module_accessor, +// "DebugTest", +// true, +// false, +// true, +// true, +// vec![ +// ("FIGHTER_INSTANCE_WORK_ID_INT_CLIFF_COUNT", FIGHTER_INSTANCE_WORK_ID_INT_CLIFF_COUNT), +// ], +// Vec::new(), +// vec![ +// ("FIGHTER_STATUS_CLIFF_FLAG_TO_FALL", FIGHTER_STATUS_CLIFF_FLAG_TO_FALL), +// ], +// ); +#[allow(clippy::too_many_arguments)] // This function has so many arguments so it's easy to quickly fill them in when debugging with the analyzer +pub fn print_fighter_info( + module_accessor: &mut app::BattleObjectModuleAccessor, + title: &str, + player_only: bool, + cpu_only: bool, + print_fighter_kind: bool, + print_status: bool, + work_int_pairs: Vec<(&str, i32)>, + work_float_pairs: Vec<(&str, i32)>, + work_flag_pairs: Vec<(&str, i32)>, +) { + unsafe { + // Don't print for fighters we don't want to + let is_cpu = is_operation_cpu(module_accessor); + if (player_only && is_cpu) || (cpu_only && !is_cpu) { + return; + } + // Print Title + print!("{}: ", title); + // Print Fighter Kind: + if print_fighter_kind { + print!("FIGHTER_KIND: {}, ", utility::get_kind(module_accessor)); + } + // Print Status: + if print_status { + print!( + "FIGHTER_STATUS: {}, ", + StatusModule::status_kind(module_accessor) + ); + } + + // Print Work Ints: + for work_int_pair in work_int_pairs { + print!( + "{}: {}, ", + work_int_pair.0, + WorkModule::get_int(module_accessor, work_int_pair.1) + ); + } + + // Print Work Floats: + for work_float_pair in work_float_pairs { + print!( + "{}: {}, ", + work_float_pair.0, + WorkModule::get_float(module_accessor, work_float_pair.1) + ); + } + + // Print Work Flags: + for work_flag_pair in work_flag_pairs { + print!( + "{}: {}, ", + work_flag_pair.0, + WorkModule::is_flag(module_accessor, work_flag_pair.1) + ); + } + + // End Line + println!("|"); + } +} diff --git a/src/training/input_record.rs b/src/training/input_record.rs index a831401..fe3c20f 100644 --- a/src/training/input_record.rs +++ b/src/training/input_record.rs @@ -366,6 +366,7 @@ pub unsafe fn playback_ledge(slot: Option) { pub unsafe fn stop_playback() { INPUT_RECORD = None; INPUT_RECORD_FRAME = 0; + POSSESSION = Player; } pub unsafe fn is_input_neutral(input_frame: usize) -> bool { diff --git a/src/training/mod.rs b/src/training/mod.rs index b3c5621..39d0bc8 100644 --- a/src/training/mod.rs +++ b/src/training/mod.rs @@ -7,7 +7,7 @@ use crate::common::{ use crate::hitbox_visualizer; use crate::input::*; use crate::logging::*; -use crate::training::character_specific::{items, pikmin}; +use crate::training::character_specific::{items, pikmin, ptrainer}; use skyline::hooks::{getRegionAddress, InlineCtx, Region}; use skyline::nn::ro::LookupSymbol; use smash::app::{self, enSEType, lua_bind::*, utility}; @@ -31,7 +31,7 @@ pub mod ui; mod air_dodge_direction; mod attack_angle; -mod character_specific; +pub mod character_specific; mod fast_fall; mod full_hop; pub mod input_delay; @@ -42,6 +42,9 @@ mod reset; pub mod save_states; mod shield_tilt; +#[cfg(debug_assertions)] +mod debug; + #[skyline::hook(replace = WorkModule::get_param_float)] pub unsafe fn handle_get_param_float( module_accessor: &mut app::BattleObjectModuleAccessor, @@ -279,7 +282,7 @@ pub unsafe fn handle_change_motion( motion_kind }; - original!()( + let ori = original!()( module_accessor, mod_motion_kind, unk1, @@ -288,7 +291,12 @@ pub unsafe fn handle_change_motion( unk4, unk5, unk6, - ) + ); + // After we've changed motion, speed up if necessary + if is_training_mode() { + ptrainer::change_motion(module_accessor, motion_kind); + } + ori } #[skyline::hook(replace = WorkModule::is_enable_transition_term)] @@ -494,7 +502,7 @@ static PLAY_SE_OFFSET: usize = 0x04cf6a0; #[skyline::hook(offset = PLAY_SE_OFFSET)] pub unsafe fn handle_fighter_play_se( sound_module: *mut FighterSoundModule, // pointer to fighter's SoundModule - my_hash: Hash40, + mut my_hash: Hash40, bool1: bool, bool2: bool, bool3: bool, @@ -504,49 +512,30 @@ pub unsafe fn handle_fighter_play_se( if !is_training_mode() { return original!()(sound_module, my_hash, bool1, bool2, bool3, bool4, se_type); } - // Supress Buff Sound Effects while buffing if buff::is_buffing_any() { - let silent_hash = Hash40::new("se_silent"); - return original!()( - sound_module, - silent_hash, - bool1, - bool2, - bool3, - bool4, - se_type, - ); + my_hash = Hash40::new("se_silent"); } - // Supress Kirby Copy Ability SFX when loading Save State if my_hash.hash == 0x1453dd86e4 || my_hash.hash == 0x14bdd3e7c8 { let module_accessor = (*sound_module).owner; if StatusModule::status_kind(module_accessor) != FIGHTER_KIRBY_STATUS_KIND_SPECIAL_N_DRINK { - let silent_hash = Hash40::new("se_silent"); - return original!()( - sound_module, - silent_hash, - bool1, - bool2, - bool3, - bool4, - se_type, - ); + my_hash = Hash40::new("se_silent"); } } + my_hash = ptrainer::handle_pokemon_sound_effect(my_hash); original!()(sound_module, my_hash, bool1, bool2, bool3, bool4, se_type) } static FOLLOW_REQ_OFFSET: usize = 0x044f860; #[skyline::hook(offset = FOLLOW_REQ_OFFSET)] // hooked to prevent score gfx from playing when loading save states pub unsafe fn handle_effect_follow( - module_accessor: &mut app::BattleObjectModuleAccessor, + effect_module: *mut FighterEffectModule, eff_hash: Hash40, eff_hash2: Hash40, pos: *const Vector3f, rot: *const Vector3f, - size: f32, + mut size: f32, arg5: bool, arg6: u32, arg7: i32, @@ -558,7 +547,7 @@ pub unsafe fn handle_effect_follow( ) -> u64 { if !is_training_mode() { return original!()( - module_accessor, + effect_module, eff_hash, eff_hash2, pos, @@ -576,25 +565,10 @@ pub unsafe fn handle_effect_follow( } // Prevent the score GFX from playing on the CPU when loading save state during hitstop if eff_hash == Hash40::new("sys_score_aura") && save_states::is_loading() { - return original!()( - module_accessor, - eff_hash, - eff_hash2, - pos, - rot, - 0.0, - arg5, - arg6, - arg7, - arg8, - arg9, - arg10, - arg11, - arg12, - ); + size = 0.0 } original!()( - module_accessor, + effect_module, eff_hash, eff_hash2, pos, @@ -611,6 +585,100 @@ pub unsafe fn handle_effect_follow( ) } +pub struct FighterEffectModule { + _table: u64, + owner: *mut app::BattleObjectModuleAccessor, +} + +static EFFECT_REQ_OFFSET: usize = 0x44de50; +#[skyline::hook(offset = EFFECT_REQ_OFFSET)] // hooked to prevent death gfx from playing when loading save states +pub unsafe fn handle_fighter_effect( + effect_module: *mut FighterEffectModule, // pointer to effect module + eff_hash: Hash40, + pos: *const Vector3f, + rot: *const Vector3f, + mut size: f32, + arg6: u32, + arg7: i32, + arg8: bool, + arg9: i32, +) -> u64 { + if !is_training_mode() { + return original!()( + effect_module, + eff_hash, + pos, + rot, + size, + arg6, + arg7, + arg8, + arg9, + ); + } + size = ptrainer::handle_pokemon_effect(&mut *(*effect_module).owner, eff_hash, size); + original!()( + effect_module, + eff_hash, + pos, + rot, + size, + arg6, + arg7, + arg8, + arg9, + ) +} + +static JOINT_EFFECT_REQ_OFFSET: usize = 0x44e1e0; +#[skyline::hook(offset = JOINT_EFFECT_REQ_OFFSET)] // hooked to prevent death gfx from playing when loading save states +pub unsafe fn handle_fighter_joint_effect( + effect_module: *mut FighterEffectModule, // pointer to effect module + eff_hash: Hash40, + joint_hash: Hash40, + pos: *const Vector3f, + rot: *const Vector3f, + mut size: f32, + pos2: *const Vector3f, //unk, maybe displacement and not pos/rot + rot2: *const Vector3f, //unk, ^ + arg5: bool, + arg6: u32, + arg7: i32, + arg9: i32, +) -> u64 { + if !is_training_mode() { + return original!()( + effect_module, + eff_hash, + joint_hash, + pos, + rot, + size, + pos2, + rot2, + arg5, + arg6, + arg7, + arg9, + ); + } + size = ptrainer::handle_pokemon_effect(&mut *(*effect_module).owner, eff_hash, size); + original!()( + effect_module, + eff_hash, + joint_hash, + pos, + rot, + size, + pos2, + rot2, + arg5, + arg6, + arg7, + arg9, + ) +} + #[skyline::hook(replace = EffectModule::req)] // hooked to prevent death gfx from playing when loading save states pub unsafe fn handle_effect( module_accessor: &mut app::BattleObjectModuleAccessor, @@ -773,15 +841,6 @@ unsafe fn handle_final_input_mapping( input_record::handle_final_input_mapping(player_idx, out); } -static BOMA_OFFSET: usize = 0x15cf1b0; - -#[skyline::hook(offset = BOMA_OFFSET)] -pub unsafe fn handle_get_module_accessor( - battle_object_id: u32, -) -> *mut app::BattleObjectModuleAccessor { - original!()(battle_object_id) -} - pub fn training_mods() { info!("Applying training mods."); @@ -863,11 +922,16 @@ pub fn training_mods() { handle_final_input_mapping, // Charge handle_article_get_int, - handle_get_module_accessor, + handle_fighter_effect, + handle_fighter_joint_effect, ); items::init(); input_record::init(); ui::init(); pikmin::init(); + ptrainer::init(); + + #[cfg(debug_assertions)] + debug::init(); } diff --git a/src/training/save_states.rs b/src/training/save_states.rs index 4eb46d1..2207fb5 100644 --- a/src/training/save_states.rs +++ b/src/training/save_states.rs @@ -3,11 +3,12 @@ use std::collections::HashMap; use log::info; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; -use smash::app::{self, lua_bind::*, Item}; +use smash::app::{self, lua_bind::*, ArticleOperationTarget, Item}; use smash::cpp::l2c_value::LuaConst; use smash::hash40; use smash::lib::lua_const::*; use smash::phx::{Hash40, Vector3f}; +use std::ptr; use training_mod_consts::{CharacterItem, SaveDamage}; use SaveState::*; @@ -22,11 +23,12 @@ use crate::common::consts::RecordTrigger; use crate::common::consts::SaveStateMirroring; //TODO: Cleanup above use crate::common::consts::SAVE_STATES_TOML_PATH; +use crate::common::get_module_accessor; use crate::common::is_dead; use crate::common::MENU; use crate::is_operation_cpu; use crate::training::buff; -use crate::training::character_specific::steve; +use crate::training::character_specific::{ptrainer, steve}; use crate::training::charge::{self, ChargeState}; use crate::training::input_record; use crate::training::items::apply_item; @@ -69,6 +71,7 @@ pub enum SaveState { PosMove, NanaPosMove, ApplyBuff, + WaitForPokemonSwitch, } #[derive(Serialize, Deserialize, Copy, Clone, Debug)] @@ -174,6 +177,20 @@ unsafe fn save_state_cpu(slot: usize) -> &'static mut SavedState { &mut (*SAVE_STATE_SLOTS.data_ptr()).cpu[slot] } +pub unsafe fn get_state_pokemon( + ptrainer_module_accessor: *mut app::BattleObjectModuleAccessor, +) -> u32 { + let selected_slot = get_slot(); + let pokemon_module_accessor = ptrainer::get_pokemon_module_accessor(ptrainer_module_accessor); + let cpu_module_accessor = get_module_accessor(FighterId::CPU); + let fighter_kind = if !ptr::eq(pokemon_module_accessor, cpu_module_accessor) { + save_state_player(selected_slot).fighter_kind + } else { + save_state_cpu(selected_slot).fighter_kind + }; + (fighter_kind - *FIGHTER_KIND_PZENIGAME) as u32 +} + // MIRROR_STATE == 1 -> Do not mirror // MIRROR_STATE == -1 -> Do Mirror static mut MIRROR_STATE: f32 = 1.0; @@ -287,49 +304,31 @@ fn set_damage(module_accessor: &mut app::BattleObjectModuleAccessor, damage: f32 } } -unsafe fn get_ptrainer_module_accessor( - module_accessor: &mut app::BattleObjectModuleAccessor, -) -> &mut app::BattleObjectModuleAccessor { - let ptrainer_object_id = - LinkModule::get_parent_object_id(module_accessor, *FIGHTER_POKEMON_LINK_NO_PTRAINER); - &mut *app::sv_battle_object::module_accessor(ptrainer_object_id as u32) -} - unsafe fn on_ptrainer_death(module_accessor: &mut app::BattleObjectModuleAccessor) { if !is_ptrainer(module_accessor) { return; } + let ptrainer_module_accessor = ptrainer::get_ptrainer_module_accessor(module_accessor); WorkModule::off_flag( - get_ptrainer_module_accessor(module_accessor), + ptrainer_module_accessor, *WEAPON_PTRAINER_PTRAINER_INSTANCE_WORK_ID_FLAG_ENABLE_CHANGE_POKEMON, ); - let ptrainer_module_accessor = get_ptrainer_module_accessor(module_accessor); MotionModule::set_rate(ptrainer_module_accessor, 1000.0); - if ArticleModule::is_exist( - ptrainer_module_accessor, - *WEAPON_PTRAINER_PTRAINER_GENERATE_ARTICLE_MBALL, - ) { - let ptrainer_masterball: *mut app::Article = ArticleModule::get_article( - ptrainer_module_accessor, - *WEAPON_PTRAINER_PTRAINER_GENERATE_ARTICLE_MBALL, - ); - let ptrainer_masterball_id = Article::get_battle_object_id(ptrainer_masterball); - let ptrainer_masterball_module_accessor = - &mut *app::sv_battle_object::module_accessor(ptrainer_masterball_id as u32); + if let Some(ptrainer_masterball_module_accessor) = + ptrainer::get_ptrainer_mball_module_accessor(ptrainer_module_accessor) + { MotionModule::set_rate(ptrainer_masterball_module_accessor, 1000.0); + ArticleModule::set_visibility_whole( + ptrainer::get_ptrainer_module_accessor(module_accessor), + *WEAPON_PTRAINER_PTRAINER_GENERATE_ARTICLE_MBALL, + false, + ArticleOperationTarget(*ARTICLE_OPE_TARGET_ALL), + ); } } -unsafe fn on_death(fighter_kind: i32, module_accessor: &mut app::BattleObjectModuleAccessor) { +pub unsafe fn on_death(fighter_kind: i32, module_accessor: &mut app::BattleObjectModuleAccessor) { SoundModule::stop_all_sound(module_accessor); - // Try moving off-screen so we don't see effects. - let pos = Vector3f { - x: -300.0, - y: -100.0, - z: 0.0, - }; - PostureModule::set_pos(module_accessor, &pos); - // All articles have ID <= 0x25 (0..=0x25) .filter(|article_idx| { @@ -439,7 +438,6 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor) if save_state.state == KillPlayer && !fighter_is_nana { on_ptrainer_death(module_accessor); if !is_dead(module_accessor) { - on_death(fighter_kind, module_accessor); StatusModule::change_status_force(module_accessor, *FIGHTER_STATUS_KIND_DEAD, true); } @@ -594,9 +592,10 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor) } if fighter_is_ptrainer { WorkModule::on_flag( - get_ptrainer_module_accessor(module_accessor), + ptrainer::get_ptrainer_module_accessor(module_accessor), *WEAPON_PTRAINER_PTRAINER_INSTANCE_WORK_ID_FLAG_ENABLE_CHANGE_POKEMON, ); + save_state.state = WaitForPokemonSwitch; } } @@ -636,6 +635,13 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor) } } + // If we switched last frame, artifacts and sound should be cleaned up, so transition into NoAction + if save_state.state == WaitForPokemonSwitch + && ptrainer::is_switched(ptrainer::get_ptrainer_module_accessor(module_accessor)) + { + save_state.state = NoAction; + } + // Save state if button_config::combo_passes(button_config::ButtonCombo::SaveState) { // Don't begin saving state if Nana's delayed input is captured