From 884c00b069ae3cb2ed0e5bd17d582e2069b79d52 Mon Sep 17 00:00:00 2001 From: jugeeya Date: Sun, 20 Feb 2022 19:14:22 -0800 Subject: [PATCH] Metrics Pipeline (#278) * initial commit * plotting working * allow plotting * Change spacing in workflow YML * Fix get_random_option calls * Format Rust code using rustfmt * Amend removed feature Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/workflows/rust.yml | 240 +++--- Cargo.toml | 101 +-- src/common/consts.rs | 952 +---------------------- src/common/events.rs | 359 ++++----- src/common/menu.rs | 1246 +++++++++++++++--------------- src/common/mod.rs | 343 ++++---- src/common/raygun_printer.rs | 486 ++++++------ src/common/release.rs | 72 +- src/lib.rs | 276 ++++--- src/templates/buff_state.svg | 164 ++-- src/templates/menu.html | 974 +++++++++++------------ src/templates/pummel_delay.svg | 232 +++--- src/templates/throw_delay.svg | 254 +++--- src/templates/throw_state.svg | 402 +++++----- src/training/buff.rs | 492 ++++++------ src/training/mod.rs | 1070 ++++++++++++------------- src/training/reset.rs | 98 +-- src/training/save_states.rs | 3 +- src/training/tech.rs | 544 ++++++------- src/training/throw.rs | 308 ++++---- training_mod_consts/Cargo.toml | 13 + training_mod_consts/src/lib.rs | 960 +++++++++++++++++++++++ training_mod_metrics/Cargo.toml | 12 + training_mod_metrics/src/main.rs | 144 ++++ 24 files changed, 4958 insertions(+), 4787 deletions(-) create mode 100644 training_mod_consts/Cargo.toml create mode 100644 training_mod_consts/src/lib.rs create mode 100644 training_mod_metrics/Cargo.toml create mode 100644 training_mod_metrics/src/main.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 284014d..4d880db 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,120 +1,120 @@ -name: Rust - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - workflow_dispatch: - -jobs: - clippy_pr: - runs-on: ubuntu-latest - container: jugeeya/cargo-skyline:2.1.0-dkp - steps: - - uses: actions/checkout@v2 - - name: Setup PATH - run: export PATH=$PATH:/root/.cargo/bin:/opt/devkitpro/devkitA64/bin - - name: Install minimal nightly rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly-2021-06-01 - components: rustfmt, clippy - default: true - target: x86_64-unknown-linux-gnu - - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - toolchain: nightly-2021-06-01 - args: --all-features --target=x86_64-unknown-linux-gnu - - uses: mbrobbel/rustfmt-check@master - with: - token: ${{ secrets.GITHUB_TOKEN }} - plugin: - runs-on: ubuntu-latest - container: - image: jugeeya/cargo-skyline:2.1.0-dkp - steps: - - uses: actions/checkout@v2 - - name: Build release NRO - run: | - PATH=$PATH:/root/.cargo/bin:/opt/devkitpro/devkitA64/bin /root/.cargo/bin/cargo-skyline skyline build --release - env: - HOME: /root - - name: Upload plugin artifact - uses: actions/upload-artifact@v2 - with: - name: plugin - path: target/aarch64-skyline-switch/release/libtraining_modpack.nro - - name: Upload menu icons - uses: actions/upload-artifact@v2 - with: - name: svg - path: src/templates/*.svg - plugin_outside_training_mode: - if: github.ref == 'refs/heads/master' - runs-on: ubuntu-latest - container: - image: jugeeya/cargo-skyline:2.1.0-dkp - steps: - - uses: actions/checkout@v2 - - name: Build outside_training_mode NRO - run: | - PATH=$PATH:/root/.cargo/bin:/opt/devkitpro/devkitA64/bin /root/.cargo/bin/cargo-skyline skyline build --release --features outside_training_mode - env: - HOME: /root - - name: Upload plugin (outside training mode) artifact - uses: actions/upload-artifact@v2 - with: - name: plugin_outside_training_mode - path: target/aarch64-skyline-switch/release/libtraining_modpack.nro - upload: - runs-on: ubuntu-latest - if: github.ref == 'refs/heads/master' - needs: - - plugin - steps: - - name: Download all artifacts - uses: actions/download-artifact@v2 - - name: Prepare zip - env: - SKYLINE_DIR: atmosphere/contents/01006A800016E000 - SMASH_PLUGIN_DIR: atmosphere/contents/01006A800016E000/romfs/skyline/plugins - SMASH_WEB_DIR: atmosphere/contents/01006A800016E000/manual_html/html-document/contents.htdocs - run: | - mkdir -p ${{env.SKYLINE_DIR}} - mkdir -p ${{env.SMASH_PLUGIN_DIR}} - mkdir -p ${{env.SMASH_WEB_DIR}} - wget https://github.com/skyline-dev/skyline/releases/download/beta/skyline.zip - unzip skyline.zip - mv exefs ${{env.SKYLINE_DIR}} - cp plugin/libtraining_modpack.nro ${{env.SMASH_PLUGIN_DIR}}/libtraining_modpack.nro - wget https://github.com/ultimate-research/params-hook-plugin/releases/download/v0.1.1/libparam_hook.nro - wget https://github.com/ultimate-research/nro-hook-plugin/releases/download/v0.3.0/libnro_hook.nro - wget https://github.com/jugeeya/nn-hid-hook/releases/download/beta/libnn_hid_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 libnn_hid_hook.nro ${{env.SMASH_PLUGIN_DIR}}/libnn_hid_hook.nro - ls -1 svg/*.svg | xargs -n 1 basename | xargs -L1 -I{} cp svg/{} ${{env.SMASH_WEB_DIR}}/{} - zip -r training_modpack_beta.zip atmosphere - - name: Update Release - uses: meeDamian/github-release@2.0 - with: - token: ${{ secrets.GITHUB_TOKEN }} - prerelease: true - allow_override: true - gzip: false - tag: beta - name: beta - body: > - Beta built off of the latest code in the repository. - - Install the same way you would install a full release. - files: > - training_modpack_beta.zip - - name: Upload zip as artifact - uses: actions/upload-artifact@v1 - with: - name: full_build - path: training_modpack_beta.zip +name: Rust + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + workflow_dispatch: + +jobs: + clippy_pr: + runs-on: ubuntu-latest + container: jugeeya/cargo-skyline:2.1.0-dkp + steps: + - uses: actions/checkout@v2 + - name: Setup PATH + run: export PATH=$PATH:/root/.cargo/bin:/opt/devkitpro/devkitA64/bin + - name: Install minimal nightly rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2021-06-01 + components: rustfmt, clippy + default: true + target: x86_64-unknown-linux-gnu + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + toolchain: nightly-2021-06-01 + args: --all-features --target=x86_64-unknown-linux-gnu + - uses: mbrobbel/rustfmt-check@master + with: + token: ${{ secrets.GITHUB_TOKEN }} + plugin: + runs-on: ubuntu-latest + container: + image: jugeeya/cargo-skyline:2.1.0-dkp + steps: + - uses: actions/checkout@v2 + - name: Build release NRO + run: | + PATH=$PATH:/root/.cargo/bin:/opt/devkitpro/devkitA64/bin /root/.cargo/bin/cargo-skyline skyline build --release + env: + HOME: /root + - name: Upload plugin artifact + uses: actions/upload-artifact@v2 + with: + name: plugin + path: target/aarch64-skyline-switch/release/libtraining_modpack.nro + - name: Upload menu icons + uses: actions/upload-artifact@v2 + with: + name: svg + path: src/templates/*.svg + plugin_outside_training_mode: + if: github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + container: + image: jugeeya/cargo-skyline:2.1.0-dkp + steps: + - uses: actions/checkout@v2 + - name: Build outside_training_mode NRO + run: | + PATH=$PATH:/root/.cargo/bin:/opt/devkitpro/devkitA64/bin /root/.cargo/bin/cargo-skyline skyline build --release --features outside_training_mode + env: + HOME: /root + - name: Upload plugin (outside training mode) artifact + uses: actions/upload-artifact@v2 + with: + name: plugin_outside_training_mode + path: target/aarch64-skyline-switch/release/libtraining_modpack.nro + upload: + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/master' + needs: + - plugin + steps: + - name: Download all artifacts + uses: actions/download-artifact@v2 + - name: Prepare zip + env: + SKYLINE_DIR: atmosphere/contents/01006A800016E000 + SMASH_PLUGIN_DIR: atmosphere/contents/01006A800016E000/romfs/skyline/plugins + SMASH_WEB_DIR: atmosphere/contents/01006A800016E000/manual_html/html-document/contents.htdocs + run: | + mkdir -p ${{env.SKYLINE_DIR}} + mkdir -p ${{env.SMASH_PLUGIN_DIR}} + mkdir -p ${{env.SMASH_WEB_DIR}} + wget https://github.com/skyline-dev/skyline/releases/download/beta/skyline.zip + unzip skyline.zip + mv exefs ${{env.SKYLINE_DIR}} + cp plugin/libtraining_modpack.nro ${{env.SMASH_PLUGIN_DIR}}/libtraining_modpack.nro + wget https://github.com/ultimate-research/params-hook-plugin/releases/download/v0.1.1/libparam_hook.nro + wget https://github.com/ultimate-research/nro-hook-plugin/releases/download/v0.3.0/libnro_hook.nro + wget https://github.com/jugeeya/nn-hid-hook/releases/download/beta/libnn_hid_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 libnn_hid_hook.nro ${{env.SMASH_PLUGIN_DIR}}/libnn_hid_hook.nro + ls -1 svg/*.svg | xargs -n 1 basename | xargs -L1 -I{} cp svg/{} ${{env.SMASH_WEB_DIR}}/{} + zip -r training_modpack_beta.zip atmosphere + - name: Update Release + uses: meeDamian/github-release@2.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + prerelease: true + allow_override: true + gzip: false + tag: beta + name: beta + body: > + Beta built off of the latest code in the repository. + + Install the same way you would install a full release. + files: > + training_modpack_beta.zip + - name: Upload zip as artifact + uses: actions/upload-artifact@v1 + with: + name: full_build + path: training_modpack_beta.zip \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index da0cc30..61508f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,50 +1,51 @@ -[package] -name = "training_modpack" -version = "3.2.0" -authors = ["jugeeya "] -edition = "2018" - -[lib] -crate-type = ["cdylib"] - -[dependencies] -skyline = { git = "https://github.com/ultimate-research/skyline-rs.git" } -skyline_smash = { git = "https://github.com/ultimate-research/skyline-smash.git" } -skyline-web = { git = "https://github.com/skyline-rs/skyline-web.git" } -bitflags = "1.2.1" -parking_lot = { version = "0.11.1", features = ["nightly"] } -lazy_static = "1.4.0" -owo-colors = "1.1.3" -ramhorns = "0.10.1" -paste = "1.0" -num = "0.3.0" -num-derive = "0.3" -num-traits = "0.2" -wsl = "0.1.0" -strum = "0.21.0" -strum_macros = "0.21.0" -minreq = { version = "=2.2.1", features = ["https", "json-using-serde"] } -serde = { version = "1", features = ["derive"] } -serde_json = "1" - -[patch.crates-io] -ring = { git = "https://github.com/skyline-rs/ring", branch = "0.16.20" } -webpki = { git = "https://github.com/skyline-rs/webpki" } - -[profile.dev] -panic = "abort" - -[profile.release] -panic = "abort" -lto = true - -[package.metadata.skyline] -titleid = "01006A800016E000" -plugin-dependencies = [ - { name = "libnro_hook.nro", url = "https://github.com/ultimate-research/nro-hook-plugin/releases/download/v0.3.0/libnro_hook.nro" }, - { name = "libparam_hook.nro", url = "https://github.com/ultimate-research/params-hook-plugin/releases/download/v0.1.1/libparam_hook.nro" }, - { name = "libnn_hid_hook.nro", url = "https://github.com/jugeeya/nn-hid-hook/releases/download/beta/libnn_hid_hook.nro" } -] - -[features] -outside_training_mode = [] +[package] +name = "training_modpack" +version = "3.2.0" +authors = ["jugeeya "] +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +skyline = { git = "https://github.com/ultimate-research/skyline-rs.git" } +skyline_smash = { git = "https://github.com/ultimate-research/skyline-smash.git" } +skyline-web = { git = "https://github.com/skyline-rs/skyline-web.git" } +bitflags = "1.2.1" +parking_lot = { version = "0.11.1", features = ["nightly"] } +lazy_static = "1.4.0" +owo-colors = "2.1.0" +ramhorns = "0.12.0" +paste = "1.0" +num = "0.4.0" +num-derive = "0.3" +num-traits = "0.2" +wsl = "0.1.0" +strum = "0.21.0" +strum_macros = "0.21.0" +minreq = { version = "=2.2.1", features = ["https", "json-using-serde"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +training_mod_consts = { path = "training_mod_consts" } + +[patch.crates-io] +ring = { git = "https://github.com/skyline-rs/ring", branch = "0.16.20" } +webpki = { git = "https://github.com/skyline-rs/webpki" } + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +lto = true + +[package.metadata.skyline] +titleid = "01006A800016E000" +plugin-dependencies = [ + { name = "libnro_hook.nro", url = "https://github.com/ultimate-research/nro-hook-plugin/releases/download/v0.3.0/libnro_hook.nro" }, + { name = "libparam_hook.nro", url = "https://github.com/ultimate-research/params-hook-plugin/releases/download/v0.1.1/libparam_hook.nro" }, + { name = "libnn_hid_hook.nro", url = "https://github.com/jugeeya/nn-hid-hook/releases/download/beta/libnn_hid_hook.nro" } +] + +[features] +outside_training_mode = [] diff --git a/src/common/consts.rs b/src/common/consts.rs index 55fd853..8c33c17 100644 --- a/src/common/consts.rs +++ b/src/common/consts.rs @@ -1,951 +1 @@ -use crate::common::get_random_int; -use core::f64::consts::PI; -use smash::lib::lua_const::*; -use strum_macros::EnumIter; - -// bitflag helper function macro -macro_rules! extra_bitflag_impls { - ($e:ty) => { - impl core::fmt::Display for $e { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - core::fmt::Debug::fmt(self, f) - } - } - - impl $e { - pub fn to_vec(&self) -> Vec::<$e> { - let mut vec = Vec::<$e>::new(); - let mut field = <$e>::from_bits_truncate(self.bits); - while !field.is_empty() { - let flag = <$e>::from_bits(1u32 << field.bits.trailing_zeros()).unwrap(); - field -= flag; - vec.push(flag); - } - return vec; - } - - pub fn to_index(&self) -> u32 { - if self.bits == 0 { - 0 - } else { - self.bits.trailing_zeros() - } - } - - pub fn get_random(&self) -> $e { - let options = self.to_vec(); - match options.len() { - 0 => { - return <$e>::empty(); - } - 1 => { - return options[0]; - } - _ => { - return *random_option(&options); - } - } - } - - pub fn to_toggle_strs() -> Vec<&'static str> { - let all_options = <$e>::all().to_vec(); - all_options.iter().map(|i| i.as_str().unwrap_or("")).collect() - } - - pub fn to_toggle_vals() -> Vec { - let all_options = <$e>::all().to_vec(); - all_options.iter().map(|i| i.bits() as usize).collect() - } - pub fn to_url_param(&self) -> String { - self.to_vec() - .into_iter() - .map(|field| field.bits().to_string()) - .collect::>() - .join(",") - } - } - } -} - -pub fn random_option(arg: &[T]) -> &T { - &arg[get_random_int(arg.len() as i32) as usize] -} - -// DI -/* - 0, 0.785398, 1.570796, 2.356194, -3.14159, -2.356194, -1.570796, -0.785398 - 0, pi/4, pi/2, 3pi/4, pi, 5pi/4, 3pi/2, 7pi/4 -*/ - -// DI / Left stick -bitflags! { - pub struct Direction : u32 - { - const OUT = 0x1; - const UP_OUT = 0x2; - const UP = 0x4; - const UP_IN = 0x8; - const IN = 0x10; - const DOWN_IN = 0x20; - const DOWN = 0x40; - const DOWN_OUT = 0x80; - const NEUTRAL = 0x100; - const LEFT = 0x200; - const RIGHT = 0x400; - } -} - -impl Direction { - pub fn into_angle(self) -> Option { - let index = self.into_index(); - - if index == 0 { - None - } else { - Some((index as i32 - 1) as f64 * PI / 4.0) - } - } - fn into_index(self) -> i32 { - match self { - Direction::OUT => 1, - Direction::UP_OUT => 2, - Direction::UP => 3, - Direction::UP_IN => 4, - Direction::IN => 5, - Direction::DOWN_IN => 6, - Direction::DOWN => 7, - Direction::DOWN_OUT => 8, - Direction::NEUTRAL => 0, - Direction::LEFT => 5, - Direction::RIGHT => 1, - _ => 0, - } - } - - fn as_str(self) -> Option<&'static str> { - Some(match self { - Direction::OUT => "Away", - Direction::UP_OUT => "Up and Away", - Direction::UP => "Up", - Direction::UP_IN => "Up and In", - Direction::IN => "In", - Direction::DOWN_IN => "Down and In", - Direction::DOWN => "Down", - Direction::DOWN_OUT => "Down and Away", - Direction::NEUTRAL => "Neutral", - Direction::LEFT => "Left", - Direction::RIGHT => "Right", - _ => return None, - }) - } -} - -extra_bitflag_impls! {Direction} - -// Ledge Option -bitflags! { - pub struct LedgeOption : u32 - { - const NEUTRAL = 0x1; - const ROLL = 0x2; - const JUMP = 0x4; - const ATTACK = 0x8; - const WAIT = 0x10; - } -} - -impl LedgeOption { - pub fn into_status(self) -> Option { - Some(match self { - LedgeOption::NEUTRAL => *FIGHTER_STATUS_KIND_CLIFF_CLIMB, - LedgeOption::ROLL => *FIGHTER_STATUS_KIND_CLIFF_ESCAPE, - LedgeOption::JUMP => *FIGHTER_STATUS_KIND_CLIFF_JUMP1, - LedgeOption::ATTACK => *FIGHTER_STATUS_KIND_CLIFF_ATTACK, - LedgeOption::WAIT => *FIGHTER_STATUS_KIND_CLIFF_WAIT, - _ => return None, - }) - } - - fn as_str(self) -> Option<&'static str> { - Some(match self { - LedgeOption::NEUTRAL => "Neutral Getup", - LedgeOption::ROLL => "Roll", - LedgeOption::JUMP => "Jump", - LedgeOption::ATTACK => "Getup Attack", - LedgeOption::WAIT => "Wait", - _ => return None, - }) - } -} - -extra_bitflag_impls! {LedgeOption} - -// Tech options -bitflags! { - pub struct TechFlags : u32 { - const NO_TECH = 0x1; - const ROLL_F = 0x2; - const ROLL_B = 0x4; - const IN_PLACE = 0x8; - } -} - -impl TechFlags { - fn as_str(self) -> Option<&'static str> { - Some(match self { - TechFlags::NO_TECH => "No Tech", - TechFlags::ROLL_F => "Roll Forwards", - TechFlags::ROLL_B => "Roll Backwards", - TechFlags::IN_PLACE => "Tech In Place", - _ => return None, - }) - } -} - -extra_bitflag_impls! {TechFlags} - -// Missed Tech Options -bitflags! { - pub struct MissTechFlags : u32 { - const GETUP = 0x1; - const ATTACK = 0x2; - const ROLL_F = 0x4; - const ROLL_B = 0x8; - } -} - -impl MissTechFlags { - fn as_str(self) -> Option<&'static str> { - Some(match self { - MissTechFlags::GETUP => "Neutral Getup", - MissTechFlags::ATTACK => "Getup Attack", - MissTechFlags::ROLL_F => "Roll Forwards", - MissTechFlags::ROLL_B => "Roll Backwards", - _ => return None, - }) - } -} - -extra_bitflag_impls! {MissTechFlags} - -/// Shield States -#[repr(i32)] -#[derive(Debug, Clone, Copy, PartialEq, FromPrimitive, EnumIter)] -pub enum Shield { - None = 0, - Infinite = 1, - Hold = 2, - Constant = 3, -} - -impl Shield { - pub fn as_str(self) -> Option<&'static str> { - Some(match self { - Shield::None => "None", - Shield::Infinite => "Infinite", - Shield::Hold => "Hold", - Shield::Constant => "Constant", - }) - } - - pub fn to_url_param(&self) -> String { - (*self as i32).to_string() - } -} - -// Save State Mirroring -#[repr(i32)] -#[derive(Debug, Clone, Copy, PartialEq, FromPrimitive, EnumIter)] -pub enum SaveStateMirroring { - None = 0, - Alternate = 1, - Random = 2, -} - -impl SaveStateMirroring { - pub fn as_str(self) -> Option<&'static str> { - Some(match self { - SaveStateMirroring::None => "None", - SaveStateMirroring::Alternate => "Alternate", - SaveStateMirroring::Random => "Random", - }) - } - - fn to_url_param(&self) -> String { - (*self as i32).to_string() - } -} - -// Defensive States -bitflags! { - pub struct Defensive : u32 { - const SPOT_DODGE = 0x1; - const ROLL_F = 0x2; - const ROLL_B = 0x4; - const JAB = 0x8; - const SHIELD = 0x10; - } -} - -impl Defensive { - fn as_str(self) -> Option<&'static str> { - Some(match self { - Defensive::SPOT_DODGE => "Spotdodge", - Defensive::ROLL_F => "Roll Forwards", - Defensive::ROLL_B => "Roll Backwards", - Defensive::JAB => "Jab", - Defensive::SHIELD => "Shield", - _ => return None, - }) - } -} - -extra_bitflag_impls! {Defensive} - -#[repr(i32)] -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum OnOff { - Off = 0, - On = 1, -} - -impl OnOff { - pub fn from_val(val: u32) -> Option { - match val { - 1 => Some(OnOff::On), - 0 => Some(OnOff::Off), - _ => None, - } - } - - pub fn as_str(self) -> Option<&'static str> { - Some(match self { - OnOff::Off => "Off", - OnOff::On => "On", - }) - } - - pub fn to_url_param(&self) -> String { - (*self as i32).to_string() - } -} - -bitflags! { - pub struct Action : u32 { - const AIR_DODGE = 0x1; - const JUMP = 0x2; - const SHIELD = 0x4; - const SPOT_DODGE = 0x8; - const ROLL_F = 0x10; - const ROLL_B = 0x20; - const NAIR = 0x40; - const FAIR = 0x80; - const BAIR = 0x100; - const UAIR = 0x200; - const DAIR = 0x400; - const NEUTRAL_B = 0x800; - const SIDE_B = 0x1000; - const UP_B = 0x2000; - const DOWN_B = 0x4000; - const F_SMASH = 0x8000; - const U_SMASH = 0x10000; - const D_SMASH = 0x20000; - const JAB = 0x40000; - const F_TILT = 0x80000; - const U_TILT = 0x0010_0000; - const D_TILT = 0x0020_0000; - const GRAB = 0x0040_0000; - // TODO: Make work - const DASH = 0x0080_0000; - const DASH_ATTACK = 0x0100_0000; - } -} - -impl Action { - pub fn into_attack_air_kind(self) -> Option { - Some(match self { - Action::NAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_N, - Action::FAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_F, - Action::BAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_B, - Action::DAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_LW, - Action::UAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_HI, - _ => return None, - }) - } - - pub fn as_str(self) -> Option<&'static str> { - Some(match self { - Action::AIR_DODGE => "Airdodge", - Action::JUMP => "Jump", - Action::SHIELD => "Shield", - Action::SPOT_DODGE => "Spotdodge", - Action::ROLL_F => "Roll Forwards", - Action::ROLL_B => "Roll Backwards", - Action::NAIR => "Neutral Aerial", - Action::FAIR => "Forward Aerial", - Action::BAIR => "Backward Aerial", - Action::UAIR => "Up Aerial", - Action::DAIR => "Down Aerial", - Action::NEUTRAL_B => "Neutral Special", - Action::SIDE_B => "Side Special", - Action::UP_B => "Up Special", - Action::DOWN_B => "Down Special", - Action::F_SMASH => "Forward Smash", - Action::U_SMASH => "Up Smash", - Action::D_SMASH => "Down Smash", - Action::JAB => "Jab", - Action::F_TILT => "Forward Tilt", - Action::U_TILT => "Up Tilt", - Action::D_TILT => "Down Tilt", - Action::GRAB => "Grab", - Action::DASH => "Dash", - Action::DASH_ATTACK => "Dash Attack", - _ => return None, - }) - } -} - -extra_bitflag_impls! {Action} - -bitflags! { - pub struct AttackAngle : u32 { - const NEUTRAL = 0x1; - const UP = 0x2; - const DOWN = 0x4; - } -} - -impl AttackAngle { - pub fn as_str(self) -> Option<&'static str> { - Some(match self { - AttackAngle::NEUTRAL => "Neutral", - AttackAngle::UP => "Up", - AttackAngle::DOWN => "Down", - _ => return None, - }) - } -} - -extra_bitflag_impls! {AttackAngle} - -bitflags! { - pub struct Delay : u32 { - const D0 = 0x1; - const D1 = 0x2; - const D2 = 0x4; - const D3 = 0x8; - const D4 = 0x10; - const D5 = 0x20; - const D6 = 0x40; - const D7 = 0x80; - const D8 = 0x100; - const D9 = 0x200; - const D10 = 0x400; - const D11 = 0x800; - const D12 = 0x1000; - const D13 = 0x2000; - const D14 = 0x4000; - const D15 = 0x8000; - const D16 = 0x10000; - const D17 = 0x20000; - const D18 = 0x40000; - const D19 = 0x80000; - const D20 = 0x0010_0000; - const D21 = 0x0020_0000; - const D22 = 0x0040_0000; - const D23 = 0x0080_0000; - const D24 = 0x0100_0000; - const D25 = 0x0200_0000; - const D26 = 0x0400_0000; - const D27 = 0x0800_0000; - const D28 = 0x1000_0000; - const D29 = 0x2000_0000; - const D30 = 0x4000_0000; - } -} - -// Throw Option -bitflags! { - pub struct ThrowOption : u32 - { - const NONE = 0x1; - const FORWARD = 0x2; - const BACKWARD = 0x4; - const UP = 0x8; - const DOWN = 0x10; - } -} - -impl ThrowOption { - pub fn into_cmd(self) -> Option { - Some(match self { - ThrowOption::NONE => 0, - ThrowOption::FORWARD => *FIGHTER_PAD_CMD_CAT2_FLAG_THROW_F, - ThrowOption::BACKWARD => *FIGHTER_PAD_CMD_CAT2_FLAG_THROW_B, - ThrowOption::UP => *FIGHTER_PAD_CMD_CAT2_FLAG_THROW_HI, - ThrowOption::DOWN => *FIGHTER_PAD_CMD_CAT2_FLAG_THROW_LW, - _ => return None, - }) - } - - pub fn as_str(self) -> Option<&'static str> { - Some(match self { - ThrowOption::NONE => "None", - ThrowOption::FORWARD => "Forward Throw", - ThrowOption::BACKWARD => "Back Throw", - ThrowOption::UP => "Up Throw", - ThrowOption::DOWN => "Down Throw", - _ => return None, - }) - } -} - -extra_bitflag_impls! {ThrowOption} - -// Buff Option -bitflags! { - pub struct BuffOption : u32 - { - const ACCELERATLE = 0x1; - const OOMPH = 0x2; - const PSYCHE = 0x4; - const BOUNCE = 0x8; - const ARSENE = 0x10; - const BREATHING = 0x20; - const LIMIT = 0x40; - const KO = 0x80; - const WING = 0x100; - } -} - -impl BuffOption { - pub fn into_int(self) -> Option { - Some(match self { - BuffOption::ACCELERATLE => *FIGHTER_BRAVE_SPECIAL_LW_COMMAND11_SPEED_UP, - BuffOption::OOMPH => *FIGHTER_BRAVE_SPECIAL_LW_COMMAND12_ATTACK_UP, - BuffOption::PSYCHE => *FIGHTER_BRAVE_SPECIAL_LW_COMMAND21_CHARGE, - BuffOption::BOUNCE => *FIGHTER_BRAVE_SPECIAL_LW_COMMAND13_REFLECT, - BuffOption::BREATHING => 1, - BuffOption::ARSENE => 1, - BuffOption::LIMIT => 1, - BuffOption::KO => 1, - BuffOption::WING => 1, - _ => return None, - }) - } - - fn as_str(self) -> Option<&'static str> { - Some(match self { - BuffOption::ACCELERATLE => "Acceleratle", - BuffOption::OOMPH => "Oomph", - BuffOption::BOUNCE => "Bounce", - BuffOption::PSYCHE => "Psyche Up", - BuffOption::BREATHING => "Deep Breathing", - BuffOption::ARSENE => "Arsene", - BuffOption::LIMIT => "Limit Break", - BuffOption::KO => "KO Punch", - BuffOption::WING => "One-Winged Angel", - _ => return None, - }) - } -} - -extra_bitflag_impls! {BuffOption} - -impl Delay { - pub fn as_str(self) -> Option<&'static str> { - Some(match self { - Delay::D0 => "0", - Delay::D1 => "1", - Delay::D2 => "2", - Delay::D3 => "3", - Delay::D4 => "4", - Delay::D5 => "5", - Delay::D6 => "6", - Delay::D7 => "7", - Delay::D8 => "8", - Delay::D9 => "9", - Delay::D10 => "10", - Delay::D11 => "11", - Delay::D12 => "12", - Delay::D13 => "13", - Delay::D14 => "14", - Delay::D15 => "15", - Delay::D16 => "16", - Delay::D17 => "17", - Delay::D18 => "18", - Delay::D19 => "19", - Delay::D20 => "20", - Delay::D21 => "21", - Delay::D22 => "22", - Delay::D23 => "23", - Delay::D24 => "24", - Delay::D25 => "25", - Delay::D26 => "26", - Delay::D27 => "27", - Delay::D28 => "28", - Delay::D29 => "29", - Delay::D30 => "30", - _ => return None, - }) - } - - pub fn into_delay(&self) -> u32 { - self.to_index() - } -} - -extra_bitflag_impls! {Delay} - -bitflags! { - pub struct MedDelay : u32 { - const D0 = 0x1; - const D5 = 0x2; - const D10 = 0x4; - const D15 = 0x8; - const D20 = 0x10; - const D25 = 0x20; - const D30 = 0x40; - const D35 = 0x80; - const D40 = 0x100; - const D45 = 0x200; - const D50 = 0x400; - const D55 = 0x800; - const D60 = 0x1000; - const D65 = 0x2000; - const D70 = 0x4000; - const D75 = 0x8000; - const D80 = 0x10000; - const D85 = 0x20000; - const D90 = 0x40000; - const D95 = 0x80000; - const D100 = 0x0010_0000; - const D105 = 0x0020_0000; - const D110 = 0x0040_0000; - const D115 = 0x0080_0000; - const D120 = 0x0100_0000; - const D125 = 0x0200_0000; - const D130 = 0x0400_0000; - const D135 = 0x0800_0000; - const D140 = 0x1000_0000; - const D145 = 0x2000_0000; - const D150 = 0x4000_0000; - } -} - -impl MedDelay { - pub fn as_str(self) -> Option<&'static str> { - Some(match self { - MedDelay::D0 => "0", - MedDelay::D5 => "5", - MedDelay::D10 => "10", - MedDelay::D15 => "15", - MedDelay::D20 => "20", - MedDelay::D25 => "25", - MedDelay::D30 => "30", - MedDelay::D35 => "35", - MedDelay::D40 => "40", - MedDelay::D45 => "45", - MedDelay::D50 => "50", - MedDelay::D55 => "55", - MedDelay::D60 => "60", - MedDelay::D65 => "65", - MedDelay::D70 => "70", - MedDelay::D75 => "75", - MedDelay::D80 => "80", - MedDelay::D85 => "85", - MedDelay::D90 => "90", - MedDelay::D95 => "95", - MedDelay::D100 => "100", - MedDelay::D105 => "105", - MedDelay::D110 => "110", - MedDelay::D115 => "115", - MedDelay::D120 => "120", - MedDelay::D125 => "125", - MedDelay::D130 => "130", - MedDelay::D135 => "135", - MedDelay::D140 => "140", - MedDelay::D145 => "145", - MedDelay::D150 => "150", - _ => return None, - }) - } - - pub fn into_meddelay(&self) -> u32 { - self.to_index() * 5 - } -} - -extra_bitflag_impls! {MedDelay} - -bitflags! { - pub struct LongDelay : u32 { - const D0 = 0x1; - const D10 = 0x2; - const D20 = 0x4; - const D30 = 0x8; - const D40 = 0x10; - const D50 = 0x20; - const D60 = 0x40; - const D70 = 0x80; - const D80 = 0x100; - const D90 = 0x200; - const D100 = 0x400; - const D110 = 0x800; - const D120 = 0x1000; - const D130 = 0x2000; - const D140 = 0x4000; - const D150 = 0x8000; - const D160 = 0x10000; - const D170 = 0x20000; - const D180 = 0x40000; - const D190 = 0x80000; - const D200 = 0x0010_0000; - const D210 = 0x0020_0000; - const D220 = 0x0040_0000; - const D230 = 0x0080_0000; - const D240 = 0x0100_0000; - const D250 = 0x0200_0000; - const D260 = 0x0400_0000; - const D270 = 0x0800_0000; - const D280 = 0x1000_0000; - const D290 = 0x2000_0000; - const D300 = 0x4000_0000; - } -} - -impl LongDelay { - pub fn as_str(self) -> Option<&'static str> { - Some(match self { - LongDelay::D0 => "0", - LongDelay::D10 => "10", - LongDelay::D20 => "20", - LongDelay::D30 => "30", - LongDelay::D40 => "40", - LongDelay::D50 => "50", - LongDelay::D60 => "60", - LongDelay::D70 => "70", - LongDelay::D80 => "80", - LongDelay::D90 => "90", - LongDelay::D100 => "100", - LongDelay::D110 => "110", - LongDelay::D120 => "120", - LongDelay::D130 => "130", - LongDelay::D140 => "140", - LongDelay::D150 => "150", - LongDelay::D160 => "160", - LongDelay::D170 => "170", - LongDelay::D180 => "180", - LongDelay::D190 => "190", - LongDelay::D200 => "200", - LongDelay::D210 => "210", - LongDelay::D220 => "220", - LongDelay::D230 => "230", - LongDelay::D240 => "240", - LongDelay::D250 => "250", - LongDelay::D260 => "260", - LongDelay::D270 => "270", - LongDelay::D280 => "280", - LongDelay::D290 => "290", - LongDelay::D300 => "300", - _ => return None, - }) - } - - pub fn into_longdelay(&self) -> u32 { - self.to_index() * 10 - } -} - -extra_bitflag_impls! {LongDelay} - -bitflags! { - pub struct BoolFlag : u32 { - const TRUE = 0x1; - const FALSE = 0x2; - } -} - -extra_bitflag_impls! {BoolFlag} - -impl BoolFlag { - pub fn into_bool(self) -> bool { - matches!(self, BoolFlag::TRUE) - } - - pub fn as_str(self) -> Option<&'static str> { - Some(match self { - BoolFlag::TRUE => "True", - _ => "False", - }) - } -} - -#[repr(u32)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, EnumIter)] -pub enum SdiStrength { - Normal = 0, - Medium = 1, - High = 2, -} - -impl SdiStrength { - pub fn into_u32(self) -> u32 { - match self { - SdiStrength::Normal => 8, - SdiStrength::Medium => 6, - SdiStrength::High => 4, - } - } - - pub fn as_str(self) -> Option<&'static str> { - Some(match self { - SdiStrength::Normal => "Normal", - SdiStrength::Medium => "Medium", - SdiStrength::High => "High", - }) - } - - pub fn to_url_param(&self) -> String { - (*self as u32).to_string() - } -} - -// For input delay -trait ToUrlParam { - fn to_url_param(&self) -> String; -} - -impl ToUrlParam for i32 { - fn to_url_param(&self) -> String { - self.to_string() - } -} - -// Macro to build the url parameter string -macro_rules! url_params { - ( - #[derive($($trait_name:ident, )*)] - pub struct $e:ident { - $(pub $field_name:ident: $field_type:ty,)* - } - ) => { - #[derive($($trait_name, )*)] - pub struct $e { - $(pub $field_name: $field_type,)* - } - impl $e { - pub fn to_url_params(&self) -> String { - let mut s = "?".to_string(); - $( - s.push_str(stringify!($field_name)); - s.push_str(&"="); - s.push_str(&self.$field_name.to_url_param()); - s.push_str(&"&"); - )* - s.pop(); - s - } - } - } -} - -#[repr(C)] -url_params! { - #[derive(Clone, Copy, )] - pub struct TrainingModpackMenu { - pub hitbox_vis: OnOff, - pub stage_hazards: OnOff, - pub di_state: Direction, - pub sdi_state: Direction, - pub sdi_strength: SdiStrength, - pub air_dodge_dir: Direction, - pub mash_state: Action, - pub follow_up: Action, - pub attack_angle: AttackAngle, - pub ledge_state: LedgeOption, - pub ledge_delay: LongDelay, - pub tech_state: TechFlags, - pub miss_tech_state: MissTechFlags, - pub shield_state: Shield, - pub defensive_state: Defensive, - pub oos_offset: Delay, - pub reaction_time: Delay, - pub shield_tilt: Direction, - pub mash_in_neutral: OnOff, - pub fast_fall: BoolFlag, - pub fast_fall_delay: Delay, - pub falling_aerials: BoolFlag, - pub aerial_delay: Delay, - pub full_hop: BoolFlag, - pub input_delay: i32, - pub save_damage: OnOff, - pub save_state_mirroring: SaveStateMirroring, - pub frame_advantage: OnOff, - pub save_state_enable: OnOff, - pub throw_state: ThrowOption, - pub throw_delay: MedDelay, - pub pummel_delay: MedDelay, - pub buff_state: BuffOption, - } -} - -macro_rules! set_by_str { - ($obj:ident, $s:ident, $($field:ident = $rhs:expr,)*) => { - $( - if $s == stringify!($field) { - $obj.$field = $rhs.unwrap(); - } - )* - } -} - -impl TrainingModpackMenu { - pub fn set(&mut self, s: &str, val: u32) { - set_by_str!( - self, - s, - aerial_delay = Delay::from_bits(val), - air_dodge_dir = Direction::from_bits(val), - attack_angle = AttackAngle::from_bits(val), - defensive_state = Defensive::from_bits(val), - di_state = Direction::from_bits(val), - falling_aerials = BoolFlag::from_bits(val), - fast_fall_delay = Delay::from_bits(val), - fast_fall = BoolFlag::from_bits(val), - follow_up = Action::from_bits(val), - full_hop = BoolFlag::from_bits(val), - hitbox_vis = OnOff::from_val(val), - input_delay = Some(val as i32), - ledge_delay = LongDelay::from_bits(val), - ledge_state = LedgeOption::from_bits(val), - mash_in_neutral = OnOff::from_val(val), - mash_state = Action::from_bits(val), - miss_tech_state = MissTechFlags::from_bits(val), - oos_offset = Delay::from_bits(val), - reaction_time = Delay::from_bits(val), - sdi_state = Direction::from_bits(val), - sdi_strength = num::FromPrimitive::from_u32(val), - shield_state = num::FromPrimitive::from_u32(val), - shield_tilt = Direction::from_bits(val), - stage_hazards = OnOff::from_val(val), - tech_state = TechFlags::from_bits(val), - save_damage = OnOff::from_val(val), - frame_advantage = OnOff::from_val(val), - save_state_mirroring = num::FromPrimitive::from_u32(val), - save_state_enable = OnOff::from_val(val), - throw_state = ThrowOption::from_bits(val), - throw_delay = MedDelay::from_bits(val), - pummel_delay = MedDelay::from_bits(val), - buff_state = BuffOption::from_bits(val), - ); - } -} - -// Fighter Ids -#[repr(i32)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum FighterId { - Player = 0, - CPU = 1, -} +pub use training_mod_consts::*; diff --git a/src/common/events.rs b/src/common/events.rs index 623c36e..343069b 100644 --- a/src/common/events.rs +++ b/src/common/events.rs @@ -1,179 +1,180 @@ -use core::lazy::OnceCell; -use serde::{Deserialize, Serialize}; -use skyline::libc::c_void; -use skyline::nn::{account, crypto, oe, time}; -use std::convert::TryInto; -use std::time::{SystemTime, UNIX_EPOCH}; - -use crate::common::release::CURRENT_VERSION; - -pub static mut EVENT_QUEUE: Vec = vec![]; -static mut SESSION_ID: OnceCell = OnceCell::new(); -static mut DEVICE_ID: OnceCell = OnceCell::new(); -static mut USER_ID: OnceCell = OnceCell::new(); - -#[derive(Debug, Default, Deserialize, Serialize)] -pub struct Event { - pub event_name: String, - pub user_id: String, - pub device_id: String, - pub event_time: u128, - pub session_id: String, - pub menu_settings: String, - pub mod_version: String, - pub smash_version: String, -} - -extern "C" { - #[link_name = "\u{1}_ZN2nn2oe17GetPseudoDeviceIdEPNS_4util4UuidE"] - pub fn GetPseudoDeviceId(arg1: *mut Uuid); -} - -#[derive(Debug)] -pub struct Uuid { - size: u32, - string_size: u32, - data: [u8; 16], -} - -impl Uuid { - pub fn to_str(&self) -> String { - self.data - .iter() - .map(|i| format!("{:02x}", i)) - .collect::() - } -} - -struct Sha256Hash { - hash: [u8; 0x20], -} - -impl Event { - pub fn new() -> Event { - let mut device_uuid = Uuid { - size: 16, - string_size: 300, - data: [0u8; 16], - }; - unsafe { - GetPseudoDeviceId(&mut device_uuid as *mut Uuid); - } - - unsafe { - time::Initialize(); - let event_time = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Time went backwards") - .as_millis(); - - if SESSION_ID.get().is_none() { - account::Initialize(); - let mut user_uid = account::Uid::new(); - account::GetLastOpenedUser(&mut user_uid); - - let mut user_id_hash = Sha256Hash { hash: [0; 0x20] }; - crypto::GenerateSha256Hash( - &mut user_id_hash as *mut _ as *mut c_void, - 0x20 * 8, - user_uid.id.as_ptr() as *const c_void, - 16 * 8, - ); - - USER_ID - .set( - user_uid - .id - .iter() - .map(|i| format!("{:02x}", i)) - .collect::>() - .join(""), - ) - .unwrap(); - - let mut device_id_hash = Sha256Hash { hash: [0; 0x20] }; - crypto::GenerateSha256Hash( - &mut device_id_hash as *mut _ as *mut c_void, - 0x20 * 8, - device_uuid.data.as_ptr() as *const c_void, - 64 * 2, - ); - DEVICE_ID - .set( - device_uuid - .data - .iter() - .map(|i| format!("{:02x}", i)) - .collect::>() - .join(""), - ) - .unwrap(); - - let mut session_id_hash = Sha256Hash { hash: [0; 0x20] }; - // let mut device_id_0_bytes : [u8; 8] = Default::default(); - // device_id_0_bytes.copy_from_slice(&device_uuid.data[0..8]); - // let mut device_id_1_bytes : [u8; 8] = Default::default(); - // device_id_1_bytes.copy_from_slice(&device_uuid.data[8..16]); - let event_time_bytes: [u8; 16] = std::mem::transmute(event_time.to_be()); - let session_id_bytes: [u8; 32] = [event_time_bytes, device_uuid.data] - .concat() - .try_into() - .unwrap(); - - crypto::GenerateSha256Hash( - &mut session_id_hash as *mut _ as *mut c_void, - 0x20 * 8, - session_id_bytes.as_ptr() as *const c_void, - 32 * 8, - ); - SESSION_ID - .set( - session_id_hash - .hash - .iter() - .map(|i| format!("{:02x}", i)) - .collect::>() - .join(""), - ) - .unwrap(); - } - - Event { - user_id: USER_ID.get().unwrap().to_string(), - device_id: DEVICE_ID.get().unwrap().to_string(), - event_time, - session_id: SESSION_ID.get().unwrap().to_string(), - mod_version: CURRENT_VERSION.to_string(), - smash_version: smash_version(), - ..Default::default() - } - } - } - - pub fn smash_open() -> Event { - Event { - event_name: "SMASH_OPEN".to_string(), - ..Event::new() - } - } - - pub fn menu_open(menu_settings: String) -> Event { - Event { - event_name: "MENU_OPEN".to_string(), - menu_settings, - ..Event::new() - } - } -} - -fn smash_version() -> String { - let mut smash_version = oe::DisplayVersion { name: [0; 16] }; - - unsafe { - oe::GetDisplayVersion(&mut smash_version); - - std::ffi::CStr::from_ptr(smash_version.name.as_ptr() as *const i8) - .to_string_lossy() - .into_owned() - } -} +use core::lazy::OnceCell; +use serde::{Deserialize, Serialize}; +use skyline::libc::c_void; +use skyline::nn::{account, crypto, oe, time}; +use std::convert::TryInto; +use std::time::{SystemTime, UNIX_EPOCH}; + +use crate::common::release::CURRENT_VERSION; + +pub static mut EVENT_QUEUE: Vec = vec![]; +static mut SESSION_ID: OnceCell = OnceCell::new(); +static mut DEVICE_ID: OnceCell = OnceCell::new(); +static mut USER_ID: OnceCell = OnceCell::new(); + +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct Event { + pub event_name: String, + pub user_id: String, + pub device_id: String, + pub event_time: u128, + pub session_id: String, + pub menu_settings: String, + pub mod_version: String, + pub smash_version: String, +} + +extern "C" { + #[link_name = "\u{1}_ZN2nn2oe17GetPseudoDeviceIdEPNS_4util4UuidE"] + pub fn GetPseudoDeviceId(arg1: *mut Uuid); +} + +#[derive(Debug)] +#[repr(C)] +pub struct Uuid { + size: u32, + string_size: u32, + data: [u8; 16], +} + +impl Uuid { + pub fn to_str(&self) -> String { + self.data + .iter() + .map(|i| format!("{:02x}", i)) + .collect::() + } +} + +struct Sha256Hash { + hash: [u8; 0x20], +} + +impl Event { + pub fn new() -> Event { + let mut device_uuid = Uuid { + size: 16, + string_size: 300, + data: [0u8; 16], + }; + unsafe { + GetPseudoDeviceId(&mut device_uuid as *mut Uuid); + } + + unsafe { + time::Initialize(); + let event_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_millis(); + + if SESSION_ID.get().is_none() { + account::Initialize(); + let mut user_uid = account::Uid::new(); + account::GetLastOpenedUser(&mut user_uid); + + let mut user_id_hash = Sha256Hash { hash: [0; 0x20] }; + crypto::GenerateSha256Hash( + &mut user_id_hash as *mut _ as *mut c_void, + 0x20 * 8, + user_uid.id.as_ptr() as *const c_void, + 16 * 8, + ); + + USER_ID + .set( + user_uid + .id + .iter() + .map(|i| format!("{:02x}", i)) + .collect::>() + .join(""), + ) + .unwrap(); + + let mut device_id_hash = Sha256Hash { hash: [0; 0x20] }; + crypto::GenerateSha256Hash( + &mut device_id_hash as *mut _ as *mut c_void, + 0x20 * 8, + device_uuid.data.as_ptr() as *const c_void, + 64 * 2, + ); + DEVICE_ID + .set( + device_uuid + .data + .iter() + .map(|i| format!("{:02x}", i)) + .collect::>() + .join(""), + ) + .unwrap(); + + let mut session_id_hash = Sha256Hash { hash: [0; 0x20] }; + // let mut device_id_0_bytes : [u8; 8] = Default::default(); + // device_id_0_bytes.copy_from_slice(&device_uuid.data[0..8]); + // let mut device_id_1_bytes : [u8; 8] = Default::default(); + // device_id_1_bytes.copy_from_slice(&device_uuid.data[8..16]); + let event_time_bytes: [u8; 16] = std::mem::transmute(event_time.to_be()); + let session_id_bytes: [u8; 32] = [event_time_bytes, device_uuid.data] + .concat() + .try_into() + .unwrap(); + + crypto::GenerateSha256Hash( + &mut session_id_hash as *mut _ as *mut c_void, + 0x20 * 8, + session_id_bytes.as_ptr() as *const c_void, + 32 * 8, + ); + SESSION_ID + .set( + session_id_hash + .hash + .iter() + .map(|i| format!("{:02x}", i)) + .collect::>() + .join(""), + ) + .unwrap(); + } + + Event { + user_id: USER_ID.get().unwrap().to_string(), + device_id: DEVICE_ID.get().unwrap().to_string(), + event_time, + session_id: SESSION_ID.get().unwrap().to_string(), + mod_version: CURRENT_VERSION.to_string(), + smash_version: smash_version(), + ..Default::default() + } + } + } + + pub fn smash_open() -> Event { + Event { + event_name: "SMASH_OPEN".to_string(), + ..Event::new() + } + } + + pub fn menu_open(menu_settings: String) -> Event { + Event { + event_name: "MENU_OPEN".to_string(), + menu_settings, + ..Event::new() + } + } +} + +fn smash_version() -> String { + let mut smash_version = oe::DisplayVersion { name: [0; 16] }; + + unsafe { + oe::GetDisplayVersion(&mut smash_version); + + std::ffi::CStr::from_ptr(smash_version.name.as_ptr() as *const i8) + .to_string_lossy() + .into_owned() + } +} diff --git a/src/common/menu.rs b/src/common/menu.rs index d623ee0..77a30dd 100644 --- a/src/common/menu.rs +++ b/src/common/menu.rs @@ -1,623 +1,623 @@ -use crate::common::*; -use crate::events::{Event, EVENT_QUEUE}; -use crate::training::frame_counter; -use ramhorns::{Content, Template}; -use skyline::info::get_program_id; -use skyline_web::{Background, BootDisplay, Webpage}; -use smash::lib::lua_const::*; -use std::fs; -use std::ops::BitOr; -use std::path::Path; -use strum::IntoEnumIterator; - -static mut FRAME_COUNTER_INDEX: usize = 0; -const MENU_LOCKOUT_FRAMES: u32 = 15; - -pub fn init() { - unsafe { - FRAME_COUNTER_INDEX = frame_counter::register_counter(); - write_menu(); - } -} - -#[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>, - sliders: Vec, - onoffselector: Vec>, - index: usize, - check_against: usize, - is_single_option: Option, - 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>, -} - -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, - 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::>(); - let toggle = toggle_value_split[0]; - if toggle.is_empty() { - continue; - } - - let toggle_vals = toggle_value_split[1]; - - let bitwise_or = >::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 { - // Only check for button combination if the counter is 0 (not locked out) - match frame_counter::get_frame_count(FRAME_COUNTER_INDEX) { - 0 => { - ControlModule::check_button_on(module_accessor, *CONTROL_PAD_BUTTON_SPECIAL) - && ControlModule::check_button_on_trriger( - module_accessor, - *CONTROL_PAD_BUTTON_APPEAL_HI, - ) - } - 1..MENU_LOCKOUT_FRAMES => false, - _ => { - // Waited longer than the lockout time, reset the counter so the menu can be opened again - frame_counter::full_reset(FRAME_COUNTER_INDEX); - false - } - } -} - -pub unsafe fn write_menu() { - let tpl = Template::new(include_str!("../templates/menu.html")).unwrap(); - - let mut overall_menu = Menu { - sub_menus: Vec::new(), - }; - - // Toggle/bitflag menus - add_bitflag_submenu!( - overall_menu, - "Mash Toggles", - mash_state, - Action, - "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); - - // Now that we have the html, write it to file - // From skyline-web - let program_id = get_program_id(); - let htdocs_dir = "contents"; - let path = Path::new("sd:/atmosphere/contents") - .join(&format!("{:016X}", program_id)) - .join(&format!("manual_html/html-document/{}.htdocs/", htdocs_dir)) - .join("training_menu.html"); - fs::write(path, data).unwrap(); -} - -const MENU_CONF_PATH: &str = "sd:/TrainingModpack/training_modpack_menu.conf"; - -pub fn spawn_menu() { - 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("contents") - .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", ""); - unsafe { - MENU = get_menu_from_url(MENU, last_url); - } - if last_url.len() != orig_last_url.len() { - // Save as default - unsafe { - DEFAULT_MENU = get_menu_from_url(DEFAULT_MENU, last_url); - write_menu(); - } - let menu_defaults_conf_path = "sd:/TrainingModpack/training_modpack_menu_defaults.conf"; - std::fs::write(menu_defaults_conf_path, last_url) - .expect("Failed to write default menu conf file"); - } - - std::fs::write(MENU_CONF_PATH, last_url).expect("Failed to write menu conf file"); - unsafe { - EVENT_QUEUE.push(Event::menu_open(last_url.to_string())); - } -} +use crate::common::*; +use crate::events::{Event, EVENT_QUEUE}; +use crate::training::frame_counter; +use ramhorns::{Content, Template}; +use skyline::info::get_program_id; +use skyline_web::{Background, BootDisplay, Webpage}; +use smash::lib::lua_const::*; +use std::fs; +use std::ops::BitOr; +use std::path::Path; +use strum::IntoEnumIterator; + +static mut FRAME_COUNTER_INDEX: usize = 0; +const MENU_LOCKOUT_FRAMES: u32 = 15; + +pub fn init() { + unsafe { + FRAME_COUNTER_INDEX = frame_counter::register_counter(); + write_menu(); + } +} + +#[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>, + sliders: Vec, + onoffselector: Vec>, + index: usize, + check_against: usize, + is_single_option: Option, + 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>, +} + +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, + 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::>(); + let toggle = toggle_value_split[0]; + if toggle.is_empty() { + continue; + } + + let toggle_vals = toggle_value_split[1]; + + let bitwise_or = >::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 { + // Only check for button combination if the counter is 0 (not locked out) + match frame_counter::get_frame_count(FRAME_COUNTER_INDEX) { + 0 => { + ControlModule::check_button_on(module_accessor, *CONTROL_PAD_BUTTON_SPECIAL) + && ControlModule::check_button_on_trriger( + module_accessor, + *CONTROL_PAD_BUTTON_APPEAL_HI, + ) + } + 1..MENU_LOCKOUT_FRAMES => false, + _ => { + // Waited longer than the lockout time, reset the counter so the menu can be opened again + frame_counter::full_reset(FRAME_COUNTER_INDEX); + false + } + } +} + +pub unsafe fn write_menu() { + let tpl = Template::new(include_str!("../templates/menu.html")).unwrap(); + + let mut overall_menu = Menu { + sub_menus: Vec::new(), + }; + + // Toggle/bitflag menus + add_bitflag_submenu!( + overall_menu, + "Mash Toggles", + mash_state, + Action, + "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); + + // Now that we have the html, write it to file + // From skyline-web + let program_id = get_program_id(); + let htdocs_dir = "contents"; + let path = Path::new("sd:/atmosphere/contents") + .join(&format!("{:016X}", program_id)) + .join(&format!("manual_html/html-document/{}.htdocs/", htdocs_dir)) + .join("training_menu.html"); + fs::write(path, data).unwrap(); +} + +const MENU_CONF_PATH: &str = "sd:/TrainingModpack/training_modpack_menu.conf"; + +pub fn spawn_menu() { + 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("contents") + .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", ""); + unsafe { + MENU = get_menu_from_url(MENU, last_url); + } + if last_url.len() != orig_last_url.len() { + // Save as default + unsafe { + DEFAULT_MENU = get_menu_from_url(DEFAULT_MENU, last_url); + write_menu(); + } + let menu_defaults_conf_path = "sd:/TrainingModpack/training_modpack_menu_defaults.conf"; + std::fs::write(menu_defaults_conf_path, last_url) + .expect("Failed to write default menu conf file"); + } + + std::fs::write(MENU_CONF_PATH, last_url).expect("Failed to write menu conf file"); + unsafe { + EVENT_QUEUE.push(Event::menu_open(last_url.to_string())); + } +} diff --git a/src/common/mod.rs b/src/common/mod.rs index 59f74f6..8158d3f 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,174 +1,169 @@ -pub mod consts; -pub mod events; -pub mod menu; -pub mod raygun_printer; -pub mod release; - -use crate::common::consts::*; -use smash::app::{self, lua_bind::*}; -use smash::hash40; -use smash::lib::lua_const::*; - -pub static BASE_MENU: consts::TrainingModpackMenu = consts::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(), -}; - -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 STAGE_MANAGER_ADDR: usize = 0; - -#[cfg(not(feature = "outside_training_mode"))] -extern "C" { - #[link_name = "\u{1}_ZN3app9smashball16is_training_modeEv"] - pub fn is_training_mode() -> bool; -} - -#[cfg(feature = "outside_training_mode")] -pub fn is_training_mode() -> bool { - return true; -} - -pub fn get_category(module_accessor: &mut app::BattleObjectModuleAccessor) -> i32 { - (module_accessor.info >> 28) as u8 as i32 -} - -pub fn get_module_accessor(fighter_id: FighterId) -> *mut app::BattleObjectModuleAccessor { - let entry_id_int = fighter_id as i32; - let entry_id = app::FighterEntryID(entry_id_int); - unsafe { - let mgr = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager); - let fighter_entry = - FighterManager::get_fighter_entry(mgr, entry_id) as *mut app::FighterEntry; - let current_fighter_id = FighterEntry::current_fighter_id(fighter_entry); - app::sv_battle_object::module_accessor(current_fighter_id as u32) - } -} - -pub fn is_fighter(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { - get_category(module_accessor) == BATTLE_OBJECT_CATEGORY_FIGHTER -} - -pub fn is_operation_cpu(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { - unsafe { - if !is_fighter(module_accessor) { - return false; - } - - let entry_id_int = - WorkModule::get_int(module_accessor, *FIGHTER_INSTANCE_WORK_ID_INT_ENTRY_ID) as i32; - - if entry_id_int == 0 { - return false; - } - - let entry_id = app::FighterEntryID(entry_id_int); - let mgr = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager); - let fighter_information = - FighterManager::get_fighter_information(mgr, entry_id) as *mut app::FighterInformation; - - FighterInformation::is_operation_cpu(fighter_information) - } -} - -pub fn is_grounded(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { - let situation_kind = unsafe { StatusModule::situation_kind(module_accessor) as i32 }; - - situation_kind == SITUATION_KIND_GROUND -} - -pub fn is_airborne(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { - let situation_kind = unsafe { StatusModule::situation_kind(module_accessor) as i32 }; - - situation_kind == SITUATION_KIND_AIR -} - -pub fn is_idle(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { - let status_kind = unsafe { StatusModule::status_kind(module_accessor) }; - - status_kind == FIGHTER_STATUS_KIND_WAIT -} - -pub fn is_in_hitstun(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { - let status_kind = unsafe { StatusModule::status_kind(module_accessor) }; - - (*FIGHTER_STATUS_KIND_DAMAGE..*FIGHTER_STATUS_KIND_DAMAGE_FALL).contains(&status_kind) -} -pub fn is_in_footstool(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { - let status_kind = unsafe { StatusModule::status_kind(module_accessor) }; - - (*FIGHTER_STATUS_KIND_TREAD_DAMAGE..=*FIGHTER_STATUS_KIND_TREAD_FALL).contains(&status_kind) -} - -pub fn is_shielding(module_accessor: *mut app::BattleObjectModuleAccessor) -> bool { - let status_kind = unsafe { StatusModule::status_kind(module_accessor) as i32 }; - - (*FIGHTER_STATUS_KIND_GUARD_ON..=*FIGHTER_STATUS_KIND_GUARD_DAMAGE).contains(&status_kind) -} - -pub fn is_in_shieldstun(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { - let status_kind = unsafe { StatusModule::status_kind(module_accessor) }; - let prev_status = unsafe { StatusModule::prev_status_kind(module_accessor, 0) }; - - // If we are taking shield damage or we are droping shield from taking shield damage we are in hitstun - status_kind == FIGHTER_STATUS_KIND_GUARD_DAMAGE - || (prev_status == FIGHTER_STATUS_KIND_GUARD_DAMAGE - && status_kind == FIGHTER_STATUS_KIND_GUARD_OFF) -} - -pub fn get_random_int(max: i32) -> i32 { - unsafe { app::sv_math::rand(hash40("fighter"), max) } -} - -pub unsafe fn is_dead(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { - let fighter_kind = app::utility::get_kind(module_accessor); - let fighter_is_ptrainer = [ - *FIGHTER_KIND_PZENIGAME, - *FIGHTER_KIND_PFUSHIGISOU, - *FIGHTER_KIND_PLIZARDON, - ] - .contains(&fighter_kind); - let status_kind = StatusModule::status_kind(module_accessor) as i32; - let prev_status_kind = StatusModule::prev_status_kind(module_accessor, 0); - // Pokemon trainer enters FIGHTER_STATUS_KIND_WAIT for one frame during their respawn animation - // And the previous status is FIGHTER_STATUS_NONE - if fighter_is_ptrainer { - [*FIGHTER_STATUS_KIND_DEAD, *FIGHTER_STATUS_KIND_STANDBY].contains(&status_kind) - || (status_kind == FIGHTER_STATUS_KIND_WAIT - && prev_status_kind == FIGHTER_STATUS_KIND_NONE) - } else { - [*FIGHTER_STATUS_KIND_DEAD, *FIGHTER_STATUS_KIND_STANDBY].contains(&status_kind) - } -} +pub mod consts; +pub mod events; +pub mod menu; +pub mod raygun_printer; +pub mod release; + +use crate::common::consts::*; +use smash::app::{self, lua_bind::*}; +use smash::lib::lua_const::*; + +pub static BASE_MENU: consts::TrainingModpackMenu = consts::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(), +}; + +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 STAGE_MANAGER_ADDR: usize = 0; + +#[cfg(not(feature = "outside_training_mode"))] +extern "C" { + #[link_name = "\u{1}_ZN3app9smashball16is_training_modeEv"] + pub fn is_training_mode() -> bool; +} + +#[cfg(feature = "outside_training_mode")] +pub fn is_training_mode() -> bool { + return true; +} + +pub fn get_category(module_accessor: &mut app::BattleObjectModuleAccessor) -> i32 { + (module_accessor.info >> 28) as u8 as i32 +} + +pub fn get_module_accessor(fighter_id: FighterId) -> *mut app::BattleObjectModuleAccessor { + let entry_id_int = fighter_id as i32; + let entry_id = app::FighterEntryID(entry_id_int); + unsafe { + let mgr = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager); + let fighter_entry = + FighterManager::get_fighter_entry(mgr, entry_id) as *mut app::FighterEntry; + let current_fighter_id = FighterEntry::current_fighter_id(fighter_entry); + app::sv_battle_object::module_accessor(current_fighter_id as u32) + } +} + +pub fn is_fighter(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { + get_category(module_accessor) == BATTLE_OBJECT_CATEGORY_FIGHTER +} + +pub fn is_operation_cpu(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { + unsafe { + if !is_fighter(module_accessor) { + return false; + } + + let entry_id_int = + WorkModule::get_int(module_accessor, *FIGHTER_INSTANCE_WORK_ID_INT_ENTRY_ID) as i32; + + if entry_id_int == 0 { + return false; + } + + let entry_id = app::FighterEntryID(entry_id_int); + let mgr = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager); + let fighter_information = + FighterManager::get_fighter_information(mgr, entry_id) as *mut app::FighterInformation; + + FighterInformation::is_operation_cpu(fighter_information) + } +} + +pub fn is_grounded(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { + let situation_kind = unsafe { StatusModule::situation_kind(module_accessor) as i32 }; + + situation_kind == SITUATION_KIND_GROUND +} + +pub fn is_airborne(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { + let situation_kind = unsafe { StatusModule::situation_kind(module_accessor) as i32 }; + + situation_kind == SITUATION_KIND_AIR +} + +pub fn is_idle(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { + let status_kind = unsafe { StatusModule::status_kind(module_accessor) }; + + status_kind == FIGHTER_STATUS_KIND_WAIT +} + +pub fn is_in_hitstun(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { + let status_kind = unsafe { StatusModule::status_kind(module_accessor) }; + + (*FIGHTER_STATUS_KIND_DAMAGE..*FIGHTER_STATUS_KIND_DAMAGE_FALL).contains(&status_kind) +} +pub fn is_in_footstool(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { + let status_kind = unsafe { StatusModule::status_kind(module_accessor) }; + + (*FIGHTER_STATUS_KIND_TREAD_DAMAGE..=*FIGHTER_STATUS_KIND_TREAD_FALL).contains(&status_kind) +} + +pub fn is_shielding(module_accessor: *mut app::BattleObjectModuleAccessor) -> bool { + let status_kind = unsafe { StatusModule::status_kind(module_accessor) as i32 }; + + (*FIGHTER_STATUS_KIND_GUARD_ON..=*FIGHTER_STATUS_KIND_GUARD_DAMAGE).contains(&status_kind) +} + +pub fn is_in_shieldstun(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { + let status_kind = unsafe { StatusModule::status_kind(module_accessor) }; + let prev_status = unsafe { StatusModule::prev_status_kind(module_accessor, 0) }; + + // If we are taking shield damage or we are droping shield from taking shield damage we are in hitstun + status_kind == FIGHTER_STATUS_KIND_GUARD_DAMAGE + || (prev_status == FIGHTER_STATUS_KIND_GUARD_DAMAGE + && status_kind == FIGHTER_STATUS_KIND_GUARD_OFF) +} + +pub unsafe fn is_dead(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { + let fighter_kind = app::utility::get_kind(module_accessor); + let fighter_is_ptrainer = [ + *FIGHTER_KIND_PZENIGAME, + *FIGHTER_KIND_PFUSHIGISOU, + *FIGHTER_KIND_PLIZARDON, + ] + .contains(&fighter_kind); + let status_kind = StatusModule::status_kind(module_accessor) as i32; + let prev_status_kind = StatusModule::prev_status_kind(module_accessor, 0); + // Pokemon trainer enters FIGHTER_STATUS_KIND_WAIT for one frame during their respawn animation + // And the previous status is FIGHTER_STATUS_NONE + if fighter_is_ptrainer { + [*FIGHTER_STATUS_KIND_DEAD, *FIGHTER_STATUS_KIND_STANDBY].contains(&status_kind) + || (status_kind == FIGHTER_STATUS_KIND_WAIT + && prev_status_kind == FIGHTER_STATUS_KIND_NONE) + } else { + [*FIGHTER_STATUS_KIND_DEAD, *FIGHTER_STATUS_KIND_STANDBY].contains(&status_kind) + } +} diff --git a/src/common/raygun_printer.rs b/src/common/raygun_printer.rs index 20b6e45..3df34d3 100644 --- a/src/common/raygun_printer.rs +++ b/src/common/raygun_printer.rs @@ -1,243 +1,243 @@ -use std::ops::Neg; - -use smash::app; -use smash::phx::{Hash40, Vector3f}; - -pub const RAYGUN_LENGTH: f32 = 8.0; -pub const RAYGUN_HEIGHT: f32 = 6.0; -pub const RAYGUN_HORIZ_OFFSET: f32 = 2.0; - -/* - segment data list : {Z, Y, X, ZRot, Size} - segment labels : - _ - |_| from top to top left, clockwise: a->f + g mid + \|/ from top mid to top left, clockwise: h->m + --two half g's: n, o - |_| /|\ -*/ - -pub static SEGMENT_DICT: [[f32; 5]; 15] = [ - [0.0, RAYGUN_HEIGHT * 2.0, 0.0, 0.0, 0.25], // a - [0.0, RAYGUN_HEIGHT, RAYGUN_LENGTH, 90.0, 0.25], // b - [0.0, 0.0, RAYGUN_LENGTH, 90.0, 0.25], // c - [0.0, 0.0, 0.0, 0.0, 0.25], // d - [0.0, 0.0, 0.0, 90.0, 0.25], // e - [0.0, RAYGUN_HEIGHT, 0.0, 90.0, 0.25], // f - [0.0, RAYGUN_HEIGHT, 0.0, 0.0, 0.25], // g mid - [0.0, RAYGUN_HEIGHT, RAYGUN_LENGTH / 2.0, 90.0, 0.25], // h - [0.0, RAYGUN_HEIGHT, RAYGUN_LENGTH / 2.0, 52.0, 0.2], // i - [0.0, RAYGUN_HEIGHT, RAYGUN_LENGTH / 2.0, -52.0, 0.2], // j - [0.0, 0.0, RAYGUN_LENGTH / 2.0, 90.0, 0.25], // k - [ - 0.0, - RAYGUN_HEIGHT / 2.0, - RAYGUN_LENGTH * 3.0 / 16.0, - 52.0, - 0.2, - ], // l - [ - 0.0, - RAYGUN_HEIGHT * 3.0 / 2.0, - RAYGUN_LENGTH * 3.0 / 16.0, - -52.0, - 0.2, - ], // m - [0.0, RAYGUN_HEIGHT, 0.0, 0.0, 0.15], // n - [0.0, RAYGUN_HEIGHT, RAYGUN_LENGTH / 2.0, 0.0, 0.15], // o -]; - -/* - Segments making up each character, each index corresponding to: - 'A' through 'Z', '0' through '9', ' ', '-', '+', '#' (where '#' is all segments) -*/ -pub static ALPHABET: [&str; 40] = [ - "abcefg", - "adefijn", - "adef", - "eflm", - "adefn", - "aefn", - "acdefo", - "bcefg", - "adhk", - "bcd", - "efnij", - "def", - "bcefim", - "bcefjm", - "abcdef", - "abefg", - "abcdefj", - "aefijn", - "acdfg", - "ahk", - "bcdef", - "efil", - "bcefjl", - "ijlm", - "ikm", - "adil", - "abcdef", - "ef", - "abdeg", - "abcdg", - "bcfg", - "acdfg", - "acdefg", - "abc", - "abcdefg", - "abcdfg", - "", - "g", - "ghk", - "abcdefhijklmno", -]; - -// Each index is a segment's corresponding flipped segment, for when facing left -pub static SEGMENT_REV: [char; 15] = [ - 'a', 'f', 'e', 'd', 'c', 'b', 'g', 'h', 'm', 'l', 'k', 'j', 'i', 'o', 'n', -]; - -fn show_segment( - module_accessor: &mut app::BattleObjectModuleAccessor, - z: f32, - y: f32, - x: f32, - zrot: f32, - size: f32, -) { - let pos = Vector3f { x, y, z }; - let rot = Vector3f { - x: 0.0, - y: 90.0, - z: zrot, - }; - let random = Vector3f { - x: 0.0, - y: 0.0, - z: 0.0, - }; - - unsafe { - app::lua_bind::EffectModule::req_on_joint( - module_accessor, - Hash40::new("sys_raygun_bullet"), - Hash40::new("top"), - &pos, - &rot, - size, - &random, - &random, - false, - 0, - 0, - 0, - ); - } -} - -fn alphabet_index(to_print: char) -> i32 { - match to_print { - 'A'..='Z' => to_print as i32 - 'A' as i32, - '0'..='9' => to_print as i32 - '0' as i32 + 'Z' as i32 - 'A' as i32 + 1, - ' ' => 36, - '-' => 37, - '+' => 38, - '#' => 39, - _ => -1, - } -} - -fn print_char( - module_accessor: &mut app::BattleObjectModuleAccessor, - to_print: char, - line_num: i32, - horiz_offset: f32, - facing_left: i32, -) { - let is_facing_left = facing_left == -1; - let x_direction = facing_left as f32; - - let alph_index = alphabet_index(to_print); - if !(0..40).contains(&alph_index) { - return; - } - let segment_str = ALPHABET[alph_index as usize]; - - let line_offset = 40.0 - ((line_num as f32) * 16.0); - - for segment_char in segment_str.chars() { - let mut index = segment_char as i32 - 'a' as i32; - - let segment: [f32; 5]; - if is_facing_left { - index = SEGMENT_REV[index as usize] as i32 - 'a' as i32; - } - segment = SEGMENT_DICT[index as usize]; - - const SIZE_MULT: f32 = 0.5; - - let x = ((segment[2] + horiz_offset) * SIZE_MULT) + (x_direction * 5.0); - let y = ((segment[1] + line_offset) * SIZE_MULT) + 5.0; - let z = segment[0] * SIZE_MULT; - - let zrot = segment[3]; - let zrot = if is_facing_left { zrot.neg() } else { zrot }; - - let size = segment[4] * SIZE_MULT; - - show_segment(module_accessor, z, y, x, zrot, size); - } -} - -pub fn print_string(module_accessor: &mut app::BattleObjectModuleAccessor, to_write: &str) { - // Delete any previous strings - unsafe { - app::lua_bind::EffectModule::kill_kind( - module_accessor, - Hash40::new("sys_raygun_bullet"), - false, - true, - ); - } - - let mut line_num = 0; - let mut horiz_offset = 0.0; - let mut char_num = 0; - - let facing_left = unsafe { app::lua_bind::PostureModule::lr(module_accessor) as i32 }; - let facing_direction = facing_left as f32; - - if to_write.len() <= 8 && !to_write.contains('\n') { - line_num = 1; - } - for curr_char in to_write.chars() { - if curr_char == '\n' { - horiz_offset = 0.0; - char_num = 0; - line_num += 1; - continue; - } - - print_char( - module_accessor, - curr_char.to_uppercase().collect::>()[0], - line_num, - horiz_offset, - facing_left, - ); - - char_num += 1; - // short characters - if curr_char == 'D' || curr_char == '1' { - horiz_offset += facing_direction * (RAYGUN_LENGTH / 2.0 + 3.0); - } else { - horiz_offset += facing_direction * (RAYGUN_LENGTH + 3.0); - } - - if char_num > 8 { - horiz_offset = 0.0; - char_num = 0; - line_num += 1; - } - } -} +use std::ops::Neg; + +use smash::app; +use smash::phx::{Hash40, Vector3f}; + +pub const RAYGUN_LENGTH: f32 = 8.0; +pub const RAYGUN_HEIGHT: f32 = 6.0; +pub const RAYGUN_HORIZ_OFFSET: f32 = 2.0; + +/* + segment data list : {Z, Y, X, ZRot, Size} + segment labels : + _ + |_| from top to top left, clockwise: a->f + g mid + \|/ from top mid to top left, clockwise: h->m + --two half g's: n, o + |_| /|\ +*/ + +pub static SEGMENT_DICT: [[f32; 5]; 15] = [ + [0.0, RAYGUN_HEIGHT * 2.0, 0.0, 0.0, 0.25], // a + [0.0, RAYGUN_HEIGHT, RAYGUN_LENGTH, 90.0, 0.25], // b + [0.0, 0.0, RAYGUN_LENGTH, 90.0, 0.25], // c + [0.0, 0.0, 0.0, 0.0, 0.25], // d + [0.0, 0.0, 0.0, 90.0, 0.25], // e + [0.0, RAYGUN_HEIGHT, 0.0, 90.0, 0.25], // f + [0.0, RAYGUN_HEIGHT, 0.0, 0.0, 0.25], // g mid + [0.0, RAYGUN_HEIGHT, RAYGUN_LENGTH / 2.0, 90.0, 0.25], // h + [0.0, RAYGUN_HEIGHT, RAYGUN_LENGTH / 2.0, 52.0, 0.2], // i + [0.0, RAYGUN_HEIGHT, RAYGUN_LENGTH / 2.0, -52.0, 0.2], // j + [0.0, 0.0, RAYGUN_LENGTH / 2.0, 90.0, 0.25], // k + [ + 0.0, + RAYGUN_HEIGHT / 2.0, + RAYGUN_LENGTH * 3.0 / 16.0, + 52.0, + 0.2, + ], // l + [ + 0.0, + RAYGUN_HEIGHT * 3.0 / 2.0, + RAYGUN_LENGTH * 3.0 / 16.0, + -52.0, + 0.2, + ], // m + [0.0, RAYGUN_HEIGHT, 0.0, 0.0, 0.15], // n + [0.0, RAYGUN_HEIGHT, RAYGUN_LENGTH / 2.0, 0.0, 0.15], // o +]; + +/* + Segments making up each character, each index corresponding to: + 'A' through 'Z', '0' through '9', ' ', '-', '+', '#' (where '#' is all segments) +*/ +pub static ALPHABET: [&str; 40] = [ + "abcefg", + "adefijn", + "adef", + "eflm", + "adefn", + "aefn", + "acdefo", + "bcefg", + "adhk", + "bcd", + "efnij", + "def", + "bcefim", + "bcefjm", + "abcdef", + "abefg", + "abcdefj", + "aefijn", + "acdfg", + "ahk", + "bcdef", + "efil", + "bcefjl", + "ijlm", + "ikm", + "adil", + "abcdef", + "ef", + "abdeg", + "abcdg", + "bcfg", + "acdfg", + "acdefg", + "abc", + "abcdefg", + "abcdfg", + "", + "g", + "ghk", + "abcdefhijklmno", +]; + +// Each index is a segment's corresponding flipped segment, for when facing left +pub static SEGMENT_REV: [char; 15] = [ + 'a', 'f', 'e', 'd', 'c', 'b', 'g', 'h', 'm', 'l', 'k', 'j', 'i', 'o', 'n', +]; + +fn show_segment( + module_accessor: &mut app::BattleObjectModuleAccessor, + z: f32, + y: f32, + x: f32, + zrot: f32, + size: f32, +) { + let pos = Vector3f { x, y, z }; + let rot = Vector3f { + x: 0.0, + y: 90.0, + z: zrot, + }; + let random = Vector3f { + x: 0.0, + y: 0.0, + z: 0.0, + }; + + unsafe { + app::lua_bind::EffectModule::req_on_joint( + module_accessor, + Hash40::new("sys_raygun_bullet"), + Hash40::new("top"), + &pos, + &rot, + size, + &random, + &random, + false, + 0, + 0, + 0, + ); + } +} + +fn alphabet_index(to_print: char) -> i32 { + match to_print { + 'A'..='Z' => to_print as i32 - 'A' as i32, + '0'..='9' => to_print as i32 - '0' as i32 + 'Z' as i32 - 'A' as i32 + 1, + ' ' => 36, + '-' => 37, + '+' => 38, + '#' => 39, + _ => -1, + } +} + +fn print_char( + module_accessor: &mut app::BattleObjectModuleAccessor, + to_print: char, + line_num: i32, + horiz_offset: f32, + facing_left: i32, +) { + let is_facing_left = facing_left == -1; + let x_direction = facing_left as f32; + + let alph_index = alphabet_index(to_print); + if !(0..40).contains(&alph_index) { + return; + } + let segment_str = ALPHABET[alph_index as usize]; + + let line_offset = 40.0 - ((line_num as f32) * 16.0); + + for segment_char in segment_str.chars() { + let mut index = segment_char as i32 - 'a' as i32; + + let segment: [f32; 5]; + if is_facing_left { + index = SEGMENT_REV[index as usize] as i32 - 'a' as i32; + } + segment = SEGMENT_DICT[index as usize]; + + const SIZE_MULT: f32 = 0.5; + + let x = ((segment[2] + horiz_offset) * SIZE_MULT) + (x_direction * 5.0); + let y = ((segment[1] + line_offset) * SIZE_MULT) + 5.0; + let z = segment[0] * SIZE_MULT; + + let zrot = segment[3]; + let zrot = if is_facing_left { zrot.neg() } else { zrot }; + + let size = segment[4] * SIZE_MULT; + + show_segment(module_accessor, z, y, x, zrot, size); + } +} + +pub fn print_string(module_accessor: &mut app::BattleObjectModuleAccessor, to_write: &str) { + // Delete any previous strings + unsafe { + app::lua_bind::EffectModule::kill_kind( + module_accessor, + Hash40::new("sys_raygun_bullet"), + false, + true, + ); + } + + let mut line_num = 0; + let mut horiz_offset = 0.0; + let mut char_num = 0; + + let facing_left = unsafe { app::lua_bind::PostureModule::lr(module_accessor) as i32 }; + let facing_direction = facing_left as f32; + + if to_write.len() <= 8 && !to_write.contains('\n') { + line_num = 1; + } + for curr_char in to_write.chars() { + if curr_char == '\n' { + horiz_offset = 0.0; + char_num = 0; + line_num += 1; + continue; + } + + print_char( + module_accessor, + curr_char.to_uppercase().collect::>()[0], + line_num, + horiz_offset, + facing_left, + ); + + char_num += 1; + // short characters + if curr_char == 'D' || curr_char == '1' { + horiz_offset += facing_direction * (RAYGUN_LENGTH / 2.0 + 3.0); + } else { + horiz_offset += facing_direction * (RAYGUN_LENGTH + 3.0); + } + + if char_num > 8 { + horiz_offset = 0.0; + char_num = 0; + line_num += 1; + } + } +} diff --git a/src/common/release.rs b/src/common/release.rs index 47a9e01..f19543a 100644 --- a/src/common/release.rs +++ b/src/common/release.rs @@ -1,36 +1,36 @@ -use skyline_web::DialogOk; -use std::fs; - -pub const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION"); -const VERSION_FILE_PATH: &str = "sd:/TrainingModpack/version.txt"; - -fn is_current_version(fpath: &str) -> bool { - // Create a blank version file if it doesn't exists - if fs::metadata(fpath).is_err() { - let _ = fs::File::create(fpath).expect("Could not create version file!"); - } - - fs::read_to_string(fpath) - .map(|content| content == CURRENT_VERSION) - .unwrap_or(false) -} - -fn record_current_version(fpath: &str) { - // Write the current version to the version file - fs::write(fpath, CURRENT_VERSION).expect("Could not record current version!") -} - -pub fn version_check() { - // Display dialog box on launch if changing versions - if !is_current_version(VERSION_FILE_PATH) { - DialogOk::ok( - format!( - "Thank you for installing version {} of the Training Modpack.\n\n\ - This version includes a change to the menu button combination, which is now SPECIAL+UPTAUNT.\n\ - Please refer to the Github page and the Discord server for a full list of recent changes.", - CURRENT_VERSION - ) - ); - record_current_version(VERSION_FILE_PATH); - } -} +use skyline_web::DialogOk; +use std::fs; + +pub const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION"); +const VERSION_FILE_PATH: &str = "sd:/TrainingModpack/version.txt"; + +fn is_current_version(fpath: &str) -> bool { + // Create a blank version file if it doesn't exists + if fs::metadata(fpath).is_err() { + let _ = fs::File::create(fpath).expect("Could not create version file!"); + } + + fs::read_to_string(fpath) + .map(|content| content == CURRENT_VERSION) + .unwrap_or(false) +} + +fn record_current_version(fpath: &str) { + // Write the current version to the version file + fs::write(fpath, CURRENT_VERSION).expect("Could not record current version!") +} + +pub fn version_check() { + // Display dialog box on launch if changing versions + if !is_current_version(VERSION_FILE_PATH) { + DialogOk::ok( + format!( + "Thank you for installing version {} of the Training Modpack.\n\n\ + This version includes a change to the menu button combination, which is now SPECIAL+UPTAUNT.\n\ + Please refer to the Github page and the Discord server for a full list of recent changes.", + CURRENT_VERSION + ) + ); + record_current_version(VERSION_FILE_PATH); + } +} diff --git a/src/lib.rs b/src/lib.rs index 33f77c1..c366659 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,141 +1,135 @@ -#![feature(proc_macro_hygiene)] -#![feature(with_options)] -#![feature(const_mut_refs)] -#![feature(exclusive_range_pattern)] -#![feature(once_cell)] -#![allow( - clippy::borrow_interior_mutable_const, - clippy::not_unsafe_ptr_arg_deref, - clippy::missing_safety_doc, - clippy::wrong_self_convention -)] - -pub mod common; -mod hazard_manager; -mod hitbox_visualizer; -mod training; - -#[cfg(test)] -mod test; - -#[macro_use] -extern crate bitflags; - -#[macro_use] -extern crate num_derive; - -use crate::common::*; -use crate::events::{Event, EVENT_QUEUE}; -use crate::menu::get_menu_from_url; - -use skyline::libc::mkdir; -use skyline::nro::{self, NroInfo}; -use std::fs; - -use owo_colors::OwoColorize; - -fn nro_main(nro: &NroInfo<'_>) { - if nro.module.isLoaded { - return; - } - - if nro.name == "common" { - skyline::install_hooks!( - training::shield::handle_sub_guard_cont, - training::directional_influence::handle_correct_damage_vector_common, - training::sdi::process_hit_stop_delay, - training::tech::handle_change_status, - ); - } -} - -macro_rules! c_str { - ($l:tt) => { - [$l.as_bytes(), "\u{0}".as_bytes()].concat().as_ptr(); - }; -} - -#[skyline::main(name = "training_modpack")] -pub fn main() { - macro_rules! log { - ($($arg:tt)*) => { - print!("{}", "[Training Modpack] ".green()); - println!($($arg)*); - }; - } - - log!("Initialized."); - unsafe { - EVENT_QUEUE.push(Event::smash_open()); - } - - hitbox_visualizer::hitbox_visualization(); - hazard_manager::hazard_manager(); - training::training_mods(); - nro::add_hook(nro_main).unwrap(); - - unsafe { - mkdir(c_str!("sd:/TrainingModpack/"), 777); - } - - let ovl_path = "sd:/switch/.overlays/ovlTrainingModpack.ovl"; - if fs::metadata(ovl_path).is_ok() { - log!("Removing ovlTrainingModpack.ovl..."); - fs::remove_file(ovl_path).unwrap(); - } - - log!("Performing version check..."); - release::version_check(); - - let menu_conf_path = "sd:/TrainingModpack/training_modpack_menu.conf"; - log!("Checking for previous menu in training_modpack_menu.conf..."); - if fs::metadata(menu_conf_path).is_ok() { - let menu_conf = fs::read(menu_conf_path).unwrap(); - if menu_conf.starts_with(b"http://localhost") { - log!("Previous menu found, loading from training_modpack_menu.conf"); - unsafe { - MENU = get_menu_from_url(MENU, std::str::from_utf8(&menu_conf).unwrap()); - } - } else { - log!("Previous menu found but is invalid."); - } - } else { - log!("No previous menu file found."); - } - - let menu_defaults_conf_path = "sd:/TrainingModpack/training_modpack_menu_defaults.conf"; - log!("Checking for previous menu defaults in training_modpack_menu_defaults.conf..."); - 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") { - log!("Menu defaults found, loading from training_modpack_menu_defaults.conf"); - unsafe { - DEFAULT_MENU = get_menu_from_url( - DEFAULT_MENU, - std::str::from_utf8(&menu_defaults_conf).unwrap(), - ); - crate::menu::write_menu(); - } - } else { - log!("Previous menu defaults found but are invalid."); - } - } else { - log!("No previous menu defaults found."); - } - - std::thread::spawn(|| loop { - std::thread::sleep(std::time::Duration::from_secs(5)); - 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(); - } - } - }); -} +#![feature(proc_macro_hygiene)] +#![feature(with_options)] +#![feature(const_mut_refs)] +#![feature(exclusive_range_pattern)] +#![feature(once_cell)] +#![allow( + clippy::borrow_interior_mutable_const, + clippy::not_unsafe_ptr_arg_deref, + clippy::missing_safety_doc, + clippy::wrong_self_convention +)] + +pub mod common; +mod hazard_manager; +mod hitbox_visualizer; +mod training; + +#[cfg(test)] +mod test; + +use crate::common::*; +use crate::events::{Event, EVENT_QUEUE}; +use crate::menu::get_menu_from_url; + +use skyline::libc::mkdir; +use skyline::nro::{self, NroInfo}; +use std::fs; + +use owo_colors::OwoColorize; + +fn nro_main(nro: &NroInfo<'_>) { + if nro.module.isLoaded { + return; + } + + if nro.name == "common" { + skyline::install_hooks!( + training::shield::handle_sub_guard_cont, + training::directional_influence::handle_correct_damage_vector_common, + training::sdi::process_hit_stop_delay, + training::tech::handle_change_status, + ); + } +} + +macro_rules! c_str { + ($l:tt) => { + [$l.as_bytes(), "\u{0}".as_bytes()].concat().as_ptr(); + }; +} + +#[skyline::main(name = "training_modpack")] +pub fn main() { + macro_rules! log { + ($($arg:tt)*) => { + print!("{}", "[Training Modpack] ".green()); + println!($($arg)*); + }; + } + + log!("Initialized."); + unsafe { + EVENT_QUEUE.push(Event::smash_open()); + } + + hitbox_visualizer::hitbox_visualization(); + hazard_manager::hazard_manager(); + training::training_mods(); + nro::add_hook(nro_main).unwrap(); + + unsafe { + mkdir(c_str!("sd:/TrainingModpack/"), 777); + } + + let ovl_path = "sd:/switch/.overlays/ovlTrainingModpack.ovl"; + if fs::metadata(ovl_path).is_ok() { + log!("Removing ovlTrainingModpack.ovl..."); + fs::remove_file(ovl_path).unwrap(); + } + + log!("Performing version check..."); + release::version_check(); + + let menu_conf_path = "sd:/TrainingModpack/training_modpack_menu.conf"; + log!("Checking for previous menu in training_modpack_menu.conf..."); + if fs::metadata(menu_conf_path).is_ok() { + let menu_conf = fs::read(menu_conf_path).unwrap(); + if menu_conf.starts_with(b"http://localhost") { + log!("Previous menu found, loading from training_modpack_menu.conf"); + unsafe { + MENU = get_menu_from_url(MENU, std::str::from_utf8(&menu_conf).unwrap()); + } + } else { + log!("Previous menu found but is invalid."); + } + } else { + log!("No previous menu file found."); + } + + let menu_defaults_conf_path = "sd:/TrainingModpack/training_modpack_menu_defaults.conf"; + log!("Checking for previous menu defaults in training_modpack_menu_defaults.conf..."); + 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") { + log!("Menu defaults found, loading from training_modpack_menu_defaults.conf"); + unsafe { + DEFAULT_MENU = get_menu_from_url( + DEFAULT_MENU, + std::str::from_utf8(&menu_defaults_conf).unwrap(), + ); + crate::menu::write_menu(); + } + } else { + log!("Previous menu defaults found but are invalid."); + } + } else { + log!("No previous menu defaults found."); + } + + std::thread::spawn(|| loop { + std::thread::sleep(std::time::Duration::from_secs(5)); + 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(); + } + } + }); +} diff --git a/src/templates/buff_state.svg b/src/templates/buff_state.svg index ce075af..71effba 100644 --- a/src/templates/buff_state.svg +++ b/src/templates/buff_state.svg @@ -1,82 +1,82 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/src/templates/menu.html b/src/templates/menu.html index 46aae67..09e0240 100644 --- a/src/templates/menu.html +++ b/src/templates/menu.html @@ -1,487 +1,487 @@ - - - - - - - Document - - - - - - - - - - - - - - - - - - -
-
-
-
- -
- - - {{#sub_menus}} -
- {{^onoffselector}} - -
-
-
- -

- {{title}} -

-
-
-
-
- - {{/onoffselector}} - {{#onoffselector}} - -
-
-
-
- -
-

- {{title}} -

-
-
-
-
-
- {{/onoffselector}} -
- {{/sub_menus}} -
- {{#sub_menus}} - {{#sliders}} - - {{/sliders}} - {{/sub_menus}} -
-

-
- - -
-
-
- - - - - + + + + + + + Document + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+ + + {{#sub_menus}} +
+ {{^onoffselector}} + +
+
+
+ +

+ {{title}} +

+
+
+
+
+ + {{/onoffselector}} + {{#onoffselector}} + +
+
+
+
+ +
+

+ {{title}} +

+
+
+
+
+
+ {{/onoffselector}} +
+ {{/sub_menus}} +
+ {{#sub_menus}} + {{#sliders}} + + {{/sliders}} + {{/sub_menus}} +
+

+
+ + +
+
+
+ + + + + diff --git a/src/templates/pummel_delay.svg b/src/templates/pummel_delay.svg index f012002..a8e38c7 100644 --- a/src/templates/pummel_delay.svg +++ b/src/templates/pummel_delay.svg @@ -1,116 +1,116 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/src/templates/throw_delay.svg b/src/templates/throw_delay.svg index 9cdc69e..927e7b2 100644 --- a/src/templates/throw_delay.svg +++ b/src/templates/throw_delay.svg @@ -1,127 +1,127 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/templates/throw_state.svg b/src/templates/throw_state.svg index 99f815f..e6ea8cc 100644 --- a/src/templates/throw_state.svg +++ b/src/templates/throw_state.svg @@ -1,201 +1,201 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/training/buff.rs b/src/training/buff.rs index bd2cbdd..bc1cb8d 100644 --- a/src/training/buff.rs +++ b/src/training/buff.rs @@ -1,246 +1,246 @@ -use crate::common::consts::*; -use crate::common::*; -use crate::is_operation_cpu; -use crate::training::frame_counter; -use crate::training::handle_add_limit; -use smash::app::{self, lua_bind::*}; -use smash::lib::lua_const::*; - -static mut BUFF_DELAY_COUNTER: usize = 0; - -static mut BUFF_REMAINING_PLAYER: i32 = 0; -static mut BUFF_REMAINING_CPU: i32 = 0; - -static mut IS_BUFFING_PLAYER: bool = false; -static mut IS_BUFFING_CPU: bool = false; - -pub fn init() { - unsafe { - BUFF_DELAY_COUNTER = frame_counter::register_counter(); - } -} - -pub unsafe fn restart_buff(module_accessor: &mut app::BattleObjectModuleAccessor) { - if is_operation_cpu(module_accessor) { - IS_BUFFING_CPU = false; - return; - } - IS_BUFFING_PLAYER = false; -} - -pub unsafe fn start_buff(module_accessor: &mut app::BattleObjectModuleAccessor) { - if is_operation_cpu(module_accessor) { - IS_BUFFING_CPU = true; - return; - } - IS_BUFFING_PLAYER = true; -} - -pub unsafe fn is_buffing(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { - if is_operation_cpu(module_accessor) { - return IS_BUFFING_CPU; - } - return IS_BUFFING_PLAYER; -} - -pub unsafe fn set_buff_rem(module_accessor: &mut app::BattleObjectModuleAccessor, new_value: i32) { - if is_operation_cpu(module_accessor) { - BUFF_REMAINING_CPU = new_value; - return; - } - BUFF_REMAINING_PLAYER = new_value; -} - -pub unsafe fn get_buff_rem(module_accessor: &mut app::BattleObjectModuleAccessor) -> i32 { - if is_operation_cpu(module_accessor) { - return BUFF_REMAINING_CPU; - } - return BUFF_REMAINING_PLAYER; -} - -fn get_spell_vec() -> Vec { - unsafe { - let menu_buff = MENU.buff_state.to_vec(); - let menu_iter = menu_buff.iter(); - let mut spell_buff: Vec = Vec::new(); - for buff in menu_iter { - if buff.into_int().unwrap_or(1) != 1 { - // all non-spells into_int as 1 - spell_buff.push(*buff); - } - } - return spell_buff; - } -} - -pub unsafe fn handle_buffs( - module_accessor: &mut app::BattleObjectModuleAccessor, - fighter_kind: i32, - status: i32, - percent: f32, -) -> bool { - SoundModule::stop_all_sound(module_accessor); // silences buff sfx other than KO Punch - ControlModule::stop_rumble(module_accessor, false); - MotionAnimcmdModule::set_sleep(module_accessor, false); - - let menu_vec = MENU.buff_state.to_vec(); - - if fighter_kind == *FIGHTER_KIND_BRAVE { - return buff_hero(module_accessor, status); - } else if fighter_kind == *FIGHTER_KIND_JACK && menu_vec.contains(&BuffOption::ARSENE) { - return buff_joker(module_accessor); - } else if fighter_kind == *FIGHTER_KIND_WIIFIT && menu_vec.contains(&BuffOption::BREATHING) { - return buff_wiifit(module_accessor, status); - } else if fighter_kind == *FIGHTER_KIND_CLOUD && menu_vec.contains(&BuffOption::LIMIT) { - return buff_cloud(module_accessor); - } else if fighter_kind == *FIGHTER_KIND_LITTLEMAC && menu_vec.contains(&BuffOption::KO) { - return buff_mac(module_accessor); - } else if fighter_kind == *FIGHTER_KIND_EDGE && menu_vec.contains(&BuffOption::WING) { - return buff_sepiroth(module_accessor, percent); - } - - return true; -} - -unsafe fn buff_hero(module_accessor: &mut app::BattleObjectModuleAccessor, status: i32) -> bool { - let buff_vec = get_spell_vec(); - if !is_buffing(module_accessor) { - // Initial set up for spells - start_buff(module_accessor); - set_buff_rem(module_accessor, buff_vec.len() as i32); - // Since it's the first step of buffing, we need to set up how many buffs there are - } - if get_buff_rem(module_accessor) <= 0 { - // If there are no buffs selected/left, we're done - return true; - } - buff_hero_single(module_accessor, status, buff_vec); - return false; -} - -unsafe fn buff_hero_single( - module_accessor: &mut app::BattleObjectModuleAccessor, - status: i32, - buff_vec: Vec, -) { - let prev_status_kind = StatusModule::prev_status_kind(module_accessor, 0); - if prev_status_kind == FIGHTER_BRAVE_STATUS_KIND_SPECIAL_LW_START { - // If we just applied a buff successfully, subtract from buffs remaining - let new_rem_value = get_buff_rem(module_accessor) - 1; - set_buff_rem(module_accessor, new_rem_value); - } - let spell_index = get_buff_rem(module_accessor) - 1; - // Used to get spell from our vector - let spell_option = buff_vec.get(spell_index as usize); - if spell_option.is_none() { - // There are no spells selected, or something went wrong with making the vector - return; - } - let real_spell_value = spell_option.unwrap().into_int().unwrap(); - if status != FIGHTER_BRAVE_STATUS_KIND_SPECIAL_LW_START { - WorkModule::set_int( - module_accessor, - real_spell_value, - *FIGHTER_BRAVE_INSTANCE_WORK_ID_INT_SPECIAL_LW_DECIDE_COMMAND, - ); - StatusModule::change_status_force( - module_accessor, - *FIGHTER_BRAVE_STATUS_KIND_SPECIAL_LW_START, - true, - // True to prevent Shielding over the spells - ); - } - if status == FIGHTER_BRAVE_STATUS_KIND_SPECIAL_LW_START { - MotionModule::set_rate(module_accessor, 50.0); - } -} - -unsafe fn buff_cloud(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { - if !is_buffing(module_accessor) { - // Only need to add to the limit gauge once - start_buff(module_accessor); - handle_add_limit(100.0, module_accessor, 0); - } - if frame_counter::should_delay(2 as u32, BUFF_DELAY_COUNTER) { - // Need to wait 2 frames to make sure we stop the limit SFX, since it's a bit delayed - return false; - } - return true; -} - -unsafe fn buff_joker(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { - if !is_buffing(module_accessor) { - // Only need to add to the rebel gauge once - start_buff(module_accessor); - let entry_id = app::FighterEntryID(FighterId::CPU as i32); - // Strangely, this doesn't actually matter and works for both fighters - app::FighterSpecializer_Jack::add_rebel_gauge(module_accessor, entry_id, 120.0); - } - if frame_counter::should_delay(2 as u32, BUFF_DELAY_COUNTER) { - // Need to wait 2 frames to make sure we stop the voice call, since it's a bit delayed - return false; - } - return true; -} - -unsafe fn buff_mac(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { - WorkModule::set_float( - module_accessor, - 100.0, - *FIGHTER_LITTLEMAC_INSTANCE_WORK_ID_FLOAT_KO_GAGE, - ); - // Trying to stop KO Punch from playing seems to make it play multiple times in rapid succession - // Look at 0x7100c44b60 for the func that handles this - // Need to figure out how to update the KO meter if this is fixed - return true; -} - -unsafe fn buff_sepiroth( - module_accessor: &mut app::BattleObjectModuleAccessor, - percent: f32, -) -> bool { - start_buff(module_accessor); - if WorkModule::get_int( - module_accessor, - *FIGHTER_EDGE_INSTANCE_WORK_ID_INT_ONE_WINGED_WING_STATE, - ) == 1 - { - // Once we're in wing, heal to correct damage - DamageModule::heal( - module_accessor, - -1.0 * DamageModule::damage(module_accessor, 0), - 0, - ); - DamageModule::add_damage(module_accessor, percent, 0); - return true; - } else { - // if we're not in wing, add damage - DamageModule::add_damage(module_accessor, 1000.0, 0); - } - return false; -} - -unsafe fn buff_wiifit(module_accessor: &mut app::BattleObjectModuleAccessor, status: i32) -> bool { - if is_buffing(module_accessor) { - if frame_counter::should_delay(2 as u32, BUFF_DELAY_COUNTER) { - // Need to wait 2 frames to make sure we stop breathing SFX - return false; - } - return true; - } - let prev_status_kind = StatusModule::prev_status_kind(module_accessor, 0); - if prev_status_kind == FIGHTER_WIIFIT_STATUS_KIND_SPECIAL_LW_SUCCESS { - start_buff(module_accessor); - return false; - } - if status != FIGHTER_WIIFIT_STATUS_KIND_SPECIAL_LW_SUCCESS { - StatusModule::change_status_force( - module_accessor, - *FIGHTER_WIIFIT_STATUS_KIND_SPECIAL_LW_SUCCESS, - false, - ); - } else { - MotionModule::set_rate(module_accessor, 40.0); - } - return false; -} +use crate::common::consts::*; +use crate::common::*; +use crate::is_operation_cpu; +use crate::training::frame_counter; +use crate::training::handle_add_limit; +use smash::app::{self, lua_bind::*}; +use smash::lib::lua_const::*; + +static mut BUFF_DELAY_COUNTER: usize = 0; + +static mut BUFF_REMAINING_PLAYER: i32 = 0; +static mut BUFF_REMAINING_CPU: i32 = 0; + +static mut IS_BUFFING_PLAYER: bool = false; +static mut IS_BUFFING_CPU: bool = false; + +pub fn init() { + unsafe { + BUFF_DELAY_COUNTER = frame_counter::register_counter(); + } +} + +pub unsafe fn restart_buff(module_accessor: &mut app::BattleObjectModuleAccessor) { + if is_operation_cpu(module_accessor) { + IS_BUFFING_CPU = false; + return; + } + IS_BUFFING_PLAYER = false; +} + +pub unsafe fn start_buff(module_accessor: &mut app::BattleObjectModuleAccessor) { + if is_operation_cpu(module_accessor) { + IS_BUFFING_CPU = true; + return; + } + IS_BUFFING_PLAYER = true; +} + +pub unsafe fn is_buffing(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { + if is_operation_cpu(module_accessor) { + return IS_BUFFING_CPU; + } + return IS_BUFFING_PLAYER; +} + +pub unsafe fn set_buff_rem(module_accessor: &mut app::BattleObjectModuleAccessor, new_value: i32) { + if is_operation_cpu(module_accessor) { + BUFF_REMAINING_CPU = new_value; + return; + } + BUFF_REMAINING_PLAYER = new_value; +} + +pub unsafe fn get_buff_rem(module_accessor: &mut app::BattleObjectModuleAccessor) -> i32 { + if is_operation_cpu(module_accessor) { + return BUFF_REMAINING_CPU; + } + return BUFF_REMAINING_PLAYER; +} + +fn get_spell_vec() -> Vec { + unsafe { + let menu_buff = MENU.buff_state.to_vec(); + let menu_iter = menu_buff.iter(); + let mut spell_buff: Vec = Vec::new(); + for buff in menu_iter { + if buff.into_int().unwrap_or(1) != 1 { + // all non-spells into_int as 1 + spell_buff.push(*buff); + } + } + return spell_buff; + } +} + +pub unsafe fn handle_buffs( + module_accessor: &mut app::BattleObjectModuleAccessor, + fighter_kind: i32, + status: i32, + percent: f32, +) -> bool { + SoundModule::stop_all_sound(module_accessor); // silences buff sfx other than KO Punch + ControlModule::stop_rumble(module_accessor, false); + MotionAnimcmdModule::set_sleep(module_accessor, false); + + let menu_vec = MENU.buff_state.to_vec(); + + if fighter_kind == *FIGHTER_KIND_BRAVE { + return buff_hero(module_accessor, status); + } else if fighter_kind == *FIGHTER_KIND_JACK && menu_vec.contains(&BuffOption::ARSENE) { + return buff_joker(module_accessor); + } else if fighter_kind == *FIGHTER_KIND_WIIFIT && menu_vec.contains(&BuffOption::BREATHING) { + return buff_wiifit(module_accessor, status); + } else if fighter_kind == *FIGHTER_KIND_CLOUD && menu_vec.contains(&BuffOption::LIMIT) { + return buff_cloud(module_accessor); + } else if fighter_kind == *FIGHTER_KIND_LITTLEMAC && menu_vec.contains(&BuffOption::KO) { + return buff_mac(module_accessor); + } else if fighter_kind == *FIGHTER_KIND_EDGE && menu_vec.contains(&BuffOption::WING) { + return buff_sepiroth(module_accessor, percent); + } + + return true; +} + +unsafe fn buff_hero(module_accessor: &mut app::BattleObjectModuleAccessor, status: i32) -> bool { + let buff_vec = get_spell_vec(); + if !is_buffing(module_accessor) { + // Initial set up for spells + start_buff(module_accessor); + set_buff_rem(module_accessor, buff_vec.len() as i32); + // Since it's the first step of buffing, we need to set up how many buffs there are + } + if get_buff_rem(module_accessor) <= 0 { + // If there are no buffs selected/left, we're done + return true; + } + buff_hero_single(module_accessor, status, buff_vec); + return false; +} + +unsafe fn buff_hero_single( + module_accessor: &mut app::BattleObjectModuleAccessor, + status: i32, + buff_vec: Vec, +) { + let prev_status_kind = StatusModule::prev_status_kind(module_accessor, 0); + if prev_status_kind == FIGHTER_BRAVE_STATUS_KIND_SPECIAL_LW_START { + // If we just applied a buff successfully, subtract from buffs remaining + let new_rem_value = get_buff_rem(module_accessor) - 1; + set_buff_rem(module_accessor, new_rem_value); + } + let spell_index = get_buff_rem(module_accessor) - 1; + // Used to get spell from our vector + let spell_option = buff_vec.get(spell_index as usize); + if spell_option.is_none() { + // There are no spells selected, or something went wrong with making the vector + return; + } + let real_spell_value = spell_option.unwrap().into_int().unwrap(); + if status != FIGHTER_BRAVE_STATUS_KIND_SPECIAL_LW_START { + WorkModule::set_int( + module_accessor, + real_spell_value, + *FIGHTER_BRAVE_INSTANCE_WORK_ID_INT_SPECIAL_LW_DECIDE_COMMAND, + ); + StatusModule::change_status_force( + module_accessor, + *FIGHTER_BRAVE_STATUS_KIND_SPECIAL_LW_START, + true, + // True to prevent Shielding over the spells + ); + } + if status == FIGHTER_BRAVE_STATUS_KIND_SPECIAL_LW_START { + MotionModule::set_rate(module_accessor, 50.0); + } +} + +unsafe fn buff_cloud(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { + if !is_buffing(module_accessor) { + // Only need to add to the limit gauge once + start_buff(module_accessor); + handle_add_limit(100.0, module_accessor, 0); + } + if frame_counter::should_delay(2 as u32, BUFF_DELAY_COUNTER) { + // Need to wait 2 frames to make sure we stop the limit SFX, since it's a bit delayed + return false; + } + return true; +} + +unsafe fn buff_joker(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { + if !is_buffing(module_accessor) { + // Only need to add to the rebel gauge once + start_buff(module_accessor); + let entry_id = app::FighterEntryID(FighterId::CPU as i32); + // Strangely, this doesn't actually matter and works for both fighters + app::FighterSpecializer_Jack::add_rebel_gauge(module_accessor, entry_id, 120.0); + } + if frame_counter::should_delay(2 as u32, BUFF_DELAY_COUNTER) { + // Need to wait 2 frames to make sure we stop the voice call, since it's a bit delayed + return false; + } + return true; +} + +unsafe fn buff_mac(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { + WorkModule::set_float( + module_accessor, + 100.0, + *FIGHTER_LITTLEMAC_INSTANCE_WORK_ID_FLOAT_KO_GAGE, + ); + // Trying to stop KO Punch from playing seems to make it play multiple times in rapid succession + // Look at 0x7100c44b60 for the func that handles this + // Need to figure out how to update the KO meter if this is fixed + return true; +} + +unsafe fn buff_sepiroth( + module_accessor: &mut app::BattleObjectModuleAccessor, + percent: f32, +) -> bool { + start_buff(module_accessor); + if WorkModule::get_int( + module_accessor, + *FIGHTER_EDGE_INSTANCE_WORK_ID_INT_ONE_WINGED_WING_STATE, + ) == 1 + { + // Once we're in wing, heal to correct damage + DamageModule::heal( + module_accessor, + -1.0 * DamageModule::damage(module_accessor, 0), + 0, + ); + DamageModule::add_damage(module_accessor, percent, 0); + return true; + } else { + // if we're not in wing, add damage + DamageModule::add_damage(module_accessor, 1000.0, 0); + } + return false; +} + +unsafe fn buff_wiifit(module_accessor: &mut app::BattleObjectModuleAccessor, status: i32) -> bool { + if is_buffing(module_accessor) { + if frame_counter::should_delay(2 as u32, BUFF_DELAY_COUNTER) { + // Need to wait 2 frames to make sure we stop breathing SFX + return false; + } + return true; + } + let prev_status_kind = StatusModule::prev_status_kind(module_accessor, 0); + if prev_status_kind == FIGHTER_WIIFIT_STATUS_KIND_SPECIAL_LW_SUCCESS { + start_buff(module_accessor); + return false; + } + if status != FIGHTER_WIIFIT_STATUS_KIND_SPECIAL_LW_SUCCESS { + StatusModule::change_status_force( + module_accessor, + *FIGHTER_WIIFIT_STATUS_KIND_SPECIAL_LW_SUCCESS, + false, + ); + } else { + MotionModule::set_rate(module_accessor, 40.0); + } + return false; +} diff --git a/src/training/mod.rs b/src/training/mod.rs index d46ee4f..858b8e7 100644 --- a/src/training/mod.rs +++ b/src/training/mod.rs @@ -1,535 +1,535 @@ -use crate::common::{is_training_mode, menu, FIGHTER_MANAGER_ADDR, STAGE_MANAGER_ADDR}; -use crate::hitbox_visualizer; -use skyline::hooks::{getRegionAddress, InlineCtx, Region}; -use skyline::nn::hid::*; -use skyline::nn::ro::LookupSymbol; -use smash::app::{self, enSEType, lua_bind::*}; -use smash::lib::lua_const::*; -use smash::params::*; -use smash::phx::{Hash40, Vector3f}; - -pub mod buff; -pub mod combo; -pub mod directional_influence; -pub mod frame_counter; -pub mod ledge; -pub mod sdi; -pub mod shield; -pub mod tech; -pub mod throw; - -mod air_dodge_direction; -mod attack_angle; -mod character_specific; -mod fast_fall; -mod full_hop; -mod input_delay; -mod input_record; -mod mash; -mod reset; -mod save_states; -mod shield_tilt; - -#[skyline::hook(replace = WorkModule::get_param_float)] -pub unsafe fn handle_get_param_float( - module_accessor: &mut app::BattleObjectModuleAccessor, - param_type: u64, - param_hash: u64, -) -> f32 { - let ori = original!()(module_accessor, param_type, param_hash); - if !is_training_mode() { - return ori; - } - - shield::get_param_float(module_accessor, param_type, param_hash).unwrap_or(ori) -} - -#[skyline::hook(replace = WorkModule::get_param_int)] -pub unsafe fn handle_get_param_int( - module_accessor: &mut app::BattleObjectModuleAccessor, - param_type: u64, - param_hash: u64, -) -> i32 { - let ori = original!()(module_accessor, param_type, param_hash); - - if !is_training_mode() { - return ori; - } - - save_states::get_param_int(module_accessor, param_type, param_hash).unwrap_or(ori) -} - -#[skyline::hook(replace = ControlModule::get_attack_air_kind)] -pub unsafe fn handle_get_attack_air_kind( - module_accessor: &mut app::BattleObjectModuleAccessor, -) -> i32 { - let ori = original!()(module_accessor); - if !is_training_mode() { - return ori; - } - - mash::get_attack_air_kind(module_accessor).unwrap_or(ori) -} - -#[skyline::hook(replace = ControlModule::get_command_flag_cat)] -pub unsafe fn handle_get_command_flag_cat( - module_accessor: &mut app::BattleObjectModuleAccessor, - category: i32, -) -> i32 { - let mut flag = original!()(module_accessor, category); - - if category == FIGHTER_PAD_COMMAND_CATEGORY1 { - shield::param_installer(); - } - - if !is_training_mode() { - return flag; - } - - flag |= mash::get_command_flag_cat(module_accessor, category); - // Get throw directions - flag |= throw::get_command_flag_throw_direction(module_accessor); - - once_per_frame_per_fighter(module_accessor, category); - - flag -} - -fn once_per_frame_per_fighter( - module_accessor: &mut app::BattleObjectModuleAccessor, - category: i32, -) { - if category != FIGHTER_PAD_COMMAND_CATEGORY1 { - return; - } - - unsafe { - if crate::common::menu::menu_condition(module_accessor) { - crate::common::menu::spawn_menu(); - } - - input_record::get_command_flag_cat(module_accessor); - combo::get_command_flag_cat(module_accessor); - hitbox_visualizer::get_command_flag_cat(module_accessor); - save_states::save_states(module_accessor); - tech::get_command_flag_cat(module_accessor); - } - - fast_fall::get_command_flag_cat(module_accessor); - frame_counter::get_command_flag_cat(module_accessor); - ledge::get_command_flag_cat(module_accessor); - shield::get_command_flag_cat(module_accessor); - directional_influence::get_command_flag_cat(module_accessor); - reset::check_reset(module_accessor); -} - -/** - * This is called to get the stick position when - * shielding (shield tilt) - * 1 is fully right, -1 is fully left - */ -#[skyline::hook(replace = ControlModule::get_stick_x_no_clamp)] -pub unsafe fn get_stick_x_no_clamp(module_accessor: &mut app::BattleObjectModuleAccessor) -> f32 { - let ori = original!()(module_accessor); - if !is_training_mode() { - return ori; - } - - shield_tilt::mod_get_stick_x(module_accessor).unwrap_or(ori) -} - -/** - * This is called to get the stick position when - * shielding (shield tilt) - * 1 is fully up, -1 is fully down - */ -#[skyline::hook(replace = ControlModule::get_stick_y_no_clamp)] -pub unsafe fn get_stick_y_no_clamp(module_accessor: &mut app::BattleObjectModuleAccessor) -> f32 { - let ori = original!()(module_accessor); - if !is_training_mode() { - return ori; - } - - shield_tilt::mod_get_stick_y(module_accessor).unwrap_or(ori) -} - -/** - * Called when: - * Walking in the facing direction - * Air Dodging - */ -#[skyline::hook(replace = ControlModule::get_stick_x)] -pub unsafe fn get_stick_x(module_accessor: &mut app::BattleObjectModuleAccessor) -> f32 { - let ori = original!()(module_accessor); - if !is_training_mode() { - return ori; - } - - air_dodge_direction::mod_get_stick_x(module_accessor).unwrap_or(ori) -} - -/** - * Called when: - * angled ftilt/fsmash - */ -#[skyline::hook(replace = ControlModule::get_stick_dir)] -pub unsafe fn get_stick_dir(module_accessor: &mut app::BattleObjectModuleAccessor) -> f32 { - let ori = original!()(module_accessor); - if !is_training_mode() { - return ori; - } - - attack_angle::mod_get_stick_dir(module_accessor).unwrap_or(ori) -} - -/** - * - */ -#[skyline::hook(replace = ControlModule::get_stick_y)] -pub unsafe fn get_stick_y(module_accessor: &mut app::BattleObjectModuleAccessor) -> f32 { - let ori = original!()(module_accessor); - if !is_training_mode() { - return ori; - } - - air_dodge_direction::mod_get_stick_y(module_accessor).unwrap_or(ori) -} - -#[skyline::hook(replace = ControlModule::check_button_on)] -pub unsafe fn handle_check_button_on( - module_accessor: &mut app::BattleObjectModuleAccessor, - button: i32, -) -> bool { - let ori = original!()(module_accessor, button); - if !is_training_mode() { - return ori; - } - - shield::check_button_on(module_accessor, button) - .unwrap_or_else(|| full_hop::check_button_on(module_accessor, button).unwrap_or(ori)) -} - -#[skyline::hook(replace = ControlModule::check_button_off)] -pub unsafe fn handle_check_button_off( - module_accessor: &mut app::BattleObjectModuleAccessor, - button: i32, -) -> bool { - let ori = original!()(module_accessor, button); - if !is_training_mode() { - return ori; - } - - shield::check_button_off(module_accessor, button) - .unwrap_or_else(|| full_hop::check_button_off(module_accessor, button).unwrap_or(ori)) -} - -#[skyline::hook(replace = MotionModule::change_motion)] -pub unsafe fn handle_change_motion( - module_accessor: &mut app::BattleObjectModuleAccessor, - motion_kind: u64, - unk1: f32, - unk2: f32, - unk3: bool, - unk4: f32, - unk5: bool, - unk6: bool, -) -> u64 { - let mod_motion_kind = if is_training_mode() { - tech::change_motion(module_accessor, motion_kind).unwrap_or(motion_kind) - } else { - motion_kind - }; - - original!()( - module_accessor, - mod_motion_kind, - unk1, - unk2, - unk3, - unk4, - unk5, - unk6, - ) -} - -#[skyline::hook(replace = WorkModule::is_enable_transition_term)] -pub unsafe fn handle_is_enable_transition_term( - module_accessor: *mut app::BattleObjectModuleAccessor, - transition_term: i32, -) -> bool { - let ori = original!()(module_accessor, transition_term); - - if !is_training_mode() { - return ori; - } - - combo::is_enable_transition_term(module_accessor, transition_term, ori); - match ledge::is_enable_transition_term(module_accessor, transition_term) { - Some(r) => r, - None => ori, - } -} - -extern "C" { - #[link_name = "\u{1}_ZN3app15sv_fighter_util15set_dead_rumbleEP9lua_State"] - pub fn set_dead_rumble(lua_state: u64) -> u64; -} - -#[skyline::hook(replace = set_dead_rumble)] -pub unsafe fn handle_set_dead_rumble(lua_state: u64) -> u64 { - if is_training_mode() { - return 0; - } - - original!()(lua_state) -} - -#[skyline::hook(replace = CameraModule::req_quake)] -pub unsafe fn handle_req_quake( - module_accessor: &mut app::BattleObjectModuleAccessor, - my_int: i32, -) -> u64 { - if !is_training_mode() { - return original!()(module_accessor, my_int); - } - if save_states::is_killing() { - return original!()(module_accessor, *CAMERA_QUAKE_KIND_NONE); - } - original!()(module_accessor, my_int) -} - -pub static mut COMMON_PARAMS: *mut CommonParams = 0 as *mut _; - -fn params_main(params_info: &ParamsInfo<'_>) { - if let Ok(common) = params_info.get::() { - unsafe { - COMMON_PARAMS = common as *mut _; - } - } -} - -static CLOUD_ADD_LIMIT_OFFSET: usize = 0x008dc140; // this function is used to add limit to Cloud's limit gauge. Hooking it here so we can call it in buff.rs -#[skyline::hook(offset = CLOUD_ADD_LIMIT_OFFSET)] -pub unsafe fn handle_add_limit( - add_limit: f32, - module_accessor: &mut app::BattleObjectModuleAccessor, - is_special_lw: u64, -) { - original!()(add_limit, module_accessor, is_special_lw) -} - -#[skyline::hook(replace = EffectModule::req_screen)] // hooked to prevent the screen from darkening when loading a save state with One-Winged Angel -pub unsafe fn handle_req_screen( - module_accessor: &mut app::BattleObjectModuleAccessor, - my_hash: Hash40, - bool_1: bool, - bool_2: bool, - bool_3: bool, -) -> u64 { - if !is_training_mode() { - return original!()(module_accessor, my_hash, bool_1, bool_2, bool_3); - } - let new_hash = my_hash.hash; - if new_hash == 72422354958 && buff::is_buffing(module_accessor) { - // Wing bg hash - let replace_hash = Hash40::new("bg"); - return original!()(module_accessor, replace_hash, bool_1, bool_2, bool_3); - } - original!()(module_accessor, my_hash, bool_1, bool_2, bool_3) -} - -#[skyline::hook(replace = app::FighterSpecializer_Jack::check_doyle_summon_dispatch)] // returns status of summon dispatch if triggered, -1 as u64 otherwise -pub unsafe fn handle_check_doyle_summon_dispatch( - module_accessor: &mut app::BattleObjectModuleAccessor, - bool_1: bool, - bool_2: bool, -) -> u64 { - let ori = original!()(module_accessor, bool_1, bool_2); - if !is_training_mode() { - return ori; - } - if ori == *FIGHTER_JACK_STATUS_KIND_SUMMON as u64 { - if buff::is_buffing(module_accessor) { - return 4294967295; - } - } - return ori; -} - -// Set Stale Moves to On -static STALE_OFFSET: usize = 0x013e88a4; -// One instruction after stale moves toggle register is set to 0 -#[skyline::hook(offset=STALE_OFFSET, inline)] -unsafe fn stale_handle(ctx: &mut InlineCtx) { - let x22 = ctx.registers[22].x.as_mut(); - let training_structure_address = (*x22 + 0xb60) as *mut u8; - *training_structure_address = 1; -} - -// Set Stale Moves to On in the menu text -static STALE_MENU_OFFSET: usize = 0x013e88a0; -// One instruction after menu text register is set to off -#[skyline::hook(offset=STALE_MENU_OFFSET, inline)] -unsafe fn stale_menu_handle(ctx: &mut InlineCtx) { - // Set the text pointer to where "mel_training_on" is located - let on_text_ptr = ((getRegionAddress(Region::Text) as u64) + (0x42b215e as u64)) as u64; - let x1 = ctx.registers[1].x.as_mut(); - *x1 = on_text_ptr; -} - -#[skyline::hook(replace = SoundModule::play_se)] // hooked to prevent death sfx from playing when loading save states -pub unsafe fn handle_se( - module_accessor: &mut app::BattleObjectModuleAccessor, - my_hash: Hash40, - bool1: bool, - bool2: bool, - bool3: bool, - bool4: bool, - se_type: enSEType, -) -> u64 { - // Make effects silent while we're killing fighters. Stops death explosion and fighter misfoot. - if save_states::is_killing() { - let silent_hash = Hash40::new("se_silent"); - return original!()( - module_accessor, - silent_hash, - bool1, - bool2, - bool3, - bool4, - se_type, - ); - } - original!()( - module_accessor, - my_hash, - bool1, - bool2, - bool3, - bool4, - se_type, - ) -} - -#[skyline::hook(replace = EffectModule::req)] // hooked to prevent death gfx from playing when loading save states -pub unsafe fn handle_effect( - module_accessor: &mut app::BattleObjectModuleAccessor, - eff_hash: Hash40, - pos: *const Vector3f, - rot: *const Vector3f, - size: f32, - arg6: u32, - arg7: i32, - arg8: bool, - arg9: i32, -) -> u64 { - if save_states::is_killing() { - // Making the size 0 prevents these effects from being displayed. Fixs throw explosions, ICs squall, etc. - return original!()( - module_accessor, - eff_hash, - pos, - rot, - 0.0, - arg6, - arg7, - arg8, - arg9, - ); - } - original!()( - module_accessor, - eff_hash, - pos, - rot, - size, - arg6, - arg7, - arg8, - arg9, - ) -} - -#[allow(improper_ctypes)] -extern "C" { - fn add_nn_hid_hook(callback: fn(*mut NpadHandheldState, *const u32)); -} - -pub fn training_mods() { - println!("[Training Modpack] Applying training mods."); - - // Input Recording/Delay - unsafe { - if (add_nn_hid_hook as *const ()).is_null() { - 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); - } - - unsafe { - LookupSymbol( - &mut FIGHTER_MANAGER_ADDR, - "_ZN3lib9SingletonIN3app14FighterManagerEE9instance_E\u{0}" - .as_bytes() - .as_ptr(), - ); - - LookupSymbol( - &mut STAGE_MANAGER_ADDR, - "_ZN3lib9SingletonIN3app12StageManagerEE9instance_E\u{0}" - .as_bytes() - .as_ptr(), - ); - - smash::params::add_hook(params_main).unwrap(); - } - - skyline::install_hooks!( - // Mash airdodge/jump - handle_get_command_flag_cat, - // Hold/Infinite shield - handle_check_button_on, - handle_check_button_off, - handle_get_param_float, - // Save states - handle_get_param_int, - handle_set_dead_rumble, - handle_req_quake, - // Mash attack - handle_get_attack_air_kind, - // Attack angle - get_stick_dir, - // Tech options - handle_change_motion, - // Directional AirDodge, - get_stick_x, - get_stick_y, - // Shield Tilt - get_stick_x_no_clamp, - get_stick_y_no_clamp, - // Combo - handle_is_enable_transition_term, - // SDI - crate::training::sdi::check_hit_stop_delay_command, - // Buffs - handle_add_limit, - handle_check_doyle_summon_dispatch, - handle_req_screen, - // Stale Moves - stale_handle, - stale_menu_handle, - // Death SFX - handle_se, - // Death GFX - handle_effect, - ); - - combo::init(); - shield::init(); - fast_fall::init(); - mash::init(); - ledge::init(); - throw::init(); - menu::init(); - buff::init(); -} +use crate::common::{is_training_mode, menu, FIGHTER_MANAGER_ADDR, STAGE_MANAGER_ADDR}; +use crate::hitbox_visualizer; +use skyline::hooks::{getRegionAddress, InlineCtx, Region}; +use skyline::nn::hid::*; +use skyline::nn::ro::LookupSymbol; +use smash::app::{self, enSEType, lua_bind::*}; +use smash::lib::lua_const::*; +use smash::params::*; +use smash::phx::{Hash40, Vector3f}; + +pub mod buff; +pub mod combo; +pub mod directional_influence; +pub mod frame_counter; +pub mod ledge; +pub mod sdi; +pub mod shield; +pub mod tech; +pub mod throw; + +mod air_dodge_direction; +mod attack_angle; +mod character_specific; +mod fast_fall; +mod full_hop; +mod input_delay; +mod input_record; +mod mash; +mod reset; +mod save_states; +mod shield_tilt; + +#[skyline::hook(replace = WorkModule::get_param_float)] +pub unsafe fn handle_get_param_float( + module_accessor: &mut app::BattleObjectModuleAccessor, + param_type: u64, + param_hash: u64, +) -> f32 { + let ori = original!()(module_accessor, param_type, param_hash); + if !is_training_mode() { + return ori; + } + + shield::get_param_float(module_accessor, param_type, param_hash).unwrap_or(ori) +} + +#[skyline::hook(replace = WorkModule::get_param_int)] +pub unsafe fn handle_get_param_int( + module_accessor: &mut app::BattleObjectModuleAccessor, + param_type: u64, + param_hash: u64, +) -> i32 { + let ori = original!()(module_accessor, param_type, param_hash); + + if !is_training_mode() { + return ori; + } + + save_states::get_param_int(module_accessor, param_type, param_hash).unwrap_or(ori) +} + +#[skyline::hook(replace = ControlModule::get_attack_air_kind)] +pub unsafe fn handle_get_attack_air_kind( + module_accessor: &mut app::BattleObjectModuleAccessor, +) -> i32 { + let ori = original!()(module_accessor); + if !is_training_mode() { + return ori; + } + + mash::get_attack_air_kind(module_accessor).unwrap_or(ori) +} + +#[skyline::hook(replace = ControlModule::get_command_flag_cat)] +pub unsafe fn handle_get_command_flag_cat( + module_accessor: &mut app::BattleObjectModuleAccessor, + category: i32, +) -> i32 { + let mut flag = original!()(module_accessor, category); + + if category == FIGHTER_PAD_COMMAND_CATEGORY1 { + shield::param_installer(); + } + + if !is_training_mode() { + return flag; + } + + flag |= mash::get_command_flag_cat(module_accessor, category); + // Get throw directions + flag |= throw::get_command_flag_throw_direction(module_accessor); + + once_per_frame_per_fighter(module_accessor, category); + + flag +} + +fn once_per_frame_per_fighter( + module_accessor: &mut app::BattleObjectModuleAccessor, + category: i32, +) { + if category != FIGHTER_PAD_COMMAND_CATEGORY1 { + return; + } + + unsafe { + if crate::common::menu::menu_condition(module_accessor) { + crate::common::menu::spawn_menu(); + } + + input_record::get_command_flag_cat(module_accessor); + combo::get_command_flag_cat(module_accessor); + hitbox_visualizer::get_command_flag_cat(module_accessor); + save_states::save_states(module_accessor); + tech::get_command_flag_cat(module_accessor); + } + + fast_fall::get_command_flag_cat(module_accessor); + frame_counter::get_command_flag_cat(module_accessor); + ledge::get_command_flag_cat(module_accessor); + shield::get_command_flag_cat(module_accessor); + directional_influence::get_command_flag_cat(module_accessor); + reset::check_reset(module_accessor); +} + +/** + * This is called to get the stick position when + * shielding (shield tilt) + * 1 is fully right, -1 is fully left + */ +#[skyline::hook(replace = ControlModule::get_stick_x_no_clamp)] +pub unsafe fn get_stick_x_no_clamp(module_accessor: &mut app::BattleObjectModuleAccessor) -> f32 { + let ori = original!()(module_accessor); + if !is_training_mode() { + return ori; + } + + shield_tilt::mod_get_stick_x(module_accessor).unwrap_or(ori) +} + +/** + * This is called to get the stick position when + * shielding (shield tilt) + * 1 is fully up, -1 is fully down + */ +#[skyline::hook(replace = ControlModule::get_stick_y_no_clamp)] +pub unsafe fn get_stick_y_no_clamp(module_accessor: &mut app::BattleObjectModuleAccessor) -> f32 { + let ori = original!()(module_accessor); + if !is_training_mode() { + return ori; + } + + shield_tilt::mod_get_stick_y(module_accessor).unwrap_or(ori) +} + +/** + * Called when: + * Walking in the facing direction + * Air Dodging + */ +#[skyline::hook(replace = ControlModule::get_stick_x)] +pub unsafe fn get_stick_x(module_accessor: &mut app::BattleObjectModuleAccessor) -> f32 { + let ori = original!()(module_accessor); + if !is_training_mode() { + return ori; + } + + air_dodge_direction::mod_get_stick_x(module_accessor).unwrap_or(ori) +} + +/** + * Called when: + * angled ftilt/fsmash + */ +#[skyline::hook(replace = ControlModule::get_stick_dir)] +pub unsafe fn get_stick_dir(module_accessor: &mut app::BattleObjectModuleAccessor) -> f32 { + let ori = original!()(module_accessor); + if !is_training_mode() { + return ori; + } + + attack_angle::mod_get_stick_dir(module_accessor).unwrap_or(ori) +} + +/** + * + */ +#[skyline::hook(replace = ControlModule::get_stick_y)] +pub unsafe fn get_stick_y(module_accessor: &mut app::BattleObjectModuleAccessor) -> f32 { + let ori = original!()(module_accessor); + if !is_training_mode() { + return ori; + } + + air_dodge_direction::mod_get_stick_y(module_accessor).unwrap_or(ori) +} + +#[skyline::hook(replace = ControlModule::check_button_on)] +pub unsafe fn handle_check_button_on( + module_accessor: &mut app::BattleObjectModuleAccessor, + button: i32, +) -> bool { + let ori = original!()(module_accessor, button); + if !is_training_mode() { + return ori; + } + + shield::check_button_on(module_accessor, button) + .unwrap_or_else(|| full_hop::check_button_on(module_accessor, button).unwrap_or(ori)) +} + +#[skyline::hook(replace = ControlModule::check_button_off)] +pub unsafe fn handle_check_button_off( + module_accessor: &mut app::BattleObjectModuleAccessor, + button: i32, +) -> bool { + let ori = original!()(module_accessor, button); + if !is_training_mode() { + return ori; + } + + shield::check_button_off(module_accessor, button) + .unwrap_or_else(|| full_hop::check_button_off(module_accessor, button).unwrap_or(ori)) +} + +#[skyline::hook(replace = MotionModule::change_motion)] +pub unsafe fn handle_change_motion( + module_accessor: &mut app::BattleObjectModuleAccessor, + motion_kind: u64, + unk1: f32, + unk2: f32, + unk3: bool, + unk4: f32, + unk5: bool, + unk6: bool, +) -> u64 { + let mod_motion_kind = if is_training_mode() { + tech::change_motion(module_accessor, motion_kind).unwrap_or(motion_kind) + } else { + motion_kind + }; + + original!()( + module_accessor, + mod_motion_kind, + unk1, + unk2, + unk3, + unk4, + unk5, + unk6, + ) +} + +#[skyline::hook(replace = WorkModule::is_enable_transition_term)] +pub unsafe fn handle_is_enable_transition_term( + module_accessor: *mut app::BattleObjectModuleAccessor, + transition_term: i32, +) -> bool { + let ori = original!()(module_accessor, transition_term); + + if !is_training_mode() { + return ori; + } + + combo::is_enable_transition_term(module_accessor, transition_term, ori); + match ledge::is_enable_transition_term(module_accessor, transition_term) { + Some(r) => r, + None => ori, + } +} + +extern "C" { + #[link_name = "\u{1}_ZN3app15sv_fighter_util15set_dead_rumbleEP9lua_State"] + pub fn set_dead_rumble(lua_state: u64) -> u64; +} + +#[skyline::hook(replace = set_dead_rumble)] +pub unsafe fn handle_set_dead_rumble(lua_state: u64) -> u64 { + if is_training_mode() { + return 0; + } + + original!()(lua_state) +} + +#[skyline::hook(replace = CameraModule::req_quake)] +pub unsafe fn handle_req_quake( + module_accessor: &mut app::BattleObjectModuleAccessor, + my_int: i32, +) -> u64 { + if !is_training_mode() { + return original!()(module_accessor, my_int); + } + if save_states::is_killing() { + return original!()(module_accessor, *CAMERA_QUAKE_KIND_NONE); + } + original!()(module_accessor, my_int) +} + +pub static mut COMMON_PARAMS: *mut CommonParams = 0 as *mut _; + +fn params_main(params_info: &ParamsInfo<'_>) { + if let Ok(common) = params_info.get::() { + unsafe { + COMMON_PARAMS = common as *mut _; + } + } +} + +static CLOUD_ADD_LIMIT_OFFSET: usize = 0x008dc140; // this function is used to add limit to Cloud's limit gauge. Hooking it here so we can call it in buff.rs +#[skyline::hook(offset = CLOUD_ADD_LIMIT_OFFSET)] +pub unsafe fn handle_add_limit( + add_limit: f32, + module_accessor: &mut app::BattleObjectModuleAccessor, + is_special_lw: u64, +) { + original!()(add_limit, module_accessor, is_special_lw) +} + +#[skyline::hook(replace = EffectModule::req_screen)] // hooked to prevent the screen from darkening when loading a save state with One-Winged Angel +pub unsafe fn handle_req_screen( + module_accessor: &mut app::BattleObjectModuleAccessor, + my_hash: Hash40, + bool_1: bool, + bool_2: bool, + bool_3: bool, +) -> u64 { + if !is_training_mode() { + return original!()(module_accessor, my_hash, bool_1, bool_2, bool_3); + } + let new_hash = my_hash.hash; + if new_hash == 72422354958 && buff::is_buffing(module_accessor) { + // Wing bg hash + let replace_hash = Hash40::new("bg"); + return original!()(module_accessor, replace_hash, bool_1, bool_2, bool_3); + } + original!()(module_accessor, my_hash, bool_1, bool_2, bool_3) +} + +#[skyline::hook(replace = app::FighterSpecializer_Jack::check_doyle_summon_dispatch)] // returns status of summon dispatch if triggered, -1 as u64 otherwise +pub unsafe fn handle_check_doyle_summon_dispatch( + module_accessor: &mut app::BattleObjectModuleAccessor, + bool_1: bool, + bool_2: bool, +) -> u64 { + let ori = original!()(module_accessor, bool_1, bool_2); + if !is_training_mode() { + return ori; + } + if ori == *FIGHTER_JACK_STATUS_KIND_SUMMON as u64 { + if buff::is_buffing(module_accessor) { + return 4294967295; + } + } + return ori; +} + +// Set Stale Moves to On +static STALE_OFFSET: usize = 0x013e88a4; +// One instruction after stale moves toggle register is set to 0 +#[skyline::hook(offset=STALE_OFFSET, inline)] +unsafe fn stale_handle(ctx: &mut InlineCtx) { + let x22 = ctx.registers[22].x.as_mut(); + let training_structure_address = (*x22 + 0xb60) as *mut u8; + *training_structure_address = 1; +} + +// Set Stale Moves to On in the menu text +static STALE_MENU_OFFSET: usize = 0x013e88a0; +// One instruction after menu text register is set to off +#[skyline::hook(offset=STALE_MENU_OFFSET, inline)] +unsafe fn stale_menu_handle(ctx: &mut InlineCtx) { + // Set the text pointer to where "mel_training_on" is located + let on_text_ptr = ((getRegionAddress(Region::Text) as u64) + (0x42b215e as u64)) as u64; + let x1 = ctx.registers[1].x.as_mut(); + *x1 = on_text_ptr; +} + +#[skyline::hook(replace = SoundModule::play_se)] // hooked to prevent death sfx from playing when loading save states +pub unsafe fn handle_se( + module_accessor: &mut app::BattleObjectModuleAccessor, + my_hash: Hash40, + bool1: bool, + bool2: bool, + bool3: bool, + bool4: bool, + se_type: enSEType, +) -> u64 { + // Make effects silent while we're killing fighters. Stops death explosion and fighter misfoot. + if save_states::is_killing() { + let silent_hash = Hash40::new("se_silent"); + return original!()( + module_accessor, + silent_hash, + bool1, + bool2, + bool3, + bool4, + se_type, + ); + } + original!()( + module_accessor, + my_hash, + bool1, + bool2, + bool3, + bool4, + se_type, + ) +} + +#[skyline::hook(replace = EffectModule::req)] // hooked to prevent death gfx from playing when loading save states +pub unsafe fn handle_effect( + module_accessor: &mut app::BattleObjectModuleAccessor, + eff_hash: Hash40, + pos: *const Vector3f, + rot: *const Vector3f, + size: f32, + arg6: u32, + arg7: i32, + arg8: bool, + arg9: i32, +) -> u64 { + if save_states::is_killing() { + // Making the size 0 prevents these effects from being displayed. Fixs throw explosions, ICs squall, etc. + return original!()( + module_accessor, + eff_hash, + pos, + rot, + 0.0, + arg6, + arg7, + arg8, + arg9, + ); + } + original!()( + module_accessor, + eff_hash, + pos, + rot, + size, + arg6, + arg7, + arg8, + arg9, + ) +} + +#[allow(improper_ctypes)] +extern "C" { + fn add_nn_hid_hook(callback: fn(*mut NpadHandheldState, *const u32)); +} + +pub fn training_mods() { + println!("[Training Modpack] Applying training mods."); + + // Input Recording/Delay + unsafe { + if (add_nn_hid_hook as *const ()).is_null() { + 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); + } + + unsafe { + LookupSymbol( + &mut FIGHTER_MANAGER_ADDR, + "_ZN3lib9SingletonIN3app14FighterManagerEE9instance_E\u{0}" + .as_bytes() + .as_ptr(), + ); + + LookupSymbol( + &mut STAGE_MANAGER_ADDR, + "_ZN3lib9SingletonIN3app12StageManagerEE9instance_E\u{0}" + .as_bytes() + .as_ptr(), + ); + + smash::params::add_hook(params_main).unwrap(); + } + + skyline::install_hooks!( + // Mash airdodge/jump + handle_get_command_flag_cat, + // Hold/Infinite shield + handle_check_button_on, + handle_check_button_off, + handle_get_param_float, + // Save states + handle_get_param_int, + handle_set_dead_rumble, + handle_req_quake, + // Mash attack + handle_get_attack_air_kind, + // Attack angle + get_stick_dir, + // Tech options + handle_change_motion, + // Directional AirDodge, + get_stick_x, + get_stick_y, + // Shield Tilt + get_stick_x_no_clamp, + get_stick_y_no_clamp, + // Combo + handle_is_enable_transition_term, + // SDI + crate::training::sdi::check_hit_stop_delay_command, + // Buffs + handle_add_limit, + handle_check_doyle_summon_dispatch, + handle_req_screen, + // Stale Moves + stale_handle, + stale_menu_handle, + // Death SFX + handle_se, + // Death GFX + handle_effect, + ); + + combo::init(); + shield::init(); + fast_fall::init(); + mash::init(); + ledge::init(); + throw::init(); + menu::init(); + buff::init(); +} diff --git a/src/training/reset.rs b/src/training/reset.rs index b13b594..e0a1c89 100644 --- a/src/training/reset.rs +++ b/src/training/reset.rs @@ -1,49 +1,49 @@ -use crate::common::*; -use crate::training::frame_counter; -use crate::training::ledge; -use crate::training::mash; -use crate::training::sdi; -use crate::training::shield_tilt; -use crate::training::throw; -use smash::app::{self, lua_bind::*}; -use smash::lib::lua_const::*; - -pub fn check_reset(module_accessor: &mut app::BattleObjectModuleAccessor) { - if !is_operation_cpu(module_accessor) { - return; - } - - if !should_reset(module_accessor) { - return; - } - - on_reset(); -} - -fn should_reset(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { - if !is_idle(module_accessor) { - return false; - } - - let prev_status; - - unsafe { - prev_status = StatusModule::prev_status_kind(module_accessor, 0); - } - - // Only reset automatically on training mode reset - if prev_status != *FIGHTER_STATUS_KIND_NONE { - return false; - } - - true -} - -pub fn on_reset() { - mash::full_reset(); - sdi::roll_direction(); - frame_counter::reset_all(); - ledge::reset_ledge_delay(); - throw::reset_throw_delay(); - shield_tilt::roll_direction(); -} +use crate::common::*; +use crate::training::frame_counter; +use crate::training::ledge; +use crate::training::mash; +use crate::training::sdi; +use crate::training::shield_tilt; +use crate::training::throw; +use smash::app::{self, lua_bind::*}; +use smash::lib::lua_const::*; + +pub fn check_reset(module_accessor: &mut app::BattleObjectModuleAccessor) { + if !is_operation_cpu(module_accessor) { + return; + } + + if !should_reset(module_accessor) { + return; + } + + on_reset(); +} + +fn should_reset(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { + if !is_idle(module_accessor) { + return false; + } + + let prev_status; + + unsafe { + prev_status = StatusModule::prev_status_kind(module_accessor, 0); + } + + // Only reset automatically on training mode reset + if prev_status != *FIGHTER_STATUS_KIND_NONE { + return false; + } + + true +} + +pub fn on_reset() { + mash::full_reset(); + sdi::roll_direction(); + frame_counter::reset_all(); + ledge::reset_ledge_delay(); + throw::reset_throw_delay(); + shield_tilt::roll_direction(); +} diff --git a/src/training/save_states.rs b/src/training/save_states.rs index 1a976f1..c56a897 100644 --- a/src/training/save_states.rs +++ b/src/training/save_states.rs @@ -1,8 +1,9 @@ +use crate::common::consts::get_random_int; use crate::common::consts::FighterId; use crate::common::consts::OnOff; use crate::common::consts::SaveStateMirroring; +use crate::common::is_dead; use crate::common::MENU; -use crate::common::{get_random_int, is_dead}; use crate::training::buff; use crate::training::reset; use smash::app::{self, lua_bind::*}; diff --git a/src/training/tech.rs b/src/training/tech.rs index d491cec..2ff20ac 100644 --- a/src/training/tech.rs +++ b/src/training/tech.rs @@ -1,272 +1,272 @@ -use crate::common::consts::*; -use crate::common::*; -use crate::training::mash; -use smash::app::sv_system; -use smash::app::{self, lua_bind::*}; -use smash::hash40; -use smash::lib::lua_const::*; -use smash::lib::L2CValue; -use smash::lua2cpp::L2CFighterBase; - -static mut TECH_ROLL_DIRECTION: Direction = Direction::empty(); -static mut MISS_TECH_ROLL_DIRECTION: Direction = Direction::empty(); - -#[skyline::hook(replace = smash::lua2cpp::L2CFighterBase_change_status)] -pub unsafe fn handle_change_status( - fighter: &mut L2CFighterBase, - status_kind: L2CValue, - unk: L2CValue, -) -> L2CValue { - let mut status_kind = status_kind; - let mut unk = unk; - - if is_training_mode() { - mod_handle_change_status(fighter, &mut status_kind, &mut unk); - } - - original!()(fighter, status_kind, unk) -} - -unsafe fn mod_handle_change_status( - fighter: &mut L2CFighterBase, - status_kind: &mut L2CValue, - unk: &mut L2CValue, -) { - let module_accessor = sv_system::battle_object_module_accessor(fighter.lua_state_agent); - if !is_operation_cpu(module_accessor) { - return; - } - - let status_kind_int = status_kind - .try_get_int() - .unwrap_or(*FIGHTER_STATUS_KIND_WAIT as u64) as i32; - - let state: TechFlags = MENU.tech_state.get_random(); - - if handle_grnd_tech(module_accessor, status_kind, unk, status_kind_int, state) { - return; - } - - if handle_wall_tech(module_accessor, status_kind, unk, status_kind_int, state) { - return; - } - - handle_ceil_tech(module_accessor, status_kind, unk, status_kind_int, state); -} -fn handle_grnd_tech( - module_accessor: &mut app::BattleObjectModuleAccessor, - status_kind: &mut L2CValue, - unk: &mut L2CValue, - status_kind_int: i32, - state: TechFlags, -) -> bool { - if status_kind_int != *FIGHTER_STATUS_KIND_DOWN - && status_kind_int != *FIGHTER_STATUS_KIND_DAMAGE_FLY_REFLECT_D - { - return false; - } - - unsafe { - let can_tech = WorkModule::is_enable_transition_term( - module_accessor, - *FIGHTER_STATUS_TRANSITION_TERM_ID_PASSIVE, - ); - - if !can_tech { - return false; - } - } - - match state { - TechFlags::IN_PLACE => { - *status_kind = FIGHTER_STATUS_KIND_PASSIVE.as_lua_int(); - *unk = LUA_TRUE; - unsafe { - mash::perform_defensive_option(); - } - } - TechFlags::ROLL_F => { - *status_kind = FIGHTER_STATUS_KIND_PASSIVE_FB.as_lua_int(); - *unk = LUA_TRUE; - unsafe { - TECH_ROLL_DIRECTION = Direction::IN; // = In - mash::perform_defensive_option(); - } - } - TechFlags::ROLL_B => { - *status_kind = FIGHTER_STATUS_KIND_PASSIVE_FB.as_lua_int(); - *unk = LUA_TRUE; - unsafe { - TECH_ROLL_DIRECTION = Direction::OUT; // = Away - mash::perform_defensive_option(); - } - } - _ => (), - } - - true -} - -fn handle_wall_tech( - module_accessor: &mut app::BattleObjectModuleAccessor, - status_kind: &mut L2CValue, - unk: &mut L2CValue, - status_kind_int: i32, - state: TechFlags, -) -> bool { - if status_kind_int != *FIGHTER_STATUS_KIND_STOP_WALL - && status_kind_int != *FIGHTER_STATUS_KIND_DAMAGE_FLY_REFLECT_LR - { - return false; - } - - if state == TechFlags::NO_TECH { - return false; - } - - unsafe { - let can_tech = WorkModule::is_enable_transition_term( - module_accessor, - *FIGHTER_STATUS_TRANSITION_TERM_ID_PASSIVE_WALL, - ); - - if !can_tech { - return false; - } - } - - match state { - TechFlags::IN_PLACE => { - *status_kind = FIGHTER_STATUS_KIND_PASSIVE_WALL.as_lua_int(); - *unk = LUA_TRUE; - } - TechFlags::ROLL_F => { - *status_kind = FIGHTER_STATUS_KIND_PASSIVE_WALL_JUMP.as_lua_int(); - *unk = LUA_TRUE; - } - _ => (), - } - - true -} - -fn handle_ceil_tech( - module_accessor: &mut app::BattleObjectModuleAccessor, - status_kind: &mut L2CValue, - unk: &mut L2CValue, - status_kind_int: i32, - state: TechFlags, -) -> bool { - if status_kind_int != *FIGHTER_STATUS_KIND_STOP_CEIL - && status_kind_int != *FIGHTER_STATUS_KIND_DAMAGE_FLY_REFLECT_U - { - return false; - } - - if state == TechFlags::NO_TECH { - return false; - } - - unsafe { - let can_tech = WorkModule::is_enable_transition_term( - module_accessor, - *FIGHTER_STATUS_TRANSITION_TERM_ID_PASSIVE_CEIL, - ); - - if !can_tech { - return false; - } - } - - *status_kind = FIGHTER_STATUS_KIND_PASSIVE_CEIL.as_lua_int(); - *unk = LUA_TRUE; - true -} - -pub unsafe fn get_command_flag_cat(module_accessor: &mut app::BattleObjectModuleAccessor) { - if !is_operation_cpu(module_accessor) { - return; - } - - if MENU.tech_state == TechFlags::empty() { - return; - } - - let status = StatusModule::status_kind(module_accessor) as i32; - - if [ - *FIGHTER_STATUS_KIND_DOWN_WAIT, // Mistech - *FIGHTER_STATUS_KIND_DOWN_WAIT_CONTINUE, // Mistech - *FIGHTER_STATUS_KIND_LAY_DOWN, // Snake down throw - ] - .contains(&status) - { - let status: i32 = match MENU.miss_tech_state.get_random() { - MissTechFlags::GETUP => *FIGHTER_STATUS_KIND_DOWN_STAND, - MissTechFlags::ATTACK => *FIGHTER_STATUS_KIND_DOWN_STAND_ATTACK, - MissTechFlags::ROLL_F => { - MISS_TECH_ROLL_DIRECTION = Direction::IN; // = In - *FIGHTER_STATUS_KIND_DOWN_STAND_FB - } - MissTechFlags::ROLL_B => { - MISS_TECH_ROLL_DIRECTION = Direction::OUT; // = Away - *FIGHTER_STATUS_KIND_DOWN_STAND_FB - } - _ => return, - }; - StatusModule::change_status_request_from_script(module_accessor, status, false); - - mash::perform_defensive_option(); - } else if [ - // Handle slips (like Diddy banana) - *FIGHTER_STATUS_KIND_SLIP_WAIT, - ] - .contains(&status) - { - let status: i32 = match MENU.miss_tech_state.get_random() { - MissTechFlags::GETUP => *FIGHTER_STATUS_KIND_SLIP_STAND, - MissTechFlags::ATTACK => *FIGHTER_STATUS_KIND_SLIP_STAND_ATTACK, - MissTechFlags::ROLL_F => *FIGHTER_STATUS_KIND_SLIP_STAND_F, - MissTechFlags::ROLL_B => *FIGHTER_STATUS_KIND_SLIP_STAND_B, - _ => return, - }; - StatusModule::change_status_request_from_script(module_accessor, status, false); - - mash::perform_defensive_option(); - }; -} - -pub unsafe fn change_motion( - module_accessor: &mut app::BattleObjectModuleAccessor, - motion_kind: u64, -) -> Option { - if !is_operation_cpu(module_accessor) { - return None; - } - - if MENU.tech_state == TechFlags::empty() { - return None; - } - - if [hash40("passive_stand_f"), hash40("passive_stand_b")].contains(&motion_kind) { - if TECH_ROLL_DIRECTION == Direction::IN { - return Some(hash40("passive_stand_f")); - } else { - return Some(hash40("passive_stand_b")); - } - } else if [hash40("down_forward_u"), hash40("down_back_u")].contains(&motion_kind) { - if MISS_TECH_ROLL_DIRECTION == Direction::IN { - return Some(hash40("down_forward_u")); - } else { - return Some(hash40("down_back_u")); - } - } else if [hash40("down_forward_d"), hash40("down_back_d")].contains(&motion_kind) { - if MISS_TECH_ROLL_DIRECTION == Direction::IN { - return Some(hash40("down_forward_d")); - } else { - return Some(hash40("down_back_d")); - } - } - - None -} +use crate::common::consts::*; +use crate::common::*; +use crate::training::mash; +use smash::app::sv_system; +use smash::app::{self, lua_bind::*}; +use smash::hash40; +use smash::lib::lua_const::*; +use smash::lib::L2CValue; +use smash::lua2cpp::L2CFighterBase; + +static mut TECH_ROLL_DIRECTION: Direction = Direction::empty(); +static mut MISS_TECH_ROLL_DIRECTION: Direction = Direction::empty(); + +#[skyline::hook(replace = smash::lua2cpp::L2CFighterBase_change_status)] +pub unsafe fn handle_change_status( + fighter: &mut L2CFighterBase, + status_kind: L2CValue, + unk: L2CValue, +) -> L2CValue { + let mut status_kind = status_kind; + let mut unk = unk; + + if is_training_mode() { + mod_handle_change_status(fighter, &mut status_kind, &mut unk); + } + + original!()(fighter, status_kind, unk) +} + +unsafe fn mod_handle_change_status( + fighter: &mut L2CFighterBase, + status_kind: &mut L2CValue, + unk: &mut L2CValue, +) { + let module_accessor = sv_system::battle_object_module_accessor(fighter.lua_state_agent); + if !is_operation_cpu(module_accessor) { + return; + } + + let status_kind_int = status_kind + .try_get_int() + .unwrap_or(*FIGHTER_STATUS_KIND_WAIT as u64) as i32; + + let state: TechFlags = MENU.tech_state.get_random(); + + if handle_grnd_tech(module_accessor, status_kind, unk, status_kind_int, state) { + return; + } + + if handle_wall_tech(module_accessor, status_kind, unk, status_kind_int, state) { + return; + } + + handle_ceil_tech(module_accessor, status_kind, unk, status_kind_int, state); +} +fn handle_grnd_tech( + module_accessor: &mut app::BattleObjectModuleAccessor, + status_kind: &mut L2CValue, + unk: &mut L2CValue, + status_kind_int: i32, + state: TechFlags, +) -> bool { + if status_kind_int != *FIGHTER_STATUS_KIND_DOWN + && status_kind_int != *FIGHTER_STATUS_KIND_DAMAGE_FLY_REFLECT_D + { + return false; + } + + unsafe { + let can_tech = WorkModule::is_enable_transition_term( + module_accessor, + *FIGHTER_STATUS_TRANSITION_TERM_ID_PASSIVE, + ); + + if !can_tech { + return false; + } + } + + match state { + TechFlags::IN_PLACE => { + *status_kind = FIGHTER_STATUS_KIND_PASSIVE.as_lua_int(); + *unk = LUA_TRUE; + unsafe { + mash::perform_defensive_option(); + } + } + TechFlags::ROLL_F => { + *status_kind = FIGHTER_STATUS_KIND_PASSIVE_FB.as_lua_int(); + *unk = LUA_TRUE; + unsafe { + TECH_ROLL_DIRECTION = Direction::IN; // = In + mash::perform_defensive_option(); + } + } + TechFlags::ROLL_B => { + *status_kind = FIGHTER_STATUS_KIND_PASSIVE_FB.as_lua_int(); + *unk = LUA_TRUE; + unsafe { + TECH_ROLL_DIRECTION = Direction::OUT; // = Away + mash::perform_defensive_option(); + } + } + _ => (), + } + + true +} + +fn handle_wall_tech( + module_accessor: &mut app::BattleObjectModuleAccessor, + status_kind: &mut L2CValue, + unk: &mut L2CValue, + status_kind_int: i32, + state: TechFlags, +) -> bool { + if status_kind_int != *FIGHTER_STATUS_KIND_STOP_WALL + && status_kind_int != *FIGHTER_STATUS_KIND_DAMAGE_FLY_REFLECT_LR + { + return false; + } + + if state == TechFlags::NO_TECH { + return false; + } + + unsafe { + let can_tech = WorkModule::is_enable_transition_term( + module_accessor, + *FIGHTER_STATUS_TRANSITION_TERM_ID_PASSIVE_WALL, + ); + + if !can_tech { + return false; + } + } + + match state { + TechFlags::IN_PLACE => { + *status_kind = FIGHTER_STATUS_KIND_PASSIVE_WALL.as_lua_int(); + *unk = LUA_TRUE; + } + TechFlags::ROLL_F => { + *status_kind = FIGHTER_STATUS_KIND_PASSIVE_WALL_JUMP.as_lua_int(); + *unk = LUA_TRUE; + } + _ => (), + } + + true +} + +fn handle_ceil_tech( + module_accessor: &mut app::BattleObjectModuleAccessor, + status_kind: &mut L2CValue, + unk: &mut L2CValue, + status_kind_int: i32, + state: TechFlags, +) -> bool { + if status_kind_int != *FIGHTER_STATUS_KIND_STOP_CEIL + && status_kind_int != *FIGHTER_STATUS_KIND_DAMAGE_FLY_REFLECT_U + { + return false; + } + + if state == TechFlags::NO_TECH { + return false; + } + + unsafe { + let can_tech = WorkModule::is_enable_transition_term( + module_accessor, + *FIGHTER_STATUS_TRANSITION_TERM_ID_PASSIVE_CEIL, + ); + + if !can_tech { + return false; + } + } + + *status_kind = FIGHTER_STATUS_KIND_PASSIVE_CEIL.as_lua_int(); + *unk = LUA_TRUE; + true +} + +pub unsafe fn get_command_flag_cat(module_accessor: &mut app::BattleObjectModuleAccessor) { + if !is_operation_cpu(module_accessor) { + return; + } + + if MENU.tech_state == TechFlags::empty() { + return; + } + + let status = StatusModule::status_kind(module_accessor) as i32; + + if [ + *FIGHTER_STATUS_KIND_DOWN_WAIT, // Mistech + *FIGHTER_STATUS_KIND_DOWN_WAIT_CONTINUE, // Mistech + *FIGHTER_STATUS_KIND_LAY_DOWN, // Snake down throw + ] + .contains(&status) + { + let status: i32 = match MENU.miss_tech_state.get_random() { + MissTechFlags::GETUP => *FIGHTER_STATUS_KIND_DOWN_STAND, + MissTechFlags::ATTACK => *FIGHTER_STATUS_KIND_DOWN_STAND_ATTACK, + MissTechFlags::ROLL_F => { + MISS_TECH_ROLL_DIRECTION = Direction::IN; // = In + *FIGHTER_STATUS_KIND_DOWN_STAND_FB + } + MissTechFlags::ROLL_B => { + MISS_TECH_ROLL_DIRECTION = Direction::OUT; // = Away + *FIGHTER_STATUS_KIND_DOWN_STAND_FB + } + _ => return, + }; + StatusModule::change_status_request_from_script(module_accessor, status, false); + + mash::perform_defensive_option(); + } else if [ + // Handle slips (like Diddy banana) + *FIGHTER_STATUS_KIND_SLIP_WAIT, + ] + .contains(&status) + { + let status: i32 = match MENU.miss_tech_state.get_random() { + MissTechFlags::GETUP => *FIGHTER_STATUS_KIND_SLIP_STAND, + MissTechFlags::ATTACK => *FIGHTER_STATUS_KIND_SLIP_STAND_ATTACK, + MissTechFlags::ROLL_F => *FIGHTER_STATUS_KIND_SLIP_STAND_F, + MissTechFlags::ROLL_B => *FIGHTER_STATUS_KIND_SLIP_STAND_B, + _ => return, + }; + StatusModule::change_status_request_from_script(module_accessor, status, false); + + mash::perform_defensive_option(); + }; +} + +pub unsafe fn change_motion( + module_accessor: &mut app::BattleObjectModuleAccessor, + motion_kind: u64, +) -> Option { + if !is_operation_cpu(module_accessor) { + return None; + } + + if MENU.tech_state == TechFlags::empty() { + return None; + } + + if [hash40("passive_stand_f"), hash40("passive_stand_b")].contains(&motion_kind) { + if TECH_ROLL_DIRECTION == Direction::IN { + return Some(hash40("passive_stand_f")); + } else { + return Some(hash40("passive_stand_b")); + } + } else if [hash40("down_forward_u"), hash40("down_back_u")].contains(&motion_kind) { + if MISS_TECH_ROLL_DIRECTION == Direction::IN { + return Some(hash40("down_forward_u")); + } else { + return Some(hash40("down_back_u")); + } + } else if [hash40("down_forward_d"), hash40("down_back_d")].contains(&motion_kind) { + if MISS_TECH_ROLL_DIRECTION == Direction::IN { + return Some(hash40("down_forward_d")); + } else { + return Some(hash40("down_back_d")); + } + } + + None +} diff --git a/src/training/throw.rs b/src/training/throw.rs index a70d7ab..6b57d84 100644 --- a/src/training/throw.rs +++ b/src/training/throw.rs @@ -1,154 +1,154 @@ -use crate::common::consts::*; -use crate::common::*; -use crate::training::frame_counter; -use crate::training::mash; -use smash::app::{self, lua_bind::*}; -use smash::lib::lua_const::*; - -const NOT_SET: u32 = 9001; -static mut THROW_DELAY: u32 = NOT_SET; -static mut THROW_DELAY_COUNTER: usize = 0; -static mut THROW_CASE: ThrowOption = ThrowOption::empty(); - -static mut PUMMEL_DELAY: u32 = NOT_SET; -static mut PUMMEL_DELAY_COUNTER: usize = 0; - -pub fn init() { - unsafe { - THROW_DELAY_COUNTER = frame_counter::register_counter(); - PUMMEL_DELAY_COUNTER = frame_counter::register_counter(); - } -} - -// Rolling Throw Delays and Pummel Delays separately - -pub fn reset_throw_delay() { - unsafe { - if THROW_DELAY != NOT_SET { - THROW_DELAY = NOT_SET; - frame_counter::full_reset(THROW_DELAY_COUNTER); - } - } -} - -pub fn reset_pummel_delay() { - unsafe { - if PUMMEL_DELAY != NOT_SET { - PUMMEL_DELAY = NOT_SET; - frame_counter::full_reset(PUMMEL_DELAY_COUNTER); - } - } -} - -pub fn reset_throw_case() { - unsafe { - if THROW_CASE != ThrowOption::empty() { - // Don't roll another throw option if one is already selected - THROW_CASE = ThrowOption::empty(); - } - } -} - -fn roll_throw_delay() { - unsafe { - if THROW_DELAY != NOT_SET { - // Don't roll another throw delay if one is already selected - return; - } - - THROW_DELAY = MENU.throw_delay.get_random().into_meddelay(); - } -} - -fn roll_pummel_delay() { - unsafe { - if PUMMEL_DELAY != NOT_SET { - // Don't roll another pummel delay if one is already selected - return; - } - - PUMMEL_DELAY = MENU.pummel_delay.get_random().into_meddelay(); - } -} - -fn roll_throw_case() { - unsafe { - // Don't re-roll if there is already a throw option selected - if THROW_CASE != ThrowOption::empty() { - return; - } - - THROW_CASE = MENU.throw_state.get_random(); - } -} - -pub unsafe fn get_command_flag_throw_direction( - module_accessor: &mut app::BattleObjectModuleAccessor, -) -> i32 { - if !is_operation_cpu(module_accessor) { - return 0; - } - - if StatusModule::status_kind(module_accessor) as i32 != *FIGHTER_STATUS_KIND_CATCH_WAIT - && StatusModule::status_kind(module_accessor) as i32 != *FIGHTER_STATUS_KIND_CATCH_PULL - && StatusModule::status_kind(module_accessor) as i32 != *FIGHTER_STATUS_KIND_CATCH_ATTACK - { - // No longer holding character, so re-roll the throw case and reset the delay counter for next time - reset_throw_case(); - reset_throw_delay(); - - reset_pummel_delay(); - return 0; - } - - if !WorkModule::is_enable_transition_term( - // If you can't throw right now, don't bother - module_accessor, - *FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_THROW_HI, - ) { - return 0; - } - - roll_throw_delay(); - roll_throw_case(); - - roll_pummel_delay(); - - if THROW_CASE == ThrowOption::NONE { - // Do nothing, but don't reroll the throw case. - return 0; - } - - if frame_counter::should_delay(THROW_DELAY, THROW_DELAY_COUNTER) { - // Not yet time to perform the throw action - if frame_counter::should_delay(PUMMEL_DELAY, PUMMEL_DELAY_COUNTER) { - // And not yet time to pummel either, so don't do anything - return 0; - } - - // If no pummel delay is selected (default), then don't pummel - if MENU.pummel_delay == MedDelay::empty() { - return 0; - } - - // (this conditional would need to be changed to speed up pummelling) - if StatusModule::status_kind(module_accessor) as i32 == *FIGHTER_STATUS_KIND_CATCH_WAIT { - let status = *FIGHTER_STATUS_KIND_CATCH_ATTACK; //.unwrap_or(0); - StatusModule::change_status_request_from_script(module_accessor, status, true); - } - - return 0; - } - - // If you can uthrow, then throw (since all throws should be possible at the same times) - if WorkModule::is_enable_transition_term( - module_accessor, - *FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_THROW_HI, - ) { - let cmd = THROW_CASE.into_cmd().unwrap_or(0); - mash::buffer_menu_mash(); - return cmd; - } - - return 0; -} +use crate::common::consts::*; +use crate::common::*; +use crate::training::frame_counter; +use crate::training::mash; +use smash::app::{self, lua_bind::*}; +use smash::lib::lua_const::*; + +const NOT_SET: u32 = 9001; +static mut THROW_DELAY: u32 = NOT_SET; +static mut THROW_DELAY_COUNTER: usize = 0; +static mut THROW_CASE: ThrowOption = ThrowOption::empty(); + +static mut PUMMEL_DELAY: u32 = NOT_SET; +static mut PUMMEL_DELAY_COUNTER: usize = 0; + +pub fn init() { + unsafe { + THROW_DELAY_COUNTER = frame_counter::register_counter(); + PUMMEL_DELAY_COUNTER = frame_counter::register_counter(); + } +} + +// Rolling Throw Delays and Pummel Delays separately + +pub fn reset_throw_delay() { + unsafe { + if THROW_DELAY != NOT_SET { + THROW_DELAY = NOT_SET; + frame_counter::full_reset(THROW_DELAY_COUNTER); + } + } +} + +pub fn reset_pummel_delay() { + unsafe { + if PUMMEL_DELAY != NOT_SET { + PUMMEL_DELAY = NOT_SET; + frame_counter::full_reset(PUMMEL_DELAY_COUNTER); + } + } +} + +pub fn reset_throw_case() { + unsafe { + if THROW_CASE != ThrowOption::empty() { + // Don't roll another throw option if one is already selected + THROW_CASE = ThrowOption::empty(); + } + } +} + +fn roll_throw_delay() { + unsafe { + if THROW_DELAY != NOT_SET { + // Don't roll another throw delay if one is already selected + return; + } + + THROW_DELAY = MENU.throw_delay.get_random().into_meddelay(); + } +} + +fn roll_pummel_delay() { + unsafe { + if PUMMEL_DELAY != NOT_SET { + // Don't roll another pummel delay if one is already selected + return; + } + + PUMMEL_DELAY = MENU.pummel_delay.get_random().into_meddelay(); + } +} + +fn roll_throw_case() { + unsafe { + // Don't re-roll if there is already a throw option selected + if THROW_CASE != ThrowOption::empty() { + return; + } + + THROW_CASE = MENU.throw_state.get_random(); + } +} + +pub unsafe fn get_command_flag_throw_direction( + module_accessor: &mut app::BattleObjectModuleAccessor, +) -> i32 { + if !is_operation_cpu(module_accessor) { + return 0; + } + + if StatusModule::status_kind(module_accessor) as i32 != *FIGHTER_STATUS_KIND_CATCH_WAIT + && StatusModule::status_kind(module_accessor) as i32 != *FIGHTER_STATUS_KIND_CATCH_PULL + && StatusModule::status_kind(module_accessor) as i32 != *FIGHTER_STATUS_KIND_CATCH_ATTACK + { + // No longer holding character, so re-roll the throw case and reset the delay counter for next time + reset_throw_case(); + reset_throw_delay(); + + reset_pummel_delay(); + return 0; + } + + if !WorkModule::is_enable_transition_term( + // If you can't throw right now, don't bother + module_accessor, + *FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_THROW_HI, + ) { + return 0; + } + + roll_throw_delay(); + roll_throw_case(); + + roll_pummel_delay(); + + if THROW_CASE == ThrowOption::NONE { + // Do nothing, but don't reroll the throw case. + return 0; + } + + if frame_counter::should_delay(THROW_DELAY, THROW_DELAY_COUNTER) { + // Not yet time to perform the throw action + if frame_counter::should_delay(PUMMEL_DELAY, PUMMEL_DELAY_COUNTER) { + // And not yet time to pummel either, so don't do anything + return 0; + } + + // If no pummel delay is selected (default), then don't pummel + if MENU.pummel_delay == MedDelay::empty() { + return 0; + } + + // (this conditional would need to be changed to speed up pummelling) + if StatusModule::status_kind(module_accessor) as i32 == *FIGHTER_STATUS_KIND_CATCH_WAIT { + let status = *FIGHTER_STATUS_KIND_CATCH_ATTACK; //.unwrap_or(0); + StatusModule::change_status_request_from_script(module_accessor, status, true); + } + + return 0; + } + + // If you can uthrow, then throw (since all throws should be possible at the same times) + if WorkModule::is_enable_transition_term( + module_accessor, + *FIGHTER_STATUS_TRANSITION_TERM_ID_CONT_THROW_HI, + ) { + let cmd = THROW_CASE.into_cmd().unwrap_or(0); + mash::buffer_menu_mash(); + return cmd; + } + + return 0; +} diff --git a/training_mod_consts/Cargo.toml b/training_mod_consts/Cargo.toml new file mode 100644 index 0000000..ece0179 --- /dev/null +++ b/training_mod_consts/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "training_mod_consts" +version = "0.1.0" +edition = "2018" + +[dependencies] +bitflags = "1.2.1" +strum = "0.21.0" +strum_macros = "0.21.0" +num = "0.4.0" +num-derive = "0.3" +num-traits = "0.2" +skyline_smash = { git = "https://github.com/ultimate-research/skyline-smash.git" } \ No newline at end of file diff --git a/training_mod_consts/src/lib.rs b/training_mod_consts/src/lib.rs new file mode 100644 index 0000000..df124ec --- /dev/null +++ b/training_mod_consts/src/lib.rs @@ -0,0 +1,960 @@ +#[macro_use] +extern crate bitflags; + +#[macro_use] +extern crate num_derive; + +use core::f64::consts::PI; +use smash::lib::lua_const::*; +use strum_macros::EnumIter; + +// bitflag helper function macro +macro_rules! extra_bitflag_impls { + ($e:ty) => { + impl core::fmt::Display for $e { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::Debug::fmt(self, f) + } + } + + impl $e { + pub fn to_vec(&self) -> Vec::<$e> { + let mut vec = Vec::<$e>::new(); + let mut field = <$e>::from_bits_truncate(self.bits); + while !field.is_empty() { + let flag = <$e>::from_bits(1u32 << field.bits.trailing_zeros()).unwrap(); + field -= flag; + vec.push(flag); + } + return vec; + } + + pub fn to_index(&self) -> u32 { + if self.bits == 0 { + 0 + } else { + self.bits.trailing_zeros() + } + } + + pub fn get_random(&self) -> $e { + let options = self.to_vec(); + match options.len() { + 0 => { + return <$e>::empty(); + } + 1 => { + return options[0]; + } + _ => { + return *random_option(&options); + } + } + } + + pub fn to_toggle_strs() -> Vec<&'static str> { + let all_options = <$e>::all().to_vec(); + all_options.iter().map(|i| i.as_str().unwrap_or("")).collect() + } + + pub fn to_toggle_vals() -> Vec { + let all_options = <$e>::all().to_vec(); + all_options.iter().map(|i| i.bits() as usize).collect() + } + pub fn to_url_param(&self) -> String { + self.to_vec() + .into_iter() + .map(|field| field.bits().to_string()) + .collect::>() + .join(",") + } + } + } +} + +pub fn get_random_int(max: i32) -> i32 { + unsafe { smash::app::sv_math::rand(smash::hash40("fighter"), max) } +} + +pub fn random_option(arg: &[T]) -> &T { + &arg[get_random_int(arg.len() as i32) as usize] +} + +// DI +/* + 0, 0.785398, 1.570796, 2.356194, -3.14159, -2.356194, -1.570796, -0.785398 + 0, pi/4, pi/2, 3pi/4, pi, 5pi/4, 3pi/2, 7pi/4 +*/ + +// DI / Left stick +bitflags! { + pub struct Direction : u32 + { + const OUT = 0x1; + const UP_OUT = 0x2; + const UP = 0x4; + const UP_IN = 0x8; + const IN = 0x10; + const DOWN_IN = 0x20; + const DOWN = 0x40; + const DOWN_OUT = 0x80; + const NEUTRAL = 0x100; + const LEFT = 0x200; + const RIGHT = 0x400; + } +} + +impl Direction { + pub fn into_angle(self) -> Option { + let index = self.into_index(); + + if index == 0 { + None + } else { + Some((index as i32 - 1) as f64 * PI / 4.0) + } + } + fn into_index(self) -> i32 { + match self { + Direction::OUT => 1, + Direction::UP_OUT => 2, + Direction::UP => 3, + Direction::UP_IN => 4, + Direction::IN => 5, + Direction::DOWN_IN => 6, + Direction::DOWN => 7, + Direction::DOWN_OUT => 8, + Direction::NEUTRAL => 0, + Direction::LEFT => 5, + Direction::RIGHT => 1, + _ => 0, + } + } + + fn as_str(self) -> Option<&'static str> { + Some(match self { + Direction::OUT => "Away", + Direction::UP_OUT => "Up and Away", + Direction::UP => "Up", + Direction::UP_IN => "Up and In", + Direction::IN => "In", + Direction::DOWN_IN => "Down and In", + Direction::DOWN => "Down", + Direction::DOWN_OUT => "Down and Away", + Direction::NEUTRAL => "Neutral", + Direction::LEFT => "Left", + Direction::RIGHT => "Right", + _ => return None, + }) + } +} + +extra_bitflag_impls! {Direction} + +// Ledge Option +bitflags! { + pub struct LedgeOption : u32 + { + const NEUTRAL = 0x1; + const ROLL = 0x2; + const JUMP = 0x4; + const ATTACK = 0x8; + const WAIT = 0x10; + } +} + +impl LedgeOption { + pub fn into_status(self) -> Option { + Some(match self { + LedgeOption::NEUTRAL => *FIGHTER_STATUS_KIND_CLIFF_CLIMB, + LedgeOption::ROLL => *FIGHTER_STATUS_KIND_CLIFF_ESCAPE, + LedgeOption::JUMP => *FIGHTER_STATUS_KIND_CLIFF_JUMP1, + LedgeOption::ATTACK => *FIGHTER_STATUS_KIND_CLIFF_ATTACK, + LedgeOption::WAIT => *FIGHTER_STATUS_KIND_CLIFF_WAIT, + _ => return None, + }) + } + + fn as_str(self) -> Option<&'static str> { + Some(match self { + LedgeOption::NEUTRAL => "Neutral Getup", + LedgeOption::ROLL => "Roll", + LedgeOption::JUMP => "Jump", + LedgeOption::ATTACK => "Getup Attack", + LedgeOption::WAIT => "Wait", + _ => return None, + }) + } +} + +extra_bitflag_impls! {LedgeOption} + +// Tech options +bitflags! { + pub struct TechFlags : u32 { + const NO_TECH = 0x1; + const ROLL_F = 0x2; + const ROLL_B = 0x4; + const IN_PLACE = 0x8; + } +} + +impl TechFlags { + fn as_str(self) -> Option<&'static str> { + Some(match self { + TechFlags::NO_TECH => "No Tech", + TechFlags::ROLL_F => "Roll Forwards", + TechFlags::ROLL_B => "Roll Backwards", + TechFlags::IN_PLACE => "Tech In Place", + _ => return None, + }) + } +} + +extra_bitflag_impls! {TechFlags} + +// Missed Tech Options +bitflags! { + pub struct MissTechFlags : u32 { + const GETUP = 0x1; + const ATTACK = 0x2; + const ROLL_F = 0x4; + const ROLL_B = 0x8; + } +} + +impl MissTechFlags { + fn as_str(self) -> Option<&'static str> { + Some(match self { + MissTechFlags::GETUP => "Neutral Getup", + MissTechFlags::ATTACK => "Getup Attack", + MissTechFlags::ROLL_F => "Roll Forwards", + MissTechFlags::ROLL_B => "Roll Backwards", + _ => return None, + }) + } +} + +extra_bitflag_impls! {MissTechFlags} + +/// Shield States +#[repr(i32)] +#[derive(Debug, Clone, Copy, PartialEq, FromPrimitive, EnumIter)] +pub enum Shield { + None = 0, + Infinite = 1, + Hold = 2, + Constant = 3, +} + +impl Shield { + pub fn as_str(self) -> Option<&'static str> { + Some(match self { + Shield::None => "None", + Shield::Infinite => "Infinite", + Shield::Hold => "Hold", + Shield::Constant => "Constant", + }) + } + + pub fn to_url_param(&self) -> String { + (*self as i32).to_string() + } +} + +// Save State Mirroring +#[repr(i32)] +#[derive(Debug, Clone, Copy, PartialEq, FromPrimitive, EnumIter)] +pub enum SaveStateMirroring { + None = 0, + Alternate = 1, + Random = 2, +} + +impl SaveStateMirroring { + pub fn as_str(self) -> Option<&'static str> { + Some(match self { + SaveStateMirroring::None => "None", + SaveStateMirroring::Alternate => "Alternate", + SaveStateMirroring::Random => "Random", + }) + } + + fn to_url_param(&self) -> String { + (*self as i32).to_string() + } +} + +// Defensive States +bitflags! { + pub struct Defensive : u32 { + const SPOT_DODGE = 0x1; + const ROLL_F = 0x2; + const ROLL_B = 0x4; + const JAB = 0x8; + const SHIELD = 0x10; + } +} + +impl Defensive { + fn as_str(self) -> Option<&'static str> { + Some(match self { + Defensive::SPOT_DODGE => "Spotdodge", + Defensive::ROLL_F => "Roll Forwards", + Defensive::ROLL_B => "Roll Backwards", + Defensive::JAB => "Jab", + Defensive::SHIELD => "Shield", + _ => return None, + }) + } +} + +extra_bitflag_impls! {Defensive} + +#[repr(i32)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum OnOff { + Off = 0, + On = 1, +} + +impl OnOff { + pub fn from_val(val: u32) -> Option { + match val { + 1 => Some(OnOff::On), + 0 => Some(OnOff::Off), + _ => None, + } + } + + pub fn as_str(self) -> Option<&'static str> { + Some(match self { + OnOff::Off => "Off", + OnOff::On => "On", + }) + } + + pub fn to_url_param(&self) -> String { + (*self as i32).to_string() + } +} + +bitflags! { + pub struct Action : u32 { + const AIR_DODGE = 0x1; + const JUMP = 0x2; + const SHIELD = 0x4; + const SPOT_DODGE = 0x8; + const ROLL_F = 0x10; + const ROLL_B = 0x20; + const NAIR = 0x40; + const FAIR = 0x80; + const BAIR = 0x100; + const UAIR = 0x200; + const DAIR = 0x400; + const NEUTRAL_B = 0x800; + const SIDE_B = 0x1000; + const UP_B = 0x2000; + const DOWN_B = 0x4000; + const F_SMASH = 0x8000; + const U_SMASH = 0x10000; + const D_SMASH = 0x20000; + const JAB = 0x40000; + const F_TILT = 0x80000; + const U_TILT = 0x0010_0000; + const D_TILT = 0x0020_0000; + const GRAB = 0x0040_0000; + // TODO: Make work + const DASH = 0x0080_0000; + const DASH_ATTACK = 0x0100_0000; + } +} + +impl Action { + pub fn into_attack_air_kind(self) -> Option { + Some(match self { + Action::NAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_N, + Action::FAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_F, + Action::BAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_B, + Action::DAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_LW, + Action::UAIR => *FIGHTER_COMMAND_ATTACK_AIR_KIND_HI, + _ => return None, + }) + } + + pub fn as_str(self) -> Option<&'static str> { + Some(match self { + Action::AIR_DODGE => "Airdodge", + Action::JUMP => "Jump", + Action::SHIELD => "Shield", + Action::SPOT_DODGE => "Spotdodge", + Action::ROLL_F => "Roll Forwards", + Action::ROLL_B => "Roll Backwards", + Action::NAIR => "Neutral Aerial", + Action::FAIR => "Forward Aerial", + Action::BAIR => "Backward Aerial", + Action::UAIR => "Up Aerial", + Action::DAIR => "Down Aerial", + Action::NEUTRAL_B => "Neutral Special", + Action::SIDE_B => "Side Special", + Action::UP_B => "Up Special", + Action::DOWN_B => "Down Special", + Action::F_SMASH => "Forward Smash", + Action::U_SMASH => "Up Smash", + Action::D_SMASH => "Down Smash", + Action::JAB => "Jab", + Action::F_TILT => "Forward Tilt", + Action::U_TILT => "Up Tilt", + Action::D_TILT => "Down Tilt", + Action::GRAB => "Grab", + Action::DASH => "Dash", + Action::DASH_ATTACK => "Dash Attack", + _ => return None, + }) + } +} + +extra_bitflag_impls! {Action} + +bitflags! { + pub struct AttackAngle : u32 { + const NEUTRAL = 0x1; + const UP = 0x2; + const DOWN = 0x4; + } +} + +impl AttackAngle { + pub fn as_str(self) -> Option<&'static str> { + Some(match self { + AttackAngle::NEUTRAL => "Neutral", + AttackAngle::UP => "Up", + AttackAngle::DOWN => "Down", + _ => return None, + }) + } +} + +extra_bitflag_impls! {AttackAngle} + +bitflags! { + pub struct Delay : u32 { + const D0 = 0x1; + const D1 = 0x2; + const D2 = 0x4; + const D3 = 0x8; + const D4 = 0x10; + const D5 = 0x20; + const D6 = 0x40; + const D7 = 0x80; + const D8 = 0x100; + const D9 = 0x200; + const D10 = 0x400; + const D11 = 0x800; + const D12 = 0x1000; + const D13 = 0x2000; + const D14 = 0x4000; + const D15 = 0x8000; + const D16 = 0x10000; + const D17 = 0x20000; + const D18 = 0x40000; + const D19 = 0x80000; + const D20 = 0x0010_0000; + const D21 = 0x0020_0000; + const D22 = 0x0040_0000; + const D23 = 0x0080_0000; + const D24 = 0x0100_0000; + const D25 = 0x0200_0000; + const D26 = 0x0400_0000; + const D27 = 0x0800_0000; + const D28 = 0x1000_0000; + const D29 = 0x2000_0000; + const D30 = 0x4000_0000; + } +} + +// Throw Option +bitflags! { + pub struct ThrowOption : u32 + { + const NONE = 0x1; + const FORWARD = 0x2; + const BACKWARD = 0x4; + const UP = 0x8; + const DOWN = 0x10; + } +} + +impl ThrowOption { + pub fn into_cmd(self) -> Option { + Some(match self { + ThrowOption::NONE => 0, + ThrowOption::FORWARD => *FIGHTER_PAD_CMD_CAT2_FLAG_THROW_F, + ThrowOption::BACKWARD => *FIGHTER_PAD_CMD_CAT2_FLAG_THROW_B, + ThrowOption::UP => *FIGHTER_PAD_CMD_CAT2_FLAG_THROW_HI, + ThrowOption::DOWN => *FIGHTER_PAD_CMD_CAT2_FLAG_THROW_LW, + _ => return None, + }) + } + + pub fn as_str(self) -> Option<&'static str> { + Some(match self { + ThrowOption::NONE => "None", + ThrowOption::FORWARD => "Forward Throw", + ThrowOption::BACKWARD => "Back Throw", + ThrowOption::UP => "Up Throw", + ThrowOption::DOWN => "Down Throw", + _ => return None, + }) + } +} + +extra_bitflag_impls! {ThrowOption} + +// Buff Option +bitflags! { + pub struct BuffOption : u32 + { + const ACCELERATLE = 0x1; + const OOMPH = 0x2; + const PSYCHE = 0x4; + const BOUNCE = 0x8; + const ARSENE = 0x10; + const BREATHING = 0x20; + const LIMIT = 0x40; + const KO = 0x80; + const WING = 0x100; + } +} + +impl BuffOption { + pub fn into_int(self) -> Option { + Some(match self { + BuffOption::ACCELERATLE => *FIGHTER_BRAVE_SPECIAL_LW_COMMAND11_SPEED_UP, + BuffOption::OOMPH => *FIGHTER_BRAVE_SPECIAL_LW_COMMAND12_ATTACK_UP, + BuffOption::PSYCHE => *FIGHTER_BRAVE_SPECIAL_LW_COMMAND21_CHARGE, + BuffOption::BOUNCE => *FIGHTER_BRAVE_SPECIAL_LW_COMMAND13_REFLECT, + BuffOption::BREATHING => 1, + BuffOption::ARSENE => 1, + BuffOption::LIMIT => 1, + BuffOption::KO => 1, + BuffOption::WING => 1, + _ => return None, + }) + } + + fn as_str(self) -> Option<&'static str> { + Some(match self { + BuffOption::ACCELERATLE => "Acceleratle", + BuffOption::OOMPH => "Oomph", + BuffOption::BOUNCE => "Bounce", + BuffOption::PSYCHE => "Psyche Up", + BuffOption::BREATHING => "Deep Breathing", + BuffOption::ARSENE => "Arsene", + BuffOption::LIMIT => "Limit Break", + BuffOption::KO => "KO Punch", + BuffOption::WING => "One-Winged Angel", + _ => return None, + }) + } +} + +extra_bitflag_impls! {BuffOption} + +impl Delay { + pub fn as_str(self) -> Option<&'static str> { + Some(match self { + Delay::D0 => "0", + Delay::D1 => "1", + Delay::D2 => "2", + Delay::D3 => "3", + Delay::D4 => "4", + Delay::D5 => "5", + Delay::D6 => "6", + Delay::D7 => "7", + Delay::D8 => "8", + Delay::D9 => "9", + Delay::D10 => "10", + Delay::D11 => "11", + Delay::D12 => "12", + Delay::D13 => "13", + Delay::D14 => "14", + Delay::D15 => "15", + Delay::D16 => "16", + Delay::D17 => "17", + Delay::D18 => "18", + Delay::D19 => "19", + Delay::D20 => "20", + Delay::D21 => "21", + Delay::D22 => "22", + Delay::D23 => "23", + Delay::D24 => "24", + Delay::D25 => "25", + Delay::D26 => "26", + Delay::D27 => "27", + Delay::D28 => "28", + Delay::D29 => "29", + Delay::D30 => "30", + _ => return None, + }) + } + + pub fn into_delay(&self) -> u32 { + self.to_index() + } +} + +extra_bitflag_impls! {Delay} + +bitflags! { + pub struct MedDelay : u32 { + const D0 = 0x1; + const D5 = 0x2; + const D10 = 0x4; + const D15 = 0x8; + const D20 = 0x10; + const D25 = 0x20; + const D30 = 0x40; + const D35 = 0x80; + const D40 = 0x100; + const D45 = 0x200; + const D50 = 0x400; + const D55 = 0x800; + const D60 = 0x1000; + const D65 = 0x2000; + const D70 = 0x4000; + const D75 = 0x8000; + const D80 = 0x10000; + const D85 = 0x20000; + const D90 = 0x40000; + const D95 = 0x80000; + const D100 = 0x0010_0000; + const D105 = 0x0020_0000; + const D110 = 0x0040_0000; + const D115 = 0x0080_0000; + const D120 = 0x0100_0000; + const D125 = 0x0200_0000; + const D130 = 0x0400_0000; + const D135 = 0x0800_0000; + const D140 = 0x1000_0000; + const D145 = 0x2000_0000; + const D150 = 0x4000_0000; + } +} + +impl MedDelay { + pub fn as_str(self) -> Option<&'static str> { + Some(match self { + MedDelay::D0 => "0", + MedDelay::D5 => "5", + MedDelay::D10 => "10", + MedDelay::D15 => "15", + MedDelay::D20 => "20", + MedDelay::D25 => "25", + MedDelay::D30 => "30", + MedDelay::D35 => "35", + MedDelay::D40 => "40", + MedDelay::D45 => "45", + MedDelay::D50 => "50", + MedDelay::D55 => "55", + MedDelay::D60 => "60", + MedDelay::D65 => "65", + MedDelay::D70 => "70", + MedDelay::D75 => "75", + MedDelay::D80 => "80", + MedDelay::D85 => "85", + MedDelay::D90 => "90", + MedDelay::D95 => "95", + MedDelay::D100 => "100", + MedDelay::D105 => "105", + MedDelay::D110 => "110", + MedDelay::D115 => "115", + MedDelay::D120 => "120", + MedDelay::D125 => "125", + MedDelay::D130 => "130", + MedDelay::D135 => "135", + MedDelay::D140 => "140", + MedDelay::D145 => "145", + MedDelay::D150 => "150", + _ => return None, + }) + } + + pub fn into_meddelay(&self) -> u32 { + self.to_index() * 5 + } +} + +extra_bitflag_impls! {MedDelay} + +bitflags! { + pub struct LongDelay : u32 { + const D0 = 0x1; + const D10 = 0x2; + const D20 = 0x4; + const D30 = 0x8; + const D40 = 0x10; + const D50 = 0x20; + const D60 = 0x40; + const D70 = 0x80; + const D80 = 0x100; + const D90 = 0x200; + const D100 = 0x400; + const D110 = 0x800; + const D120 = 0x1000; + const D130 = 0x2000; + const D140 = 0x4000; + const D150 = 0x8000; + const D160 = 0x10000; + const D170 = 0x20000; + const D180 = 0x40000; + const D190 = 0x80000; + const D200 = 0x0010_0000; + const D210 = 0x0020_0000; + const D220 = 0x0040_0000; + const D230 = 0x0080_0000; + const D240 = 0x0100_0000; + const D250 = 0x0200_0000; + const D260 = 0x0400_0000; + const D270 = 0x0800_0000; + const D280 = 0x1000_0000; + const D290 = 0x2000_0000; + const D300 = 0x4000_0000; + } +} + +impl LongDelay { + pub fn as_str(self) -> Option<&'static str> { + Some(match self { + LongDelay::D0 => "0", + LongDelay::D10 => "10", + LongDelay::D20 => "20", + LongDelay::D30 => "30", + LongDelay::D40 => "40", + LongDelay::D50 => "50", + LongDelay::D60 => "60", + LongDelay::D70 => "70", + LongDelay::D80 => "80", + LongDelay::D90 => "90", + LongDelay::D100 => "100", + LongDelay::D110 => "110", + LongDelay::D120 => "120", + LongDelay::D130 => "130", + LongDelay::D140 => "140", + LongDelay::D150 => "150", + LongDelay::D160 => "160", + LongDelay::D170 => "170", + LongDelay::D180 => "180", + LongDelay::D190 => "190", + LongDelay::D200 => "200", + LongDelay::D210 => "210", + LongDelay::D220 => "220", + LongDelay::D230 => "230", + LongDelay::D240 => "240", + LongDelay::D250 => "250", + LongDelay::D260 => "260", + LongDelay::D270 => "270", + LongDelay::D280 => "280", + LongDelay::D290 => "290", + LongDelay::D300 => "300", + _ => return None, + }) + } + + pub fn into_longdelay(&self) -> u32 { + self.to_index() * 10 + } +} + +extra_bitflag_impls! {LongDelay} + +bitflags! { + pub struct BoolFlag : u32 { + const TRUE = 0x1; + const FALSE = 0x2; + } +} + +extra_bitflag_impls! {BoolFlag} + +impl BoolFlag { + pub fn into_bool(self) -> bool { + matches!(self, BoolFlag::TRUE) + } + + pub fn as_str(self) -> Option<&'static str> { + Some(match self { + BoolFlag::TRUE => "True", + _ => "False", + }) + } +} + +#[repr(u32)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, EnumIter)] +pub enum SdiStrength { + Normal = 0, + Medium = 1, + High = 2, +} + +impl SdiStrength { + pub fn into_u32(self) -> u32 { + match self { + SdiStrength::Normal => 8, + SdiStrength::Medium => 6, + SdiStrength::High => 4, + } + } + + pub fn as_str(self) -> Option<&'static str> { + Some(match self { + SdiStrength::Normal => "Normal", + SdiStrength::Medium => "Medium", + SdiStrength::High => "High", + }) + } + + pub fn to_url_param(&self) -> String { + (*self as u32).to_string() + } +} + +// For input delay +trait ToUrlParam { + fn to_url_param(&self) -> String; +} + +impl ToUrlParam for i32 { + fn to_url_param(&self) -> String { + self.to_string() + } +} + +// Macro to build the url parameter string +macro_rules! url_params { + ( + #[derive($($trait_name:ident, )*)] + pub struct $e:ident { + $(pub $field_name:ident: $field_type:ty,)* + } + ) => { + #[derive($($trait_name, )*)] + pub struct $e { + $(pub $field_name: $field_type,)* + } + impl $e { + pub fn to_url_params(&self) -> String { + let mut s = "?".to_string(); + $( + s.push_str(stringify!($field_name)); + s.push_str(&"="); + s.push_str(&self.$field_name.to_url_param()); + s.push_str(&"&"); + )* + s.pop(); + s + } + } + } +} + +#[repr(C)] +url_params! { + #[derive(Clone, Copy, )] + pub struct TrainingModpackMenu { + pub hitbox_vis: OnOff, + pub stage_hazards: OnOff, + pub di_state: Direction, + pub sdi_state: Direction, + pub sdi_strength: SdiStrength, + pub air_dodge_dir: Direction, + pub mash_state: Action, + pub follow_up: Action, + pub attack_angle: AttackAngle, + pub ledge_state: LedgeOption, + pub ledge_delay: LongDelay, + pub tech_state: TechFlags, + pub miss_tech_state: MissTechFlags, + pub shield_state: Shield, + pub defensive_state: Defensive, + pub oos_offset: Delay, + pub reaction_time: Delay, + pub shield_tilt: Direction, + pub mash_in_neutral: OnOff, + pub fast_fall: BoolFlag, + pub fast_fall_delay: Delay, + pub falling_aerials: BoolFlag, + pub aerial_delay: Delay, + pub full_hop: BoolFlag, + pub input_delay: i32, + pub save_damage: OnOff, + pub save_state_mirroring: SaveStateMirroring, + pub frame_advantage: OnOff, + pub save_state_enable: OnOff, + pub throw_state: ThrowOption, + pub throw_delay: MedDelay, + pub pummel_delay: MedDelay, + pub buff_state: BuffOption, + } +} + +macro_rules! set_by_str { + ($obj:ident, $s:ident, $($field:ident = $rhs:expr,)*) => { + $( + if $s == stringify!($field) { + $obj.$field = $rhs.unwrap(); + } + )* + } +} + +impl TrainingModpackMenu { + pub fn set(&mut self, s: &str, val: u32) { + set_by_str!( + self, + s, + aerial_delay = Delay::from_bits(val), + air_dodge_dir = Direction::from_bits(val), + attack_angle = AttackAngle::from_bits(val), + defensive_state = Defensive::from_bits(val), + di_state = Direction::from_bits(val), + falling_aerials = BoolFlag::from_bits(val), + fast_fall_delay = Delay::from_bits(val), + fast_fall = BoolFlag::from_bits(val), + follow_up = Action::from_bits(val), + full_hop = BoolFlag::from_bits(val), + hitbox_vis = OnOff::from_val(val), + input_delay = Some(val as i32), + ledge_delay = LongDelay::from_bits(val), + ledge_state = LedgeOption::from_bits(val), + mash_in_neutral = OnOff::from_val(val), + mash_state = Action::from_bits(val), + miss_tech_state = MissTechFlags::from_bits(val), + oos_offset = Delay::from_bits(val), + reaction_time = Delay::from_bits(val), + sdi_state = Direction::from_bits(val), + sdi_strength = num::FromPrimitive::from_u32(val), + shield_state = num::FromPrimitive::from_u32(val), + shield_tilt = Direction::from_bits(val), + stage_hazards = OnOff::from_val(val), + tech_state = TechFlags::from_bits(val), + save_damage = OnOff::from_val(val), + frame_advantage = OnOff::from_val(val), + save_state_mirroring = num::FromPrimitive::from_u32(val), + save_state_enable = OnOff::from_val(val), + throw_state = ThrowOption::from_bits(val), + throw_delay = MedDelay::from_bits(val), + pummel_delay = MedDelay::from_bits(val), + buff_state = BuffOption::from_bits(val), + ); + } +} + +// Fighter Ids +#[repr(i32)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FighterId { + Player = 0, + CPU = 1, +} diff --git a/training_mod_metrics/Cargo.toml b/training_mod_metrics/Cargo.toml new file mode 100644 index 0000000..4753903 --- /dev/null +++ b/training_mod_metrics/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "training_mod_metrics" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +datafusion = "5.0.0" +tokio = "1.11.0" +plotters = "0.3.1" +chrono = "0.4.19" \ No newline at end of file diff --git a/training_mod_metrics/src/main.rs b/training_mod_metrics/src/main.rs new file mode 100644 index 0000000..14db349 --- /dev/null +++ b/training_mod_metrics/src/main.rs @@ -0,0 +1,144 @@ +use datafusion::prelude::*; +use datafusion::arrow::record_batch::RecordBatch; +use datafusion::datasource::json::NdJsonFile; +use datafusion::physical_plan::json::NdJsonReadOptions; +use datafusion::arrow::datatypes::{Schema, Field, DataType}; + +use std::sync::Arc; + +// export.json is relative to /event/ +// cat export.json | jq -c '.SMASH_OPEN.device[][][]' > smash_open.json +#[derive(Debug)] +struct Event { + device_id: String, + event_name: String, + event_time: i64, + menu_settings: String, + mod_version: String, + session_id: String, + smash_version: String, + user_id: String +} + +use chrono::{DateTime, NaiveDateTime, Utc}; +fn timestamp_secs_to_datetime(ts: i64) -> DateTime { + DateTime::::from_utc(NaiveDateTime::from_timestamp(ts, 0), Utc) +} + +use plotters::prelude::*; +const OUT_FILE_NAME: &'static str = "boxplot.svg"; +fn draw_chart(results: Vec) -> Result<(), Box> { + let num_devices_idx = results[0].schema().column_with_name("num_devices").unwrap().0; + let num_sessions_idx = results[0].schema().column_with_name("num_sessions").unwrap().0; + let timestamps_idx = results[0].schema().column_with_name("date").unwrap().0; + + let num_devices = results[0].column(num_devices_idx).as_any() + .downcast_ref::() + .expect("Failed to downcast").values(); + let num_sessions = results[0].column(num_sessions_idx).as_any() + .downcast_ref::() + .expect("Failed to downcast").values(); + let timestamp_millis = results[0].column(timestamps_idx).as_any() + .downcast_ref::() + .expect("Failed to downcast").values(); + + let device_data_points = num_devices.iter() + .enumerate().map(|(i, x)| (timestamp_secs_to_datetime(timestamp_millis[i] / 1000), *x)); + let session_data_points = num_sessions.iter() + .enumerate().map(|(i, x)| (timestamp_secs_to_datetime(timestamp_millis[i] / 1000), *x)); + + let root = SVGBackend::new(OUT_FILE_NAME, (1024, 768)).into_drawing_area(); + root.fill(&WHITE)?; + let mut chart = ChartBuilder::on(&root) + .caption("Users and Sessions by Date", ("sans-serif", 50).into_font()) + .margin(5) + .x_label_area_size(30) + .y_label_area_size(30) + .build_cartesian_2d( + (timestamp_secs_to_datetime(timestamp_millis[0] / 1000))..(timestamp_secs_to_datetime(*timestamp_millis.last().unwrap() / 1000)), + 0..*num_sessions.iter().max().unwrap())?; + + chart.configure_mesh().draw()?; + + chart + .draw_series(LineSeries::new( + device_data_points, + &RED, + ))? + .label("Unique Devices") + .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &RED)); + chart + .draw_series(LineSeries::new( + session_data_points, + &BLUE, + ))? + .label("Unique Sessions") + .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &BLUE)); + + chart + .configure_series_labels() + .background_style(&WHITE.mix(0.8)) + .border_style(&BLACK) + .draw()?; + + Ok(()) +} + +#[tokio::main] +async fn main() -> datafusion::error::Result<()> { + // let smash_open_table = NdJsonFile::try_new( + // "smash_open.json", + // NdJsonReadOptions{ + // schema: None, + // schema_infer_max_records: 1, + // file_extension: ".json", + // } + // ).unwrap(); + + let menu_open_table = NdJsonFile::try_new( + "menu_open.json", + NdJsonReadOptions{ + schema: Some(Arc::new(Schema::new(vec![ + Field::new("device_id", DataType::Utf8, false), + Field::new("event_name", DataType::Utf8, false), + Field::new("event_time", DataType::Int64, false), + Field::new("menu_settings", DataType::Utf8, false), + Field::new("session_id", DataType::Utf8, false), + Field::new("smash_version", DataType::Utf8, false), + Field::new("mod_version", DataType::Utf8, false), + Field::new("user_id", DataType::Utf8, false), + ]))), + schema_infer_max_records: 0, + file_extension: ".json", + } + ).unwrap(); + + // // declare a new context. In spark API, this corresponds to a new spark SQLsession + let mut ctx = ExecutionContext::new(); + + // ctx.register_table("smash_open", Arc::new(smash_open_table))?; + ctx.register_table("menu_open", Arc::new(menu_open_table))?; + + // create a plan to run a SQL query + let df = ctx.sql( + "SELECT + COUNT(DISTINCT device_id) num_devices, + COUNT(DISTINCT session_id) num_sessions, + COUNT(*) num_events, + TO_TIMESTAMP_MILLIS(DATE_TRUNC('day', CAST(event_time * 1000000 AS timestamp))) AS date FROM menu_open + WHERE + -- after 09/01/2021 + event_time > 1630454400000 + -- before today + AND CAST(event_time * 1000000 AS timestamp) < NOW() + GROUP BY date ORDER BY date" + )?; + + let results: Vec = df.collect().await?; + // use datafusion::arrow::util::pretty::pretty_format_batches; + // println!("{}", pretty_format_batches(&results)?); + + draw_chart(results).unwrap(); + + Ok(()) +} \ No newline at end of file