1
0
Fork 0
mirror of https://github.com/jugeeya/UltimateTrainingModpack.git synced 2024-11-27 20:34:03 +00:00

Input Logger (#591)

* Initial

* Begin to use font glyphs, small refactor

* Colored icons in 5 slots

* Dedupe, some color changes

* Add 10 input log spaces

* Add lots, but glitchy fade out needs fixing

* Frame Counter fixes

* Remove testing bflyt

* Use just text for now

* Add support for raw inputs, menu option

* Fix stick thresholding for the most part?

* Small change

* Add statuses!

* Some fixes

* Fix softlock thing, in general fix frame-by-frame + TTL weirdness

* Mid-refactor

* Use i8 for thresholds; update layout.arc to master's

* Merge fixes

* More fixes

* Formatting

* Last fixes for clippy / formatting

* Input Display Status Breakdown

* More formatting

* More fixes

* Fix again

* Replaced input log bflyt, added input icon bflyt

* Fixed sizes of all top level frames

* Added Icon part pane

* Got icons imported to bflyt

* Added icon parts to Icons Pane

* Raised size of layout

* Commented out handling of icons as text

* Added most icons, updated icons to match names

* Added plus and minus

* Added analog stick panes to layout

* Got icons working in game

* Fix for lua consts; update print_fighter_info to use consts with names

* Update for consts

* Fix again

* into_iter

* from_iter

* import trait

* Try setting colors

* Fixes

* deref

* Clippy

* Try white color

* Use same upstream skyline-smash

* Try black color with alpha of 0

* Use white for lstick

* Added input display menu icon, removed breakdown from input status display

* Fix formatting + clippy

* Fixed layout, updated icon colours

---------

Co-authored-by: Matthew Edell <edell.matthew@gmail.com>
This commit is contained in:
jugeeya 2023-09-01 13:53:04 -07:00 committed by GitHub
parent ebe6393143
commit 7e0617e377
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 10090 additions and 334 deletions

View file

@ -13,7 +13,9 @@ skyline_smash = { git = "https://github.com/GradualSyrup/skyline-smash.git", bra
skyline-web = { git = "https://github.com/skyline-rs/skyline-web.git" }
bitflags = "1.2.1"
parking_lot = { version = "0.12.0", features = ["nightly"] }
convert_case = "0.6.0"
include-flate = "0.1.4"
itertools = "0.11.0"
lazy_static = "1.4.0"
modular-bitfield = "0.11.2"
owo-colors = "2.1.0"
@ -29,13 +31,14 @@ minreq = { version = "2", features = ["https-native", "json-using-serde"] }
sarc = { version = "1.2.0", features = [], default_features = false }
serde = { version = "1", features = ["derive"] }
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_tui = { path = "training_mod_tui" }
native-tls = { version = "0.2.11", features = ["vendored"] }
log = "0.4.17"
byte-unit = "4.0.18"
zip = { version = "0.6", default-features = false, features = ["deflate"]}
zip = { version = "0.6", default-features = false, features = ["deflate"] }
anyhow = "1.0.72"
@ -54,7 +57,7 @@ lto = true
titleid = "01006A800016E000"
plugin-dependencies = [
{ name = "libnro_hook.nro", url = "https://github.com/ultimate-research/nro-hook-plugin/releases/download/v0.4.0/libnro_hook.nro" },
{ name = "libparam_hook.nro", url = "https://github.com/ultimate-research/params-hook-plugin/releases/download/v0.1.1/libparam_hook.nro" }
{ name = "libparam_hook.nro", url = "https://github.com/ultimate-research/params-hook-plugin/releases/download/v0.1.1/libparam_hook.nro" },
]
[features]

View file

@ -49,6 +49,114 @@ pub fn button_mapping(
}
}
pub fn name_to_font_glyph(button: ButtonConfig, style: ControllerStyle) -> Option<u16> {
let is_gcc = style == ControllerStyle::GCController;
Some(match button {
ButtonConfig::A => 0xE0E0,
// TODO: Find one that works in training...
ButtonConfig::B => 0xE0E0,
ButtonConfig::X => {
if is_gcc {
0xE206
} else {
0xE0E2
}
}
ButtonConfig::Y => {
if is_gcc {
0xE207
} else {
0xE0E3
}
}
ButtonConfig::L => {
if is_gcc {
return None;
} else {
0xE0E4
}
}
ButtonConfig::R => {
if is_gcc {
0xE205
} else {
0xE0E5
}
}
ButtonConfig::ZL => {
if is_gcc {
0xE204
} else {
0xE0E6
}
}
ButtonConfig::ZR => {
if is_gcc {
0xE208
} else {
0xE0E7
}
}
ButtonConfig::DPAD_UP => {
if is_gcc {
0xE209
} else {
0xE079
}
}
ButtonConfig::DPAD_DOWN => {
if is_gcc {
0xE20A
} else {
0xE07A
}
}
ButtonConfig::DPAD_LEFT => {
if is_gcc {
0xE20B
} else {
0xE07B
}
}
ButtonConfig::DPAD_RIGHT => {
if is_gcc {
0xE20C
} else {
0xE07C
}
}
ButtonConfig::PLUS => {
if is_gcc {
0xE20D
} else {
0xE0EF
}
}
ButtonConfig::MINUS => {
if is_gcc {
return None;
} else {
0xE0F0
}
}
ButtonConfig::LSTICK => {
if is_gcc {
return None;
} else {
0xE104
}
}
ButtonConfig::RSTICK => {
if is_gcc {
return None;
} else {
0xE105
}
}
_ => return None,
})
}
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)]
pub enum ButtonCombo {
OpenMenu,

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,5 @@
#![allow(dead_code)] // TODO: Yeah don't do this
use crate::extra_bitflag_impls;
use bitflags::bitflags;
use modular_bitfield::{bitfield, specifiers::*};
@ -54,7 +55,7 @@ pub struct ControlModuleStored {
/// Re-ordered bitfield the game uses for buttons
#[bitfield]
#[derive(Debug, Default, Copy, Clone)]
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
#[repr(C)]
pub struct ButtonBitfield {
pub dpad_up: bool,
@ -190,6 +191,7 @@ pub struct ControllerMapping {
//type Buttons = u32; // may need to actually implement (like label and such)? Not for now though
bitflags! {
#[derive(Default)]
pub struct Buttons: u32 {
const ATTACK = 0x1;
const SPECIAL = 0x2;
@ -212,6 +214,16 @@ bitflags! {
}
}
// This requires some imports to work
use training_mod_consts::{random_option, ToggleTrait};
impl std::fmt::Display for Buttons {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
todo!()
}
}
extra_bitflag_impls!(Buttons);
// Controller class used internally by the game
#[derive(Debug, Default, Copy, Clone)]
#[repr(C)]
@ -267,7 +279,7 @@ pub struct SomeControllerStruct {
}
// Define struct used for final controller inputs
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, Default)]
#[repr(C)]
pub struct MappedInputs {
pub buttons: Buttons,

View file

@ -42,14 +42,25 @@ pub fn is_emulator() -> bool {
}
pub fn get_module_accessor(fighter_id: FighterId) -> *mut app::BattleObjectModuleAccessor {
try_get_module_accessor(fighter_id).unwrap()
}
pub fn try_get_module_accessor(
fighter_id: FighterId,
) -> Option<*mut app::BattleObjectModuleAccessor> {
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 fighter_entry =
FighterManager::get_fighter_entry(mgr, entry_id) as *mut app::FighterEntry;
if fighter_entry.is_null() {
return None;
}
let current_fighter_id = FighterEntry::current_fighter_id(fighter_entry);
app::sv_battle_object::module_accessor(current_fighter_id as u32)
Some(app::sv_battle_object::module_accessor(
current_fighter_id as u32,
))
}
}
@ -305,14 +316,15 @@ pub fn print_fighter_info(
// Print Title
print!("{}: ", title);
// Print Fighter Kind:
let fighter_kind = utility::get_kind(module_accessor);
if print_fighter_kind {
print!("FIGHTER_KIND: {}, ", utility::get_kind(module_accessor));
print!("FIGHTER_KIND: {:#?}, ", kind_to_char(fighter_kind));
}
// Print Status:
if print_status {
print!(
"FIGHTER_STATUS: {}, ",
StatusModule::status_kind(module_accessor)
status_display_name(fighter_kind, StatusModule::status_kind(module_accessor))
);
}

View file

@ -18,7 +18,7 @@ use std::fs;
use std::path::PathBuf;
use skyline::nro::{self, NroInfo};
use training_mod_consts::{OnOff, LEGACY_TRAINING_MODPACK_ROOT};
use training_mod_consts::{extra_bitflag_impls, OnOff, LEGACY_TRAINING_MODPACK_ROOT};
use crate::common::button_config::DEFAULT_OPEN_MENU_CONFIG;
use crate::common::events::events_loop;

Binary file not shown.

340
src/training/input_log.rs Normal file
View file

@ -0,0 +1,340 @@
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 skyline::nn::ui2d::ResColor;
use smash::app::{lua_bind::*, utility};
use training_mod_consts::{FighterId, InputDisplay, MENU};
use super::{frame_counter, input_record::STICK_CLAMP_MULTIPLIER};
const GREEN: ResColor = ResColor {
r: 22,
g: 156,
b: 0,
a: 0,
};
const RED: ResColor = ResColor {
r: 153,
g: 10,
b: 10,
a: 0,
};
const CYAN: ResColor = ResColor {
r: 0,
g: 255,
b: 255,
a: 0,
};
const BLUE: ResColor = ResColor {
r: 0,
g: 40,
b: 108,
a: 0,
};
const PURPLE: ResColor = ResColor {
r: 100,
g: 66,
b: 202,
a: 0,
};
pub const YELLOW: ResColor = ResColor {
r: 230,
g: 180,
b: 14,
a: 0,
};
pub const WHITE: ResColor = ResColor {
r: 255,
g: 255,
b: 255,
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 const NUM_LOGS: usize = 10;
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub enum DirectionStrength {
None,
Weak,
// Strong,
}
#[derive(Copy, Clone, Default)]
pub struct InputLog {
pub ttl: u32,
pub frames: u32,
pub overall_frame: u32,
pub raw_inputs: Controller,
pub smash_inputs: MappedInputs,
pub status: i32,
pub fighter_kind: i32,
}
const WALK_THRESHOLD_X: i8 = 20;
const _DASH_THRESHOLD_X: i8 = 102;
const DEADZONE_THRESHOLD_Y: i8 = 30;
const _TAP_JUMP_THRESHOLD_Y: i8 = 90;
fn bin_stick_values(x: i8, y: i8) -> (DirectionStrength, f32) {
(
// TODO
DirectionStrength::Weak,
match (x, y) {
// X only
(x, y) if y.abs() < DEADZONE_THRESHOLD_Y => match x {
x if x > WALK_THRESHOLD_X => 0.0,
x if x < -WALK_THRESHOLD_X => 180.0,
_ => return (DirectionStrength::None, 0.0),
},
// Y only
(x, y) if x.abs() < WALK_THRESHOLD_X => match y {
y if y > DEADZONE_THRESHOLD_Y => 90.0,
y if y < -DEADZONE_THRESHOLD_Y => 270.0,
_ => return (DirectionStrength::None, 0.0),
},
// Positive Y
(x, y) if y > DEADZONE_THRESHOLD_Y => match x {
x if x > WALK_THRESHOLD_X => 45.0,
x if x < -WALK_THRESHOLD_X => 135.0,
_ => return (DirectionStrength::Weak, 90.0),
},
// Negative Y
(x, y) if y < DEADZONE_THRESHOLD_Y => match x {
x if x > WALK_THRESHOLD_X => 315.0,
x if x < -WALK_THRESHOLD_X => 225.0,
_ => return (DirectionStrength::Weak, 270.0),
},
_ => return (DirectionStrength::None, 0.0),
},
)
}
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::None => false,
}
}
}
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::None => panic!("Invalid input display to log"),
}
}
}
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::None => panic!("Invalid input display to log"),
}
}
}
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::None => panic!("Invalid input display to log"),
}
}
}
fn smash_button_icons(&self) -> VecDeque<(&str, ResColor)> {
self.smash_inputs
.buttons
.to_vec()
.iter()
.filter_map(|button| {
Some(match *button {
Buttons::ATTACK | Buttons::ATTACK_RAW => ("a", GREEN),
Buttons::SPECIAL | Buttons::SPECIAL_RAW | Buttons::SPECIAL_RAW2 => ("b", RED),
Buttons::JUMP => ("x", CYAN),
Buttons::GUARD | Buttons::GUARD_HOLD => ("lb", BLUE),
Buttons::CATCH => ("zr", PURPLE),
Buttons::STOCK_SHARE => ("plus", WHITE),
Buttons::APPEAL_HI => ("dpad_up", WHITE),
Buttons::APPEAL_LW => ("dpad_down", WHITE),
Buttons::APPEAL_SL => ("dpad_right", WHITE),
Buttons::APPEAL_SR => ("dpad_left", WHITE),
_ => return None,
})
})
.unique_by(|(s, _)| *s)
.collect::<VecDeque<(&str, ResColor)>>()
}
fn raw_button_icons(&self) -> VecDeque<(&str, ResColor)> {
let buttons = self.raw_inputs.current_buttons;
let mut icons = VecDeque::new();
if buttons.a() {
icons.push_front(("a", GREEN));
}
if buttons.b() {
icons.push_front(("b", RED));
}
if buttons.x() {
icons.push_front(("x", CYAN));
}
if buttons.y() {
icons.push_front(("y", CYAN));
}
if buttons.l() || buttons.real_digital_l() {
icons.push_front(("lb", BLUE));
}
if buttons.r() || buttons.real_digital_r() {
icons.push_front(("rb", BLUE));
}
if buttons.zl() {
icons.push_front(("zl", PURPLE));
}
if buttons.zr() {
icons.push_front(("zr", PURPLE));
}
if buttons.plus() {
icons.push_front(("plus", WHITE));
}
if buttons.minus() {
icons.push_front(("minus", WHITE));
}
icons
}
fn is_smash_different(&self, other: &InputLog) -> bool {
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)
}
fn smash_binned_lstick(&self) -> (DirectionStrength, f32) {
bin_stick_values(self.smash_inputs.lstick_x, self.smash_inputs.lstick_y)
}
fn smash_binned_rstick(&self) -> (DirectionStrength, f32) {
bin_stick_values(self.smash_inputs.rstick_x, self.smash_inputs.rstick_y)
}
fn is_raw_different(&self, other: &InputLog) -> bool {
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)
}
fn raw_binned_lstick(&self) -> (DirectionStrength, f32) {
let x = (self.raw_inputs.left_stick_x / STICK_CLAMP_MULTIPLIER) as i8;
let y = (self.raw_inputs.left_stick_y / STICK_CLAMP_MULTIPLIER) as i8;
bin_stick_values(x, y)
}
fn raw_binned_rstick(&self) -> (DirectionStrength, f32) {
let x = (self.raw_inputs.right_stick_x / STICK_CLAMP_MULTIPLIER) as i8;
let y = (self.raw_inputs.right_stick_y / STICK_CLAMP_MULTIPLIER) as i8;
bin_stick_values(x, y)
}
}
fn insert_in_place<T>(array: &mut [T], value: T, index: usize) {
array[index..].rotate_right(1);
array[index] = value;
}
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 fn handle_final_input_mapping(
player_idx: i32,
controller_struct: &SomeControllerStruct,
out: *mut MappedInputs,
) {
unsafe {
if MENU.input_display == InputDisplay::None {
return;
}
if QUICK_MENU_ACTIVE {
return;
}
if player_idx == 0 {
let module_accessor = try_get_module_accessor(FighterId::Player);
if module_accessor.is_none() {
return;
}
let module_accessor = module_accessor.unwrap();
let current_frame = frame_counter::get_frame_count(*PER_LOG_FRAME_COUNTER);
let current_overall_frame = frame_counter::get_frame_count(*OVERALL_FRAME_COUNTER);
// We should always be counting
frame_counter::start_counting(*PER_LOG_FRAME_COUNTER);
frame_counter::start_counting(*OVERALL_FRAME_COUNTER);
let potential_input_log = InputLog {
ttl: 600,
frames: 1,
overall_frame: current_overall_frame,
raw_inputs: *controller_struct.controller,
smash_inputs: *out,
status: StatusModule::status_kind(module_accessor),
fighter_kind: utility::get_kind(&mut *module_accessor),
};
let input_logs = &mut *P1_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;
// Only update if we are on a new frame according to the latest log
let is_new_frame = prev_overall_frames != current_overall_frame;
if is_new_frame && latest_input_log.is_different(&potential_input_log) {
frame_counter::reset_frame_count(*PER_LOG_FRAME_COUNTER);
// We should count this frame already
frame_counter::tick_idx(*PER_LOG_FRAME_COUNTER);
insert_in_front(input_logs, potential_input_log);
} else if is_new_frame {
*latest_input_log = potential_input_log;
latest_input_log.frames = std::cmp::min(current_frame, 99);
latest_input_log.ttl = prev_ttl;
}
// Decrease TTL
for input_log in input_logs.iter_mut().take(NUM_LOGS) {
if input_log.ttl > 0 && is_new_frame {
input_log.ttl -= 1;
}
}
}
}
}

View file

@ -12,7 +12,9 @@ use PossessionState::*;
use crate::common::consts::{FighterId, HitstunPlayback, OnOff, RecordTrigger};
use crate::common::input::*;
use crate::common::{button_config, is_training_mode};
use crate::common::{get_module_accessor, is_in_hitstun, is_in_shieldstun, MENU};
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};
@ -48,8 +50,8 @@ pub enum StartingStatus {
Other,
}
const STICK_NEUTRAL: f32 = 0.2;
const STICK_CLAMP_MULTIPLIER: f32 = 1.0 / 120.0; // 120.0 = CLAMP_MAX
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;
@ -440,7 +442,14 @@ unsafe fn set_cpu_controls(p_data: *mut *mut u8) {
should_mash_playback();
}
let cpu_module_accessor = get_module_accessor(FighterId::CPU);
let cpu_module_accessor = try_get_module_accessor(FighterId::CPU);
// Sometimes we can try to grab their module accessor before they are valid?
if cpu_module_accessor.is_none() {
return;
}
let cpu_module_accessor = cpu_module_accessor.unwrap();
if INPUT_RECORD == Pause {
match LOCKOUT_FRAME.cmp(&0) {
Ordering::Greater => LOCKOUT_FRAME -= 1,

View file

@ -1,2 +0,0 @@
#[macro_use]
pub mod structures;

View file

@ -1,307 +0,0 @@
#![allow(dead_code)] // TODO: Yeah don't do this
use crate::common::events::smash_version;
use crate::common::release::CURRENT_VERSION;
use crate::training::save_states::SavedState;
use bitflags::bitflags;
use training_mod_consts::TrainingModpackMenu;
use crate::default_save_state;
use crate::training::character_specific::steve;
use crate::training::charge::ChargeState;
use crate::training::save_states::SaveState::NoAction;
// Need to define necesary structures here. Probably should move to consts or something. Realistically, should be in skyline smash prob tho.
// Final final controls used for controlmodule
// can I actually derive these?
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct ControlModuleInternal {
pub vtable: *mut u8,
pub controller_index: i32,
pub buttons: Buttons,
pub stick_x: f32,
pub stick_y: f32,
pub padding: [f32; 2],
pub unk: [u32; 8],
pub clamped_lstick_x: f32,
pub clamped_lstick_y: f32,
pub padding2: [f32; 2],
pub clamped_rstick_x: f32,
pub clamped_rstick_y: f32,
}
impl ControlModuleInternal {
pub fn _clear(&mut self) {
// Try to nullify controls so we can't control player 1 during recording
self.stick_x = 0.0;
self.stick_y = 0.0;
self.buttons = Buttons::empty();
self.clamped_lstick_x = 0.0;
self.clamped_lstick_y = 0.0;
self.clamped_rstick_x = 0.0;
self.clamped_rstick_y = 0.0;
}
}
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct ControlModuleStored {
// Custom type for saving only necessary controls/not saving vtable
pub buttons: Buttons,
pub stick_x: f32,
pub stick_y: f32,
pub padding: [f32; 2],
pub unk: [u32; 8],
pub clamped_lstick_x: f32,
pub clamped_lstick_y: f32,
pub padding2: [f32; 2],
pub clamped_rstick_x: f32,
pub clamped_rstick_y: f32,
}
// Re-ordered bitfield the game uses for buttons - TODO: Is this a problem? What's the original order?
pub type ButtonBitfield = i32; // may need to actually implement? Not for now though
/// Controller style declaring what kind of controller is being used
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
#[repr(u32)]
pub enum ControllerStyle {
Handheld = 0x1,
DualJoycon = 0x2,
LeftJoycon = 0x3,
RightJoycon = 0x4,
ProController = 0x5,
DebugPad = 0x6, // probably
GCController = 0x7,
}
#[repr(C)]
pub struct AutorepeatInfo {
field: [u8; 0x18],
}
// Can map any of these over any button - what does this mean?
#[repr(u8)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum InputKind {
Attack = 0x0,
Special = 0x1,
Jump = 0x2,
Guard = 0x3,
Grab = 0x4,
SmashAttack = 0x5,
AppealHi = 0xA,
AppealS = 0xB,
AppealLw = 0xC,
Unset = 0xD,
}
// 0x50 Byte struct containing the information for controller mappings
#[derive(Debug)]
#[repr(C)]
pub struct ControllerMapping {
pub gc_l: InputKind,
pub gc_r: InputKind,
pub gc_z: InputKind,
pub gc_dup: InputKind,
pub gc_dlr: InputKind,
pub gc_ddown: InputKind,
pub gc_a: InputKind,
pub gc_b: InputKind,
pub gc_cstick: InputKind,
pub gc_y: InputKind,
pub gc_x: InputKind,
pub gc_rumble: bool,
pub gc_absmash: bool,
pub gc_tapjump: bool,
pub gc_sensitivity: u8,
// 0xF
pub pro_l: InputKind,
pub pro_r: InputKind,
pub pro_zl: InputKind,
pub pro_zr: InputKind,
pub pro_dup: InputKind,
pub pro_dlr: InputKind,
pub pro_ddown: InputKind,
pub pro_a: InputKind,
pub pro_b: InputKind,
pub pro_cstick: InputKind,
pub pro_x: InputKind,
pub pro_y: InputKind,
pub pro_rumble: bool,
pub pro_absmash: bool,
pub pro_tapjump: bool,
pub pro_sensitivity: u8,
// 0x1F
pub joy_shoulder: InputKind,
pub joy_zshoulder: InputKind,
pub joy_sl: InputKind,
pub joy_sr: InputKind,
pub joy_up: InputKind,
pub joy_right: InputKind,
pub joy_left: InputKind,
pub joy_down: InputKind,
pub joy_rumble: bool,
pub joy_absmash: bool,
pub joy_tapjump: bool,
pub joy_sensitivity: u8,
// 0x2B
pub _2b: u8,
pub _2c: u8,
pub _2d: u8,
pub _2e: u8,
pub _2f: u8,
pub _30: u8,
pub _31: u8,
pub _32: u8,
pub is_absmash: bool,
pub _34: [u8; 0x1C],
}
//type Buttons = u32; // may need to actually implement (like label and such)? Not for now though
bitflags! {
pub struct Buttons: u32 {
const ATTACK = 0x1;
const SPECIAL = 0x2;
const JUMP = 0x4;
const GUARD = 0x8;
const CATCH = 0x10;
const SMASH = 0x20;
const JUMP_MINI = 0x40;
const CSTICK_ON = 0x80;
const STOCK_SHARE = 0x100;
const ATTACK_RAW = 0x200;
const APPEAL_HI = 0x400;
const SPECIAL_RAW = 0x800;
const APPEAL_LW = 0x1000;
const APPEAL_SL = 0x2000;
const APPEAL_SR = 0x4000;
const FLICK_JUMP = 0x8000;
const GUARD_HOLD = 0x10000;
const SPECIAL_RAW2 = 0x20000;
}
}
// Controller class used internally by the game
#[repr(C)]
pub struct Controller {
pub vtable: *const u64,
pub current_buttons: ButtonBitfield,
pub previous_buttons: ButtonBitfield,
pub left_stick_x: f32,
pub left_stick_y: f32,
pub left_trigger: f32,
pub _left_padding: u32,
pub right_stick_x: f32,
pub right_stick_y: f32,
pub right_trigger: f32,
pub _right_padding: u32,
pub gyro: [f32; 4],
pub button_timespan: AutorepeatInfo,
pub lstick_timespan: AutorepeatInfo,
pub rstick_timespan: AutorepeatInfo,
pub just_down: ButtonBitfield,
pub just_release: ButtonBitfield,
pub autorepeat_keys: u32,
pub autorepeat_threshold: u32,
pub autorepeat_initial_press_threshold: u32,
pub style: ControllerStyle,
pub controller_id: u32,
pub primary_controller_color1: u32,
pub primary_controller_color2: u32,
pub secondary_controller_color1: u32,
pub secondary_controller_color2: u32,
pub led_pattern: u8,
pub button_autorepeat_initial_press: bool,
pub lstick_autorepeat_initial_press: bool,
pub rstick_autorepeat_initial_press: bool,
pub is_valid_controller: bool,
pub _x_b9: [u8; 2],
pub is_connected: bool,
pub is_left_connected: bool,
pub is_right_connected: bool,
pub is_wired: bool,
pub is_left_wired: bool,
pub is_right_wired: bool,
pub _x_c1: [u8; 3],
pub npad_number: u32,
pub _x_c8: [u8; 8],
}
// SomeControllerStruct used in hooked function - need to ask blujay what this is again
#[repr(C)]
pub struct SomeControllerStruct {
padding: [u8; 0x10],
controller: &'static mut Controller,
}
// Define struct used for final controller inputs
#[derive(Copy, Clone)]
#[repr(C)]
pub struct MappedInputs {
pub buttons: Buttons,
pub lstick_x: i8,
pub lstick_y: i8,
pub rstick_x: i8,
pub rstick_y: i8,
}
impl MappedInputs {
// pub needed?
pub fn default() -> MappedInputs {
MappedInputs {
buttons: Buttons::empty(),
lstick_x: 0,
lstick_y: 0,
rstick_x: 0,
rstick_y: 0,
}
}
}
// Final Structure containing all input recording slots, menu options, and save states.
// 5 Input Recording Slots should be fine for now for most mix up scenarios
// When loading a "scenario", we want to load all menu options (with maybe overrides in the config?), load savestate(s), and load input recording slots.
// If we have submenus for input recording slots, we need to get that info as well, and we want to apply saved damage from save states to the menu.
// Damage range seems to be saved in menu for range of damage, so that's taken care of with menu.
#[derive(Clone)]
#[repr(C)]
pub struct Scenario {
pub record_slots: Vec<Vec<MappedInputs>>,
pub starting_statuses: Vec<i32>,
pub menu: TrainingModpackMenu,
pub save_states: Vec<SavedState>,
pub player_char: i32, // fighter_kind
pub cpu_char: i32, // fighter_kind
pub stage: i32, // index of stage, but -1 = random
pub title: String,
pub description: String,
pub mod_version: String,
pub smash_version: String,
// depending on version, we need to modify newly added menu options, so that regardless of their defaults they reflect the previous version to minimize breakage of old scenarios
// we may also add more scenario parts to the struct in the future etc.
// pub screenshot: image????
// datetime?
// author?
// mirroring?
}
impl Scenario {
pub fn default() -> Scenario {
Scenario {
record_slots: vec![vec![MappedInputs::default(); 600]; 5],
starting_statuses: vec![0],
menu: crate::common::consts::DEFAULTS_MENU,
save_states: vec![default_save_state!(); 5],
player_char: 0,
cpu_char: 0,
stage: -1, // index of stage, but -1 = random/any stage
title: "Scenario Title".to_string(),
description: "Description...".to_string(),
mod_version: CURRENT_VERSION.to_string(),
smash_version: smash_version(),
}
}
}

View file

@ -35,6 +35,7 @@ mod character_specific;
mod fast_fall;
mod full_hop;
pub mod input_delay;
mod input_log;
mod input_record;
mod mash;
mod reset;
@ -764,6 +765,9 @@ unsafe fn handle_final_input_mapping(
// MUTATES controller state to delay inputs
input_delay::handle_final_input_mapping(player_idx, out);
// Read potentially delayed state for loggers
input_log::handle_final_input_mapping(player_idx, controller_struct, out);
// Potentially apply input recording, thus with delay
// MUTATES controller state to apply recording or playback
input_record::handle_final_input_mapping(player_idx, out);

View file

@ -0,0 +1,178 @@
use std::collections::VecDeque;
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, P1_INPUT_LOGS, WHITE, YELLOW},
ui::{fade_out, menu::VANILLA_MENU_ACTIVE},
},
};
macro_rules! log_parent_fmt {
($x:ident) => {
format!("TrModInputLog{}", $x).as_str()
};
}
fn get_input_icons(log: &InputLog) -> VecDeque<(&str, ResColor)> {
let mut icons = log.button_icons();
let (rstick_strength, rstick_angle) = log.binned_rstick();
let rstick_icon = if rstick_strength != DirectionStrength::None {
match rstick_angle as u32 {
0 => "right",
45 => "up_right",
90 => "up",
135 => "up_left",
180 => "left",
225 => "down_left",
270 => "down",
315 => "down_right",
_ => "?",
}
} else {
""
};
if !rstick_icon.is_empty() {
icons.push_front((rstick_icon, YELLOW));
}
let (lstick_strength, lstick_angle) = log.binned_lstick();
let lstick_icon = if lstick_strength != DirectionStrength::None {
match lstick_angle as u32 {
0 => "right",
45 => "up_right",
90 => "up",
135 => "up_left",
180 => "left",
225 => "down_left",
270 => "down",
315 => "down_right",
_ => "?",
}
} else {
""
};
if !lstick_icon.is_empty() {
icons.push_front((lstick_icon, WHITE));
}
icons
}
unsafe fn draw_log(root_pane: &Pane, log_idx: usize, log: &InputLog) {
let log_pane = root_pane
.find_pane_by_name_recursive(log_parent_fmt!(log_idx))
.unwrap();
log_pane.set_visible(
!QUICK_MENU_ACTIVE && !VANILLA_MENU_ACTIVE && MENU.input_display != InputDisplay::None,
);
if MENU.input_display == InputDisplay::None {
return;
}
const FADE_FRAMES: u32 = 200;
fade_out(log_pane, log.ttl, FADE_FRAMES);
let icons = get_input_icons(log);
// Empty them first
const NUM_ICON_SLOTS: usize = 5;
let available_icons = vec![
"a",
"b",
"x",
"y",
"lb",
"rb",
"zl",
"zr",
"up",
"down",
"left",
"right",
"up_left",
"up_right",
"down_left",
"down_right",
"gcc_l",
"gcc_r",
"gcc_z",
"plus",
"minus",
"l_stick",
"r_stick",
"gcc_c_stick",
];
for idx in 0..NUM_ICON_SLOTS {
let input_pane = log_pane
.find_pane_by_name_recursive(format!("Input{}", idx).as_str())
.unwrap();
available_icons
.iter()
.map(|name| input_pane.find_pane_by_name_recursive(name).unwrap())
.for_each(|icon_pane| {
icon_pane.set_visible(false);
});
}
for (index, icon) in icons.iter().enumerate() {
// Temporarily comparing to the list of available icons until they are all in
// Just in case we run into an icon name that isn't present
let (icon_name, icon_color) = icon;
if index >= NUM_ICON_SLOTS || !available_icons.contains(icon_name) {
continue;
}
let input_pane = log_pane
.find_pane_by_name_recursive(format!("Input{}", index).as_str())
.unwrap();
let icon_pane = input_pane
.find_pane_by_name_recursive(icon_name)
.unwrap()
.as_picture();
icon_pane.set_visible(true);
(*icon_pane.material).set_black_res_color(*icon_color);
icon_pane.flags |= PaneFlag::IsGlobalMatrixDirty as u8;
}
let frame_text = format!("{}", log.frames);
log_pane
.find_pane_by_name_recursive("Frame")
.unwrap()
.as_textbox()
.set_text_string(frame_text.as_str());
let status_text = if MENU.input_display_status.as_bool() {
status_display_name(log.fighter_kind, log.status)
} else {
"".to_string()
};
log_pane
.find_pane_by_name_recursive("Status")
.unwrap()
.as_textbox()
.set_text_string(status_text.as_str());
}
pub unsafe fn draw(root_pane: &Pane) {
let logs_ptr = P1_INPUT_LOGS.data_ptr();
if logs_ptr.is_null() {
return;
}
let logs = &*logs_ptr;
for (log_idx, log) in logs.iter().enumerate() {
draw_log(root_pane, log_idx, log);
}
}

View file

@ -11,6 +11,7 @@ use crate::training::frame_counter;
use crate::{common, common::menu::QUICK_MENU_ACTIVE, input::*};
use super::fade_out;
use super::set_icon_text;
pub static NUM_MENU_TEXT_OPTIONS: usize = 32;
pub static _NUM_MENU_TABS: usize = 3;
@ -483,10 +484,7 @@ pub unsafe fn draw(root_pane: &Pane) {
// Left/Right tabs have keys
if let Some(key) = key {
let it = icon_pane.text_buf as *mut u16;
icon_pane.text_len = 1;
*it = **key;
*(it.add(1)) = 0x0;
set_icon_text(icon_pane, &vec![**key]);
}
if *name == "CurrentTab" {
@ -510,11 +508,7 @@ pub unsafe fn draw(root_pane: &Pane) {
.find_pane_by_name_recursive("set_txt_icon")
.unwrap()
.as_textbox();
icon_pane.set_text_string("");
let it = icon_pane.text_buf as *mut u16;
icon_pane.text_len = 1;
*it = *key.unwrap();
*(it.add(1)) = 0x0;
set_icon_text(icon_pane, &vec![*key.unwrap()]);
key_help_pane
.find_pane_by_name_recursive("set_txt_help")

View file

@ -2,6 +2,7 @@
use byte_unit::MEBIBYTE;
use sarc::SarcFile;
use skyline::nn::ui2d::*;
use smash::ui2d::SmashTextBox;
use training_mod_consts::{OnOff, MENU};
#[cfg(feature = "layout_arc_from_file")]
@ -13,11 +14,28 @@ use crate::{
mod damage;
mod display;
mod input_log;
pub mod menu;
pub mod notifications;
pub unsafe fn set_icon_text(pane: &mut TextBox, icons: &Vec<u16>) {
pane.set_text_string("");
let it = pane.text_buf as *mut u16;
pane.text_len = icons.len() as u16;
for (idx, icon) in icons.iter().enumerate() {
*(it.add(idx)) = *icon;
}
// Add nullptr at end to be sure
*(it.add(icons.len())) = 0x0;
}
pub fn fade_out(pane: &mut Pane, current_frame: u32, total_frames: u32) {
if current_frame < total_frames {
if current_frame >= total_frames {
pane.alpha = 255;
pane.global_alpha = 255;
} else if current_frame > 0 {
// Logarithmic fade out
let alpha = ((255.0 / (total_frames as f32 + 1.0).log10())
* (current_frame as f32 + 1.0).log10()) as u8;
@ -60,6 +78,7 @@ pub unsafe fn handle_draw(layout: *mut Layout, draw_info: u64, cmd_buffer: u64)
if layout_name == "info_training" {
frame_counter::tick_real();
input_log::draw(root_pane);
display::draw(root_pane);
menu::draw(root_pane);
}
@ -71,7 +90,7 @@ pub unsafe fn handle_draw(layout: *mut Layout, draw_info: u64, cmd_buffer: u64)
// in order for us to be able to swap the 'layout.arc' with the current
// version of the file in between loads of training mode.
#[cfg(feature = "layout_arc_from_file")]
const LAYOUT_ARC_SIZE: usize = (3 * MEBIBYTE) as usize;
const LAYOUT_ARC_SIZE: usize = (4 * MEBIBYTE) as usize;
#[cfg(feature = "layout_arc_from_file")]
static mut LAYOUT_ARC: &mut [u8; LAYOUT_ARC_SIZE] = &mut [0u8; LAYOUT_ARC_SIZE];

View file

@ -35,6 +35,8 @@ pub struct TrainingModpackMenu {
pub frame_advantage: OnOff,
pub full_hop: BoolFlag,
pub hitbox_vis: OnOff,
pub input_display: InputDisplay,
pub input_display_status: OnOff,
pub hud: OnOff,
pub input_delay: Delay,
pub ledge_delay: LongDelay,
@ -140,7 +142,9 @@ pub static DEFAULTS_MENU: TrainingModpackMenu = TrainingModpackMenu {
follow_up: Action::empty(),
frame_advantage: OnOff::Off,
full_hop: BoolFlag::TRUE,
hitbox_vis: OnOff::Off,
hitbox_vis: OnOff::On,
input_display: InputDisplay::Smash,
input_display_status: OnOff::Off,
hud: OnOff::On,
input_delay: Delay::D0,
ledge_delay: LongDelay::empty(),
@ -809,6 +813,20 @@ pub unsafe fn ui_menu(menu: TrainingModpackMenu) -> UiMenu {
true,
&(menu.hitbox_vis as u32),
);
misc_tab.add_submenu_with_toggles::<InputDisplay>(
"Input Display".to_string(),
"input_display".to_string(),
"Input Display: Log inputs in a queue on the left of the screen".to_string(),
true,
&(menu.input_display as u32),
);
misc_tab.add_submenu_with_toggles::<OnOff>(
"Input Display Status".to_string(),
"input_display_status".to_string(),
"Input Display Status: Group input logs by status in which they occurred".to_string(),
true,
&(menu.input_display_status as u32),
);
misc_tab.add_submenu_with_toggles::<Delay>(
"Input Delay".to_string(),
"input_delay".to_string(),

View file

@ -29,6 +29,7 @@ pub trait SliderTrait {
}
// bitflag helper function macro
#[macro_export]
macro_rules! extra_bitflag_impls {
($e:ty) => {
impl $e {
@ -1709,3 +1710,37 @@ impl ToggleTrait for UpdatePolicy {
UpdatePolicy::iter().map(|i| i.to_string()).collect()
}
}
#[repr(u32)]
#[derive(
Debug, Clone, Copy, PartialEq, FromPrimitive, EnumIter, Serialize_repr, Deserialize_repr,
)]
pub enum InputDisplay {
None,
Smash,
Raw,
}
impl fmt::Display for InputDisplay {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match *self {
InputDisplay::None => "None",
InputDisplay::Smash => "Smash Inputs",
InputDisplay::Raw => "Raw Inputs",
}
)
}
}
impl ToggleTrait for InputDisplay {
fn to_toggle_vals() -> Vec<u32> {
InputDisplay::iter().map(|i| i as u32).collect()
}
fn to_toggle_strings() -> Vec<String> {
InputDisplay::iter().map(|i| i.to_string()).collect()
}
}