1
0
Fork 0
mirror of https://github.com/jugeeya/UltimateTrainingModpack.git synced 2025-03-14 02:16:10 +00:00

Save State Slots across game load + Fixes galore: overwrite button config if invalid; exclusive button combos; remove wait when opening menu (#476)

* Overwrite button config if invalid; exclusive button combos

* Tons of fixes

* Fix save state settings load from file
This commit is contained in:
jugeeya 2023-02-11 16:49:05 -08:00 committed by GitHub
parent bf78f06f7d
commit 94ff25a921
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 192 additions and 119 deletions

View file

@ -2,6 +2,10 @@ use lazy_static::lazy_static;
use serde::Deserialize;
use smash::app::lua_bind::ControlModule;
use std::collections::HashMap;
use std::fs;
use log::info;
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
use toml;
lazy_static! {
@ -43,7 +47,7 @@ static mut BUTTON_COMBO_CONFIG: BtnComboConfig = BtnComboConfig {
},
};
#[derive(Debug)]
#[derive(Debug, EnumIter, PartialEq)]
pub enum ButtonCombo {
OpenMenu,
SaveState,
@ -68,45 +72,37 @@ struct BtnComboConfig {
}
#[derive(Deserialize)]
struct TopLevelBtnComboConfig {
pub struct TopLevelBtnComboConfig {
button_config: BtnComboConfig,
}
pub fn validate_config(data: &str) -> bool {
let conf: TopLevelBtnComboConfig =
toml::from_str(data).expect("Custom button config has invalid schema");
let conf = conf.button_config;
let configs = [conf.open_menu, conf.save_state, conf.load_state,
conf.previous_save_state_slot, conf.next_save_state_slot];
let bad_keys = configs
.iter()
.flat_map(|btn_list| {
btn_list
.hold
.iter()
.chain(btn_list.press.iter())
.filter(|x| !BUTTON_MAPPING.contains_key(x.to_uppercase().as_str()))
})
.collect::<Vec<&String>>();
pub fn load_from_file() {
let combo_path = "sd:/TrainingModpack/training_modpack.toml";
info!("Checking for previous button combo settings in training_modpack.toml...");
let mut valid_button_config = false;
if fs::metadata(combo_path).is_ok() {
info!("Previous button combo settings found. Loading...");
let combo_conf =
fs::read_to_string(combo_path).unwrap_or_else(|_| panic!("Could not read {}", combo_path));
let conf: Result<TopLevelBtnComboConfig, toml::de::Error> = toml::from_str(&combo_conf);
if let Ok(conf) = conf {
if validate_config(conf) {
save_all_btn_config_from_toml(&combo_conf);
valid_button_config = true;
}
}
}
if !bad_keys.is_empty() {
skyline::error::show_error(
0x71,
"Training Modpack custom button\nconfiguration is invalid!\0",
&format!(
"The following keys are invalid in\nsd:/TrainingModpack/training_modpack.toml:\n\
{:?}\n\nPossible Keys: {:#?}\0",
&bad_keys,
BUTTON_MAPPING.keys()
),
);
false
} else {
true
if !valid_button_config {
info!("No previous button combo file found. Creating...");
fs::write(combo_path, DEFAULT_BTN_CONFIG)
.expect("Failed to write button config conf file");
save_all_btn_config_from_defaults();
}
}
pub fn save_all_btn_config_from_defaults() {
fn save_all_btn_config_from_defaults() {
let conf = TopLevelBtnComboConfig {
button_config: BtnComboConfig {
open_menu: BtnList {
@ -138,7 +134,7 @@ pub fn save_all_btn_config_from_defaults() {
}
}
pub fn save_all_btn_config_from_toml(data: &str) {
fn save_all_btn_config_from_toml(data: &str) {
let conf: TopLevelBtnComboConfig = toml::from_str(data).expect("Could not parse button config");
unsafe {
// This println is necessary. Why?.......
@ -147,44 +143,92 @@ pub fn save_all_btn_config_from_toml(data: &str) {
}
}
pub fn combo_passes(
fn validate_config(conf: TopLevelBtnComboConfig) -> bool {
let conf = conf.button_config;
let configs = [conf.open_menu, conf.save_state, conf.load_state,
conf.previous_save_state_slot, conf.next_save_state_slot];
let bad_keys = configs
.iter()
.flat_map(|btn_list| {
btn_list
.hold
.iter()
.chain(btn_list.press.iter())
.filter(|x| !BUTTON_MAPPING.contains_key(x.to_uppercase().as_str()))
})
.collect::<Vec<&String>>();
if !bad_keys.is_empty() {
skyline::error::show_error(
0x71,
"Training Modpack custom button\nconfiguration is invalid!\0",
&format!(
"The following keys are invalid in\nsd:/TrainingModpack/training_modpack.toml:\n\
{:?}\n\nPossible Keys: {:#?}\0",
&bad_keys,
BUTTON_MAPPING.keys()
),
);
false
} else {
true
}
}
unsafe fn get_combo_keys(combo: ButtonCombo) -> (&'static Vec<String>, &'static Vec<String>) {
match combo {
ButtonCombo::OpenMenu => (
&BUTTON_COMBO_CONFIG.open_menu.hold,
&BUTTON_COMBO_CONFIG.open_menu.press,
),
ButtonCombo::SaveState => (
&BUTTON_COMBO_CONFIG.save_state.hold,
&BUTTON_COMBO_CONFIG.save_state.press,
),
ButtonCombo::LoadState => (
&BUTTON_COMBO_CONFIG.load_state.hold,
&BUTTON_COMBO_CONFIG.load_state.press,
),
ButtonCombo::PrevSaveStateSlot => (
&BUTTON_COMBO_CONFIG.previous_save_state_slot.hold,
&BUTTON_COMBO_CONFIG.previous_save_state_slot.press,
),
ButtonCombo::NextSaveStateSlot => (
&BUTTON_COMBO_CONFIG.next_save_state_slot.hold,
&BUTTON_COMBO_CONFIG.next_save_state_slot.press,
),
}
}
fn combo_passes(
module_accessor: *mut smash::app::BattleObjectModuleAccessor,
combo: ButtonCombo,
) -> bool {
unsafe {
let (hold, press) = match combo {
ButtonCombo::OpenMenu => (
&BUTTON_COMBO_CONFIG.open_menu.hold,
&BUTTON_COMBO_CONFIG.open_menu.press,
),
ButtonCombo::SaveState => (
&BUTTON_COMBO_CONFIG.save_state.hold,
&BUTTON_COMBO_CONFIG.save_state.press,
),
ButtonCombo::LoadState => (
&BUTTON_COMBO_CONFIG.load_state.hold,
&BUTTON_COMBO_CONFIG.load_state.press,
),
ButtonCombo::PrevSaveStateSlot => (
&BUTTON_COMBO_CONFIG.previous_save_state_slot.hold,
&BUTTON_COMBO_CONFIG.previous_save_state_slot.press,
),
ButtonCombo::NextSaveStateSlot => (
&BUTTON_COMBO_CONFIG.next_save_state_slot.hold,
&BUTTON_COMBO_CONFIG.next_save_state_slot.press,
),
};
hold.iter()
let (hold, press) = get_combo_keys(combo);
let this_combo_passes = hold.iter()
.map(|hold| *BUTTON_MAPPING.get(&*hold.to_uppercase()).unwrap())
.all(|hold| ControlModule::check_button_on(module_accessor, hold))
&& press
.iter()
.map(|press| *BUTTON_MAPPING.get(&*press.to_uppercase()).unwrap())
.all(|press| ControlModule::check_button_trigger(module_accessor, press))
.all(|press| ControlModule::check_button_trigger(module_accessor, press));
this_combo_passes
}
}
pub const DEFAULT_BTN_CONFIG: &str = r#"[button_config]
pub fn combo_passes_exclusive(
module_accessor: *mut smash::app::BattleObjectModuleAccessor,
combo: ButtonCombo
) -> bool {
let other_combo_passes = ButtonCombo::iter()
.filter(|other_combo| *other_combo != combo)
.any(|other_combo| combo_passes(module_accessor, other_combo));
combo_passes(module_accessor, combo) && !other_combo_passes
}
const DEFAULT_BTN_CONFIG: &str = r#"[button_config]
# Available Options:
#
# ATTACK

View file

@ -1,3 +1,4 @@
use std::fs;
use crate::common::*;
use crate::events::{Event, EVENT_QUEUE};
use crate::logging::*;
@ -13,11 +14,33 @@ const MENU_CLOSE_WAIT_FRAMES : u32 = 60;
pub static mut QUICK_MENU_ACTIVE: bool = false;
pub unsafe fn menu_condition(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
button_config::combo_passes(module_accessor, button_config::ButtonCombo::OpenMenu)
button_config::combo_passes_exclusive(module_accessor, button_config::ButtonCombo::OpenMenu)
}
const MENU_CONF_PATH: &str = "sd:/TrainingModpack/training_modpack_menu.json";
pub fn load_from_file() {
let menu_conf_path = "sd:/TrainingModpack/training_modpack_menu.json";
info!("Checking for previous menu in training_modpack_menu.json...");
if fs::metadata(menu_conf_path).is_ok() {
let menu_conf = fs::read_to_string(menu_conf_path)
.unwrap_or_else(|_| panic!("Could not remove {}", menu_conf_path));
if let Ok(menu_conf_json) = serde_json::from_str::<MenuJsonStruct>(&menu_conf) {
unsafe {
MENU = menu_conf_json.menu;
DEFAULTS_MENU = menu_conf_json.defaults_menu;
info!("Previous menu found. Loading...");
}
} else {
warn!("Previous menu found but is invalid. Deleting...");
fs::remove_file(menu_conf_path)
.unwrap_or_else(|_| panic!("{} has invalid schema but could not be deleted!", menu_conf_path));
}
} else {
info!("No previous menu file found.");
}
}
pub unsafe fn set_menu_from_json(message: &str) {
let response = serde_json::from_str::<MenuJsonStruct>(message);
info!("Received menu message: {message}");
@ -160,10 +183,6 @@ pub fn handle_get_npad_state(state: *mut NpadGcState, _controller_id: *const u32
// BUTTON_PRESSES.down.is_pressed = (*state).Buttons & ((1 << 15) | (1 << 19)) > 0;
// BUTTON_PRESSES.up.is_pressed = (*state).Buttons & ((1 << 13) | (1 << 17)) > 0;
if FRAME_COUNTER < MENU_INPUT_WAIT_FRAMES {
return;
}
if (*state).Buttons & (1 << 0) > 0 {
BUTTON_PRESSES.a.is_pressed = true;
}
@ -179,7 +198,8 @@ pub fn handle_get_npad_state(state: *mut NpadGcState, _controller_id: *const u32
if (*state).Buttons & (1 << 7) > 0 {
BUTTON_PRESSES.r.is_pressed = true;
}
if (*state).Buttons & (1 << 8) > 0 {
// Special case for frame-by-frame
if FRAME_COUNTER < MENU_INPUT_WAIT_FRAMES && (*state).Buttons & (1 << 8) > 0 {
BUTTON_PRESSES.zl.is_pressed = true;
}
if (*state).Buttons & (1 << 9) > 0 {

View file

@ -31,7 +31,6 @@ use std::fs;
use crate::logging::*;
use crate::menu::quick_menu_loop;
use training_mod_consts::MenuJsonStruct;
use crate::training::ui::notifications::notification;
fn nro_main(nro: &NroInfo<'_>) {
@ -103,43 +102,8 @@ pub fn main() {
info!("Performing version check...");
release::version_check();
let menu_conf_path = "sd:/TrainingModpack/training_modpack_menu.json";
info!("Checking for previous menu in training_modpack_menu.json...");
if fs::metadata(menu_conf_path).is_ok() {
let menu_conf = fs::read_to_string(menu_conf_path)
.unwrap_or_else(|_| panic!("Could not remove {}", menu_conf_path));
if let Ok(menu_conf_json) = serde_json::from_str::<MenuJsonStruct>(&menu_conf) {
unsafe {
MENU = menu_conf_json.menu;
DEFAULTS_MENU = menu_conf_json.defaults_menu;
info!("Previous menu found. Loading...");
}
} else {
warn!("Previous menu found but is invalid. Deleting...");
fs::remove_file(menu_conf_path)
.unwrap_or_else(|_| panic!("{} has invalid schema but could not be deleted!", menu_conf_path));
}
} else {
info!("No previous menu file found.");
}
let combo_path = "sd:/TrainingModpack/training_modpack.toml";
info!("Checking for previous button combo settings in training_modpack.toml...");
if fs::metadata(combo_path).is_ok() {
info!("Previous button combo settings found. Loading...");
let combo_conf =
fs::read_to_string(combo_path).unwrap_or_else(|_| panic!("Could not remove {}", combo_path));
if button_config::validate_config(&combo_conf) {
button_config::save_all_btn_config_from_toml(&combo_conf);
} else {
button_config::save_all_btn_config_from_defaults();
}
} else {
info!("No previous button combo file found. Creating...");
fs::write(combo_path, button_config::DEFAULT_BTN_CONFIG)
.expect("Failed to write button config conf file");
button_config::save_all_btn_config_from_defaults();
}
menu::load_from_file();
button_config::load_from_file();
std::thread::spawn(|| loop {
std::thread::sleep(std::time::Duration::from_secs(10));

Binary file not shown.

View file

@ -1,7 +1,8 @@
use serde::{Serialize, Deserialize};
use smash::app::{self, lua_bind::*};
use smash::lib::lua_const::*;
#[derive(Copy, Clone)]
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
pub struct SteveState {
pub mat_g1: i32,
pub mat_wood: i32,

View file

@ -1,8 +1,9 @@
use serde::{Serialize, Deserialize};
use smash::app::{self, lua_bind::*, ArticleOperationTarget, FighterFacial, FighterUtil};
use smash::lib::lua_const::*;
use smash::phx::{Hash40, Vector3f};
#[derive(Default, Copy, Clone)]
#[derive(Serialize, Deserialize, Default, Copy, Clone, Debug)]
pub struct ChargeState {
pub int_x: Option<i32>,
pub int_y: Option<i32>,

View file

@ -33,7 +33,7 @@ pub(crate) mod input_delay;
mod input_record;
mod mash;
mod reset;
mod save_states;
pub(crate) mod save_states;
mod shield_tilt;
#[skyline::hook(replace = WorkModule::get_param_float)]

View file

@ -14,11 +14,14 @@ use crate::training::items::apply_item;
use crate::training::reset;
use crate::{is_ptrainer, ITEM_MANAGER_ADDR};
use SaveState::*;
use parking_lot::Mutex;
use serde::{Serialize, Deserialize};
use smash::app::{self, lua_bind::*, Item};
use smash::hash40;
use smash::lib::lua_const::*;
use smash::phx::{Hash40, Vector3f};
use std::collections::HashMap;
use log::info;
use training_mod_consts::{CharacterItem, SaveDamage};
use crate::training::ui::notifications;
@ -27,7 +30,7 @@ extern "C" {
pub fn stage_id() -> i32;
}
#[derive(PartialEq, Copy, Clone)]
#[derive(Serialize, Deserialize, PartialEq, Copy, Clone, Debug)]
enum SaveState {
Save,
NoAction,
@ -37,7 +40,7 @@ enum SaveState {
ApplyBuff,
}
#[derive(Copy, Clone)]
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
struct SavedState {
x: f32,
y: f32,
@ -89,20 +92,55 @@ macro_rules! default_save_state {
};
}
// static mut SAVE_STATE_PLAYER: SavedState = default_save_state!();
// static mut SAVE_STATE_CPU: SavedState = default_save_state!();
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
pub struct SaveStateSlots {
player: [SavedState; NUM_SAVE_STATE_SLOTS],
cpu: [SavedState; NUM_SAVE_STATE_SLOTS],
}
const NUM_SAVE_STATE_SLOTS : usize = 5;
static mut SAVE_STATE_PLAYER : [SavedState; NUM_SAVE_STATE_SLOTS] = [default_save_state!(); NUM_SAVE_STATE_SLOTS];
static mut SAVE_STATE_CPU : [SavedState; NUM_SAVE_STATE_SLOTS] = [default_save_state!(); NUM_SAVE_STATE_SLOTS];
// I actually had to do it this way, a simple load-from-file in main() caused crashes.
lazy_static::lazy_static! {
static ref SAVE_STATE_SLOTS : Mutex<SaveStateSlots> = Mutex::new(load_from_file());
}
static mut SAVE_STATE_SLOT : usize = 0;
pub fn load_from_file() -> SaveStateSlots {
let defaults = SaveStateSlots{
player: [default_save_state!(); NUM_SAVE_STATE_SLOTS],
cpu: [default_save_state!(); NUM_SAVE_STATE_SLOTS],
};
let save_states_path = "sd:/TrainingModpack/save_states.toml";
info!("Checking for previous save state settings in save_states.toml...");
if std::fs::metadata(save_states_path).is_err() {
return defaults;
}
info!("Previous save state settings found. Loading...");
if let Ok(data) = std::fs::read_to_string(save_states_path) {
let input_slots = toml::from_str::<SaveStateSlots>(&data);
if let Ok(input_slots) = input_slots {
return input_slots;
}
}
defaults
}
pub unsafe fn save_to_file() {
let save_states_str = toml::to_string_pretty(&*SAVE_STATE_SLOTS.data_ptr())
.expect("Error serializing save state information");
std::fs::write("sd:/TrainingModpack/save_states.toml", save_states_str)
.expect("Could not write save state information to file");
}
unsafe fn save_state_player() -> &'static mut SavedState {
&mut SAVE_STATE_PLAYER[SAVE_STATE_SLOT]
&mut (*SAVE_STATE_SLOTS.data_ptr()).player[SAVE_STATE_SLOT]
}
unsafe fn save_state_cpu() -> &'static mut SavedState {
&mut SAVE_STATE_CPU[SAVE_STATE_SLOT]
&mut (*SAVE_STATE_SLOTS.data_ptr()).cpu[SAVE_STATE_SLOT]
}
// MIRROR_STATE == 1 -> Do not mirror
@ -256,7 +294,7 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
.contains(&fighter_kind);
if !is_operation_cpu(module_accessor) &&
button_config::combo_passes(module_accessor, button_config::ButtonCombo::PrevSaveStateSlot) {
button_config::combo_passes_exclusive(module_accessor, button_config::ButtonCombo::PrevSaveStateSlot) {
SAVE_STATE_SLOT = if SAVE_STATE_SLOT == 0 {
NUM_SAVE_STATE_SLOTS - 1
} else {
@ -269,7 +307,7 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
}
if !is_operation_cpu(module_accessor) &&
button_config::combo_passes(module_accessor, button_config::ButtonCombo::NextSaveStateSlot) {
button_config::combo_passes_exclusive(module_accessor, button_config::ButtonCombo::NextSaveStateSlot) {
SAVE_STATE_SLOT = (SAVE_STATE_SLOT + 1) % NUM_SAVE_STATE_SLOTS;
notifications::clear_notifications("Save State");
notifications::notification("Save State".to_string(), format!("Switched to Slot {SAVE_STATE_SLOT}"), 120);
@ -284,7 +322,7 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
let mut triggered_reset: bool = false;
if !is_operation_cpu(module_accessor) {
triggered_reset =
button_config::combo_passes(module_accessor, button_config::ButtonCombo::LoadState);
button_config::combo_passes_exclusive(module_accessor, button_config::ButtonCombo::LoadState);
}
if (autoload_reset || triggered_reset) && !fighter_is_nana {
if save_state.state == NoAction {
@ -531,7 +569,7 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
}
// Grab + Dpad down: Save state
if button_config::combo_passes(module_accessor, button_config::ButtonCombo::SaveState) {
if button_config::combo_passes_exclusive(module_accessor, button_config::ButtonCombo::SaveState) {
// Don't begin saving state if Nana's delayed input is captured
MIRROR_STATE = 1.0;
save_state_player().state = Save;
@ -580,5 +618,10 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
0,
0,
);
// If both chars finished saving by now
if save_state_player().state != Save && save_state_cpu().state != Save {
save_to_file();
}
}
}