mirror of
https://github.com/jugeeya/UltimateTrainingModpack.git
synced 2025-03-25 07:46:11 +00:00
Refactor to remove static mut's, frame advantage on hit (#708)
* Move QUICK_MENU_ACTIVE to RwLock
* VANILLA_MENU_ACTIVE
* EVENT_QUEUE and CURRENT_VERSION
* Stage Hazard Code
* Notifications
* FIGHTER_MANAGER, ITEM_MANAGER, and STAGE_MANAGER
* Airdodge STICK_DIRECTION
* Shield STICK_DIRECTION
* Attack angle DIRECTION
* offsets
* button_config
* imports in menu.rs
* frame_counter
* common::consts
* dev_config
* mappings in training/ui/menu
* input_log
* Move LazyLock to be a re-export
* buffs
* clatter
* directional influence
* fast_fall
* full_hop
* ledge
* sdi
* shield
* tech
* throw
* Refactor combo. Also allow frame advantage on hit.
* Fix bugs in frame advantage
* Move sync into a separate crate so we can import it in subcrates
* Update MENU
* Update DEFAULTS_MENU
* menu.rs and menu.rs
* Fix warning from ambiguous imports
* ptrainer
* mash.rs
* is_transitioning_dash
* items.rs
* input_record.rs
* Small adjustment to notifications
* Revert "Small adjustment to notifications"
This reverts commit 6629c6f456
.
* input_delay.rs
* Misc nits
* Rename for clarity and format
This commit is contained in:
parent
2457c5b127
commit
858d35142e
48 changed files with 3670 additions and 3630 deletions
Cargo.toml
src
common
hazard_manager
hitbox_visualizer
lib.rstraining
training_mod_consts
training_mod_sync
training_mod_tui/src
|
@ -34,6 +34,7 @@ serde_json = "1"
|
|||
smush_info_shared = { git = "https://github.com/jam1garner/smush_info_shared.git" }
|
||||
toml = "0.5.9"
|
||||
training_mod_consts = { path = "training_mod_consts" }
|
||||
training_mod_sync = { path = "training_mod_sync" }
|
||||
training_mod_tui = { path = "training_mod_tui" }
|
||||
native-tls = { version = "0.2.11", features = ["vendored"] }
|
||||
log = "0.4.17"
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::common::*;
|
||||
use crate::common::menu::{MENU_CLOSE_FRAME_COUNTER, QUICK_MENU_ACTIVE};
|
||||
use crate::common::ButtonConfig;
|
||||
use crate::input::{ControllerStyle::*, *};
|
||||
use crate::training::frame_counter;
|
||||
use crate::training::ui::menu::VANILLA_MENU_ACTIVE;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use strum_macros::EnumIter;
|
||||
use training_mod_consts::{OnOff, MENU};
|
||||
use training_mod_sync::*;
|
||||
|
||||
use super::menu::QUICK_MENU_ACTIVE;
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
pub fn button_mapping(
|
||||
button_config: ButtonConfig,
|
||||
|
@ -176,29 +176,29 @@ unsafe fn get_combo_keys(combo: ButtonCombo) -> ButtonConfig {
|
|||
match combo {
|
||||
// For OpenMenu, have a default in addition to accepting start press
|
||||
ButtonCombo::OpenMenu => DEFAULT_OPEN_MENU_CONFIG,
|
||||
ButtonCombo::SaveState => MENU.save_state_save,
|
||||
ButtonCombo::LoadState => MENU.save_state_load,
|
||||
ButtonCombo::InputRecord => MENU.input_record,
|
||||
ButtonCombo::InputPlayback => MENU.input_playback,
|
||||
ButtonCombo::SaveState => read(&MENU).save_state_save,
|
||||
ButtonCombo::LoadState => read(&MENU).save_state_load,
|
||||
ButtonCombo::InputRecord => read(&MENU).input_record,
|
||||
ButtonCombo::InputPlayback => read(&MENU).input_playback,
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref BUTTON_COMBO_REQUESTS: Mutex<HashMap<ButtonCombo, bool>> =
|
||||
Mutex::new(HashMap::from([
|
||||
(ButtonCombo::OpenMenu, false),
|
||||
(ButtonCombo::SaveState, false),
|
||||
(ButtonCombo::LoadState, false),
|
||||
(ButtonCombo::InputRecord, false),
|
||||
(ButtonCombo::InputPlayback, false),
|
||||
]));
|
||||
static ref START_HOLD_FRAMES: Mutex<u32> = Mutex::new(0);
|
||||
}
|
||||
// Note: in addition to RwLock we also need a LazyLock initializer because HashMap::from() is not const
|
||||
static BUTTON_COMBO_REQUESTS: LazyLock<RwLock<HashMap<ButtonCombo, bool>>> = LazyLock::new(|| {
|
||||
RwLock::new(HashMap::from([
|
||||
(ButtonCombo::OpenMenu, false),
|
||||
(ButtonCombo::SaveState, false),
|
||||
(ButtonCombo::LoadState, false),
|
||||
(ButtonCombo::InputRecord, false),
|
||||
(ButtonCombo::InputPlayback, false),
|
||||
]))
|
||||
});
|
||||
static START_HOLD_FRAMES: RwLock<u32> = RwLock::new(0);
|
||||
|
||||
fn _combo_passes(p1_controller: Controller, combo: ButtonCombo) -> bool {
|
||||
unsafe {
|
||||
// Prevent button combos from passing if either the vanilla or mod menu is open
|
||||
if VANILLA_MENU_ACTIVE || QUICK_MENU_ACTIVE {
|
||||
if read(&VANILLA_MENU_ACTIVE) || read(&QUICK_MENU_ACTIVE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -226,19 +226,16 @@ fn _combo_passes(p1_controller: Controller, combo: ButtonCombo) -> bool {
|
|||
}
|
||||
|
||||
pub fn combo_passes(combo: ButtonCombo) -> bool {
|
||||
unsafe {
|
||||
let button_combo_requests = &mut *BUTTON_COMBO_REQUESTS.data_ptr();
|
||||
let passes = button_combo_requests.get_mut(&combo);
|
||||
let mut did_pass = false;
|
||||
if let Some(passes) = passes {
|
||||
if *passes {
|
||||
did_pass = true;
|
||||
}
|
||||
*passes = false;
|
||||
let mut button_combo_requests_lock = lock_write(&BUTTON_COMBO_REQUESTS);
|
||||
let passes = (*button_combo_requests_lock).get_mut(&combo);
|
||||
let mut did_pass = false;
|
||||
if let Some(passes) = passes {
|
||||
if *passes {
|
||||
did_pass = true;
|
||||
}
|
||||
|
||||
did_pass
|
||||
*passes = false;
|
||||
}
|
||||
did_pass
|
||||
}
|
||||
|
||||
pub fn handle_final_input_mapping(player_idx: i32, controller_struct: &mut SomeControllerStruct) {
|
||||
|
@ -246,37 +243,36 @@ pub fn handle_final_input_mapping(player_idx: i32, controller_struct: &mut SomeC
|
|||
let p1_controller = &mut *controller_struct.controller;
|
||||
let mut start_menu_request = false;
|
||||
|
||||
let menu_close_wait_frame = frame_counter::get_frame_count(*menu::MENU_CLOSE_FRAME_COUNTER);
|
||||
if unsafe { MENU.menu_open_start_press == OnOff::ON } {
|
||||
let start_hold_frames = &mut *START_HOLD_FRAMES.lock();
|
||||
let menu_close_wait_frame = frame_counter::get_frame_count(*MENU_CLOSE_FRAME_COUNTER);
|
||||
if read(&MENU).menu_open_start_press == OnOff::ON {
|
||||
let mut start_hold_frames = read(&START_HOLD_FRAMES);
|
||||
if p1_controller.current_buttons.plus() {
|
||||
*start_hold_frames += 1;
|
||||
start_hold_frames += 1;
|
||||
p1_controller.previous_buttons.set_plus(false);
|
||||
p1_controller.current_buttons.set_plus(false);
|
||||
p1_controller.just_down.set_plus(false);
|
||||
p1_controller.just_release.set_plus(false);
|
||||
if *start_hold_frames >= 10 && unsafe { !VANILLA_MENU_ACTIVE } {
|
||||
if start_hold_frames >= 10 && !read(&VANILLA_MENU_ACTIVE) {
|
||||
// If we've held for more than 10 frames,
|
||||
// let's open the training mod menu
|
||||
start_menu_request = true;
|
||||
}
|
||||
} else {
|
||||
// Here, we just finished holding start
|
||||
if *start_hold_frames > 0
|
||||
&& *start_hold_frames < 10
|
||||
&& unsafe { !QUICK_MENU_ACTIVE }
|
||||
if start_hold_frames > 0
|
||||
&& start_hold_frames < 10
|
||||
&& !read(&QUICK_MENU_ACTIVE)
|
||||
&& menu_close_wait_frame == 0
|
||||
{
|
||||
// If we held for fewer than 10 frames, let's let the game know that
|
||||
// we had pressed start
|
||||
p1_controller.current_buttons.set_plus(true);
|
||||
p1_controller.just_down.set_plus(true);
|
||||
unsafe {
|
||||
VANILLA_MENU_ACTIVE = true;
|
||||
}
|
||||
assign(&VANILLA_MENU_ACTIVE, true);
|
||||
}
|
||||
*start_hold_frames = 0;
|
||||
start_hold_frames = 0;
|
||||
}
|
||||
assign(&START_HOLD_FRAMES, start_hold_frames);
|
||||
|
||||
// If we ever press minus, open the mod menu
|
||||
if p1_controller.current_buttons.minus() {
|
||||
|
@ -284,13 +280,13 @@ pub fn handle_final_input_mapping(player_idx: i32, controller_struct: &mut SomeC
|
|||
}
|
||||
}
|
||||
|
||||
let button_combo_requests = &mut *BUTTON_COMBO_REQUESTS.lock();
|
||||
button_combo_requests
|
||||
let mut button_combo_requests_lock = lock_write(&BUTTON_COMBO_REQUESTS);
|
||||
(*button_combo_requests_lock)
|
||||
.iter_mut()
|
||||
.for_each(|(combo, is_request)| {
|
||||
if !*is_request {
|
||||
*is_request = _combo_passes(*p1_controller, *combo);
|
||||
if *combo == button_config::ButtonCombo::OpenMenu && start_menu_request {
|
||||
if *combo == ButtonCombo::OpenMenu && start_menu_request {
|
||||
*is_request = true;
|
||||
}
|
||||
}
|
||||
|
|
4577
src/common/consts.rs
4577
src/common/consts.rs
File diff suppressed because it is too large
Load diff
|
@ -1,12 +1,11 @@
|
|||
use std::fs;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::common::input::*;
|
||||
use crate::consts::DEV_TOML_PATH;
|
||||
use crate::logging::info;
|
||||
use training_mod_sync::*;
|
||||
|
||||
/// Hot-reloadable configs for quicker development
|
||||
///
|
||||
|
@ -29,16 +28,15 @@ use crate::logging::info;
|
|||
/// quit_menu_button.pos_y = dev_config.quit_menu_pos_y;
|
||||
/// quit_menu_text.as_textbox().set_text_string(&dev_config.quit_menu_title);
|
||||
/// ```
|
||||
#[derive(Deserialize, Default)]
|
||||
#[derive(Deserialize, Default, Clone)]
|
||||
pub struct DevConfig {}
|
||||
|
||||
pub unsafe fn config() -> &'static DevConfig {
|
||||
&*DEV_CONFIG.data_ptr()
|
||||
pub unsafe fn config() -> DevConfig {
|
||||
read_clone(&(*DEV_CONFIG))
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref DEV_CONFIG: Mutex<DevConfig> = Mutex::new(DevConfig::load_from_toml());
|
||||
}
|
||||
pub static DEV_CONFIG: LazyLock<RwLock<DevConfig>> =
|
||||
LazyLock::new(|| RwLock::new(DevConfig::load_from_toml()));
|
||||
|
||||
impl DevConfig {
|
||||
fn load_from_toml() -> DevConfig {
|
||||
|
@ -57,7 +55,6 @@ impl DevConfig {
|
|||
pub fn handle_final_input_mapping(player_idx: i32, controller_struct: &SomeControllerStruct) {
|
||||
let current_buttons = controller_struct.controller.current_buttons;
|
||||
if player_idx == 0 && current_buttons.l() && current_buttons.r() && current_buttons.a() {
|
||||
let mut dev_config = DEV_CONFIG.lock();
|
||||
*dev_config = DevConfig::load_from_toml();
|
||||
assign(&(*DEV_CONFIG), DevConfig::load_from_toml());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,16 +2,94 @@ use std::convert::TryInto;
|
|||
use std::ffi::{c_char, c_void};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use once_cell::sync::OnceCell;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use skyline::nn::{account, oe, time};
|
||||
|
||||
use crate::common::release::CURRENT_VERSION;
|
||||
use training_mod_sync::*;
|
||||
|
||||
pub static mut EVENT_QUEUE: Vec<Event> = vec![];
|
||||
static mut SESSION_ID: OnceCell<String> = OnceCell::new();
|
||||
static mut DEVICE_ID: OnceCell<String> = OnceCell::new();
|
||||
static mut USER_ID: OnceCell<String> = OnceCell::new();
|
||||
pub static EVENT_QUEUE: RwLock<Vec<Event>> = RwLock::new(vec![]);
|
||||
static SESSION_ID: LazyLock<String> = LazyLock::new(|| unsafe {
|
||||
let mut device_uuid = Uuid {
|
||||
size: 16,
|
||||
string_size: 300,
|
||||
data: [0u8; 16],
|
||||
};
|
||||
GetPseudoDeviceId(&mut device_uuid as *mut Uuid);
|
||||
time::Initialize();
|
||||
let event_time = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Time went backwards")
|
||||
.as_millis();
|
||||
let mut session_id_hash = Sha256Hash { hash: [0; 0x20] };
|
||||
let event_time_bytes: [u8; 16] = std::mem::transmute(event_time.to_be());
|
||||
let session_id_bytes: [u8; 32] = [event_time_bytes, device_uuid.data]
|
||||
.concat()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
GenerateSha256Hash(
|
||||
&mut session_id_hash as *mut _ as *mut c_void,
|
||||
0x20 * 8,
|
||||
session_id_bytes.as_ptr() as *const c_void,
|
||||
32 * 8,
|
||||
);
|
||||
session_id_hash
|
||||
.hash
|
||||
.iter()
|
||||
.map(|i| format!("{i:02x}"))
|
||||
.collect::<Vec<String>>()
|
||||
.join("")
|
||||
});
|
||||
static DEVICE_ID: LazyLock<String> = LazyLock::new(|| unsafe {
|
||||
let mut device_uuid = Uuid {
|
||||
size: 16,
|
||||
string_size: 300,
|
||||
data: [0u8; 16],
|
||||
};
|
||||
GetPseudoDeviceId(&mut device_uuid as *mut Uuid);
|
||||
let mut device_id_hash = Sha256Hash { hash: [0; 0x20] };
|
||||
GenerateSha256Hash(
|
||||
&mut device_id_hash as *mut _ as *mut c_void,
|
||||
0x20 * 8,
|
||||
device_uuid.data.as_ptr() as *const c_void,
|
||||
64 * 2,
|
||||
);
|
||||
device_uuid
|
||||
.data
|
||||
.iter()
|
||||
.map(|i| format!("{i:02x}"))
|
||||
.collect::<Vec<String>>()
|
||||
.join("")
|
||||
});
|
||||
static USER_ID: LazyLock<String> = LazyLock::new(|| unsafe {
|
||||
account::Initialize();
|
||||
let mut user_uid = account::Uid::new();
|
||||
account::GetLastOpenedUser(&mut user_uid);
|
||||
let mut user_id_hash = Sha256Hash { hash: [0; 0x20] };
|
||||
GenerateSha256Hash(
|
||||
&mut user_id_hash as *mut _ as *mut c_void,
|
||||
0x20 * 8,
|
||||
user_uid.id.as_ptr() as *const c_void,
|
||||
16 * 8,
|
||||
);
|
||||
user_uid
|
||||
.id
|
||||
.iter()
|
||||
.map(|i| format!("{i:02x}"))
|
||||
.collect::<Vec<String>>()
|
||||
.join("")
|
||||
});
|
||||
static SMASH_VERSION: LazyLock<String> = LazyLock::new(|| {
|
||||
let mut smash_version = oe::DisplayVersion { name: [0; 16] };
|
||||
unsafe {
|
||||
oe::GetDisplayVersion(&mut smash_version);
|
||||
|
||||
std::ffi::CStr::from_ptr(smash_version.name.as_ptr() as *const c_char)
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
}
|
||||
});
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
pub struct Event {
|
||||
|
@ -59,15 +137,6 @@ extern "C" {
|
|||
|
||||
impl Event {
|
||||
pub fn new() -> Event {
|
||||
let mut device_uuid = Uuid {
|
||||
size: 16,
|
||||
string_size: 300,
|
||||
data: [0u8; 16],
|
||||
};
|
||||
unsafe {
|
||||
GetPseudoDeviceId(&mut device_uuid as *mut Uuid);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
time::Initialize();
|
||||
let event_time = SystemTime::now()
|
||||
|
@ -75,84 +144,13 @@ impl Event {
|
|||
.expect("Time went backwards")
|
||||
.as_millis();
|
||||
|
||||
if SESSION_ID.get().is_none() {
|
||||
account::Initialize();
|
||||
let mut user_uid = account::Uid::new();
|
||||
account::GetLastOpenedUser(&mut user_uid);
|
||||
|
||||
let mut user_id_hash = Sha256Hash { hash: [0; 0x20] };
|
||||
GenerateSha256Hash(
|
||||
&mut user_id_hash as *mut _ as *mut c_void,
|
||||
0x20 * 8,
|
||||
user_uid.id.as_ptr() as *const c_void,
|
||||
16 * 8,
|
||||
);
|
||||
|
||||
USER_ID
|
||||
.set(
|
||||
user_uid
|
||||
.id
|
||||
.iter()
|
||||
.map(|i| format!("{i:02x}"))
|
||||
.collect::<Vec<String>>()
|
||||
.join(""),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut device_id_hash = Sha256Hash { hash: [0; 0x20] };
|
||||
GenerateSha256Hash(
|
||||
&mut device_id_hash as *mut _ as *mut c_void,
|
||||
0x20 * 8,
|
||||
device_uuid.data.as_ptr() as *const c_void,
|
||||
64 * 2,
|
||||
);
|
||||
DEVICE_ID
|
||||
.set(
|
||||
device_uuid
|
||||
.data
|
||||
.iter()
|
||||
.map(|i| format!("{i:02x}"))
|
||||
.collect::<Vec<String>>()
|
||||
.join(""),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut session_id_hash = Sha256Hash { hash: [0; 0x20] };
|
||||
// let mut device_id_0_bytes : [u8; 8] = Default::default();
|
||||
// device_id_0_bytes.copy_from_slice(&device_uuid.data[0..8]);
|
||||
// let mut device_id_1_bytes : [u8; 8] = Default::default();
|
||||
// device_id_1_bytes.copy_from_slice(&device_uuid.data[8..16]);
|
||||
let event_time_bytes: [u8; 16] = std::mem::transmute(event_time.to_be());
|
||||
let session_id_bytes: [u8; 32] = [event_time_bytes, device_uuid.data]
|
||||
.concat()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
GenerateSha256Hash(
|
||||
&mut session_id_hash as *mut _ as *mut c_void,
|
||||
0x20 * 8,
|
||||
session_id_bytes.as_ptr() as *const c_void,
|
||||
32 * 8,
|
||||
);
|
||||
SESSION_ID
|
||||
.set(
|
||||
session_id_hash
|
||||
.hash
|
||||
.iter()
|
||||
.map(|i| format!("{i:02x}"))
|
||||
.collect::<Vec<String>>()
|
||||
.join(""),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Event {
|
||||
user_id: USER_ID.get().unwrap().to_string(),
|
||||
device_id: DEVICE_ID.get().unwrap().to_string(),
|
||||
user_id: USER_ID.clone(),
|
||||
device_id: DEVICE_ID.clone(),
|
||||
event_time,
|
||||
session_id: SESSION_ID.get().unwrap().to_string(),
|
||||
mod_version: CURRENT_VERSION.lock().to_string(),
|
||||
smash_version: smash_version(),
|
||||
session_id: SESSION_ID.clone(),
|
||||
mod_version: CURRENT_VERSION.clone(),
|
||||
smash_version: SMASH_VERSION.clone(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
@ -174,36 +172,24 @@ impl Event {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn smash_version() -> String {
|
||||
let mut smash_version = oe::DisplayVersion { name: [0; 16] };
|
||||
|
||||
unsafe {
|
||||
oe::GetDisplayVersion(&mut smash_version);
|
||||
|
||||
std::ffi::CStr::from_ptr(smash_version.name.as_ptr() as *const c_char)
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn events_loop() {
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(10));
|
||||
unsafe {
|
||||
while let Some(event) = EVENT_QUEUE.pop() {
|
||||
let host = "https://my-project-1511972643240-default-rtdb.firebaseio.com";
|
||||
let path = format!(
|
||||
"/event/{}/device/{}/{}.json",
|
||||
event.event_name, event.device_id, event.event_time
|
||||
);
|
||||
let mut event_queue_lock = lock_write(&EVENT_QUEUE);
|
||||
while let Some(event) = (*event_queue_lock).pop() {
|
||||
let host = "https://my-project-1511972643240-default-rtdb.firebaseio.com";
|
||||
let path = format!(
|
||||
"/event/{}/device/{}/{}.json",
|
||||
event.event_name, event.device_id, event.event_time
|
||||
);
|
||||
|
||||
let url = format!("{host}{path}");
|
||||
minreq::post(url)
|
||||
.with_json(&event)
|
||||
.expect("Failed to send info to firebase")
|
||||
.send()
|
||||
.ok();
|
||||
}
|
||||
let url = format!("{host}{path}");
|
||||
minreq::post(url)
|
||||
.with_json(&event)
|
||||
.expect("Failed to send info to firebase")
|
||||
.send()
|
||||
.ok();
|
||||
}
|
||||
drop(event_queue_lock);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -299,7 +299,7 @@ pub struct MappedInputs {
|
|||
}
|
||||
|
||||
impl MappedInputs {
|
||||
pub fn empty() -> MappedInputs {
|
||||
pub const fn empty() -> MappedInputs {
|
||||
MappedInputs {
|
||||
buttons: Buttons::empty(),
|
||||
lstick_x: 0,
|
||||
|
|
|
@ -1,22 +1,24 @@
|
|||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::io::BufReader;
|
||||
use std::ptr::addr_of;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use skyline::nn::hid::GetNpadStyleSet;
|
||||
|
||||
use crate::common::button_config::button_mapping;
|
||||
use crate::common::*;
|
||||
use crate::common::{button_config, ButtonConfig};
|
||||
use crate::common::{DEFAULTS_MENU, MENU};
|
||||
use crate::events::{Event, EVENT_QUEUE};
|
||||
use crate::input::*;
|
||||
use crate::input::{ButtonBitfield, ControllerStyle, MappedInputs, SomeControllerStruct};
|
||||
use crate::logging::*;
|
||||
use crate::training::frame_counter;
|
||||
|
||||
use training_mod_consts::{create_app, InputControl, MenuJsonStruct, MENU_OPTIONS_PATH};
|
||||
use training_mod_sync::*;
|
||||
use training_mod_tui::AppPage;
|
||||
|
||||
use DirectionButton::*;
|
||||
|
||||
pub const MENU_CLOSE_WAIT_FRAMES: u32 = 15;
|
||||
pub static mut QUICK_MENU_ACTIVE: bool = false;
|
||||
pub static QUICK_MENU_ACTIVE: RwLock<bool> = RwLock::new(false);
|
||||
|
||||
pub unsafe fn menu_condition() -> bool {
|
||||
button_config::combo_passes(button_config::ButtonCombo::OpenMenu)
|
||||
|
@ -32,11 +34,9 @@ pub fn load_from_file() {
|
|||
let reader = BufReader::new(menu_conf);
|
||||
if let Ok(menu_conf_json) = serde_json::from_reader::<BufReader<_>, MenuJsonStruct>(reader)
|
||||
{
|
||||
unsafe {
|
||||
MENU = menu_conf_json.menu;
|
||||
DEFAULTS_MENU = menu_conf_json.defaults_menu;
|
||||
info!("Previous menu found. Loading...");
|
||||
}
|
||||
assign(&MENU, menu_conf_json.menu);
|
||||
assign(&DEFAULTS_MENU, menu_conf_json.defaults_menu);
|
||||
info!("Previous menu found. Loading...");
|
||||
} else {
|
||||
warn!("Previous menu found but is invalid. Deleting...");
|
||||
let err_msg = format!(
|
||||
|
@ -49,23 +49,21 @@ pub fn load_from_file() {
|
|||
info!("No previous menu file found.");
|
||||
}
|
||||
info!("Setting initial menu selections...");
|
||||
unsafe {
|
||||
let mut app = QUICK_MENU_APP.lock();
|
||||
app.serialized_default_settings = serde_json::to_string(&*addr_of!(DEFAULTS_MENU))
|
||||
.expect("Could not serialize DEFAULTS_MENU");
|
||||
app.update_all_from_json(
|
||||
&serde_json::to_string(&*addr_of!(MENU)).expect("Could not serialize MENU"),
|
||||
);
|
||||
}
|
||||
let mut app = lock_write(&QUICK_MENU_APP);
|
||||
app.serialized_default_settings =
|
||||
serde_json::to_string(&read(&DEFAULTS_MENU)).expect("Could not serialize DEFAULTS_MENU");
|
||||
app.update_all_from_json(
|
||||
&serde_json::to_string(&read(&MENU)).expect("Could not serialize MENU"),
|
||||
);
|
||||
}
|
||||
|
||||
pub unsafe fn set_menu_from_json(message: &str) {
|
||||
pub fn set_menu_from_json(message: &str) {
|
||||
let response = serde_json::from_str::<MenuJsonStruct>(message);
|
||||
info!("Received menu message: {message}");
|
||||
if let Ok(message_json) = response {
|
||||
// Includes both MENU and DEFAULTS_MENU
|
||||
MENU = message_json.menu;
|
||||
DEFAULTS_MENU = message_json.defaults_menu;
|
||||
assign(&MENU, message_json.menu);
|
||||
assign(&DEFAULTS_MENU, message_json.defaults_menu);
|
||||
fs::write(
|
||||
MENU_OPTIONS_PATH,
|
||||
serde_json::to_string_pretty(&message_json).unwrap(),
|
||||
|
@ -81,12 +79,10 @@ pub unsafe fn set_menu_from_json(message: &str) {
|
|||
}
|
||||
|
||||
pub fn spawn_menu() {
|
||||
unsafe {
|
||||
QUICK_MENU_ACTIVE = true;
|
||||
let mut app = QUICK_MENU_APP.lock();
|
||||
app.page = AppPage::SUBMENU;
|
||||
*MENU_RECEIVED_INPUT.data_ptr() = true;
|
||||
}
|
||||
assign(&QUICK_MENU_ACTIVE, true);
|
||||
let mut app = lock_write(&QUICK_MENU_APP);
|
||||
app.page = AppPage::SUBMENU;
|
||||
assign(&MENU_RECEIVED_INPUT, true);
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Hash, Copy, Clone)]
|
||||
|
@ -101,16 +97,17 @@ enum DirectionButton {
|
|||
RUp,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref QUICK_MENU_APP: Mutex<training_mod_tui::App<'static>> = Mutex::new({
|
||||
pub static QUICK_MENU_APP: LazyLock<RwLock<training_mod_tui::App<'static>>> = LazyLock::new(|| {
|
||||
RwLock::new({
|
||||
info!("Initialized lazy_static: QUICK_MENU_APP");
|
||||
unsafe { create_app() }
|
||||
});
|
||||
pub static ref P1_CONTROLLER_STYLE: Mutex<ControllerStyle> =
|
||||
Mutex::new(ControllerStyle::default());
|
||||
static ref DIRECTION_HOLD_FRAMES: Mutex<HashMap<DirectionButton, u32>> = {
|
||||
use DirectionButton::*;
|
||||
Mutex::new(HashMap::from([
|
||||
})
|
||||
});
|
||||
pub static P1_CONTROLLER_STYLE: LazyLock<RwLock<ControllerStyle>> =
|
||||
LazyLock::new(|| RwLock::new(ControllerStyle::default()));
|
||||
static DIRECTION_HOLD_FRAMES: LazyLock<RwLock<HashMap<DirectionButton, u32>>> =
|
||||
LazyLock::new(|| {
|
||||
RwLock::new(HashMap::from([
|
||||
(LLeft, 0),
|
||||
(RLeft, 0),
|
||||
(LDown, 0),
|
||||
|
@ -120,12 +117,11 @@ lazy_static! {
|
|||
(LUp, 0),
|
||||
(RUp, 0),
|
||||
]))
|
||||
};
|
||||
pub static ref MENU_RECEIVED_INPUT: Mutex<bool> = Mutex::new(true);
|
||||
}
|
||||
});
|
||||
pub static MENU_RECEIVED_INPUT: RwLock<bool> = RwLock::new(true);
|
||||
|
||||
pub static MENU_CLOSE_FRAME_COUNTER: Lazy<usize> =
|
||||
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::Real));
|
||||
pub static MENU_CLOSE_FRAME_COUNTER: LazyLock<usize> =
|
||||
LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::Real));
|
||||
|
||||
pub fn handle_final_input_mapping(
|
||||
player_idx: i32,
|
||||
|
@ -135,7 +131,7 @@ pub fn handle_final_input_mapping(
|
|||
unsafe {
|
||||
if player_idx == 0 {
|
||||
let p1_controller = &mut *controller_struct.controller;
|
||||
*P1_CONTROLLER_STYLE.lock() = p1_controller.style;
|
||||
assign(&P1_CONTROLLER_STYLE, p1_controller.style);
|
||||
let visual_frame_count = frame_counter::get_frame_count(*MENU_CLOSE_FRAME_COUNTER);
|
||||
if visual_frame_count > 0 && visual_frame_count < MENU_CLOSE_WAIT_FRAMES {
|
||||
// If we just closed the menu, kill all inputs to avoid accidental presses
|
||||
|
@ -149,7 +145,7 @@ pub fn handle_final_input_mapping(
|
|||
frame_counter::reset_frame_count(*MENU_CLOSE_FRAME_COUNTER);
|
||||
}
|
||||
|
||||
if QUICK_MENU_ACTIVE {
|
||||
if read(&QUICK_MENU_ACTIVE) {
|
||||
// If we're here, remove all other presses
|
||||
*out = MappedInputs::empty();
|
||||
|
||||
|
@ -157,7 +153,7 @@ pub fn handle_final_input_mapping(
|
|||
|
||||
const DIRECTION_HOLD_REPEAT_FRAMES: u32 = 20;
|
||||
use DirectionButton::*;
|
||||
let direction_hold_frames = &mut *DIRECTION_HOLD_FRAMES.lock();
|
||||
let mut direction_hold_frames = read_clone(&DIRECTION_HOLD_FRAMES); // TODO!("Refactor this, it doesn't need to be a hashmap")
|
||||
|
||||
// Check for all controllers unplugged
|
||||
let mut potential_controller_ids = (0..8).collect::<Vec<u32>>();
|
||||
|
@ -166,7 +162,7 @@ pub fn handle_final_input_mapping(
|
|||
.iter()
|
||||
.all(|i| GetNpadStyleSet(i as *const _).flags == 0)
|
||||
{
|
||||
QUICK_MENU_ACTIVE = false;
|
||||
assign(&QUICK_MENU_ACTIVE, false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -194,41 +190,44 @@ pub fn handle_final_input_mapping(
|
|||
}
|
||||
});
|
||||
|
||||
let app = &mut *QUICK_MENU_APP.data_ptr(); // TODO: Why aren't we taking a lock here?
|
||||
button_mapping(ButtonConfig::A, style, button_presses).then(|| {
|
||||
let mut app = lock_write(&QUICK_MENU_APP);
|
||||
button_config::button_mapping(ButtonConfig::A, style, button_presses).then(|| {
|
||||
app.on_a();
|
||||
received_input = true;
|
||||
});
|
||||
button_mapping(ButtonConfig::B, style, button_presses).then(|| {
|
||||
button_config::button_mapping(ButtonConfig::B, style, button_presses).then(|| {
|
||||
received_input = true;
|
||||
app.on_b();
|
||||
if app.page == AppPage::CLOSE {
|
||||
// Leave menu.
|
||||
frame_counter::start_counting(*MENU_CLOSE_FRAME_COUNTER);
|
||||
QUICK_MENU_ACTIVE = false;
|
||||
assign(&QUICK_MENU_ACTIVE, false);
|
||||
let menu_json = app.get_serialized_settings_with_defaults();
|
||||
set_menu_from_json(&menu_json);
|
||||
EVENT_QUEUE.push(Event::menu_open(menu_json));
|
||||
|
||||
let mut event_queue_lock = lock_write(&EVENT_QUEUE);
|
||||
(*event_queue_lock).push(Event::menu_open(menu_json));
|
||||
drop(event_queue_lock);
|
||||
}
|
||||
});
|
||||
button_mapping(ButtonConfig::X, style, button_presses).then(|| {
|
||||
button_config::button_mapping(ButtonConfig::X, style, button_presses).then(|| {
|
||||
app.on_x();
|
||||
received_input = true;
|
||||
});
|
||||
button_mapping(ButtonConfig::Y, style, button_presses).then(|| {
|
||||
button_config::button_mapping(ButtonConfig::Y, style, button_presses).then(|| {
|
||||
app.on_y();
|
||||
received_input = true;
|
||||
});
|
||||
|
||||
button_mapping(ButtonConfig::ZL, style, button_presses).then(|| {
|
||||
button_config::button_mapping(ButtonConfig::ZL, style, button_presses).then(|| {
|
||||
app.on_zl();
|
||||
received_input = true;
|
||||
});
|
||||
button_mapping(ButtonConfig::ZR, style, button_presses).then(|| {
|
||||
button_config::button_mapping(ButtonConfig::ZR, style, button_presses).then(|| {
|
||||
app.on_zr();
|
||||
received_input = true;
|
||||
});
|
||||
button_mapping(ButtonConfig::R, style, button_presses).then(|| {
|
||||
button_config::button_mapping(ButtonConfig::R, style, button_presses).then(|| {
|
||||
app.on_r();
|
||||
received_input = true;
|
||||
});
|
||||
|
@ -271,7 +270,7 @@ pub fn handle_final_input_mapping(
|
|||
|
||||
if received_input {
|
||||
direction_hold_frames.iter_mut().for_each(|(_, f)| *f = 0);
|
||||
*MENU_RECEIVED_INPUT.lock() = true;
|
||||
assign(&MENU_RECEIVED_INPUT, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ pub use crate::common::consts::MENU;
|
|||
use crate::common::consts::*;
|
||||
use crate::common::offsets::OFFSET_GET_BATTLE_OBJECT_FROM_ID;
|
||||
use crate::training::character_specific::ptrainer;
|
||||
use training_mod_sync::*;
|
||||
|
||||
pub mod button_config;
|
||||
pub mod consts;
|
||||
|
@ -19,11 +20,9 @@ pub mod offsets;
|
|||
pub mod raygun_printer;
|
||||
pub mod release;
|
||||
|
||||
pub static mut DEFAULTS_MENU: TrainingModpackMenu = consts::DEFAULTS_MENU;
|
||||
pub static mut BASE_MENU: TrainingModpackMenu = unsafe { DEFAULTS_MENU };
|
||||
pub static mut FIGHTER_MANAGER_ADDR: usize = 0;
|
||||
pub static mut ITEM_MANAGER_ADDR: usize = 0;
|
||||
pub static mut STAGE_MANAGER_ADDR: usize = 0;
|
||||
pub static FIGHTER_MANAGER_ADDR: RwLock<usize> = RwLock::new(0);
|
||||
pub static ITEM_MANAGER_ADDR: RwLock<usize> = RwLock::new(0);
|
||||
pub static STAGE_MANAGER_ADDR: RwLock<usize> = RwLock::new(0);
|
||||
pub static mut TRAINING_MENU_ADDR: *mut PauseMenu = core::ptr::null_mut();
|
||||
|
||||
#[cfg(not(feature = "outside_training_mode"))]
|
||||
|
@ -74,7 +73,7 @@ pub fn try_get_module_accessor(
|
|||
let entry_id_int = fighter_id as i32;
|
||||
let entry_id = app::FighterEntryID(entry_id_int);
|
||||
unsafe {
|
||||
let mgr = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager);
|
||||
let mgr = *(read(&FIGHTER_MANAGER_ADDR) as *mut *mut app::FighterManager);
|
||||
let fighter_entry =
|
||||
FighterManager::get_fighter_entry(mgr, entry_id) as *mut app::FighterEntry;
|
||||
if fighter_entry.is_null() {
|
||||
|
@ -105,7 +104,7 @@ pub fn is_operation_cpu(module_accessor: &mut app::BattleObjectModuleAccessor) -
|
|||
}
|
||||
|
||||
let entry_id = app::FighterEntryID(entry_id_int);
|
||||
let mgr = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager);
|
||||
let mgr = *(read(&FIGHTER_MANAGER_ADDR) as *mut *mut app::FighterManager);
|
||||
let fighter_information = FighterManager::get_fighter_information(mgr, entry_id);
|
||||
|
||||
FighterInformation::is_operation_cpu(fighter_information)
|
||||
|
@ -269,12 +268,12 @@ pub unsafe fn is_in_landing(module_accessor: &mut app::BattleObjectModuleAccesso
|
|||
|
||||
// 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);
|
||||
let fighter_manager = *(read(&FIGHTER_MANAGER_ADDR) as *mut *mut app::FighterManager);
|
||||
FighterManager::is_ready_go(fighter_manager)
|
||||
}
|
||||
|
||||
pub unsafe fn entry_count() -> i32 {
|
||||
let fighter_manager = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager);
|
||||
let fighter_manager = *(read(&FIGHTER_MANAGER_ADDR) as *mut *mut app::FighterManager);
|
||||
FighterManager::entry_count(fighter_manager)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// TODO!(Do all these need to be referenced by offset? Or do some of them have symbols?)
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)] // We want the assembly needles to stay in lines of four bytes each
|
||||
use crate::logging::*;
|
||||
use lazy_static::lazy_static;
|
||||
use training_mod_sync::LazyLock;
|
||||
|
||||
// Stolen from HDR who stole it from Arcropolis
|
||||
// https://github.com/HDR-Development/HewDraw-Remix/blob/dev/dynamic/src/util.rs
|
||||
|
@ -36,9 +36,7 @@ fn find_offset(name: &str, needle: &[u8]) -> Option<usize> {
|
|||
macro_rules! impl_offset {
|
||||
($fn_name:ident) => {
|
||||
paste::paste! {
|
||||
lazy_static! {
|
||||
pub static ref [<OFFSET_ $fn_name>]: usize = find_offset(stringify!($fn_name), [<NEEDLE_ $fn_name>]).expect(stringify!(Failed to find offset for $fn_name));
|
||||
}
|
||||
pub static [<OFFSET_ $fn_name>]: LazyLock<usize> = LazyLock::new(|| find_offset(stringify!($fn_name), [<NEEDLE_ $fn_name>]).expect(stringify!(Failed to find offset for $fn_name)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
#![allow(clippy::unnecessary_unwrap)]
|
||||
use crate::common::dialog;
|
||||
use crate::consts::*;
|
||||
use crate::logging::*;
|
||||
use anyhow::{anyhow, Result};
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use serde_json::Value;
|
||||
use zip::ZipArchive;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CURRENT_VERSION: Mutex<String> = Mutex::new({
|
||||
info!("Initialized lazy_static: CURRENT_VERSION");
|
||||
match get_current_version() {
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("Could not find current modpack version!: {}", e),
|
||||
}
|
||||
});
|
||||
}
|
||||
use crate::common::dialog;
|
||||
use crate::consts::*;
|
||||
use crate::logging::*;
|
||||
|
||||
use training_mod_sync::*;
|
||||
|
||||
pub static CURRENT_VERSION: LazyLock<String> = LazyLock::new(|| {
|
||||
info!("Initialized lazy static value: CURRENT_VERSION");
|
||||
match get_current_version() {
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("Could not find current modpack version!: {}", e),
|
||||
}
|
||||
});
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Release {
|
||||
|
@ -72,13 +70,12 @@ impl Release {
|
|||
// alphabetical order == chronological order
|
||||
//
|
||||
// https://datatracker.ietf.org/doc/html/rfc3339#section-5.1
|
||||
let current_version = CURRENT_VERSION.lock();
|
||||
self.published_at.as_str() <= current_version.as_str()
|
||||
self.published_at.as_str() <= (*CURRENT_VERSION).as_str()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_update_policy() -> UpdatePolicy {
|
||||
unsafe { MENU.update_policy }
|
||||
read(&MENU).update_policy
|
||||
}
|
||||
|
||||
fn get_release(beta: bool) -> Result<Release> {
|
||||
|
@ -190,10 +187,8 @@ pub fn perform_version_check() {
|
|||
};
|
||||
if release_to_apply.is_ok() {
|
||||
let published_at = release_to_apply.as_ref().unwrap().published_at.clone();
|
||||
let current_version = CURRENT_VERSION.lock();
|
||||
info!("Current version: {}", current_version);
|
||||
info!("Current version: {}", *CURRENT_VERSION);
|
||||
info!("Github version: {}", published_at);
|
||||
drop(current_version); // Explicitly unlock, since we also acquire a lock in is_older_than_installed()
|
||||
if release_to_apply.as_ref().unwrap().is_older_than_installed() {
|
||||
release_to_apply = Err(anyhow!(
|
||||
"Github version is not newer than the current installed version.",
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
#![allow(dead_code)]
|
||||
#![allow(unused_assignments)]
|
||||
#![allow(unused_variables)]
|
||||
|
||||
use skyline::error::show_error;
|
||||
use skyline::hook;
|
||||
use skyline::hooks::A64InlineHook;
|
||||
use skyline::text_iter::{add_get_imm, adrp_get_imm, Instruction::*, TextIter};
|
||||
use smash::app::smashball::is_training_mode;
|
||||
|
||||
use HazardState::*;
|
||||
use HookState::*;
|
||||
|
||||
use crate::common::consts::*;
|
||||
use crate::logging::*;
|
||||
|
||||
use training_mod_sync::*;
|
||||
|
||||
use HazardState::*;
|
||||
use HookState::*;
|
||||
|
||||
enum HazardState {
|
||||
Begin,
|
||||
Adrp1,
|
||||
|
@ -29,6 +27,9 @@ enum HookState {
|
|||
Ldrsw2,
|
||||
}
|
||||
|
||||
static HAZARD_FLAG_ADDRESS: LazyLock<usize> = LazyLock::new(get_hazard_flag_address);
|
||||
static LOAD_ADDRESS: LazyLock<usize> = LazyLock::new(get_hazard_hook_address);
|
||||
|
||||
fn get_hazard_flag_address() -> usize {
|
||||
let mut state = HazardState::Begin;
|
||||
let mut flag_pos = 0;
|
||||
|
@ -81,12 +82,8 @@ fn get_hazard_hook_address() -> usize {
|
|||
flag_pos
|
||||
}
|
||||
|
||||
// 8.1.0 Defaults
|
||||
static mut HAZARD_FLAG_ADDRESS: *mut u8 = 0x04eb_bf95 as *mut u8;
|
||||
static mut LOAD_ADDRESS: usize = 0x0214_bde8;
|
||||
|
||||
#[hook(offset = LOAD_ADDRESS, inline)]
|
||||
fn hazard_intercept(ctx: &skyline::hooks::InlineCtx) {
|
||||
#[hook(offset = *LOAD_ADDRESS, inline)]
|
||||
fn hazard_intercept(_ctx: &skyline::hooks::InlineCtx) {
|
||||
unsafe {
|
||||
if is_training_mode() {
|
||||
mod_handle_hazards();
|
||||
|
@ -96,22 +93,20 @@ fn hazard_intercept(ctx: &skyline::hooks::InlineCtx) {
|
|||
|
||||
fn mod_handle_hazards() {
|
||||
unsafe {
|
||||
*HAZARD_FLAG_ADDRESS = (MENU.stage_hazards == OnOff::ON) as u8;
|
||||
let address = *HAZARD_FLAG_ADDRESS as *mut u8;
|
||||
*address = (read(&MENU).stage_hazards == OnOff::ON) as u8;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn validate_hazards_addrs() -> Result<(), ()> {
|
||||
HAZARD_FLAG_ADDRESS = get_hazard_flag_address() as *mut u8;
|
||||
LOAD_ADDRESS = get_hazard_hook_address();
|
||||
|
||||
let mut error_string: String = String::new();
|
||||
let mut error_id = 0;
|
||||
|
||||
if HAZARD_FLAG_ADDRESS.is_null() {
|
||||
if *HAZARD_FLAG_ADDRESS == 0 {
|
||||
error_string += &String::from("The Ultimate Training Modpack was unable to locate stage loading code in your version of the game.\n\n");
|
||||
error_id += 1000;
|
||||
}
|
||||
if LOAD_ADDRESS == 0 {
|
||||
if *LOAD_ADDRESS == 0 {
|
||||
error_string += &String::from("The Ultimate Training Modpack was unable to locate the global hazard address in your version of the game.\n\n");
|
||||
error_id += 1000;
|
||||
}
|
||||
|
@ -133,10 +128,8 @@ pub fn hazard_manager() {
|
|||
info!("Applying hazard control mods.");
|
||||
unsafe {
|
||||
if let Ok(()) = validate_hazards_addrs() {
|
||||
HAZARD_FLAG_ADDRESS = get_hazard_flag_address() as *mut u8;
|
||||
LOAD_ADDRESS = get_hazard_hook_address();
|
||||
A64InlineHook(
|
||||
LOAD_ADDRESS as *const skyline::libc::c_void,
|
||||
(*LOAD_ADDRESS) as *const skyline::libc::c_void,
|
||||
hazard_intercept as *const skyline::libc::c_void,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ use smash::phx::{Hash40, Vector3f};
|
|||
use crate::common::{consts::*, *};
|
||||
use crate::logging::*;
|
||||
|
||||
use training_mod_sync::*;
|
||||
|
||||
pub const ID_COLORS: &[Vector3f] = &[
|
||||
// used to tint the hitbox effects -- make sure that at least one component
|
||||
// is equal to 1.0
|
||||
|
@ -135,7 +137,7 @@ pub unsafe fn get_command_flag_cat(module_accessor: &mut app::BattleObjectModule
|
|||
// Resume Effect AnimCMD incase we don't display hitboxes
|
||||
MotionAnimcmdModule::set_sleep_effect(module_accessor, false);
|
||||
|
||||
if MENU.hitbox_vis == OnOff::OFF {
|
||||
if read(&MENU).hitbox_vis == OnOff::OFF {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -205,7 +207,7 @@ unsafe fn mod_handle_attack(lua_state: u64) {
|
|||
|
||||
// necessary if param object fails
|
||||
// hacky way of forcing no shield damage on all hitboxes
|
||||
if MENU.shield_state == Shield::INFINITE {
|
||||
if read(&MENU).shield_state == Shield::INFINITE {
|
||||
let mut hitbox_params: Vec<L2CValue> =
|
||||
(0..36).map(|i| l2c_agent.pop_lua_stack(i + 1)).collect();
|
||||
l2c_agent.clear_lua_stack();
|
||||
|
@ -219,7 +221,7 @@ unsafe fn mod_handle_attack(lua_state: u64) {
|
|||
}
|
||||
|
||||
// Hitbox Visualization
|
||||
if MENU.hitbox_vis == OnOff::ON {
|
||||
if read(&MENU).hitbox_vis == OnOff::ON {
|
||||
// get all necessary grabbox params
|
||||
let id = l2c_agent.pop_lua_stack(1); // int
|
||||
let joint = l2c_agent.pop_lua_stack(3); // hash40
|
||||
|
@ -274,7 +276,7 @@ unsafe fn handle_catch(lua_state: u64) {
|
|||
}
|
||||
|
||||
unsafe fn mod_handle_catch(lua_state: u64) {
|
||||
if MENU.hitbox_vis == OnOff::OFF {
|
||||
if read(&MENU).hitbox_vis == OnOff::OFF {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
71
src/lib.rs
71
src/lib.rs
|
@ -21,6 +21,7 @@ use std::path::PathBuf;
|
|||
|
||||
use skyline::nro::{self, NroInfo};
|
||||
use training_mod_consts::{OnOff, LEGACY_TRAINING_MODPACK_ROOT};
|
||||
use training_mod_sync::*;
|
||||
|
||||
use crate::common::button_config::DEFAULT_OPEN_MENU_CONFIG;
|
||||
use crate::common::events::events_loop;
|
||||
|
@ -75,10 +76,10 @@ pub fn main() {
|
|||
|
||||
info!("Initialized.");
|
||||
|
||||
unsafe {
|
||||
EVENT_QUEUE.push(Event::smash_open());
|
||||
notification("Training Modpack".to_string(), "Welcome!".to_string(), 60);
|
||||
}
|
||||
let mut event_queue = lock_write(&EVENT_QUEUE);
|
||||
(*event_queue).push(Event::smash_open());
|
||||
drop(event_queue);
|
||||
notification("Training Modpack".to_string(), "Welcome!".to_string(), 60);
|
||||
|
||||
hitbox_visualizer::hitbox_visualization();
|
||||
hazard_manager::hazard_manager();
|
||||
|
@ -125,38 +126,36 @@ pub fn main() {
|
|||
info!("Skipping version check because we are using an emulator");
|
||||
}
|
||||
|
||||
unsafe {
|
||||
notification("Training Modpack".to_string(), "Welcome!".to_string(), 60);
|
||||
notification(
|
||||
"Open Menu".to_string(),
|
||||
if MENU.menu_open_start_press == OnOff::ON {
|
||||
"Hold Start".to_string()
|
||||
} else {
|
||||
DEFAULT_OPEN_MENU_CONFIG.to_string()
|
||||
},
|
||||
120,
|
||||
);
|
||||
notification(
|
||||
"Save State".to_string(),
|
||||
MENU.save_state_save.to_string(),
|
||||
120,
|
||||
);
|
||||
notification(
|
||||
"Load State".to_string(),
|
||||
MENU.save_state_load.to_string(),
|
||||
120,
|
||||
);
|
||||
notification(
|
||||
"Input Record".to_string(),
|
||||
MENU.input_record.to_string(),
|
||||
120,
|
||||
);
|
||||
notification(
|
||||
"Input Playback".to_string(),
|
||||
MENU.input_playback.to_string(),
|
||||
120,
|
||||
);
|
||||
}
|
||||
notification("Training Modpack".to_string(), "Welcome!".to_string(), 60);
|
||||
notification(
|
||||
"Open Menu".to_string(),
|
||||
if read(&MENU).menu_open_start_press == OnOff::ON {
|
||||
"Hold Start".to_string()
|
||||
} else {
|
||||
DEFAULT_OPEN_MENU_CONFIG.to_string()
|
||||
},
|
||||
120,
|
||||
);
|
||||
notification(
|
||||
"Save State".to_string(),
|
||||
read(&MENU).save_state_save.to_string(),
|
||||
120,
|
||||
);
|
||||
notification(
|
||||
"Load State".to_string(),
|
||||
read(&MENU).save_state_load.to_string(),
|
||||
120,
|
||||
);
|
||||
notification(
|
||||
"Input Record".to_string(),
|
||||
read(&MENU).input_record.to_string(),
|
||||
120,
|
||||
);
|
||||
notification(
|
||||
"Input Playback".to_string(),
|
||||
read(&MENU).input_playback.to_string(),
|
||||
120,
|
||||
);
|
||||
|
||||
std::thread::spawn(events_loop);
|
||||
}
|
||||
|
|
|
@ -6,8 +6,9 @@ use smash::lib::lua_const::*;
|
|||
use crate::common::consts::*;
|
||||
use crate::common::*;
|
||||
use crate::training::directional_influence::should_reverse_angle;
|
||||
use training_mod_sync::*;
|
||||
|
||||
static mut STICK_DIRECTION: Direction = Direction::empty();
|
||||
static AIRDODGE_STICK_DIRECTION: RwLock<Direction> = RwLock::new(Direction::empty());
|
||||
|
||||
pub unsafe fn mod_get_stick_x(
|
||||
module_accessor: &mut app::BattleObjectModuleAccessor,
|
||||
|
@ -31,9 +32,13 @@ unsafe fn get_angle(module_accessor: &mut app::BattleObjectModuleAccessor) -> Op
|
|||
return None;
|
||||
}
|
||||
|
||||
STICK_DIRECTION = MENU.air_dodge_dir.get_random();
|
||||
STICK_DIRECTION.into_angle().map(|angle| {
|
||||
if !should_reverse_angle(STICK_DIRECTION) {
|
||||
assign(
|
||||
&AIRDODGE_STICK_DIRECTION,
|
||||
read(&MENU).air_dodge_dir.get_random(),
|
||||
);
|
||||
let direction = read(&AIRDODGE_STICK_DIRECTION);
|
||||
direction.into_angle().map(|angle| {
|
||||
if !should_reverse_angle(direction) {
|
||||
// Direction is LEFT/RIGHT, so don't perform any adjustment
|
||||
angle
|
||||
} else {
|
||||
|
|
|
@ -2,13 +2,15 @@ use smash::app::{self};
|
|||
|
||||
use crate::common::consts::*;
|
||||
use crate::common::*;
|
||||
use training_mod_sync::*;
|
||||
|
||||
static mut DIRECTION: AttackAngle = AttackAngle::NEUTRAL;
|
||||
static ATTACK_ANGLE_DIRECTION: RwLock<AttackAngle> = RwLock::new(AttackAngle::NEUTRAL);
|
||||
|
||||
pub fn roll_direction() {
|
||||
unsafe {
|
||||
DIRECTION = MENU.attack_angle.get_random();
|
||||
}
|
||||
assign(
|
||||
&ATTACK_ANGLE_DIRECTION,
|
||||
read(&MENU).attack_angle.get_random(),
|
||||
);
|
||||
}
|
||||
|
||||
pub unsafe fn mod_get_stick_dir(
|
||||
|
@ -18,7 +20,7 @@ pub unsafe fn mod_get_stick_dir(
|
|||
return None;
|
||||
}
|
||||
|
||||
match DIRECTION {
|
||||
match read(&ATTACK_ANGLE_DIRECTION) {
|
||||
AttackAngle::UP => Some(1.0),
|
||||
AttackAngle::DOWN => Some(-1.0),
|
||||
_ => None,
|
||||
|
|
|
@ -8,60 +8,59 @@ use crate::is_operation_cpu;
|
|||
use crate::training::frame_counter;
|
||||
use crate::training::handle_add_limit;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use training_mod_sync::*;
|
||||
|
||||
static mut BUFF_REMAINING_PLAYER: usize = 0;
|
||||
static mut BUFF_REMAINING_CPU: usize = 0;
|
||||
static BUFF_REMAINING_PLAYER: RwLock<usize> = RwLock::new(0);
|
||||
static BUFF_REMAINING_CPU: RwLock<usize> = RwLock::new(0);
|
||||
|
||||
static mut IS_BUFFING_PLAYER: bool = false;
|
||||
static mut IS_BUFFING_CPU: bool = false;
|
||||
static IS_BUFFING_PLAYER: RwLock<bool> = RwLock::new(false);
|
||||
static IS_BUFFING_CPU: RwLock<bool> = RwLock::new(false);
|
||||
|
||||
static BUFF_DELAY_COUNTER: Lazy<usize> =
|
||||
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
|
||||
static BUFF_DELAY_COUNTER: LazyLock<usize> =
|
||||
LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
|
||||
|
||||
pub unsafe fn restart_buff(module_accessor: &mut app::BattleObjectModuleAccessor) {
|
||||
pub fn restart_buff(module_accessor: &mut app::BattleObjectModuleAccessor) {
|
||||
if is_operation_cpu(module_accessor) {
|
||||
IS_BUFFING_CPU = false;
|
||||
return;
|
||||
assign(&IS_BUFFING_CPU, false);
|
||||
} else {
|
||||
assign(&IS_BUFFING_PLAYER, false);
|
||||
}
|
||||
IS_BUFFING_PLAYER = false;
|
||||
}
|
||||
|
||||
pub unsafe fn start_buff(module_accessor: &mut app::BattleObjectModuleAccessor) {
|
||||
pub fn start_buff(module_accessor: &mut app::BattleObjectModuleAccessor) {
|
||||
if is_operation_cpu(module_accessor) {
|
||||
IS_BUFFING_CPU = true;
|
||||
return;
|
||||
assign(&IS_BUFFING_CPU, true);
|
||||
} else {
|
||||
assign(&IS_BUFFING_PLAYER, true);
|
||||
}
|
||||
IS_BUFFING_PLAYER = true;
|
||||
}
|
||||
|
||||
pub unsafe fn is_buffing(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
|
||||
pub fn is_buffing(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
|
||||
if is_operation_cpu(module_accessor) {
|
||||
return IS_BUFFING_CPU;
|
||||
read(&IS_BUFFING_CPU)
|
||||
} else {
|
||||
read(&IS_BUFFING_PLAYER)
|
||||
}
|
||||
IS_BUFFING_PLAYER
|
||||
}
|
||||
|
||||
pub unsafe fn is_buffing_any() -> bool {
|
||||
IS_BUFFING_CPU || IS_BUFFING_PLAYER
|
||||
pub fn is_buffing_any() -> bool {
|
||||
read(&IS_BUFFING_CPU) || read(&IS_BUFFING_PLAYER)
|
||||
}
|
||||
|
||||
pub unsafe fn set_buff_rem(
|
||||
module_accessor: &mut app::BattleObjectModuleAccessor,
|
||||
new_value: usize,
|
||||
) {
|
||||
pub fn set_buff_rem(module_accessor: &mut app::BattleObjectModuleAccessor, new_value: usize) {
|
||||
if is_operation_cpu(module_accessor) {
|
||||
BUFF_REMAINING_CPU = new_value;
|
||||
return;
|
||||
assign(&BUFF_REMAINING_CPU, new_value);
|
||||
} else {
|
||||
assign(&BUFF_REMAINING_PLAYER, new_value);
|
||||
}
|
||||
BUFF_REMAINING_PLAYER = new_value;
|
||||
}
|
||||
|
||||
pub unsafe fn get_buff_rem(module_accessor: &mut app::BattleObjectModuleAccessor) -> usize {
|
||||
pub fn get_buff_rem(module_accessor: &mut app::BattleObjectModuleAccessor) -> usize {
|
||||
if is_operation_cpu(module_accessor) {
|
||||
return BUFF_REMAINING_CPU;
|
||||
read(&BUFF_REMAINING_CPU)
|
||||
} else {
|
||||
read(&BUFF_REMAINING_PLAYER)
|
||||
}
|
||||
BUFF_REMAINING_PLAYER
|
||||
}
|
||||
|
||||
pub unsafe fn handle_buffs(
|
||||
|
@ -79,7 +78,7 @@ pub unsafe fn handle_buffs(
|
|||
CameraModule::stop_quake(module_accessor, *CAMERA_QUAKE_KIND_M); // stops Psyche-Up quake
|
||||
CameraModule::stop_quake(module_accessor, *CAMERA_QUAKE_KIND_S); // stops Monado Art quake
|
||||
|
||||
let menu_vec = MENU.buff_state;
|
||||
let menu_vec = read(&MENU).buff_state;
|
||||
|
||||
if fighter_kind == *FIGHTER_KIND_BRAVE {
|
||||
return buff_hero(module_accessor, status);
|
||||
|
@ -104,7 +103,7 @@ pub unsafe fn handle_buffs(
|
|||
}
|
||||
|
||||
unsafe fn buff_hero(module_accessor: &mut app::BattleObjectModuleAccessor, status: i32) -> bool {
|
||||
let buff_vec: Vec<BuffOption> = MENU.buff_state.hero_buffs().to_vec();
|
||||
let buff_vec: Vec<BuffOption> = read(&MENU).buff_state.hero_buffs().to_vec();
|
||||
if !is_buffing(module_accessor) {
|
||||
// Initial set up for spells
|
||||
start_buff(module_accessor);
|
||||
|
@ -241,7 +240,7 @@ unsafe fn buff_sepiroth(module_accessor: &mut app::BattleObjectModuleAccessor) -
|
|||
|
||||
unsafe fn buff_wario(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
|
||||
if !is_buffing(module_accessor) {
|
||||
let waft_level: BuffOption = MENU.buff_state.wario_buffs().get_random();
|
||||
let waft_level: BuffOption = read(&MENU).buff_state.wario_buffs().get_random();
|
||||
let waft_count_secs = match waft_level {
|
||||
BuffOption::WAFT_MINI => WorkModule::get_param_float(
|
||||
module_accessor,
|
||||
|
@ -277,7 +276,7 @@ unsafe fn buff_wario(module_accessor: &mut app::BattleObjectModuleAccessor) -> b
|
|||
}
|
||||
|
||||
unsafe fn buff_shulk(module_accessor: &mut app::BattleObjectModuleAccessor, status: i32) -> bool {
|
||||
let current_art = MENU.buff_state.shulk_buffs().get_random();
|
||||
let current_art = read(&MENU).buff_state.shulk_buffs().get_random();
|
||||
if current_art == BuffOption::empty() {
|
||||
// No Monado Arts selected in the buff menu, so we don't need to buff
|
||||
return true;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// TODO!() There's some crazy pointer magic happening in this file, we should try to refactor to avoid that
|
||||
use smash::app;
|
||||
use smash::app::lua_bind::*;
|
||||
use smash::app::ItemKind;
|
||||
|
@ -9,7 +10,10 @@ use crate::common::consts::*;
|
|||
use crate::common::*;
|
||||
use crate::offsets::OFFSET_GENERATE_ARTICLE_FOR_TARGET;
|
||||
use crate::training::mash;
|
||||
use training_mod_sync::*;
|
||||
|
||||
pub static TURNIP_CHOSEN: RwLock<Option<u32>> = RwLock::new(None);
|
||||
pub static TARGET_PLAYER: RwLock<Option<BattleObjectModuleAccessor>> = RwLock::new(None);
|
||||
pub struct CharItem {
|
||||
pub fighter_kind: LuaConst,
|
||||
pub item_kind: Option<LuaConst>,
|
||||
|
@ -326,9 +330,6 @@ pub const ALL_CHAR_ITEMS: [CharItem; 45] = [
|
|||
},
|
||||
];
|
||||
|
||||
pub static mut TURNIP_CHOSEN: Option<u32> = None;
|
||||
pub static mut TARGET_PLAYER: Option<*mut BattleObjectModuleAccessor> = None;
|
||||
|
||||
unsafe fn apply_single_item(player_fighter_kind: i32, item: &CharItem) {
|
||||
let player_module_accessor = get_module_accessor(FighterId::Player);
|
||||
let cpu_module_accessor = get_module_accessor(FighterId::CPU);
|
||||
|
@ -352,7 +353,7 @@ unsafe fn apply_single_item(player_fighter_kind: i32, item: &CharItem) {
|
|||
if player_fighter_kind != *FIGHTER_KIND_LINK {
|
||||
ItemModule::drop_item(cpu_module_accessor, 0.0, 0.0, 0);
|
||||
//ItemModule::eject_have_item(cpu_module_accessor, 0, false, false);
|
||||
let item_mgr = *(ITEM_MANAGER_ADDR as *mut *mut app::ItemManager);
|
||||
let item_mgr = *(read(&ITEM_MANAGER_ADDR) as *mut *mut app::ItemManager);
|
||||
let item_ptr = ItemManager::get_active_item(item_mgr, 0);
|
||||
ItemModule::have_item_instance(
|
||||
player_module_accessor,
|
||||
|
@ -377,25 +378,26 @@ unsafe fn apply_single_item(player_fighter_kind: i32, item: &CharItem) {
|
|||
});
|
||||
|
||||
item.article_kind.as_ref().map(|article_kind| {
|
||||
TURNIP_CHOSEN = if [*ITEM_VARIATION_PEACHDAIKON_8, *ITEM_VARIATION_DAISYDAIKON_8]
|
||||
.contains(&variation)
|
||||
{
|
||||
Some(8)
|
||||
} else if [*ITEM_VARIATION_PEACHDAIKON_7, *ITEM_VARIATION_DAISYDAIKON_7]
|
||||
.contains(&variation)
|
||||
{
|
||||
Some(7)
|
||||
} else if [*ITEM_VARIATION_PEACHDAIKON_6, *ITEM_VARIATION_DAISYDAIKON_6]
|
||||
.contains(&variation)
|
||||
{
|
||||
Some(6)
|
||||
} else if [*ITEM_VARIATION_PEACHDAIKON_1, *ITEM_VARIATION_DAISYDAIKON_1]
|
||||
.contains(&variation)
|
||||
{
|
||||
Some(1)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
assign(
|
||||
&TURNIP_CHOSEN,
|
||||
if [*ITEM_VARIATION_PEACHDAIKON_8, *ITEM_VARIATION_DAISYDAIKON_8].contains(&variation) {
|
||||
Some(8)
|
||||
} else if [*ITEM_VARIATION_PEACHDAIKON_7, *ITEM_VARIATION_DAISYDAIKON_7]
|
||||
.contains(&variation)
|
||||
{
|
||||
Some(7)
|
||||
} else if [*ITEM_VARIATION_PEACHDAIKON_6, *ITEM_VARIATION_DAISYDAIKON_6]
|
||||
.contains(&variation)
|
||||
{
|
||||
Some(6)
|
||||
} else if [*ITEM_VARIATION_PEACHDAIKON_1, *ITEM_VARIATION_DAISYDAIKON_1]
|
||||
.contains(&variation)
|
||||
{
|
||||
Some(1)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
);
|
||||
|
||||
let article_kind = **article_kind;
|
||||
if article_kind == FIGHTER_DIDDY_GENERATE_ARTICLE_ITEM_BANANA {
|
||||
|
@ -416,7 +418,7 @@ unsafe fn apply_single_item(player_fighter_kind: i32, item: &CharItem) {
|
|||
false,
|
||||
);
|
||||
// Grab item from the middle of the stage where it gets shot
|
||||
let item_mgr = *(ITEM_MANAGER_ADDR as *mut *mut app::ItemManager);
|
||||
let item_mgr = *(read(&ITEM_MANAGER_ADDR) as *mut *mut app::ItemManager);
|
||||
let item = ItemManager::get_active_item(item_mgr, 0);
|
||||
ItemModule::have_item_instance(
|
||||
player_module_accessor,
|
||||
|
@ -428,16 +430,18 @@ unsafe fn apply_single_item(player_fighter_kind: i32, item: &CharItem) {
|
|||
false,
|
||||
);
|
||||
} else {
|
||||
TARGET_PLAYER = Some(player_module_accessor); // set so we generate CPU article on the player (in dittos, items always belong to player, even if cpu item is chosen)
|
||||
// Set the target player so we generate CPU article on the player during handle_generate_article_for_target
|
||||
// (in dittos, items always belong to player, even if cpu item is chosen)
|
||||
assign(&TARGET_PLAYER, Some(*player_module_accessor));
|
||||
ArticleModule::generate_article(
|
||||
generator_module_accessor, // we want CPU's article
|
||||
article_kind,
|
||||
false,
|
||||
0,
|
||||
);
|
||||
TARGET_PLAYER = None;
|
||||
assign(&TARGET_PLAYER, None);
|
||||
}
|
||||
TURNIP_CHOSEN = None;
|
||||
assign(&TURNIP_CHOSEN, None);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -479,9 +483,10 @@ macro_rules! daikon_replace {
|
|||
pub unsafe fn [<handle_ $char daikon_ $num _prob>]() -> f32 {
|
||||
let orig = original!()();
|
||||
if is_training_mode() {
|
||||
if TURNIP_CHOSEN == Some($num) {
|
||||
let turnip_chosen = read(&TURNIP_CHOSEN);
|
||||
if turnip_chosen == Some($num) {
|
||||
return 58.0;
|
||||
} else if TURNIP_CHOSEN != None {
|
||||
} else if turnip_chosen != None {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
@ -512,14 +517,14 @@ daikon_replace!(DAISY, daisy, 1);
|
|||
// GenerateArticleForTarget for Peach/Diddy(/Link?) item creation
|
||||
#[skyline::hook(offset = *OFFSET_GENERATE_ARTICLE_FOR_TARGET)]
|
||||
pub unsafe fn handle_generate_article_for_target(
|
||||
article_module_accessor: *mut BattleObjectModuleAccessor,
|
||||
article_module_accessor: BattleObjectModuleAccessor,
|
||||
int_1: i32,
|
||||
module_accessor: *mut BattleObjectModuleAccessor, // this is always 0x0 normally
|
||||
module_accessor: BattleObjectModuleAccessor, // this is always 0x0 normally
|
||||
bool_1: bool,
|
||||
int_2: i32,
|
||||
) -> u64 {
|
||||
// unknown return value, gets cast to an (Article *)
|
||||
let target_module_accessor = TARGET_PLAYER.unwrap_or(module_accessor);
|
||||
let target_module_accessor = read(&TARGET_PLAYER).unwrap_or(module_accessor);
|
||||
|
||||
original!()(
|
||||
article_module_accessor,
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
use crate::offsets::OFFSET_POKEMON_DECIDE;
|
||||
use crate::training::frame_counter;
|
||||
use crate::training::save_states;
|
||||
use once_cell::sync::Lazy;
|
||||
use skyline::hooks::InlineCtx;
|
||||
use smash::app::{self, lua_bind::*, smashball::is_training_mode};
|
||||
use smash::hash40;
|
||||
use smash::lib::lua_const::*;
|
||||
use smash::phx::Hash40;
|
||||
|
||||
static SWITCH_DELAY_COUNTER: Lazy<usize> =
|
||||
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
|
||||
use training_mod_sync::LazyLock;
|
||||
|
||||
static SWITCH_DELAY_COUNTER: LazyLock<usize> =
|
||||
LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
|
||||
|
||||
pub unsafe fn is_switched(ptrainer_module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
|
||||
let status_kind = StatusModule::status_kind(ptrainer_module_accessor);
|
||||
|
|
|
@ -5,12 +5,14 @@ use smash::phx::{Hash40, Vector3f};
|
|||
|
||||
use crate::common::consts::*;
|
||||
use crate::common::*;
|
||||
use training_mod_sync::*;
|
||||
|
||||
static mut COUNTER: u32 = 0;
|
||||
static mut CLATTER_STEP: f32 = 8.0;
|
||||
static COUNTER: RwLock<u32> = RwLock::new(0);
|
||||
static CLATTER_STEP: RwLock<f32> = RwLock::new(8.0);
|
||||
|
||||
unsafe fn do_clatter_input(module_accessor: &mut BattleObjectModuleAccessor) {
|
||||
ControlModule::add_clatter_time(module_accessor, -1.0 * CLATTER_STEP, 0);
|
||||
let clatter_step = read(&CLATTER_STEP);
|
||||
ControlModule::add_clatter_time(module_accessor, -1.0 * clatter_step, 0);
|
||||
let zeros = Vector3f {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
|
@ -45,10 +47,11 @@ pub unsafe fn handle_clatter(module_accessor: &mut BattleObjectModuleAccessor) {
|
|||
// Don't do clatter inputs if we're not in clatter
|
||||
return;
|
||||
}
|
||||
let repeat = MENU.clatter_strength.into_u32();
|
||||
let repeat = read(&MENU).clatter_strength.into_u32();
|
||||
|
||||
COUNTER = (COUNTER + 1) % repeat;
|
||||
if COUNTER == repeat - 1 {
|
||||
let mut counter_lock = lock_write(&COUNTER);
|
||||
*counter_lock = ((*counter_lock) + 1) % repeat;
|
||||
if *counter_lock == repeat - 1 {
|
||||
do_clatter_input(module_accessor);
|
||||
}
|
||||
}
|
||||
|
@ -71,7 +74,7 @@ pub unsafe fn hook_start_clatter(
|
|||
// Most of the time this is 8 frames, but could be less depending on
|
||||
// the status (e.g. freeze is 4 frames / input)
|
||||
if is_training_mode() && is_operation_cpu(module_accessor) {
|
||||
CLATTER_STEP = manual_recovery_rate;
|
||||
assign(&CLATTER_STEP, manual_recovery_rate);
|
||||
}
|
||||
original!()(
|
||||
module_accessor,
|
||||
|
|
|
@ -1,168 +1,174 @@
|
|||
use skyline::nn::ui2d::ResColor;
|
||||
use training_mod_consts::OnOff;
|
||||
use smash::app::lua_bind::{CancelModule, StatusModule, WorkModule};
|
||||
use smash::app::BattleObjectModuleAccessor;
|
||||
use smash::lib::lua_const::*;
|
||||
|
||||
use crate::common::*;
|
||||
use crate::training::*;
|
||||
use crate::consts::Action;
|
||||
use crate::get_module_accessor;
|
||||
use crate::training::frame_counter;
|
||||
use crate::training::ui::notifications;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use training_mod_consts::{FighterId, OnOff, MENU};
|
||||
use training_mod_sync::*;
|
||||
|
||||
pub static mut FRAME_ADVANTAGE: i32 = 0;
|
||||
static mut PLAYER_ACTIONABLE: bool = false;
|
||||
static mut CPU_ACTIONABLE: bool = false;
|
||||
static mut PLAYER_ACTIVE_FRAME: u32 = 0;
|
||||
static mut CPU_ACTIVE_FRAME: u32 = 0;
|
||||
static mut FRAME_ADVANTAGE_CHECK: bool = false;
|
||||
static PLAYER_WAS_ACTIONABLE: RwLock<bool> = RwLock::new(false);
|
||||
static CPU_WAS_ACTIONABLE: RwLock<bool> = RwLock::new(false);
|
||||
|
||||
static FRAME_COUNTER_INDEX: Lazy<usize> =
|
||||
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
|
||||
static PLAYER_FRAME_COUNTER_INDEX: LazyLock<usize> =
|
||||
LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
|
||||
static CPU_FRAME_COUNTER_INDEX: LazyLock<usize> =
|
||||
LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
|
||||
|
||||
unsafe fn _was_in_hitstun(module_accessor: *mut BattleObjectModuleAccessor) -> bool {
|
||||
unsafe fn was_in_hitstun(module_accessor: *mut BattleObjectModuleAccessor) -> bool {
|
||||
let prev_status = StatusModule::prev_status_kind(module_accessor, 0);
|
||||
(*FIGHTER_STATUS_KIND_DAMAGE..*FIGHTER_STATUS_KIND_DAMAGE_FALL).contains(&prev_status)
|
||||
}
|
||||
|
||||
unsafe fn is_in_hitstun(module_accessor: *mut BattleObjectModuleAccessor) -> bool {
|
||||
(*FIGHTER_STATUS_KIND_DAMAGE..*FIGHTER_STATUS_KIND_DAMAGE_FALL)
|
||||
.contains(&StatusModule::status_kind(module_accessor))
|
||||
}
|
||||
|
||||
unsafe fn was_in_shieldstun(module_accessor: *mut BattleObjectModuleAccessor) -> bool {
|
||||
let prev_status = StatusModule::prev_status_kind(module_accessor, 0);
|
||||
prev_status == FIGHTER_STATUS_KIND_GUARD_DAMAGE
|
||||
}
|
||||
|
||||
macro_rules! actionable_statuses {
|
||||
() => {
|
||||
[
|
||||
FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_ESCAPE_AIR,
|
||||
FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_ATTACK_AIR,
|
||||
FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_GUARD_ON,
|
||||
FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_ESCAPE,
|
||||
]
|
||||
};
|
||||
unsafe fn is_in_shieldstun(module_accessor: *mut BattleObjectModuleAccessor) -> bool {
|
||||
StatusModule::status_kind(module_accessor) == FIGHTER_STATUS_KIND_GUARD_DAMAGE
|
||||
}
|
||||
|
||||
unsafe fn is_actionable(module_accessor: *mut BattleObjectModuleAccessor) -> bool {
|
||||
actionable_statuses!().iter().any(|actionable_transition| {
|
||||
[
|
||||
FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_ESCAPE_AIR, // Airdodge
|
||||
FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_ATTACK_AIR, // Aerial
|
||||
FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_GUARD_ON, // Shield
|
||||
FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_ESCAPE, // Spotdodge/Roll
|
||||
FIGHTER_STATUS_TRANSITION_TERM_ID_DOWN_STAND, // Neutral Getup from Tech/Slip
|
||||
]
|
||||
.iter()
|
||||
.any(|actionable_transition| {
|
||||
WorkModule::is_enable_transition_term(module_accessor, **actionable_transition)
|
||||
}) || CancelModule::is_enable_cancel(module_accessor)
|
||||
}
|
||||
|
||||
fn update_frame_advantage(new_frame_adv: i32) {
|
||||
unsafe {
|
||||
FRAME_ADVANTAGE = new_frame_adv;
|
||||
if MENU.frame_advantage == OnOff::ON {
|
||||
// Prioritize Frame Advantage over Input Recording Playback
|
||||
ui::notifications::clear_notifications("Input Recording");
|
||||
ui::notifications::clear_notifications("Frame Advantage");
|
||||
ui::notifications::color_notification(
|
||||
"Frame Advantage".to_string(),
|
||||
format!("{FRAME_ADVANTAGE}"),
|
||||
60,
|
||||
match FRAME_ADVANTAGE {
|
||||
x if x < 0 => ResColor {
|
||||
r: 200,
|
||||
g: 8,
|
||||
b: 8,
|
||||
a: 255,
|
||||
},
|
||||
0 => ResColor {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 255,
|
||||
},
|
||||
_ => ResColor {
|
||||
r: 31,
|
||||
g: 198,
|
||||
b: 0,
|
||||
a: 255,
|
||||
},
|
||||
fn update_frame_advantage(frame_advantage: i32) {
|
||||
if read(&MENU).frame_advantage == OnOff::ON {
|
||||
// Prioritize Frame Advantage over Input Recording Playback
|
||||
notifications::clear_notification("Input Recording");
|
||||
notifications::clear_notification("Frame Advantage");
|
||||
notifications::color_notification(
|
||||
"Frame Advantage".to_string(),
|
||||
format!("{frame_advantage}"),
|
||||
60,
|
||||
match frame_advantage {
|
||||
x if x < 0 => ResColor {
|
||||
r: 200,
|
||||
g: 8,
|
||||
b: 8,
|
||||
a: 255,
|
||||
},
|
||||
);
|
||||
}
|
||||
0 => ResColor {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 255,
|
||||
},
|
||||
_ => ResColor {
|
||||
r: 31,
|
||||
g: 198,
|
||||
b: 0,
|
||||
a: 255,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn is_enable_transition_term(
|
||||
module_accessor: *mut BattleObjectModuleAccessor,
|
||||
transition_term: i32,
|
||||
is: bool,
|
||||
) {
|
||||
pub unsafe fn once_per_frame(module_accessor: &mut BattleObjectModuleAccessor) {
|
||||
// Skip the CPU so we don't run twice per frame
|
||||
let entry_id_int = WorkModule::get_int(module_accessor, *FIGHTER_INSTANCE_WORK_ID_INT_ENTRY_ID);
|
||||
|
||||
if entry_id_int != (FighterId::Player as i32) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Extra check later in the frame.
|
||||
// This is in the case that the transition term becomes enabled after our initial check
|
||||
// and the user buffers that action on that frame.
|
||||
|
||||
if !PLAYER_ACTIONABLE
|
||||
&& ((is
|
||||
&& actionable_statuses!()
|
||||
.iter()
|
||||
.any(|actionable_transition| *actionable_transition == transition_term))
|
||||
|| (CancelModule::is_enable_cancel(module_accessor)))
|
||||
{
|
||||
PLAYER_ACTIVE_FRAME = frame_counter::get_frame_count(*FRAME_COUNTER_INDEX);
|
||||
PLAYER_ACTIONABLE = true;
|
||||
|
||||
// if both are now active
|
||||
if PLAYER_ACTIONABLE && CPU_ACTIONABLE && FRAME_ADVANTAGE_CHECK {
|
||||
let cpu_module_accessor = get_module_accessor(FighterId::CPU);
|
||||
if was_in_shieldstun(cpu_module_accessor) {
|
||||
update_frame_advantage(
|
||||
(CPU_ACTIVE_FRAME as i64 - PLAYER_ACTIVE_FRAME as i64) as i32,
|
||||
);
|
||||
}
|
||||
|
||||
frame_counter::stop_counting(*FRAME_COUNTER_INDEX);
|
||||
FRAME_ADVANTAGE_CHECK = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn get_command_flag_cat(module_accessor: &mut BattleObjectModuleAccessor) {
|
||||
let entry_id_int = WorkModule::get_int(module_accessor, *FIGHTER_INSTANCE_WORK_ID_INT_ENTRY_ID);
|
||||
// do only once.
|
||||
if entry_id_int != (FighterId::Player as i32) {
|
||||
return;
|
||||
}
|
||||
|
||||
let player_module_accessor = get_module_accessor(FighterId::Player);
|
||||
let cpu_module_accessor = get_module_accessor(FighterId::CPU);
|
||||
let player_is_actionable = is_actionable(player_module_accessor);
|
||||
let player_was_actionable = read(&PLAYER_WAS_ACTIONABLE);
|
||||
let player_just_actionable = !player_was_actionable && player_is_actionable;
|
||||
let cpu_is_actionable = is_actionable(cpu_module_accessor);
|
||||
let cpu_was_actionable = read(&CPU_WAS_ACTIONABLE);
|
||||
let cpu_just_actionable = !cpu_was_actionable && cpu_is_actionable;
|
||||
let is_counting = frame_counter::is_counting(*PLAYER_FRAME_COUNTER_INDEX)
|
||||
|| frame_counter::is_counting(*CPU_FRAME_COUNTER_INDEX);
|
||||
|
||||
// Use to factor in that we should only update frame advantage if
|
||||
// there's been a hit that connects
|
||||
// if AttackModule::is_infliction(
|
||||
// player_module_accessor,
|
||||
// *COLLISION_KIND_MASK_HIT | *COLLISION_KIND_MASK_SHIELD) {
|
||||
|
||||
// the frame the fighter *becomes* actionable
|
||||
if !CPU_ACTIONABLE && is_actionable(cpu_module_accessor) {
|
||||
CPU_ACTIVE_FRAME = frame_counter::get_frame_count(*FRAME_COUNTER_INDEX);
|
||||
}
|
||||
|
||||
if !PLAYER_ACTIONABLE && is_actionable(player_module_accessor) {
|
||||
PLAYER_ACTIVE_FRAME = frame_counter::get_frame_count(*FRAME_COUNTER_INDEX);
|
||||
}
|
||||
|
||||
CPU_ACTIONABLE = is_actionable(cpu_module_accessor);
|
||||
PLAYER_ACTIONABLE = is_actionable(player_module_accessor);
|
||||
|
||||
// if neither are active
|
||||
if !CPU_ACTIONABLE && !PLAYER_ACTIONABLE {
|
||||
if !FRAME_ADVANTAGE_CHECK {
|
||||
frame_counter::reset_frame_count(*FRAME_COUNTER_INDEX);
|
||||
frame_counter::start_counting(*FRAME_COUNTER_INDEX);
|
||||
if !is_counting {
|
||||
if read(&MENU).mash_state == Action::empty()
|
||||
&& !player_is_actionable
|
||||
&& !cpu_is_actionable
|
||||
&& (!was_in_shieldstun(cpu_module_accessor) && is_in_shieldstun(cpu_module_accessor)
|
||||
|| (!was_in_hitstun(cpu_module_accessor) && is_in_hitstun(cpu_module_accessor)))
|
||||
{
|
||||
// Start counting when:
|
||||
// 1. We have no mash option selected AND
|
||||
// 2. Neither fighter is currently actionable AND
|
||||
// 3. Either
|
||||
// a. the CPU has just entered shieldstun
|
||||
// b. the CPU has just entered hitstun
|
||||
//
|
||||
// If a mash option is selected, this can interfere with our ability to determine when
|
||||
// a character becomes actionable. So don't ever start counting if we can't reliably stop.
|
||||
//
|
||||
// Since our "just_actionable" checks assume that neither character is already actionable,
|
||||
// we need to guard against instances where the player is already actionable by the time that
|
||||
// the CPU get hit, such as if the player threw a projectile from far away.
|
||||
// Otherwise our "just_actionable" checks are not valid.
|
||||
//
|
||||
// We also need to guard against instances where the CPU's status is in hitstun but they are actually actionable.
|
||||
// I dunno, makes no sense to me either. Can trigger this edge case with PAC-MAN jab 1 against Lucas at 0%.
|
||||
// This shows up as the count restarting immediately after the last one ended.
|
||||
frame_counter::reset_frame_count(*PLAYER_FRAME_COUNTER_INDEX);
|
||||
frame_counter::reset_frame_count(*CPU_FRAME_COUNTER_INDEX);
|
||||
frame_counter::start_counting(*PLAYER_FRAME_COUNTER_INDEX);
|
||||
frame_counter::start_counting(*CPU_FRAME_COUNTER_INDEX);
|
||||
}
|
||||
FRAME_ADVANTAGE_CHECK = true;
|
||||
}
|
||||
} else {
|
||||
// Uncomment this if you want some frame logging
|
||||
// if (player_is_actionable && cpu_is_actionable) {
|
||||
// info!("!");
|
||||
// } else if (!player_is_actionable && cpu_is_actionable) {
|
||||
// info!("-");
|
||||
// } else if (player_is_actionable && !cpu_is_actionable) {
|
||||
// info!("+");
|
||||
// } else {
|
||||
// info!(".");
|
||||
// }
|
||||
|
||||
// if both are now active
|
||||
if PLAYER_ACTIONABLE && CPU_ACTIONABLE && FRAME_ADVANTAGE_CHECK {
|
||||
if was_in_shieldstun(cpu_module_accessor) {
|
||||
update_frame_advantage((CPU_ACTIVE_FRAME as i64 - PLAYER_ACTIVE_FRAME as i64) as i32);
|
||||
// Stop counting as soon as each fighter becomes actionable
|
||||
if player_just_actionable {
|
||||
frame_counter::stop_counting(*PLAYER_FRAME_COUNTER_INDEX);
|
||||
}
|
||||
|
||||
frame_counter::stop_counting(*FRAME_COUNTER_INDEX);
|
||||
FRAME_ADVANTAGE_CHECK = false;
|
||||
if cpu_just_actionable {
|
||||
frame_counter::stop_counting(*CPU_FRAME_COUNTER_INDEX);
|
||||
}
|
||||
|
||||
// If we just finished counting for the second fighter, then display frame advantage
|
||||
if !frame_counter::is_counting(*PLAYER_FRAME_COUNTER_INDEX)
|
||||
&& !frame_counter::is_counting(*CPU_FRAME_COUNTER_INDEX)
|
||||
&& (player_just_actionable || cpu_just_actionable)
|
||||
{
|
||||
update_frame_advantage(
|
||||
frame_counter::get_frame_count(*CPU_FRAME_COUNTER_INDEX) as i32
|
||||
- frame_counter::get_frame_count(*PLAYER_FRAME_COUNTER_INDEX) as i32,
|
||||
);
|
||||
// Frame counters should reset before we start again, but reset them just to be safe
|
||||
frame_counter::reset_frame_count(*PLAYER_FRAME_COUNTER_INDEX);
|
||||
frame_counter::reset_frame_count(*CPU_FRAME_COUNTER_INDEX);
|
||||
};
|
||||
|
||||
// Store the current actionability state for next frame
|
||||
assign(&PLAYER_WAS_ACTIONABLE, player_is_actionable);
|
||||
assign(&CPU_WAS_ACTIONABLE, cpu_is_actionable);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,13 +4,15 @@ use smash::lib::lua_const::*;
|
|||
use crate::common::consts::OnOff;
|
||||
use crate::common::*;
|
||||
|
||||
use training_mod_sync::*;
|
||||
|
||||
pub unsafe fn mod_get_stick_y(module_accessor: &mut BattleObjectModuleAccessor) -> Option<f32> {
|
||||
if !is_operation_cpu(module_accessor) {
|
||||
return None;
|
||||
}
|
||||
let fighter_status_kind = StatusModule::status_kind(module_accessor);
|
||||
|
||||
if MENU.crouch == OnOff::ON
|
||||
if read(&MENU).crouch == OnOff::ON
|
||||
&& [
|
||||
*FIGHTER_STATUS_KIND_WAIT,
|
||||
*FIGHTER_STATUS_KIND_SQUAT,
|
||||
|
|
|
@ -7,18 +7,17 @@ use smash::lua2cpp::L2CFighterCommon;
|
|||
|
||||
use crate::common::consts::*;
|
||||
use crate::common::*;
|
||||
use training_mod_sync::*;
|
||||
|
||||
static mut DI_CASE: Direction = Direction::empty();
|
||||
static DI_CASE: RwLock<Direction> = RwLock::new(Direction::empty());
|
||||
|
||||
pub fn roll_di_case() {
|
||||
unsafe {
|
||||
if DI_CASE != Direction::empty() {
|
||||
// DI direction already selected, don't pick a new one
|
||||
return;
|
||||
}
|
||||
|
||||
DI_CASE = MENU.di_state.get_random();
|
||||
let mut di_case_lock = lock_write(&DI_CASE);
|
||||
if *di_case_lock != Direction::empty() {
|
||||
// DI direction already selected, don't pick a new one
|
||||
return;
|
||||
}
|
||||
*di_case_lock = read(&MENU).di_state.get_random();
|
||||
}
|
||||
|
||||
pub fn reset_di_case(module_accessor: &mut app::BattleObjectModuleAccessor) {
|
||||
|
@ -26,10 +25,9 @@ pub fn reset_di_case(module_accessor: &mut app::BattleObjectModuleAccessor) {
|
|||
// Don't reset the DI direction during hitstun
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
if DI_CASE != Direction::empty() {
|
||||
DI_CASE = Direction::empty();
|
||||
}
|
||||
let mut di_case_lock = lock_write(&DI_CASE);
|
||||
if *di_case_lock != Direction::empty() {
|
||||
*di_case_lock = Direction::empty();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,7 +44,7 @@ pub unsafe fn handle_correct_damage_vector_common(
|
|||
}
|
||||
|
||||
unsafe fn mod_handle_di(fighter: &L2CFighterCommon, _arg1: L2CValue) {
|
||||
if MENU.di_state == Direction::empty() {
|
||||
if read(&MENU).di_state == Direction::empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -56,9 +54,9 @@ unsafe fn mod_handle_di(fighter: &L2CFighterCommon, _arg1: L2CValue) {
|
|||
}
|
||||
|
||||
roll_di_case();
|
||||
|
||||
let angle_tuple = DI_CASE.into_angle().map_or((0.0, 0.0), |angle| {
|
||||
let a = if should_reverse_angle(DI_CASE) {
|
||||
let di_case = read(&DI_CASE);
|
||||
let angle_tuple = di_case.into_angle().map_or((0.0, 0.0), |angle| {
|
||||
let a = if should_reverse_angle(di_case) {
|
||||
PI - angle
|
||||
} else {
|
||||
angle
|
||||
|
|
|
@ -5,26 +5,21 @@ use smash::phx::{Hash40, Vector3f};
|
|||
use crate::common::*;
|
||||
use crate::training::{frame_counter, input_record};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use training_mod_sync::*;
|
||||
|
||||
// The current fastfall delay
|
||||
static mut DELAY: u32 = 0;
|
||||
|
||||
static mut FAST_FALL: bool = false;
|
||||
static DELAY: RwLock<u32> = RwLock::new(0);
|
||||
static FAST_FALL: RwLock<bool> = RwLock::new(false);
|
||||
static FRAME_COUNTER_INDEX: LazyLock<usize> =
|
||||
LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
|
||||
|
||||
fn should_fast_fall() -> bool {
|
||||
unsafe { FAST_FALL }
|
||||
read(&FAST_FALL)
|
||||
}
|
||||
|
||||
pub fn roll_fast_fall() {
|
||||
unsafe {
|
||||
FAST_FALL = MENU.fast_fall.get_random().into_bool();
|
||||
}
|
||||
assign(&FAST_FALL, read(&MENU).fast_fall.get_random().into_bool());
|
||||
}
|
||||
|
||||
static FRAME_COUNTER_INDEX: Lazy<usize> =
|
||||
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
|
||||
|
||||
pub fn get_command_flag_cat(module_accessor: &mut app::BattleObjectModuleAccessor) {
|
||||
if !should_fast_fall() {
|
||||
return;
|
||||
|
@ -42,7 +37,10 @@ pub fn get_command_flag_cat(module_accessor: &mut app::BattleObjectModuleAccesso
|
|||
unsafe {
|
||||
if !is_falling(module_accessor) {
|
||||
// Roll FF delay
|
||||
DELAY = MENU.fast_fall_delay.get_random().into_delay();
|
||||
assign(
|
||||
&DELAY,
|
||||
read(&MENU).fast_fall_delay.get_random().into_delay(),
|
||||
);
|
||||
frame_counter::full_reset(*FRAME_COUNTER_INDEX);
|
||||
return;
|
||||
}
|
||||
|
@ -57,7 +55,8 @@ pub fn get_command_flag_cat(module_accessor: &mut app::BattleObjectModuleAccesso
|
|||
}
|
||||
|
||||
// Check delay
|
||||
if frame_counter::should_delay(DELAY, *FRAME_COUNTER_INDEX) {
|
||||
let delay = read(&DELAY);
|
||||
if frame_counter::should_delay(delay, *FRAME_COUNTER_INDEX) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
use training_mod_sync::*;
|
||||
|
||||
static COUNTERS: RwLock<Vec<FrameCounter>> = RwLock::new(vec![]);
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum FrameCounterType {
|
||||
InGame,
|
||||
|
@ -13,38 +16,35 @@ pub struct FrameCounter {
|
|||
counter_type: FrameCounterType,
|
||||
}
|
||||
|
||||
static mut COUNTERS: Vec<FrameCounter> = vec![];
|
||||
|
||||
pub fn register_counter(counter_type: FrameCounterType) -> usize {
|
||||
unsafe {
|
||||
let index = COUNTERS.len();
|
||||
|
||||
COUNTERS.push(FrameCounter {
|
||||
count: 0,
|
||||
should_count: false,
|
||||
counter_type,
|
||||
});
|
||||
|
||||
index
|
||||
}
|
||||
let mut counters_lock = lock_write(&COUNTERS);
|
||||
let index = (*counters_lock).len();
|
||||
(*counters_lock).push(FrameCounter {
|
||||
count: 0,
|
||||
should_count: false,
|
||||
counter_type,
|
||||
});
|
||||
index
|
||||
}
|
||||
|
||||
pub fn start_counting(index: usize) {
|
||||
unsafe {
|
||||
COUNTERS[index].should_count = true;
|
||||
}
|
||||
let mut counters_lock = lock_write(&COUNTERS);
|
||||
(*counters_lock)[index].should_count = true;
|
||||
}
|
||||
|
||||
pub fn stop_counting(index: usize) {
|
||||
unsafe {
|
||||
COUNTERS[index].should_count = false;
|
||||
}
|
||||
let mut counters_lock = lock_write(&COUNTERS);
|
||||
(*counters_lock)[index].should_count = false;
|
||||
}
|
||||
|
||||
pub fn is_counting(index: usize) -> bool {
|
||||
let counters_lock = lock_read(&COUNTERS);
|
||||
(*counters_lock)[index].should_count
|
||||
}
|
||||
|
||||
pub fn reset_frame_count(index: usize) {
|
||||
unsafe {
|
||||
COUNTERS[index].count = 0;
|
||||
}
|
||||
let mut counters_lock = lock_write(&COUNTERS);
|
||||
(*counters_lock)[index].count = 0;
|
||||
}
|
||||
|
||||
pub fn full_reset(index: usize) {
|
||||
|
@ -75,47 +75,48 @@ pub fn should_delay(delay: u32, index: usize) -> bool {
|
|||
}
|
||||
|
||||
pub fn get_frame_count(index: usize) -> u32 {
|
||||
unsafe { COUNTERS[index].count }
|
||||
let counters_lock = lock_read(&COUNTERS);
|
||||
(*counters_lock)[index].count
|
||||
}
|
||||
|
||||
pub fn tick_idx(index: usize) {
|
||||
unsafe {
|
||||
COUNTERS[index].count += 1;
|
||||
}
|
||||
let mut counters_lock = lock_write(&COUNTERS);
|
||||
(*counters_lock)[index].count += 1;
|
||||
}
|
||||
|
||||
pub fn tick_ingame() {
|
||||
unsafe {
|
||||
for (index, counter) in COUNTERS.iter().enumerate() {
|
||||
if !counter.should_count || counter.counter_type == FrameCounterType::Real {
|
||||
continue;
|
||||
}
|
||||
tick_idx(index);
|
||||
let mut counters_lock = lock_write(&COUNTERS);
|
||||
for counter in (*counters_lock).iter_mut() {
|
||||
if !counter.should_count || counter.counter_type == FrameCounterType::Real {
|
||||
continue;
|
||||
}
|
||||
// same as full_reset, but we already have the lock so we can't lock again
|
||||
counter.count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tick_real() {
|
||||
unsafe {
|
||||
for (index, counter) in COUNTERS.iter().enumerate() {
|
||||
if !counter.should_count
|
||||
|| (counter.counter_type == FrameCounterType::InGame
|
||||
|| counter.counter_type == FrameCounterType::InGameNoReset)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
tick_idx(index);
|
||||
let mut counters_lock = lock_write(&COUNTERS);
|
||||
for counter in (*counters_lock).iter_mut() {
|
||||
if !counter.should_count
|
||||
|| (counter.counter_type == FrameCounterType::InGame
|
||||
|| counter.counter_type == FrameCounterType::InGameNoReset)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// same as full_reset, but we already have the lock so we can't lock again
|
||||
counter.count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_all() {
|
||||
unsafe {
|
||||
for (index, counter) in COUNTERS.iter().enumerate() {
|
||||
if counter.counter_type != FrameCounterType::InGame {
|
||||
continue;
|
||||
}
|
||||
full_reset(index);
|
||||
let mut counters_lock = lock_write(&COUNTERS);
|
||||
for counter in (*counters_lock).iter_mut() {
|
||||
if counter.counter_type != FrameCounterType::InGame {
|
||||
continue;
|
||||
}
|
||||
// same as full_reset, but we already have the lock so we can't lock again
|
||||
counter.count = 0;
|
||||
counter.should_count = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,18 +3,17 @@ use smash::app::{self, lua_bind::*};
|
|||
use smash::lib::lua_const::*;
|
||||
|
||||
use crate::common::*;
|
||||
use training_mod_sync::*;
|
||||
|
||||
// the current full hop status
|
||||
static mut FULL_HOP: bool = false;
|
||||
static FULL_HOP: RwLock<bool> = RwLock::new(false);
|
||||
|
||||
pub fn should_full_hop() -> bool {
|
||||
unsafe { FULL_HOP }
|
||||
read(&FULL_HOP)
|
||||
}
|
||||
|
||||
pub fn roll_full_hop() {
|
||||
unsafe {
|
||||
FULL_HOP = MENU.full_hop.get_random().into_bool();
|
||||
}
|
||||
assign(&FULL_HOP, read(&MENU).full_hop.get_random().into_bool());
|
||||
}
|
||||
|
||||
pub unsafe fn check_button_on(
|
||||
|
|
|
@ -1,30 +1,23 @@
|
|||
use std::collections::VecDeque;
|
||||
|
||||
use crate::common::input::*;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use training_mod_sync::*;
|
||||
|
||||
use crate::common::MENU;
|
||||
|
||||
lazy_static! {
|
||||
static ref P1_DELAYED_INPUT_MAPPINGS: Mutex<VecDeque<MappedInputs>> =
|
||||
Mutex::new(VecDeque::new());
|
||||
}
|
||||
static P1_DELAYED_INPUT_MAPPINGS: RwLock<VecDeque<MappedInputs>> = RwLock::new(VecDeque::new());
|
||||
|
||||
pub fn handle_final_input_mapping(player_idx: i32, out: *mut MappedInputs) {
|
||||
unsafe {
|
||||
if player_idx == 0 {
|
||||
let mut delayed_mappings = P1_DELAYED_INPUT_MAPPINGS.lock();
|
||||
let actual_mapping = *out;
|
||||
|
||||
if delayed_mappings.len() < MENU.input_delay.into_delay() as usize {
|
||||
*out = MappedInputs::empty();
|
||||
} else if let Some(delayed_mapping) = delayed_mappings.back() {
|
||||
*out = *delayed_mapping;
|
||||
}
|
||||
|
||||
delayed_mappings.push_front(actual_mapping);
|
||||
delayed_mappings.truncate(MENU.input_delay.into_delay() as usize);
|
||||
pub unsafe fn handle_final_input_mapping(player_idx: i32, out: *mut MappedInputs) {
|
||||
if player_idx == 0 {
|
||||
let mut delayed_mappings = lock_write(&P1_DELAYED_INPUT_MAPPINGS);
|
||||
let actual_mapping = *out;
|
||||
if delayed_mappings.len() < read(&MENU).input_delay.into_delay() as usize {
|
||||
*out = MappedInputs::empty();
|
||||
} else if let Some(delayed_mapping) = delayed_mappings.back() {
|
||||
*out = *delayed_mapping;
|
||||
}
|
||||
delayed_mappings.push_front(actual_mapping);
|
||||
delayed_mappings.truncate(read(&MENU).input_delay.into_delay() as usize);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use itertools::Itertools;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use crate::common::{input::*, menu::QUICK_MENU_ACTIVE, try_get_module_accessor};
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use crate::common::input::*;
|
||||
use crate::menu::QUICK_MENU_ACTIVE;
|
||||
use crate::try_get_module_accessor;
|
||||
use skyline::nn::ui2d::ResColor;
|
||||
use smash::app::{lua_bind::*, utility};
|
||||
use training_mod_consts::{FighterId, InputDisplay, MENU};
|
||||
use training_mod_sync::*;
|
||||
|
||||
use super::{frame_counter, input_record::STICK_CLAMP_MULTIPLIER};
|
||||
|
||||
|
@ -60,13 +60,15 @@ pub const WHITE: ResColor = ResColor {
|
|||
a: 0,
|
||||
};
|
||||
|
||||
pub static PER_LOG_FRAME_COUNTER: Lazy<usize> =
|
||||
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGameNoReset));
|
||||
pub static OVERALL_FRAME_COUNTER: Lazy<usize> =
|
||||
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGameNoReset));
|
||||
pub static PER_LOG_FRAME_COUNTER: LazyLock<usize> = LazyLock::new(|| {
|
||||
frame_counter::register_counter(frame_counter::FrameCounterType::InGameNoReset)
|
||||
});
|
||||
pub static OVERALL_FRAME_COUNTER: LazyLock<usize> = LazyLock::new(|| {
|
||||
frame_counter::register_counter(frame_counter::FrameCounterType::InGameNoReset)
|
||||
});
|
||||
|
||||
pub const NUM_LOGS: usize = 15;
|
||||
pub static mut DRAW_LOG_BASE_IDX: Lazy<Mutex<usize>> = Lazy::new(|| Mutex::new(0));
|
||||
pub static DRAW_LOG_BASE_IDX: RwLock<usize> = RwLock::new(0);
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
|
||||
pub enum DirectionStrength {
|
||||
|
@ -134,50 +136,51 @@ fn bin_stick_values(x: i8, y: i8) -> (DirectionStrength, f32) {
|
|||
|
||||
impl InputLog {
|
||||
pub fn is_different(&self, other: &InputLog) -> bool {
|
||||
unsafe {
|
||||
match MENU.input_display {
|
||||
InputDisplay::SMASH => self.is_smash_different(other),
|
||||
InputDisplay::RAW => self.is_raw_different(other),
|
||||
InputDisplay::STATUS => self.is_status_different(other),
|
||||
InputDisplay::NONE => false,
|
||||
_ => panic!("Invalid value in is_different: {}", MENU.input_display),
|
||||
}
|
||||
match read(&MENU).input_display {
|
||||
InputDisplay::SMASH => self.is_smash_different(other),
|
||||
InputDisplay::RAW => self.is_raw_different(other),
|
||||
InputDisplay::STATUS => self.is_status_different(other),
|
||||
InputDisplay::NONE => false,
|
||||
_ => panic!(
|
||||
"Invalid value in is_different: {}",
|
||||
read(&MENU).input_display
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn binned_lstick(&self) -> (DirectionStrength, f32) {
|
||||
unsafe {
|
||||
match MENU.input_display {
|
||||
InputDisplay::SMASH => self.smash_binned_lstick(),
|
||||
InputDisplay::RAW => self.raw_binned_lstick(),
|
||||
InputDisplay::STATUS => (DirectionStrength::None, 0.0),
|
||||
InputDisplay::NONE => panic!("Invalid input display to log"),
|
||||
_ => panic!("Invalid value in binned_lstick: {}", MENU.input_display),
|
||||
}
|
||||
match read(&MENU).input_display {
|
||||
InputDisplay::SMASH => self.smash_binned_lstick(),
|
||||
InputDisplay::RAW => self.raw_binned_lstick(),
|
||||
InputDisplay::STATUS => (DirectionStrength::None, 0.0),
|
||||
InputDisplay::NONE => panic!("Invalid input display to log"),
|
||||
_ => panic!(
|
||||
"Invalid value in binned_lstick: {}",
|
||||
read(&MENU).input_display
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn binned_rstick(&self) -> (DirectionStrength, f32) {
|
||||
unsafe {
|
||||
match MENU.input_display {
|
||||
InputDisplay::SMASH => self.smash_binned_rstick(),
|
||||
InputDisplay::RAW => self.raw_binned_rstick(),
|
||||
InputDisplay::STATUS => (DirectionStrength::None, 0.0),
|
||||
InputDisplay::NONE => panic!("Invalid input display to log"),
|
||||
_ => panic!("Invalid value in binned_rstick: {}", MENU.input_display),
|
||||
}
|
||||
match read(&MENU).input_display {
|
||||
InputDisplay::SMASH => self.smash_binned_rstick(),
|
||||
InputDisplay::RAW => self.raw_binned_rstick(),
|
||||
InputDisplay::STATUS => (DirectionStrength::None, 0.0),
|
||||
InputDisplay::NONE => panic!("Invalid input display to log"),
|
||||
_ => panic!(
|
||||
"Invalid value in binned_rstick: {}",
|
||||
read(&MENU).input_display
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn button_icons(&self) -> VecDeque<(&str, ResColor)> {
|
||||
unsafe {
|
||||
match MENU.input_display {
|
||||
InputDisplay::SMASH => self.smash_button_icons(),
|
||||
InputDisplay::RAW => self.raw_button_icons(),
|
||||
InputDisplay::STATUS => VecDeque::new(),
|
||||
InputDisplay::NONE => panic!("Invalid input display to log"),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
match read(&MENU).input_display {
|
||||
InputDisplay::SMASH => self.smash_button_icons(),
|
||||
InputDisplay::RAW => self.raw_button_icons(),
|
||||
InputDisplay::STATUS => VecDeque::new(),
|
||||
InputDisplay::NONE => panic!("Invalid input display to log"),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -258,14 +261,12 @@ impl InputLog {
|
|||
self.smash_inputs.buttons != other.smash_inputs.buttons
|
||||
|| self.smash_binned_lstick() != other.smash_binned_lstick()
|
||||
|| self.smash_binned_rstick() != other.smash_binned_rstick()
|
||||
|| (unsafe { MENU.input_display_status.as_bool() } && self.status != other.status)
|
||||
|| (read(&MENU).input_display_status.as_bool() && self.status != other.status)
|
||||
}
|
||||
|
||||
fn is_status_different(&self, other: &InputLog) -> bool {
|
||||
unsafe {
|
||||
let input_display_status = MENU.input_display_status.as_bool();
|
||||
input_display_status && (self.status != other.status)
|
||||
}
|
||||
let input_display_status = read(&MENU).input_display_status.as_bool();
|
||||
input_display_status && (self.status != other.status)
|
||||
}
|
||||
|
||||
fn smash_binned_lstick(&self) -> (DirectionStrength, f32) {
|
||||
|
@ -280,7 +281,7 @@ impl InputLog {
|
|||
self.raw_inputs.current_buttons != other.raw_inputs.current_buttons
|
||||
|| self.raw_binned_lstick() != other.raw_binned_lstick()
|
||||
|| self.raw_binned_rstick() != other.raw_binned_rstick()
|
||||
|| (unsafe { MENU.input_display_status.as_bool() } && self.status != other.status)
|
||||
|| (read(&MENU).input_display_status.as_bool() && self.status != other.status)
|
||||
}
|
||||
|
||||
fn raw_binned_lstick(&self) -> (DirectionStrength, f32) {
|
||||
|
@ -305,10 +306,8 @@ fn insert_in_front<T>(array: &mut [T], value: T) {
|
|||
insert_in_place(array, value, 0);
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref P1_INPUT_LOGS: Mutex<[InputLog; NUM_LOGS]> =
|
||||
Mutex::new([InputLog::default(); NUM_LOGS]);
|
||||
}
|
||||
pub static P1_INPUT_LOGS: LazyLock<RwLock<[InputLog; NUM_LOGS]>> =
|
||||
LazyLock::new(|| RwLock::new([InputLog::default(); NUM_LOGS]));
|
||||
|
||||
pub fn handle_final_input_mapping(
|
||||
player_idx: i32,
|
||||
|
@ -316,11 +315,11 @@ pub fn handle_final_input_mapping(
|
|||
out: *mut MappedInputs,
|
||||
) {
|
||||
unsafe {
|
||||
if MENU.input_display == InputDisplay::NONE {
|
||||
if read(&MENU).input_display == InputDisplay::NONE {
|
||||
return;
|
||||
}
|
||||
|
||||
if QUICK_MENU_ACTIVE {
|
||||
if read(&QUICK_MENU_ACTIVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -347,7 +346,8 @@ pub fn handle_final_input_mapping(
|
|||
fighter_kind: utility::get_kind(&mut *module_accessor),
|
||||
};
|
||||
|
||||
let input_logs = &mut *P1_INPUT_LOGS.lock();
|
||||
let mut input_logs_lock = lock_write(&(*P1_INPUT_LOGS));
|
||||
let input_logs = &mut *input_logs_lock;
|
||||
let latest_input_log = input_logs.first_mut().unwrap();
|
||||
let prev_overall_frames = latest_input_log.overall_frame;
|
||||
let prev_ttl = latest_input_log.ttl;
|
||||
|
@ -358,8 +358,9 @@ pub fn handle_final_input_mapping(
|
|||
// We should count this frame already
|
||||
frame_counter::tick_idx(*PER_LOG_FRAME_COUNTER);
|
||||
insert_in_front(input_logs, potential_input_log);
|
||||
let draw_log_base_idx = &mut *DRAW_LOG_BASE_IDX.data_ptr();
|
||||
*draw_log_base_idx = (*draw_log_base_idx + 1) % NUM_LOGS;
|
||||
let mut draw_log_base_idx_lock = lock_write(&DRAW_LOG_BASE_IDX);
|
||||
*draw_log_base_idx_lock = (*draw_log_base_idx_lock + 1) % NUM_LOGS;
|
||||
drop(draw_log_base_idx_lock);
|
||||
} else if is_new_frame {
|
||||
*latest_input_log = potential_input_log;
|
||||
latest_input_log.frames = std::cmp::min(current_frame, 99);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use skyline::nn::ui2d::ResColor;
|
||||
use smash::app::{lua_bind::*, utility, BattleObjectModuleAccessor};
|
||||
use smash::lib::lua_const::*;
|
||||
|
@ -17,10 +15,12 @@ use crate::common::{
|
|||
get_module_accessor, is_in_hitstun, is_in_shieldstun, try_get_module_accessor, MENU,
|
||||
};
|
||||
use crate::training::mash;
|
||||
use crate::training::ui::notifications::{clear_notifications, color_notification};
|
||||
use crate::training::ui::notifications::{clear_notification, color_notification};
|
||||
use crate::{error, warn};
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
use training_mod_sync::*;
|
||||
|
||||
#[derive(PartialEq, Debug, Clone, Copy)]
|
||||
pub enum InputRecordState {
|
||||
None,
|
||||
Pause,
|
||||
|
@ -28,7 +28,7 @@ pub enum InputRecordState {
|
|||
Playback,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
#[derive(PartialEq, Debug, Clone, Copy)]
|
||||
pub enum PossessionState {
|
||||
Player,
|
||||
Cpu,
|
||||
|
@ -56,30 +56,26 @@ pub const STICK_NEUTRAL: f32 = 0.2;
|
|||
pub const STICK_CLAMP_MULTIPLIER: f32 = 1.0 / 120.0; // 120.0 = CLAMP_MAX
|
||||
const FINAL_RECORD_MAX: usize = 600; // Maximum length for input recording sequences (capacity)
|
||||
const TOTAL_SLOT_COUNT: usize = 5; // Total number of input recording slots
|
||||
pub static mut INPUT_RECORD: InputRecordState = InputRecordState::None;
|
||||
pub static mut INPUT_RECORD_FRAME: usize = 0;
|
||||
pub static mut POSSESSION: PossessionState = PossessionState::Player;
|
||||
pub static mut LOCKOUT_FRAME: usize = 0;
|
||||
pub static mut BUFFER_FRAME: usize = 0;
|
||||
pub static mut RECORDED_LR: f32 = 1.0; // The direction the CPU was facing before the current recording was recorded
|
||||
pub static mut CURRENT_LR: f32 = 1.0; // The direction the CPU was facing at the beginning of this playback
|
||||
pub static mut STARTING_STATUS: i32 = 0; // The first status entered in the recording outside of waits
|
||||
// used to calculate if the input playback should begin before hitstun would normally end (hitstun cancel, monado art?)
|
||||
pub static mut CURRENT_RECORD_SLOT: usize = 0; // Which slot is being used for recording right now? Want to make sure this is synced with menu choices, maybe just use menu instead
|
||||
pub static mut CURRENT_PLAYBACK_SLOT: usize = 0; // Which slot is being used for playback right now?
|
||||
pub static mut CURRENT_FRAME_LENGTH: usize = 60;
|
||||
|
||||
lazy_static! {
|
||||
static ref P1_FINAL_MAPPING: Mutex<[[MappedInputs; FINAL_RECORD_MAX]; TOTAL_SLOT_COUNT]> =
|
||||
Mutex::new([[{ MappedInputs::empty() }; FINAL_RECORD_MAX]; TOTAL_SLOT_COUNT]);
|
||||
static ref P1_FRAME_LENGTH_MAPPING: Mutex<[usize; TOTAL_SLOT_COUNT]> =
|
||||
Mutex::new([60usize; TOTAL_SLOT_COUNT]);
|
||||
static ref P1_STARTING_STATUSES: Mutex<[StartingStatus; TOTAL_SLOT_COUNT]> =
|
||||
Mutex::new([{ StartingStatus::Other }; TOTAL_SLOT_COUNT]);
|
||||
}
|
||||
pub static INPUT_RECORD: RwLock<InputRecordState> = RwLock::new(InputRecordState::None);
|
||||
pub static INPUT_RECORD_FRAME: RwLock<usize> = RwLock::new(0);
|
||||
pub static POSSESSION: RwLock<PossessionState> = RwLock::new(PossessionState::Player);
|
||||
pub static LOCKOUT_FRAME: RwLock<usize> = RwLock::new(0);
|
||||
pub static BUFFER_FRAME: RwLock<usize> = RwLock::new(0);
|
||||
pub static RECORDED_LR: RwLock<f32> = RwLock::new(1.0); // The direction the CPU was facing before the current recording was recorded
|
||||
pub static CURRENT_LR: RwLock<f32> = RwLock::new(1.0); // The direction the CPU was facing at the beginning of this playback
|
||||
pub static STARTING_STATUS: RwLock<i32> = RwLock::new(0); // The first status entered in the recording outside of waits
|
||||
// used to calculate if the input playback should begin before hitstun would normally end (hitstun cancel, monado art?)
|
||||
pub static CURRENT_RECORD_SLOT: RwLock<usize> = RwLock::new(0); // Which slot is being used for recording right now? Want to make sure this is synced with menu choices, maybe just use menu instead
|
||||
pub static CURRENT_PLAYBACK_SLOT: RwLock<usize> = RwLock::new(0); // Which slot is being used for playback right now?
|
||||
pub static CURRENT_FRAME_LENGTH: RwLock<usize> = RwLock::new(60);
|
||||
pub static P1_FINAL_MAPPING: RwLock<[[MappedInputs; FINAL_RECORD_MAX]; TOTAL_SLOT_COUNT]> =
|
||||
RwLock::new([[{ MappedInputs::empty() }; FINAL_RECORD_MAX]; TOTAL_SLOT_COUNT]);
|
||||
pub static P1_FRAME_LENGTH_MAPPING: RwLock<[usize; TOTAL_SLOT_COUNT]> =
|
||||
RwLock::new([60; TOTAL_SLOT_COUNT]);
|
||||
// pub static P1_STARTING_STATUSES: RwLock<[StartingStatus; TOTAL_SLOT_COUNT]> = RwLock::new([StartingStatus::Other; TOTAL_SLOT_COUNT]); // TODO! Not used currently
|
||||
|
||||
unsafe fn can_transition(module_accessor: *mut BattleObjectModuleAccessor) -> bool {
|
||||
let transition_term = into_transition_term(into_starting_status(STARTING_STATUS));
|
||||
let transition_term = into_transition_term(into_starting_status(read(&STARTING_STATUS)));
|
||||
WorkModule::is_enable_transition_term(module_accessor, transition_term)
|
||||
}
|
||||
|
||||
|
@ -104,17 +100,18 @@ unsafe fn should_mash_playback() {
|
|||
|
||||
if is_in_hitstun(&mut *cpu_module_accessor) {
|
||||
// if we're in hitstun and want to enter the frame we start hitstop for SDI, start if we're in any damage status instantly
|
||||
if MENU.hitstun_playback == HitstunPlayback::INSTANT {
|
||||
if read(&MENU).hitstun_playback == HitstunPlayback::INSTANT {
|
||||
should_playback = true;
|
||||
}
|
||||
// if we want to wait until we exit hitstop and begin flying away for shield art etc, start if we're not in hitstop
|
||||
if MENU.hitstun_playback == HitstunPlayback::HITSTOP
|
||||
if read(&MENU).hitstun_playback == HitstunPlayback::HITSTOP
|
||||
&& !StopModule::is_stop(cpu_module_accessor)
|
||||
{
|
||||
should_playback = true;
|
||||
}
|
||||
// if we're in hitstun and want to wait till FAF to act, then we want to match our starting status to the correct transition term to see if we can hitstun cancel
|
||||
if MENU.hitstun_playback == HitstunPlayback::HITSTUN && can_transition(cpu_module_accessor)
|
||||
if read(&MENU).hitstun_playback == HitstunPlayback::HITSTUN
|
||||
&& can_transition(cpu_module_accessor)
|
||||
{
|
||||
should_playback = true;
|
||||
}
|
||||
|
@ -193,53 +190,65 @@ unsafe fn handle_recording_for_fighter(module_accessor: &mut BattleObjectModuleA
|
|||
let fighter_kind = utility::get_kind(module_accessor);
|
||||
let fighter_is_nana = fighter_kind == *FIGHTER_KIND_NANA;
|
||||
|
||||
CURRENT_RECORD_SLOT = MENU.recording_slot.into_idx().unwrap_or(0);
|
||||
assign(
|
||||
&CURRENT_RECORD_SLOT,
|
||||
read(&MENU).recording_slot.into_idx().unwrap_or(0),
|
||||
);
|
||||
|
||||
if entry_id_int == 0 && !fighter_is_nana {
|
||||
if button_config::combo_passes(button_config::ButtonCombo::InputPlayback) {
|
||||
playback(MENU.playback_button_slots.get_random().into_idx());
|
||||
} else if MENU.record_trigger.contains(&RecordTrigger::COMMAND)
|
||||
playback(read(&MENU).playback_button_slots.get_random().into_idx());
|
||||
} else if read(&MENU).record_trigger.contains(&RecordTrigger::COMMAND)
|
||||
&& button_config::combo_passes(button_config::ButtonCombo::InputRecord)
|
||||
{
|
||||
lockout_record();
|
||||
}
|
||||
if INPUT_RECORD == None {
|
||||
clear_notifications("Input Recording");
|
||||
let input_record = read(&INPUT_RECORD);
|
||||
if input_record == None {
|
||||
clear_notification("Input Recording");
|
||||
}
|
||||
// Handle recording end
|
||||
if (INPUT_RECORD == Record || INPUT_RECORD == Playback)
|
||||
&& INPUT_RECORD_FRAME >= CURRENT_FRAME_LENGTH - 1
|
||||
let mut input_record_frame = lock_write(&INPUT_RECORD_FRAME);
|
||||
if (input_record == Record || input_record == Playback)
|
||||
&& *input_record_frame >= read(&CURRENT_FRAME_LENGTH) - 1
|
||||
{
|
||||
POSSESSION = Player;
|
||||
assign(&POSSESSION, Player);
|
||||
if mash::is_playback_queued() {
|
||||
mash::reset();
|
||||
}
|
||||
|
||||
// If we need to crop the recording for neutral input
|
||||
// INPUT_RECORD_FRAME must be > 0 to prevent bounding errors
|
||||
if INPUT_RECORD == Record && MENU.recording_crop == OnOff::ON && INPUT_RECORD_FRAME > 0
|
||||
if input_record == Record
|
||||
&& read(&MENU).recording_crop == OnOff::ON
|
||||
&& *input_record_frame > 0
|
||||
{
|
||||
while INPUT_RECORD_FRAME > 0 && is_input_neutral(INPUT_RECORD_FRAME - 1) {
|
||||
while *input_record_frame > 0 && is_input_neutral(*input_record_frame - 1) {
|
||||
// Discard frames at the end of the recording until the last frame with input
|
||||
INPUT_RECORD_FRAME -= 1;
|
||||
*input_record_frame -= 1;
|
||||
}
|
||||
CURRENT_FRAME_LENGTH = INPUT_RECORD_FRAME;
|
||||
P1_FRAME_LENGTH_MAPPING.lock()[CURRENT_RECORD_SLOT] = CURRENT_FRAME_LENGTH;
|
||||
assign(&CURRENT_FRAME_LENGTH, *input_record_frame);
|
||||
let mut p1_frame_length_mapping = lock_write(&P1_FRAME_LENGTH_MAPPING);
|
||||
(*p1_frame_length_mapping)[read(&CURRENT_RECORD_SLOT)] = *input_record_frame;
|
||||
drop(p1_frame_length_mapping);
|
||||
}
|
||||
|
||||
INPUT_RECORD_FRAME = 0;
|
||||
*input_record_frame = 0;
|
||||
|
||||
if MENU.playback_loop == OnOff::ON && INPUT_RECORD == Playback {
|
||||
playback(Some(CURRENT_PLAYBACK_SLOT));
|
||||
if read(&MENU).playback_loop == OnOff::ON && input_record == Playback {
|
||||
let playback_slot = read(&CURRENT_PLAYBACK_SLOT);
|
||||
playback(Some(playback_slot));
|
||||
} else {
|
||||
INPUT_RECORD = None;
|
||||
assign(&INPUT_RECORD, None);
|
||||
}
|
||||
}
|
||||
drop(input_record_frame);
|
||||
}
|
||||
|
||||
// Handle Possession Coloring
|
||||
if entry_id_int == 1 && POSSESSION == Lockout {
|
||||
clear_notifications("Input Recording");
|
||||
let possession = read(&POSSESSION);
|
||||
if entry_id_int == 1 && possession == Lockout {
|
||||
clear_notification("Input Recording");
|
||||
color_notification(
|
||||
"Input Recording".to_string(),
|
||||
"Lockout".to_owned(),
|
||||
|
@ -258,8 +267,8 @@ unsafe fn handle_recording_for_fighter(module_accessor: &mut BattleObjectModuleA
|
|||
1.0,
|
||||
*MODEL_COLOR_TYPE_COLOR_BLEND,
|
||||
);
|
||||
} else if entry_id_int == 1 && POSSESSION == Standby {
|
||||
clear_notifications("Input Recording");
|
||||
} else if entry_id_int == 1 && possession == Standby {
|
||||
clear_notification("Input Recording");
|
||||
color_notification(
|
||||
"Input Recording".to_string(),
|
||||
"Standby".to_owned(),
|
||||
|
@ -278,8 +287,8 @@ unsafe fn handle_recording_for_fighter(module_accessor: &mut BattleObjectModuleA
|
|||
1.0,
|
||||
*MODEL_COLOR_TYPE_COLOR_BLEND,
|
||||
);
|
||||
} else if entry_id_int == 1 && POSSESSION == Cpu {
|
||||
clear_notifications("Input Recording");
|
||||
} else if entry_id_int == 1 && possession == Cpu {
|
||||
clear_notification("Input Recording");
|
||||
color_notification(
|
||||
"Input Recording".to_string(),
|
||||
"Recording".to_owned(),
|
||||
|
@ -298,15 +307,17 @@ unsafe fn handle_recording_for_fighter(module_accessor: &mut BattleObjectModuleA
|
|||
0.0,
|
||||
*MODEL_COLOR_TYPE_COLOR_BLEND,
|
||||
);
|
||||
} else if entry_id_int == 1 && POSSESSION == Player && INPUT_RECORD == Playback {
|
||||
} else if entry_id_int == 1 && possession == Player && read(&INPUT_RECORD) == Playback {
|
||||
// Need to re-read INPUT_RECORD instead of using the local variable because we might have assigned to it early
|
||||
// Displays if the inputs from the current frame were a result of playback
|
||||
if INPUT_RECORD_FRAME == 0 || INPUT_RECORD_FRAME == 1 {
|
||||
let input_record_frame = read(&INPUT_RECORD_FRAME);
|
||||
if input_record_frame == 0 || input_record_frame == 1 {
|
||||
// can be either, seems like a thread issue
|
||||
clear_notifications("Input Recording");
|
||||
clear_notification("Input Recording");
|
||||
color_notification(
|
||||
"Input Recording".to_string(),
|
||||
"Playback".to_owned(),
|
||||
CURRENT_FRAME_LENGTH as u32,
|
||||
read(&CURRENT_FRAME_LENGTH) as u32,
|
||||
ResColor {
|
||||
r: 0,
|
||||
g: 0,
|
||||
|
@ -319,27 +330,29 @@ unsafe fn handle_recording_for_fighter(module_accessor: &mut BattleObjectModuleA
|
|||
}
|
||||
|
||||
pub unsafe fn lockout_record() {
|
||||
INPUT_RECORD = Pause;
|
||||
INPUT_RECORD_FRAME = 0;
|
||||
POSSESSION = Lockout;
|
||||
P1_FINAL_MAPPING.lock()[CURRENT_RECORD_SLOT]
|
||||
.iter_mut()
|
||||
.for_each(|mapped_input| {
|
||||
*mapped_input = MappedInputs::empty();
|
||||
});
|
||||
CURRENT_FRAME_LENGTH = MENU.recording_duration.into_frames();
|
||||
P1_FRAME_LENGTH_MAPPING.lock()[CURRENT_RECORD_SLOT] = CURRENT_FRAME_LENGTH;
|
||||
LOCKOUT_FRAME = 30; // This needs to be this high or issues occur dropping shield - but does this cause problems when trying to record ledge?
|
||||
BUFFER_FRAME = 0;
|
||||
// Store the direction the CPU is facing when we initially record, so we can turn their inputs around if needed
|
||||
let cpu_module_accessor = get_module_accessor(FighterId::CPU);
|
||||
RECORDED_LR = PostureModule::lr(cpu_module_accessor);
|
||||
CURRENT_LR = RECORDED_LR;
|
||||
let recording_duration = read(&MENU).recording_duration.into_frames();
|
||||
let current_record_slot = read(&CURRENT_RECORD_SLOT);
|
||||
let mut p1_final_mapping = lock_write(&P1_FINAL_MAPPING);
|
||||
(*p1_final_mapping)[current_record_slot] = [{ MappedInputs::empty() }; FINAL_RECORD_MAX];
|
||||
drop(p1_final_mapping);
|
||||
let mut p1_frame_length_mapping = lock_write(&P1_FRAME_LENGTH_MAPPING);
|
||||
(*p1_frame_length_mapping)[current_record_slot] = recording_duration;
|
||||
drop(p1_frame_length_mapping);
|
||||
assign(&CURRENT_FRAME_LENGTH, recording_duration);
|
||||
assign(&INPUT_RECORD, Pause);
|
||||
assign(&INPUT_RECORD_FRAME, 0);
|
||||
assign(&POSSESSION, Lockout);
|
||||
assign(&LOCKOUT_FRAME, 30); // This needs to be this high or issues occur dropping shield - but does this cause problems when trying to record ledge?
|
||||
assign(&BUFFER_FRAME, 0);
|
||||
// Store the direction the CPU is facing when we initially record, so we can turn their inputs around if needed
|
||||
assign(&RECORDED_LR, PostureModule::lr(cpu_module_accessor));
|
||||
assign(&CURRENT_LR, PostureModule::lr(cpu_module_accessor));
|
||||
}
|
||||
|
||||
// Returns whether we did playback
|
||||
pub unsafe fn playback(slot: Option<usize>) -> bool {
|
||||
if INPUT_RECORD == Pause {
|
||||
if read(&INPUT_RECORD) == Pause {
|
||||
warn!("Tried to playback during lockout!");
|
||||
return false;
|
||||
}
|
||||
|
@ -348,15 +361,15 @@ pub unsafe fn playback(slot: Option<usize>) -> bool {
|
|||
return false;
|
||||
}
|
||||
let slot = slot.unwrap();
|
||||
|
||||
CURRENT_PLAYBACK_SLOT = slot;
|
||||
CURRENT_FRAME_LENGTH = P1_FRAME_LENGTH_MAPPING.lock()[CURRENT_PLAYBACK_SLOT];
|
||||
INPUT_RECORD = Playback;
|
||||
POSSESSION = Player;
|
||||
INPUT_RECORD_FRAME = 0;
|
||||
BUFFER_FRAME = 0;
|
||||
let cpu_module_accessor = get_module_accessor(FighterId::CPU);
|
||||
CURRENT_LR = PostureModule::lr(cpu_module_accessor);
|
||||
let frame_length = read(&P1_FRAME_LENGTH_MAPPING)[slot];
|
||||
assign(&CURRENT_FRAME_LENGTH, frame_length);
|
||||
assign(&CURRENT_PLAYBACK_SLOT, slot);
|
||||
assign(&INPUT_RECORD, Playback);
|
||||
assign(&POSSESSION, Player);
|
||||
assign(&INPUT_RECORD_FRAME, 0);
|
||||
assign(&BUFFER_FRAME, 0);
|
||||
assign(&CURRENT_LR, PostureModule::lr(cpu_module_accessor));
|
||||
|
||||
true
|
||||
}
|
||||
|
@ -364,26 +377,28 @@ pub unsafe fn playback(slot: Option<usize>) -> bool {
|
|||
pub unsafe fn playback_ledge(slot: Option<usize>) {
|
||||
let did_playback = playback(slot);
|
||||
if did_playback {
|
||||
BUFFER_FRAME = 5; // So we can make sure the option is buffered and won't get ledge trumped if delay is 0
|
||||
// drop down from ledge can't be buffered on the same frame as jump/attack/roll/ngu so we have to do this
|
||||
// Need to buffer 1 less frame for non-lassos
|
||||
let mut buffer_frame = lock_write(&BUFFER_FRAME);
|
||||
*buffer_frame = 5; // So we can make sure the option is buffered and won't get ledge trumped if delay is 0
|
||||
// drop down from ledge can't be buffered on the same frame as jump/attack/roll/ngu so we have to do this
|
||||
// Need to buffer 1 less frame for non-lassos
|
||||
let cpu_module_accessor = get_module_accessor(FighterId::CPU);
|
||||
let status_kind = StatusModule::status_kind(cpu_module_accessor);
|
||||
if status_kind == *FIGHTER_STATUS_KIND_CLIFF_CATCH {
|
||||
BUFFER_FRAME -= 1;
|
||||
*buffer_frame -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn stop_playback() {
|
||||
INPUT_RECORD = None;
|
||||
INPUT_RECORD_FRAME = 0;
|
||||
POSSESSION = Player;
|
||||
assign(&INPUT_RECORD, None);
|
||||
assign(&INPUT_RECORD_FRAME, 0);
|
||||
assign(&POSSESSION, Player);
|
||||
}
|
||||
|
||||
pub unsafe fn is_input_neutral(input_frame: usize) -> bool {
|
||||
// Returns whether we should be done with standby this frame (if any significant controller input has been made)
|
||||
let frame_input = P1_FINAL_MAPPING.lock()[CURRENT_RECORD_SLOT][input_frame];
|
||||
let current_record_slot = read(&CURRENT_RECORD_SLOT);
|
||||
let frame_input = read(&P1_FINAL_MAPPING)[current_record_slot][input_frame];
|
||||
|
||||
let clamped_lstick_x =
|
||||
((frame_input.lstick_x as f32) * STICK_CLAMP_MULTIPLIER).clamp(-1.0, 1.0);
|
||||
|
@ -405,31 +420,40 @@ pub unsafe fn is_input_neutral(input_frame: usize) -> bool {
|
|||
}
|
||||
|
||||
pub unsafe fn handle_final_input_mapping(player_idx: i32, out: *mut MappedInputs) {
|
||||
let mut possession = lock_write(&POSSESSION);
|
||||
if player_idx == 0 {
|
||||
// if player 1
|
||||
if INPUT_RECORD == Record {
|
||||
if read(&INPUT_RECORD) == Record {
|
||||
let mut input_record_frame = lock_write(&INPUT_RECORD_FRAME);
|
||||
// check for standby before starting action:
|
||||
if POSSESSION == Standby && !is_input_neutral(0) {
|
||||
if *possession == Standby && !is_input_neutral(0) {
|
||||
// last input made us start an action, so start recording and end standby.
|
||||
INPUT_RECORD_FRAME += 1;
|
||||
POSSESSION = Cpu;
|
||||
*input_record_frame += 1;
|
||||
*possession = Cpu;
|
||||
}
|
||||
|
||||
if INPUT_RECORD_FRAME == 1 {
|
||||
if *input_record_frame == 1 {
|
||||
// We're on the second frame of recording, grabbing the status should give us the status that resulted from the first frame of input
|
||||
// We'll want to save this status so that we use the correct TRANSITION TERM for hitstun cancelling out of damage fly
|
||||
let cpu_module_accessor = get_module_accessor(FighterId::CPU);
|
||||
P1_STARTING_STATUSES.lock()[CURRENT_PLAYBACK_SLOT] =
|
||||
into_starting_status(StatusModule::status_kind(cpu_module_accessor));
|
||||
STARTING_STATUS = StatusModule::status_kind(cpu_module_accessor);
|
||||
assign(
|
||||
&STARTING_STATUS,
|
||||
StatusModule::status_kind(cpu_module_accessor),
|
||||
);
|
||||
// TODO: Handle this based on slot later instead
|
||||
// let p1_starting_statuses = lock_write_rwlock(&P1_STARTING_STATUSES);
|
||||
// (*p1_starting_statuses)[read(&CURRENT_PLAYBACK_SLOT)] =
|
||||
// into_starting_status(StatusModule::status_kind(cpu_module_accessor));
|
||||
// drop(p1_starting_statuses);
|
||||
}
|
||||
|
||||
P1_FINAL_MAPPING.lock()[CURRENT_RECORD_SLOT][INPUT_RECORD_FRAME] = *out;
|
||||
let mut p1_final_mapping = lock_write(&P1_FINAL_MAPPING);
|
||||
let current_record_slot = read(&CURRENT_RECORD_SLOT);
|
||||
(*p1_final_mapping)[current_record_slot][*input_record_frame] = *out;
|
||||
drop(p1_final_mapping);
|
||||
*out = MappedInputs::empty(); // don't control player while recording
|
||||
}
|
||||
// Don't allow for player input during Lockout
|
||||
if POSSESSION == Lockout {
|
||||
if *possession == Lockout {
|
||||
*out = MappedInputs::empty();
|
||||
}
|
||||
}
|
||||
|
@ -449,7 +473,7 @@ unsafe fn set_cpu_controls(p_data: *mut *mut u8) {
|
|||
// TODO: Setup STARTING_STATUS based on current playback slot here
|
||||
|
||||
// This check prevents out of shield if mash exiting is on
|
||||
if INPUT_RECORD == None {
|
||||
if read(&INPUT_RECORD) == None {
|
||||
should_mash_playback();
|
||||
}
|
||||
|
||||
|
@ -461,20 +485,22 @@ unsafe fn set_cpu_controls(p_data: *mut *mut u8) {
|
|||
}
|
||||
let cpu_module_accessor = cpu_module_accessor.unwrap();
|
||||
|
||||
if INPUT_RECORD == Pause {
|
||||
match LOCKOUT_FRAME.cmp(&0) {
|
||||
Ordering::Greater => LOCKOUT_FRAME -= 1,
|
||||
if read(&INPUT_RECORD) == Pause {
|
||||
let lockout_frame = read(&LOCKOUT_FRAME);
|
||||
match lockout_frame.cmp(&0) {
|
||||
Ordering::Greater => assign(&LOCKOUT_FRAME, lockout_frame - 1),
|
||||
Ordering::Equal => {
|
||||
INPUT_RECORD = Record;
|
||||
POSSESSION = Standby;
|
||||
assign(&INPUT_RECORD, Record);
|
||||
assign(&POSSESSION, Standby);
|
||||
}
|
||||
Ordering::Less => error!("LOCKOUT_FRAME OUT OF BOUNDS"),
|
||||
}
|
||||
}
|
||||
|
||||
if INPUT_RECORD == Record || INPUT_RECORD == Playback {
|
||||
let input_record = read(&INPUT_RECORD);
|
||||
if input_record == Record || input_record == Playback {
|
||||
// if we aren't facing the way we were when we initially recorded, we reverse horizontal inputs
|
||||
let mut x_input_multiplier = RECORDED_LR * CURRENT_LR;
|
||||
let mut x_input_multiplier = read(&RECORDED_LR) * read(&CURRENT_LR);
|
||||
// Don't flip Shulk's dial inputs
|
||||
let fighter_kind = utility::get_kind(&mut *cpu_module_accessor);
|
||||
if fighter_kind == *FIGHTER_KIND_SHULK {
|
||||
|
@ -504,13 +530,16 @@ unsafe fn set_cpu_controls(p_data: *mut *mut u8) {
|
|||
);
|
||||
}
|
||||
|
||||
let mut saved_mapped_inputs = P1_FINAL_MAPPING.lock()[if INPUT_RECORD == Record {
|
||||
CURRENT_RECORD_SLOT
|
||||
let mut input_record_frame = lock_write(&INPUT_RECORD_FRAME);
|
||||
let slot = if input_record == Record {
|
||||
read(&CURRENT_RECORD_SLOT)
|
||||
} else {
|
||||
CURRENT_PLAYBACK_SLOT
|
||||
}][INPUT_RECORD_FRAME];
|
||||
|
||||
if BUFFER_FRAME <= 3 && BUFFER_FRAME > 0 {
|
||||
read(&CURRENT_PLAYBACK_SLOT)
|
||||
};
|
||||
let p1_final_mapping = lock_write(&P1_FINAL_MAPPING);
|
||||
let mut saved_mapped_inputs = p1_final_mapping[slot][*input_record_frame];
|
||||
let mut buffer_frame = lock_write(&BUFFER_FRAME);
|
||||
if (0 < *buffer_frame) && (*buffer_frame <= 3) {
|
||||
// Our option is already buffered, now we need to 0 out inputs to make sure our future controls act like flicks/presses instead of holding the button
|
||||
saved_mapped_inputs = MappedInputs::empty();
|
||||
}
|
||||
|
@ -539,24 +568,28 @@ unsafe fn set_cpu_controls(p_data: *mut *mut u8) {
|
|||
|
||||
// Keep counting frames, unless we're in standby waiting for an input, or are buffering an option
|
||||
// When buffering an option, we keep inputting the first frame of input during the buffer window
|
||||
if BUFFER_FRAME > 0 {
|
||||
BUFFER_FRAME -= 1;
|
||||
} else if INPUT_RECORD_FRAME < CURRENT_FRAME_LENGTH - 1 && POSSESSION != Standby {
|
||||
INPUT_RECORD_FRAME += 1;
|
||||
if *buffer_frame > 0 {
|
||||
*buffer_frame -= 1;
|
||||
} else if *input_record_frame < read(&CURRENT_FRAME_LENGTH) - 1
|
||||
&& read(&POSSESSION) != Standby
|
||||
{
|
||||
*input_record_frame += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn is_playback() -> bool {
|
||||
INPUT_RECORD == Record || INPUT_RECORD == Playback
|
||||
pub fn is_playback() -> bool {
|
||||
let input_record = read(&INPUT_RECORD);
|
||||
input_record == Record || input_record == Playback
|
||||
}
|
||||
|
||||
pub unsafe fn is_recording() -> bool {
|
||||
INPUT_RECORD == Record
|
||||
pub fn is_recording() -> bool {
|
||||
read(&INPUT_RECORD) == Record
|
||||
}
|
||||
|
||||
pub unsafe fn is_standby() -> bool {
|
||||
POSSESSION == Standby || POSSESSION == Lockout
|
||||
let possession = read(&POSSESSION);
|
||||
possession == Standby || possession == Lockout
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
|
|
@ -5,92 +5,84 @@ use crate::common::consts::*;
|
|||
use crate::common::*;
|
||||
use crate::training::{frame_counter, input_record, mash};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use training_mod_sync::*;
|
||||
|
||||
const NOT_SET: u32 = 9001;
|
||||
static mut LEDGE_DELAY: u32 = NOT_SET;
|
||||
static mut LEDGE_CASE: LedgeOption = LedgeOption::empty();
|
||||
static LEDGE_DELAY: RwLock<u32> = RwLock::new(NOT_SET);
|
||||
static LEDGE_CASE: RwLock<LedgeOption> = RwLock::new(LedgeOption::empty());
|
||||
|
||||
static LEDGE_DELAY_COUNTER: Lazy<usize> =
|
||||
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
|
||||
static LEDGE_DELAY_COUNTER: LazyLock<usize> =
|
||||
LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
|
||||
|
||||
pub fn reset_ledge_delay() {
|
||||
unsafe {
|
||||
if LEDGE_DELAY != NOT_SET {
|
||||
LEDGE_DELAY = NOT_SET;
|
||||
frame_counter::full_reset(*LEDGE_DELAY_COUNTER);
|
||||
}
|
||||
let mut ledge_delay_lock = lock_write(&LEDGE_DELAY);
|
||||
if *ledge_delay_lock != NOT_SET {
|
||||
*ledge_delay_lock = NOT_SET;
|
||||
frame_counter::full_reset(*LEDGE_DELAY_COUNTER);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_ledge_case() {
|
||||
unsafe {
|
||||
if LEDGE_CASE != LedgeOption::empty() {
|
||||
// Don't roll another ledge option if one is already selected
|
||||
LEDGE_CASE = LedgeOption::empty();
|
||||
}
|
||||
let mut ledge_case_lock = lock_write(&LEDGE_CASE);
|
||||
if *ledge_case_lock != LedgeOption::empty() {
|
||||
// Don't roll another ledge option if one is already selected
|
||||
*ledge_case_lock = LedgeOption::empty();
|
||||
}
|
||||
}
|
||||
|
||||
fn roll_ledge_delay() {
|
||||
unsafe {
|
||||
if LEDGE_DELAY != NOT_SET {
|
||||
// Don't roll another ledge delay if one is already selected
|
||||
return;
|
||||
}
|
||||
|
||||
LEDGE_DELAY = MENU.ledge_delay.get_random().into_longdelay();
|
||||
let mut ledge_delay_lock = lock_write(&LEDGE_DELAY);
|
||||
if *ledge_delay_lock != NOT_SET {
|
||||
// Don't roll another ledge delay if one is already selected
|
||||
return;
|
||||
}
|
||||
*ledge_delay_lock = read(&MENU).ledge_delay.get_random().into_longdelay();
|
||||
}
|
||||
|
||||
fn roll_ledge_case() {
|
||||
unsafe {
|
||||
// Don't re-roll if there is already a ledge option selected
|
||||
// This prevents choosing a different ledge option during LedgeOption::WAIT
|
||||
if LEDGE_CASE != LedgeOption::empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
LEDGE_CASE = MENU.ledge_state.get_random();
|
||||
// Don't re-roll if there is already a ledge option selected
|
||||
// This prevents choosing a different ledge option during LedgeOption::WAIT
|
||||
let mut ledge_case_lock = lock_write(&LEDGE_CASE);
|
||||
if *ledge_case_lock != LedgeOption::empty() {
|
||||
return;
|
||||
}
|
||||
*ledge_case_lock = read(&MENU).ledge_state.get_random();
|
||||
}
|
||||
|
||||
fn get_ledge_option() -> Option<Action> {
|
||||
unsafe {
|
||||
let mut override_action: Option<Action> = None;
|
||||
let regular_action = if MENU.mash_triggers.contains(&MashTrigger::LEDGE) {
|
||||
Some(MENU.mash_state.get_random())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut override_action: Option<Action> = None;
|
||||
let regular_action = if read(&MENU).mash_triggers.contains(&MashTrigger::LEDGE) {
|
||||
Some(read(&MENU).mash_state.get_random())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match LEDGE_CASE {
|
||||
LedgeOption::NEUTRAL => {
|
||||
if MENU.ledge_neutral_override != Action::empty() {
|
||||
override_action = Some(MENU.ledge_neutral_override.get_random());
|
||||
}
|
||||
}
|
||||
LedgeOption::ROLL => {
|
||||
if MENU.ledge_roll_override != Action::empty() {
|
||||
override_action = Some(MENU.ledge_roll_override.get_random());
|
||||
}
|
||||
}
|
||||
LedgeOption::JUMP => {
|
||||
if MENU.ledge_jump_override != Action::empty() {
|
||||
override_action = Some(MENU.ledge_jump_override.get_random());
|
||||
}
|
||||
}
|
||||
LedgeOption::ATTACK => {
|
||||
if MENU.ledge_attack_override != Action::empty() {
|
||||
override_action = Some(MENU.ledge_attack_override.get_random());
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
override_action = None;
|
||||
match read(&LEDGE_CASE) {
|
||||
LedgeOption::NEUTRAL => {
|
||||
if read(&MENU).ledge_neutral_override != Action::empty() {
|
||||
override_action = Some(read(&MENU).ledge_neutral_override.get_random());
|
||||
}
|
||||
}
|
||||
override_action.or(regular_action)
|
||||
LedgeOption::ROLL => {
|
||||
if read(&MENU).ledge_roll_override != Action::empty() {
|
||||
override_action = Some(read(&MENU).ledge_roll_override.get_random());
|
||||
}
|
||||
}
|
||||
LedgeOption::JUMP => {
|
||||
if read(&MENU).ledge_jump_override != Action::empty() {
|
||||
override_action = Some(read(&MENU).ledge_jump_override.get_random());
|
||||
}
|
||||
}
|
||||
LedgeOption::ATTACK => {
|
||||
if read(&MENU).ledge_attack_override != Action::empty() {
|
||||
override_action = Some(read(&MENU).ledge_attack_override.get_random());
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
override_action = None;
|
||||
}
|
||||
}
|
||||
override_action.or(regular_action)
|
||||
}
|
||||
|
||||
pub unsafe fn force_option(module_accessor: &mut app::BattleObjectModuleAccessor) {
|
||||
|
@ -108,11 +100,13 @@ pub unsafe fn force_option(module_accessor: &mut app::BattleObjectModuleAccessor
|
|||
let flag_cliff =
|
||||
WorkModule::is_flag(module_accessor, *FIGHTER_INSTANCE_WORK_ID_FLAG_CATCH_CLIFF);
|
||||
let current_frame = MotionModule::frame(module_accessor) as i32;
|
||||
let ledge_delay = read(&LEDGE_DELAY);
|
||||
let ledge_case = read(&LEDGE_CASE);
|
||||
// Allow this because sometimes we want to make sure our NNSDK doesn't have
|
||||
// an erroneous definition
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
let status_kind = StatusModule::status_kind(module_accessor) as i32;
|
||||
let should_buffer_playback = (LEDGE_DELAY == 0) && (current_frame == 13); // 18 - 5 of buffer
|
||||
let should_buffer_playback = (ledge_delay == 0) && (current_frame == 13); // 18 - 5 of buffer
|
||||
let should_buffer;
|
||||
let prev_status_kind = StatusModule::prev_status_kind(module_accessor, 0);
|
||||
|
||||
|
@ -120,10 +114,10 @@ pub unsafe fn force_option(module_accessor: &mut app::BattleObjectModuleAccessor
|
|||
&& prev_status_kind == *FIGHTER_STATUS_KIND_CLIFF_CATCH
|
||||
{
|
||||
// For regular ledge grabs, we were just in catch and want to buffer on this frame
|
||||
should_buffer = (LEDGE_DELAY == 0) && (current_frame == 19) && (!flag_cliff);
|
||||
should_buffer = (ledge_delay == 0) && (current_frame == 19) && (!flag_cliff);
|
||||
} else if status_kind == *FIGHTER_STATUS_KIND_CLIFF_WAIT {
|
||||
// otherwise we're in "wait" from grabbing with lasso, so we want to buffer on frame
|
||||
should_buffer = (LEDGE_DELAY == 0) && (current_frame == 18) && (flag_cliff);
|
||||
should_buffer = (ledge_delay == 0) && (current_frame == 18) && (flag_cliff);
|
||||
} else {
|
||||
should_buffer = false;
|
||||
}
|
||||
|
@ -135,10 +129,10 @@ pub unsafe fn force_option(module_accessor: &mut app::BattleObjectModuleAccessor
|
|||
// Not able to take any action yet
|
||||
// We buffer playback on frame 18 because we don't change status this frame from inputting on next frame; do we need to do one earlier for lasso?
|
||||
if should_buffer_playback
|
||||
&& LEDGE_CASE.is_playback()
|
||||
&& MENU.ledge_delay != LongDelay::empty()
|
||||
&& ledge_case.is_playback()
|
||||
&& read(&MENU).ledge_delay != LongDelay::empty()
|
||||
{
|
||||
input_record::playback_ledge(LEDGE_CASE.playback_slot());
|
||||
input_record::playback_ledge(ledge_case.playback_slot());
|
||||
return;
|
||||
}
|
||||
// This check isn't reliable for buffered options in time, so don't return if we need to buffer an option this frame
|
||||
|
@ -147,19 +141,19 @@ pub unsafe fn force_option(module_accessor: &mut app::BattleObjectModuleAccessor
|
|||
}
|
||||
}
|
||||
|
||||
if LEDGE_CASE == LedgeOption::WAIT {
|
||||
if ledge_case == LedgeOption::WAIT {
|
||||
// Do nothing, but don't reset the ledge case.
|
||||
return;
|
||||
}
|
||||
|
||||
if frame_counter::should_delay(LEDGE_DELAY, *LEDGE_DELAY_COUNTER) {
|
||||
if frame_counter::should_delay(ledge_delay, *LEDGE_DELAY_COUNTER) {
|
||||
// Not yet time to perform the ledge action
|
||||
return;
|
||||
}
|
||||
|
||||
let status = LEDGE_CASE.into_status().unwrap_or(0);
|
||||
if LEDGE_CASE.is_playback() {
|
||||
input_record::playback(LEDGE_CASE.playback_slot());
|
||||
let status = ledge_case.into_status().unwrap_or(0);
|
||||
if ledge_case.is_playback() {
|
||||
input_record::playback(ledge_case.playback_slot());
|
||||
} else {
|
||||
StatusModule::change_status_request_from_script(module_accessor, status, true);
|
||||
}
|
||||
|
@ -179,16 +173,18 @@ pub unsafe fn is_enable_transition_term(
|
|||
|
||||
// Only handle ledge scenarios from menu
|
||||
if StatusModule::status_kind(_module_accessor) != *FIGHTER_STATUS_KIND_CLIFF_WAIT
|
||||
|| MENU.ledge_state == LedgeOption::empty()
|
||||
|| read(&MENU).ledge_state == LedgeOption::empty()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
// Disallow the default cliff-climb if we are waiting or we didn't get up during a recording
|
||||
let ledge_case = read(&LEDGE_CASE);
|
||||
let ledge_delay = read(&LEDGE_DELAY);
|
||||
if term == *FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_CLIFF_CLIMB
|
||||
&& ((LEDGE_CASE == LedgeOption::WAIT
|
||||
|| frame_counter::get_frame_count(*LEDGE_DELAY_COUNTER) < LEDGE_DELAY)
|
||||
|| (LEDGE_CASE.is_playback() && !input_record::is_playback()))
|
||||
&& ((ledge_case == LedgeOption::WAIT
|
||||
|| frame_counter::get_frame_count(*LEDGE_DELAY_COUNTER) < ledge_delay)
|
||||
|| (ledge_case.is_playback() && !input_record::is_playback()))
|
||||
{
|
||||
return Some(false);
|
||||
}
|
||||
|
@ -207,7 +203,7 @@ pub fn get_command_flag_cat(module_accessor: &mut app::BattleObjectModuleAccesso
|
|||
return;
|
||||
}
|
||||
|
||||
if MENU.ledge_state == LedgeOption::empty() {
|
||||
if read(&MENU).ledge_state == LedgeOption::empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,24 +11,20 @@ use crate::training::input_record;
|
|||
use crate::training::shield;
|
||||
use crate::training::{attack_angle, save_states};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use training_mod_sync::*;
|
||||
|
||||
const DISTANCE_CLOSE_THRESHOLD: f32 = 16.0;
|
||||
const DISTANCE_MID_THRESHOLD: f32 = 37.0;
|
||||
const DISTANCE_FAR_THRESHOLD: f32 = 64.0;
|
||||
|
||||
static mut CURRENT_AERIAL: Action = Action::NAIR;
|
||||
static mut QUEUE: Vec<Action> = vec![];
|
||||
static CURRENT_AERIAL: RwLock<Action> = RwLock::new(Action::NAIR);
|
||||
static QUEUE: RwLock<Vec<Action>> = RwLock::new(Vec::new());
|
||||
static FALLING_AERIAL: RwLock<bool> = RwLock::new(false);
|
||||
static AERIAL_DELAY: RwLock<u32> = RwLock::new(0);
|
||||
static IS_TRANSITIONING_DASH: RwLock<bool> = RwLock::new(false);
|
||||
|
||||
static mut FALLING_AERIAL: bool = false;
|
||||
|
||||
static mut AERIAL_DELAY: u32 = 0;
|
||||
|
||||
static AERIAL_DELAY_COUNTER: Lazy<usize> =
|
||||
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
|
||||
|
||||
// Track if we're about to do another command flag cat run in the same frame for a dash or dash attack
|
||||
static mut IS_TRANSITIONING_DASH: bool = false;
|
||||
static AERIAL_DELAY_COUNTER: LazyLock<usize> =
|
||||
LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
|
||||
|
||||
unsafe fn is_beginning_dash_attack(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
|
||||
let current_status = StatusModule::status_kind(module_accessor);
|
||||
|
@ -44,7 +40,8 @@ unsafe fn is_beginning_dash_attack(module_accessor: &mut app::BattleObjectModule
|
|||
}
|
||||
|
||||
unsafe fn dash_transition_check(module_accessor: &mut app::BattleObjectModuleAccessor) {
|
||||
IS_TRANSITIONING_DASH &= is_dashing_for_dash_attack(module_accessor);
|
||||
let mut is_transitioning_dash = lock_write(&IS_TRANSITIONING_DASH);
|
||||
*is_transitioning_dash &= is_dashing_for_dash_attack(module_accessor);
|
||||
}
|
||||
|
||||
pub fn is_playback_queued() -> bool {
|
||||
|
@ -66,21 +63,22 @@ pub unsafe fn is_dashing_for_dash_attack(
|
|||
}
|
||||
|
||||
pub fn buffer_action(action: Action) {
|
||||
unsafe {
|
||||
if !QUEUE.is_empty() {
|
||||
return;
|
||||
}
|
||||
let queue = lock_read(&QUEUE);
|
||||
if !queue.is_empty() {
|
||||
// Something is already buffered
|
||||
return;
|
||||
}
|
||||
drop(queue);
|
||||
|
||||
if action == Action::empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// We want to allow for triggering a mash to end playback for neutral playbacks, but not for SDI/disadv playbacks
|
||||
// exit playback if we want to perform mash actions out of it
|
||||
// TODO: Figure out some way to deal with trying to playback into another playback
|
||||
unsafe {
|
||||
// exit playback if we want to perform mash actions out of it
|
||||
// TODO: Figure out some way to deal with trying to playback into another playback
|
||||
if MENU.playback_mash == OnOff::ON
|
||||
if read(&MENU).playback_mash == OnOff::ON
|
||||
&& input_record::is_playback()
|
||||
&& !input_record::is_recording()
|
||||
&& !input_record::is_standby()
|
||||
|
@ -96,21 +94,16 @@ pub fn buffer_action(action: Action) {
|
|||
}
|
||||
|
||||
attack_angle::roll_direction();
|
||||
|
||||
roll_aerial_delay(action);
|
||||
|
||||
unsafe {
|
||||
QUEUE.insert(0, action);
|
||||
buffer_follow_up();
|
||||
}
|
||||
let mut queue = lock_write(&QUEUE);
|
||||
queue.insert(0, action);
|
||||
drop(queue);
|
||||
buffer_follow_up();
|
||||
}
|
||||
|
||||
pub fn buffer_follow_up() {
|
||||
let action;
|
||||
|
||||
unsafe {
|
||||
action = MENU.follow_up.get_random();
|
||||
}
|
||||
let action = read(&MENU).follow_up.get_random();
|
||||
|
||||
if action == Action::empty() {
|
||||
return;
|
||||
|
@ -118,60 +111,44 @@ pub fn buffer_follow_up() {
|
|||
|
||||
roll_aerial_delay(action);
|
||||
|
||||
unsafe {
|
||||
QUEUE.insert(0, action);
|
||||
}
|
||||
let mut queue = lock_write(&QUEUE);
|
||||
queue.insert(0, action);
|
||||
drop(queue);
|
||||
}
|
||||
|
||||
pub fn get_current_buffer() -> Action {
|
||||
unsafe {
|
||||
if QUEUE.is_empty() {
|
||||
return Action::empty();
|
||||
}
|
||||
|
||||
*QUEUE.last().unwrap()
|
||||
}
|
||||
let queue = lock_read(&QUEUE);
|
||||
*(queue.last().unwrap_or(&Action::empty()))
|
||||
}
|
||||
|
||||
pub fn reset() {
|
||||
unsafe {
|
||||
QUEUE.pop();
|
||||
}
|
||||
let mut queue = lock_write(&QUEUE);
|
||||
queue.pop();
|
||||
drop(queue);
|
||||
|
||||
shield::suspend_shield(get_current_buffer());
|
||||
|
||||
unsafe {
|
||||
frame_counter::full_reset(*AERIAL_DELAY_COUNTER);
|
||||
AERIAL_DELAY = 0;
|
||||
}
|
||||
frame_counter::full_reset(*AERIAL_DELAY_COUNTER);
|
||||
assign(&AERIAL_DELAY, 0);
|
||||
}
|
||||
|
||||
pub fn full_reset() {
|
||||
unsafe {
|
||||
while !QUEUE.is_empty() {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
clear_queue();
|
||||
reset();
|
||||
}
|
||||
|
||||
pub fn clear_queue() {
|
||||
unsafe { QUEUE.clear() }
|
||||
assign(&QUEUE, Vec::new());
|
||||
}
|
||||
|
||||
pub fn set_aerial(attack: Action) {
|
||||
unsafe {
|
||||
CURRENT_AERIAL = attack;
|
||||
}
|
||||
assign(&CURRENT_AERIAL, attack);
|
||||
}
|
||||
|
||||
pub unsafe fn get_attack_air_kind(
|
||||
module_accessor: &mut app::BattleObjectModuleAccessor,
|
||||
) -> Option<i32> {
|
||||
pub fn get_attack_air_kind(module_accessor: &mut app::BattleObjectModuleAccessor) -> Option<i32> {
|
||||
if !is_operation_cpu(module_accessor) {
|
||||
return None;
|
||||
}
|
||||
|
||||
CURRENT_AERIAL.into_attack_air_kind()
|
||||
read(&CURRENT_AERIAL).into_attack_air_kind()
|
||||
}
|
||||
|
||||
pub unsafe fn get_command_flag_cat(
|
||||
|
@ -216,91 +193,92 @@ unsafe fn get_buffered_action(
|
|||
return None;
|
||||
}
|
||||
let fighter_distance = get_fighter_distance();
|
||||
let menu = read(&MENU);
|
||||
if is_in_tech(module_accessor) {
|
||||
let action = MENU.tech_action_override.get_random();
|
||||
let action = menu.tech_action_override.get_random();
|
||||
if action != Action::empty() {
|
||||
Some(action)
|
||||
} else if MENU.mash_triggers.contains(&MashTrigger::TECH) {
|
||||
Some(MENU.mash_state.get_random())
|
||||
} else if menu.mash_triggers.contains(&MashTrigger::TECH) {
|
||||
Some(menu.mash_state.get_random())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if is_in_clatter(module_accessor) {
|
||||
let action = MENU.clatter_override.get_random();
|
||||
let action = menu.clatter_override.get_random();
|
||||
if action != Action::empty() {
|
||||
Some(action)
|
||||
} else if MENU.mash_triggers.contains(&MashTrigger::CLATTER) {
|
||||
Some(MENU.mash_state.get_random())
|
||||
} else if menu.mash_triggers.contains(&MashTrigger::CLATTER) {
|
||||
Some(menu.mash_state.get_random())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if is_in_tumble(module_accessor) {
|
||||
// Note that the tumble check needs to come before hitstun,
|
||||
// otherwise the hitstun check will always return first
|
||||
let action = MENU.tumble_override.get_random();
|
||||
let action = menu.tumble_override.get_random();
|
||||
if action != Action::empty() {
|
||||
Some(action)
|
||||
} else if MENU.mash_triggers.contains(&MashTrigger::TUMBLE) {
|
||||
Some(MENU.mash_state.get_random())
|
||||
} else if menu.mash_triggers.contains(&MashTrigger::TUMBLE) {
|
||||
Some(menu.mash_state.get_random())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if is_in_hitstun(module_accessor) {
|
||||
let action = MENU.hitstun_override.get_random();
|
||||
let action = menu.hitstun_override.get_random();
|
||||
if action != Action::empty() {
|
||||
Some(action)
|
||||
} else if MENU.mash_triggers.contains(&MashTrigger::HIT) {
|
||||
Some(MENU.mash_state.get_random())
|
||||
} else if menu.mash_triggers.contains(&MashTrigger::HIT) {
|
||||
Some(menu.mash_state.get_random())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if is_in_parry(module_accessor) {
|
||||
let action = MENU.parry_override.get_random();
|
||||
let action = menu.parry_override.get_random();
|
||||
if action != Action::empty() {
|
||||
Some(action)
|
||||
} else if MENU.mash_triggers.contains(&MashTrigger::PARRY) {
|
||||
Some(MENU.mash_state.get_random())
|
||||
} else if menu.mash_triggers.contains(&MashTrigger::PARRY) {
|
||||
Some(menu.mash_state.get_random())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if is_in_footstool(module_accessor) {
|
||||
let action = MENU.footstool_override.get_random();
|
||||
let action = menu.footstool_override.get_random();
|
||||
if action != Action::empty() {
|
||||
Some(action)
|
||||
} else if MENU.mash_triggers.contains(&MashTrigger::FOOTSTOOL) {
|
||||
Some(MENU.mash_state.get_random())
|
||||
} else if menu.mash_triggers.contains(&MashTrigger::FOOTSTOOL) {
|
||||
Some(menu.mash_state.get_random())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if is_in_ledgetrump(module_accessor) {
|
||||
let action = MENU.trump_override.get_random();
|
||||
let action = menu.trump_override.get_random();
|
||||
if action != Action::empty() {
|
||||
Some(action)
|
||||
} else if MENU.mash_triggers.contains(&MashTrigger::TRUMP) {
|
||||
Some(MENU.mash_state.get_random())
|
||||
} else if menu.mash_triggers.contains(&MashTrigger::TRUMP) {
|
||||
Some(menu.mash_state.get_random())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if is_in_landing(module_accessor) {
|
||||
let action = MENU.landing_override.get_random();
|
||||
let action = menu.landing_override.get_random();
|
||||
if action != Action::empty() {
|
||||
Some(action)
|
||||
} else if MENU.mash_triggers.contains(&MashTrigger::LANDING) {
|
||||
Some(MENU.mash_state.get_random())
|
||||
} else if menu.mash_triggers.contains(&MashTrigger::LANDING) {
|
||||
Some(menu.mash_state.get_random())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if (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)
|
||||
} else if (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)
|
||||
|| (menu.mash_triggers.contains(&MashTrigger::DISTANCE_MID)
|
||||
&& fighter_distance < DISTANCE_MID_THRESHOLD)
|
||||
|| (MENU.mash_triggers.contains(&MashTrigger::DISTANCE_FAR)
|
||||
|| (menu.mash_triggers.contains(&MashTrigger::DISTANCE_FAR)
|
||||
&& fighter_distance < DISTANCE_FAR_THRESHOLD)
|
||||
|| MENU.mash_triggers.contains(&MashTrigger::ALWAYS)
|
||||
|| menu.mash_triggers.contains(&MashTrigger::ALWAYS)
|
||||
{
|
||||
Some(MENU.mash_state.get_random())
|
||||
Some(menu.mash_state.get_random())
|
||||
} else {
|
||||
// SHIELD handled in shield.rs
|
||||
// LEDGE handled in ledge.rs
|
||||
|
@ -309,12 +287,13 @@ unsafe fn get_buffered_action(
|
|||
}
|
||||
|
||||
fn buffer_menu_mash(action: Action) {
|
||||
unsafe {
|
||||
buffer_action(action);
|
||||
full_hop::roll_full_hop();
|
||||
fast_fall::roll_fast_fall();
|
||||
FALLING_AERIAL = MENU.falling_aerials.get_random().into_bool();
|
||||
}
|
||||
buffer_action(action);
|
||||
full_hop::roll_full_hop();
|
||||
fast_fall::roll_fast_fall();
|
||||
assign(
|
||||
&FALLING_AERIAL,
|
||||
read(&MENU).falling_aerials.get_random().into_bool(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn external_buffer_menu_mash(action: Action) {
|
||||
|
@ -510,9 +489,10 @@ unsafe fn get_attack_flag(
|
|||
|
||||
if current_status == *FIGHTER_STATUS_KIND_DASH && motion_frame == 0.0 && is_motion_dash
|
||||
{
|
||||
if !IS_TRANSITIONING_DASH {
|
||||
let mut is_transitioning_dash = lock_write(&IS_TRANSITIONING_DASH);
|
||||
if !*is_transitioning_dash {
|
||||
// The first time these conditions are met, we aren't ready to begin dash attacking, so get ready to transition next frame
|
||||
IS_TRANSITIONING_DASH = true;
|
||||
*is_transitioning_dash = true;
|
||||
} else {
|
||||
// Begin dash attacking now that we've dashed for one frame
|
||||
StatusModule::change_status_request_from_script(module_accessor, status, true);
|
||||
|
@ -546,7 +526,7 @@ unsafe fn get_aerial_flag(
|
|||
|
||||
let status = *FIGHTER_STATUS_KIND_ATTACK_AIR;
|
||||
|
||||
if FALLING_AERIAL && !fast_fall::is_falling(module_accessor) {
|
||||
if read(&FALLING_AERIAL) && !fast_fall::is_falling(module_accessor) {
|
||||
return flag;
|
||||
}
|
||||
|
||||
|
@ -576,17 +556,20 @@ fn roll_aerial_delay(action: Action) {
|
|||
if !shield::is_aerial(action) {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
AERIAL_DELAY = MENU.aerial_delay.get_random().into_delay();
|
||||
}
|
||||
|
||||
assign(
|
||||
&AERIAL_DELAY,
|
||||
read(&MENU).aerial_delay.get_random().into_delay(),
|
||||
);
|
||||
}
|
||||
|
||||
fn should_delay_aerial(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
|
||||
unsafe {
|
||||
if AERIAL_DELAY == 0 {
|
||||
return false;
|
||||
}
|
||||
let aerial_delay = read(&AERIAL_DELAY);
|
||||
if aerial_delay == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
if StatusModule::status_kind(module_accessor) == *FIGHTER_STATUS_KIND_ATTACK_AIR {
|
||||
return false;
|
||||
}
|
||||
|
@ -597,9 +580,8 @@ fn should_delay_aerial(module_accessor: &mut app::BattleObjectModuleAccessor) ->
|
|||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
frame_counter::should_delay(AERIAL_DELAY, *AERIAL_DELAY_COUNTER)
|
||||
}
|
||||
frame_counter::should_delay(aerial_delay, *AERIAL_DELAY_COUNTER)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,6 +19,7 @@ use smash::app::{
|
|||
use smash::lib::lua_const::*;
|
||||
use smash::params::*;
|
||||
use smash::phx::{Hash40, Vector3f};
|
||||
use training_mod_sync::*;
|
||||
|
||||
pub mod buff;
|
||||
pub mod charge;
|
||||
|
@ -133,7 +134,7 @@ fn once_per_frame_per_fighter(module_accessor: &mut BattleObjectModuleAccessor,
|
|||
// Handle dodge staling here b/c input recording or mash can cause dodging
|
||||
WorkModule::set_flag(
|
||||
module_accessor,
|
||||
!(MENU.stale_dodges.as_bool()),
|
||||
!(read(&MENU).stale_dodges.as_bool()),
|
||||
*FIGHTER_INSTANCE_WORK_ID_FLAG_DISABLE_ESCAPE_PENALTY,
|
||||
);
|
||||
input_record::handle_recording();
|
||||
|
@ -141,7 +142,7 @@ fn once_per_frame_per_fighter(module_accessor: &mut BattleObjectModuleAccessor,
|
|||
tech::hide_tech();
|
||||
}
|
||||
|
||||
combo::get_command_flag_cat(module_accessor);
|
||||
combo::once_per_frame(module_accessor);
|
||||
hitbox_visualizer::get_command_flag_cat(module_accessor);
|
||||
save_states::save_states(module_accessor);
|
||||
tech::get_command_flag_cat(module_accessor);
|
||||
|
@ -312,7 +313,6 @@ pub unsafe fn handle_is_enable_transition_term(
|
|||
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,
|
||||
|
@ -428,7 +428,7 @@ pub unsafe fn handle_add_damage(
|
|||
#[skyline::hook(offset = *OFFSET_TRAINING_RESET_CHECK, inline)]
|
||||
unsafe fn lra_handle(ctx: &mut InlineCtx) {
|
||||
let x8 = ctx.registers[8].x.as_mut();
|
||||
if !(MENU.lra_reset.as_bool()) {
|
||||
if !(read(&MENU).lra_reset.as_bool()) {
|
||||
*x8 = 0;
|
||||
}
|
||||
}
|
||||
|
@ -766,7 +766,7 @@ pub unsafe fn handle_reused_ui(
|
|||
// If Little Mac is in the game and we're buffing him, set the meter to 100
|
||||
if (player_fighter_kind == *FIGHTER_KIND_LITTLEMAC
|
||||
|| cpu_fighter_kind == *FIGHTER_KIND_LITTLEMAC)
|
||||
&& MENU.buff_state.contains(&BuffOption::KO)
|
||||
&& read(&MENU).buff_state.contains(&BuffOption::KO)
|
||||
{
|
||||
param_2 = 100;
|
||||
}
|
||||
|
@ -837,26 +837,33 @@ pub fn training_mods() {
|
|||
info!("Applying training mods.");
|
||||
|
||||
unsafe {
|
||||
let mut fighter_manager_addr_lock = lock_write(&FIGHTER_MANAGER_ADDR);
|
||||
LookupSymbol(
|
||||
addr_of_mut!(FIGHTER_MANAGER_ADDR),
|
||||
addr_of_mut!(*fighter_manager_addr_lock),
|
||||
"_ZN3lib9SingletonIN3app14FighterManagerEE9instance_E\u{0}"
|
||||
.as_bytes()
|
||||
.as_ptr(),
|
||||
);
|
||||
drop(fighter_manager_addr_lock);
|
||||
|
||||
// TODO!("This seems unused? Can we remove it?")
|
||||
let mut stage_manager_addr_lock = lock_write(&STAGE_MANAGER_ADDR);
|
||||
LookupSymbol(
|
||||
addr_of_mut!(STAGE_MANAGER_ADDR),
|
||||
addr_of_mut!(*stage_manager_addr_lock),
|
||||
"_ZN3lib9SingletonIN3app12StageManagerEE9instance_E\u{0}"
|
||||
.as_bytes()
|
||||
.as_ptr(),
|
||||
);
|
||||
drop(stage_manager_addr_lock);
|
||||
|
||||
let mut item_manager_addr_lock = lock_write(&ITEM_MANAGER_ADDR);
|
||||
LookupSymbol(
|
||||
addr_of_mut!(ITEM_MANAGER_ADDR),
|
||||
addr_of_mut!(*item_manager_addr_lock),
|
||||
"_ZN3lib9SingletonIN3app11ItemManagerEE9instance_E\0"
|
||||
.as_bytes()
|
||||
.as_ptr(),
|
||||
);
|
||||
drop(item_manager_addr_lock);
|
||||
|
||||
add_hook(params_main).unwrap();
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ use crate::training::items::apply_item;
|
|||
use crate::training::reset;
|
||||
use crate::training::ui::notifications;
|
||||
use crate::{is_ptrainer, ITEM_MANAGER_ADDR};
|
||||
use training_mod_sync::*;
|
||||
|
||||
// Don't remove Mii hats, Pikmin, Luma, or crafting table
|
||||
const ARTICLE_ALLOWLIST: [(LuaConst, LuaConst); 9] = [
|
||||
|
@ -232,11 +233,11 @@ static mut MIRROR_STATE: f32 = 1.0;
|
|||
static mut RANDOM_SLOT: usize = 0;
|
||||
|
||||
unsafe fn get_slot() -> usize {
|
||||
let random_slot = MENU.randomize_slots.get_random();
|
||||
let random_slot = read(&MENU).randomize_slots.get_random();
|
||||
if random_slot != SaveStateSlot::empty() {
|
||||
RANDOM_SLOT
|
||||
} else {
|
||||
MENU.save_state_slot.into_idx().unwrap_or(0)
|
||||
read(&MENU).save_state_slot.into_idx().unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -255,13 +256,13 @@ pub unsafe fn is_loading() -> bool {
|
|||
}
|
||||
|
||||
pub unsafe fn should_mirror() -> f32 {
|
||||
match MENU.save_state_mirroring {
|
||||
match read(&MENU).save_state_mirroring {
|
||||
SaveStateMirroring::NONE => 1.0,
|
||||
SaveStateMirroring::ALTERNATE => -1.0 * MIRROR_STATE,
|
||||
SaveStateMirroring::RANDOM => ([-1.0, 1.0])[get_random_int(2) as usize],
|
||||
_ => panic!(
|
||||
"Invalid value in should_mirror: {}",
|
||||
MENU.save_state_mirroring
|
||||
read(&MENU).save_state_mirroring
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -383,7 +384,7 @@ pub unsafe fn on_death(fighter_kind: i32, module_accessor: &mut app::BattleObjec
|
|||
ArticleModule::remove_exist_object_id(module_accessor, article_object_id as u32);
|
||||
}
|
||||
});
|
||||
let item_mgr = *(ITEM_MANAGER_ADDR as *mut *mut app::ItemManager);
|
||||
let item_mgr = *(read(&ITEM_MANAGER_ADDR) as *mut *mut app::ItemManager);
|
||||
(0..ItemManager::get_num_of_active_item_all(item_mgr)).for_each(|item_idx| {
|
||||
let item = ItemManager::get_active_item(item_mgr, item_idx);
|
||||
if item != 0 {
|
||||
|
@ -416,7 +417,7 @@ pub unsafe fn on_death(fighter_kind: i32, module_accessor: &mut app::BattleObjec
|
|||
}
|
||||
|
||||
pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor) {
|
||||
if MENU.save_state_enable == OnOff::OFF {
|
||||
if read(&MENU).save_state_enable == OnOff::OFF {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -448,7 +449,7 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
|
|||
.contains(&fighter_kind);
|
||||
|
||||
// Reset state
|
||||
let autoload_reset = MENU.save_state_autoload == OnOff::ON
|
||||
let autoload_reset = read(&MENU).save_state_autoload == OnOff::ON
|
||||
&& save_state.state == NoAction
|
||||
&& is_dead(module_accessor);
|
||||
let mut triggered_reset: bool = false;
|
||||
|
@ -457,7 +458,7 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
|
|||
}
|
||||
if (autoload_reset || triggered_reset) && !fighter_is_nana {
|
||||
if save_state.state == NoAction {
|
||||
let random_slot = MENU.randomize_slots.get_random();
|
||||
let random_slot = read(&MENU).randomize_slots.get_random();
|
||||
let slot = if random_slot != SaveStateSlot::empty() {
|
||||
RANDOM_SLOT = random_slot.into_idx().unwrap_or(0);
|
||||
RANDOM_SLOT
|
||||
|
@ -572,48 +573,48 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
|
|||
if save_state.state == NoAction {
|
||||
// Set damage of the save state
|
||||
if !is_cpu {
|
||||
match MENU.save_damage_player {
|
||||
match read(&MENU).save_damage_player {
|
||||
SaveDamage::SAVED => {
|
||||
set_damage(module_accessor, save_state.percent);
|
||||
}
|
||||
SaveDamage::RANDOM => {
|
||||
// Gen random value
|
||||
let pct: f32 = get_random_float(
|
||||
MENU.save_damage_limits_player.0 as f32,
|
||||
MENU.save_damage_limits_player.1 as f32,
|
||||
read(&MENU).save_damage_limits_player.0 as f32,
|
||||
read(&MENU).save_damage_limits_player.1 as f32,
|
||||
);
|
||||
set_damage(module_accessor, pct);
|
||||
}
|
||||
SaveDamage::DEFAULT => {}
|
||||
_ => panic!(
|
||||
"Invalid value in save_states()::save_damage_player: {}",
|
||||
MENU.save_damage_player
|
||||
read(&MENU).save_damage_player
|
||||
),
|
||||
}
|
||||
} else {
|
||||
match MENU.save_damage_cpu {
|
||||
match read(&MENU).save_damage_cpu {
|
||||
SaveDamage::SAVED => {
|
||||
set_damage(module_accessor, save_state.percent);
|
||||
}
|
||||
SaveDamage::RANDOM => {
|
||||
// Gen random value
|
||||
let pct: f32 = get_random_float(
|
||||
MENU.save_damage_limits_cpu.0 as f32,
|
||||
MENU.save_damage_limits_cpu.1 as f32,
|
||||
read(&MENU).save_damage_limits_cpu.0 as f32,
|
||||
read(&MENU).save_damage_limits_cpu.1 as f32,
|
||||
);
|
||||
set_damage(module_accessor, pct);
|
||||
}
|
||||
SaveDamage::DEFAULT => {}
|
||||
_ => panic!(
|
||||
"Invalid value in save_states()::save_damage_cpu: {}",
|
||||
MENU.save_damage_cpu
|
||||
read(&MENU).save_damage_cpu
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// Set to held item
|
||||
if !is_cpu && !fighter_is_nana && MENU.character_item != CharacterItem::NONE {
|
||||
apply_item(MENU.character_item);
|
||||
if !is_cpu && !fighter_is_nana && read(&MENU).character_item != CharacterItem::NONE {
|
||||
apply_item(read(&MENU).character_item);
|
||||
}
|
||||
|
||||
// Set the charge of special moves if the fighter matches the kind in the save state
|
||||
|
@ -661,17 +662,20 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
|
|||
}
|
||||
|
||||
// if we're recording on state load, record
|
||||
if MENU.record_trigger.contains(&RecordTrigger::SAVESTATE) {
|
||||
if read(&MENU)
|
||||
.record_trigger
|
||||
.contains(&RecordTrigger::SAVESTATE)
|
||||
{
|
||||
input_record::lockout_record();
|
||||
return;
|
||||
}
|
||||
// otherwise, begin input recording playback if selected
|
||||
// for ledge, don't do this - if you want playback on a ledge, you have to set it as a ledge option,
|
||||
// otherwise there too many edge cases here
|
||||
else if MENU.save_state_playback.get_random() != PlaybackSlot::empty()
|
||||
else if read(&MENU).save_state_playback.get_random() != PlaybackSlot::empty()
|
||||
&& save_state.situation_kind != SITUATION_KIND_CLIFF
|
||||
{
|
||||
input_record::playback(MENU.save_state_playback.get_random().into_idx());
|
||||
input_record::playback(read(&MENU).save_state_playback.get_random().into_idx());
|
||||
}
|
||||
|
||||
return;
|
||||
|
@ -706,12 +710,12 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
|
|||
if button_config::combo_passes(button_config::ButtonCombo::SaveState) {
|
||||
// Don't begin saving state if Nana's delayed input is captured
|
||||
MIRROR_STATE = 1.0;
|
||||
save_state_player(MENU.save_state_slot.into_idx().unwrap_or(0)).state = Save;
|
||||
save_state_cpu(MENU.save_state_slot.into_idx().unwrap_or(0)).state = Save;
|
||||
notifications::clear_notifications("Save State");
|
||||
save_state_player(read(&MENU).save_state_slot.into_idx().unwrap_or(0)).state = Save;
|
||||
save_state_cpu(read(&MENU).save_state_slot.into_idx().unwrap_or(0)).state = Save;
|
||||
notifications::clear_notification("Save State");
|
||||
notifications::notification(
|
||||
"Save State".to_string(),
|
||||
format!("Saved Slot {}", MENU.save_state_slot),
|
||||
format!("Saved Slot {}", read(&MENU).save_state_slot),
|
||||
120,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,21 +6,21 @@ use smash::Vector2f;
|
|||
use crate::common::consts::*;
|
||||
use crate::common::*;
|
||||
use crate::training::directional_influence;
|
||||
use training_mod_sync::*;
|
||||
|
||||
static mut COUNTER: u32 = 0;
|
||||
|
||||
static mut DIRECTION: Direction = Direction::NEUTRAL;
|
||||
static COUNTER: RwLock<u32> = RwLock::new(0);
|
||||
static DIRECTION: RwLock<Direction> = RwLock::new(Direction::NEUTRAL);
|
||||
|
||||
// TODO! Bug - we only roll a new direction when loading a save state or on LRA reset
|
||||
pub fn roll_direction() {
|
||||
unsafe {
|
||||
COUNTER = 0;
|
||||
DIRECTION = MENU.sdi_state.get_random();
|
||||
}
|
||||
assign(&COUNTER, 0);
|
||||
assign(&DIRECTION, read(&MENU).sdi_state.get_random());
|
||||
}
|
||||
|
||||
unsafe fn get_sdi_direction() -> Option<f64> {
|
||||
DIRECTION.into_angle().map(|angle| {
|
||||
if directional_influence::should_reverse_angle(DIRECTION) {
|
||||
let direction = read(&DIRECTION);
|
||||
direction.into_angle().map(|angle| {
|
||||
if directional_influence::should_reverse_angle(direction) {
|
||||
PI - angle
|
||||
} else {
|
||||
angle
|
||||
|
@ -38,10 +38,10 @@ pub unsafe fn check_hit_stop_delay_command(
|
|||
if !is_training_mode() || !is_operation_cpu(module_accessor) {
|
||||
return original!()(module_accessor, sdi_direction);
|
||||
}
|
||||
let repeat = MENU.sdi_strength.into_u32();
|
||||
|
||||
COUNTER = (COUNTER + 1) % repeat;
|
||||
if COUNTER == repeat - 1 {
|
||||
let repeat = read(&MENU).sdi_strength.into_u32();
|
||||
let mut counter_lock = lock_write(&COUNTER);
|
||||
*counter_lock = (*counter_lock + 1) % repeat;
|
||||
if *counter_lock == repeat - 1 {
|
||||
if let Some(angle) = get_sdi_direction() {
|
||||
// If there is a non-neutral direction picked,
|
||||
// modify the SDI angle Vector2f as a side-effect
|
||||
|
|
|
@ -10,72 +10,81 @@ use crate::common::consts::*;
|
|||
use crate::common::*;
|
||||
use crate::training::{frame_counter, input_record, mash, save_states};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use training_mod_sync::*;
|
||||
|
||||
// TODO!() We only reset this on save state load or LRA reset
|
||||
// How many hits to hold shield until picking an Out Of Shield option
|
||||
static mut MULTI_HIT_OFFSET: u32 = 0;
|
||||
static MULTI_HIT_OFFSET: RwLock<u32> = RwLock::new(0);
|
||||
|
||||
// The current set delay
|
||||
static mut SHIELD_DELAY: u32 = 0;
|
||||
static SHIELD_DELAY: RwLock<u32> = RwLock::new(0);
|
||||
|
||||
// Used to only decrease once per shieldstun change
|
||||
static mut WAS_IN_SHIELDSTUN: bool = false;
|
||||
static WAS_IN_SHIELDSTUN: RwLock<bool> = RwLock::new(false);
|
||||
|
||||
// For how many frames should the shield hold be overwritten
|
||||
static mut SUSPEND_SHIELD: bool = false;
|
||||
|
||||
static REACTION_COUNTER_INDEX: Lazy<usize> =
|
||||
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
|
||||
static SUSPEND_SHIELD: RwLock<bool> = RwLock::new(false);
|
||||
|
||||
// Toggle for shield decay
|
||||
static mut SHIELD_DECAY: bool = false;
|
||||
static SHIELD_DECAY: RwLock<bool> = RwLock::new(false);
|
||||
|
||||
/// This is the cached shield damage multiplier.
|
||||
/// Vanilla is 1.19, but mods can change this.
|
||||
static CACHED_SHIELD_DAMAGE_MUL: RwLock<Option<f32>> = RwLock::new(None);
|
||||
|
||||
static REACTION_COUNTER_INDEX: LazyLock<usize> =
|
||||
LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
|
||||
|
||||
fn set_shield_decay(value: bool) {
|
||||
unsafe {
|
||||
SHIELD_DECAY = value;
|
||||
}
|
||||
assign(&SHIELD_DECAY, value);
|
||||
}
|
||||
|
||||
fn should_pause_shield_decay() -> bool {
|
||||
unsafe { !SHIELD_DECAY }
|
||||
!read(&SHIELD_DECAY)
|
||||
}
|
||||
|
||||
fn reset_oos_offset() {
|
||||
unsafe {
|
||||
/*
|
||||
* Need to offset by 1, since we decrease as soon as shield gets hit
|
||||
* but only check later if we can OOS
|
||||
*/
|
||||
MULTI_HIT_OFFSET = MENU.oos_offset.get_random().into_delay() + 1;
|
||||
}
|
||||
/*
|
||||
* Need to offset by 1, since we decrease as soon as shield gets hit
|
||||
* but only check later if we can OOS
|
||||
*/
|
||||
assign(
|
||||
&MULTI_HIT_OFFSET,
|
||||
read(&MENU).oos_offset.get_random().into_delay() + 1,
|
||||
);
|
||||
}
|
||||
|
||||
unsafe fn handle_oos_offset(module_accessor: &mut app::BattleObjectModuleAccessor) {
|
||||
fn handle_oos_offset(module_accessor: &mut app::BattleObjectModuleAccessor) {
|
||||
// Check if we are currently in shield stun
|
||||
let mut was_in_shieldstun_lock = lock_write(&WAS_IN_SHIELDSTUN);
|
||||
if !is_in_shieldstun(module_accessor) {
|
||||
// Make sure we don't forget and wait until we get hit on shield
|
||||
WAS_IN_SHIELDSTUN = false;
|
||||
*was_in_shieldstun_lock = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we just freshly entered shield stun
|
||||
if WAS_IN_SHIELDSTUN {
|
||||
if *was_in_shieldstun_lock {
|
||||
return;
|
||||
}
|
||||
|
||||
// Roll shield delay
|
||||
SHIELD_DELAY = MENU.reaction_time.get_random().into_delay();
|
||||
assign(
|
||||
&SHIELD_DELAY,
|
||||
read(&MENU).reaction_time.get_random().into_delay(),
|
||||
);
|
||||
|
||||
// Decrease offset once if needed
|
||||
MULTI_HIT_OFFSET = MULTI_HIT_OFFSET.saturating_sub(1);
|
||||
let mut multi_hit_offset_lock = lock_write(&MULTI_HIT_OFFSET);
|
||||
*multi_hit_offset_lock = (*multi_hit_offset_lock).saturating_sub(1);
|
||||
|
||||
// Mark that we were in shield stun, so we don't decrease again
|
||||
WAS_IN_SHIELDSTUN = true;
|
||||
*was_in_shieldstun_lock = true;
|
||||
}
|
||||
|
||||
pub unsafe fn allow_oos() -> bool {
|
||||
pub fn allow_oos() -> bool {
|
||||
// Delay OOS until offset hits 0
|
||||
MULTI_HIT_OFFSET == 0
|
||||
read(&MULTI_HIT_OFFSET) == 0
|
||||
}
|
||||
|
||||
pub fn get_command_flag_cat(module_accessor: &mut app::BattleObjectModuleAccessor) {
|
||||
|
@ -107,7 +116,7 @@ pub unsafe fn get_param_float(
|
|||
return None;
|
||||
}
|
||||
|
||||
if MENU.shield_state != Shield::NONE {
|
||||
if read(&MENU).shield_state != Shield::NONE {
|
||||
handle_oos_offset(module_accessor);
|
||||
}
|
||||
|
||||
|
@ -116,10 +125,7 @@ pub unsafe fn get_param_float(
|
|||
|
||||
// Shield Decay//Recovery
|
||||
fn handle_shield_decay(param_type: u64, param_hash: u64) -> Option<f32> {
|
||||
let menu_state;
|
||||
unsafe {
|
||||
menu_state = MENU.shield_state;
|
||||
}
|
||||
let menu_state = read(&MENU).shield_state;
|
||||
|
||||
if menu_state != Shield::INFINITE
|
||||
&& menu_state != Shield::CONSTANT
|
||||
|
@ -143,10 +149,6 @@ fn handle_shield_decay(param_type: u64, param_hash: u64) -> Option<f32> {
|
|||
None
|
||||
}
|
||||
|
||||
/// This is the cached shield damage multiplier.
|
||||
/// Vanilla is 1.19, but mods can change this.
|
||||
static mut CACHED_SHIELD_DAMAGE_MUL: Option<f32> = None;
|
||||
|
||||
/// sets/resets the shield_damage_mul within
|
||||
/// the game's internal structure.
|
||||
///
|
||||
|
@ -155,20 +157,20 @@ static mut CACHED_SHIELD_DAMAGE_MUL: Option<f32> = None;
|
|||
pub unsafe fn param_installer() {
|
||||
if crate::training::COMMON_PARAMS as usize != 0 {
|
||||
let common_params = &mut *crate::training::COMMON_PARAMS;
|
||||
|
||||
let mut cached_shield_damage_mul_lock = lock_write(&CACHED_SHIELD_DAMAGE_MUL);
|
||||
// cache the original shield damage multiplier once
|
||||
if CACHED_SHIELD_DAMAGE_MUL.is_none() {
|
||||
CACHED_SHIELD_DAMAGE_MUL = Some(common_params.shield_damage_mul);
|
||||
if (*cached_shield_damage_mul_lock).is_none() {
|
||||
*cached_shield_damage_mul_lock = Some(common_params.shield_damage_mul);
|
||||
}
|
||||
|
||||
if is_training_mode() && (MENU.shield_state == Shield::INFINITE) {
|
||||
if is_training_mode() && (read(&MENU).shield_state == Shield::INFINITE) {
|
||||
// if you are in training mode and have infinite shield enabled,
|
||||
// set the game's shield_damage_mul to 0.0
|
||||
common_params.shield_damage_mul = 0.0;
|
||||
} else {
|
||||
// reset the game's shield_damage_mul back to what
|
||||
// it originally was at game boot.
|
||||
common_params.shield_damage_mul = CACHED_SHIELD_DAMAGE_MUL.unwrap();
|
||||
common_params.shield_damage_mul = (*cached_shield_damage_mul_lock).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -185,10 +187,7 @@ pub fn should_hold_shield(module_accessor: &mut app::BattleObjectModuleAccessor)
|
|||
return true;
|
||||
}
|
||||
|
||||
let shield_state;
|
||||
unsafe {
|
||||
shield_state = &MENU.shield_state;
|
||||
}
|
||||
let shield_state = &read(&MENU).shield_state;
|
||||
|
||||
// We should hold shield if the state requires it
|
||||
if unsafe { save_states::is_loading() }
|
||||
|
@ -223,7 +222,7 @@ unsafe fn mod_handle_sub_guard_cont(fighter: &mut L2CFighterCommon) {
|
|||
}
|
||||
|
||||
// Enable shield decay
|
||||
if MENU.shield_state == Shield::HOLD {
|
||||
if read(&MENU).shield_state == Shield::HOLD {
|
||||
set_shield_decay(true);
|
||||
}
|
||||
|
||||
|
@ -237,7 +236,7 @@ unsafe fn mod_handle_sub_guard_cont(fighter: &mut L2CFighterCommon) {
|
|||
return;
|
||||
}
|
||||
|
||||
if frame_counter::should_delay(SHIELD_DELAY, *REACTION_COUNTER_INDEX) {
|
||||
if frame_counter::should_delay(read(&SHIELD_DELAY), *REACTION_COUNTER_INDEX) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -245,11 +244,11 @@ unsafe fn mod_handle_sub_guard_cont(fighter: &mut L2CFighterCommon) {
|
|||
return;
|
||||
}
|
||||
|
||||
if MENU.mash_triggers.contains(&MashTrigger::SHIELDSTUN) {
|
||||
if MENU.shieldstun_override == Action::empty() {
|
||||
mash::external_buffer_menu_mash(MENU.mash_state.get_random())
|
||||
if read(&MENU).mash_triggers.contains(&MashTrigger::SHIELDSTUN) {
|
||||
if read(&MENU).shieldstun_override == Action::empty() {
|
||||
mash::external_buffer_menu_mash(read(&MENU).mash_state.get_random())
|
||||
} else {
|
||||
mash::external_buffer_menu_mash(MENU.shieldstun_override.get_random())
|
||||
mash::external_buffer_menu_mash(read(&MENU).shieldstun_override.get_random())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -355,10 +354,7 @@ fn needs_oos_handling_drop_shield() -> bool {
|
|||
}
|
||||
// Make sure we only flicker shield when Airdodge and Shield mash options are selected
|
||||
if action == Action::AIR_DODGE {
|
||||
let shield_state;
|
||||
unsafe {
|
||||
shield_state = &MENU.shield_state;
|
||||
}
|
||||
let shield_state = &read(&MENU).shield_state;
|
||||
// If we're supposed to be holding shield, let airdodge make us drop shield
|
||||
if [Shield::HOLD, Shield::INFINITE, Shield::CONSTANT].contains(shield_state) {
|
||||
suspend_shield(Action::AIR_DODGE);
|
||||
|
@ -368,10 +364,7 @@ fn needs_oos_handling_drop_shield() -> bool {
|
|||
|
||||
// Make sure we only flicker shield when Airdodge and Shield mash options are selected
|
||||
if action == Action::AIR_DODGE {
|
||||
let shield_state;
|
||||
unsafe {
|
||||
shield_state = &MENU.shield_state;
|
||||
}
|
||||
let shield_state = &read(&MENU).shield_state;
|
||||
// If we're supposed to be holding shield, let airdodge make us drop shield
|
||||
if [Shield::HOLD, Shield::INFINITE, Shield::CONSTANT].contains(shield_state) {
|
||||
suspend_shield(Action::AIR_DODGE);
|
||||
|
@ -380,10 +373,7 @@ fn needs_oos_handling_drop_shield() -> bool {
|
|||
}
|
||||
|
||||
if action == Action::SHIELD {
|
||||
let shield_state;
|
||||
unsafe {
|
||||
shield_state = &MENU.shield_state;
|
||||
}
|
||||
let shield_state = &read(&MENU).shield_state;
|
||||
// Don't drop shield on shield hit if we're supposed to be holding shield
|
||||
if [Shield::HOLD, Shield::INFINITE, Shield::CONSTANT].contains(shield_state) {
|
||||
return false;
|
||||
|
@ -402,9 +392,7 @@ pub fn is_aerial(action: Action) -> bool {
|
|||
|
||||
// Needed for shield drop options
|
||||
pub fn suspend_shield(action: Action) {
|
||||
unsafe {
|
||||
SUSPEND_SHIELD = need_suspend_shield(action);
|
||||
}
|
||||
assign(&SUSPEND_SHIELD, need_suspend_shield(action));
|
||||
}
|
||||
|
||||
fn need_suspend_shield(action: Action) -> bool {
|
||||
|
@ -427,7 +415,7 @@ fn need_suspend_shield(action: Action) -> bool {
|
|||
* Needed for these options to work OOS
|
||||
*/
|
||||
fn shield_is_suspended() -> bool {
|
||||
unsafe { SUSPEND_SHIELD }
|
||||
read(&SUSPEND_SHIELD)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,31 +2,29 @@ use smash::app::{self};
|
|||
|
||||
use crate::common::consts::*;
|
||||
use crate::common::*;
|
||||
use training_mod_sync::*;
|
||||
|
||||
static mut STICK_DIRECTION: Direction = Direction::OUT;
|
||||
static SHIELD_STICK_DIRECTION: RwLock<Direction> = RwLock::new(Direction::OUT);
|
||||
|
||||
pub fn roll_direction() {
|
||||
unsafe {
|
||||
STICK_DIRECTION = MENU.shield_tilt.get_random();
|
||||
}
|
||||
assign(
|
||||
&SHIELD_STICK_DIRECTION,
|
||||
read(&MENU).shield_tilt.get_random(),
|
||||
);
|
||||
}
|
||||
|
||||
pub unsafe fn mod_get_stick_x(
|
||||
module_accessor: &mut app::BattleObjectModuleAccessor,
|
||||
) -> Option<f32> {
|
||||
pub fn mod_get_stick_x(module_accessor: &mut app::BattleObjectModuleAccessor) -> Option<f32> {
|
||||
get_angle(module_accessor).map(|a| a.cos() as f32)
|
||||
}
|
||||
|
||||
pub unsafe fn mod_get_stick_y(
|
||||
module_accessor: &mut app::BattleObjectModuleAccessor,
|
||||
) -> Option<f32> {
|
||||
pub fn mod_get_stick_y(module_accessor: &mut app::BattleObjectModuleAccessor) -> Option<f32> {
|
||||
get_angle(module_accessor).map(|a| a.sin() as f32)
|
||||
}
|
||||
|
||||
unsafe fn get_angle(module_accessor: &mut app::BattleObjectModuleAccessor) -> Option<f64> {
|
||||
fn get_angle(module_accessor: &mut app::BattleObjectModuleAccessor) -> Option<f64> {
|
||||
if !is_operation_cpu(module_accessor) {
|
||||
return None;
|
||||
}
|
||||
|
||||
STICK_DIRECTION.into_angle()
|
||||
let stick_direction = read(&SHIELD_STICK_DIRECTION);
|
||||
stick_direction.into_angle()
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use skyline::hooks::{getRegionAddress, Region};
|
||||
use smash::app::{lua_bind::*, sv_system, BattleObjectModuleAccessor};
|
||||
use smash::hash40;
|
||||
use smash::lib::lua_const::*;
|
||||
|
@ -6,28 +9,24 @@ use smash::lua2cpp::L2CFighterBase;
|
|||
use smash::phx::{Hash40, Vector3f};
|
||||
|
||||
use crate::common::consts::*;
|
||||
use crate::common::offsets::OFFSET_CHANGE_ACTIVE_CAMERA;
|
||||
|
||||
use crate::common::offsets::OFFSET_SET_TRAINING_FIXED_CAMERA_VALUES;
|
||||
use crate::common::offsets::{
|
||||
OFFSET_CHANGE_ACTIVE_CAMERA, OFFSET_SET_TRAINING_FIXED_CAMERA_VALUES,
|
||||
};
|
||||
use crate::common::*;
|
||||
use crate::training::{frame_counter, mash, save_states};
|
||||
use training_mod_sync::*;
|
||||
|
||||
use skyline::hooks::{getRegionAddress, Region};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
|
||||
static mut TECH_ROLL_DIRECTION: Direction = Direction::empty();
|
||||
static mut MISS_TECH_ROLL_DIRECTION: Direction = Direction::empty();
|
||||
static mut NEEDS_VISIBLE: bool = false;
|
||||
static mut DEFAULT_FIXED_CAM_CENTER: Vector3f = Vector3f {
|
||||
static TECH_ROLL_DIRECTION: RwLock<Direction> = RwLock::new(Direction::empty());
|
||||
static MISS_TECH_ROLL_DIRECTION: RwLock<Direction> = RwLock::new(Direction::empty());
|
||||
static NEEDS_VISIBLE: RwLock<bool> = RwLock::new(false);
|
||||
static DEFAULT_FIXED_CAM_CENTER: RwLock<Vector3f> = RwLock::new(Vector3f {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
};
|
||||
});
|
||||
|
||||
static FRAME_COUNTER: Lazy<usize> =
|
||||
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
|
||||
static FRAME_COUNTER: LazyLock<usize> =
|
||||
LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
|
||||
|
||||
unsafe fn is_enable_passive(module_accessor: &mut BattleObjectModuleAccessor) -> bool {
|
||||
let fighter = get_fighter_common_from_accessor(module_accessor);
|
||||
|
@ -64,7 +63,7 @@ unsafe fn mod_handle_change_status(
|
|||
.try_get_int()
|
||||
.unwrap_or(*FIGHTER_STATUS_KIND_WAIT as u64) as i32;
|
||||
|
||||
let state: TechFlags = MENU.tech_state.get_random();
|
||||
let state: TechFlags = read(&MENU).tech_state.get_random();
|
||||
|
||||
if handle_grnd_tech(module_accessor, status_kind, unk, status_kind_int, state) {
|
||||
return;
|
||||
|
@ -115,22 +114,22 @@ unsafe fn handle_grnd_tech(
|
|||
TechFlags::ROLL_F => {
|
||||
*status_kind = FIGHTER_STATUS_KIND_PASSIVE_FB.as_lua_int();
|
||||
*unk = LUA_TRUE;
|
||||
TECH_ROLL_DIRECTION = Direction::IN; // = In
|
||||
assign(&TECH_ROLL_DIRECTION, Direction::IN);
|
||||
true
|
||||
}
|
||||
TechFlags::ROLL_B => {
|
||||
*status_kind = FIGHTER_STATUS_KIND_PASSIVE_FB.as_lua_int();
|
||||
*unk = LUA_TRUE;
|
||||
TECH_ROLL_DIRECTION = Direction::OUT; // = Away
|
||||
assign(&TECH_ROLL_DIRECTION, Direction::OUT);
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
if do_tech && MENU.mash_triggers.contains(&MashTrigger::TECH) {
|
||||
if MENU.tech_action_override == Action::empty() {
|
||||
mash::external_buffer_menu_mash(MENU.mash_state.get_random())
|
||||
if do_tech && read(&MENU).mash_triggers.contains(&MashTrigger::TECH) {
|
||||
if read(&MENU).tech_action_override == Action::empty() {
|
||||
mash::external_buffer_menu_mash(read(&MENU).mash_state.get_random())
|
||||
} else {
|
||||
mash::external_buffer_menu_mash(MENU.tech_action_override.get_random())
|
||||
mash::external_buffer_menu_mash(read(&MENU).tech_action_override.get_random())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,11 +172,11 @@ unsafe fn handle_wall_tech(
|
|||
}
|
||||
_ => false,
|
||||
};
|
||||
if do_tech && MENU.mash_triggers.contains(&MashTrigger::TECH) {
|
||||
if MENU.tech_action_override == Action::empty() {
|
||||
mash::external_buffer_menu_mash(MENU.mash_state.get_random())
|
||||
if do_tech && read(&MENU).mash_triggers.contains(&MashTrigger::TECH) {
|
||||
if read(&MENU).tech_action_override == Action::empty() {
|
||||
mash::external_buffer_menu_mash(read(&MENU).mash_state.get_random())
|
||||
} else {
|
||||
mash::external_buffer_menu_mash(MENU.tech_action_override.get_random())
|
||||
mash::external_buffer_menu_mash(read(&MENU).tech_action_override.get_random())
|
||||
}
|
||||
}
|
||||
true
|
||||
|
@ -208,18 +207,18 @@ unsafe fn handle_ceil_tech(
|
|||
|
||||
*status_kind = FIGHTER_STATUS_KIND_PASSIVE_CEIL.as_lua_int();
|
||||
*unk = LUA_TRUE;
|
||||
if MENU.mash_triggers.contains(&MashTrigger::TECH) {
|
||||
if MENU.tech_action_override == Action::empty() {
|
||||
mash::external_buffer_menu_mash(MENU.mash_state.get_random())
|
||||
if read(&MENU).mash_triggers.contains(&MashTrigger::TECH) {
|
||||
if read(&MENU).tech_action_override == Action::empty() {
|
||||
mash::external_buffer_menu_mash(read(&MENU).mash_state.get_random())
|
||||
} else {
|
||||
mash::external_buffer_menu_mash(MENU.tech_action_override.get_random())
|
||||
mash::external_buffer_menu_mash(read(&MENU).tech_action_override.get_random())
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub unsafe fn get_command_flag_cat(module_accessor: &mut BattleObjectModuleAccessor) {
|
||||
if !is_operation_cpu(module_accessor) || MENU.tech_state == TechFlags::empty() {
|
||||
if !is_operation_cpu(module_accessor) || read(&MENU).tech_state == TechFlags::empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -232,15 +231,15 @@ pub unsafe fn get_command_flag_cat(module_accessor: &mut BattleObjectModuleAcces
|
|||
.contains(&status)
|
||||
{
|
||||
// Mistech
|
||||
requested_status = match MENU.miss_tech_state.get_random() {
|
||||
requested_status = match read(&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
|
||||
assign(&MISS_TECH_ROLL_DIRECTION, Direction::IN);
|
||||
*FIGHTER_STATUS_KIND_DOWN_STAND_FB
|
||||
}
|
||||
MissTechFlags::ROLL_B => {
|
||||
MISS_TECH_ROLL_DIRECTION = Direction::OUT; // = Away
|
||||
assign(&MISS_TECH_ROLL_DIRECTION, Direction::OUT);
|
||||
*FIGHTER_STATUS_KIND_DOWN_STAND_FB
|
||||
}
|
||||
_ => return,
|
||||
|
@ -251,22 +250,22 @@ pub unsafe fn get_command_flag_cat(module_accessor: &mut BattleObjectModuleAcces
|
|||
if frame_counter::should_delay(lockout_time, *FRAME_COUNTER) {
|
||||
return;
|
||||
};
|
||||
requested_status = match MENU.miss_tech_state.get_random() {
|
||||
requested_status = match read(&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
|
||||
assign(&MISS_TECH_ROLL_DIRECTION, Direction::IN);
|
||||
*FIGHTER_STATUS_KIND_DOWN_STAND_FB
|
||||
}
|
||||
MissTechFlags::ROLL_B => {
|
||||
MISS_TECH_ROLL_DIRECTION = Direction::OUT; // = Away
|
||||
assign(&MISS_TECH_ROLL_DIRECTION, Direction::OUT);
|
||||
*FIGHTER_STATUS_KIND_DOWN_STAND_FB
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
} else if status == *FIGHTER_STATUS_KIND_SLIP_WAIT {
|
||||
// Handle slips (like Diddy banana)
|
||||
requested_status = match MENU.miss_tech_state.get_random() {
|
||||
requested_status = match read(&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,
|
||||
|
@ -280,11 +279,11 @@ pub unsafe fn get_command_flag_cat(module_accessor: &mut BattleObjectModuleAcces
|
|||
|
||||
if requested_status != 0 {
|
||||
StatusModule::change_status_force(module_accessor, requested_status, true);
|
||||
if MENU.mash_triggers.contains(&MashTrigger::MISTECH) {
|
||||
if MENU.tech_action_override == Action::empty() {
|
||||
mash::external_buffer_menu_mash(MENU.mash_state.get_random())
|
||||
if read(&MENU).mash_triggers.contains(&MashTrigger::MISTECH) {
|
||||
if read(&MENU).tech_action_override == Action::empty() {
|
||||
mash::external_buffer_menu_mash(read(&MENU).mash_state.get_random())
|
||||
} else {
|
||||
mash::external_buffer_menu_mash(MENU.tech_action_override.get_random())
|
||||
mash::external_buffer_menu_mash(read(&MENU).tech_action_override.get_random())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -298,24 +297,24 @@ pub unsafe fn change_motion(
|
|||
return None;
|
||||
}
|
||||
|
||||
if MENU.tech_state == TechFlags::empty() {
|
||||
if read(&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 {
|
||||
if read(&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 {
|
||||
if read(&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 {
|
||||
if read(&MISS_TECH_ROLL_DIRECTION) == Direction::IN {
|
||||
return Some(hash40("down_forward_d"));
|
||||
} else {
|
||||
return Some(hash40("down_back_d"));
|
||||
|
@ -350,7 +349,7 @@ unsafe fn get_snake_laydown_lockout_time(module_accessor: &mut BattleObjectModul
|
|||
}
|
||||
|
||||
pub unsafe fn hide_tech() {
|
||||
if !is_training_mode() || MENU.tech_hide == OnOff::OFF {
|
||||
if !is_training_mode() || read(&MENU).tech_hide == OnOff::OFF {
|
||||
return;
|
||||
}
|
||||
let module_accessor = get_module_accessor(FighterId::CPU);
|
||||
|
@ -370,7 +369,7 @@ pub unsafe fn hide_tech() {
|
|||
);
|
||||
// Disable visibility
|
||||
if MotionModule::frame(module_accessor) >= 6.0 {
|
||||
NEEDS_VISIBLE = true;
|
||||
assign(&NEEDS_VISIBLE, true);
|
||||
VisibilityModule::set_whole(module_accessor, false);
|
||||
EffectModule::set_visible_kind(module_accessor, Hash40::new("sys_nopassive"), false);
|
||||
EffectModule::set_visible_kind(module_accessor, Hash40::new("sys_down_smoke"), false);
|
||||
|
@ -384,15 +383,17 @@ pub unsafe fn hide_tech() {
|
|||
}
|
||||
if MotionModule::end_frame(module_accessor) - MotionModule::frame(module_accessor) <= 5.0 {
|
||||
// Re-enable visibility
|
||||
NEEDS_VISIBLE = false;
|
||||
assign(&NEEDS_VISIBLE, false);
|
||||
VisibilityModule::set_whole(module_accessor, true);
|
||||
}
|
||||
} else {
|
||||
// If the CPU's tech status was interrupted, make them visible again
|
||||
if NEEDS_VISIBLE {
|
||||
NEEDS_VISIBLE = false;
|
||||
let mut needs_visible_lock = lock_write(&NEEDS_VISIBLE);
|
||||
if *needs_visible_lock {
|
||||
*needs_visible_lock = false;
|
||||
VisibilityModule::set_whole(module_accessor, true);
|
||||
}
|
||||
drop(needs_visible_lock);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -406,7 +407,7 @@ pub unsafe fn handle_fighter_req_quake_pos(
|
|||
return original!()(module_accessor, quake_kind);
|
||||
}
|
||||
let status = StatusModule::status_kind(module_accessor);
|
||||
if status == FIGHTER_STATUS_KIND_DOWN && MENU.tech_hide == OnOff::ON {
|
||||
if status == FIGHTER_STATUS_KIND_DOWN && read(&MENU).tech_hide == OnOff::ON {
|
||||
// We're hiding techs, prevent mistech quake from giving away missed tech
|
||||
return original!()(module_accessor, *CAMERA_QUAKE_KIND_NONE);
|
||||
}
|
||||
|
@ -449,9 +450,9 @@ pub struct CameraManager {
|
|||
|
||||
unsafe fn set_fixed_camera_values() {
|
||||
let camera_manager = get_camera_manager();
|
||||
if MENU.tech_hide == OnOff::OFF {
|
||||
if read(&MENU).tech_hide == OnOff::OFF {
|
||||
// Use Stage's Default Values for fixed Camera
|
||||
camera_manager.fixed_camera_center = DEFAULT_FIXED_CAM_CENTER;
|
||||
camera_manager.fixed_camera_center = read(&DEFAULT_FIXED_CAM_CENTER);
|
||||
} else {
|
||||
// We're in CameraMode 4, which is Fixed, and we are hiding tech chases, so we want a better view of the stage
|
||||
if let Some(camera_vector) = get_stage_camera_values(save_states::stage_id()) {
|
||||
|
@ -738,7 +739,10 @@ pub unsafe fn handle_set_training_fixed_camera_values(
|
|||
if !is_training_mode() {
|
||||
return original!()(camera_manager, fixed_camera_values);
|
||||
}
|
||||
DEFAULT_FIXED_CAM_CENTER = fixed_camera_values.fixed_camera_center;
|
||||
assign(
|
||||
&DEFAULT_FIXED_CAM_CENTER,
|
||||
fixed_camera_values.fixed_camera_center,
|
||||
);
|
||||
original!()(camera_manager, fixed_camera_values);
|
||||
// Set Fixed Camera Values now, since L + R + A reset switches without calling ChangeActiveCamera
|
||||
set_fixed_camera_values();
|
||||
|
|
|
@ -5,78 +5,65 @@ use crate::common::consts::*;
|
|||
use crate::common::*;
|
||||
use crate::training::frame_counter;
|
||||
use crate::training::mash;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use training_mod_sync::*;
|
||||
|
||||
const NOT_SET: u32 = 9001;
|
||||
static mut THROW_DELAY: u32 = NOT_SET;
|
||||
static mut PUMMEL_DELAY: u32 = NOT_SET;
|
||||
static mut THROW_CASE: ThrowOption = ThrowOption::empty();
|
||||
static THROW_DELAY: RwLock<u32> = RwLock::new(NOT_SET);
|
||||
static PUMMEL_DELAY: RwLock<u32> = RwLock::new(NOT_SET);
|
||||
static THROW_CASE: RwLock<ThrowOption> = RwLock::new(ThrowOption::empty());
|
||||
|
||||
static THROW_DELAY_COUNTER: Lazy<usize> =
|
||||
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
|
||||
static PUMMEL_DELAY_COUNTER: Lazy<usize> =
|
||||
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
|
||||
static THROW_DELAY_COUNTER: LazyLock<usize> =
|
||||
LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
|
||||
static PUMMEL_DELAY_COUNTER: LazyLock<usize> =
|
||||
LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
|
||||
|
||||
// Rolling Throw Delays and Pummel Delays separately
|
||||
|
||||
pub fn reset_throw_delay() {
|
||||
unsafe {
|
||||
if THROW_DELAY != NOT_SET {
|
||||
THROW_DELAY = NOT_SET;
|
||||
frame_counter::full_reset(*THROW_DELAY_COUNTER);
|
||||
}
|
||||
if read(&THROW_DELAY) != NOT_SET {
|
||||
assign(&THROW_DELAY, NOT_SET);
|
||||
frame_counter::full_reset(*THROW_DELAY_COUNTER);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_pummel_delay() {
|
||||
unsafe {
|
||||
if PUMMEL_DELAY != NOT_SET {
|
||||
PUMMEL_DELAY = NOT_SET;
|
||||
frame_counter::full_reset(*PUMMEL_DELAY_COUNTER);
|
||||
}
|
||||
if read(&PUMMEL_DELAY) != NOT_SET {
|
||||
assign(&PUMMEL_DELAY, NOT_SET);
|
||||
frame_counter::full_reset(*PUMMEL_DELAY_COUNTER);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_throw_case() {
|
||||
unsafe {
|
||||
if THROW_CASE != ThrowOption::empty() {
|
||||
// Don't roll another throw option if one is already selected
|
||||
THROW_CASE = ThrowOption::empty();
|
||||
}
|
||||
if read(&THROW_CASE) != ThrowOption::empty() {
|
||||
// Don't roll another throw option if one is already selected
|
||||
assign(&THROW_CASE, ThrowOption::empty());
|
||||
}
|
||||
}
|
||||
|
||||
fn roll_throw_delay() {
|
||||
unsafe {
|
||||
if THROW_DELAY != NOT_SET {
|
||||
// Don't roll another throw delay if one is already selected
|
||||
return;
|
||||
}
|
||||
|
||||
THROW_DELAY = MENU.throw_delay.get_random().into_meddelay();
|
||||
if read(&THROW_DELAY) == NOT_SET {
|
||||
// Only roll another throw delay if one is not already selected
|
||||
assign(
|
||||
&THROW_DELAY,
|
||||
read(&MENU).throw_delay.get_random().into_meddelay(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn roll_pummel_delay() {
|
||||
unsafe {
|
||||
if PUMMEL_DELAY != NOT_SET {
|
||||
// Don't roll another pummel delay if one is already selected
|
||||
return;
|
||||
}
|
||||
|
||||
PUMMEL_DELAY = MENU.pummel_delay.get_random().into_meddelay();
|
||||
if read(&PUMMEL_DELAY) == NOT_SET {
|
||||
// Don't roll another pummel delay if one is already selected
|
||||
assign(
|
||||
&PUMMEL_DELAY,
|
||||
read(&MENU).pummel_delay.get_random().into_meddelay(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn roll_throw_case() {
|
||||
unsafe {
|
||||
// Don't re-roll if there is already a throw option selected
|
||||
if THROW_CASE != ThrowOption::empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
THROW_CASE = MENU.throw_state.get_random();
|
||||
if read(&THROW_CASE) == ThrowOption::empty() {
|
||||
// Only re-roll if there is not already a throw option selected
|
||||
assign(&THROW_CASE, read(&MENU).throw_state.get_random());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,20 +99,20 @@ pub unsafe fn get_command_flag_throw_direction(
|
|||
|
||||
roll_pummel_delay();
|
||||
|
||||
if THROW_CASE == ThrowOption::NONE {
|
||||
if read(&THROW_CASE) == ThrowOption::NONE {
|
||||
// Do nothing, but don't reroll the throw case.
|
||||
return 0;
|
||||
}
|
||||
|
||||
if frame_counter::should_delay(THROW_DELAY, *THROW_DELAY_COUNTER) {
|
||||
if frame_counter::should_delay(read(&THROW_DELAY), *THROW_DELAY_COUNTER) {
|
||||
// Not yet time to perform the throw action
|
||||
if frame_counter::should_delay(PUMMEL_DELAY, *PUMMEL_DELAY_COUNTER) {
|
||||
if frame_counter::should_delay(read(&PUMMEL_DELAY), *PUMMEL_DELAY_COUNTER) {
|
||||
// And not yet time to pummel either, so don't do anything
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If no pummel delay is selected (default), then don't pummel
|
||||
if MENU.pummel_delay == MedDelay::empty() {
|
||||
if read(&MENU).pummel_delay == MedDelay::empty() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -143,8 +130,8 @@ pub unsafe fn get_command_flag_throw_direction(
|
|||
module_accessor,
|
||||
*FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_THROW_HI,
|
||||
) {
|
||||
let cmd = THROW_CASE.into_cmd().unwrap_or(0);
|
||||
mash::external_buffer_menu_mash(MENU.mash_state.get_random());
|
||||
let cmd = read(&THROW_CASE).into_cmd().unwrap_or(0);
|
||||
mash::external_buffer_menu_mash(read(&MENU).mash_state.get_random());
|
||||
return cmd;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use std::ptr::addr_of_mut;
|
||||
|
||||
use skyline::nn::ui2d::*;
|
||||
use smash::ui2d::{SmashPane, SmashTextBox};
|
||||
|
||||
use crate::common::menu::QUICK_MENU_ACTIVE;
|
||||
use crate::common::TRAINING_MENU_ADDR;
|
||||
use crate::training::ui;
|
||||
use crate::training::ui::notifications::*;
|
||||
use training_mod_sync::*;
|
||||
|
||||
macro_rules! display_parent_fmt {
|
||||
($x:ident) => {
|
||||
format!("TrModDisp{}", $x).as_str()
|
||||
|
@ -25,22 +25,23 @@ macro_rules! display_txt_fmt {
|
|||
}
|
||||
|
||||
pub unsafe fn draw(root_pane: &Pane) {
|
||||
let notification_idx = 0;
|
||||
|
||||
let queue = addr_of_mut!(ui::notifications::QUEUE);
|
||||
if (*TRAINING_MENU_ADDR).combo_display_toggle == 0 {
|
||||
// User has turned off the "combo display" option in the vanilla menu
|
||||
// Remove all notifications from the queue so we don't show them
|
||||
// This will also set the pane's visibility to false
|
||||
(*queue).clear();
|
||||
clear_all_notifications();
|
||||
}
|
||||
|
||||
let notification = (*queue).first_mut();
|
||||
let notification_idx = 0;
|
||||
let mut queue_lock = lock_write(&NOTIFICATIONS_QUEUE);
|
||||
|
||||
let notification = (*queue_lock).first_mut();
|
||||
|
||||
root_pane
|
||||
.find_pane_by_name_recursive(display_parent_fmt!(notification_idx))
|
||||
.unwrap()
|
||||
.set_visible(notification.is_some() && !QUICK_MENU_ACTIVE);
|
||||
.set_visible(notification.is_some() && !read(&QUICK_MENU_ACTIVE));
|
||||
|
||||
if notification.is_none() {
|
||||
return;
|
||||
}
|
||||
|
@ -66,8 +67,8 @@ pub unsafe fn draw(root_pane: &Pane) {
|
|||
text.set_color(color.r, color.g, color.b, color.a);
|
||||
}
|
||||
|
||||
let has_completed = notification.check_completed();
|
||||
if has_completed {
|
||||
(*queue).remove(0);
|
||||
if notification.has_completed() {
|
||||
(*queue_lock).remove(0);
|
||||
}
|
||||
drop(queue_lock);
|
||||
}
|
||||
|
|
|
@ -4,15 +4,14 @@ use skyline::nn::ui2d::*;
|
|||
use smash::ui2d::{SmashPane, SmashTextBox};
|
||||
use training_mod_consts::{InputDisplay, MENU};
|
||||
|
||||
use crate::{
|
||||
common::{consts::status_display_name, menu::QUICK_MENU_ACTIVE},
|
||||
training::{
|
||||
input_log::{
|
||||
DirectionStrength, InputLog, DRAW_LOG_BASE_IDX, NUM_LOGS, P1_INPUT_LOGS, WHITE, YELLOW,
|
||||
},
|
||||
ui::{fade_out, menu::VANILLA_MENU_ACTIVE},
|
||||
},
|
||||
use crate::common::consts::status_display_name;
|
||||
use crate::menu::QUICK_MENU_ACTIVE;
|
||||
use crate::training::input_log::{
|
||||
DirectionStrength, InputLog, DRAW_LOG_BASE_IDX, NUM_LOGS, P1_INPUT_LOGS, WHITE, YELLOW,
|
||||
};
|
||||
use crate::training::ui::fade_out;
|
||||
use crate::training::ui::menu::VANILLA_MENU_ACTIVE;
|
||||
use training_mod_sync::*;
|
||||
|
||||
macro_rules! log_parent_fmt {
|
||||
($x:ident) => {
|
||||
|
@ -69,7 +68,8 @@ fn get_input_icons(log: &InputLog) -> VecDeque<(&str, ResColor)> {
|
|||
}
|
||||
|
||||
unsafe fn draw_log(root_pane: &Pane, log_idx: usize, log: &InputLog) {
|
||||
let draw_log_idx = (log_idx + (NUM_LOGS - *DRAW_LOG_BASE_IDX.data_ptr())) % NUM_LOGS;
|
||||
let draw_log_base_idx = read(&DRAW_LOG_BASE_IDX);
|
||||
let draw_log_idx = (log_idx + (NUM_LOGS - draw_log_base_idx)) % NUM_LOGS;
|
||||
let log_pane = root_pane
|
||||
.find_pane_by_name_recursive(log_parent_fmt!(draw_log_idx))
|
||||
.unwrap();
|
||||
|
@ -176,7 +176,7 @@ unsafe fn draw_log(root_pane: &Pane, log_idx: usize, log: &InputLog) {
|
|||
.as_textbox()
|
||||
.set_text_string(frame_text.as_str());
|
||||
|
||||
let status_text = if MENU.input_display_status.as_bool() {
|
||||
let status_text = if read(&MENU).input_display_status.as_bool() {
|
||||
status_display_name(log.fighter_kind, log.status)
|
||||
} else {
|
||||
"".to_string()
|
||||
|
@ -193,17 +193,15 @@ pub unsafe fn draw(root_pane: &Pane) {
|
|||
.find_pane_by_name_recursive("TrModInputLog")
|
||||
.unwrap();
|
||||
logs_pane.set_visible(
|
||||
!QUICK_MENU_ACTIVE && !VANILLA_MENU_ACTIVE && MENU.input_display != InputDisplay::NONE,
|
||||
!read(&QUICK_MENU_ACTIVE)
|
||||
&& !read(&VANILLA_MENU_ACTIVE)
|
||||
&& read(&MENU).input_display != InputDisplay::NONE,
|
||||
);
|
||||
if MENU.input_display == InputDisplay::NONE {
|
||||
if read(&MENU).input_display == InputDisplay::NONE {
|
||||
return;
|
||||
}
|
||||
|
||||
let logs_ptr = P1_INPUT_LOGS.data_ptr();
|
||||
if logs_ptr.is_null() {
|
||||
return;
|
||||
}
|
||||
let logs = &*logs_ptr;
|
||||
let logs = read(&(*P1_INPUT_LOGS));
|
||||
|
||||
for (log_idx, log) in logs.iter().enumerate() {
|
||||
draw_log(root_pane, log_idx, log);
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use skyline::nn::ui2d::*;
|
||||
use smash::ui2d::{SmashPane, SmashTextBox};
|
||||
use training_mod_tui::{
|
||||
App, AppPage, ConfirmationState, SliderState, NX_SUBMENU_COLUMNS, NX_SUBMENU_ROWS,
|
||||
};
|
||||
|
||||
use crate::common::menu::{MENU_CLOSE_FRAME_COUNTER, MENU_CLOSE_WAIT_FRAMES, MENU_RECEIVED_INPUT};
|
||||
use crate::common::menu::{
|
||||
MENU_CLOSE_FRAME_COUNTER, MENU_CLOSE_WAIT_FRAMES, MENU_RECEIVED_INPUT, P1_CONTROLLER_STYLE,
|
||||
QUICK_MENU_ACTIVE, QUICK_MENU_APP,
|
||||
};
|
||||
use crate::input::*;
|
||||
use crate::training::frame_counter;
|
||||
use crate::{common, common::menu::QUICK_MENU_ACTIVE, input::*};
|
||||
use training_mod_consts::TOGGLE_MAX;
|
||||
use training_mod_sync::*;
|
||||
|
||||
use super::fade_out;
|
||||
use super::set_icon_text;
|
||||
|
@ -57,25 +60,27 @@ const BG_LEFT_SELECTED_WHITE_COLOR: ResColor = ResColor {
|
|||
a: 255,
|
||||
};
|
||||
|
||||
pub static mut VANILLA_MENU_ACTIVE: bool = false;
|
||||
pub static VANILLA_MENU_ACTIVE: RwLock<bool> = RwLock::new(false);
|
||||
|
||||
lazy_static! {
|
||||
static ref GCC_BUTTON_MAPPING: HashMap<&'static str, u16> = HashMap::from([
|
||||
static GCC_BUTTON_MAPPING: LazyLock<HashMap<&'static str, u16>> = LazyLock::new(|| {
|
||||
HashMap::from([
|
||||
("L", 0xE204),
|
||||
("R", 0xE205),
|
||||
("X", 0xE206),
|
||||
("Y", 0xE207),
|
||||
("Z", 0xE208)
|
||||
]);
|
||||
static ref PROCON_BUTTON_MAPPING: HashMap<&'static str, u16> = HashMap::from([
|
||||
("Z", 0xE208),
|
||||
])
|
||||
});
|
||||
static PROCON_BUTTON_MAPPING: LazyLock<HashMap<&'static str, u16>> = LazyLock::new(|| {
|
||||
HashMap::from([
|
||||
("L", 0xE0E4),
|
||||
("R", 0xE0E5),
|
||||
("X", 0xE0E2),
|
||||
("Y", 0xE0E3),
|
||||
("ZL", 0xE0E6),
|
||||
("ZR", 0xE0E7)
|
||||
]);
|
||||
}
|
||||
("ZR", 0xE0E7),
|
||||
])
|
||||
});
|
||||
|
||||
unsafe fn render_submenu_page(app: &mut App, root_pane: &Pane) {
|
||||
let tabs_clone = app.tabs.clone(); // Need this to avoid double-borrow later on
|
||||
|
@ -457,14 +462,17 @@ pub unsafe fn draw(root_pane: &Pane) {
|
|||
// Determine if we're in the menu by seeing if the "help" footer has
|
||||
// begun moving upward. It starts at -80 and moves to 0 over 10 frames
|
||||
// in info_training_in_menu.bflan
|
||||
VANILLA_MENU_ACTIVE = root_pane
|
||||
.find_pane_by_name_recursive("L_staying_help")
|
||||
.unwrap()
|
||||
.pos_y
|
||||
!= -80.0;
|
||||
assign(
|
||||
&VANILLA_MENU_ACTIVE,
|
||||
root_pane
|
||||
.find_pane_by_name_recursive("L_staying_help")
|
||||
.unwrap()
|
||||
.pos_y
|
||||
!= -80.0,
|
||||
);
|
||||
|
||||
let overall_parent_pane = root_pane.find_pane_by_name_recursive("TrModMenu").unwrap();
|
||||
overall_parent_pane.set_visible(QUICK_MENU_ACTIVE && !VANILLA_MENU_ACTIVE);
|
||||
overall_parent_pane.set_visible(read(&QUICK_MENU_ACTIVE) && !read(&VANILLA_MENU_ACTIVE));
|
||||
let menu_close_wait_frame = frame_counter::get_frame_count(*MENU_CLOSE_FRAME_COUNTER);
|
||||
fade_out(
|
||||
overall_parent_pane,
|
||||
|
@ -473,11 +481,11 @@ pub unsafe fn draw(root_pane: &Pane) {
|
|||
);
|
||||
|
||||
// Only submit updates if we have received input
|
||||
let received_input = &mut *MENU_RECEIVED_INPUT.data_ptr();
|
||||
if !*received_input {
|
||||
let received_input = read(&MENU_RECEIVED_INPUT);
|
||||
if !received_input {
|
||||
return;
|
||||
} else {
|
||||
*received_input = false;
|
||||
assign(&MENU_RECEIVED_INPUT, false);
|
||||
}
|
||||
|
||||
if let Some(quit_button) = root_pane.find_pane_by_name_recursive("TrModTitle") {
|
||||
|
@ -514,7 +522,7 @@ pub unsafe fn draw(root_pane: &Pane) {
|
|||
.find_pane_by_name_recursive("status_R")
|
||||
.expect("Unable to find status_R pane");
|
||||
// status_r_pane.flags |= 1 << PaneFlag::InfluencedAlpha as u8;
|
||||
status_r_pane.set_visible(!QUICK_MENU_ACTIVE);
|
||||
status_r_pane.set_visible(!read(&QUICK_MENU_ACTIVE));
|
||||
|
||||
root_pane
|
||||
.find_pane_by_name_recursive("TrModSlider")
|
||||
|
@ -523,8 +531,8 @@ pub unsafe fn draw(root_pane: &Pane) {
|
|||
|
||||
// Update menu display
|
||||
// Grabbing lock as read-only, essentially
|
||||
let mut app = lock_write(&QUICK_MENU_APP);
|
||||
// We don't really need to change anything, but get_before_selected requires &mut self
|
||||
let app = &mut *crate::common::menu::QUICK_MENU_APP.data_ptr();
|
||||
|
||||
let tab_titles = [
|
||||
app.tabs
|
||||
|
@ -538,11 +546,11 @@ pub unsafe fn draw(root_pane: &Pane) {
|
|||
.title,
|
||||
];
|
||||
|
||||
let is_gcc = (*common::menu::P1_CONTROLLER_STYLE.data_ptr()) == ControllerStyle::GCController;
|
||||
let is_gcc = read(&P1_CONTROLLER_STYLE) == ControllerStyle::GCController;
|
||||
let button_mapping = if is_gcc {
|
||||
GCC_BUTTON_MAPPING.clone()
|
||||
&(*GCC_BUTTON_MAPPING)
|
||||
} else {
|
||||
PROCON_BUTTON_MAPPING.clone()
|
||||
&(*PROCON_BUTTON_MAPPING)
|
||||
};
|
||||
|
||||
let (x_key, y_key, l_key, r_key, zl_key, zr_key, z_key) = (
|
||||
|
@ -659,10 +667,10 @@ pub unsafe fn draw(root_pane: &Pane) {
|
|||
}
|
||||
|
||||
match app.page {
|
||||
AppPage::SUBMENU => render_submenu_page(app, root_pane),
|
||||
AppPage::SLIDER => render_slider_page(app, root_pane),
|
||||
AppPage::TOGGLE => render_toggle_page(app, root_pane),
|
||||
AppPage::CONFIRMATION => render_confirmation_page(app, root_pane),
|
||||
AppPage::SUBMENU => render_submenu_page(&mut app, root_pane),
|
||||
AppPage::SLIDER => render_slider_page(&mut app, root_pane),
|
||||
AppPage::TOGGLE => render_toggle_page(&mut app, root_pane),
|
||||
AppPage::CONFIRMATION => render_confirmation_page(&mut app, root_pane),
|
||||
AppPage::CLOSE => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ use crate::common::{is_ready_go, is_training_mode};
|
|||
#[cfg(feature = "layout_arc_from_file")]
|
||||
use crate::consts::LAYOUT_ARC_PATH;
|
||||
use crate::training::frame_counter;
|
||||
use training_mod_sync::*;
|
||||
|
||||
mod damage;
|
||||
mod display;
|
||||
|
@ -71,7 +72,7 @@ pub unsafe fn handle_draw(layout: *mut Layout, draw_info: u64, cmd_buffer: u64)
|
|||
{
|
||||
// InfluencedAlpha means "Should my children panes' alpha be influenced by mine, as the parent?"
|
||||
root_pane.flags |= 1 << PaneFlag::InfluencedAlpha as u8;
|
||||
root_pane.set_visible(MENU.hud == OnOff::ON && !QUICK_MENU_ACTIVE);
|
||||
root_pane.set_visible(read(&MENU).hud == OnOff::ON && !read(&QUICK_MENU_ACTIVE));
|
||||
}
|
||||
|
||||
damage::draw(root_pane, &layout_name);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use std::ptr::addr_of_mut;
|
||||
|
||||
use skyline::nn::ui2d::ResColor;
|
||||
|
||||
pub static mut QUEUE: Vec<Notification> = vec![];
|
||||
use training_mod_sync::*;
|
||||
|
||||
pub static NOTIFICATIONS_QUEUE: RwLock<Vec<Notification>> = RwLock::new(vec![]);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Notification {
|
||||
|
@ -36,42 +36,49 @@ impl Notification {
|
|||
self.length -= 1;
|
||||
}
|
||||
|
||||
// Returns: has_completed
|
||||
pub fn check_completed(&mut self) -> bool {
|
||||
if self.length <= 1 {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
pub fn has_completed(&self) -> bool {
|
||||
self.length <= 1
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notification(header: String, message: String, len: u32) {
|
||||
unsafe {
|
||||
let queue = addr_of_mut!(QUEUE);
|
||||
(*queue).push(Notification::new(
|
||||
header,
|
||||
message,
|
||||
len,
|
||||
ResColor {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 255,
|
||||
},
|
||||
));
|
||||
}
|
||||
let mut queue_lock = lock_write(&NOTIFICATIONS_QUEUE);
|
||||
(*queue_lock).push(Notification::new(
|
||||
header,
|
||||
message,
|
||||
len,
|
||||
ResColor {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 255,
|
||||
},
|
||||
));
|
||||
drop(queue_lock);
|
||||
}
|
||||
|
||||
pub fn color_notification(header: String, message: String, len: u32, color: ResColor) {
|
||||
unsafe {
|
||||
let queue = addr_of_mut!(QUEUE);
|
||||
(*queue).push(Notification::new(header, message, len, color));
|
||||
}
|
||||
let mut queue_lock = lock_write(&NOTIFICATIONS_QUEUE);
|
||||
(*queue_lock).push(Notification::new(header, message, len, color));
|
||||
drop(queue_lock);
|
||||
}
|
||||
|
||||
pub fn clear_notifications(header: &'static str) {
|
||||
unsafe {
|
||||
let queue = addr_of_mut!(QUEUE);
|
||||
(*queue).retain(|notif| notif.header != header);
|
||||
pub fn clear_notification(header: &'static str) {
|
||||
if (*lock_read(&NOTIFICATIONS_QUEUE)).is_empty() {
|
||||
// Before acquiring an exclusive write lock, check if there are even any notifications to clear out
|
||||
return;
|
||||
}
|
||||
let mut queue_lock = lock_write(&NOTIFICATIONS_QUEUE);
|
||||
(*queue_lock).retain(|notif| notif.header != header);
|
||||
drop(queue_lock);
|
||||
}
|
||||
|
||||
pub fn clear_all_notifications() {
|
||||
if (*lock_read(&NOTIFICATIONS_QUEUE)).is_empty() {
|
||||
// Before acquiring an exclusive write lock, check if there are even any notifications to clear out
|
||||
return;
|
||||
}
|
||||
let mut queue_lock = lock_write(&NOTIFICATIONS_QUEUE);
|
||||
(*queue_lock).clear();
|
||||
drop(queue_lock);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,8 @@ skyline_smash = { git = "https://github.com/ultimate-research/skyline-smash.git"
|
|||
toml = "0.5.9"
|
||||
anyhow = "1.0.72"
|
||||
rand = { git = "https://github.com/skyline-rs/rand" }
|
||||
training_mod_tui = { path = "../training_mod_tui"}
|
||||
training_mod_sync = { path = "../training_mod_sync" }
|
||||
training_mod_tui = { path = "../training_mod_tui" }
|
||||
|
||||
[features]
|
||||
default = ["smash"]
|
||||
|
|
|
@ -12,6 +12,8 @@ pub mod config;
|
|||
pub use config::*;
|
||||
|
||||
use paste::paste;
|
||||
|
||||
use training_mod_sync::*;
|
||||
pub use training_mod_tui::*;
|
||||
|
||||
pub const TOGGLE_MAX: u8 = 5;
|
||||
|
@ -110,7 +112,7 @@ pub enum FighterId {
|
|||
CPU = 1,
|
||||
}
|
||||
|
||||
pub static DEFAULTS_MENU: TrainingModpackMenu = TrainingModpackMenu {
|
||||
pub static BASE_MENU: TrainingModpackMenu = TrainingModpackMenu {
|
||||
aerial_delay: Delay::empty(),
|
||||
air_dodge_dir: Direction::empty(),
|
||||
attack_angle: AttackAngle::empty(),
|
||||
|
@ -204,7 +206,8 @@ pub static DEFAULTS_MENU: TrainingModpackMenu = TrainingModpackMenu {
|
|||
lra_reset: OnOff::ON,
|
||||
};
|
||||
|
||||
pub static mut MENU: TrainingModpackMenu = DEFAULTS_MENU;
|
||||
pub static DEFAULTS_MENU: RwLock<TrainingModpackMenu> = RwLock::new(BASE_MENU);
|
||||
pub static MENU: RwLock<TrainingModpackMenu> = RwLock::new(BASE_MENU);
|
||||
|
||||
impl_toggletrait! {
|
||||
OnOff,
|
||||
|
|
6
training_mod_sync/Cargo.toml
Normal file
6
training_mod_sync/Cargo.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
[package]
|
||||
name = "training_mod_sync"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
36
training_mod_sync/src/lib.rs
Normal file
36
training_mod_sync/src/lib.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
/// Convenience functions for interacting with RwLocks
|
||||
pub use std::sync::{LazyLock, RwLock};
|
||||
use std::sync::{RwLockReadGuard, RwLockWriteGuard};
|
||||
|
||||
/// Gets a copy of a value inside a RwLock and immediately unlocks
|
||||
///
|
||||
/// Requires <T: Copy> such as a bool or usize
|
||||
pub fn read<T: Copy>(rwlock: &RwLock<T>) -> T {
|
||||
*rwlock.read().unwrap()
|
||||
}
|
||||
|
||||
/// Gets a clone of a value inside a RwLock and immediately unlocks
|
||||
///
|
||||
/// Can be used if <T> is not Copy, such as Vec<u32>
|
||||
pub fn read_clone<T: Clone>(rwlock: &RwLock<T>) -> T {
|
||||
rwlock.read().unwrap().clone()
|
||||
}
|
||||
|
||||
/// Assigns a new value to a RwLock and immediately unlocks
|
||||
pub fn assign<T>(rwlock: &RwLock<T>, new_val: T) {
|
||||
*rwlock.write().unwrap() = new_val
|
||||
}
|
||||
|
||||
/// Locks a RwLock for writing and returns the guard
|
||||
///
|
||||
/// Don't forget to drop the guard as soon as you're finished with it
|
||||
pub fn lock_write<T>(rwlock: &RwLock<T>) -> RwLockWriteGuard<T> {
|
||||
rwlock.write().unwrap()
|
||||
}
|
||||
|
||||
/// Locks a RwLock for reading and returns the guard
|
||||
///
|
||||
/// Don't forget to drop the guard as soon as you're finished with it
|
||||
pub fn lock_read<T>(rwlock: &RwLock<T>) -> RwLockReadGuard<T> {
|
||||
rwlock.read().unwrap()
|
||||
}
|
|
@ -13,7 +13,7 @@ pub enum AppPage {
|
|||
CLOSE,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
pub enum ConfirmationState {
|
||||
HoverNo,
|
||||
HoverYes,
|
||||
|
@ -40,7 +40,7 @@ impl ConfirmationState {
|
|||
// │ OR
|
||||
// │
|
||||
// └─ Option<Slider>
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct App<'a> {
|
||||
pub tabs: StatefulList<Tab<'a>>,
|
||||
pub page: AppPage,
|
||||
|
|
|
@ -266,7 +266,7 @@ impl<'a, T: Clone + Serialize> Iterator for StatefulTableIteratorMut<'a, T> {
|
|||
}
|
||||
|
||||
impl<'a, T: Clone + Serialize + 'a> StatefulTable<T> {
|
||||
pub fn iter_mut(&'a mut self) -> StatefulTableIteratorMut<T> {
|
||||
pub fn iter_mut(&'a mut self) -> StatefulTableIteratorMut<'a, T> {
|
||||
StatefulTableIteratorMut {
|
||||
inner: self.items.iter_mut().flatten(),
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue