1
0
Fork 0
mirror of https://github.com/jugeeya/UltimateTrainingModpack.git synced 2025-01-19 17:00:15 +00:00

Fix issue with not being able to reopen menu after fighter reset/reentry into training

This commit is contained in:
jugeeya 2022-10-02 09:38:40 -07:00
parent 17f2b05940
commit 88abeadfdd
2 changed files with 620 additions and 613 deletions

View file

@ -1,464 +1,465 @@
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 owo_colors::OwoColorize;
use ramhorns::Template; use ramhorns::Template;
use skyline::info::get_program_id; use skyline::info::get_program_id;
use skyline::nn::hid::NpadGcState; use skyline::nn::hid::NpadGcState;
use skyline::nn::web::WebSessionBootMode; use skyline::nn::web::WebSessionBootMode;
use skyline_web::{Background, WebSession, Webpage}; 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::{MenuJsonStruct, TrainingModpackMenu}; use training_mod_consts::{MenuJsonStruct, TrainingModpackMenu};
use training_mod_tui::Color; 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;
const MENU_LOCKOUT_FRAMES: u32 = 15; const MENU_LOCKOUT_FRAMES: u32 = 15;
pub static mut QUICK_MENU_ACTIVE: bool = false; pub static mut QUICK_MENU_ACTIVE: bool = false;
pub fn init() { pub fn init() {
unsafe { unsafe {
FRAME_COUNTER_INDEX = frame_counter::register_counter(); FRAME_COUNTER_INDEX = frame_counter::register_counter();
QUICK_MENU_FRAME_COUNTER_INDEX = frame_counter::register_counter(); QUICK_MENU_FRAME_COUNTER_INDEX = frame_counter::register_counter();
write_menu(); write_menu();
} }
} }
pub unsafe fn menu_condition(module_accessor: &mut smash::app::BattleObjectModuleAccessor) -> bool { pub unsafe fn menu_condition(module_accessor: &mut smash::app::BattleObjectModuleAccessor) -> bool {
// also ensure quick menu is reset // also ensure quick menu is reset
if frame_counter::get_frame_count(QUICK_MENU_FRAME_COUNTER_INDEX) > 60 { if frame_counter::get_frame_count(QUICK_MENU_FRAME_COUNTER_INDEX) > 60 {
frame_counter::full_reset(QUICK_MENU_FRAME_COUNTER_INDEX); frame_counter::full_reset(QUICK_MENU_FRAME_COUNTER_INDEX);
} }
// Only check for button combination if the counter is 0 (not locked out) // Only check for button combination if the counter is 0 (not locked out)
match frame_counter::get_frame_count(FRAME_COUNTER_INDEX) { match frame_counter::get_frame_count(FRAME_COUNTER_INDEX) {
0 => { 0 => {
ControlModule::check_button_on(module_accessor, *CONTROL_PAD_BUTTON_SPECIAL) ControlModule::check_button_on(module_accessor, *CONTROL_PAD_BUTTON_SPECIAL)
&& ControlModule::check_button_on_trriger( && ControlModule::check_button_on_trriger(
module_accessor, module_accessor,
*CONTROL_PAD_BUTTON_APPEAL_HI, *CONTROL_PAD_BUTTON_APPEAL_HI,
) )
} }
1..MENU_LOCKOUT_FRAMES => false, 1..MENU_LOCKOUT_FRAMES => false,
_ => { _ => {
// Waited longer than the lockout time, reset the counter so the menu can be opened again // Waited longer than the lockout time, reset the counter so the menu can be opened again
frame_counter::full_reset(FRAME_COUNTER_INDEX); frame_counter::full_reset(FRAME_COUNTER_INDEX);
false false
} }
} }
} }
pub unsafe fn write_menu() { pub unsafe fn write_menu() {
let tpl = Template::new(include_str!("../templates/menu.html")).unwrap(); let tpl = Template::new(include_str!("../templates/menu.html")).unwrap();
let overall_menu = get_menu(); let overall_menu = get_menu();
let data = tpl.render(&overall_menu); let data = tpl.render(&overall_menu);
// Now that we have the html, write it to file // Now that we have the html, write it to file
// From skyline-web // From skyline-web
let program_id = get_program_id(); let program_id = get_program_id();
let htdocs_dir = "training_modpack"; let htdocs_dir = "training_modpack";
let menu_html_path = Path::new("sd:/atmosphere/contents") let menu_html_path = Path::new("sd:/atmosphere/contents")
.join(&format!("{:016X}", program_id)) .join(&format!("{:016X}", program_id))
.join(&format!("manual_html/html-document/{}.htdocs/", htdocs_dir)) .join(&format!("manual_html/html-document/{}.htdocs/", htdocs_dir))
.join("training_menu.html"); .join("training_menu.html");
let write_resp = fs::write(menu_html_path, data); let write_resp = fs::write(menu_html_path, data);
if write_resp.is_err() { if write_resp.is_err() {
println!("Error!: {}", write_resp.err().unwrap()); println!("Error!: {}", write_resp.err().unwrap());
} }
} }
const MENU_CONF_PATH: &str = "sd:/TrainingModpack/training_modpack_menu.conf"; const MENU_CONF_PATH: &str = "sd:/TrainingModpack/training_modpack_menu.conf";
pub unsafe fn set_menu_from_json(message: &str) { pub unsafe fn set_menu_from_json(message: &str) {
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(
0x69, 0x69,
"Cannot use web menu on emulator.\n\0", "Cannot use web menu on emulator.\n\0",
"Only the quick menu is runnable via emulator currently.\n\0", "Only the quick menu is runnable via emulator currently.\n\0",
); );
MENU.quick_menu = OnOff::On; MENU.quick_menu = OnOff::On;
} }
} }
if let Ok(message_json) = serde_json::from_str::<MenuJsonStruct>(message) { if let Ok(message_json) = serde_json::from_str::<MenuJsonStruct>(message) {
// Includes both MENU and DEFAULTS_MENU // Includes both MENU and DEFAULTS_MENU
// From Web Applet // From Web Applet
MENU = message_json.menu; MENU = message_json.menu;
DEFAULTS_MENU = message_json.defaults_menu; DEFAULTS_MENU = message_json.defaults_menu;
std::fs::write( std::fs::write(
MENU_CONF_PATH, MENU_CONF_PATH,
serde_json::to_string_pretty(&message_json).unwrap(), serde_json::to_string_pretty(&message_json).unwrap(),
) )
.expect("Failed to write menu conf file"); .expect("Failed to write menu conf file");
} else if let Ok(message_json) = serde_json::from_str::<TrainingModpackMenu>(message) { } else if let Ok(message_json) = serde_json::from_str::<TrainingModpackMenu>(message) {
// Only includes MENU // Only includes MENU
// From TUI // From TUI
MENU = message_json; MENU = message_json;
let conf = MenuJsonStruct { let conf = MenuJsonStruct {
menu: MENU, menu: MENU,
defaults_menu: DEFAULTS_MENU, defaults_menu: DEFAULTS_MENU,
}; };
std::fs::write(MENU_CONF_PATH, serde_json::to_string_pretty(&conf).unwrap()) std::fs::write(MENU_CONF_PATH, serde_json::to_string_pretty(&conf).unwrap())
.expect("Failed to write menu conf file"); .expect("Failed to write menu conf file");
} else { } else {
skyline::error::show_error( skyline::error::show_error(
0x70, 0x70,
"Could not parse the menu response!\nPlease send a screenshot of the details page to the developers.\n\0", "Could not parse the menu response!\nPlease send a screenshot of the details page to the developers.\n\0",
message message
); );
}; };
EVENT_QUEUE.push(Event::menu_open(message.to_string())); EVENT_QUEUE.push(Event::menu_open(message.to_string()));
} }
pub fn spawn_menu() { pub fn spawn_menu() {
unsafe { unsafe {
frame_counter::reset_frame_count(FRAME_COUNTER_INDEX); frame_counter::reset_frame_count(FRAME_COUNTER_INDEX);
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);
if MENU.quick_menu == OnOff::Off { if MENU.quick_menu == OnOff::Off {
WEB_MENU_ACTIVE = true; WEB_MENU_ACTIVE = true;
} else { } else {
QUICK_MENU_ACTIVE = true; QUICK_MENU_ACTIVE = true;
} }
} }
} }
pub struct ButtonPresses { pub struct ButtonPresses {
pub a: ButtonPress, pub a: ButtonPress,
pub b: ButtonPress, pub b: ButtonPress,
pub zr: ButtonPress, pub zr: ButtonPress,
pub zl: ButtonPress, pub zl: ButtonPress,
pub left: ButtonPress, pub left: ButtonPress,
pub right: ButtonPress, pub right: ButtonPress,
pub up: ButtonPress, pub up: ButtonPress,
pub down: ButtonPress, pub down: ButtonPress,
} }
pub struct ButtonPress { pub struct ButtonPress {
pub prev_frame_is_pressed: bool, pub prev_frame_is_pressed: bool,
pub is_pressed: bool, pub is_pressed: bool,
pub lockout_frames: usize, pub lockout_frames: usize,
} }
impl ButtonPress { impl ButtonPress {
pub fn default() -> ButtonPress { pub fn default() -> ButtonPress {
ButtonPress { ButtonPress {
prev_frame_is_pressed: false, prev_frame_is_pressed: false,
is_pressed: false, is_pressed: false,
lockout_frames: 0, lockout_frames: 0,
} }
} }
pub fn read_press(&mut self) -> bool { pub fn read_press(&mut self) -> bool {
let is_pressed = self.is_pressed; let is_pressed = self.is_pressed;
if self.is_pressed { if self.is_pressed {
self.is_pressed = false; self.is_pressed = false;
if self.lockout_frames == 0 { if self.lockout_frames == 0 {
self.prev_frame_is_pressed = true; self.prev_frame_is_pressed = true;
self.lockout_frames = 10; self.lockout_frames = 10;
return true; return true;
} }
} }
if self.lockout_frames > 0 { if self.lockout_frames > 0 {
self.lockout_frames -= 1; self.lockout_frames -= 1;
} }
self.prev_frame_is_pressed = is_pressed; self.prev_frame_is_pressed = is_pressed;
false false
} }
} }
pub static mut BUTTON_PRESSES: ButtonPresses = ButtonPresses { pub static mut BUTTON_PRESSES: ButtonPresses = ButtonPresses {
a: ButtonPress { a: ButtonPress {
prev_frame_is_pressed: false, prev_frame_is_pressed: false,
is_pressed: false, is_pressed: false,
lockout_frames: 0, lockout_frames: 0,
}, },
b: ButtonPress { b: ButtonPress {
prev_frame_is_pressed: false, prev_frame_is_pressed: false,
is_pressed: false, is_pressed: false,
lockout_frames: 0, lockout_frames: 0,
}, },
zr: ButtonPress { zr: ButtonPress {
prev_frame_is_pressed: false, prev_frame_is_pressed: false,
is_pressed: false, is_pressed: false,
lockout_frames: 0, lockout_frames: 0,
}, },
zl: ButtonPress { zl: ButtonPress {
prev_frame_is_pressed: false, prev_frame_is_pressed: false,
is_pressed: false, is_pressed: false,
lockout_frames: 0, lockout_frames: 0,
}, },
left: ButtonPress { left: ButtonPress {
prev_frame_is_pressed: false, prev_frame_is_pressed: false,
is_pressed: false, is_pressed: false,
lockout_frames: 0, lockout_frames: 0,
}, },
right: ButtonPress { right: ButtonPress {
prev_frame_is_pressed: false, prev_frame_is_pressed: false,
is_pressed: false, is_pressed: false,
lockout_frames: 0, lockout_frames: 0,
}, },
up: ButtonPress { up: ButtonPress {
prev_frame_is_pressed: false, prev_frame_is_pressed: false,
is_pressed: false, is_pressed: false,
lockout_frames: 0, lockout_frames: 0,
}, },
down: ButtonPress { down: ButtonPress {
prev_frame_is_pressed: false, prev_frame_is_pressed: false,
is_pressed: false, is_pressed: false,
lockout_frames: 0, lockout_frames: 0,
}, },
}; };
pub fn handle_get_npad_state(state: *mut NpadGcState, _controller_id: *const u32) { pub fn handle_get_npad_state(state: *mut NpadGcState, _controller_id: *const u32) {
unsafe { unsafe {
if menu::QUICK_MENU_ACTIVE { if menu::QUICK_MENU_ACTIVE {
// TODO: This should make more sense, look into. // TODO: This should make more sense, look into.
// BUTTON_PRESSES.a.is_pressed = (*state).Buttons & (1 << 0) > 0; // BUTTON_PRESSES.a.is_pressed = (*state).Buttons & (1 << 0) > 0;
// BUTTON_PRESSES.b.is_pressed = (*state).Buttons & (1 << 1) > 0; // BUTTON_PRESSES.b.is_pressed = (*state).Buttons & (1 << 1) > 0;
// BUTTON_PRESSES.zl.is_pressed = (*state).Buttons & (1 << 8) > 0; // BUTTON_PRESSES.zl.is_pressed = (*state).Buttons & (1 << 8) > 0;
// BUTTON_PRESSES.zr.is_pressed = (*state).Buttons & (1 << 9) > 0; // BUTTON_PRESSES.zr.is_pressed = (*state).Buttons & (1 << 9) > 0;
// BUTTON_PRESSES.left.is_pressed = (*state).Buttons & ((1 << 12) | (1 << 16)) > 0; // BUTTON_PRESSES.left.is_pressed = (*state).Buttons & ((1 << 12) | (1 << 16)) > 0;
// BUTTON_PRESSES.right.is_pressed = (*state).Buttons & ((1 << 14) | (1 << 18)) > 0; // BUTTON_PRESSES.right.is_pressed = (*state).Buttons & ((1 << 14) | (1 << 18)) > 0;
// BUTTON_PRESSES.down.is_pressed = (*state).Buttons & ((1 << 15) | (1 << 19)) > 0; // BUTTON_PRESSES.down.is_pressed = (*state).Buttons & ((1 << 15) | (1 << 19)) > 0;
// BUTTON_PRESSES.up.is_pressed = (*state).Buttons & ((1 << 13) | (1 << 17)) > 0; // BUTTON_PRESSES.up.is_pressed = (*state).Buttons & ((1 << 13) | (1 << 17)) > 0;
if frame_counter::get_frame_count(FRAME_COUNTER_INDEX) != 0 { if frame_counter::get_frame_count(FRAME_COUNTER_INDEX) != 0 {
return; return;
} }
if (*state).Buttons & (1 << 0) > 0 { if (*state).Buttons & (1 << 0) > 0 {
BUTTON_PRESSES.a.is_pressed = true; BUTTON_PRESSES.a.is_pressed = true;
} }
if (*state).Buttons & (1 << 1) > 0 { if (*state).Buttons & (1 << 1) > 0 {
BUTTON_PRESSES.b.is_pressed = true; BUTTON_PRESSES.b.is_pressed = true;
} }
if (*state).Buttons & (1 << 8) > 0 { if (*state).Buttons & (1 << 8) > 0 {
BUTTON_PRESSES.zl.is_pressed = true; BUTTON_PRESSES.zl.is_pressed = true;
} }
if (*state).Buttons & (1 << 9) > 0 { if (*state).Buttons & (1 << 9) > 0 {
BUTTON_PRESSES.zr.is_pressed = true; BUTTON_PRESSES.zr.is_pressed = true;
} }
if (*state).Buttons & ((1 << 12) | (1 << 16)) > 0 { if (*state).Buttons & ((1 << 12) | (1 << 16)) > 0 {
BUTTON_PRESSES.left.is_pressed = true; BUTTON_PRESSES.left.is_pressed = true;
} }
if (*state).Buttons & ((1 << 14) | (1 << 18)) > 0 { if (*state).Buttons & ((1 << 14) | (1 << 18)) > 0 {
BUTTON_PRESSES.right.is_pressed = true; BUTTON_PRESSES.right.is_pressed = true;
} }
if (*state).Buttons & ((1 << 15) | (1 << 19)) > 0 { if (*state).Buttons & ((1 << 15) | (1 << 19)) > 0 {
BUTTON_PRESSES.down.is_pressed = true; BUTTON_PRESSES.down.is_pressed = true;
} }
if (*state).Buttons & ((1 << 13) | (1 << 17)) > 0 { if (*state).Buttons & ((1 << 13) | (1 << 17)) > 0 {
BUTTON_PRESSES.up.is_pressed = true; BUTTON_PRESSES.up.is_pressed = true;
} }
// If we're here, remove all other Npad presses... // If we're here, remove all other Npad presses...
// Should we exclude the home button? // Should we exclude the home button?
(*state) = NpadGcState::default(); (*state) = NpadGcState::default();
} }
} }
} }
extern "C" { extern "C" {
#[link_name = "render_text_to_screen"] #[link_name = "render_text_to_screen"]
pub fn render_text_to_screen_cstr(str: *const skyline::libc::c_char); pub fn render_text_to_screen_cstr(str: *const skyline::libc::c_char);
#[link_name = "set_should_display_text_to_screen"] #[link_name = "set_should_display_text_to_screen"]
pub fn set_should_display_text_to_screen(toggle: bool); 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) { pub fn render_text_to_screen(s: &str) {
unsafe { unsafe {
render_text_to_screen_cstr(c_str!(s)); render_text_to_screen_cstr(c_str!(s));
} }
} }
pub unsafe fn quick_menu_loop() { pub unsafe fn quick_menu_loop() {
loop { loop {
std::thread::sleep(std::time::Duration::from_secs(10)); std::thread::sleep(std::time::Duration::from_secs(10));
let menu = consts::get_menu(); let menu = consts::get_menu();
let mut app = training_mod_tui::App::new(menu); let mut app = training_mod_tui::App::new(menu);
let backend = training_mod_tui::TestBackend::new(75, 15); let backend = training_mod_tui::TestBackend::new(75, 15);
let mut terminal = training_mod_tui::Terminal::new(backend).unwrap(); let mut terminal = training_mod_tui::Terminal::new(backend).unwrap();
let mut has_slept_millis = 0; let mut has_slept_millis = 0;
let render_frames = 5; let render_frames = 5;
let mut json_response = String::new(); let mut json_response = String::new();
let button_presses = &mut menu::BUTTON_PRESSES; let button_presses = &mut menu::BUTTON_PRESSES;
let mut received_input = true; let mut received_input = true;
loop { loop {
button_presses.a.read_press().then(|| { button_presses.a.read_press().then(|| {
app.on_a(); app.on_a();
received_input = true; received_input = true;
}); });
let b_press = &mut button_presses.b; let b_press = &mut button_presses.b;
b_press.read_press().then(|| { b_press.read_press().then(|| {
received_input = true; received_input = true;
if !app.outer_list { if !app.outer_list {
app.on_b() app.on_b()
} else if frame_counter::get_frame_count(menu::QUICK_MENU_FRAME_COUNTER_INDEX) == 0 } else if frame_counter::get_frame_count(menu::QUICK_MENU_FRAME_COUNTER_INDEX) == 0
{ {
// Leave menu. // Leave menu.
menu::QUICK_MENU_ACTIVE = false; menu::QUICK_MENU_ACTIVE = false;
menu::set_menu_from_json(&json_response); menu::set_menu_from_json(&json_response);
} }
}); });
button_presses.zl.read_press().then(|| { button_presses.zl.read_press().then(|| {
app.on_l(); app.on_l();
received_input = true; received_input = true;
}); });
button_presses.zr.read_press().then(|| { button_presses.zr.read_press().then(|| {
app.on_r(); app.on_r();
received_input = true; received_input = true;
}); });
button_presses.left.read_press().then(|| { button_presses.left.read_press().then(|| {
app.on_left(); app.on_left();
received_input = true; received_input = true;
}); });
button_presses.right.read_press().then(|| { button_presses.right.read_press().then(|| {
app.on_right(); app.on_right();
received_input = true; received_input = true;
}); });
button_presses.up.read_press().then(|| { button_presses.up.read_press().then(|| {
app.on_up(); app.on_up();
received_input = true; received_input = true;
}); });
button_presses.down.read_press().then(|| { button_presses.down.read_press().then(|| {
app.on_down(); app.on_down();
received_input = true; received_input = true;
}); });
std::thread::sleep(std::time::Duration::from_millis(16)); std::thread::sleep(std::time::Duration::from_millis(16));
has_slept_millis += 16; has_slept_millis += 16;
if has_slept_millis < 16 * render_frames { if has_slept_millis < 16 * render_frames {
continue; continue;
} }
has_slept_millis = 16; has_slept_millis = 16;
if !menu::QUICK_MENU_ACTIVE { if !menu::QUICK_MENU_ACTIVE {
app = training_mod_tui::App::new(consts::get_menu()); app = training_mod_tui::App::new(consts::get_menu());
set_should_display_text_to_screen(false); set_should_display_text_to_screen(false);
continue; continue;
} }
if !received_input { if !received_input {
continue; continue;
} }
let mut view = String::new(); let mut view = String::new();
let frame_res = terminal let frame_res = terminal
.draw(|f| json_response = training_mod_tui::ui(f, &mut app)) .draw(|f| json_response = training_mod_tui::ui(f, &mut app))
.unwrap(); .unwrap();
use std::fmt::Write; use std::fmt::Write;
for (i, cell) in frame_res.buffer.content().iter().enumerate() { for (i, cell) in frame_res.buffer.content().iter().enumerate() {
match cell.fg { match cell.fg {
Color::Black => write!(&mut view, "{}", &cell.symbol.black()), Color::Black => write!(&mut view, "{}", &cell.symbol.black()),
Color::Blue => write!(&mut view, "{}", &cell.symbol.blue()), Color::Blue => write!(&mut view, "{}", &cell.symbol.blue()),
Color::LightBlue => write!(&mut view, "{}", &cell.symbol.bright_blue()), Color::LightBlue => write!(&mut view, "{}", &cell.symbol.bright_blue()),
Color::Cyan => write!(&mut view, "{}", &cell.symbol.cyan()), Color::Cyan => write!(&mut view, "{}", &cell.symbol.cyan()),
Color::LightCyan => write!(&mut view, "{}", &cell.symbol.cyan()), Color::LightCyan => write!(&mut view, "{}", &cell.symbol.cyan()),
Color::Red => write!(&mut view, "{}", &cell.symbol.red()), Color::Red => write!(&mut view, "{}", &cell.symbol.red()),
Color::LightRed => write!(&mut view, "{}", &cell.symbol.bright_red()), Color::LightRed => write!(&mut view, "{}", &cell.symbol.bright_red()),
Color::LightGreen => write!(&mut view, "{}", &cell.symbol.bright_green()), Color::LightGreen => write!(&mut view, "{}", &cell.symbol.bright_green()),
Color::Green => write!(&mut view, "{}", &cell.symbol.green()), Color::Green => write!(&mut view, "{}", &cell.symbol.green()),
Color::Yellow => write!(&mut view, "{}", &cell.symbol.yellow()), Color::Yellow => write!(&mut view, "{}", &cell.symbol.yellow()),
Color::LightYellow => write!(&mut view, "{}", &cell.symbol.bright_yellow()), Color::LightYellow => write!(&mut view, "{}", &cell.symbol.bright_yellow()),
Color::Magenta => write!(&mut view, "{}", &cell.symbol.magenta()), Color::Magenta => write!(&mut view, "{}", &cell.symbol.magenta()),
Color::LightMagenta => { Color::LightMagenta => {
write!(&mut view, "{}", &cell.symbol.bright_magenta()) write!(&mut view, "{}", &cell.symbol.bright_magenta())
} }
_ => write!(&mut view, "{}", &cell.symbol), _ => write!(&mut view, "{}", &cell.symbol),
} }
.unwrap(); .unwrap();
if i % frame_res.area.width as usize == frame_res.area.width as usize - 1 { if i % frame_res.area.width as usize == frame_res.area.width as usize - 1 {
writeln!(&mut view).unwrap(); writeln!(&mut view).unwrap();
} }
} }
writeln!(&mut view).unwrap(); writeln!(&mut view).unwrap();
render_text_to_screen(view.as_str()); render_text_to_screen(view.as_str());
received_input = false; received_input = false;
} }
} }
} }
static mut WEB_MENU_ACTIVE: bool = false; static mut WEB_MENU_ACTIVE: bool = false;
pub unsafe fn web_session_loop() { pub unsafe fn web_session_loop() {
// Don't query the fightermanager too early otherwise it will crash... // 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 std::thread::sleep(std::time::Duration::new(30, 0)); // sleep for 30 secs on bootup
let mut web_session: Option<WebSession> = None; let mut web_session: Option<WebSession> = None;
loop { loop {
std::thread::sleep(std::time::Duration::from_millis(100)); std::thread::sleep(std::time::Duration::from_millis(100));
if is_ready_go() & is_training_mode() { if (is_ready_go() || entry_count() > 0) && is_training_mode() {
if web_session.is_some() { if web_session.is_some() {
if WEB_MENU_ACTIVE { if WEB_MENU_ACTIVE {
println!("[Training Modpack] Opening menu session..."); println!("[Training Modpack] Opening menu session...");
let session = web_session.unwrap(); let session = web_session.unwrap();
let message_send = MenuJsonStruct { let message_send = MenuJsonStruct {
menu: MENU, menu: MENU,
defaults_menu: DEFAULTS_MENU, defaults_menu: DEFAULTS_MENU,
}; };
session.send_json(&message_send); session.send_json(&message_send);
println!( println!(
"[Training Modpack] Sending message:\n{}", "[Training Modpack] Sending message:\n{}",
serde_json::to_string_pretty(&message_send).unwrap() serde_json::to_string_pretty(&message_send).unwrap()
); );
session.show(); session.show();
let message_recv = session.recv(); let message_recv = session.recv();
println!( println!(
"[Training Modpack] Received menu from web:\n{}", "[Training Modpack] Received menu from web:\n{}",
&message_recv &message_recv
); );
println!("[Training Modpack] Tearing down Training Modpack menu session"); println!("[Training Modpack] Tearing down Training Modpack menu session");
session.exit(); session.exit();
session.wait_for_exit(); session.wait_for_exit();
web_session = None; web_session = None;
set_menu_from_json(&message_recv); set_menu_from_json(&message_recv);
WEB_MENU_ACTIVE = false; WEB_MENU_ACTIVE = false;
} }
} else { } else {
// TODO // TODO
// Starting a new session causes some ingame lag. // Starting a new session causes some ingame lag.
// Investigate whether we can minimize this lag by // Investigate whether we can minimize this lag by
// waiting until the player is idle or using CPU boost mode // waiting until the player is idle or using CPU boost mode
println!("[Training Modpack] Starting new menu session..."); println!("[Training Modpack] Starting new menu session...");
web_session = Some( web_session = Some(
Webpage::new() Webpage::new()
.background(Background::BlurredScreenshot) .background(Background::BlurredScreenshot)
.htdocs_dir("training_modpack") .htdocs_dir("training_modpack")
.start_page("training_menu.html") .start_page("training_menu.html")
.open_session(WebSessionBootMode::InitiallyHidden) .open_session(WebSessionBootMode::InitiallyHidden)
.unwrap(), .unwrap(),
); );
} }
} else { } else {
// No longer in training mode, tear down the session. // No longer in training mode, tear down the session.
// This will avoid conflicts with other web plugins, and helps with stability. // 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 // Having the session open too long, especially if the switch has been put to sleep, can cause freezes
if web_session.is_some() { if let Some(web_session_to_kill) = web_session {
println!("[Training Modpack] Tearing down Training Modpack menu session"); println!("[Training Modpack] Tearing down Training Modpack menu session");
web_session.unwrap().exit(); web_session_to_kill.exit();
} web_session_to_kill.wait_for_exit();
web_session = None; }
} web_session = None;
} }
} }
}

View file

@ -1,149 +1,155 @@
pub mod consts; pub mod consts;
pub mod events; pub mod events;
pub mod menu; pub mod menu;
pub mod raygun_printer; pub mod raygun_printer;
pub mod release; pub mod release;
use crate::common::consts::*; use crate::common::consts::*;
use smash::app::{self, lua_bind::*}; use smash::app::{self, lua_bind::*};
use smash::lib::lua_const::*; use smash::lib::lua_const::*;
pub use crate::common::consts::MENU; pub use crate::common::consts::MENU;
pub static mut DEFAULTS_MENU: TrainingModpackMenu = crate::common::consts::DEFAULTS_MENU; pub static mut DEFAULTS_MENU: TrainingModpackMenu = crate::common::consts::DEFAULTS_MENU;
pub static mut BASE_MENU: TrainingModpackMenu = unsafe { DEFAULTS_MENU }; pub static mut BASE_MENU: TrainingModpackMenu = unsafe { DEFAULTS_MENU };
pub static mut FIGHTER_MANAGER_ADDR: usize = 0; pub static mut FIGHTER_MANAGER_ADDR: usize = 0;
pub static mut ITEM_MANAGER_ADDR: usize = 0; pub static mut ITEM_MANAGER_ADDR: usize = 0;
pub static mut STAGE_MANAGER_ADDR: usize = 0; pub static mut STAGE_MANAGER_ADDR: usize = 0;
#[cfg(not(feature = "outside_training_mode"))] #[cfg(not(feature = "outside_training_mode"))]
extern "C" { extern "C" {
#[link_name = "\u{1}_ZN3app9smashball16is_training_modeEv"] #[link_name = "\u{1}_ZN3app9smashball16is_training_modeEv"]
pub fn is_training_mode() -> bool; pub fn is_training_mode() -> bool;
} }
#[cfg(feature = "outside_training_mode")] #[cfg(feature = "outside_training_mode")]
pub fn is_training_mode() -> bool { pub fn is_training_mode() -> bool {
return true; return true;
} }
pub fn get_category(module_accessor: &mut app::BattleObjectModuleAccessor) -> i32 { pub fn get_category(module_accessor: &mut app::BattleObjectModuleAccessor) -> i32 {
(module_accessor.info >> 28) as u8 as i32 (module_accessor.info >> 28) as u8 as i32
} }
pub fn is_emulator() -> bool { pub fn is_emulator() -> bool {
unsafe { skyline::hooks::getRegionAddress(skyline::hooks::Region::Text) as u64 == 0x8004000 } unsafe { skyline::hooks::getRegionAddress(skyline::hooks::Region::Text) as u64 == 0x8004000 }
} }
pub fn get_module_accessor(fighter_id: FighterId) -> *mut app::BattleObjectModuleAccessor { pub fn get_module_accessor(fighter_id: FighterId) -> *mut app::BattleObjectModuleAccessor {
let entry_id_int = fighter_id as i32; let entry_id_int = fighter_id as i32;
let entry_id = app::FighterEntryID(entry_id_int); let entry_id = app::FighterEntryID(entry_id_int);
unsafe { unsafe {
let mgr = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager); let mgr = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager);
let fighter_entry = let fighter_entry =
FighterManager::get_fighter_entry(mgr, entry_id) as *mut app::FighterEntry; FighterManager::get_fighter_entry(mgr, entry_id) as *mut app::FighterEntry;
let current_fighter_id = FighterEntry::current_fighter_id(fighter_entry); let current_fighter_id = FighterEntry::current_fighter_id(fighter_entry);
app::sv_battle_object::module_accessor(current_fighter_id as u32) app::sv_battle_object::module_accessor(current_fighter_id as u32)
} }
} }
pub fn is_fighter(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { pub fn is_fighter(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
get_category(module_accessor) == BATTLE_OBJECT_CATEGORY_FIGHTER get_category(module_accessor) == BATTLE_OBJECT_CATEGORY_FIGHTER
} }
pub fn is_operation_cpu(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { pub fn is_operation_cpu(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
unsafe { unsafe {
if !is_fighter(module_accessor) { if !is_fighter(module_accessor) {
return false; return false;
} }
let entry_id_int = let entry_id_int =
WorkModule::get_int(module_accessor, *FIGHTER_INSTANCE_WORK_ID_INT_ENTRY_ID) as i32; WorkModule::get_int(module_accessor, *FIGHTER_INSTANCE_WORK_ID_INT_ENTRY_ID) as i32;
if entry_id_int == 0 { if entry_id_int == 0 {
return false; return false;
} }
let entry_id = app::FighterEntryID(entry_id_int); let entry_id = app::FighterEntryID(entry_id_int);
let mgr = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager); let mgr = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager);
let fighter_information = let fighter_information =
FighterManager::get_fighter_information(mgr, entry_id) as *mut app::FighterInformation; FighterManager::get_fighter_information(mgr, entry_id) as *mut app::FighterInformation;
FighterInformation::is_operation_cpu(fighter_information) FighterInformation::is_operation_cpu(fighter_information)
} }
} }
pub fn is_grounded(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { pub fn is_grounded(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
let situation_kind = unsafe { StatusModule::situation_kind(module_accessor) as i32 }; let situation_kind = unsafe { StatusModule::situation_kind(module_accessor) as i32 };
situation_kind == SITUATION_KIND_GROUND situation_kind == SITUATION_KIND_GROUND
} }
pub fn is_airborne(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { pub fn is_airborne(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
let situation_kind = unsafe { StatusModule::situation_kind(module_accessor) as i32 }; let situation_kind = unsafe { StatusModule::situation_kind(module_accessor) as i32 };
situation_kind == SITUATION_KIND_AIR situation_kind == SITUATION_KIND_AIR
} }
pub fn is_idle(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { pub fn is_idle(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
let status_kind = unsafe { StatusModule::status_kind(module_accessor) }; let status_kind = unsafe { StatusModule::status_kind(module_accessor) };
status_kind == FIGHTER_STATUS_KIND_WAIT status_kind == FIGHTER_STATUS_KIND_WAIT
} }
pub fn is_in_hitstun(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { pub fn is_in_hitstun(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
let status_kind = unsafe { StatusModule::status_kind(module_accessor) }; let status_kind = unsafe { StatusModule::status_kind(module_accessor) };
(*FIGHTER_STATUS_KIND_DAMAGE..*FIGHTER_STATUS_KIND_DAMAGE_FALL).contains(&status_kind) (*FIGHTER_STATUS_KIND_DAMAGE..*FIGHTER_STATUS_KIND_DAMAGE_FALL).contains(&status_kind)
} }
pub fn is_in_footstool(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { pub fn is_in_footstool(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
let status_kind = unsafe { StatusModule::status_kind(module_accessor) }; let status_kind = unsafe { StatusModule::status_kind(module_accessor) };
(*FIGHTER_STATUS_KIND_TREAD_DAMAGE..=*FIGHTER_STATUS_KIND_TREAD_FALL).contains(&status_kind) (*FIGHTER_STATUS_KIND_TREAD_DAMAGE..=*FIGHTER_STATUS_KIND_TREAD_FALL).contains(&status_kind)
} }
pub fn is_shielding(module_accessor: *mut app::BattleObjectModuleAccessor) -> bool { pub fn is_shielding(module_accessor: *mut app::BattleObjectModuleAccessor) -> bool {
let status_kind = unsafe { StatusModule::status_kind(module_accessor) as i32 }; let status_kind = unsafe { StatusModule::status_kind(module_accessor) as i32 };
(*FIGHTER_STATUS_KIND_GUARD_ON..=*FIGHTER_STATUS_KIND_GUARD_DAMAGE).contains(&status_kind) (*FIGHTER_STATUS_KIND_GUARD_ON..=*FIGHTER_STATUS_KIND_GUARD_DAMAGE).contains(&status_kind)
} }
pub fn is_in_shieldstun(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { pub fn is_in_shieldstun(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
let status_kind = unsafe { StatusModule::status_kind(module_accessor) }; let status_kind = unsafe { StatusModule::status_kind(module_accessor) };
let prev_status = unsafe { StatusModule::prev_status_kind(module_accessor, 0) }; let prev_status = unsafe { StatusModule::prev_status_kind(module_accessor, 0) };
// If we are taking shield damage or we are droping shield from taking shield damage we are in hitstun // If we are taking shield damage or we are droping shield from taking shield damage we are in hitstun
status_kind == FIGHTER_STATUS_KIND_GUARD_DAMAGE status_kind == FIGHTER_STATUS_KIND_GUARD_DAMAGE
|| (prev_status == FIGHTER_STATUS_KIND_GUARD_DAMAGE || (prev_status == FIGHTER_STATUS_KIND_GUARD_DAMAGE
&& status_kind == FIGHTER_STATUS_KIND_GUARD_OFF) && status_kind == FIGHTER_STATUS_KIND_GUARD_OFF)
} }
pub unsafe fn is_dead(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { pub unsafe fn is_dead(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
let fighter_kind = app::utility::get_kind(module_accessor); let fighter_kind = app::utility::get_kind(module_accessor);
let fighter_is_ptrainer = [ let fighter_is_ptrainer = [
*FIGHTER_KIND_PZENIGAME, *FIGHTER_KIND_PZENIGAME,
*FIGHTER_KIND_PFUSHIGISOU, *FIGHTER_KIND_PFUSHIGISOU,
*FIGHTER_KIND_PLIZARDON, *FIGHTER_KIND_PLIZARDON,
] ]
.contains(&fighter_kind); .contains(&fighter_kind);
let status_kind = StatusModule::status_kind(module_accessor) as i32; let status_kind = StatusModule::status_kind(module_accessor) as i32;
let prev_status_kind = StatusModule::prev_status_kind(module_accessor, 0); let prev_status_kind = StatusModule::prev_status_kind(module_accessor, 0);
// Pokemon trainer enters FIGHTER_STATUS_KIND_WAIT for one frame during their respawn animation // Pokemon trainer enters FIGHTER_STATUS_KIND_WAIT for one frame during their respawn animation
// And the previous status is FIGHTER_STATUS_NONE // And the previous status is FIGHTER_STATUS_NONE
if fighter_is_ptrainer { if fighter_is_ptrainer {
[*FIGHTER_STATUS_KIND_DEAD, *FIGHTER_STATUS_KIND_STANDBY].contains(&status_kind) [*FIGHTER_STATUS_KIND_DEAD, *FIGHTER_STATUS_KIND_STANDBY].contains(&status_kind)
|| (status_kind == FIGHTER_STATUS_KIND_WAIT || (status_kind == FIGHTER_STATUS_KIND_WAIT
&& prev_status_kind == FIGHTER_STATUS_KIND_NONE) && prev_status_kind == FIGHTER_STATUS_KIND_NONE)
} else { } else {
[*FIGHTER_STATUS_KIND_DEAD, *FIGHTER_STATUS_KIND_STANDBY].contains(&status_kind) [*FIGHTER_STATUS_KIND_DEAD, *FIGHTER_STATUS_KIND_STANDBY].contains(&status_kind)
} }
} }
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 // Returns true if a match is currently active
pub unsafe fn is_ready_go() -> bool { pub unsafe fn is_ready_go() -> bool {
let fighter_manager = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager); let fighter_manager = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager);
FighterManager::is_ready_go(fighter_manager) FighterManager::is_ready_go(fighter_manager)
} }
// Returns true if a match is currently active
pub unsafe fn entry_count() -> i32 {
let fighter_manager = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager);
FighterManager::entry_count(fighter_manager)
}