diff --git a/src/common/consts.rs b/src/common/consts.rs index 40d0f99..bc027e1 100644 --- a/src/common/consts.rs +++ b/src/common/consts.rs @@ -55,6 +55,21 @@ macro_rules! extra_bitflag_impls { let all_options = <$e>::all().to_vec(); all_options.iter().map(|i| i.bits() as usize).collect() } + pub fn to_url_param(&self) -> String { + let mut vec = self.to_vec(); + let mut s = String::new(); + let mut first = true; + while !vec.is_empty() { + let field = vec.pop().unwrap().bits(); + if !first { + s.push_str(","); + } else { + first = false; + } + s.push_str(&field.to_string()); + } + s + } } } } @@ -107,6 +122,7 @@ impl Direction { Direction::DOWN_IN => 6, Direction::DOWN => 7, Direction::DOWN_OUT => 8, + Direction::NEUTRAL => 0, Direction::LEFT => 5, Direction::RIGHT => 1, _ => 0, @@ -123,6 +139,7 @@ impl Direction { Direction::DOWN_IN => "Down and In", Direction::DOWN => "Down", Direction::DOWN_OUT => "Down and Away", + Direction::NEUTRAL => "Neutral", Direction::LEFT => "Left", Direction::RIGHT => "Right", _ => "", @@ -235,7 +252,15 @@ impl Shield { Shield::Infinite => "Infinite", Shield::Hold => "Hold", Shield::Constant => "Constant", - _ => "", + }.to_string() + } + + pub fn to_url_param(&self) -> String { + match self { + Shield::None => "0", + Shield::Infinite => "1", + Shield::Hold => "2", + Shield::Constant => "3", }.to_string() } } @@ -286,7 +311,13 @@ impl OnOff { match self { OnOff::Off => "Off", OnOff::On => "On", - _ => "" + }.to_string() + } + + pub fn to_url_param(&self) -> String { + match self { + OnOff::Off => "0", + OnOff::On => "1", }.to_string() } } @@ -335,8 +366,34 @@ impl Action { } pub fn into_string(self) -> String { - // TODO: add - return self.to_string() + match self { + Action::AIR_DODGE => "Airdodge", + Action::JUMP => "Jump", + Action::SHIELD => "Shield", + Action::SPOT_DODGE => "Spotdodge", + Action::ROLL_F => "Roll Forwards", + Action::ROLL_B => "Roll Backwards", + Action::NAIR => "Neutral Aerial", + Action::FAIR => "Forward Aerial", + Action::BAIR => "Backward Aerial", + Action::UAIR => "Up Aerial", + Action::DAIR => "Down Aerial", + Action::NEUTRAL_B => "Neutral Special", + Action::SIDE_B => "Side Special", + Action::UP_B => "Up Special", + Action::DOWN_B => "Down Special", + Action::F_SMASH => "Forward Smash", + Action::U_SMASH => "Up Smash", + Action::D_SMASH => "Down Smash", + Action::JAB => "Jab", + Action::F_TILT => "Forward Tilt", + Action::U_TILT => "Up Tilt", + Action::D_TILT => "Down Tilt", + Action::GRAB => "Grab", + Action::DASH => "Dash", + Action::DASH_ATTACK => "Dash Attack", + _ => "", + }.to_string() } } @@ -402,8 +459,40 @@ bitflags! { impl Delay { pub fn into_string(self) -> String { - // TODO: add - return self.to_string() + match self { + Delay::D0 => "0", + Delay::D1 => "1", + Delay::D2 => "2", + Delay::D3 => "3", + Delay::D4 => "4", + Delay::D5 => "5", + Delay::D6 => "6", + Delay::D7 => "7", + Delay::D8 => "8", + Delay::D9 => "9", + Delay::D10 => "10", + Delay::D11 => "11", + Delay::D12 => "12", + Delay::D13 => "13", + Delay::D14 => "14", + Delay::D15 => "15", + Delay::D16 => "16", + Delay::D17 => "17", + Delay::D18 => "18", + Delay::D19 => "19", + Delay::D20 => "20", + Delay::D21 => "21", + Delay::D22 => "22", + Delay::D23 => "23", + Delay::D24 => "24", + Delay::D25 => "25", + Delay::D26 => "26", + Delay::D27 => "27", + Delay::D28 => "28", + Delay::D29 => "29", + Delay::D30 => "30", + _ => "", + }.to_string() } pub fn into_delay(&self) -> u32 { @@ -451,8 +540,40 @@ bitflags! { impl LongDelay { pub fn into_string(self) -> String { - // TODO: Is this used for the menu? - return self.to_string() + match self { + LongDelay::D0 => "0", + LongDelay::D10 => "1", + LongDelay::D20 => "2", + LongDelay::D30 => "3", + LongDelay::D40 => "4", + LongDelay::D50 => "5", + LongDelay::D60 => "6", + LongDelay::D70 => "7", + LongDelay::D80 => "8", + LongDelay::D90 => "9", + LongDelay::D100 => "10", + LongDelay::D110 => "11", + LongDelay::D120 => "12", + LongDelay::D130 => "13", + LongDelay::D140 => "14", + LongDelay::D150 => "15", + LongDelay::D160 => "16", + LongDelay::D170 => "17", + LongDelay::D180 => "18", + LongDelay::D190 => "19", + LongDelay::D200 => "20", + LongDelay::D210 => "21", + LongDelay::D220 => "22", + LongDelay::D230 => "23", + LongDelay::D240 => "24", + LongDelay::D250 => "25", + LongDelay::D260 => "26", + LongDelay::D270 => "27", + LongDelay::D280 => "28", + LongDelay::D290 => "29", + LongDelay::D300 => "30", + _ => "", + }.to_string() } pub fn into_longdelay(&self) -> u32 { @@ -480,8 +601,10 @@ impl BoolFlag { } pub fn into_string(self) -> String { - // TODO: add - return self.to_string() + match self { + BoolFlag::TRUE => "True", + _ => "False", + }.to_string() } } @@ -509,39 +632,85 @@ impl SdiStrength { SdiStrength::Normal => "Normal", SdiStrength::Medium => "Medium", SdiStrength::High => "High", - _ => "" + }.to_string() + } + + pub fn to_url_param(&self) -> String { + match self { + SdiStrength::Normal => "0", + SdiStrength::Medium => "1", + SdiStrength::High => "2", }.to_string() } } +// For input delay +trait to_url_param { + fn to_url_param(&self) -> String; +} + +impl to_url_param for i32 { + fn to_url_param(&self) -> String { + self.to_string() + } +} + +// Macro to build the url parameter string +macro_rules! url_params { + ( + pub struct $e:ident { + $(pub $field_name:ident: $field_type:ty,)* + } + ) => { + pub struct $e { + $(pub $field_name: $field_type,)* + } + impl $e { + pub fn to_url_params(&self) -> String { + let mut s = "?".to_string(); + $( + 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)] -pub struct TrainingModpackMenu { - pub hitbox_vis: OnOff, - pub stage_hazards: OnOff, - pub di_state: Direction, - pub sdi_state: Direction, - pub sdi_strength: SdiStrength, - 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 input_delay: i32, - pub save_damage: OnOff, +url_params! { + pub struct TrainingModpackMenu { + pub hitbox_vis: OnOff, + pub stage_hazards: OnOff, + pub di_state: Direction, + pub sdi_state: Direction, + pub sdi_strength: SdiStrength, + 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 input_delay: i32, + pub save_damage: OnOff, + } } macro_rules! set_by_str { @@ -557,34 +726,30 @@ macro_rules! set_by_str { impl TrainingModpackMenu { pub fn set(&mut self, s: &str, val: u32) { set_by_str!(self, s, - (di_state = Direction::from_bits(val)) - (sdi_state = Direction::from_bits(val)) - (shield_tilt = Direction::from_bits(val)) + (aerial_delay = Delay::from_bits(val)) (air_dodge_dir = Direction::from_bits(val)) - - (mash_state = Action::from_bits(val)) - (follow_up = Action::from_bits(val)) - - (ledge_state = LedgeOption::from_bits(val)) - (ledge_delay = LongDelay::from_bits(val)) - (tech_state = TechFlags::from_bits(val)) - (miss_tech_state = MissTechFlags::from_bits(val)) - - (shield_state = num::FromPrimitive::from_u32(val)) + (attack_angle = AttackAngle::from_bits(val)) (defensive_state = Defensive::from_bits(val)) + (di_state = Direction::from_bits(val)) + (falling_aerials = BoolFlag::from_bits(val)) + (fast_fall_delay = Delay::from_bits(val)) + (fast_fall = BoolFlag::from_bits(val)) + (follow_up = Action::from_bits(val)) + (full_hop = BoolFlag::from_bits(val)) + (hitbox_vis = OnOff::from_val(val)) + (input_delay = Some(val as i32)) + (ledge_delay = LongDelay::from_bits(val)) + (ledge_state = LedgeOption::from_bits(val)) + (mash_in_neutral = OnOff::from_val(val)) + (mash_state = Action::from_bits(val)) + (miss_tech_state = MissTechFlags::from_bits(val)) (oos_offset = Delay::from_bits(val)) (reaction_time = Delay::from_bits(val)) - - (fast_fall = BoolFlag::from_bits(val)) - (fast_fall_delay = Delay::from_bits(val)) - (falling_aerials = BoolFlag::from_bits(val)) - (aerial_delay = Delay::from_bits(val)) - (full_hop = BoolFlag::from_bits(val)) - - (hitbox_vis = OnOff::from_val(val)) + (sdi_state = Direction::from_bits(val)) + (shield_state = num::FromPrimitive::from_u32(val)) + (shield_tilt = Direction::from_bits(val)) (stage_hazards = OnOff::from_val(val)) - - (input_delay = Some(val as i32)) + (tech_state = TechFlags::from_bits(val)) ); } } diff --git a/src/common/menu.rs b/src/common/menu.rs index 3340d39..b60b00f 100644 --- a/src/common/menu.rs +++ b/src/common/menu.rs @@ -1,9 +1,10 @@ - +use std::fs; +use std::path::Path; use crate::common::*; +use skyline::info::get_program_id; use skyline::nn::hid::NpadHandheldState; use smash::lib::lua_const::*; - use skyline_web::{Background, BootDisplay, Webpage}; use ramhorns::{Template, Content}; @@ -34,12 +35,28 @@ impl<'a> Toggle<'a> { } } +#[derive(Content)] +struct OnOffSelector<'a> { + title: &'a str, + checked: &'a str +} + +impl <'a>OnOffSelector<'a> { + pub fn new(title: &'a str, checked: bool) -> OnOffSelector<'a> { + OnOffSelector { + title: title, + checked: if checked { "is-appear "} else { "is-hidden" } + } + } +} + #[derive(Content)] struct SubMenu<'a> { title: &'a str, id: &'a str, toggles: Vec>, sliders: Vec, + onoffselector: Vec>, index: usize, check_against: usize } @@ -70,6 +87,15 @@ impl<'a> SubMenu<'a> { value }); } + + pub fn add_onoffselector(&mut self, title: &'a str, checked: 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: title, + checked: if checked { "is-appear "} else { "is-hidden" } + }); + } } #[derive(Content)] @@ -92,6 +118,7 @@ impl<'a> Menu<'a> { id: id, toggles: Vec::new(), sliders: Vec::new(), + onoffselector: Vec::new(), index: self.max_idx() + 1, check_against: check_against }; @@ -113,6 +140,7 @@ impl<'a> Menu<'a> { id: id, toggles: Vec::new(), sliders: Vec::new(), + onoffselector: Vec::new(), index: self.max_idx() + 1, check_against: check_against }; @@ -125,6 +153,21 @@ impl<'a> Menu<'a> { 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) { + let mut sub_menu = SubMenu { + title: title, + id: id, + toggles: Vec::new(), + sliders: Vec::new(), + onoffselector: Vec::new(), + index: self.max_idx() + 1, + check_against: check_against + }; + + sub_menu.add_onoffselector(title, checked); + self.sub_menus.push(sub_menu); + } } macro_rules! add_bitflag_submenu { @@ -145,13 +188,13 @@ macro_rules! add_bitflag_submenu { } pub fn set_menu_from_url(s: &str) { - let base_url_len = "http://localhost/".len(); + 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::>(); + let toggle_value_split = toggle_values.split("=").collect::>(); let toggle = toggle_value_split[0]; if toggle == "" { continue; } @@ -161,7 +204,7 @@ pub fn set_menu_from_url(s: &str) { for toggle_val in toggle_vals.split(",") { if toggle_val == "" { continue; } - let mut val = toggle_val.parse::().unwrap(); + let val = toggle_val.parse::().unwrap(); bits = bits | val; } @@ -177,7 +220,7 @@ pub unsafe fn menu_condition(module_accessor: &mut smash::app::BattleObjectModul ControlModule::check_button_on_trriger(module_accessor, *CONTROL_PAD_BUTTON_APPEAL_HI) } -pub unsafe fn render_menu() -> String { +pub unsafe fn write_menu() { let tpl = Template::new(include_str!("../templates/menu.html")).unwrap(); let mut overall_menu = Menu { @@ -236,52 +279,50 @@ pub unsafe fn render_menu() -> String { // SDI strength - // TODO: OnOff flags... need a different sort of submenu. - overall_menu.add_sub_menu( - "Hitbox Visualization", - "hitbox_vis", + // OnOff flags + overall_menu.add_sub_menu_onoff( + "Hitbox Visualization", + "hitbox_vis", MENU.hitbox_vis as usize, - [ - ("Off", OnOff::Off as usize), - ("On", OnOff::On as usize), - ].to_vec(), - [].to_vec() + (MENU.hitbox_vis as usize & OnOff::On as usize) != 0 ); - overall_menu.add_sub_menu( - "Stage Hazards", - "stage_hazards", + + overall_menu.add_sub_menu_onoff( + "Stage Hazards", + "stage_hazards", MENU.stage_hazards as usize, - [ - ("Off", OnOff::Off as usize), - ("On", OnOff::On as usize), - ].to_vec(), - [].to_vec() + (MENU.stage_hazards as usize & OnOff::On as usize) != 0 ); - overall_menu.add_sub_menu( - "Mash In Neutral", - "mash_in_neutral", + overall_menu.add_sub_menu_onoff( + "Mash In Neutral", + "mash_in_neutral", MENU.mash_in_neutral as usize, - [ - ("Off", OnOff::Off as usize), - ("On", OnOff::On as usize), - ].to_vec(), - [].to_vec() + (MENU.mash_in_neutral as usize & OnOff::On as usize) != 0 ); + let data = tpl.render(&overall_menu); - - 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 data = render_menu(); + let fname = "index.html"; + let params = MENU.to_url_params(); let response = Webpage::new() .background(Background::BlurredScreenshot) - .file("index.html", &data) .htdocs_dir("contents") .boot_display(BootDisplay::BlurredScreenshot) .boot_icon(true) + .start_page(&format!("{}{}", fname, params)) .open() .unwrap(); diff --git a/src/templates/menu.html b/src/templates/menu.html index c44dd16..1bb499b 100644 --- a/src/templates/menu.html +++ b/src/templates/menu.html @@ -1,99 +1,108 @@ - - - - Document - - - - - - - + .l-qa:last-child .qa.is-opened { + margin-bottom: 0px; + } - - - - - - - + /* Fade icons slightly */ + img.question-icon { + opacity: 75%; + } - - - - {{/sub_menus}} + {{/sub_menus}} - - {{#sub_menus}} - {{#sliders}} - - {{/sliders}} - {{/sub_menus}} - + {{/sliders}} + {{/sub_menus}} + - + function goBackHook() { + // If any submenus are open, close them + // Otherwise if all submenus are closed, exit the menu and return to the game + if ($(".qa.is-opened").length == 0) { + // If all submenus are closed, exit and return through localhost + $('.is-focused').addClass('is-pause-anim') + $('#ret-button').addClass('is-focus') - \ No newline at end of file + disabledOtherLink() + + playSound('cancel') + + fadeOutPage(function () { + window.history.back() + }) + + + var url = "http://localhost/" + + var settings = []; + + // Collect settings for toggles + $("ul.l-grid").each(function () { + var section = this.id; + var val = ""; + + var children = this.children; + for (var i = 0; i < children.length; i++) { + var child = children[i]; + if (child.innerHTML.includes("is-appear")) { + val += child.getAttribute("val") + ","; + } + } + + settings.push({ + name: section, + value: val + }); + }); + + // Collect settings for OnOffs + $("div.onoff").each(function () { + var section = this.id; + var val = ""; + if (this.innerHTML.includes("is-appear")) { + val = "1" + } else { + val = "0" + } + settings.push({ + name: section, + value: val + }); + }); + + location.href = url + "?" + decodeURIComponent($.param(settings)); + } else { + // Close any open submenus + $(".qa.is-opened").each(function () { openAnswer(this); }); + } + } + + function clickToggle(e) { + var toggleImage = e.children[0]; + if (toggleImage.innerHTML.includes("is-appear")) { + toggleImage.innerHTML = toggleImage.innerHTML.replace("is-appear", "is-hidden"); + } else { + toggleImage.innerHTML = toggleImage.innerHTML.replace("is-hidden", "is-appear"); + } + } + + function getParams(url) { + var regex = /[?&]([^=#]+)=([^&#]*)/g, + params = {}, + match; + while(match = regex.exec(url)) { + params[match[1]] = match[2]; + } + return params; + } + + function setSettings() { + // Get settings from the URL GET parameters + const settings = getParams(document.URL); + + // Set Toggles + $("ul.l-grid").each(function () { + var section = this.id; + var section_setting = decodeURIComponent(settings[section]); + + var children = $(this).children("li"); + for (var i = 0; i < children.length; i++) { + var child = children[i]; + var e = $(child).find("img.toggle")[0]; + if (section_setting.split(",").includes(child.getAttribute("val"))) { + e.classList.add("is-appear"); + e.classList.remove("is-hidden"); + } else { + e.classList.remove("is-appear"); + e.classList.add("is-hidden"); + }; + } + }); + + // Set OnOffs + $("div.onoff").each(function () { + var section = this.id; + var section_setting = decodeURIComponent(settings[section]); + var e = $(this).find("img.toggle")[0]; + if (section_setting == "1") { + e.classList.add("is-appear"); + e.classList.remove("is-hidden"); + } else { + e.classList.remove("is-appear"); + e.classList.add("is-hidden"); + } + }); + } + + + + + diff --git a/src/test.rs b/src/test.rs index 72420d9..a2c3c30 100644 --- a/src/test.rs +++ b/src/test.rs @@ -4,17 +4,17 @@ /// This will run and render the default menu in your default HTML opening program, ideally Chrome. #[test] -fn render_menu() { +fn write_menu() { unsafe { use std::process::Command; - use crate::common::menu::render_menu; + use crate::common::menu::write_menu; let folder_path = "../contents.htdocs"; let path = "../contents.htdocs/index.html"; assert!(std::path::Path::new(folder_path).exists(), "Needs required folder: ../contents.htdocs!"); - std::fs::write(path, render_menu()).unwrap(); + std::fs::write(path, write_menu()).unwrap(); let (cmd, args) = if wsl::is_wsl() || cfg!(target_os = "windows") { ("cmd.exe", ["/C", "start", path]) diff --git a/src/training/mod.rs b/src/training/mod.rs index 1f28639..9dab3dd 100644 --- a/src/training/mod.rs +++ b/src/training/mod.rs @@ -1,4 +1,4 @@ -use crate::common::{is_training_mode, FIGHTER_MANAGER_ADDR, STAGE_MANAGER_ADDR}; +use crate::common::{is_training_mode, menu, FIGHTER_MANAGER_ADDR, STAGE_MANAGER_ADDR}; use crate::hitbox_visualizer; use skyline::nn::ro::LookupSymbol; use skyline::nn::hid::*; @@ -387,4 +387,8 @@ pub fn training_mods() { fast_fall::init(); mash::init(); ledge::init(); + + println!("[Training Modpack] Writing menu file index.html"); + unsafe { menu::write_menu(); } + println!("[Training Modpack] Wrote menu file."); }