diff --git a/Cargo.toml b/Cargo.toml index 7c6d61c..2c46617 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ strum_macros = "0.21.0" minreq = { version = "=2.2.1", features = ["https", "json-using-serde"] } serde = { version = "1", features = ["derive"] } serde_json = "1" +toml = "0.5.9" training_mod_consts = { path = "training_mod_consts" } training_mod_tui = { path = "training_mod_tui" } diff --git a/src/common/button_config.rs b/src/common/button_config.rs new file mode 100644 index 0000000..a5f8bcc --- /dev/null +++ b/src/common/button_config.rs @@ -0,0 +1,141 @@ +use lazy_static::lazy_static; +use parking_lot::Mutex; +use serde::Deserialize; +use std::collections::HashMap; +use toml; + +lazy_static! { + // Using the LuaConst names wasn't working for some reason... + static ref BUTTON_MAPPING: HashMap<&'static str, i32> = HashMap::from([ + ("ATTACK", 0xE), // *CONTROL_PAD_BUTTON_ATTACK_RAW + ("SPECIAL", 0xF), // *CONTROL_PAD_BUTTON_SPECIAL_RAW + ("SHIELD", 3), // *CONTROL_PAD_BUTTON_GUARD + ("GRAB", 9), // *CONTROL_PAD_BUTTON_CATCH + ("JUMP", 2), // *CONTROL_PAD_BUTTON_JUMP + ("UPTAUNT", 5), // *CONTROL_PAD_BUTTON_APPEAL_HI + ("DOWNTAUNT", 6), // *CONTROL_PAD_BUTTON_APPEAL_LW + ("LEFTTAUNT", 7), // *CONTROL_PAD_BUTTON_APPEAL_S_L + ("RIGHTTAUNT", 8), // *CONTROL_PAD_BUTTON_APPEAL_S_R + ("SHARESTOCK", 0xD), // *CONTROL_PAD_BUTTON_STOCK_SHARE + ("JUMPMINI", 0xA), // *CONTROL_PAD_BUTTON_JUMP_MINI + ]); + pub static ref OPEN_MENU_BTN_HOLD: Mutex> = Mutex::new(vec![BUTTON_MAPPING["SPECIAL"]]); + pub static ref OPEN_MENU_BTN_PRESS: Mutex> = Mutex::new(vec![BUTTON_MAPPING["UPTAUNT"]]); + pub static ref SAVE_STATE_BTN_HOLD: Mutex> = Mutex::new(vec![BUTTON_MAPPING["GRAB"]]); + pub static ref SAVE_STATE_BTN_PRESS: Mutex> = Mutex::new(vec![BUTTON_MAPPING["DOWNTAUNT"]]); + pub static ref LOAD_STATE_BTN_HOLD: Mutex> = Mutex::new(vec![BUTTON_MAPPING["GRAB"]]); + pub static ref LOAD_STATE_BTN_PRESS: Mutex> = Mutex::new(vec![BUTTON_MAPPING["UPTAUNT"]]); +} + +#[derive(Deserialize)] +struct BtnList { + hold: Vec, + press: Vec, +} + +#[derive(Deserialize)] +struct BtnComboConfig { + open_menu: BtnList, + save_state: BtnList, + load_state: BtnList, +} + +#[derive(Deserialize)] +struct TopLevelBtnComboConfig { + button_config: BtnComboConfig, +} + +fn save_btn_config(btnlist: BtnList, mutex_hold: &Mutex>, mutex_press: &Mutex>) { + let bad_keys: Vec<&String> = btnlist.hold.iter() + .chain(btnlist.press.iter()) + .filter(|x| !BUTTON_MAPPING.contains_key(x.as_str())) + .collect(); + if !bad_keys.is_empty() { + skyline::error::show_error( + 0x71, + "Training Modpack custom button\nconfiguration is invalid!", + &format!("The following keys are invalid in\nsd:/TrainingModpack/training_modpack.toml:\n{:?}", &bad_keys) + ); + } + + // HOLD + let mut global_hold = mutex_hold.lock(); + let vecopt_hold: Vec> = btnlist + .hold + .iter() + .map(|x| BUTTON_MAPPING.get(x.as_str())) + .collect(); + if vecopt_hold.iter().all(|x| x.is_some()) { + // All entries valid keys of BUTTON_MAPPING + global_hold.clear(); + global_hold.extend_from_slice( + &vecopt_hold + .into_iter() + .map(|x| *x.unwrap()) + .collect::>(), + ); + } + + // PRESS + let mut global_press = mutex_press.lock(); + let vecopt_press: Vec> = btnlist + .press + .into_iter() + .map(|x| BUTTON_MAPPING.get(x.as_str())) + .collect(); + if vecopt_press.iter().all(|x| x.is_some()) { + // All entries valid keys of BUTTON_MAPPING + global_press.clear(); + global_press.extend_from_slice( + &vecopt_press + .into_iter() + .map(|x| *x.unwrap()) + .collect::>(), + ); + } +} + +pub fn save_all_btn_config_from_toml(data: &str) { + let conf: TopLevelBtnComboConfig = toml::from_str(data).unwrap(); + let open_menu_conf: BtnList = conf.button_config.open_menu; + let save_state_conf: BtnList = conf.button_config.save_state; + let load_state_conf: BtnList = conf.button_config.load_state; + + save_btn_config(open_menu_conf, &OPEN_MENU_BTN_HOLD, &OPEN_MENU_BTN_PRESS); + save_btn_config(save_state_conf, &SAVE_STATE_BTN_HOLD, &SAVE_STATE_BTN_PRESS); + save_btn_config(load_state_conf, &LOAD_STATE_BTN_HOLD, &LOAD_STATE_BTN_PRESS); +} + +pub const DEFAULT_BTN_CONFIG: &'static str = r#"[button_config] +# Available Options: +# +# ATTACK +# SPECIAL +# SHIELD +# GRAB +# JUMP +# UPTAUNT +# DOWNTAUNT +# LEFTTAUNT +# RIGHTTAUNT +# SHARESTOCK +# JUMPMINI +# +# It is recommended to only put one button in the "press" section for each button +# combination, but you can add several buttons to "hold" like this: +# hold=["ATTACK", "SPECIAL",] +# +# SHARESTOCK is typically A+B +# JUMPMINI is the combination of two jump buttons +[button_config.open_menu] +hold=["SPECIAL",] +press=["UPTAUNT",] + +[button_config.save_state] +hold=["GRAB",] +press=["DOWNTAUNT",] + +[button_config.load_state] +hold=["GRAB",] +press=["UPTAUNT",] +"#; diff --git a/src/common/menu.rs b/src/common/menu.rs index 6b866d5..5d18fdd 100644 --- a/src/common/menu.rs +++ b/src/common/menu.rs @@ -36,11 +36,15 @@ pub unsafe fn menu_condition(module_accessor: &mut smash::app::BattleObjectModul // Only check for button combination if the counter is 0 (not locked out) match frame_counter::get_frame_count(FRAME_COUNTER_INDEX) { 0 => { - ControlModule::check_button_on(module_accessor, *CONTROL_PAD_BUTTON_SPECIAL) - && ControlModule::check_button_on_trriger( - module_accessor, - *CONTROL_PAD_BUTTON_APPEAL_HI, - ) + let open_menu_btn_hold = button_config::OPEN_MENU_BTN_HOLD.lock(); + let open_menu_btn_press = button_config::OPEN_MENU_BTN_PRESS.lock(); + let return_value: bool = open_menu_btn_hold + .iter() + .all(|btn| ControlModule::check_button_on(module_accessor, *btn)) + && open_menu_btn_press + .iter() + .all(|btn| ControlModule::check_button_trigger(module_accessor, *btn)); + return_value } 1..MENU_LOCKOUT_FRAMES => false, _ => { @@ -73,7 +77,7 @@ pub unsafe fn write_menu() { } } -const MENU_CONF_PATH: &str = "sd:/TrainingModpack/training_modpack_menu.conf"; +const MENU_CONF_PATH: &str = "sd:/TrainingModpack/training_modpack_menu.json"; pub unsafe fn set_menu_from_json(message: &str) { if let Ok(message_json) = serde_json::from_str::(message) { @@ -85,7 +89,7 @@ pub unsafe fn set_menu_from_json(message: &str) { MENU_CONF_PATH, serde_json::to_string_pretty(&message_json).unwrap(), ) - .expect("Failed to write menu conf file"); + .expect("Failed to write menu settings file"); } else if let Ok(message_json) = serde_json::from_str::(message) { // Only includes MENU // From TUI @@ -96,7 +100,7 @@ pub unsafe fn set_menu_from_json(message: &str) { defaults_menu: DEFAULTS_MENU, }; std::fs::write(MENU_CONF_PATH, serde_json::to_string_pretty(&conf).unwrap()) - .expect("Failed to write menu conf file"); + .expect("Failed to write menu settings file"); } else { skyline::error::show_error( 0x70, diff --git a/src/common/mod.rs b/src/common/mod.rs index bd9cc19..bd98281 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,3 +1,4 @@ +pub mod button_config; pub mod consts; pub mod events; pub mod menu; diff --git a/src/common/release.rs b/src/common/release.rs index ef7bed0..b24de22 100644 --- a/src/common/release.rs +++ b/src/common/release.rs @@ -46,6 +46,7 @@ pub fn version_check() { ); // Remove old menu selections, silently ignoring errors (i.e. if the file doesn't exist) fs::remove_file("sd:/TrainingModpack/training_modpack_menu.conf").unwrap_or({}); + fs::remove_file("sd:/TrainingModpack/training_modpack_menu.json").unwrap_or({}); fs::remove_file("sd:/TrainingModpack/training_modpack_menu_defaults.conf") .unwrap_or({}); record_current_version(VERSION_FILE_PATH); diff --git a/src/lib.rs b/src/lib.rs index 2be5209..81f1433 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,8 +81,8 @@ pub fn main() { log!("Performing version check..."); release::version_check(); - let menu_conf_path = "sd:/TrainingModpack/training_modpack_menu.conf"; - log!("Checking for previous menu in training_modpack_menu.conf..."); + let menu_conf_path = "sd:/TrainingModpack/training_modpack_menu.json"; + log!("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(); if let Ok(menu_conf_json) = serde_json::from_str::(&menu_conf) { @@ -93,15 +93,29 @@ pub fn main() { } } else if menu_conf.starts_with("http://localhost") { log!("Previous menu found, with URL schema. Deleting..."); - fs::remove_file(menu_conf_path).expect("Could not delete conf file!"); + fs::remove_file(menu_conf_path).expect("Could not delete menu conf file!"); } else { log!("Previous menu found but is invalid. Deleting..."); - fs::remove_file(menu_conf_path).expect("Could not delete conf file!"); + fs::remove_file(menu_conf_path).expect("Could not delete menu conf file!"); } } else { log!("No previous menu file found."); } + let combo_path = "sd:/TrainingModpack/training_modpack.toml"; + log!("Checking for previous button combo settings in training_modpack.toml..."); + if fs::metadata(combo_path).is_ok() { + log!("Previous button combo settings found. Loading..."); + let combo_conf = fs::read_to_string(&combo_path).unwrap(); + button_config::save_all_btn_config_from_toml(&combo_conf); + } else { + log!("No previous button combo file found. Creating..."); + std::fs::write(combo_path, button_config::DEFAULT_BTN_CONFIG) + .expect("Failed to write button config conf file"); + // No need to run save_all_btn_config_from_toml() + // since the statics are preloaded with the defaults + } + if is_emulator() { unsafe { DEFAULTS_MENU.quick_menu = OnOff::On; diff --git a/src/training/save_states.rs b/src/training/save_states.rs index 66212d8..93977b9 100644 --- a/src/training/save_states.rs +++ b/src/training/save_states.rs @@ -1,3 +1,5 @@ +use crate::is_operation_cpu; +use crate::common::button_config; use crate::common::consts::get_random_int; use crate::common::consts::FighterId; use crate::common::consts::OnOff; @@ -233,9 +235,17 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor) let autoload_reset = MENU.save_state_autoload == OnOff::On && save_state.state == NoAction && is_dead(module_accessor); - let triggered_reset = - ControlModule::check_button_on(module_accessor, *CONTROL_PAD_BUTTON_CATCH) - && ControlModule::check_button_trigger(module_accessor, *CONTROL_PAD_BUTTON_APPEAL_HI); + let mut triggered_reset: bool = false; + if !is_operation_cpu(module_accessor) { + let load_state_btn_hold = button_config::LOAD_STATE_BTN_HOLD.lock(); + let load_state_btn_press = button_config::LOAD_STATE_BTN_PRESS.lock(); + triggered_reset = load_state_btn_hold + .iter() + .all(|btn| ControlModule::check_button_on(module_accessor, *btn)) + && load_state_btn_press + .iter() + .all(|btn| ControlModule::check_button_trigger(module_accessor, *btn)); + } if (autoload_reset || triggered_reset) && !fighter_is_nana { if save_state.state == NoAction { SAVE_STATE_PLAYER.state = KillPlayer; @@ -420,11 +430,17 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor) } // Grab + Dpad down: Save state - if ControlModule::check_button_on(module_accessor, *CONTROL_PAD_BUTTON_CATCH) - && ControlModule::check_button_trigger(module_accessor, *CONTROL_PAD_BUTTON_APPEAL_LW) - && !fighter_is_nana - // Don't begin saving state if Nana's delayed input is captured - { + let save_state_btn_hold = button_config::SAVE_STATE_BTN_HOLD.lock(); + let save_state_btn_press = button_config::SAVE_STATE_BTN_PRESS.lock(); + let save_state_condition: bool = save_state_btn_hold + .iter() + .all(|btn| ControlModule::check_button_on(module_accessor, *btn)) + && save_state_btn_press + .iter() + .all(|btn| ControlModule::check_button_trigger(module_accessor, *btn)) + && !fighter_is_nana; + if save_state_condition { + // Don't begin saving state if Nana's delayed input is captured MIRROR_STATE = 1.0; SAVE_STATE_PLAYER.state = Save; SAVE_STATE_CPU.state = Save;