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:
parent
ebe6393143
commit
7e0617e377
18 changed files with 10090 additions and 334 deletions
|
@ -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,6 +31,7 @@ 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" }
|
||||
|
@ -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]
|
||||
|
|
|
@ -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,
|
||||
|
|
9333
src/common/consts.rs
9333
src/common/consts.rs
File diff suppressed because it is too large
Load diff
|
@ -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,
|
||||
|
|
|
@ -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))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
340
src/training/input_log.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
#[macro_use]
|
||||
pub mod structures;
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
178
src/training/ui/input_log.rs
Normal file
178
src/training/ui/input_log.rs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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];
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue