1
0
mirror of https://github.com/jugeeya/UltimateTrainingModpack.git synced 2025-07-10 04:47:05 +00:00

Refactor to remove static mut's, frame advantage on hit ()

* 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:
asimon-1
2024-11-17 15:49:18 -08:00
committed by GitHub
parent 2457c5b127
commit 858d35142e
48 changed files with 3670 additions and 3630 deletions

@ -34,6 +34,7 @@ serde_json = "1"
smush_info_shared = { git = "https://github.com/jam1garner/smush_info_shared.git" } smush_info_shared = { git = "https://github.com/jam1garner/smush_info_shared.git" }
toml = "0.5.9" toml = "0.5.9"
training_mod_consts = { path = "training_mod_consts" } training_mod_consts = { path = "training_mod_consts" }
training_mod_sync = { path = "training_mod_sync" }
training_mod_tui = { path = "training_mod_tui" } training_mod_tui = { path = "training_mod_tui" }
native-tls = { version = "0.2.11", features = ["vendored"] } native-tls = { version = "0.2.11", features = ["vendored"] }
log = "0.4.17" log = "0.4.17"

@ -1,15 +1,15 @@
use std::collections::HashMap; 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::input::{ControllerStyle::*, *};
use crate::training::frame_counter; use crate::training::frame_counter;
use crate::training::ui::menu::VANILLA_MENU_ACTIVE; use crate::training::ui::menu::VANILLA_MENU_ACTIVE;
use lazy_static::lazy_static; use training_mod_consts::{OnOff, MENU};
use parking_lot::Mutex; use training_mod_sync::*;
use strum_macros::EnumIter;
use super::menu::QUICK_MENU_ACTIVE; use strum_macros::EnumIter;
pub fn button_mapping( pub fn button_mapping(
button_config: ButtonConfig, button_config: ButtonConfig,
@ -176,29 +176,29 @@ unsafe fn get_combo_keys(combo: ButtonCombo) -> ButtonConfig {
match combo { match combo {
// For OpenMenu, have a default in addition to accepting start press // For OpenMenu, have a default in addition to accepting start press
ButtonCombo::OpenMenu => DEFAULT_OPEN_MENU_CONFIG, ButtonCombo::OpenMenu => DEFAULT_OPEN_MENU_CONFIG,
ButtonCombo::SaveState => MENU.save_state_save, ButtonCombo::SaveState => read(&MENU).save_state_save,
ButtonCombo::LoadState => MENU.save_state_load, ButtonCombo::LoadState => read(&MENU).save_state_load,
ButtonCombo::InputRecord => MENU.input_record, ButtonCombo::InputRecord => read(&MENU).input_record,
ButtonCombo::InputPlayback => MENU.input_playback, ButtonCombo::InputPlayback => read(&MENU).input_playback,
} }
} }
lazy_static! { // Note: in addition to RwLock we also need a LazyLock initializer because HashMap::from() is not const
static ref BUTTON_COMBO_REQUESTS: Mutex<HashMap<ButtonCombo, bool>> = static BUTTON_COMBO_REQUESTS: LazyLock<RwLock<HashMap<ButtonCombo, bool>>> = LazyLock::new(|| {
Mutex::new(HashMap::from([ RwLock::new(HashMap::from([
(ButtonCombo::OpenMenu, false), (ButtonCombo::OpenMenu, false),
(ButtonCombo::SaveState, false), (ButtonCombo::SaveState, false),
(ButtonCombo::LoadState, false), (ButtonCombo::LoadState, false),
(ButtonCombo::InputRecord, false), (ButtonCombo::InputRecord, false),
(ButtonCombo::InputPlayback, false), (ButtonCombo::InputPlayback, false),
])); ]))
static ref START_HOLD_FRAMES: Mutex<u32> = Mutex::new(0); });
} static START_HOLD_FRAMES: RwLock<u32> = RwLock::new(0);
fn _combo_passes(p1_controller: Controller, combo: ButtonCombo) -> bool { fn _combo_passes(p1_controller: Controller, combo: ButtonCombo) -> bool {
unsafe { unsafe {
// Prevent button combos from passing if either the vanilla or mod menu is open // 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; return false;
} }
@ -226,19 +226,16 @@ fn _combo_passes(p1_controller: Controller, combo: ButtonCombo) -> bool {
} }
pub fn combo_passes(combo: ButtonCombo) -> bool { pub fn combo_passes(combo: ButtonCombo) -> bool {
unsafe { let mut button_combo_requests_lock = lock_write(&BUTTON_COMBO_REQUESTS);
let button_combo_requests = &mut *BUTTON_COMBO_REQUESTS.data_ptr(); let passes = (*button_combo_requests_lock).get_mut(&combo);
let passes = button_combo_requests.get_mut(&combo); let mut did_pass = false;
let mut did_pass = false; if let Some(passes) = passes {
if let Some(passes) = passes { if *passes {
if *passes { did_pass = true;
did_pass = true;
}
*passes = false;
} }
*passes = false;
did_pass
} }
did_pass
} }
pub fn handle_final_input_mapping(player_idx: i32, controller_struct: &mut SomeControllerStruct) { 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 p1_controller = &mut *controller_struct.controller;
let mut start_menu_request = false; let mut start_menu_request = false;
let menu_close_wait_frame = frame_counter::get_frame_count(*menu::MENU_CLOSE_FRAME_COUNTER); let menu_close_wait_frame = frame_counter::get_frame_count(*MENU_CLOSE_FRAME_COUNTER);
if unsafe { MENU.menu_open_start_press == OnOff::ON } { if read(&MENU).menu_open_start_press == OnOff::ON {
let start_hold_frames = &mut *START_HOLD_FRAMES.lock(); let mut start_hold_frames = read(&START_HOLD_FRAMES);
if p1_controller.current_buttons.plus() { if p1_controller.current_buttons.plus() {
*start_hold_frames += 1; start_hold_frames += 1;
p1_controller.previous_buttons.set_plus(false); p1_controller.previous_buttons.set_plus(false);
p1_controller.current_buttons.set_plus(false); p1_controller.current_buttons.set_plus(false);
p1_controller.just_down.set_plus(false); p1_controller.just_down.set_plus(false);
p1_controller.just_release.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, // If we've held for more than 10 frames,
// let's open the training mod menu // let's open the training mod menu
start_menu_request = true; start_menu_request = true;
} }
} else { } else {
// Here, we just finished holding start // Here, we just finished holding start
if *start_hold_frames > 0 if start_hold_frames > 0
&& *start_hold_frames < 10 && start_hold_frames < 10
&& unsafe { !QUICK_MENU_ACTIVE } && !read(&QUICK_MENU_ACTIVE)
&& menu_close_wait_frame == 0 && menu_close_wait_frame == 0
{ {
// If we held for fewer than 10 frames, let's let the game know that // If we held for fewer than 10 frames, let's let the game know that
// we had pressed start // we had pressed start
p1_controller.current_buttons.set_plus(true); p1_controller.current_buttons.set_plus(true);
p1_controller.just_down.set_plus(true); p1_controller.just_down.set_plus(true);
unsafe { assign(&VANILLA_MENU_ACTIVE, true);
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 we ever press minus, open the mod menu
if p1_controller.current_buttons.minus() { 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(); let mut button_combo_requests_lock = lock_write(&BUTTON_COMBO_REQUESTS);
button_combo_requests (*button_combo_requests_lock)
.iter_mut() .iter_mut()
.for_each(|(combo, is_request)| { .for_each(|(combo, is_request)| {
if !*is_request { if !*is_request {
*is_request = _combo_passes(*p1_controller, *combo); *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; *is_request = true;
} }
} }

File diff suppressed because it is too large Load Diff

@ -1,12 +1,11 @@
use std::fs; use std::fs;
use lazy_static::lazy_static;
use parking_lot::Mutex;
use serde::Deserialize; use serde::Deserialize;
use crate::common::input::*; use crate::common::input::*;
use crate::consts::DEV_TOML_PATH; use crate::consts::DEV_TOML_PATH;
use crate::logging::info; use crate::logging::info;
use training_mod_sync::*;
/// Hot-reloadable configs for quicker development /// 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_button.pos_y = dev_config.quit_menu_pos_y;
/// quit_menu_text.as_textbox().set_text_string(&dev_config.quit_menu_title); /// quit_menu_text.as_textbox().set_text_string(&dev_config.quit_menu_title);
/// ``` /// ```
#[derive(Deserialize, Default)] #[derive(Deserialize, Default, Clone)]
pub struct DevConfig {} pub struct DevConfig {}
pub unsafe fn config() -> &'static DevConfig { pub unsafe fn config() -> DevConfig {
&*DEV_CONFIG.data_ptr() read_clone(&(*DEV_CONFIG))
} }
lazy_static! { pub static DEV_CONFIG: LazyLock<RwLock<DevConfig>> =
pub static ref DEV_CONFIG: Mutex<DevConfig> = Mutex::new(DevConfig::load_from_toml()); LazyLock::new(|| RwLock::new(DevConfig::load_from_toml()));
}
impl DevConfig { impl DevConfig {
fn load_from_toml() -> DevConfig { fn load_from_toml() -> DevConfig {
@ -57,7 +55,6 @@ impl DevConfig {
pub fn handle_final_input_mapping(player_idx: i32, controller_struct: &SomeControllerStruct) { pub fn handle_final_input_mapping(player_idx: i32, controller_struct: &SomeControllerStruct) {
let current_buttons = controller_struct.controller.current_buttons; let current_buttons = controller_struct.controller.current_buttons;
if player_idx == 0 && current_buttons.l() && current_buttons.r() && current_buttons.a() { if player_idx == 0 && current_buttons.l() && current_buttons.r() && current_buttons.a() {
let mut dev_config = DEV_CONFIG.lock(); assign(&(*DEV_CONFIG), DevConfig::load_from_toml());
*dev_config = DevConfig::load_from_toml();
} }
} }

@ -2,16 +2,94 @@ use std::convert::TryInto;
use std::ffi::{c_char, c_void}; use std::ffi::{c_char, c_void};
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use once_cell::sync::OnceCell;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use skyline::nn::{account, oe, time}; use skyline::nn::{account, oe, time};
use crate::common::release::CURRENT_VERSION; use crate::common::release::CURRENT_VERSION;
use training_mod_sync::*;
pub static mut EVENT_QUEUE: Vec<Event> = vec![]; pub static EVENT_QUEUE: RwLock<Vec<Event>> = RwLock::new(vec![]);
static mut SESSION_ID: OnceCell<String> = OnceCell::new(); static SESSION_ID: LazyLock<String> = LazyLock::new(|| unsafe {
static mut DEVICE_ID: OnceCell<String> = OnceCell::new(); let mut device_uuid = Uuid {
static mut USER_ID: OnceCell<String> = OnceCell::new(); 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)] #[derive(Debug, Default, Deserialize, Serialize)]
pub struct Event { pub struct Event {
@ -59,15 +137,6 @@ extern "C" {
impl Event { impl Event {
pub fn new() -> 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 { unsafe {
time::Initialize(); time::Initialize();
let event_time = SystemTime::now() let event_time = SystemTime::now()
@ -75,84 +144,13 @@ impl Event {
.expect("Time went backwards") .expect("Time went backwards")
.as_millis(); .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 { Event {
user_id: USER_ID.get().unwrap().to_string(), user_id: USER_ID.clone(),
device_id: DEVICE_ID.get().unwrap().to_string(), device_id: DEVICE_ID.clone(),
event_time, event_time,
session_id: SESSION_ID.get().unwrap().to_string(), session_id: SESSION_ID.clone(),
mod_version: CURRENT_VERSION.lock().to_string(), mod_version: CURRENT_VERSION.clone(),
smash_version: smash_version(), smash_version: SMASH_VERSION.clone(),
..Default::default() ..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() { pub fn events_loop() {
loop { loop {
std::thread::sleep(std::time::Duration::from_secs(10)); std::thread::sleep(std::time::Duration::from_secs(10));
unsafe { let mut event_queue_lock = lock_write(&EVENT_QUEUE);
while let Some(event) = EVENT_QUEUE.pop() { while let Some(event) = (*event_queue_lock).pop() {
let host = "https://my-project-1511972643240-default-rtdb.firebaseio.com"; let host = "https://my-project-1511972643240-default-rtdb.firebaseio.com";
let path = format!( let path = format!(
"/event/{}/device/{}/{}.json", "/event/{}/device/{}/{}.json",
event.event_name, event.device_id, event.event_time event.event_name, event.device_id, event.event_time
); );
let url = format!("{host}{path}"); let url = format!("{host}{path}");
minreq::post(url) minreq::post(url)
.with_json(&event) .with_json(&event)
.expect("Failed to send info to firebase") .expect("Failed to send info to firebase")
.send() .send()
.ok(); .ok();
}
} }
drop(event_queue_lock);
} }
} }

@ -299,7 +299,7 @@ pub struct MappedInputs {
} }
impl MappedInputs { impl MappedInputs {
pub fn empty() -> MappedInputs { pub const fn empty() -> MappedInputs {
MappedInputs { MappedInputs {
buttons: Buttons::empty(), buttons: Buttons::empty(),
lstick_x: 0, lstick_x: 0,

@ -1,22 +1,24 @@
use once_cell::sync::Lazy;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs; use std::fs;
use std::io::BufReader; use std::io::BufReader;
use std::ptr::addr_of;
use lazy_static::lazy_static;
use parking_lot::Mutex;
use skyline::nn::hid::GetNpadStyleSet; use skyline::nn::hid::GetNpadStyleSet;
use crate::common::button_config::button_mapping; use crate::common::{button_config, ButtonConfig};
use crate::common::*; use crate::common::{DEFAULTS_MENU, MENU};
use crate::events::{Event, EVENT_QUEUE}; use crate::events::{Event, EVENT_QUEUE};
use crate::input::*; use crate::input::{ButtonBitfield, ControllerStyle, MappedInputs, SomeControllerStruct};
use crate::logging::*; use crate::logging::*;
use crate::training::frame_counter; 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 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 { pub unsafe fn menu_condition() -> bool {
button_config::combo_passes(button_config::ButtonCombo::OpenMenu) button_config::combo_passes(button_config::ButtonCombo::OpenMenu)
@ -32,11 +34,9 @@ pub fn load_from_file() {
let reader = BufReader::new(menu_conf); let reader = BufReader::new(menu_conf);
if let Ok(menu_conf_json) = serde_json::from_reader::<BufReader<_>, MenuJsonStruct>(reader) if let Ok(menu_conf_json) = serde_json::from_reader::<BufReader<_>, MenuJsonStruct>(reader)
{ {
unsafe { assign(&MENU, menu_conf_json.menu);
MENU = menu_conf_json.menu; assign(&DEFAULTS_MENU, menu_conf_json.defaults_menu);
DEFAULTS_MENU = menu_conf_json.defaults_menu; info!("Previous menu found. Loading...");
info!("Previous menu found. Loading...");
}
} else { } else {
warn!("Previous menu found but is invalid. Deleting..."); warn!("Previous menu found but is invalid. Deleting...");
let err_msg = format!( let err_msg = format!(
@ -49,23 +49,21 @@ pub fn load_from_file() {
info!("No previous menu file found."); info!("No previous menu file found.");
} }
info!("Setting initial menu selections..."); info!("Setting initial menu selections...");
unsafe { let mut app = lock_write(&QUICK_MENU_APP);
let mut app = QUICK_MENU_APP.lock(); app.serialized_default_settings =
app.serialized_default_settings = serde_json::to_string(&*addr_of!(DEFAULTS_MENU)) serde_json::to_string(&read(&DEFAULTS_MENU)).expect("Could not serialize DEFAULTS_MENU");
.expect("Could not serialize DEFAULTS_MENU"); app.update_all_from_json(
app.update_all_from_json( &serde_json::to_string(&read(&MENU)).expect("Could not serialize MENU"),
&serde_json::to_string(&*addr_of!(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); let response = serde_json::from_str::<MenuJsonStruct>(message);
info!("Received menu message: {message}"); info!("Received menu message: {message}");
if let Ok(message_json) = response { if let Ok(message_json) = response {
// Includes both MENU and DEFAULTS_MENU // Includes both MENU and DEFAULTS_MENU
MENU = message_json.menu; assign(&MENU, message_json.menu);
DEFAULTS_MENU = message_json.defaults_menu; assign(&DEFAULTS_MENU, message_json.defaults_menu);
fs::write( fs::write(
MENU_OPTIONS_PATH, MENU_OPTIONS_PATH,
serde_json::to_string_pretty(&message_json).unwrap(), 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() { pub fn spawn_menu() {
unsafe { assign(&QUICK_MENU_ACTIVE, true);
QUICK_MENU_ACTIVE = true; let mut app = lock_write(&QUICK_MENU_APP);
let mut app = QUICK_MENU_APP.lock(); app.page = AppPage::SUBMENU;
app.page = AppPage::SUBMENU; assign(&MENU_RECEIVED_INPUT, true);
*MENU_RECEIVED_INPUT.data_ptr() = true;
}
} }
#[derive(Eq, PartialEq, Hash, Copy, Clone)] #[derive(Eq, PartialEq, Hash, Copy, Clone)]
@ -101,16 +97,17 @@ enum DirectionButton {
RUp, RUp,
} }
lazy_static! { pub static QUICK_MENU_APP: LazyLock<RwLock<training_mod_tui::App<'static>>> = LazyLock::new(|| {
pub static ref QUICK_MENU_APP: Mutex<training_mod_tui::App<'static>> = Mutex::new({ RwLock::new({
info!("Initialized lazy_static: QUICK_MENU_APP"); info!("Initialized lazy_static: QUICK_MENU_APP");
unsafe { create_app() } unsafe { create_app() }
}); })
pub static ref P1_CONTROLLER_STYLE: Mutex<ControllerStyle> = });
Mutex::new(ControllerStyle::default()); pub static P1_CONTROLLER_STYLE: LazyLock<RwLock<ControllerStyle>> =
static ref DIRECTION_HOLD_FRAMES: Mutex<HashMap<DirectionButton, u32>> = { LazyLock::new(|| RwLock::new(ControllerStyle::default()));
use DirectionButton::*; static DIRECTION_HOLD_FRAMES: LazyLock<RwLock<HashMap<DirectionButton, u32>>> =
Mutex::new(HashMap::from([ LazyLock::new(|| {
RwLock::new(HashMap::from([
(LLeft, 0), (LLeft, 0),
(RLeft, 0), (RLeft, 0),
(LDown, 0), (LDown, 0),
@ -120,12 +117,11 @@ lazy_static! {
(LUp, 0), (LUp, 0),
(RUp, 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> = pub static MENU_CLOSE_FRAME_COUNTER: LazyLock<usize> =
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::Real)); LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::Real));
pub fn handle_final_input_mapping( pub fn handle_final_input_mapping(
player_idx: i32, player_idx: i32,
@ -135,7 +131,7 @@ pub fn handle_final_input_mapping(
unsafe { unsafe {
if player_idx == 0 { if player_idx == 0 {
let p1_controller = &mut *controller_struct.controller; 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); 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 visual_frame_count > 0 && visual_frame_count < MENU_CLOSE_WAIT_FRAMES {
// If we just closed the menu, kill all inputs to avoid accidental presses // 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); 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 // If we're here, remove all other presses
*out = MappedInputs::empty(); *out = MappedInputs::empty();
@ -157,7 +153,7 @@ pub fn handle_final_input_mapping(
const DIRECTION_HOLD_REPEAT_FRAMES: u32 = 20; const DIRECTION_HOLD_REPEAT_FRAMES: u32 = 20;
use DirectionButton::*; 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 // Check for all controllers unplugged
let mut potential_controller_ids = (0..8).collect::<Vec<u32>>(); let mut potential_controller_ids = (0..8).collect::<Vec<u32>>();
@ -166,7 +162,7 @@ pub fn handle_final_input_mapping(
.iter() .iter()
.all(|i| GetNpadStyleSet(i as *const _).flags == 0) .all(|i| GetNpadStyleSet(i as *const _).flags == 0)
{ {
QUICK_MENU_ACTIVE = false; assign(&QUICK_MENU_ACTIVE, false);
return; 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? let mut app = lock_write(&QUICK_MENU_APP);
button_mapping(ButtonConfig::A, style, button_presses).then(|| { button_config::button_mapping(ButtonConfig::A, style, button_presses).then(|| {
app.on_a(); app.on_a();
received_input = true; received_input = true;
}); });
button_mapping(ButtonConfig::B, style, button_presses).then(|| { button_config::button_mapping(ButtonConfig::B, style, button_presses).then(|| {
received_input = true; received_input = true;
app.on_b(); app.on_b();
if app.page == AppPage::CLOSE { if app.page == AppPage::CLOSE {
// Leave menu. // Leave menu.
frame_counter::start_counting(*MENU_CLOSE_FRAME_COUNTER); 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(); let menu_json = app.get_serialized_settings_with_defaults();
set_menu_from_json(&menu_json); 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(); app.on_x();
received_input = true; received_input = true;
}); });
button_mapping(ButtonConfig::Y, style, button_presses).then(|| { button_config::button_mapping(ButtonConfig::Y, style, button_presses).then(|| {
app.on_y(); app.on_y();
received_input = true; received_input = true;
}); });
button_mapping(ButtonConfig::ZL, style, button_presses).then(|| { button_config::button_mapping(ButtonConfig::ZL, style, button_presses).then(|| {
app.on_zl(); app.on_zl();
received_input = true; received_input = true;
}); });
button_mapping(ButtonConfig::ZR, style, button_presses).then(|| { button_config::button_mapping(ButtonConfig::ZR, style, button_presses).then(|| {
app.on_zr(); app.on_zr();
received_input = true; received_input = true;
}); });
button_mapping(ButtonConfig::R, style, button_presses).then(|| { button_config::button_mapping(ButtonConfig::R, style, button_presses).then(|| {
app.on_r(); app.on_r();
received_input = true; received_input = true;
}); });
@ -271,7 +270,7 @@ pub fn handle_final_input_mapping(
if received_input { if received_input {
direction_hold_frames.iter_mut().for_each(|(_, f)| *f = 0); 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::consts::*;
use crate::common::offsets::OFFSET_GET_BATTLE_OBJECT_FROM_ID; use crate::common::offsets::OFFSET_GET_BATTLE_OBJECT_FROM_ID;
use crate::training::character_specific::ptrainer; use crate::training::character_specific::ptrainer;
use training_mod_sync::*;
pub mod button_config; pub mod button_config;
pub mod consts; pub mod consts;
@ -19,11 +20,9 @@ pub mod offsets;
pub mod raygun_printer; pub mod raygun_printer;
pub mod release; pub mod release;
pub static mut DEFAULTS_MENU: TrainingModpackMenu = consts::DEFAULTS_MENU; pub static FIGHTER_MANAGER_ADDR: RwLock<usize> = RwLock::new(0);
pub static mut BASE_MENU: TrainingModpackMenu = unsafe { DEFAULTS_MENU }; pub static ITEM_MANAGER_ADDR: RwLock<usize> = RwLock::new(0);
pub static mut FIGHTER_MANAGER_ADDR: usize = 0; pub static STAGE_MANAGER_ADDR: RwLock<usize> = RwLock::new(0);
pub static mut ITEM_MANAGER_ADDR: usize = 0;
pub static mut STAGE_MANAGER_ADDR: usize = 0;
pub static mut TRAINING_MENU_ADDR: *mut PauseMenu = core::ptr::null_mut(); pub static mut TRAINING_MENU_ADDR: *mut PauseMenu = core::ptr::null_mut();
#[cfg(not(feature = "outside_training_mode"))] #[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_int = fighter_id as i32;
let entry_id = app::FighterEntryID(entry_id_int); let entry_id = app::FighterEntryID(entry_id_int);
unsafe { 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 = let fighter_entry =
FighterManager::get_fighter_entry(mgr, entry_id) as *mut app::FighterEntry; FighterManager::get_fighter_entry(mgr, entry_id) as *mut app::FighterEntry;
if fighter_entry.is_null() { 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 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); let fighter_information = FighterManager::get_fighter_information(mgr, entry_id);
FighterInformation::is_operation_cpu(fighter_information) 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 // Returns true if a match is currently active
pub unsafe fn is_ready_go() -> bool { 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) FighterManager::is_ready_go(fighter_manager)
} }
pub unsafe fn entry_count() -> i32 { 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) 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?) // 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 #![cfg_attr(rustfmt, rustfmt_skip)] // We want the assembly needles to stay in lines of four bytes each
use crate::logging::*; use crate::logging::*;
use lazy_static::lazy_static; use training_mod_sync::LazyLock;
// Stolen from HDR who stole it from Arcropolis // Stolen from HDR who stole it from Arcropolis
// https://github.com/HDR-Development/HewDraw-Remix/blob/dev/dynamic/src/util.rs // 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 { macro_rules! impl_offset {
($fn_name:ident) => { ($fn_name:ident) => {
paste::paste! { paste::paste! {
lazy_static! { 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)));
pub static ref [<OFFSET_ $fn_name>]: usize = 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 anyhow::{anyhow, Result};
use lazy_static::lazy_static;
use parking_lot::Mutex;
use serde_json::Value; use serde_json::Value;
use zip::ZipArchive; use zip::ZipArchive;
lazy_static! { use crate::common::dialog;
pub static ref CURRENT_VERSION: Mutex<String> = Mutex::new({ use crate::consts::*;
info!("Initialized lazy_static: CURRENT_VERSION"); use crate::logging::*;
match get_current_version() {
Ok(v) => v, use training_mod_sync::*;
Err(e) => panic!("Could not find current modpack version!: {}", e),
} 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)] #[derive(Debug)]
pub struct Release { pub struct Release {
@ -72,13 +70,12 @@ impl Release {
// alphabetical order == chronological order // alphabetical order == chronological order
// //
// https://datatracker.ietf.org/doc/html/rfc3339#section-5.1 // 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 { fn get_update_policy() -> UpdatePolicy {
unsafe { MENU.update_policy } read(&MENU).update_policy
} }
fn get_release(beta: bool) -> Result<Release> { fn get_release(beta: bool) -> Result<Release> {
@ -190,10 +187,8 @@ pub fn perform_version_check() {
}; };
if release_to_apply.is_ok() { if release_to_apply.is_ok() {
let published_at = release_to_apply.as_ref().unwrap().published_at.clone(); 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); 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() { if release_to_apply.as_ref().unwrap().is_older_than_installed() {
release_to_apply = Err(anyhow!( release_to_apply = Err(anyhow!(
"Github version is not newer than the current installed version.", "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::error::show_error;
use skyline::hook; use skyline::hook;
use skyline::hooks::A64InlineHook; use skyline::hooks::A64InlineHook;
use skyline::text_iter::{add_get_imm, adrp_get_imm, Instruction::*, TextIter}; use skyline::text_iter::{add_get_imm, adrp_get_imm, Instruction::*, TextIter};
use smash::app::smashball::is_training_mode; use smash::app::smashball::is_training_mode;
use HazardState::*;
use HookState::*;
use crate::common::consts::*; use crate::common::consts::*;
use crate::logging::*; use crate::logging::*;
use training_mod_sync::*;
use HazardState::*;
use HookState::*;
enum HazardState { enum HazardState {
Begin, Begin,
Adrp1, Adrp1,
@ -29,6 +27,9 @@ enum HookState {
Ldrsw2, 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 { fn get_hazard_flag_address() -> usize {
let mut state = HazardState::Begin; let mut state = HazardState::Begin;
let mut flag_pos = 0; let mut flag_pos = 0;
@ -81,12 +82,8 @@ fn get_hazard_hook_address() -> usize {
flag_pos flag_pos
} }
// 8.1.0 Defaults #[hook(offset = *LOAD_ADDRESS, inline)]
static mut HAZARD_FLAG_ADDRESS: *mut u8 = 0x04eb_bf95 as *mut u8; fn hazard_intercept(_ctx: &skyline::hooks::InlineCtx) {
static mut LOAD_ADDRESS: usize = 0x0214_bde8;
#[hook(offset = LOAD_ADDRESS, inline)]
fn hazard_intercept(ctx: &skyline::hooks::InlineCtx) {
unsafe { unsafe {
if is_training_mode() { if is_training_mode() {
mod_handle_hazards(); mod_handle_hazards();
@ -96,22 +93,20 @@ fn hazard_intercept(ctx: &skyline::hooks::InlineCtx) {
fn mod_handle_hazards() { fn mod_handle_hazards() {
unsafe { 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<(), ()> { 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_string: String = String::new();
let mut error_id = 0; 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_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; 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_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; error_id += 1000;
} }
@ -133,10 +128,8 @@ pub fn hazard_manager() {
info!("Applying hazard control mods."); info!("Applying hazard control mods.");
unsafe { unsafe {
if let Ok(()) = validate_hazards_addrs() { if let Ok(()) = validate_hazards_addrs() {
HAZARD_FLAG_ADDRESS = get_hazard_flag_address() as *mut u8;
LOAD_ADDRESS = get_hazard_hook_address();
A64InlineHook( 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, hazard_intercept as *const skyline::libc::c_void,
); );
} }

@ -5,6 +5,8 @@ use smash::phx::{Hash40, Vector3f};
use crate::common::{consts::*, *}; use crate::common::{consts::*, *};
use crate::logging::*; use crate::logging::*;
use training_mod_sync::*;
pub const ID_COLORS: &[Vector3f] = &[ pub const ID_COLORS: &[Vector3f] = &[
// used to tint the hitbox effects -- make sure that at least one component // used to tint the hitbox effects -- make sure that at least one component
// is equal to 1.0 // 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 // Resume Effect AnimCMD incase we don't display hitboxes
MotionAnimcmdModule::set_sleep_effect(module_accessor, false); MotionAnimcmdModule::set_sleep_effect(module_accessor, false);
if MENU.hitbox_vis == OnOff::OFF { if read(&MENU).hitbox_vis == OnOff::OFF {
return; return;
} }
@ -205,7 +207,7 @@ unsafe fn mod_handle_attack(lua_state: u64) {
// necessary if param object fails // necessary if param object fails
// hacky way of forcing no shield damage on all hitboxes // 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> = let mut hitbox_params: Vec<L2CValue> =
(0..36).map(|i| l2c_agent.pop_lua_stack(i + 1)).collect(); (0..36).map(|i| l2c_agent.pop_lua_stack(i + 1)).collect();
l2c_agent.clear_lua_stack(); l2c_agent.clear_lua_stack();
@ -219,7 +221,7 @@ unsafe fn mod_handle_attack(lua_state: u64) {
} }
// Hitbox Visualization // Hitbox Visualization
if MENU.hitbox_vis == OnOff::ON { if read(&MENU).hitbox_vis == OnOff::ON {
// get all necessary grabbox params // get all necessary grabbox params
let id = l2c_agent.pop_lua_stack(1); // int let id = l2c_agent.pop_lua_stack(1); // int
let joint = l2c_agent.pop_lua_stack(3); // hash40 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) { unsafe fn mod_handle_catch(lua_state: u64) {
if MENU.hitbox_vis == OnOff::OFF { if read(&MENU).hitbox_vis == OnOff::OFF {
return; return;
} }

@ -21,6 +21,7 @@ use std::path::PathBuf;
use skyline::nro::{self, NroInfo}; use skyline::nro::{self, NroInfo};
use training_mod_consts::{OnOff, LEGACY_TRAINING_MODPACK_ROOT}; 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::button_config::DEFAULT_OPEN_MENU_CONFIG;
use crate::common::events::events_loop; use crate::common::events::events_loop;
@ -75,10 +76,10 @@ pub fn main() {
info!("Initialized."); info!("Initialized.");
unsafe { let mut event_queue = lock_write(&EVENT_QUEUE);
EVENT_QUEUE.push(Event::smash_open()); (*event_queue).push(Event::smash_open());
notification("Training Modpack".to_string(), "Welcome!".to_string(), 60); drop(event_queue);
} notification("Training Modpack".to_string(), "Welcome!".to_string(), 60);
hitbox_visualizer::hitbox_visualization(); hitbox_visualizer::hitbox_visualization();
hazard_manager::hazard_manager(); hazard_manager::hazard_manager();
@ -125,38 +126,36 @@ pub fn main() {
info!("Skipping version check because we are using an emulator"); info!("Skipping version check because we are using an emulator");
} }
unsafe { notification("Training Modpack".to_string(), "Welcome!".to_string(), 60);
notification("Training Modpack".to_string(), "Welcome!".to_string(), 60); notification(
notification( "Open Menu".to_string(),
"Open Menu".to_string(), if read(&MENU).menu_open_start_press == OnOff::ON {
if MENU.menu_open_start_press == OnOff::ON { "Hold Start".to_string()
"Hold Start".to_string() } else {
} else { DEFAULT_OPEN_MENU_CONFIG.to_string()
DEFAULT_OPEN_MENU_CONFIG.to_string() },
}, 120,
120, );
); notification(
notification( "Save State".to_string(),
"Save State".to_string(), read(&MENU).save_state_save.to_string(),
MENU.save_state_save.to_string(), 120,
120, );
); notification(
notification( "Load State".to_string(),
"Load State".to_string(), read(&MENU).save_state_load.to_string(),
MENU.save_state_load.to_string(), 120,
120, );
); notification(
notification( "Input Record".to_string(),
"Input Record".to_string(), read(&MENU).input_record.to_string(),
MENU.input_record.to_string(), 120,
120, );
); notification(
notification( "Input Playback".to_string(),
"Input Playback".to_string(), read(&MENU).input_playback.to_string(),
MENU.input_playback.to_string(), 120,
120, );
);
}
std::thread::spawn(events_loop); std::thread::spawn(events_loop);
} }

@ -6,8 +6,9 @@ use smash::lib::lua_const::*;
use crate::common::consts::*; use crate::common::consts::*;
use crate::common::*; use crate::common::*;
use crate::training::directional_influence::should_reverse_angle; 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( pub unsafe fn mod_get_stick_x(
module_accessor: &mut app::BattleObjectModuleAccessor, module_accessor: &mut app::BattleObjectModuleAccessor,
@ -31,9 +32,13 @@ unsafe fn get_angle(module_accessor: &mut app::BattleObjectModuleAccessor) -> Op
return None; return None;
} }
STICK_DIRECTION = MENU.air_dodge_dir.get_random(); assign(
STICK_DIRECTION.into_angle().map(|angle| { &AIRDODGE_STICK_DIRECTION,
if !should_reverse_angle(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 // Direction is LEFT/RIGHT, so don't perform any adjustment
angle angle
} else { } else {

@ -2,13 +2,15 @@ use smash::app::{self};
use crate::common::consts::*; use crate::common::consts::*;
use crate::common::*; 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() { pub fn roll_direction() {
unsafe { assign(
DIRECTION = MENU.attack_angle.get_random(); &ATTACK_ANGLE_DIRECTION,
} read(&MENU).attack_angle.get_random(),
);
} }
pub unsafe fn mod_get_stick_dir( pub unsafe fn mod_get_stick_dir(
@ -18,7 +20,7 @@ pub unsafe fn mod_get_stick_dir(
return None; return None;
} }
match DIRECTION { match read(&ATTACK_ANGLE_DIRECTION) {
AttackAngle::UP => Some(1.0), AttackAngle::UP => Some(1.0),
AttackAngle::DOWN => Some(-1.0), AttackAngle::DOWN => Some(-1.0),
_ => None, _ => None,

@ -8,60 +8,59 @@ use crate::is_operation_cpu;
use crate::training::frame_counter; use crate::training::frame_counter;
use crate::training::handle_add_limit; use crate::training::handle_add_limit;
use once_cell::sync::Lazy; use training_mod_sync::*;
static mut BUFF_REMAINING_PLAYER: usize = 0; static BUFF_REMAINING_PLAYER: RwLock<usize> = RwLock::new(0);
static mut BUFF_REMAINING_CPU: usize = 0; static BUFF_REMAINING_CPU: RwLock<usize> = RwLock::new(0);
static mut IS_BUFFING_PLAYER: bool = false; static IS_BUFFING_PLAYER: RwLock<bool> = RwLock::new(false);
static mut IS_BUFFING_CPU: bool = false; static IS_BUFFING_CPU: RwLock<bool> = RwLock::new(false);
static BUFF_DELAY_COUNTER: Lazy<usize> = static BUFF_DELAY_COUNTER: LazyLock<usize> =
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame)); 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) { if is_operation_cpu(module_accessor) {
IS_BUFFING_CPU = false; assign(&IS_BUFFING_CPU, false);
return; } 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) { if is_operation_cpu(module_accessor) {
IS_BUFFING_CPU = true; assign(&IS_BUFFING_CPU, true);
return; } 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) { 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 { pub fn is_buffing_any() -> bool {
IS_BUFFING_CPU || IS_BUFFING_PLAYER read(&IS_BUFFING_CPU) || read(&IS_BUFFING_PLAYER)
} }
pub unsafe fn set_buff_rem( pub fn set_buff_rem(module_accessor: &mut app::BattleObjectModuleAccessor, new_value: usize) {
module_accessor: &mut app::BattleObjectModuleAccessor,
new_value: usize,
) {
if is_operation_cpu(module_accessor) { if is_operation_cpu(module_accessor) {
BUFF_REMAINING_CPU = new_value; assign(&BUFF_REMAINING_CPU, new_value);
return; } 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) { 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( 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_M); // stops Psyche-Up quake
CameraModule::stop_quake(module_accessor, *CAMERA_QUAKE_KIND_S); // stops Monado Art 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 { if fighter_kind == *FIGHTER_KIND_BRAVE {
return buff_hero(module_accessor, status); 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 { 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) { if !is_buffing(module_accessor) {
// Initial set up for spells // Initial set up for spells
start_buff(module_accessor); 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 { unsafe fn buff_wario(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
if !is_buffing(module_accessor) { 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 { let waft_count_secs = match waft_level {
BuffOption::WAFT_MINI => WorkModule::get_param_float( BuffOption::WAFT_MINI => WorkModule::get_param_float(
module_accessor, 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 { 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() { if current_art == BuffOption::empty() {
// No Monado Arts selected in the buff menu, so we don't need to buff // No Monado Arts selected in the buff menu, so we don't need to buff
return true; 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;
use smash::app::lua_bind::*; use smash::app::lua_bind::*;
use smash::app::ItemKind; use smash::app::ItemKind;
@ -9,7 +10,10 @@ use crate::common::consts::*;
use crate::common::*; use crate::common::*;
use crate::offsets::OFFSET_GENERATE_ARTICLE_FOR_TARGET; use crate::offsets::OFFSET_GENERATE_ARTICLE_FOR_TARGET;
use crate::training::mash; 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 struct CharItem {
pub fighter_kind: LuaConst, pub fighter_kind: LuaConst,
pub item_kind: Option<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) { unsafe fn apply_single_item(player_fighter_kind: i32, item: &CharItem) {
let player_module_accessor = get_module_accessor(FighterId::Player); let player_module_accessor = get_module_accessor(FighterId::Player);
let cpu_module_accessor = get_module_accessor(FighterId::CPU); 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 { if player_fighter_kind != *FIGHTER_KIND_LINK {
ItemModule::drop_item(cpu_module_accessor, 0.0, 0.0, 0); ItemModule::drop_item(cpu_module_accessor, 0.0, 0.0, 0);
//ItemModule::eject_have_item(cpu_module_accessor, 0, false, false); //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); let item_ptr = ItemManager::get_active_item(item_mgr, 0);
ItemModule::have_item_instance( ItemModule::have_item_instance(
player_module_accessor, 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| { item.article_kind.as_ref().map(|article_kind| {
TURNIP_CHOSEN = if [*ITEM_VARIATION_PEACHDAIKON_8, *ITEM_VARIATION_DAISYDAIKON_8] assign(
.contains(&variation) &TURNIP_CHOSEN,
{ if [*ITEM_VARIATION_PEACHDAIKON_8, *ITEM_VARIATION_DAISYDAIKON_8].contains(&variation) {
Some(8) Some(8)
} else if [*ITEM_VARIATION_PEACHDAIKON_7, *ITEM_VARIATION_DAISYDAIKON_7] } else if [*ITEM_VARIATION_PEACHDAIKON_7, *ITEM_VARIATION_DAISYDAIKON_7]
.contains(&variation) .contains(&variation)
{ {
Some(7) Some(7)
} else if [*ITEM_VARIATION_PEACHDAIKON_6, *ITEM_VARIATION_DAISYDAIKON_6] } else if [*ITEM_VARIATION_PEACHDAIKON_6, *ITEM_VARIATION_DAISYDAIKON_6]
.contains(&variation) .contains(&variation)
{ {
Some(6) Some(6)
} else if [*ITEM_VARIATION_PEACHDAIKON_1, *ITEM_VARIATION_DAISYDAIKON_1] } else if [*ITEM_VARIATION_PEACHDAIKON_1, *ITEM_VARIATION_DAISYDAIKON_1]
.contains(&variation) .contains(&variation)
{ {
Some(1) Some(1)
} else { } else {
None None
}; },
);
let article_kind = **article_kind; let article_kind = **article_kind;
if article_kind == FIGHTER_DIDDY_GENERATE_ARTICLE_ITEM_BANANA { 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, false,
); );
// Grab item from the middle of the stage where it gets shot // 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); let item = ItemManager::get_active_item(item_mgr, 0);
ItemModule::have_item_instance( ItemModule::have_item_instance(
player_module_accessor, player_module_accessor,
@ -428,16 +430,18 @@ unsafe fn apply_single_item(player_fighter_kind: i32, item: &CharItem) {
false, false,
); );
} else { } 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( ArticleModule::generate_article(
generator_module_accessor, // we want CPU's article generator_module_accessor, // we want CPU's article
article_kind, article_kind,
false, false,
0, 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 { pub unsafe fn [<handle_ $char daikon_ $num _prob>]() -> f32 {
let orig = original!()(); let orig = original!()();
if is_training_mode() { if is_training_mode() {
if TURNIP_CHOSEN == Some($num) { let turnip_chosen = read(&TURNIP_CHOSEN);
if turnip_chosen == Some($num) {
return 58.0; return 58.0;
} else if TURNIP_CHOSEN != None { } else if turnip_chosen != None {
return 0.0; return 0.0;
} }
} }
@ -512,14 +517,14 @@ daikon_replace!(DAISY, daisy, 1);
// GenerateArticleForTarget for Peach/Diddy(/Link?) item creation // GenerateArticleForTarget for Peach/Diddy(/Link?) item creation
#[skyline::hook(offset = *OFFSET_GENERATE_ARTICLE_FOR_TARGET)] #[skyline::hook(offset = *OFFSET_GENERATE_ARTICLE_FOR_TARGET)]
pub unsafe fn handle_generate_article_for_target( pub unsafe fn handle_generate_article_for_target(
article_module_accessor: *mut BattleObjectModuleAccessor, article_module_accessor: BattleObjectModuleAccessor,
int_1: i32, int_1: i32,
module_accessor: *mut BattleObjectModuleAccessor, // this is always 0x0 normally module_accessor: BattleObjectModuleAccessor, // this is always 0x0 normally
bool_1: bool, bool_1: bool,
int_2: i32, int_2: i32,
) -> u64 { ) -> u64 {
// unknown return value, gets cast to an (Article *) // 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!()( original!()(
article_module_accessor, article_module_accessor,

@ -1,15 +1,16 @@
use crate::offsets::OFFSET_POKEMON_DECIDE; use crate::offsets::OFFSET_POKEMON_DECIDE;
use crate::training::frame_counter; use crate::training::frame_counter;
use crate::training::save_states; use crate::training::save_states;
use once_cell::sync::Lazy;
use skyline::hooks::InlineCtx; use skyline::hooks::InlineCtx;
use smash::app::{self, lua_bind::*, smashball::is_training_mode}; use smash::app::{self, lua_bind::*, smashball::is_training_mode};
use smash::hash40; use smash::hash40;
use smash::lib::lua_const::*; use smash::lib::lua_const::*;
use smash::phx::Hash40; use smash::phx::Hash40;
static SWITCH_DELAY_COUNTER: Lazy<usize> = use training_mod_sync::LazyLock;
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
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 { pub unsafe fn is_switched(ptrainer_module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
let status_kind = StatusModule::status_kind(ptrainer_module_accessor); 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::consts::*;
use crate::common::*; use crate::common::*;
use training_mod_sync::*;
static mut COUNTER: u32 = 0; static COUNTER: RwLock<u32> = RwLock::new(0);
static mut CLATTER_STEP: f32 = 8.0; static CLATTER_STEP: RwLock<f32> = RwLock::new(8.0);
unsafe fn do_clatter_input(module_accessor: &mut BattleObjectModuleAccessor) { 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 { let zeros = Vector3f {
x: 0.0, x: 0.0,
y: 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 // Don't do clatter inputs if we're not in clatter
return; return;
} }
let repeat = MENU.clatter_strength.into_u32(); let repeat = read(&MENU).clatter_strength.into_u32();
COUNTER = (COUNTER + 1) % repeat; let mut counter_lock = lock_write(&COUNTER);
if COUNTER == repeat - 1 { *counter_lock = ((*counter_lock) + 1) % repeat;
if *counter_lock == repeat - 1 {
do_clatter_input(module_accessor); 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 // Most of the time this is 8 frames, but could be less depending on
// the status (e.g. freeze is 4 frames / input) // the status (e.g. freeze is 4 frames / input)
if is_training_mode() && is_operation_cpu(module_accessor) { if is_training_mode() && is_operation_cpu(module_accessor) {
CLATTER_STEP = manual_recovery_rate; assign(&CLATTER_STEP, manual_recovery_rate);
} }
original!()( original!()(
module_accessor, module_accessor,

@ -1,168 +1,174 @@
use skyline::nn::ui2d::ResColor; 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::consts::Action;
use crate::training::*; 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 PLAYER_WAS_ACTIONABLE: RwLock<bool> = RwLock::new(false);
static mut PLAYER_ACTIONABLE: bool = false; static CPU_WAS_ACTIONABLE: RwLock<bool> = RwLock::new(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 FRAME_COUNTER_INDEX: Lazy<usize> = static PLAYER_FRAME_COUNTER_INDEX: LazyLock<usize> =
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame)); 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); let prev_status = StatusModule::prev_status_kind(module_accessor, 0);
(*FIGHTER_STATUS_KIND_DAMAGE..*FIGHTER_STATUS_KIND_DAMAGE_FALL).contains(&prev_status) (*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 { unsafe fn was_in_shieldstun(module_accessor: *mut BattleObjectModuleAccessor) -> bool {
let prev_status = StatusModule::prev_status_kind(module_accessor, 0); let prev_status = StatusModule::prev_status_kind(module_accessor, 0);
prev_status == FIGHTER_STATUS_KIND_GUARD_DAMAGE prev_status == FIGHTER_STATUS_KIND_GUARD_DAMAGE
} }
macro_rules! actionable_statuses { unsafe fn is_in_shieldstun(module_accessor: *mut BattleObjectModuleAccessor) -> bool {
() => { StatusModule::status_kind(module_accessor) == FIGHTER_STATUS_KIND_GUARD_DAMAGE
[
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_actionable(module_accessor: *mut BattleObjectModuleAccessor) -> bool { 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) WorkModule::is_enable_transition_term(module_accessor, **actionable_transition)
}) || CancelModule::is_enable_cancel(module_accessor) }) || CancelModule::is_enable_cancel(module_accessor)
} }
fn update_frame_advantage(new_frame_adv: i32) { fn update_frame_advantage(frame_advantage: i32) {
unsafe { if read(&MENU).frame_advantage == OnOff::ON {
FRAME_ADVANTAGE = new_frame_adv; // Prioritize Frame Advantage over Input Recording Playback
if MENU.frame_advantage == OnOff::ON { notifications::clear_notification("Input Recording");
// Prioritize Frame Advantage over Input Recording Playback notifications::clear_notification("Frame Advantage");
ui::notifications::clear_notifications("Input Recording"); notifications::color_notification(
ui::notifications::clear_notifications("Frame Advantage"); "Frame Advantage".to_string(),
ui::notifications::color_notification( format!("{frame_advantage}"),
"Frame Advantage".to_string(), 60,
format!("{FRAME_ADVANTAGE}"), match frame_advantage {
60, x if x < 0 => ResColor {
match FRAME_ADVANTAGE { r: 200,
x if x < 0 => ResColor { g: 8,
r: 200, b: 8,
g: 8, a: 255,
b: 8,
a: 255,
},
0 => ResColor {
r: 0,
g: 0,
b: 0,
a: 255,
},
_ => ResColor {
r: 31,
g: 198,
b: 0,
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( pub unsafe fn once_per_frame(module_accessor: &mut BattleObjectModuleAccessor) {
module_accessor: *mut BattleObjectModuleAccessor, // Skip the CPU so we don't run twice per frame
transition_term: i32,
is: bool,
) {
let entry_id_int = WorkModule::get_int(module_accessor, *FIGHTER_INSTANCE_WORK_ID_INT_ENTRY_ID); let entry_id_int = WorkModule::get_int(module_accessor, *FIGHTER_INSTANCE_WORK_ID_INT_ENTRY_ID);
if entry_id_int != (FighterId::Player as i32) { if entry_id_int != (FighterId::Player as i32) {
return; 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 player_module_accessor = get_module_accessor(FighterId::Player);
let cpu_module_accessor = get_module_accessor(FighterId::CPU); 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 if !is_counting {
// there's been a hit that connects if read(&MENU).mash_state == Action::empty()
// if AttackModule::is_infliction( && !player_is_actionable
// player_module_accessor, && !cpu_is_actionable
// *COLLISION_KIND_MASK_HIT | *COLLISION_KIND_MASK_SHIELD) { && (!was_in_shieldstun(cpu_module_accessor) && is_in_shieldstun(cpu_module_accessor)
|| (!was_in_hitstun(cpu_module_accessor) && is_in_hitstun(cpu_module_accessor)))
// the frame the fighter *becomes* actionable {
if !CPU_ACTIONABLE && is_actionable(cpu_module_accessor) { // Start counting when:
CPU_ACTIVE_FRAME = frame_counter::get_frame_count(*FRAME_COUNTER_INDEX); // 1. We have no mash option selected AND
} // 2. Neither fighter is currently actionable AND
// 3. Either
if !PLAYER_ACTIONABLE && is_actionable(player_module_accessor) { // a. the CPU has just entered shieldstun
PLAYER_ACTIVE_FRAME = frame_counter::get_frame_count(*FRAME_COUNTER_INDEX); // b. the CPU has just entered hitstun
} //
// If a mash option is selected, this can interfere with our ability to determine when
CPU_ACTIONABLE = is_actionable(cpu_module_accessor); // a character becomes actionable. So don't ever start counting if we can't reliably stop.
PLAYER_ACTIONABLE = is_actionable(player_module_accessor); //
// Since our "just_actionable" checks assume that neither character is already actionable,
// if neither are active // we need to guard against instances where the player is already actionable by the time that
if !CPU_ACTIONABLE && !PLAYER_ACTIONABLE { // the CPU get hit, such as if the player threw a projectile from far away.
if !FRAME_ADVANTAGE_CHECK { // Otherwise our "just_actionable" checks are not valid.
frame_counter::reset_frame_count(*FRAME_COUNTER_INDEX); //
frame_counter::start_counting(*FRAME_COUNTER_INDEX); // 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 // Stop counting as soon as each fighter becomes actionable
if PLAYER_ACTIONABLE && CPU_ACTIONABLE && FRAME_ADVANTAGE_CHECK { if player_just_actionable {
if was_in_shieldstun(cpu_module_accessor) { frame_counter::stop_counting(*PLAYER_FRAME_COUNTER_INDEX);
update_frame_advantage((CPU_ACTIVE_FRAME as i64 - PLAYER_ACTIVE_FRAME as i64) as i32);
} }
frame_counter::stop_counting(*FRAME_COUNTER_INDEX); if cpu_just_actionable {
FRAME_ADVANTAGE_CHECK = false; 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::consts::OnOff;
use crate::common::*; use crate::common::*;
use training_mod_sync::*;
pub unsafe fn mod_get_stick_y(module_accessor: &mut BattleObjectModuleAccessor) -> Option<f32> { pub unsafe fn mod_get_stick_y(module_accessor: &mut BattleObjectModuleAccessor) -> Option<f32> {
if !is_operation_cpu(module_accessor) { if !is_operation_cpu(module_accessor) {
return None; return None;
} }
let fighter_status_kind = StatusModule::status_kind(module_accessor); 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_WAIT,
*FIGHTER_STATUS_KIND_SQUAT, *FIGHTER_STATUS_KIND_SQUAT,

@ -7,18 +7,17 @@ use smash::lua2cpp::L2CFighterCommon;
use crate::common::consts::*; use crate::common::consts::*;
use crate::common::*; 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() { pub fn roll_di_case() {
unsafe { let mut di_case_lock = lock_write(&DI_CASE);
if DI_CASE != Direction::empty() { if *di_case_lock != Direction::empty() {
// DI direction already selected, don't pick a new one // DI direction already selected, don't pick a new one
return; return;
}
DI_CASE = MENU.di_state.get_random();
} }
*di_case_lock = read(&MENU).di_state.get_random();
} }
pub fn reset_di_case(module_accessor: &mut app::BattleObjectModuleAccessor) { 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 // Don't reset the DI direction during hitstun
return; return;
} }
unsafe { let mut di_case_lock = lock_write(&DI_CASE);
if DI_CASE != Direction::empty() { if *di_case_lock != Direction::empty() {
DI_CASE = 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) { unsafe fn mod_handle_di(fighter: &L2CFighterCommon, _arg1: L2CValue) {
if MENU.di_state == Direction::empty() { if read(&MENU).di_state == Direction::empty() {
return; return;
} }
@ -56,9 +54,9 @@ unsafe fn mod_handle_di(fighter: &L2CFighterCommon, _arg1: L2CValue) {
} }
roll_di_case(); roll_di_case();
let di_case = read(&DI_CASE);
let angle_tuple = DI_CASE.into_angle().map_or((0.0, 0.0), |angle| { let angle_tuple = di_case.into_angle().map_or((0.0, 0.0), |angle| {
let a = if should_reverse_angle(DI_CASE) { let a = if should_reverse_angle(di_case) {
PI - angle PI - angle
} else { } else {
angle angle

@ -5,26 +5,21 @@ use smash::phx::{Hash40, Vector3f};
use crate::common::*; use crate::common::*;
use crate::training::{frame_counter, input_record}; use crate::training::{frame_counter, input_record};
use once_cell::sync::Lazy; use training_mod_sync::*;
// The current fastfall delay static DELAY: RwLock<u32> = RwLock::new(0);
static mut DELAY: u32 = 0; static FAST_FALL: RwLock<bool> = RwLock::new(false);
static FRAME_COUNTER_INDEX: LazyLock<usize> =
static mut FAST_FALL: bool = false; LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
fn should_fast_fall() -> bool { fn should_fast_fall() -> bool {
unsafe { FAST_FALL } read(&FAST_FALL)
} }
pub fn roll_fast_fall() { pub fn roll_fast_fall() {
unsafe { assign(&FAST_FALL, read(&MENU).fast_fall.get_random().into_bool());
FAST_FALL = 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) { pub fn get_command_flag_cat(module_accessor: &mut app::BattleObjectModuleAccessor) {
if !should_fast_fall() { if !should_fast_fall() {
return; return;
@ -42,7 +37,10 @@ pub fn get_command_flag_cat(module_accessor: &mut app::BattleObjectModuleAccesso
unsafe { unsafe {
if !is_falling(module_accessor) { if !is_falling(module_accessor) {
// Roll FF delay // 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); frame_counter::full_reset(*FRAME_COUNTER_INDEX);
return; return;
} }
@ -57,7 +55,8 @@ pub fn get_command_flag_cat(module_accessor: &mut app::BattleObjectModuleAccesso
} }
// Check delay // 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; return;
} }

@ -1,3 +1,6 @@
use training_mod_sync::*;
static COUNTERS: RwLock<Vec<FrameCounter>> = RwLock::new(vec![]);
#[derive(PartialEq, Eq)] #[derive(PartialEq, Eq)]
pub enum FrameCounterType { pub enum FrameCounterType {
InGame, InGame,
@ -13,38 +16,35 @@ pub struct FrameCounter {
counter_type: FrameCounterType, counter_type: FrameCounterType,
} }
static mut COUNTERS: Vec<FrameCounter> = vec![];
pub fn register_counter(counter_type: FrameCounterType) -> usize { pub fn register_counter(counter_type: FrameCounterType) -> usize {
unsafe { let mut counters_lock = lock_write(&COUNTERS);
let index = COUNTERS.len(); let index = (*counters_lock).len();
(*counters_lock).push(FrameCounter {
COUNTERS.push(FrameCounter { count: 0,
count: 0, should_count: false,
should_count: false, counter_type,
counter_type, });
}); index
index
}
} }
pub fn start_counting(index: usize) { pub fn start_counting(index: usize) {
unsafe { let mut counters_lock = lock_write(&COUNTERS);
COUNTERS[index].should_count = true; (*counters_lock)[index].should_count = true;
}
} }
pub fn stop_counting(index: usize) { pub fn stop_counting(index: usize) {
unsafe { let mut counters_lock = lock_write(&COUNTERS);
COUNTERS[index].should_count = false; (*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) { pub fn reset_frame_count(index: usize) {
unsafe { let mut counters_lock = lock_write(&COUNTERS);
COUNTERS[index].count = 0; (*counters_lock)[index].count = 0;
}
} }
pub fn full_reset(index: usize) { 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 { 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) { pub fn tick_idx(index: usize) {
unsafe { let mut counters_lock = lock_write(&COUNTERS);
COUNTERS[index].count += 1; (*counters_lock)[index].count += 1;
}
} }
pub fn tick_ingame() { pub fn tick_ingame() {
unsafe { let mut counters_lock = lock_write(&COUNTERS);
for (index, counter) in COUNTERS.iter().enumerate() { for counter in (*counters_lock).iter_mut() {
if !counter.should_count || counter.counter_type == FrameCounterType::Real { if !counter.should_count || counter.counter_type == FrameCounterType::Real {
continue; continue;
}
tick_idx(index);
} }
// same as full_reset, but we already have the lock so we can't lock again
counter.count += 1;
} }
} }
pub fn tick_real() { pub fn tick_real() {
unsafe { let mut counters_lock = lock_write(&COUNTERS);
for (index, counter) in COUNTERS.iter().enumerate() { for counter in (*counters_lock).iter_mut() {
if !counter.should_count if !counter.should_count
|| (counter.counter_type == FrameCounterType::InGame || (counter.counter_type == FrameCounterType::InGame
|| counter.counter_type == FrameCounterType::InGameNoReset) || counter.counter_type == FrameCounterType::InGameNoReset)
{ {
continue; continue;
}
tick_idx(index);
} }
// same as full_reset, but we already have the lock so we can't lock again
counter.count += 1;
} }
} }
pub fn reset_all() { pub fn reset_all() {
unsafe { let mut counters_lock = lock_write(&COUNTERS);
for (index, counter) in COUNTERS.iter().enumerate() { for counter in (*counters_lock).iter_mut() {
if counter.counter_type != FrameCounterType::InGame { if counter.counter_type != FrameCounterType::InGame {
continue; continue;
}
full_reset(index);
} }
// 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 smash::lib::lua_const::*;
use crate::common::*; use crate::common::*;
use training_mod_sync::*;
// the current full hop status // 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 { pub fn should_full_hop() -> bool {
unsafe { FULL_HOP } read(&FULL_HOP)
} }
pub fn roll_full_hop() { pub fn roll_full_hop() {
unsafe { assign(&FULL_HOP, read(&MENU).full_hop.get_random().into_bool());
FULL_HOP = MENU.full_hop.get_random().into_bool();
}
} }
pub unsafe fn check_button_on( pub unsafe fn check_button_on(

@ -1,30 +1,23 @@
use std::collections::VecDeque; use std::collections::VecDeque;
use crate::common::input::*; use crate::common::input::*;
use lazy_static::lazy_static;
use parking_lot::Mutex; use training_mod_sync::*;
use crate::common::MENU; use crate::common::MENU;
lazy_static! { static P1_DELAYED_INPUT_MAPPINGS: RwLock<VecDeque<MappedInputs>> = RwLock::new(VecDeque::new());
static ref P1_DELAYED_INPUT_MAPPINGS: Mutex<VecDeque<MappedInputs>> =
Mutex::new(VecDeque::new());
}
pub fn handle_final_input_mapping(player_idx: i32, out: *mut MappedInputs) { pub unsafe fn handle_final_input_mapping(player_idx: i32, out: *mut MappedInputs) {
unsafe { if player_idx == 0 {
if player_idx == 0 { let mut delayed_mappings = lock_write(&P1_DELAYED_INPUT_MAPPINGS);
let mut delayed_mappings = P1_DELAYED_INPUT_MAPPINGS.lock(); let actual_mapping = *out;
let actual_mapping = *out; if delayed_mappings.len() < read(&MENU).input_delay.into_delay() as usize {
*out = MappedInputs::empty();
if delayed_mappings.len() < MENU.input_delay.into_delay() as usize { } else if let Some(delayed_mapping) = delayed_mappings.back() {
*out = MappedInputs::empty(); *out = *delayed_mapping;
} 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);
} }
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 itertools::Itertools;
use once_cell::sync::Lazy;
use std::collections::VecDeque; use std::collections::VecDeque;
use crate::common::{input::*, menu::QUICK_MENU_ACTIVE, try_get_module_accessor}; use crate::common::input::*;
use lazy_static::lazy_static; use crate::menu::QUICK_MENU_ACTIVE;
use parking_lot::Mutex; use crate::try_get_module_accessor;
use skyline::nn::ui2d::ResColor; use skyline::nn::ui2d::ResColor;
use smash::app::{lua_bind::*, utility}; use smash::app::{lua_bind::*, utility};
use training_mod_consts::{FighterId, InputDisplay, MENU}; use training_mod_consts::{FighterId, InputDisplay, MENU};
use training_mod_sync::*;
use super::{frame_counter, input_record::STICK_CLAMP_MULTIPLIER}; use super::{frame_counter, input_record::STICK_CLAMP_MULTIPLIER};
@ -60,13 +60,15 @@ pub const WHITE: ResColor = ResColor {
a: 0, a: 0,
}; };
pub static PER_LOG_FRAME_COUNTER: Lazy<usize> = pub static PER_LOG_FRAME_COUNTER: LazyLock<usize> = LazyLock::new(|| {
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGameNoReset)); 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 OVERALL_FRAME_COUNTER: LazyLock<usize> = LazyLock::new(|| {
frame_counter::register_counter(frame_counter::FrameCounterType::InGameNoReset)
});
pub const NUM_LOGS: usize = 15; 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)] #[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub enum DirectionStrength { pub enum DirectionStrength {
@ -134,50 +136,51 @@ fn bin_stick_values(x: i8, y: i8) -> (DirectionStrength, f32) {
impl InputLog { impl InputLog {
pub fn is_different(&self, other: &InputLog) -> bool { pub fn is_different(&self, other: &InputLog) -> bool {
unsafe { match read(&MENU).input_display {
match MENU.input_display { InputDisplay::SMASH => self.is_smash_different(other),
InputDisplay::SMASH => self.is_smash_different(other), InputDisplay::RAW => self.is_raw_different(other),
InputDisplay::RAW => self.is_raw_different(other), InputDisplay::STATUS => self.is_status_different(other),
InputDisplay::STATUS => self.is_status_different(other), InputDisplay::NONE => false,
InputDisplay::NONE => false, _ => panic!(
_ => panic!("Invalid value in is_different: {}", MENU.input_display), "Invalid value in is_different: {}",
} read(&MENU).input_display
),
} }
} }
pub fn binned_lstick(&self) -> (DirectionStrength, f32) { pub fn binned_lstick(&self) -> (DirectionStrength, f32) {
unsafe { match read(&MENU).input_display {
match MENU.input_display { InputDisplay::SMASH => self.smash_binned_lstick(),
InputDisplay::SMASH => self.smash_binned_lstick(), InputDisplay::RAW => self.raw_binned_lstick(),
InputDisplay::RAW => self.raw_binned_lstick(), InputDisplay::STATUS => (DirectionStrength::None, 0.0),
InputDisplay::STATUS => (DirectionStrength::None, 0.0), InputDisplay::NONE => panic!("Invalid input display to log"),
InputDisplay::NONE => panic!("Invalid input display to log"), _ => panic!(
_ => panic!("Invalid value in binned_lstick: {}", MENU.input_display), "Invalid value in binned_lstick: {}",
} read(&MENU).input_display
),
} }
} }
pub fn binned_rstick(&self) -> (DirectionStrength, f32) { pub fn binned_rstick(&self) -> (DirectionStrength, f32) {
unsafe { match read(&MENU).input_display {
match MENU.input_display { InputDisplay::SMASH => self.smash_binned_rstick(),
InputDisplay::SMASH => self.smash_binned_rstick(), InputDisplay::RAW => self.raw_binned_rstick(),
InputDisplay::RAW => self.raw_binned_rstick(), InputDisplay::STATUS => (DirectionStrength::None, 0.0),
InputDisplay::STATUS => (DirectionStrength::None, 0.0), InputDisplay::NONE => panic!("Invalid input display to log"),
InputDisplay::NONE => panic!("Invalid input display to log"), _ => panic!(
_ => panic!("Invalid value in binned_rstick: {}", MENU.input_display), "Invalid value in binned_rstick: {}",
} read(&MENU).input_display
),
} }
} }
pub fn button_icons(&self) -> VecDeque<(&str, ResColor)> { pub fn button_icons(&self) -> VecDeque<(&str, ResColor)> {
unsafe { match read(&MENU).input_display {
match MENU.input_display { InputDisplay::SMASH => self.smash_button_icons(),
InputDisplay::SMASH => self.smash_button_icons(), InputDisplay::RAW => self.raw_button_icons(),
InputDisplay::RAW => self.raw_button_icons(), InputDisplay::STATUS => VecDeque::new(),
InputDisplay::STATUS => VecDeque::new(), InputDisplay::NONE => panic!("Invalid input display to log"),
InputDisplay::NONE => panic!("Invalid input display to log"), _ => unreachable!(),
_ => unreachable!(),
}
} }
} }
@ -258,14 +261,12 @@ impl InputLog {
self.smash_inputs.buttons != other.smash_inputs.buttons self.smash_inputs.buttons != other.smash_inputs.buttons
|| self.smash_binned_lstick() != other.smash_binned_lstick() || self.smash_binned_lstick() != other.smash_binned_lstick()
|| self.smash_binned_rstick() != other.smash_binned_rstick() || 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 { fn is_status_different(&self, other: &InputLog) -> bool {
unsafe { let input_display_status = read(&MENU).input_display_status.as_bool();
let input_display_status = MENU.input_display_status.as_bool(); input_display_status && (self.status != other.status)
input_display_status && (self.status != other.status)
}
} }
fn smash_binned_lstick(&self) -> (DirectionStrength, f32) { 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_inputs.current_buttons != other.raw_inputs.current_buttons
|| self.raw_binned_lstick() != other.raw_binned_lstick() || self.raw_binned_lstick() != other.raw_binned_lstick()
|| self.raw_binned_rstick() != other.raw_binned_rstick() || 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) { 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); insert_in_place(array, value, 0);
} }
lazy_static! { pub static P1_INPUT_LOGS: LazyLock<RwLock<[InputLog; NUM_LOGS]>> =
pub static ref P1_INPUT_LOGS: Mutex<[InputLog; NUM_LOGS]> = LazyLock::new(|| RwLock::new([InputLog::default(); NUM_LOGS]));
Mutex::new([InputLog::default(); NUM_LOGS]);
}
pub fn handle_final_input_mapping( pub fn handle_final_input_mapping(
player_idx: i32, player_idx: i32,
@ -316,11 +315,11 @@ pub fn handle_final_input_mapping(
out: *mut MappedInputs, out: *mut MappedInputs,
) { ) {
unsafe { unsafe {
if MENU.input_display == InputDisplay::NONE { if read(&MENU).input_display == InputDisplay::NONE {
return; return;
} }
if QUICK_MENU_ACTIVE { if read(&QUICK_MENU_ACTIVE) {
return; return;
} }
@ -347,7 +346,8 @@ pub fn handle_final_input_mapping(
fighter_kind: utility::get_kind(&mut *module_accessor), 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 latest_input_log = input_logs.first_mut().unwrap();
let prev_overall_frames = latest_input_log.overall_frame; let prev_overall_frames = latest_input_log.overall_frame;
let prev_ttl = latest_input_log.ttl; let prev_ttl = latest_input_log.ttl;
@ -358,8 +358,9 @@ pub fn handle_final_input_mapping(
// We should count this frame already // We should count this frame already
frame_counter::tick_idx(*PER_LOG_FRAME_COUNTER); frame_counter::tick_idx(*PER_LOG_FRAME_COUNTER);
insert_in_front(input_logs, potential_input_log); insert_in_front(input_logs, potential_input_log);
let draw_log_base_idx = &mut *DRAW_LOG_BASE_IDX.data_ptr(); let mut draw_log_base_idx_lock = lock_write(&DRAW_LOG_BASE_IDX);
*draw_log_base_idx = (*draw_log_base_idx + 1) % NUM_LOGS; *draw_log_base_idx_lock = (*draw_log_base_idx_lock + 1) % NUM_LOGS;
drop(draw_log_base_idx_lock);
} else if is_new_frame { } else if is_new_frame {
*latest_input_log = potential_input_log; *latest_input_log = potential_input_log;
latest_input_log.frames = std::cmp::min(current_frame, 99); latest_input_log.frames = std::cmp::min(current_frame, 99);

@ -1,7 +1,5 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use lazy_static::lazy_static;
use parking_lot::Mutex;
use skyline::nn::ui2d::ResColor; use skyline::nn::ui2d::ResColor;
use smash::app::{lua_bind::*, utility, BattleObjectModuleAccessor}; use smash::app::{lua_bind::*, utility, BattleObjectModuleAccessor};
use smash::lib::lua_const::*; 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, get_module_accessor, is_in_hitstun, is_in_shieldstun, try_get_module_accessor, MENU,
}; };
use crate::training::mash; 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}; use crate::{error, warn};
#[derive(PartialEq, Debug)] use training_mod_sync::*;
#[derive(PartialEq, Debug, Clone, Copy)]
pub enum InputRecordState { pub enum InputRecordState {
None, None,
Pause, Pause,
@ -28,7 +28,7 @@ pub enum InputRecordState {
Playback, Playback,
} }
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug, Clone, Copy)]
pub enum PossessionState { pub enum PossessionState {
Player, Player,
Cpu, 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 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 FINAL_RECORD_MAX: usize = 600; // Maximum length for input recording sequences (capacity)
const TOTAL_SLOT_COUNT: usize = 5; // Total number of input recording slots const TOTAL_SLOT_COUNT: usize = 5; // Total number of input recording slots
pub static mut INPUT_RECORD: InputRecordState = InputRecordState::None; pub static INPUT_RECORD: RwLock<InputRecordState> = RwLock::new(InputRecordState::None);
pub static mut INPUT_RECORD_FRAME: usize = 0; pub static INPUT_RECORD_FRAME: RwLock<usize> = RwLock::new(0);
pub static mut POSSESSION: PossessionState = PossessionState::Player; pub static POSSESSION: RwLock<PossessionState> = RwLock::new(PossessionState::Player);
pub static mut LOCKOUT_FRAME: usize = 0; pub static LOCKOUT_FRAME: RwLock<usize> = RwLock::new(0);
pub static mut BUFFER_FRAME: usize = 0; pub static BUFFER_FRAME: RwLock<usize> = RwLock::new(0);
pub static mut RECORDED_LR: f32 = 1.0; // The direction the CPU was facing before the current recording was recorded pub static RECORDED_LR: RwLock<f32> = RwLock::new(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 CURRENT_LR: RwLock<f32> = RwLock::new(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 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?) // 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 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 mut CURRENT_PLAYBACK_SLOT: usize = 0; // Which slot is being used for playback right now? pub static CURRENT_PLAYBACK_SLOT: RwLock<usize> = RwLock::new(0); // Which slot is being used for playback right now?
pub static mut CURRENT_FRAME_LENGTH: usize = 60; pub static CURRENT_FRAME_LENGTH: RwLock<usize> = RwLock::new(60);
pub static P1_FINAL_MAPPING: RwLock<[[MappedInputs; FINAL_RECORD_MAX]; TOTAL_SLOT_COUNT]> =
lazy_static! { RwLock::new([[{ MappedInputs::empty() }; FINAL_RECORD_MAX]; TOTAL_SLOT_COUNT]);
static ref P1_FINAL_MAPPING: Mutex<[[MappedInputs; FINAL_RECORD_MAX]; TOTAL_SLOT_COUNT]> = pub static P1_FRAME_LENGTH_MAPPING: RwLock<[usize; TOTAL_SLOT_COUNT]> =
Mutex::new([[{ MappedInputs::empty() }; FINAL_RECORD_MAX]; TOTAL_SLOT_COUNT]); RwLock::new([60; TOTAL_SLOT_COUNT]);
static ref P1_FRAME_LENGTH_MAPPING: Mutex<[usize; TOTAL_SLOT_COUNT]> = // pub static P1_STARTING_STATUSES: RwLock<[StartingStatus; TOTAL_SLOT_COUNT]> = RwLock::new([StartingStatus::Other; TOTAL_SLOT_COUNT]); // TODO! Not used currently
Mutex::new([60usize; TOTAL_SLOT_COUNT]);
static ref P1_STARTING_STATUSES: Mutex<[StartingStatus; TOTAL_SLOT_COUNT]> =
Mutex::new([{ StartingStatus::Other }; TOTAL_SLOT_COUNT]);
}
unsafe fn can_transition(module_accessor: *mut BattleObjectModuleAccessor) -> bool { 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) 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 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 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; 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 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) && !StopModule::is_stop(cpu_module_accessor)
{ {
should_playback = true; 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 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; 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_kind = utility::get_kind(module_accessor);
let fighter_is_nana = fighter_kind == *FIGHTER_KIND_NANA; 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 entry_id_int == 0 && !fighter_is_nana {
if button_config::combo_passes(button_config::ButtonCombo::InputPlayback) { if button_config::combo_passes(button_config::ButtonCombo::InputPlayback) {
playback(MENU.playback_button_slots.get_random().into_idx()); playback(read(&MENU).playback_button_slots.get_random().into_idx());
} else if MENU.record_trigger.contains(&RecordTrigger::COMMAND) } else if read(&MENU).record_trigger.contains(&RecordTrigger::COMMAND)
&& button_config::combo_passes(button_config::ButtonCombo::InputRecord) && button_config::combo_passes(button_config::ButtonCombo::InputRecord)
{ {
lockout_record(); lockout_record();
} }
if INPUT_RECORD == None { let input_record = read(&INPUT_RECORD);
clear_notifications("Input Recording"); if input_record == None {
clear_notification("Input Recording");
} }
// Handle recording end // Handle recording end
if (INPUT_RECORD == Record || INPUT_RECORD == Playback) let mut input_record_frame = lock_write(&INPUT_RECORD_FRAME);
&& INPUT_RECORD_FRAME >= CURRENT_FRAME_LENGTH - 1 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() { if mash::is_playback_queued() {
mash::reset(); mash::reset();
} }
// If we need to crop the recording for neutral input // If we need to crop the recording for neutral input
// INPUT_RECORD_FRAME must be > 0 to prevent bounding errors // 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 // 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; assign(&CURRENT_FRAME_LENGTH, *input_record_frame);
P1_FRAME_LENGTH_MAPPING.lock()[CURRENT_RECORD_SLOT] = CURRENT_FRAME_LENGTH; 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 { if read(&MENU).playback_loop == OnOff::ON && input_record == Playback {
playback(Some(CURRENT_PLAYBACK_SLOT)); let playback_slot = read(&CURRENT_PLAYBACK_SLOT);
playback(Some(playback_slot));
} else { } else {
INPUT_RECORD = None; assign(&INPUT_RECORD, None);
} }
} }
drop(input_record_frame);
} }
// Handle Possession Coloring // Handle Possession Coloring
if entry_id_int == 1 && POSSESSION == Lockout { let possession = read(&POSSESSION);
clear_notifications("Input Recording"); if entry_id_int == 1 && possession == Lockout {
clear_notification("Input Recording");
color_notification( color_notification(
"Input Recording".to_string(), "Input Recording".to_string(),
"Lockout".to_owned(), "Lockout".to_owned(),
@ -258,8 +267,8 @@ unsafe fn handle_recording_for_fighter(module_accessor: &mut BattleObjectModuleA
1.0, 1.0,
*MODEL_COLOR_TYPE_COLOR_BLEND, *MODEL_COLOR_TYPE_COLOR_BLEND,
); );
} else if entry_id_int == 1 && POSSESSION == Standby { } else if entry_id_int == 1 && possession == Standby {
clear_notifications("Input Recording"); clear_notification("Input Recording");
color_notification( color_notification(
"Input Recording".to_string(), "Input Recording".to_string(),
"Standby".to_owned(), "Standby".to_owned(),
@ -278,8 +287,8 @@ unsafe fn handle_recording_for_fighter(module_accessor: &mut BattleObjectModuleA
1.0, 1.0,
*MODEL_COLOR_TYPE_COLOR_BLEND, *MODEL_COLOR_TYPE_COLOR_BLEND,
); );
} else if entry_id_int == 1 && POSSESSION == Cpu { } else if entry_id_int == 1 && possession == Cpu {
clear_notifications("Input Recording"); clear_notification("Input Recording");
color_notification( color_notification(
"Input Recording".to_string(), "Input Recording".to_string(),
"Recording".to_owned(), "Recording".to_owned(),
@ -298,15 +307,17 @@ unsafe fn handle_recording_for_fighter(module_accessor: &mut BattleObjectModuleA
0.0, 0.0,
*MODEL_COLOR_TYPE_COLOR_BLEND, *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 // 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 // can be either, seems like a thread issue
clear_notifications("Input Recording"); clear_notification("Input Recording");
color_notification( color_notification(
"Input Recording".to_string(), "Input Recording".to_string(),
"Playback".to_owned(), "Playback".to_owned(),
CURRENT_FRAME_LENGTH as u32, read(&CURRENT_FRAME_LENGTH) as u32,
ResColor { ResColor {
r: 0, r: 0,
g: 0, g: 0,
@ -319,27 +330,29 @@ unsafe fn handle_recording_for_fighter(module_accessor: &mut BattleObjectModuleA
} }
pub unsafe fn lockout_record() { 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); let cpu_module_accessor = get_module_accessor(FighterId::CPU);
RECORDED_LR = PostureModule::lr(cpu_module_accessor); let recording_duration = read(&MENU).recording_duration.into_frames();
CURRENT_LR = RECORDED_LR; 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 // Returns whether we did playback
pub unsafe fn playback(slot: Option<usize>) -> bool { pub unsafe fn playback(slot: Option<usize>) -> bool {
if INPUT_RECORD == Pause { if read(&INPUT_RECORD) == Pause {
warn!("Tried to playback during lockout!"); warn!("Tried to playback during lockout!");
return false; return false;
} }
@ -348,15 +361,15 @@ pub unsafe fn playback(slot: Option<usize>) -> bool {
return false; return false;
} }
let slot = slot.unwrap(); 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); 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 true
} }
@ -364,26 +377,28 @@ pub unsafe fn playback(slot: Option<usize>) -> bool {
pub unsafe fn playback_ledge(slot: Option<usize>) { pub unsafe fn playback_ledge(slot: Option<usize>) {
let did_playback = playback(slot); let did_playback = playback(slot);
if did_playback { 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 let mut buffer_frame = lock_write(&BUFFER_FRAME);
// drop down from ledge can't be buffered on the same frame as jump/attack/roll/ngu so we have to do this *buffer_frame = 5; // So we can make sure the option is buffered and won't get ledge trumped if delay is 0
// Need to buffer 1 less frame for non-lassos // 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 cpu_module_accessor = get_module_accessor(FighterId::CPU);
let status_kind = StatusModule::status_kind(cpu_module_accessor); let status_kind = StatusModule::status_kind(cpu_module_accessor);
if status_kind == *FIGHTER_STATUS_KIND_CLIFF_CATCH { if status_kind == *FIGHTER_STATUS_KIND_CLIFF_CATCH {
BUFFER_FRAME -= 1; *buffer_frame -= 1;
} }
} }
} }
pub unsafe fn stop_playback() { pub unsafe fn stop_playback() {
INPUT_RECORD = None; assign(&INPUT_RECORD, None);
INPUT_RECORD_FRAME = 0; assign(&INPUT_RECORD_FRAME, 0);
POSSESSION = Player; assign(&POSSESSION, Player);
} }
pub unsafe fn is_input_neutral(input_frame: usize) -> bool { 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) // 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 = let clamped_lstick_x =
((frame_input.lstick_x as f32) * STICK_CLAMP_MULTIPLIER).clamp(-1.0, 1.0); ((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) { 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_idx == 0 {
// if player 1 // 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: // 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. // last input made us start an action, so start recording and end standby.
INPUT_RECORD_FRAME += 1; *input_record_frame += 1;
POSSESSION = Cpu; *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'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 // 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); let cpu_module_accessor = get_module_accessor(FighterId::CPU);
P1_STARTING_STATUSES.lock()[CURRENT_PLAYBACK_SLOT] = assign(
into_starting_status(StatusModule::status_kind(cpu_module_accessor)); &STARTING_STATUS,
STARTING_STATUS = StatusModule::status_kind(cpu_module_accessor); StatusModule::status_kind(cpu_module_accessor),
);
// TODO: Handle this based on slot later instead // 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);
} }
let mut p1_final_mapping = lock_write(&P1_FINAL_MAPPING);
P1_FINAL_MAPPING.lock()[CURRENT_RECORD_SLOT][INPUT_RECORD_FRAME] = *out; 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 *out = MappedInputs::empty(); // don't control player while recording
} }
// Don't allow for player input during Lockout // Don't allow for player input during Lockout
if POSSESSION == Lockout { if *possession == Lockout {
*out = MappedInputs::empty(); *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 // TODO: Setup STARTING_STATUS based on current playback slot here
// This check prevents out of shield if mash exiting is on // This check prevents out of shield if mash exiting is on
if INPUT_RECORD == None { if read(&INPUT_RECORD) == None {
should_mash_playback(); 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(); let cpu_module_accessor = cpu_module_accessor.unwrap();
if INPUT_RECORD == Pause { if read(&INPUT_RECORD) == Pause {
match LOCKOUT_FRAME.cmp(&0) { let lockout_frame = read(&LOCKOUT_FRAME);
Ordering::Greater => LOCKOUT_FRAME -= 1, match lockout_frame.cmp(&0) {
Ordering::Greater => assign(&LOCKOUT_FRAME, lockout_frame - 1),
Ordering::Equal => { Ordering::Equal => {
INPUT_RECORD = Record; assign(&INPUT_RECORD, Record);
POSSESSION = Standby; assign(&POSSESSION, Standby);
} }
Ordering::Less => error!("LOCKOUT_FRAME OUT OF BOUNDS"), 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 // 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 // Don't flip Shulk's dial inputs
let fighter_kind = utility::get_kind(&mut *cpu_module_accessor); let fighter_kind = utility::get_kind(&mut *cpu_module_accessor);
if fighter_kind == *FIGHTER_KIND_SHULK { 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 { let mut input_record_frame = lock_write(&INPUT_RECORD_FRAME);
CURRENT_RECORD_SLOT let slot = if input_record == Record {
read(&CURRENT_RECORD_SLOT)
} else { } else {
CURRENT_PLAYBACK_SLOT read(&CURRENT_PLAYBACK_SLOT)
}][INPUT_RECORD_FRAME]; };
let p1_final_mapping = lock_write(&P1_FINAL_MAPPING);
if BUFFER_FRAME <= 3 && BUFFER_FRAME > 0 { 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 // 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(); 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 // 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 // When buffering an option, we keep inputting the first frame of input during the buffer window
if BUFFER_FRAME > 0 { if *buffer_frame > 0 {
BUFFER_FRAME -= 1; *buffer_frame -= 1;
} else if INPUT_RECORD_FRAME < CURRENT_FRAME_LENGTH - 1 && POSSESSION != Standby { } else if *input_record_frame < read(&CURRENT_FRAME_LENGTH) - 1
INPUT_RECORD_FRAME += 1; && read(&POSSESSION) != Standby
{
*input_record_frame += 1;
} }
} }
} }
pub unsafe fn is_playback() -> bool { pub fn is_playback() -> bool {
INPUT_RECORD == Record || INPUT_RECORD == Playback let input_record = read(&INPUT_RECORD);
input_record == Record || input_record == Playback
} }
pub unsafe fn is_recording() -> bool { pub fn is_recording() -> bool {
INPUT_RECORD == Record read(&INPUT_RECORD) == Record
} }
pub unsafe fn is_standby() -> bool { pub unsafe fn is_standby() -> bool {
POSSESSION == Standby || POSSESSION == Lockout let possession = read(&POSSESSION);
possession == Standby || possession == Lockout
} }
extern "C" { extern "C" {

@ -5,92 +5,84 @@ use crate::common::consts::*;
use crate::common::*; use crate::common::*;
use crate::training::{frame_counter, input_record, mash}; use crate::training::{frame_counter, input_record, mash};
use once_cell::sync::Lazy; use training_mod_sync::*;
const NOT_SET: u32 = 9001; const NOT_SET: u32 = 9001;
static mut LEDGE_DELAY: u32 = NOT_SET; static LEDGE_DELAY: RwLock<u32> = RwLock::new(NOT_SET);
static mut LEDGE_CASE: LedgeOption = LedgeOption::empty(); static LEDGE_CASE: RwLock<LedgeOption> = RwLock::new(LedgeOption::empty());
static LEDGE_DELAY_COUNTER: Lazy<usize> = static LEDGE_DELAY_COUNTER: LazyLock<usize> =
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame)); LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
pub fn reset_ledge_delay() { pub fn reset_ledge_delay() {
unsafe { let mut ledge_delay_lock = lock_write(&LEDGE_DELAY);
if LEDGE_DELAY != NOT_SET { if *ledge_delay_lock != NOT_SET {
LEDGE_DELAY = NOT_SET; *ledge_delay_lock = NOT_SET;
frame_counter::full_reset(*LEDGE_DELAY_COUNTER); frame_counter::full_reset(*LEDGE_DELAY_COUNTER);
}
} }
} }
pub fn reset_ledge_case() { pub fn reset_ledge_case() {
unsafe { let mut ledge_case_lock = lock_write(&LEDGE_CASE);
if LEDGE_CASE != LedgeOption::empty() { if *ledge_case_lock != LedgeOption::empty() {
// Don't roll another ledge option if one is already selected // Don't roll another ledge option if one is already selected
LEDGE_CASE = LedgeOption::empty(); *ledge_case_lock = LedgeOption::empty();
}
} }
} }
fn roll_ledge_delay() { fn roll_ledge_delay() {
unsafe { let mut ledge_delay_lock = lock_write(&LEDGE_DELAY);
if LEDGE_DELAY != NOT_SET { if *ledge_delay_lock != NOT_SET {
// Don't roll another ledge delay if one is already selected // Don't roll another ledge delay if one is already selected
return; return;
}
LEDGE_DELAY = MENU.ledge_delay.get_random().into_longdelay();
} }
*ledge_delay_lock = read(&MENU).ledge_delay.get_random().into_longdelay();
} }
fn roll_ledge_case() { fn roll_ledge_case() {
unsafe { // Don't re-roll if there is already a ledge option selected
// Don't re-roll if there is already a ledge option selected // This prevents choosing a different ledge option during LedgeOption::WAIT
// This prevents choosing a different ledge option during LedgeOption::WAIT let mut ledge_case_lock = lock_write(&LEDGE_CASE);
if LEDGE_CASE != LedgeOption::empty() { if *ledge_case_lock != LedgeOption::empty() {
return; return;
}
LEDGE_CASE = MENU.ledge_state.get_random();
} }
*ledge_case_lock = read(&MENU).ledge_state.get_random();
} }
fn get_ledge_option() -> Option<Action> { fn get_ledge_option() -> Option<Action> {
unsafe { let mut override_action: Option<Action> = None;
let mut override_action: Option<Action> = None; let regular_action = if read(&MENU).mash_triggers.contains(&MashTrigger::LEDGE) {
let regular_action = if MENU.mash_triggers.contains(&MashTrigger::LEDGE) { Some(read(&MENU).mash_state.get_random())
Some(MENU.mash_state.get_random()) } else {
} else { None
None };
};
match LEDGE_CASE { match read(&LEDGE_CASE) {
LedgeOption::NEUTRAL => { LedgeOption::NEUTRAL => {
if MENU.ledge_neutral_override != Action::empty() { if read(&MENU).ledge_neutral_override != Action::empty() {
override_action = Some(MENU.ledge_neutral_override.get_random()); override_action = Some(read(&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;
} }
} }
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) { 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 = let flag_cliff =
WorkModule::is_flag(module_accessor, *FIGHTER_INSTANCE_WORK_ID_FLAG_CATCH_CLIFF); WorkModule::is_flag(module_accessor, *FIGHTER_INSTANCE_WORK_ID_FLAG_CATCH_CLIFF);
let current_frame = MotionModule::frame(module_accessor) as i32; 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 // Allow this because sometimes we want to make sure our NNSDK doesn't have
// an erroneous definition // an erroneous definition
#[allow(clippy::unnecessary_cast)] #[allow(clippy::unnecessary_cast)]
let status_kind = StatusModule::status_kind(module_accessor) as i32; 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 should_buffer;
let prev_status_kind = StatusModule::prev_status_kind(module_accessor, 0); 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 && prev_status_kind == *FIGHTER_STATUS_KIND_CLIFF_CATCH
{ {
// For regular ledge grabs, we were just in catch and want to buffer on this frame // 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 { } 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 // 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 { } else {
should_buffer = false; should_buffer = false;
} }
@ -135,10 +129,10 @@ pub unsafe fn force_option(module_accessor: &mut app::BattleObjectModuleAccessor
// Not able to take any action yet // 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? // 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 if should_buffer_playback
&& LEDGE_CASE.is_playback() && ledge_case.is_playback()
&& MENU.ledge_delay != LongDelay::empty() && read(&MENU).ledge_delay != LongDelay::empty()
{ {
input_record::playback_ledge(LEDGE_CASE.playback_slot()); input_record::playback_ledge(ledge_case.playback_slot());
return; return;
} }
// This check isn't reliable for buffered options in time, so don't return if we need to buffer an option this frame // 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. // Do nothing, but don't reset the ledge case.
return; 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 // Not yet time to perform the ledge action
return; return;
} }
let status = LEDGE_CASE.into_status().unwrap_or(0); let status = ledge_case.into_status().unwrap_or(0);
if LEDGE_CASE.is_playback() { if ledge_case.is_playback() {
input_record::playback(LEDGE_CASE.playback_slot()); input_record::playback(ledge_case.playback_slot());
} else { } else {
StatusModule::change_status_request_from_script(module_accessor, status, true); 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 // Only handle ledge scenarios from menu
if StatusModule::status_kind(_module_accessor) != *FIGHTER_STATUS_KIND_CLIFF_WAIT if StatusModule::status_kind(_module_accessor) != *FIGHTER_STATUS_KIND_CLIFF_WAIT
|| MENU.ledge_state == LedgeOption::empty() || read(&MENU).ledge_state == LedgeOption::empty()
{ {
return None; return None;
} }
// Disallow the default cliff-climb if we are waiting or we didn't get up during a recording // 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 if term == *FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_CLIFF_CLIMB
&& ((LEDGE_CASE == LedgeOption::WAIT && ((ledge_case == LedgeOption::WAIT
|| frame_counter::get_frame_count(*LEDGE_DELAY_COUNTER) < LEDGE_DELAY) || frame_counter::get_frame_count(*LEDGE_DELAY_COUNTER) < ledge_delay)
|| (LEDGE_CASE.is_playback() && !input_record::is_playback())) || (ledge_case.is_playback() && !input_record::is_playback()))
{ {
return Some(false); return Some(false);
} }
@ -207,7 +203,7 @@ pub fn get_command_flag_cat(module_accessor: &mut app::BattleObjectModuleAccesso
return; return;
} }
if MENU.ledge_state == LedgeOption::empty() { if read(&MENU).ledge_state == LedgeOption::empty() {
return; return;
} }

@ -11,24 +11,20 @@ use crate::training::input_record;
use crate::training::shield; use crate::training::shield;
use crate::training::{attack_angle, save_states}; 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_CLOSE_THRESHOLD: f32 = 16.0;
const DISTANCE_MID_THRESHOLD: f32 = 37.0; const DISTANCE_MID_THRESHOLD: f32 = 37.0;
const DISTANCE_FAR_THRESHOLD: f32 = 64.0; const DISTANCE_FAR_THRESHOLD: f32 = 64.0;
static mut CURRENT_AERIAL: Action = Action::NAIR; static CURRENT_AERIAL: RwLock<Action> = RwLock::new(Action::NAIR);
static mut QUEUE: Vec<Action> = vec![]; 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 AERIAL_DELAY_COUNTER: LazyLock<usize> =
LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
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;
unsafe fn is_beginning_dash_attack(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { unsafe fn is_beginning_dash_attack(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
let current_status = StatusModule::status_kind(module_accessor); 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) { 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 { pub fn is_playback_queued() -> bool {
@ -66,21 +63,22 @@ pub unsafe fn is_dashing_for_dash_attack(
} }
pub fn buffer_action(action: Action) { pub fn buffer_action(action: Action) {
unsafe { let queue = lock_read(&QUEUE);
if !QUEUE.is_empty() { if !queue.is_empty() {
return; // Something is already buffered
} return;
} }
drop(queue);
if action == Action::empty() { if action == Action::empty() {
return; return;
} }
// We want to allow for triggering a mash to end playback for neutral playbacks, but not for SDI/disadv playbacks // 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 { unsafe {
// exit playback if we want to perform mash actions out of it if read(&MENU).playback_mash == OnOff::ON
// TODO: Figure out some way to deal with trying to playback into another playback
if MENU.playback_mash == OnOff::ON
&& input_record::is_playback() && input_record::is_playback()
&& !input_record::is_recording() && !input_record::is_recording()
&& !input_record::is_standby() && !input_record::is_standby()
@ -96,21 +94,16 @@ pub fn buffer_action(action: Action) {
} }
attack_angle::roll_direction(); attack_angle::roll_direction();
roll_aerial_delay(action); roll_aerial_delay(action);
unsafe { let mut queue = lock_write(&QUEUE);
QUEUE.insert(0, action); queue.insert(0, action);
buffer_follow_up(); drop(queue);
} buffer_follow_up();
} }
pub fn buffer_follow_up() { pub fn buffer_follow_up() {
let action; let action = read(&MENU).follow_up.get_random();
unsafe {
action = MENU.follow_up.get_random();
}
if action == Action::empty() { if action == Action::empty() {
return; return;
@ -118,60 +111,44 @@ pub fn buffer_follow_up() {
roll_aerial_delay(action); roll_aerial_delay(action);
unsafe { let mut queue = lock_write(&QUEUE);
QUEUE.insert(0, action); queue.insert(0, action);
} drop(queue);
} }
pub fn get_current_buffer() -> Action { pub fn get_current_buffer() -> Action {
unsafe { let queue = lock_read(&QUEUE);
if QUEUE.is_empty() { *(queue.last().unwrap_or(&Action::empty()))
return Action::empty();
}
*QUEUE.last().unwrap()
}
} }
pub fn reset() { pub fn reset() {
unsafe { let mut queue = lock_write(&QUEUE);
QUEUE.pop(); queue.pop();
} drop(queue);
shield::suspend_shield(get_current_buffer()); shield::suspend_shield(get_current_buffer());
frame_counter::full_reset(*AERIAL_DELAY_COUNTER);
unsafe { assign(&AERIAL_DELAY, 0);
frame_counter::full_reset(*AERIAL_DELAY_COUNTER);
AERIAL_DELAY = 0;
}
} }
pub fn full_reset() { pub fn full_reset() {
unsafe { clear_queue();
while !QUEUE.is_empty() { reset();
reset();
}
}
} }
pub fn clear_queue() { pub fn clear_queue() {
unsafe { QUEUE.clear() } assign(&QUEUE, Vec::new());
} }
pub fn set_aerial(attack: Action) { pub fn set_aerial(attack: Action) {
unsafe { assign(&CURRENT_AERIAL, attack);
CURRENT_AERIAL = attack;
}
} }
pub unsafe fn get_attack_air_kind( pub fn get_attack_air_kind(module_accessor: &mut app::BattleObjectModuleAccessor) -> Option<i32> {
module_accessor: &mut app::BattleObjectModuleAccessor,
) -> Option<i32> {
if !is_operation_cpu(module_accessor) { if !is_operation_cpu(module_accessor) {
return None; return None;
} }
read(&CURRENT_AERIAL).into_attack_air_kind()
CURRENT_AERIAL.into_attack_air_kind()
} }
pub unsafe fn get_command_flag_cat( pub unsafe fn get_command_flag_cat(
@ -216,91 +193,92 @@ unsafe fn get_buffered_action(
return None; return None;
} }
let fighter_distance = get_fighter_distance(); let fighter_distance = get_fighter_distance();
let menu = read(&MENU);
if is_in_tech(module_accessor) { 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() { if action != Action::empty() {
Some(action) Some(action)
} else if MENU.mash_triggers.contains(&MashTrigger::TECH) { } else if menu.mash_triggers.contains(&MashTrigger::TECH) {
Some(MENU.mash_state.get_random()) Some(menu.mash_state.get_random())
} else { } else {
None None
} }
} else if is_in_clatter(module_accessor) { } 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() { if action != Action::empty() {
Some(action) Some(action)
} else if MENU.mash_triggers.contains(&MashTrigger::CLATTER) { } else if menu.mash_triggers.contains(&MashTrigger::CLATTER) {
Some(MENU.mash_state.get_random()) Some(menu.mash_state.get_random())
} else { } else {
None None
} }
} else if is_in_tumble(module_accessor) { } else if is_in_tumble(module_accessor) {
// Note that the tumble check needs to come before hitstun, // Note that the tumble check needs to come before hitstun,
// otherwise the hitstun check will always return first // 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() { if action != Action::empty() {
Some(action) Some(action)
} else if MENU.mash_triggers.contains(&MashTrigger::TUMBLE) { } else if menu.mash_triggers.contains(&MashTrigger::TUMBLE) {
Some(MENU.mash_state.get_random()) Some(menu.mash_state.get_random())
} else { } else {
None None
} }
} else if is_in_hitstun(module_accessor) { } 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() { if action != Action::empty() {
Some(action) Some(action)
} else if MENU.mash_triggers.contains(&MashTrigger::HIT) { } else if menu.mash_triggers.contains(&MashTrigger::HIT) {
Some(MENU.mash_state.get_random()) Some(menu.mash_state.get_random())
} else { } else {
None None
} }
} else if is_in_parry(module_accessor) { } 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() { if action != Action::empty() {
Some(action) Some(action)
} else if MENU.mash_triggers.contains(&MashTrigger::PARRY) { } else if menu.mash_triggers.contains(&MashTrigger::PARRY) {
Some(MENU.mash_state.get_random()) Some(menu.mash_state.get_random())
} else { } else {
None None
} }
} else if is_in_footstool(module_accessor) { } 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() { if action != Action::empty() {
Some(action) Some(action)
} else if MENU.mash_triggers.contains(&MashTrigger::FOOTSTOOL) { } else if menu.mash_triggers.contains(&MashTrigger::FOOTSTOOL) {
Some(MENU.mash_state.get_random()) Some(menu.mash_state.get_random())
} else { } else {
None None
} }
} else if is_in_ledgetrump(module_accessor) { } 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() { if action != Action::empty() {
Some(action) Some(action)
} else if MENU.mash_triggers.contains(&MashTrigger::TRUMP) { } else if menu.mash_triggers.contains(&MashTrigger::TRUMP) {
Some(MENU.mash_state.get_random()) Some(menu.mash_state.get_random())
} else { } else {
None None
} }
} else if is_in_landing(module_accessor) { } 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() { if action != Action::empty() {
Some(action) Some(action)
} else if MENU.mash_triggers.contains(&MashTrigger::LANDING) { } else if menu.mash_triggers.contains(&MashTrigger::LANDING) {
Some(MENU.mash_state.get_random()) Some(menu.mash_state.get_random())
} else { } else {
None None
} }
} else if (MENU.mash_triggers.contains(&MashTrigger::GROUNDED) && is_grounded(module_accessor)) } 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::AIRBORNE) && is_airborne(module_accessor))
|| (MENU.mash_triggers.contains(&MashTrigger::DISTANCE_CLOSE) || (menu.mash_triggers.contains(&MashTrigger::DISTANCE_CLOSE)
&& fighter_distance < DISTANCE_CLOSE_THRESHOLD) && fighter_distance < DISTANCE_CLOSE_THRESHOLD)
|| (MENU.mash_triggers.contains(&MashTrigger::DISTANCE_MID) || (menu.mash_triggers.contains(&MashTrigger::DISTANCE_MID)
&& fighter_distance < DISTANCE_MID_THRESHOLD) && fighter_distance < DISTANCE_MID_THRESHOLD)
|| (MENU.mash_triggers.contains(&MashTrigger::DISTANCE_FAR) || (menu.mash_triggers.contains(&MashTrigger::DISTANCE_FAR)
&& fighter_distance < DISTANCE_FAR_THRESHOLD) && 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 { } else {
// SHIELD handled in shield.rs // SHIELD handled in shield.rs
// LEDGE handled in ledge.rs // LEDGE handled in ledge.rs
@ -309,12 +287,13 @@ unsafe fn get_buffered_action(
} }
fn buffer_menu_mash(action: Action) { fn buffer_menu_mash(action: Action) {
unsafe { buffer_action(action);
buffer_action(action); full_hop::roll_full_hop();
full_hop::roll_full_hop(); fast_fall::roll_fast_fall();
fast_fall::roll_fast_fall(); assign(
FALLING_AERIAL = MENU.falling_aerials.get_random().into_bool(); &FALLING_AERIAL,
} read(&MENU).falling_aerials.get_random().into_bool(),
);
} }
pub fn external_buffer_menu_mash(action: Action) { 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 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 // 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 { } else {
// Begin dash attacking now that we've dashed for one frame // Begin dash attacking now that we've dashed for one frame
StatusModule::change_status_request_from_script(module_accessor, status, true); 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; 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; return flag;
} }
@ -576,17 +556,20 @@ fn roll_aerial_delay(action: Action) {
if !shield::is_aerial(action) { if !shield::is_aerial(action) {
return; 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 { fn should_delay_aerial(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
unsafe { let aerial_delay = read(&AERIAL_DELAY);
if AERIAL_DELAY == 0 { if aerial_delay == 0 {
return false; return false;
} }
unsafe {
if StatusModule::status_kind(module_accessor) == *FIGHTER_STATUS_KIND_ATTACK_AIR { if StatusModule::status_kind(module_accessor) == *FIGHTER_STATUS_KIND_ATTACK_AIR {
return false; return false;
} }
@ -597,9 +580,8 @@ fn should_delay_aerial(module_accessor: &mut app::BattleObjectModuleAccessor) ->
) { ) {
return true; 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::lib::lua_const::*;
use smash::params::*; use smash::params::*;
use smash::phx::{Hash40, Vector3f}; use smash::phx::{Hash40, Vector3f};
use training_mod_sync::*;
pub mod buff; pub mod buff;
pub mod charge; 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 // Handle dodge staling here b/c input recording or mash can cause dodging
WorkModule::set_flag( WorkModule::set_flag(
module_accessor, module_accessor,
!(MENU.stale_dodges.as_bool()), !(read(&MENU).stale_dodges.as_bool()),
*FIGHTER_INSTANCE_WORK_ID_FLAG_DISABLE_ESCAPE_PENALTY, *FIGHTER_INSTANCE_WORK_ID_FLAG_DISABLE_ESCAPE_PENALTY,
); );
input_record::handle_recording(); input_record::handle_recording();
@ -141,7 +142,7 @@ fn once_per_frame_per_fighter(module_accessor: &mut BattleObjectModuleAccessor,
tech::hide_tech(); tech::hide_tech();
} }
combo::get_command_flag_cat(module_accessor); combo::once_per_frame(module_accessor);
hitbox_visualizer::get_command_flag_cat(module_accessor); hitbox_visualizer::get_command_flag_cat(module_accessor);
save_states::save_states(module_accessor); save_states::save_states(module_accessor);
tech::get_command_flag_cat(module_accessor); tech::get_command_flag_cat(module_accessor);
@ -312,7 +313,6 @@ pub unsafe fn handle_is_enable_transition_term(
return ori; return ori;
} }
combo::is_enable_transition_term(module_accessor, transition_term, ori);
match ledge::is_enable_transition_term(module_accessor, transition_term) { match ledge::is_enable_transition_term(module_accessor, transition_term) {
Some(r) => r, Some(r) => r,
None => ori, None => ori,
@ -428,7 +428,7 @@ pub unsafe fn handle_add_damage(
#[skyline::hook(offset = *OFFSET_TRAINING_RESET_CHECK, inline)] #[skyline::hook(offset = *OFFSET_TRAINING_RESET_CHECK, inline)]
unsafe fn lra_handle(ctx: &mut InlineCtx) { unsafe fn lra_handle(ctx: &mut InlineCtx) {
let x8 = ctx.registers[8].x.as_mut(); let x8 = ctx.registers[8].x.as_mut();
if !(MENU.lra_reset.as_bool()) { if !(read(&MENU).lra_reset.as_bool()) {
*x8 = 0; *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 Little Mac is in the game and we're buffing him, set the meter to 100
if (player_fighter_kind == *FIGHTER_KIND_LITTLEMAC if (player_fighter_kind == *FIGHTER_KIND_LITTLEMAC
|| cpu_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; param_2 = 100;
} }
@ -837,26 +837,33 @@ pub fn training_mods() {
info!("Applying training mods."); info!("Applying training mods.");
unsafe { unsafe {
let mut fighter_manager_addr_lock = lock_write(&FIGHTER_MANAGER_ADDR);
LookupSymbol( LookupSymbol(
addr_of_mut!(FIGHTER_MANAGER_ADDR), addr_of_mut!(*fighter_manager_addr_lock),
"_ZN3lib9SingletonIN3app14FighterManagerEE9instance_E\u{0}" "_ZN3lib9SingletonIN3app14FighterManagerEE9instance_E\u{0}"
.as_bytes() .as_bytes()
.as_ptr(), .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( LookupSymbol(
addr_of_mut!(STAGE_MANAGER_ADDR), addr_of_mut!(*stage_manager_addr_lock),
"_ZN3lib9SingletonIN3app12StageManagerEE9instance_E\u{0}" "_ZN3lib9SingletonIN3app12StageManagerEE9instance_E\u{0}"
.as_bytes() .as_bytes()
.as_ptr(), .as_ptr(),
); );
drop(stage_manager_addr_lock);
let mut item_manager_addr_lock = lock_write(&ITEM_MANAGER_ADDR);
LookupSymbol( LookupSymbol(
addr_of_mut!(ITEM_MANAGER_ADDR), addr_of_mut!(*item_manager_addr_lock),
"_ZN3lib9SingletonIN3app11ItemManagerEE9instance_E\0" "_ZN3lib9SingletonIN3app11ItemManagerEE9instance_E\0"
.as_bytes() .as_bytes()
.as_ptr(), .as_ptr(),
); );
drop(item_manager_addr_lock);
add_hook(params_main).unwrap(); add_hook(params_main).unwrap();
} }

@ -35,6 +35,7 @@ use crate::training::items::apply_item;
use crate::training::reset; use crate::training::reset;
use crate::training::ui::notifications; use crate::training::ui::notifications;
use crate::{is_ptrainer, ITEM_MANAGER_ADDR}; use crate::{is_ptrainer, ITEM_MANAGER_ADDR};
use training_mod_sync::*;
// Don't remove Mii hats, Pikmin, Luma, or crafting table // Don't remove Mii hats, Pikmin, Luma, or crafting table
const ARTICLE_ALLOWLIST: [(LuaConst, LuaConst); 9] = [ const ARTICLE_ALLOWLIST: [(LuaConst, LuaConst); 9] = [
@ -232,11 +233,11 @@ static mut MIRROR_STATE: f32 = 1.0;
static mut RANDOM_SLOT: usize = 0; static mut RANDOM_SLOT: usize = 0;
unsafe fn get_slot() -> usize { 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() { if random_slot != SaveStateSlot::empty() {
RANDOM_SLOT RANDOM_SLOT
} else { } 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 { pub unsafe fn should_mirror() -> f32 {
match MENU.save_state_mirroring { match read(&MENU).save_state_mirroring {
SaveStateMirroring::NONE => 1.0, SaveStateMirroring::NONE => 1.0,
SaveStateMirroring::ALTERNATE => -1.0 * MIRROR_STATE, SaveStateMirroring::ALTERNATE => -1.0 * MIRROR_STATE,
SaveStateMirroring::RANDOM => ([-1.0, 1.0])[get_random_int(2) as usize], SaveStateMirroring::RANDOM => ([-1.0, 1.0])[get_random_int(2) as usize],
_ => panic!( _ => panic!(
"Invalid value in should_mirror: {}", "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); 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| { (0..ItemManager::get_num_of_active_item_all(item_mgr)).for_each(|item_idx| {
let item = ItemManager::get_active_item(item_mgr, item_idx); let item = ItemManager::get_active_item(item_mgr, item_idx);
if item != 0 { 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) { 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; return;
} }
@ -448,7 +449,7 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
.contains(&fighter_kind); .contains(&fighter_kind);
// Reset state // 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 && save_state.state == NoAction
&& is_dead(module_accessor); && is_dead(module_accessor);
let mut triggered_reset: bool = false; 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 (autoload_reset || triggered_reset) && !fighter_is_nana {
if save_state.state == NoAction { 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() { let slot = if random_slot != SaveStateSlot::empty() {
RANDOM_SLOT = random_slot.into_idx().unwrap_or(0); RANDOM_SLOT = random_slot.into_idx().unwrap_or(0);
RANDOM_SLOT RANDOM_SLOT
@ -572,48 +573,48 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
if save_state.state == NoAction { if save_state.state == NoAction {
// Set damage of the save state // Set damage of the save state
if !is_cpu { if !is_cpu {
match MENU.save_damage_player { match read(&MENU).save_damage_player {
SaveDamage::SAVED => { SaveDamage::SAVED => {
set_damage(module_accessor, save_state.percent); set_damage(module_accessor, save_state.percent);
} }
SaveDamage::RANDOM => { SaveDamage::RANDOM => {
// Gen random value // Gen random value
let pct: f32 = get_random_float( let pct: f32 = get_random_float(
MENU.save_damage_limits_player.0 as f32, read(&MENU).save_damage_limits_player.0 as f32,
MENU.save_damage_limits_player.1 as f32, read(&MENU).save_damage_limits_player.1 as f32,
); );
set_damage(module_accessor, pct); set_damage(module_accessor, pct);
} }
SaveDamage::DEFAULT => {} SaveDamage::DEFAULT => {}
_ => panic!( _ => panic!(
"Invalid value in save_states()::save_damage_player: {}", "Invalid value in save_states()::save_damage_player: {}",
MENU.save_damage_player read(&MENU).save_damage_player
), ),
} }
} else { } else {
match MENU.save_damage_cpu { match read(&MENU).save_damage_cpu {
SaveDamage::SAVED => { SaveDamage::SAVED => {
set_damage(module_accessor, save_state.percent); set_damage(module_accessor, save_state.percent);
} }
SaveDamage::RANDOM => { SaveDamage::RANDOM => {
// Gen random value // Gen random value
let pct: f32 = get_random_float( let pct: f32 = get_random_float(
MENU.save_damage_limits_cpu.0 as f32, read(&MENU).save_damage_limits_cpu.0 as f32,
MENU.save_damage_limits_cpu.1 as f32, read(&MENU).save_damage_limits_cpu.1 as f32,
); );
set_damage(module_accessor, pct); set_damage(module_accessor, pct);
} }
SaveDamage::DEFAULT => {} SaveDamage::DEFAULT => {}
_ => panic!( _ => panic!(
"Invalid value in save_states()::save_damage_cpu: {}", "Invalid value in save_states()::save_damage_cpu: {}",
MENU.save_damage_cpu read(&MENU).save_damage_cpu
), ),
} }
} }
// Set to held item // Set to held item
if !is_cpu && !fighter_is_nana && MENU.character_item != CharacterItem::NONE { if !is_cpu && !fighter_is_nana && read(&MENU).character_item != CharacterItem::NONE {
apply_item(MENU.character_item); apply_item(read(&MENU).character_item);
} }
// Set the charge of special moves if the fighter matches the kind in the save state // 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 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(); input_record::lockout_record();
return; return;
} }
// otherwise, begin input recording playback if selected // 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, // 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 // 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 && 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; return;
@ -706,12 +710,12 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
if button_config::combo_passes(button_config::ButtonCombo::SaveState) { if button_config::combo_passes(button_config::ButtonCombo::SaveState) {
// Don't begin saving state if Nana's delayed input is captured // Don't begin saving state if Nana's delayed input is captured
MIRROR_STATE = 1.0; MIRROR_STATE = 1.0;
save_state_player(MENU.save_state_slot.into_idx().unwrap_or(0)).state = Save; save_state_player(read(&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; save_state_cpu(read(&MENU).save_state_slot.into_idx().unwrap_or(0)).state = Save;
notifications::clear_notifications("Save State"); notifications::clear_notification("Save State");
notifications::notification( notifications::notification(
"Save State".to_string(), "Save State".to_string(),
format!("Saved Slot {}", MENU.save_state_slot), format!("Saved Slot {}", read(&MENU).save_state_slot),
120, 120,
); );
} }

@ -6,21 +6,21 @@ use smash::Vector2f;
use crate::common::consts::*; use crate::common::consts::*;
use crate::common::*; use crate::common::*;
use crate::training::directional_influence; use crate::training::directional_influence;
use training_mod_sync::*;
static mut COUNTER: u32 = 0; static COUNTER: RwLock<u32> = RwLock::new(0);
static DIRECTION: RwLock<Direction> = RwLock::new(Direction::NEUTRAL);
static mut DIRECTION: Direction = Direction::NEUTRAL;
// TODO! Bug - we only roll a new direction when loading a save state or on LRA reset
pub fn roll_direction() { pub fn roll_direction() {
unsafe { assign(&COUNTER, 0);
COUNTER = 0; assign(&DIRECTION, read(&MENU).sdi_state.get_random());
DIRECTION = MENU.sdi_state.get_random();
}
} }
unsafe fn get_sdi_direction() -> Option<f64> { unsafe fn get_sdi_direction() -> Option<f64> {
DIRECTION.into_angle().map(|angle| { let direction = read(&DIRECTION);
if directional_influence::should_reverse_angle(DIRECTION) { direction.into_angle().map(|angle| {
if directional_influence::should_reverse_angle(direction) {
PI - angle PI - angle
} else { } else {
angle angle
@ -38,10 +38,10 @@ pub unsafe fn check_hit_stop_delay_command(
if !is_training_mode() || !is_operation_cpu(module_accessor) { if !is_training_mode() || !is_operation_cpu(module_accessor) {
return original!()(module_accessor, sdi_direction); return original!()(module_accessor, sdi_direction);
} }
let repeat = MENU.sdi_strength.into_u32(); let repeat = read(&MENU).sdi_strength.into_u32();
let mut counter_lock = lock_write(&COUNTER);
COUNTER = (COUNTER + 1) % repeat; *counter_lock = (*counter_lock + 1) % repeat;
if COUNTER == repeat - 1 { if *counter_lock == repeat - 1 {
if let Some(angle) = get_sdi_direction() { if let Some(angle) = get_sdi_direction() {
// If there is a non-neutral direction picked, // If there is a non-neutral direction picked,
// modify the SDI angle Vector2f as a side-effect // modify the SDI angle Vector2f as a side-effect

@ -10,72 +10,81 @@ use crate::common::consts::*;
use crate::common::*; use crate::common::*;
use crate::training::{frame_counter, input_record, mash, save_states}; 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 // 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 // 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 // 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 // For how many frames should the shield hold be overwritten
static mut SUSPEND_SHIELD: bool = false; static SUSPEND_SHIELD: RwLock<bool> = RwLock::new(false);
static REACTION_COUNTER_INDEX: Lazy<usize> =
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
// Toggle for shield decay // 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) { fn set_shield_decay(value: bool) {
unsafe { assign(&SHIELD_DECAY, value);
SHIELD_DECAY = value;
}
} }
fn should_pause_shield_decay() -> bool { fn should_pause_shield_decay() -> bool {
unsafe { !SHIELD_DECAY } !read(&SHIELD_DECAY)
} }
fn reset_oos_offset() { fn reset_oos_offset() {
unsafe { /*
/* * Need to offset by 1, since we decrease as soon as shield gets hit
* Need to offset by 1, since we decrease as soon as shield gets hit * but only check later if we can OOS
* but only check later if we can OOS */
*/ assign(
MULTI_HIT_OFFSET = MENU.oos_offset.get_random().into_delay() + 1; &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 // 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) { if !is_in_shieldstun(module_accessor) {
// Make sure we don't forget and wait until we get hit on shield // Make sure we don't forget and wait until we get hit on shield
WAS_IN_SHIELDSTUN = false; *was_in_shieldstun_lock = false;
return; return;
} }
// Make sure we just freshly entered shield stun // Make sure we just freshly entered shield stun
if WAS_IN_SHIELDSTUN { if *was_in_shieldstun_lock {
return; return;
} }
// Roll shield delay // 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 // 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 // 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 // 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) { pub fn get_command_flag_cat(module_accessor: &mut app::BattleObjectModuleAccessor) {
@ -107,7 +116,7 @@ pub unsafe fn get_param_float(
return None; return None;
} }
if MENU.shield_state != Shield::NONE { if read(&MENU).shield_state != Shield::NONE {
handle_oos_offset(module_accessor); handle_oos_offset(module_accessor);
} }
@ -116,10 +125,7 @@ pub unsafe fn get_param_float(
// Shield Decay//Recovery // Shield Decay//Recovery
fn handle_shield_decay(param_type: u64, param_hash: u64) -> Option<f32> { fn handle_shield_decay(param_type: u64, param_hash: u64) -> Option<f32> {
let menu_state; let menu_state = read(&MENU).shield_state;
unsafe {
menu_state = MENU.shield_state;
}
if menu_state != Shield::INFINITE if menu_state != Shield::INFINITE
&& menu_state != Shield::CONSTANT && menu_state != Shield::CONSTANT
@ -143,10 +149,6 @@ fn handle_shield_decay(param_type: u64, param_hash: u64) -> Option<f32> {
None 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 /// sets/resets the shield_damage_mul within
/// the game's internal structure. /// the game's internal structure.
/// ///
@ -155,20 +157,20 @@ static mut CACHED_SHIELD_DAMAGE_MUL: Option<f32> = None;
pub unsafe fn param_installer() { pub unsafe fn param_installer() {
if crate::training::COMMON_PARAMS as usize != 0 { if crate::training::COMMON_PARAMS as usize != 0 {
let common_params = &mut *crate::training::COMMON_PARAMS; 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 // cache the original shield damage multiplier once
if CACHED_SHIELD_DAMAGE_MUL.is_none() { if (*cached_shield_damage_mul_lock).is_none() {
CACHED_SHIELD_DAMAGE_MUL = Some(common_params.shield_damage_mul); *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, // if you are in training mode and have infinite shield enabled,
// set the game's shield_damage_mul to 0.0 // set the game's shield_damage_mul to 0.0
common_params.shield_damage_mul = 0.0; common_params.shield_damage_mul = 0.0;
} else { } else {
// reset the game's shield_damage_mul back to what // reset the game's shield_damage_mul back to what
// it originally was at game boot. // 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; return true;
} }
let shield_state; let shield_state = &read(&MENU).shield_state;
unsafe {
shield_state = &MENU.shield_state;
}
// We should hold shield if the state requires it // We should hold shield if the state requires it
if unsafe { save_states::is_loading() } if unsafe { save_states::is_loading() }
@ -223,7 +222,7 @@ unsafe fn mod_handle_sub_guard_cont(fighter: &mut L2CFighterCommon) {
} }
// Enable shield decay // Enable shield decay
if MENU.shield_state == Shield::HOLD { if read(&MENU).shield_state == Shield::HOLD {
set_shield_decay(true); set_shield_decay(true);
} }
@ -237,7 +236,7 @@ unsafe fn mod_handle_sub_guard_cont(fighter: &mut L2CFighterCommon) {
return; return;
} }
if frame_counter::should_delay(SHIELD_DELAY, *REACTION_COUNTER_INDEX) { if frame_counter::should_delay(read(&SHIELD_DELAY), *REACTION_COUNTER_INDEX) {
return; return;
} }
@ -245,11 +244,11 @@ unsafe fn mod_handle_sub_guard_cont(fighter: &mut L2CFighterCommon) {
return; return;
} }
if MENU.mash_triggers.contains(&MashTrigger::SHIELDSTUN) { if read(&MENU).mash_triggers.contains(&MashTrigger::SHIELDSTUN) {
if MENU.shieldstun_override == Action::empty() { if read(&MENU).shieldstun_override == Action::empty() {
mash::external_buffer_menu_mash(MENU.mash_state.get_random()) mash::external_buffer_menu_mash(read(&MENU).mash_state.get_random())
} else { } 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 // Make sure we only flicker shield when Airdodge and Shield mash options are selected
if action == Action::AIR_DODGE { if action == Action::AIR_DODGE {
let shield_state; let shield_state = &read(&MENU).shield_state;
unsafe {
shield_state = &MENU.shield_state;
}
// If we're supposed to be holding shield, let airdodge make us drop shield // If we're supposed to be holding shield, let airdodge make us drop shield
if [Shield::HOLD, Shield::INFINITE, Shield::CONSTANT].contains(shield_state) { if [Shield::HOLD, Shield::INFINITE, Shield::CONSTANT].contains(shield_state) {
suspend_shield(Action::AIR_DODGE); 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 // Make sure we only flicker shield when Airdodge and Shield mash options are selected
if action == Action::AIR_DODGE { if action == Action::AIR_DODGE {
let shield_state; let shield_state = &read(&MENU).shield_state;
unsafe {
shield_state = &MENU.shield_state;
}
// If we're supposed to be holding shield, let airdodge make us drop shield // If we're supposed to be holding shield, let airdodge make us drop shield
if [Shield::HOLD, Shield::INFINITE, Shield::CONSTANT].contains(shield_state) { if [Shield::HOLD, Shield::INFINITE, Shield::CONSTANT].contains(shield_state) {
suspend_shield(Action::AIR_DODGE); suspend_shield(Action::AIR_DODGE);
@ -380,10 +373,7 @@ fn needs_oos_handling_drop_shield() -> bool {
} }
if action == Action::SHIELD { if action == Action::SHIELD {
let shield_state; let shield_state = &read(&MENU).shield_state;
unsafe {
shield_state = &MENU.shield_state;
}
// Don't drop shield on shield hit if we're supposed to be holding shield // 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) { if [Shield::HOLD, Shield::INFINITE, Shield::CONSTANT].contains(shield_state) {
return false; return false;
@ -402,9 +392,7 @@ pub fn is_aerial(action: Action) -> bool {
// Needed for shield drop options // Needed for shield drop options
pub fn suspend_shield(action: Action) { pub fn suspend_shield(action: Action) {
unsafe { assign(&SUSPEND_SHIELD, need_suspend_shield(action));
SUSPEND_SHIELD = need_suspend_shield(action);
}
} }
fn need_suspend_shield(action: Action) -> bool { 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 * Needed for these options to work OOS
*/ */
fn shield_is_suspended() -> bool { 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::consts::*;
use crate::common::*; 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() { pub fn roll_direction() {
unsafe { assign(
STICK_DIRECTION = MENU.shield_tilt.get_random(); &SHIELD_STICK_DIRECTION,
} read(&MENU).shield_tilt.get_random(),
);
} }
pub unsafe fn mod_get_stick_x( pub fn mod_get_stick_x(module_accessor: &mut app::BattleObjectModuleAccessor) -> Option<f32> {
module_accessor: &mut app::BattleObjectModuleAccessor,
) -> Option<f32> {
get_angle(module_accessor).map(|a| a.cos() as f32) get_angle(module_accessor).map(|a| a.cos() as f32)
} }
pub unsafe fn mod_get_stick_y( pub fn mod_get_stick_y(module_accessor: &mut app::BattleObjectModuleAccessor) -> Option<f32> {
module_accessor: &mut app::BattleObjectModuleAccessor,
) -> Option<f32> {
get_angle(module_accessor).map(|a| a.sin() as 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) { if !is_operation_cpu(module_accessor) {
return None; return None;
} }
let stick_direction = read(&SHIELD_STICK_DIRECTION);
STICK_DIRECTION.into_angle() 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::app::{lua_bind::*, sv_system, BattleObjectModuleAccessor};
use smash::hash40; use smash::hash40;
use smash::lib::lua_const::*; use smash::lib::lua_const::*;
@ -6,28 +9,24 @@ use smash::lua2cpp::L2CFighterBase;
use smash::phx::{Hash40, Vector3f}; use smash::phx::{Hash40, Vector3f};
use crate::common::consts::*; use crate::common::consts::*;
use crate::common::offsets::OFFSET_CHANGE_ACTIVE_CAMERA; use crate::common::offsets::{
OFFSET_CHANGE_ACTIVE_CAMERA, OFFSET_SET_TRAINING_FIXED_CAMERA_VALUES,
use crate::common::offsets::OFFSET_SET_TRAINING_FIXED_CAMERA_VALUES; };
use crate::common::*; use crate::common::*;
use crate::training::{frame_counter, mash, save_states}; use crate::training::{frame_counter, mash, save_states};
use training_mod_sync::*;
use skyline::hooks::{getRegionAddress, Region}; static TECH_ROLL_DIRECTION: RwLock<Direction> = RwLock::new(Direction::empty());
static MISS_TECH_ROLL_DIRECTION: RwLock<Direction> = RwLock::new(Direction::empty());
use once_cell::sync::Lazy; static NEEDS_VISIBLE: RwLock<bool> = RwLock::new(false);
use std::collections::HashMap; static DEFAULT_FIXED_CAM_CENTER: RwLock<Vector3f> = RwLock::new(Vector3f {
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 {
x: 0.0, x: 0.0,
y: 0.0, y: 0.0,
z: 0.0, z: 0.0,
}; });
static FRAME_COUNTER: Lazy<usize> = static FRAME_COUNTER: LazyLock<usize> =
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame)); LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
unsafe fn is_enable_passive(module_accessor: &mut BattleObjectModuleAccessor) -> bool { unsafe fn is_enable_passive(module_accessor: &mut BattleObjectModuleAccessor) -> bool {
let fighter = get_fighter_common_from_accessor(module_accessor); let fighter = get_fighter_common_from_accessor(module_accessor);
@ -64,7 +63,7 @@ unsafe fn mod_handle_change_status(
.try_get_int() .try_get_int()
.unwrap_or(*FIGHTER_STATUS_KIND_WAIT as u64) as i32; .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) { if handle_grnd_tech(module_accessor, status_kind, unk, status_kind_int, state) {
return; return;
@ -115,22 +114,22 @@ unsafe fn handle_grnd_tech(
TechFlags::ROLL_F => { TechFlags::ROLL_F => {
*status_kind = FIGHTER_STATUS_KIND_PASSIVE_FB.as_lua_int(); *status_kind = FIGHTER_STATUS_KIND_PASSIVE_FB.as_lua_int();
*unk = LUA_TRUE; *unk = LUA_TRUE;
TECH_ROLL_DIRECTION = Direction::IN; // = In assign(&TECH_ROLL_DIRECTION, Direction::IN);
true true
} }
TechFlags::ROLL_B => { TechFlags::ROLL_B => {
*status_kind = FIGHTER_STATUS_KIND_PASSIVE_FB.as_lua_int(); *status_kind = FIGHTER_STATUS_KIND_PASSIVE_FB.as_lua_int();
*unk = LUA_TRUE; *unk = LUA_TRUE;
TECH_ROLL_DIRECTION = Direction::OUT; // = Away assign(&TECH_ROLL_DIRECTION, Direction::OUT);
true true
} }
_ => false, _ => false,
}; };
if do_tech && MENU.mash_triggers.contains(&MashTrigger::TECH) { if do_tech && read(&MENU).mash_triggers.contains(&MashTrigger::TECH) {
if MENU.tech_action_override == Action::empty() { if read(&MENU).tech_action_override == Action::empty() {
mash::external_buffer_menu_mash(MENU.mash_state.get_random()) mash::external_buffer_menu_mash(read(&MENU).mash_state.get_random())
} else { } 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, _ => false,
}; };
if do_tech && MENU.mash_triggers.contains(&MashTrigger::TECH) { if do_tech && read(&MENU).mash_triggers.contains(&MashTrigger::TECH) {
if MENU.tech_action_override == Action::empty() { if read(&MENU).tech_action_override == Action::empty() {
mash::external_buffer_menu_mash(MENU.mash_state.get_random()) mash::external_buffer_menu_mash(read(&MENU).mash_state.get_random())
} else { } 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 true
@ -208,18 +207,18 @@ unsafe fn handle_ceil_tech(
*status_kind = FIGHTER_STATUS_KIND_PASSIVE_CEIL.as_lua_int(); *status_kind = FIGHTER_STATUS_KIND_PASSIVE_CEIL.as_lua_int();
*unk = LUA_TRUE; *unk = LUA_TRUE;
if MENU.mash_triggers.contains(&MashTrigger::TECH) { if read(&MENU).mash_triggers.contains(&MashTrigger::TECH) {
if MENU.tech_action_override == Action::empty() { if read(&MENU).tech_action_override == Action::empty() {
mash::external_buffer_menu_mash(MENU.mash_state.get_random()) mash::external_buffer_menu_mash(read(&MENU).mash_state.get_random())
} else { } 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 true
} }
pub unsafe fn get_command_flag_cat(module_accessor: &mut BattleObjectModuleAccessor) { 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; return;
} }
@ -232,15 +231,15 @@ pub unsafe fn get_command_flag_cat(module_accessor: &mut BattleObjectModuleAcces
.contains(&status) .contains(&status)
{ {
// Mistech // 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::GETUP => *FIGHTER_STATUS_KIND_DOWN_STAND,
MissTechFlags::ATTACK => *FIGHTER_STATUS_KIND_DOWN_STAND_ATTACK, MissTechFlags::ATTACK => *FIGHTER_STATUS_KIND_DOWN_STAND_ATTACK,
MissTechFlags::ROLL_F => { MissTechFlags::ROLL_F => {
MISS_TECH_ROLL_DIRECTION = Direction::IN; // = In assign(&MISS_TECH_ROLL_DIRECTION, Direction::IN);
*FIGHTER_STATUS_KIND_DOWN_STAND_FB *FIGHTER_STATUS_KIND_DOWN_STAND_FB
} }
MissTechFlags::ROLL_B => { MissTechFlags::ROLL_B => {
MISS_TECH_ROLL_DIRECTION = Direction::OUT; // = Away assign(&MISS_TECH_ROLL_DIRECTION, Direction::OUT);
*FIGHTER_STATUS_KIND_DOWN_STAND_FB *FIGHTER_STATUS_KIND_DOWN_STAND_FB
} }
_ => return, _ => 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) { if frame_counter::should_delay(lockout_time, *FRAME_COUNTER) {
return; 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::GETUP => *FIGHTER_STATUS_KIND_DOWN_STAND,
MissTechFlags::ATTACK => *FIGHTER_STATUS_KIND_DOWN_STAND_ATTACK, MissTechFlags::ATTACK => *FIGHTER_STATUS_KIND_DOWN_STAND_ATTACK,
MissTechFlags::ROLL_F => { MissTechFlags::ROLL_F => {
MISS_TECH_ROLL_DIRECTION = Direction::IN; // = In assign(&MISS_TECH_ROLL_DIRECTION, Direction::IN);
*FIGHTER_STATUS_KIND_DOWN_STAND_FB *FIGHTER_STATUS_KIND_DOWN_STAND_FB
} }
MissTechFlags::ROLL_B => { MissTechFlags::ROLL_B => {
MISS_TECH_ROLL_DIRECTION = Direction::OUT; // = Away assign(&MISS_TECH_ROLL_DIRECTION, Direction::OUT);
*FIGHTER_STATUS_KIND_DOWN_STAND_FB *FIGHTER_STATUS_KIND_DOWN_STAND_FB
} }
_ => return, _ => return,
}; };
} else if status == *FIGHTER_STATUS_KIND_SLIP_WAIT { } else if status == *FIGHTER_STATUS_KIND_SLIP_WAIT {
// Handle slips (like Diddy banana) // 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::GETUP => *FIGHTER_STATUS_KIND_SLIP_STAND,
MissTechFlags::ATTACK => *FIGHTER_STATUS_KIND_SLIP_STAND_ATTACK, MissTechFlags::ATTACK => *FIGHTER_STATUS_KIND_SLIP_STAND_ATTACK,
MissTechFlags::ROLL_F => *FIGHTER_STATUS_KIND_SLIP_STAND_F, 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 { if requested_status != 0 {
StatusModule::change_status_force(module_accessor, requested_status, true); StatusModule::change_status_force(module_accessor, requested_status, true);
if MENU.mash_triggers.contains(&MashTrigger::MISTECH) { if read(&MENU).mash_triggers.contains(&MashTrigger::MISTECH) {
if MENU.tech_action_override == Action::empty() { if read(&MENU).tech_action_override == Action::empty() {
mash::external_buffer_menu_mash(MENU.mash_state.get_random()) mash::external_buffer_menu_mash(read(&MENU).mash_state.get_random())
} else { } 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; return None;
} }
if MENU.tech_state == TechFlags::empty() { if read(&MENU).tech_state == TechFlags::empty() {
return None; return None;
} }
if [hash40("passive_stand_f"), hash40("passive_stand_b")].contains(&motion_kind) { 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")); return Some(hash40("passive_stand_f"));
} else { } else {
return Some(hash40("passive_stand_b")); return Some(hash40("passive_stand_b"));
} }
} else if [hash40("down_forward_u"), hash40("down_back_u")].contains(&motion_kind) { } 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")); return Some(hash40("down_forward_u"));
} else { } else {
return Some(hash40("down_back_u")); return Some(hash40("down_back_u"));
} }
} else if [hash40("down_forward_d"), hash40("down_back_d")].contains(&motion_kind) { } 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")); return Some(hash40("down_forward_d"));
} else { } else {
return Some(hash40("down_back_d")); 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() { 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; return;
} }
let module_accessor = get_module_accessor(FighterId::CPU); let module_accessor = get_module_accessor(FighterId::CPU);
@ -370,7 +369,7 @@ pub unsafe fn hide_tech() {
); );
// Disable visibility // Disable visibility
if MotionModule::frame(module_accessor) >= 6.0 { if MotionModule::frame(module_accessor) >= 6.0 {
NEEDS_VISIBLE = true; assign(&NEEDS_VISIBLE, true);
VisibilityModule::set_whole(module_accessor, false); 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_nopassive"), false);
EffectModule::set_visible_kind(module_accessor, Hash40::new("sys_down_smoke"), 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 { if MotionModule::end_frame(module_accessor) - MotionModule::frame(module_accessor) <= 5.0 {
// Re-enable visibility // Re-enable visibility
NEEDS_VISIBLE = false; assign(&NEEDS_VISIBLE, false);
VisibilityModule::set_whole(module_accessor, true); VisibilityModule::set_whole(module_accessor, true);
} }
} else { } else {
// If the CPU's tech status was interrupted, make them visible again // If the CPU's tech status was interrupted, make them visible again
if NEEDS_VISIBLE { let mut needs_visible_lock = lock_write(&NEEDS_VISIBLE);
NEEDS_VISIBLE = false; if *needs_visible_lock {
*needs_visible_lock = false;
VisibilityModule::set_whole(module_accessor, true); 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); return original!()(module_accessor, quake_kind);
} }
let status = StatusModule::status_kind(module_accessor); 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 // We're hiding techs, prevent mistech quake from giving away missed tech
return original!()(module_accessor, *CAMERA_QUAKE_KIND_NONE); return original!()(module_accessor, *CAMERA_QUAKE_KIND_NONE);
} }
@ -449,9 +450,9 @@ pub struct CameraManager {
unsafe fn set_fixed_camera_values() { unsafe fn set_fixed_camera_values() {
let camera_manager = get_camera_manager(); 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 // 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 { } else {
// We're in CameraMode 4, which is Fixed, and we are hiding tech chases, so we want a better view of the stage // 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()) { 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() { if !is_training_mode() {
return original!()(camera_manager, fixed_camera_values); 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); original!()(camera_manager, fixed_camera_values);
// Set Fixed Camera Values now, since L + R + A reset switches without calling ChangeActiveCamera // Set Fixed Camera Values now, since L + R + A reset switches without calling ChangeActiveCamera
set_fixed_camera_values(); set_fixed_camera_values();

@ -5,78 +5,65 @@ use crate::common::consts::*;
use crate::common::*; use crate::common::*;
use crate::training::frame_counter; use crate::training::frame_counter;
use crate::training::mash; use crate::training::mash;
use training_mod_sync::*;
use once_cell::sync::Lazy;
const NOT_SET: u32 = 9001; const NOT_SET: u32 = 9001;
static mut THROW_DELAY: u32 = NOT_SET; static THROW_DELAY: RwLock<u32> = RwLock::new(NOT_SET);
static mut PUMMEL_DELAY: u32 = NOT_SET; static PUMMEL_DELAY: RwLock<u32> = RwLock::new(NOT_SET);
static mut THROW_CASE: ThrowOption = ThrowOption::empty(); static THROW_CASE: RwLock<ThrowOption> = RwLock::new(ThrowOption::empty());
static THROW_DELAY_COUNTER: Lazy<usize> = static THROW_DELAY_COUNTER: LazyLock<usize> =
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame)); LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
static PUMMEL_DELAY_COUNTER: Lazy<usize> = static PUMMEL_DELAY_COUNTER: LazyLock<usize> =
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame)); LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
// Rolling Throw Delays and Pummel Delays separately // Rolling Throw Delays and Pummel Delays separately
pub fn reset_throw_delay() { pub fn reset_throw_delay() {
unsafe { if read(&THROW_DELAY) != NOT_SET {
if THROW_DELAY != NOT_SET { assign(&THROW_DELAY, NOT_SET);
THROW_DELAY = NOT_SET; frame_counter::full_reset(*THROW_DELAY_COUNTER);
frame_counter::full_reset(*THROW_DELAY_COUNTER);
}
} }
} }
pub fn reset_pummel_delay() { pub fn reset_pummel_delay() {
unsafe { if read(&PUMMEL_DELAY) != NOT_SET {
if PUMMEL_DELAY != NOT_SET { assign(&PUMMEL_DELAY, NOT_SET);
PUMMEL_DELAY = NOT_SET; frame_counter::full_reset(*PUMMEL_DELAY_COUNTER);
frame_counter::full_reset(*PUMMEL_DELAY_COUNTER);
}
} }
} }
pub fn reset_throw_case() { pub fn reset_throw_case() {
unsafe { if read(&THROW_CASE) != ThrowOption::empty() {
if THROW_CASE != ThrowOption::empty() { // Don't roll another throw option if one is already selected
// Don't roll another throw option if one is already selected assign(&THROW_CASE, ThrowOption::empty());
THROW_CASE = ThrowOption::empty();
}
} }
} }
fn roll_throw_delay() { fn roll_throw_delay() {
unsafe { if read(&THROW_DELAY) == NOT_SET {
if THROW_DELAY != NOT_SET { // Only roll another throw delay if one is not already selected
// Don't roll another throw delay if one is already selected assign(
return; &THROW_DELAY,
} read(&MENU).throw_delay.get_random().into_meddelay(),
);
THROW_DELAY = MENU.throw_delay.get_random().into_meddelay();
} }
} }
fn roll_pummel_delay() { fn roll_pummel_delay() {
unsafe { if read(&PUMMEL_DELAY) == NOT_SET {
if PUMMEL_DELAY != NOT_SET { // Don't roll another pummel delay if one is already selected
// Don't roll another pummel delay if one is already selected assign(
return; &PUMMEL_DELAY,
} read(&MENU).pummel_delay.get_random().into_meddelay(),
);
PUMMEL_DELAY = MENU.pummel_delay.get_random().into_meddelay();
} }
} }
fn roll_throw_case() { fn roll_throw_case() {
unsafe { if read(&THROW_CASE) == ThrowOption::empty() {
// Don't re-roll if there is already a throw option selected // Only re-roll if there is not already a throw option selected
if THROW_CASE != ThrowOption::empty() { assign(&THROW_CASE, read(&MENU).throw_state.get_random());
return;
}
THROW_CASE = MENU.throw_state.get_random();
} }
} }
@ -112,20 +99,20 @@ pub unsafe fn get_command_flag_throw_direction(
roll_pummel_delay(); roll_pummel_delay();
if THROW_CASE == ThrowOption::NONE { if read(&THROW_CASE) == ThrowOption::NONE {
// Do nothing, but don't reroll the throw case. // Do nothing, but don't reroll the throw case.
return 0; 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 // 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 // And not yet time to pummel either, so don't do anything
return 0; return 0;
} }
// If no pummel delay is selected (default), then don't pummel // 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; return 0;
} }
@ -143,8 +130,8 @@ pub unsafe fn get_command_flag_throw_direction(
module_accessor, module_accessor,
*FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_THROW_HI, *FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_THROW_HI,
) { ) {
let cmd = THROW_CASE.into_cmd().unwrap_or(0); let cmd = read(&THROW_CASE).into_cmd().unwrap_or(0);
mash::external_buffer_menu_mash(MENU.mash_state.get_random()); mash::external_buffer_menu_mash(read(&MENU).mash_state.get_random());
return cmd; return cmd;
} }

@ -1,11 +1,11 @@
use std::ptr::addr_of_mut;
use skyline::nn::ui2d::*; use skyline::nn::ui2d::*;
use smash::ui2d::{SmashPane, SmashTextBox}; use smash::ui2d::{SmashPane, SmashTextBox};
use crate::common::menu::QUICK_MENU_ACTIVE; use crate::common::menu::QUICK_MENU_ACTIVE;
use crate::common::TRAINING_MENU_ADDR; use crate::common::TRAINING_MENU_ADDR;
use crate::training::ui; use crate::training::ui::notifications::*;
use training_mod_sync::*;
macro_rules! display_parent_fmt { macro_rules! display_parent_fmt {
($x:ident) => { ($x:ident) => {
format!("TrModDisp{}", $x).as_str() format!("TrModDisp{}", $x).as_str()
@ -25,22 +25,23 @@ macro_rules! display_txt_fmt {
} }
pub unsafe fn draw(root_pane: &Pane) { 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 { if (*TRAINING_MENU_ADDR).combo_display_toggle == 0 {
// User has turned off the "combo display" option in the vanilla menu // User has turned off the "combo display" option in the vanilla menu
// Remove all notifications from the queue so we don't show them // Remove all notifications from the queue so we don't show them
// This will also set the pane's visibility to false // 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 root_pane
.find_pane_by_name_recursive(display_parent_fmt!(notification_idx)) .find_pane_by_name_recursive(display_parent_fmt!(notification_idx))
.unwrap() .unwrap()
.set_visible(notification.is_some() && !QUICK_MENU_ACTIVE); .set_visible(notification.is_some() && !read(&QUICK_MENU_ACTIVE));
if notification.is_none() { if notification.is_none() {
return; return;
} }
@ -66,8 +67,8 @@ pub unsafe fn draw(root_pane: &Pane) {
text.set_color(color.r, color.g, color.b, color.a); text.set_color(color.r, color.g, color.b, color.a);
} }
let has_completed = notification.check_completed(); if notification.has_completed() {
if has_completed { (*queue_lock).remove(0);
(*queue).remove(0);
} }
drop(queue_lock);
} }

@ -4,15 +4,14 @@ use skyline::nn::ui2d::*;
use smash::ui2d::{SmashPane, SmashTextBox}; use smash::ui2d::{SmashPane, SmashTextBox};
use training_mod_consts::{InputDisplay, MENU}; use training_mod_consts::{InputDisplay, MENU};
use crate::{ use crate::common::consts::status_display_name;
common::{consts::status_display_name, menu::QUICK_MENU_ACTIVE}, use crate::menu::QUICK_MENU_ACTIVE;
training::{ use crate::training::input_log::{
input_log::{ DirectionStrength, InputLog, DRAW_LOG_BASE_IDX, NUM_LOGS, P1_INPUT_LOGS, WHITE, YELLOW,
DirectionStrength, InputLog, DRAW_LOG_BASE_IDX, NUM_LOGS, P1_INPUT_LOGS, WHITE, YELLOW,
},
ui::{fade_out, menu::VANILLA_MENU_ACTIVE},
},
}; };
use crate::training::ui::fade_out;
use crate::training::ui::menu::VANILLA_MENU_ACTIVE;
use training_mod_sync::*;
macro_rules! log_parent_fmt { macro_rules! log_parent_fmt {
($x:ident) => { ($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) { 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 let log_pane = root_pane
.find_pane_by_name_recursive(log_parent_fmt!(draw_log_idx)) .find_pane_by_name_recursive(log_parent_fmt!(draw_log_idx))
.unwrap(); .unwrap();
@ -176,7 +176,7 @@ unsafe fn draw_log(root_pane: &Pane, log_idx: usize, log: &InputLog) {
.as_textbox() .as_textbox()
.set_text_string(frame_text.as_str()); .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) status_display_name(log.fighter_kind, log.status)
} else { } else {
"".to_string() "".to_string()
@ -193,17 +193,15 @@ pub unsafe fn draw(root_pane: &Pane) {
.find_pane_by_name_recursive("TrModInputLog") .find_pane_by_name_recursive("TrModInputLog")
.unwrap(); .unwrap();
logs_pane.set_visible( 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; return;
} }
let logs_ptr = P1_INPUT_LOGS.data_ptr(); let logs = read(&(*P1_INPUT_LOGS));
if logs_ptr.is_null() {
return;
}
let logs = &*logs_ptr;
for (log_idx, log) in logs.iter().enumerate() { for (log_idx, log) in logs.iter().enumerate() {
draw_log(root_pane, log_idx, log); draw_log(root_pane, log_idx, log);

@ -1,16 +1,19 @@
use std::collections::HashMap; use std::collections::HashMap;
use lazy_static::lazy_static;
use skyline::nn::ui2d::*; use skyline::nn::ui2d::*;
use smash::ui2d::{SmashPane, SmashTextBox}; use smash::ui2d::{SmashPane, SmashTextBox};
use training_mod_tui::{ use training_mod_tui::{
App, AppPage, ConfirmationState, SliderState, NX_SUBMENU_COLUMNS, NX_SUBMENU_ROWS, 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::training::frame_counter;
use crate::{common, common::menu::QUICK_MENU_ACTIVE, input::*};
use training_mod_consts::TOGGLE_MAX; use training_mod_consts::TOGGLE_MAX;
use training_mod_sync::*;
use super::fade_out; use super::fade_out;
use super::set_icon_text; use super::set_icon_text;
@ -57,25 +60,27 @@ const BG_LEFT_SELECTED_WHITE_COLOR: ResColor = ResColor {
a: 255, a: 255,
}; };
pub static mut VANILLA_MENU_ACTIVE: bool = false; pub static VANILLA_MENU_ACTIVE: RwLock<bool> = RwLock::new(false);
lazy_static! { static GCC_BUTTON_MAPPING: LazyLock<HashMap<&'static str, u16>> = LazyLock::new(|| {
static ref GCC_BUTTON_MAPPING: HashMap<&'static str, u16> = HashMap::from([ HashMap::from([
("L", 0xE204), ("L", 0xE204),
("R", 0xE205), ("R", 0xE205),
("X", 0xE206), ("X", 0xE206),
("Y", 0xE207), ("Y", 0xE207),
("Z", 0xE208) ("Z", 0xE208),
]); ])
static ref PROCON_BUTTON_MAPPING: HashMap<&'static str, u16> = HashMap::from([ });
static PROCON_BUTTON_MAPPING: LazyLock<HashMap<&'static str, u16>> = LazyLock::new(|| {
HashMap::from([
("L", 0xE0E4), ("L", 0xE0E4),
("R", 0xE0E5), ("R", 0xE0E5),
("X", 0xE0E2), ("X", 0xE0E2),
("Y", 0xE0E3), ("Y", 0xE0E3),
("ZL", 0xE0E6), ("ZL", 0xE0E6),
("ZR", 0xE0E7) ("ZR", 0xE0E7),
]); ])
} });
unsafe fn render_submenu_page(app: &mut App, root_pane: &Pane) { 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 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 // 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 // begun moving upward. It starts at -80 and moves to 0 over 10 frames
// in info_training_in_menu.bflan // in info_training_in_menu.bflan
VANILLA_MENU_ACTIVE = root_pane assign(
.find_pane_by_name_recursive("L_staying_help") &VANILLA_MENU_ACTIVE,
.unwrap() root_pane
.pos_y .find_pane_by_name_recursive("L_staying_help")
!= -80.0; .unwrap()
.pos_y
!= -80.0,
);
let overall_parent_pane = root_pane.find_pane_by_name_recursive("TrModMenu").unwrap(); 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); let menu_close_wait_frame = frame_counter::get_frame_count(*MENU_CLOSE_FRAME_COUNTER);
fade_out( fade_out(
overall_parent_pane, overall_parent_pane,
@ -473,11 +481,11 @@ pub unsafe fn draw(root_pane: &Pane) {
); );
// Only submit updates if we have received input // Only submit updates if we have received input
let received_input = &mut *MENU_RECEIVED_INPUT.data_ptr(); let received_input = read(&MENU_RECEIVED_INPUT);
if !*received_input { if !received_input {
return; return;
} else { } else {
*received_input = false; assign(&MENU_RECEIVED_INPUT, false);
} }
if let Some(quit_button) = root_pane.find_pane_by_name_recursive("TrModTitle") { 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") .find_pane_by_name_recursive("status_R")
.expect("Unable to find status_R pane"); .expect("Unable to find status_R pane");
// status_r_pane.flags |= 1 << PaneFlag::InfluencedAlpha as u8; // 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 root_pane
.find_pane_by_name_recursive("TrModSlider") .find_pane_by_name_recursive("TrModSlider")
@ -523,8 +531,8 @@ pub unsafe fn draw(root_pane: &Pane) {
// Update menu display // Update menu display
// Grabbing lock as read-only, essentially // 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 // 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 = [ let tab_titles = [
app.tabs app.tabs
@ -538,11 +546,11 @@ pub unsafe fn draw(root_pane: &Pane) {
.title, .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 { let button_mapping = if is_gcc {
GCC_BUTTON_MAPPING.clone() &(*GCC_BUTTON_MAPPING)
} else { } else {
PROCON_BUTTON_MAPPING.clone() &(*PROCON_BUTTON_MAPPING)
}; };
let (x_key, y_key, l_key, r_key, zl_key, zr_key, z_key) = ( 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 { match app.page {
AppPage::SUBMENU => render_submenu_page(app, root_pane), AppPage::SUBMENU => render_submenu_page(&mut app, root_pane),
AppPage::SLIDER => render_slider_page(app, root_pane), AppPage::SLIDER => render_slider_page(&mut app, root_pane),
AppPage::TOGGLE => render_toggle_page(app, root_pane), AppPage::TOGGLE => render_toggle_page(&mut app, root_pane),
AppPage::CONFIRMATION => render_confirmation_page(app, root_pane), AppPage::CONFIRMATION => render_confirmation_page(&mut app, root_pane),
AppPage::CLOSE => {} AppPage::CLOSE => {}
} }
} }

@ -11,6 +11,7 @@ use crate::common::{is_ready_go, is_training_mode};
#[cfg(feature = "layout_arc_from_file")] #[cfg(feature = "layout_arc_from_file")]
use crate::consts::LAYOUT_ARC_PATH; use crate::consts::LAYOUT_ARC_PATH;
use crate::training::frame_counter; use crate::training::frame_counter;
use training_mod_sync::*;
mod damage; mod damage;
mod display; 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?" // InfluencedAlpha means "Should my children panes' alpha be influenced by mine, as the parent?"
root_pane.flags |= 1 << PaneFlag::InfluencedAlpha as u8; 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); damage::draw(root_pane, &layout_name);

@ -1,8 +1,8 @@
use std::ptr::addr_of_mut;
use skyline::nn::ui2d::ResColor; 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)] #[derive(Clone)]
pub struct Notification { pub struct Notification {
@ -36,42 +36,49 @@ impl Notification {
self.length -= 1; self.length -= 1;
} }
// Returns: has_completed pub fn has_completed(&self) -> bool {
pub fn check_completed(&mut self) -> bool { self.length <= 1
if self.length <= 1 {
return true;
}
false
} }
} }
pub fn notification(header: String, message: String, len: u32) { pub fn notification(header: String, message: String, len: u32) {
unsafe { let mut queue_lock = lock_write(&NOTIFICATIONS_QUEUE);
let queue = addr_of_mut!(QUEUE); (*queue_lock).push(Notification::new(
(*queue).push(Notification::new( header,
header, message,
message, len,
len, ResColor {
ResColor { r: 0,
r: 0, g: 0,
g: 0, b: 0,
b: 0, a: 255,
a: 255, },
}, ));
)); drop(queue_lock);
}
} }
pub fn color_notification(header: String, message: String, len: u32, color: ResColor) { pub fn color_notification(header: String, message: String, len: u32, color: ResColor) {
unsafe { let mut queue_lock = lock_write(&NOTIFICATIONS_QUEUE);
let queue = addr_of_mut!(QUEUE); (*queue_lock).push(Notification::new(header, message, len, color));
(*queue).push(Notification::new(header, message, len, color)); drop(queue_lock);
}
} }
pub fn clear_notifications(header: &'static str) { pub fn clear_notification(header: &'static str) {
unsafe { if (*lock_read(&NOTIFICATIONS_QUEUE)).is_empty() {
let queue = addr_of_mut!(QUEUE); // Before acquiring an exclusive write lock, check if there are even any notifications to clear out
(*queue).retain(|notif| notif.header != header); 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" toml = "0.5.9"
anyhow = "1.0.72" anyhow = "1.0.72"
rand = { git = "https://github.com/skyline-rs/rand" } 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] [features]
default = ["smash"] default = ["smash"]

@ -12,6 +12,8 @@ pub mod config;
pub use config::*; pub use config::*;
use paste::paste; use paste::paste;
use training_mod_sync::*;
pub use training_mod_tui::*; pub use training_mod_tui::*;
pub const TOGGLE_MAX: u8 = 5; pub const TOGGLE_MAX: u8 = 5;
@ -110,7 +112,7 @@ pub enum FighterId {
CPU = 1, CPU = 1,
} }
pub static DEFAULTS_MENU: TrainingModpackMenu = TrainingModpackMenu { pub static BASE_MENU: TrainingModpackMenu = TrainingModpackMenu {
aerial_delay: Delay::empty(), aerial_delay: Delay::empty(),
air_dodge_dir: Direction::empty(), air_dodge_dir: Direction::empty(),
attack_angle: AttackAngle::empty(), attack_angle: AttackAngle::empty(),
@ -204,7 +206,8 @@ pub static DEFAULTS_MENU: TrainingModpackMenu = TrainingModpackMenu {
lra_reset: OnOff::ON, 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! { impl_toggletrait! {
OnOff, OnOff,

@ -0,0 +1,6 @@
[package]
name = "training_mod_sync"
version = "0.1.0"
edition = "2021"
[dependencies]

@ -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, CLOSE,
} }
#[derive(PartialEq)] #[derive(PartialEq, Clone, Copy)]
pub enum ConfirmationState { pub enum ConfirmationState {
HoverNo, HoverNo,
HoverYes, HoverYes,
@ -40,7 +40,7 @@ impl ConfirmationState {
// │ OR // │ OR
// │ // │
// └─ Option<Slider> // └─ Option<Slider>
#[derive(Clone)]
pub struct App<'a> { pub struct App<'a> {
pub tabs: StatefulList<Tab<'a>>, pub tabs: StatefulList<Tab<'a>>,
pub page: AppPage, 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> { 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 { StatefulTableIteratorMut {
inner: self.items.iter_mut().flatten(), inner: self.items.iter_mut().flatten(),
} }