1
0
Fork 0
mirror of https://github.com/jugeeya/UltimateTrainingModpack.git synced 2025-03-28 17:22:42 +00:00
UltimateTrainingModpack/src/common/menu.rs
2021-08-16 09:35:11 -07:00

382 lines
12 KiB
Rust

use std::fs;
use std::path::Path;
use crate::common::*;
use skyline::info::get_program_id;
use smash::lib::lua_const::*;
use skyline_web::{Background, BootDisplay, Webpage};
use ramhorns::{Template, Content};
#[derive(Content)]
struct Slider {
min: usize,
max: usize,
index: usize,
value: usize
}
#[derive(Content)]
struct Toggle<'a> {
title: &'a str,
checked: &'a str,
index: usize,
value: usize,
default: &'a str,
}
#[derive(Content)]
struct OnOffSelector<'a> {
title: &'a str,
checked: &'a str,
default: &'a str,
}
#[derive(Content)]
struct SubMenu<'a> {
title: &'a str,
id: &'a str,
toggles: Vec<Toggle<'a>>,
sliders: Vec<Slider>,
onoffselector: Vec<OnOffSelector<'a>>,
index: usize,
check_against: usize
}
impl<'a> SubMenu<'a> {
pub fn max_idx(&self) -> usize {
self.toggles
.iter()
.max_by(|t1,t2| t1.index.cmp(&t2.index))
.map(|t| t.index)
.unwrap_or(self.index)
}
pub fn add_toggle(&mut self, title: &'a str, checked: bool, value: usize, default: bool) {
self.toggles.push(Toggle{
title,
checked: if checked { "is-appear"} else { "is-hidden" },
index: self.max_idx() + 1,
value,
default: if default { "is-appear"} else { "is-hidden" },
});
}
pub fn add_slider(&mut self, min: usize, max: usize, value: usize) {
self.sliders.push(Slider{
min,
max,
index: self.max_idx() + 1,
value
});
}
pub fn add_onoffselector(&mut self, title: &'a str, checked: bool, default: bool) {
// TODO: Is there a more elegant way to do this?
// The HTML only supports a single onoffselector but the SubMenu stores it as a Vec
self.onoffselector.push(OnOffSelector{
title,
checked: if checked { "is-appear"} else { "is-hidden" },
default: if default { "is-appear"} else { "is-hidden" },
});
}
}
#[derive(Content)]
struct Menu<'a> {
sub_menus: Vec<SubMenu<'a>>
}
impl<'a> Menu<'a> {
pub fn max_idx(&self) -> usize {
self.sub_menus
.iter()
.max_by(|x, y| x.max_idx().cmp(&y.max_idx()))
.map(|sub_menu| sub_menu.max_idx())
.unwrap_or(0)
}
pub fn add_sub_menu(&mut self, title: &'a str, id: &'a str, check_against: usize, toggles: Vec<(&'a str, usize)>, sliders: Vec<(usize,usize,usize)>, defaults: usize) {
let mut sub_menu = SubMenu {
title,
id,
toggles: Vec::new(),
sliders: Vec::new(),
onoffselector: Vec::new(),
index: self.max_idx() + 1,
check_against
};
for toggle in toggles {
sub_menu.add_toggle(toggle.0, (check_against & toggle.1) != 0, toggle.1, (defaults & toggle.1) != 0)
}
for slider in sliders {
sub_menu.add_slider(slider.0, slider.1, slider.2);
}
self.sub_menus.push(sub_menu);
}
pub fn add_sub_menu_sep(&mut self, title: &'a str, id: &'a str, check_against: usize, strs: Vec<&'a str>, vals: Vec<usize>, defaults: usize) {
let mut sub_menu = SubMenu {
title,
id,
toggles: Vec::new(),
sliders: Vec::new(),
onoffselector: Vec::new(),
index: self.max_idx() + 1,
check_against
};
for i in 0..strs.len() {
sub_menu.add_toggle(strs[i], (check_against & vals[i]) != 0, vals[i], (defaults & vals[i]) != 0)
}
// TODO: add sliders?
self.sub_menus.push(sub_menu);
}
pub fn add_sub_menu_onoff(&mut self, title: &'a str, id: &'a str, check_against: usize, checked: bool, default: usize) {
let mut sub_menu = SubMenu {
title,
id,
toggles: Vec::new(),
sliders: Vec::new(),
onoffselector: Vec::new(),
index: self.max_idx() + 1,
check_against
};
sub_menu.add_onoffselector(title, checked, (default & OnOff::On as usize) != 0);
self.sub_menus.push(sub_menu);
}
}
macro_rules! add_bitflag_submenu {
($menu:ident, $title:literal, $id:ident, $e:ty) => {
paste::paste!{
let [<$id _strs>] = <$e>::to_toggle_strs();
let [<$id _vals>] = <$e>::to_toggle_vals();
$menu.add_sub_menu_sep(
$title,
stringify!($id),
MENU.$id.bits() as usize,
[<$id _strs>].iter().map(|i| i.as_str()).collect(),
[<$id _vals>],
DEFAULT_MENU.$id.bits() as usize,
);
}
}
}
// macro_rules! add_single_option_submenu {
// ($menu:ident, $title:literal, $id:ident, $e:ty) => {
// paste::paste!{
// let [<$id _toggles>] = Vec::new();
// for val in [<$e>]::iter() {
// [<$id _toggles>].push((val.into_string().as_str(), val as usize));
// }
// $menu.add_sub_menu(
// $title,
// stringify!($id),
// MENU.$id as usize,
// [<$id _toggles>],
// [].to_vec()
// );
// }
// }
// }
pub fn set_menu_from_url(s: &str) {
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 toggle = toggle_value_split[0];
if toggle.is_empty() { continue; }
let toggle_vals = toggle_value_split[1];
let mut bits = 0;
for toggle_val in toggle_vals.split(',') {
if toggle_val.is_empty() { continue; }
let val = toggle_val.parse::<u32>().unwrap();
bits |= val;
}
unsafe {
MENU.set(toggle, bits);
}
}
}
pub unsafe fn menu_condition(module_accessor: &mut smash::app::BattleObjectModuleAccessor) -> bool {
ControlModule::check_button_on(module_accessor, *CONTROL_PAD_BUTTON_SPECIAL) &&
ControlModule::check_button_on_trriger(module_accessor, *CONTROL_PAD_BUTTON_APPEAL_HI)
}
pub unsafe fn write_menu() {
let tpl = Template::new(include_str!("../templates/menu.html")).unwrap();
let mut overall_menu = Menu {
sub_menus: Vec::new()
};
// Toggle/bitflag menus
add_bitflag_submenu!(overall_menu, "Mash Toggles", mash_state, Action);
add_bitflag_submenu!(overall_menu, "Followup Toggles", follow_up, Action);
add_bitflag_submenu!(overall_menu, "Attack Angle", attack_angle, AttackAngle);
add_bitflag_submenu!(overall_menu, "Ledge Options", ledge_state, LedgeOption);
add_bitflag_submenu!(overall_menu, "Ledge Delay", ledge_delay, LongDelay);
add_bitflag_submenu!(overall_menu, "Tech Options", tech_state, TechFlags);
add_bitflag_submenu!(overall_menu, "Miss Tech Options", miss_tech_state, MissTechFlags);
add_bitflag_submenu!(overall_menu, "Defensive Options", defensive_state, Defensive);
add_bitflag_submenu!(overall_menu, "Aerial Delay", aerial_delay, Delay);
add_bitflag_submenu!(overall_menu, "OoS Offset", oos_offset, Delay);
add_bitflag_submenu!(overall_menu, "Reaction Time", reaction_time, Delay);
add_bitflag_submenu!(overall_menu, "Fast Fall", fast_fall, BoolFlag);
add_bitflag_submenu!(overall_menu, "Fast Fall Delay", fast_fall_delay, Delay);
add_bitflag_submenu!(overall_menu, "Falling Aerials", falling_aerials, BoolFlag);
add_bitflag_submenu!(overall_menu, "Full Hop", full_hop, BoolFlag);
add_bitflag_submenu!(overall_menu, "Shield Tilt", shield_tilt, Direction);
add_bitflag_submenu!(overall_menu, "DI Direction", di_state, Direction);
add_bitflag_submenu!(overall_menu, "SDI Direction", sdi_state, Direction);
add_bitflag_submenu!(overall_menu, "Airdodge Direction", air_dodge_dir, Direction);
overall_menu.add_sub_menu(
"SDI Strength",
"sdi_strength",
MENU.sdi_strength as usize,
[
("Normal", SdiStrength::Normal as usize),
("Medium", SdiStrength::Medium as usize),
("High", SdiStrength::High as usize),
].to_vec(),
[].to_vec(),
DEFAULT_MENU.sdi_strength as usize,
);
overall_menu.add_sub_menu(
"Shield Toggles",
"shield_state",
MENU.shield_state as usize,
[
("None", Shield::None as usize),
("Hold", Shield::Hold as usize),
("Infinite", Shield::Infinite as usize),
].to_vec(),
[].to_vec(),
DEFAULT_MENU.shield_state as usize,
);
overall_menu.add_sub_menu(
"Mirroring",
"save_state_mirroring",
MENU.save_state_mirroring as usize,
[
("None", SaveStateMirroring::None as usize),
("Alternate", SaveStateMirroring::Alternate as usize),
("Random", SaveStateMirroring::Random as usize),
].to_vec(),
[].to_vec(),
DEFAULT_MENU.save_state_mirroring as usize,
);
// Slider menus
overall_menu.add_sub_menu(
"Input Delay",
"input_delay",
// unnecessary for slider?
MENU.input_delay as usize,
[].to_vec(),
[
(0, 10, MENU.input_delay as usize)
].to_vec(),
DEFAULT_MENU.input_delay as usize,
);
// OnOff flags
overall_menu.add_sub_menu_onoff(
"Save Damage",
"save_damage",
MENU.save_damage as usize,
(MENU.save_damage as usize & OnOff::On as usize) != 0,
DEFAULT_MENU.save_damage as usize,
);
overall_menu.add_sub_menu_onoff(
"Hitbox Visualization",
"hitbox_vis",
MENU.hitbox_vis as usize,
(MENU.hitbox_vis as usize & OnOff::On as usize) != 0,
DEFAULT_MENU.hitbox_vis as usize,
);
overall_menu.add_sub_menu_onoff(
"Stage Hazards",
"stage_hazards",
MENU.stage_hazards as usize,
(MENU.stage_hazards as usize & OnOff::On as usize) != 0,
DEFAULT_MENU.stage_hazards as usize,
);
overall_menu.add_sub_menu_onoff(
"Frame Advantage",
"frame_advantage",
MENU.frame_advantage as usize,
(MENU.frame_advantage as usize & OnOff::On as usize) != 0,
DEFAULT_MENU.frame_advantage as usize,
);
overall_menu.add_sub_menu_onoff(
"Mash In Neutral",
"mash_in_neutral",
MENU.mash_in_neutral as usize,
(MENU.mash_in_neutral as usize & OnOff::On as usize) != 0,
DEFAULT_MENU.mash_in_neutral as usize,
);
let data = tpl.render(&overall_menu);
// Now that we have the html, write it to file
// From skyline-web
let program_id = get_program_id();
let htdocs_dir = "contents";
let path = Path::new("sd:/atmosphere/contents")
.join(&format!("{:016X}", program_id))
.join(&format!("manual_html/html-document/{}.htdocs/", htdocs_dir))
.join("index.html");
fs::write(path, data).unwrap();
}
pub unsafe fn spawn_menu() {
let fname = "index.html";
let params = MENU.to_url_params();
let page_response = Webpage::new()
.background(Background::BlurredScreenshot)
.htdocs_dir("contents")
.boot_display(BootDisplay::BlurredScreenshot)
.boot_icon(true)
.start_page(&format!("{}{}", fname, params))
.open()
.unwrap();
let last_url = page_response
.get_last_url()
.unwrap();
set_menu_from_url(last_url);
let menu_conf_path = "sd:/TrainingModpack/training_modpack_menu.conf";
std::fs::write(menu_conf_path, last_url)
.expect("Failed to write menu conf file");
}