diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index a85e7de..c9f585e 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,149 +1,149 @@ -name: Rust - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - workflow_dispatch: - -jobs: - checker: - name: Check, Clippy, Tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Install minimal nightly rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - components: rustfmt, clippy - default: true - target: x86_64-unknown-linux-gnu - - uses: Swatinem/rust-cache@v2 - name: Rust Cache - with: - prefix-key: "checker" - - name: Clippy - uses: actions-rs/cargo@v1 - continue-on-error: false - with: - command: clippy - args: --all-targets --all-features --target=x86_64-unknown-linux-gnu -- -D warnings - - name: TUI Test - uses: actions-rs/cargo@v1 - continue-on-error: false - with: - working-directory: training_mod_tui - plugin: - name: Plugin NRO - runs-on: ubuntu-latest - container: - image: jugeeya/cargo-skyline:3.2.0-no-dkp - steps: - - uses: actions/checkout@v2 - - uses: Swatinem/rust-cache@v2 - name: Rust Cache - with: - prefix-key: "plugin" - - name: Build release NRO - id: build_release - run: 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 - plugin_outside_training_mode: - name: Plugin NRO (Outside Training Mode) - if: github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - container: - image: jugeeya/cargo-skyline:3.2.0-no-dkp - steps: - - uses: actions/checkout@v2 - - uses: Swatinem/rust-cache@v2 - name: Rust Cache - with: - prefix-key: "plugin" - - name: Build outside_training_mode NRO - run: | - 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: - name: Upload Beta Release - runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' - needs: - - plugin - steps: - - name: Download all artifacts - uses: actions/download-artifact@v2 - - name: Prepare zip - id: prepare_zip - env: - SMASH_PLUGIN_DIR: atmosphere/contents/01006A800016E000/romfs/skyline/plugins - run: | - mkdir -p ${{env.SMASH_PLUGIN_DIR}} - cp plugin/libtraining_modpack.nro ${{env.SMASH_PLUGIN_DIR}}/libtraining_modpack.nro - wget https://github.com/ultimate-research/params-hook-plugin/releases/download/v13.0.1/libparam_hook.nro - wget https://github.com/ultimate-research/nro-hook-plugin/releases/download/v0.4.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 - zip -r training_modpack_beta.zip atmosphere - - name: Delete Release - uses: dev-drprasad/delete-tag-and-release@v0.2.0 - with: - tag_name: beta - delete_release: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Update Release - uses: meeDamian/github-release@2.0 - with: - token: ${{ secrets.GITHUB_TOKEN }} - prerelease: true - allow_override: true - gzip: false - tag: beta - commitish: main - name: beta - body: > - Beta built off of the latest code in the repository. - - # Changelog - - You can find the changelog here: https://github.com/jugeeya/UltimateTrainingModpack#beta-changelog - - - ## Installation - - *For fuller instructions, please join the [Discord](https://discord.gg/xUZWJ5BWe7) and visit the #setup-and-download channel.* - - - (*Console only*) Install Atmosphere to your hacked Switch. One great guide can be found at https://switch.homebrew.guide/ - - - Place the **contents** of the `training_modpack_beta.zip` on the root of your SD card. This means that you first unzip the file, then place its folder on the SD card root. The `atmosphere` folder should be **merged** onto the root of your SD card. - - - *For Ryujinx*: Paste the `contents` folder inside `atmosphere` into `%AppData%/Ryujinx/mods/` - - - Download Skyline: https://github.com/skyline-dev/skyline/releases. Place the `exefs` folder from the zip into `atmosphere/contents/01006A800016E000` on your SD card. - - - *For Ryujinx*: Paste these files in `%AppData%/Ryujinx/mods/contents/01006a800016e000` - 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: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: + +jobs: + checker: + name: Check, Clippy, Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install minimal nightly rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + components: rustfmt, clippy + default: true + target: x86_64-unknown-linux-gnu + - uses: Swatinem/rust-cache@v2 + name: Rust Cache + with: + prefix-key: "checker" + - name: Clippy + uses: actions-rs/cargo@v1 + continue-on-error: false + with: + command: clippy + args: --all-targets --all-features --target=x86_64-unknown-linux-gnu -- -D warnings + - name: TUI Test + uses: actions-rs/cargo@v1 + continue-on-error: false + with: + working-directory: training_mod_tui + plugin: + name: Plugin NRO + runs-on: ubuntu-latest + container: + image: jugeeya/cargo-skyline:3.2.0-no-dkp + steps: + - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v2 + name: Rust Cache + with: + prefix-key: "plugin" + - name: Build release NRO + id: build_release + run: 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 + plugin_outside_training_mode: + name: Plugin NRO (Outside Training Mode) + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + container: + image: jugeeya/cargo-skyline:3.2.0-no-dkp + steps: + - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v2 + name: Rust Cache + with: + prefix-key: "plugin" + - name: Build outside_training_mode NRO + run: | + 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: + name: Upload Beta Release + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + needs: + - plugin + steps: + - name: Download all artifacts + uses: actions/download-artifact@v2 + - name: Prepare zip + id: prepare_zip + env: + SMASH_PLUGIN_DIR: atmosphere/contents/01006A800016E000/romfs/skyline/plugins + run: | + mkdir -p ${{env.SMASH_PLUGIN_DIR}} + cp plugin/libtraining_modpack.nro ${{env.SMASH_PLUGIN_DIR}}/libtraining_modpack.nro + wget https://github.com/ultimate-research/params-hook-plugin/releases/download/v13.0.1/libparam_hook.nro + wget https://github.com/ultimate-research/nro-hook-plugin/releases/download/v0.4.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 + zip -r training_modpack_beta.zip atmosphere + - name: Delete Release + uses: dev-drprasad/delete-tag-and-release@v0.2.0 + with: + tag_name: beta + delete_release: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Update Release + uses: meeDamian/github-release@2.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + prerelease: true + allow_override: true + gzip: false + tag: beta + commitish: main + name: beta + body: > + Beta built off of the latest code in the repository. + + # Changelog + + You can find the changelog here: https://github.com/jugeeya/UltimateTrainingModpack#beta-changelog + + + ## Installation + + *For fuller instructions, please join the [Discord](https://discord.gg/xUZWJ5BWe7) and visit the #setup-and-download channel.* + + - (*Console only*) Install Atmosphere to your hacked Switch. One great guide can be found at https://switch.homebrew.guide/ + + - Place the **contents** of the `training_modpack_beta.zip` on the root of your SD card. This means that you first unzip the file, then place its folder on the SD card root. The `atmosphere` folder should be **merged** onto the root of your SD card. + + - *For Ryujinx*: Paste the `contents` folder inside `atmosphere` into `%AppData%/Ryujinx/mods/` + + - Download Skyline: https://github.com/skyline-dev/skyline/releases. Place the `exefs` folder from the zip into `atmosphere/contents/01006A800016E000` on your SD card. + + - *For Ryujinx*: Paste these files in `%AppData%/Ryujinx/mods/contents/01006a800016e000` + files: > + training_modpack_beta.zip + - name: Upload zip as artifact + uses: actions/upload-artifact@v1 + with: + name: full_build + path: training_modpack_beta.zip diff --git a/src/common/consts.rs b/src/common/consts.rs index 08e83cd..4a311f4 100644 --- a/src/common/consts.rs +++ b/src/common/consts.rs @@ -1,2 +1,2 @@ -pub use training_mod_consts::*; - +pub use training_mod_consts::*; + diff --git a/src/common/raygun_printer.rs b/src/common/raygun_printer.rs index cc2f875..9319dad 100644 --- a/src/common/raygun_printer.rs +++ b/src/common/raygun_printer.rs @@ -1,242 +1,242 @@ -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; - - if is_facing_left { - index = SEGMENT_REV[index as usize] as i32 - 'a' as i32; - } - let 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; + + if is_facing_left { + index = SEGMENT_REV[index as usize] as i32 - 'a' as i32; + } + let 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/training/charge.rs b/src/training/charge.rs index 0343e25..1d311e9 100644 --- a/src/training/charge.rs +++ b/src/training/charge.rs @@ -1,821 +1,821 @@ -use serde::{Deserialize, Serialize}; -use smash::app::{self, ArticleOperationTarget, FighterFacial, FighterUtil, lua_bind::*}; -use smash::lib::lua_const::*; -use smash::phx::{Hash40, Vector3f}; - -#[derive(Serialize, Deserialize, Default, Copy, Clone, Debug)] -pub struct ChargeState { - pub int_x: Option, - pub int_y: Option, - pub float_x: Option, - pub float_y: Option, - pub float_z: Option, - pub has_charge: Option, -} - -impl ChargeState { - fn int_x(mut self, int_x: i32) -> Self { - self.int_x = Some(int_x); - self - } - - fn int_y(mut self, int_y: i32) -> Self { - self.int_y = Some(int_y); - self - } - - fn float_x(mut self, float_x: f32) -> Self { - self.float_x = Some(float_x); - self - } - - fn float_y(mut self, float_y: f32) -> Self { - self.float_y = Some(float_y); - self - } - - fn float_z(mut self, float_z: f32) -> Self { - self.float_z = Some(float_z); - self - } - - fn has_charge(mut self, has_charge: bool) -> Self { - self.has_charge = Some(has_charge); - self - } -} - -pub unsafe fn get_charge( - module_accessor: &mut app::BattleObjectModuleAccessor, - fighter_kind: i32, -) -> ChargeState { - let charge_state = ChargeState::default(); - // Mario FLUDD - if fighter_kind == FIGHTER_KIND_MARIO { - let my_charge = WorkModule::get_int( - module_accessor, - *FIGHTER_MARIO_INSTANCE_WORK_ID_INT_SPECIAL_LW_CHARGE, - ); - charge_state.int_x(my_charge) - } - // Donkey Kong Giant Punch - else if fighter_kind == FIGHTER_KIND_DONKEY { - let my_charge = WorkModule::get_int( - module_accessor, - *FIGHTER_DONKEY_INSTANCE_WORK_ID_INT_SPECIAL_N_COUNT, - ); - charge_state.int_x(my_charge) - } - // Samus/Dark Samus Charge Shot - else if fighter_kind == FIGHTER_KIND_SAMUS || fighter_kind == FIGHTER_KIND_SAMUSD { - let my_charge = WorkModule::get_int( - module_accessor, - *FIGHTER_SAMUS_INSTANCE_WORK_ID_INT_SPECIAL_N_COUNT, - ); - charge_state.int_x(my_charge) - } - // Sheik Needles - else if fighter_kind == FIGHTER_KIND_SHEIK { - let my_charge = WorkModule::get_int( - module_accessor, - *FIGHTER_SHEIK_INSTANCE_WORK_ID_INT_NEEDLE_COUNT, - ); - charge_state.int_x(my_charge) - } - // Mewtwo Shadowball - else if fighter_kind == FIGHTER_KIND_MEWTWO { - let my_charge = WorkModule::get_int( - module_accessor, - *FIGHTER_MEWTWO_INSTANCE_WORK_ID_INT_SHADOWBALL_CHARGE_FRAME, - ); - let prev_frame = WorkModule::get_int( - module_accessor, - *FIGHTER_MEWTWO_INSTANCE_WORK_ID_INT_PREV_SHADOWBALL_CHARGE_FRAME, - ); - let ball_had = WorkModule::is_flag( - module_accessor, - *FIGHTER_MEWTWO_INSTANCE_WORK_ID_FLAG_SHADOWBALL_HAD, - ); - charge_state - .int_x(my_charge) - .int_y(prev_frame) - .has_charge(ball_had) - } - // Game and Watch Bucket - else if fighter_kind == FIGHTER_KIND_GAMEWATCH { - let my_charge = WorkModule::get_float( - module_accessor, - *FIGHTER_GAMEWATCH_INSTANCE_WORK_ID_FLOAT_SPECIAL_LW_GAUGE, - ); - let my_attack = WorkModule::get_float( - module_accessor, - *FIGHTER_GAMEWATCH_INSTANCE_WORK_ID_FLOAT_SPECIAL_LW_ATTACK, - ); - charge_state.float_x(my_charge).float_y(my_attack) - } - // Wario Waft - else if fighter_kind == FIGHTER_KIND_WARIO { - let my_charge = WorkModule::get_int(module_accessor, 0x100000BF); // FIGHTER_WARIO_INSTANCE_WORK_ID_INT_GASS_COUNT - charge_state.int_x(my_charge) - } - // Squirtle Water Gun - else if fighter_kind == FIGHTER_KIND_PZENIGAME { - let my_charge = WorkModule::get_int( - module_accessor, - *FIGHTER_PZENIGAME_INSTANCE_WORK_ID_INT_SPECIAL_N_CHARGE, - ); - charge_state.int_x(my_charge) - } - // Lucario Aura Sphere - else if fighter_kind == FIGHTER_KIND_LUCARIO { - let my_charge = WorkModule::get_int( - module_accessor, - *FIGHTER_LUCARIO_INSTANCE_WORK_ID_INT_AURABALL_CHARGE_FRAME, - ); - let prev_frame = WorkModule::get_int( - module_accessor, - *FIGHTER_LUCARIO_INSTANCE_WORK_ID_INT_PREV_AURABALL_CHARGE_FRAME, - ); - let ball_had = WorkModule::is_flag( - module_accessor, - *FIGHTER_LUCARIO_INSTANCE_WORK_ID_FLAG_AURABALL_HAD, - ); - charge_state - .int_x(my_charge) - .int_y(prev_frame) - .has_charge(ball_had) - } - // ROB Gyro/Laser/Fuel - else if fighter_kind == FIGHTER_KIND_ROBOT { - let laser_charge = WorkModule::get_float( - module_accessor, - *FIGHTER_ROBOT_INSTANCE_WORK_ID_FLOAT_BEAM_ENERGY_VALUE, - ); - let gyro_charge = WorkModule::get_float( - module_accessor, - *FIGHTER_ROBOT_INSTANCE_WORK_ID_FLOAT_GYRO_CHARGE_VALUE, - ); - let fuel_charge = WorkModule::get_float( - module_accessor, - *FIGHTER_ROBOT_INSTANCE_WORK_ID_FLOAT_BURNER_ENERGY_VALUE, - ); - charge_state - .float_x(laser_charge) - .float_y(gyro_charge) - .float_z(fuel_charge) - } - // Wii Fit Sun Salutation - else if fighter_kind == FIGHTER_KIND_WIIFIT { - let my_charge = WorkModule::get_float( - module_accessor, - *FIGHTER_WIIFIT_INSTANCE_WORK_ID_FLOAT_SPECIAL_N_CHARGE_LEVEL_RATIO, - ); - charge_state.float_x(my_charge) - } - // Pac-Man Bonus Fruit - else if fighter_kind == FIGHTER_KIND_PACMAN { - let my_charge = WorkModule::get_int(module_accessor, 0x100000C1); // FIGHTER_PACMAN_INSTANCE_WORK_ID_INT_SPECIAL_N_CHARGE_RANK - let fruit_have = WorkModule::is_flag( - module_accessor, - *FIGHTER_PACMAN_INSTANCE_WORK_ID_FLAG_SPECIAL_N_PULL_THROW, - ); - charge_state.int_x(my_charge).has_charge(fruit_have) - } - // Robin Thunder Tome Spells - else if fighter_kind == FIGHTER_KIND_REFLET { - let my_charge = WorkModule::get_int( - module_accessor, - *FIGHTER_REFLET_INSTANCE_WORK_ID_INT_SPECIAL_N_THUNDER_KIND, - ); - charge_state.int_x(my_charge) - } - // Plant Poison Breath - else if fighter_kind == FIGHTER_KIND_PACKUN { - let my_charge = WorkModule::get_int( - module_accessor, - *FIGHTER_PACKUN_INSTANCE_WORK_ID_INT_SPECIAL_S_COUNT, - ); - charge_state.int_x(my_charge) - } - // Hero (Ka)frizz(le) - else if fighter_kind == FIGHTER_KIND_BRAVE { - let my_charge = WorkModule::get_int( - module_accessor, - *FIGHTER_BRAVE_INSTANCE_WORK_ID_INT_SPECIAL_N_HOLD_FRAME, - ); - charge_state.int_x(my_charge) - } - // Banjo Wonderwing - else if fighter_kind == FIGHTER_KIND_BUDDY { - let my_charge = WorkModule::get_int( - module_accessor, - *FIGHTER_BUDDY_INSTANCE_WORK_ID_INT_SPECIAL_S_REMAIN, - ); - charge_state.int_x(my_charge) - } - // Mii Gunner Charge Blast - else if fighter_kind == FIGHTER_KIND_MIIGUNNER { - let my_charge = WorkModule::get_int( - module_accessor, - *FIGHTER_MIIGUNNER_INSTANCE_WORK_ID_INT_GUNNER_CHARGE_COUNT, - ); - charge_state.int_x(my_charge) - } else { - charge_state - } -} - -pub unsafe fn handle_charge( - module_accessor: &mut app::BattleObjectModuleAccessor, - fighter_kind: i32, - charge: ChargeState, -) { - // Mario Fludd - 0 to 80 - if fighter_kind == FIGHTER_KIND_MARIO { - charge.int_x.map(|fludd_charge| { - WorkModule::set_int( - module_accessor, - fludd_charge, - *FIGHTER_MARIO_INSTANCE_WORK_ID_INT_SPECIAL_LW_CHARGE, - ); - if fludd_charge == 80 { - EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); - } - }); - } - // DK Punch - 0 to 110 - else if fighter_kind == FIGHTER_KIND_DONKEY { - charge.int_x.map(|punch_charge| { - WorkModule::set_int( - module_accessor, - punch_charge, - *FIGHTER_DONKEY_INSTANCE_WORK_ID_INT_SPECIAL_N_COUNT, - ); - if punch_charge == 110 { - FighterUtil::set_face_motion_by_priority( - module_accessor, - FighterFacial(*FIGHTER_FACIAL_SPECIAL), - Hash40::new("special_n_max_face"), - ); - } - }); - } - // Samus/Dark Samus Charge Shot - 0 to 112 - else if fighter_kind == FIGHTER_KIND_SAMUS || fighter_kind == FIGHTER_KIND_SAMUSD { - charge.int_x.map(|shot_charge| { - WorkModule::set_int( - module_accessor, - shot_charge, - *FIGHTER_SAMUS_INSTANCE_WORK_ID_INT_SPECIAL_N_COUNT, - ); - if shot_charge == 112 { - EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); - let samus_cshot_hash = if fighter_kind == FIGHTER_KIND_SAMUS { - Hash40::new("samus_cshot_max") - } else { - Hash40::new("samusd_cshot_max") - }; - let joint_hash = Hash40::new("armr"); - let pos = Vector3f { - x: 7.98004, - y: -0.50584, - z: -0.25092, - }; - let rot = Vector3f { - x: -91.2728, - y: -1.7974, - z: 176.373, - }; - let efh = EffectModule::req_follow( - module_accessor, - samus_cshot_hash, - joint_hash, - &pos, - &rot, - 1.0, - false, - 0, - 0, - 0, - 0, - 0, - false, - false, - ); - WorkModule::set_int( - module_accessor, - efh as i32, - *FIGHTER_SAMUS_INSTANCE_WORK_ID_INT_EFH_CHARGE_MAX, - ); - } - }); - } - // Sheik Needles - 0 to 6 - else if fighter_kind == FIGHTER_KIND_SHEIK { - charge.int_x.map(|needle_charge| { - WorkModule::set_int( - module_accessor, - needle_charge, - *FIGHTER_SHEIK_INSTANCE_WORK_ID_INT_NEEDLE_COUNT, - ); - ArticleModule::generate_article_enable( - module_accessor, - *FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, - false, - -1, - ); - let hash_main = Hash40::new("set_main"); - match needle_charge { - 6 => { - EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); - ArticleModule::set_visibility( - module_accessor, - *FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, - hash_main, - Hash40::new("group_main_default"), - ArticleOperationTarget(0), - ); - } - 5 => { - ArticleModule::set_visibility( - module_accessor, - *FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, - hash_main, - Hash40::new("group_main_5"), - ArticleOperationTarget(0), - ); - } - 4 => { - ArticleModule::set_visibility( - module_accessor, - *FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, - hash_main, - Hash40::new("group_main_4"), - ArticleOperationTarget(0), - ); - } - 3 => { - ArticleModule::set_visibility( - module_accessor, - *FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, - hash_main, - Hash40::new("group_main_3"), - ArticleOperationTarget(0), - ); - } - 2 => { - ArticleModule::set_visibility( - module_accessor, - *FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, - hash_main, - Hash40::new("group_main_2"), - ArticleOperationTarget(0), - ); - } - 1 => { - ArticleModule::set_visibility( - module_accessor, - *FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, - hash_main, - Hash40::new("group_main_1"), - ArticleOperationTarget(0), - ); - } - _ => { - ArticleModule::set_visibility( - module_accessor, - *FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, - hash_main, - Hash40::new("group_main_0"), - ArticleOperationTarget(0), - ); - } - } - }); - } - // Mewtwo Shadowball - 0 to 120, Boolean - else if fighter_kind == FIGHTER_KIND_MEWTWO { - charge.int_x.map(|charge_frame| { - WorkModule::set_int( - module_accessor, - charge_frame, - *FIGHTER_MEWTWO_INSTANCE_WORK_ID_INT_SHADOWBALL_CHARGE_FRAME, - ); - }); - charge.int_y.map(|prev_frame| { - WorkModule::set_int( - module_accessor, - prev_frame, - *FIGHTER_MEWTWO_INSTANCE_WORK_ID_INT_PREV_SHADOWBALL_CHARGE_FRAME, - ); - if prev_frame == 120 { - EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); - let effect_hash = Hash40::new("mewtwo_shadowball_max_hand"); - let joint_hash_1 = Hash40::new("handl"); - let joint_hash_2 = Hash40::new("handr"); - let pos = Vector3f { - x: 1.0, - y: 0.5, - z: 0.0, - }; - let rot = Vector3f { - x: 0.0, - y: 0.0, - z: 0.0, - }; - let efh_1 = EffectModule::req_follow( - module_accessor, - effect_hash, - joint_hash_1, - &pos, - &rot, - 1.0, - false, - 0, - 0, - -1, - 0, - 0, - false, - false, - ); - let efh_2 = EffectModule::req_follow( - module_accessor, - effect_hash, - joint_hash_2, - &pos, - &rot, - 1.0, - false, - 0, - 0, - -1, - 0, - 0, - false, - false, - ); - WorkModule::set_int( - module_accessor, - efh_1 as i32, - *FIGHTER_MEWTWO_INSTANCE_WORK_ID_INT_EF_ID_SHADOWBALL_MAX_L, - ); - WorkModule::set_int( - module_accessor, - efh_2 as i32, - *FIGHTER_MEWTWO_INSTANCE_WORK_ID_INT_EF_ID_SHADOWBALL_MAX_R, - ); - } - }); - charge.has_charge.map(|has_shadowball| { - WorkModule::set_flag( - module_accessor, - has_shadowball, - *FIGHTER_MEWTWO_INSTANCE_WORK_ID_FLAG_SHADOWBALL_HAD, - ); - }); - } - // GnW Bucket - 0 to 3, Attack not tested - else if fighter_kind == FIGHTER_KIND_GAMEWATCH { - charge.float_x.map(|bucket_level| { - WorkModule::set_float( - module_accessor, - bucket_level, - *FIGHTER_GAMEWATCH_INSTANCE_WORK_ID_FLOAT_SPECIAL_LW_GAUGE, - ); - if bucket_level == 3.0 { - EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); - } else { - // GnW flashes when successfully bucketing, and it will persist if state is loaded during that time, so we remove it here - EffectModule::remove_common(module_accessor, Hash40::new("charge_max")); - } - }); - charge.float_y.map(|bucket_attack| { - WorkModule::set_float( - module_accessor, - bucket_attack, - *FIGHTER_GAMEWATCH_INSTANCE_WORK_ID_FLOAT_SPECIAL_LW_ATTACK, - ); - }); - } - // Wario Waft - 0 to 6000 - else if fighter_kind == FIGHTER_KIND_WARIO { - charge.int_x.map(|waft_count| { - WorkModule::set_int(module_accessor, waft_count, 0x100000BF); // FIGHTER_WARIO_INSTANCE_WORK_ID_INT_GASS_COUNT - }); - } - // Squirtle Water Gun - 0 to 45 - else if fighter_kind == FIGHTER_KIND_PZENIGAME { - charge.int_x.map(|water_charge| { - WorkModule::set_int( - module_accessor, - water_charge, - *FIGHTER_PZENIGAME_INSTANCE_WORK_ID_INT_SPECIAL_N_CHARGE, - ); - if water_charge == 45 { - EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); - } - }); - } - // Lucario Aura Sphere - 0 to 90, Boolean - else if fighter_kind == FIGHTER_KIND_LUCARIO { - charge.int_x.map(|charge_frame| { - WorkModule::set_int( - module_accessor, - charge_frame, - *FIGHTER_LUCARIO_INSTANCE_WORK_ID_INT_AURABALL_CHARGE_FRAME, - ); - }); - charge.int_y.map(|prev_frame| { - WorkModule::set_int( - module_accessor, - prev_frame, - *FIGHTER_LUCARIO_INSTANCE_WORK_ID_INT_PREV_AURABALL_CHARGE_FRAME, - ); - if prev_frame == 90 { - EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); - let effect_hash_1 = Hash40::new("lucario_hadoudan_max_l"); - let effect_hash_2 = Hash40::new("lucario_hadoudan_max_r"); - let joint_hash_1 = Hash40::new("handl"); - let joint_hash_2 = Hash40::new("handr"); - let pos = Vector3f { - x: 0.0, - y: 0.0, - z: 0.0, - }; - let rot = Vector3f { - x: 0.0, - y: 0.0, - z: 0.0, - }; - let efh_1 = EffectModule::req_follow( - module_accessor, - effect_hash_1, - joint_hash_1, - &pos, - &rot, - 1.0, - false, - 0, - 0, - -1, - 0, - 0, - false, - false, - ); - let efh_2 = EffectModule::req_follow( - module_accessor, - effect_hash_2, - joint_hash_2, - &pos, - &rot, - 1.0, - false, - 0, - 0, - -1, - 0, - 0, - false, - false, - ); - WorkModule::set_int( - module_accessor, - efh_1 as i32, - *FIGHTER_LUCARIO_INSTANCE_WORK_ID_INT_EF_ID_AURABALL_MAX_L, - ); - WorkModule::set_int( - module_accessor, - efh_2 as i32, - *FIGHTER_LUCARIO_INSTANCE_WORK_ID_INT_EF_ID_AURABALL_MAX_R, - ); - } - }); - charge.has_charge.map(|has_aurasphere| { - WorkModule::set_flag( - module_accessor, - has_aurasphere, - *FIGHTER_LUCARIO_INSTANCE_WORK_ID_FLAG_AURABALL_HAD, - ); - }); - } - // ROB Gyro/Laser/Fuel - Gyro from 0 to 90, rest unchecked - else if fighter_kind == FIGHTER_KIND_ROBOT { - charge.float_x.map(|beam_energy| { - WorkModule::set_float( - module_accessor, - beam_energy, - *FIGHTER_ROBOT_INSTANCE_WORK_ID_FLOAT_BEAM_ENERGY_VALUE, - ); - }); - charge.float_y.map(|gyro_charge| { - WorkModule::set_float( - module_accessor, - gyro_charge, - *FIGHTER_ROBOT_INSTANCE_WORK_ID_FLOAT_GYRO_CHARGE_VALUE, - ); - if gyro_charge == 90.0 { - EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); - } - }); - charge.float_z.map(|burner_energy| { - WorkModule::set_float( - module_accessor, - burner_energy, - *FIGHTER_ROBOT_INSTANCE_WORK_ID_FLOAT_BURNER_ENERGY_VALUE, - ); - }); - } - // Wii Fit Sun Salutation - 0 to 1 - else if fighter_kind == FIGHTER_KIND_WIIFIT { - charge.float_x.map(|sun_ratio| { - WorkModule::set_float( - module_accessor, - sun_ratio, - *FIGHTER_WIIFIT_INSTANCE_WORK_ID_FLOAT_SPECIAL_N_CHARGE_LEVEL_RATIO, - ) - }); - } - // Pac-Man Bonus Fruit - 0 to 12 - else if fighter_kind == FIGHTER_KIND_PACMAN { - let mut has_key = false; - charge.int_x.map(|charge_rank| { - WorkModule::set_int(module_accessor, charge_rank, 0x100000C1); // FIGHTER_PACMAN_INSTANCE_WORK_ID_INT_SPECIAL_N_CHARGE_RANK - - if charge_rank == 12 { - EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); - has_key = true; - } - }); - charge.has_charge.map(|has_fruit| { - WorkModule::set_flag( - module_accessor, - has_fruit, - *FIGHTER_PACMAN_INSTANCE_WORK_ID_FLAG_SPECIAL_N_PULL_THROW, - ); - if has_key { - WorkModule::set_flag( - module_accessor, - has_key, - *FIGHTER_PACMAN_INSTANCE_WORK_ID_FLAG_SPECIAL_N_MAX_HAVE_ITEM, - ); - } - }); - } - // Robin Thunder Tome Spells - 0 to 3 - else if fighter_kind == FIGHTER_KIND_REFLET { - charge.int_x.map(|thunder_kind| { - WorkModule::set_int( - module_accessor, - thunder_kind, - *FIGHTER_REFLET_INSTANCE_WORK_ID_INT_SPECIAL_N_THUNDER_KIND, - ); - if thunder_kind == 3 { - EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); - let reflet_hash = Hash40::new("reflet_thunder_max"); - let joint_hash = Hash40::new("handl"); - let pos = Vector3f { - x: 1.0, - y: 2.0, - z: 0.0, - }; - let rot = Vector3f { - x: 0.0, - y: 0.0, - z: 0.0, - }; - EffectModule::req_follow( - module_accessor, - reflet_hash, - joint_hash, - &pos, - &rot, - 1.0, - false, - 0, - 0, - -1, - 0, - 0, - false, - false, - ); - } - }); - } - // Mii Gunner Charge Blast - 0 to 120 - else if fighter_kind == FIGHTER_KIND_MIIGUNNER { - charge.int_x.map(|blast_charge| { - WorkModule::set_int( - module_accessor, - blast_charge, - *FIGHTER_MIIGUNNER_INSTANCE_WORK_ID_INT_GUNNER_CHARGE_COUNT, - ); - if blast_charge == 120 { - EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); - let gunner_hash = Hash40::new("miigunner_cshot_max"); - let joint_hash = Hash40::new("armr"); - let pos = Vector3f { - x: 6.0, - y: 0.0, - z: 0.0, - }; - let rot = Vector3f { - x: 0.0, - y: 0.0, - z: 0.0, - }; - let efh = EffectModule::req_follow( - module_accessor, - gunner_hash, - joint_hash, - &pos, - &rot, - 1.0, - false, - 0, - 0, - 0, - 0, - 0, - false, - false, - ); - WorkModule::set_int( - module_accessor, - efh as i32, - *FIGHTER_MIIGUNNER_INSTANCE_WORK_ID_INT_EFH_CHARGE_MAX, - ); - } - }); - } - // Plant Poison - 0 to 75 - else if fighter_kind == FIGHTER_KIND_PACKUN { - charge.int_x.map(|poison_count| { - WorkModule::set_int( - module_accessor, - poison_count, - *FIGHTER_PACKUN_INSTANCE_WORK_ID_INT_SPECIAL_S_COUNT, - ); - if poison_count == 75 { - EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); - let plant_hash = Hash40::new("packun_poison_max_smoke"); - let joint_hash = Hash40::new("hip"); - let pos = Vector3f { - x: 0.0, - y: 0.0, - z: 0.0, - }; - let rot = Vector3f { - x: 0.0, - y: 0.0, - z: 0.0, - }; - let efh = EffectModule::req_follow( - module_accessor, - plant_hash, - joint_hash, - &pos, - &rot, - 1.0, - false, - 32768, - 0, - -1, - 0, - 0, - false, - false, - ); - WorkModule::set_int( - module_accessor, - efh as i32, - *FIGHTER_PACKUN_INSTANCE_WORK_ID_INT_SPECIAL_S_CHARGE_MAX_EFFECT_HANDLE, - ); - } - }); - } - // Hero (Ka)frizz(le) - 0 to 81 - else if fighter_kind == FIGHTER_KIND_BRAVE { - EffectModule::remove_common(module_accessor, Hash40::new("charge_max")); - WorkModule::off_flag(module_accessor, 0x200000E8); // FIGHTER_BRAVE_INSTANCE_WORK_ID_FLAG_SPECIAL_N_MAX_EFFECT - charge.int_x.map(|frizz_charge| { - WorkModule::set_int( - module_accessor, - frizz_charge, - *FIGHTER_BRAVE_INSTANCE_WORK_ID_INT_SPECIAL_N_HOLD_FRAME, - ); - }); - } - // Banjo Wonderwing - 0 to 5 - else if fighter_kind == FIGHTER_KIND_BUDDY { - charge.int_x.map(|wing_remain| { - WorkModule::set_int( - module_accessor, - wing_remain, - *FIGHTER_BUDDY_INSTANCE_WORK_ID_INT_SPECIAL_S_REMAIN, - ); - }); - } -} +use serde::{Deserialize, Serialize}; +use smash::app::{self, ArticleOperationTarget, FighterFacial, FighterUtil, lua_bind::*}; +use smash::lib::lua_const::*; +use smash::phx::{Hash40, Vector3f}; + +#[derive(Serialize, Deserialize, Default, Copy, Clone, Debug)] +pub struct ChargeState { + pub int_x: Option, + pub int_y: Option, + pub float_x: Option, + pub float_y: Option, + pub float_z: Option, + pub has_charge: Option, +} + +impl ChargeState { + fn int_x(mut self, int_x: i32) -> Self { + self.int_x = Some(int_x); + self + } + + fn int_y(mut self, int_y: i32) -> Self { + self.int_y = Some(int_y); + self + } + + fn float_x(mut self, float_x: f32) -> Self { + self.float_x = Some(float_x); + self + } + + fn float_y(mut self, float_y: f32) -> Self { + self.float_y = Some(float_y); + self + } + + fn float_z(mut self, float_z: f32) -> Self { + self.float_z = Some(float_z); + self + } + + fn has_charge(mut self, has_charge: bool) -> Self { + self.has_charge = Some(has_charge); + self + } +} + +pub unsafe fn get_charge( + module_accessor: &mut app::BattleObjectModuleAccessor, + fighter_kind: i32, +) -> ChargeState { + let charge_state = ChargeState::default(); + // Mario FLUDD + if fighter_kind == FIGHTER_KIND_MARIO { + let my_charge = WorkModule::get_int( + module_accessor, + *FIGHTER_MARIO_INSTANCE_WORK_ID_INT_SPECIAL_LW_CHARGE, + ); + charge_state.int_x(my_charge) + } + // Donkey Kong Giant Punch + else if fighter_kind == FIGHTER_KIND_DONKEY { + let my_charge = WorkModule::get_int( + module_accessor, + *FIGHTER_DONKEY_INSTANCE_WORK_ID_INT_SPECIAL_N_COUNT, + ); + charge_state.int_x(my_charge) + } + // Samus/Dark Samus Charge Shot + else if fighter_kind == FIGHTER_KIND_SAMUS || fighter_kind == FIGHTER_KIND_SAMUSD { + let my_charge = WorkModule::get_int( + module_accessor, + *FIGHTER_SAMUS_INSTANCE_WORK_ID_INT_SPECIAL_N_COUNT, + ); + charge_state.int_x(my_charge) + } + // Sheik Needles + else if fighter_kind == FIGHTER_KIND_SHEIK { + let my_charge = WorkModule::get_int( + module_accessor, + *FIGHTER_SHEIK_INSTANCE_WORK_ID_INT_NEEDLE_COUNT, + ); + charge_state.int_x(my_charge) + } + // Mewtwo Shadowball + else if fighter_kind == FIGHTER_KIND_MEWTWO { + let my_charge = WorkModule::get_int( + module_accessor, + *FIGHTER_MEWTWO_INSTANCE_WORK_ID_INT_SHADOWBALL_CHARGE_FRAME, + ); + let prev_frame = WorkModule::get_int( + module_accessor, + *FIGHTER_MEWTWO_INSTANCE_WORK_ID_INT_PREV_SHADOWBALL_CHARGE_FRAME, + ); + let ball_had = WorkModule::is_flag( + module_accessor, + *FIGHTER_MEWTWO_INSTANCE_WORK_ID_FLAG_SHADOWBALL_HAD, + ); + charge_state + .int_x(my_charge) + .int_y(prev_frame) + .has_charge(ball_had) + } + // Game and Watch Bucket + else if fighter_kind == FIGHTER_KIND_GAMEWATCH { + let my_charge = WorkModule::get_float( + module_accessor, + *FIGHTER_GAMEWATCH_INSTANCE_WORK_ID_FLOAT_SPECIAL_LW_GAUGE, + ); + let my_attack = WorkModule::get_float( + module_accessor, + *FIGHTER_GAMEWATCH_INSTANCE_WORK_ID_FLOAT_SPECIAL_LW_ATTACK, + ); + charge_state.float_x(my_charge).float_y(my_attack) + } + // Wario Waft + else if fighter_kind == FIGHTER_KIND_WARIO { + let my_charge = WorkModule::get_int(module_accessor, 0x100000BF); // FIGHTER_WARIO_INSTANCE_WORK_ID_INT_GASS_COUNT + charge_state.int_x(my_charge) + } + // Squirtle Water Gun + else if fighter_kind == FIGHTER_KIND_PZENIGAME { + let my_charge = WorkModule::get_int( + module_accessor, + *FIGHTER_PZENIGAME_INSTANCE_WORK_ID_INT_SPECIAL_N_CHARGE, + ); + charge_state.int_x(my_charge) + } + // Lucario Aura Sphere + else if fighter_kind == FIGHTER_KIND_LUCARIO { + let my_charge = WorkModule::get_int( + module_accessor, + *FIGHTER_LUCARIO_INSTANCE_WORK_ID_INT_AURABALL_CHARGE_FRAME, + ); + let prev_frame = WorkModule::get_int( + module_accessor, + *FIGHTER_LUCARIO_INSTANCE_WORK_ID_INT_PREV_AURABALL_CHARGE_FRAME, + ); + let ball_had = WorkModule::is_flag( + module_accessor, + *FIGHTER_LUCARIO_INSTANCE_WORK_ID_FLAG_AURABALL_HAD, + ); + charge_state + .int_x(my_charge) + .int_y(prev_frame) + .has_charge(ball_had) + } + // ROB Gyro/Laser/Fuel + else if fighter_kind == FIGHTER_KIND_ROBOT { + let laser_charge = WorkModule::get_float( + module_accessor, + *FIGHTER_ROBOT_INSTANCE_WORK_ID_FLOAT_BEAM_ENERGY_VALUE, + ); + let gyro_charge = WorkModule::get_float( + module_accessor, + *FIGHTER_ROBOT_INSTANCE_WORK_ID_FLOAT_GYRO_CHARGE_VALUE, + ); + let fuel_charge = WorkModule::get_float( + module_accessor, + *FIGHTER_ROBOT_INSTANCE_WORK_ID_FLOAT_BURNER_ENERGY_VALUE, + ); + charge_state + .float_x(laser_charge) + .float_y(gyro_charge) + .float_z(fuel_charge) + } + // Wii Fit Sun Salutation + else if fighter_kind == FIGHTER_KIND_WIIFIT { + let my_charge = WorkModule::get_float( + module_accessor, + *FIGHTER_WIIFIT_INSTANCE_WORK_ID_FLOAT_SPECIAL_N_CHARGE_LEVEL_RATIO, + ); + charge_state.float_x(my_charge) + } + // Pac-Man Bonus Fruit + else if fighter_kind == FIGHTER_KIND_PACMAN { + let my_charge = WorkModule::get_int(module_accessor, 0x100000C1); // FIGHTER_PACMAN_INSTANCE_WORK_ID_INT_SPECIAL_N_CHARGE_RANK + let fruit_have = WorkModule::is_flag( + module_accessor, + *FIGHTER_PACMAN_INSTANCE_WORK_ID_FLAG_SPECIAL_N_PULL_THROW, + ); + charge_state.int_x(my_charge).has_charge(fruit_have) + } + // Robin Thunder Tome Spells + else if fighter_kind == FIGHTER_KIND_REFLET { + let my_charge = WorkModule::get_int( + module_accessor, + *FIGHTER_REFLET_INSTANCE_WORK_ID_INT_SPECIAL_N_THUNDER_KIND, + ); + charge_state.int_x(my_charge) + } + // Plant Poison Breath + else if fighter_kind == FIGHTER_KIND_PACKUN { + let my_charge = WorkModule::get_int( + module_accessor, + *FIGHTER_PACKUN_INSTANCE_WORK_ID_INT_SPECIAL_S_COUNT, + ); + charge_state.int_x(my_charge) + } + // Hero (Ka)frizz(le) + else if fighter_kind == FIGHTER_KIND_BRAVE { + let my_charge = WorkModule::get_int( + module_accessor, + *FIGHTER_BRAVE_INSTANCE_WORK_ID_INT_SPECIAL_N_HOLD_FRAME, + ); + charge_state.int_x(my_charge) + } + // Banjo Wonderwing + else if fighter_kind == FIGHTER_KIND_BUDDY { + let my_charge = WorkModule::get_int( + module_accessor, + *FIGHTER_BUDDY_INSTANCE_WORK_ID_INT_SPECIAL_S_REMAIN, + ); + charge_state.int_x(my_charge) + } + // Mii Gunner Charge Blast + else if fighter_kind == FIGHTER_KIND_MIIGUNNER { + let my_charge = WorkModule::get_int( + module_accessor, + *FIGHTER_MIIGUNNER_INSTANCE_WORK_ID_INT_GUNNER_CHARGE_COUNT, + ); + charge_state.int_x(my_charge) + } else { + charge_state + } +} + +pub unsafe fn handle_charge( + module_accessor: &mut app::BattleObjectModuleAccessor, + fighter_kind: i32, + charge: ChargeState, +) { + // Mario Fludd - 0 to 80 + if fighter_kind == FIGHTER_KIND_MARIO { + charge.int_x.map(|fludd_charge| { + WorkModule::set_int( + module_accessor, + fludd_charge, + *FIGHTER_MARIO_INSTANCE_WORK_ID_INT_SPECIAL_LW_CHARGE, + ); + if fludd_charge == 80 { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + } + }); + } + // DK Punch - 0 to 110 + else if fighter_kind == FIGHTER_KIND_DONKEY { + charge.int_x.map(|punch_charge| { + WorkModule::set_int( + module_accessor, + punch_charge, + *FIGHTER_DONKEY_INSTANCE_WORK_ID_INT_SPECIAL_N_COUNT, + ); + if punch_charge == 110 { + FighterUtil::set_face_motion_by_priority( + module_accessor, + FighterFacial(*FIGHTER_FACIAL_SPECIAL), + Hash40::new("special_n_max_face"), + ); + } + }); + } + // Samus/Dark Samus Charge Shot - 0 to 112 + else if fighter_kind == FIGHTER_KIND_SAMUS || fighter_kind == FIGHTER_KIND_SAMUSD { + charge.int_x.map(|shot_charge| { + WorkModule::set_int( + module_accessor, + shot_charge, + *FIGHTER_SAMUS_INSTANCE_WORK_ID_INT_SPECIAL_N_COUNT, + ); + if shot_charge == 112 { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + let samus_cshot_hash = if fighter_kind == FIGHTER_KIND_SAMUS { + Hash40::new("samus_cshot_max") + } else { + Hash40::new("samusd_cshot_max") + }; + let joint_hash = Hash40::new("armr"); + let pos = Vector3f { + x: 7.98004, + y: -0.50584, + z: -0.25092, + }; + let rot = Vector3f { + x: -91.2728, + y: -1.7974, + z: 176.373, + }; + let efh = EffectModule::req_follow( + module_accessor, + samus_cshot_hash, + joint_hash, + &pos, + &rot, + 1.0, + false, + 0, + 0, + 0, + 0, + 0, + false, + false, + ); + WorkModule::set_int( + module_accessor, + efh as i32, + *FIGHTER_SAMUS_INSTANCE_WORK_ID_INT_EFH_CHARGE_MAX, + ); + } + }); + } + // Sheik Needles - 0 to 6 + else if fighter_kind == FIGHTER_KIND_SHEIK { + charge.int_x.map(|needle_charge| { + WorkModule::set_int( + module_accessor, + needle_charge, + *FIGHTER_SHEIK_INSTANCE_WORK_ID_INT_NEEDLE_COUNT, + ); + ArticleModule::generate_article_enable( + module_accessor, + *FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, + false, + -1, + ); + let hash_main = Hash40::new("set_main"); + match needle_charge { + 6 => { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + ArticleModule::set_visibility( + module_accessor, + *FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, + hash_main, + Hash40::new("group_main_default"), + ArticleOperationTarget(0), + ); + } + 5 => { + ArticleModule::set_visibility( + module_accessor, + *FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, + hash_main, + Hash40::new("group_main_5"), + ArticleOperationTarget(0), + ); + } + 4 => { + ArticleModule::set_visibility( + module_accessor, + *FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, + hash_main, + Hash40::new("group_main_4"), + ArticleOperationTarget(0), + ); + } + 3 => { + ArticleModule::set_visibility( + module_accessor, + *FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, + hash_main, + Hash40::new("group_main_3"), + ArticleOperationTarget(0), + ); + } + 2 => { + ArticleModule::set_visibility( + module_accessor, + *FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, + hash_main, + Hash40::new("group_main_2"), + ArticleOperationTarget(0), + ); + } + 1 => { + ArticleModule::set_visibility( + module_accessor, + *FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, + hash_main, + Hash40::new("group_main_1"), + ArticleOperationTarget(0), + ); + } + _ => { + ArticleModule::set_visibility( + module_accessor, + *FIGHTER_SHEIK_GENERATE_ARTICLE_NEEDLEHAVE, + hash_main, + Hash40::new("group_main_0"), + ArticleOperationTarget(0), + ); + } + } + }); + } + // Mewtwo Shadowball - 0 to 120, Boolean + else if fighter_kind == FIGHTER_KIND_MEWTWO { + charge.int_x.map(|charge_frame| { + WorkModule::set_int( + module_accessor, + charge_frame, + *FIGHTER_MEWTWO_INSTANCE_WORK_ID_INT_SHADOWBALL_CHARGE_FRAME, + ); + }); + charge.int_y.map(|prev_frame| { + WorkModule::set_int( + module_accessor, + prev_frame, + *FIGHTER_MEWTWO_INSTANCE_WORK_ID_INT_PREV_SHADOWBALL_CHARGE_FRAME, + ); + if prev_frame == 120 { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + let effect_hash = Hash40::new("mewtwo_shadowball_max_hand"); + let joint_hash_1 = Hash40::new("handl"); + let joint_hash_2 = Hash40::new("handr"); + let pos = Vector3f { + x: 1.0, + y: 0.5, + z: 0.0, + }; + let rot = Vector3f { + x: 0.0, + y: 0.0, + z: 0.0, + }; + let efh_1 = EffectModule::req_follow( + module_accessor, + effect_hash, + joint_hash_1, + &pos, + &rot, + 1.0, + false, + 0, + 0, + -1, + 0, + 0, + false, + false, + ); + let efh_2 = EffectModule::req_follow( + module_accessor, + effect_hash, + joint_hash_2, + &pos, + &rot, + 1.0, + false, + 0, + 0, + -1, + 0, + 0, + false, + false, + ); + WorkModule::set_int( + module_accessor, + efh_1 as i32, + *FIGHTER_MEWTWO_INSTANCE_WORK_ID_INT_EF_ID_SHADOWBALL_MAX_L, + ); + WorkModule::set_int( + module_accessor, + efh_2 as i32, + *FIGHTER_MEWTWO_INSTANCE_WORK_ID_INT_EF_ID_SHADOWBALL_MAX_R, + ); + } + }); + charge.has_charge.map(|has_shadowball| { + WorkModule::set_flag( + module_accessor, + has_shadowball, + *FIGHTER_MEWTWO_INSTANCE_WORK_ID_FLAG_SHADOWBALL_HAD, + ); + }); + } + // GnW Bucket - 0 to 3, Attack not tested + else if fighter_kind == FIGHTER_KIND_GAMEWATCH { + charge.float_x.map(|bucket_level| { + WorkModule::set_float( + module_accessor, + bucket_level, + *FIGHTER_GAMEWATCH_INSTANCE_WORK_ID_FLOAT_SPECIAL_LW_GAUGE, + ); + if bucket_level == 3.0 { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + } else { + // GnW flashes when successfully bucketing, and it will persist if state is loaded during that time, so we remove it here + EffectModule::remove_common(module_accessor, Hash40::new("charge_max")); + } + }); + charge.float_y.map(|bucket_attack| { + WorkModule::set_float( + module_accessor, + bucket_attack, + *FIGHTER_GAMEWATCH_INSTANCE_WORK_ID_FLOAT_SPECIAL_LW_ATTACK, + ); + }); + } + // Wario Waft - 0 to 6000 + else if fighter_kind == FIGHTER_KIND_WARIO { + charge.int_x.map(|waft_count| { + WorkModule::set_int(module_accessor, waft_count, 0x100000BF); // FIGHTER_WARIO_INSTANCE_WORK_ID_INT_GASS_COUNT + }); + } + // Squirtle Water Gun - 0 to 45 + else if fighter_kind == FIGHTER_KIND_PZENIGAME { + charge.int_x.map(|water_charge| { + WorkModule::set_int( + module_accessor, + water_charge, + *FIGHTER_PZENIGAME_INSTANCE_WORK_ID_INT_SPECIAL_N_CHARGE, + ); + if water_charge == 45 { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + } + }); + } + // Lucario Aura Sphere - 0 to 90, Boolean + else if fighter_kind == FIGHTER_KIND_LUCARIO { + charge.int_x.map(|charge_frame| { + WorkModule::set_int( + module_accessor, + charge_frame, + *FIGHTER_LUCARIO_INSTANCE_WORK_ID_INT_AURABALL_CHARGE_FRAME, + ); + }); + charge.int_y.map(|prev_frame| { + WorkModule::set_int( + module_accessor, + prev_frame, + *FIGHTER_LUCARIO_INSTANCE_WORK_ID_INT_PREV_AURABALL_CHARGE_FRAME, + ); + if prev_frame == 90 { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + let effect_hash_1 = Hash40::new("lucario_hadoudan_max_l"); + let effect_hash_2 = Hash40::new("lucario_hadoudan_max_r"); + let joint_hash_1 = Hash40::new("handl"); + let joint_hash_2 = Hash40::new("handr"); + let pos = Vector3f { + x: 0.0, + y: 0.0, + z: 0.0, + }; + let rot = Vector3f { + x: 0.0, + y: 0.0, + z: 0.0, + }; + let efh_1 = EffectModule::req_follow( + module_accessor, + effect_hash_1, + joint_hash_1, + &pos, + &rot, + 1.0, + false, + 0, + 0, + -1, + 0, + 0, + false, + false, + ); + let efh_2 = EffectModule::req_follow( + module_accessor, + effect_hash_2, + joint_hash_2, + &pos, + &rot, + 1.0, + false, + 0, + 0, + -1, + 0, + 0, + false, + false, + ); + WorkModule::set_int( + module_accessor, + efh_1 as i32, + *FIGHTER_LUCARIO_INSTANCE_WORK_ID_INT_EF_ID_AURABALL_MAX_L, + ); + WorkModule::set_int( + module_accessor, + efh_2 as i32, + *FIGHTER_LUCARIO_INSTANCE_WORK_ID_INT_EF_ID_AURABALL_MAX_R, + ); + } + }); + charge.has_charge.map(|has_aurasphere| { + WorkModule::set_flag( + module_accessor, + has_aurasphere, + *FIGHTER_LUCARIO_INSTANCE_WORK_ID_FLAG_AURABALL_HAD, + ); + }); + } + // ROB Gyro/Laser/Fuel - Gyro from 0 to 90, rest unchecked + else if fighter_kind == FIGHTER_KIND_ROBOT { + charge.float_x.map(|beam_energy| { + WorkModule::set_float( + module_accessor, + beam_energy, + *FIGHTER_ROBOT_INSTANCE_WORK_ID_FLOAT_BEAM_ENERGY_VALUE, + ); + }); + charge.float_y.map(|gyro_charge| { + WorkModule::set_float( + module_accessor, + gyro_charge, + *FIGHTER_ROBOT_INSTANCE_WORK_ID_FLOAT_GYRO_CHARGE_VALUE, + ); + if gyro_charge == 90.0 { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + } + }); + charge.float_z.map(|burner_energy| { + WorkModule::set_float( + module_accessor, + burner_energy, + *FIGHTER_ROBOT_INSTANCE_WORK_ID_FLOAT_BURNER_ENERGY_VALUE, + ); + }); + } + // Wii Fit Sun Salutation - 0 to 1 + else if fighter_kind == FIGHTER_KIND_WIIFIT { + charge.float_x.map(|sun_ratio| { + WorkModule::set_float( + module_accessor, + sun_ratio, + *FIGHTER_WIIFIT_INSTANCE_WORK_ID_FLOAT_SPECIAL_N_CHARGE_LEVEL_RATIO, + ) + }); + } + // Pac-Man Bonus Fruit - 0 to 12 + else if fighter_kind == FIGHTER_KIND_PACMAN { + let mut has_key = false; + charge.int_x.map(|charge_rank| { + WorkModule::set_int(module_accessor, charge_rank, 0x100000C1); // FIGHTER_PACMAN_INSTANCE_WORK_ID_INT_SPECIAL_N_CHARGE_RANK + + if charge_rank == 12 { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + has_key = true; + } + }); + charge.has_charge.map(|has_fruit| { + WorkModule::set_flag( + module_accessor, + has_fruit, + *FIGHTER_PACMAN_INSTANCE_WORK_ID_FLAG_SPECIAL_N_PULL_THROW, + ); + if has_key { + WorkModule::set_flag( + module_accessor, + has_key, + *FIGHTER_PACMAN_INSTANCE_WORK_ID_FLAG_SPECIAL_N_MAX_HAVE_ITEM, + ); + } + }); + } + // Robin Thunder Tome Spells - 0 to 3 + else if fighter_kind == FIGHTER_KIND_REFLET { + charge.int_x.map(|thunder_kind| { + WorkModule::set_int( + module_accessor, + thunder_kind, + *FIGHTER_REFLET_INSTANCE_WORK_ID_INT_SPECIAL_N_THUNDER_KIND, + ); + if thunder_kind == 3 { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + let reflet_hash = Hash40::new("reflet_thunder_max"); + let joint_hash = Hash40::new("handl"); + let pos = Vector3f { + x: 1.0, + y: 2.0, + z: 0.0, + }; + let rot = Vector3f { + x: 0.0, + y: 0.0, + z: 0.0, + }; + EffectModule::req_follow( + module_accessor, + reflet_hash, + joint_hash, + &pos, + &rot, + 1.0, + false, + 0, + 0, + -1, + 0, + 0, + false, + false, + ); + } + }); + } + // Mii Gunner Charge Blast - 0 to 120 + else if fighter_kind == FIGHTER_KIND_MIIGUNNER { + charge.int_x.map(|blast_charge| { + WorkModule::set_int( + module_accessor, + blast_charge, + *FIGHTER_MIIGUNNER_INSTANCE_WORK_ID_INT_GUNNER_CHARGE_COUNT, + ); + if blast_charge == 120 { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + let gunner_hash = Hash40::new("miigunner_cshot_max"); + let joint_hash = Hash40::new("armr"); + let pos = Vector3f { + x: 6.0, + y: 0.0, + z: 0.0, + }; + let rot = Vector3f { + x: 0.0, + y: 0.0, + z: 0.0, + }; + let efh = EffectModule::req_follow( + module_accessor, + gunner_hash, + joint_hash, + &pos, + &rot, + 1.0, + false, + 0, + 0, + 0, + 0, + 0, + false, + false, + ); + WorkModule::set_int( + module_accessor, + efh as i32, + *FIGHTER_MIIGUNNER_INSTANCE_WORK_ID_INT_EFH_CHARGE_MAX, + ); + } + }); + } + // Plant Poison - 0 to 75 + else if fighter_kind == FIGHTER_KIND_PACKUN { + charge.int_x.map(|poison_count| { + WorkModule::set_int( + module_accessor, + poison_count, + *FIGHTER_PACKUN_INSTANCE_WORK_ID_INT_SPECIAL_S_COUNT, + ); + if poison_count == 75 { + EffectModule::req_common(module_accessor, Hash40::new("charge_max"), 0.0); + let plant_hash = Hash40::new("packun_poison_max_smoke"); + let joint_hash = Hash40::new("hip"); + let pos = Vector3f { + x: 0.0, + y: 0.0, + z: 0.0, + }; + let rot = Vector3f { + x: 0.0, + y: 0.0, + z: 0.0, + }; + let efh = EffectModule::req_follow( + module_accessor, + plant_hash, + joint_hash, + &pos, + &rot, + 1.0, + false, + 32768, + 0, + -1, + 0, + 0, + false, + false, + ); + WorkModule::set_int( + module_accessor, + efh as i32, + *FIGHTER_PACKUN_INSTANCE_WORK_ID_INT_SPECIAL_S_CHARGE_MAX_EFFECT_HANDLE, + ); + } + }); + } + // Hero (Ka)frizz(le) - 0 to 81 + else if fighter_kind == FIGHTER_KIND_BRAVE { + EffectModule::remove_common(module_accessor, Hash40::new("charge_max")); + WorkModule::off_flag(module_accessor, 0x200000E8); // FIGHTER_BRAVE_INSTANCE_WORK_ID_FLAG_SPECIAL_N_MAX_EFFECT + charge.int_x.map(|frizz_charge| { + WorkModule::set_int( + module_accessor, + frizz_charge, + *FIGHTER_BRAVE_INSTANCE_WORK_ID_INT_SPECIAL_N_HOLD_FRAME, + ); + }); + } + // Banjo Wonderwing - 0 to 5 + else if fighter_kind == FIGHTER_KIND_BUDDY { + charge.int_x.map(|wing_remain| { + WorkModule::set_int( + module_accessor, + wing_remain, + *FIGHTER_BUDDY_INSTANCE_WORK_ID_INT_SPECIAL_S_REMAIN, + ); + }); + } +} diff --git a/src/training/reset.rs b/src/training/reset.rs index deae88c..42eb0c9 100644 --- a/src/training/reset.rs +++ b/src/training/reset.rs @@ -1,50 +1,50 @@ -use smash::app::{self, lua_bind::*}; -use smash::lib::lua_const::*; - -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; - -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 smash::app::{self, lua_bind::*}; +use smash::lib::lua_const::*; + +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; + +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/throw.rs b/src/training/throw.rs index 6f5caff..a713eea 100644 --- a/src/training/throw.rs +++ b/src/training/throw.rs @@ -1,155 +1,155 @@ -use smash::app::{self, lua_bind::*}; -use smash::lib::lua_const::*; - -use crate::common::*; -use crate::common::consts::*; -use crate::training::frame_counter; -use crate::training::mash; - -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) != *FIGHTER_STATUS_KIND_CATCH_WAIT - && StatusModule::status_kind(module_accessor) != *FIGHTER_STATUS_KIND_CATCH_PULL - && StatusModule::status_kind(module_accessor) != *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) == *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; - } - - 0 -} +use smash::app::{self, lua_bind::*}; +use smash::lib::lua_const::*; + +use crate::common::*; +use crate::common::consts::*; +use crate::training::frame_counter; +use crate::training::mash; + +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) != *FIGHTER_STATUS_KIND_CATCH_WAIT + && StatusModule::status_kind(module_accessor) != *FIGHTER_STATUS_KIND_CATCH_PULL + && StatusModule::status_kind(module_accessor) != *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) == *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; + } + + 0 +} diff --git a/training_mod_consts/Cargo.toml b/training_mod_consts/Cargo.toml index 0066043..821b71e 100644 --- a/training_mod_consts/Cargo.toml +++ b/training_mod_consts/Cargo.toml @@ -1,22 +1,22 @@ -[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" -paste = "1.0" -serde = { version = "1.0", features = ["derive"] } -serde_repr = "0.1.8" -serde_json = "1" -bitflags_serde_shim = "0.2" -skyline_smash = { git = "https://github.com/ultimate-research/skyline-smash.git", optional = true } - -[features] -default = ["smash"] +[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" +paste = "1.0" +serde = { version = "1.0", features = ["derive"] } +serde_repr = "0.1.8" +serde_json = "1" +bitflags_serde_shim = "0.2" +skyline_smash = { git = "https://github.com/ultimate-research/skyline-smash.git", optional = true } + +[features] +default = ["smash"] smash = ["skyline_smash"] \ No newline at end of file diff --git a/training_mod_metrics/Cargo.toml b/training_mod_metrics/Cargo.toml index 4753903..cc97c69 100644 --- a/training_mod_metrics/Cargo.toml +++ b/training_mod_metrics/Cargo.toml @@ -1,12 +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" +[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 index 14db349..8617564 100644 --- a/training_mod_metrics/src/main.rs +++ b/training_mod_metrics/src/main.rs @@ -1,144 +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(()) +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 diff --git a/training_mod_tui/training_mod_tui.iml b/training_mod_tui/training_mod_tui.iml index 8f66a1c..2fecef3 100644 --- a/training_mod_tui/training_mod_tui.iml +++ b/training_mod_tui/training_mod_tui.iml @@ -1,12 +1,12 @@ - - - - - - - - - - - + + + + + + + + + + + \ No newline at end of file