1
0
Fork 0
mirror of https://github.com/jugeeya/UltimateTrainingModpack.git synced 2024-10-02 17:24:28 +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:
asimon-1 2022-10-09 14:09:47 -07:00 committed by GitHub
parent bbc0c93c9a
commit a1d5fe7fac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 198 additions and 20 deletions

View file

@ -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
View 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",]
"#;

View file

@ -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,

View file

@ -1,3 +1,4 @@
pub mod button_config;
pub mod consts;
pub mod events;
pub mod menu;

View file

@ -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);

View file

@ -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;

View file

@ -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;