1
0
Fork 0
mirror of https://github.com/jugeeya/UltimateTrainingModpack.git synced 2025-03-25 07:46:11 +00:00

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

* Move QUICK_MENU_ACTIVE to RwLock

* VANILLA_MENU_ACTIVE

* EVENT_QUEUE and CURRENT_VERSION

* Stage Hazard Code

* Notifications

* FIGHTER_MANAGER, ITEM_MANAGER, and STAGE_MANAGER

* Airdodge STICK_DIRECTION

* Shield STICK_DIRECTION

* Attack angle DIRECTION

* offsets

* button_config

* imports in menu.rs

* frame_counter

* common::consts

* dev_config

* mappings in training/ui/menu

* input_log

* Move LazyLock to be a re-export

* buffs

* clatter

* directional influence

* fast_fall

* full_hop

* ledge

* sdi

* shield

* tech

* throw

* Refactor combo. Also allow frame advantage on hit.

* Fix bugs in frame advantage

* Move sync into a separate crate so we can import it in subcrates

* Update MENU

* Update DEFAULTS_MENU

* menu.rs and menu.rs

* Fix warning from ambiguous imports

* ptrainer

* mash.rs

* is_transitioning_dash

* items.rs

* input_record.rs

* Small adjustment to notifications

* Revert "Small adjustment to notifications"

This reverts commit 6629c6f456.

* input_delay.rs

* Misc nits

* Rename for clarity and format
This commit is contained in:
asimon-1 2024-11-17 15:49:18 -08:00 committed by GitHub
parent 2457c5b127
commit 858d35142e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 3670 additions and 3630 deletions

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -2,16 +2,94 @@ use std::convert::TryInto;
use std::ffi::{c_char, c_void};
use std::time::{SystemTime, UNIX_EPOCH};
use once_cell::sync::OnceCell;
use serde::{Deserialize, Serialize};
use skyline::nn::{account, oe, time};
use crate::common::release::CURRENT_VERSION;
use training_mod_sync::*;
pub static mut EVENT_QUEUE: Vec<Event> = vec![];
static mut SESSION_ID: OnceCell<String> = OnceCell::new();
static mut DEVICE_ID: OnceCell<String> = OnceCell::new();
static mut USER_ID: OnceCell<String> = OnceCell::new();
pub static EVENT_QUEUE: RwLock<Vec<Event>> = RwLock::new(vec![]);
static SESSION_ID: LazyLock<String> = LazyLock::new(|| unsafe {
let mut device_uuid = Uuid {
size: 16,
string_size: 300,
data: [0u8; 16],
};
GetPseudoDeviceId(&mut device_uuid as *mut Uuid);
time::Initialize();
let event_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_millis();
let mut session_id_hash = Sha256Hash { hash: [0; 0x20] };
let event_time_bytes: [u8; 16] = std::mem::transmute(event_time.to_be());
let session_id_bytes: [u8; 32] = [event_time_bytes, device_uuid.data]
.concat()
.try_into()
.unwrap();
GenerateSha256Hash(
&mut session_id_hash as *mut _ as *mut c_void,
0x20 * 8,
session_id_bytes.as_ptr() as *const c_void,
32 * 8,
);
session_id_hash
.hash
.iter()
.map(|i| format!("{i:02x}"))
.collect::<Vec<String>>()
.join("")
});
static DEVICE_ID: LazyLock<String> = LazyLock::new(|| unsafe {
let mut device_uuid = Uuid {
size: 16,
string_size: 300,
data: [0u8; 16],
};
GetPseudoDeviceId(&mut device_uuid as *mut Uuid);
let mut device_id_hash = Sha256Hash { hash: [0; 0x20] };
GenerateSha256Hash(
&mut device_id_hash as *mut _ as *mut c_void,
0x20 * 8,
device_uuid.data.as_ptr() as *const c_void,
64 * 2,
);
device_uuid
.data
.iter()
.map(|i| format!("{i:02x}"))
.collect::<Vec<String>>()
.join("")
});
static USER_ID: LazyLock<String> = LazyLock::new(|| unsafe {
account::Initialize();
let mut user_uid = account::Uid::new();
account::GetLastOpenedUser(&mut user_uid);
let mut user_id_hash = Sha256Hash { hash: [0; 0x20] };
GenerateSha256Hash(
&mut user_id_hash as *mut _ as *mut c_void,
0x20 * 8,
user_uid.id.as_ptr() as *const c_void,
16 * 8,
);
user_uid
.id
.iter()
.map(|i| format!("{i:02x}"))
.collect::<Vec<String>>()
.join("")
});
static SMASH_VERSION: LazyLock<String> = LazyLock::new(|| {
let mut smash_version = oe::DisplayVersion { name: [0; 16] };
unsafe {
oe::GetDisplayVersion(&mut smash_version);
std::ffi::CStr::from_ptr(smash_version.name.as_ptr() as *const c_char)
.to_string_lossy()
.into_owned()
}
});
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct Event {
@ -59,15 +137,6 @@ extern "C" {
impl Event {
pub fn new() -> Event {
let mut device_uuid = Uuid {
size: 16,
string_size: 300,
data: [0u8; 16],
};
unsafe {
GetPseudoDeviceId(&mut device_uuid as *mut Uuid);
}
unsafe {
time::Initialize();
let event_time = SystemTime::now()
@ -75,84 +144,13 @@ impl Event {
.expect("Time went backwards")
.as_millis();
if SESSION_ID.get().is_none() {
account::Initialize();
let mut user_uid = account::Uid::new();
account::GetLastOpenedUser(&mut user_uid);
let mut user_id_hash = Sha256Hash { hash: [0; 0x20] };
GenerateSha256Hash(
&mut user_id_hash as *mut _ as *mut c_void,
0x20 * 8,
user_uid.id.as_ptr() as *const c_void,
16 * 8,
);
USER_ID
.set(
user_uid
.id
.iter()
.map(|i| format!("{i:02x}"))
.collect::<Vec<String>>()
.join(""),
)
.unwrap();
let mut device_id_hash = Sha256Hash { hash: [0; 0x20] };
GenerateSha256Hash(
&mut device_id_hash as *mut _ as *mut c_void,
0x20 * 8,
device_uuid.data.as_ptr() as *const c_void,
64 * 2,
);
DEVICE_ID
.set(
device_uuid
.data
.iter()
.map(|i| format!("{i:02x}"))
.collect::<Vec<String>>()
.join(""),
)
.unwrap();
let mut session_id_hash = Sha256Hash { hash: [0; 0x20] };
// let mut device_id_0_bytes : [u8; 8] = Default::default();
// device_id_0_bytes.copy_from_slice(&device_uuid.data[0..8]);
// let mut device_id_1_bytes : [u8; 8] = Default::default();
// device_id_1_bytes.copy_from_slice(&device_uuid.data[8..16]);
let event_time_bytes: [u8; 16] = std::mem::transmute(event_time.to_be());
let session_id_bytes: [u8; 32] = [event_time_bytes, device_uuid.data]
.concat()
.try_into()
.unwrap();
GenerateSha256Hash(
&mut session_id_hash as *mut _ as *mut c_void,
0x20 * 8,
session_id_bytes.as_ptr() as *const c_void,
32 * 8,
);
SESSION_ID
.set(
session_id_hash
.hash
.iter()
.map(|i| format!("{i:02x}"))
.collect::<Vec<String>>()
.join(""),
)
.unwrap();
}
Event {
user_id: USER_ID.get().unwrap().to_string(),
device_id: DEVICE_ID.get().unwrap().to_string(),
user_id: USER_ID.clone(),
device_id: DEVICE_ID.clone(),
event_time,
session_id: SESSION_ID.get().unwrap().to_string(),
mod_version: CURRENT_VERSION.lock().to_string(),
smash_version: smash_version(),
session_id: SESSION_ID.clone(),
mod_version: CURRENT_VERSION.clone(),
smash_version: SMASH_VERSION.clone(),
..Default::default()
}
}
@ -174,36 +172,24 @@ impl Event {
}
}
pub fn smash_version() -> String {
let mut smash_version = oe::DisplayVersion { name: [0; 16] };
unsafe {
oe::GetDisplayVersion(&mut smash_version);
std::ffi::CStr::from_ptr(smash_version.name.as_ptr() as *const c_char)
.to_string_lossy()
.into_owned()
}
}
pub fn events_loop() {
loop {
std::thread::sleep(std::time::Duration::from_secs(10));
unsafe {
while let Some(event) = EVENT_QUEUE.pop() {
let host = "https://my-project-1511972643240-default-rtdb.firebaseio.com";
let path = format!(
"/event/{}/device/{}/{}.json",
event.event_name, event.device_id, event.event_time
);
let mut event_queue_lock = lock_write(&EVENT_QUEUE);
while let Some(event) = (*event_queue_lock).pop() {
let host = "https://my-project-1511972643240-default-rtdb.firebaseio.com";
let path = format!(
"/event/{}/device/{}/{}.json",
event.event_name, event.device_id, event.event_time
);
let url = format!("{host}{path}");
minreq::post(url)
.with_json(&event)
.expect("Failed to send info to firebase")
.send()
.ok();
}
let url = format!("{host}{path}");
minreq::post(url)
.with_json(&event)
.expect("Failed to send info to firebase")
.send()
.ok();
}
drop(event_queue_lock);
}
}

View file

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

View file

@ -1,22 +1,24 @@
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::fs;
use std::io::BufReader;
use std::ptr::addr_of;
use lazy_static::lazy_static;
use parking_lot::Mutex;
use skyline::nn::hid::GetNpadStyleSet;
use crate::common::button_config::button_mapping;
use crate::common::*;
use crate::common::{button_config, ButtonConfig};
use crate::common::{DEFAULTS_MENU, MENU};
use crate::events::{Event, EVENT_QUEUE};
use crate::input::*;
use crate::input::{ButtonBitfield, ControllerStyle, MappedInputs, SomeControllerStruct};
use crate::logging::*;
use crate::training::frame_counter;
use training_mod_consts::{create_app, InputControl, MenuJsonStruct, MENU_OPTIONS_PATH};
use training_mod_sync::*;
use training_mod_tui::AppPage;
use DirectionButton::*;
pub const MENU_CLOSE_WAIT_FRAMES: u32 = 15;
pub static mut QUICK_MENU_ACTIVE: bool = false;
pub static QUICK_MENU_ACTIVE: RwLock<bool> = RwLock::new(false);
pub unsafe fn menu_condition() -> bool {
button_config::combo_passes(button_config::ButtonCombo::OpenMenu)
@ -32,11 +34,9 @@ pub fn load_from_file() {
let reader = BufReader::new(menu_conf);
if let Ok(menu_conf_json) = serde_json::from_reader::<BufReader<_>, MenuJsonStruct>(reader)
{
unsafe {
MENU = menu_conf_json.menu;
DEFAULTS_MENU = menu_conf_json.defaults_menu;
info!("Previous menu found. Loading...");
}
assign(&MENU, menu_conf_json.menu);
assign(&DEFAULTS_MENU, menu_conf_json.defaults_menu);
info!("Previous menu found. Loading...");
} else {
warn!("Previous menu found but is invalid. Deleting...");
let err_msg = format!(
@ -49,23 +49,21 @@ pub fn load_from_file() {
info!("No previous menu file found.");
}
info!("Setting initial menu selections...");
unsafe {
let mut app = QUICK_MENU_APP.lock();
app.serialized_default_settings = serde_json::to_string(&*addr_of!(DEFAULTS_MENU))
.expect("Could not serialize DEFAULTS_MENU");
app.update_all_from_json(
&serde_json::to_string(&*addr_of!(MENU)).expect("Could not serialize MENU"),
);
}
let mut app = lock_write(&QUICK_MENU_APP);
app.serialized_default_settings =
serde_json::to_string(&read(&DEFAULTS_MENU)).expect("Could not serialize DEFAULTS_MENU");
app.update_all_from_json(
&serde_json::to_string(&read(&MENU)).expect("Could not serialize MENU"),
);
}
pub unsafe fn set_menu_from_json(message: &str) {
pub fn set_menu_from_json(message: &str) {
let response = serde_json::from_str::<MenuJsonStruct>(message);
info!("Received menu message: {message}");
if let Ok(message_json) = response {
// Includes both MENU and DEFAULTS_MENU
MENU = message_json.menu;
DEFAULTS_MENU = message_json.defaults_menu;
assign(&MENU, message_json.menu);
assign(&DEFAULTS_MENU, message_json.defaults_menu);
fs::write(
MENU_OPTIONS_PATH,
serde_json::to_string_pretty(&message_json).unwrap(),
@ -81,12 +79,10 @@ pub unsafe fn set_menu_from_json(message: &str) {
}
pub fn spawn_menu() {
unsafe {
QUICK_MENU_ACTIVE = true;
let mut app = QUICK_MENU_APP.lock();
app.page = AppPage::SUBMENU;
*MENU_RECEIVED_INPUT.data_ptr() = true;
}
assign(&QUICK_MENU_ACTIVE, true);
let mut app = lock_write(&QUICK_MENU_APP);
app.page = AppPage::SUBMENU;
assign(&MENU_RECEIVED_INPUT, true);
}
#[derive(Eq, PartialEq, Hash, Copy, Clone)]
@ -101,16 +97,17 @@ enum DirectionButton {
RUp,
}
lazy_static! {
pub static ref QUICK_MENU_APP: Mutex<training_mod_tui::App<'static>> = Mutex::new({
pub static QUICK_MENU_APP: LazyLock<RwLock<training_mod_tui::App<'static>>> = LazyLock::new(|| {
RwLock::new({
info!("Initialized lazy_static: QUICK_MENU_APP");
unsafe { create_app() }
});
pub static ref P1_CONTROLLER_STYLE: Mutex<ControllerStyle> =
Mutex::new(ControllerStyle::default());
static ref DIRECTION_HOLD_FRAMES: Mutex<HashMap<DirectionButton, u32>> = {
use DirectionButton::*;
Mutex::new(HashMap::from([
})
});
pub static P1_CONTROLLER_STYLE: LazyLock<RwLock<ControllerStyle>> =
LazyLock::new(|| RwLock::new(ControllerStyle::default()));
static DIRECTION_HOLD_FRAMES: LazyLock<RwLock<HashMap<DirectionButton, u32>>> =
LazyLock::new(|| {
RwLock::new(HashMap::from([
(LLeft, 0),
(RLeft, 0),
(LDown, 0),
@ -120,12 +117,11 @@ lazy_static! {
(LUp, 0),
(RUp, 0),
]))
};
pub static ref MENU_RECEIVED_INPUT: Mutex<bool> = Mutex::new(true);
}
});
pub static MENU_RECEIVED_INPUT: RwLock<bool> = RwLock::new(true);
pub static MENU_CLOSE_FRAME_COUNTER: Lazy<usize> =
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::Real));
pub static MENU_CLOSE_FRAME_COUNTER: LazyLock<usize> =
LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::Real));
pub fn handle_final_input_mapping(
player_idx: i32,
@ -135,7 +131,7 @@ pub fn handle_final_input_mapping(
unsafe {
if player_idx == 0 {
let p1_controller = &mut *controller_struct.controller;
*P1_CONTROLLER_STYLE.lock() = p1_controller.style;
assign(&P1_CONTROLLER_STYLE, p1_controller.style);
let visual_frame_count = frame_counter::get_frame_count(*MENU_CLOSE_FRAME_COUNTER);
if visual_frame_count > 0 && visual_frame_count < MENU_CLOSE_WAIT_FRAMES {
// If we just closed the menu, kill all inputs to avoid accidental presses
@ -149,7 +145,7 @@ pub fn handle_final_input_mapping(
frame_counter::reset_frame_count(*MENU_CLOSE_FRAME_COUNTER);
}
if QUICK_MENU_ACTIVE {
if read(&QUICK_MENU_ACTIVE) {
// If we're here, remove all other presses
*out = MappedInputs::empty();
@ -157,7 +153,7 @@ pub fn handle_final_input_mapping(
const DIRECTION_HOLD_REPEAT_FRAMES: u32 = 20;
use DirectionButton::*;
let direction_hold_frames = &mut *DIRECTION_HOLD_FRAMES.lock();
let mut direction_hold_frames = read_clone(&DIRECTION_HOLD_FRAMES); // TODO!("Refactor this, it doesn't need to be a hashmap")
// Check for all controllers unplugged
let mut potential_controller_ids = (0..8).collect::<Vec<u32>>();
@ -166,7 +162,7 @@ pub fn handle_final_input_mapping(
.iter()
.all(|i| GetNpadStyleSet(i as *const _).flags == 0)
{
QUICK_MENU_ACTIVE = false;
assign(&QUICK_MENU_ACTIVE, false);
return;
}
@ -194,41 +190,44 @@ pub fn handle_final_input_mapping(
}
});
let app = &mut *QUICK_MENU_APP.data_ptr(); // TODO: Why aren't we taking a lock here?
button_mapping(ButtonConfig::A, style, button_presses).then(|| {
let mut app = lock_write(&QUICK_MENU_APP);
button_config::button_mapping(ButtonConfig::A, style, button_presses).then(|| {
app.on_a();
received_input = true;
});
button_mapping(ButtonConfig::B, style, button_presses).then(|| {
button_config::button_mapping(ButtonConfig::B, style, button_presses).then(|| {
received_input = true;
app.on_b();
if app.page == AppPage::CLOSE {
// Leave menu.
frame_counter::start_counting(*MENU_CLOSE_FRAME_COUNTER);
QUICK_MENU_ACTIVE = false;
assign(&QUICK_MENU_ACTIVE, false);
let menu_json = app.get_serialized_settings_with_defaults();
set_menu_from_json(&menu_json);
EVENT_QUEUE.push(Event::menu_open(menu_json));
let mut event_queue_lock = lock_write(&EVENT_QUEUE);
(*event_queue_lock).push(Event::menu_open(menu_json));
drop(event_queue_lock);
}
});
button_mapping(ButtonConfig::X, style, button_presses).then(|| {
button_config::button_mapping(ButtonConfig::X, style, button_presses).then(|| {
app.on_x();
received_input = true;
});
button_mapping(ButtonConfig::Y, style, button_presses).then(|| {
button_config::button_mapping(ButtonConfig::Y, style, button_presses).then(|| {
app.on_y();
received_input = true;
});
button_mapping(ButtonConfig::ZL, style, button_presses).then(|| {
button_config::button_mapping(ButtonConfig::ZL, style, button_presses).then(|| {
app.on_zl();
received_input = true;
});
button_mapping(ButtonConfig::ZR, style, button_presses).then(|| {
button_config::button_mapping(ButtonConfig::ZR, style, button_presses).then(|| {
app.on_zr();
received_input = true;
});
button_mapping(ButtonConfig::R, style, button_presses).then(|| {
button_config::button_mapping(ButtonConfig::R, style, button_presses).then(|| {
app.on_r();
received_input = true;
});
@ -271,7 +270,7 @@ pub fn handle_final_input_mapping(
if received_input {
direction_hold_frames.iter_mut().for_each(|(_, f)| *f = 0);
*MENU_RECEIVED_INPUT.lock() = true;
assign(&MENU_RECEIVED_INPUT, true);
}
}
}

View file

@ -7,6 +7,7 @@ pub use crate::common::consts::MENU;
use crate::common::consts::*;
use crate::common::offsets::OFFSET_GET_BATTLE_OBJECT_FROM_ID;
use crate::training::character_specific::ptrainer;
use training_mod_sync::*;
pub mod button_config;
pub mod consts;
@ -19,11 +20,9 @@ pub mod offsets;
pub mod raygun_printer;
pub mod release;
pub static mut DEFAULTS_MENU: TrainingModpackMenu = consts::DEFAULTS_MENU;
pub static mut BASE_MENU: TrainingModpackMenu = unsafe { DEFAULTS_MENU };
pub static mut FIGHTER_MANAGER_ADDR: usize = 0;
pub static mut ITEM_MANAGER_ADDR: usize = 0;
pub static mut STAGE_MANAGER_ADDR: usize = 0;
pub static FIGHTER_MANAGER_ADDR: RwLock<usize> = RwLock::new(0);
pub static ITEM_MANAGER_ADDR: RwLock<usize> = RwLock::new(0);
pub static STAGE_MANAGER_ADDR: RwLock<usize> = RwLock::new(0);
pub static mut TRAINING_MENU_ADDR: *mut PauseMenu = core::ptr::null_mut();
#[cfg(not(feature = "outside_training_mode"))]
@ -74,7 +73,7 @@ pub fn try_get_module_accessor(
let entry_id_int = fighter_id as i32;
let entry_id = app::FighterEntryID(entry_id_int);
unsafe {
let mgr = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager);
let mgr = *(read(&FIGHTER_MANAGER_ADDR) as *mut *mut app::FighterManager);
let fighter_entry =
FighterManager::get_fighter_entry(mgr, entry_id) as *mut app::FighterEntry;
if fighter_entry.is_null() {
@ -105,7 +104,7 @@ pub fn is_operation_cpu(module_accessor: &mut app::BattleObjectModuleAccessor) -
}
let entry_id = app::FighterEntryID(entry_id_int);
let mgr = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager);
let mgr = *(read(&FIGHTER_MANAGER_ADDR) as *mut *mut app::FighterManager);
let fighter_information = FighterManager::get_fighter_information(mgr, entry_id);
FighterInformation::is_operation_cpu(fighter_information)
@ -269,12 +268,12 @@ pub unsafe fn is_in_landing(module_accessor: &mut app::BattleObjectModuleAccesso
// Returns true if a match is currently active
pub unsafe fn is_ready_go() -> bool {
let fighter_manager = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager);
let fighter_manager = *(read(&FIGHTER_MANAGER_ADDR) as *mut *mut app::FighterManager);
FighterManager::is_ready_go(fighter_manager)
}
pub unsafe fn entry_count() -> i32 {
let fighter_manager = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager);
let fighter_manager = *(read(&FIGHTER_MANAGER_ADDR) as *mut *mut app::FighterManager);
FighterManager::entry_count(fighter_manager)
}

View file

@ -1,7 +1,7 @@
// TODO!(Do all these need to be referenced by offset? Or do some of them have symbols?)
#![cfg_attr(rustfmt, rustfmt_skip)] // We want the assembly needles to stay in lines of four bytes each
use crate::logging::*;
use lazy_static::lazy_static;
use training_mod_sync::LazyLock;
// Stolen from HDR who stole it from Arcropolis
// https://github.com/HDR-Development/HewDraw-Remix/blob/dev/dynamic/src/util.rs
@ -36,9 +36,7 @@ fn find_offset(name: &str, needle: &[u8]) -> Option<usize> {
macro_rules! impl_offset {
($fn_name:ident) => {
paste::paste! {
lazy_static! {
pub static ref [<OFFSET_ $fn_name>]: usize = find_offset(stringify!($fn_name), [<NEEDLE_ $fn_name>]).expect(stringify!(Failed to find offset for $fn_name));
}
pub static [<OFFSET_ $fn_name>]: LazyLock<usize> = LazyLock::new(|| find_offset(stringify!($fn_name), [<NEEDLE_ $fn_name>]).expect(stringify!(Failed to find offset for $fn_name)));
}
}
}

View file

@ -1,22 +1,20 @@
#![allow(clippy::unnecessary_unwrap)]
use crate::common::dialog;
use crate::consts::*;
use crate::logging::*;
use anyhow::{anyhow, Result};
use lazy_static::lazy_static;
use parking_lot::Mutex;
use serde_json::Value;
use zip::ZipArchive;
lazy_static! {
pub static ref CURRENT_VERSION: Mutex<String> = Mutex::new({
info!("Initialized lazy_static: CURRENT_VERSION");
match get_current_version() {
Ok(v) => v,
Err(e) => panic!("Could not find current modpack version!: {}", e),
}
});
}
use crate::common::dialog;
use crate::consts::*;
use crate::logging::*;
use training_mod_sync::*;
pub static CURRENT_VERSION: LazyLock<String> = LazyLock::new(|| {
info!("Initialized lazy static value: CURRENT_VERSION");
match get_current_version() {
Ok(v) => v,
Err(e) => panic!("Could not find current modpack version!: {}", e),
}
});
#[derive(Debug)]
pub struct Release {
@ -72,13 +70,12 @@ impl Release {
// alphabetical order == chronological order
//
// https://datatracker.ietf.org/doc/html/rfc3339#section-5.1
let current_version = CURRENT_VERSION.lock();
self.published_at.as_str() <= current_version.as_str()
self.published_at.as_str() <= (*CURRENT_VERSION).as_str()
}
}
fn get_update_policy() -> UpdatePolicy {
unsafe { MENU.update_policy }
read(&MENU).update_policy
}
fn get_release(beta: bool) -> Result<Release> {
@ -190,10 +187,8 @@ pub fn perform_version_check() {
};
if release_to_apply.is_ok() {
let published_at = release_to_apply.as_ref().unwrap().published_at.clone();
let current_version = CURRENT_VERSION.lock();
info!("Current version: {}", current_version);
info!("Current version: {}", *CURRENT_VERSION);
info!("Github version: {}", published_at);
drop(current_version); // Explicitly unlock, since we also acquire a lock in is_older_than_installed()
if release_to_apply.as_ref().unwrap().is_older_than_installed() {
release_to_apply = Err(anyhow!(
"Github version is not newer than the current installed version.",

View file

@ -1,19 +1,17 @@
#![allow(dead_code)]
#![allow(unused_assignments)]
#![allow(unused_variables)]
use skyline::error::show_error;
use skyline::hook;
use skyline::hooks::A64InlineHook;
use skyline::text_iter::{add_get_imm, adrp_get_imm, Instruction::*, TextIter};
use smash::app::smashball::is_training_mode;
use HazardState::*;
use HookState::*;
use crate::common::consts::*;
use crate::logging::*;
use training_mod_sync::*;
use HazardState::*;
use HookState::*;
enum HazardState {
Begin,
Adrp1,
@ -29,6 +27,9 @@ enum HookState {
Ldrsw2,
}
static HAZARD_FLAG_ADDRESS: LazyLock<usize> = LazyLock::new(get_hazard_flag_address);
static LOAD_ADDRESS: LazyLock<usize> = LazyLock::new(get_hazard_hook_address);
fn get_hazard_flag_address() -> usize {
let mut state = HazardState::Begin;
let mut flag_pos = 0;
@ -81,12 +82,8 @@ fn get_hazard_hook_address() -> usize {
flag_pos
}
// 8.1.0 Defaults
static mut HAZARD_FLAG_ADDRESS: *mut u8 = 0x04eb_bf95 as *mut u8;
static mut LOAD_ADDRESS: usize = 0x0214_bde8;
#[hook(offset = LOAD_ADDRESS, inline)]
fn hazard_intercept(ctx: &skyline::hooks::InlineCtx) {
#[hook(offset = *LOAD_ADDRESS, inline)]
fn hazard_intercept(_ctx: &skyline::hooks::InlineCtx) {
unsafe {
if is_training_mode() {
mod_handle_hazards();
@ -96,22 +93,20 @@ fn hazard_intercept(ctx: &skyline::hooks::InlineCtx) {
fn mod_handle_hazards() {
unsafe {
*HAZARD_FLAG_ADDRESS = (MENU.stage_hazards == OnOff::ON) as u8;
let address = *HAZARD_FLAG_ADDRESS as *mut u8;
*address = (read(&MENU).stage_hazards == OnOff::ON) as u8;
}
}
unsafe fn validate_hazards_addrs() -> Result<(), ()> {
HAZARD_FLAG_ADDRESS = get_hazard_flag_address() as *mut u8;
LOAD_ADDRESS = get_hazard_hook_address();
let mut error_string: String = String::new();
let mut error_id = 0;
if HAZARD_FLAG_ADDRESS.is_null() {
if *HAZARD_FLAG_ADDRESS == 0 {
error_string += &String::from("The Ultimate Training Modpack was unable to locate stage loading code in your version of the game.\n\n");
error_id += 1000;
}
if LOAD_ADDRESS == 0 {
if *LOAD_ADDRESS == 0 {
error_string += &String::from("The Ultimate Training Modpack was unable to locate the global hazard address in your version of the game.\n\n");
error_id += 1000;
}
@ -133,10 +128,8 @@ pub fn hazard_manager() {
info!("Applying hazard control mods.");
unsafe {
if let Ok(()) = validate_hazards_addrs() {
HAZARD_FLAG_ADDRESS = get_hazard_flag_address() as *mut u8;
LOAD_ADDRESS = get_hazard_hook_address();
A64InlineHook(
LOAD_ADDRESS as *const skyline::libc::c_void,
(*LOAD_ADDRESS) as *const skyline::libc::c_void,
hazard_intercept as *const skyline::libc::c_void,
);
}

View file

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

View file

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

View file

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

View file

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

View file

@ -8,60 +8,59 @@ use crate::is_operation_cpu;
use crate::training::frame_counter;
use crate::training::handle_add_limit;
use once_cell::sync::Lazy;
use training_mod_sync::*;
static mut BUFF_REMAINING_PLAYER: usize = 0;
static mut BUFF_REMAINING_CPU: usize = 0;
static BUFF_REMAINING_PLAYER: RwLock<usize> = RwLock::new(0);
static BUFF_REMAINING_CPU: RwLock<usize> = RwLock::new(0);
static mut IS_BUFFING_PLAYER: bool = false;
static mut IS_BUFFING_CPU: bool = false;
static IS_BUFFING_PLAYER: RwLock<bool> = RwLock::new(false);
static IS_BUFFING_CPU: RwLock<bool> = RwLock::new(false);
static BUFF_DELAY_COUNTER: Lazy<usize> =
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
static BUFF_DELAY_COUNTER: LazyLock<usize> =
LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
pub unsafe fn restart_buff(module_accessor: &mut app::BattleObjectModuleAccessor) {
pub fn restart_buff(module_accessor: &mut app::BattleObjectModuleAccessor) {
if is_operation_cpu(module_accessor) {
IS_BUFFING_CPU = false;
return;
assign(&IS_BUFFING_CPU, false);
} else {
assign(&IS_BUFFING_PLAYER, false);
}
IS_BUFFING_PLAYER = false;
}
pub unsafe fn start_buff(module_accessor: &mut app::BattleObjectModuleAccessor) {
pub fn start_buff(module_accessor: &mut app::BattleObjectModuleAccessor) {
if is_operation_cpu(module_accessor) {
IS_BUFFING_CPU = true;
return;
assign(&IS_BUFFING_CPU, true);
} else {
assign(&IS_BUFFING_PLAYER, true);
}
IS_BUFFING_PLAYER = true;
}
pub unsafe fn is_buffing(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
pub fn is_buffing(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
if is_operation_cpu(module_accessor) {
return IS_BUFFING_CPU;
read(&IS_BUFFING_CPU)
} else {
read(&IS_BUFFING_PLAYER)
}
IS_BUFFING_PLAYER
}
pub unsafe fn is_buffing_any() -> bool {
IS_BUFFING_CPU || IS_BUFFING_PLAYER
pub fn is_buffing_any() -> bool {
read(&IS_BUFFING_CPU) || read(&IS_BUFFING_PLAYER)
}
pub unsafe fn set_buff_rem(
module_accessor: &mut app::BattleObjectModuleAccessor,
new_value: usize,
) {
pub fn set_buff_rem(module_accessor: &mut app::BattleObjectModuleAccessor, new_value: usize) {
if is_operation_cpu(module_accessor) {
BUFF_REMAINING_CPU = new_value;
return;
assign(&BUFF_REMAINING_CPU, new_value);
} else {
assign(&BUFF_REMAINING_PLAYER, new_value);
}
BUFF_REMAINING_PLAYER = new_value;
}
pub unsafe fn get_buff_rem(module_accessor: &mut app::BattleObjectModuleAccessor) -> usize {
pub fn get_buff_rem(module_accessor: &mut app::BattleObjectModuleAccessor) -> usize {
if is_operation_cpu(module_accessor) {
return BUFF_REMAINING_CPU;
read(&BUFF_REMAINING_CPU)
} else {
read(&BUFF_REMAINING_PLAYER)
}
BUFF_REMAINING_PLAYER
}
pub unsafe fn handle_buffs(
@ -79,7 +78,7 @@ pub unsafe fn handle_buffs(
CameraModule::stop_quake(module_accessor, *CAMERA_QUAKE_KIND_M); // stops Psyche-Up quake
CameraModule::stop_quake(module_accessor, *CAMERA_QUAKE_KIND_S); // stops Monado Art quake
let menu_vec = MENU.buff_state;
let menu_vec = read(&MENU).buff_state;
if fighter_kind == *FIGHTER_KIND_BRAVE {
return buff_hero(module_accessor, status);
@ -104,7 +103,7 @@ pub unsafe fn handle_buffs(
}
unsafe fn buff_hero(module_accessor: &mut app::BattleObjectModuleAccessor, status: i32) -> bool {
let buff_vec: Vec<BuffOption> = MENU.buff_state.hero_buffs().to_vec();
let buff_vec: Vec<BuffOption> = read(&MENU).buff_state.hero_buffs().to_vec();
if !is_buffing(module_accessor) {
// Initial set up for spells
start_buff(module_accessor);
@ -241,7 +240,7 @@ unsafe fn buff_sepiroth(module_accessor: &mut app::BattleObjectModuleAccessor) -
unsafe fn buff_wario(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
if !is_buffing(module_accessor) {
let waft_level: BuffOption = MENU.buff_state.wario_buffs().get_random();
let waft_level: BuffOption = read(&MENU).buff_state.wario_buffs().get_random();
let waft_count_secs = match waft_level {
BuffOption::WAFT_MINI => WorkModule::get_param_float(
module_accessor,
@ -277,7 +276,7 @@ unsafe fn buff_wario(module_accessor: &mut app::BattleObjectModuleAccessor) -> b
}
unsafe fn buff_shulk(module_accessor: &mut app::BattleObjectModuleAccessor, status: i32) -> bool {
let current_art = MENU.buff_state.shulk_buffs().get_random();
let current_art = read(&MENU).buff_state.shulk_buffs().get_random();
if current_art == BuffOption::empty() {
// No Monado Arts selected in the buff menu, so we don't need to buff
return true;

View file

@ -1,3 +1,4 @@
// TODO!() There's some crazy pointer magic happening in this file, we should try to refactor to avoid that
use smash::app;
use smash::app::lua_bind::*;
use smash::app::ItemKind;
@ -9,7 +10,10 @@ use crate::common::consts::*;
use crate::common::*;
use crate::offsets::OFFSET_GENERATE_ARTICLE_FOR_TARGET;
use crate::training::mash;
use training_mod_sync::*;
pub static TURNIP_CHOSEN: RwLock<Option<u32>> = RwLock::new(None);
pub static TARGET_PLAYER: RwLock<Option<BattleObjectModuleAccessor>> = RwLock::new(None);
pub struct CharItem {
pub fighter_kind: LuaConst,
pub item_kind: Option<LuaConst>,
@ -326,9 +330,6 @@ pub const ALL_CHAR_ITEMS: [CharItem; 45] = [
},
];
pub static mut TURNIP_CHOSEN: Option<u32> = None;
pub static mut TARGET_PLAYER: Option<*mut BattleObjectModuleAccessor> = None;
unsafe fn apply_single_item(player_fighter_kind: i32, item: &CharItem) {
let player_module_accessor = get_module_accessor(FighterId::Player);
let cpu_module_accessor = get_module_accessor(FighterId::CPU);
@ -352,7 +353,7 @@ unsafe fn apply_single_item(player_fighter_kind: i32, item: &CharItem) {
if player_fighter_kind != *FIGHTER_KIND_LINK {
ItemModule::drop_item(cpu_module_accessor, 0.0, 0.0, 0);
//ItemModule::eject_have_item(cpu_module_accessor, 0, false, false);
let item_mgr = *(ITEM_MANAGER_ADDR as *mut *mut app::ItemManager);
let item_mgr = *(read(&ITEM_MANAGER_ADDR) as *mut *mut app::ItemManager);
let item_ptr = ItemManager::get_active_item(item_mgr, 0);
ItemModule::have_item_instance(
player_module_accessor,
@ -377,25 +378,26 @@ unsafe fn apply_single_item(player_fighter_kind: i32, item: &CharItem) {
});
item.article_kind.as_ref().map(|article_kind| {
TURNIP_CHOSEN = if [*ITEM_VARIATION_PEACHDAIKON_8, *ITEM_VARIATION_DAISYDAIKON_8]
.contains(&variation)
{
Some(8)
} else if [*ITEM_VARIATION_PEACHDAIKON_7, *ITEM_VARIATION_DAISYDAIKON_7]
.contains(&variation)
{
Some(7)
} else if [*ITEM_VARIATION_PEACHDAIKON_6, *ITEM_VARIATION_DAISYDAIKON_6]
.contains(&variation)
{
Some(6)
} else if [*ITEM_VARIATION_PEACHDAIKON_1, *ITEM_VARIATION_DAISYDAIKON_1]
.contains(&variation)
{
Some(1)
} else {
None
};
assign(
&TURNIP_CHOSEN,
if [*ITEM_VARIATION_PEACHDAIKON_8, *ITEM_VARIATION_DAISYDAIKON_8].contains(&variation) {
Some(8)
} else if [*ITEM_VARIATION_PEACHDAIKON_7, *ITEM_VARIATION_DAISYDAIKON_7]
.contains(&variation)
{
Some(7)
} else if [*ITEM_VARIATION_PEACHDAIKON_6, *ITEM_VARIATION_DAISYDAIKON_6]
.contains(&variation)
{
Some(6)
} else if [*ITEM_VARIATION_PEACHDAIKON_1, *ITEM_VARIATION_DAISYDAIKON_1]
.contains(&variation)
{
Some(1)
} else {
None
},
);
let article_kind = **article_kind;
if article_kind == FIGHTER_DIDDY_GENERATE_ARTICLE_ITEM_BANANA {
@ -416,7 +418,7 @@ unsafe fn apply_single_item(player_fighter_kind: i32, item: &CharItem) {
false,
);
// Grab item from the middle of the stage where it gets shot
let item_mgr = *(ITEM_MANAGER_ADDR as *mut *mut app::ItemManager);
let item_mgr = *(read(&ITEM_MANAGER_ADDR) as *mut *mut app::ItemManager);
let item = ItemManager::get_active_item(item_mgr, 0);
ItemModule::have_item_instance(
player_module_accessor,
@ -428,16 +430,18 @@ unsafe fn apply_single_item(player_fighter_kind: i32, item: &CharItem) {
false,
);
} else {
TARGET_PLAYER = Some(player_module_accessor); // set so we generate CPU article on the player (in dittos, items always belong to player, even if cpu item is chosen)
// Set the target player so we generate CPU article on the player during handle_generate_article_for_target
// (in dittos, items always belong to player, even if cpu item is chosen)
assign(&TARGET_PLAYER, Some(*player_module_accessor));
ArticleModule::generate_article(
generator_module_accessor, // we want CPU's article
article_kind,
false,
0,
);
TARGET_PLAYER = None;
assign(&TARGET_PLAYER, None);
}
TURNIP_CHOSEN = None;
assign(&TURNIP_CHOSEN, None);
});
}
@ -479,9 +483,10 @@ macro_rules! daikon_replace {
pub unsafe fn [<handle_ $char daikon_ $num _prob>]() -> f32 {
let orig = original!()();
if is_training_mode() {
if TURNIP_CHOSEN == Some($num) {
let turnip_chosen = read(&TURNIP_CHOSEN);
if turnip_chosen == Some($num) {
return 58.0;
} else if TURNIP_CHOSEN != None {
} else if turnip_chosen != None {
return 0.0;
}
}
@ -512,14 +517,14 @@ daikon_replace!(DAISY, daisy, 1);
// GenerateArticleForTarget for Peach/Diddy(/Link?) item creation
#[skyline::hook(offset = *OFFSET_GENERATE_ARTICLE_FOR_TARGET)]
pub unsafe fn handle_generate_article_for_target(
article_module_accessor: *mut BattleObjectModuleAccessor,
article_module_accessor: BattleObjectModuleAccessor,
int_1: i32,
module_accessor: *mut BattleObjectModuleAccessor, // this is always 0x0 normally
module_accessor: BattleObjectModuleAccessor, // this is always 0x0 normally
bool_1: bool,
int_2: i32,
) -> u64 {
// unknown return value, gets cast to an (Article *)
let target_module_accessor = TARGET_PLAYER.unwrap_or(module_accessor);
let target_module_accessor = read(&TARGET_PLAYER).unwrap_or(module_accessor);
original!()(
article_module_accessor,

View file

@ -1,15 +1,16 @@
use crate::offsets::OFFSET_POKEMON_DECIDE;
use crate::training::frame_counter;
use crate::training::save_states;
use once_cell::sync::Lazy;
use skyline::hooks::InlineCtx;
use smash::app::{self, lua_bind::*, smashball::is_training_mode};
use smash::hash40;
use smash::lib::lua_const::*;
use smash::phx::Hash40;
static SWITCH_DELAY_COUNTER: Lazy<usize> =
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
use training_mod_sync::LazyLock;
static SWITCH_DELAY_COUNTER: LazyLock<usize> =
LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
pub unsafe fn is_switched(ptrainer_module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
let status_kind = StatusModule::status_kind(ptrainer_module_accessor);

View file

@ -5,12 +5,14 @@ use smash::phx::{Hash40, Vector3f};
use crate::common::consts::*;
use crate::common::*;
use training_mod_sync::*;
static mut COUNTER: u32 = 0;
static mut CLATTER_STEP: f32 = 8.0;
static COUNTER: RwLock<u32> = RwLock::new(0);
static CLATTER_STEP: RwLock<f32> = RwLock::new(8.0);
unsafe fn do_clatter_input(module_accessor: &mut BattleObjectModuleAccessor) {
ControlModule::add_clatter_time(module_accessor, -1.0 * CLATTER_STEP, 0);
let clatter_step = read(&CLATTER_STEP);
ControlModule::add_clatter_time(module_accessor, -1.0 * clatter_step, 0);
let zeros = Vector3f {
x: 0.0,
y: 0.0,
@ -45,10 +47,11 @@ pub unsafe fn handle_clatter(module_accessor: &mut BattleObjectModuleAccessor) {
// Don't do clatter inputs if we're not in clatter
return;
}
let repeat = MENU.clatter_strength.into_u32();
let repeat = read(&MENU).clatter_strength.into_u32();
COUNTER = (COUNTER + 1) % repeat;
if COUNTER == repeat - 1 {
let mut counter_lock = lock_write(&COUNTER);
*counter_lock = ((*counter_lock) + 1) % repeat;
if *counter_lock == repeat - 1 {
do_clatter_input(module_accessor);
}
}
@ -71,7 +74,7 @@ pub unsafe fn hook_start_clatter(
// Most of the time this is 8 frames, but could be less depending on
// the status (e.g. freeze is 4 frames / input)
if is_training_mode() && is_operation_cpu(module_accessor) {
CLATTER_STEP = manual_recovery_rate;
assign(&CLATTER_STEP, manual_recovery_rate);
}
original!()(
module_accessor,

View file

@ -1,168 +1,174 @@
use skyline::nn::ui2d::ResColor;
use training_mod_consts::OnOff;
use smash::app::lua_bind::{CancelModule, StatusModule, WorkModule};
use smash::app::BattleObjectModuleAccessor;
use smash::lib::lua_const::*;
use crate::common::*;
use crate::training::*;
use crate::consts::Action;
use crate::get_module_accessor;
use crate::training::frame_counter;
use crate::training::ui::notifications;
use once_cell::sync::Lazy;
use training_mod_consts::{FighterId, OnOff, MENU};
use training_mod_sync::*;
pub static mut FRAME_ADVANTAGE: i32 = 0;
static mut PLAYER_ACTIONABLE: bool = false;
static mut CPU_ACTIONABLE: bool = false;
static mut PLAYER_ACTIVE_FRAME: u32 = 0;
static mut CPU_ACTIVE_FRAME: u32 = 0;
static mut FRAME_ADVANTAGE_CHECK: bool = false;
static PLAYER_WAS_ACTIONABLE: RwLock<bool> = RwLock::new(false);
static CPU_WAS_ACTIONABLE: RwLock<bool> = RwLock::new(false);
static FRAME_COUNTER_INDEX: Lazy<usize> =
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
static PLAYER_FRAME_COUNTER_INDEX: LazyLock<usize> =
LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
static CPU_FRAME_COUNTER_INDEX: LazyLock<usize> =
LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
unsafe fn _was_in_hitstun(module_accessor: *mut BattleObjectModuleAccessor) -> bool {
unsafe fn was_in_hitstun(module_accessor: *mut BattleObjectModuleAccessor) -> bool {
let prev_status = StatusModule::prev_status_kind(module_accessor, 0);
(*FIGHTER_STATUS_KIND_DAMAGE..*FIGHTER_STATUS_KIND_DAMAGE_FALL).contains(&prev_status)
}
unsafe fn is_in_hitstun(module_accessor: *mut BattleObjectModuleAccessor) -> bool {
(*FIGHTER_STATUS_KIND_DAMAGE..*FIGHTER_STATUS_KIND_DAMAGE_FALL)
.contains(&StatusModule::status_kind(module_accessor))
}
unsafe fn was_in_shieldstun(module_accessor: *mut BattleObjectModuleAccessor) -> bool {
let prev_status = StatusModule::prev_status_kind(module_accessor, 0);
prev_status == FIGHTER_STATUS_KIND_GUARD_DAMAGE
}
macro_rules! actionable_statuses {
() => {
[
FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_ESCAPE_AIR,
FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_ATTACK_AIR,
FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_GUARD_ON,
FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_ESCAPE,
]
};
unsafe fn is_in_shieldstun(module_accessor: *mut BattleObjectModuleAccessor) -> bool {
StatusModule::status_kind(module_accessor) == FIGHTER_STATUS_KIND_GUARD_DAMAGE
}
unsafe fn is_actionable(module_accessor: *mut BattleObjectModuleAccessor) -> bool {
actionable_statuses!().iter().any(|actionable_transition| {
[
FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_ESCAPE_AIR, // Airdodge
FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_ATTACK_AIR, // Aerial
FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_GUARD_ON, // Shield
FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_ESCAPE, // Spotdodge/Roll
FIGHTER_STATUS_TRANSITION_TERM_ID_DOWN_STAND, // Neutral Getup from Tech/Slip
]
.iter()
.any(|actionable_transition| {
WorkModule::is_enable_transition_term(module_accessor, **actionable_transition)
}) || CancelModule::is_enable_cancel(module_accessor)
}
fn update_frame_advantage(new_frame_adv: i32) {
unsafe {
FRAME_ADVANTAGE = new_frame_adv;
if MENU.frame_advantage == OnOff::ON {
// Prioritize Frame Advantage over Input Recording Playback
ui::notifications::clear_notifications("Input Recording");
ui::notifications::clear_notifications("Frame Advantage");
ui::notifications::color_notification(
"Frame Advantage".to_string(),
format!("{FRAME_ADVANTAGE}"),
60,
match FRAME_ADVANTAGE {
x if x < 0 => ResColor {
r: 200,
g: 8,
b: 8,
a: 255,
},
0 => ResColor {
r: 0,
g: 0,
b: 0,
a: 255,
},
_ => ResColor {
r: 31,
g: 198,
b: 0,
a: 255,
},
fn update_frame_advantage(frame_advantage: i32) {
if read(&MENU).frame_advantage == OnOff::ON {
// Prioritize Frame Advantage over Input Recording Playback
notifications::clear_notification("Input Recording");
notifications::clear_notification("Frame Advantage");
notifications::color_notification(
"Frame Advantage".to_string(),
format!("{frame_advantage}"),
60,
match frame_advantage {
x if x < 0 => ResColor {
r: 200,
g: 8,
b: 8,
a: 255,
},
);
}
0 => ResColor {
r: 0,
g: 0,
b: 0,
a: 255,
},
_ => ResColor {
r: 31,
g: 198,
b: 0,
a: 255,
},
},
);
}
}
pub unsafe fn is_enable_transition_term(
module_accessor: *mut BattleObjectModuleAccessor,
transition_term: i32,
is: bool,
) {
pub unsafe fn once_per_frame(module_accessor: &mut BattleObjectModuleAccessor) {
// Skip the CPU so we don't run twice per frame
let entry_id_int = WorkModule::get_int(module_accessor, *FIGHTER_INSTANCE_WORK_ID_INT_ENTRY_ID);
if entry_id_int != (FighterId::Player as i32) {
return;
}
// Extra check later in the frame.
// This is in the case that the transition term becomes enabled after our initial check
// and the user buffers that action on that frame.
if !PLAYER_ACTIONABLE
&& ((is
&& actionable_statuses!()
.iter()
.any(|actionable_transition| *actionable_transition == transition_term))
|| (CancelModule::is_enable_cancel(module_accessor)))
{
PLAYER_ACTIVE_FRAME = frame_counter::get_frame_count(*FRAME_COUNTER_INDEX);
PLAYER_ACTIONABLE = true;
// if both are now active
if PLAYER_ACTIONABLE && CPU_ACTIONABLE && FRAME_ADVANTAGE_CHECK {
let cpu_module_accessor = get_module_accessor(FighterId::CPU);
if was_in_shieldstun(cpu_module_accessor) {
update_frame_advantage(
(CPU_ACTIVE_FRAME as i64 - PLAYER_ACTIVE_FRAME as i64) as i32,
);
}
frame_counter::stop_counting(*FRAME_COUNTER_INDEX);
FRAME_ADVANTAGE_CHECK = false;
}
}
}
pub unsafe fn get_command_flag_cat(module_accessor: &mut BattleObjectModuleAccessor) {
let entry_id_int = WorkModule::get_int(module_accessor, *FIGHTER_INSTANCE_WORK_ID_INT_ENTRY_ID);
// do only once.
if entry_id_int != (FighterId::Player as i32) {
return;
}
let player_module_accessor = get_module_accessor(FighterId::Player);
let cpu_module_accessor = get_module_accessor(FighterId::CPU);
let player_is_actionable = is_actionable(player_module_accessor);
let player_was_actionable = read(&PLAYER_WAS_ACTIONABLE);
let player_just_actionable = !player_was_actionable && player_is_actionable;
let cpu_is_actionable = is_actionable(cpu_module_accessor);
let cpu_was_actionable = read(&CPU_WAS_ACTIONABLE);
let cpu_just_actionable = !cpu_was_actionable && cpu_is_actionable;
let is_counting = frame_counter::is_counting(*PLAYER_FRAME_COUNTER_INDEX)
|| frame_counter::is_counting(*CPU_FRAME_COUNTER_INDEX);
// Use to factor in that we should only update frame advantage if
// there's been a hit that connects
// if AttackModule::is_infliction(
// player_module_accessor,
// *COLLISION_KIND_MASK_HIT | *COLLISION_KIND_MASK_SHIELD) {
// the frame the fighter *becomes* actionable
if !CPU_ACTIONABLE && is_actionable(cpu_module_accessor) {
CPU_ACTIVE_FRAME = frame_counter::get_frame_count(*FRAME_COUNTER_INDEX);
}
if !PLAYER_ACTIONABLE && is_actionable(player_module_accessor) {
PLAYER_ACTIVE_FRAME = frame_counter::get_frame_count(*FRAME_COUNTER_INDEX);
}
CPU_ACTIONABLE = is_actionable(cpu_module_accessor);
PLAYER_ACTIONABLE = is_actionable(player_module_accessor);
// if neither are active
if !CPU_ACTIONABLE && !PLAYER_ACTIONABLE {
if !FRAME_ADVANTAGE_CHECK {
frame_counter::reset_frame_count(*FRAME_COUNTER_INDEX);
frame_counter::start_counting(*FRAME_COUNTER_INDEX);
if !is_counting {
if read(&MENU).mash_state == Action::empty()
&& !player_is_actionable
&& !cpu_is_actionable
&& (!was_in_shieldstun(cpu_module_accessor) && is_in_shieldstun(cpu_module_accessor)
|| (!was_in_hitstun(cpu_module_accessor) && is_in_hitstun(cpu_module_accessor)))
{
// Start counting when:
// 1. We have no mash option selected AND
// 2. Neither fighter is currently actionable AND
// 3. Either
// a. the CPU has just entered shieldstun
// b. the CPU has just entered hitstun
//
// If a mash option is selected, this can interfere with our ability to determine when
// a character becomes actionable. So don't ever start counting if we can't reliably stop.
//
// Since our "just_actionable" checks assume that neither character is already actionable,
// we need to guard against instances where the player is already actionable by the time that
// the CPU get hit, such as if the player threw a projectile from far away.
// Otherwise our "just_actionable" checks are not valid.
//
// We also need to guard against instances where the CPU's status is in hitstun but they are actually actionable.
// I dunno, makes no sense to me either. Can trigger this edge case with PAC-MAN jab 1 against Lucas at 0%.
// This shows up as the count restarting immediately after the last one ended.
frame_counter::reset_frame_count(*PLAYER_FRAME_COUNTER_INDEX);
frame_counter::reset_frame_count(*CPU_FRAME_COUNTER_INDEX);
frame_counter::start_counting(*PLAYER_FRAME_COUNTER_INDEX);
frame_counter::start_counting(*CPU_FRAME_COUNTER_INDEX);
}
FRAME_ADVANTAGE_CHECK = true;
}
} else {
// Uncomment this if you want some frame logging
// if (player_is_actionable && cpu_is_actionable) {
// info!("!");
// } else if (!player_is_actionable && cpu_is_actionable) {
// info!("-");
// } else if (player_is_actionable && !cpu_is_actionable) {
// info!("+");
// } else {
// info!(".");
// }
// if both are now active
if PLAYER_ACTIONABLE && CPU_ACTIONABLE && FRAME_ADVANTAGE_CHECK {
if was_in_shieldstun(cpu_module_accessor) {
update_frame_advantage((CPU_ACTIVE_FRAME as i64 - PLAYER_ACTIVE_FRAME as i64) as i32);
// Stop counting as soon as each fighter becomes actionable
if player_just_actionable {
frame_counter::stop_counting(*PLAYER_FRAME_COUNTER_INDEX);
}
frame_counter::stop_counting(*FRAME_COUNTER_INDEX);
FRAME_ADVANTAGE_CHECK = false;
if cpu_just_actionable {
frame_counter::stop_counting(*CPU_FRAME_COUNTER_INDEX);
}
// If we just finished counting for the second fighter, then display frame advantage
if !frame_counter::is_counting(*PLAYER_FRAME_COUNTER_INDEX)
&& !frame_counter::is_counting(*CPU_FRAME_COUNTER_INDEX)
&& (player_just_actionable || cpu_just_actionable)
{
update_frame_advantage(
frame_counter::get_frame_count(*CPU_FRAME_COUNTER_INDEX) as i32
- frame_counter::get_frame_count(*PLAYER_FRAME_COUNTER_INDEX) as i32,
);
// Frame counters should reset before we start again, but reset them just to be safe
frame_counter::reset_frame_count(*PLAYER_FRAME_COUNTER_INDEX);
frame_counter::reset_frame_count(*CPU_FRAME_COUNTER_INDEX);
};
// Store the current actionability state for next frame
assign(&PLAYER_WAS_ACTIONABLE, player_is_actionable);
assign(&CPU_WAS_ACTIONABLE, cpu_is_actionable);
}
}

View file

@ -4,13 +4,15 @@ use smash::lib::lua_const::*;
use crate::common::consts::OnOff;
use crate::common::*;
use training_mod_sync::*;
pub unsafe fn mod_get_stick_y(module_accessor: &mut BattleObjectModuleAccessor) -> Option<f32> {
if !is_operation_cpu(module_accessor) {
return None;
}
let fighter_status_kind = StatusModule::status_kind(module_accessor);
if MENU.crouch == OnOff::ON
if read(&MENU).crouch == OnOff::ON
&& [
*FIGHTER_STATUS_KIND_WAIT,
*FIGHTER_STATUS_KIND_SQUAT,

View file

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

View file

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

View file

@ -1,3 +1,6 @@
use training_mod_sync::*;
static COUNTERS: RwLock<Vec<FrameCounter>> = RwLock::new(vec![]);
#[derive(PartialEq, Eq)]
pub enum FrameCounterType {
InGame,
@ -13,38 +16,35 @@ pub struct FrameCounter {
counter_type: FrameCounterType,
}
static mut COUNTERS: Vec<FrameCounter> = vec![];
pub fn register_counter(counter_type: FrameCounterType) -> usize {
unsafe {
let index = COUNTERS.len();
COUNTERS.push(FrameCounter {
count: 0,
should_count: false,
counter_type,
});
index
}
let mut counters_lock = lock_write(&COUNTERS);
let index = (*counters_lock).len();
(*counters_lock).push(FrameCounter {
count: 0,
should_count: false,
counter_type,
});
index
}
pub fn start_counting(index: usize) {
unsafe {
COUNTERS[index].should_count = true;
}
let mut counters_lock = lock_write(&COUNTERS);
(*counters_lock)[index].should_count = true;
}
pub fn stop_counting(index: usize) {
unsafe {
COUNTERS[index].should_count = false;
}
let mut counters_lock = lock_write(&COUNTERS);
(*counters_lock)[index].should_count = false;
}
pub fn is_counting(index: usize) -> bool {
let counters_lock = lock_read(&COUNTERS);
(*counters_lock)[index].should_count
}
pub fn reset_frame_count(index: usize) {
unsafe {
COUNTERS[index].count = 0;
}
let mut counters_lock = lock_write(&COUNTERS);
(*counters_lock)[index].count = 0;
}
pub fn full_reset(index: usize) {
@ -75,47 +75,48 @@ pub fn should_delay(delay: u32, index: usize) -> bool {
}
pub fn get_frame_count(index: usize) -> u32 {
unsafe { COUNTERS[index].count }
let counters_lock = lock_read(&COUNTERS);
(*counters_lock)[index].count
}
pub fn tick_idx(index: usize) {
unsafe {
COUNTERS[index].count += 1;
}
let mut counters_lock = lock_write(&COUNTERS);
(*counters_lock)[index].count += 1;
}
pub fn tick_ingame() {
unsafe {
for (index, counter) in COUNTERS.iter().enumerate() {
if !counter.should_count || counter.counter_type == FrameCounterType::Real {
continue;
}
tick_idx(index);
let mut counters_lock = lock_write(&COUNTERS);
for counter in (*counters_lock).iter_mut() {
if !counter.should_count || counter.counter_type == FrameCounterType::Real {
continue;
}
// same as full_reset, but we already have the lock so we can't lock again
counter.count += 1;
}
}
pub fn tick_real() {
unsafe {
for (index, counter) in COUNTERS.iter().enumerate() {
if !counter.should_count
|| (counter.counter_type == FrameCounterType::InGame
|| counter.counter_type == FrameCounterType::InGameNoReset)
{
continue;
}
tick_idx(index);
let mut counters_lock = lock_write(&COUNTERS);
for counter in (*counters_lock).iter_mut() {
if !counter.should_count
|| (counter.counter_type == FrameCounterType::InGame
|| counter.counter_type == FrameCounterType::InGameNoReset)
{
continue;
}
// same as full_reset, but we already have the lock so we can't lock again
counter.count += 1;
}
}
pub fn reset_all() {
unsafe {
for (index, counter) in COUNTERS.iter().enumerate() {
if counter.counter_type != FrameCounterType::InGame {
continue;
}
full_reset(index);
let mut counters_lock = lock_write(&COUNTERS);
for counter in (*counters_lock).iter_mut() {
if counter.counter_type != FrameCounterType::InGame {
continue;
}
// same as full_reset, but we already have the lock so we can't lock again
counter.count = 0;
counter.should_count = false;
}
}

View file

@ -3,18 +3,17 @@ use smash::app::{self, lua_bind::*};
use smash::lib::lua_const::*;
use crate::common::*;
use training_mod_sync::*;
// the current full hop status
static mut FULL_HOP: bool = false;
static FULL_HOP: RwLock<bool> = RwLock::new(false);
pub fn should_full_hop() -> bool {
unsafe { FULL_HOP }
read(&FULL_HOP)
}
pub fn roll_full_hop() {
unsafe {
FULL_HOP = MENU.full_hop.get_random().into_bool();
}
assign(&FULL_HOP, read(&MENU).full_hop.get_random().into_bool());
}
pub unsafe fn check_button_on(

View file

@ -1,30 +1,23 @@
use std::collections::VecDeque;
use crate::common::input::*;
use lazy_static::lazy_static;
use parking_lot::Mutex;
use training_mod_sync::*;
use crate::common::MENU;
lazy_static! {
static ref P1_DELAYED_INPUT_MAPPINGS: Mutex<VecDeque<MappedInputs>> =
Mutex::new(VecDeque::new());
}
static P1_DELAYED_INPUT_MAPPINGS: RwLock<VecDeque<MappedInputs>> = RwLock::new(VecDeque::new());
pub fn handle_final_input_mapping(player_idx: i32, out: *mut MappedInputs) {
unsafe {
if player_idx == 0 {
let mut delayed_mappings = P1_DELAYED_INPUT_MAPPINGS.lock();
let actual_mapping = *out;
if delayed_mappings.len() < MENU.input_delay.into_delay() as usize {
*out = MappedInputs::empty();
} else if let Some(delayed_mapping) = delayed_mappings.back() {
*out = *delayed_mapping;
}
delayed_mappings.push_front(actual_mapping);
delayed_mappings.truncate(MENU.input_delay.into_delay() as usize);
pub unsafe fn handle_final_input_mapping(player_idx: i32, out: *mut MappedInputs) {
if player_idx == 0 {
let mut delayed_mappings = lock_write(&P1_DELAYED_INPUT_MAPPINGS);
let actual_mapping = *out;
if delayed_mappings.len() < read(&MENU).input_delay.into_delay() as usize {
*out = MappedInputs::empty();
} else if let Some(delayed_mapping) = delayed_mappings.back() {
*out = *delayed_mapping;
}
delayed_mappings.push_front(actual_mapping);
delayed_mappings.truncate(read(&MENU).input_delay.into_delay() as usize);
}
}

View file

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

View file

@ -1,7 +1,5 @@
use std::cmp::Ordering;
use lazy_static::lazy_static;
use parking_lot::Mutex;
use skyline::nn::ui2d::ResColor;
use smash::app::{lua_bind::*, utility, BattleObjectModuleAccessor};
use smash::lib::lua_const::*;
@ -17,10 +15,12 @@ use crate::common::{
get_module_accessor, is_in_hitstun, is_in_shieldstun, try_get_module_accessor, MENU,
};
use crate::training::mash;
use crate::training::ui::notifications::{clear_notifications, color_notification};
use crate::training::ui::notifications::{clear_notification, color_notification};
use crate::{error, warn};
#[derive(PartialEq, Debug)]
use training_mod_sync::*;
#[derive(PartialEq, Debug, Clone, Copy)]
pub enum InputRecordState {
None,
Pause,
@ -28,7 +28,7 @@ pub enum InputRecordState {
Playback,
}
#[derive(PartialEq, Debug)]
#[derive(PartialEq, Debug, Clone, Copy)]
pub enum PossessionState {
Player,
Cpu,
@ -56,30 +56,26 @@ pub const STICK_NEUTRAL: f32 = 0.2;
pub const STICK_CLAMP_MULTIPLIER: f32 = 1.0 / 120.0; // 120.0 = CLAMP_MAX
const FINAL_RECORD_MAX: usize = 600; // Maximum length for input recording sequences (capacity)
const TOTAL_SLOT_COUNT: usize = 5; // Total number of input recording slots
pub static mut INPUT_RECORD: InputRecordState = InputRecordState::None;
pub static mut INPUT_RECORD_FRAME: usize = 0;
pub static mut POSSESSION: PossessionState = PossessionState::Player;
pub static mut LOCKOUT_FRAME: usize = 0;
pub static mut BUFFER_FRAME: usize = 0;
pub static mut RECORDED_LR: f32 = 1.0; // The direction the CPU was facing before the current recording was recorded
pub static mut CURRENT_LR: f32 = 1.0; // The direction the CPU was facing at the beginning of this playback
pub static mut STARTING_STATUS: i32 = 0; // The first status entered in the recording outside of waits
// used to calculate if the input playback should begin before hitstun would normally end (hitstun cancel, monado art?)
pub static mut CURRENT_RECORD_SLOT: usize = 0; // Which slot is being used for recording right now? Want to make sure this is synced with menu choices, maybe just use menu instead
pub static mut CURRENT_PLAYBACK_SLOT: usize = 0; // Which slot is being used for playback right now?
pub static mut CURRENT_FRAME_LENGTH: usize = 60;
lazy_static! {
static ref P1_FINAL_MAPPING: Mutex<[[MappedInputs; FINAL_RECORD_MAX]; TOTAL_SLOT_COUNT]> =
Mutex::new([[{ MappedInputs::empty() }; FINAL_RECORD_MAX]; TOTAL_SLOT_COUNT]);
static ref P1_FRAME_LENGTH_MAPPING: Mutex<[usize; TOTAL_SLOT_COUNT]> =
Mutex::new([60usize; TOTAL_SLOT_COUNT]);
static ref P1_STARTING_STATUSES: Mutex<[StartingStatus; TOTAL_SLOT_COUNT]> =
Mutex::new([{ StartingStatus::Other }; TOTAL_SLOT_COUNT]);
}
pub static INPUT_RECORD: RwLock<InputRecordState> = RwLock::new(InputRecordState::None);
pub static INPUT_RECORD_FRAME: RwLock<usize> = RwLock::new(0);
pub static POSSESSION: RwLock<PossessionState> = RwLock::new(PossessionState::Player);
pub static LOCKOUT_FRAME: RwLock<usize> = RwLock::new(0);
pub static BUFFER_FRAME: RwLock<usize> = RwLock::new(0);
pub static RECORDED_LR: RwLock<f32> = RwLock::new(1.0); // The direction the CPU was facing before the current recording was recorded
pub static CURRENT_LR: RwLock<f32> = RwLock::new(1.0); // The direction the CPU was facing at the beginning of this playback
pub static STARTING_STATUS: RwLock<i32> = RwLock::new(0); // The first status entered in the recording outside of waits
// used to calculate if the input playback should begin before hitstun would normally end (hitstun cancel, monado art?)
pub static CURRENT_RECORD_SLOT: RwLock<usize> = RwLock::new(0); // Which slot is being used for recording right now? Want to make sure this is synced with menu choices, maybe just use menu instead
pub static CURRENT_PLAYBACK_SLOT: RwLock<usize> = RwLock::new(0); // Which slot is being used for playback right now?
pub static CURRENT_FRAME_LENGTH: RwLock<usize> = RwLock::new(60);
pub static P1_FINAL_MAPPING: RwLock<[[MappedInputs; FINAL_RECORD_MAX]; TOTAL_SLOT_COUNT]> =
RwLock::new([[{ MappedInputs::empty() }; FINAL_RECORD_MAX]; TOTAL_SLOT_COUNT]);
pub static P1_FRAME_LENGTH_MAPPING: RwLock<[usize; TOTAL_SLOT_COUNT]> =
RwLock::new([60; TOTAL_SLOT_COUNT]);
// pub static P1_STARTING_STATUSES: RwLock<[StartingStatus; TOTAL_SLOT_COUNT]> = RwLock::new([StartingStatus::Other; TOTAL_SLOT_COUNT]); // TODO! Not used currently
unsafe fn can_transition(module_accessor: *mut BattleObjectModuleAccessor) -> bool {
let transition_term = into_transition_term(into_starting_status(STARTING_STATUS));
let transition_term = into_transition_term(into_starting_status(read(&STARTING_STATUS)));
WorkModule::is_enable_transition_term(module_accessor, transition_term)
}
@ -104,17 +100,18 @@ unsafe fn should_mash_playback() {
if is_in_hitstun(&mut *cpu_module_accessor) {
// if we're in hitstun and want to enter the frame we start hitstop for SDI, start if we're in any damage status instantly
if MENU.hitstun_playback == HitstunPlayback::INSTANT {
if read(&MENU).hitstun_playback == HitstunPlayback::INSTANT {
should_playback = true;
}
// if we want to wait until we exit hitstop and begin flying away for shield art etc, start if we're not in hitstop
if MENU.hitstun_playback == HitstunPlayback::HITSTOP
if read(&MENU).hitstun_playback == HitstunPlayback::HITSTOP
&& !StopModule::is_stop(cpu_module_accessor)
{
should_playback = true;
}
// if we're in hitstun and want to wait till FAF to act, then we want to match our starting status to the correct transition term to see if we can hitstun cancel
if MENU.hitstun_playback == HitstunPlayback::HITSTUN && can_transition(cpu_module_accessor)
if read(&MENU).hitstun_playback == HitstunPlayback::HITSTUN
&& can_transition(cpu_module_accessor)
{
should_playback = true;
}
@ -193,53 +190,65 @@ unsafe fn handle_recording_for_fighter(module_accessor: &mut BattleObjectModuleA
let fighter_kind = utility::get_kind(module_accessor);
let fighter_is_nana = fighter_kind == *FIGHTER_KIND_NANA;
CURRENT_RECORD_SLOT = MENU.recording_slot.into_idx().unwrap_or(0);
assign(
&CURRENT_RECORD_SLOT,
read(&MENU).recording_slot.into_idx().unwrap_or(0),
);
if entry_id_int == 0 && !fighter_is_nana {
if button_config::combo_passes(button_config::ButtonCombo::InputPlayback) {
playback(MENU.playback_button_slots.get_random().into_idx());
} else if MENU.record_trigger.contains(&RecordTrigger::COMMAND)
playback(read(&MENU).playback_button_slots.get_random().into_idx());
} else if read(&MENU).record_trigger.contains(&RecordTrigger::COMMAND)
&& button_config::combo_passes(button_config::ButtonCombo::InputRecord)
{
lockout_record();
}
if INPUT_RECORD == None {
clear_notifications("Input Recording");
let input_record = read(&INPUT_RECORD);
if input_record == None {
clear_notification("Input Recording");
}
// Handle recording end
if (INPUT_RECORD == Record || INPUT_RECORD == Playback)
&& INPUT_RECORD_FRAME >= CURRENT_FRAME_LENGTH - 1
let mut input_record_frame = lock_write(&INPUT_RECORD_FRAME);
if (input_record == Record || input_record == Playback)
&& *input_record_frame >= read(&CURRENT_FRAME_LENGTH) - 1
{
POSSESSION = Player;
assign(&POSSESSION, Player);
if mash::is_playback_queued() {
mash::reset();
}
// If we need to crop the recording for neutral input
// INPUT_RECORD_FRAME must be > 0 to prevent bounding errors
if INPUT_RECORD == Record && MENU.recording_crop == OnOff::ON && INPUT_RECORD_FRAME > 0
if input_record == Record
&& read(&MENU).recording_crop == OnOff::ON
&& *input_record_frame > 0
{
while INPUT_RECORD_FRAME > 0 && is_input_neutral(INPUT_RECORD_FRAME - 1) {
while *input_record_frame > 0 && is_input_neutral(*input_record_frame - 1) {
// Discard frames at the end of the recording until the last frame with input
INPUT_RECORD_FRAME -= 1;
*input_record_frame -= 1;
}
CURRENT_FRAME_LENGTH = INPUT_RECORD_FRAME;
P1_FRAME_LENGTH_MAPPING.lock()[CURRENT_RECORD_SLOT] = CURRENT_FRAME_LENGTH;
assign(&CURRENT_FRAME_LENGTH, *input_record_frame);
let mut p1_frame_length_mapping = lock_write(&P1_FRAME_LENGTH_MAPPING);
(*p1_frame_length_mapping)[read(&CURRENT_RECORD_SLOT)] = *input_record_frame;
drop(p1_frame_length_mapping);
}
INPUT_RECORD_FRAME = 0;
*input_record_frame = 0;
if MENU.playback_loop == OnOff::ON && INPUT_RECORD == Playback {
playback(Some(CURRENT_PLAYBACK_SLOT));
if read(&MENU).playback_loop == OnOff::ON && input_record == Playback {
let playback_slot = read(&CURRENT_PLAYBACK_SLOT);
playback(Some(playback_slot));
} else {
INPUT_RECORD = None;
assign(&INPUT_RECORD, None);
}
}
drop(input_record_frame);
}
// Handle Possession Coloring
if entry_id_int == 1 && POSSESSION == Lockout {
clear_notifications("Input Recording");
let possession = read(&POSSESSION);
if entry_id_int == 1 && possession == Lockout {
clear_notification("Input Recording");
color_notification(
"Input Recording".to_string(),
"Lockout".to_owned(),
@ -258,8 +267,8 @@ unsafe fn handle_recording_for_fighter(module_accessor: &mut BattleObjectModuleA
1.0,
*MODEL_COLOR_TYPE_COLOR_BLEND,
);
} else if entry_id_int == 1 && POSSESSION == Standby {
clear_notifications("Input Recording");
} else if entry_id_int == 1 && possession == Standby {
clear_notification("Input Recording");
color_notification(
"Input Recording".to_string(),
"Standby".to_owned(),
@ -278,8 +287,8 @@ unsafe fn handle_recording_for_fighter(module_accessor: &mut BattleObjectModuleA
1.0,
*MODEL_COLOR_TYPE_COLOR_BLEND,
);
} else if entry_id_int == 1 && POSSESSION == Cpu {
clear_notifications("Input Recording");
} else if entry_id_int == 1 && possession == Cpu {
clear_notification("Input Recording");
color_notification(
"Input Recording".to_string(),
"Recording".to_owned(),
@ -298,15 +307,17 @@ unsafe fn handle_recording_for_fighter(module_accessor: &mut BattleObjectModuleA
0.0,
*MODEL_COLOR_TYPE_COLOR_BLEND,
);
} else if entry_id_int == 1 && POSSESSION == Player && INPUT_RECORD == Playback {
} else if entry_id_int == 1 && possession == Player && read(&INPUT_RECORD) == Playback {
// Need to re-read INPUT_RECORD instead of using the local variable because we might have assigned to it early
// Displays if the inputs from the current frame were a result of playback
if INPUT_RECORD_FRAME == 0 || INPUT_RECORD_FRAME == 1 {
let input_record_frame = read(&INPUT_RECORD_FRAME);
if input_record_frame == 0 || input_record_frame == 1 {
// can be either, seems like a thread issue
clear_notifications("Input Recording");
clear_notification("Input Recording");
color_notification(
"Input Recording".to_string(),
"Playback".to_owned(),
CURRENT_FRAME_LENGTH as u32,
read(&CURRENT_FRAME_LENGTH) as u32,
ResColor {
r: 0,
g: 0,
@ -319,27 +330,29 @@ unsafe fn handle_recording_for_fighter(module_accessor: &mut BattleObjectModuleA
}
pub unsafe fn lockout_record() {
INPUT_RECORD = Pause;
INPUT_RECORD_FRAME = 0;
POSSESSION = Lockout;
P1_FINAL_MAPPING.lock()[CURRENT_RECORD_SLOT]
.iter_mut()
.for_each(|mapped_input| {
*mapped_input = MappedInputs::empty();
});
CURRENT_FRAME_LENGTH = MENU.recording_duration.into_frames();
P1_FRAME_LENGTH_MAPPING.lock()[CURRENT_RECORD_SLOT] = CURRENT_FRAME_LENGTH;
LOCKOUT_FRAME = 30; // This needs to be this high or issues occur dropping shield - but does this cause problems when trying to record ledge?
BUFFER_FRAME = 0;
// Store the direction the CPU is facing when we initially record, so we can turn their inputs around if needed
let cpu_module_accessor = get_module_accessor(FighterId::CPU);
RECORDED_LR = PostureModule::lr(cpu_module_accessor);
CURRENT_LR = RECORDED_LR;
let recording_duration = read(&MENU).recording_duration.into_frames();
let current_record_slot = read(&CURRENT_RECORD_SLOT);
let mut p1_final_mapping = lock_write(&P1_FINAL_MAPPING);
(*p1_final_mapping)[current_record_slot] = [{ MappedInputs::empty() }; FINAL_RECORD_MAX];
drop(p1_final_mapping);
let mut p1_frame_length_mapping = lock_write(&P1_FRAME_LENGTH_MAPPING);
(*p1_frame_length_mapping)[current_record_slot] = recording_duration;
drop(p1_frame_length_mapping);
assign(&CURRENT_FRAME_LENGTH, recording_duration);
assign(&INPUT_RECORD, Pause);
assign(&INPUT_RECORD_FRAME, 0);
assign(&POSSESSION, Lockout);
assign(&LOCKOUT_FRAME, 30); // This needs to be this high or issues occur dropping shield - but does this cause problems when trying to record ledge?
assign(&BUFFER_FRAME, 0);
// Store the direction the CPU is facing when we initially record, so we can turn their inputs around if needed
assign(&RECORDED_LR, PostureModule::lr(cpu_module_accessor));
assign(&CURRENT_LR, PostureModule::lr(cpu_module_accessor));
}
// Returns whether we did playback
pub unsafe fn playback(slot: Option<usize>) -> bool {
if INPUT_RECORD == Pause {
if read(&INPUT_RECORD) == Pause {
warn!("Tried to playback during lockout!");
return false;
}
@ -348,15 +361,15 @@ pub unsafe fn playback(slot: Option<usize>) -> bool {
return false;
}
let slot = slot.unwrap();
CURRENT_PLAYBACK_SLOT = slot;
CURRENT_FRAME_LENGTH = P1_FRAME_LENGTH_MAPPING.lock()[CURRENT_PLAYBACK_SLOT];
INPUT_RECORD = Playback;
POSSESSION = Player;
INPUT_RECORD_FRAME = 0;
BUFFER_FRAME = 0;
let cpu_module_accessor = get_module_accessor(FighterId::CPU);
CURRENT_LR = PostureModule::lr(cpu_module_accessor);
let frame_length = read(&P1_FRAME_LENGTH_MAPPING)[slot];
assign(&CURRENT_FRAME_LENGTH, frame_length);
assign(&CURRENT_PLAYBACK_SLOT, slot);
assign(&INPUT_RECORD, Playback);
assign(&POSSESSION, Player);
assign(&INPUT_RECORD_FRAME, 0);
assign(&BUFFER_FRAME, 0);
assign(&CURRENT_LR, PostureModule::lr(cpu_module_accessor));
true
}
@ -364,26 +377,28 @@ pub unsafe fn playback(slot: Option<usize>) -> bool {
pub unsafe fn playback_ledge(slot: Option<usize>) {
let did_playback = playback(slot);
if did_playback {
BUFFER_FRAME = 5; // So we can make sure the option is buffered and won't get ledge trumped if delay is 0
// drop down from ledge can't be buffered on the same frame as jump/attack/roll/ngu so we have to do this
// Need to buffer 1 less frame for non-lassos
let mut buffer_frame = lock_write(&BUFFER_FRAME);
*buffer_frame = 5; // So we can make sure the option is buffered and won't get ledge trumped if delay is 0
// drop down from ledge can't be buffered on the same frame as jump/attack/roll/ngu so we have to do this
// Need to buffer 1 less frame for non-lassos
let cpu_module_accessor = get_module_accessor(FighterId::CPU);
let status_kind = StatusModule::status_kind(cpu_module_accessor);
if status_kind == *FIGHTER_STATUS_KIND_CLIFF_CATCH {
BUFFER_FRAME -= 1;
*buffer_frame -= 1;
}
}
}
pub unsafe fn stop_playback() {
INPUT_RECORD = None;
INPUT_RECORD_FRAME = 0;
POSSESSION = Player;
assign(&INPUT_RECORD, None);
assign(&INPUT_RECORD_FRAME, 0);
assign(&POSSESSION, Player);
}
pub unsafe fn is_input_neutral(input_frame: usize) -> bool {
// Returns whether we should be done with standby this frame (if any significant controller input has been made)
let frame_input = P1_FINAL_MAPPING.lock()[CURRENT_RECORD_SLOT][input_frame];
let current_record_slot = read(&CURRENT_RECORD_SLOT);
let frame_input = read(&P1_FINAL_MAPPING)[current_record_slot][input_frame];
let clamped_lstick_x =
((frame_input.lstick_x as f32) * STICK_CLAMP_MULTIPLIER).clamp(-1.0, 1.0);
@ -405,31 +420,40 @@ pub unsafe fn is_input_neutral(input_frame: usize) -> bool {
}
pub unsafe fn handle_final_input_mapping(player_idx: i32, out: *mut MappedInputs) {
let mut possession = lock_write(&POSSESSION);
if player_idx == 0 {
// if player 1
if INPUT_RECORD == Record {
if read(&INPUT_RECORD) == Record {
let mut input_record_frame = lock_write(&INPUT_RECORD_FRAME);
// check for standby before starting action:
if POSSESSION == Standby && !is_input_neutral(0) {
if *possession == Standby && !is_input_neutral(0) {
// last input made us start an action, so start recording and end standby.
INPUT_RECORD_FRAME += 1;
POSSESSION = Cpu;
*input_record_frame += 1;
*possession = Cpu;
}
if INPUT_RECORD_FRAME == 1 {
if *input_record_frame == 1 {
// We're on the second frame of recording, grabbing the status should give us the status that resulted from the first frame of input
// We'll want to save this status so that we use the correct TRANSITION TERM for hitstun cancelling out of damage fly
let cpu_module_accessor = get_module_accessor(FighterId::CPU);
P1_STARTING_STATUSES.lock()[CURRENT_PLAYBACK_SLOT] =
into_starting_status(StatusModule::status_kind(cpu_module_accessor));
STARTING_STATUS = StatusModule::status_kind(cpu_module_accessor);
assign(
&STARTING_STATUS,
StatusModule::status_kind(cpu_module_accessor),
);
// TODO: Handle this based on slot later instead
// let p1_starting_statuses = lock_write_rwlock(&P1_STARTING_STATUSES);
// (*p1_starting_statuses)[read(&CURRENT_PLAYBACK_SLOT)] =
// into_starting_status(StatusModule::status_kind(cpu_module_accessor));
// drop(p1_starting_statuses);
}
P1_FINAL_MAPPING.lock()[CURRENT_RECORD_SLOT][INPUT_RECORD_FRAME] = *out;
let mut p1_final_mapping = lock_write(&P1_FINAL_MAPPING);
let current_record_slot = read(&CURRENT_RECORD_SLOT);
(*p1_final_mapping)[current_record_slot][*input_record_frame] = *out;
drop(p1_final_mapping);
*out = MappedInputs::empty(); // don't control player while recording
}
// Don't allow for player input during Lockout
if POSSESSION == Lockout {
if *possession == Lockout {
*out = MappedInputs::empty();
}
}
@ -449,7 +473,7 @@ unsafe fn set_cpu_controls(p_data: *mut *mut u8) {
// TODO: Setup STARTING_STATUS based on current playback slot here
// This check prevents out of shield if mash exiting is on
if INPUT_RECORD == None {
if read(&INPUT_RECORD) == None {
should_mash_playback();
}
@ -461,20 +485,22 @@ unsafe fn set_cpu_controls(p_data: *mut *mut u8) {
}
let cpu_module_accessor = cpu_module_accessor.unwrap();
if INPUT_RECORD == Pause {
match LOCKOUT_FRAME.cmp(&0) {
Ordering::Greater => LOCKOUT_FRAME -= 1,
if read(&INPUT_RECORD) == Pause {
let lockout_frame = read(&LOCKOUT_FRAME);
match lockout_frame.cmp(&0) {
Ordering::Greater => assign(&LOCKOUT_FRAME, lockout_frame - 1),
Ordering::Equal => {
INPUT_RECORD = Record;
POSSESSION = Standby;
assign(&INPUT_RECORD, Record);
assign(&POSSESSION, Standby);
}
Ordering::Less => error!("LOCKOUT_FRAME OUT OF BOUNDS"),
}
}
if INPUT_RECORD == Record || INPUT_RECORD == Playback {
let input_record = read(&INPUT_RECORD);
if input_record == Record || input_record == Playback {
// if we aren't facing the way we were when we initially recorded, we reverse horizontal inputs
let mut x_input_multiplier = RECORDED_LR * CURRENT_LR;
let mut x_input_multiplier = read(&RECORDED_LR) * read(&CURRENT_LR);
// Don't flip Shulk's dial inputs
let fighter_kind = utility::get_kind(&mut *cpu_module_accessor);
if fighter_kind == *FIGHTER_KIND_SHULK {
@ -504,13 +530,16 @@ unsafe fn set_cpu_controls(p_data: *mut *mut u8) {
);
}
let mut saved_mapped_inputs = P1_FINAL_MAPPING.lock()[if INPUT_RECORD == Record {
CURRENT_RECORD_SLOT
let mut input_record_frame = lock_write(&INPUT_RECORD_FRAME);
let slot = if input_record == Record {
read(&CURRENT_RECORD_SLOT)
} else {
CURRENT_PLAYBACK_SLOT
}][INPUT_RECORD_FRAME];
if BUFFER_FRAME <= 3 && BUFFER_FRAME > 0 {
read(&CURRENT_PLAYBACK_SLOT)
};
let p1_final_mapping = lock_write(&P1_FINAL_MAPPING);
let mut saved_mapped_inputs = p1_final_mapping[slot][*input_record_frame];
let mut buffer_frame = lock_write(&BUFFER_FRAME);
if (0 < *buffer_frame) && (*buffer_frame <= 3) {
// Our option is already buffered, now we need to 0 out inputs to make sure our future controls act like flicks/presses instead of holding the button
saved_mapped_inputs = MappedInputs::empty();
}
@ -539,24 +568,28 @@ unsafe fn set_cpu_controls(p_data: *mut *mut u8) {
// Keep counting frames, unless we're in standby waiting for an input, or are buffering an option
// When buffering an option, we keep inputting the first frame of input during the buffer window
if BUFFER_FRAME > 0 {
BUFFER_FRAME -= 1;
} else if INPUT_RECORD_FRAME < CURRENT_FRAME_LENGTH - 1 && POSSESSION != Standby {
INPUT_RECORD_FRAME += 1;
if *buffer_frame > 0 {
*buffer_frame -= 1;
} else if *input_record_frame < read(&CURRENT_FRAME_LENGTH) - 1
&& read(&POSSESSION) != Standby
{
*input_record_frame += 1;
}
}
}
pub unsafe fn is_playback() -> bool {
INPUT_RECORD == Record || INPUT_RECORD == Playback
pub fn is_playback() -> bool {
let input_record = read(&INPUT_RECORD);
input_record == Record || input_record == Playback
}
pub unsafe fn is_recording() -> bool {
INPUT_RECORD == Record
pub fn is_recording() -> bool {
read(&INPUT_RECORD) == Record
}
pub unsafe fn is_standby() -> bool {
POSSESSION == Standby || POSSESSION == Lockout
let possession = read(&POSSESSION);
possession == Standby || possession == Lockout
}
extern "C" {

View file

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

View file

@ -11,24 +11,20 @@ use crate::training::input_record;
use crate::training::shield;
use crate::training::{attack_angle, save_states};
use once_cell::sync::Lazy;
use training_mod_sync::*;
const DISTANCE_CLOSE_THRESHOLD: f32 = 16.0;
const DISTANCE_MID_THRESHOLD: f32 = 37.0;
const DISTANCE_FAR_THRESHOLD: f32 = 64.0;
static mut CURRENT_AERIAL: Action = Action::NAIR;
static mut QUEUE: Vec<Action> = vec![];
static CURRENT_AERIAL: RwLock<Action> = RwLock::new(Action::NAIR);
static QUEUE: RwLock<Vec<Action>> = RwLock::new(Vec::new());
static FALLING_AERIAL: RwLock<bool> = RwLock::new(false);
static AERIAL_DELAY: RwLock<u32> = RwLock::new(0);
static IS_TRANSITIONING_DASH: RwLock<bool> = RwLock::new(false);
static mut FALLING_AERIAL: bool = false;
static mut AERIAL_DELAY: u32 = 0;
static AERIAL_DELAY_COUNTER: Lazy<usize> =
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
// Track if we're about to do another command flag cat run in the same frame for a dash or dash attack
static mut IS_TRANSITIONING_DASH: bool = false;
static AERIAL_DELAY_COUNTER: LazyLock<usize> =
LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
unsafe fn is_beginning_dash_attack(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
let current_status = StatusModule::status_kind(module_accessor);
@ -44,7 +40,8 @@ unsafe fn is_beginning_dash_attack(module_accessor: &mut app::BattleObjectModule
}
unsafe fn dash_transition_check(module_accessor: &mut app::BattleObjectModuleAccessor) {
IS_TRANSITIONING_DASH &= is_dashing_for_dash_attack(module_accessor);
let mut is_transitioning_dash = lock_write(&IS_TRANSITIONING_DASH);
*is_transitioning_dash &= is_dashing_for_dash_attack(module_accessor);
}
pub fn is_playback_queued() -> bool {
@ -66,21 +63,22 @@ pub unsafe fn is_dashing_for_dash_attack(
}
pub fn buffer_action(action: Action) {
unsafe {
if !QUEUE.is_empty() {
return;
}
let queue = lock_read(&QUEUE);
if !queue.is_empty() {
// Something is already buffered
return;
}
drop(queue);
if action == Action::empty() {
return;
}
// We want to allow for triggering a mash to end playback for neutral playbacks, but not for SDI/disadv playbacks
// exit playback if we want to perform mash actions out of it
// TODO: Figure out some way to deal with trying to playback into another playback
unsafe {
// exit playback if we want to perform mash actions out of it
// TODO: Figure out some way to deal with trying to playback into another playback
if MENU.playback_mash == OnOff::ON
if read(&MENU).playback_mash == OnOff::ON
&& input_record::is_playback()
&& !input_record::is_recording()
&& !input_record::is_standby()
@ -96,21 +94,16 @@ pub fn buffer_action(action: Action) {
}
attack_angle::roll_direction();
roll_aerial_delay(action);
unsafe {
QUEUE.insert(0, action);
buffer_follow_up();
}
let mut queue = lock_write(&QUEUE);
queue.insert(0, action);
drop(queue);
buffer_follow_up();
}
pub fn buffer_follow_up() {
let action;
unsafe {
action = MENU.follow_up.get_random();
}
let action = read(&MENU).follow_up.get_random();
if action == Action::empty() {
return;
@ -118,60 +111,44 @@ pub fn buffer_follow_up() {
roll_aerial_delay(action);
unsafe {
QUEUE.insert(0, action);
}
let mut queue = lock_write(&QUEUE);
queue.insert(0, action);
drop(queue);
}
pub fn get_current_buffer() -> Action {
unsafe {
if QUEUE.is_empty() {
return Action::empty();
}
*QUEUE.last().unwrap()
}
let queue = lock_read(&QUEUE);
*(queue.last().unwrap_or(&Action::empty()))
}
pub fn reset() {
unsafe {
QUEUE.pop();
}
let mut queue = lock_write(&QUEUE);
queue.pop();
drop(queue);
shield::suspend_shield(get_current_buffer());
unsafe {
frame_counter::full_reset(*AERIAL_DELAY_COUNTER);
AERIAL_DELAY = 0;
}
frame_counter::full_reset(*AERIAL_DELAY_COUNTER);
assign(&AERIAL_DELAY, 0);
}
pub fn full_reset() {
unsafe {
while !QUEUE.is_empty() {
reset();
}
}
clear_queue();
reset();
}
pub fn clear_queue() {
unsafe { QUEUE.clear() }
assign(&QUEUE, Vec::new());
}
pub fn set_aerial(attack: Action) {
unsafe {
CURRENT_AERIAL = attack;
}
assign(&CURRENT_AERIAL, attack);
}
pub unsafe fn get_attack_air_kind(
module_accessor: &mut app::BattleObjectModuleAccessor,
) -> Option<i32> {
pub fn get_attack_air_kind(module_accessor: &mut app::BattleObjectModuleAccessor) -> Option<i32> {
if !is_operation_cpu(module_accessor) {
return None;
}
CURRENT_AERIAL.into_attack_air_kind()
read(&CURRENT_AERIAL).into_attack_air_kind()
}
pub unsafe fn get_command_flag_cat(
@ -216,91 +193,92 @@ unsafe fn get_buffered_action(
return None;
}
let fighter_distance = get_fighter_distance();
let menu = read(&MENU);
if is_in_tech(module_accessor) {
let action = MENU.tech_action_override.get_random();
let action = menu.tech_action_override.get_random();
if action != Action::empty() {
Some(action)
} else if MENU.mash_triggers.contains(&MashTrigger::TECH) {
Some(MENU.mash_state.get_random())
} else if menu.mash_triggers.contains(&MashTrigger::TECH) {
Some(menu.mash_state.get_random())
} else {
None
}
} else if is_in_clatter(module_accessor) {
let action = MENU.clatter_override.get_random();
let action = menu.clatter_override.get_random();
if action != Action::empty() {
Some(action)
} else if MENU.mash_triggers.contains(&MashTrigger::CLATTER) {
Some(MENU.mash_state.get_random())
} else if menu.mash_triggers.contains(&MashTrigger::CLATTER) {
Some(menu.mash_state.get_random())
} else {
None
}
} else if is_in_tumble(module_accessor) {
// Note that the tumble check needs to come before hitstun,
// otherwise the hitstun check will always return first
let action = MENU.tumble_override.get_random();
let action = menu.tumble_override.get_random();
if action != Action::empty() {
Some(action)
} else if MENU.mash_triggers.contains(&MashTrigger::TUMBLE) {
Some(MENU.mash_state.get_random())
} else if menu.mash_triggers.contains(&MashTrigger::TUMBLE) {
Some(menu.mash_state.get_random())
} else {
None
}
} else if is_in_hitstun(module_accessor) {
let action = MENU.hitstun_override.get_random();
let action = menu.hitstun_override.get_random();
if action != Action::empty() {
Some(action)
} else if MENU.mash_triggers.contains(&MashTrigger::HIT) {
Some(MENU.mash_state.get_random())
} else if menu.mash_triggers.contains(&MashTrigger::HIT) {
Some(menu.mash_state.get_random())
} else {
None
}
} else if is_in_parry(module_accessor) {
let action = MENU.parry_override.get_random();
let action = menu.parry_override.get_random();
if action != Action::empty() {
Some(action)
} else if MENU.mash_triggers.contains(&MashTrigger::PARRY) {
Some(MENU.mash_state.get_random())
} else if menu.mash_triggers.contains(&MashTrigger::PARRY) {
Some(menu.mash_state.get_random())
} else {
None
}
} else if is_in_footstool(module_accessor) {
let action = MENU.footstool_override.get_random();
let action = menu.footstool_override.get_random();
if action != Action::empty() {
Some(action)
} else if MENU.mash_triggers.contains(&MashTrigger::FOOTSTOOL) {
Some(MENU.mash_state.get_random())
} else if menu.mash_triggers.contains(&MashTrigger::FOOTSTOOL) {
Some(menu.mash_state.get_random())
} else {
None
}
} else if is_in_ledgetrump(module_accessor) {
let action = MENU.trump_override.get_random();
let action = menu.trump_override.get_random();
if action != Action::empty() {
Some(action)
} else if MENU.mash_triggers.contains(&MashTrigger::TRUMP) {
Some(MENU.mash_state.get_random())
} else if menu.mash_triggers.contains(&MashTrigger::TRUMP) {
Some(menu.mash_state.get_random())
} else {
None
}
} else if is_in_landing(module_accessor) {
let action = MENU.landing_override.get_random();
let action = menu.landing_override.get_random();
if action != Action::empty() {
Some(action)
} else if MENU.mash_triggers.contains(&MashTrigger::LANDING) {
Some(MENU.mash_state.get_random())
} else if menu.mash_triggers.contains(&MashTrigger::LANDING) {
Some(menu.mash_state.get_random())
} else {
None
}
} else if (MENU.mash_triggers.contains(&MashTrigger::GROUNDED) && is_grounded(module_accessor))
|| (MENU.mash_triggers.contains(&MashTrigger::AIRBORNE) && is_airborne(module_accessor))
|| (MENU.mash_triggers.contains(&MashTrigger::DISTANCE_CLOSE)
} else if (menu.mash_triggers.contains(&MashTrigger::GROUNDED) && is_grounded(module_accessor))
|| (menu.mash_triggers.contains(&MashTrigger::AIRBORNE) && is_airborne(module_accessor))
|| (menu.mash_triggers.contains(&MashTrigger::DISTANCE_CLOSE)
&& fighter_distance < DISTANCE_CLOSE_THRESHOLD)
|| (MENU.mash_triggers.contains(&MashTrigger::DISTANCE_MID)
|| (menu.mash_triggers.contains(&MashTrigger::DISTANCE_MID)
&& fighter_distance < DISTANCE_MID_THRESHOLD)
|| (MENU.mash_triggers.contains(&MashTrigger::DISTANCE_FAR)
|| (menu.mash_triggers.contains(&MashTrigger::DISTANCE_FAR)
&& fighter_distance < DISTANCE_FAR_THRESHOLD)
|| MENU.mash_triggers.contains(&MashTrigger::ALWAYS)
|| menu.mash_triggers.contains(&MashTrigger::ALWAYS)
{
Some(MENU.mash_state.get_random())
Some(menu.mash_state.get_random())
} else {
// SHIELD handled in shield.rs
// LEDGE handled in ledge.rs
@ -309,12 +287,13 @@ unsafe fn get_buffered_action(
}
fn buffer_menu_mash(action: Action) {
unsafe {
buffer_action(action);
full_hop::roll_full_hop();
fast_fall::roll_fast_fall();
FALLING_AERIAL = MENU.falling_aerials.get_random().into_bool();
}
buffer_action(action);
full_hop::roll_full_hop();
fast_fall::roll_fast_fall();
assign(
&FALLING_AERIAL,
read(&MENU).falling_aerials.get_random().into_bool(),
);
}
pub fn external_buffer_menu_mash(action: Action) {
@ -510,9 +489,10 @@ unsafe fn get_attack_flag(
if current_status == *FIGHTER_STATUS_KIND_DASH && motion_frame == 0.0 && is_motion_dash
{
if !IS_TRANSITIONING_DASH {
let mut is_transitioning_dash = lock_write(&IS_TRANSITIONING_DASH);
if !*is_transitioning_dash {
// The first time these conditions are met, we aren't ready to begin dash attacking, so get ready to transition next frame
IS_TRANSITIONING_DASH = true;
*is_transitioning_dash = true;
} else {
// Begin dash attacking now that we've dashed for one frame
StatusModule::change_status_request_from_script(module_accessor, status, true);
@ -546,7 +526,7 @@ unsafe fn get_aerial_flag(
let status = *FIGHTER_STATUS_KIND_ATTACK_AIR;
if FALLING_AERIAL && !fast_fall::is_falling(module_accessor) {
if read(&FALLING_AERIAL) && !fast_fall::is_falling(module_accessor) {
return flag;
}
@ -576,17 +556,20 @@ fn roll_aerial_delay(action: Action) {
if !shield::is_aerial(action) {
return;
}
unsafe {
AERIAL_DELAY = MENU.aerial_delay.get_random().into_delay();
}
assign(
&AERIAL_DELAY,
read(&MENU).aerial_delay.get_random().into_delay(),
);
}
fn should_delay_aerial(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
unsafe {
if AERIAL_DELAY == 0 {
return false;
}
let aerial_delay = read(&AERIAL_DELAY);
if aerial_delay == 0 {
return false;
}
unsafe {
if StatusModule::status_kind(module_accessor) == *FIGHTER_STATUS_KIND_ATTACK_AIR {
return false;
}
@ -597,9 +580,8 @@ fn should_delay_aerial(module_accessor: &mut app::BattleObjectModuleAccessor) ->
) {
return true;
}
frame_counter::should_delay(AERIAL_DELAY, *AERIAL_DELAY_COUNTER)
}
frame_counter::should_delay(aerial_delay, *AERIAL_DELAY_COUNTER)
}
/**

View file

@ -19,6 +19,7 @@ use smash::app::{
use smash::lib::lua_const::*;
use smash::params::*;
use smash::phx::{Hash40, Vector3f};
use training_mod_sync::*;
pub mod buff;
pub mod charge;
@ -133,7 +134,7 @@ fn once_per_frame_per_fighter(module_accessor: &mut BattleObjectModuleAccessor,
// Handle dodge staling here b/c input recording or mash can cause dodging
WorkModule::set_flag(
module_accessor,
!(MENU.stale_dodges.as_bool()),
!(read(&MENU).stale_dodges.as_bool()),
*FIGHTER_INSTANCE_WORK_ID_FLAG_DISABLE_ESCAPE_PENALTY,
);
input_record::handle_recording();
@ -141,7 +142,7 @@ fn once_per_frame_per_fighter(module_accessor: &mut BattleObjectModuleAccessor,
tech::hide_tech();
}
combo::get_command_flag_cat(module_accessor);
combo::once_per_frame(module_accessor);
hitbox_visualizer::get_command_flag_cat(module_accessor);
save_states::save_states(module_accessor);
tech::get_command_flag_cat(module_accessor);
@ -312,7 +313,6 @@ pub unsafe fn handle_is_enable_transition_term(
return ori;
}
combo::is_enable_transition_term(module_accessor, transition_term, ori);
match ledge::is_enable_transition_term(module_accessor, transition_term) {
Some(r) => r,
None => ori,
@ -428,7 +428,7 @@ pub unsafe fn handle_add_damage(
#[skyline::hook(offset = *OFFSET_TRAINING_RESET_CHECK, inline)]
unsafe fn lra_handle(ctx: &mut InlineCtx) {
let x8 = ctx.registers[8].x.as_mut();
if !(MENU.lra_reset.as_bool()) {
if !(read(&MENU).lra_reset.as_bool()) {
*x8 = 0;
}
}
@ -766,7 +766,7 @@ pub unsafe fn handle_reused_ui(
// If Little Mac is in the game and we're buffing him, set the meter to 100
if (player_fighter_kind == *FIGHTER_KIND_LITTLEMAC
|| cpu_fighter_kind == *FIGHTER_KIND_LITTLEMAC)
&& MENU.buff_state.contains(&BuffOption::KO)
&& read(&MENU).buff_state.contains(&BuffOption::KO)
{
param_2 = 100;
}
@ -837,26 +837,33 @@ pub fn training_mods() {
info!("Applying training mods.");
unsafe {
let mut fighter_manager_addr_lock = lock_write(&FIGHTER_MANAGER_ADDR);
LookupSymbol(
addr_of_mut!(FIGHTER_MANAGER_ADDR),
addr_of_mut!(*fighter_manager_addr_lock),
"_ZN3lib9SingletonIN3app14FighterManagerEE9instance_E\u{0}"
.as_bytes()
.as_ptr(),
);
drop(fighter_manager_addr_lock);
// TODO!("This seems unused? Can we remove it?")
let mut stage_manager_addr_lock = lock_write(&STAGE_MANAGER_ADDR);
LookupSymbol(
addr_of_mut!(STAGE_MANAGER_ADDR),
addr_of_mut!(*stage_manager_addr_lock),
"_ZN3lib9SingletonIN3app12StageManagerEE9instance_E\u{0}"
.as_bytes()
.as_ptr(),
);
drop(stage_manager_addr_lock);
let mut item_manager_addr_lock = lock_write(&ITEM_MANAGER_ADDR);
LookupSymbol(
addr_of_mut!(ITEM_MANAGER_ADDR),
addr_of_mut!(*item_manager_addr_lock),
"_ZN3lib9SingletonIN3app11ItemManagerEE9instance_E\0"
.as_bytes()
.as_ptr(),
);
drop(item_manager_addr_lock);
add_hook(params_main).unwrap();
}

View file

@ -35,6 +35,7 @@ use crate::training::items::apply_item;
use crate::training::reset;
use crate::training::ui::notifications;
use crate::{is_ptrainer, ITEM_MANAGER_ADDR};
use training_mod_sync::*;
// Don't remove Mii hats, Pikmin, Luma, or crafting table
const ARTICLE_ALLOWLIST: [(LuaConst, LuaConst); 9] = [
@ -232,11 +233,11 @@ static mut MIRROR_STATE: f32 = 1.0;
static mut RANDOM_SLOT: usize = 0;
unsafe fn get_slot() -> usize {
let random_slot = MENU.randomize_slots.get_random();
let random_slot = read(&MENU).randomize_slots.get_random();
if random_slot != SaveStateSlot::empty() {
RANDOM_SLOT
} else {
MENU.save_state_slot.into_idx().unwrap_or(0)
read(&MENU).save_state_slot.into_idx().unwrap_or(0)
}
}
@ -255,13 +256,13 @@ pub unsafe fn is_loading() -> bool {
}
pub unsafe fn should_mirror() -> f32 {
match MENU.save_state_mirroring {
match read(&MENU).save_state_mirroring {
SaveStateMirroring::NONE => 1.0,
SaveStateMirroring::ALTERNATE => -1.0 * MIRROR_STATE,
SaveStateMirroring::RANDOM => ([-1.0, 1.0])[get_random_int(2) as usize],
_ => panic!(
"Invalid value in should_mirror: {}",
MENU.save_state_mirroring
read(&MENU).save_state_mirroring
),
}
}
@ -383,7 +384,7 @@ pub unsafe fn on_death(fighter_kind: i32, module_accessor: &mut app::BattleObjec
ArticleModule::remove_exist_object_id(module_accessor, article_object_id as u32);
}
});
let item_mgr = *(ITEM_MANAGER_ADDR as *mut *mut app::ItemManager);
let item_mgr = *(read(&ITEM_MANAGER_ADDR) as *mut *mut app::ItemManager);
(0..ItemManager::get_num_of_active_item_all(item_mgr)).for_each(|item_idx| {
let item = ItemManager::get_active_item(item_mgr, item_idx);
if item != 0 {
@ -416,7 +417,7 @@ pub unsafe fn on_death(fighter_kind: i32, module_accessor: &mut app::BattleObjec
}
pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor) {
if MENU.save_state_enable == OnOff::OFF {
if read(&MENU).save_state_enable == OnOff::OFF {
return;
}
@ -448,7 +449,7 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
.contains(&fighter_kind);
// Reset state
let autoload_reset = MENU.save_state_autoload == OnOff::ON
let autoload_reset = read(&MENU).save_state_autoload == OnOff::ON
&& save_state.state == NoAction
&& is_dead(module_accessor);
let mut triggered_reset: bool = false;
@ -457,7 +458,7 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
}
if (autoload_reset || triggered_reset) && !fighter_is_nana {
if save_state.state == NoAction {
let random_slot = MENU.randomize_slots.get_random();
let random_slot = read(&MENU).randomize_slots.get_random();
let slot = if random_slot != SaveStateSlot::empty() {
RANDOM_SLOT = random_slot.into_idx().unwrap_or(0);
RANDOM_SLOT
@ -572,48 +573,48 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
if save_state.state == NoAction {
// Set damage of the save state
if !is_cpu {
match MENU.save_damage_player {
match read(&MENU).save_damage_player {
SaveDamage::SAVED => {
set_damage(module_accessor, save_state.percent);
}
SaveDamage::RANDOM => {
// Gen random value
let pct: f32 = get_random_float(
MENU.save_damage_limits_player.0 as f32,
MENU.save_damage_limits_player.1 as f32,
read(&MENU).save_damage_limits_player.0 as f32,
read(&MENU).save_damage_limits_player.1 as f32,
);
set_damage(module_accessor, pct);
}
SaveDamage::DEFAULT => {}
_ => panic!(
"Invalid value in save_states()::save_damage_player: {}",
MENU.save_damage_player
read(&MENU).save_damage_player
),
}
} else {
match MENU.save_damage_cpu {
match read(&MENU).save_damage_cpu {
SaveDamage::SAVED => {
set_damage(module_accessor, save_state.percent);
}
SaveDamage::RANDOM => {
// Gen random value
let pct: f32 = get_random_float(
MENU.save_damage_limits_cpu.0 as f32,
MENU.save_damage_limits_cpu.1 as f32,
read(&MENU).save_damage_limits_cpu.0 as f32,
read(&MENU).save_damage_limits_cpu.1 as f32,
);
set_damage(module_accessor, pct);
}
SaveDamage::DEFAULT => {}
_ => panic!(
"Invalid value in save_states()::save_damage_cpu: {}",
MENU.save_damage_cpu
read(&MENU).save_damage_cpu
),
}
}
// Set to held item
if !is_cpu && !fighter_is_nana && MENU.character_item != CharacterItem::NONE {
apply_item(MENU.character_item);
if !is_cpu && !fighter_is_nana && read(&MENU).character_item != CharacterItem::NONE {
apply_item(read(&MENU).character_item);
}
// Set the charge of special moves if the fighter matches the kind in the save state
@ -661,17 +662,20 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
}
// if we're recording on state load, record
if MENU.record_trigger.contains(&RecordTrigger::SAVESTATE) {
if read(&MENU)
.record_trigger
.contains(&RecordTrigger::SAVESTATE)
{
input_record::lockout_record();
return;
}
// otherwise, begin input recording playback if selected
// for ledge, don't do this - if you want playback on a ledge, you have to set it as a ledge option,
// otherwise there too many edge cases here
else if MENU.save_state_playback.get_random() != PlaybackSlot::empty()
else if read(&MENU).save_state_playback.get_random() != PlaybackSlot::empty()
&& save_state.situation_kind != SITUATION_KIND_CLIFF
{
input_record::playback(MENU.save_state_playback.get_random().into_idx());
input_record::playback(read(&MENU).save_state_playback.get_random().into_idx());
}
return;
@ -706,12 +710,12 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
if button_config::combo_passes(button_config::ButtonCombo::SaveState) {
// Don't begin saving state if Nana's delayed input is captured
MIRROR_STATE = 1.0;
save_state_player(MENU.save_state_slot.into_idx().unwrap_or(0)).state = Save;
save_state_cpu(MENU.save_state_slot.into_idx().unwrap_or(0)).state = Save;
notifications::clear_notifications("Save State");
save_state_player(read(&MENU).save_state_slot.into_idx().unwrap_or(0)).state = Save;
save_state_cpu(read(&MENU).save_state_slot.into_idx().unwrap_or(0)).state = Save;
notifications::clear_notification("Save State");
notifications::notification(
"Save State".to_string(),
format!("Saved Slot {}", MENU.save_state_slot),
format!("Saved Slot {}", read(&MENU).save_state_slot),
120,
);
}

View file

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

View file

@ -10,72 +10,81 @@ use crate::common::consts::*;
use crate::common::*;
use crate::training::{frame_counter, input_record, mash, save_states};
use once_cell::sync::Lazy;
use training_mod_sync::*;
// TODO!() We only reset this on save state load or LRA reset
// How many hits to hold shield until picking an Out Of Shield option
static mut MULTI_HIT_OFFSET: u32 = 0;
static MULTI_HIT_OFFSET: RwLock<u32> = RwLock::new(0);
// The current set delay
static mut SHIELD_DELAY: u32 = 0;
static SHIELD_DELAY: RwLock<u32> = RwLock::new(0);
// Used to only decrease once per shieldstun change
static mut WAS_IN_SHIELDSTUN: bool = false;
static WAS_IN_SHIELDSTUN: RwLock<bool> = RwLock::new(false);
// For how many frames should the shield hold be overwritten
static mut SUSPEND_SHIELD: bool = false;
static REACTION_COUNTER_INDEX: Lazy<usize> =
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
static SUSPEND_SHIELD: RwLock<bool> = RwLock::new(false);
// Toggle for shield decay
static mut SHIELD_DECAY: bool = false;
static SHIELD_DECAY: RwLock<bool> = RwLock::new(false);
/// This is the cached shield damage multiplier.
/// Vanilla is 1.19, but mods can change this.
static CACHED_SHIELD_DAMAGE_MUL: RwLock<Option<f32>> = RwLock::new(None);
static REACTION_COUNTER_INDEX: LazyLock<usize> =
LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
fn set_shield_decay(value: bool) {
unsafe {
SHIELD_DECAY = value;
}
assign(&SHIELD_DECAY, value);
}
fn should_pause_shield_decay() -> bool {
unsafe { !SHIELD_DECAY }
!read(&SHIELD_DECAY)
}
fn reset_oos_offset() {
unsafe {
/*
* Need to offset by 1, since we decrease as soon as shield gets hit
* but only check later if we can OOS
*/
MULTI_HIT_OFFSET = MENU.oos_offset.get_random().into_delay() + 1;
}
/*
* Need to offset by 1, since we decrease as soon as shield gets hit
* but only check later if we can OOS
*/
assign(
&MULTI_HIT_OFFSET,
read(&MENU).oos_offset.get_random().into_delay() + 1,
);
}
unsafe fn handle_oos_offset(module_accessor: &mut app::BattleObjectModuleAccessor) {
fn handle_oos_offset(module_accessor: &mut app::BattleObjectModuleAccessor) {
// Check if we are currently in shield stun
let mut was_in_shieldstun_lock = lock_write(&WAS_IN_SHIELDSTUN);
if !is_in_shieldstun(module_accessor) {
// Make sure we don't forget and wait until we get hit on shield
WAS_IN_SHIELDSTUN = false;
*was_in_shieldstun_lock = false;
return;
}
// Make sure we just freshly entered shield stun
if WAS_IN_SHIELDSTUN {
if *was_in_shieldstun_lock {
return;
}
// Roll shield delay
SHIELD_DELAY = MENU.reaction_time.get_random().into_delay();
assign(
&SHIELD_DELAY,
read(&MENU).reaction_time.get_random().into_delay(),
);
// Decrease offset once if needed
MULTI_HIT_OFFSET = MULTI_HIT_OFFSET.saturating_sub(1);
let mut multi_hit_offset_lock = lock_write(&MULTI_HIT_OFFSET);
*multi_hit_offset_lock = (*multi_hit_offset_lock).saturating_sub(1);
// Mark that we were in shield stun, so we don't decrease again
WAS_IN_SHIELDSTUN = true;
*was_in_shieldstun_lock = true;
}
pub unsafe fn allow_oos() -> bool {
pub fn allow_oos() -> bool {
// Delay OOS until offset hits 0
MULTI_HIT_OFFSET == 0
read(&MULTI_HIT_OFFSET) == 0
}
pub fn get_command_flag_cat(module_accessor: &mut app::BattleObjectModuleAccessor) {
@ -107,7 +116,7 @@ pub unsafe fn get_param_float(
return None;
}
if MENU.shield_state != Shield::NONE {
if read(&MENU).shield_state != Shield::NONE {
handle_oos_offset(module_accessor);
}
@ -116,10 +125,7 @@ pub unsafe fn get_param_float(
// Shield Decay//Recovery
fn handle_shield_decay(param_type: u64, param_hash: u64) -> Option<f32> {
let menu_state;
unsafe {
menu_state = MENU.shield_state;
}
let menu_state = read(&MENU).shield_state;
if menu_state != Shield::INFINITE
&& menu_state != Shield::CONSTANT
@ -143,10 +149,6 @@ fn handle_shield_decay(param_type: u64, param_hash: u64) -> Option<f32> {
None
}
/// This is the cached shield damage multiplier.
/// Vanilla is 1.19, but mods can change this.
static mut CACHED_SHIELD_DAMAGE_MUL: Option<f32> = None;
/// sets/resets the shield_damage_mul within
/// the game's internal structure.
///
@ -155,20 +157,20 @@ static mut CACHED_SHIELD_DAMAGE_MUL: Option<f32> = None;
pub unsafe fn param_installer() {
if crate::training::COMMON_PARAMS as usize != 0 {
let common_params = &mut *crate::training::COMMON_PARAMS;
let mut cached_shield_damage_mul_lock = lock_write(&CACHED_SHIELD_DAMAGE_MUL);
// cache the original shield damage multiplier once
if CACHED_SHIELD_DAMAGE_MUL.is_none() {
CACHED_SHIELD_DAMAGE_MUL = Some(common_params.shield_damage_mul);
if (*cached_shield_damage_mul_lock).is_none() {
*cached_shield_damage_mul_lock = Some(common_params.shield_damage_mul);
}
if is_training_mode() && (MENU.shield_state == Shield::INFINITE) {
if is_training_mode() && (read(&MENU).shield_state == Shield::INFINITE) {
// if you are in training mode and have infinite shield enabled,
// set the game's shield_damage_mul to 0.0
common_params.shield_damage_mul = 0.0;
} else {
// reset the game's shield_damage_mul back to what
// it originally was at game boot.
common_params.shield_damage_mul = CACHED_SHIELD_DAMAGE_MUL.unwrap();
common_params.shield_damage_mul = (*cached_shield_damage_mul_lock).unwrap();
}
}
}
@ -185,10 +187,7 @@ pub fn should_hold_shield(module_accessor: &mut app::BattleObjectModuleAccessor)
return true;
}
let shield_state;
unsafe {
shield_state = &MENU.shield_state;
}
let shield_state = &read(&MENU).shield_state;
// We should hold shield if the state requires it
if unsafe { save_states::is_loading() }
@ -223,7 +222,7 @@ unsafe fn mod_handle_sub_guard_cont(fighter: &mut L2CFighterCommon) {
}
// Enable shield decay
if MENU.shield_state == Shield::HOLD {
if read(&MENU).shield_state == Shield::HOLD {
set_shield_decay(true);
}
@ -237,7 +236,7 @@ unsafe fn mod_handle_sub_guard_cont(fighter: &mut L2CFighterCommon) {
return;
}
if frame_counter::should_delay(SHIELD_DELAY, *REACTION_COUNTER_INDEX) {
if frame_counter::should_delay(read(&SHIELD_DELAY), *REACTION_COUNTER_INDEX) {
return;
}
@ -245,11 +244,11 @@ unsafe fn mod_handle_sub_guard_cont(fighter: &mut L2CFighterCommon) {
return;
}
if MENU.mash_triggers.contains(&MashTrigger::SHIELDSTUN) {
if MENU.shieldstun_override == Action::empty() {
mash::external_buffer_menu_mash(MENU.mash_state.get_random())
if read(&MENU).mash_triggers.contains(&MashTrigger::SHIELDSTUN) {
if read(&MENU).shieldstun_override == Action::empty() {
mash::external_buffer_menu_mash(read(&MENU).mash_state.get_random())
} else {
mash::external_buffer_menu_mash(MENU.shieldstun_override.get_random())
mash::external_buffer_menu_mash(read(&MENU).shieldstun_override.get_random())
}
}
@ -355,10 +354,7 @@ fn needs_oos_handling_drop_shield() -> bool {
}
// Make sure we only flicker shield when Airdodge and Shield mash options are selected
if action == Action::AIR_DODGE {
let shield_state;
unsafe {
shield_state = &MENU.shield_state;
}
let shield_state = &read(&MENU).shield_state;
// If we're supposed to be holding shield, let airdodge make us drop shield
if [Shield::HOLD, Shield::INFINITE, Shield::CONSTANT].contains(shield_state) {
suspend_shield(Action::AIR_DODGE);
@ -368,10 +364,7 @@ fn needs_oos_handling_drop_shield() -> bool {
// Make sure we only flicker shield when Airdodge and Shield mash options are selected
if action == Action::AIR_DODGE {
let shield_state;
unsafe {
shield_state = &MENU.shield_state;
}
let shield_state = &read(&MENU).shield_state;
// If we're supposed to be holding shield, let airdodge make us drop shield
if [Shield::HOLD, Shield::INFINITE, Shield::CONSTANT].contains(shield_state) {
suspend_shield(Action::AIR_DODGE);
@ -380,10 +373,7 @@ fn needs_oos_handling_drop_shield() -> bool {
}
if action == Action::SHIELD {
let shield_state;
unsafe {
shield_state = &MENU.shield_state;
}
let shield_state = &read(&MENU).shield_state;
// Don't drop shield on shield hit if we're supposed to be holding shield
if [Shield::HOLD, Shield::INFINITE, Shield::CONSTANT].contains(shield_state) {
return false;
@ -402,9 +392,7 @@ pub fn is_aerial(action: Action) -> bool {
// Needed for shield drop options
pub fn suspend_shield(action: Action) {
unsafe {
SUSPEND_SHIELD = need_suspend_shield(action);
}
assign(&SUSPEND_SHIELD, need_suspend_shield(action));
}
fn need_suspend_shield(action: Action) -> bool {
@ -427,7 +415,7 @@ fn need_suspend_shield(action: Action) -> bool {
* Needed for these options to work OOS
*/
fn shield_is_suspended() -> bool {
unsafe { SUSPEND_SHIELD }
read(&SUSPEND_SHIELD)
}
/**

View file

@ -2,31 +2,29 @@ use smash::app::{self};
use crate::common::consts::*;
use crate::common::*;
use training_mod_sync::*;
static mut STICK_DIRECTION: Direction = Direction::OUT;
static SHIELD_STICK_DIRECTION: RwLock<Direction> = RwLock::new(Direction::OUT);
pub fn roll_direction() {
unsafe {
STICK_DIRECTION = MENU.shield_tilt.get_random();
}
assign(
&SHIELD_STICK_DIRECTION,
read(&MENU).shield_tilt.get_random(),
);
}
pub unsafe fn mod_get_stick_x(
module_accessor: &mut app::BattleObjectModuleAccessor,
) -> Option<f32> {
pub fn mod_get_stick_x(module_accessor: &mut app::BattleObjectModuleAccessor) -> Option<f32> {
get_angle(module_accessor).map(|a| a.cos() as f32)
}
pub unsafe fn mod_get_stick_y(
module_accessor: &mut app::BattleObjectModuleAccessor,
) -> Option<f32> {
pub fn mod_get_stick_y(module_accessor: &mut app::BattleObjectModuleAccessor) -> Option<f32> {
get_angle(module_accessor).map(|a| a.sin() as f32)
}
unsafe fn get_angle(module_accessor: &mut app::BattleObjectModuleAccessor) -> Option<f64> {
fn get_angle(module_accessor: &mut app::BattleObjectModuleAccessor) -> Option<f64> {
if !is_operation_cpu(module_accessor) {
return None;
}
STICK_DIRECTION.into_angle()
let stick_direction = read(&SHIELD_STICK_DIRECTION);
stick_direction.into_angle()
}

View file

@ -1,3 +1,6 @@
use std::collections::HashMap;
use skyline::hooks::{getRegionAddress, Region};
use smash::app::{lua_bind::*, sv_system, BattleObjectModuleAccessor};
use smash::hash40;
use smash::lib::lua_const::*;
@ -6,28 +9,24 @@ use smash::lua2cpp::L2CFighterBase;
use smash::phx::{Hash40, Vector3f};
use crate::common::consts::*;
use crate::common::offsets::OFFSET_CHANGE_ACTIVE_CAMERA;
use crate::common::offsets::OFFSET_SET_TRAINING_FIXED_CAMERA_VALUES;
use crate::common::offsets::{
OFFSET_CHANGE_ACTIVE_CAMERA, OFFSET_SET_TRAINING_FIXED_CAMERA_VALUES,
};
use crate::common::*;
use crate::training::{frame_counter, mash, save_states};
use training_mod_sync::*;
use skyline::hooks::{getRegionAddress, Region};
use once_cell::sync::Lazy;
use std::collections::HashMap;
static mut TECH_ROLL_DIRECTION: Direction = Direction::empty();
static mut MISS_TECH_ROLL_DIRECTION: Direction = Direction::empty();
static mut NEEDS_VISIBLE: bool = false;
static mut DEFAULT_FIXED_CAM_CENTER: Vector3f = Vector3f {
static TECH_ROLL_DIRECTION: RwLock<Direction> = RwLock::new(Direction::empty());
static MISS_TECH_ROLL_DIRECTION: RwLock<Direction> = RwLock::new(Direction::empty());
static NEEDS_VISIBLE: RwLock<bool> = RwLock::new(false);
static DEFAULT_FIXED_CAM_CENTER: RwLock<Vector3f> = RwLock::new(Vector3f {
x: 0.0,
y: 0.0,
z: 0.0,
};
});
static FRAME_COUNTER: Lazy<usize> =
Lazy::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
static FRAME_COUNTER: LazyLock<usize> =
LazyLock::new(|| frame_counter::register_counter(frame_counter::FrameCounterType::InGame));
unsafe fn is_enable_passive(module_accessor: &mut BattleObjectModuleAccessor) -> bool {
let fighter = get_fighter_common_from_accessor(module_accessor);
@ -64,7 +63,7 @@ unsafe fn mod_handle_change_status(
.try_get_int()
.unwrap_or(*FIGHTER_STATUS_KIND_WAIT as u64) as i32;
let state: TechFlags = MENU.tech_state.get_random();
let state: TechFlags = read(&MENU).tech_state.get_random();
if handle_grnd_tech(module_accessor, status_kind, unk, status_kind_int, state) {
return;
@ -115,22 +114,22 @@ unsafe fn handle_grnd_tech(
TechFlags::ROLL_F => {
*status_kind = FIGHTER_STATUS_KIND_PASSIVE_FB.as_lua_int();
*unk = LUA_TRUE;
TECH_ROLL_DIRECTION = Direction::IN; // = In
assign(&TECH_ROLL_DIRECTION, Direction::IN);
true
}
TechFlags::ROLL_B => {
*status_kind = FIGHTER_STATUS_KIND_PASSIVE_FB.as_lua_int();
*unk = LUA_TRUE;
TECH_ROLL_DIRECTION = Direction::OUT; // = Away
assign(&TECH_ROLL_DIRECTION, Direction::OUT);
true
}
_ => false,
};
if do_tech && MENU.mash_triggers.contains(&MashTrigger::TECH) {
if MENU.tech_action_override == Action::empty() {
mash::external_buffer_menu_mash(MENU.mash_state.get_random())
if do_tech && read(&MENU).mash_triggers.contains(&MashTrigger::TECH) {
if read(&MENU).tech_action_override == Action::empty() {
mash::external_buffer_menu_mash(read(&MENU).mash_state.get_random())
} else {
mash::external_buffer_menu_mash(MENU.tech_action_override.get_random())
mash::external_buffer_menu_mash(read(&MENU).tech_action_override.get_random())
}
}
@ -173,11 +172,11 @@ unsafe fn handle_wall_tech(
}
_ => false,
};
if do_tech && MENU.mash_triggers.contains(&MashTrigger::TECH) {
if MENU.tech_action_override == Action::empty() {
mash::external_buffer_menu_mash(MENU.mash_state.get_random())
if do_tech && read(&MENU).mash_triggers.contains(&MashTrigger::TECH) {
if read(&MENU).tech_action_override == Action::empty() {
mash::external_buffer_menu_mash(read(&MENU).mash_state.get_random())
} else {
mash::external_buffer_menu_mash(MENU.tech_action_override.get_random())
mash::external_buffer_menu_mash(read(&MENU).tech_action_override.get_random())
}
}
true
@ -208,18 +207,18 @@ unsafe fn handle_ceil_tech(
*status_kind = FIGHTER_STATUS_KIND_PASSIVE_CEIL.as_lua_int();
*unk = LUA_TRUE;
if MENU.mash_triggers.contains(&MashTrigger::TECH) {
if MENU.tech_action_override == Action::empty() {
mash::external_buffer_menu_mash(MENU.mash_state.get_random())
if read(&MENU).mash_triggers.contains(&MashTrigger::TECH) {
if read(&MENU).tech_action_override == Action::empty() {
mash::external_buffer_menu_mash(read(&MENU).mash_state.get_random())
} else {
mash::external_buffer_menu_mash(MENU.tech_action_override.get_random())
mash::external_buffer_menu_mash(read(&MENU).tech_action_override.get_random())
}
}
true
}
pub unsafe fn get_command_flag_cat(module_accessor: &mut BattleObjectModuleAccessor) {
if !is_operation_cpu(module_accessor) || MENU.tech_state == TechFlags::empty() {
if !is_operation_cpu(module_accessor) || read(&MENU).tech_state == TechFlags::empty() {
return;
}
@ -232,15 +231,15 @@ pub unsafe fn get_command_flag_cat(module_accessor: &mut BattleObjectModuleAcces
.contains(&status)
{
// Mistech
requested_status = match MENU.miss_tech_state.get_random() {
requested_status = match read(&MENU).miss_tech_state.get_random() {
MissTechFlags::GETUP => *FIGHTER_STATUS_KIND_DOWN_STAND,
MissTechFlags::ATTACK => *FIGHTER_STATUS_KIND_DOWN_STAND_ATTACK,
MissTechFlags::ROLL_F => {
MISS_TECH_ROLL_DIRECTION = Direction::IN; // = In
assign(&MISS_TECH_ROLL_DIRECTION, Direction::IN);
*FIGHTER_STATUS_KIND_DOWN_STAND_FB
}
MissTechFlags::ROLL_B => {
MISS_TECH_ROLL_DIRECTION = Direction::OUT; // = Away
assign(&MISS_TECH_ROLL_DIRECTION, Direction::OUT);
*FIGHTER_STATUS_KIND_DOWN_STAND_FB
}
_ => return,
@ -251,22 +250,22 @@ pub unsafe fn get_command_flag_cat(module_accessor: &mut BattleObjectModuleAcces
if frame_counter::should_delay(lockout_time, *FRAME_COUNTER) {
return;
};
requested_status = match MENU.miss_tech_state.get_random() {
requested_status = match read(&MENU).miss_tech_state.get_random() {
MissTechFlags::GETUP => *FIGHTER_STATUS_KIND_DOWN_STAND,
MissTechFlags::ATTACK => *FIGHTER_STATUS_KIND_DOWN_STAND_ATTACK,
MissTechFlags::ROLL_F => {
MISS_TECH_ROLL_DIRECTION = Direction::IN; // = In
assign(&MISS_TECH_ROLL_DIRECTION, Direction::IN);
*FIGHTER_STATUS_KIND_DOWN_STAND_FB
}
MissTechFlags::ROLL_B => {
MISS_TECH_ROLL_DIRECTION = Direction::OUT; // = Away
assign(&MISS_TECH_ROLL_DIRECTION, Direction::OUT);
*FIGHTER_STATUS_KIND_DOWN_STAND_FB
}
_ => return,
};
} else if status == *FIGHTER_STATUS_KIND_SLIP_WAIT {
// Handle slips (like Diddy banana)
requested_status = match MENU.miss_tech_state.get_random() {
requested_status = match read(&MENU).miss_tech_state.get_random() {
MissTechFlags::GETUP => *FIGHTER_STATUS_KIND_SLIP_STAND,
MissTechFlags::ATTACK => *FIGHTER_STATUS_KIND_SLIP_STAND_ATTACK,
MissTechFlags::ROLL_F => *FIGHTER_STATUS_KIND_SLIP_STAND_F,
@ -280,11 +279,11 @@ pub unsafe fn get_command_flag_cat(module_accessor: &mut BattleObjectModuleAcces
if requested_status != 0 {
StatusModule::change_status_force(module_accessor, requested_status, true);
if MENU.mash_triggers.contains(&MashTrigger::MISTECH) {
if MENU.tech_action_override == Action::empty() {
mash::external_buffer_menu_mash(MENU.mash_state.get_random())
if read(&MENU).mash_triggers.contains(&MashTrigger::MISTECH) {
if read(&MENU).tech_action_override == Action::empty() {
mash::external_buffer_menu_mash(read(&MENU).mash_state.get_random())
} else {
mash::external_buffer_menu_mash(MENU.tech_action_override.get_random())
mash::external_buffer_menu_mash(read(&MENU).tech_action_override.get_random())
}
}
}
@ -298,24 +297,24 @@ pub unsafe fn change_motion(
return None;
}
if MENU.tech_state == TechFlags::empty() {
if read(&MENU).tech_state == TechFlags::empty() {
return None;
}
if [hash40("passive_stand_f"), hash40("passive_stand_b")].contains(&motion_kind) {
if TECH_ROLL_DIRECTION == Direction::IN {
if read(&TECH_ROLL_DIRECTION) == Direction::IN {
return Some(hash40("passive_stand_f"));
} else {
return Some(hash40("passive_stand_b"));
}
} else if [hash40("down_forward_u"), hash40("down_back_u")].contains(&motion_kind) {
if MISS_TECH_ROLL_DIRECTION == Direction::IN {
if read(&MISS_TECH_ROLL_DIRECTION) == Direction::IN {
return Some(hash40("down_forward_u"));
} else {
return Some(hash40("down_back_u"));
}
} else if [hash40("down_forward_d"), hash40("down_back_d")].contains(&motion_kind) {
if MISS_TECH_ROLL_DIRECTION == Direction::IN {
if read(&MISS_TECH_ROLL_DIRECTION) == Direction::IN {
return Some(hash40("down_forward_d"));
} else {
return Some(hash40("down_back_d"));
@ -350,7 +349,7 @@ unsafe fn get_snake_laydown_lockout_time(module_accessor: &mut BattleObjectModul
}
pub unsafe fn hide_tech() {
if !is_training_mode() || MENU.tech_hide == OnOff::OFF {
if !is_training_mode() || read(&MENU).tech_hide == OnOff::OFF {
return;
}
let module_accessor = get_module_accessor(FighterId::CPU);
@ -370,7 +369,7 @@ pub unsafe fn hide_tech() {
);
// Disable visibility
if MotionModule::frame(module_accessor) >= 6.0 {
NEEDS_VISIBLE = true;
assign(&NEEDS_VISIBLE, true);
VisibilityModule::set_whole(module_accessor, false);
EffectModule::set_visible_kind(module_accessor, Hash40::new("sys_nopassive"), false);
EffectModule::set_visible_kind(module_accessor, Hash40::new("sys_down_smoke"), false);
@ -384,15 +383,17 @@ pub unsafe fn hide_tech() {
}
if MotionModule::end_frame(module_accessor) - MotionModule::frame(module_accessor) <= 5.0 {
// Re-enable visibility
NEEDS_VISIBLE = false;
assign(&NEEDS_VISIBLE, false);
VisibilityModule::set_whole(module_accessor, true);
}
} else {
// If the CPU's tech status was interrupted, make them visible again
if NEEDS_VISIBLE {
NEEDS_VISIBLE = false;
let mut needs_visible_lock = lock_write(&NEEDS_VISIBLE);
if *needs_visible_lock {
*needs_visible_lock = false;
VisibilityModule::set_whole(module_accessor, true);
}
drop(needs_visible_lock);
}
}
@ -406,7 +407,7 @@ pub unsafe fn handle_fighter_req_quake_pos(
return original!()(module_accessor, quake_kind);
}
let status = StatusModule::status_kind(module_accessor);
if status == FIGHTER_STATUS_KIND_DOWN && MENU.tech_hide == OnOff::ON {
if status == FIGHTER_STATUS_KIND_DOWN && read(&MENU).tech_hide == OnOff::ON {
// We're hiding techs, prevent mistech quake from giving away missed tech
return original!()(module_accessor, *CAMERA_QUAKE_KIND_NONE);
}
@ -449,9 +450,9 @@ pub struct CameraManager {
unsafe fn set_fixed_camera_values() {
let camera_manager = get_camera_manager();
if MENU.tech_hide == OnOff::OFF {
if read(&MENU).tech_hide == OnOff::OFF {
// Use Stage's Default Values for fixed Camera
camera_manager.fixed_camera_center = DEFAULT_FIXED_CAM_CENTER;
camera_manager.fixed_camera_center = read(&DEFAULT_FIXED_CAM_CENTER);
} else {
// We're in CameraMode 4, which is Fixed, and we are hiding tech chases, so we want a better view of the stage
if let Some(camera_vector) = get_stage_camera_values(save_states::stage_id()) {
@ -738,7 +739,10 @@ pub unsafe fn handle_set_training_fixed_camera_values(
if !is_training_mode() {
return original!()(camera_manager, fixed_camera_values);
}
DEFAULT_FIXED_CAM_CENTER = fixed_camera_values.fixed_camera_center;
assign(
&DEFAULT_FIXED_CAM_CENTER,
fixed_camera_values.fixed_camera_center,
);
original!()(camera_manager, fixed_camera_values);
// Set Fixed Camera Values now, since L + R + A reset switches without calling ChangeActiveCamera
set_fixed_camera_values();

View file

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

View file

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

View file

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

View file

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

View file

@ -11,6 +11,7 @@ use crate::common::{is_ready_go, is_training_mode};
#[cfg(feature = "layout_arc_from_file")]
use crate::consts::LAYOUT_ARC_PATH;
use crate::training::frame_counter;
use training_mod_sync::*;
mod damage;
mod display;
@ -71,7 +72,7 @@ pub unsafe fn handle_draw(layout: *mut Layout, draw_info: u64, cmd_buffer: u64)
{
// InfluencedAlpha means "Should my children panes' alpha be influenced by mine, as the parent?"
root_pane.flags |= 1 << PaneFlag::InfluencedAlpha as u8;
root_pane.set_visible(MENU.hud == OnOff::ON && !QUICK_MENU_ACTIVE);
root_pane.set_visible(read(&MENU).hud == OnOff::ON && !read(&QUICK_MENU_ACTIVE));
}
damage::draw(root_pane, &layout_name);

View file

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

View file

@ -17,7 +17,8 @@ skyline_smash = { git = "https://github.com/ultimate-research/skyline-smash.git"
toml = "0.5.9"
anyhow = "1.0.72"
rand = { git = "https://github.com/skyline-rs/rand" }
training_mod_tui = { path = "../training_mod_tui"}
training_mod_sync = { path = "../training_mod_sync" }
training_mod_tui = { path = "../training_mod_tui" }
[features]
default = ["smash"]

View file

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

View file

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

View file

@ -0,0 +1,36 @@
/// Convenience functions for interacting with RwLocks
pub use std::sync::{LazyLock, RwLock};
use std::sync::{RwLockReadGuard, RwLockWriteGuard};
/// Gets a copy of a value inside a RwLock and immediately unlocks
///
/// Requires <T: Copy> such as a bool or usize
pub fn read<T: Copy>(rwlock: &RwLock<T>) -> T {
*rwlock.read().unwrap()
}
/// Gets a clone of a value inside a RwLock and immediately unlocks
///
/// Can be used if <T> is not Copy, such as Vec<u32>
pub fn read_clone<T: Clone>(rwlock: &RwLock<T>) -> T {
rwlock.read().unwrap().clone()
}
/// Assigns a new value to a RwLock and immediately unlocks
pub fn assign<T>(rwlock: &RwLock<T>, new_val: T) {
*rwlock.write().unwrap() = new_val
}
/// Locks a RwLock for writing and returns the guard
///
/// Don't forget to drop the guard as soon as you're finished with it
pub fn lock_write<T>(rwlock: &RwLock<T>) -> RwLockWriteGuard<T> {
rwlock.write().unwrap()
}
/// Locks a RwLock for reading and returns the guard
///
/// Don't forget to drop the guard as soon as you're finished with it
pub fn lock_read<T>(rwlock: &RwLock<T>) -> RwLockReadGuard<T> {
rwlock.read().unwrap()
}

View file

@ -13,7 +13,7 @@ pub enum AppPage {
CLOSE,
}
#[derive(PartialEq)]
#[derive(PartialEq, Clone, Copy)]
pub enum ConfirmationState {
HoverNo,
HoverYes,
@ -40,7 +40,7 @@ impl ConfirmationState {
// │ OR
// │
// └─ Option<Slider>
#[derive(Clone)]
pub struct App<'a> {
pub tabs: StatefulList<Tab<'a>>,
pub page: AppPage,

View file

@ -266,7 +266,7 @@ impl<'a, T: Clone + Serialize> Iterator for StatefulTableIteratorMut<'a, T> {
}
impl<'a, T: Clone + Serialize + 'a> StatefulTable<T> {
pub fn iter_mut(&'a mut self) -> StatefulTableIteratorMut<T> {
pub fn iter_mut(&'a mut self) -> StatefulTableIteratorMut<'a, T> {
StatefulTableIteratorMut {
inner: self.items.iter_mut().flatten(),
}