1
0
Fork 0
mirror of https://github.com/jugeeya/UltimateTrainingModpack.git synced 2025-03-26 08:12:42 +00:00

input_record.rs

This commit is contained in:
asimon-1 2024-11-14 19:21:41 -05:00
parent 301916024d
commit a685a446d7
2 changed files with 143 additions and 114 deletions
src

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,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" {