diff --git a/README.md b/README.md index 8a2f6d6..ece45b0 100644 --- a/README.md +++ b/README.md @@ -139,17 +139,16 @@ The CPU can be instructed to perform a wide array of different actions in respon - Ledge: To be performed when hanging on the ledge - Tech: To be performed when slammed into the ground or wall - Miss Tech: To be performed after failing to tech -- Defensive: To be performed after the ledge, tech, or miss tech option The timing of the CPU option can be influenced by the following settings: +- Mash Triggers - Aerial Delay - Ledge Delay - OoS Offset - Reaction Time - Fast Fall Delay - Falling Aerials -- Mash in Neutral ----- @@ -162,6 +161,7 @@ When multiple options are selected, one of the selected options will be chosen a |:---:|:---:|:---|:---| | Mash Settings | Mash Toggles | Actions to be performed as soon as possible out of hitstun or shieldstun | Airdodge, jump, shield, spotdodge, roll in, roll out, aerials, jab, tilts, smash attacks, grab, dash, dash attack | | Mash Settings | Followup Toggles | Actions to be performed after the Mash option | Airdodge, jump, shield, spotdodge, roll in, roll out, aerials, jab, tilts, smash attacks, grab, dash, dash attack | +| Mash Settings | Mash Triggers | Conditions which will cause the CPU to perform their mash action | Hitstun, shieldstun, parry, tumble, landing, ledge trump, footstool, clatter, ledge option, tech option, grounded, airborne, distance: close, distance: mid, distance: far, always | | Mash Settings | Attack Angles | For attacks that can be angled, such as some forward tilts | Neutral, up, down | | Mash Settings | Throw Options | Throw to be performed when a grab is landed | None, Forward Throw, Back Throw, Up Throw, Down Throw | | Mash Settings | Throw Delay | How many frames to delay the throw option | 0 to 150 frames (2.5 seconds) in increments of 5 frames | @@ -173,7 +173,6 @@ When multiple options are selected, one of the selected options will be chosen a | Mash Settings | Fast Fall Delay | How many frames the CPU should delay their fastfall | 0 to 30 frames (0.5 seconds) | | Mash Settings | OoS Offset | How many times the CPU shield can be hit before performing a Mash option | 0 to 30 hits | | Mash Settings | Reaction Time | How many frames to delay before performing an option out of shield | 0 to 30 frames (0.5 seconds) | -| Mash Settings | Mash in Neutral | Should Mash options be performed repeatedly or only when the CPU is hit | Yes, No | | ----- | ----- | ----- | ----- | | Defensive Settings | Airdodge Direction | Direction to angle airdodges | Neutral, out, up-out, up, up-in, in, down-in, down, down-out, left, right | | Defensive Settings | DI Direction | Direction to angle the directional influence during hitlag | Neutral, out, up-out, up, up-in, in, down-in, down, down-out, left, right | @@ -186,7 +185,6 @@ When multiple options are selected, one of the selected options will be chosen a | Defensive Settings | Mistech Options | Actions to take after missing a tech | Neutral getup, getup attack, roll in, roll out | | Defensive Settings | Shield Toggles | CPU Shield Behavior | None, Infinite (no shield damage or decay), Hold (no shield decay until the shield is hit for the first time), Constant (no shield decay) | | Defensive Settings | Shield Tilt | Direction to tilt the shield | Neutral, out, up-out, up, up-in, in, down-in, down, down-out, left, right | -| Defensive Settings | Defensive Options | Actions to take after a ledge option, tech option, or miss tech option | Spotdodge, roll in, roll out, jab, shield | | Defensive Settings | Buff Options | Buff(s) to be applied to respective character when loading save states | Acceleratle, Oomph, Psyche Up, Bounce, Arsene, Deep Breathing, Limit, K.O. Punch, Wing | | Defensive Settings | Character Item | CPU/Player item to hold when loading a save state | None, Player 1st Variation througher 8th variation, CPU 1st variation through 8th variation | | ----- | ----- | ----- | ----- | @@ -228,7 +226,6 @@ SD Card Root │ │ ├── buff_state.svg │ │ ├── check.svg │ │ ├── clatter_strength.svg - │ │ ├── defensive_state.svg │ │ ├── di_state.svg │ │ ├── falling_aerials.svg │ │ ├── fast_fall.svg @@ -239,8 +236,8 @@ SD Card Root │ │ ├── input_delay.svg │ │ ├── ledge_delay.svg │ │ ├── ledge_state.svg - │ │ ├── mash_in_neutral.svg │ │ ├── mash_state.svg + │ │ ├── mash_triggers.svg │ │ ├── miss_tech_state.svg │ │ ├── oos_offset.svg │ │ ├── pummel_delay.svg diff --git a/src/common/mod.rs b/src/common/mod.rs index 853c2aa..c581009 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -7,6 +7,7 @@ pub mod release; use crate::common::consts::*; use smash::app::{self, lua_bind::*}; use smash::lib::lua_const::*; +use smash::hash40; pub use crate::common::consts::MENU; pub static mut DEFAULTS_MENU: TrainingModpackMenu = crate::common::consts::DEFAULTS_MENU; @@ -92,7 +93,7 @@ pub fn is_idle(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { pub fn is_in_hitstun(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { let status_kind = unsafe { StatusModule::status_kind(module_accessor) }; - + // TODO: Should this be *FIGHTER_STATUS_KIND_DAMAGE..*FIGHTER_STATUS_KIND_DAMAGE_AIR ? (*FIGHTER_STATUS_KIND_DAMAGE..*FIGHTER_STATUS_KIND_DAMAGE_FALL).contains(&status_kind) } pub fn is_in_footstool(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { @@ -142,6 +143,30 @@ pub unsafe fn is_in_clatter(module_accessor: &mut app::BattleObjectModuleAccesso ControlModule::get_clatter_time(module_accessor, 0) > 0.0 } +pub unsafe fn is_in_ledgetrump(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { + let status_kind = StatusModule::status_kind(module_accessor); + + status_kind == FIGHTER_STATUS_KIND_CLIFF_ROBBED +} + +pub unsafe fn is_in_parry(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { + let motion_kind = MotionModule::motion_kind(module_accessor); + + motion_kind == hash40("just_shield_off") +} + +pub unsafe fn is_in_tumble(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { + let status_kind = StatusModule::status_kind(module_accessor); + + (*FIGHTER_STATUS_KIND_DAMAGE_FLY..=*FIGHTER_STATUS_KIND_DAMAGE_FALL).contains(&status_kind) +} + +pub unsafe fn is_in_landing(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { + let status_kind = StatusModule::status_kind(module_accessor); + + (*FIGHTER_STATUS_KIND_LANDING..=*FIGHTER_STATUS_KIND_LANDING_LIGHT).contains(&status_kind) +} + // Returns true if a match is currently active pub unsafe fn is_ready_go() -> bool { let fighter_manager = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager); @@ -153,3 +178,18 @@ pub unsafe fn entry_count() -> i32 { let fighter_manager = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager); FighterManager::entry_count(fighter_manager) } + +pub unsafe fn get_fighter_distance() -> f32 { + let player_module_accessor = get_module_accessor(FighterId::Player); + let cpu_module_accessor = get_module_accessor(FighterId::CPU); + let player_pos = *PostureModule::pos(player_module_accessor); + let cpu_pos = *PostureModule::pos(cpu_module_accessor); + app::sv_math::vec3_distance( + player_pos.x, + player_pos.y, + player_pos.z, + cpu_pos.x, + cpu_pos.y, + cpu_pos.z + ) +} \ No newline at end of file diff --git a/src/static/img/defensive_state.svg b/src/static/img/defensive_state.svg deleted file mode 100644 index c7d6903..0000000 --- a/src/static/img/defensive_state.svg +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/static/img/mash_in_neutral.svg b/src/static/img/mash_in_neutral.svg deleted file mode 100644 index 2ec302b..0000000 --- a/src/static/img/mash_in_neutral.svg +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/src/static/img/mash_triggers.svg b/src/static/img/mash_triggers.svg new file mode 100644 index 0000000..0eea891 --- /dev/null +++ b/src/static/img/mash_triggers.svg @@ -0,0 +1,72 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/src/training/clatter.rs b/src/training/clatter.rs index dcba848..322fbfd 100644 --- a/src/training/clatter.rs +++ b/src/training/clatter.rs @@ -1,10 +1,13 @@ -use crate::common::{is_in_clatter, is_operation_cpu, is_training_mode, MENU}; +use crate::common::consts::*; +use crate::common::*; +use crate::training::mash; use smash::app::lua_bind::{ControlModule, EffectModule}; use smash::app::BattleObjectModuleAccessor; use smash::lib::lua_const::*; use smash::phx::{Hash40, Vector3f}; static mut COUNTER: u32 = 0; +static mut WAS_IN_CLATTER_FLAG: bool = false; unsafe fn do_clatter_input(module_accessor: &mut BattleObjectModuleAccessor) { ControlModule::add_clatter_time(module_accessor, -8.0, 0); @@ -36,10 +39,14 @@ pub unsafe fn handle_clatter(module_accessor: &mut BattleObjectModuleAccessor) { if !is_training_mode() || !is_operation_cpu(module_accessor) { return; } - if !is_in_clatter(module_accessor) { + if WAS_IN_CLATTER_FLAG && MENU.mash_triggers.contains(MashTrigger::CLATTER) { + mash::buffer_menu_mash(); + } + WAS_IN_CLATTER_FLAG = false; return; } + WAS_IN_CLATTER_FLAG = true; let repeat = MENU.clatter_strength.into_u32(); COUNTER = (COUNTER + 1) % repeat; diff --git a/src/training/ledge.rs b/src/training/ledge.rs index 96c452e..d4fe384 100644 --- a/src/training/ledge.rs +++ b/src/training/ledge.rs @@ -100,11 +100,8 @@ pub unsafe fn force_option(module_accessor: &mut app::BattleObjectModuleAccessor StatusModule::change_status_request_from_script(module_accessor, status, true); - match LEDGE_CASE { - LedgeOption::JUMP => { - mash::buffer_menu_mash(); - } - _ => mash::perform_defensive_option(), + if MENU.mash_triggers.contains(MashTrigger::LEDGE) { + mash::buffer_menu_mash(); } } diff --git a/src/training/mash.rs b/src/training/mash.rs index d530c2d..62d67dc 100644 --- a/src/training/mash.rs +++ b/src/training/mash.rs @@ -9,6 +9,10 @@ use crate::training::shield; use smash::app::{self, lua_bind::*}; use smash::lib::lua_const::*; +const DISTANCE_CLOSE_THRESHOLD: f32 = 10.0; +const DISTANCE_MID_THRESHOLD: f32 = 50.0; +const DISTANCE_FAR_THRESHOLD: f32 = 100.0; + static mut CURRENT_AERIAL: Action = Action::NAIR; static mut QUEUE: Vec = vec![]; @@ -137,22 +141,30 @@ unsafe fn check_buffer(module_accessor: &mut app::BattleObjectModuleAccessor) { buffer_menu_mash(); } -fn should_buffer(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { - unsafe { - if MENU.mash_in_neutral == OnOff::On { - return true; - } +unsafe fn should_buffer(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { + let fighter_distance = get_fighter_distance(); + if MENU.mash_triggers.contains(MashTrigger::ALWAYS) + || (MENU.mash_triggers.contains(MashTrigger::HIT) && is_in_hitstun(module_accessor)) + // BLOCK handled in shield.rs + || (MENU.mash_triggers.contains(MashTrigger::PARRY) && is_in_parry(module_accessor)) + || (MENU.mash_triggers.contains(MashTrigger::TUMBLE) && is_in_tumble(module_accessor)) + || (MENU.mash_triggers.contains(MashTrigger::LANDING) && is_in_landing(module_accessor)) + || (MENU.mash_triggers.contains(MashTrigger::TRUMP) && is_in_ledgetrump(module_accessor)) + || (MENU.mash_triggers.contains(MashTrigger::FOOTSTOOL) && is_in_footstool(module_accessor)) + // CLATTER handled in clatter.rs + // LEDGE handled in ledge.rs + // TECH handled in tech.rs + // MISTECH handled in tech.rs + || (MENU.mash_triggers.contains(MashTrigger::GROUNDED) && is_grounded(module_accessor)) + || (MENU.mash_triggers.contains(MashTrigger::AIRBORNE) && is_airborne(module_accessor)) + || (MENU.mash_triggers.contains(MashTrigger::DISTANCE_CLOSE) && fighter_distance < DISTANCE_CLOSE_THRESHOLD) + || (MENU.mash_triggers.contains(MashTrigger::DISTANCE_MID) && fighter_distance < DISTANCE_MID_THRESHOLD) + || (MENU.mash_triggers.contains(MashTrigger::DISTANCE_FAR) && fighter_distance < DISTANCE_FAR_THRESHOLD) + { + true + } else { + false } - - if is_in_hitstun(module_accessor) { - return true; - } - - if is_in_footstool(module_accessor) { - return true; - } - - false } // Temp Translation @@ -160,7 +172,6 @@ pub fn buffer_menu_mash() { unsafe { let action = MENU.mash_state.get_random(); buffer_action(action); - full_hop::roll_full_hop(); fast_fall::roll_fast_fall(); FALLING_AERIAL = MENU.falling_aerials.get_random().into_bool(); @@ -169,7 +180,6 @@ pub fn buffer_menu_mash() { unsafe fn perform_action(module_accessor: &mut app::BattleObjectModuleAccessor) -> i32 { let action = get_current_buffer(); - match action { Action::AIR_DODGE => { let (expected_status, command_flag) = if is_grounded(module_accessor) { @@ -481,21 +491,3 @@ fn try_change_status( true } - -pub unsafe fn perform_defensive_option() { - full_reset(); - - let action = match MENU.defensive_state.get_random() { - Defensive::ROLL_F => Action::ROLL_F, - Defensive::ROLL_B => Action::ROLL_B, - Defensive::SPOT_DODGE => Action::SPOT_DODGE, - Defensive::JAB => Action::JAB, - Defensive::SHIELD => Action::SHIELD, - _ => Action::empty(), - }; - - buffer_action(action); - - // Suspend shield hold to allow for other defensive options - shield::suspend_shield(action); -} diff --git a/src/training/mod.rs b/src/training/mod.rs index 2f1217a..edb5c00 100644 --- a/src/training/mod.rs +++ b/src/training/mod.rs @@ -1,5 +1,5 @@ use crate::common::{ - is_training_mode, menu, FIGHTER_MANAGER_ADDR, ITEM_MANAGER_ADDR, STAGE_MANAGER_ADDR, + is_training_mode, menu, FIGHTER_MANAGER_ADDR, ITEM_MANAGER_ADDR, STAGE_MANAGER_ADDR }; use crate::hitbox_visualizer; use crate::training::character_specific::items; diff --git a/src/training/shield.rs b/src/training/shield.rs index 5f751df..18c0a9c 100644 --- a/src/training/shield.rs +++ b/src/training/shield.rs @@ -218,7 +218,9 @@ unsafe fn mod_handle_sub_guard_cont(fighter: &mut L2CFighterCommon) { return; } - mash::buffer_menu_mash(); + if MENU.mash_triggers.contains(MashTrigger::BLOCK) { + mash::buffer_menu_mash(); + } let action = mash::get_current_buffer(); if handle_escape_option(fighter, module_accessor) { diff --git a/src/training/tech.rs b/src/training/tech.rs index fc142aa..a33d49e 100644 --- a/src/training/tech.rs +++ b/src/training/tech.rs @@ -1,278 +1,269 @@ -use crate::common::consts::*; -use crate::common::*; -use crate::training::mash; -use smash::app::sv_system; -use smash::app::{self, lua_bind::*}; -use smash::hash40; -use smash::lib::lua_const::*; -use smash::lib::L2CValue; -use smash::lua2cpp::L2CFighterBase; - -static mut TECH_ROLL_DIRECTION: Direction = Direction::empty(); -static mut MISS_TECH_ROLL_DIRECTION: Direction = Direction::empty(); - -#[skyline::hook(replace = smash::lua2cpp::L2CFighterBase_change_status)] -pub unsafe fn handle_change_status( - fighter: &mut L2CFighterBase, - status_kind: L2CValue, - unk: L2CValue, -) -> L2CValue { - let mut status_kind = status_kind; - let mut unk = unk; - - if is_training_mode() { - mod_handle_change_status(fighter, &mut status_kind, &mut unk); - } - - original!()(fighter, status_kind, unk) -} - -unsafe fn mod_handle_change_status( - fighter: &mut L2CFighterBase, - status_kind: &mut L2CValue, - unk: &mut L2CValue, -) { - let module_accessor = sv_system::battle_object_module_accessor(fighter.lua_state_agent); - if !is_operation_cpu(module_accessor) { - return; - } - - let status_kind_int = status_kind - .try_get_int() - .unwrap_or(*FIGHTER_STATUS_KIND_WAIT as u64) as i32; - - let state: TechFlags = MENU.tech_state.get_random(); - - if handle_grnd_tech(module_accessor, status_kind, unk, status_kind_int, state) { - return; - } - - if handle_wall_tech(module_accessor, status_kind, unk, status_kind_int, state) { - return; - } - - handle_ceil_tech(module_accessor, status_kind, unk, status_kind_int, state); -} -fn handle_grnd_tech( - module_accessor: &mut app::BattleObjectModuleAccessor, - status_kind: &mut L2CValue, - unk: &mut L2CValue, - status_kind_int: i32, - state: TechFlags, -) -> bool { - if status_kind_int != *FIGHTER_STATUS_KIND_DOWN - && status_kind_int != *FIGHTER_STATUS_KIND_DAMAGE_FLY_REFLECT_D - { - return false; - } - - unsafe { - // prev_status_kind(module_accessor, 0) gets the 1st previous status, - // which is FIGHTER_STATUS_KIND_CATCHED_AIR_END_GANON for both aerial/grounded sideb - // prev_status_kind(module_accessor, 1) gets the 2nd previous status, - // which is FIGHTER_STATUS_KIND_CATCHED_GANON for grounded sideb - // and FIGHTER_STATUS_KIND_CATCHED_AIR_GANON for aerial sideb - let second_prev_status = StatusModule::prev_status_kind(module_accessor, 1); - let can_tech = WorkModule::is_enable_transition_term( - module_accessor, - *FIGHTER_STATUS_TRANSITION_TERM_ID_PASSIVE, - ) && (second_prev_status != FIGHTER_STATUS_KIND_CATCHED_AIR_FALL_GANON); - - if !can_tech { - return false; - } - } - - match state { - TechFlags::IN_PLACE => { - *status_kind = FIGHTER_STATUS_KIND_PASSIVE.as_lua_int(); - *unk = LUA_TRUE; - unsafe { - mash::perform_defensive_option(); - } - } - TechFlags::ROLL_F => { - *status_kind = FIGHTER_STATUS_KIND_PASSIVE_FB.as_lua_int(); - *unk = LUA_TRUE; - unsafe { - TECH_ROLL_DIRECTION = Direction::IN; // = In - mash::perform_defensive_option(); - } - } - TechFlags::ROLL_B => { - *status_kind = FIGHTER_STATUS_KIND_PASSIVE_FB.as_lua_int(); - *unk = LUA_TRUE; - unsafe { - TECH_ROLL_DIRECTION = Direction::OUT; // = Away - mash::perform_defensive_option(); - } - } - _ => (), - } - - true -} - -fn handle_wall_tech( - module_accessor: &mut app::BattleObjectModuleAccessor, - status_kind: &mut L2CValue, - unk: &mut L2CValue, - status_kind_int: i32, - state: TechFlags, -) -> bool { - if status_kind_int != *FIGHTER_STATUS_KIND_STOP_WALL - && status_kind_int != *FIGHTER_STATUS_KIND_DAMAGE_FLY_REFLECT_LR - { - return false; - } - - if state == TechFlags::NO_TECH { - return false; - } - - unsafe { - let can_tech = WorkModule::is_enable_transition_term( - module_accessor, - *FIGHTER_STATUS_TRANSITION_TERM_ID_PASSIVE_WALL, - ); - - if !can_tech { - return false; - } - } - - match state { - TechFlags::IN_PLACE => { - *status_kind = FIGHTER_STATUS_KIND_PASSIVE_WALL.as_lua_int(); - *unk = LUA_TRUE; - } - TechFlags::ROLL_F => { - *status_kind = FIGHTER_STATUS_KIND_PASSIVE_WALL_JUMP.as_lua_int(); - *unk = LUA_TRUE; - } - _ => (), - } - - true -} - -fn handle_ceil_tech( - module_accessor: &mut app::BattleObjectModuleAccessor, - status_kind: &mut L2CValue, - unk: &mut L2CValue, - status_kind_int: i32, - state: TechFlags, -) -> bool { - if status_kind_int != *FIGHTER_STATUS_KIND_STOP_CEIL - && status_kind_int != *FIGHTER_STATUS_KIND_DAMAGE_FLY_REFLECT_U - { - return false; - } - - if state == TechFlags::NO_TECH { - return false; - } - - unsafe { - let can_tech = WorkModule::is_enable_transition_term( - module_accessor, - *FIGHTER_STATUS_TRANSITION_TERM_ID_PASSIVE_CEIL, - ); - - if !can_tech { - return false; - } - } - - *status_kind = FIGHTER_STATUS_KIND_PASSIVE_CEIL.as_lua_int(); - *unk = LUA_TRUE; - true -} - -pub unsafe fn get_command_flag_cat(module_accessor: &mut app::BattleObjectModuleAccessor) { - if !is_operation_cpu(module_accessor) { - return; - } - - if MENU.tech_state == TechFlags::empty() { - return; - } - - let status = StatusModule::status_kind(module_accessor) as i32; - - if [ - *FIGHTER_STATUS_KIND_DOWN_WAIT, // Mistech - *FIGHTER_STATUS_KIND_DOWN_WAIT_CONTINUE, // Mistech - *FIGHTER_STATUS_KIND_LAY_DOWN, // Snake down throw - ] - .contains(&status) - { - let status: i32 = match MENU.miss_tech_state.get_random() { - MissTechFlags::GETUP => *FIGHTER_STATUS_KIND_DOWN_STAND, - MissTechFlags::ATTACK => *FIGHTER_STATUS_KIND_DOWN_STAND_ATTACK, - MissTechFlags::ROLL_F => { - MISS_TECH_ROLL_DIRECTION = Direction::IN; // = In - *FIGHTER_STATUS_KIND_DOWN_STAND_FB - } - MissTechFlags::ROLL_B => { - MISS_TECH_ROLL_DIRECTION = Direction::OUT; // = Away - *FIGHTER_STATUS_KIND_DOWN_STAND_FB - } - _ => return, - }; - StatusModule::change_status_request_from_script(module_accessor, status, false); - - mash::perform_defensive_option(); - } else if [ - // Handle slips (like Diddy banana) - *FIGHTER_STATUS_KIND_SLIP_WAIT, - ] - .contains(&status) - { - let status: i32 = match MENU.miss_tech_state.get_random() { - MissTechFlags::GETUP => *FIGHTER_STATUS_KIND_SLIP_STAND, - MissTechFlags::ATTACK => *FIGHTER_STATUS_KIND_SLIP_STAND_ATTACK, - MissTechFlags::ROLL_F => *FIGHTER_STATUS_KIND_SLIP_STAND_F, - MissTechFlags::ROLL_B => *FIGHTER_STATUS_KIND_SLIP_STAND_B, - _ => return, - }; - StatusModule::change_status_request_from_script(module_accessor, status, false); - - mash::perform_defensive_option(); - }; -} - -pub unsafe fn change_motion( - module_accessor: &mut app::BattleObjectModuleAccessor, - motion_kind: u64, -) -> Option { - if !is_operation_cpu(module_accessor) { - return None; - } - - if MENU.tech_state == TechFlags::empty() { - return None; - } - - if [hash40("passive_stand_f"), hash40("passive_stand_b")].contains(&motion_kind) { - if TECH_ROLL_DIRECTION == Direction::IN { - return Some(hash40("passive_stand_f")); - } else { - return Some(hash40("passive_stand_b")); - } - } else if [hash40("down_forward_u"), hash40("down_back_u")].contains(&motion_kind) { - if MISS_TECH_ROLL_DIRECTION == Direction::IN { - return Some(hash40("down_forward_u")); - } else { - return Some(hash40("down_back_u")); - } - } else if [hash40("down_forward_d"), hash40("down_back_d")].contains(&motion_kind) { - if MISS_TECH_ROLL_DIRECTION == Direction::IN { - return Some(hash40("down_forward_d")); - } else { - return Some(hash40("down_back_d")); - } - } - - None -} +use crate::common::consts::*; +use crate::common::*; +use crate::training::mash; +use smash::app::sv_system; +use smash::app::{self, lua_bind::*}; +use smash::hash40; +use smash::lib::lua_const::*; +use smash::lib::L2CValue; +use smash::lua2cpp::L2CFighterBase; + +static mut TECH_ROLL_DIRECTION: Direction = Direction::empty(); +static mut MISS_TECH_ROLL_DIRECTION: Direction = Direction::empty(); + +#[skyline::hook(replace = smash::lua2cpp::L2CFighterBase_change_status)] +pub unsafe fn handle_change_status( + fighter: &mut L2CFighterBase, + status_kind: L2CValue, + unk: L2CValue, +) -> L2CValue { + let mut status_kind = status_kind; + let mut unk = unk; + + if is_training_mode() { + mod_handle_change_status(fighter, &mut status_kind, &mut unk); + } + + original!()(fighter, status_kind, unk) +} + +unsafe fn mod_handle_change_status( + fighter: &mut L2CFighterBase, + status_kind: &mut L2CValue, + unk: &mut L2CValue, +) { + let module_accessor = sv_system::battle_object_module_accessor(fighter.lua_state_agent); + if !is_operation_cpu(module_accessor) { + return; + } + + let status_kind_int = status_kind + .try_get_int() + .unwrap_or(*FIGHTER_STATUS_KIND_WAIT as u64) as i32; + + let state: TechFlags = MENU.tech_state.get_random(); + + if handle_grnd_tech(module_accessor, status_kind, unk, status_kind_int, state) { + return; + } + + if handle_wall_tech(module_accessor, status_kind, unk, status_kind_int, state) { + return; + } + + handle_ceil_tech(module_accessor, status_kind, unk, status_kind_int, state); +} + +unsafe fn handle_grnd_tech( + module_accessor: &mut app::BattleObjectModuleAccessor, + status_kind: &mut L2CValue, + unk: &mut L2CValue, + status_kind_int: i32, + state: TechFlags, +) -> bool { + if status_kind_int != *FIGHTER_STATUS_KIND_DOWN + && status_kind_int != *FIGHTER_STATUS_KIND_DAMAGE_FLY_REFLECT_D + { + return false; + } + + // prev_status_kind(module_accessor, 0) gets the 1st previous status, + // which is FIGHTER_STATUS_KIND_CATCHED_AIR_END_GANON for both aerial/grounded sideb + // prev_status_kind(module_accessor, 1) gets the 2nd previous status, + // which is FIGHTER_STATUS_KIND_CATCHED_GANON for grounded sideb + // and FIGHTER_STATUS_KIND_CATCHED_AIR_GANON for aerial sideb + let second_prev_status = StatusModule::prev_status_kind(module_accessor, 1); + let can_tech = WorkModule::is_enable_transition_term( + module_accessor, + *FIGHTER_STATUS_TRANSITION_TERM_ID_PASSIVE, + ) && (second_prev_status != FIGHTER_STATUS_KIND_CATCHED_AIR_FALL_GANON); + + if !can_tech { + return false; + } + + let do_tech: bool = match state { + TechFlags::IN_PLACE => { + *status_kind = FIGHTER_STATUS_KIND_PASSIVE.as_lua_int(); + *unk = LUA_TRUE; + true + } + TechFlags::ROLL_F => { + *status_kind = FIGHTER_STATUS_KIND_PASSIVE_FB.as_lua_int(); + *unk = LUA_TRUE; + TECH_ROLL_DIRECTION = Direction::IN; // = In + true + } + TechFlags::ROLL_B => { + *status_kind = FIGHTER_STATUS_KIND_PASSIVE_FB.as_lua_int(); + *unk = LUA_TRUE; + TECH_ROLL_DIRECTION = Direction::OUT; // = Away + true + } + _ => false, + }; + if do_tech && MENU.mash_triggers.contains(MashTrigger::TECH) { + mash::buffer_menu_mash(); + } + + true +} + +unsafe fn handle_wall_tech( + module_accessor: &mut app::BattleObjectModuleAccessor, + status_kind: &mut L2CValue, + unk: &mut L2CValue, + status_kind_int: i32, + state: TechFlags, +) -> bool { + let can_tech = WorkModule::is_enable_transition_term( + module_accessor, + *FIGHTER_STATUS_TRANSITION_TERM_ID_PASSIVE_WALL, + ); + + if ![ + *FIGHTER_STATUS_KIND_STOP_WALL, + *FIGHTER_STATUS_KIND_DAMAGE_FLY_REFLECT_LR, + ] + .contains(&status_kind_int) + || state == TechFlags::NO_TECH + || !can_tech + { + return false; + } + + let do_tech: bool = match state { + TechFlags::IN_PLACE => { + *status_kind = FIGHTER_STATUS_KIND_PASSIVE_WALL.as_lua_int(); + *unk = LUA_TRUE; + true + } + TechFlags::ROLL_F => { + *status_kind = FIGHTER_STATUS_KIND_PASSIVE_WALL_JUMP.as_lua_int(); + *unk = LUA_TRUE; + true + } + _ => false, + }; + if do_tech && MENU.mash_triggers.contains(MashTrigger::TECH) { + mash::buffer_menu_mash(); + } + true +} + +unsafe fn handle_ceil_tech( + module_accessor: &mut app::BattleObjectModuleAccessor, + status_kind: &mut L2CValue, + unk: &mut L2CValue, + status_kind_int: i32, + state: TechFlags, +) -> bool { + let can_tech = WorkModule::is_enable_transition_term( + module_accessor, + *FIGHTER_STATUS_TRANSITION_TERM_ID_PASSIVE_CEIL, + ); + + if ![ + *FIGHTER_STATUS_KIND_STOP_CEIL, + *FIGHTER_STATUS_KIND_DAMAGE_FLY_REFLECT_U, + ] + .contains(&status_kind_int) + || state == TechFlags::NO_TECH + || !can_tech + { + return false; + } + + *status_kind = FIGHTER_STATUS_KIND_PASSIVE_CEIL.as_lua_int(); + *unk = LUA_TRUE; + if MENU.mash_triggers.contains(MashTrigger::TECH) { + mash::buffer_menu_mash(); + } + true +} + +pub unsafe fn get_command_flag_cat(module_accessor: &mut app::BattleObjectModuleAccessor) { + if !is_operation_cpu(module_accessor) || MENU.tech_state == TechFlags::empty() { + return; + } + + let status = StatusModule::status_kind(module_accessor) as i32; + + if [ + *FIGHTER_STATUS_KIND_DOWN_WAIT, // Mistech + *FIGHTER_STATUS_KIND_DOWN_WAIT_CONTINUE, // Mistech + *FIGHTER_STATUS_KIND_LAY_DOWN, // Snake down throw + ] + .contains(&status) + { + let status: i32 = match MENU.miss_tech_state.get_random() { + MissTechFlags::GETUP => *FIGHTER_STATUS_KIND_DOWN_STAND, + MissTechFlags::ATTACK => *FIGHTER_STATUS_KIND_DOWN_STAND_ATTACK, + MissTechFlags::ROLL_F => { + MISS_TECH_ROLL_DIRECTION = Direction::IN; // = In + *FIGHTER_STATUS_KIND_DOWN_STAND_FB + } + MissTechFlags::ROLL_B => { + MISS_TECH_ROLL_DIRECTION = Direction::OUT; // = Away + *FIGHTER_STATUS_KIND_DOWN_STAND_FB + } + _ => return, + }; + StatusModule::change_status_request_from_script(module_accessor, status, false); + if MENU.mash_triggers.contains(MashTrigger::MISTECH) { + mash::buffer_menu_mash(); + } + } else if [ + // Handle slips (like Diddy banana) + *FIGHTER_STATUS_KIND_SLIP_WAIT, + ] + .contains(&status) + { + let status: i32 = match MENU.miss_tech_state.get_random() { + MissTechFlags::GETUP => *FIGHTER_STATUS_KIND_SLIP_STAND, + MissTechFlags::ATTACK => *FIGHTER_STATUS_KIND_SLIP_STAND_ATTACK, + MissTechFlags::ROLL_F => *FIGHTER_STATUS_KIND_SLIP_STAND_F, + MissTechFlags::ROLL_B => *FIGHTER_STATUS_KIND_SLIP_STAND_B, + _ => return, + }; + StatusModule::change_status_request_from_script(module_accessor, status, false); + if MENU.mash_triggers.contains(MashTrigger::MISTECH) { + mash::buffer_menu_mash(); + } + }; +} + +pub unsafe fn change_motion( + module_accessor: &mut app::BattleObjectModuleAccessor, + motion_kind: u64, +) -> Option { + if !is_operation_cpu(module_accessor) { + return None; + } + + if MENU.tech_state == TechFlags::empty() { + return None; + } + + if [hash40("passive_stand_f"), hash40("passive_stand_b")].contains(&motion_kind) { + if TECH_ROLL_DIRECTION == Direction::IN { + return Some(hash40("passive_stand_f")); + } else { + return Some(hash40("passive_stand_b")); + } + } else if [hash40("down_forward_u"), hash40("down_back_u")].contains(&motion_kind) { + if MISS_TECH_ROLL_DIRECTION == Direction::IN { + return Some(hash40("down_forward_u")); + } else { + return Some(hash40("down_back_u")); + } + } else if [hash40("down_forward_d"), hash40("down_back_d")].contains(&motion_kind) { + if MISS_TECH_ROLL_DIRECTION == Direction::IN { + return Some(hash40("down_forward_d")); + } else { + return Some(hash40("down_back_d")); + } + } + + None +} diff --git a/training_mod_consts/src/lib.rs b/training_mod_consts/src/lib.rs index 5e46348..43fd9f1 100644 --- a/training_mod_consts/src/lib.rs +++ b/training_mod_consts/src/lib.rs @@ -1,3 +1,4 @@ +#![feature(const_option)] #[macro_use] extern crate bitflags; @@ -13,9 +14,9 @@ use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; #[cfg(feature = "smash")] use smash::lib::lua_const::*; +use std::collections::HashMap; use strum::IntoEnumIterator; use strum_macros::EnumIter; -use std::collections::HashMap; pub trait ToggleTrait { fn to_toggle_strs() -> Vec<&'static str>; @@ -326,33 +327,6 @@ impl ToggleTrait for SaveStateMirroring { } } -// Defensive States -bitflags! { - pub struct Defensive : u32 { - const SPOT_DODGE = 0x1; - const ROLL_F = 0x2; - const ROLL_B = 0x4; - const JAB = 0x8; - const SHIELD = 0x10; - } -} - -impl Defensive { - fn as_str(self) -> Option<&'static str> { - Some(match self { - Defensive::SPOT_DODGE => "Spotdodge", - Defensive::ROLL_F => "Roll Forwards", - Defensive::ROLL_B => "Roll Backwards", - Defensive::JAB => "Jab", - Defensive::SHIELD => "Shield", - _ => return None, - }) - } -} - -extra_bitflag_impls! {Defensive} -impl_serde_for_bitflags!(Defensive); - #[repr(i32)] #[derive(Debug, Clone, Copy, PartialEq, Serialize_repr, Deserialize_repr)] pub enum OnOff { @@ -971,49 +945,103 @@ impl ToggleTrait for CharacterItem { } } -#[repr(C)] -#[derive(Clone, Copy, Serialize, Deserialize, Debug, )] -pub struct TrainingModpackMenu { - pub hitbox_vis: OnOff, - pub stage_hazards: OnOff, - pub di_state: Direction, - pub sdi_state: Direction, - pub sdi_strength: InputFrequency, - pub clatter_strength: InputFrequency, - pub air_dodge_dir: Direction, - pub mash_state: Action, - pub follow_up: Action, - pub attack_angle: AttackAngle, - pub ledge_state: LedgeOption, - pub ledge_delay: LongDelay, - pub tech_state: TechFlags, - pub miss_tech_state: MissTechFlags, - pub shield_state: Shield, - pub defensive_state: Defensive, - pub oos_offset: Delay, - pub reaction_time: Delay, - pub shield_tilt: Direction, - pub mash_in_neutral: OnOff, - pub fast_fall: BoolFlag, - pub fast_fall_delay: Delay, - pub falling_aerials: BoolFlag, - pub aerial_delay: Delay, - pub full_hop: BoolFlag, - pub crouch: OnOff, - pub input_delay: i32, - pub save_damage: OnOff, - pub save_state_mirroring: SaveStateMirroring, - pub frame_advantage: OnOff, - pub save_state_enable: OnOff, - pub save_state_autoload: OnOff, - pub throw_state: ThrowOption, - pub throw_delay: MedDelay, - pub pummel_delay: MedDelay, - pub buff_state: BuffOption, - pub character_item: CharacterItem, - pub quick_menu: OnOff, +bitflags! { + pub struct MashTrigger : u32 { + const HIT = 0b0000_0000_0000_0000_0001; + const BLOCK = 0b0000_0000_0000_0000_0010; + const PARRY = 0b0000_0000_0000_0000_0100; + const TUMBLE = 0b0000_0000_0000_0000_1000; + const LANDING = 0b0000_0000_0000_0001_0000; + const TRUMP = 0b0000_0000_0000_0010_0000; + const FOOTSTOOL = 0b0000_0000_0000_0100_0000; + const CLATTER = 0b0000_0000_0000_1000_0000; + const LEDGE = 0b0000_0000_0001_0000_0000; + const TECH = 0b0000_0000_0010_0000_0000; + const MISTECH = 0b0000_0000_0100_0000_0000; + const GROUNDED = 0b0000_0000_1000_0000_0000; + const AIRBORNE = 0b0000_0001_0000_0000_0000; + const DISTANCE_CLOSE = 0b0000_0010_0000_0000_0000; + const DISTANCE_MID = 0b0000_0100_0000_0000_0000; + const DISTANCE_FAR = 0b0000_1000_0000_0000_0000; + const ALWAYS = 0b0001_0000_0000_0000_0000; + } } +impl MashTrigger { + pub fn as_str(self) -> Option<&'static str> { + Some(match self { + MashTrigger::HIT => "Hitstun", + MashTrigger::BLOCK => "Shieldstun", + MashTrigger::PARRY => "Parry", + MashTrigger::TUMBLE => "Tumble", + MashTrigger::LANDING => "Landing", + MashTrigger::TRUMP => "Ledge Trump", + MashTrigger::FOOTSTOOL => "Footstool", + MashTrigger::CLATTER => "Clatter", + MashTrigger::LEDGE => "Ledge Option", + MashTrigger::TECH => "Tech Option", + MashTrigger::MISTECH => "Mistech Option", + MashTrigger::GROUNDED => "Grounded", + MashTrigger::AIRBORNE => "Airborne", + MashTrigger::DISTANCE_CLOSE => "Distance: Close", + MashTrigger::DISTANCE_MID => "Distance: Mid", + MashTrigger::DISTANCE_FAR => "Distance: Far", + MashTrigger::ALWAYS => "Always", + _ => return None, + }) + } + + const fn default() -> MashTrigger { + // Hit, block, clatter + MashTrigger::HIT.union(MashTrigger::BLOCK).union(MashTrigger::CLATTER) + } +} + +extra_bitflag_impls! {MashTrigger} +impl_serde_for_bitflags!(MashTrigger); + +#[repr(C)] +#[derive(Clone, Copy, Serialize, Deserialize, Debug)] +pub struct TrainingModpackMenu { + // Mash Tab + pub aerial_delay: Delay, + pub air_dodge_dir: Direction, + pub attack_angle: AttackAngle, + pub buff_state: BuffOption, + pub character_item: CharacterItem, + pub clatter_strength: InputFrequency, + pub crouch: OnOff, + pub di_state: Direction, + pub falling_aerials: BoolFlag, + pub fast_fall_delay: Delay, + pub fast_fall: BoolFlag, + pub follow_up: Action, + pub frame_advantage: OnOff, + pub full_hop: BoolFlag, + pub hitbox_vis: OnOff, + pub input_delay: i32, + pub ledge_delay: LongDelay, + pub ledge_state: LedgeOption, + pub mash_state: Action, + pub mash_triggers: MashTrigger, + pub miss_tech_state: MissTechFlags, + pub oos_offset: Delay, + pub pummel_delay: MedDelay, + pub quick_menu: OnOff, + pub reaction_time: Delay, + pub save_damage: OnOff, + pub save_state_autoload: OnOff, + pub save_state_enable: OnOff, + pub save_state_mirroring: SaveStateMirroring, + pub sdi_state: Direction, + pub sdi_strength: InputFrequency, + pub shield_state: Shield, + pub shield_tilt: Direction, + pub stage_hazards: OnOff, + pub tech_state: TechFlags, + pub throw_delay: MedDelay, + pub throw_state: ThrowOption, +} macro_rules! set_by_str { ($obj:ident, $s:ident, $($field:ident = $rhs:expr,)*) => { @@ -1047,7 +1075,6 @@ impl TrainingModpackMenu { attack_angle = AttackAngle::from_bits(val), clatter_strength = num::FromPrimitive::from_u32(val), crouch = OnOff::from_val(val), - defensive_state = Defensive::from_bits(val), di_state = Direction::from_bits(val), falling_aerials = BoolFlag::from_bits(val), fast_fall_delay = Delay::from_bits(val), @@ -1058,8 +1085,8 @@ impl TrainingModpackMenu { input_delay = Some(log_2(val) as i32), ledge_delay = LongDelay::from_bits(val), ledge_state = LedgeOption::from_bits(val), - mash_in_neutral = OnOff::from_val(val), mash_state = Action::from_bits(val), + mash_triggers = MashTrigger::from_bits(val), miss_tech_state = MissTechFlags::from_bits(val), oos_offset = Delay::from_bits(val), reaction_time = Delay::from_bits(val), @@ -1089,7 +1116,7 @@ impl TrainingModpackMenu { pub struct MenuJsonStruct { pub menu: TrainingModpackMenu, pub defaults_menu: TrainingModpackMenu, - // pub last_focused_submenu: &str + // pub last_focused_submenu: &str } // Fighter Ids @@ -1117,44 +1144,43 @@ impl SubMenuType { } pub static DEFAULTS_MENU: TrainingModpackMenu = TrainingModpackMenu { - hitbox_vis: OnOff::On, - stage_hazards: OnOff::Off, - di_state: Direction::empty(), - sdi_state: Direction::empty(), - sdi_strength: InputFrequency::None, - clatter_strength: InputFrequency::None, - air_dodge_dir: Direction::empty(), - mash_state: Action::empty(), - follow_up: Action::empty(), - attack_angle: AttackAngle::empty(), - ledge_state: LedgeOption::all(), - ledge_delay: LongDelay::empty(), - tech_state: TechFlags::all(), - miss_tech_state: MissTechFlags::all(), - shield_state: Shield::None, - defensive_state: Defensive::all(), - oos_offset: Delay::empty(), - shield_tilt: Direction::empty(), - reaction_time: Delay::empty(), - mash_in_neutral: OnOff::Off, - fast_fall: BoolFlag::empty(), - fast_fall_delay: Delay::empty(), - falling_aerials: BoolFlag::empty(), aerial_delay: Delay::empty(), - full_hop: BoolFlag::empty(), - crouch: OnOff::Off, - input_delay: 0, - save_damage: OnOff::On, - save_state_mirroring: SaveStateMirroring::None, - frame_advantage: OnOff::Off, - save_state_enable: OnOff::On, - save_state_autoload: OnOff::Off, - throw_state: ThrowOption::NONE, - throw_delay: MedDelay::empty(), - pummel_delay: MedDelay::empty(), + air_dodge_dir: Direction::empty(), + attack_angle: AttackAngle::empty(), buff_state: BuffOption::empty(), character_item: CharacterItem::None, + clatter_strength: InputFrequency::None, + crouch: OnOff::Off, + di_state: Direction::empty(), + falling_aerials: BoolFlag::empty(), + fast_fall_delay: Delay::empty(), + fast_fall: BoolFlag::empty(), + follow_up: Action::empty(), + frame_advantage: OnOff::Off, + full_hop: BoolFlag::empty(), + hitbox_vis: OnOff::On, + input_delay: 0, + ledge_delay: LongDelay::empty(), + ledge_state: LedgeOption::all(), + mash_state: Action::empty(), + mash_triggers: MashTrigger::default(), + miss_tech_state: MissTechFlags::all(), + oos_offset: Delay::empty(), + pummel_delay: MedDelay::empty(), quick_menu: OnOff::Off, + reaction_time: Delay::empty(), + save_damage: OnOff::On, + save_state_autoload: OnOff::Off, + save_state_enable: OnOff::On, + save_state_mirroring: SaveStateMirroring::None, + sdi_state: Direction::empty(), + sdi_strength: InputFrequency::None, + shield_state: Shield::None, + shield_tilt: Direction::empty(), + stage_hazards: OnOff::Off, + tech_state: TechFlags::all(), + throw_delay: MedDelay::empty(), + throw_state: ThrowOption::NONE, }; pub static mut MENU: TrainingModpackMenu = DEFAULTS_MENU; @@ -1265,6 +1291,12 @@ pub unsafe fn get_menu() -> UiMenu<'static> { "Followup Toggles: Actions to be performed after the Mash option", false, ); + mash_tab.add_submenu_with_toggles::( + "Mash Triggers", + "mash_triggers", + "Mash triggers: When the Mash Option will be performed", + false, + ); mash_tab.add_submenu_with_toggles::( "Attack Angle", "attack_angle", @@ -1331,12 +1363,6 @@ pub unsafe fn get_menu() -> UiMenu<'static> { "Reaction Time: How many frames to delay before performing a mash option", false, ); - mash_tab.add_submenu_with_toggles::( - "Mash in Neutral", - "mash_in_neutral", - "Mash In Neutral: Should Mash options be performed repeatedly or only when the CPU is hit", - true, - ); overall_menu.tabs.push(mash_tab); let mut defensive_tab = Tab { @@ -1410,12 +1436,6 @@ pub unsafe fn get_menu() -> UiMenu<'static> { "Shield Tilt: Direction to tilt the shield", false, // TODO: Should this be true? ); - defensive_tab.add_submenu_with_toggles::( - "Escape Toggles", - "defensive_state", - "Escape Options: Actions to take after a ledge option, tech option, or mistech option", - false, - ); defensive_tab.add_submenu_with_toggles::( "Buff Options", "buff_state", @@ -1497,7 +1517,8 @@ pub unsafe fn get_menu() -> UiMenu<'static> { ); overall_menu.tabs.push(misc_tab); - let non_ui_menu = serde_json::to_string(&MENU).unwrap() + let non_ui_menu = serde_json::to_string(&MENU) + .unwrap() .replace("\"", "") .replace("{", "") .replace("}", "");