1
0
Fork 0
mirror of https://github.com/jugeeya/UltimateTrainingModpack.git synced 2024-11-24 02:44:17 +00:00

Functional PT Savestates and additional debug (#616)

* Add reusable print_fighter_info()

* rustfmt

* Clippy

* starting charge

* Working Pikmin Save States, motion not working, order can be messed up by loading during an aerial, if you save too quickly you lose pikmin in the state

* often failing boid read

* notes

* Prevent Pikmin from being cleaned up by the regular cleanup process

* migration progress

* skyline-smash branch, more pikmin work

* Using buff to spawn pikmin, character specific file for cleanliness

* failed reordering attempt

* more failed ordering

* almost kind of works but out of order

* failed all, need to try individual hold and status change

* hard reorder crash

* battleobject not boma

* comment clarification

* messing around with printing

* printing, about to try to find where autonomy is being set

* solution found, going to hook backshield to find where autonomy is being set to true

* found where to hook, will start hooking

* switched to charge, issues with inline hook

* need to only have autonomy ignored on save state load

* distance and status controls (doesn't work perfectly)

* Christmas Miracle

* Working w/ Cleanup

* training mode check, improved owner check

* Debug for hooking, Working Implementation (No Transform, No Charge)

* Working for Aegis, PT having issues (LRA into state load works, but not regular state load)

* Working ptrainer hats

* debugging ptrainer

* great debug

* Pokemon Swaps to Saved State

* invisible ball, need to silence trainer/ball and hook joint eff

* cpu isn't being saved properly, lra + state load crash fixed

* CPU working, unsure if flag is necessary though

* Visual fixes working, can't load states again if player if trainer, cpu getting stuck?

* working, trainer stuck if no move on state load, switch initialization issue

* PT is saved

* debug cleanup

* rustfmt, still needs clippy and main merge

* clippy fmt

* one more fmt

* prevent flying plate from existing if you load state during death

* debug cleanup

* rustfmt

* cleanup
This commit is contained in:
GradualSyrup 2023-09-01 19:02:02 -05:00 committed by GitHub
parent 7e0617e377
commit 7fa0e933e9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 508 additions and 98 deletions

View file

@ -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 {

View file

@ -3,6 +3,7 @@ use smash::app::{self};
mod bowser;
pub mod items;
pub mod pikmin;
pub mod ptrainer;
pub mod steve;
/**

View file

@ -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<usize> =
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,);
}

174
src/training/debug.rs Normal file
View file

@ -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!("|");
}
}

View file

@ -366,6 +366,7 @@ pub unsafe fn playback_ledge(slot: Option<usize>) {
pub unsafe fn stop_playback() {
INPUT_RECORD = None;
INPUT_RECORD_FRAME = 0;
POSSESSION = Player;
}
pub unsafe fn is_input_neutral(input_frame: usize) -> bool {

View file

@ -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();
}

View file

@ -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