mirror of
https://github.com/jugeeya/UltimateTrainingModpack.git
synced 2024-11-20 00:46:34 +00:00
Quick Menu + Ryujinx Compatibility (#313)
* Initial commit * Format Rust code using rustfmt * Add back fs calls * working with drawing * wow we're almost there * multi-lists working, selection within tui working * working with tabs * working with smash * amend warnings, fix menu actually saving inputs * small refactors * Fully working! * Fix warnings Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
parent
c6c4105fc3
commit
e4e2de0a79
17 changed files with 1916 additions and 774 deletions
1
.github/workflows/rust.yml
vendored
1
.github/workflows/rust.yml
vendored
|
@ -96,6 +96,7 @@ jobs:
|
||||||
cp libparam_hook.nro ${{env.SMASH_PLUGIN_DIR}}/libparam_hook.nro
|
cp libparam_hook.nro ${{env.SMASH_PLUGIN_DIR}}/libparam_hook.nro
|
||||||
cp libnro_hook.nro ${{env.SMASH_PLUGIN_DIR}}/libnro_hook.nro
|
cp libnro_hook.nro ${{env.SMASH_PLUGIN_DIR}}/libnro_hook.nro
|
||||||
cp libnn_hid_hook.nro ${{env.SMASH_PLUGIN_DIR}}/libnn_hid_hook.nro
|
cp libnn_hid_hook.nro ${{env.SMASH_PLUGIN_DIR}}/libnn_hid_hook.nro
|
||||||
|
cp libtraining_modpack_menu.nro ${{env.SMASH_PLUGIN_DIR}}/libtraining_modpack_menu.nro
|
||||||
cp -r static/* ${{env.SMASH_WEB_DIR}}
|
cp -r static/* ${{env.SMASH_WEB_DIR}}
|
||||||
rm ${{env.SMASH_WEB_DIR}}/fonts -r
|
rm ${{env.SMASH_WEB_DIR}}/fonts -r
|
||||||
zip -r training_modpack_beta.zip atmosphere
|
zip -r training_modpack_beta.zip atmosphere
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -3,7 +3,7 @@
|
||||||
**/*.pyc
|
**/*.pyc
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
|
||||||
TrainingModpackOverlay/build/
|
.idea/
|
||||||
|
|
||||||
*.ovl
|
*.ovl
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ minreq = { version = "=2.2.1", features = ["https", "json-using-serde"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
training_mod_consts = { path = "training_mod_consts" }
|
training_mod_consts = { path = "training_mod_consts" }
|
||||||
|
training_mod_tui = { path = "training_mod_tui" }
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
ring = { git = "https://github.com/skyline-rs/ring", branch = "0.16.20" }
|
ring = { git = "https://github.com/skyline-rs/ring", branch = "0.16.20" }
|
||||||
|
|
17
ryujinx_build.sh
Normal file
17
ryujinx_build.sh
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# Obviously adjust these based on your paths
|
||||||
|
RYUJINX_APPLICATION_PATH="/mnt/c/Users/Jdsam/Downloads/ryujinx-Release-1.0.0+ba3ae74-win_x64/Ryujinx.exe"
|
||||||
|
SMASH_APPLICATION_PATH="C:\Users\Jdsam\Downloads\Super Smash Bros. Ultimate (World) (En,Ja,Fr,De,Es,It,Nl,Zh-Hant,Zh-Hans,Ko,Ru)\Super Smash Bros. Ultimate (World) (En,Ja,Fr,De,Es,It,Nl,Zh-Hant,Zh-Hans,Ko,Ru).xci"
|
||||||
|
RYUJINX_SMASH_SKYLINE_PLUGINS_PATH="/mnt/c/Users/Jdsam/AppData/Roaming/Ryujinx/mods/contents/01006a800016e000/romfs/skyline/plugins"
|
||||||
|
|
||||||
|
# Build with release feature
|
||||||
|
cargo skyline build --release
|
||||||
|
|
||||||
|
# Copy over to plugins path
|
||||||
|
cp target/aarch64-skyline-switch/release/libtraining_modpack.nro $RYUJINX_SMASH_SKYLINE_PLUGINS_PATH
|
||||||
|
|
||||||
|
# Run Ryujinx
|
||||||
|
$RYUJINX_APPLICATION_PATH "${SMASH_APPLICATION_PATH}"
|
||||||
|
|
||||||
|
# Here, you can run `cargo skyline set-ip {IP address...}; cargo skyline listen` for logs
|
|
@ -1,17 +1,18 @@
|
||||||
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 ramhorns::{Content, Template};
|
use crate::common::consts::get_menu_from_url;
|
||||||
|
use ramhorns::Template;
|
||||||
use skyline::info::get_program_id;
|
use skyline::info::get_program_id;
|
||||||
use skyline_web::{Background, BootDisplay, Webpage};
|
use skyline_web::{Background, BootDisplay, Webpage};
|
||||||
use smash::lib::lua_const::*;
|
use smash::lib::lua_const::*;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::ops::BitOr;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use strum::IntoEnumIterator;
|
use crate::mkdir;
|
||||||
|
|
||||||
static mut FRAME_COUNTER_INDEX: usize = 0;
|
static mut 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 fn init() {
|
pub fn init() {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -20,282 +21,6 @@ pub fn init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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,
|
|
||||||
is_single_option: Option<bool>,
|
|
||||||
help_text: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
help_text: &'a str,
|
|
||||||
) {
|
|
||||||
let mut sub_menu = SubMenu {
|
|
||||||
title,
|
|
||||||
id,
|
|
||||||
toggles: Vec::new(),
|
|
||||||
sliders: Vec::new(),
|
|
||||||
onoffselector: Vec::new(),
|
|
||||||
index: self.max_idx() + 1,
|
|
||||||
check_against,
|
|
||||||
is_single_option: Some(true),
|
|
||||||
help_text,
|
|
||||||
};
|
|
||||||
|
|
||||||
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,
|
|
||||||
help_text: &'a str,
|
|
||||||
) {
|
|
||||||
let mut sub_menu = SubMenu {
|
|
||||||
title,
|
|
||||||
id,
|
|
||||||
toggles: Vec::new(),
|
|
||||||
sliders: Vec::new(),
|
|
||||||
onoffselector: Vec::new(),
|
|
||||||
index: self.max_idx() + 1,
|
|
||||||
check_against,
|
|
||||||
is_single_option: None,
|
|
||||||
help_text,
|
|
||||||
};
|
|
||||||
|
|
||||||
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,
|
|
||||||
help_text: &'a str,
|
|
||||||
) {
|
|
||||||
let mut sub_menu = SubMenu {
|
|
||||||
title,
|
|
||||||
id,
|
|
||||||
toggles: Vec::new(),
|
|
||||||
sliders: Vec::new(),
|
|
||||||
onoffselector: Vec::new(),
|
|
||||||
index: self.max_idx() + 1,
|
|
||||||
check_against,
|
|
||||||
is_single_option: None,
|
|
||||||
help_text,
|
|
||||||
};
|
|
||||||
|
|
||||||
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, $help_text:literal) => {
|
|
||||||
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>],
|
|
||||||
[<$id _vals>],
|
|
||||||
DEFAULT_MENU.$id.bits() as usize,
|
|
||||||
stringify!($help_text),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! add_single_option_submenu {
|
|
||||||
($menu:ident, $title:literal, $id:ident, $e:ty, $help_text:literal) => {
|
|
||||||
paste::paste!{
|
|
||||||
let mut [<$id _toggles>] = Vec::new();
|
|
||||||
for val in [<$e>]::iter() {
|
|
||||||
[<$id _toggles>].push((val.as_str().unwrap_or(""), val as usize));
|
|
||||||
}
|
|
||||||
|
|
||||||
$menu.add_sub_menu(
|
|
||||||
$title,
|
|
||||||
stringify!($id),
|
|
||||||
MENU.$id as usize,
|
|
||||||
[<$id _toggles>],
|
|
||||||
[].to_vec(),
|
|
||||||
DEFAULT_MENU.$id as usize,
|
|
||||||
stringify!($help_text),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! add_onoff_submenu {
|
|
||||||
($menu:ident, $title:literal, $id:ident, $help_text:literal) => {
|
|
||||||
paste::paste! {
|
|
||||||
$menu.add_sub_menu_onoff(
|
|
||||||
$title,
|
|
||||||
stringify!($id),
|
|
||||||
MENU.$id as usize,
|
|
||||||
(MENU.$id as usize & OnOff::On as usize) != 0,
|
|
||||||
DEFAULT_MENU.$id as usize,
|
|
||||||
stringify!($help_text),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_menu_from_url(mut menu: TrainingModpackMenu, s: &str) -> 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 toggle = toggle_value_split[0];
|
|
||||||
if toggle.is_empty() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let toggle_vals = toggle_value_split[1];
|
|
||||||
|
|
||||||
let bitwise_or = <u32 as BitOr<u32>>::bitor;
|
|
||||||
let bits = toggle_vals
|
|
||||||
.split(',')
|
|
||||||
.filter(|val| !val.is_empty())
|
|
||||||
.map(|val| val.parse().unwrap())
|
|
||||||
.fold(0, bitwise_or);
|
|
||||||
|
|
||||||
menu.set(toggle, bits);
|
|
||||||
}
|
|
||||||
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 {
|
||||||
// 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) {
|
||||||
|
@ -318,255 +43,7 @@ pub unsafe fn menu_condition(module_accessor: &mut smash::app::BattleObjectModul
|
||||||
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 mut overall_menu = Menu {
|
let overall_menu = get_menu();
|
||||||
sub_menus: Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Toggle/bitflag menus
|
|
||||||
add_bitflag_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Mash Toggles",
|
|
||||||
mash_state,
|
|
||||||
Action,
|
|
||||||
"Mash Toggles: Actions to be performed as soon as possible"
|
|
||||||
);
|
|
||||||
add_bitflag_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Followup Toggles",
|
|
||||||
follow_up,
|
|
||||||
Action,
|
|
||||||
"Followup Toggles: Actions to be performed after the Mash option"
|
|
||||||
);
|
|
||||||
add_bitflag_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Attack Angle",
|
|
||||||
attack_angle,
|
|
||||||
AttackAngle,
|
|
||||||
"Attack Angle: For attacks that can be angled, such as some forward tilts"
|
|
||||||
);
|
|
||||||
|
|
||||||
add_bitflag_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Ledge Options",
|
|
||||||
ledge_state,
|
|
||||||
LedgeOption,
|
|
||||||
"Ledge Options: Actions to be taken when on the ledge"
|
|
||||||
);
|
|
||||||
add_bitflag_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Ledge Delay",
|
|
||||||
ledge_delay,
|
|
||||||
LongDelay,
|
|
||||||
"Ledge Delay: How many frames to delay the ledge option"
|
|
||||||
);
|
|
||||||
add_bitflag_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Tech Options",
|
|
||||||
tech_state,
|
|
||||||
TechFlags,
|
|
||||||
"Tech Options: Actions to take when slammed into a hard surface"
|
|
||||||
);
|
|
||||||
add_bitflag_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Miss Tech Options",
|
|
||||||
miss_tech_state,
|
|
||||||
MissTechFlags,
|
|
||||||
"Miss Tech Options: Actions to take after missing a tech"
|
|
||||||
);
|
|
||||||
add_bitflag_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Defensive Options",
|
|
||||||
defensive_state,
|
|
||||||
Defensive,
|
|
||||||
"Defensive Options: Actions to take after a ledge option, tech option, or miss tech option"
|
|
||||||
);
|
|
||||||
|
|
||||||
add_bitflag_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Aerial Delay",
|
|
||||||
aerial_delay,
|
|
||||||
Delay,
|
|
||||||
"Aerial Delay: How long to delay a Mash aerial attack"
|
|
||||||
);
|
|
||||||
add_bitflag_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"OoS Offset",
|
|
||||||
oos_offset,
|
|
||||||
Delay,
|
|
||||||
"OoS Offset: How many times the CPU shield can be hit before performing a Mash option"
|
|
||||||
);
|
|
||||||
add_bitflag_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Reaction Time",
|
|
||||||
reaction_time,
|
|
||||||
Delay,
|
|
||||||
"Reaction Time: How many frames to delay before performing an option out of shield"
|
|
||||||
);
|
|
||||||
|
|
||||||
add_bitflag_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Fast Fall",
|
|
||||||
fast_fall,
|
|
||||||
BoolFlag,
|
|
||||||
"Fast Fall: Should the CPU fastfall during a jump"
|
|
||||||
);
|
|
||||||
add_bitflag_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Fast Fall Delay",
|
|
||||||
fast_fall_delay,
|
|
||||||
Delay,
|
|
||||||
"Fast Fall Delay: How many frames the CPU should delay their fastfall"
|
|
||||||
);
|
|
||||||
add_bitflag_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Falling Aerials",
|
|
||||||
falling_aerials,
|
|
||||||
BoolFlag,
|
|
||||||
"Falling Aerials: Should aerials be performed when rising or when falling"
|
|
||||||
);
|
|
||||||
add_bitflag_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Full Hop",
|
|
||||||
full_hop,
|
|
||||||
BoolFlag,
|
|
||||||
"Full Hop: Should the CPU perform a full hop or a short hop"
|
|
||||||
);
|
|
||||||
|
|
||||||
add_bitflag_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Shield Tilt",
|
|
||||||
shield_tilt,
|
|
||||||
Direction,
|
|
||||||
"Shield Tilt: Direction to tilt the shield"
|
|
||||||
);
|
|
||||||
add_bitflag_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"DI Direction",
|
|
||||||
di_state,
|
|
||||||
Direction,
|
|
||||||
"DI Direction: Direction to angle the directional influence during hitlag"
|
|
||||||
);
|
|
||||||
add_bitflag_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"SDI Direction",
|
|
||||||
sdi_state,
|
|
||||||
Direction,
|
|
||||||
"SDI Direction: Direction to angle the smash directional influence during hitlag"
|
|
||||||
);
|
|
||||||
add_bitflag_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Airdodge Direction",
|
|
||||||
air_dodge_dir,
|
|
||||||
Direction,
|
|
||||||
"Airdodge Direction: Direction to angle airdodges"
|
|
||||||
);
|
|
||||||
|
|
||||||
add_single_option_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"SDI Strength",
|
|
||||||
sdi_strength,
|
|
||||||
SdiStrength,
|
|
||||||
"SDI Strength: Relative strength of the smash directional influence inputs"
|
|
||||||
);
|
|
||||||
add_single_option_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Shield Toggles",
|
|
||||||
shield_state,
|
|
||||||
Shield,
|
|
||||||
"Shield Toggles: CPU Shield Behavior"
|
|
||||||
);
|
|
||||||
add_single_option_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Mirroring",
|
|
||||||
save_state_mirroring,
|
|
||||||
SaveStateMirroring,
|
|
||||||
"Mirroring: Flips save states in the left-right direction across the stage center"
|
|
||||||
);
|
|
||||||
add_bitflag_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Throw Options",
|
|
||||||
throw_state,
|
|
||||||
ThrowOption,
|
|
||||||
"Throw Options: Throw to be performed when a grab is landed"
|
|
||||||
);
|
|
||||||
add_bitflag_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Throw Delay",
|
|
||||||
throw_delay,
|
|
||||||
MedDelay,
|
|
||||||
"Throw Delay: How many frames to delay the throw option"
|
|
||||||
);
|
|
||||||
add_bitflag_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Pummel Delay",
|
|
||||||
pummel_delay,
|
|
||||||
MedDelay,
|
|
||||||
"Pummel Delay: How many frames after a grab to wait before starting to pummel"
|
|
||||||
);
|
|
||||||
add_bitflag_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Buff Options",
|
|
||||||
buff_state,
|
|
||||||
BuffOption,
|
|
||||||
"Buff Options: Buff(s) to be applied to respective character when loading save states"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Slider menus
|
|
||||||
overall_menu.add_sub_menu(
|
|
||||||
"Input Delay",
|
|
||||||
"input_delay",
|
|
||||||
// unnecessary for slider?
|
|
||||||
MENU.input_delay as usize,
|
|
||||||
[
|
|
||||||
("0", 0),
|
|
||||||
("1", 1),
|
|
||||||
("2", 2),
|
|
||||||
("3", 3),
|
|
||||||
("4", 4),
|
|
||||||
("5", 5),
|
|
||||||
("6", 6),
|
|
||||||
("7", 7),
|
|
||||||
("8", 8),
|
|
||||||
("9", 9),
|
|
||||||
("10", 10),
|
|
||||||
]
|
|
||||||
.to_vec(),
|
|
||||||
[].to_vec(), //(0, 10, MENU.input_delay as usize)
|
|
||||||
DEFAULT_MENU.input_delay as usize,
|
|
||||||
stringify!("Input Delay: Frames to delay player inputs by"),
|
|
||||||
);
|
|
||||||
|
|
||||||
add_onoff_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Save States",
|
|
||||||
save_state_enable,
|
|
||||||
"Save States: Enable save states! Save a state with Grab+Down Taunt, load it with Grab+Up Taunt."
|
|
||||||
);
|
|
||||||
add_onoff_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Save Damage",
|
|
||||||
save_damage,
|
|
||||||
"Save Damage: Should save states retain player/CPU damage"
|
|
||||||
);
|
|
||||||
add_onoff_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Hitbox Visualization",
|
|
||||||
hitbox_vis,
|
|
||||||
"Hitbox Visualization: Should hitboxes be displayed, hiding other visual effects"
|
|
||||||
);
|
|
||||||
add_onoff_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Stage Hazards",
|
|
||||||
stage_hazards,
|
|
||||||
"Stage Hazards: Should stage hazards be present"
|
|
||||||
);
|
|
||||||
add_onoff_submenu!(overall_menu, "Frame Advantage", frame_advantage, "Frame Advantage: Display the time difference between when the player is actionable and the CPU is actionable");
|
|
||||||
add_onoff_submenu!(
|
|
||||||
overall_menu,
|
|
||||||
"Mash In Neutral",
|
|
||||||
mash_in_neutral,
|
|
||||||
"Mash In Neutral: Should Mash options be performed repeatedly or only when the CPU is hit"
|
|
||||||
);
|
|
||||||
|
|
||||||
let data = tpl.render(&overall_menu);
|
let data = tpl.render(&overall_menu);
|
||||||
|
|
||||||
|
@ -574,41 +51,45 @@ pub unsafe fn write_menu() {
|
||||||
// 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 path = Path::new("sd:/atmosphere/contents")
|
let menu_dir_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));
|
||||||
|
|
||||||
|
let menu_html_path = menu_dir_path
|
||||||
.join("training_menu.html");
|
.join("training_menu.html");
|
||||||
fs::write(path, data).unwrap();
|
|
||||||
|
mkdir(menu_dir_path.to_str().unwrap().as_bytes().as_ptr(), 777);
|
||||||
|
let write_resp = fs::write(menu_html_path, data);
|
||||||
|
if write_resp.is_err() {
|
||||||
|
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 fn spawn_menu() {
|
pub fn set_menu_from_url(orig_last_url: &str) {
|
||||||
unsafe {
|
|
||||||
frame_counter::reset_frame_count(FRAME_COUNTER_INDEX);
|
|
||||||
frame_counter::start_counting(FRAME_COUNTER_INDEX);
|
|
||||||
}
|
|
||||||
|
|
||||||
let fname = "training_menu.html";
|
|
||||||
let params = unsafe { MENU.to_url_params() };
|
|
||||||
let page_response = Webpage::new()
|
|
||||||
.background(Background::BlurredScreenshot)
|
|
||||||
.htdocs_dir("training_modpack")
|
|
||||||
.boot_display(BootDisplay::BlurredScreenshot)
|
|
||||||
.boot_icon(true)
|
|
||||||
.start_page(&format!("{}{}", fname, params))
|
|
||||||
.open()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let orig_last_url = page_response.get_last_url().unwrap();
|
|
||||||
let last_url = &orig_last_url.replace("&save_defaults=1", "");
|
let last_url = &orig_last_url.replace("&save_defaults=1", "");
|
||||||
unsafe {
|
unsafe {
|
||||||
MENU = get_menu_from_url(MENU, last_url);
|
MENU = get_menu_from_url(MENU, last_url);
|
||||||
|
|
||||||
|
if MENU.quick_menu == OnOff::Off {
|
||||||
|
let is_emulator = skyline::hooks::getRegionAddress(skyline::hooks::Region::Text) as u64 == 0x8004000;
|
||||||
|
if is_emulator {
|
||||||
|
skyline::error::show_error(
|
||||||
|
0x69,
|
||||||
|
"Cannot use web menu on emulator.\n",
|
||||||
|
"Only the quick menu is runnable via emulator currently.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
MENU.quick_menu = OnOff::On;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if last_url.len() != orig_last_url.len() {
|
if last_url.len() != orig_last_url.len() {
|
||||||
// Save as default
|
// Save as default
|
||||||
unsafe {
|
unsafe {
|
||||||
DEFAULT_MENU = get_menu_from_url(DEFAULT_MENU, last_url);
|
DEFAULT_MENU = MENU;
|
||||||
write_menu();
|
write_menu();
|
||||||
}
|
}
|
||||||
let menu_defaults_conf_path = "sd:/TrainingModpack/training_modpack_menu_defaults.conf";
|
let menu_defaults_conf_path = "sd:/TrainingModpack/training_modpack_menu_defaults.conf";
|
||||||
|
@ -621,3 +102,152 @@ pub fn spawn_menu() {
|
||||||
EVENT_QUEUE.push(Event::menu_open(last_url.to_string()));
|
EVENT_QUEUE.push(Event::menu_open(last_url.to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn spawn_menu() {
|
||||||
|
unsafe {
|
||||||
|
frame_counter::reset_frame_count(FRAME_COUNTER_INDEX);
|
||||||
|
frame_counter::start_counting(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";
|
||||||
|
let params = unsafe { MENU.to_url_params() };
|
||||||
|
let page_response = Webpage::new()
|
||||||
|
.background(Background::BlurredScreenshot)
|
||||||
|
.htdocs_dir("training_modpack")
|
||||||
|
.boot_display(BootDisplay::BlurredScreenshot)
|
||||||
|
.boot_icon(true)
|
||||||
|
.start_page(&format!("{}{}", fname, params))
|
||||||
|
.open()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let orig_last_url = page_response.get_last_url().unwrap();
|
||||||
|
|
||||||
|
set_menu_from_url(orig_last_url);
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
QUICK_MENU_ACTIVE = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use skyline::nn::hid::NpadGcState;
|
||||||
|
|
||||||
|
pub struct ButtonPresses {
|
||||||
|
pub a: ButtonPress,
|
||||||
|
pub b: ButtonPress,
|
||||||
|
pub zr: ButtonPress,
|
||||||
|
pub zl: ButtonPress,
|
||||||
|
pub left: ButtonPress,
|
||||||
|
pub right: ButtonPress,
|
||||||
|
pub up: ButtonPress,
|
||||||
|
pub down: ButtonPress
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ButtonPress {
|
||||||
|
pub is_pressed: bool,
|
||||||
|
pub lockout_frames: usize
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ButtonPress {
|
||||||
|
pub fn default() -> ButtonPress {
|
||||||
|
ButtonPress{
|
||||||
|
is_pressed: false,
|
||||||
|
lockout_frames: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_press(&mut self) -> bool {
|
||||||
|
if self.is_pressed {
|
||||||
|
self.is_pressed = false;
|
||||||
|
if self.lockout_frames == 0 {
|
||||||
|
self.lockout_frames = 15;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.lockout_frames > 0 {
|
||||||
|
self.lockout_frames -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ButtonPresses {
|
||||||
|
pub fn default() -> ButtonPresses {
|
||||||
|
ButtonPresses{
|
||||||
|
a: ButtonPress::default(),
|
||||||
|
b: ButtonPress::default(),
|
||||||
|
zr: ButtonPress::default(),
|
||||||
|
zl: ButtonPress::default(),
|
||||||
|
left: ButtonPress::default(),
|
||||||
|
right: ButtonPress::default(),
|
||||||
|
up: ButtonPress::default(),
|
||||||
|
down: ButtonPress::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static mut BUTTON_PRESSES : ButtonPresses = ButtonPresses{
|
||||||
|
a: ButtonPress{is_pressed: false, lockout_frames: 0},
|
||||||
|
b: ButtonPress{is_pressed: false, lockout_frames: 0},
|
||||||
|
zr: ButtonPress{is_pressed: false, lockout_frames: 0},
|
||||||
|
zl: ButtonPress{is_pressed: false, lockout_frames: 0},
|
||||||
|
left: ButtonPress{is_pressed: false, lockout_frames: 0},
|
||||||
|
right: ButtonPress{is_pressed: false, lockout_frames: 0},
|
||||||
|
up: ButtonPress{is_pressed: false, lockout_frames: 0},
|
||||||
|
down: ButtonPress{is_pressed: false, lockout_frames: 0},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn handle_get_npad_state(state: *mut NpadGcState, _controller_id: *const u32) {
|
||||||
|
unsafe {
|
||||||
|
if menu::QUICK_MENU_ACTIVE {
|
||||||
|
// TODO: This should make more sense, look into.
|
||||||
|
// BUTTON_PRESSES.a.is_pressed = (*state).Buttons & (1 << 0) > 0;
|
||||||
|
// BUTTON_PRESSES.b.is_pressed = (*state).Buttons & (1 << 1) > 0;
|
||||||
|
// BUTTON_PRESSES.zl.is_pressed = (*state).Buttons & (1 << 8) > 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.right.is_pressed = (*state).Buttons & ((1 << 14) | (1 << 18)) > 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;
|
||||||
|
if (*state).Buttons & (1 << 0) > 0 {
|
||||||
|
BUTTON_PRESSES.a.is_pressed = true;
|
||||||
|
}
|
||||||
|
if (*state).Buttons & (1 << 1) > 0 {
|
||||||
|
BUTTON_PRESSES.b.is_pressed = true;
|
||||||
|
}
|
||||||
|
if (*state).Buttons & (1 << 8) > 0 {
|
||||||
|
BUTTON_PRESSES.zl.is_pressed = true;
|
||||||
|
}
|
||||||
|
if (*state).Buttons & (1 << 9) > 0 {
|
||||||
|
BUTTON_PRESSES.zr.is_pressed = true;
|
||||||
|
}
|
||||||
|
if (*state).Buttons & ((1 << 12) | (1 << 16)) > 0 {
|
||||||
|
BUTTON_PRESSES.left.is_pressed = true;
|
||||||
|
}
|
||||||
|
if (*state).Buttons & ((1 << 14) | (1 << 18)) > 0 {
|
||||||
|
BUTTON_PRESSES.right.is_pressed = true;
|
||||||
|
}
|
||||||
|
if (*state).Buttons & ((1 << 15) | (1 << 19)) > 0 {
|
||||||
|
BUTTON_PRESSES.down.is_pressed = true;
|
||||||
|
}
|
||||||
|
if (*state).Buttons & ((1 << 13) | (1 << 17)) > 0 {
|
||||||
|
BUTTON_PRESSES.up.is_pressed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're here, remove all other Npad presses...
|
||||||
|
// Should we exclude the home button?
|
||||||
|
(*state) = NpadGcState::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,44 +8,9 @@ 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 static BASE_MENU: consts::TrainingModpackMenu = consts::TrainingModpackMenu {
|
pub use crate::common::consts::MENU;
|
||||||
hitbox_vis: OnOff::On,
|
pub static mut DEFAULT_MENU: TrainingModpackMenu = crate::common::consts::DEFAULT_MENU;
|
||||||
stage_hazards: OnOff::Off,
|
pub static mut BASE_MENU: TrainingModpackMenu = unsafe { DEFAULT_MENU };
|
||||||
di_state: Direction::empty(),
|
|
||||||
sdi_state: Direction::empty(),
|
|
||||||
sdi_strength: SdiStrength::Normal,
|
|
||||||
air_dodge_dir: Direction::empty(),
|
|
||||||
mash_state: Action::empty(),
|
|
||||||
follow_up: Action::empty(),
|
|
||||||
attack_angle: AttackAngle::empty(),
|
|
||||||
ledge_state: LedgeOption::all(),
|
|
||||||
ledge_delay: LongDelay::empty(),
|
|
||||||
tech_state: TechFlags::all(),
|
|
||||||
miss_tech_state: MissTechFlags::all(),
|
|
||||||
shield_state: Shield::None,
|
|
||||||
defensive_state: Defensive::all(),
|
|
||||||
oos_offset: Delay::empty(),
|
|
||||||
shield_tilt: Direction::empty(),
|
|
||||||
reaction_time: Delay::empty(),
|
|
||||||
mash_in_neutral: OnOff::Off,
|
|
||||||
fast_fall: BoolFlag::empty(),
|
|
||||||
fast_fall_delay: Delay::empty(),
|
|
||||||
falling_aerials: BoolFlag::empty(),
|
|
||||||
aerial_delay: Delay::empty(),
|
|
||||||
full_hop: BoolFlag::empty(),
|
|
||||||
input_delay: 0,
|
|
||||||
save_damage: OnOff::On,
|
|
||||||
save_state_mirroring: SaveStateMirroring::None,
|
|
||||||
frame_advantage: OnOff::Off,
|
|
||||||
save_state_enable: OnOff::On,
|
|
||||||
throw_state: ThrowOption::NONE,
|
|
||||||
throw_delay: MedDelay::empty(),
|
|
||||||
pummel_delay: MedDelay::empty(),
|
|
||||||
buff_state: BuffOption::empty(),
|
|
||||||
};
|
|
||||||
|
|
||||||
pub static mut DEFAULT_MENU: TrainingModpackMenu = BASE_MENU;
|
|
||||||
pub static mut MENU: TrainingModpackMenu = BASE_MENU;
|
|
||||||
pub static mut FIGHTER_MANAGER_ADDR: usize = 0;
|
pub static mut FIGHTER_MANAGER_ADDR: usize = 0;
|
||||||
pub static mut STAGE_MANAGER_ADDR: usize = 0;
|
pub static mut STAGE_MANAGER_ADDR: usize = 0;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
#![allow(unused_assignments)]
|
#![allow(unused_assignments)]
|
||||||
#![allow(unused_variables)]
|
#![allow(unused_variables)]
|
||||||
use crate::common::{consts::*, *};
|
use crate::common::consts::*;
|
||||||
use skyline::error::show_error;
|
use skyline::error::show_error;
|
||||||
use skyline::hook;
|
use skyline::hook;
|
||||||
use skyline::hooks::A64InlineHook;
|
use skyline::hooks::A64InlineHook;
|
||||||
|
|
349
src/lib.rs
349
src/lib.rs
|
@ -1,135 +1,214 @@
|
||||||
#![feature(proc_macro_hygiene)]
|
#![feature(proc_macro_hygiene)]
|
||||||
#![feature(with_options)]
|
#![feature(with_options)]
|
||||||
#![feature(const_mut_refs)]
|
#![feature(const_mut_refs)]
|
||||||
#![feature(exclusive_range_pattern)]
|
#![feature(exclusive_range_pattern)]
|
||||||
#![feature(once_cell)]
|
#![feature(once_cell)]
|
||||||
#![allow(
|
#![allow(
|
||||||
clippy::borrow_interior_mutable_const,
|
clippy::borrow_interior_mutable_const,
|
||||||
clippy::not_unsafe_ptr_arg_deref,
|
clippy::not_unsafe_ptr_arg_deref,
|
||||||
clippy::missing_safety_doc,
|
clippy::missing_safety_doc,
|
||||||
clippy::wrong_self_convention
|
clippy::wrong_self_convention
|
||||||
)]
|
)]
|
||||||
|
|
||||||
pub mod common;
|
pub mod common;
|
||||||
mod hazard_manager;
|
mod hazard_manager;
|
||||||
mod hitbox_visualizer;
|
mod hitbox_visualizer;
|
||||||
mod training;
|
mod training;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test;
|
mod test;
|
||||||
|
|
||||||
use crate::common::*;
|
use crate::common::*;
|
||||||
use crate::events::{Event, EVENT_QUEUE};
|
use crate::events::{Event, EVENT_QUEUE};
|
||||||
use crate::menu::get_menu_from_url;
|
use crate::common::consts::get_menu_from_url;
|
||||||
|
|
||||||
use skyline::libc::mkdir;
|
use skyline::libc::{c_char, mkdir};
|
||||||
use skyline::nro::{self, NroInfo};
|
use skyline::nro::{self, NroInfo};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
|
|
||||||
fn nro_main(nro: &NroInfo<'_>) {
|
fn nro_main(nro: &NroInfo<'_>) {
|
||||||
if nro.module.isLoaded {
|
if nro.module.isLoaded {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if nro.name == "common" {
|
if nro.name == "common" {
|
||||||
skyline::install_hooks!(
|
skyline::install_hooks!(
|
||||||
training::shield::handle_sub_guard_cont,
|
training::shield::handle_sub_guard_cont,
|
||||||
training::directional_influence::handle_correct_damage_vector_common,
|
training::directional_influence::handle_correct_damage_vector_common,
|
||||||
training::sdi::process_hit_stop_delay,
|
training::sdi::process_hit_stop_delay,
|
||||||
training::tech::handle_change_status,
|
training::tech::handle_change_status,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! c_str {
|
extern "C" {
|
||||||
($l:tt) => {
|
#[link_name = "render_text_to_screen"]
|
||||||
[$l.as_bytes(), "\u{0}".as_bytes()].concat().as_ptr();
|
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);
|
||||||
#[skyline::main(name = "training_modpack")]
|
}
|
||||||
pub fn main() {
|
|
||||||
macro_rules! log {
|
macro_rules! c_str {
|
||||||
($($arg:tt)*) => {
|
($l:tt) => {
|
||||||
print!("{}", "[Training Modpack] ".green());
|
[$l.as_bytes(), "\u{0}".as_bytes()].concat().as_ptr();
|
||||||
println!($($arg)*);
|
};
|
||||||
};
|
}
|
||||||
}
|
|
||||||
|
pub fn render_text_to_screen(s: &str) {
|
||||||
log!("Initialized.");
|
unsafe {
|
||||||
unsafe {
|
render_text_to_screen_cstr(c_str!(s));
|
||||||
EVENT_QUEUE.push(Event::smash_open());
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hitbox_visualizer::hitbox_visualization();
|
#[skyline::main(name = "training_modpack")]
|
||||||
hazard_manager::hazard_manager();
|
pub fn main() {
|
||||||
training::training_mods();
|
macro_rules! log {
|
||||||
nro::add_hook(nro_main).unwrap();
|
($($arg:tt)*) => {
|
||||||
|
print!("{}", "[Training Modpack] ".green());
|
||||||
unsafe {
|
println!($($arg)*);
|
||||||
mkdir(c_str!("sd:/TrainingModpack/"), 777);
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let ovl_path = "sd:/switch/.overlays/ovlTrainingModpack.ovl";
|
log!("Initialized.");
|
||||||
if fs::metadata(ovl_path).is_ok() {
|
unsafe {
|
||||||
log!("Removing ovlTrainingModpack.ovl...");
|
EVENT_QUEUE.push(Event::smash_open());
|
||||||
fs::remove_file(ovl_path).unwrap();
|
}
|
||||||
}
|
|
||||||
|
hitbox_visualizer::hitbox_visualization();
|
||||||
log!("Performing version check...");
|
hazard_manager::hazard_manager();
|
||||||
release::version_check();
|
training::training_mods();
|
||||||
|
nro::add_hook(nro_main).unwrap();
|
||||||
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() {
|
unsafe {
|
||||||
let menu_conf = fs::read(menu_conf_path).unwrap();
|
mkdir(c_str!("sd:/TrainingModpack/"), 777);
|
||||||
if menu_conf.starts_with(b"http://localhost") {
|
}
|
||||||
log!("Previous menu found, loading from training_modpack_menu.conf");
|
|
||||||
unsafe {
|
let ovl_path = "sd:/switch/.overlays/ovlTrainingModpack.ovl";
|
||||||
MENU = get_menu_from_url(MENU, std::str::from_utf8(&menu_conf).unwrap());
|
if fs::metadata(ovl_path).is_ok() {
|
||||||
}
|
log!("Removing ovlTrainingModpack.ovl...");
|
||||||
} else {
|
fs::remove_file(ovl_path).unwrap();
|
||||||
log!("Previous menu found but is invalid.");
|
}
|
||||||
}
|
|
||||||
} else {
|
log!("Performing version check...");
|
||||||
log!("No previous menu file found.");
|
release::version_check();
|
||||||
}
|
|
||||||
|
let menu_conf_path = "sd:/TrainingModpack/training_modpack_menu.conf";
|
||||||
let menu_defaults_conf_path = "sd:/TrainingModpack/training_modpack_menu_defaults.conf";
|
log!("Checking for previous menu in training_modpack_menu.conf...");
|
||||||
log!("Checking for previous menu defaults in training_modpack_menu_defaults.conf...");
|
if fs::metadata(menu_conf_path).is_ok() {
|
||||||
if fs::metadata(menu_defaults_conf_path).is_ok() {
|
let menu_conf = fs::read(menu_conf_path).unwrap();
|
||||||
let menu_defaults_conf = fs::read(menu_defaults_conf_path).unwrap();
|
if menu_conf.starts_with(b"http://localhost") {
|
||||||
if menu_defaults_conf.starts_with(b"http://localhost") {
|
log!("Previous menu found, loading from training_modpack_menu.conf");
|
||||||
log!("Menu defaults found, loading from training_modpack_menu_defaults.conf");
|
unsafe {
|
||||||
unsafe {
|
MENU = get_menu_from_url(MENU, std::str::from_utf8(&menu_conf).unwrap());
|
||||||
DEFAULT_MENU = get_menu_from_url(
|
}
|
||||||
DEFAULT_MENU,
|
} else {
|
||||||
std::str::from_utf8(&menu_defaults_conf).unwrap(),
|
log!("Previous menu found but is invalid.");
|
||||||
);
|
}
|
||||||
crate::menu::write_menu();
|
} else {
|
||||||
}
|
log!("No previous menu file found.");
|
||||||
} else {
|
}
|
||||||
log!("Previous menu defaults found but are invalid.");
|
|
||||||
}
|
let menu_defaults_conf_path = "sd:/TrainingModpack/training_modpack_menu_defaults.conf";
|
||||||
} else {
|
log!("Checking for previous menu defaults in training_modpack_menu_defaults.conf...");
|
||||||
log!("No previous menu defaults found.");
|
if fs::metadata(menu_defaults_conf_path).is_ok() {
|
||||||
}
|
let menu_defaults_conf = fs::read(menu_defaults_conf_path).unwrap();
|
||||||
|
if menu_defaults_conf.starts_with(b"http://localhost") {
|
||||||
std::thread::spawn(|| loop {
|
log!("Menu defaults found, loading from training_modpack_menu_defaults.conf");
|
||||||
std::thread::sleep(std::time::Duration::from_secs(5));
|
unsafe {
|
||||||
unsafe {
|
DEFAULT_MENU = get_menu_from_url(
|
||||||
while let Some(event) = EVENT_QUEUE.pop() {
|
DEFAULT_MENU,
|
||||||
let host = "https://my-project-1511972643240-default-rtdb.firebaseio.com";
|
std::str::from_utf8(&menu_defaults_conf).unwrap(),
|
||||||
let path = format!(
|
);
|
||||||
"/event/{}/device/{}/{}.json",
|
crate::menu::write_menu();
|
||||||
event.event_name, event.device_id, event.event_time
|
}
|
||||||
);
|
} else {
|
||||||
|
log!("Previous menu defaults found but are invalid.");
|
||||||
let url = format!("{}{}", host, path);
|
}
|
||||||
minreq::post(url).with_json(&event).unwrap().send().ok();
|
} else {
|
||||||
}
|
log!("No previous menu defaults found.");
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
std::thread::spawn(|| loop {
|
||||||
|
std::thread::sleep(std::time::Duration::from_secs(10));
|
||||||
|
unsafe {
|
||||||
|
while let Some(event) = EVENT_QUEUE.pop() {
|
||||||
|
let host = "https://my-project-1511972643240-default-rtdb.firebaseio.com";
|
||||||
|
let path = format!(
|
||||||
|
"/event/{}/device/{}/{}.json",
|
||||||
|
event.event_name, event.device_id, event.event_time
|
||||||
|
);
|
||||||
|
|
||||||
|
let url = format!("{}{}", host, path);
|
||||||
|
minreq::post(url).with_json(&event).unwrap().send().ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
std::thread::spawn(|| {
|
||||||
|
std::thread::sleep(std::time::Duration::from_secs(10));
|
||||||
|
let menu;
|
||||||
|
unsafe {
|
||||||
|
menu = crate::common::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();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let mut has_slept_millis = 0;
|
||||||
|
let mut url = String::new();
|
||||||
|
let button_presses = &mut common::menu::BUTTON_PRESSES;
|
||||||
|
loop {
|
||||||
|
button_presses.a.read_press().then(|| app.on_a());
|
||||||
|
button_presses.b.read_press().then(|| {
|
||||||
|
if app.outer_list == false {
|
||||||
|
app.on_b()
|
||||||
|
} else {
|
||||||
|
// Leave menu.
|
||||||
|
menu::QUICK_MENU_ACTIVE = false;
|
||||||
|
crate::menu::set_menu_from_url(url.as_str());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
button_presses.zl.read_press().then(|| app.on_l());
|
||||||
|
button_presses.zl.read_press().then(|| app.on_r());
|
||||||
|
button_presses.left.read_press().then(|| app.on_left());
|
||||||
|
button_presses.right.read_press().then(|| app.on_right());
|
||||||
|
button_presses.up.read_press().then(|| app.on_up());
|
||||||
|
button_presses.down.read_press().then(|| app.on_down());
|
||||||
|
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(16));
|
||||||
|
has_slept_millis += 16;
|
||||||
|
let render_frames = 5;
|
||||||
|
if has_slept_millis > 16 * render_frames {
|
||||||
|
has_slept_millis = 16;
|
||||||
|
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().into_iter().enumerate() {
|
||||||
|
write!(&mut view, "{}", cell.symbol).unwrap();
|
||||||
|
if i % frame_res.area.width as usize == frame_res.area.width as usize - 1 {
|
||||||
|
write!(&mut view, "\n").unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(&mut view, "\n").unwrap();
|
||||||
|
|
||||||
|
if menu::QUICK_MENU_ACTIVE {
|
||||||
|
render_text_to_screen(view.as_str());
|
||||||
|
} else {
|
||||||
|
set_should_display_text_to_screen(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::common::consts::*;
|
use crate::common::consts::*;
|
||||||
use crate::common::*;
|
|
||||||
use crate::is_operation_cpu;
|
use crate::is_operation_cpu;
|
||||||
use crate::training::frame_counter;
|
use crate::training::frame_counter;
|
||||||
use crate::training::handle_add_limit;
|
use crate::training::handle_add_limit;
|
||||||
|
|
|
@ -24,7 +24,7 @@ mod attack_angle;
|
||||||
mod character_specific;
|
mod character_specific;
|
||||||
mod fast_fall;
|
mod fast_fall;
|
||||||
mod full_hop;
|
mod full_hop;
|
||||||
mod input_delay;
|
pub(crate) mod input_delay;
|
||||||
mod input_record;
|
mod input_record;
|
||||||
mod mash;
|
mod mash;
|
||||||
mod reset;
|
mod reset;
|
||||||
|
@ -465,6 +465,7 @@ pub fn training_mods() {
|
||||||
panic!("The NN-HID hook plugin could not be found and is required to add NRO hooks. Make sure libnn_hid_hook.nro is installed.");
|
panic!("The NN-HID hook plugin could not be found and is required to add NRO hooks. Make sure libnn_hid_hook.nro is installed.");
|
||||||
}
|
}
|
||||||
add_nn_hid_hook(input_delay::handle_get_npad_state);
|
add_nn_hid_hook(input_delay::handle_get_npad_state);
|
||||||
|
add_nn_hid_hook(menu::handle_get_npad_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
|
@ -10,4 +10,11 @@ strum_macros = "0.21.0"
|
||||||
num = "0.4.0"
|
num = "0.4.0"
|
||||||
num-derive = "0.3"
|
num-derive = "0.3"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
skyline_smash = { git = "https://github.com/ultimate-research/skyline-smash.git" }
|
ramhorns = "0.12.0"
|
||||||
|
paste = "1.0"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
skyline_smash = { git = "https://github.com/ultimate-research/skyline-smash.git", optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["smash"]
|
||||||
|
smash = ["skyline_smash"]
|
|
@ -5,8 +5,13 @@ extern crate bitflags;
|
||||||
extern crate num_derive;
|
extern crate num_derive;
|
||||||
|
|
||||||
use core::f64::consts::PI;
|
use core::f64::consts::PI;
|
||||||
|
#[cfg(feature = "smash")]
|
||||||
use smash::lib::lua_const::*;
|
use smash::lib::lua_const::*;
|
||||||
use strum_macros::EnumIter;
|
use strum_macros::EnumIter;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use ramhorns::Content;
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
use std::ops::BitOr;
|
||||||
|
|
||||||
// bitflag helper function macro
|
// bitflag helper function macro
|
||||||
macro_rules! extra_bitflag_impls {
|
macro_rules! extra_bitflag_impls {
|
||||||
|
@ -72,8 +77,12 @@ macro_rules! extra_bitflag_impls {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_random_int(max: i32) -> i32 {
|
pub fn get_random_int(_max: i32) -> i32 {
|
||||||
unsafe { smash::app::sv_math::rand(smash::hash40("fighter"), max) }
|
#[cfg(feature = "smash")]
|
||||||
|
unsafe { smash::app::sv_math::rand(smash::hash40("fighter"), _max) }
|
||||||
|
|
||||||
|
#[cfg(not(feature = "smash"))]
|
||||||
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn random_option<T>(arg: &[T]) -> &T {
|
pub fn random_option<T>(arg: &[T]) -> &T {
|
||||||
|
@ -88,8 +97,8 @@ pub fn random_option<T>(arg: &[T]) -> &T {
|
||||||
|
|
||||||
// DI / Left stick
|
// DI / Left stick
|
||||||
bitflags! {
|
bitflags! {
|
||||||
pub struct Direction : u32
|
#[derive(Serialize, Deserialize)]
|
||||||
{
|
pub struct Direction : u32 {
|
||||||
const OUT = 0x1;
|
const OUT = 0x1;
|
||||||
const UP_OUT = 0x2;
|
const UP_OUT = 0x2;
|
||||||
const UP = 0x4;
|
const UP = 0x4;
|
||||||
|
@ -153,6 +162,7 @@ extra_bitflag_impls! {Direction}
|
||||||
|
|
||||||
// Ledge Option
|
// Ledge Option
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct LedgeOption : u32
|
pub struct LedgeOption : u32
|
||||||
{
|
{
|
||||||
const NEUTRAL = 0x1;
|
const NEUTRAL = 0x1;
|
||||||
|
@ -165,14 +175,19 @@ bitflags! {
|
||||||
|
|
||||||
impl LedgeOption {
|
impl LedgeOption {
|
||||||
pub fn into_status(self) -> Option<i32> {
|
pub fn into_status(self) -> Option<i32> {
|
||||||
Some(match self {
|
#[cfg(feature = "smash")] {
|
||||||
LedgeOption::NEUTRAL => *FIGHTER_STATUS_KIND_CLIFF_CLIMB,
|
Some(match self {
|
||||||
LedgeOption::ROLL => *FIGHTER_STATUS_KIND_CLIFF_ESCAPE,
|
LedgeOption::NEUTRAL => *FIGHTER_STATUS_KIND_CLIFF_CLIMB,
|
||||||
LedgeOption::JUMP => *FIGHTER_STATUS_KIND_CLIFF_JUMP1,
|
LedgeOption::ROLL => *FIGHTER_STATUS_KIND_CLIFF_ESCAPE,
|
||||||
LedgeOption::ATTACK => *FIGHTER_STATUS_KIND_CLIFF_ATTACK,
|
LedgeOption::JUMP => *FIGHTER_STATUS_KIND_CLIFF_JUMP1,
|
||||||
LedgeOption::WAIT => *FIGHTER_STATUS_KIND_CLIFF_WAIT,
|
LedgeOption::ATTACK => *FIGHTER_STATUS_KIND_CLIFF_ATTACK,
|
||||||
_ => return None,
|
LedgeOption::WAIT => *FIGHTER_STATUS_KIND_CLIFF_WAIT,
|
||||||
})
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "smash"))]
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_str(self) -> Option<&'static str> {
|
fn as_str(self) -> Option<&'static str> {
|
||||||
|
@ -191,6 +206,7 @@ extra_bitflag_impls! {LedgeOption}
|
||||||
|
|
||||||
// Tech options
|
// Tech options
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct TechFlags : u32 {
|
pub struct TechFlags : u32 {
|
||||||
const NO_TECH = 0x1;
|
const NO_TECH = 0x1;
|
||||||
const ROLL_F = 0x2;
|
const ROLL_F = 0x2;
|
||||||
|
@ -215,6 +231,7 @@ extra_bitflag_impls! {TechFlags}
|
||||||
|
|
||||||
// Missed Tech Options
|
// Missed Tech Options
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct MissTechFlags : u32 {
|
pub struct MissTechFlags : u32 {
|
||||||
const GETUP = 0x1;
|
const GETUP = 0x1;
|
||||||
const ATTACK = 0x2;
|
const ATTACK = 0x2;
|
||||||
|
@ -239,7 +256,7 @@ extra_bitflag_impls! {MissTechFlags}
|
||||||
|
|
||||||
/// Shield States
|
/// Shield States
|
||||||
#[repr(i32)]
|
#[repr(i32)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, FromPrimitive, EnumIter)]
|
#[derive(Debug, Clone, Copy, PartialEq, FromPrimitive, EnumIter, Serialize, Deserialize)]
|
||||||
pub enum Shield {
|
pub enum Shield {
|
||||||
None = 0,
|
None = 0,
|
||||||
Infinite = 1,
|
Infinite = 1,
|
||||||
|
@ -264,7 +281,7 @@ impl Shield {
|
||||||
|
|
||||||
// Save State Mirroring
|
// Save State Mirroring
|
||||||
#[repr(i32)]
|
#[repr(i32)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, FromPrimitive, EnumIter)]
|
#[derive(Debug, Clone, Copy, PartialEq, FromPrimitive, EnumIter, Serialize, Deserialize)]
|
||||||
pub enum SaveStateMirroring {
|
pub enum SaveStateMirroring {
|
||||||
None = 0,
|
None = 0,
|
||||||
Alternate = 1,
|
Alternate = 1,
|
||||||
|
@ -287,6 +304,7 @@ impl SaveStateMirroring {
|
||||||
|
|
||||||
// Defensive States
|
// Defensive States
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Defensive : u32 {
|
pub struct Defensive : u32 {
|
||||||
const SPOT_DODGE = 0x1;
|
const SPOT_DODGE = 0x1;
|
||||||
const ROLL_F = 0x2;
|
const ROLL_F = 0x2;
|
||||||
|
@ -312,7 +330,7 @@ impl Defensive {
|
||||||
extra_bitflag_impls! {Defensive}
|
extra_bitflag_impls! {Defensive}
|
||||||
|
|
||||||
#[repr(i32)]
|
#[repr(i32)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum OnOff {
|
pub enum OnOff {
|
||||||
Off = 0,
|
Off = 0,
|
||||||
On = 1,
|
On = 1,
|
||||||
|
@ -340,6 +358,7 @@ impl OnOff {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Action : u32 {
|
pub struct Action : u32 {
|
||||||
const AIR_DODGE = 0x1;
|
const AIR_DODGE = 0x1;
|
||||||
const JUMP = 0x2;
|
const JUMP = 0x2;
|
||||||
|
@ -372,14 +391,19 @@ bitflags! {
|
||||||
|
|
||||||
impl Action {
|
impl Action {
|
||||||
pub fn into_attack_air_kind(self) -> Option<i32> {
|
pub fn into_attack_air_kind(self) -> Option<i32> {
|
||||||
Some(match self {
|
#[cfg(feature = "smash")] {
|
||||||
Action::NAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_N,
|
Some(match self {
|
||||||
Action::FAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_F,
|
Action::NAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_N,
|
||||||
Action::BAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_B,
|
Action::FAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_F,
|
||||||
Action::DAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_LW,
|
Action::BAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_B,
|
||||||
Action::UAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_HI,
|
Action::DAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_LW,
|
||||||
_ => return None,
|
Action::UAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_HI,
|
||||||
})
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "smash"))]
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_str(self) -> Option<&'static str> {
|
pub fn as_str(self) -> Option<&'static str> {
|
||||||
|
@ -417,6 +441,7 @@ impl Action {
|
||||||
extra_bitflag_impls! {Action}
|
extra_bitflag_impls! {Action}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct AttackAngle : u32 {
|
pub struct AttackAngle : u32 {
|
||||||
const NEUTRAL = 0x1;
|
const NEUTRAL = 0x1;
|
||||||
const UP = 0x2;
|
const UP = 0x2;
|
||||||
|
@ -438,6 +463,7 @@ impl AttackAngle {
|
||||||
extra_bitflag_impls! {AttackAngle}
|
extra_bitflag_impls! {AttackAngle}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Delay : u32 {
|
pub struct Delay : u32 {
|
||||||
const D0 = 0x1;
|
const D0 = 0x1;
|
||||||
const D1 = 0x2;
|
const D1 = 0x2;
|
||||||
|
@ -475,6 +501,7 @@ bitflags! {
|
||||||
|
|
||||||
// Throw Option
|
// Throw Option
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct ThrowOption : u32
|
pub struct ThrowOption : u32
|
||||||
{
|
{
|
||||||
const NONE = 0x1;
|
const NONE = 0x1;
|
||||||
|
@ -487,14 +514,19 @@ bitflags! {
|
||||||
|
|
||||||
impl ThrowOption {
|
impl ThrowOption {
|
||||||
pub fn into_cmd(self) -> Option<i32> {
|
pub fn into_cmd(self) -> Option<i32> {
|
||||||
Some(match self {
|
#[cfg(feature = "smash")] {
|
||||||
ThrowOption::NONE => 0,
|
Some(match self {
|
||||||
ThrowOption::FORWARD => *FIGHTER_PAD_CMD_CAT2_FLAG_THROW_F,
|
ThrowOption::NONE => 0,
|
||||||
ThrowOption::BACKWARD => *FIGHTER_PAD_CMD_CAT2_FLAG_THROW_B,
|
ThrowOption::FORWARD => *FIGHTER_PAD_CMD_CAT2_FLAG_THROW_F,
|
||||||
ThrowOption::UP => *FIGHTER_PAD_CMD_CAT2_FLAG_THROW_HI,
|
ThrowOption::BACKWARD => *FIGHTER_PAD_CMD_CAT2_FLAG_THROW_B,
|
||||||
ThrowOption::DOWN => *FIGHTER_PAD_CMD_CAT2_FLAG_THROW_LW,
|
ThrowOption::UP => *FIGHTER_PAD_CMD_CAT2_FLAG_THROW_HI,
|
||||||
_ => return None,
|
ThrowOption::DOWN => *FIGHTER_PAD_CMD_CAT2_FLAG_THROW_LW,
|
||||||
})
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "smash"))]
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_str(self) -> Option<&'static str> {
|
pub fn as_str(self) -> Option<&'static str> {
|
||||||
|
@ -513,6 +545,7 @@ extra_bitflag_impls! {ThrowOption}
|
||||||
|
|
||||||
// Buff Option
|
// Buff Option
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct BuffOption : u32
|
pub struct BuffOption : u32
|
||||||
{
|
{
|
||||||
const ACCELERATLE = 0x1;
|
const ACCELERATLE = 0x1;
|
||||||
|
@ -529,18 +562,23 @@ bitflags! {
|
||||||
|
|
||||||
impl BuffOption {
|
impl BuffOption {
|
||||||
pub fn into_int(self) -> Option<i32> {
|
pub fn into_int(self) -> Option<i32> {
|
||||||
Some(match self {
|
#[cfg(feature = "smash")] {
|
||||||
BuffOption::ACCELERATLE => *FIGHTER_BRAVE_SPECIAL_LW_COMMAND11_SPEED_UP,
|
Some(match self {
|
||||||
BuffOption::OOMPH => *FIGHTER_BRAVE_SPECIAL_LW_COMMAND12_ATTACK_UP,
|
BuffOption::ACCELERATLE => *FIGHTER_BRAVE_SPECIAL_LW_COMMAND11_SPEED_UP,
|
||||||
BuffOption::PSYCHE => *FIGHTER_BRAVE_SPECIAL_LW_COMMAND21_CHARGE,
|
BuffOption::OOMPH => *FIGHTER_BRAVE_SPECIAL_LW_COMMAND12_ATTACK_UP,
|
||||||
BuffOption::BOUNCE => *FIGHTER_BRAVE_SPECIAL_LW_COMMAND13_REFLECT,
|
BuffOption::PSYCHE => *FIGHTER_BRAVE_SPECIAL_LW_COMMAND21_CHARGE,
|
||||||
BuffOption::BREATHING => 1,
|
BuffOption::BOUNCE => *FIGHTER_BRAVE_SPECIAL_LW_COMMAND13_REFLECT,
|
||||||
BuffOption::ARSENE => 1,
|
BuffOption::BREATHING => 1,
|
||||||
BuffOption::LIMIT => 1,
|
BuffOption::ARSENE => 1,
|
||||||
BuffOption::KO => 1,
|
BuffOption::LIMIT => 1,
|
||||||
BuffOption::WING => 1,
|
BuffOption::KO => 1,
|
||||||
_ => return None,
|
BuffOption::WING => 1,
|
||||||
})
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "smash"))]
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_str(self) -> Option<&'static str> {
|
fn as_str(self) -> Option<&'static str> {
|
||||||
|
@ -607,6 +645,7 @@ impl Delay {
|
||||||
extra_bitflag_impls! {Delay}
|
extra_bitflag_impls! {Delay}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct MedDelay : u32 {
|
pub struct MedDelay : u32 {
|
||||||
const D0 = 0x1;
|
const D0 = 0x1;
|
||||||
const D5 = 0x2;
|
const D5 = 0x2;
|
||||||
|
@ -688,6 +727,7 @@ impl MedDelay {
|
||||||
extra_bitflag_impls! {MedDelay}
|
extra_bitflag_impls! {MedDelay}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct LongDelay : u32 {
|
pub struct LongDelay : u32 {
|
||||||
const D0 = 0x1;
|
const D0 = 0x1;
|
||||||
const D10 = 0x2;
|
const D10 = 0x2;
|
||||||
|
@ -769,6 +809,7 @@ impl LongDelay {
|
||||||
extra_bitflag_impls! {LongDelay}
|
extra_bitflag_impls! {LongDelay}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct BoolFlag : u32 {
|
pub struct BoolFlag : u32 {
|
||||||
const TRUE = 0x1;
|
const TRUE = 0x1;
|
||||||
const FALSE = 0x2;
|
const FALSE = 0x2;
|
||||||
|
@ -791,7 +832,7 @@ impl BoolFlag {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, EnumIter)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, EnumIter, Serialize, Deserialize)]
|
||||||
pub enum SdiStrength {
|
pub enum SdiStrength {
|
||||||
Normal = 0,
|
Normal = 0,
|
||||||
Medium = 1,
|
Medium = 1,
|
||||||
|
@ -834,11 +875,13 @@ impl ToUrlParam for i32 {
|
||||||
// Macro to build the url parameter string
|
// Macro to build the url parameter string
|
||||||
macro_rules! url_params {
|
macro_rules! url_params {
|
||||||
(
|
(
|
||||||
|
#[repr(C)]
|
||||||
#[derive($($trait_name:ident, )*)]
|
#[derive($($trait_name:ident, )*)]
|
||||||
pub struct $e:ident {
|
pub struct $e:ident {
|
||||||
$(pub $field_name:ident: $field_type:ty,)*
|
$(pub $field_name:ident: $field_type:ty,)*
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
|
#[repr(C)]
|
||||||
#[derive($($trait_name, )*)]
|
#[derive($($trait_name, )*)]
|
||||||
pub struct $e {
|
pub struct $e {
|
||||||
$(pub $field_name: $field_type,)*
|
$(pub $field_name: $field_type,)*
|
||||||
|
@ -859,9 +902,9 @@ macro_rules! url_params {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
url_params! {
|
url_params! {
|
||||||
#[derive(Clone, Copy, )]
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Serialize, Deserialize, Debug, )]
|
||||||
pub struct TrainingModpackMenu {
|
pub struct TrainingModpackMenu {
|
||||||
pub hitbox_vis: OnOff,
|
pub hitbox_vis: OnOff,
|
||||||
pub stage_hazards: OnOff,
|
pub stage_hazards: OnOff,
|
||||||
|
@ -896,6 +939,7 @@ url_params! {
|
||||||
pub throw_delay: MedDelay,
|
pub throw_delay: MedDelay,
|
||||||
pub pummel_delay: MedDelay,
|
pub pummel_delay: MedDelay,
|
||||||
pub buff_state: BuffOption,
|
pub buff_state: BuffOption,
|
||||||
|
pub quick_menu: OnOff,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -947,6 +991,7 @@ impl TrainingModpackMenu {
|
||||||
throw_delay = MedDelay::from_bits(val),
|
throw_delay = MedDelay::from_bits(val),
|
||||||
pummel_delay = MedDelay::from_bits(val),
|
pummel_delay = MedDelay::from_bits(val),
|
||||||
buff_state = BuffOption::from_bits(val),
|
buff_state = BuffOption::from_bits(val),
|
||||||
|
quick_menu = OnOff::from_val(val),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -958,3 +1003,612 @@ pub enum FighterId {
|
||||||
Player = 0,
|
Player = 0,
|
||||||
CPU = 1,
|
CPU = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Content, Clone)]
|
||||||
|
pub struct Slider {
|
||||||
|
pub min: usize,
|
||||||
|
pub max: usize,
|
||||||
|
pub index: usize,
|
||||||
|
pub value: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Content, Clone)]
|
||||||
|
pub struct Toggle<'a> {
|
||||||
|
pub title: &'a str,
|
||||||
|
pub checked: &'a str,
|
||||||
|
pub index: usize,
|
||||||
|
pub value: usize,
|
||||||
|
pub default: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Content, Clone)]
|
||||||
|
pub struct OnOffSelector<'a> {
|
||||||
|
pub title: &'a str,
|
||||||
|
pub checked: &'a str,
|
||||||
|
pub default: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum SubMenuType {
|
||||||
|
TOGGLE,
|
||||||
|
SLIDER,
|
||||||
|
ONOFF,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SubMenuType {
|
||||||
|
pub fn from_str(s : &str) -> SubMenuType {
|
||||||
|
match s {
|
||||||
|
"toggle" => SubMenuType::TOGGLE,
|
||||||
|
"slider" => SubMenuType::SLIDER,
|
||||||
|
"onoff" => SubMenuType::ONOFF,
|
||||||
|
_ => panic!("Unexpected SubMenuType!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Content, Clone)]
|
||||||
|
pub struct SubMenu<'a> {
|
||||||
|
pub title: &'a str,
|
||||||
|
pub id: &'a str,
|
||||||
|
pub _type: &'a str,
|
||||||
|
pub toggles: Vec<Toggle<'a>>,
|
||||||
|
pub sliders: Vec<Slider>,
|
||||||
|
pub onoffselector: Vec<OnOffSelector<'a>>,
|
||||||
|
pub index: usize,
|
||||||
|
pub check_against: usize,
|
||||||
|
pub is_single_option: Option<bool>,
|
||||||
|
pub help_text: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
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" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub static DEFAULT_MENU: TrainingModpackMenu = TrainingModpackMenu {
|
||||||
|
hitbox_vis: OnOff::On,
|
||||||
|
stage_hazards: OnOff::Off,
|
||||||
|
di_state: Direction::empty(),
|
||||||
|
sdi_state: Direction::empty(),
|
||||||
|
sdi_strength: SdiStrength::Normal,
|
||||||
|
air_dodge_dir: Direction::empty(),
|
||||||
|
mash_state: Action::empty(),
|
||||||
|
follow_up: Action::empty(),
|
||||||
|
attack_angle: AttackAngle::empty(),
|
||||||
|
ledge_state: LedgeOption::all(),
|
||||||
|
ledge_delay: LongDelay::empty(),
|
||||||
|
tech_state: TechFlags::all(),
|
||||||
|
miss_tech_state: MissTechFlags::all(),
|
||||||
|
shield_state: Shield::None,
|
||||||
|
defensive_state: Defensive::all(),
|
||||||
|
oos_offset: Delay::empty(),
|
||||||
|
shield_tilt: Direction::empty(),
|
||||||
|
reaction_time: Delay::empty(),
|
||||||
|
mash_in_neutral: OnOff::Off,
|
||||||
|
fast_fall: BoolFlag::empty(),
|
||||||
|
fast_fall_delay: Delay::empty(),
|
||||||
|
falling_aerials: BoolFlag::empty(),
|
||||||
|
aerial_delay: Delay::empty(),
|
||||||
|
full_hop: BoolFlag::empty(),
|
||||||
|
input_delay: 0,
|
||||||
|
save_damage: OnOff::On,
|
||||||
|
save_state_mirroring: SaveStateMirroring::None,
|
||||||
|
frame_advantage: OnOff::Off,
|
||||||
|
save_state_enable: OnOff::On,
|
||||||
|
throw_state: ThrowOption::NONE,
|
||||||
|
throw_delay: MedDelay::empty(),
|
||||||
|
pummel_delay: MedDelay::empty(),
|
||||||
|
buff_state: BuffOption::empty(),
|
||||||
|
quick_menu: OnOff::On,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub static mut MENU: TrainingModpackMenu = DEFAULT_MENU;
|
||||||
|
|
||||||
|
#[derive(Content, Clone)]
|
||||||
|
pub struct Menu<'a> {
|
||||||
|
pub 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,
|
||||||
|
_type: &'a str,
|
||||||
|
check_against: usize,
|
||||||
|
toggles: Vec<(&'a str, usize)>,
|
||||||
|
sliders: Vec<(usize, usize, usize)>,
|
||||||
|
defaults: usize,
|
||||||
|
help_text: &'a str,
|
||||||
|
) {
|
||||||
|
let mut sub_menu = SubMenu {
|
||||||
|
title,
|
||||||
|
id,
|
||||||
|
_type,
|
||||||
|
toggles: Vec::new(),
|
||||||
|
sliders: Vec::new(),
|
||||||
|
onoffselector: Vec::new(),
|
||||||
|
index: self.max_idx() + 1,
|
||||||
|
check_against,
|
||||||
|
is_single_option: Some(true),
|
||||||
|
help_text,
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
_type: &'a str,
|
||||||
|
check_against: usize,
|
||||||
|
strs: Vec<&'a str>,
|
||||||
|
vals: Vec<usize>,
|
||||||
|
defaults: usize,
|
||||||
|
help_text: &'a str,
|
||||||
|
) {
|
||||||
|
let mut sub_menu = SubMenu {
|
||||||
|
title,
|
||||||
|
id,
|
||||||
|
_type,
|
||||||
|
toggles: Vec::new(),
|
||||||
|
sliders: Vec::new(),
|
||||||
|
onoffselector: Vec::new(),
|
||||||
|
index: self.max_idx() + 1,
|
||||||
|
check_against,
|
||||||
|
is_single_option: None,
|
||||||
|
help_text,
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
_type: &'a str,
|
||||||
|
check_against: usize,
|
||||||
|
checked: bool,
|
||||||
|
default: usize,
|
||||||
|
help_text: &'a str,
|
||||||
|
) {
|
||||||
|
let mut sub_menu = SubMenu {
|
||||||
|
title,
|
||||||
|
id,
|
||||||
|
_type,
|
||||||
|
toggles: Vec::new(),
|
||||||
|
sliders: Vec::new(),
|
||||||
|
onoffselector: Vec::new(),
|
||||||
|
index: self.max_idx() + 1,
|
||||||
|
check_against,
|
||||||
|
is_single_option: None,
|
||||||
|
help_text,
|
||||||
|
};
|
||||||
|
|
||||||
|
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, $help_text:literal) => {
|
||||||
|
paste::paste!{
|
||||||
|
let [<$id _strs>] = <$e>::to_toggle_strs();
|
||||||
|
let [<$id _vals>] = <$e>::to_toggle_vals();
|
||||||
|
|
||||||
|
$menu.add_sub_menu_sep(
|
||||||
|
$title,
|
||||||
|
stringify!($id),
|
||||||
|
"toggle",
|
||||||
|
MENU.$id.bits() as usize,
|
||||||
|
[<$id _strs>],
|
||||||
|
[<$id _vals>],
|
||||||
|
DEFAULT_MENU.$id.bits() as usize,
|
||||||
|
stringify!($help_text),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! add_single_option_submenu {
|
||||||
|
($menu:ident, $title:literal, $id:ident, $e:ty, $help_text:literal) => {
|
||||||
|
paste::paste!{
|
||||||
|
let mut [<$id _toggles>] = Vec::new();
|
||||||
|
for val in [<$e>]::iter() {
|
||||||
|
[<$id _toggles>].push((val.as_str().unwrap_or(""), val as usize));
|
||||||
|
}
|
||||||
|
|
||||||
|
$menu.add_sub_menu(
|
||||||
|
$title,
|
||||||
|
stringify!($id),
|
||||||
|
"toggle",
|
||||||
|
MENU.$id as usize,
|
||||||
|
[<$id _toggles>],
|
||||||
|
[].to_vec(),
|
||||||
|
DEFAULT_MENU.$id as usize,
|
||||||
|
stringify!($help_text),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! add_onoff_submenu {
|
||||||
|
($menu:ident, $title:literal, $id:ident, $help_text:literal) => {
|
||||||
|
paste::paste! {
|
||||||
|
$menu.add_sub_menu_onoff(
|
||||||
|
$title,
|
||||||
|
stringify!($id),
|
||||||
|
"onoff",
|
||||||
|
MENU.$id as usize,
|
||||||
|
(MENU.$id as usize & OnOff::On as usize) != 0,
|
||||||
|
DEFAULT_MENU.$id as usize,
|
||||||
|
stringify!($help_text),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn get_menu() -> Menu<'static> {
|
||||||
|
let mut overall_menu = Menu {
|
||||||
|
sub_menus: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Toggle/bitflag menus
|
||||||
|
add_bitflag_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Mash Toggles",
|
||||||
|
mash_state,
|
||||||
|
Action,
|
||||||
|
"Mash Toggles: Actions to be performed as soon as possible"
|
||||||
|
);
|
||||||
|
add_bitflag_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Followup Toggles",
|
||||||
|
follow_up,
|
||||||
|
Action,
|
||||||
|
"Followup Toggles: Actions to be performed after the Mash option"
|
||||||
|
);
|
||||||
|
add_bitflag_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Attack Angle",
|
||||||
|
attack_angle,
|
||||||
|
AttackAngle,
|
||||||
|
"Attack Angle: For attacks that can be angled, such as some forward tilts"
|
||||||
|
);
|
||||||
|
|
||||||
|
add_bitflag_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Ledge Options",
|
||||||
|
ledge_state,
|
||||||
|
LedgeOption,
|
||||||
|
"Ledge Options: Actions to be taken when on the ledge"
|
||||||
|
);
|
||||||
|
add_bitflag_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Ledge Delay",
|
||||||
|
ledge_delay,
|
||||||
|
LongDelay,
|
||||||
|
"Ledge Delay: How many frames to delay the ledge option"
|
||||||
|
);
|
||||||
|
add_bitflag_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Tech Options",
|
||||||
|
tech_state,
|
||||||
|
TechFlags,
|
||||||
|
"Tech Options: Actions to take when slammed into a hard surface"
|
||||||
|
);
|
||||||
|
add_bitflag_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Miss Tech Options",
|
||||||
|
miss_tech_state,
|
||||||
|
MissTechFlags,
|
||||||
|
"Miss Tech Options: Actions to take after missing a tech"
|
||||||
|
);
|
||||||
|
add_bitflag_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Defensive Options",
|
||||||
|
defensive_state,
|
||||||
|
Defensive,
|
||||||
|
"Defensive Options: Actions to take after a ledge option, tech option, or miss tech option"
|
||||||
|
);
|
||||||
|
|
||||||
|
add_bitflag_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Aerial Delay",
|
||||||
|
aerial_delay,
|
||||||
|
Delay,
|
||||||
|
"Aerial Delay: How long to delay a Mash aerial attack"
|
||||||
|
);
|
||||||
|
add_bitflag_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"OoS Offset",
|
||||||
|
oos_offset,
|
||||||
|
Delay,
|
||||||
|
"OoS Offset: How many times the CPU shield can be hit before performing a Mash option"
|
||||||
|
);
|
||||||
|
add_bitflag_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Reaction Time",
|
||||||
|
reaction_time,
|
||||||
|
Delay,
|
||||||
|
"Reaction Time: How many frames to delay before performing an option out of shield"
|
||||||
|
);
|
||||||
|
|
||||||
|
add_bitflag_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Fast Fall",
|
||||||
|
fast_fall,
|
||||||
|
BoolFlag,
|
||||||
|
"Fast Fall: Should the CPU fastfall during a jump"
|
||||||
|
);
|
||||||
|
add_bitflag_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Fast Fall Delay",
|
||||||
|
fast_fall_delay,
|
||||||
|
Delay,
|
||||||
|
"Fast Fall Delay: How many frames the CPU should delay their fastfall"
|
||||||
|
);
|
||||||
|
add_bitflag_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Falling Aerials",
|
||||||
|
falling_aerials,
|
||||||
|
BoolFlag,
|
||||||
|
"Falling Aerials: Should aerials be performed when rising or when falling"
|
||||||
|
);
|
||||||
|
add_bitflag_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Full Hop",
|
||||||
|
full_hop,
|
||||||
|
BoolFlag,
|
||||||
|
"Full Hop: Should the CPU perform a full hop or a short hop"
|
||||||
|
);
|
||||||
|
|
||||||
|
add_bitflag_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Shield Tilt",
|
||||||
|
shield_tilt,
|
||||||
|
Direction,
|
||||||
|
"Shield Tilt: Direction to tilt the shield"
|
||||||
|
);
|
||||||
|
add_bitflag_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"DI Direction",
|
||||||
|
di_state,
|
||||||
|
Direction,
|
||||||
|
"DI Direction: Direction to angle the directional influence during hitlag"
|
||||||
|
);
|
||||||
|
add_bitflag_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"SDI Direction",
|
||||||
|
sdi_state,
|
||||||
|
Direction,
|
||||||
|
"SDI Direction: Direction to angle the smash directional influence during hitlag"
|
||||||
|
);
|
||||||
|
add_bitflag_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Airdodge Direction",
|
||||||
|
air_dodge_dir,
|
||||||
|
Direction,
|
||||||
|
"Airdodge Direction: Direction to angle airdodges"
|
||||||
|
);
|
||||||
|
|
||||||
|
add_single_option_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"SDI Strength",
|
||||||
|
sdi_strength,
|
||||||
|
SdiStrength,
|
||||||
|
"SDI Strength: Relative strength of the smash directional influence inputs"
|
||||||
|
);
|
||||||
|
add_single_option_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Shield Toggles",
|
||||||
|
shield_state,
|
||||||
|
Shield,
|
||||||
|
"Shield Toggles: CPU Shield Behavior"
|
||||||
|
);
|
||||||
|
add_single_option_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Mirroring",
|
||||||
|
save_state_mirroring,
|
||||||
|
SaveStateMirroring,
|
||||||
|
"Mirroring: Flips save states in the left-right direction across the stage center"
|
||||||
|
);
|
||||||
|
add_bitflag_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Throw Options",
|
||||||
|
throw_state,
|
||||||
|
ThrowOption,
|
||||||
|
"Throw Options: Throw to be performed when a grab is landed"
|
||||||
|
);
|
||||||
|
add_bitflag_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Throw Delay",
|
||||||
|
throw_delay,
|
||||||
|
MedDelay,
|
||||||
|
"Throw Delay: How many frames to delay the throw option"
|
||||||
|
);
|
||||||
|
add_bitflag_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Pummel Delay",
|
||||||
|
pummel_delay,
|
||||||
|
MedDelay,
|
||||||
|
"Pummel Delay: How many frames after a grab to wait before starting to pummel"
|
||||||
|
);
|
||||||
|
add_bitflag_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Buff Options",
|
||||||
|
buff_state,
|
||||||
|
BuffOption,
|
||||||
|
"Buff Options: Buff(s) to be applied to respective character when loading save states"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Slider menus
|
||||||
|
overall_menu.add_sub_menu(
|
||||||
|
"Input Delay",
|
||||||
|
"input_delay",
|
||||||
|
// unnecessary for slider?
|
||||||
|
"toggle",
|
||||||
|
0,
|
||||||
|
[
|
||||||
|
("0", 0),
|
||||||
|
("1", 1),
|
||||||
|
("2", 2),
|
||||||
|
("3", 3),
|
||||||
|
("4", 4),
|
||||||
|
("5", 5),
|
||||||
|
("6", 6),
|
||||||
|
("7", 7),
|
||||||
|
("8", 8),
|
||||||
|
("9", 9),
|
||||||
|
("10", 10),
|
||||||
|
]
|
||||||
|
.to_vec(),
|
||||||
|
[].to_vec(), //(0, 10, MENU.input_delay as usize)
|
||||||
|
DEFAULT_MENU.input_delay as usize,
|
||||||
|
stringify!("Input Delay: Frames to delay player inputs by"),
|
||||||
|
);
|
||||||
|
|
||||||
|
add_onoff_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Save States",
|
||||||
|
save_state_enable,
|
||||||
|
"Save States: Enable save states! Save a state with Grab+Down Taunt, load it with Grab+Up Taunt."
|
||||||
|
);
|
||||||
|
add_onoff_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Save Damage",
|
||||||
|
save_damage,
|
||||||
|
"Save Damage: Should save states retain player/CPU damage"
|
||||||
|
);
|
||||||
|
add_onoff_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Hitbox Visualization",
|
||||||
|
hitbox_vis,
|
||||||
|
"Hitbox Visualization: Should hitboxes be displayed, hiding other visual effects"
|
||||||
|
);
|
||||||
|
add_onoff_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Stage Hazards",
|
||||||
|
stage_hazards,
|
||||||
|
"Stage Hazards: Should stage hazards be present"
|
||||||
|
);
|
||||||
|
add_onoff_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Frame Advantage",
|
||||||
|
frame_advantage,
|
||||||
|
"Frame Advantage: Display the time difference between when the player is actionable and the CPU is actionable");
|
||||||
|
add_onoff_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Mash In Neutral",
|
||||||
|
mash_in_neutral,
|
||||||
|
"Mash In Neutral: Should Mash options be performed repeatedly or only when the CPU is hit"
|
||||||
|
);
|
||||||
|
add_onoff_submenu!(
|
||||||
|
overall_menu,
|
||||||
|
"Quick Menu",
|
||||||
|
quick_menu,
|
||||||
|
"Quick Menu: Whether to use Quick Menu or Web Menu"
|
||||||
|
);
|
||||||
|
|
||||||
|
overall_menu
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_menu_from_url(mut menu: TrainingModpackMenu, s: &str) -> 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 toggle = toggle_value_split[0];
|
||||||
|
if toggle.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let toggle_vals = toggle_value_split[1];
|
||||||
|
|
||||||
|
let bitwise_or = <u32 as BitOr<u32>>::bitor;
|
||||||
|
let bits = toggle_vals
|
||||||
|
.split(',')
|
||||||
|
.filter(|val| !val.is_empty())
|
||||||
|
.map(|val| val.parse().unwrap())
|
||||||
|
.fold(0, bitwise_or);
|
||||||
|
|
||||||
|
menu.set(toggle, bits);
|
||||||
|
}
|
||||||
|
menu
|
||||||
|
}
|
18
training_mod_tui/Cargo.toml
Normal file
18
training_mod_tui/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "training_mod_tui"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tui = { version = "0.16.0", default-features = false }
|
||||||
|
unicode-width = "0.1.9"
|
||||||
|
training_mod_consts = { path = "../training_mod_consts", default-features = false}
|
||||||
|
serde_json = "1.0.79"
|
||||||
|
bitflags = "1.2.1"
|
||||||
|
crossterm = { version = "0.22.1", optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
has_terminal = ["crossterm", "tui/crossterm"]
|
454
training_mod_tui/src/lib.rs
Normal file
454
training_mod_tui/src/lib.rs
Normal file
|
@ -0,0 +1,454 @@
|
||||||
|
use training_mod_consts::{OnOffSelector, Slider, SubMenu, SubMenuType, Toggle};
|
||||||
|
use tui::{
|
||||||
|
backend::{Backend},
|
||||||
|
layout::{Constraint, Corner, Direction, Layout},
|
||||||
|
style::{Color, Modifier, Style},
|
||||||
|
text::Spans,
|
||||||
|
widgets::{Tabs, Paragraph, Block, List, ListItem, ListState},
|
||||||
|
Frame,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub use tui::{backend::TestBackend, Terminal};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
mod list;
|
||||||
|
|
||||||
|
use crate::list::{StatefulList, MultiStatefulList};
|
||||||
|
|
||||||
|
/// We should hold a list of SubMenus.
|
||||||
|
/// The currently selected SubMenu should also have an associated list with necessary information.
|
||||||
|
/// We can convert the option types (Toggle, OnOff, Slider) to lists
|
||||||
|
pub struct App<'a> {
|
||||||
|
pub tabs: StatefulList<&'a str>,
|
||||||
|
pub menu_items: HashMap<&'a str, MultiStatefulList<SubMenu<'a>>>,
|
||||||
|
pub selected_sub_menu_toggles: MultiStatefulList<Toggle<'a>>,
|
||||||
|
pub selected_sub_menu_onoff_selectors: MultiStatefulList<OnOffSelector<'a>>,
|
||||||
|
pub selected_sub_menu_sliders: MultiStatefulList<Slider>,
|
||||||
|
pub outer_list: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> App<'a> {
|
||||||
|
pub fn new(menu: training_mod_consts::Menu<'a>) -> App<'a> {
|
||||||
|
let tab_specifiers = vec![
|
||||||
|
("Mash Settings", vec![
|
||||||
|
"Mash Toggles",
|
||||||
|
"Followup Toggles",
|
||||||
|
"Attack Angle",
|
||||||
|
"Ledge Options",
|
||||||
|
"Ledge Delay",
|
||||||
|
"Tech Options",
|
||||||
|
"Miss Tech Options",
|
||||||
|
"Defensive Options",
|
||||||
|
"Aerial Delay",
|
||||||
|
"OoS Offset",
|
||||||
|
"Reaction Time",
|
||||||
|
]),
|
||||||
|
("Defensive Settings", vec![
|
||||||
|
"Fast Fall",
|
||||||
|
"Fast Fall Delay",
|
||||||
|
"Falling Aerials",
|
||||||
|
"Full Hop",
|
||||||
|
"Shield Tilt",
|
||||||
|
"DI Direction",
|
||||||
|
"SDI Direction",
|
||||||
|
"Airdodge Direction",
|
||||||
|
"SDI Strength",
|
||||||
|
"Shield Toggles",
|
||||||
|
"Mirroring",
|
||||||
|
"Throw Options",
|
||||||
|
"Throw Delay",
|
||||||
|
"Pummel Delay",
|
||||||
|
"Buff Options",
|
||||||
|
]),
|
||||||
|
("Other Settings", vec![
|
||||||
|
"Input Delay",
|
||||||
|
"Save States",
|
||||||
|
"Save Damage",
|
||||||
|
"Hitbox Visualization",
|
||||||
|
"Stage Hazards",
|
||||||
|
"Frame Advantage",
|
||||||
|
"Mash In Neutral",
|
||||||
|
"Quick Menu"
|
||||||
|
])
|
||||||
|
];
|
||||||
|
let mut tabs: std::collections::HashMap<&str, Vec<SubMenu>> = std::collections::HashMap::new();
|
||||||
|
tabs.insert("Mash Settings", vec![]);
|
||||||
|
tabs.insert("Defensive Settings", vec![]);
|
||||||
|
tabs.insert("Other Settings", vec![]);
|
||||||
|
|
||||||
|
for sub_menu in menu.sub_menus.iter() {
|
||||||
|
for tab_spec in tab_specifiers.iter() {
|
||||||
|
if tab_spec.1.contains(&sub_menu.title) {
|
||||||
|
tabs.get_mut(tab_spec.0).unwrap().push(sub_menu.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let num_lists = 3;
|
||||||
|
|
||||||
|
let mut menu_items_stateful = HashMap::new();
|
||||||
|
tabs.keys().for_each(|k| {
|
||||||
|
menu_items_stateful.insert(
|
||||||
|
k.clone(),
|
||||||
|
MultiStatefulList::with_items(tabs.get(k).unwrap().clone(), num_lists)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
let mut app = App {
|
||||||
|
tabs: StatefulList::with_items(tab_specifiers.iter().map(|(tab_title, _)| *tab_title).collect()),
|
||||||
|
menu_items: menu_items_stateful,
|
||||||
|
selected_sub_menu_toggles: MultiStatefulList::with_items(vec![], 0),
|
||||||
|
selected_sub_menu_onoff_selectors: MultiStatefulList::with_items(vec![], 0),
|
||||||
|
selected_sub_menu_sliders: MultiStatefulList::with_items(vec![], 0),
|
||||||
|
outer_list: true
|
||||||
|
};
|
||||||
|
app.set_sub_menu_items();
|
||||||
|
app
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_sub_menu_items(&mut self) {
|
||||||
|
let (list_section, list_idx) = self.menu_items.get(self.tab_selected()).unwrap().idx_to_list_idx(self.menu_items.get(self.tab_selected()).unwrap().state);
|
||||||
|
let selected_sub_menu = &self.menu_items.get(self.tab_selected()).unwrap().lists[list_section].items.get(list_idx).unwrap();
|
||||||
|
|
||||||
|
let toggles = selected_sub_menu.toggles.clone();
|
||||||
|
let sliders = selected_sub_menu.sliders.clone();
|
||||||
|
let onoffs = selected_sub_menu.onoffselector.clone();
|
||||||
|
match SubMenuType::from_str(self.sub_menu_selected()._type) {
|
||||||
|
SubMenuType::TOGGLE => {
|
||||||
|
self.selected_sub_menu_toggles = MultiStatefulList::with_items(
|
||||||
|
toggles,
|
||||||
|
if selected_sub_menu.toggles.len() >= 3 { 3 } else { selected_sub_menu.toggles.len()} )
|
||||||
|
},
|
||||||
|
SubMenuType::SLIDER => {
|
||||||
|
self.selected_sub_menu_sliders = MultiStatefulList::with_items(
|
||||||
|
sliders,
|
||||||
|
if selected_sub_menu.sliders.len() >= 3 { 3 } else { selected_sub_menu.sliders.len()} )
|
||||||
|
},
|
||||||
|
SubMenuType::ONOFF => {
|
||||||
|
self.selected_sub_menu_onoff_selectors = MultiStatefulList::with_items(
|
||||||
|
onoffs,
|
||||||
|
if selected_sub_menu.onoffselector.len() >= 3 { 3 } else { selected_sub_menu.onoffselector.len()} )
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tab_selected(&self) -> &str {
|
||||||
|
self.tabs.items.get(self.tabs.state.selected().unwrap()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sub_menu_selected(&self) -> &SubMenu {
|
||||||
|
let (list_section, list_idx) = self.menu_items.get(self.tab_selected()).unwrap().idx_to_list_idx(self.menu_items.get(self.tab_selected()).unwrap().state);
|
||||||
|
&self.menu_items.get(self.tab_selected()).unwrap().lists[list_section].items.get(list_idx).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sub_menu_next(&mut self) {
|
||||||
|
match SubMenuType::from_str(self.sub_menu_selected()._type) {
|
||||||
|
SubMenuType::TOGGLE => self.selected_sub_menu_toggles.next(),
|
||||||
|
SubMenuType::SLIDER => self.selected_sub_menu_sliders.next(),
|
||||||
|
SubMenuType::ONOFF => self.selected_sub_menu_onoff_selectors.next(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sub_menu_next_list(&mut self) {
|
||||||
|
match SubMenuType::from_str(self.sub_menu_selected()._type) {
|
||||||
|
SubMenuType::TOGGLE => self.selected_sub_menu_toggles.next_list(),
|
||||||
|
SubMenuType::SLIDER => self.selected_sub_menu_sliders.next_list(),
|
||||||
|
SubMenuType::ONOFF => self.selected_sub_menu_onoff_selectors.next_list(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sub_menu_previous(&mut self) {
|
||||||
|
match SubMenuType::from_str(self.sub_menu_selected()._type) {
|
||||||
|
SubMenuType::TOGGLE => self.selected_sub_menu_toggles.previous(),
|
||||||
|
SubMenuType::SLIDER => self.selected_sub_menu_sliders.previous(),
|
||||||
|
SubMenuType::ONOFF => self.selected_sub_menu_onoff_selectors.previous(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sub_menu_previous_list(&mut self) {
|
||||||
|
match SubMenuType::from_str(self.sub_menu_selected()._type) {
|
||||||
|
SubMenuType::TOGGLE => self.selected_sub_menu_toggles.previous_list(),
|
||||||
|
SubMenuType::SLIDER => self.selected_sub_menu_sliders.previous_list(),
|
||||||
|
SubMenuType::ONOFF => self.selected_sub_menu_onoff_selectors.previous_list(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sub_menu_strs_and_states(&mut self) -> (&str, &str, Vec<(Vec<(&str, &str)>, ListState)>) {
|
||||||
|
(self.sub_menu_selected().title, self.sub_menu_selected().help_text,
|
||||||
|
match SubMenuType::from_str(self.sub_menu_selected()._type) {
|
||||||
|
SubMenuType::TOGGLE => {
|
||||||
|
self.selected_sub_menu_toggles.lists.iter().map(|toggle_list| {
|
||||||
|
(toggle_list.items.iter().map(
|
||||||
|
|toggle| (toggle.checked, toggle.title)
|
||||||
|
).collect(), toggle_list.state.clone())
|
||||||
|
}).collect()
|
||||||
|
},
|
||||||
|
SubMenuType::SLIDER => {
|
||||||
|
vec![(vec![], ListState::default())]
|
||||||
|
},
|
||||||
|
SubMenuType::ONOFF => {
|
||||||
|
self.selected_sub_menu_onoff_selectors.lists.iter().map(|onoff_list| {
|
||||||
|
(onoff_list.items.iter().map(
|
||||||
|
|onoff| (onoff.checked, onoff.title)
|
||||||
|
).collect(), onoff_list.state.clone())
|
||||||
|
}).collect()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_a(&mut self) {
|
||||||
|
if self.outer_list {
|
||||||
|
self.outer_list = false;
|
||||||
|
} else {
|
||||||
|
let tab_selected = self.tabs.items.get(self.tabs.state.selected().unwrap()).unwrap();
|
||||||
|
let (list_section, list_idx) = self.menu_items.get(tab_selected)
|
||||||
|
.unwrap()
|
||||||
|
.idx_to_list_idx(self.menu_items.get(tab_selected).unwrap().state);
|
||||||
|
let selected_sub_menu = self.menu_items.get_mut(tab_selected)
|
||||||
|
.unwrap()
|
||||||
|
.lists[list_section]
|
||||||
|
.items.get_mut(list_idx).unwrap();
|
||||||
|
match SubMenuType::from_str(selected_sub_menu._type) {
|
||||||
|
SubMenuType::TOGGLE => {
|
||||||
|
let is_single_option = selected_sub_menu.is_single_option.is_some();
|
||||||
|
let state = self.selected_sub_menu_toggles.state;
|
||||||
|
self.selected_sub_menu_toggles.lists.iter_mut()
|
||||||
|
.map(|list| (list.state.selected(), &mut list.items))
|
||||||
|
.for_each(|(state, toggle_list)| toggle_list.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.for_each(|(i, o)|
|
||||||
|
if state.is_some() && i == state.unwrap() {
|
||||||
|
if o.checked != "is-appear" {
|
||||||
|
o.checked = "is-appear";
|
||||||
|
} else {
|
||||||
|
o.checked = "is-hidden";
|
||||||
|
}
|
||||||
|
} else if is_single_option {
|
||||||
|
o.checked = "is-hidden";
|
||||||
|
}
|
||||||
|
));
|
||||||
|
selected_sub_menu.toggles.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.for_each(|(i, o)| {
|
||||||
|
if i == state {
|
||||||
|
if o.checked != "is-appear" {
|
||||||
|
o.checked = "is-appear";
|
||||||
|
} else {
|
||||||
|
o.checked = "is-hidden";
|
||||||
|
}
|
||||||
|
} else if is_single_option {
|
||||||
|
o.checked = "is-hidden";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
SubMenuType::ONOFF => {
|
||||||
|
let onoff = self.selected_sub_menu_onoff_selectors.selected_list_item();
|
||||||
|
if onoff.checked != "is-appear" {
|
||||||
|
onoff.checked = "is-appear";
|
||||||
|
} else {
|
||||||
|
onoff.checked = "is-hidden";
|
||||||
|
}
|
||||||
|
selected_sub_menu.onoffselector.iter_mut()
|
||||||
|
.filter(|o| o.title == onoff.title)
|
||||||
|
.for_each(|o| o.checked = onoff.checked);
|
||||||
|
},
|
||||||
|
SubMenuType::SLIDER => {
|
||||||
|
// self.selected_sub_menu_sliders.selected_list_item().checked = "is-appear";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_b(&mut self) {
|
||||||
|
self.outer_list = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_l(&mut self) {
|
||||||
|
if self.outer_list {
|
||||||
|
self.tabs.previous();
|
||||||
|
self.set_sub_menu_items();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_r(&mut self) {
|
||||||
|
if self.outer_list {
|
||||||
|
self.tabs.next();
|
||||||
|
self.set_sub_menu_items();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_up(&mut self) {
|
||||||
|
if self.outer_list {
|
||||||
|
self.menu_items.get_mut(self.tabs.items.get(self.tabs.state.selected().unwrap()).unwrap()).unwrap().previous();
|
||||||
|
self.set_sub_menu_items();
|
||||||
|
} else {
|
||||||
|
self.sub_menu_previous();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_down(&mut self) {
|
||||||
|
if self.outer_list {
|
||||||
|
self.menu_items.get_mut(self.tabs.items.get(self.tabs.state.selected().unwrap()).unwrap()).unwrap().next();
|
||||||
|
self.set_sub_menu_items();
|
||||||
|
} else {
|
||||||
|
self.sub_menu_next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_left(&mut self) {
|
||||||
|
if self.outer_list {
|
||||||
|
self.menu_items.get_mut(self.tabs.items.get(self.tabs.state.selected().unwrap()).unwrap()).unwrap().previous_list();
|
||||||
|
self.set_sub_menu_items();
|
||||||
|
} else {
|
||||||
|
self.sub_menu_previous_list();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_right(&mut self) {
|
||||||
|
if self.outer_list {
|
||||||
|
self.menu_items.get_mut(self.tabs.items.get(self.tabs.state.selected().unwrap()).unwrap()).unwrap().next_list();
|
||||||
|
self.set_sub_menu_items();
|
||||||
|
} else {
|
||||||
|
self.sub_menu_next_list();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) -> String {
|
||||||
|
let app_tabs = &app.tabs;
|
||||||
|
let tab_selected = app_tabs.state.selected().unwrap();
|
||||||
|
let titles = app_tabs.items.iter().cloned().enumerate().map(|(idx, tab)|{
|
||||||
|
if idx == tab_selected {
|
||||||
|
Spans::from(">> ".to_owned() + tab)
|
||||||
|
} else {
|
||||||
|
Spans::from(" ".to_owned() + tab)
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
|
let tabs = Tabs::new(titles)
|
||||||
|
.block(Block::default().title("Ultimate Training Modpack Menu"))
|
||||||
|
.style(Style::default().fg(Color::White))
|
||||||
|
.highlight_style(Style::default().fg(Color::Yellow))
|
||||||
|
.divider("|")
|
||||||
|
.select(tab_selected);
|
||||||
|
|
||||||
|
let vertical_chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([
|
||||||
|
Constraint::Length(2),
|
||||||
|
Constraint::Max(10),
|
||||||
|
Constraint::Length(2)].as_ref())
|
||||||
|
.split(f.size());
|
||||||
|
|
||||||
|
let list_chunks = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints([Constraint::Percentage(33), Constraint::Percentage(32), Constraint::Percentage(33)].as_ref())
|
||||||
|
.split(vertical_chunks[1]);
|
||||||
|
|
||||||
|
f.render_widget(tabs, vertical_chunks[0]);
|
||||||
|
|
||||||
|
if app.outer_list {
|
||||||
|
let tab_selected = app.tab_selected();
|
||||||
|
let mut item_help = None;
|
||||||
|
for list_section in 0..app.menu_items.get(tab_selected).unwrap().lists.len() {
|
||||||
|
let stateful_list = &app.menu_items.get(tab_selected).unwrap().lists[list_section];
|
||||||
|
let items: Vec<ListItem> = stateful_list
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.map(|i| {
|
||||||
|
let lines = vec![Spans::from(
|
||||||
|
if stateful_list.state.selected().is_some() {
|
||||||
|
i.title.to_owned()
|
||||||
|
} else {
|
||||||
|
" ".to_owned() + i.title
|
||||||
|
})];
|
||||||
|
ListItem::new(lines).style(Style::default().fg(Color::Black).bg(Color::White))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let list = List::new(items)
|
||||||
|
.block(Block::default().title(if list_section == 0 { "Options" } else { "" }))
|
||||||
|
.highlight_style(
|
||||||
|
Style::default()
|
||||||
|
.bg(Color::LightBlue)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
)
|
||||||
|
.highlight_symbol(">> ");
|
||||||
|
|
||||||
|
let mut state = stateful_list.state.clone();
|
||||||
|
if state.selected().is_some() {
|
||||||
|
item_help = Some(stateful_list.items[state.selected().unwrap()].help_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
f.render_stateful_widget(list, list_chunks[list_section], &mut state);
|
||||||
|
}
|
||||||
|
|
||||||
|
let help_paragraph = Paragraph::new(
|
||||||
|
item_help.unwrap_or("").replace("\"", "") +
|
||||||
|
"\nA: Enter sub-menu | B: Exit menu | ZL/ZR: Next tab | R: Save defaults"
|
||||||
|
);
|
||||||
|
f.render_widget(help_paragraph, vertical_chunks[2]);
|
||||||
|
} else {
|
||||||
|
let (title, help_text, mut sub_menu_str_lists) = app.sub_menu_strs_and_states();
|
||||||
|
for list_section in 0..sub_menu_str_lists.len() {
|
||||||
|
let sub_menu_str = sub_menu_str_lists[list_section].0.clone();
|
||||||
|
let sub_menu_state = &mut sub_menu_str_lists[list_section].1;
|
||||||
|
let values_items: Vec<ListItem> = sub_menu_str.iter().map(|s| {
|
||||||
|
ListItem::new(
|
||||||
|
vec![
|
||||||
|
Spans::from((if s.0 == "is-appear" { "X " } else { " " }).to_owned() + s.1)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let values_list = List::new(values_items)
|
||||||
|
.block(Block::default().title(if list_section == 0 { title } else { "" }))
|
||||||
|
.start_corner(Corner::TopLeft)
|
||||||
|
.highlight_style(
|
||||||
|
Style::default()
|
||||||
|
.bg(Color::LightGreen)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
)
|
||||||
|
.highlight_symbol(">> ");
|
||||||
|
f.render_stateful_widget(values_list, list_chunks[list_section], sub_menu_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
let help_paragraph = Paragraph::new(
|
||||||
|
help_text.replace("\"", "") +
|
||||||
|
"\nA: Select toggle | B: Exit submenu"
|
||||||
|
);
|
||||||
|
f.render_widget(help_paragraph, vertical_chunks[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let mut url = "http://localhost/".to_owned();
|
||||||
|
let mut settings = HashMap::new();
|
||||||
|
|
||||||
|
// Collect settings for toggles
|
||||||
|
for key in app.menu_items.keys() {
|
||||||
|
for list in &app.menu_items.get(key).unwrap().lists {
|
||||||
|
for sub_menu in &list.items {
|
||||||
|
let mut val = String::new();
|
||||||
|
sub_menu.toggles.iter()
|
||||||
|
.filter(|t| t.checked == "is-appear")
|
||||||
|
.for_each(|t| val.push_str(format!("{},", t.value).as_str()));
|
||||||
|
|
||||||
|
sub_menu.onoffselector.iter()
|
||||||
|
.for_each(|o| {
|
||||||
|
val.push_str(
|
||||||
|
format!("{}", if o.checked == "is-appear" { 1 } else { 0 }).as_str())
|
||||||
|
});
|
||||||
|
settings.insert(sub_menu.id, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
url.push_str("?");
|
||||||
|
settings.iter()
|
||||||
|
.for_each(|(section, val)| url.push_str(format!("{}={}&", section, val).as_str()));
|
||||||
|
url
|
||||||
|
|
||||||
|
// TODO: Add saveDefaults
|
||||||
|
// if (document.getElementById("saveDefaults").checked) {
|
||||||
|
// url += "save_defaults=1";
|
||||||
|
// } else {
|
||||||
|
// url = url.slice(0, -1);
|
||||||
|
// }
|
||||||
|
}
|
191
training_mod_tui/src/list.rs
Normal file
191
training_mod_tui/src/list.rs
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
use tui::widgets::ListState;
|
||||||
|
|
||||||
|
pub struct MultiStatefulList<T> {
|
||||||
|
pub lists: Vec<StatefulList<T>>,
|
||||||
|
pub state: usize,
|
||||||
|
pub total_len: usize
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> MultiStatefulList<T> {
|
||||||
|
pub fn selected_list_item(&mut self) -> &mut T {
|
||||||
|
let (list_section, list_idx) = self.idx_to_list_idx(self.state);
|
||||||
|
&mut self
|
||||||
|
.lists[list_section]
|
||||||
|
.items[list_idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn idx_to_list_idx(&self, idx: usize) -> (usize, usize) {
|
||||||
|
for list_section in 0..self.lists.len() {
|
||||||
|
let list_section_min_idx = (self.total_len as f32 / self.lists.len() as f32).ceil() as usize * list_section;
|
||||||
|
let list_section_max_idx = std::cmp::min(
|
||||||
|
(self.total_len as f32 / self.lists.len() as f32).ceil() as usize * (list_section + 1),
|
||||||
|
self.total_len);
|
||||||
|
if (list_section_min_idx..list_section_max_idx).contains(&idx) {
|
||||||
|
// println!("\n{}: ({}, {})", idx, list_section_min_idx, list_section_max_idx);
|
||||||
|
return (list_section, idx - list_section_min_idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_idx_to_idx(&self, list_idx: (usize, usize)) -> usize {
|
||||||
|
let list_section = list_idx.0;
|
||||||
|
let mut list_idx = list_idx.1;
|
||||||
|
for list_section in 0..list_section {
|
||||||
|
list_idx += self.lists[list_section].items.len();
|
||||||
|
}
|
||||||
|
list_idx
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_items(items: Vec<T>, num_lists: usize) -> MultiStatefulList<T> {
|
||||||
|
let lists = (0..num_lists).map(|list_section| {
|
||||||
|
let list_section_min_idx = (items.len() as f32 / num_lists as f32).ceil() as usize * list_section;
|
||||||
|
let list_section_max_idx = std::cmp::min(
|
||||||
|
(items.len() as f32 / num_lists as f32).ceil() as usize * (list_section + 1),
|
||||||
|
items.len());
|
||||||
|
let mut state = ListState::default();
|
||||||
|
if list_section == 0 {
|
||||||
|
// Enforce state as first of list
|
||||||
|
state.select(Some(0));
|
||||||
|
}
|
||||||
|
StatefulList {
|
||||||
|
state: state,
|
||||||
|
items: items[list_section_min_idx..list_section_max_idx].to_vec(),
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
|
let total_len = items.len();
|
||||||
|
MultiStatefulList {
|
||||||
|
lists: lists,
|
||||||
|
total_len: total_len,
|
||||||
|
state: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self) {
|
||||||
|
let (list_section, _) = self.idx_to_list_idx(self.state);
|
||||||
|
let (next_list_section, next_list_idx) = self.idx_to_list_idx(self.state+1);
|
||||||
|
|
||||||
|
if list_section != next_list_section {
|
||||||
|
self.lists[list_section].unselect();
|
||||||
|
}
|
||||||
|
let state;
|
||||||
|
if self.state + 1 >= self.total_len {
|
||||||
|
state = (0, 0);
|
||||||
|
} else {
|
||||||
|
state = (next_list_section, next_list_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.lists[state.0].state.select(Some(state.1));
|
||||||
|
self.state = self.list_idx_to_idx(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn previous(&mut self) {
|
||||||
|
let (list_section, _) = self.idx_to_list_idx(self.state);
|
||||||
|
let (last_list_section, last_list_idx) = (self.lists.len() - 1, self.lists[self.lists.len() - 1].items.len() - 1);
|
||||||
|
|
||||||
|
self.lists[list_section].unselect();
|
||||||
|
let state;
|
||||||
|
if self.state == 0 {
|
||||||
|
state = (last_list_section, last_list_idx);
|
||||||
|
} else {
|
||||||
|
let (prev_list_section, prev_list_idx) = self.idx_to_list_idx(self.state - 1);
|
||||||
|
state = (prev_list_section, prev_list_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.lists[state.0].state.select(Some(state.1));
|
||||||
|
self.state = self.list_idx_to_idx(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_list(&mut self) {
|
||||||
|
let (list_section, list_idx) = self.idx_to_list_idx(self.state);
|
||||||
|
let next_list_section = (list_section + 1) % self.lists.len();
|
||||||
|
let next_list_idx;
|
||||||
|
if list_idx > self.lists[next_list_section].items.len() - 1 {
|
||||||
|
next_list_idx = self.lists[next_list_section].items.len() - 1;
|
||||||
|
} else {
|
||||||
|
next_list_idx = list_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
if list_section != next_list_section {
|
||||||
|
self.lists[list_section].unselect();
|
||||||
|
}
|
||||||
|
let state = (next_list_section, next_list_idx);
|
||||||
|
|
||||||
|
self.lists[state.0].state.select(Some(state.1));
|
||||||
|
self.state = self.list_idx_to_idx(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn previous_list(&mut self) {
|
||||||
|
let (list_section, list_idx) = self.idx_to_list_idx(self.state);
|
||||||
|
let prev_list_section;
|
||||||
|
if list_section == 0 {
|
||||||
|
prev_list_section = self.lists.len() - 1;
|
||||||
|
} else {
|
||||||
|
prev_list_section = list_section - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let prev_list_idx;
|
||||||
|
if list_idx > self.lists[prev_list_section].items.len() - 1 {
|
||||||
|
prev_list_idx = self.lists[prev_list_section].items.len() - 1;
|
||||||
|
} else {
|
||||||
|
prev_list_idx = list_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
if list_section != prev_list_section {
|
||||||
|
self.lists[list_section].unselect();
|
||||||
|
}
|
||||||
|
let state = (prev_list_section, prev_list_idx);
|
||||||
|
|
||||||
|
self.lists[state.0].state.select(Some(state.1));
|
||||||
|
self.state = self.list_idx_to_idx(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StatefulList<T> {
|
||||||
|
pub state: ListState,
|
||||||
|
pub items: Vec<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> StatefulList<T> {
|
||||||
|
pub fn with_items(items: Vec<T>) -> StatefulList<T> {
|
||||||
|
let mut state = ListState::default();
|
||||||
|
// Enforce state as first of list
|
||||||
|
state.select(Some(0));
|
||||||
|
StatefulList {
|
||||||
|
state: state,
|
||||||
|
items,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self) {
|
||||||
|
let i = match self.state.selected() {
|
||||||
|
Some(i) => {
|
||||||
|
if i >= self.items.len() - 1 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
self.state.select(Some(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn previous(&mut self) {
|
||||||
|
let i = match self.state.selected() {
|
||||||
|
Some(i) => {
|
||||||
|
if i == 0 {
|
||||||
|
self.items.len() - 1
|
||||||
|
} else {
|
||||||
|
i - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
self.state.select(Some(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unselect(&mut self) {
|
||||||
|
self.state.select(None);
|
||||||
|
}
|
||||||
|
}
|
113
training_mod_tui/src/main.rs
Normal file
113
training_mod_tui/src/main.rs
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
#[cfg(feature = "has_terminal")]
|
||||||
|
use crossterm::{
|
||||||
|
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||||
|
execute,
|
||||||
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "has_terminal")]
|
||||||
|
use tui::backend::CrosstermBackend;
|
||||||
|
#[cfg(feature = "has_terminal")]
|
||||||
|
use std::{
|
||||||
|
io,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
use std::error::Error;
|
||||||
|
use tui::Terminal;
|
||||||
|
|
||||||
|
use training_mod_consts::*;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let menu;
|
||||||
|
unsafe {
|
||||||
|
menu = get_menu();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "has_terminal"))] {
|
||||||
|
let mut app = training_mod_tui::App::new(menu);
|
||||||
|
let backend = tui::backend::TestBackend::new(75, 15);
|
||||||
|
let mut terminal = Terminal::new(backend)?;
|
||||||
|
let mut state = tui::widgets::ListState::default();
|
||||||
|
state.select(Some(1));
|
||||||
|
let mut url = String::new();
|
||||||
|
let frame_res = terminal.draw(|f| url = training_mod_tui::ui(f, &mut app))?;
|
||||||
|
|
||||||
|
for (i, cell) in frame_res.buffer.content().into_iter().enumerate() {
|
||||||
|
print!("{}", cell.symbol);
|
||||||
|
if i % frame_res.area.width as usize == frame_res.area.width as usize - 1 {
|
||||||
|
print!("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
|
||||||
|
println!("URL: {}", url);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "has_terminal")] {
|
||||||
|
let app = training_mod_tui::App::new(menu);
|
||||||
|
|
||||||
|
// setup terminal
|
||||||
|
enable_raw_mode()?;
|
||||||
|
let mut stdout = io::stdout();
|
||||||
|
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||||
|
let backend = CrosstermBackend::new(stdout);
|
||||||
|
let mut terminal = Terminal::new(backend)?;
|
||||||
|
|
||||||
|
let tick_rate = Duration::from_millis(250);
|
||||||
|
let res = run_app(&mut terminal, app, tick_rate);
|
||||||
|
|
||||||
|
// restore terminal
|
||||||
|
disable_raw_mode()?;
|
||||||
|
execute!(
|
||||||
|
terminal.backend_mut(),
|
||||||
|
LeaveAlternateScreen,
|
||||||
|
DisableMouseCapture
|
||||||
|
)?;
|
||||||
|
terminal.show_cursor()?;
|
||||||
|
|
||||||
|
if let Err(err) = res {
|
||||||
|
println!("{:?}", err)
|
||||||
|
} else {
|
||||||
|
println!("URL: {}", res.as_ref().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "has_terminal")]
|
||||||
|
fn run_app<B: tui::backend::Backend>(
|
||||||
|
terminal: &mut Terminal<B>,
|
||||||
|
mut app: training_mod_tui::App,
|
||||||
|
tick_rate: Duration,
|
||||||
|
) -> io::Result<String> {
|
||||||
|
let mut last_tick = Instant::now();
|
||||||
|
let mut url = String::new();
|
||||||
|
loop {
|
||||||
|
terminal.draw(|f| url = training_mod_tui::ui(f, &mut app).clone())?;
|
||||||
|
|
||||||
|
let timeout = tick_rate
|
||||||
|
.checked_sub(last_tick.elapsed())
|
||||||
|
.unwrap_or_else(|| Duration::from_secs(0));
|
||||||
|
|
||||||
|
if crossterm::event::poll(timeout)? {
|
||||||
|
if let Event::Key(key) = event::read()? {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Char('q') => return Ok(url),
|
||||||
|
KeyCode::Char('r') => app.on_r(),
|
||||||
|
KeyCode::Char('l') => app.on_l(),
|
||||||
|
KeyCode::Left => app.on_left(),
|
||||||
|
KeyCode::Right => app.on_right(),
|
||||||
|
KeyCode::Down => app.on_down(),
|
||||||
|
KeyCode::Up => app.on_up(),
|
||||||
|
KeyCode::Enter => app.on_a(),
|
||||||
|
KeyCode::Backspace => app.on_b(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if last_tick.elapsed() >= tick_rate {
|
||||||
|
last_tick = Instant::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
training_mod_tui/training_mod_tui.iml
Normal file
12
training_mod_tui/training_mod_tui.iml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="RUST_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
Loading…
Reference in a new issue