From 720f904985ad00a605ccc09e7c97394c2bd42913 Mon Sep 17 00:00:00 2001 From: GradualSyrup <68757075+GradualSyrup@users.noreply.github.com> Date: Mon, 28 Feb 2022 22:47:12 -0600 Subject: [PATCH] Special charge (#303) * Mario, Samus, Sheik, GnW, Wii Fit, Hero. Wario incomplete. * Wario, Mewtwo, Plant * Rob, Lucario, Squirtle * Steve, Sora, Incin attempted, all easy ones done * Steve! * Donkey Kong * Most flashes, Samus/Gunner effects * All except Sheik and DK * Sheik and DK * Hero Fix * First Cleanup * Cleanup 2 * Remove Steve * Cleanup with labelling and Pac-Man improvement * Add charge from problematic merge * ChargeState struct added, all getters converted * All charges implemented, matching with Sheik, slight cleanup * charge getter cleanup * setter syntax sugar * more getter cleanup --- README.md | 2 +- src/training/charge.rs | 532 +++++++++++++++++ src/training/mod.rs | 1071 ++++++++++++++++++----------------- src/training/save_states.rs | 28 +- 4 files changed, 1090 insertions(+), 543 deletions(-) create mode 100644 src/training/charge.rs diff --git a/README.md b/README.md index 1e88157..ea78219 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Set stage hazards on or off in Training Mode! Use this to practice on tournament ## Save States -At any time in Training Mode, you can press `Grab + Down Taunt` to save the state of training mode. This will save the position, state, and damage of each fighter, which can then be reverted to at any time with `Grab + Up Taunt`. With the mirroring setting,loading the save state will flip the positions, allowing you to practice your skills facing both directions. Use this instead of the built-in training mode reset! +At any time in Training Mode, you can press `Grab + Down Taunt` to save the state of training mode. This will save the position, state, and damage of each fighter, which can then be reverted to at any time with `Grab + Up Taunt`. With the mirroring setting, loading the save state will flip the positions, allowing you to practice your skills facing both directions. Use this instead of the built-in training mode reset! diff --git a/src/training/charge.rs b/src/training/charge.rs new file mode 100644 index 0000000..d3c4fd7 --- /dev/null +++ b/src/training/charge.rs @@ -0,0 +1,532 @@ +use smash::app::{self, lua_bind::*, ArticleOperationTarget, FighterUtil, FighterFacial}; +use smash::lib::lua_const::*; +use smash::phx::{Hash40, Vector3f}; + +#[derive(Copy, Clone)] +pub struct ChargeState { + pub int_x: Option, + pub int_y: Option, + pub float_x: Option, + pub float_y: Option, + pub float_z: Option, + pub has_charge: Option +} + +impl ChargeState { + fn int_x(mut self, int_x: i32) -> Self { + self.int_x = Some(int_x); + self + } + + fn int_y(mut self, int_y: i32) -> Self { + self.int_y = Some(int_y); + self + } + + fn float_x(mut self, float_x: f32) -> Self { + self.float_x = Some(float_x); + self + } + + fn float_y(mut self, float_y: f32) -> Self { + self.float_y = Some(float_y); + self + } + + fn float_z(mut self, float_z: f32) -> Self { + self.float_z = Some(float_z); + self + } + + fn has_charge(mut self, has_charge: bool) -> Self { + self.has_charge = Some(has_charge); + self + } + +} + +impl Default for ChargeState { + fn default() -> Self { + Self { + int_x: None, + int_y: None, + float_x: None, + float_y: None, + float_z: None, + has_charge: None + } + } +} + +pub unsafe fn get_charge(module_accessor: &mut app::BattleObjectModuleAccessor, fighter_kind: i32) -> ChargeState { + let charge_state = ChargeState::default(); + // Mario FLUDD + if fighter_kind == FIGHTER_KIND_MARIO { + let my_charge = WorkModule::get_int(module_accessor, *FIGHTER_MARIO_INSTANCE_WORK_ID_INT_SPECIAL_LW_CHARGE); + charge_state.int_x(my_charge) + } + + // Donkey Kong Giant Punch + else if fighter_kind == FIGHTER_KIND_DONKEY { + let my_charge = WorkModule::get_int(module_accessor, *FIGHTER_DONKEY_INSTANCE_WORK_ID_INT_SPECIAL_N_COUNT); + charge_state.int_x(my_charge) + } + + // Samus/Dark Samus Charge Shot + else if fighter_kind == FIGHTER_KIND_SAMUS || fighter_kind == FIGHTER_KIND_SAMUSD { + let my_charge = WorkModule::get_int(module_accessor, *FIGHTER_SAMUS_INSTANCE_WORK_ID_INT_SPECIAL_N_COUNT); + charge_state.int_x(my_charge) + } + + // Sheik Needles + else if fighter_kind == FIGHTER_KIND_SHEIK { + let my_charge = WorkModule::get_int(module_accessor, *FIGHTER_SHEIK_INSTANCE_WORK_ID_INT_NEEDLE_COUNT); + charge_state.int_x(my_charge) + } + + // Mewtwo Shadowball + else if fighter_kind == FIGHTER_KIND_MEWTWO { + let my_charge = WorkModule::get_int(module_accessor, *FIGHTER_MEWTWO_INSTANCE_WORK_ID_INT_SHADOWBALL_CHARGE_FRAME); + let prev_frame = WorkModule::get_int(module_accessor, *FIGHTER_MEWTWO_INSTANCE_WORK_ID_INT_PREV_SHADOWBALL_CHARGE_FRAME); + let ball_had = WorkModule::is_flag(module_accessor, *FIGHTER_MEWTWO_INSTANCE_WORK_ID_FLAG_SHADOWBALL_HAD); + charge_state.int_x(my_charge).int_y(prev_frame).has_charge(ball_had) + } + + // Game and Watch Bucket + else if fighter_kind == FIGHTER_KIND_GAMEWATCH { + let my_charge = WorkModule::get_float(module_accessor, *FIGHTER_GAMEWATCH_INSTANCE_WORK_ID_FLOAT_SPECIAL_LW_GAUGE); + let my_attack = WorkModule::get_float(module_accessor, *FIGHTER_GAMEWATCH_INSTANCE_WORK_ID_FLOAT_SPECIAL_LW_ATTACK); + charge_state.float_x(my_charge).float_y(my_attack) + } + + // Wario Waft + else if fighter_kind == FIGHTER_KIND_WARIO { + let my_charge = WorkModule::get_int(module_accessor, 0x100000BF); // FIGHTER_WARIO_INSTANCE_WORK_ID_INT_GASS_COUNT + charge_state.int_x(my_charge) + } + + // Squirtle Water Gun + else if fighter_kind == FIGHTER_KIND_PZENIGAME { + let my_charge = WorkModule::get_int(module_accessor, *FIGHTER_PZENIGAME_INSTANCE_WORK_ID_INT_SPECIAL_N_CHARGE); + charge_state.int_x(my_charge) + } + + // Lucario Aura Sphere + else if fighter_kind == FIGHTER_KIND_LUCARIO { + let my_charge = WorkModule::get_int(module_accessor, *FIGHTER_LUCARIO_INSTANCE_WORK_ID_INT_AURABALL_CHARGE_FRAME); + let prev_frame = WorkModule::get_int(module_accessor, *FIGHTER_LUCARIO_INSTANCE_WORK_ID_INT_PREV_AURABALL_CHARGE_FRAME); + let ball_had = WorkModule::is_flag(module_accessor, *FIGHTER_LUCARIO_INSTANCE_WORK_ID_FLAG_AURABALL_HAD); + charge_state.int_x(my_charge).int_y(prev_frame).has_charge(ball_had) + } + + // ROB Gyro/Laser/Fuel + else if fighter_kind == FIGHTER_KIND_ROBOT { + let laser_charge = WorkModule::get_float(module_accessor, *FIGHTER_ROBOT_INSTANCE_WORK_ID_FLOAT_BEAM_ENERGY_VALUE); + let gyro_charge = WorkModule::get_float(module_accessor, *FIGHTER_ROBOT_INSTANCE_WORK_ID_FLOAT_GYRO_CHARGE_VALUE); + let fuel_charge = WorkModule::get_float(module_accessor, *FIGHTER_ROBOT_INSTANCE_WORK_ID_FLOAT_BURNER_ENERGY_VALUE); + charge_state.float_x(laser_charge).float_y(gyro_charge).float_z(fuel_charge) + } + + // Wii Fit Sun Salutation + else if fighter_kind == FIGHTER_KIND_WIIFIT { + let my_charge = WorkModule::get_float(module_accessor, *FIGHTER_WIIFIT_INSTANCE_WORK_ID_FLOAT_SPECIAL_N_CHARGE_LEVEL_RATIO); + charge_state.float_x(my_charge) + } + + // Pac-Man Bonus Fruit + else if fighter_kind == FIGHTER_KIND_PACMAN { + let my_charge = WorkModule::get_int(module_accessor, 0x100000C1); // FIGHTER_PACMAN_INSTANCE_WORK_ID_INT_SPECIAL_N_CHARGE_RANK + let max_have = WorkModule::is_flag(module_accessor, *FIGHTER_PACMAN_INSTANCE_WORK_ID_FLAG_SPECIAL_N_MAX_HAVE_ITEM); + charge_state.int_x(my_charge).has_charge(max_have) + } + + // Robin Thunder Tome Spells + else if fighter_kind == FIGHTER_KIND_REFLET { + let my_charge = WorkModule::get_int(module_accessor, *FIGHTER_REFLET_INSTANCE_WORK_ID_INT_SPECIAL_N_THUNDER_KIND); + charge_state.int_x(my_charge) + } + + // Plant Poison Breath + else if fighter_kind == FIGHTER_KIND_PACKUN { + let my_charge = WorkModule::get_int(module_accessor, *FIGHTER_PACKUN_INSTANCE_WORK_ID_INT_SPECIAL_S_COUNT); + charge_state.int_x(my_charge) + } + + // Hero (Ka)frizz(le) + else if fighter_kind == FIGHTER_KIND_BRAVE { + let my_charge = WorkModule::get_int(module_accessor, *FIGHTER_BRAVE_INSTANCE_WORK_ID_INT_SPECIAL_N_HOLD_FRAME); + charge_state.int_x(my_charge) + } + + // Banjo Wonderwing + else if fighter_kind == FIGHTER_KIND_BUDDY { + let my_charge = WorkModule::get_int(module_accessor, *FIGHTER_BUDDY_INSTANCE_WORK_ID_INT_SPECIAL_S_REMAIN); + charge_state.int_x(my_charge) + } + + // Mii Gunner Charge Blast + else if fighter_kind == FIGHTER_KIND_MIIGUNNER { + let my_charge = WorkModule::get_int(module_accessor, *FIGHTER_MIIGUNNER_INSTANCE_WORK_ID_INT_GUNNER_CHARGE_COUNT); + charge_state.int_x(my_charge) + } else { + charge_state + } +} + +pub unsafe fn handle_charge(module_accessor: &mut app::BattleObjectModuleAccessor, fighter_kind: i32, charge: ChargeState) { + // Mario Fludd - 0 to 80 + if fighter_kind == FIGHTER_KIND_MARIO { + charge.int_x.map(|fludd_charge| { + WorkModule::set_int(module_accessor, fludd_charge, *FIGHTER_MARIO_INSTANCE_WORK_ID_INT_SPECIAL_LW_CHARGE); + if fludd_charge == 80 { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + } + }); + } + + // DK Punch - 0 to 110 + else if fighter_kind == FIGHTER_KIND_DONKEY { + charge.int_x.map(|punch_charge| { + WorkModule::set_int(module_accessor, punch_charge, *FIGHTER_DONKEY_INSTANCE_WORK_ID_INT_SPECIAL_N_COUNT); + if punch_charge == 110 { + FighterUtil::set_face_motion_by_priority(module_accessor,FighterFacial(*FIGHTER_FACIAL_SPECIAL), Hash40::new("special_n_max_face")); + } + }); + } + + // Samus/Dark Samus Charge Shot - 0 to 112 + else if fighter_kind == FIGHTER_KIND_SAMUS || fighter_kind == FIGHTER_KIND_SAMUSD { + charge.int_x.map(|shot_charge| { + WorkModule::set_int(module_accessor, shot_charge, *FIGHTER_SAMUS_INSTANCE_WORK_ID_INT_SPECIAL_N_COUNT); + if shot_charge == 112 { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + let samus_cshot_hash; + if fighter_kind == FIGHTER_KIND_SAMUS { + samus_cshot_hash = Hash40::new("samus_cshot_max"); + } else { + samus_cshot_hash = Hash40::new("samusd_cshot_max"); + } + let joint_hash = Hash40::new("armr"); + let pos = Vector3f { + x: 7.98004, + y: -0.50584, + z: -0.25092, + }; + let rot = Vector3f { + x: -91.2728, + y: -1.7974, + z: 176.373, + }; + let efh = EffectModule::req_follow( + module_accessor, samus_cshot_hash, + joint_hash, &pos, &rot, 1.0, false, + 0, 0, 0, 0, 0, + false, false + ); + WorkModule::set_int(module_accessor, efh as i32, *FIGHTER_SAMUS_INSTANCE_WORK_ID_INT_EFH_CHARGE_MAX); + } + }); + } + + // Sheik Needles - 0 to 6 + else if fighter_kind == FIGHTER_KIND_SHEIK { + charge.int_x.map(|needle_charge| { + WorkModule::set_int(module_accessor, needle_charge, *FIGHTER_SHEIK_INSTANCE_WORK_ID_INT_NEEDLE_COUNT); + ArticleModule::generate_article_enable(module_accessor,*FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, false, -1); + let hash_main = Hash40::new("set_main"); + match needle_charge { + 6 => { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + ArticleModule::set_visibility(module_accessor,*FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, + hash_main, Hash40::new("group_main_default"),ArticleOperationTarget(0)); + } + 5 => { + ArticleModule::set_visibility(module_accessor,*FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, + hash_main, Hash40::new("group_main_5"),ArticleOperationTarget(0)); + } + 4 => { + ArticleModule::set_visibility(module_accessor,*FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, + hash_main, Hash40::new("group_main_4"),ArticleOperationTarget(0)); + } + 3 => { + ArticleModule::set_visibility(module_accessor,*FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, + hash_main, Hash40::new("group_main_3"),ArticleOperationTarget(0)); + } + 2 => { + ArticleModule::set_visibility(module_accessor,*FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, + hash_main, Hash40::new("group_main_2"),ArticleOperationTarget(0)); + } + 1 => { + ArticleModule::set_visibility(module_accessor,*FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, + hash_main, Hash40::new("group_main_1"),ArticleOperationTarget(0)); + } + _ => { + ArticleModule::set_visibility(module_accessor,*FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, + hash_main, Hash40::new("group_main_0"),ArticleOperationTarget(0)); + } + + } + + }); + } + + // Mewtwo Shadowball - 0 to 120, Boolean + else if fighter_kind == FIGHTER_KIND_MEWTWO { + charge.int_x.map(|charge_frame| { + WorkModule::set_int(module_accessor, charge_frame, *FIGHTER_MEWTWO_INSTANCE_WORK_ID_INT_SHADOWBALL_CHARGE_FRAME); + }); + charge.int_y.map(|prev_frame| { + WorkModule::set_int(module_accessor, prev_frame, *FIGHTER_MEWTWO_INSTANCE_WORK_ID_INT_PREV_SHADOWBALL_CHARGE_FRAME); + if prev_frame == 120 { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + let effect_hash = Hash40::new("mewtwo_shadowball_max_hand"); + let joint_hash_1 = Hash40::new("handl"); + let joint_hash_2 = Hash40::new("handr"); + let pos = Vector3f { + x: 1.0, + y: 0.5, + z: 0.0, + }; + let rot = Vector3f { + x: 0.0, + y: 0.0, + z: 0.0, + }; + let efh_1 = EffectModule::req_follow( + module_accessor, effect_hash, + joint_hash_1, &pos, &rot, 1.0, false, + 0, 0, -1, 0, 0, + false, false + ); + let efh_2 = EffectModule::req_follow( + module_accessor, effect_hash, + joint_hash_2, &pos, &rot, 1.0, false, + 0, 0, -1, 0, 0, + false, false + ); + WorkModule::set_int(module_accessor, efh_1 as i32, *FIGHTER_MEWTWO_INSTANCE_WORK_ID_INT_EF_ID_SHADOWBALL_MAX_L); + WorkModule::set_int(module_accessor, efh_2 as i32, *FIGHTER_MEWTWO_INSTANCE_WORK_ID_INT_EF_ID_SHADOWBALL_MAX_R); + } + }); + charge.has_charge.map(|has_shadowball| { + WorkModule::set_flag(module_accessor, has_shadowball, *FIGHTER_MEWTWO_INSTANCE_WORK_ID_FLAG_SHADOWBALL_HAD); + }); + } + + // GnW Bucket - 0 to 3, Attack not tested + else if fighter_kind == FIGHTER_KIND_GAMEWATCH { + charge.float_x.map(|bucket_level| { + WorkModule::set_float(module_accessor, bucket_level, *FIGHTER_GAMEWATCH_INSTANCE_WORK_ID_FLOAT_SPECIAL_LW_GAUGE); + if bucket_level == 3.0 { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + } else { + // GnW flashes when successfully bucketing, and it will persist if state is loaded during that time, so we remove it here + EffectModule::remove_common(module_accessor, Hash40::new("charge_max")); + } + }); + charge.float_y.map(|bucket_attack| { + WorkModule::set_float(module_accessor, bucket_attack, *FIGHTER_GAMEWATCH_INSTANCE_WORK_ID_FLOAT_SPECIAL_LW_ATTACK); + }); + } + + // Wario Waft - 0 to 6000 + else if fighter_kind == FIGHTER_KIND_WARIO { + charge.int_x.map(|waft_count| { + WorkModule::set_int(module_accessor, waft_count, 0x100000BF); // FIGHTER_WARIO_INSTANCE_WORK_ID_INT_GASS_COUNT + }); + } + + // Squirtle Water Gun - 0 to 45 + else if fighter_kind == FIGHTER_KIND_PZENIGAME { + charge.int_x.map(|water_charge| { + WorkModule::set_int(module_accessor, water_charge, *FIGHTER_PZENIGAME_INSTANCE_WORK_ID_INT_SPECIAL_N_CHARGE); + if water_charge == 45 { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + } + }); + } + + // Lucario Aura Sphere - 0 to 90, Boolean + else if fighter_kind == FIGHTER_KIND_LUCARIO { + charge.int_x.map(|charge_frame| { + WorkModule::set_int(module_accessor, charge_frame, *FIGHTER_LUCARIO_INSTANCE_WORK_ID_INT_AURABALL_CHARGE_FRAME); + }); + charge.int_y.map(|prev_frame| { + WorkModule::set_int(module_accessor, prev_frame, *FIGHTER_LUCARIO_INSTANCE_WORK_ID_INT_PREV_AURABALL_CHARGE_FRAME); + if prev_frame == 90 { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + let effect_hash_1 = Hash40::new("lucario_hadoudan_max_l"); + let effect_hash_2 = Hash40::new("lucario_hadoudan_max_r"); + let joint_hash_1 = Hash40::new("handl"); + let joint_hash_2 = Hash40::new("handr"); + let pos = Vector3f { + x: 0.0, + y: 0.0, + z: 0.0, + }; + let rot = Vector3f { + x: 0.0, + y: 0.0, + z: 0.0, + }; + let efh_1 = EffectModule::req_follow( + module_accessor, effect_hash_1, + joint_hash_1, &pos, &rot, 1.0, false, + 0, 0, -1, 0, 0, + false, false + ); + let efh_2 = EffectModule::req_follow( + module_accessor, effect_hash_2, + joint_hash_2, &pos, &rot, 1.0, false, + 0, 0, -1, 0, 0, + false, false + ); + WorkModule::set_int(module_accessor, efh_1 as i32, *FIGHTER_LUCARIO_INSTANCE_WORK_ID_INT_EF_ID_AURABALL_MAX_L); + WorkModule::set_int(module_accessor, efh_2 as i32, *FIGHTER_LUCARIO_INSTANCE_WORK_ID_INT_EF_ID_AURABALL_MAX_R); + } + }); + charge.has_charge.map(|has_aurasphere| { + WorkModule::set_flag(module_accessor, has_aurasphere, *FIGHTER_LUCARIO_INSTANCE_WORK_ID_FLAG_AURABALL_HAD); + }); + } + + // ROB Gyro/Laser/Fuel - Gyro from 0 to 90, rest unchecked + else if fighter_kind == FIGHTER_KIND_ROBOT { + charge.float_x.map(|beam_energy| { + WorkModule::set_float(module_accessor, beam_energy, *FIGHTER_ROBOT_INSTANCE_WORK_ID_FLOAT_BEAM_ENERGY_VALUE); + }); + charge.float_y.map(|gyro_charge| { + WorkModule::set_float(module_accessor, gyro_charge, *FIGHTER_ROBOT_INSTANCE_WORK_ID_FLOAT_GYRO_CHARGE_VALUE); + if gyro_charge == 90.0 { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + } + }); + charge.float_z.map(|burner_energy| { + WorkModule::set_float(module_accessor, burner_energy, *FIGHTER_ROBOT_INSTANCE_WORK_ID_FLOAT_BURNER_ENERGY_VALUE); + }); + } + + // Wii Fit Sun Salutation - 0 to 1 + else if fighter_kind == FIGHTER_KIND_WIIFIT { + charge.float_x.map(|sun_ratio| { + WorkModule::set_float(module_accessor, sun_ratio, *FIGHTER_WIIFIT_INSTANCE_WORK_ID_FLOAT_SPECIAL_N_CHARGE_LEVEL_RATIO) + }); + } + + // Pac-Man Bonus Fruit - 0 to 12 + else if fighter_kind == FIGHTER_KIND_PACMAN { + charge.int_x.map(|charge_rank| { + WorkModule::set_int(module_accessor, charge_rank, 0x100000C1); // FIGHTER_PACMAN_INSTANCE_WORK_ID_INT_SPECIAL_N_CHARGE_RANK + + if charge_rank == 12 { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + } + }); + charge.has_charge.map(|has_fruit| { + WorkModule::set_flag(module_accessor, has_fruit, *FIGHTER_PACMAN_INSTANCE_WORK_ID_FLAG_SPECIAL_N_MAX_HAVE_ITEM); + }); + } + + // Robin Thunder Tome Spells - 0 to 3 + else if fighter_kind == FIGHTER_KIND_REFLET { + charge.int_x.map(|thunder_kind| { + WorkModule::set_int(module_accessor, thunder_kind, *FIGHTER_REFLET_INSTANCE_WORK_ID_INT_SPECIAL_N_THUNDER_KIND); + if thunder_kind == 3 { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + let reflet_hash = Hash40::new("reflet_thunder_max"); + let joint_hash = Hash40::new("handl"); + let pos = Vector3f { + x: 1.0, + y: 2.0, + z: 0.0, + }; + let rot = Vector3f { + x: 0.0, + y: 0.0, + z: 0.0, + }; + EffectModule::req_follow( + module_accessor, reflet_hash, + joint_hash, &pos, &rot, 1.0, false, + 0, 0, -1, 0, 0, + false, false + ); + } + }); + } + + // Mii Gunner Charge Blast - 0 to 120 + else if fighter_kind == FIGHTER_KIND_MIIGUNNER { + charge.int_x.map(|blast_charge| { + WorkModule::set_int(module_accessor, blast_charge, *FIGHTER_MIIGUNNER_INSTANCE_WORK_ID_INT_GUNNER_CHARGE_COUNT); + if blast_charge == 120 { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + let gunner_hash = Hash40::new("miigunner_cshot_max"); + let joint_hash = Hash40::new("armr"); + let pos = Vector3f { + x: 6.0, + y: 0.0, + z: 0.0, + }; + let rot = Vector3f { + x: 0.0, + y: 0.0, + z: 0.0, + }; + let efh = EffectModule::req_follow( + module_accessor, gunner_hash, + joint_hash, &pos, &rot, 1.0, false, + 0, 0, 0, 0, 0, + false, false + ); + WorkModule::set_int(module_accessor, efh as i32, *FIGHTER_MIIGUNNER_INSTANCE_WORK_ID_INT_EFH_CHARGE_MAX); + } + }); + } + + // Plant Poison - 0 to 75 + else if fighter_kind == FIGHTER_KIND_PACKUN { + charge.int_x.map(|poison_count| { + WorkModule::set_int(module_accessor, poison_count, *FIGHTER_PACKUN_INSTANCE_WORK_ID_INT_SPECIAL_S_COUNT); + if poison_count == 75 { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + let plant_hash = Hash40::new("packun_poison_max_smoke"); + let joint_hash = Hash40::new("hip"); + let pos = Vector3f { + x: 0.0, + y: 0.0, + z: 0.0, + }; + let rot = Vector3f { + x: 0.0, + y: 0.0, + z: 0.0, + }; + let efh = EffectModule::req_follow( + module_accessor, plant_hash, + joint_hash, &pos, &rot, 1.0, false, + 32768, 0, -1, 0, 0, + false, false + ); + WorkModule::set_int(module_accessor, efh as i32, *FIGHTER_PACKUN_INSTANCE_WORK_ID_INT_SPECIAL_S_CHARGE_MAX_EFFECT_HANDLE); + } + }); + } + + // Hero (Ka)frizz(le) - 0 to 81 + else if fighter_kind == FIGHTER_KIND_BRAVE { + WorkModule::off_flag(module_accessor, 0x200000E8); // FIGHTER_BRAVE_INSTANCE_WORK_ID_FLAG_SPECIAL_N_MAX_EFFECT + charge.int_x.map(|frizz_charge| { + WorkModule::set_int(module_accessor, frizz_charge, *FIGHTER_BRAVE_INSTANCE_WORK_ID_INT_SPECIAL_N_HOLD_FRAME); + }); + } + + // Banjo Wonderwing - 0 to 5 + else if fighter_kind == FIGHTER_KIND_BUDDY { + charge.int_x.map(|wing_remain| { + WorkModule::set_int(module_accessor, wing_remain, *FIGHTER_BUDDY_INSTANCE_WORK_ID_INT_SPECIAL_S_REMAIN); + }); + } + + return; +} diff --git a/src/training/mod.rs b/src/training/mod.rs index 8bda33e..3c21b76 100644 --- a/src/training/mod.rs +++ b/src/training/mod.rs @@ -1,535 +1,536 @@ -use crate::common::{is_training_mode, menu, FIGHTER_MANAGER_ADDR, STAGE_MANAGER_ADDR}; -use crate::hitbox_visualizer; -use skyline::hooks::{getRegionAddress, InlineCtx, Region}; -use skyline::nn::hid::*; -use skyline::nn::ro::LookupSymbol; -use smash::app::{self, enSEType, lua_bind::*}; -use smash::lib::lua_const::*; -use smash::params::*; -use smash::phx::{Hash40, Vector3f}; - -pub mod buff; -pub mod combo; -pub mod directional_influence; -pub mod frame_counter; -pub mod ledge; -pub mod sdi; -pub mod shield; -pub mod tech; -pub mod throw; - -mod air_dodge_direction; -mod attack_angle; -mod character_specific; -mod fast_fall; -mod full_hop; -mod input_delay; -mod input_record; -mod mash; -mod reset; -mod save_states; -mod shield_tilt; - -#[skyline::hook(replace = WorkModule::get_param_float)] -pub unsafe fn handle_get_param_float( - module_accessor: &mut app::BattleObjectModuleAccessor, - param_type: u64, - param_hash: u64, -) -> f32 { - let ori = original!()(module_accessor, param_type, param_hash); - if !is_training_mode() { - return ori; - } - - shield::get_param_float(module_accessor, param_type, param_hash).unwrap_or(ori) -} - -#[skyline::hook(replace = WorkModule::get_param_int)] -pub unsafe fn handle_get_param_int( - module_accessor: &mut app::BattleObjectModuleAccessor, - param_type: u64, - param_hash: u64, -) -> i32 { - let ori = original!()(module_accessor, param_type, param_hash); - - if !is_training_mode() { - return ori; - } - - save_states::get_param_int(module_accessor, param_type, param_hash).unwrap_or(ori) -} - -#[skyline::hook(replace = ControlModule::get_attack_air_kind)] -pub unsafe fn handle_get_attack_air_kind( - module_accessor: &mut app::BattleObjectModuleAccessor, -) -> i32 { - let ori = original!()(module_accessor); - if !is_training_mode() { - return ori; - } - - mash::get_attack_air_kind(module_accessor).unwrap_or(ori) -} - -#[skyline::hook(replace = ControlModule::get_command_flag_cat)] -pub unsafe fn handle_get_command_flag_cat( - module_accessor: &mut app::BattleObjectModuleAccessor, - category: i32, -) -> i32 { - let mut flag = original!()(module_accessor, category); - - if category == FIGHTER_PAD_COMMAND_CATEGORY1 { - shield::param_installer(); - } - - if !is_training_mode() { - return flag; - } - - flag |= mash::get_command_flag_cat(module_accessor, category); - // Get throw directions - flag |= throw::get_command_flag_throw_direction(module_accessor); - - once_per_frame_per_fighter(module_accessor, category); - - flag -} - -fn once_per_frame_per_fighter( - module_accessor: &mut app::BattleObjectModuleAccessor, - category: i32, -) { - if category != FIGHTER_PAD_COMMAND_CATEGORY1 { - return; - } - - unsafe { - if crate::common::menu::menu_condition(module_accessor) { - crate::common::menu::spawn_menu(); - } - - input_record::get_command_flag_cat(module_accessor); - combo::get_command_flag_cat(module_accessor); - hitbox_visualizer::get_command_flag_cat(module_accessor); - save_states::save_states(module_accessor); - tech::get_command_flag_cat(module_accessor); - } - - fast_fall::get_command_flag_cat(module_accessor); - frame_counter::get_command_flag_cat(module_accessor); - ledge::get_command_flag_cat(module_accessor); - shield::get_command_flag_cat(module_accessor); - directional_influence::get_command_flag_cat(module_accessor); - reset::check_reset(module_accessor); -} - -/** - * This is called to get the stick position when - * shielding (shield tilt) - * 1 is fully right, -1 is fully left - */ -#[skyline::hook(replace = ControlModule::get_stick_x_no_clamp)] -pub unsafe fn get_stick_x_no_clamp(module_accessor: &mut app::BattleObjectModuleAccessor) -> f32 { - let ori = original!()(module_accessor); - if !is_training_mode() { - return ori; - } - - shield_tilt::mod_get_stick_x(module_accessor).unwrap_or(ori) -} - -/** - * This is called to get the stick position when - * shielding (shield tilt) - * 1 is fully up, -1 is fully down - */ -#[skyline::hook(replace = ControlModule::get_stick_y_no_clamp)] -pub unsafe fn get_stick_y_no_clamp(module_accessor: &mut app::BattleObjectModuleAccessor) -> f32 { - let ori = original!()(module_accessor); - if !is_training_mode() { - return ori; - } - - shield_tilt::mod_get_stick_y(module_accessor).unwrap_or(ori) -} - -/** - * Called when: - * Walking in the facing direction - * Air Dodging - */ -#[skyline::hook(replace = ControlModule::get_stick_x)] -pub unsafe fn get_stick_x(module_accessor: &mut app::BattleObjectModuleAccessor) -> f32 { - let ori = original!()(module_accessor); - if !is_training_mode() { - return ori; - } - - air_dodge_direction::mod_get_stick_x(module_accessor).unwrap_or(ori) -} - -/** - * Called when: - * angled ftilt/fsmash - */ -#[skyline::hook(replace = ControlModule::get_stick_dir)] -pub unsafe fn get_stick_dir(module_accessor: &mut app::BattleObjectModuleAccessor) -> f32 { - let ori = original!()(module_accessor); - if !is_training_mode() { - return ori; - } - - attack_angle::mod_get_stick_dir(module_accessor).unwrap_or(ori) -} - -/** - * - */ -#[skyline::hook(replace = ControlModule::get_stick_y)] -pub unsafe fn get_stick_y(module_accessor: &mut app::BattleObjectModuleAccessor) -> f32 { - let ori = original!()(module_accessor); - if !is_training_mode() { - return ori; - } - - air_dodge_direction::mod_get_stick_y(module_accessor).unwrap_or(ori) -} - -#[skyline::hook(replace = ControlModule::check_button_on)] -pub unsafe fn handle_check_button_on( - module_accessor: &mut app::BattleObjectModuleAccessor, - button: i32, -) -> bool { - let ori = original!()(module_accessor, button); - if !is_training_mode() { - return ori; - } - - shield::check_button_on(module_accessor, button) - .unwrap_or_else(|| full_hop::check_button_on(module_accessor, button).unwrap_or(ori)) -} - -#[skyline::hook(replace = ControlModule::check_button_off)] -pub unsafe fn handle_check_button_off( - module_accessor: &mut app::BattleObjectModuleAccessor, - button: i32, -) -> bool { - let ori = original!()(module_accessor, button); - if !is_training_mode() { - return ori; - } - - shield::check_button_off(module_accessor, button) - .unwrap_or_else(|| full_hop::check_button_off(module_accessor, button).unwrap_or(ori)) -} - -#[skyline::hook(replace = MotionModule::change_motion)] -pub unsafe fn handle_change_motion( - module_accessor: &mut app::BattleObjectModuleAccessor, - motion_kind: u64, - unk1: f32, - unk2: f32, - unk3: bool, - unk4: f32, - unk5: bool, - unk6: bool, -) -> u64 { - let mod_motion_kind = if is_training_mode() { - tech::change_motion(module_accessor, motion_kind).unwrap_or(motion_kind) - } else { - motion_kind - }; - - original!()( - module_accessor, - mod_motion_kind, - unk1, - unk2, - unk3, - unk4, - unk5, - unk6, - ) -} - -#[skyline::hook(replace = WorkModule::is_enable_transition_term)] -pub unsafe fn handle_is_enable_transition_term( - module_accessor: *mut app::BattleObjectModuleAccessor, - transition_term: i32, -) -> bool { - let ori = original!()(module_accessor, transition_term); - - if !is_training_mode() { - return ori; - } - - combo::is_enable_transition_term(module_accessor, transition_term, ori); - match ledge::is_enable_transition_term(module_accessor, transition_term) { - Some(r) => r, - None => ori, - } -} - -extern "C" { - #[link_name = "\u{1}_ZN3app15sv_fighter_util15set_dead_rumbleEP9lua_State"] - pub fn set_dead_rumble(lua_state: u64) -> u64; -} - -#[skyline::hook(replace = set_dead_rumble)] -pub unsafe fn handle_set_dead_rumble(lua_state: u64) -> u64 { - if is_training_mode() { - return 0; - } - - original!()(lua_state) -} - -#[skyline::hook(replace = CameraModule::req_quake)] -pub unsafe fn handle_req_quake( - module_accessor: &mut app::BattleObjectModuleAccessor, - my_int: i32, -) -> u64 { - if !is_training_mode() { - return original!()(module_accessor, my_int); - } - if save_states::is_killing() { - return original!()(module_accessor, *CAMERA_QUAKE_KIND_NONE); - } - original!()(module_accessor, my_int) -} - -pub static mut COMMON_PARAMS: *mut CommonParams = 0 as *mut _; - -fn params_main(params_info: &ParamsInfo<'_>) { - if let Ok(common) = params_info.get::() { - unsafe { - COMMON_PARAMS = common as *mut _; - } - } -} - -static CLOUD_ADD_LIMIT_OFFSET: usize = 0x008dc140; // this function is used to add limit to Cloud's limit gauge. Hooking it here so we can call it in buff.rs -#[skyline::hook(offset = CLOUD_ADD_LIMIT_OFFSET)] -pub unsafe fn handle_add_limit( - add_limit: f32, - module_accessor: &mut app::BattleObjectModuleAccessor, - is_special_lw: u64, -) { - original!()(add_limit, module_accessor, is_special_lw) -} - -#[skyline::hook(replace = EffectModule::req_screen)] // hooked to prevent the screen from darkening when loading a save state with One-Winged Angel -pub unsafe fn handle_req_screen( - module_accessor: &mut app::BattleObjectModuleAccessor, - my_hash: Hash40, - bool_1: bool, - bool_2: bool, - bool_3: bool, -) -> u64 { - if !is_training_mode() { - return original!()(module_accessor, my_hash, bool_1, bool_2, bool_3); - } - let new_hash = my_hash.hash; - if new_hash == 72422354958 && buff::is_buffing(module_accessor) { - // Wing bg hash - let replace_hash = Hash40::new("bg"); - return original!()(module_accessor, replace_hash, bool_1, bool_2, bool_3); - } - original!()(module_accessor, my_hash, bool_1, bool_2, bool_3) -} - -#[skyline::hook(replace = app::FighterSpecializer_Jack::check_doyle_summon_dispatch)] // returns status of summon dispatch if triggered, -1 as u64 otherwise -pub unsafe fn handle_check_doyle_summon_dispatch( - module_accessor: &mut app::BattleObjectModuleAccessor, - bool_1: bool, - bool_2: bool, -) -> u64 { - let ori = original!()(module_accessor, bool_1, bool_2); - if !is_training_mode() { - return ori; - } - if ori == *FIGHTER_JACK_STATUS_KIND_SUMMON as u64 { - if buff::is_buffing(module_accessor) { - return 4294967295; - } - } - return ori; -} - -// Set Stale Moves to On -static STALE_OFFSET: usize = 0x013e88a4; -// One instruction after stale moves toggle register is set to 0 -#[skyline::hook(offset=STALE_OFFSET, inline)] -unsafe fn stale_handle(ctx: &mut InlineCtx) { - let x22 = ctx.registers[22].x.as_mut(); - let training_structure_address = (*x22 + 0xb60) as *mut u8; - *training_structure_address = 1; -} - -// Set Stale Moves to On in the menu text -static STALE_MENU_OFFSET: usize = 0x013e88a0; -// One instruction after menu text register is set to off -#[skyline::hook(offset=STALE_MENU_OFFSET, inline)] -unsafe fn stale_menu_handle(ctx: &mut InlineCtx) { - // Set the text pointer to where "mel_training_on" is located - let on_text_ptr = ((getRegionAddress(Region::Text) as u64) + (0x42b215e as u64)) as u64; - let x1 = ctx.registers[1].x.as_mut(); - *x1 = on_text_ptr; -} - -#[skyline::hook(replace = SoundModule::play_se)] // hooked to prevent death sfx from playing when loading save states -pub unsafe fn handle_se( - module_accessor: &mut app::BattleObjectModuleAccessor, - my_hash: Hash40, - bool1: bool, - bool2: bool, - bool3: bool, - bool4: bool, - se_type: enSEType, -) -> u64 { - // Make effects silent while we're killing fighters. Stops death explosion and fighter misfoot. - if save_states::is_killing() { - let silent_hash = Hash40::new("se_silent"); - return original!()( - module_accessor, - silent_hash, - bool1, - bool2, - bool3, - bool4, - se_type, - ); - } - original!()( - module_accessor, - my_hash, - bool1, - bool2, - bool3, - bool4, - se_type, - ) -} - -#[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, - eff_hash: Hash40, - pos: *const Vector3f, - rot: *const Vector3f, - size: f32, - arg6: u32, - arg7: i32, - arg8: bool, - arg9: i32, -) -> u64 { - if save_states::is_killing() { - // Making the size 0 prevents these effects from being displayed. Fixs throw explosions, ICs squall, etc. - return original!()( - module_accessor, - eff_hash, - pos, - rot, - 0.0, - arg6, - arg7, - arg8, - arg9, - ); - } - original!()( - module_accessor, - eff_hash, - pos, - rot, - size, - arg6, - arg7, - arg8, - arg9, - ) -} - -#[allow(improper_ctypes)] -extern "C" { - fn add_nn_hid_hook(callback: fn(*mut NpadGcState, *const u32)); -} - -pub fn training_mods() { - println!("[Training Modpack] Applying training mods."); - - // Input Recording/Delay - unsafe { - if (add_nn_hid_hook as *const ()).is_null() { - panic!("The NN-HID hook plugin could not be found and is required to add NRO hooks. Make sure libnn_hid_hook.nro is installed."); - } - add_nn_hid_hook(input_delay::handle_get_npad_state); - } - - unsafe { - LookupSymbol( - &mut FIGHTER_MANAGER_ADDR, - "_ZN3lib9SingletonIN3app14FighterManagerEE9instance_E\u{0}" - .as_bytes() - .as_ptr(), - ); - - LookupSymbol( - &mut STAGE_MANAGER_ADDR, - "_ZN3lib9SingletonIN3app12StageManagerEE9instance_E\u{0}" - .as_bytes() - .as_ptr(), - ); - - smash::params::add_hook(params_main).unwrap(); - } - - skyline::install_hooks!( - // Mash airdodge/jump - handle_get_command_flag_cat, - // Hold/Infinite shield - handle_check_button_on, - handle_check_button_off, - handle_get_param_float, - // Save states - handle_get_param_int, - handle_set_dead_rumble, - handle_req_quake, - // Mash attack - handle_get_attack_air_kind, - // Attack angle - get_stick_dir, - // Tech options - handle_change_motion, - // Directional AirDodge, - get_stick_x, - get_stick_y, - // Shield Tilt - get_stick_x_no_clamp, - get_stick_y_no_clamp, - // Combo - handle_is_enable_transition_term, - // SDI - crate::training::sdi::check_hit_stop_delay_command, - // Buffs - handle_add_limit, - handle_check_doyle_summon_dispatch, - handle_req_screen, - // Stale Moves - stale_handle, - stale_menu_handle, - // Death SFX - handle_se, - // Death GFX - handle_effect, - ); - - combo::init(); - shield::init(); - fast_fall::init(); - mash::init(); - ledge::init(); - throw::init(); - menu::init(); - buff::init(); -} +use crate::common::{is_training_mode, menu, FIGHTER_MANAGER_ADDR, STAGE_MANAGER_ADDR}; +use crate::hitbox_visualizer; +use skyline::hooks::{getRegionAddress, InlineCtx, Region}; +use skyline::nn::hid::*; +use skyline::nn::ro::LookupSymbol; +use smash::app::{self, enSEType, lua_bind::*}; +use smash::lib::lua_const::*; +use smash::params::*; +use smash::phx::{Hash40, Vector3f}; + +pub mod buff; +pub mod combo; +pub mod directional_influence; +pub mod frame_counter; +pub mod ledge; +pub mod sdi; +pub mod shield; +pub mod tech; +pub mod throw; +pub mod charge; + +mod air_dodge_direction; +mod attack_angle; +mod character_specific; +mod fast_fall; +mod full_hop; +mod input_delay; +mod input_record; +mod mash; +mod reset; +mod save_states; +mod shield_tilt; + +#[skyline::hook(replace = WorkModule::get_param_float)] +pub unsafe fn handle_get_param_float( + module_accessor: &mut app::BattleObjectModuleAccessor, + param_type: u64, + param_hash: u64, +) -> f32 { + let ori = original!()(module_accessor, param_type, param_hash); + if !is_training_mode() { + return ori; + } + + shield::get_param_float(module_accessor, param_type, param_hash).unwrap_or(ori) +} + +#[skyline::hook(replace = WorkModule::get_param_int)] +pub unsafe fn handle_get_param_int( + module_accessor: &mut app::BattleObjectModuleAccessor, + param_type: u64, + param_hash: u64, +) -> i32 { + let ori = original!()(module_accessor, param_type, param_hash); + + if !is_training_mode() { + return ori; + } + + save_states::get_param_int(module_accessor, param_type, param_hash).unwrap_or(ori) +} + +#[skyline::hook(replace = ControlModule::get_attack_air_kind)] +pub unsafe fn handle_get_attack_air_kind( + module_accessor: &mut app::BattleObjectModuleAccessor, +) -> i32 { + let ori = original!()(module_accessor); + if !is_training_mode() { + return ori; + } + + mash::get_attack_air_kind(module_accessor).unwrap_or(ori) +} + +#[skyline::hook(replace = ControlModule::get_command_flag_cat)] +pub unsafe fn handle_get_command_flag_cat( + module_accessor: &mut app::BattleObjectModuleAccessor, + category: i32, +) -> i32 { + let mut flag = original!()(module_accessor, category); + + if category == FIGHTER_PAD_COMMAND_CATEGORY1 { + shield::param_installer(); + } + + if !is_training_mode() { + return flag; + } + + flag |= mash::get_command_flag_cat(module_accessor, category); + // Get throw directions + flag |= throw::get_command_flag_throw_direction(module_accessor); + + once_per_frame_per_fighter(module_accessor, category); + + flag +} + +fn once_per_frame_per_fighter( + module_accessor: &mut app::BattleObjectModuleAccessor, + category: i32, +) { + if category != FIGHTER_PAD_COMMAND_CATEGORY1 { + return; + } + + unsafe { + if crate::common::menu::menu_condition(module_accessor) { + crate::common::menu::spawn_menu(); + } + + input_record::get_command_flag_cat(module_accessor); + combo::get_command_flag_cat(module_accessor); + hitbox_visualizer::get_command_flag_cat(module_accessor); + save_states::save_states(module_accessor); + tech::get_command_flag_cat(module_accessor); + } + + fast_fall::get_command_flag_cat(module_accessor); + frame_counter::get_command_flag_cat(module_accessor); + ledge::get_command_flag_cat(module_accessor); + shield::get_command_flag_cat(module_accessor); + directional_influence::get_command_flag_cat(module_accessor); + reset::check_reset(module_accessor); +} + +/** + * This is called to get the stick position when + * shielding (shield tilt) + * 1 is fully right, -1 is fully left + */ +#[skyline::hook(replace = ControlModule::get_stick_x_no_clamp)] +pub unsafe fn get_stick_x_no_clamp(module_accessor: &mut app::BattleObjectModuleAccessor) -> f32 { + let ori = original!()(module_accessor); + if !is_training_mode() { + return ori; + } + + shield_tilt::mod_get_stick_x(module_accessor).unwrap_or(ori) +} + +/** + * This is called to get the stick position when + * shielding (shield tilt) + * 1 is fully up, -1 is fully down + */ +#[skyline::hook(replace = ControlModule::get_stick_y_no_clamp)] +pub unsafe fn get_stick_y_no_clamp(module_accessor: &mut app::BattleObjectModuleAccessor) -> f32 { + let ori = original!()(module_accessor); + if !is_training_mode() { + return ori; + } + + shield_tilt::mod_get_stick_y(module_accessor).unwrap_or(ori) +} + +/** + * Called when: + * Walking in the facing direction + * Air Dodging + */ +#[skyline::hook(replace = ControlModule::get_stick_x)] +pub unsafe fn get_stick_x(module_accessor: &mut app::BattleObjectModuleAccessor) -> f32 { + let ori = original!()(module_accessor); + if !is_training_mode() { + return ori; + } + + air_dodge_direction::mod_get_stick_x(module_accessor).unwrap_or(ori) +} + +/** + * Called when: + * angled ftilt/fsmash + */ +#[skyline::hook(replace = ControlModule::get_stick_dir)] +pub unsafe fn get_stick_dir(module_accessor: &mut app::BattleObjectModuleAccessor) -> f32 { + let ori = original!()(module_accessor); + if !is_training_mode() { + return ori; + } + + attack_angle::mod_get_stick_dir(module_accessor).unwrap_or(ori) +} + +/** + * + */ +#[skyline::hook(replace = ControlModule::get_stick_y)] +pub unsafe fn get_stick_y(module_accessor: &mut app::BattleObjectModuleAccessor) -> f32 { + let ori = original!()(module_accessor); + if !is_training_mode() { + return ori; + } + + air_dodge_direction::mod_get_stick_y(module_accessor).unwrap_or(ori) +} + +#[skyline::hook(replace = ControlModule::check_button_on)] +pub unsafe fn handle_check_button_on( + module_accessor: &mut app::BattleObjectModuleAccessor, + button: i32, +) -> bool { + let ori = original!()(module_accessor, button); + if !is_training_mode() { + return ori; + } + + shield::check_button_on(module_accessor, button) + .unwrap_or_else(|| full_hop::check_button_on(module_accessor, button).unwrap_or(ori)) +} + +#[skyline::hook(replace = ControlModule::check_button_off)] +pub unsafe fn handle_check_button_off( + module_accessor: &mut app::BattleObjectModuleAccessor, + button: i32, +) -> bool { + let ori = original!()(module_accessor, button); + if !is_training_mode() { + return ori; + } + + shield::check_button_off(module_accessor, button) + .unwrap_or_else(|| full_hop::check_button_off(module_accessor, button).unwrap_or(ori)) +} + +#[skyline::hook(replace = MotionModule::change_motion)] +pub unsafe fn handle_change_motion( + module_accessor: &mut app::BattleObjectModuleAccessor, + motion_kind: u64, + unk1: f32, + unk2: f32, + unk3: bool, + unk4: f32, + unk5: bool, + unk6: bool, +) -> u64 { + let mod_motion_kind = if is_training_mode() { + tech::change_motion(module_accessor, motion_kind).unwrap_or(motion_kind) + } else { + motion_kind + }; + + original!()( + module_accessor, + mod_motion_kind, + unk1, + unk2, + unk3, + unk4, + unk5, + unk6, + ) +} + +#[skyline::hook(replace = WorkModule::is_enable_transition_term)] +pub unsafe fn handle_is_enable_transition_term( + module_accessor: *mut app::BattleObjectModuleAccessor, + transition_term: i32, +) -> bool { + let ori = original!()(module_accessor, transition_term); + + if !is_training_mode() { + return ori; + } + + combo::is_enable_transition_term(module_accessor, transition_term, ori); + match ledge::is_enable_transition_term(module_accessor, transition_term) { + Some(r) => r, + None => ori, + } +} + +extern "C" { + #[link_name = "\u{1}_ZN3app15sv_fighter_util15set_dead_rumbleEP9lua_State"] + pub fn set_dead_rumble(lua_state: u64) -> u64; +} + +#[skyline::hook(replace = set_dead_rumble)] +pub unsafe fn handle_set_dead_rumble(lua_state: u64) -> u64 { + if is_training_mode() { + return 0; + } + + original!()(lua_state) +} + +#[skyline::hook(replace = CameraModule::req_quake)] +pub unsafe fn handle_req_quake( + module_accessor: &mut app::BattleObjectModuleAccessor, + my_int: i32, +) -> u64 { + if !is_training_mode() { + return original!()(module_accessor, my_int); + } + if save_states::is_killing() { + return original!()(module_accessor, *CAMERA_QUAKE_KIND_NONE); + } + original!()(module_accessor, my_int) +} + +pub static mut COMMON_PARAMS: *mut CommonParams = 0 as *mut _; + +fn params_main(params_info: &ParamsInfo<'_>) { + if let Ok(common) = params_info.get::() { + unsafe { + COMMON_PARAMS = common as *mut _; + } + } +} + +static CLOUD_ADD_LIMIT_OFFSET: usize = 0x008dc140; // this function is used to add limit to Cloud's limit gauge. Hooking it here so we can call it in buff.rs +#[skyline::hook(offset = CLOUD_ADD_LIMIT_OFFSET)] +pub unsafe fn handle_add_limit( + add_limit: f32, + module_accessor: &mut app::BattleObjectModuleAccessor, + is_special_lw: u64, +) { + original!()(add_limit, module_accessor, is_special_lw) +} + +#[skyline::hook(replace = EffectModule::req_screen)] // hooked to prevent the screen from darkening when loading a save state with One-Winged Angel +pub unsafe fn handle_req_screen( + module_accessor: &mut app::BattleObjectModuleAccessor, + my_hash: Hash40, + bool_1: bool, + bool_2: bool, + bool_3: bool, +) -> u64 { + if !is_training_mode() { + return original!()(module_accessor, my_hash, bool_1, bool_2, bool_3); + } + let new_hash = my_hash.hash; + if new_hash == 72422354958 && buff::is_buffing(module_accessor) { + // Wing bg hash + let replace_hash = Hash40::new("bg"); + return original!()(module_accessor, replace_hash, bool_1, bool_2, bool_3); + } + original!()(module_accessor, my_hash, bool_1, bool_2, bool_3) +} + +#[skyline::hook(replace = app::FighterSpecializer_Jack::check_doyle_summon_dispatch)] // returns status of summon dispatch if triggered, -1 as u64 otherwise +pub unsafe fn handle_check_doyle_summon_dispatch( + module_accessor: &mut app::BattleObjectModuleAccessor, + bool_1: bool, + bool_2: bool, +) -> u64 { + let ori = original!()(module_accessor, bool_1, bool_2); + if !is_training_mode() { + return ori; + } + if ori == *FIGHTER_JACK_STATUS_KIND_SUMMON as u64 { + if buff::is_buffing(module_accessor) { + return 4294967295; + } + } + return ori; +} + +// Set Stale Moves to On +static STALE_OFFSET: usize = 0x013e88a4; +// One instruction after stale moves toggle register is set to 0 +#[skyline::hook(offset=STALE_OFFSET, inline)] +unsafe fn stale_handle(ctx: &mut InlineCtx) { + let x22 = ctx.registers[22].x.as_mut(); + let training_structure_address = (*x22 + 0xb60) as *mut u8; + *training_structure_address = 1; +} + +// Set Stale Moves to On in the menu text +static STALE_MENU_OFFSET: usize = 0x013e88a0; +// One instruction after menu text register is set to off +#[skyline::hook(offset=STALE_MENU_OFFSET, inline)] +unsafe fn stale_menu_handle(ctx: &mut InlineCtx) { + // Set the text pointer to where "mel_training_on" is located + let on_text_ptr = ((getRegionAddress(Region::Text) as u64) + (0x42b215e as u64)) as u64; + let x1 = ctx.registers[1].x.as_mut(); + *x1 = on_text_ptr; +} + +#[skyline::hook(replace = SoundModule::play_se)] // hooked to prevent death sfx from playing when loading save states +pub unsafe fn handle_se( + module_accessor: &mut app::BattleObjectModuleAccessor, + my_hash: Hash40, + bool1: bool, + bool2: bool, + bool3: bool, + bool4: bool, + se_type: enSEType, +) -> u64 { + // Make effects silent while we're killing fighters. Stops death explosion and fighter misfoot. + if save_states::is_killing() { + let silent_hash = Hash40::new("se_silent"); + return original!()( + module_accessor, + silent_hash, + bool1, + bool2, + bool3, + bool4, + se_type, + ); + } + original!()( + module_accessor, + my_hash, + bool1, + bool2, + bool3, + bool4, + se_type, + ) +} + +#[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, + eff_hash: Hash40, + pos: *const Vector3f, + rot: *const Vector3f, + size: f32, + arg6: u32, + arg7: i32, + arg8: bool, + arg9: i32, +) -> u64 { + if save_states::is_killing() { + // Making the size 0 prevents these effects from being displayed. Fixs throw explosions, ICs squall, etc. + return original!()( + module_accessor, + eff_hash, + pos, + rot, + 0.0, + arg6, + arg7, + arg8, + arg9, + ); + } + original!()( + module_accessor, + eff_hash, + pos, + rot, + size, + arg6, + arg7, + arg8, + arg9, + ) +} + +#[allow(improper_ctypes)] +extern "C" { + fn add_nn_hid_hook(callback: fn(*mut NpadGcState, *const u32)); +} + +pub fn training_mods() { + println!("[Training Modpack] Applying training mods."); + + // Input Recording/Delay + unsafe { + if (add_nn_hid_hook as *const ()).is_null() { + panic!("The NN-HID hook plugin could not be found and is required to add NRO hooks. Make sure libnn_hid_hook.nro is installed."); + } + add_nn_hid_hook(input_delay::handle_get_npad_state); + } + + unsafe { + LookupSymbol( + &mut FIGHTER_MANAGER_ADDR, + "_ZN3lib9SingletonIN3app14FighterManagerEE9instance_E\u{0}" + .as_bytes() + .as_ptr(), + ); + + LookupSymbol( + &mut STAGE_MANAGER_ADDR, + "_ZN3lib9SingletonIN3app12StageManagerEE9instance_E\u{0}" + .as_bytes() + .as_ptr(), + ); + + smash::params::add_hook(params_main).unwrap(); + } + + skyline::install_hooks!( + // Mash airdodge/jump + handle_get_command_flag_cat, + // Hold/Infinite shield + handle_check_button_on, + handle_check_button_off, + handle_get_param_float, + // Save states + handle_get_param_int, + handle_set_dead_rumble, + handle_req_quake, + // Mash attack + handle_get_attack_air_kind, + // Attack angle + get_stick_dir, + // Tech options + handle_change_motion, + // Directional AirDodge, + get_stick_x, + get_stick_y, + // Shield Tilt + get_stick_x_no_clamp, + get_stick_y_no_clamp, + // Combo + handle_is_enable_transition_term, + // SDI + crate::training::sdi::check_hit_stop_delay_command, + // Buffs + handle_add_limit, + handle_check_doyle_summon_dispatch, + handle_req_screen, + // Stale Moves + stale_handle, + stale_menu_handle, + // Death SFX + handle_se, + // Death GFX + handle_effect, + ); + + combo::init(); + shield::init(); + fast_fall::init(); + mash::init(); + ledge::init(); + throw::init(); + menu::init(); + buff::init(); +} diff --git a/src/training/save_states.rs b/src/training/save_states.rs index c56a897..fb204b5 100644 --- a/src/training/save_states.rs +++ b/src/training/save_states.rs @@ -6,6 +6,9 @@ use crate::common::is_dead; use crate::common::MENU; use crate::training::buff; use crate::training::reset; +//use crate::training::charge; +//use crate::training::charge::ChargeState; +use crate::training::charge::{self, ChargeState}; // is this the same as the above 2 lines? use smash::app::{self, lua_bind::*}; use smash::hash40; use smash::lib::lua_const::*; @@ -29,6 +32,7 @@ struct SavedState { situation_kind: i32, state: SaveState, fighter_kind: i32, + charge: ChargeState, } macro_rules! default_save_state { @@ -41,6 +45,14 @@ macro_rules! default_save_state { situation_kind: 0, state: NoAction, fighter_kind: -1, + charge: ChargeState { + int_x: None, + int_y: None, + float_x: None, + float_y: None, + float_z: None, + has_charge: None + } } }; } @@ -265,9 +277,14 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor) save_state.state = NoAction; } - // if we're done moving, reset percent and apply buffs + // If we're done moving, reset percent, handle charges, and apply buffs if save_state.state == NoAction { set_damage(module_accessor, save_state.percent); + // Set the charge of special moves if the fighter matches the kind in the save state + if save_state.fighter_kind == fighter_kind { + charge::handle_charge(module_accessor, fighter_kind, save_state.charge); + } + // Buff the fighter if they're one of the fighters who can be buffed if fighter_is_buffable { save_state.state = ApplyBuff; } @@ -325,12 +342,9 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor) save_state.lr = PostureModule::lr(module_accessor); save_state.percent = DamageModule::damage(module_accessor, 0); save_state.situation_kind = StatusModule::situation_kind(module_accessor); - if fighter_is_ptrainer { - // Only store the fighter_kind for pokemon trainer - save_state.fighter_kind = app::utility::get_kind(module_accessor); - } else { - save_state.fighter_kind = -1; - } + // Always store fighter kind so that charges are handled properly + save_state.fighter_kind = app::utility::get_kind(module_accessor); + save_state.charge = charge::get_charge(module_accessor, fighter_kind); let zeros = Vector3f { x: 0.0,