mirror of
https://github.com/jugeeya/UltimateTrainingModpack.git
synced 2024-11-24 10:54:16 +00:00
Instead of hardcoding, load button combo configuration from a file (#388)
* Instead of hardcoding, load button combo configuration from a file * Rename conf files, remove extra logging * Inform the user which keys are invalid
This commit is contained in:
parent
bbc0c93c9a
commit
a1d5fe7fac
7 changed files with 198 additions and 20 deletions
|
@ -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" }
|
||||
|
||||
|
|
141
src/common/button_config.rs
Normal file
141
src/common/button_config.rs
Normal file
|
@ -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<Vec<i32>> = Mutex::new(vec![BUTTON_MAPPING["SPECIAL"]]);
|
||||
pub static ref OPEN_MENU_BTN_PRESS: Mutex<Vec<i32>> = Mutex::new(vec![BUTTON_MAPPING["UPTAUNT"]]);
|
||||
pub static ref SAVE_STATE_BTN_HOLD: Mutex<Vec<i32>> = Mutex::new(vec![BUTTON_MAPPING["GRAB"]]);
|
||||
pub static ref SAVE_STATE_BTN_PRESS: Mutex<Vec<i32>> = Mutex::new(vec![BUTTON_MAPPING["DOWNTAUNT"]]);
|
||||
pub static ref LOAD_STATE_BTN_HOLD: Mutex<Vec<i32>> = Mutex::new(vec![BUTTON_MAPPING["GRAB"]]);
|
||||
pub static ref LOAD_STATE_BTN_PRESS: Mutex<Vec<i32>> = Mutex::new(vec![BUTTON_MAPPING["UPTAUNT"]]);
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct BtnList {
|
||||
hold: Vec<String>,
|
||||
press: Vec<String>,
|
||||
}
|
||||
|
||||
#[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<Vec<i32>>, mutex_press: &Mutex<Vec<i32>>) {
|
||||
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<Option<&i32>> = 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::<Vec<i32>>(),
|
||||
);
|
||||
}
|
||||
|
||||
// PRESS
|
||||
let mut global_press = mutex_press.lock();
|
||||
let vecopt_press: Vec<Option<&i32>> = 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::<Vec<i32>>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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",]
|
||||
"#;
|
|
@ -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::<MenuJsonStruct>(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::<TrainingModpackMenu>(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,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod button_config;
|
||||
pub mod consts;
|
||||
pub mod events;
|
||||
pub mod menu;
|
||||
|
|
|
@ -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);
|
||||
|
|
22
src/lib.rs
22
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::<MenuJsonStruct>(&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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue