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::<Vec<_>>()[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::<Vec<_>>()[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<i32>,
-    pub int_y: Option<i32>,
-    pub float_x: Option<f32>,
-    pub float_y: Option<f32>,
-    pub float_z: Option<f32>,
-    pub has_charge: Option<bool>,
-}
-
-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<i32>,
+    pub int_y: Option<i32>,
+    pub float_x: Option<f32>,
+    pub float_y: Option<f32>,
+    pub float_z: Option<f32>,
+    pub has_charge: Option<bool>,
+}
+
+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<Utc> {
-    DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(ts, 0), Utc)
-}
-
-use plotters::prelude::*;
-const OUT_FILE_NAME: &'static str = "boxplot.svg";
-fn draw_chart(results: Vec<RecordBatch>) -> Result<(), Box<dyn std::error::Error>> {
-    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::<datafusion::arrow::array::UInt64Array>()
-        .expect("Failed to downcast").values();
-    let num_sessions = results[0].column(num_sessions_idx).as_any()
-        .downcast_ref::<datafusion::arrow::array::UInt64Array>()
-        .expect("Failed to downcast").values();
-    let timestamp_millis = results[0].column(timestamps_idx).as_any()
-        .downcast_ref::<datafusion::arrow::array::TimestampMillisecondArray>()
-        .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<RecordBatch> = 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<Utc> {
+    DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(ts, 0), Utc)
+}
+
+use plotters::prelude::*;
+const OUT_FILE_NAME: &'static str = "boxplot.svg";
+fn draw_chart(results: Vec<RecordBatch>) -> Result<(), Box<dyn std::error::Error>> {
+    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::<datafusion::arrow::array::UInt64Array>()
+        .expect("Failed to downcast").values();
+    let num_sessions = results[0].column(num_sessions_idx).as_any()
+        .downcast_ref::<datafusion::arrow::array::UInt64Array>()
+        .expect("Failed to downcast").values();
+    let timestamp_millis = results[0].column(timestamps_idx).as_any()
+        .downcast_ref::<datafusion::arrow::array::TimestampMillisecondArray>()
+        .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<RecordBatch> = 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 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="RUST_MODULE" version="4">
-  <component name="NewModuleRootManager" inherit-compiler-output="true">
-    <exclude-output />
-    <content url="file://$MODULE_DIR$">
-      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
-      <excludeFolder url="file://$MODULE_DIR$/target" />
-    </content>
-    <orderEntry type="inheritedJdk" />
-    <orderEntry type="sourceFolder" forTests="false" />
-  </component>
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="RUST_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+      <excludeFolder url="file://$MODULE_DIR$/target" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
 </module>
\ No newline at end of file