mirror of
https://github.com/jugeeya/UltimateTrainingModpack.git
synced 2024-11-24 02:44:17 +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::events::{Event, EVENT_QUEUE};
|
||||
use crate::training::frame_counter;
|
||||
|
||||
use owo_colors::OwoColorize;
|
||||
use ramhorns::Template;
|
||||
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 std::fs;
|
||||
use std::path::Path;
|
||||
use training_mod_consts::{TrainingModpackMenu, MenuJsonStruct};
|
||||
use training_mod_tui::Color;
|
||||
|
||||
static mut FRAME_COUNTER_INDEX: usize = 0;
|
||||
pub static mut QUICK_MENU_FRAME_COUNTER_INDEX: usize = 0;
|
||||
|
@ -70,27 +75,49 @@ pub unsafe fn write_menu() {
|
|||
|
||||
const MENU_CONF_PATH: &str = "sd:/TrainingModpack/training_modpack_menu.conf";
|
||||
|
||||
pub fn set_menu_from_url(last_url: &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 is_emulator() {
|
||||
skyline::error::show_error(
|
||||
0x69,
|
||||
"Cannot use web menu on emulator.\n\0",
|
||||
"Only the quick menu is runnable via emulator currently.\n\0",
|
||||
);
|
||||
MENU.quick_menu = OnOff::On;
|
||||
}
|
||||
pub unsafe fn set_menu_from_json(message: &str) {
|
||||
if MENU.quick_menu == OnOff::Off {
|
||||
if is_emulator() {
|
||||
skyline::error::show_error(
|
||||
0x69,
|
||||
"Cannot use web menu on emulator.\n\0",
|
||||
"Only the quick menu is runnable via emulator currently.\n\0",
|
||||
);
|
||||
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");
|
||||
unsafe {
|
||||
EVENT_QUEUE.push(Event::menu_open(last_url.to_string()));
|
||||
}
|
||||
let conf = MenuJsonStruct {
|
||||
menu: MENU,
|
||||
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() {
|
||||
|
@ -99,42 +126,15 @@ pub fn spawn_menu() {
|
|||
frame_counter::start_counting(FRAME_COUNTER_INDEX);
|
||||
frame_counter::reset_frame_count(QUICK_MENU_FRAME_COUNTER_INDEX);
|
||||
frame_counter::start_counting(QUICK_MENU_FRAME_COUNTER_INDEX);
|
||||
}
|
||||
|
||||
let mut quick_menu = false;
|
||||
unsafe {
|
||||
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 {
|
||||
unsafe {
|
||||
if MENU.quick_menu == OnOff::Off {
|
||||
WEB_MENU_ACTIVE = true;
|
||||
} else {
|
||||
QUICK_MENU_ACTIVE = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use skyline::nn::hid::NpadGcState;
|
||||
|
||||
pub struct ButtonPresses {
|
||||
pub a: 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 {
|
||||
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)]
|
||||
mod test;
|
||||
|
||||
use crate::common::consts::get_menu_from_url;
|
||||
use crate::common::*;
|
||||
use crate::events::{Event, EVENT_QUEUE};
|
||||
|
||||
use skyline::libc::{c_char, mkdir};
|
||||
use skyline::libc::mkdir;
|
||||
use skyline::nro::{self, NroInfo};
|
||||
use std::fs;
|
||||
|
||||
use crate::training::frame_counter;
|
||||
use crate::menu::{quick_menu_loop, web_session_loop};
|
||||
use owo_colors::OwoColorize;
|
||||
use training_mod_consts::OnOff;
|
||||
use training_mod_tui::Color;
|
||||
use training_mod_consts::{OnOff, MenuJsonStruct};
|
||||
|
||||
fn nro_main(nro: &NroInfo<'_>) {
|
||||
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 {
|
||||
($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));
|
||||
}
|
||||
}
|
||||
|
||||
#[skyline::main(name = "training_modpack")]
|
||||
pub fn main() {
|
||||
macro_rules! log {
|
||||
|
@ -100,19 +84,19 @@ pub fn main() {
|
|||
let menu_conf_path = "sd:/TrainingModpack/training_modpack_menu.conf";
|
||||
log!("Checking for previous menu in training_modpack_menu.conf...");
|
||||
if fs::metadata(menu_conf_path).is_ok() {
|
||||
let menu_conf = fs::read(menu_conf_path).unwrap();
|
||||
if menu_conf.starts_with(b"http://localhost") {
|
||||
log!("Previous menu found, loading from training_modpack_menu.conf");
|
||||
let menu_conf = fs::read_to_string(&menu_conf_path).unwrap();
|
||||
if let Ok(menu_conf_json) = serde_json::from_str::<MenuJsonStruct>(&menu_conf) {
|
||||
unsafe {
|
||||
MENU = get_menu_from_url(MENU, std::str::from_utf8(&menu_conf).unwrap(), false);
|
||||
DEFAULTS_MENU = get_menu_from_url(
|
||||
DEFAULTS_MENU,
|
||||
std::str::from_utf8(&menu_conf).unwrap(),
|
||||
true,
|
||||
);
|
||||
MENU = menu_conf_json.menu;
|
||||
DEFAULTS_MENU = menu_conf_json.defaults_menu;
|
||||
log!("Previous menu found. Loading...");
|
||||
}
|
||||
} 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 {
|
||||
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 {
|
||||
log!("No previous menu file found.");
|
||||
|
@ -141,118 +125,7 @@ pub fn main() {
|
|||
}
|
||||
});
|
||||
|
||||
std::thread::spawn(|| {
|
||||
std::thread::sleep(std::time::Duration::from_secs(10));
|
||||
let menu;
|
||||
unsafe {
|
||||
menu = consts::get_menu();
|
||||
}
|
||||
std::thread::spawn(|| unsafe { quick_menu_loop() });
|
||||
|
||||
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();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
std::thread::spawn(|| unsafe { web_session_loop() });
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ if (isNx) {
|
|||
window.nx.footer.setAssign('R', '', saveDefaults, { se: '' });
|
||||
window.nx.footer.setAssign('ZR', '', cycleNextTab, { se: '' });
|
||||
window.nx.footer.setAssign('ZL', '', cyclePrevTab, { se: '' });
|
||||
window.nx.addEventListener("message", function(msg) { setSettingsFromJSON(msg)});
|
||||
} else {
|
||||
document.addEventListener('keypress', (event) => {
|
||||
switch (event.key) {
|
||||
|
@ -42,7 +43,7 @@ if (isNx) {
|
|||
break;
|
||||
case 'l':
|
||||
console.log('l');
|
||||
resetAllSubmenus();
|
||||
resetAllMenus();
|
||||
break;
|
||||
case 'r':
|
||||
console.log('r');
|
||||
|
@ -63,15 +64,12 @@ if (isNx) {
|
|||
const onLoad = () => {
|
||||
// Activate the first tab
|
||||
openTab(document.querySelector('button.tab-button'));
|
||||
|
||||
// Extract URL params and set appropriate settings
|
||||
setSettingsFromURL();
|
||||
populateMenuFromSettings();
|
||||
};
|
||||
|
||||
window.onload = onLoad;
|
||||
|
||||
var settings;
|
||||
var defaultSettings;
|
||||
|
||||
var lastFocusedItem = document.querySelector('.menu-item > button');
|
||||
const currentTabContent = () => {
|
||||
|
@ -181,13 +179,17 @@ function playSound(label) {
|
|||
|
||||
const exit = () => {
|
||||
playSound('SeFooterDecideBack');
|
||||
|
||||
const url = buildURLFromSettings();
|
||||
const messageObject = {
|
||||
menu: settings,
|
||||
defaults_menu: defaultSettings
|
||||
}
|
||||
|
||||
if (isNx) {
|
||||
window.location.href = url;
|
||||
window.nx.sendMessage(
|
||||
JSON.stringify(messageObject)
|
||||
);
|
||||
} else {
|
||||
console.log(url);
|
||||
console.log(JSON.stringify(messageObject));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -205,18 +207,42 @@ function closeOrExit() {
|
|||
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() {
|
||||
var { search } = window.location;
|
||||
// Actual settings
|
||||
const settingsFromSearch = search
|
||||
.replace('?', '')
|
||||
.split('&')
|
||||
.reduce((accumulator, currentValue) => {
|
||||
var [key, value] = currentValue.split('=');
|
||||
accumulator[key] = parseInt(value);
|
||||
if (!key.startsWith('__')) {
|
||||
accumulator[key] = parseInt(value);
|
||||
}
|
||||
return accumulator;
|
||||
}, {});
|
||||
|
||||
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() {
|
||||
|
@ -288,7 +314,7 @@ function resetCurrentMenu() {
|
|||
const menu = document.querySelector('.modal:not(.hide)');
|
||||
|
||||
const menuId = menu.dataset.id;
|
||||
const defaultSectionMask = settings[DEFAULTS_PREFIX + menuId];
|
||||
const defaultSectionMask = defaultSettings[menuId];
|
||||
|
||||
settings[menuId] = defaultSectionMask;
|
||||
|
||||
|
@ -299,8 +325,8 @@ function resetAllMenus() {
|
|||
// Resets all submenus to the default values
|
||||
if (confirm('Are you sure that you want to reset all menu settings to the default?')) {
|
||||
document.querySelectorAll('.menu-item').forEach(function (item) {
|
||||
const defaultMenuId = DEFAULTS_PREFIX + item.id;
|
||||
const defaultMask = settings[defaultMenuId];
|
||||
const defaultMenuId = item.id;
|
||||
const defaultMask = defaultSettings[defaultMenuId];
|
||||
|
||||
settings[item.id] = defaultMask;
|
||||
|
||||
|
@ -316,9 +342,9 @@ function setHelpText(text) {
|
|||
function saveDefaults() {
|
||||
if (confirm('Are you sure that you want to change the default menu settings to the current selections?')) {
|
||||
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"
|
||||
paste = "1.0"
|
||||
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 }
|
||||
|
||||
[features]
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
|
||||
#[macro_use]
|
||||
extern crate bitflags_serde_shim;
|
||||
|
||||
#[macro_use]
|
||||
extern crate num_derive;
|
||||
|
||||
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")]
|
||||
use smash::lib::lua_const::*;
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::EnumIter;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use ramhorns::Content;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub trait ToggleTrait {
|
||||
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()
|
||||
}
|
||||
}
|
||||
impl ToUrlParam for $e {
|
||||
fn to_url_param(&self) -> String {
|
||||
self.bits().to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_random_int(_max: i32) -> i32 {
|
||||
#[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"))]
|
||||
0
|
||||
|
@ -101,7 +102,6 @@ pub fn random_option<T>(arg: &[T]) -> &T {
|
|||
|
||||
// DI / Left stick
|
||||
bitflags! {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Direction : u32 {
|
||||
const OUT = 0x1;
|
||||
const UP_OUT = 0x2;
|
||||
|
@ -163,10 +163,10 @@ impl Direction {
|
|||
}
|
||||
|
||||
extra_bitflag_impls! {Direction}
|
||||
impl_serde_for_bitflags!(Direction);
|
||||
|
||||
// Ledge Option
|
||||
bitflags! {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct LedgeOption : u32
|
||||
{
|
||||
const NEUTRAL = 0x1;
|
||||
|
@ -179,7 +179,8 @@ bitflags! {
|
|||
|
||||
impl LedgeOption {
|
||||
pub fn into_status(self) -> Option<i32> {
|
||||
#[cfg(feature = "smash")] {
|
||||
#[cfg(feature = "smash")]
|
||||
{
|
||||
Some(match self {
|
||||
LedgeOption::NEUTRAL => *FIGHTER_STATUS_KIND_CLIFF_CLIMB,
|
||||
LedgeOption::ROLL => *FIGHTER_STATUS_KIND_CLIFF_ESCAPE,
|
||||
|
@ -207,10 +208,10 @@ impl LedgeOption {
|
|||
}
|
||||
|
||||
extra_bitflag_impls! {LedgeOption}
|
||||
impl_serde_for_bitflags!(LedgeOption);
|
||||
|
||||
// Tech options
|
||||
bitflags! {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct TechFlags : u32 {
|
||||
const NO_TECH = 0x1;
|
||||
const ROLL_F = 0x2;
|
||||
|
@ -232,10 +233,10 @@ impl TechFlags {
|
|||
}
|
||||
|
||||
extra_bitflag_impls! {TechFlags}
|
||||
impl_serde_for_bitflags!(TechFlags);
|
||||
|
||||
// Missed Tech Options
|
||||
bitflags! {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct MissTechFlags : u32 {
|
||||
const GETUP = 0x1;
|
||||
const ATTACK = 0x2;
|
||||
|
@ -257,10 +258,13 @@ impl MissTechFlags {
|
|||
}
|
||||
|
||||
extra_bitflag_impls! {MissTechFlags}
|
||||
impl_serde_for_bitflags!(MissTechFlags);
|
||||
|
||||
/// Shield States
|
||||
#[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 {
|
||||
None = 0x0,
|
||||
Infinite = 0x1,
|
||||
|
@ -277,10 +281,6 @@ impl Shield {
|
|||
Shield::Constant => "Constant",
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_url_param(&self) -> String {
|
||||
(*self as i32).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToggleTrait for Shield {
|
||||
|
@ -295,7 +295,9 @@ impl ToggleTrait for Shield {
|
|||
|
||||
// Save State Mirroring
|
||||
#[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 {
|
||||
None = 0x0,
|
||||
Alternate = 0x1,
|
||||
|
@ -310,15 +312,13 @@ impl SaveStateMirroring {
|
|||
SaveStateMirroring::Random => "Random",
|
||||
})
|
||||
}
|
||||
|
||||
fn to_url_param(&self) -> String {
|
||||
(*self as i32).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToggleTrait for SaveStateMirroring {
|
||||
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> {
|
||||
|
@ -328,7 +328,6 @@ impl ToggleTrait for SaveStateMirroring {
|
|||
|
||||
// Defensive States
|
||||
bitflags! {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Defensive : u32 {
|
||||
const SPOT_DODGE = 0x1;
|
||||
const ROLL_F = 0x2;
|
||||
|
@ -352,9 +351,10 @@ impl Defensive {
|
|||
}
|
||||
|
||||
extra_bitflag_impls! {Defensive}
|
||||
impl_serde_for_bitflags!(Defensive);
|
||||
|
||||
#[repr(i32)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize_repr, Deserialize_repr)]
|
||||
pub enum OnOff {
|
||||
Off = 0,
|
||||
On = 1,
|
||||
|
@ -375,10 +375,6 @@ impl OnOff {
|
|||
OnOff::On => "On",
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_url_param(&self) -> String {
|
||||
(*self as i32).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToggleTrait for OnOff {
|
||||
|
@ -391,7 +387,6 @@ impl ToggleTrait for OnOff {
|
|||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Action : u32 {
|
||||
const AIR_DODGE = 0x1;
|
||||
const JUMP = 0x2;
|
||||
|
@ -424,7 +419,8 @@ bitflags! {
|
|||
|
||||
impl Action {
|
||||
pub fn into_attack_air_kind(self) -> Option<i32> {
|
||||
#[cfg(feature = "smash")] {
|
||||
#[cfg(feature = "smash")]
|
||||
{
|
||||
Some(match self {
|
||||
Action::NAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_N,
|
||||
Action::FAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_F,
|
||||
|
@ -472,9 +468,9 @@ impl Action {
|
|||
}
|
||||
|
||||
extra_bitflag_impls! {Action}
|
||||
impl_serde_for_bitflags!(Action);
|
||||
|
||||
bitflags! {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct AttackAngle : u32 {
|
||||
const NEUTRAL = 0x1;
|
||||
const UP = 0x2;
|
||||
|
@ -494,9 +490,9 @@ impl AttackAngle {
|
|||
}
|
||||
|
||||
extra_bitflag_impls! {AttackAngle}
|
||||
impl_serde_for_bitflags!(AttackAngle);
|
||||
|
||||
bitflags! {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Delay : u32 {
|
||||
const D0 = 0x1;
|
||||
const D1 = 0x2;
|
||||
|
@ -534,7 +530,6 @@ bitflags! {
|
|||
|
||||
// Throw Option
|
||||
bitflags! {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ThrowOption : u32
|
||||
{
|
||||
const NONE = 0x1;
|
||||
|
@ -547,7 +542,8 @@ bitflags! {
|
|||
|
||||
impl ThrowOption {
|
||||
pub fn into_cmd(self) -> Option<i32> {
|
||||
#[cfg(feature = "smash")] {
|
||||
#[cfg(feature = "smash")]
|
||||
{
|
||||
Some(match self {
|
||||
ThrowOption::NONE => 0,
|
||||
ThrowOption::FORWARD => *FIGHTER_PAD_CMD_CAT2_FLAG_THROW_F,
|
||||
|
@ -575,10 +571,10 @@ impl ThrowOption {
|
|||
}
|
||||
|
||||
extra_bitflag_impls! {ThrowOption}
|
||||
impl_serde_for_bitflags!(ThrowOption);
|
||||
|
||||
// Buff Option
|
||||
bitflags! {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct BuffOption : u32
|
||||
{
|
||||
const ACCELERATLE = 0x1;
|
||||
|
@ -595,7 +591,8 @@ bitflags! {
|
|||
|
||||
impl BuffOption {
|
||||
pub fn into_int(self) -> Option<i32> {
|
||||
#[cfg(feature = "smash")] {
|
||||
#[cfg(feature = "smash")]
|
||||
{
|
||||
Some(match self {
|
||||
BuffOption::ACCELERATLE => *FIGHTER_BRAVE_SPECIAL_LW_COMMAND11_SPEED_UP,
|
||||
BuffOption::OOMPH => *FIGHTER_BRAVE_SPECIAL_LW_COMMAND12_ATTACK_UP,
|
||||
|
@ -631,6 +628,7 @@ impl BuffOption {
|
|||
}
|
||||
|
||||
extra_bitflag_impls! {BuffOption}
|
||||
impl_serde_for_bitflags!(BuffOption);
|
||||
|
||||
impl Delay {
|
||||
pub fn as_str(self) -> Option<&'static str> {
|
||||
|
@ -676,9 +674,9 @@ impl Delay {
|
|||
}
|
||||
|
||||
extra_bitflag_impls! {Delay}
|
||||
impl_serde_for_bitflags!(Delay);
|
||||
|
||||
bitflags! {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct MedDelay : u32 {
|
||||
const D0 = 0x1;
|
||||
const D5 = 0x2;
|
||||
|
@ -758,9 +756,9 @@ impl MedDelay {
|
|||
}
|
||||
|
||||
extra_bitflag_impls! {MedDelay}
|
||||
impl_serde_for_bitflags!(MedDelay);
|
||||
|
||||
bitflags! {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct LongDelay : u32 {
|
||||
const D0 = 0x1;
|
||||
const D10 = 0x2;
|
||||
|
@ -840,9 +838,9 @@ impl LongDelay {
|
|||
}
|
||||
|
||||
extra_bitflag_impls! {LongDelay}
|
||||
impl_serde_for_bitflags!(LongDelay);
|
||||
|
||||
bitflags! {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct BoolFlag : u32 {
|
||||
const TRUE = 0x1;
|
||||
const FALSE = 0x2;
|
||||
|
@ -850,6 +848,7 @@ bitflags! {
|
|||
}
|
||||
|
||||
extra_bitflag_impls! {BoolFlag}
|
||||
impl_serde_for_bitflags!(BoolFlag);
|
||||
|
||||
impl BoolFlag {
|
||||
pub fn into_bool(self) -> bool {
|
||||
|
@ -865,7 +864,9 @@ impl BoolFlag {
|
|||
}
|
||||
|
||||
#[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 {
|
||||
None = 0,
|
||||
Normal = 1,
|
||||
|
@ -891,15 +892,13 @@ impl InputFrequency {
|
|||
InputFrequency::High => "High",
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_url_param(&self) -> String {
|
||||
(*self as u32).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToggleTrait for InputFrequency {
|
||||
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> {
|
||||
|
@ -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
|
||||
#[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 {
|
||||
None = 0,
|
||||
PlayerVariation1 = 0x1,
|
||||
|
@ -969,15 +957,13 @@ impl CharacterItem {
|
|||
_ => "None",
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_url_param(&self) -> String {
|
||||
(*self as i32).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToggleTrait for CharacterItem {
|
||||
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> {
|
||||
|
@ -985,85 +971,49 @@ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug, )]
|
||||
pub struct TrainingModpackMenu {
|
||||
pub hitbox_vis: OnOff,
|
||||
pub stage_hazards: OnOff,
|
||||
pub di_state: Direction,
|
||||
pub sdi_state: Direction,
|
||||
pub sdi_strength: InputFrequency,
|
||||
pub clatter_strength: InputFrequency,
|
||||
pub air_dodge_dir: Direction,
|
||||
pub mash_state: Action,
|
||||
pub follow_up: Action,
|
||||
pub attack_angle: AttackAngle,
|
||||
pub ledge_state: LedgeOption,
|
||||
pub ledge_delay: LongDelay,
|
||||
pub tech_state: TechFlags,
|
||||
pub miss_tech_state: MissTechFlags,
|
||||
pub shield_state: Shield,
|
||||
pub defensive_state: Defensive,
|
||||
pub oos_offset: Delay,
|
||||
pub reaction_time: Delay,
|
||||
pub shield_tilt: Direction,
|
||||
pub mash_in_neutral: OnOff,
|
||||
pub fast_fall: BoolFlag,
|
||||
pub fast_fall_delay: Delay,
|
||||
pub falling_aerials: BoolFlag,
|
||||
pub aerial_delay: Delay,
|
||||
pub full_hop: BoolFlag,
|
||||
pub crouch: OnOff,
|
||||
pub input_delay: i32,
|
||||
pub save_damage: OnOff,
|
||||
pub save_state_mirroring: SaveStateMirroring,
|
||||
pub frame_advantage: OnOff,
|
||||
pub save_state_enable: OnOff,
|
||||
pub save_state_autoload: OnOff,
|
||||
pub throw_state: ThrowOption,
|
||||
pub throw_delay: MedDelay,
|
||||
pub pummel_delay: MedDelay,
|
||||
pub buff_state: BuffOption,
|
||||
pub character_item: CharacterItem,
|
||||
pub quick_menu: OnOff,
|
||||
}
|
||||
|
||||
url_params! {
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug, )]
|
||||
pub struct TrainingModpackMenu {
|
||||
pub hitbox_vis: OnOff,
|
||||
pub stage_hazards: OnOff,
|
||||
pub di_state: Direction,
|
||||
pub sdi_state: Direction,
|
||||
pub sdi_strength: InputFrequency,
|
||||
pub clatter_strength: InputFrequency,
|
||||
pub air_dodge_dir: Direction,
|
||||
pub mash_state: Action,
|
||||
pub follow_up: Action,
|
||||
pub attack_angle: AttackAngle,
|
||||
pub ledge_state: LedgeOption,
|
||||
pub ledge_delay: LongDelay,
|
||||
pub tech_state: TechFlags,
|
||||
pub miss_tech_state: MissTechFlags,
|
||||
pub shield_state: Shield,
|
||||
pub defensive_state: Defensive,
|
||||
pub oos_offset: Delay,
|
||||
pub reaction_time: Delay,
|
||||
pub shield_tilt: Direction,
|
||||
pub mash_in_neutral: OnOff,
|
||||
pub fast_fall: BoolFlag,
|
||||
pub fast_fall_delay: Delay,
|
||||
pub falling_aerials: BoolFlag,
|
||||
pub aerial_delay: Delay,
|
||||
pub full_hop: BoolFlag,
|
||||
pub crouch: OnOff,
|
||||
pub input_delay: i32,
|
||||
pub save_damage: OnOff,
|
||||
pub save_state_mirroring: SaveStateMirroring,
|
||||
pub frame_advantage: OnOff,
|
||||
pub save_state_enable: OnOff,
|
||||
pub save_state_autoload: OnOff,
|
||||
pub throw_state: ThrowOption,
|
||||
pub throw_delay: MedDelay,
|
||||
pub pummel_delay: MedDelay,
|
||||
pub buff_state: BuffOption,
|
||||
pub character_item: CharacterItem,
|
||||
pub quick_menu: OnOff,
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! set_by_str {
|
||||
($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 {
|
||||
if x == 0 { 0 }
|
||||
else {
|
||||
if x == 0 {
|
||||
0
|
||||
} else {
|
||||
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
|
||||
#[repr(i32)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
@ -1146,11 +1107,11 @@ pub enum SubMenuType {
|
|||
}
|
||||
|
||||
impl SubMenuType {
|
||||
pub fn from_str(s : &str) -> SubMenuType {
|
||||
pub fn from_str(s: &str) -> SubMenuType {
|
||||
match s {
|
||||
"toggle" => SubMenuType::TOGGLE,
|
||||
"slider" => SubMenuType::SLIDER,
|
||||
_ => panic!("Unexpected SubMenuType!")
|
||||
_ => panic!("Unexpected SubMenuType!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1224,43 +1185,34 @@ pub struct SubMenu<'a> {
|
|||
}
|
||||
|
||||
impl<'a> SubMenu<'a> {
|
||||
pub fn add_toggle(
|
||||
&mut self,
|
||||
toggle_value: usize,
|
||||
toggle_title: &'a str
|
||||
) {
|
||||
self.toggles.push(
|
||||
Toggle {
|
||||
toggle_value: toggle_value,
|
||||
toggle_title: toggle_title,
|
||||
checked: false
|
||||
}
|
||||
);
|
||||
pub fn add_toggle(&mut self, toggle_value: usize, toggle_title: &'a str) {
|
||||
self.toggles.push(Toggle {
|
||||
toggle_value: toggle_value,
|
||||
toggle_title: toggle_title,
|
||||
checked: false,
|
||||
});
|
||||
}
|
||||
pub fn new_with_toggles<T:ToggleTrait>(
|
||||
pub fn new_with_toggles<T: ToggleTrait>(
|
||||
submenu_title: &'a str,
|
||||
submenu_id: &'a str,
|
||||
help_text: &'a str,
|
||||
is_single_option: bool,
|
||||
) -> SubMenu<'a> {
|
||||
let mut instance = SubMenu {
|
||||
submenu_title: submenu_title,
|
||||
submenu_id: submenu_id,
|
||||
help_text: help_text,
|
||||
is_single_option: is_single_option,
|
||||
toggles: Vec::new(),
|
||||
_type: "toggle"
|
||||
};
|
||||
|
||||
let values = T::to_toggle_vals();
|
||||
let titles = T::to_toggle_strs();
|
||||
for i in 0..values.len() {
|
||||
instance.add_toggle(
|
||||
values[i],
|
||||
titles[i],
|
||||
);
|
||||
}
|
||||
instance
|
||||
let mut instance = SubMenu {
|
||||
submenu_title: submenu_title,
|
||||
submenu_id: submenu_id,
|
||||
help_text: help_text,
|
||||
is_single_option: is_single_option,
|
||||
toggles: Vec::new(),
|
||||
_type: "toggle",
|
||||
};
|
||||
|
||||
let values = T::to_toggle_vals();
|
||||
let titles = T::to_toggle_strs();
|
||||
for i in 0..values.len() {
|
||||
instance.add_toggle(values[i], titles[i]);
|
||||
}
|
||||
instance
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1279,14 +1231,12 @@ impl<'a> Tab<'a> {
|
|||
help_text: &'a str,
|
||||
is_single_option: bool,
|
||||
) {
|
||||
self.tab_submenus.push(
|
||||
SubMenu::new_with_toggles::<T>(
|
||||
submenu_title,
|
||||
submenu_id,
|
||||
help_text,
|
||||
is_single_option,
|
||||
)
|
||||
);
|
||||
self.tab_submenus.push(SubMenu::new_with_toggles::<T>(
|
||||
submenu_title,
|
||||
submenu_id,
|
||||
help_text,
|
||||
is_single_option,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1295,39 +1245,8 @@ pub struct UiMenu<'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> {
|
||||
let mut overall_menu = UiMenu {
|
||||
tabs: Vec::new(),
|
||||
};
|
||||
let mut overall_menu = UiMenu { tabs: Vec::new() };
|
||||
|
||||
let mut mash_tab = Tab {
|
||||
tab_id: "mash",
|
||||
|
@ -1420,7 +1339,6 @@ pub unsafe fn get_menu() -> UiMenu<'static> {
|
|||
);
|
||||
overall_menu.tabs.push(mash_tab);
|
||||
|
||||
|
||||
let mut defensive_tab = Tab {
|
||||
tab_id: "defensive",
|
||||
tab_title: "Defensive Settings",
|
||||
|
@ -1508,13 +1426,13 @@ pub unsafe fn get_menu() -> UiMenu<'static> {
|
|||
"Character Item",
|
||||
"character_item",
|
||||
"Character Item: CPU/Player item to hold when loading a save state",
|
||||
true
|
||||
true,
|
||||
);
|
||||
defensive_tab.add_submenu_with_toggles::<OnOff>(
|
||||
"Crouch",
|
||||
"crouch",
|
||||
"Crouch: Should the CPU crouch when on the ground",
|
||||
true
|
||||
true,
|
||||
);
|
||||
overall_menu.tabs.push(defensive_tab);
|
||||
|
||||
|
@ -1569,41 +1487,42 @@ pub unsafe fn get_menu() -> UiMenu<'static> {
|
|||
"Stage Hazards",
|
||||
"stage_hazards",
|
||||
"Stage Hazards: Should stage hazards be present",
|
||||
true
|
||||
true,
|
||||
);
|
||||
misc_tab.add_submenu_with_toggles::<OnOff>(
|
||||
"Quick Menu",
|
||||
"quick_menu",
|
||||
"Quick Menu: Should use quick or web menu",
|
||||
true
|
||||
true,
|
||||
);
|
||||
overall_menu.tabs.push(misc_tab);
|
||||
|
||||
let non_ui_menu = MENU;
|
||||
let url_params = non_ui_menu.to_url_params(false);
|
||||
let toggle_values_all = url_params.split("&");
|
||||
let mut sub_menu_id_to_vals : HashMap<&str, u32> = HashMap::new();
|
||||
let non_ui_menu = serde_json::to_string(&MENU).unwrap().replace("\"", "");
|
||||
let toggle_values_all = non_ui_menu.split(',').collect::<Vec<&str>>();
|
||||
let mut sub_menu_id_to_vals: HashMap<&str, u32> = HashMap::new();
|
||||
for toggle_values in toggle_values_all {
|
||||
let toggle_value_split = toggle_values.split('=').collect::<Vec<&str>>();
|
||||
let mut sub_menu_id = toggle_value_split[0];
|
||||
if sub_menu_id.is_empty() { continue }
|
||||
sub_menu_id = sub_menu_id.strip_prefix("__").unwrap_or(sub_menu_id);
|
||||
let toggle_value_split = toggle_values.split(':').collect::<Vec<&str>>();
|
||||
let sub_menu_id = toggle_value_split[0];
|
||||
if sub_menu_id.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
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| {
|
||||
tab.tab_submenus.iter_mut().for_each(|sub_menu| {
|
||||
let sub_menu_id = sub_menu.submenu_id;
|
||||
sub_menu.toggles.iter_mut().for_each(|toggle| {
|
||||
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) {
|
||||
toggle.checked = true
|
||||
}
|
||||
})
|
||||
|
||||
overall_menu.tabs.iter_mut().for_each(|tab| {
|
||||
tab.tab_submenus.iter_mut().for_each(|sub_menu| {
|
||||
let sub_menu_id = sub_menu.submenu_id;
|
||||
sub_menu.toggles.iter_mut().for_each(|toggle| {
|
||||
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)
|
||||
{
|
||||
toggle.checked = true
|
||||
}
|
||||
})
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
overall_menu
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
||||
|
||||
let mut url = "http://localhost/".to_owned();
|
||||
let mut settings = HashMap::new();
|
||||
|
||||
// Collect settings for toggles
|
||||
|
@ -358,11 +356,7 @@ pub fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) -> String {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
url.push('?');
|
||||
settings.iter()
|
||||
.for_each(|(section, val)| url.push_str(format!("{}={}&", section, val).as_str()));
|
||||
url
|
||||
serde_json::to_string(&settings).unwrap()
|
||||
|
||||
// TODO: Add saveDefaults
|
||||
// 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 url = String::new();
|
||||
let _frame_res = terminal.draw(|f| url = training_mod_tui::ui(f, &mut app))?;
|
||||
|
||||
let mut json_response = String::new();
|
||||
let _frame_res = terminal.draw(|f| json_response = training_mod_tui::ui(f, &mut app))?;
|
||||
set_menu_from_json(json_response);
|
||||
unsafe {
|
||||
// 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());
|
||||
}
|
||||
|
||||
|
@ -58,8 +58,8 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
|
||||
#[cfg(not(feature = "has_terminal"))] {
|
||||
let (mut terminal, mut app) = test_backend_setup(menu)?;
|
||||
let mut url = String::new();
|
||||
let frame_res = terminal.draw(|f| url = training_mod_tui::ui(f, &mut app))?;
|
||||
let mut json_response = String::new();
|
||||
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() {
|
||||
print!("{}", cell.symbol);
|
||||
|
@ -69,7 +69,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
}
|
||||
println!();
|
||||
|
||||
println!("URL: {}", url);
|
||||
println!("json_response:\n{}", json_response);
|
||||
}
|
||||
|
||||
#[cfg(feature = "has_terminal")] {
|
||||
|
@ -111,9 +111,9 @@ fn run_app<B: tui::backend::Backend>(
|
|||
tick_rate: Duration,
|
||||
) -> io::Result<String> {
|
||||
let mut last_tick = Instant::now();
|
||||
let mut url = String::new();
|
||||
let mut json_response = String::new();
|
||||
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
|
||||
.checked_sub(last_tick.elapsed())
|
||||
|
@ -122,7 +122,7 @@ fn run_app<B: tui::backend::Backend>(
|
|||
if crossterm::event::poll(timeout)? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
match key.code {
|
||||
KeyCode::Char('q') => return Ok(url),
|
||||
KeyCode::Char('q') => return Ok(json_response),
|
||||
KeyCode::Char('r') => app.on_r(),
|
||||
KeyCode::Char('l') => app.on_l(),
|
||||
KeyCode::Left => app.on_left(),
|
||||
|
|
Loading…
Reference in a new issue