diff --git a/src/common/input.rs b/src/common/input.rs index 8335a6b..e1bb417 100644 --- a/src/common/input.rs +++ b/src/common/input.rs @@ -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, diff --git a/src/training/input_record.rs b/src/training/input_record.rs index 34f4953..d7b7143 100644 --- a/src/training/input_record.rs +++ b/src/training/input_record.rs @@ -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::*; @@ -22,7 +20,7 @@ use crate::{error, warn}; use training_mod_sync::*; -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Debug, Clone, Copy)] pub enum InputRecordState { None, Pause, @@ -30,7 +28,7 @@ pub enum InputRecordState { Playback, } -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Debug, Clone, Copy)] pub enum PossessionState { Player, Cpu, @@ -58,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_rwlock(&STARTING_STATUS))); WorkModule::is_enable_transition_term(module_accessor, transition_term) } @@ -196,7 +190,10 @@ 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 = get(&MENU).recording_slot.into_idx().unwrap_or(0); + assign_rwlock( + &CURRENT_RECORD_SLOT, + get(&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) { @@ -206,44 +203,51 @@ unsafe fn handle_recording_for_fighter(module_accessor: &mut BattleObjectModuleA { lockout_record(); } - if INPUT_RECORD == None { + let input_record = read_rwlock(&INPUT_RECORD); + if input_record == None { clear_notifications_except("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_rwlock(&INPUT_RECORD_FRAME); + if (input_record == Record || input_record == Playback) + && *input_record_frame >= read_rwlock(&CURRENT_FRAME_LENGTH) - 1 { - POSSESSION = Player; + assign_rwlock(&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 + if input_record == Record && get(&MENU).recording_crop == OnOff::ON - && INPUT_RECORD_FRAME > 0 + && *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_rwlock(&CURRENT_FRAME_LENGTH, *input_record_frame); + let mut p1_frame_length_mapping = lock_write_rwlock(&P1_FRAME_LENGTH_MAPPING); + (*p1_frame_length_mapping)[read_rwlock(&CURRENT_RECORD_SLOT)] = *input_record_frame; + drop(p1_frame_length_mapping); } - INPUT_RECORD_FRAME = 0; + *input_record_frame = 0; - if get(&MENU).playback_loop == OnOff::ON && INPUT_RECORD == Playback { - playback(Some(CURRENT_PLAYBACK_SLOT)); + if get(&MENU).playback_loop == OnOff::ON && input_record == Playback { + let playback_slot = read_rwlock(&CURRENT_PLAYBACK_SLOT); + playback(Some(playback_slot)); } else { - INPUT_RECORD = None; + assign_rwlock(&INPUT_RECORD, None); } } + drop(input_record_frame); } // Handle Possession Coloring - if entry_id_int == 1 && POSSESSION == Lockout { + let possession = read_rwlock(&POSSESSION); + if entry_id_int == 1 && possession == Lockout { clear_notifications_except("Input Recording"); color_notification( "Input Recording".to_string(), @@ -263,7 +267,7 @@ 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 { + } else if entry_id_int == 1 && possession == Standby { clear_notifications_except("Input Recording"); color_notification( "Input Recording".to_string(), @@ -283,7 +287,7 @@ 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 { + } else if entry_id_int == 1 && possession == Cpu { clear_notifications_except("Input Recording"); color_notification( "Input Recording".to_string(), @@ -303,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_rwlock(&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_rwlock(&INPUT_RECORD_FRAME); + if input_record_frame == 0 || input_record_frame == 1 { // can be either, seems like a thread issue clear_notifications_except("Input Recording"); color_notification( "Input Recording".to_string(), "Playback".to_owned(), - CURRENT_FRAME_LENGTH as u32, + read_rwlock(&CURRENT_FRAME_LENGTH) as u32, ResColor { r: 0, g: 0, @@ -324,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 = get(&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 = get(&MENU).recording_duration.into_frames(); + let current_record_slot = read_rwlock(&CURRENT_RECORD_SLOT); + let mut p1_final_mapping = lock_write_rwlock(&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_rwlock(&P1_FRAME_LENGTH_MAPPING); + (*p1_frame_length_mapping)[current_record_slot] = recording_duration; + drop(p1_frame_length_mapping); + assign_rwlock(&CURRENT_FRAME_LENGTH, recording_duration); + assign_rwlock(&INPUT_RECORD, Pause); + assign_rwlock(&INPUT_RECORD_FRAME, 0); + assign_rwlock(&POSSESSION, Lockout); + assign_rwlock(&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_rwlock(&BUFFER_FRAME, 0); + // Store the direction the CPU is facing when we initially record, so we can turn their inputs around if needed + assign_rwlock(&RECORDED_LR, PostureModule::lr(cpu_module_accessor)); + assign_rwlock(&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_rwlock(&INPUT_RECORD) == Pause { warn!("Tried to playback during lockout!"); return false; } @@ -353,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_rwlock(&P1_FRAME_LENGTH_MAPPING)[slot]; + assign_rwlock(&CURRENT_FRAME_LENGTH, frame_length); + assign_rwlock(&CURRENT_PLAYBACK_SLOT, slot); + assign_rwlock(&INPUT_RECORD, Playback); + assign_rwlock(&POSSESSION, Player); + assign_rwlock(&INPUT_RECORD_FRAME, 0); + assign_rwlock(&BUFFER_FRAME, 0); + assign_rwlock(&CURRENT_LR, PostureModule::lr(cpu_module_accessor)); true } @@ -369,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_rwlock(&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_rwlock(&INPUT_RECORD, None); + assign_rwlock(&INPUT_RECORD_FRAME, 0); + assign_rwlock(&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_rwlock(&CURRENT_RECORD_SLOT); + let frame_input = read_rwlock(&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); @@ -410,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_rwlock(&POSSESSION); if player_idx == 0 { // if player 1 - if INPUT_RECORD == Record { + if read_rwlock(&INPUT_RECORD) == Record { + let mut input_record_frame = lock_write_rwlock(&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_rwlock( + &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_rwlock(&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_rwlock(&P1_FINAL_MAPPING); + let current_record_slot = read_rwlock(&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(); } } @@ -454,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_rwlock(&INPUT_RECORD) == None { should_mash_playback(); } @@ -466,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_rwlock(&INPUT_RECORD) == Pause { + let lockout_frame = read_rwlock(&LOCKOUT_FRAME); + match lockout_frame.cmp(&0) { + Ordering::Greater => assign_rwlock(&LOCKOUT_FRAME, lockout_frame - 1), Ordering::Equal => { - INPUT_RECORD = Record; - POSSESSION = Standby; + assign_rwlock(&INPUT_RECORD, Record); + assign_rwlock(&POSSESSION, Standby); } Ordering::Less => error!("LOCKOUT_FRAME OUT OF BOUNDS"), } } - if INPUT_RECORD == Record || INPUT_RECORD == Playback { + let input_record = read_rwlock(&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_rwlock(&RECORDED_LR) * read_rwlock(&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 { @@ -509,13 +530,17 @@ 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_rwlock(&INPUT_RECORD_FRAME); + let slot = if input_record == Record { + read_rwlock(&CURRENT_RECORD_SLOT) } else { - CURRENT_PLAYBACK_SLOT - }][INPUT_RECORD_FRAME]; - - if BUFFER_FRAME <= 3 && BUFFER_FRAME > 0 { + read_rwlock(&CURRENT_PLAYBACK_SLOT) + }; + // TODO! I think something is wrong here, clippy says p1_final_mapping doesn't need to be mutable but we're definitely modifying its contents? + let mut p1_final_mapping = lock_write_rwlock(&P1_FINAL_MAPPING); + let mut saved_mapped_inputs = (*p1_final_mapping)[slot][*input_record_frame]; + let mut buffer_frame = lock_write_rwlock(&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(); } @@ -544,24 +569,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_rwlock(&CURRENT_FRAME_LENGTH) - 1 + && read_rwlock(&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_rwlock(&INPUT_RECORD); + input_record == Record || input_record == Playback } -pub unsafe fn is_recording() -> bool { - INPUT_RECORD == Record +pub fn is_recording() -> bool { + read_rwlock(&INPUT_RECORD) == Record } pub unsafe fn is_standby() -> bool { - POSSESSION == Standby || POSSESSION == Lockout + let possession = read_rwlock(&POSSESSION); + possession == Standby || possession == Lockout } extern "C" {