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,