mirror of
https://github.com/jugeeya/UltimateTrainingModpack.git
synced 2025-03-23 14:56:11 +00:00
Refactor to use Web Sessions (#384)
* Initial commit * don't rewrite file each time * Format Rust code using rustfmt * Receive menu settings from web via message * Adjust logic * Stub changes for json messages * Rust: receive menu as json * Small JS changes for desktop * Attempt to fix exit issues * Fix filename issue * JS: Export menu as json * Use JSON for Rust -> JS message * Update .conf file schema to json * Tear down session when leaving training mode * Remove URL behavior, rename WebAppletResponse -> MenuJsonStruct * Update TUI to use JSON Co-authored-by: jugeeya <jugeeya@live.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
parent
55ee4733e2
commit
36c6e8859a
8 changed files with 484 additions and 470 deletions
|
@ -1,13 +1,18 @@
|
||||||
use crate::common::consts::get_menu_from_url;
|
|
||||||
use crate::common::*;
|
use crate::common::*;
|
||||||
use crate::events::{Event, EVENT_QUEUE};
|
use crate::events::{Event, EVENT_QUEUE};
|
||||||
use crate::training::frame_counter;
|
use crate::training::frame_counter;
|
||||||
|
|
||||||
|
use owo_colors::OwoColorize;
|
||||||
use ramhorns::Template;
|
use ramhorns::Template;
|
||||||
use skyline::info::get_program_id;
|
use skyline::info::get_program_id;
|
||||||
use skyline_web::{Background, BootDisplay, Webpage};
|
use skyline::nn::hid::NpadGcState;
|
||||||
|
use skyline::nn::web::WebSessionBootMode;
|
||||||
|
use skyline_web::{Background, WebSession, Webpage};
|
||||||
use smash::lib::lua_const::*;
|
use smash::lib::lua_const::*;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use training_mod_consts::{TrainingModpackMenu, MenuJsonStruct};
|
||||||
|
use training_mod_tui::Color;
|
||||||
|
|
||||||
static mut FRAME_COUNTER_INDEX: usize = 0;
|
static mut FRAME_COUNTER_INDEX: usize = 0;
|
||||||
pub static mut QUICK_MENU_FRAME_COUNTER_INDEX: usize = 0;
|
pub static mut QUICK_MENU_FRAME_COUNTER_INDEX: usize = 0;
|
||||||
|
@ -70,11 +75,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.conf";
|
||||||
|
|
||||||
pub fn set_menu_from_url(last_url: &str) {
|
pub unsafe fn set_menu_from_json(message: &str) {
|
||||||
unsafe {
|
|
||||||
MENU = get_menu_from_url(MENU, last_url, false);
|
|
||||||
DEFAULTS_MENU = get_menu_from_url(MENU, last_url, true);
|
|
||||||
|
|
||||||
if MENU.quick_menu == OnOff::Off {
|
if MENU.quick_menu == OnOff::Off {
|
||||||
if is_emulator() {
|
if is_emulator() {
|
||||||
skyline::error::show_error(
|
skyline::error::show_error(
|
||||||
|
@ -85,12 +86,38 @@ pub fn set_menu_from_url(last_url: &str) {
|
||||||
MENU.quick_menu = OnOff::On;
|
MENU.quick_menu = OnOff::On;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if let Ok(message_json) = serde_json::from_str::<MenuJsonStruct>(message) {
|
||||||
|
// Includes both MENU and DEFAULTS_MENU
|
||||||
|
// From Web Applet
|
||||||
|
MENU = message_json.menu;
|
||||||
|
DEFAULTS_MENU = message_json.defaults_menu;
|
||||||
|
std::fs::write(
|
||||||
|
MENU_CONF_PATH,
|
||||||
|
serde_json::to_string_pretty(&message_json).unwrap()
|
||||||
|
)
|
||||||
|
.expect("Failed to write menu conf file");
|
||||||
|
} else if let Ok(message_json) = serde_json::from_str::<TrainingModpackMenu>(message) {
|
||||||
|
// Only includes MENU
|
||||||
|
// From TUI
|
||||||
|
MENU = message_json;
|
||||||
|
|
||||||
std::fs::write(MENU_CONF_PATH, last_url).expect("Failed to write menu conf file");
|
let conf = MenuJsonStruct {
|
||||||
unsafe {
|
menu: MENU,
|
||||||
EVENT_QUEUE.push(Event::menu_open(last_url.to_string()));
|
defaults_menu: DEFAULTS_MENU,
|
||||||
}
|
};
|
||||||
|
std::fs::write(
|
||||||
|
MENU_CONF_PATH,
|
||||||
|
serde_json::to_string_pretty(&conf).unwrap()
|
||||||
|
)
|
||||||
|
.expect("Failed to write menu conf file");
|
||||||
|
} else {
|
||||||
|
skyline::error::show_error(
|
||||||
|
0x70,
|
||||||
|
"Could not parse the menu response!\nPlease send a screenshot of the details page to the developers.\n\0",
|
||||||
|
message
|
||||||
|
);
|
||||||
|
};
|
||||||
|
EVENT_QUEUE.push(Event::menu_open(message.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn_menu() {
|
pub fn spawn_menu() {
|
||||||
|
@ -99,42 +126,15 @@ pub fn spawn_menu() {
|
||||||
frame_counter::start_counting(FRAME_COUNTER_INDEX);
|
frame_counter::start_counting(FRAME_COUNTER_INDEX);
|
||||||
frame_counter::reset_frame_count(QUICK_MENU_FRAME_COUNTER_INDEX);
|
frame_counter::reset_frame_count(QUICK_MENU_FRAME_COUNTER_INDEX);
|
||||||
frame_counter::start_counting(QUICK_MENU_FRAME_COUNTER_INDEX);
|
frame_counter::start_counting(QUICK_MENU_FRAME_COUNTER_INDEX);
|
||||||
}
|
|
||||||
|
|
||||||
let mut quick_menu = false;
|
if MENU.quick_menu == OnOff::Off {
|
||||||
unsafe {
|
WEB_MENU_ACTIVE = true;
|
||||||
if MENU.quick_menu == OnOff::On {
|
|
||||||
quick_menu = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !quick_menu {
|
|
||||||
let fname = "training_menu.html";
|
|
||||||
unsafe {
|
|
||||||
let params = MENU.to_url_params(false);
|
|
||||||
let default_params = DEFAULTS_MENU.to_url_params(true);
|
|
||||||
let page_response = Webpage::new()
|
|
||||||
.background(Background::BlurredScreenshot)
|
|
||||||
.htdocs_dir("training_modpack")
|
|
||||||
.boot_display(BootDisplay::BlurredScreenshot)
|
|
||||||
.boot_icon(true)
|
|
||||||
.start_page(&format!("{}?{}&{}", fname, params, default_params))
|
|
||||||
.open()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let last_url = page_response.get_last_url().unwrap();
|
|
||||||
println!("Received URL from web menu: {}", last_url);
|
|
||||||
set_menu_from_url(last_url);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
unsafe {
|
|
||||||
QUICK_MENU_ACTIVE = true;
|
QUICK_MENU_ACTIVE = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use skyline::nn::hid::NpadGcState;
|
|
||||||
|
|
||||||
pub struct ButtonPresses {
|
pub struct ButtonPresses {
|
||||||
pub a: ButtonPress,
|
pub a: ButtonPress,
|
||||||
pub b: ButtonPress,
|
pub b: ButtonPress,
|
||||||
|
@ -272,3 +272,196 @@ pub fn handle_get_npad_state(state: *mut NpadGcState, _controller_id: *const u32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#[link_name = "render_text_to_screen"]
|
||||||
|
pub fn render_text_to_screen_cstr(str: *const skyline::libc::c_char);
|
||||||
|
|
||||||
|
#[link_name = "set_should_display_text_to_screen"]
|
||||||
|
pub fn set_should_display_text_to_screen(toggle: bool);
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! c_str {
|
||||||
|
($l:tt) => {
|
||||||
|
[$l.as_bytes(), "\u{0}".as_bytes()].concat().as_ptr()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_text_to_screen(s: &str) {
|
||||||
|
unsafe {
|
||||||
|
render_text_to_screen_cstr(c_str!(s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn quick_menu_loop() {
|
||||||
|
loop {
|
||||||
|
std::thread::sleep(std::time::Duration::from_secs(10));
|
||||||
|
let menu = consts::get_menu();
|
||||||
|
|
||||||
|
let mut app = training_mod_tui::App::new(menu);
|
||||||
|
|
||||||
|
let backend = training_mod_tui::TestBackend::new(75, 15);
|
||||||
|
let mut terminal = training_mod_tui::Terminal::new(backend).unwrap();
|
||||||
|
|
||||||
|
let mut has_slept_millis = 0;
|
||||||
|
let render_frames = 5;
|
||||||
|
let mut json_response = String::new();
|
||||||
|
let button_presses = &mut menu::BUTTON_PRESSES;
|
||||||
|
let mut received_input = true;
|
||||||
|
loop {
|
||||||
|
button_presses.a.read_press().then(|| {
|
||||||
|
app.on_a();
|
||||||
|
received_input = true;
|
||||||
|
});
|
||||||
|
let b_press = &mut button_presses.b;
|
||||||
|
b_press.read_press().then(|| {
|
||||||
|
received_input = true;
|
||||||
|
if !app.outer_list {
|
||||||
|
app.on_b()
|
||||||
|
} else if frame_counter::get_frame_count(menu::QUICK_MENU_FRAME_COUNTER_INDEX) == 0
|
||||||
|
{
|
||||||
|
// Leave menu.
|
||||||
|
menu::QUICK_MENU_ACTIVE = false;
|
||||||
|
menu::set_menu_from_json(&json_response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
button_presses.zl.read_press().then(|| {
|
||||||
|
app.on_l();
|
||||||
|
received_input = true;
|
||||||
|
});
|
||||||
|
button_presses.zr.read_press().then(|| {
|
||||||
|
app.on_r();
|
||||||
|
received_input = true;
|
||||||
|
});
|
||||||
|
button_presses.left.read_press().then(|| {
|
||||||
|
app.on_left();
|
||||||
|
received_input = true;
|
||||||
|
});
|
||||||
|
button_presses.right.read_press().then(|| {
|
||||||
|
app.on_right();
|
||||||
|
received_input = true;
|
||||||
|
});
|
||||||
|
button_presses.up.read_press().then(|| {
|
||||||
|
app.on_up();
|
||||||
|
received_input = true;
|
||||||
|
});
|
||||||
|
button_presses.down.read_press().then(|| {
|
||||||
|
app.on_down();
|
||||||
|
received_input = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(16));
|
||||||
|
has_slept_millis += 16;
|
||||||
|
if has_slept_millis < 16 * render_frames {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
has_slept_millis = 16;
|
||||||
|
if !menu::QUICK_MENU_ACTIVE {
|
||||||
|
app = training_mod_tui::App::new(consts::get_menu());
|
||||||
|
set_should_display_text_to_screen(false);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !received_input {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut view = String::new();
|
||||||
|
|
||||||
|
let frame_res = terminal
|
||||||
|
.draw(|f| json_response = training_mod_tui::ui(f, &mut app))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
use std::fmt::Write;
|
||||||
|
for (i, cell) in frame_res.buffer.content().iter().enumerate() {
|
||||||
|
match cell.fg {
|
||||||
|
Color::Black => write!(&mut view, "{}", &cell.symbol.black()),
|
||||||
|
Color::Blue => write!(&mut view, "{}", &cell.symbol.blue()),
|
||||||
|
Color::LightBlue => write!(&mut view, "{}", &cell.symbol.bright_blue()),
|
||||||
|
Color::Cyan => write!(&mut view, "{}", &cell.symbol.cyan()),
|
||||||
|
Color::LightCyan => write!(&mut view, "{}", &cell.symbol.cyan()),
|
||||||
|
Color::Red => write!(&mut view, "{}", &cell.symbol.red()),
|
||||||
|
Color::LightRed => write!(&mut view, "{}", &cell.symbol.bright_red()),
|
||||||
|
Color::LightGreen => write!(&mut view, "{}", &cell.symbol.bright_green()),
|
||||||
|
Color::Green => write!(&mut view, "{}", &cell.symbol.green()),
|
||||||
|
Color::Yellow => write!(&mut view, "{}", &cell.symbol.yellow()),
|
||||||
|
Color::LightYellow => write!(&mut view, "{}", &cell.symbol.bright_yellow()),
|
||||||
|
Color::Magenta => write!(&mut view, "{}", &cell.symbol.magenta()),
|
||||||
|
Color::LightMagenta => {
|
||||||
|
write!(&mut view, "{}", &cell.symbol.bright_magenta())
|
||||||
|
}
|
||||||
|
_ => write!(&mut view, "{}", &cell.symbol),
|
||||||
|
}
|
||||||
|
.unwrap();
|
||||||
|
if i % frame_res.area.width as usize == frame_res.area.width as usize - 1 {
|
||||||
|
writeln!(&mut view).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeln!(&mut view).unwrap();
|
||||||
|
|
||||||
|
render_text_to_screen(view.as_str());
|
||||||
|
received_input = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static mut WEB_MENU_ACTIVE: bool = false;
|
||||||
|
|
||||||
|
pub unsafe fn web_session_loop() {
|
||||||
|
// Don't query the fightermanager too early otherwise it will crash...
|
||||||
|
std::thread::sleep(std::time::Duration::new(30, 0)); // sleep for 30 secs on bootup
|
||||||
|
let mut web_session: Option<WebSession> = None;
|
||||||
|
loop {
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||||
|
if is_ready_go() & is_training_mode() {
|
||||||
|
if web_session.is_some() {
|
||||||
|
if WEB_MENU_ACTIVE {
|
||||||
|
println!("[Training Modpack] Opening menu session...");
|
||||||
|
let session = web_session.unwrap();
|
||||||
|
let message_send = MenuJsonStruct {
|
||||||
|
menu: MENU,
|
||||||
|
defaults_menu: DEFAULTS_MENU,
|
||||||
|
};
|
||||||
|
session.send_json(&message_send);
|
||||||
|
println!(
|
||||||
|
"[Training Modpack] Sending message:\n{}",
|
||||||
|
serde_json::to_string_pretty(&message_send).unwrap()
|
||||||
|
);
|
||||||
|
session.show();
|
||||||
|
let message_recv = session.recv();
|
||||||
|
println!(
|
||||||
|
"[Training Modpack] Received menu from web:\n{}",
|
||||||
|
&message_recv
|
||||||
|
);
|
||||||
|
println!("[Training Modpack] Tearing down Training Modpack menu session");
|
||||||
|
session.exit();
|
||||||
|
session.wait_for_exit();
|
||||||
|
web_session = None;
|
||||||
|
set_menu_from_json(&message_recv);
|
||||||
|
WEB_MENU_ACTIVE = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO
|
||||||
|
// Starting a new session causes some ingame lag.
|
||||||
|
// Investigate whether we can minimize this lag by
|
||||||
|
// waiting until the player is idle or using CPU boost mode
|
||||||
|
println!("[Training Modpack] Starting new menu session...");
|
||||||
|
web_session = Some(
|
||||||
|
Webpage::new()
|
||||||
|
.background(Background::BlurredScreenshot)
|
||||||
|
.htdocs_dir("training_modpack")
|
||||||
|
.start_page("training_menu.html")
|
||||||
|
.open_session(WebSessionBootMode::InitiallyHidden)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No longer in training mode, tear down the session.
|
||||||
|
// This will avoid conflicts with other web plugins, and helps with stability.
|
||||||
|
// Having the session open too long, especially if the switch has been put to sleep, can cause freezes
|
||||||
|
if web_session.is_some() {
|
||||||
|
println!("[Training Modpack] Tearing down Training Modpack menu session");
|
||||||
|
web_session.unwrap().exit();
|
||||||
|
}
|
||||||
|
web_session = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -141,3 +141,9 @@ pub unsafe fn is_dead(module_accessor: &mut app::BattleObjectModuleAccessor) ->
|
||||||
pub unsafe fn is_in_clatter(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
|
pub unsafe fn is_in_clatter(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
|
||||||
ControlModule::get_clatter_time(module_accessor, 0) > 0.0
|
ControlModule::get_clatter_time(module_accessor, 0) > 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns true if a match is currently active
|
||||||
|
pub unsafe fn is_ready_go() -> bool {
|
||||||
|
let fighter_manager = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager);
|
||||||
|
FighterManager::is_ready_go(fighter_manager)
|
||||||
|
}
|
||||||
|
|
157
src/lib.rs
157
src/lib.rs
|
@ -19,18 +19,16 @@ mod training;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test;
|
mod test;
|
||||||
|
|
||||||
use crate::common::consts::get_menu_from_url;
|
|
||||||
use crate::common::*;
|
use crate::common::*;
|
||||||
use crate::events::{Event, EVENT_QUEUE};
|
use crate::events::{Event, EVENT_QUEUE};
|
||||||
|
|
||||||
use skyline::libc::{c_char, mkdir};
|
use skyline::libc::mkdir;
|
||||||
use skyline::nro::{self, NroInfo};
|
use skyline::nro::{self, NroInfo};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use crate::training::frame_counter;
|
use crate::menu::{quick_menu_loop, web_session_loop};
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use training_mod_consts::OnOff;
|
use training_mod_consts::{OnOff, MenuJsonStruct};
|
||||||
use training_mod_tui::Color;
|
|
||||||
|
|
||||||
fn nro_main(nro: &NroInfo<'_>) {
|
fn nro_main(nro: &NroInfo<'_>) {
|
||||||
if nro.module.isLoaded {
|
if nro.module.isLoaded {
|
||||||
|
@ -46,26 +44,12 @@ fn nro_main(nro: &NroInfo<'_>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
#[link_name = "render_text_to_screen"]
|
|
||||||
pub fn render_text_to_screen_cstr(str: *const c_char);
|
|
||||||
|
|
||||||
#[link_name = "set_should_display_text_to_screen"]
|
|
||||||
pub fn set_should_display_text_to_screen(toggle: bool);
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! c_str {
|
macro_rules! c_str {
|
||||||
($l:tt) => {
|
($l:tt) => {
|
||||||
[$l.as_bytes(), "\u{0}".as_bytes()].concat().as_ptr()
|
[$l.as_bytes(), "\u{0}".as_bytes()].concat().as_ptr()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_text_to_screen(s: &str) {
|
|
||||||
unsafe {
|
|
||||||
render_text_to_screen_cstr(c_str!(s));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[skyline::main(name = "training_modpack")]
|
#[skyline::main(name = "training_modpack")]
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
macro_rules! log {
|
macro_rules! log {
|
||||||
|
@ -100,19 +84,19 @@ pub fn main() {
|
||||||
let menu_conf_path = "sd:/TrainingModpack/training_modpack_menu.conf";
|
let menu_conf_path = "sd:/TrainingModpack/training_modpack_menu.conf";
|
||||||
log!("Checking for previous menu in training_modpack_menu.conf...");
|
log!("Checking for previous menu in training_modpack_menu.conf...");
|
||||||
if fs::metadata(menu_conf_path).is_ok() {
|
if fs::metadata(menu_conf_path).is_ok() {
|
||||||
let menu_conf = fs::read(menu_conf_path).unwrap();
|
let menu_conf = fs::read_to_string(&menu_conf_path).unwrap();
|
||||||
if menu_conf.starts_with(b"http://localhost") {
|
if let Ok(menu_conf_json) = serde_json::from_str::<MenuJsonStruct>(&menu_conf) {
|
||||||
log!("Previous menu found, loading from training_modpack_menu.conf");
|
|
||||||
unsafe {
|
unsafe {
|
||||||
MENU = get_menu_from_url(MENU, std::str::from_utf8(&menu_conf).unwrap(), false);
|
MENU = menu_conf_json.menu;
|
||||||
DEFAULTS_MENU = get_menu_from_url(
|
DEFAULTS_MENU = menu_conf_json.defaults_menu;
|
||||||
DEFAULTS_MENU,
|
log!("Previous menu found. Loading...");
|
||||||
std::str::from_utf8(&menu_conf).unwrap(),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
} 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!");
|
||||||
} else {
|
} else {
|
||||||
log!("Previous menu found but is invalid.");
|
log!("Previous menu found but is invalid. Deleting...");
|
||||||
|
fs::remove_file(menu_conf_path).expect("Could not delete conf file!");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log!("No previous menu file found.");
|
log!("No previous menu file found.");
|
||||||
|
@ -141,118 +125,7 @@ pub fn main() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
std::thread::spawn(|| {
|
std::thread::spawn(|| unsafe { quick_menu_loop() });
|
||||||
std::thread::sleep(std::time::Duration::from_secs(10));
|
|
||||||
let menu;
|
|
||||||
unsafe {
|
|
||||||
menu = consts::get_menu();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut app = training_mod_tui::App::new(menu);
|
std::thread::spawn(|| unsafe { web_session_loop() });
|
||||||
|
|
||||||
let backend = training_mod_tui::TestBackend::new(75, 15);
|
|
||||||
let mut terminal = training_mod_tui::Terminal::new(backend).unwrap();
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let mut has_slept_millis = 0;
|
|
||||||
let render_frames = 5;
|
|
||||||
let mut url = String::new();
|
|
||||||
let button_presses = &mut menu::BUTTON_PRESSES;
|
|
||||||
let mut received_input = true;
|
|
||||||
loop {
|
|
||||||
button_presses.a.read_press().then(|| {
|
|
||||||
app.on_a();
|
|
||||||
received_input = true;
|
|
||||||
});
|
|
||||||
let b_press = &mut button_presses.b;
|
|
||||||
b_press.read_press().then(|| {
|
|
||||||
received_input = true;
|
|
||||||
if !app.outer_list {
|
|
||||||
app.on_b()
|
|
||||||
} else if frame_counter::get_frame_count(menu::QUICK_MENU_FRAME_COUNTER_INDEX)
|
|
||||||
== 0
|
|
||||||
{
|
|
||||||
// Leave menu.
|
|
||||||
menu::QUICK_MENU_ACTIVE = false;
|
|
||||||
menu::set_menu_from_url(url.as_str());
|
|
||||||
println!("URL: {}", url.as_str());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
button_presses.zl.read_press().then(|| {
|
|
||||||
app.on_l();
|
|
||||||
received_input = true;
|
|
||||||
});
|
|
||||||
button_presses.zr.read_press().then(|| {
|
|
||||||
app.on_r();
|
|
||||||
received_input = true;
|
|
||||||
});
|
|
||||||
button_presses.left.read_press().then(|| {
|
|
||||||
app.on_left();
|
|
||||||
received_input = true;
|
|
||||||
});
|
|
||||||
button_presses.right.read_press().then(|| {
|
|
||||||
app.on_right();
|
|
||||||
received_input = true;
|
|
||||||
});
|
|
||||||
button_presses.up.read_press().then(|| {
|
|
||||||
app.on_up();
|
|
||||||
received_input = true;
|
|
||||||
});
|
|
||||||
button_presses.down.read_press().then(|| {
|
|
||||||
app.on_down();
|
|
||||||
received_input = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(16));
|
|
||||||
has_slept_millis += 16;
|
|
||||||
if has_slept_millis < 16 * render_frames {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
has_slept_millis = 16;
|
|
||||||
if !menu::QUICK_MENU_ACTIVE {
|
|
||||||
app = training_mod_tui::App::new(consts::get_menu());
|
|
||||||
set_should_display_text_to_screen(false);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if !received_input {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let mut view = String::new();
|
|
||||||
|
|
||||||
let frame_res = terminal
|
|
||||||
.draw(|f| url = training_mod_tui::ui(f, &mut app))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
use std::fmt::Write;
|
|
||||||
for (i, cell) in frame_res.buffer.content().iter().enumerate() {
|
|
||||||
match cell.fg {
|
|
||||||
Color::Black => write!(&mut view, "{}", &cell.symbol.black()),
|
|
||||||
Color::Blue => write!(&mut view, "{}", &cell.symbol.blue()),
|
|
||||||
Color::LightBlue => write!(&mut view, "{}", &cell.symbol.bright_blue()),
|
|
||||||
Color::Cyan => write!(&mut view, "{}", &cell.symbol.cyan()),
|
|
||||||
Color::LightCyan => write!(&mut view, "{}", &cell.symbol.cyan()),
|
|
||||||
Color::Red => write!(&mut view, "{}", &cell.symbol.red()),
|
|
||||||
Color::LightRed => write!(&mut view, "{}", &cell.symbol.bright_red()),
|
|
||||||
Color::LightGreen => write!(&mut view, "{}", &cell.symbol.bright_green()),
|
|
||||||
Color::Green => write!(&mut view, "{}", &cell.symbol.green()),
|
|
||||||
Color::Yellow => write!(&mut view, "{}", &cell.symbol.yellow()),
|
|
||||||
Color::LightYellow => write!(&mut view, "{}", &cell.symbol.bright_yellow()),
|
|
||||||
Color::Magenta => write!(&mut view, "{}", &cell.symbol.magenta()),
|
|
||||||
Color::LightMagenta => {
|
|
||||||
write!(&mut view, "{}", &cell.symbol.bright_magenta())
|
|
||||||
}
|
|
||||||
_ => write!(&mut view, "{}", &cell.symbol),
|
|
||||||
}
|
|
||||||
.unwrap();
|
|
||||||
if i % frame_res.area.width as usize == frame_res.area.width as usize - 1 {
|
|
||||||
writeln!(&mut view).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeln!(&mut view).unwrap();
|
|
||||||
|
|
||||||
render_text_to_screen(view.as_str());
|
|
||||||
received_input = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ if (isNx) {
|
||||||
window.nx.footer.setAssign('R', '', saveDefaults, { se: '' });
|
window.nx.footer.setAssign('R', '', saveDefaults, { se: '' });
|
||||||
window.nx.footer.setAssign('ZR', '', cycleNextTab, { se: '' });
|
window.nx.footer.setAssign('ZR', '', cycleNextTab, { se: '' });
|
||||||
window.nx.footer.setAssign('ZL', '', cyclePrevTab, { se: '' });
|
window.nx.footer.setAssign('ZL', '', cyclePrevTab, { se: '' });
|
||||||
|
window.nx.addEventListener("message", function(msg) { setSettingsFromJSON(msg)});
|
||||||
} else {
|
} else {
|
||||||
document.addEventListener('keypress', (event) => {
|
document.addEventListener('keypress', (event) => {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
|
@ -42,7 +43,7 @@ if (isNx) {
|
||||||
break;
|
break;
|
||||||
case 'l':
|
case 'l':
|
||||||
console.log('l');
|
console.log('l');
|
||||||
resetAllSubmenus();
|
resetAllMenus();
|
||||||
break;
|
break;
|
||||||
case 'r':
|
case 'r':
|
||||||
console.log('r');
|
console.log('r');
|
||||||
|
@ -63,15 +64,12 @@ if (isNx) {
|
||||||
const onLoad = () => {
|
const onLoad = () => {
|
||||||
// Activate the first tab
|
// Activate the first tab
|
||||||
openTab(document.querySelector('button.tab-button'));
|
openTab(document.querySelector('button.tab-button'));
|
||||||
|
|
||||||
// Extract URL params and set appropriate settings
|
|
||||||
setSettingsFromURL();
|
|
||||||
populateMenuFromSettings();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.onload = onLoad;
|
window.onload = onLoad;
|
||||||
|
|
||||||
var settings;
|
var settings;
|
||||||
|
var defaultSettings;
|
||||||
|
|
||||||
var lastFocusedItem = document.querySelector('.menu-item > button');
|
var lastFocusedItem = document.querySelector('.menu-item > button');
|
||||||
const currentTabContent = () => {
|
const currentTabContent = () => {
|
||||||
|
@ -181,13 +179,17 @@ function playSound(label) {
|
||||||
|
|
||||||
const exit = () => {
|
const exit = () => {
|
||||||
playSound('SeFooterDecideBack');
|
playSound('SeFooterDecideBack');
|
||||||
|
const messageObject = {
|
||||||
const url = buildURLFromSettings();
|
menu: settings,
|
||||||
|
defaults_menu: defaultSettings
|
||||||
|
}
|
||||||
|
|
||||||
if (isNx) {
|
if (isNx) {
|
||||||
window.location.href = url;
|
window.nx.sendMessage(
|
||||||
|
JSON.stringify(messageObject)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
console.log(url);
|
console.log(JSON.stringify(messageObject));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -205,18 +207,42 @@ function closeOrExit() {
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setSettingsFromJSON(msg) {
|
||||||
|
// Receive a menu message and set settings
|
||||||
|
var msg_json = JSON.parse(msg.data);
|
||||||
|
settings = msg_json["menu"];
|
||||||
|
defaultSettings = msg_json["defaults_menu"];
|
||||||
|
populateMenuFromSettings();
|
||||||
|
}
|
||||||
|
|
||||||
function setSettingsFromURL() {
|
function setSettingsFromURL() {
|
||||||
var { search } = window.location;
|
var { search } = window.location;
|
||||||
|
// Actual settings
|
||||||
const settingsFromSearch = search
|
const settingsFromSearch = search
|
||||||
.replace('?', '')
|
.replace('?', '')
|
||||||
.split('&')
|
.split('&')
|
||||||
.reduce((accumulator, currentValue) => {
|
.reduce((accumulator, currentValue) => {
|
||||||
var [key, value] = currentValue.split('=');
|
var [key, value] = currentValue.split('=');
|
||||||
|
if (!key.startsWith('__')) {
|
||||||
accumulator[key] = parseInt(value);
|
accumulator[key] = parseInt(value);
|
||||||
|
}
|
||||||
return accumulator;
|
return accumulator;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
settings = settingsFromSearch;
|
settings = settingsFromSearch;
|
||||||
|
|
||||||
|
// Default settings
|
||||||
|
const defaultSettingsFromSearch = search
|
||||||
|
.replace('?', '')
|
||||||
|
.split('&')
|
||||||
|
.reduce((accumulator, currentValue) => {
|
||||||
|
var [key, value] = currentValue.split('=');
|
||||||
|
if (key.startsWith('__')) {
|
||||||
|
accumulator[key.replace('__','')] = parseInt(value);
|
||||||
|
}
|
||||||
|
return accumulator;
|
||||||
|
}, {});
|
||||||
|
defaultSettings = defaultSettingsFromSearch;
|
||||||
|
populateMenuFromSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildURLFromSettings() {
|
function buildURLFromSettings() {
|
||||||
|
@ -288,7 +314,7 @@ function resetCurrentMenu() {
|
||||||
const menu = document.querySelector('.modal:not(.hide)');
|
const menu = document.querySelector('.modal:not(.hide)');
|
||||||
|
|
||||||
const menuId = menu.dataset.id;
|
const menuId = menu.dataset.id;
|
||||||
const defaultSectionMask = settings[DEFAULTS_PREFIX + menuId];
|
const defaultSectionMask = defaultSettings[menuId];
|
||||||
|
|
||||||
settings[menuId] = defaultSectionMask;
|
settings[menuId] = defaultSectionMask;
|
||||||
|
|
||||||
|
@ -299,8 +325,8 @@ function resetAllMenus() {
|
||||||
// Resets all submenus to the default values
|
// Resets all submenus to the default values
|
||||||
if (confirm('Are you sure that you want to reset all menu settings to the default?')) {
|
if (confirm('Are you sure that you want to reset all menu settings to the default?')) {
|
||||||
document.querySelectorAll('.menu-item').forEach(function (item) {
|
document.querySelectorAll('.menu-item').forEach(function (item) {
|
||||||
const defaultMenuId = DEFAULTS_PREFIX + item.id;
|
const defaultMenuId = item.id;
|
||||||
const defaultMask = settings[defaultMenuId];
|
const defaultMask = defaultSettings[defaultMenuId];
|
||||||
|
|
||||||
settings[item.id] = defaultMask;
|
settings[item.id] = defaultMask;
|
||||||
|
|
||||||
|
@ -316,9 +342,9 @@ function setHelpText(text) {
|
||||||
function saveDefaults() {
|
function saveDefaults() {
|
||||||
if (confirm('Are you sure that you want to change the default menu settings to the current selections?')) {
|
if (confirm('Are you sure that you want to change the default menu settings to the current selections?')) {
|
||||||
document.querySelectorAll('.menu-item').forEach((item) => {
|
document.querySelectorAll('.menu-item').forEach((item) => {
|
||||||
const menu = DEFAULTS_PREFIX + item.id;
|
const menu = item.id;
|
||||||
|
|
||||||
settings[menu] = getMaskFromMenuID(item.id);
|
defaultSettings[menu] = getMaskFromMenuID(item.id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,9 @@ num-traits = "0.2"
|
||||||
ramhorns = "0.12.0"
|
ramhorns = "0.12.0"
|
||||||
paste = "1.0"
|
paste = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_repr = "0.1.8"
|
||||||
|
serde_json = "1"
|
||||||
|
bitflags_serde_shim = "0.2"
|
||||||
skyline_smash = { git = "https://github.com/ultimate-research/skyline-smash.git", optional = true }
|
skyline_smash = { git = "https://github.com/ultimate-research/skyline-smash.git", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|
|
@ -1,17 +1,21 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate bitflags;
|
extern crate bitflags;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate bitflags_serde_shim;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate num_derive;
|
extern crate num_derive;
|
||||||
|
|
||||||
use core::f64::consts::PI;
|
use core::f64::consts::PI;
|
||||||
use std::collections::HashMap;
|
use ramhorns::Content;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||||
#[cfg(feature = "smash")]
|
#[cfg(feature = "smash")]
|
||||||
use smash::lib::lua_const::*;
|
use smash::lib::lua_const::*;
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
use strum_macros::EnumIter;
|
use strum_macros::EnumIter;
|
||||||
use serde::{Serialize, Deserialize};
|
use std::collections::HashMap;
|
||||||
use ramhorns::Content;
|
|
||||||
|
|
||||||
pub trait ToggleTrait {
|
pub trait ToggleTrait {
|
||||||
fn to_toggle_strs() -> Vec<&'static str>;
|
fn to_toggle_strs() -> Vec<&'static str>;
|
||||||
|
@ -73,17 +77,14 @@ macro_rules! extra_bitflag_impls {
|
||||||
all_options.iter().map(|i| i.bits() as usize).collect()
|
all_options.iter().map(|i| i.bits() as usize).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl ToUrlParam for $e {
|
|
||||||
fn to_url_param(&self) -> String {
|
|
||||||
self.bits().to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_random_int(_max: i32) -> i32 {
|
pub fn get_random_int(_max: i32) -> i32 {
|
||||||
#[cfg(feature = "smash")]
|
#[cfg(feature = "smash")]
|
||||||
unsafe { smash::app::sv_math::rand(smash::hash40("fighter"), _max) }
|
unsafe {
|
||||||
|
smash::app::sv_math::rand(smash::hash40("fighter"), _max)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "smash"))]
|
#[cfg(not(feature = "smash"))]
|
||||||
0
|
0
|
||||||
|
@ -101,7 +102,6 @@ pub fn random_option<T>(arg: &[T]) -> &T {
|
||||||
|
|
||||||
// DI / Left stick
|
// DI / Left stick
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct Direction : u32 {
|
pub struct Direction : u32 {
|
||||||
const OUT = 0x1;
|
const OUT = 0x1;
|
||||||
const UP_OUT = 0x2;
|
const UP_OUT = 0x2;
|
||||||
|
@ -163,10 +163,10 @@ impl Direction {
|
||||||
}
|
}
|
||||||
|
|
||||||
extra_bitflag_impls! {Direction}
|
extra_bitflag_impls! {Direction}
|
||||||
|
impl_serde_for_bitflags!(Direction);
|
||||||
|
|
||||||
// Ledge Option
|
// Ledge Option
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct LedgeOption : u32
|
pub struct LedgeOption : u32
|
||||||
{
|
{
|
||||||
const NEUTRAL = 0x1;
|
const NEUTRAL = 0x1;
|
||||||
|
@ -179,7 +179,8 @@ bitflags! {
|
||||||
|
|
||||||
impl LedgeOption {
|
impl LedgeOption {
|
||||||
pub fn into_status(self) -> Option<i32> {
|
pub fn into_status(self) -> Option<i32> {
|
||||||
#[cfg(feature = "smash")] {
|
#[cfg(feature = "smash")]
|
||||||
|
{
|
||||||
Some(match self {
|
Some(match self {
|
||||||
LedgeOption::NEUTRAL => *FIGHTER_STATUS_KIND_CLIFF_CLIMB,
|
LedgeOption::NEUTRAL => *FIGHTER_STATUS_KIND_CLIFF_CLIMB,
|
||||||
LedgeOption::ROLL => *FIGHTER_STATUS_KIND_CLIFF_ESCAPE,
|
LedgeOption::ROLL => *FIGHTER_STATUS_KIND_CLIFF_ESCAPE,
|
||||||
|
@ -207,10 +208,10 @@ impl LedgeOption {
|
||||||
}
|
}
|
||||||
|
|
||||||
extra_bitflag_impls! {LedgeOption}
|
extra_bitflag_impls! {LedgeOption}
|
||||||
|
impl_serde_for_bitflags!(LedgeOption);
|
||||||
|
|
||||||
// Tech options
|
// Tech options
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct TechFlags : u32 {
|
pub struct TechFlags : u32 {
|
||||||
const NO_TECH = 0x1;
|
const NO_TECH = 0x1;
|
||||||
const ROLL_F = 0x2;
|
const ROLL_F = 0x2;
|
||||||
|
@ -232,10 +233,10 @@ impl TechFlags {
|
||||||
}
|
}
|
||||||
|
|
||||||
extra_bitflag_impls! {TechFlags}
|
extra_bitflag_impls! {TechFlags}
|
||||||
|
impl_serde_for_bitflags!(TechFlags);
|
||||||
|
|
||||||
// Missed Tech Options
|
// Missed Tech Options
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct MissTechFlags : u32 {
|
pub struct MissTechFlags : u32 {
|
||||||
const GETUP = 0x1;
|
const GETUP = 0x1;
|
||||||
const ATTACK = 0x2;
|
const ATTACK = 0x2;
|
||||||
|
@ -257,10 +258,13 @@ impl MissTechFlags {
|
||||||
}
|
}
|
||||||
|
|
||||||
extra_bitflag_impls! {MissTechFlags}
|
extra_bitflag_impls! {MissTechFlags}
|
||||||
|
impl_serde_for_bitflags!(MissTechFlags);
|
||||||
|
|
||||||
/// Shield States
|
/// Shield States
|
||||||
#[repr(i32)]
|
#[repr(i32)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, FromPrimitive, EnumIter, Serialize, Deserialize)]
|
#[derive(
|
||||||
|
Debug, Clone, Copy, PartialEq, FromPrimitive, EnumIter, Serialize_repr, Deserialize_repr,
|
||||||
|
)]
|
||||||
pub enum Shield {
|
pub enum Shield {
|
||||||
None = 0x0,
|
None = 0x0,
|
||||||
Infinite = 0x1,
|
Infinite = 0x1,
|
||||||
|
@ -277,10 +281,6 @@ impl Shield {
|
||||||
Shield::Constant => "Constant",
|
Shield::Constant => "Constant",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_url_param(&self) -> String {
|
|
||||||
(*self as i32).to_string()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToggleTrait for Shield {
|
impl ToggleTrait for Shield {
|
||||||
|
@ -295,7 +295,9 @@ impl ToggleTrait for Shield {
|
||||||
|
|
||||||
// Save State Mirroring
|
// Save State Mirroring
|
||||||
#[repr(i32)]
|
#[repr(i32)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, FromPrimitive, EnumIter, Serialize, Deserialize)]
|
#[derive(
|
||||||
|
Debug, Clone, Copy, PartialEq, FromPrimitive, EnumIter, Serialize_repr, Deserialize_repr,
|
||||||
|
)]
|
||||||
pub enum SaveStateMirroring {
|
pub enum SaveStateMirroring {
|
||||||
None = 0x0,
|
None = 0x0,
|
||||||
Alternate = 0x1,
|
Alternate = 0x1,
|
||||||
|
@ -310,15 +312,13 @@ impl SaveStateMirroring {
|
||||||
SaveStateMirroring::Random => "Random",
|
SaveStateMirroring::Random => "Random",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_url_param(&self) -> String {
|
|
||||||
(*self as i32).to_string()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToggleTrait for SaveStateMirroring {
|
impl ToggleTrait for SaveStateMirroring {
|
||||||
fn to_toggle_strs() -> Vec<&'static str> {
|
fn to_toggle_strs() -> Vec<&'static str> {
|
||||||
SaveStateMirroring::iter().map(|i| i.as_str().unwrap_or("")).collect()
|
SaveStateMirroring::iter()
|
||||||
|
.map(|i| i.as_str().unwrap_or(""))
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_toggle_vals() -> Vec<usize> {
|
fn to_toggle_vals() -> Vec<usize> {
|
||||||
|
@ -328,7 +328,6 @@ impl ToggleTrait for SaveStateMirroring {
|
||||||
|
|
||||||
// Defensive States
|
// Defensive States
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct Defensive : u32 {
|
pub struct Defensive : u32 {
|
||||||
const SPOT_DODGE = 0x1;
|
const SPOT_DODGE = 0x1;
|
||||||
const ROLL_F = 0x2;
|
const ROLL_F = 0x2;
|
||||||
|
@ -352,9 +351,10 @@ impl Defensive {
|
||||||
}
|
}
|
||||||
|
|
||||||
extra_bitflag_impls! {Defensive}
|
extra_bitflag_impls! {Defensive}
|
||||||
|
impl_serde_for_bitflags!(Defensive);
|
||||||
|
|
||||||
#[repr(i32)]
|
#[repr(i32)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize_repr, Deserialize_repr)]
|
||||||
pub enum OnOff {
|
pub enum OnOff {
|
||||||
Off = 0,
|
Off = 0,
|
||||||
On = 1,
|
On = 1,
|
||||||
|
@ -375,10 +375,6 @@ impl OnOff {
|
||||||
OnOff::On => "On",
|
OnOff::On => "On",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_url_param(&self) -> String {
|
|
||||||
(*self as i32).to_string()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToggleTrait for OnOff {
|
impl ToggleTrait for OnOff {
|
||||||
|
@ -391,7 +387,6 @@ impl ToggleTrait for OnOff {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct Action : u32 {
|
pub struct Action : u32 {
|
||||||
const AIR_DODGE = 0x1;
|
const AIR_DODGE = 0x1;
|
||||||
const JUMP = 0x2;
|
const JUMP = 0x2;
|
||||||
|
@ -424,7 +419,8 @@ bitflags! {
|
||||||
|
|
||||||
impl Action {
|
impl Action {
|
||||||
pub fn into_attack_air_kind(self) -> Option<i32> {
|
pub fn into_attack_air_kind(self) -> Option<i32> {
|
||||||
#[cfg(feature = "smash")] {
|
#[cfg(feature = "smash")]
|
||||||
|
{
|
||||||
Some(match self {
|
Some(match self {
|
||||||
Action::NAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_N,
|
Action::NAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_N,
|
||||||
Action::FAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_F,
|
Action::FAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_F,
|
||||||
|
@ -472,9 +468,9 @@ impl Action {
|
||||||
}
|
}
|
||||||
|
|
||||||
extra_bitflag_impls! {Action}
|
extra_bitflag_impls! {Action}
|
||||||
|
impl_serde_for_bitflags!(Action);
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct AttackAngle : u32 {
|
pub struct AttackAngle : u32 {
|
||||||
const NEUTRAL = 0x1;
|
const NEUTRAL = 0x1;
|
||||||
const UP = 0x2;
|
const UP = 0x2;
|
||||||
|
@ -494,9 +490,9 @@ impl AttackAngle {
|
||||||
}
|
}
|
||||||
|
|
||||||
extra_bitflag_impls! {AttackAngle}
|
extra_bitflag_impls! {AttackAngle}
|
||||||
|
impl_serde_for_bitflags!(AttackAngle);
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct Delay : u32 {
|
pub struct Delay : u32 {
|
||||||
const D0 = 0x1;
|
const D0 = 0x1;
|
||||||
const D1 = 0x2;
|
const D1 = 0x2;
|
||||||
|
@ -534,7 +530,6 @@ bitflags! {
|
||||||
|
|
||||||
// Throw Option
|
// Throw Option
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct ThrowOption : u32
|
pub struct ThrowOption : u32
|
||||||
{
|
{
|
||||||
const NONE = 0x1;
|
const NONE = 0x1;
|
||||||
|
@ -547,7 +542,8 @@ bitflags! {
|
||||||
|
|
||||||
impl ThrowOption {
|
impl ThrowOption {
|
||||||
pub fn into_cmd(self) -> Option<i32> {
|
pub fn into_cmd(self) -> Option<i32> {
|
||||||
#[cfg(feature = "smash")] {
|
#[cfg(feature = "smash")]
|
||||||
|
{
|
||||||
Some(match self {
|
Some(match self {
|
||||||
ThrowOption::NONE => 0,
|
ThrowOption::NONE => 0,
|
||||||
ThrowOption::FORWARD => *FIGHTER_PAD_CMD_CAT2_FLAG_THROW_F,
|
ThrowOption::FORWARD => *FIGHTER_PAD_CMD_CAT2_FLAG_THROW_F,
|
||||||
|
@ -575,10 +571,10 @@ impl ThrowOption {
|
||||||
}
|
}
|
||||||
|
|
||||||
extra_bitflag_impls! {ThrowOption}
|
extra_bitflag_impls! {ThrowOption}
|
||||||
|
impl_serde_for_bitflags!(ThrowOption);
|
||||||
|
|
||||||
// Buff Option
|
// Buff Option
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct BuffOption : u32
|
pub struct BuffOption : u32
|
||||||
{
|
{
|
||||||
const ACCELERATLE = 0x1;
|
const ACCELERATLE = 0x1;
|
||||||
|
@ -595,7 +591,8 @@ bitflags! {
|
||||||
|
|
||||||
impl BuffOption {
|
impl BuffOption {
|
||||||
pub fn into_int(self) -> Option<i32> {
|
pub fn into_int(self) -> Option<i32> {
|
||||||
#[cfg(feature = "smash")] {
|
#[cfg(feature = "smash")]
|
||||||
|
{
|
||||||
Some(match self {
|
Some(match self {
|
||||||
BuffOption::ACCELERATLE => *FIGHTER_BRAVE_SPECIAL_LW_COMMAND11_SPEED_UP,
|
BuffOption::ACCELERATLE => *FIGHTER_BRAVE_SPECIAL_LW_COMMAND11_SPEED_UP,
|
||||||
BuffOption::OOMPH => *FIGHTER_BRAVE_SPECIAL_LW_COMMAND12_ATTACK_UP,
|
BuffOption::OOMPH => *FIGHTER_BRAVE_SPECIAL_LW_COMMAND12_ATTACK_UP,
|
||||||
|
@ -631,6 +628,7 @@ impl BuffOption {
|
||||||
}
|
}
|
||||||
|
|
||||||
extra_bitflag_impls! {BuffOption}
|
extra_bitflag_impls! {BuffOption}
|
||||||
|
impl_serde_for_bitflags!(BuffOption);
|
||||||
|
|
||||||
impl Delay {
|
impl Delay {
|
||||||
pub fn as_str(self) -> Option<&'static str> {
|
pub fn as_str(self) -> Option<&'static str> {
|
||||||
|
@ -676,9 +674,9 @@ impl Delay {
|
||||||
}
|
}
|
||||||
|
|
||||||
extra_bitflag_impls! {Delay}
|
extra_bitflag_impls! {Delay}
|
||||||
|
impl_serde_for_bitflags!(Delay);
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct MedDelay : u32 {
|
pub struct MedDelay : u32 {
|
||||||
const D0 = 0x1;
|
const D0 = 0x1;
|
||||||
const D5 = 0x2;
|
const D5 = 0x2;
|
||||||
|
@ -758,9 +756,9 @@ impl MedDelay {
|
||||||
}
|
}
|
||||||
|
|
||||||
extra_bitflag_impls! {MedDelay}
|
extra_bitflag_impls! {MedDelay}
|
||||||
|
impl_serde_for_bitflags!(MedDelay);
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct LongDelay : u32 {
|
pub struct LongDelay : u32 {
|
||||||
const D0 = 0x1;
|
const D0 = 0x1;
|
||||||
const D10 = 0x2;
|
const D10 = 0x2;
|
||||||
|
@ -840,9 +838,9 @@ impl LongDelay {
|
||||||
}
|
}
|
||||||
|
|
||||||
extra_bitflag_impls! {LongDelay}
|
extra_bitflag_impls! {LongDelay}
|
||||||
|
impl_serde_for_bitflags!(LongDelay);
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct BoolFlag : u32 {
|
pub struct BoolFlag : u32 {
|
||||||
const TRUE = 0x1;
|
const TRUE = 0x1;
|
||||||
const FALSE = 0x2;
|
const FALSE = 0x2;
|
||||||
|
@ -850,6 +848,7 @@ bitflags! {
|
||||||
}
|
}
|
||||||
|
|
||||||
extra_bitflag_impls! {BoolFlag}
|
extra_bitflag_impls! {BoolFlag}
|
||||||
|
impl_serde_for_bitflags!(BoolFlag);
|
||||||
|
|
||||||
impl BoolFlag {
|
impl BoolFlag {
|
||||||
pub fn into_bool(self) -> bool {
|
pub fn into_bool(self) -> bool {
|
||||||
|
@ -865,7 +864,9 @@ impl BoolFlag {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, EnumIter, Serialize, Deserialize)]
|
#[derive(
|
||||||
|
Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, EnumIter, Serialize_repr, Deserialize_repr,
|
||||||
|
)]
|
||||||
pub enum InputFrequency {
|
pub enum InputFrequency {
|
||||||
None = 0,
|
None = 0,
|
||||||
Normal = 1,
|
Normal = 1,
|
||||||
|
@ -891,15 +892,13 @@ impl InputFrequency {
|
||||||
InputFrequency::High => "High",
|
InputFrequency::High => "High",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_url_param(&self) -> String {
|
|
||||||
(*self as u32).to_string()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToggleTrait for InputFrequency {
|
impl ToggleTrait for InputFrequency {
|
||||||
fn to_toggle_strs() -> Vec<&'static str> {
|
fn to_toggle_strs() -> Vec<&'static str> {
|
||||||
InputFrequency::iter().map(|i| i.as_str().unwrap_or("")).collect()
|
InputFrequency::iter()
|
||||||
|
.map(|i| i.as_str().unwrap_or(""))
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_toggle_vals() -> Vec<usize> {
|
fn to_toggle_vals() -> Vec<usize> {
|
||||||
|
@ -907,22 +906,11 @@ impl ToggleTrait for InputFrequency {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For input delay
|
|
||||||
trait ToUrlParam {
|
|
||||||
fn to_url_param(&self) -> String;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The i32 is now in log form, need to exponentiate
|
|
||||||
// back into 2^X when we pass back to the menu
|
|
||||||
impl ToUrlParam for i32 {
|
|
||||||
fn to_url_param(&self) -> String {
|
|
||||||
2_i32.pow(*self as u32).to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Item Selections
|
/// Item Selections
|
||||||
#[repr(i32)]
|
#[repr(i32)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, FromPrimitive, EnumIter, Serialize, Deserialize)]
|
#[derive(
|
||||||
|
Debug, Clone, Copy, PartialEq, FromPrimitive, EnumIter, Serialize_repr, Deserialize_repr,
|
||||||
|
)]
|
||||||
pub enum CharacterItem {
|
pub enum CharacterItem {
|
||||||
None = 0,
|
None = 0,
|
||||||
PlayerVariation1 = 0x1,
|
PlayerVariation1 = 0x1,
|
||||||
|
@ -969,15 +957,13 @@ impl CharacterItem {
|
||||||
_ => "None",
|
_ => "None",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_url_param(&self) -> String {
|
|
||||||
(*self as i32).to_string()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToggleTrait for CharacterItem {
|
impl ToggleTrait for CharacterItem {
|
||||||
fn to_toggle_strs() -> Vec<&'static str> {
|
fn to_toggle_strs() -> Vec<&'static str> {
|
||||||
CharacterItem::iter().map(|i| i.as_str().unwrap_or("")).collect()
|
CharacterItem::iter()
|
||||||
|
.map(|i| i.as_str().unwrap_or(""))
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_toggle_vals() -> Vec<usize> {
|
fn to_toggle_vals() -> Vec<usize> {
|
||||||
|
@ -985,42 +971,6 @@ impl ToggleTrait for CharacterItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Macro to build the url parameter string
|
|
||||||
macro_rules! url_params {
|
|
||||||
(
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive($($trait_name:ident, )*)]
|
|
||||||
pub struct $e:ident {
|
|
||||||
$(pub $field_name:ident: $field_type:ty,)*
|
|
||||||
}
|
|
||||||
) => {
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive($($trait_name, )*)]
|
|
||||||
pub struct $e {
|
|
||||||
$(pub $field_name: $field_type,)*
|
|
||||||
}
|
|
||||||
impl $e {
|
|
||||||
pub fn to_url_params(&self, defaults: bool) -> String {
|
|
||||||
let mut s = "".to_string();
|
|
||||||
let defaults_str = match defaults {
|
|
||||||
true => &"__", // Prefix field name with "__" if it is for the default menu
|
|
||||||
false => &"",
|
|
||||||
};
|
|
||||||
$(
|
|
||||||
s.push_str(defaults_str);
|
|
||||||
s.push_str(stringify!($field_name));
|
|
||||||
s.push_str(&"=");
|
|
||||||
s.push_str(&self.$field_name.to_url_param());
|
|
||||||
s.push_str(&"&");
|
|
||||||
)*
|
|
||||||
s.pop();
|
|
||||||
s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
url_params! {
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug, )]
|
#[derive(Clone, Copy, Serialize, Deserialize, Debug, )]
|
||||||
pub struct TrainingModpackMenu {
|
pub struct TrainingModpackMenu {
|
||||||
|
@ -1063,7 +1013,7 @@ url_params! {
|
||||||
pub character_item: CharacterItem,
|
pub character_item: CharacterItem,
|
||||||
pub quick_menu: OnOff,
|
pub quick_menu: OnOff,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! set_by_str {
|
macro_rules! set_by_str {
|
||||||
($obj:ident, $s:ident, $($field:ident = $rhs:expr,)*) => {
|
($obj:ident, $s:ident, $($field:ident = $rhs:expr,)*) => {
|
||||||
|
@ -1075,11 +1025,14 @@ macro_rules! set_by_str {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn num_bits<T>() -> usize { std::mem::size_of::<T>() * 8 }
|
const fn num_bits<T>() -> usize {
|
||||||
|
std::mem::size_of::<T>() * 8
|
||||||
|
}
|
||||||
|
|
||||||
fn log_2(x: u32) -> u32 {
|
fn log_2(x: u32) -> u32 {
|
||||||
if x == 0 { 0 }
|
if x == 0 {
|
||||||
else {
|
0
|
||||||
|
} else {
|
||||||
num_bits::<u32>() as u32 - x.leading_zeros() - 1
|
num_bits::<u32>() as u32 - x.leading_zeros() - 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1131,6 +1084,14 @@ impl TrainingModpackMenu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct MenuJsonStruct {
|
||||||
|
pub menu: TrainingModpackMenu,
|
||||||
|
pub defaults_menu: TrainingModpackMenu,
|
||||||
|
// pub last_focused_submenu: &str
|
||||||
|
}
|
||||||
|
|
||||||
// Fighter Ids
|
// Fighter Ids
|
||||||
#[repr(i32)]
|
#[repr(i32)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
@ -1150,7 +1111,7 @@ impl SubMenuType {
|
||||||
match s {
|
match s {
|
||||||
"toggle" => SubMenuType::TOGGLE,
|
"toggle" => SubMenuType::TOGGLE,
|
||||||
"slider" => SubMenuType::SLIDER,
|
"slider" => SubMenuType::SLIDER,
|
||||||
_ => panic!("Unexpected SubMenuType!")
|
_ => panic!("Unexpected SubMenuType!"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1224,18 +1185,12 @@ pub struct SubMenu<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> SubMenu<'a> {
|
impl<'a> SubMenu<'a> {
|
||||||
pub fn add_toggle(
|
pub fn add_toggle(&mut self, toggle_value: usize, toggle_title: &'a str) {
|
||||||
&mut self,
|
self.toggles.push(Toggle {
|
||||||
toggle_value: usize,
|
|
||||||
toggle_title: &'a str
|
|
||||||
) {
|
|
||||||
self.toggles.push(
|
|
||||||
Toggle {
|
|
||||||
toggle_value: toggle_value,
|
toggle_value: toggle_value,
|
||||||
toggle_title: toggle_title,
|
toggle_title: toggle_title,
|
||||||
checked: false
|
checked: false,
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
pub fn new_with_toggles<T: ToggleTrait>(
|
pub fn new_with_toggles<T: ToggleTrait>(
|
||||||
submenu_title: &'a str,
|
submenu_title: &'a str,
|
||||||
|
@ -1249,16 +1204,13 @@ impl<'a> SubMenu<'a> {
|
||||||
help_text: help_text,
|
help_text: help_text,
|
||||||
is_single_option: is_single_option,
|
is_single_option: is_single_option,
|
||||||
toggles: Vec::new(),
|
toggles: Vec::new(),
|
||||||
_type: "toggle"
|
_type: "toggle",
|
||||||
};
|
};
|
||||||
|
|
||||||
let values = T::to_toggle_vals();
|
let values = T::to_toggle_vals();
|
||||||
let titles = T::to_toggle_strs();
|
let titles = T::to_toggle_strs();
|
||||||
for i in 0..values.len() {
|
for i in 0..values.len() {
|
||||||
instance.add_toggle(
|
instance.add_toggle(values[i], titles[i]);
|
||||||
values[i],
|
|
||||||
titles[i],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
instance
|
instance
|
||||||
}
|
}
|
||||||
|
@ -1279,14 +1231,12 @@ impl<'a> Tab<'a> {
|
||||||
help_text: &'a str,
|
help_text: &'a str,
|
||||||
is_single_option: bool,
|
is_single_option: bool,
|
||||||
) {
|
) {
|
||||||
self.tab_submenus.push(
|
self.tab_submenus.push(SubMenu::new_with_toggles::<T>(
|
||||||
SubMenu::new_with_toggles::<T>(
|
|
||||||
submenu_title,
|
submenu_title,
|
||||||
submenu_id,
|
submenu_id,
|
||||||
help_text,
|
help_text,
|
||||||
is_single_option,
|
is_single_option,
|
||||||
)
|
));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1295,39 +1245,8 @@ pub struct UiMenu<'a> {
|
||||||
pub tabs: Vec<Tab<'a>>,
|
pub tabs: Vec<Tab<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_menu_from_url(mut menu: TrainingModpackMenu, s: &str, defaults: bool) -> TrainingModpackMenu {
|
|
||||||
let base_url_len = "http://localhost/?".len();
|
|
||||||
let total_len = s.len();
|
|
||||||
|
|
||||||
let ss: String = s
|
|
||||||
.chars()
|
|
||||||
.skip(base_url_len)
|
|
||||||
.take(total_len - base_url_len)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for toggle_values in ss.split('&') {
|
|
||||||
let toggle_value_split = toggle_values.split('=').collect::<Vec<&str>>();
|
|
||||||
let mut toggle = toggle_value_split[0];
|
|
||||||
if toggle.is_empty() | (
|
|
||||||
// Default menu settings begin with the prefix "__"
|
|
||||||
// So if skip toggles without the prefix if defaults is true
|
|
||||||
// And skip toggles with the prefix if defaults is false
|
|
||||||
defaults ^ toggle.starts_with("__")
|
|
||||||
) { continue }
|
|
||||||
toggle = toggle.strip_prefix("__").unwrap_or(toggle);
|
|
||||||
|
|
||||||
let bits: u32 = toggle_value_split[1].parse().unwrap_or(0);
|
|
||||||
menu.set(toggle, bits);
|
|
||||||
}
|
|
||||||
menu
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub unsafe fn get_menu() -> UiMenu<'static> {
|
pub unsafe fn get_menu() -> UiMenu<'static> {
|
||||||
let mut overall_menu = UiMenu {
|
let mut overall_menu = UiMenu { tabs: Vec::new() };
|
||||||
tabs: Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut mash_tab = Tab {
|
let mut mash_tab = Tab {
|
||||||
tab_id: "mash",
|
tab_id: "mash",
|
||||||
|
@ -1420,7 +1339,6 @@ pub unsafe fn get_menu() -> UiMenu<'static> {
|
||||||
);
|
);
|
||||||
overall_menu.tabs.push(mash_tab);
|
overall_menu.tabs.push(mash_tab);
|
||||||
|
|
||||||
|
|
||||||
let mut defensive_tab = Tab {
|
let mut defensive_tab = Tab {
|
||||||
tab_id: "defensive",
|
tab_id: "defensive",
|
||||||
tab_title: "Defensive Settings",
|
tab_title: "Defensive Settings",
|
||||||
|
@ -1508,13 +1426,13 @@ pub unsafe fn get_menu() -> UiMenu<'static> {
|
||||||
"Character Item",
|
"Character Item",
|
||||||
"character_item",
|
"character_item",
|
||||||
"Character Item: CPU/Player item to hold when loading a save state",
|
"Character Item: CPU/Player item to hold when loading a save state",
|
||||||
true
|
true,
|
||||||
);
|
);
|
||||||
defensive_tab.add_submenu_with_toggles::<OnOff>(
|
defensive_tab.add_submenu_with_toggles::<OnOff>(
|
||||||
"Crouch",
|
"Crouch",
|
||||||
"crouch",
|
"crouch",
|
||||||
"Crouch: Should the CPU crouch when on the ground",
|
"Crouch: Should the CPU crouch when on the ground",
|
||||||
true
|
true,
|
||||||
);
|
);
|
||||||
overall_menu.tabs.push(defensive_tab);
|
overall_menu.tabs.push(defensive_tab);
|
||||||
|
|
||||||
|
@ -1569,36 +1487,37 @@ pub unsafe fn get_menu() -> UiMenu<'static> {
|
||||||
"Stage Hazards",
|
"Stage Hazards",
|
||||||
"stage_hazards",
|
"stage_hazards",
|
||||||
"Stage Hazards: Should stage hazards be present",
|
"Stage Hazards: Should stage hazards be present",
|
||||||
true
|
true,
|
||||||
);
|
);
|
||||||
misc_tab.add_submenu_with_toggles::<OnOff>(
|
misc_tab.add_submenu_with_toggles::<OnOff>(
|
||||||
"Quick Menu",
|
"Quick Menu",
|
||||||
"quick_menu",
|
"quick_menu",
|
||||||
"Quick Menu: Should use quick or web menu",
|
"Quick Menu: Should use quick or web menu",
|
||||||
true
|
true,
|
||||||
);
|
);
|
||||||
overall_menu.tabs.push(misc_tab);
|
overall_menu.tabs.push(misc_tab);
|
||||||
|
|
||||||
let non_ui_menu = MENU;
|
let non_ui_menu = serde_json::to_string(&MENU).unwrap().replace("\"", "");
|
||||||
let url_params = non_ui_menu.to_url_params(false);
|
let toggle_values_all = non_ui_menu.split(',').collect::<Vec<&str>>();
|
||||||
let toggle_values_all = url_params.split("&");
|
|
||||||
let mut sub_menu_id_to_vals: HashMap<&str, u32> = HashMap::new();
|
let mut sub_menu_id_to_vals: HashMap<&str, u32> = HashMap::new();
|
||||||
for toggle_values in toggle_values_all {
|
for toggle_values in toggle_values_all {
|
||||||
let toggle_value_split = toggle_values.split('=').collect::<Vec<&str>>();
|
let toggle_value_split = toggle_values.split(':').collect::<Vec<&str>>();
|
||||||
let mut sub_menu_id = toggle_value_split[0];
|
let sub_menu_id = toggle_value_split[0];
|
||||||
if sub_menu_id.is_empty() { continue }
|
if sub_menu_id.is_empty() {
|
||||||
sub_menu_id = sub_menu_id.strip_prefix("__").unwrap_or(sub_menu_id);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let full_bits: u32 = toggle_value_split[1].parse().unwrap_or(0);
|
let full_bits: u32 = toggle_value_split[1].parse().unwrap_or(0);
|
||||||
sub_menu_id_to_vals.insert(sub_menu_id, full_bits);
|
sub_menu_id_to_vals.insert(&sub_menu_id, full_bits);
|
||||||
}
|
}
|
||||||
overall_menu.tabs.iter_mut()
|
|
||||||
.for_each(|tab| {
|
overall_menu.tabs.iter_mut().for_each(|tab| {
|
||||||
tab.tab_submenus.iter_mut().for_each(|sub_menu| {
|
tab.tab_submenus.iter_mut().for_each(|sub_menu| {
|
||||||
let sub_menu_id = sub_menu.submenu_id;
|
let sub_menu_id = sub_menu.submenu_id;
|
||||||
sub_menu.toggles.iter_mut().for_each(|toggle| {
|
sub_menu.toggles.iter_mut().for_each(|toggle| {
|
||||||
if sub_menu_id_to_vals.contains_key(sub_menu_id) &&
|
if sub_menu_id_to_vals.contains_key(sub_menu_id)
|
||||||
(sub_menu_id_to_vals[sub_menu_id] & (toggle.toggle_value as u32) != 0) {
|
&& (sub_menu_id_to_vals[sub_menu_id] & (toggle.toggle_value as u32) != 0)
|
||||||
|
{
|
||||||
toggle.checked = true
|
toggle.checked = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -341,8 +341,6 @@ pub fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) -> String {
|
||||||
f.render_widget(help_paragraph, vertical_chunks[2]);
|
f.render_widget(help_paragraph, vertical_chunks[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let mut url = "http://localhost/".to_owned();
|
|
||||||
let mut settings = HashMap::new();
|
let mut settings = HashMap::new();
|
||||||
|
|
||||||
// Collect settings for toggles
|
// Collect settings for toggles
|
||||||
|
@ -358,11 +356,7 @@ pub fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
serde_json::to_string(&settings).unwrap()
|
||||||
url.push('?');
|
|
||||||
settings.iter()
|
|
||||||
.for_each(|(section, val)| url.push_str(format!("{}={}&", section, val).as_str()));
|
|
||||||
url
|
|
||||||
|
|
||||||
// TODO: Add saveDefaults
|
// TODO: Add saveDefaults
|
||||||
// if (document.getElementById("saveDefaults").checked) {
|
// if (document.getElementById("saveDefaults").checked) {
|
||||||
|
|
|
@ -38,12 +38,12 @@ fn ensure_menu_retains_multi_selections() -> Result<(), Box<dyn Error>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let (mut terminal, mut app) = test_backend_setup(menu)?;
|
let (mut terminal, mut app) = test_backend_setup(menu)?;
|
||||||
let mut url = String::new();
|
let mut json_response = String::new();
|
||||||
let _frame_res = terminal.draw(|f| url = training_mod_tui::ui(f, &mut app))?;
|
let _frame_res = terminal.draw(|f| json_response = training_mod_tui::ui(f, &mut app))?;
|
||||||
|
set_menu_from_json(json_response);
|
||||||
unsafe {
|
unsafe {
|
||||||
// At this point, we didn't change the menu at all; we should still see all missed tech flags.
|
// At this point, we didn't change the menu at all; we should still see all missed tech flags.
|
||||||
assert_eq!(get_menu_from_url(MENU, url.as_str(), false).miss_tech_state,
|
assert_eq!(MENU.miss_tech_state,
|
||||||
MissTechFlags::all());
|
MissTechFlags::all());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,8 +58,8 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
|
||||||
#[cfg(not(feature = "has_terminal"))] {
|
#[cfg(not(feature = "has_terminal"))] {
|
||||||
let (mut terminal, mut app) = test_backend_setup(menu)?;
|
let (mut terminal, mut app) = test_backend_setup(menu)?;
|
||||||
let mut url = String::new();
|
let mut json_response = String::new();
|
||||||
let frame_res = terminal.draw(|f| url = training_mod_tui::ui(f, &mut app))?;
|
let frame_res = terminal.draw(|f| json_response = training_mod_tui::ui(f, &mut app))?;
|
||||||
|
|
||||||
for (i, cell) in frame_res.buffer.content().iter().enumerate() {
|
for (i, cell) in frame_res.buffer.content().iter().enumerate() {
|
||||||
print!("{}", cell.symbol);
|
print!("{}", cell.symbol);
|
||||||
|
@ -69,7 +69,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
}
|
}
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
println!("URL: {}", url);
|
println!("json_response:\n{}", json_response);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "has_terminal")] {
|
#[cfg(feature = "has_terminal")] {
|
||||||
|
@ -111,9 +111,9 @@ fn run_app<B: tui::backend::Backend>(
|
||||||
tick_rate: Duration,
|
tick_rate: Duration,
|
||||||
) -> io::Result<String> {
|
) -> io::Result<String> {
|
||||||
let mut last_tick = Instant::now();
|
let mut last_tick = Instant::now();
|
||||||
let mut url = String::new();
|
let mut json_response = String::new();
|
||||||
loop {
|
loop {
|
||||||
terminal.draw(|f| url = training_mod_tui::ui(f, &mut app).clone())?;
|
terminal.draw(|f| json_response = training_mod_tui::ui(f, &mut app).clone())?;
|
||||||
|
|
||||||
let timeout = tick_rate
|
let timeout = tick_rate
|
||||||
.checked_sub(last_tick.elapsed())
|
.checked_sub(last_tick.elapsed())
|
||||||
|
@ -122,7 +122,7 @@ fn run_app<B: tui::backend::Backend>(
|
||||||
if crossterm::event::poll(timeout)? {
|
if crossterm::event::poll(timeout)? {
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Char('q') => return Ok(url),
|
KeyCode::Char('q') => return Ok(json_response),
|
||||||
KeyCode::Char('r') => app.on_r(),
|
KeyCode::Char('r') => app.on_r(),
|
||||||
KeyCode::Char('l') => app.on_l(),
|
KeyCode::Char('l') => app.on_l(),
|
||||||
KeyCode::Left => app.on_left(),
|
KeyCode::Left => app.on_left(),
|
||||||
|
|
Loading…
Add table
Reference in a new issue