diff --git a/.clang-format b/.clang-format index 7501bc99..f1a58223 100644 --- a/.clang-format +++ b/.clang-format @@ -1,3 +1,4 @@ +--- DisableFormat: false AlignAfterOpenBracket: BlockIndent AlignArrayOfStructures: Left @@ -36,7 +37,7 @@ PackConstructorInitializers: CurrentLine PenaltyReturnTypeOnItsOwnLine: 2000 PointerAlignment: Right QualifierAlignment: Custom -QualifierOrder: ['inline', 'static', 'const', 'constexpr', 'restrict', 'volatile', 'type'] +QualifierOrder: ['inline', 'static', 'constexpr', 'const', 'restrict', 'volatile', 'type'] ReferenceAlignment: Right ReflowComments: true SeparateDefinitionBlocks: Always @@ -57,3 +58,6 @@ SpaceBeforeCtorInitializerColon: true SpacesInContainerLiterals: false TabWidth: 4 UseTab: Never +--- +Language: Proto +ColumnLimit: 100 \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 9ac69999..f29baabe 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,12 +1,12 @@ { "name": "HayBox", - "image": "mcr.microsoft.com/devcontainers/base:ubuntu-22.04", + "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.11", "features": { "ghcr.io/rocker-org/devcontainer-features/apt-packages:1": { "packages": "python3-pip,python3-venv,clang-format-14" } }, - "onCreateCommand": "python3 -m pip install -U platformio && pio pkg install && pio pkg update -g -p https://github.com/maxgerhardt/platform-raspberrypi", + "onCreateCommand": "python3 -m pip install -U platformio && pio pkg install && pio pkg update -g -p https://github.com/maxgerhardt/platform-raspberrypi.git#5e87ae34ca025274df25b3303e9e9cb6c120123c", "customizations": { "vscode": { "settings": { diff --git a/.github/workflows/build-device-config.yml b/.github/workflows/build-device-config.yml new file mode 100644 index 00000000..541be7b5 --- /dev/null +++ b/.github/workflows/build-device-config.yml @@ -0,0 +1,86 @@ +name: Build device config + +on: [workflow_call] + +jobs: + metadata: + runs-on: ubuntu-latest + name: Parse config metadata + outputs: + meta_json: ${{ steps.parse_yaml.outputs.metadata }} + build_matrix: ${{ steps.parse_yaml.outputs.metadata.build }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install yaml2json + run: python3 -m pip install remarshal + + - name: Read metadata from yaml file + id: parse_yaml + run: | + echo "metadata=$(yaml2json 'meta.yaml')" >> "$GITHUB_OUTPUT" + + build: + runs-on: ubuntu-latest + permissions: + contents: write + needs: metadata + env: + HAYBOX_REPO: ${{ fromJson(needs.metadata.outputs.meta_json).repo }} + HAYBOX_REVISION: ${{ fromJson(needs.metadata.outputs.meta_json).revision }} + DEVICE_CONFIG_REVISION: ${{ github.ref_type == 'tag' && github.ref_name || github.sha }} + PIO_ENV: ${{ matrix.env }} + BIN_EXT: ${{ matrix.bin_ext }} + strategy: + fail-fast: false + matrix: + include: ${{ fromJson(needs.metadata.outputs.meta_json).build }} + + steps: + - name: Check out specific HayBox revision + run: | + git clone "https://github.com/$HAYBOX_REPO" . + git checkout "$HAYBOX_REVISION" + + - name: Check out config repo + uses: actions/checkout@v4 + with: + path: config/device + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install PlatformIO + run: | + python -m pip install --upgrade pip + pip install --upgrade platformio + + - name: Set artifact filename environment variable + run: | + echo "ARTIFACT_NAME=HayBox-${HAYBOX_REVISION}-${PIO_ENV}-${DEVICE_CONFIG_REVISION}.${BIN_EXT}" >> "$GITHUB_ENV" + + - name: Set artifact path environment variable + run: | + echo "ARTIFACT_PATH=${PIO_ENV}/${ARTIFACT_NAME}" >> "$GITHUB_ENV" + + - name: Build ${{ matrix.env }} env + run: | + pio run -e "$PIO_ENV" + mkdir -p "$PIO_ENV" + + cp ".pio/build/${PIO_ENV}/firmware.${BIN_EXT}" "$ARTIFACT_PATH" + + - name: Publish ${{ matrix.env }} artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ env.ARTIFACT_NAME }} + path: ${{ env.ARTIFACT_PATH }} + + - name: Upload binaries to release + uses: softprops/action-gh-release@v1 + if: github.ref_type == 'tag' + with: + files: ${{ env.ARTIFACT_PATH }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 04b5a3da..818d3d54 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,44 +9,23 @@ jobs: contents: write env: VERSION_REF: ${{ github.ref_type == 'tag' && github.ref_name || github.sha }} + PIO_ENV: ${{ matrix.env }} + BIN_EXT: ${{ matrix.bin_ext }} strategy: - matrix: - include: - - env: c53 - bin_ext: uf2 - - env: pico - bin_ext: uf2 - - env: arduino_uno - bin_ext: hex - - env: arduino_nano - bin_ext: hex - - env: arduino_mega - bin_ext: hex - - env: arduino_leonardo - bin_ext: hex - - env: arduino_micro - bin_ext: hex - - env: b0xx_r1 - bin_ext: hex - - env: b0xx_r2 - bin_ext: hex - - env: gccmx - bin_ext: hex - - env: gccpcb1 - bin_ext: hex - - env: gccpcb2 - bin_ext: hex - - env: lbx - bin_ext: hex - - env: smashbox - bin_ext: hex + fail-fast: false + matrix: + include: + - env: pico + bin_ext: uf2 + - env: schism + bin_ext: uf2 steps: - name: Check out source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.10' @@ -55,20 +34,28 @@ jobs: python -m pip install --upgrade pip pip install --upgrade platformio + - name: Set artifact filename environment variable + run: | + echo "ARTIFACT_NAME=HayBox-${VERSION_REF}-${PIO_ENV}.${BIN_EXT}" >> "$GITHUB_ENV" + + - name: Set artifact path environment variable + run: | + echo "ARTIFACT_PATH=${PIO_ENV}/${ARTIFACT_NAME}" >> "$GITHUB_ENV" + - name: Build ${{ matrix.env }} env run: | - pio run -e ${{ matrix.env }} - mkdir -p ${{ matrix.env }} - cp .pio/build/${{ matrix.env }}/firmware.${{ matrix.bin_ext }} ${{ matrix.env }}/HayBox-${{ env.VERSION_REF }}-${{ matrix.env }}.${{ matrix.bin_ext }} + pio run -e "$PIO_ENV" + mkdir -p "$PIO_ENV" + cp ".pio/build/${PIO_ENV}/firmware.${BIN_EXT}" "$ARTIFACT_PATH" - name: Publish ${{ matrix.env }} artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: HayBox-${{ env.VERSION_REF }}-${{ matrix.env }}.${{ matrix.bin_ext }} - path: ${{ matrix.env }}/HayBox-${{ env.VERSION_REF }}-${{ matrix.env }}.${{ matrix.bin_ext }} + name: ${{ env.ARTIFACT_NAME }} + path: ${{ env.ARTIFACT_PATH }} - name: Upload binaries to release uses: softprops/action-gh-release@v1 if: github.ref_type == 'tag' with: - files: ${{ matrix.env }}/HayBox-${{ env.VERSION_REF }}-${{ matrix.env }}.${{ matrix.bin_ext }} \ No newline at end of file + files: ${{ env.ARTIFACT_PATH }} diff --git a/.gitignore b/.gitignore index 89cc49cb..2b19f80e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ .vscode/c_cpp_properties.json .vscode/launch.json .vscode/ipch +.vscode/extensions.json +haybox-cli-v0.1.5.exe diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 0f0d7401..080e70d0 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,7 +1,10 @@ -{ - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "platformio.platformio-ide" - ] -} +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/HAL/avr/avr_nousb/include/comms/backend_init.hpp.bak b/HAL/avr/avr_nousb/include/comms/backend_init.hpp.bak new file mode 100644 index 00000000..0d778251 --- /dev/null +++ b/HAL/avr/avr_nousb/include/comms/backend_init.hpp.bak @@ -0,0 +1,39 @@ +#ifndef _COMMS_BACKEND_INIT_HPP +#define _COMMS_BACKEND_INIT_HPP + +#include "core/CommunicationBackend.hpp" +#include "core/InputSource.hpp" +#include "core/pinout.hpp" +#include "core/state.hpp" + +#include + +/** + * @brief Initialize the backends array and return the number of elements in the array + * + * @param backends The reference to assign to the created backends array + * @param inputs Reference to the InputState struct to pass into each backend's constructor + * @param input_sources Input sources array to pass into each backend's constructor + * @param input_source_count Number of elements in the input_sources array + * @param config Reference to global config struct + * @param pinout Pinout struct used for GameCube/N64 communication backends + * @return size_t The number of backends in the array + */ +size_t initialize_backends( + CommunicationBackend **&backends, + InputState &inputs, + InputSource **input_sources, + size_t input_source_count, + const Config &config, + const Pinout &pinout +); + +bool initialize_backends_custom( + CommunicationBackend **&backends, + InputSource **input_sources, + size_t input_source_count +) __attribute__((weak)); + +void select_backend_custom(CommunicationBackendConfig &backend_config) __attribute__((weak)); + +#endif \ No newline at end of file diff --git a/HAL/avr/avr_nousb/include/config_defaults.hpp b/HAL/avr/avr_nousb/include/config_defaults.hpp new file mode 100644 index 00000000..b1eec225 --- /dev/null +++ b/HAL/avr/avr_nousb/include/config_defaults.hpp @@ -0,0 +1,128 @@ +#ifndef _CONFIG_DEFAULTS_HPP +#define _CONFIG_DEFAULTS_HPP + +#include + +// clang-format off + +const Config default_config = { + .game_mode_configs_count = 4, + .game_mode_configs = new GameModeConfig[4] { + GameModeConfig { + .mode_id = MODE_MELEE, + .name = {}, + .socd_pairs_count = 4, + .socd_pairs = new SocdPair[4] { + SocdPair { .button_dir1 = BTN_LF3, .button_dir2 = BTN_LF1, .socd_type = SOCD_2IP_NO_REAC }, + SocdPair { .button_dir1 = BTN_LF2, .button_dir2 = BTN_RF4, .socd_type = SOCD_2IP_NO_REAC }, + SocdPair { .button_dir1 = BTN_RT3, .button_dir2 = BTN_RT5, .socd_type = SOCD_2IP_NO_REAC }, + SocdPair { .button_dir1 = BTN_RT2, .button_dir2 = BTN_RT4, .socd_type = SOCD_2IP_NO_REAC }, + }, + .button_remapping_count = 0, + .button_remapping = {}, + .activation_binding_count = 3, + .activation_binding = new Button[3] { BTN_LT1, BTN_MB1, BTN_LF4 }, + .custom_mode_config = 0, + .keyboard_mode_config = 0, + .rgb_config = 0, + }, + GameModeConfig { + .mode_id = MODE_PROJECT_M, + .name = {}, + .socd_pairs_count = 4, + .socd_pairs = new SocdPair[4] { + SocdPair { .button_dir1 = BTN_LF3, .button_dir2 = BTN_LF1, .socd_type = SOCD_2IP_NO_REAC }, + SocdPair { .button_dir1 = BTN_LF2, .button_dir2 = BTN_RF4, .socd_type = SOCD_2IP_NO_REAC }, + SocdPair { .button_dir1 = BTN_RT3, .button_dir2 = BTN_RT5, .socd_type = SOCD_2IP_NO_REAC }, + SocdPair { .button_dir1 = BTN_RT2, .button_dir2 = BTN_RT4, .socd_type = SOCD_2IP_NO_REAC }, + }, + .button_remapping_count = 0, + .button_remapping = {}, + .activation_binding_count = 3, + .activation_binding = new Button[3] { BTN_LT1, BTN_MB1, BTN_LF3 }, + .custom_mode_config = 0, + .keyboard_mode_config = 0, + .rgb_config = 0, + }, + GameModeConfig { + .mode_id = MODE_ULTIMATE, + .name = {}, + .socd_pairs_count = 4, + .socd_pairs = new SocdPair[4] { + SocdPair { .button_dir1 = BTN_LF3, .button_dir2 = BTN_LF1, .socd_type = SOCD_2IP }, + SocdPair { .button_dir1 = BTN_LF2, .button_dir2 = BTN_RF4, .socd_type = SOCD_2IP }, + SocdPair { .button_dir1 = BTN_RT3, .button_dir2 = BTN_RT5, .socd_type = SOCD_2IP }, + SocdPair { .button_dir1 = BTN_RT2, .button_dir2 = BTN_RT4, .socd_type = SOCD_2IP }, + }, + .button_remapping_count = 0, + .button_remapping = {}, + .activation_binding_count = 3, + .activation_binding = new Button[3] { BTN_LT1, BTN_MB1, BTN_LF2 }, + .custom_mode_config = 0, + .keyboard_mode_config = 0, + .rgb_config = 0, + }, + GameModeConfig { + .mode_id = MODE_FGC, + .name = {}, + .socd_pairs_count = 2, + .socd_pairs = new SocdPair[2] { + SocdPair { .button_dir1 = BTN_LF3, .button_dir2 = BTN_LF1, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_LT1, .button_dir2 = BTN_RT4, .socd_type = SOCD_NEUTRAL }, + }, + .button_remapping_count = 1, + .button_remapping = new ButtonRemap[1] { + ButtonRemap { .physical_button = BTN_RT4, .activates = BTN_LT1 }, + }, + .activation_binding_count = 3, + .activation_binding = new Button[3] { BTN_LT1, BTN_MB1, BTN_LF1 }, + .custom_mode_config = 0, + .keyboard_mode_config = 0, + .rgb_config = 0, + }, + }, + .communication_backend_configs_count = 2, + .communication_backend_configs = new CommunicationBackendConfig[2] { + CommunicationBackendConfig { + .backend_id = COMMS_BACKEND_GAMECUBE, + .default_mode_config = 1, + .activation_binding_count = 1, + .activation_binding = new Button[1] { BTN_RT1 }, + .secondary_backends = {}, + }, + CommunicationBackendConfig { + .backend_id = COMMS_BACKEND_N64, + .default_mode_config = 1, + .activation_binding_count = 1, + .activation_binding = new Button[1] { BTN_RT3 }, + .secondary_backends = {}, + }, + }, + .custom_modes_count = 0, + .custom_modes = {}, + .keyboard_modes_count = 0, + .keyboard_modes = {}, + .rgb_configs_count = 0, + .rgb_configs = {}, + .default_backend_config = 1, + .default_usb_backend_config = 1, + .rgb_brightness = 0, + .has_melee_options = true, + .melee_options = { + .crouch_walk_os = false, + .disable_ledgedash_socd_override = false, + .has_custom_airdodge = false, + .custom_airdodge = { .x = 0, .y = 0 }, + }, + .has_project_m_options = true, + .project_m_options = { + .true_z_press = true, + .disable_ledgedash_socd_override = false, + .has_custom_airdodge = false, + .custom_airdodge = { .x = 0, .y = 0 }, + }, +}; + +// clang-format on + +#endif diff --git a/HAL/avr/avr_nousb/include/core/KeyboardMode.hpp b/HAL/avr/avr_nousb/include/core/KeyboardMode.hpp index 7c0b7c8f..8d816c8f 100644 --- a/HAL/avr/avr_nousb/include/core/KeyboardMode.hpp +++ b/HAL/avr/avr_nousb/include/core/KeyboardMode.hpp @@ -10,13 +10,15 @@ class KeyboardMode : public InputMode { public: KeyboardMode(); ~KeyboardMode(); - void SendReport(InputState &inputs); + void SendReport(const InputState &inputs); + + void UpdateOutputs(const InputState &inputs, OutputState &outputs) {} protected: void Press(uint8_t keycode, bool press); private: - virtual void UpdateKeys(InputState &inputs) = 0; + virtual void UpdateKeys(const InputState &inputs) = 0; }; #endif diff --git a/HAL/avr/avr_nousb/src/comms/backend_init.cpp b/HAL/avr/avr_nousb/src/comms/backend_init.cpp new file mode 100644 index 00000000..388088d2 --- /dev/null +++ b/HAL/avr/avr_nousb/src/comms/backend_init.cpp @@ -0,0 +1,148 @@ +#include "comms/backend_init.hpp" + +#include "comms/GamecubeBackend.hpp" +#include "comms/N64Backend.hpp" +#include "core/CommunicationBackend.hpp" +#include "core/mode_selection.hpp" +#include "core/pinout.hpp" +#include "util/config_util.hpp" + +#include + +size_t initialize_backends( + CommunicationBackend **&backends, + InputState &inputs, + InputSource **input_sources, + size_t input_source_count, + Config &config, + const Pinout &pinout, + backend_config_selector_t get_backend_config, + usb_backend_getter_t get_usb_backend_config, + detect_console_t detect_console, + secondary_backend_initializer_t init_secondary_backends, + primary_backend_initializer_t init_primary_backend +) { + // Make sure required function pointers are not null. + if (get_backend_config == nullptr || init_primary_backend == nullptr || + detect_console == nullptr) { + return 0; + } + + CommunicationBackendConfig backend_config = CommunicationBackendConfig_init_zero; + get_backend_config(backend_config, inputs, config); + + /* If no match found for button hold, use default backend config. */ + if (backend_config.backend_id == COMMS_BACKEND_UNSPECIFIED && + config.default_backend_config > 0) { + backend_config = config.communication_backend_configs[config.default_backend_config - 1]; + } + + /* If no default backend config use GameCube backend. */ + if (backend_config.backend_id == COMMS_BACKEND_UNSPECIFIED) { + backend_config = backend_config_from_id( + COMMS_BACKEND_GAMECUBE, + config.communication_backend_configs, + config.communication_backend_configs_count + ); + } + + CommunicationBackend *primary_backend = nullptr; + + init_primary_backend( + primary_backend, + backend_config.backend_id, + inputs, + input_sources, + input_source_count, + config, + pinout + ); + + size_t backend_count = 1; + if (init_secondary_backends != nullptr) { + backend_count = init_secondary_backends( + backends, + primary_backend, + backend_config.backend_id, + inputs, + input_sources, + input_source_count, + config, + pinout + ); + } + + if (backend_config.default_mode_config > 0) { + GameModeConfig &mode_config = + config.game_mode_configs[backend_config.default_mode_config - 1]; + for (size_t i = 0; i < backend_count; i++) { + set_mode(backends[i], mode_config, config); + } + } + + return backend_count; +} + +void init_primary_backend( + CommunicationBackend *&primary_backend, + CommunicationBackendId backend_id, + InputState &inputs, + InputSource **input_sources, + size_t input_source_count, + Config &config, + const Pinout &pinout +) { + switch (backend_id) { + case COMMS_BACKEND_N64: + delete primary_backend; + primary_backend = + new N64Backend(inputs, input_sources, input_source_count, 60, pinout.joybus_data); + break; + case COMMS_BACKEND_GAMECUBE: + case COMMS_BACKEND_UNSPECIFIED: // Fall back to GameCube if invalid backend selected. + default: + delete primary_backend; + primary_backend = new GamecubeBackend( + inputs, + input_sources, + input_source_count, + inputs.rt1 ? 0 : 125, + pinout.joybus_data + ); + } +} + +size_t init_secondary_backends( + CommunicationBackend **&backends, + CommunicationBackend *&primary_backend, + CommunicationBackendId backend_id, + InputState &inputs, + InputSource **input_sources, + size_t input_source_count, + Config &config, + const Pinout &pinout +) { + return 0; +} + +// clang-format off + +/* Default is to first check button holds for a matching comms backend config. */ +backend_config_selector_t get_backend_config_default = []( + CommunicationBackendConfig &backend_config, + const InputState &inputs, + Config &config +) { + backend_config = backend_config_from_buttons( + inputs, + config.communication_backend_configs, + config.communication_backend_configs_count + ); +}; + +usb_backend_getter_t get_usb_backend_config_default = nullptr; + +// clang-format on + +primary_backend_initializer_t init_primary_backend_default = &init_primary_backend; +secondary_backend_initializer_t init_secondary_backends_default = nullptr; diff --git a/HAL/avr/avr_nousb/src/comms/console_detection.cpp b/HAL/avr/avr_nousb/src/comms/console_detection.cpp new file mode 100644 index 00000000..86c51b06 --- /dev/null +++ b/HAL/avr/avr_nousb/src/comms/console_detection.cpp @@ -0,0 +1,9 @@ +#include "comms/console_detection.hpp" + +#include "core/pinout.hpp" + +#include + +CommunicationBackendId detect_console(const Pinout &pinout) { + return COMMS_BACKEND_GAMECUBE; +} diff --git a/HAL/avr/avr_nousb/src/core/KeyboardMode.cpp b/HAL/avr/avr_nousb/src/core/KeyboardMode.cpp index b7ce470d..1c96e6e3 100644 --- a/HAL/avr/avr_nousb/src/core/KeyboardMode.cpp +++ b/HAL/avr/avr_nousb/src/core/KeyboardMode.cpp @@ -2,10 +2,10 @@ #include "core/InputMode.hpp" -KeyboardMode::KeyboardMode() {} +KeyboardMode::KeyboardMode() : InputMode() {} KeyboardMode::~KeyboardMode() {} -void KeyboardMode::SendReport(InputState &inputs) {} +void KeyboardMode::SendReport(const InputState &inputs) {} void KeyboardMode::Press(uint8_t keycode, bool press) {} \ No newline at end of file diff --git a/HAL/avr/avr_usb/include/comms/DInputBackend.hpp b/HAL/avr/avr_usb/include/comms/DInputBackend.hpp index 247eecde..f0e7b785 100644 --- a/HAL/avr/avr_usb/include/comms/DInputBackend.hpp +++ b/HAL/avr/avr_usb/include/comms/DInputBackend.hpp @@ -11,13 +11,13 @@ class DInputBackend : public CommunicationBackend { public: - DInputBackend(InputSource **input_sources, size_t input_source_count); + DInputBackend(InputState &inputs, InputSource **input_sources, size_t input_source_count); ~DInputBackend(); + CommunicationBackendId BackendId(); void SendReport(); private: - int16_t GetDpadAngle(bool left, bool right, bool down, bool up); - Joystick_ *_joystick; + Joystick_ _joystick; }; #endif diff --git a/HAL/avr/avr_usb/include/config_defaults.hpp b/HAL/avr/avr_usb/include/config_defaults.hpp new file mode 100644 index 00000000..2a52c39a --- /dev/null +++ b/HAL/avr/avr_usb/include/config_defaults.hpp @@ -0,0 +1,181 @@ +#ifndef _CONFIG_DEFAULTS_HPP +#define _CONFIG_DEFAULTS_HPP + +#include +#include + +// clang-format off + +const Config default_config = { + .game_mode_configs_count = 5, + .game_mode_configs = new GameModeConfig[5] { + GameModeConfig { + .mode_id = MODE_MELEE, + .name = {}, + .socd_pairs_count = 4, + .socd_pairs = new SocdPair[4] { + SocdPair { .button_dir1 = BTN_LF3, .button_dir2 = BTN_LF1, .socd_type = SOCD_2IP_NO_REAC }, + SocdPair { .button_dir1 = BTN_LF2, .button_dir2 = BTN_RF4, .socd_type = SOCD_2IP_NO_REAC }, + SocdPair { .button_dir1 = BTN_RT3, .button_dir2 = BTN_RT5, .socd_type = SOCD_2IP_NO_REAC }, + SocdPair { .button_dir1 = BTN_RT2, .button_dir2 = BTN_RT4, .socd_type = SOCD_2IP_NO_REAC }, + }, + .button_remapping_count = 0, + .button_remapping = {}, + .activation_binding_count = 3, + .activation_binding = new Button[3] { BTN_LT1, BTN_MB1, BTN_LF4 }, + .custom_mode_config = 0, + .keyboard_mode_config = 0, + .rgb_config = 0, + }, + GameModeConfig { + .mode_id = MODE_PROJECT_M, + .name = {}, + .socd_pairs_count = 4, + .socd_pairs = new SocdPair[4] { + SocdPair { .button_dir1 = BTN_LF3, .button_dir2 = BTN_LF1, .socd_type = SOCD_2IP_NO_REAC }, + SocdPair { .button_dir1 = BTN_LF2, .button_dir2 = BTN_RF4, .socd_type = SOCD_2IP_NO_REAC }, + SocdPair { .button_dir1 = BTN_RT3, .button_dir2 = BTN_RT5, .socd_type = SOCD_2IP_NO_REAC }, + SocdPair { .button_dir1 = BTN_RT2, .button_dir2 = BTN_RT4, .socd_type = SOCD_2IP_NO_REAC }, + }, + .button_remapping_count = 0, + .button_remapping = {}, + .activation_binding_count = 3, + .activation_binding = new Button[3] { BTN_LT1, BTN_MB1, BTN_LF3 }, + .custom_mode_config = 0, + .keyboard_mode_config = 0, + .rgb_config = 0, + }, + GameModeConfig { + .mode_id = MODE_ULTIMATE, + .name = {}, + .socd_pairs_count = 4, + .socd_pairs = new SocdPair[4] { + SocdPair { .button_dir1 = BTN_LF3, .button_dir2 = BTN_LF1, .socd_type = SOCD_2IP }, + SocdPair { .button_dir1 = BTN_LF2, .button_dir2 = BTN_RF4, .socd_type = SOCD_2IP }, + SocdPair { .button_dir1 = BTN_RT3, .button_dir2 = BTN_RT5, .socd_type = SOCD_2IP }, + SocdPair { .button_dir1 = BTN_RT2, .button_dir2 = BTN_RT4, .socd_type = SOCD_2IP }, + }, + .button_remapping_count = 0, + .button_remapping = {}, + .activation_binding_count = 3, + .activation_binding = new Button[3] { BTN_LT1, BTN_MB1, BTN_LF2 }, + .custom_mode_config = 0, + .keyboard_mode_config = 0, + .rgb_config = 0, + }, + GameModeConfig { + .mode_id = MODE_FGC, + .name = {}, + .socd_pairs_count = 2, + .socd_pairs = new SocdPair[2] { + SocdPair { .button_dir1 = BTN_LF3, .button_dir2 = BTN_LF1, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_LT1, .button_dir2 = BTN_RT4, .socd_type = SOCD_NEUTRAL }, + }, + .button_remapping_count = 1, + .button_remapping = new ButtonRemap[1] { + ButtonRemap { .physical_button = BTN_RT4, .activates = BTN_LT1 }, + }, + .activation_binding_count = 3, + .activation_binding = new Button[3] { BTN_LT1, BTN_MB1, BTN_LF1 }, + .custom_mode_config = 0, + .keyboard_mode_config = 0, + .rgb_config = 0, + }, + GameModeConfig { + .mode_id = MODE_KEYBOARD, + .name = {}, + .socd_pairs_count = 2, + .socd_pairs = new SocdPair[2] { + SocdPair { .button_dir1 = BTN_LF3, .button_dir2 = BTN_LF1, .socd_type = SOCD_2IP }, + SocdPair { .button_dir1 = BTN_LT1, .button_dir2 = BTN_RT4, .socd_type = SOCD_2IP }, + }, + .button_remapping_count = 0, + .button_remapping = {}, + .activation_binding_count = 3, + .activation_binding = new Button[3] { BTN_LT2, BTN_MB1, BTN_LF4 }, + .custom_mode_config = 0, + .keyboard_mode_config = 1, + .rgb_config = 0, + }, + }, + .communication_backend_configs_count = 3, + .communication_backend_configs = new CommunicationBackendConfig[3] { + CommunicationBackendConfig { + .backend_id = COMMS_BACKEND_DINPUT, + .default_mode_config = 1, + .activation_binding_count = 1, + .activation_binding = new Button[1] { BTN_RF3 }, + .secondary_backends = {}, + }, + CommunicationBackendConfig { + .backend_id = COMMS_BACKEND_GAMECUBE, + .default_mode_config = 1, + .activation_binding_count = 1, + .activation_binding = new Button[1] { BTN_RT1 }, + .secondary_backends = {}, + }, + CommunicationBackendConfig { + .backend_id = COMMS_BACKEND_N64, + .default_mode_config = 1, + .activation_binding_count = 1, + .activation_binding = new Button[1] { BTN_RT3 }, + .secondary_backends = {}, + }, + }, + .custom_modes_count = 0, + .custom_modes = {}, + .keyboard_modes_count = 1, + .keyboard_modes = new KeyboardModeConfig[1] { + KeyboardModeConfig { + 0, + 22, + new ButtonToKeycodeMapping[22] { + { BTN_LF4, HID_KEY_A }, + { BTN_LF3, HID_KEY_B }, + { BTN_LF2, HID_KEY_C }, + { BTN_LF1, HID_KEY_D }, + { BTN_LT1, HID_KEY_E }, + { BTN_LT2, HID_KEY_F }, + { BTN_MB3, HID_KEY_G }, + { BTN_MB1, HID_KEY_H }, + { BTN_MB2, HID_KEY_I }, + { BTN_RF5, HID_KEY_J }, + { BTN_RF6, HID_KEY_K }, + { BTN_RF7, HID_KEY_L }, + { BTN_RF8, HID_KEY_M }, + { BTN_RF1, HID_KEY_N }, + { BTN_RF2, HID_KEY_O }, + { BTN_RF3, HID_KEY_P }, + { BTN_RF4, HID_KEY_Q }, + { BTN_RT4, HID_KEY_R }, + { BTN_RT3, HID_KEY_S }, + { BTN_RT5, HID_KEY_T }, + { BTN_RT1, HID_KEY_U }, + { BTN_RT2, HID_KEY_V }, + }, + }, + }, + .rgb_configs_count = 0, + .rgb_configs = {}, + .default_backend_config = 1, + .default_usb_backend_config = 1, + .rgb_brightness = 0, + .has_melee_options = true, + .melee_options = { + .crouch_walk_os = false, + .disable_ledgedash_socd_override = false, + .has_custom_airdodge = false, + .custom_airdodge = { .x = 0, .y = 0 }, + }, + .has_project_m_options = true, + .project_m_options = { + .true_z_press = true, + .disable_ledgedash_socd_override = false, + .has_custom_airdodge = false, + .custom_airdodge = { .x = 0, .y = 0 }, + }, +}; + +// clang-format on + +#endif diff --git a/HAL/avr/avr_usb/include/core/KeyboardMode.hpp b/HAL/avr/avr_usb/include/core/KeyboardMode.hpp index bcdcf9c6..974ad30f 100644 --- a/HAL/avr/avr_usb/include/core/KeyboardMode.hpp +++ b/HAL/avr/avr_usb/include/core/KeyboardMode.hpp @@ -11,13 +11,15 @@ class KeyboardMode : public InputMode { public: KeyboardMode(); ~KeyboardMode(); - void SendReport(InputState &inputs); + void SendReport(const InputState &inputs); + + void UpdateOutputs(const InputState &inputs, OutputState &outputs) {} protected: void Press(uint8_t keycode, bool press); private: - virtual void UpdateKeys(InputState &inputs) = 0; + virtual void UpdateKeys(const InputState &inputs) = 0; }; #endif diff --git a/HAL/avr/avr_usb/src/comms/DInputBackend.cpp b/HAL/avr/avr_usb/src/comms/DInputBackend.cpp index 6dbebf9a..cc1440b2 100644 --- a/HAL/avr/avr_usb/src/comms/DInputBackend.cpp +++ b/HAL/avr/avr_usb/src/comms/DInputBackend.cpp @@ -5,38 +5,21 @@ #include -DInputBackend::DInputBackend(InputSource **input_sources, size_t input_source_count) - : CommunicationBackend(input_sources, input_source_count) { - _joystick = new Joystick_( - JOYSTICK_DEFAULT_REPORT_ID, - JOYSTICK_TYPE_GAMEPAD, - 16, // Button Count - 1, // Hat Switch Count - true, // X Axis - true, // Y Axis - true, // Z Axis - true, // Rx Axis - true, // Ry Axis - true, // Rz Axis - false, // No rudder - false, // No throttle - false, // No accelerator - false, // No brake - false // No steering - ); - - _joystick->begin(false); - _joystick->setXAxisRange(0, 255); - _joystick->setYAxisRange(0, 255); - _joystick->setRxAxisRange(0, 255); - _joystick->setRyAxisRange(0, 255); - _joystick->setZAxisRange(0, 255); - _joystick->setRzAxisRange(0, 255); +DInputBackend::DInputBackend( + InputState &inputs, + InputSource **input_sources, + size_t input_source_count +) + : CommunicationBackend(inputs, input_sources, input_source_count) { + _joystick.begin(); } DInputBackend::~DInputBackend() { - _joystick->end(); - delete _joystick; + _joystick.end(); +} + +CommunicationBackendId DInputBackend::BackendId() { + return COMMS_BACKEND_DINPUT; } void DInputBackend::SendReport() { @@ -46,55 +29,31 @@ void DInputBackend::SendReport() { UpdateOutputs(); // Digital outputs - _joystick->setButton(0, _outputs.b); - _joystick->setButton(1, _outputs.a); - _joystick->setButton(2, _outputs.y); - _joystick->setButton(3, _outputs.x); - _joystick->setButton(4, _outputs.buttonR); - _joystick->setButton(5, _outputs.triggerRDigital); - _joystick->setButton(6, _outputs.buttonL); - _joystick->setButton(7, _outputs.triggerLDigital); - _joystick->setButton(8, _outputs.select); - _joystick->setButton(9, _outputs.start); - _joystick->setButton(10, _outputs.rightStickClick); - _joystick->setButton(11, _outputs.leftStickClick); - _joystick->setButton(12, _outputs.home); + _joystick.setButton(0, _outputs.b); + _joystick.setButton(1, _outputs.a); + _joystick.setButton(2, _outputs.y); + _joystick.setButton(3, _outputs.x); + _joystick.setButton(4, _outputs.buttonR); + _joystick.setButton(5, _outputs.triggerRDigital); + _joystick.setButton(6, _outputs.buttonL); + _joystick.setButton(7, _outputs.triggerLDigital); + _joystick.setButton(8, _outputs.select); + _joystick.setButton(9, _outputs.start); + _joystick.setButton(10, _outputs.rightStickClick); + _joystick.setButton(11, _outputs.leftStickClick); + _joystick.setButton(12, _outputs.home); // Analog outputs - _joystick->setXAxis(_outputs.leftStickX); - _joystick->setYAxis(255 - _outputs.leftStickY); - _joystick->setRxAxis(_outputs.rightStickX); - _joystick->setRyAxis(255 - _outputs.rightStickY); - _joystick->setZAxis(_outputs.triggerLAnalog + 1); - _joystick->setRzAxis(_outputs.triggerRAnalog + 1); + _joystick.setLeftXAxis(_outputs.leftStickX); + _joystick.setLeftYAxis(255 - _outputs.leftStickY); + _joystick.setRightXAxis(_outputs.rightStickX); + _joystick.setRightYAxis(255 - _outputs.rightStickY); + _joystick.setLeftTrigger(_outputs.triggerLAnalog + 1); + _joystick.setRightTrigger(_outputs.triggerRAnalog + 1); // D-pad Hat Switch - _joystick->setHatSwitch( - 0, - GetDpadAngle(_outputs.dpadLeft, _outputs.dpadRight, _outputs.dpadDown, _outputs.dpadUp) - ); - - _joystick->sendState(); -} + _joystick + .setHatSwitch(_outputs.dpadLeft, _outputs.dpadRight, _outputs.dpadDown, _outputs.dpadUp); -int16_t DInputBackend::GetDpadAngle(bool left, bool right, bool down, bool up) { - int16_t angle = -1; - if (right && !left) { - angle = 90; - if (down) - angle = 135; - if (up) - angle = 45; - } else if (left && !right) { - angle = 270; - if (down) - angle = 225; - if (up) - angle = 315; - } else if (down && !up) { - angle = 180; - } else if (up && !down) { - angle = 0; - } - return angle; + _joystick.sendState(); } diff --git a/HAL/avr/avr_usb/src/comms/backend_init.cpp b/HAL/avr/avr_usb/src/comms/backend_init.cpp new file mode 100644 index 00000000..a433edad --- /dev/null +++ b/HAL/avr/avr_usb/src/comms/backend_init.cpp @@ -0,0 +1,197 @@ +#include "comms/backend_init.hpp" + +#include "comms/B0XXInputViewer.hpp" +#include "comms/DInputBackend.hpp" +#include "comms/GamecubeBackend.hpp" +#include "comms/N64Backend.hpp" +#include "core/CommunicationBackend.hpp" +#include "core/mode_selection.hpp" +#include "core/pinout.hpp" +#include "util/config_util.hpp" + +#include + +size_t initialize_backends( + CommunicationBackend **&backends, + InputState &inputs, + InputSource **input_sources, + size_t input_source_count, + Config &config, + const Pinout &pinout, + backend_config_selector_t get_backend_config, + usb_backend_getter_t get_usb_backend_config, + detect_console_t detect_console, + secondary_backend_initializer_t init_secondary_backends, + primary_backend_initializer_t init_primary_backend +) { + // Make sure required function pointers are not null. + if (get_backend_config == nullptr || get_usb_backend_config == nullptr || + init_primary_backend == nullptr || detect_console == nullptr) { + return 0; + } + + CommunicationBackendConfig backend_config = CommunicationBackendConfig_init_zero; + get_backend_config(backend_config, inputs, config); + + CommunicationBackend *primary_backend = nullptr; + + /* If no match found for button hold, use console/USB detection to select backend instead. */ + if (backend_config.backend_id == COMMS_BACKEND_UNSPECIFIED) { + /* Must check default USB backend here and initialize it before console detection, so that + * we can respond correctly to device descriptor requests from host. */ + CommunicationBackendConfig usb_backend_config; + get_usb_backend_config(usb_backend_config, config); + init_primary_backend( + primary_backend, + usb_backend_config.backend_id, + inputs, + input_sources, + input_source_count, + config, + pinout + ); + CommunicationBackendId detected_backend_id = COMMS_BACKEND_UNSPECIFIED; + detected_backend_id = detect_console(pinout); + if (detected_backend_id == COMMS_BACKEND_XINPUT) { + backend_config = usb_backend_config; + } else { + backend_config = backend_config_from_id( + detected_backend_id, + config.communication_backend_configs, + config.communication_backend_configs_count + ); + } + } + if (backend_config.backend_id == COMMS_BACKEND_UNSPECIFIED && + config.default_backend_config > 0) { + backend_config = config.communication_backend_configs[config.default_backend_config - 1]; + } + + init_primary_backend( + primary_backend, + backend_config.backend_id, + inputs, + input_sources, + input_source_count, + config, + pinout + ); + + size_t backend_count = 1; + if (init_secondary_backends != nullptr) { + backend_count = init_secondary_backends( + backends, + primary_backend, + backend_config.backend_id, + inputs, + input_sources, + input_source_count, + config, + pinout + ); + } + + if (backend_config.default_mode_config > 0) { + GameModeConfig &mode_config = + config.game_mode_configs[backend_config.default_mode_config - 1]; + for (size_t i = 0; i < backend_count; i++) { + set_mode(backends[i], mode_config, config); + } + } + + return backend_count; +} + +void init_primary_backend( + CommunicationBackend *&primary_backend, + CommunicationBackendId backend_id, + InputState &inputs, + InputSource **input_sources, + size_t input_source_count, + Config &config, + const Pinout &pinout +) { + switch (backend_id) { + case COMMS_BACKEND_GAMECUBE: + delete primary_backend; + primary_backend = new GamecubeBackend( + inputs, + input_sources, + input_source_count, + inputs.rt1 ? 0 : 125, + pinout.joybus_data + ); + break; + case COMMS_BACKEND_N64: + delete primary_backend; + primary_backend = + new N64Backend(inputs, input_sources, input_source_count, 60, pinout.joybus_data); + break; + case COMMS_BACKEND_UNSPECIFIED: // Fall back to DInput if invalid backend selected. + case COMMS_BACKEND_DINPUT: + default: + if (primary_backend == nullptr) { + primary_backend = new DInputBackend(inputs, input_sources, input_source_count); + } + break; + } +} + +size_t init_secondary_backends( + CommunicationBackend **&backends, + CommunicationBackend *&primary_backend, + CommunicationBackendId backend_id, + InputState &inputs, + InputSource **input_sources, + size_t input_source_count, + Config &config, + const Pinout &pinout +) { + size_t backend_count = 0; + + switch (backend_id) { + case COMMS_BACKEND_DINPUT: + backend_count = 2; + backends = new CommunicationBackend *[backend_count] { + primary_backend, new B0XXInputViewer(inputs, input_sources, input_source_count) + }; + break; + default: + backend_count = 1; + backends = new CommunicationBackend *[backend_count] { primary_backend }; + } + + return backend_count; +} + +// clang-format off + +/* Default is to first check button holds for a matching comms backend config. */ +backend_config_selector_t get_backend_config_default = []( + CommunicationBackendConfig &backend_config, + const InputState &inputs, + Config &config +) { + backend_config = backend_config_from_buttons( + inputs, + config.communication_backend_configs, + config.communication_backend_configs_count + ); +}; + +/* Default is to get default USB backend from config. */ +usb_backend_getter_t get_usb_backend_config_default = []( + CommunicationBackendConfig &backend_config, + const Config &config +) { + if (config.default_usb_backend_config > 0 && + config.default_usb_backend_config <= config.communication_backend_configs_count) { + backend_config = + config.communication_backend_configs[config.default_usb_backend_config - 1]; + } +}; + +// clang-format on + +primary_backend_initializer_t init_primary_backend_default = &init_primary_backend; +secondary_backend_initializer_t init_secondary_backends_default = &init_secondary_backends; diff --git a/HAL/avr/avr_usb/src/comms/console_detection.cpp b/HAL/avr/avr_usb/src/comms/console_detection.cpp new file mode 100644 index 00000000..e639e0a0 --- /dev/null +++ b/HAL/avr/avr_usb/src/comms/console_detection.cpp @@ -0,0 +1,18 @@ +#include "comms/console_detection.hpp" + +#include "comms/DInputBackend.hpp" +#include "core/pinout.hpp" +#include "stdlib.hpp" + +#include + +CommunicationBackendId detect_console(const Pinout &pinout) { + delay(500); + bool usb_connected = UDADDR & _BV(ADDEN); + + if (usb_connected) { + return COMMS_BACKEND_DINPUT; + } + + return COMMS_BACKEND_GAMECUBE; +} \ No newline at end of file diff --git a/HAL/avr/avr_usb/src/core/KeyboardMode.cpp b/HAL/avr/avr_usb/src/core/KeyboardMode.cpp index edf6435e..e7eaa625 100644 --- a/HAL/avr/avr_usb/src/core/KeyboardMode.cpp +++ b/HAL/avr/avr_usb/src/core/KeyboardMode.cpp @@ -4,15 +4,17 @@ #include -KeyboardMode::KeyboardMode() {} +KeyboardMode::KeyboardMode() : InputMode() {} KeyboardMode::~KeyboardMode() { _keyboard.releaseAll(); _keyboard.sendReport(); } -void KeyboardMode::SendReport(InputState &inputs) { - HandleSocd(inputs); +void KeyboardMode::SendReport(const InputState &inputs) { + InputState remapped_inputs = inputs; + HandleRemap(inputs, remapped_inputs); + HandleSocd(remapped_inputs); UpdateKeys(inputs); _keyboard.sendReport(); } diff --git a/HAL/avr/include/comms/GamecubeBackend.hpp b/HAL/avr/include/comms/GamecubeBackend.hpp index 41da94f5..2780d774 100644 --- a/HAL/avr/include/comms/GamecubeBackend.hpp +++ b/HAL/avr/include/comms/GamecubeBackend.hpp @@ -9,16 +9,17 @@ class GamecubeBackend : public CommunicationBackend { public: GamecubeBackend( + InputState &inputs, InputSource **input_sources, size_t input_source_count, int polling_rate, int data_pin ); - ~GamecubeBackend(); + CommunicationBackendId BackendId(); void SendReport(); private: - CGamecubeConsole *_gamecube; + CGamecubeConsole _gamecube; Gamecube_Data_t _data; int _delay; }; diff --git a/HAL/avr/include/comms/N64Backend.hpp b/HAL/avr/include/comms/N64Backend.hpp index 68324780..34a141fa 100644 --- a/HAL/avr/include/comms/N64Backend.hpp +++ b/HAL/avr/include/comms/N64Backend.hpp @@ -9,16 +9,17 @@ class N64Backend : public CommunicationBackend { public: N64Backend( + InputState &inputs, InputSource **input_sources, size_t input_source_count, int polling_rate, int data_pin ); - ~N64Backend(); + CommunicationBackendId BackendId(); void SendReport(); private: - CN64Console *_n64; + CN64Console _n64; N64_Data_t _data; int _delay; }; diff --git a/HAL/avr/include/util/state_util.hpp b/HAL/avr/include/util/state_util.hpp new file mode 100644 index 00000000..65c01e0b --- /dev/null +++ b/HAL/avr/include/util/state_util.hpp @@ -0,0 +1,479 @@ +#ifndef _UTIL_STATE_UTIL_HPP +#define _UTIL_STATE_UTIL_HPP + +#include "stdlib.hpp" + +#include + +typedef struct _ButtonState { + bool lf1 : 1; + bool lf2 : 1; + bool lf3 : 1; + bool lf4 : 1; + bool lf5 : 1; + bool lf6 : 1; + bool lf7 : 1; + bool lf8 : 1; + bool lf9 : 1; + bool lf10 : 1; + bool lf11 : 1; + bool lf12 : 1; + bool lf13 : 1; + bool lf14 : 1; + bool lf15 : 1; + bool lf16 : 1; + bool rf1 : 1; + bool rf2 : 1; + bool rf3 : 1; + bool rf4 : 1; + bool rf5 : 1; + bool rf6 : 1; + bool rf7 : 1; + bool rf8 : 1; + bool rf9 : 1; + bool rf10 : 1; + bool rf11 : 1; + bool rf12 : 1; + bool rf13 : 1; + bool rf14 : 1; + bool rf15 : 1; + bool rf16 : 1; + bool lt1 : 1; + bool lt2 : 1; + bool lt3 : 1; + bool lt4 : 1; + bool lt5 : 1; + bool lt6 : 1; + bool lt7 : 1; + bool lt8 : 1; + bool rt1 : 1; + bool rt2 : 1; + bool rt3 : 1; + bool rt4 : 1; + bool rt5 : 1; + bool rt6 : 1; + bool rt7 : 1; + bool rt8 : 1; + bool mb1 : 1; + bool mb2 : 1; + bool mb3 : 1; + bool mb4 : 1; + bool mb5 : 1; + bool mb6 : 1; + bool mb7 : 1; + bool mb8 : 1; + bool mb9 : 1; + bool mb10 : 1; + bool mb11 : 1; + bool mb12 : 1; +} ButtonState; + +inline void set_button(uint64_t &buttons, Button button_index, bool pressed) { + ButtonState &inputs = (ButtonState &)buttons; + if (button_index == BTN_UNSPECIFIED) { + return; + } + switch (button_index) { + case BTN_LF1: + inputs.lf1 = pressed; + break; + case BTN_LF2: + inputs.lf2 = pressed; + break; + case BTN_LF3: + inputs.lf3 = pressed; + break; + case BTN_LF4: + inputs.lf4 = pressed; + break; + case BTN_LF5: + inputs.lf5 = pressed; + break; + case BTN_LF6: + inputs.lf6 = pressed; + break; + case BTN_LF7: + inputs.lf7 = pressed; + break; + case BTN_LF8: + inputs.lf8 = pressed; + break; + case BTN_LF9: + inputs.lf9 = pressed; + break; + case BTN_LF10: + inputs.lf10 = pressed; + break; + case BTN_LF11: + inputs.lf11 = pressed; + break; + case BTN_LF12: + inputs.lf12 = pressed; + break; + case BTN_LF13: + inputs.lf13 = pressed; + break; + case BTN_LF14: + inputs.lf14 = pressed; + break; + case BTN_LF15: + inputs.lf15 = pressed; + break; + case BTN_LF16: + inputs.lf16 = pressed; + break; + case BTN_RF1: + inputs.rf1 = pressed; + break; + case BTN_RF2: + inputs.rf2 = pressed; + break; + case BTN_RF3: + inputs.rf3 = pressed; + break; + case BTN_RF4: + inputs.rf4 = pressed; + break; + case BTN_RF5: + inputs.rf5 = pressed; + break; + case BTN_RF6: + inputs.rf6 = pressed; + break; + case BTN_RF7: + inputs.rf7 = pressed; + break; + case BTN_RF8: + inputs.rf8 = pressed; + break; + case BTN_RF9: + inputs.rf9 = pressed; + break; + case BTN_RF10: + inputs.rf10 = pressed; + break; + case BTN_RF11: + inputs.rf11 = pressed; + break; + case BTN_RF12: + inputs.rf12 = pressed; + break; + case BTN_RF13: + inputs.rf13 = pressed; + break; + case BTN_RF14: + inputs.rf14 = pressed; + break; + case BTN_RF15: + inputs.rf15 = pressed; + break; + case BTN_RF16: + inputs.rf16 = pressed; + break; + case BTN_LT1: + inputs.lt1 = pressed; + break; + case BTN_LT2: + inputs.lt2 = pressed; + break; + case BTN_LT3: + inputs.lt3 = pressed; + break; + case BTN_LT4: + inputs.lt4 = pressed; + break; + case BTN_LT5: + inputs.lt5 = pressed; + break; + case BTN_LT6: + inputs.lt6 = pressed; + break; + case BTN_LT7: + inputs.lt7 = pressed; + break; + case BTN_LT8: + inputs.lt8 = pressed; + break; + case BTN_RT1: + inputs.rt1 = pressed; + break; + case BTN_RT2: + inputs.rt2 = pressed; + break; + case BTN_RT3: + inputs.rt3 = pressed; + break; + case BTN_RT4: + inputs.rt4 = pressed; + break; + case BTN_RT5: + inputs.rt5 = pressed; + break; + case BTN_RT6: + inputs.rt6 = pressed; + break; + case BTN_RT7: + inputs.rt7 = pressed; + break; + case BTN_RT8: + inputs.rt8 = pressed; + break; + case BTN_MB1: + inputs.mb1 = pressed; + break; + case BTN_MB2: + inputs.mb2 = pressed; + break; + case BTN_MB3: + inputs.mb3 = pressed; + break; + case BTN_MB4: + inputs.mb4 = pressed; + break; + case BTN_MB5: + inputs.mb5 = pressed; + break; + case BTN_MB6: + inputs.mb6 = pressed; + break; + case BTN_MB7: + inputs.mb7 = pressed; + break; + case BTN_MB8: + inputs.mb8 = pressed; + break; + case BTN_MB9: + inputs.mb9 = pressed; + break; + case BTN_MB10: + inputs.mb10 = pressed; + break; + case BTN_MB11: + inputs.mb11 = pressed; + break; + case BTN_MB12: + inputs.mb12 = pressed; + break; + default: + break; + } +} + +inline bool get_button(const uint64_t &buttons, Button button_index) { + ButtonState &inputs = (ButtonState &)buttons; + switch (button_index) { + case BTN_LF1: + return inputs.lf1; + case BTN_LF2: + return inputs.lf2; + case BTN_LF3: + return inputs.lf3; + case BTN_LF4: + return inputs.lf4; + case BTN_LF5: + return inputs.lf5; + case BTN_LF6: + return inputs.lf6; + case BTN_LF7: + return inputs.lf7; + case BTN_LF8: + return inputs.lf8; + case BTN_LF9: + return inputs.lf9; + case BTN_LF10: + return inputs.lf10; + case BTN_LF11: + return inputs.lf11; + case BTN_LF12: + return inputs.lf12; + case BTN_LF13: + return inputs.lf13; + case BTN_LF14: + return inputs.lf14; + case BTN_LF15: + return inputs.lf15; + case BTN_LF16: + return inputs.lf16; + case BTN_RF1: + return inputs.rf1; + case BTN_RF2: + return inputs.rf2; + case BTN_RF3: + return inputs.rf3; + case BTN_RF4: + return inputs.rf4; + case BTN_RF5: + return inputs.rf5; + case BTN_RF6: + return inputs.rf6; + case BTN_RF7: + return inputs.rf7; + case BTN_RF8: + return inputs.rf8; + case BTN_RF9: + return inputs.rf9; + case BTN_RF10: + return inputs.rf10; + case BTN_RF11: + return inputs.rf11; + case BTN_RF12: + return inputs.rf12; + case BTN_RF13: + return inputs.rf13; + case BTN_RF14: + return inputs.rf14; + case BTN_RF15: + return inputs.rf15; + case BTN_RF16: + return inputs.rf16; + case BTN_LT1: + return inputs.lt1; + case BTN_LT2: + return inputs.lt2; + case BTN_LT3: + return inputs.lt3; + case BTN_LT4: + return inputs.lt4; + case BTN_LT5: + return inputs.lt5; + case BTN_LT6: + return inputs.lt6; + case BTN_LT7: + return inputs.lt7; + case BTN_LT8: + return inputs.lt8; + case BTN_RT1: + return inputs.rt1; + case BTN_RT2: + return inputs.rt2; + case BTN_RT3: + return inputs.rt3; + case BTN_RT4: + return inputs.rt4; + case BTN_RT5: + return inputs.rt5; + case BTN_RT6: + return inputs.rt6; + case BTN_RT7: + return inputs.rt7; + case BTN_RT8: + return inputs.rt8; + case BTN_MB1: + return inputs.mb1; + case BTN_MB2: + return inputs.mb2; + case BTN_MB3: + return inputs.mb3; + case BTN_MB4: + return inputs.mb4; + case BTN_MB5: + return inputs.mb5; + case BTN_MB6: + return inputs.mb6; + case BTN_MB7: + return inputs.mb7; + case BTN_MB8: + return inputs.mb8; + case BTN_MB9: + return inputs.mb9; + case BTN_MB10: + return inputs.mb10; + case BTN_MB11: + return inputs.mb11; + case BTN_MB12: + return inputs.mb12; + default: + return false; + } +} + +inline uint64_t make_button_mask(const Button *buttons, size_t buttons_count) { + uint64_t button_mask = 0; + for (size_t j = 0; j < buttons_count; j++) { + button_mask |= (1ULL << (buttons[j] - 1)); + } + return button_mask; +} + +inline bool all_buttons_held(const uint64_t &buttons, uint64_t button_mask) { + return button_mask != 0 && (buttons & button_mask) == button_mask; +} + +inline bool any_button_held(const uint64_t &buttons, uint64_t button_mask) { + return button_mask != 0 && (buttons & button_mask); +} + +/* OutputState utils */ + +inline void set_output(uint32_t &buttons, DigitalOutput output_index, bool pressed) { + if (output_index == GP_UNSPECIFIED) { + return; + } + DigitalOutput output_index_adjusted = (DigitalOutput)(output_index - 1); + buttons = + (buttons & ~(1UL << output_index_adjusted)) | ((uint32_t)pressed << output_index_adjusted); +} + +inline uint8_t OutputState::*axis_pointer(AnalogAxis axis) { + switch (axis) { + case AXIS_LSTICK_X: + return &OutputState::leftStickX; + case AXIS_LSTICK_Y: + return &OutputState::leftStickY; + case AXIS_RSTICK_X: + return &OutputState::rightStickX; + case AXIS_RSTICK_Y: + return &OutputState::rightStickY; + case AXIS_LTRIGGER: + return &OutputState::triggerLAnalog; + case AXIS_RTRIGGER: + return &OutputState::triggerRAnalog; + default: + return nullptr; + } +} + +constexpr const char *digital_output_name(DigitalOutput output) { + switch (output) { + case GP_A: + return "A"; + case GP_B: + return "B"; + case GP_X: + return "X"; + case GP_Y: + return "Y"; + case GP_LB: + return "L1"; + case GP_RB: + return "R1"; + case GP_LT: + return "L2"; + case GP_RT: + return "R2"; + case GP_START: + return "Start"; + case GP_SELECT: + return "Select"; + case GP_HOME: + return "Home"; + case GP_CAPTURE: + return "Capture"; + case GP_DPAD_UP: + return "D-Pad Up"; + case GP_DPAD_DOWN: + return "D-Pad Down"; + case GP_DPAD_LEFT: + return "D-Pad Left"; + case GP_DPAD_RIGHT: + return "D-Pad Right"; + case GP_LSTICK_CLICK: + return "L3"; + case GP_RSTICK_CLICK: + return "R3"; + default: + return "Unknown"; + } +} + +#endif \ No newline at end of file diff --git a/HAL/avr/proto/config.options b/HAL/avr/proto/config.options new file mode 100644 index 00000000..de7a0a38 --- /dev/null +++ b/HAL/avr/proto/config.options @@ -0,0 +1,99 @@ +ButtonRemap.physical_button int_size:IS_8 +ButtonRemap.activates int_size:IS_8 + +SocdPair.button_dir1 int_size:IS_8 +SocdPair.button_dir2 int_size:IS_8 +SocdPair.socd_type int_size:IS_8 + +AnalogTriggerMapping.button int_size:IS_8 +AnalogTriggerMapping.trigger int_size:IS_8 +AnalogTriggerMapping.value int_size:IS_8 + +AnalogModifier.buttons int_size:IS_8 +AnalogModifier.buttons max_count:3 +AnalogModifier.buttons type:FT_POINTER +AnalogModifier.axis int_size:IS_8 +AnalogModifier.combination_mode int_size:IS_8 + +ButtonComboMapping.buttons int_size:IS_8 +ButtonComboMapping.buttons max_count:3 +ButtonComboMapping.buttons type:FT_POINTER +ButtonComboMapping.digital_output int_size:IS_8 + +Coords.x int_size:IS_8 +Coords.y int_size:IS_8 + +ButtonToKeycodeMapping.button int_size:IS_8 +ButtonToKeycodeMapping.keycode int_size:IS_8 + +ButtonToColorMapping.button int_size:IS_8 + +GameModeConfig.mode_id int_size:IS_8 +GameModeConfig.name max_length:17 +GameModeConfig.name type:FT_POINTER +GameModeConfig.socd_pairs max_count:10 +GameModeConfig.socd_pairs type:FT_POINTER +GameModeConfig.button_remapping max_count:60 +GameModeConfig.button_remapping type:FT_POINTER +GameModeConfig.activation_binding max_count:4 +GameModeConfig.activation_binding type:FT_POINTER +GameModeConfig.custom_mode_config int_size:IS_8 +GameModeConfig.keyboard_mode_config int_size:IS_8 +GameModeConfig.rgb_config int_size:IS_8 + +CustomModeConfig.id int_size:IS_8 +CustomModeConfig.digital_button_mappings max_count:18 +CustomModeConfig.digital_button_mappings type:FT_POINTER +CustomModeConfig.stick_direction_mappings max_count:8 +CustomModeConfig.stick_direction_mappings type:FT_POINTER +CustomModeConfig.analog_trigger_mappings max_count:4 +CustomModeConfig.analog_trigger_mappings type:FT_POINTER +CustomModeConfig.button_combo_mappings max_count:5 +CustomModeConfig.button_combo_mappings type:FT_POINTER +CustomModeConfig.modifiers max_count:20 +CustomModeConfig.modifiers type:FT_POINTER +CustomModeConfig.stick_range int_size:IS_8 + +KeyboardModeConfig.id int_size:IS_8 +KeyboardModeConfig.buttons_to_keycodes max_count:60 +KeyboardModeConfig.buttons_to_keycodes type:FT_POINTER + +CommunicationBackendConfig.backend_id int_size:IS_8 +CommunicationBackendConfig.default_mode_config int_size:IS_8 +CommunicationBackendConfig.activation_binding max_count:2 +CommunicationBackendConfig.activation_binding type:FT_POINTER + +RgbConfig.button_colors max_count:60 +RgbConfig.button_colors type:FT_POINTER +RgbConfig.animation int_size:IS_8 +RgbConfig.speed int_size:IS_8 + +Config.game_mode_configs max_count:10 +Config.game_mode_configs type:FT_POINTER +Config.communication_backend_configs max_count:10 +Config.communication_backend_configs type:FT_POINTER +Config.custom_modes max_count:5 +Config.custom_modes type:FT_POINTER +Config.keyboard_modes max_count:5 +Config.keyboard_modes type:FT_POINTER +Config.rgb_configs max_count:10 +Config.rgb_configs type:FT_POINTER +Config.default_backend_config int_size:IS_8 +Config.default_usb_backend_config int_size:IS_8 +Config.rgb_brightness int_size:IS_8 + +DeviceInfo.firmware_name max_length:25 +DeviceInfo.firmware_version max_length:8 +DeviceInfo.device_name max_length:30 + +Command long_names:false +Button long_names:false +DigitalOutput long_names:false +StickDirectionButton long_names:false +AnalogAxis long_names:false +AnalogTrigger long_names:false +ModifierCombinationMode long_names:false +SocdType long_names:false +GameModeId long_names:false +CommunicationBackendId long_names:false +RgbAnimationId long_names:false \ No newline at end of file diff --git a/HAL/avr/src/comms/GamecubeBackend.cpp b/HAL/avr/src/comms/GamecubeBackend.cpp index 4869e055..370d6f71 100644 --- a/HAL/avr/src/comms/GamecubeBackend.cpp +++ b/HAL/avr/src/comms/GamecubeBackend.cpp @@ -6,13 +6,14 @@ #include GamecubeBackend::GamecubeBackend( + InputState &inputs, InputSource **input_sources, size_t input_source_count, int polling_rate, int data_pin ) - : CommunicationBackend(input_sources, input_source_count) { - _gamecube = new CGamecubeConsole(data_pin); + : CommunicationBackend(inputs, input_sources, input_source_count), + _gamecube(data_pin) { _data = defaultGamecubeData; if (polling_rate > 0) { @@ -25,8 +26,8 @@ GamecubeBackend::GamecubeBackend( } } -GamecubeBackend::~GamecubeBackend() { - delete _gamecube; +CommunicationBackendId GamecubeBackend::BackendId() { + return COMMS_BACKEND_GAMECUBE; } void GamecubeBackend::SendReport() { @@ -59,7 +60,7 @@ void GamecubeBackend::SendReport() { _data.report.right = _outputs.triggerRAnalog + 31; // Send outputs to console. - _gamecube->write(_data); + _gamecube.write(_data); delayMicroseconds(_delay); } diff --git a/HAL/avr/src/comms/N64Backend.cpp b/HAL/avr/src/comms/N64Backend.cpp index 0e6c84af..b7939a2e 100644 --- a/HAL/avr/src/comms/N64Backend.cpp +++ b/HAL/avr/src/comms/N64Backend.cpp @@ -3,13 +3,14 @@ #include N64Backend::N64Backend( + InputState &inputs, InputSource **input_sources, size_t input_source_count, int polling_rate, int data_pin ) - : CommunicationBackend(input_sources, input_source_count) { - _n64 = new CN64Console(data_pin); + : CommunicationBackend(inputs, input_sources, input_source_count), + _n64(data_pin) { _data = defaultN64Data; if (polling_rate > 0) { @@ -22,8 +23,8 @@ N64Backend::N64Backend( } } -N64Backend::~N64Backend() { - delete _n64; +CommunicationBackendId N64Backend::BackendId() { + return COMMS_BACKEND_N64; } void N64Backend::SendReport() { @@ -55,7 +56,7 @@ void N64Backend::SendReport() { _data.report.yAxis = _outputs.leftStickY - 128; // Send outputs to console. - _n64->write(_data); + _n64.write(_data); delayMicroseconds(_delay); } diff --git a/HAL/avr/src/core/mode_selection.cpp b/HAL/avr/src/core/mode_selection.cpp new file mode 100644 index 00000000..127212a8 --- /dev/null +++ b/HAL/avr/src/core/mode_selection.cpp @@ -0,0 +1,140 @@ +#include "core/mode_selection.hpp" + +#include "core/state.hpp" +#include "modes/CustomControllerMode.hpp" +#include "modes/CustomKeyboardMode.hpp" +#include "modes/FgcMode.hpp" +#include "modes/Melee20Button.hpp" +#include "modes/ProjectM.hpp" +#include "modes/RivalsOfAether.hpp" +#include "modes/Ultimate.hpp" +#include "util/state_util.hpp" + +#include + +Melee20Button melee_mode; +ProjectM projectm_mode; +Ultimate ultimate_mode; +FgcMode fgc_mode; +RivalsOfAether rivals_mode; +CustomKeyboardMode keyboard_mode; +CustomControllerMode custom_mode; + +uint64_t mode_activation_masks[10]; + +size_t current_mode_index = SIZE_MAX; + +void set_mode(CommunicationBackend *backend, ControllerMode *mode) { + // Delete keyboard mode in case one is set, so we don't end up getting both controller and + // keyboard inputs. + current_kb_mode = nullptr; + + // Set new controller mode. + backend->SetGameMode(mode); +} + +void set_mode(CommunicationBackend *backend, KeyboardMode *mode) { + // Only DInputBackend supports keyboard modes. + if (backend->BackendId() != COMMS_BACKEND_DINPUT) { + return; + } + + // Delete and reassign current keyboard mode. + current_kb_mode = mode; + + // Unset the current controller mode so backend only gives neutral inputs. + backend->SetGameMode(mode); +} + +void set_mode(CommunicationBackend *backend, GameModeConfig &mode_config, Config &config) { + switch (mode_config.mode_id) { + case MODE_MELEE: + melee_mode.SetConfig(mode_config, config.melee_options); + set_mode(backend, &melee_mode); + break; + case MODE_PROJECT_M: + projectm_mode.SetConfig(mode_config, config.project_m_options); + set_mode(backend, &projectm_mode); + break; + case MODE_ULTIMATE: + ultimate_mode.SetConfig(mode_config); + set_mode(backend, &ultimate_mode); + break; + case MODE_FGC: + fgc_mode.SetConfig(mode_config); + set_mode(backend, &fgc_mode); + break; + case MODE_RIVALS_OF_AETHER: + rivals_mode.SetConfig(mode_config); + set_mode(backend, &rivals_mode); + break; + case MODE_KEYBOARD: + if (backend->BackendId() != COMMS_BACKEND_DINPUT || + mode_config.keyboard_mode_config < 1 || + mode_config.keyboard_mode_config > config.keyboard_modes_count) { + break; + } + keyboard_mode.SetConfig( + mode_config, + config.keyboard_modes[mode_config.keyboard_mode_config - 1] + ); + set_mode(backend, &keyboard_mode); + break; + case MODE_CUSTOM: + if (mode_config.custom_mode_config < 1 || + mode_config.custom_mode_config > config.custom_modes_count) { + break; + } + custom_mode.SetConfig( + mode_config, + config.custom_modes[mode_config.custom_mode_config - 1] + ); + set_mode(backend, &custom_mode); + break; + case MODE_UNSPECIFIED: + default: + break; + } +} + +// TODO: Maybe remove this overload in favour of looking up the gamemode outside of here using a +// config_utils function. +void set_mode(CommunicationBackend *backend, GameModeId mode_id, Config &config) { + // In this overload we only know the mode id so we need to find a mode config that matches this + // ID. + for (size_t i = 0; i < config.game_mode_configs_count; i++) { + GameModeConfig &mode = config.game_mode_configs[i]; + if (mode.mode_id == mode_id) { + set_mode(backend, mode, config); + return; + } + } +} + +void select_mode(CommunicationBackend **backends, size_t backends_count, Config &config) { + // TODO: Use a counter variable to only run the contents of this function every x iterations + // rather than on every single poll. + + InputState &inputs = backends[0]->GetInputs(); + + for (size_t i = 0; i < config.game_mode_configs_count; i++) { + GameModeConfig &mode_config = config.game_mode_configs[i]; + if (all_buttons_held(inputs.buttons, mode_activation_masks[i]) && i != current_mode_index) { + current_mode_index = i; + for (size_t i = 0; i < backends_count; i++) { + set_mode(backends[i], mode_config, config); + } + return; + } + } +} + +void setup_mode_activation_bindings(const GameModeConfig *mode_configs, size_t mode_configs_count) { + // Build bit masks for checking for matching button holds. + for (size_t i = 0; i < mode_configs_count; i++) { + mode_activation_masks[i] = make_button_mask( + mode_configs[i].activation_binding, + mode_configs[i].activation_binding_count + ); + } +} diff --git a/HAL/avr/src/reboot.cpp b/HAL/avr/src/reboot.cpp new file mode 100644 index 00000000..110d4bd3 --- /dev/null +++ b/HAL/avr/src/reboot.cpp @@ -0,0 +1,23 @@ +#include "reboot.hpp" + +#include "stdlib.hpp" + +#include + +void reboot_firmware() { + wdt_enable(WDTO_15MS); + while (true) { + delay(1); + } +} + +void reboot_bootloader() { +#ifdef MAGIC_KEY_POS + uint16_t *magic_key_pos = (uint16_t *)MAGIC_KEY_POS; + *magic_key_pos = MAGIC_KEY; +#endif + wdt_enable(WDTO_15MS); + while (true) { + delay(1); + } +} \ No newline at end of file diff --git a/HAL/pico/include/comms/ConfiguratorBackend.hpp b/HAL/pico/include/comms/ConfiguratorBackend.hpp new file mode 100644 index 00000000..28f4f61a --- /dev/null +++ b/HAL/pico/include/comms/ConfiguratorBackend.hpp @@ -0,0 +1,55 @@ +/* + * This file is part of HayBox + * Copyright (C) 2024 Jonathan Haylett + * + * HayBox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software. If not, see . + */ + +#ifndef _COMMS_CONFIGURATORBACKEND_HPP +#define _COMMS_CONFIGURATORBACKEND_HPP + +#include "cobs/Print.h" +#include "cobs/Stream.h" +#include "core/CommunicationBackend.hpp" + +#include + +class ConfiguratorBackend : public CommunicationBackend { + public: + ConfiguratorBackend( + InputState &inputs, + InputSource **input_sources, + size_t input_source_count, + Config &config, + Stream &stream + ); + CommunicationBackendId BackendId(); + void SendReport(); + + private: + size_t ReadPacket(uint8_t *buffer, size_t max_len); + int ReadByte(); + void SkipToNextPacket(); + bool WritePacket(Command command_id, uint8_t *buffer, size_t len); + bool HandleUnknownCommand(Command command); + bool HandleGetDeviceInfo(); + bool HandleGetConfig(); + bool HandleSetConfig(); + + packetio::COBSStream _in; + packetio::COBSPrint _out; + Stream &_base_stream; + Config &_config; +}; + +#endif \ No newline at end of file diff --git a/HAL/pico/include/comms/DInputBackend.hpp b/HAL/pico/include/comms/DInputBackend.hpp index 08af6eb0..1758c361 100644 --- a/HAL/pico/include/comms/DInputBackend.hpp +++ b/HAL/pico/include/comms/DInputBackend.hpp @@ -9,12 +9,13 @@ class DInputBackend : public CommunicationBackend { public: - DInputBackend(InputSource **input_sources, size_t input_source_count); + DInputBackend(InputState &inputs, InputSource **input_sources, size_t input_source_count); ~DInputBackend(); + CommunicationBackendId BackendId(); void SendReport(); private: - TUGamepad *_gamepad; + TUGamepad _gamepad; }; #endif diff --git a/HAL/pico/include/comms/GamecubeBackend.hpp b/HAL/pico/include/comms/GamecubeBackend.hpp index 742e030e..c08c3e7e 100644 --- a/HAL/pico/include/comms/GamecubeBackend.hpp +++ b/HAL/pico/include/comms/GamecubeBackend.hpp @@ -9,6 +9,7 @@ class GamecubeBackend : public CommunicationBackend { public: GamecubeBackend( + InputState &inputs, InputSource **input_sources, size_t input_source_count, uint data_pin, @@ -16,12 +17,12 @@ class GamecubeBackend : public CommunicationBackend { int sm = -1, int offset = -1 ); - ~GamecubeBackend(); + CommunicationBackendId BackendId(); void SendReport(); int GetOffset(); private: - GamecubeConsole *_gamecube; + GamecubeConsole _gamecube; gc_report_t _report; }; diff --git a/HAL/pico/include/comms/N64Backend.hpp b/HAL/pico/include/comms/N64Backend.hpp index b9c9972e..f82c676b 100644 --- a/HAL/pico/include/comms/N64Backend.hpp +++ b/HAL/pico/include/comms/N64Backend.hpp @@ -9,6 +9,7 @@ class N64Backend : public CommunicationBackend { public: N64Backend( + InputState &inputs, InputSource **input_sources, size_t input_source_count, uint data_pin, @@ -16,12 +17,12 @@ class N64Backend : public CommunicationBackend { int sm = -1, int offset = -1 ); - ~N64Backend(); + CommunicationBackendId BackendId(); void SendReport(); int GetOffset(); private: - N64Console *_n64; + N64Console _n64; n64_report_t _report; }; diff --git a/HAL/pico/include/comms/NeoPixelBackend.hpp b/HAL/pico/include/comms/NeoPixelBackend.hpp new file mode 100644 index 00000000..0b5e348a --- /dev/null +++ b/HAL/pico/include/comms/NeoPixelBackend.hpp @@ -0,0 +1,89 @@ +#ifndef _COMMS_NEOPIXELBACKEND_HPP +#define _COMMS_NEOPIXELBACKEND_HPP + +#include "core/CommunicationBackend.hpp" + +#include +#include +#include + +template class NeoPixelBackend : public CommunicationBackend { + public: + NeoPixelBackend( + InputState &inputs, + InputSource **input_sources, + size_t input_source_count, + const Button *button_mappings, + const RgbConfig *rgb_configs, + const size_t rgb_configs_count, + const uint8_t &brightness + ) + : CommunicationBackend(inputs, input_sources, input_source_count), + _button_mappings(button_mappings), + _rgb_configs(rgb_configs), + _rgb_configs_count(rgb_configs_count), + _brightness(brightness) { + FastLED.addLeds(_leds, led_count); + FastLED.setMaxPowerInVoltsAndMilliamps(5, 200); + FastLED.setMaxRefreshRate(0); + } + + ~NeoPixelBackend() { FastLED.clear(true); } + + virtual void SetGameMode(InputMode *gamemode) { + // Clear current button colors. + for (size_t i = 0; i < button_colors_count; i++) { + _button_colors[i] = 0; + } + + if (gamemode == nullptr || gamemode->GetConfig() == nullptr) { + _config = nullptr; + return; + } + + uint8_t rgb_config_id = gamemode->GetConfig()->rgb_config; + if (rgb_config_id == 0 || rgb_config_id > _rgb_configs_count) { + _config = nullptr; + return; + } + _config = &_rgb_configs[rgb_config_id - 1]; + + // Build new mapping array to give O(n) lookup later. + for (size_t i = 0; i < _config->button_colors_count; i++) { + ButtonToColorMapping mapping = _config->button_colors[i]; + _button_colors[max(0, mapping.button - 1)] = mapping.color; + } + } + + virtual void SendReport() { + // Use timeout to avoid refreshing too fast which results in FastLED library blocking. + if (!time_reached(_refresh_timeout)) { + return; + } + _refresh_timeout = make_timeout_time_ms(refresh_interval_ms); + + for (int i = 0; i < led_count; i++) { + Button button = this->_button_mappings[i]; + _leds[i] = _config != nullptr ? _button_colors[max(0, button - 1)] : 0; + } + FastLED.setBrightness(_brightness); + FastLED.show(); + } + + protected: + static constexpr size_t button_colors_count = + sizeof(RgbConfig::button_colors) / sizeof(ButtonToColorMapping); + static constexpr uint64_t refresh_interval_ms = 4; // 250Hz refresh rate + + const Button *_button_mappings; + const RgbConfig *_rgb_configs; + const size_t _rgb_configs_count; + const RgbConfig *_config = nullptr; + const uint8_t &_brightness; + + CRGB _leds[led_count]; + uint32_t _button_colors[button_colors_count]; + absolute_time_t _refresh_timeout = 0; +}; + +#endif \ No newline at end of file diff --git a/HAL/pico/include/comms/NesBackend.hpp b/HAL/pico/include/comms/NesBackend.hpp new file mode 100644 index 00000000..83378a88 --- /dev/null +++ b/HAL/pico/include/comms/NesBackend.hpp @@ -0,0 +1,30 @@ +#ifndef _COMMS_NESBACKEND_HPP +#define _COMMS_NESBACKEND_HPP + +#include "core/CommunicationBackend.hpp" + +#include + +class NesBackend : public CommunicationBackend { + public: + NesBackend( + InputState &inputs, + InputSource **input_sources, + size_t input_source_count, + uint data_pin, + uint clock_pin, + uint latch_pin, + PIO pio = pio0, + int sm = -1, + int offset = -1 + ); + CommunicationBackendId BackendId(); + void SendReport(); + int GetOffset(); + + private: + NesConsole _nes; + nes_report_t _report; +}; + +#endif \ No newline at end of file diff --git a/HAL/pico/include/comms/NintendoSwitchBackend.hpp b/HAL/pico/include/comms/NintendoSwitchBackend.hpp index 693d00bf..1f5c76f0 100644 --- a/HAL/pico/include/comms/NintendoSwitchBackend.hpp +++ b/HAL/pico/include/comms/NintendoSwitchBackend.hpp @@ -41,16 +41,21 @@ typedef struct __attribute__((packed, aligned(1))) { class NintendoSwitchBackend : public CommunicationBackend { public: - NintendoSwitchBackend(InputSource **input_sources, size_t input_source_count); + NintendoSwitchBackend( + InputState &inputs, + InputSource **input_sources, + size_t input_source_count + ); ~NintendoSwitchBackend(); static void RegisterDescriptor(); + CommunicationBackendId BackendId(); void SendReport(); protected: static const uint8_t _report_id = 0; - static uint8_t _descriptor[]; + static const uint8_t _descriptor[]; switch_gamepad_report_t _report; diff --git a/HAL/pico/include/comms/SnesBackend.hpp b/HAL/pico/include/comms/SnesBackend.hpp new file mode 100644 index 00000000..aca980c9 --- /dev/null +++ b/HAL/pico/include/comms/SnesBackend.hpp @@ -0,0 +1,30 @@ +#ifndef _COMMS_SNESBACKEND_HPP +#define _COMMS_SNESBACKEND_HPP + +#include "core/CommunicationBackend.hpp" + +#include + +class SnesBackend : public CommunicationBackend { + public: + SnesBackend( + InputState &inputs, + InputSource **input_sources, + size_t input_source_count, + uint data_pin, + uint clock_pin, + uint latch_pin, + PIO pio = pio0, + int sm = -1, + int offset = -1 + ); + CommunicationBackendId BackendId(); + void SendReport(); + int GetOffset(); + + private: + SnesConsole _snes; + snes_report_t _report; +}; + +#endif \ No newline at end of file diff --git a/HAL/pico/include/comms/XInputBackend.hpp b/HAL/pico/include/comms/XInputBackend.hpp index 6dfefd79..d0f884c9 100644 --- a/HAL/pico/include/comms/XInputBackend.hpp +++ b/HAL/pico/include/comms/XInputBackend.hpp @@ -9,12 +9,12 @@ class XInputBackend : public CommunicationBackend { public: - XInputBackend(InputSource **input_sources, size_t input_source_count); - ~XInputBackend(); + XInputBackend(InputState &inputs, InputSource **input_sources, size_t input_source_count); + CommunicationBackendId BackendId(); void SendReport(); private: - Adafruit_USBD_XInput *_xinput; + Adafruit_USBD_XInput _xinput; xinput_report_t _report = {}; }; diff --git a/HAL/pico/include/config_defaults.hpp b/HAL/pico/include/config_defaults.hpp new file mode 100644 index 00000000..3cdb27e7 --- /dev/null +++ b/HAL/pico/include/config_defaults.hpp @@ -0,0 +1,260 @@ +#ifndef _CONFIG_DEFAULTS_HPP +#define _CONFIG_DEFAULTS_HPP + +#include +#include + +// clang-format off + +const Config default_config = { + .game_mode_configs_count = 10, + .game_mode_configs = { + GameModeConfig { + .mode_id = MODE_MELEE, + .socd_pairs_count = 4, + .socd_pairs = { + SocdPair { .button_dir1 = BTN_LF3, .button_dir2 = BTN_LF1, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_LF2, .button_dir2 = BTN_RF4, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_RT3, .button_dir2 = BTN_RT5, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_RT2, .button_dir2 = BTN_RT4, .socd_type = SOCD_NEUTRAL }, + }, + .button_remapping_count = 0, + .activation_binding_count = 3, + .activation_binding = { BTN_LT1, BTN_MB1, BTN_LF4 }, + }, + GameModeConfig { + .mode_id = MODE_PROJECT_M, + .socd_pairs_count = 4, + .socd_pairs = { + SocdPair { .button_dir1 = BTN_LF3, .button_dir2 = BTN_LF1, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_LF2, .button_dir2 = BTN_RF4, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_RT3, .button_dir2 = BTN_RT5, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_RT2, .button_dir2 = BTN_RT4, .socd_type = SOCD_NEUTRAL }, + }, + .button_remapping_count = 1, + .button_remapping = { + ButtonRemap { .physical_button = BTN_LF5, .activates = BTN_RF4 }, // LF5 activates Up + }, + .activation_binding_count = 3, + .activation_binding = { BTN_LT1, BTN_MB1, BTN_LF3 }, + }, + GameModeConfig { + .mode_id = MODE_ULTIMATE, + .socd_pairs_count = 4, + .socd_pairs = { + SocdPair { .button_dir1 = BTN_LF3, .button_dir2 = BTN_LF1, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_LF2, .button_dir2 = BTN_RF4, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_RT3, .button_dir2 = BTN_RT5, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_RT2, .button_dir2 = BTN_RT4, .socd_type = SOCD_NEUTRAL }, + }, + .button_remapping_count = 1, + .button_remapping = { + ButtonRemap { .physical_button = BTN_LF5, .activates = BTN_RF4 }, // LF5 activates Up + }, + .activation_binding_count = 3, + .activation_binding = { BTN_LT1, BTN_MB1, BTN_LF2 }, + }, + GameModeConfig { + .mode_id = MODE_PROJECT_M, + .socd_pairs_count = 4, + .socd_pairs = { + SocdPair { .button_dir1 = BTN_LF3, .button_dir2 = BTN_LF1, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_LF2, .button_dir2 = BTN_RF4, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_RT3, .button_dir2 = BTN_RT5, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_RT2, .button_dir2 = BTN_RT4, .socd_type = SOCD_NEUTRAL }, + }, + .button_remapping_count = 1, + .button_remapping = { + ButtonRemap { .physical_button = BTN_LF5, .activates = BTN_RT1 }, // LF5 activates A + }, + .activation_binding_count = 3, + .activation_binding = { BTN_LT1, BTN_MB1, BTN_RT1 }, + }, + GameModeConfig { + .mode_id = MODE_PROJECT_M, + .socd_pairs_count = 4, + .socd_pairs = { + SocdPair { .button_dir1 = BTN_LF3, .button_dir2 = BTN_LF1, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_LF2, .button_dir2 = BTN_RF4, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_RT3, .button_dir2 = BTN_RT5, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_RT2, .button_dir2 = BTN_RT4, .socd_type = SOCD_NEUTRAL }, + }, + .button_remapping_count = 1, + .button_remapping = { + ButtonRemap { .physical_button = BTN_LF5, .activates = BTN_LF15 }, // LF5 activates C45 + }, + .activation_binding_count = 3, + .activation_binding = { BTN_LT1, BTN_MB1, BTN_RF7 }, + }, + GameModeConfig { + .mode_id = MODE_PROJECT_M, + .socd_pairs_count = 4, + .socd_pairs = { + SocdPair { .button_dir1 = BTN_LF3, .button_dir2 = BTN_LF1, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_LF2, .button_dir2 = BTN_RF4, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_RT3, .button_dir2 = BTN_RT5, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_RT2, .button_dir2 = BTN_RT4, .socd_type = SOCD_NEUTRAL }, + }, + .button_remapping_count = 2, + .button_remapping = { + ButtonRemap { .physical_button = BTN_LF5, .activates = BTN_LF14 }, // LF5 activates DP* + ButtonRemap { .physical_button = BTN_RF8, .activates = BTN_RF16 }, // D-Pad Down instead of D-Pad Up + }, + .activation_binding_count = 3, + .activation_binding = { BTN_LT1, BTN_MB1, BTN_RF8 }, + }, + GameModeConfig { + .mode_id = MODE_FGC, + .socd_pairs_count = 4, + .socd_pairs = { + SocdPair { .button_dir1 = BTN_LF3, .button_dir2 = BTN_LF1, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_LF2, .button_dir2 = BTN_RF4, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_RT3, .button_dir2 = BTN_RT5, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_RT2, .button_dir2 = BTN_RT4, .socd_type = SOCD_NEUTRAL }, + }, + .button_remapping_count = 1, + .button_remapping = { + ButtonRemap { .physical_button = BTN_LF5, .activates = BTN_RF4 }, // LF5 activates Up + }, + .activation_binding_count = 3, + .activation_binding = { BTN_LT1, BTN_MB1, BTN_LF5 }, + }, + GameModeConfig { + .mode_id = MODE_RIVALS_OF_AETHER, + .socd_pairs_count = 4, + .socd_pairs = { + SocdPair { .button_dir1 = BTN_LF3, .button_dir2 = BTN_LF1, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_LF2, .button_dir2 = BTN_RF4, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_RT3, .button_dir2 = BTN_RT5, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_RT2, .button_dir2 = BTN_RT4, .socd_type = SOCD_NEUTRAL }, + }, + .button_remapping_count = 1, + .button_remapping = { + ButtonRemap { .physical_button = BTN_LF5, .activates = BTN_RF4 }, // LF5 activates Up + }, + .activation_binding_count = 3, + .activation_binding = { BTN_LT1, BTN_MB1, BTN_RF1 }, + }, + GameModeConfig { + .mode_id = MODE_RIVALS_2, + .socd_pairs_count = 4, + .socd_pairs = { + SocdPair { .button_dir1 = BTN_LF3, .button_dir2 = BTN_LF1, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_LF2, .button_dir2 = BTN_RF4, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_RT3, .button_dir2 = BTN_RT5, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_RT2, .button_dir2 = BTN_RT4, .socd_type = SOCD_NEUTRAL }, + }, + .button_remapping_count = 1, + .button_remapping = { + ButtonRemap { .physical_button = BTN_LF5, .activates = BTN_RF4 }, // LF5 activates Up + }, + .activation_binding_count = 3, + .activation_binding = { BTN_LT1, BTN_MB1, BTN_RF5 }, + }, + GameModeConfig { + .mode_id = MODE_CUSTOM, + .socd_pairs_count = 2, + .socd_pairs = { + SocdPair { .button_dir1 = BTN_LF3, .button_dir2 = BTN_LF1, .socd_type = SOCD_NEUTRAL }, + SocdPair { .button_dir1 = BTN_LF2, .button_dir2 = BTN_LT1, .socd_type = SOCD_NEUTRAL }, + }, + .button_remapping_count = 2, + .button_remapping = { + ButtonRemap { .physical_button = BTN_RT4, .activates = BTN_LT1 }, + ButtonRemap { .physical_button = BTN_LF5, .activates = BTN_LT1 }, + }, + .activation_binding = { BTN_LT1, BTN_MB1, BTN_LF1 }, + .custom_mode_config = 1, + }, + }, + .communication_backend_configs_count = 8, + .communication_backend_configs = { + CommunicationBackendConfig { + .backend_id = COMMS_BACKEND_XINPUT, + .default_mode_config = 2, + }, + CommunicationBackendConfig { + .backend_id = COMMS_BACKEND_DINPUT, + .default_mode_config = 2, + .activation_binding_count = 1, + .activation_binding = { BTN_RF3 }, + }, + CommunicationBackendConfig { + .backend_id = COMMS_BACKEND_NINTENDO_SWITCH, + .default_mode_config = 3, + .activation_binding_count = 1, + .activation_binding = { BTN_RF2 }, + }, + CommunicationBackendConfig { + .backend_id = COMMS_BACKEND_GAMECUBE, + .default_mode_config = 2, + }, + CommunicationBackendConfig { + .backend_id = COMMS_BACKEND_N64, + .default_mode_config = 1, + .activation_binding = { BTN_RT3 }, + }, + CommunicationBackendConfig { + .backend_id = COMMS_BACKEND_NES, + .default_mode_config = 1, + .activation_binding_count = 1, + .activation_binding = { BTN_LT1 }, + }, + CommunicationBackendConfig { + .backend_id = COMMS_BACKEND_SNES, + .default_mode_config = 1, + .activation_binding_count = 1, + .activation_binding = { BTN_LT2 }, + }, + CommunicationBackendConfig { + .backend_id = COMMS_BACKEND_CONFIGURATOR, + .activation_binding_count = 1, + .activation_binding = { BTN_MB1 }, + } + }, + .custom_modes_count = 1, + .custom_modes = { + CustomModeConfig { + .digital_button_mappings_count = 9, + .digital_button_mappings = { + { BTN_RF1 }, + { BTN_RF2 }, + { BTN_RF5 }, + { BTN_RF6 }, + { BTN_RF8 }, + { BTN_RF7 }, + { BTN_RF3 }, + { BTN_RF4 }, + { BTN_MB1 }, + { BTN_RT3 }, + { BTN_RT1 }, + { BTN_RT5 }, + { BTN_LT1 }, + { BTN_LF2 }, + { BTN_LF3 }, + { BTN_LF1 }, + { BTN_LT2 }, + { BTN_RT2 }, + }, + .analog_trigger_mappings = { + { BTN_RF4, TRIGGER_LT, 255 }, + { BTN_RF3, TRIGGER_RT, 255 }, + }, + } + }, + .keyboard_modes_count = 0, + .default_backend_config = 1, + .default_usb_backend_config = 1, + .melee_options = { + .crouch_walk_os = false, + .disable_ledgedash_socd_override = true, + }, + .project_m_options = { + .true_z_press = true, + .disable_ledgedash_socd_override = true, + }, +}; + +// clang-format on + +#endif diff --git a/HAL/pico/include/core/KeyboardMode.hpp b/HAL/pico/include/core/KeyboardMode.hpp index e2d5e79f..b32a09db 100644 --- a/HAL/pico/include/core/KeyboardMode.hpp +++ b/HAL/pico/include/core/KeyboardMode.hpp @@ -6,12 +6,15 @@ #include "core/state.hpp" #include +#include class KeyboardMode : public InputMode { public: KeyboardMode(); ~KeyboardMode(); - void SendReport(InputState &inputs); + void SendReport(const InputState &inputs); + + void UpdateOutputs(const InputState &inputs, OutputState &outputs) {} protected: void Press(uint8_t keycode, bool press); @@ -19,7 +22,7 @@ class KeyboardMode : public InputMode { private: TUKeyboard *_keyboard; - virtual void UpdateKeys(InputState &inputs) = 0; + virtual void UpdateKeys(const InputState &inputs) = 0; }; #endif diff --git a/HAL/pico/include/core/Persistence.hpp b/HAL/pico/include/core/Persistence.hpp new file mode 100644 index 00000000..43cbd3f3 --- /dev/null +++ b/HAL/pico/include/core/Persistence.hpp @@ -0,0 +1,49 @@ +/* + * This file is part of HayBox + * Copyright (C) 2024 Jonathan Haylett + * + * HayBox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software. If not, see . + */ + +#ifndef _CORE_PERSISTENCE_HPP +#define _CORE_PERSISTENCE_HPP + +#include +#include + +class Persistence { + public: + typedef struct _ConfigHeader { + size_t config_size = 0; + uint32_t config_crc = 0; + } ConfigHeader; + + Persistence(); + ~Persistence(); + + bool SaveConfig(Config &config); + bool LoadConfig(Config &config); + bool CheckSavedConfig(); + size_t LoadConfigRaw(Print &out, bool validate = true); + + static constexpr size_t config_offset = sizeof(ConfigHeader); + + private: + static constexpr char config_filename[] = "config.bin"; + + bool CheckSavedConfig(File &config_file); +}; + +extern Persistence persistence; + +#endif \ No newline at end of file diff --git a/HAL/pico/include/display/ConfigMenu.hpp b/HAL/pico/include/display/ConfigMenu.hpp new file mode 100644 index 00000000..55e4b5f1 --- /dev/null +++ b/HAL/pico/include/display/ConfigMenu.hpp @@ -0,0 +1,61 @@ +#ifndef _DISPLAY_CONFIGMENU_HPP +#define _DISPLAY_CONFIGMENU_HPP + +#include "comms/IntegratedDisplay.hpp" +#include "display/DisplayMode.hpp" + +#include + +class ConfigMenu; + +// clang-format off + +typedef struct _MenuPage { + typedef struct _MenuItem { + char text[20]; + uint8_t key = 0; + void (*action)( + IntegratedDisplay *instance, + ConfigMenu *menu, + Config &config, + uint8_t key + ) = nullptr; + struct _MenuPage *page = nullptr; + } MenuItem; + + struct _MenuPage *parent = nullptr; + MenuItem *items = nullptr; + size_t items_count = 0; +} MenuPage; + +// clang-format on + +class ConfigMenu : public DisplayMode { + public: + ConfigMenu(Config &config, CommunicationBackend **backends, size_t backends_count); + DisplayModeId GetId(); + virtual void HandleControls( + IntegratedDisplay *instance, + const DisplayControls &controls, + Button button + ); + virtual void UpdateDisplay(IntegratedDisplay *instance, Adafruit_GFX &display); + virtual void ReturnToDashboard(IntegratedDisplay *instance); + + protected: + Config &_config; + CommunicationBackend **_backends; + size_t _backends_count; + + MenuPage *_top_level_page = nullptr; + MenuPage *_current_menu_page = nullptr; + uint8_t _highlighted_menu_item = 0; + int _current_menu_offset = 0; + + private: + static constexpr uint8_t padding = 2; + static constexpr uint8_t max_visible_lines = 6; + static constexpr char highlight_string[] = ">"; +}; + +#endif \ No newline at end of file diff --git a/HAL/pico/include/display/DefaultConfigMenu.hpp b/HAL/pico/include/display/DefaultConfigMenu.hpp new file mode 100644 index 00000000..49a6e628 --- /dev/null +++ b/HAL/pico/include/display/DefaultConfigMenu.hpp @@ -0,0 +1,40 @@ +#ifndef _DISPLAY_DEFAULTCONFIGMENU_HPP +#define _DISPLAY_DEFAULTCONFIGMENU_HPP + +#include "display/ConfigMenu.hpp" + +class DefaultConfigMenu : public ConfigMenu { + public: + DefaultConfigMenu(Config &config, CommunicationBackend **backends, size_t backends_count); + ~DefaultConfigMenu(); + + protected: + static void SetDefaultMode( + IntegratedDisplay *instance, + ConfigMenu *menu, + Config &config, + uint8_t mode_config_index + ); + static void SetUsbBackend( + IntegratedDisplay *instance, + ConfigMenu *menu, + Config &config, + uint8_t backend_config_index + ); + static void SetSocdType( + IntegratedDisplay *instance, + ConfigMenu *menu, + Config &config, + uint8_t socd_type + ); + + MenuPage _usb_backends_page; + MenuPage _gamemode_options_page; + + private: + static constexpr uint8_t padding = 2; + static constexpr uint8_t max_visible_lines = 6; + static constexpr char highlight_string[] = ">"; +}; + +#endif \ No newline at end of file diff --git a/HAL/pico/include/display/DisplayMode.hpp b/HAL/pico/include/display/DisplayMode.hpp new file mode 100644 index 00000000..047398b9 --- /dev/null +++ b/HAL/pico/include/display/DisplayMode.hpp @@ -0,0 +1,31 @@ +#ifndef _DISPLAY_DISPLAYMODE_HPP +#define _DISPLAY_DISPLAYMODE_HPP + +#include +#include + +typedef struct _DisplayControls DisplayControls; +class IntegratedDisplay; + +typedef enum _DisplayModeId { + DISPLAY_MODE_VIEWER, + DISPLAY_MODE_CONFIG, + DISPLAY_MODE_RGB_BRIGHTNESS, + DISPLAY_MODE_BUTTON_HINTS +} DisplayModeId; + +class DisplayMode { + public: + DisplayMode(){}; + virtual ~DisplayMode(){}; + + virtual DisplayModeId GetId() = 0; + virtual void HandleControls( + IntegratedDisplay *instance, + const DisplayControls &controls, + Button button + ) = 0; + virtual void UpdateDisplay(IntegratedDisplay *instance, Adafruit_GFX &display) = 0; +}; + +#endif diff --git a/HAL/pico/include/display/InputDisplay.hpp b/HAL/pico/include/display/InputDisplay.hpp new file mode 100644 index 00000000..641dc7e6 --- /dev/null +++ b/HAL/pico/include/display/InputDisplay.hpp @@ -0,0 +1,38 @@ +#ifndef _DISPLAY_INPUTDISPLAY_HPP +#define _DISPLAY_INPUTDISPLAY_HPP + +#include "display/DisplayMode.hpp" + +typedef struct _InputViewerButton { + Button button; + uint8_t center_x; + uint8_t center_y; + uint8_t radius; +} InputViewerButton; + +class InputDisplay : public DisplayMode { + public: + InputDisplay( + InputViewerButton *input_viewer_buttons, + size_t input_viewer_buttons_count, + CommunicationBackendId backend_id + ); + DisplayModeId GetId(); + void HandleControls( + IntegratedDisplay *instance, + const DisplayControls &controls, + Button button + ); + void UpdateDisplay(IntegratedDisplay *instance, Adafruit_GFX &display); + void UpdateButtonLayout( + InputViewerButton *input_viewer_buttons, + size_t input_viewer_buttons_count + ); + + protected: + InputViewerButton *_input_viewer_buttons; + size_t _input_viewer_buttons_count; + CommunicationBackendId _backend_id; +}; + +#endif \ No newline at end of file diff --git a/HAL/pico/include/display/RgbBrightnessMenu.hpp b/HAL/pico/include/display/RgbBrightnessMenu.hpp new file mode 100644 index 00000000..d44ee328 --- /dev/null +++ b/HAL/pico/include/display/RgbBrightnessMenu.hpp @@ -0,0 +1,21 @@ +#ifndef _DISPLAY_RGBBRIGHTNESSMENU_HPP +#define _DISPLAY_RGBBRIGHTNESSMENU_HPP + +#include "display/DisplayMode.hpp" + +class RgbBrightnessMenu : public DisplayMode { + public: + RgbBrightnessMenu(Config &config); + DisplayModeId GetId(); + void HandleControls( + IntegratedDisplay *instance, + const DisplayControls &controls, + Button button + ); + void UpdateDisplay(IntegratedDisplay *instance, Adafruit_GFX &display); + + private: + Config &_config; +}; + +#endif \ No newline at end of file diff --git a/HAL/pico/include/input/DebouncedGpioButtonInput.hpp b/HAL/pico/include/input/DebouncedGpioButtonInput.hpp new file mode 100644 index 00000000..916c34d9 --- /dev/null +++ b/HAL/pico/include/input/DebouncedGpioButtonInput.hpp @@ -0,0 +1,35 @@ +#ifndef _INPUT_DEBOUNCEDGPIOBUTTONINPUT_HPP +#define _INPUT_DEBOUNCEDGPIOBUTTONINPUT_HPP + +#include "core/state.hpp" +#include "input/GpioButtonInput.hpp" +#include "input/debounce.hpp" +#include "util/state_util.hpp" + +template class DebouncedGpioButtonInput : public GpioButtonInput { + public: + DebouncedGpioButtonInput( + const GpioButtonMapping button_mappings[button_count], + uint32_t debounce_period_ms = 5 + ) + : GpioButtonInput(button_mappings, button_count) { + _debounce_period_ms = debounce_period_ms; + } + + private: + DebounceState _debounce_state[button_count]; + uint32_t _debounce_period_ms; + + void UpdateButtonState(InputState &inputs, size_t button_mapping_index, bool pressed) { + bool state_changed = update_debounce_state( + _debounce_state[button_mapping_index], + pressed, + _debounce_period_ms + ); + if (state_changed) { + set_button(inputs.buttons, _button_mappings[button_mapping_index].button, pressed); + } + } +}; + +#endif \ No newline at end of file diff --git a/HAL/pico/include/input/DebouncedSwitchMatrixInput.hpp b/HAL/pico/include/input/DebouncedSwitchMatrixInput.hpp new file mode 100644 index 00000000..bd6d6dd8 --- /dev/null +++ b/HAL/pico/include/input/DebouncedSwitchMatrixInput.hpp @@ -0,0 +1,38 @@ +#ifndef _INPUT_DEBOUNCEDSWITCHMATRIXINPUT_HPP +#define _INPUT_DEBOUNCEDSWITCHMATRIXINPUT_HPP + +#include "input/SwitchMatrixInput.hpp" +#include "input/debounce.hpp" + +template +class DebouncedSwitchMatrixInput : public SwitchMatrixInput { + public: + DebouncedSwitchMatrixInput( + const uint row_pins[num_rows], + const uint col_pins[num_cols], + const Button (&matrix)[num_rows][num_cols], + DiodeDirection direction, + uint32_t debounce_period_ms = 5 + ) + : SwitchMatrixInput(row_pins, col_pins, matrix, direction) { + _debounce_period_ms = debounce_period_ms; + } + + private: + DebounceState _debounce_state[num_rows][num_cols]; + uint32_t _debounce_period_ms; + + void UpdateButtonState(InputState &inputs, size_t col_index, size_t row_index, bool pressed) { + bool state_changed = update_debounce_state( + _debounce_state[col_index][row_index], + pressed, + _debounce_period_ms + ); + if (state_changed) { + Button button = this->_matrix[col_index][row_index]; + set_button(inputs.buttons, button, pressed); + } + }; +}; + +#endif diff --git a/HAL/pico/include/input/Pca9671Input.hpp b/HAL/pico/include/input/Pca9671Input.hpp new file mode 100644 index 00000000..1ed9d404 --- /dev/null +++ b/HAL/pico/include/input/Pca9671Input.hpp @@ -0,0 +1,37 @@ +#ifndef _INPUT_PCA9671INPUT_HPP +#define _INPUT_PCA9671INPUT_HPP + +#include "core/InputSource.hpp" + +#include +#include +#include + +typedef struct { + Button button; + uint8_t bit; +} Pca9671ButtonMapping; + +class Pca9671Input : public InputSource { + public: + Pca9671Input( + const Pca9671ButtonMapping *button_mappings, + size_t button_count, + TwoWire &wire = Wire, + int sda_pin = -1, + int scl_pin = -1, + uint8_t i2c_addr = 0x20 + ); + InputScanSpeed ScanSpeed(); + void UpdateInputs(InputState &inputs); + + protected: + const Pca9671ButtonMapping *_button_mappings; + size_t _button_count; + PCF8575 _pcf; + + private: + virtual void UpdateButtonState(InputState &inputs, size_t button_mapping_index, bool pressed); +}; + +#endif \ No newline at end of file diff --git a/HAL/pico/include/input/debounce.hpp b/HAL/pico/include/input/debounce.hpp new file mode 100644 index 00000000..3ce9513d --- /dev/null +++ b/HAL/pico/include/input/debounce.hpp @@ -0,0 +1,31 @@ +#ifndef _DEBOUNCE_HPP +#define _DEBOUNCE_HPP + +#include "stdlib.hpp" + +typedef struct _DebounceState { + absolute_time_t locked_until = 0; + bool pressed = false; +} DebounceState; + +inline bool update_debounce_state( + DebounceState &debounce_state, + bool current_reading, + uint32_t debounce_period_ms +) { + // If currently locked out, do nothing. + if (!time_reached(debounce_state.locked_until)) { + return false; + } + + // If latest reading differs from the last known state, update the value and timeout. + if (current_reading != debounce_state.pressed) { + debounce_state.pressed = current_reading; + debounce_state.locked_until = make_timeout_time_ms(debounce_period_ms); + return true; + } + + return false; +} + +#endif \ No newline at end of file diff --git a/HAL/pico/include/joybus_utils.hpp b/HAL/pico/include/joybus_utils.hpp deleted file mode 100644 index 2bb493c4..00000000 --- a/HAL/pico/include/joybus_utils.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef _JOYBUS_UTILS_HPP -#define _JOYBUS_UTILS_HPP - -#include - -enum class ConnectedConsole { - GAMECUBE, - N64, - NONE, -}; - -ConnectedConsole detect_console(uint joybus_pin); - -#endif \ No newline at end of file diff --git a/HAL/pico/include/util/state_util.hpp b/HAL/pico/include/util/state_util.hpp new file mode 100644 index 00000000..40b8aeb9 --- /dev/null +++ b/HAL/pico/include/util/state_util.hpp @@ -0,0 +1,115 @@ +#ifndef _UTIL_STATE_UTIL_HPP +#define _UTIL_STATE_UTIL_HPP + +#include "stdlib.hpp" + +#include + +/* InputState utils */ + +inline void set_button(uint64_t &buttons, Button button_index, bool pressed) { + if (button_index == BTN_UNSPECIFIED) { + return; + } + Button button_index_adjusted = (Button)(button_index - 1); + buttons = + (buttons & ~(1ULL << button_index_adjusted)) | ((uint64_t)pressed << button_index_adjusted); +} + +inline bool get_button(const uint64_t &buttons, Button button_index) { + if (button_index == BTN_UNSPECIFIED) { + return false; + } + return buttons & (1ULL << (button_index - 1)); +} + +inline uint64_t make_button_mask(const Button *buttons, size_t buttons_count) { + uint64_t button_mask = 0; + for (size_t j = 0; j < buttons_count; j++) { + button_mask |= (1ULL << (buttons[j] - 1)); + } + return button_mask; +} + +inline bool all_buttons_held(const uint64_t &buttons, uint64_t button_mask) { + return button_mask != 0 && (buttons & button_mask) == button_mask; +} + +inline bool any_button_held(const uint64_t &buttons, uint64_t button_mask) { + return button_mask != 0 && (buttons & button_mask); +} + +/* OutputState utils */ + +inline void set_output(uint32_t &buttons, DigitalOutput output_index, bool pressed) { + if (output_index == GP_UNSPECIFIED) { + return; + } + DigitalOutput output_index_adjusted = (DigitalOutput)(output_index - 1); + buttons = + (buttons & ~(1UL << output_index_adjusted)) | ((uint32_t)pressed << output_index_adjusted); +} + +inline uint8_t OutputState::*axis_pointer(AnalogAxis axis) { + switch (axis) { + case AXIS_LSTICK_X: + return &OutputState::leftStickX; + case AXIS_LSTICK_Y: + return &OutputState::leftStickY; + case AXIS_RSTICK_X: + return &OutputState::rightStickX; + case AXIS_RSTICK_Y: + return &OutputState::rightStickY; + case AXIS_LTRIGGER: + return &OutputState::triggerLAnalog; + case AXIS_RTRIGGER: + return &OutputState::triggerRAnalog; + default: + return nullptr; + } +} + +constexpr const char *digital_output_name(DigitalOutput output) { + switch (output) { + case GP_A: + return "A"; + case GP_B: + return "B"; + case GP_X: + return "X"; + case GP_Y: + return "Y"; + case GP_LB: + return "L1"; + case GP_RB: + return "R1"; + case GP_LT: + return "L2"; + case GP_RT: + return "R2"; + case GP_START: + return "Start"; + case GP_SELECT: + return "Select"; + case GP_HOME: + return "Home"; + case GP_CAPTURE: + return "Capture"; + case GP_DPAD_UP: + return "D-Pad Up"; + case GP_DPAD_DOWN: + return "D-Pad Down"; + case GP_DPAD_LEFT: + return "D-Pad Left"; + case GP_DPAD_RIGHT: + return "D-Pad Right"; + case GP_LSTICK_CLICK: + return "L3"; + case GP_RSTICK_CLICK: + return "R3"; + default: + return "Unknown"; + } +} + +#endif \ No newline at end of file diff --git a/HAL/pico/src/comms/ConfiguratorBackend.cpp b/HAL/pico/src/comms/ConfiguratorBackend.cpp new file mode 100644 index 00000000..96c3cc91 --- /dev/null +++ b/HAL/pico/src/comms/ConfiguratorBackend.cpp @@ -0,0 +1,276 @@ +/* + * This file is part of HayBox + * Copyright (C) 2024 Jonathan Haylett + * + * HayBox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software. If not, see . + */ + +#include "comms/ConfiguratorBackend.hpp" + +#include "core/InputSource.hpp" +#include "core/Persistence.hpp" +#include "reboot.hpp" + +#include +#include +#include + +ConfiguratorBackend::ConfiguratorBackend( + InputState &inputs, + InputSource **input_sources, + size_t input_source_count, + Config &config, + Stream &stream +) + : CommunicationBackend(inputs, input_sources, input_source_count), + _in(stream), + _out(stream), + _base_stream(stream), + _config(config) {} + +CommunicationBackendId ConfiguratorBackend::BackendId() { + return COMMS_BACKEND_CONFIGURATOR; +} + +void ConfiguratorBackend::SendReport() { + int data = ReadByte(); + if (data < 0) { + if (data == _in.EOP) { + SkipToNextPacket(); + } + return; + } + + Command command = (Command)data; + switch (command) { + case CMD_GET_DEVICE_INFO: + HandleGetDeviceInfo(); + break; + case CMD_GET_CONFIG: + HandleGetConfig(); + break; + case CMD_SET_CONFIG: + HandleSetConfig(); + break; + case CMD_REBOOT_FIRMWARE: + reboot_firmware(); + break; + case CMD_REBOOT_BOOTLOADER: + reboot_bootloader(); + break; + case CMD_UNSPECIFIED: + default: + HandleUnknownCommand(command); + break; + } + + SkipToNextPacket(); +} + +size_t ConfiguratorBackend::ReadPacket(uint8_t *buffer, size_t max_len) { + size_t bytes_read = 0; + while (!_in.available()) { + delay(1); + } + while (true) { + int result = _in.read(); + if (result == _in.EOF) { + continue; + } + if (result == _in.EOP) { + break; + } + buffer[bytes_read++] = result; + if (bytes_read >= max_len) { + _in.next(); + return bytes_read; + } + } + _in.next(); + + return bytes_read; +} + +int ConfiguratorBackend::ReadByte() { + if (!_base_stream.available()) { + return -1; + } + return _in.read(); +} + +void ConfiguratorBackend::SkipToNextPacket() { + _in.next(); +} + +bool ConfiguratorBackend::WritePacket(Command command_id, uint8_t *buffer, size_t len) { + _out.write((uint8_t)command_id); + for (size_t i = 0; i < len; i++) { + _out.write(buffer[i]); + } + return _out.end(); +} + +bool ConfiguratorBackend::HandleGetDeviceInfo() { + static const DeviceInfo device_info = { + FIRMWARE_NAME, + FIRMWARE_VERSION, + DEVICE_NAME, + }; + + // Ensure device info encodes correctly. + size_t size; + if (!pb_get_encoded_size(&size, DeviceInfo_fields, &device_info)) { + char errmsg[] = "Failed to encode device info"; + WritePacket(CMD_ERROR, (uint8_t *)errmsg, sizeof(errmsg)); + return false; + } + + _out.write(CMD_SET_DEVICE_INFO); + pb_ostream_t ostream = as_pb_ostream(_out); + pb_encode(&ostream, DeviceInfo_fields, &device_info); + return _out.end(); +} + +bool ConfiguratorBackend::HandleGetConfig() { + if (!persistence.CheckSavedConfig()) { + char errmsg[] = "Config file is invalid"; + WritePacket(CMD_ERROR, (uint8_t *)errmsg, sizeof(errmsg)); + return false; + } + + // Write raw config data to output stream. + _out.write(CMD_SET_CONFIG); + persistence.LoadConfigRaw(_out, false); + return _out.end(); +} + +bool ConfiguratorBackend::HandleSetConfig() { + // Reset config defaults first, so config is completely replaced rather than merged. + _config = Config_init_default; + + pb_istream_t istream = as_pb_istream(_in); + if (!pb_decode(&istream, Config_fields, &_config)) { + char errmsg[100]; + size_t errmsg_len = + snprintf(errmsg, sizeof(errmsg), "Failed to decode config: %s", istream.errmsg); + WritePacket(CMD_ERROR, (uint8_t *)errmsg, errmsg_len); + + // Restore old config. + persistence.LoadConfig(_config); + return false; + } + + if (_config.default_backend_config > _config.communication_backend_configs_count) { + char errmsg[75]; + size_t errmsg_len = snprintf( + errmsg, + sizeof(errmsg), + "Default backend ID is %d but only %d backend configs are defined", + (uint8_t)_config.default_backend_config, + (uint8_t)_config.communication_backend_configs_count + ); + WritePacket(CMD_ERROR, (uint8_t *)errmsg, errmsg_len); + return false; + } + + for (size_t i = 0; i < _config.communication_backend_configs_count; i++) { + uint8_t default_mode_id = _config.communication_backend_configs[i].default_mode_config; + if (default_mode_id > _config.game_mode_configs_count) { + char errmsg[75]; + size_t errmsg_len = snprintf( + errmsg, + sizeof(errmsg), + "Default mode ID is %d for backend %d but only %d modes are defined", + (uint8_t)default_mode_id, + (uint8_t)i + 1, + (uint8_t)_config.game_mode_configs_count + ); + WritePacket(CMD_ERROR, (uint8_t *)errmsg, errmsg_len); + return false; + } + } + + for (size_t i = 0; i < _config.game_mode_configs_count; i++) { + const GameModeConfig &gamemode_config = _config.game_mode_configs[i]; + uint8_t keyboard_mode_id = gamemode_config.keyboard_mode_config; + uint8_t custom_mode_id = gamemode_config.custom_mode_config; + + if (keyboard_mode_id > 0 && gamemode_config.mode_id != MODE_KEYBOARD) { + char errmsg[80]; + size_t errmsg_len = snprintf( + errmsg, + sizeof(errmsg), + "keyboard_mode_id is set for game mode %d but mode_id is not MODE_KEYBOARD", + (uint8_t)i + 1 + ); + WritePacket(CMD_ERROR, (uint8_t *)errmsg, errmsg_len); + return false; + } + + if (custom_mode_id > 0 && gamemode_config.mode_id != MODE_CUSTOM) { + char errmsg[75]; + size_t errmsg_len = snprintf( + errmsg, + sizeof(errmsg), + "custom_mode_id is set for game mode %d but mode_id is not MODE_CUSTOM", + (uint8_t)i + 1 + ); + WritePacket(CMD_ERROR, (uint8_t *)errmsg, errmsg_len); + return false; + } + + if (keyboard_mode_id > _config.keyboard_modes_count) { + char errmsg[85]; + size_t errmsg_len = snprintf( + errmsg, + sizeof(errmsg), + "Keyboard mode ID %d is for game mode %d but only %d keyboard modes are defined", + (uint8_t)keyboard_mode_id, + (uint8_t)i + 1, + (uint8_t)_config.keyboard_modes_count + ); + WritePacket(CMD_ERROR, (uint8_t *)errmsg, errmsg_len); + return false; + } + + if (custom_mode_id > _config.custom_modes_count) { + char errmsg[85]; + size_t errmsg_len = snprintf( + errmsg, + sizeof(errmsg), + "Custom mode ID %d is for game mode config %d but only %d custom modes are defined", + (uint8_t)custom_mode_id, + (uint8_t)i + 1, + (uint8_t)_config.custom_modes_count + ); + WritePacket(CMD_ERROR, (uint8_t *)errmsg, errmsg_len); + return false; + } + } + + if (!persistence.SaveConfig(_config)) { + char errmsg[] = "Failed to save config to memory"; + WritePacket(CMD_ERROR, (uint8_t *)errmsg, sizeof(errmsg)); + return false; + } + + WritePacket(CMD_SUCCESS, nullptr, 0); + return true; +} + +bool ConfiguratorBackend::HandleUnknownCommand(Command command) { + char errmsg[50]; + size_t errmsg_len = + snprintf(errmsg, sizeof(errmsg), "Unknown command ID: %d", (uint8_t)command); + return WritePacket(CMD_ERROR, (uint8_t *)errmsg, errmsg_len); +} \ No newline at end of file diff --git a/HAL/pico/src/comms/DInputBackend.cpp b/HAL/pico/src/comms/DInputBackend.cpp index 5945e1e1..e1941c55 100644 --- a/HAL/pico/src/comms/DInputBackend.cpp +++ b/HAL/pico/src/comms/DInputBackend.cpp @@ -5,26 +5,28 @@ #include -DInputBackend::DInputBackend(InputSource **input_sources, size_t input_source_count) - : CommunicationBackend(input_sources, input_source_count) { - _gamepad = new TUGamepad(); - _gamepad->begin(); - - while (!USBDevice.mounted()) { - delay(1); - } +DInputBackend::DInputBackend( + InputState &inputs, + InputSource **input_sources, + size_t input_source_count +) + : CommunicationBackend(inputs, input_sources, input_source_count) { + _gamepad.begin(); } DInputBackend::~DInputBackend() { - _gamepad->resetInputs(); - delete _gamepad; + _gamepad.resetInputs(); +} + +CommunicationBackendId DInputBackend::BackendId() { + return COMMS_BACKEND_DINPUT; } void DInputBackend::SendReport() { ScanInputs(InputScanSpeed::SLOW); ScanInputs(InputScanSpeed::MEDIUM); - while (!_gamepad->ready()) { + while (!_gamepad.ready()) { tight_loop_contents(); } @@ -33,30 +35,30 @@ void DInputBackend::SendReport() { UpdateOutputs(); // Digital outputs - _gamepad->setButton(0, _outputs.b); - _gamepad->setButton(1, _outputs.a); - _gamepad->setButton(2, _outputs.y); - _gamepad->setButton(3, _outputs.x); - _gamepad->setButton(4, _outputs.buttonR); - _gamepad->setButton(5, _outputs.triggerRDigital); - _gamepad->setButton(6, _outputs.buttonL); - _gamepad->setButton(7, _outputs.triggerLDigital); - _gamepad->setButton(8, _outputs.select); - _gamepad->setButton(9, _outputs.start); - _gamepad->setButton(10, _outputs.rightStickClick); - _gamepad->setButton(11, _outputs.leftStickClick); - _gamepad->setButton(12, _outputs.home); + _gamepad.setButton(0, _outputs.b); + _gamepad.setButton(1, _outputs.a); + _gamepad.setButton(2, _outputs.y); + _gamepad.setButton(3, _outputs.x); + _gamepad.setButton(4, _outputs.buttonR); + _gamepad.setButton(5, _outputs.triggerRDigital); + _gamepad.setButton(6, _outputs.buttonL); + _gamepad.setButton(7, _outputs.triggerLDigital); + _gamepad.setButton(8, _outputs.select); + _gamepad.setButton(9, _outputs.start); + _gamepad.setButton(10, _outputs.rightStickClick); + _gamepad.setButton(11, _outputs.leftStickClick); + _gamepad.setButton(12, _outputs.home); // Analog outputs - _gamepad->leftXAxis(_outputs.leftStickX); - _gamepad->leftYAxis(255 - _outputs.leftStickY); - _gamepad->rightXAxis(_outputs.rightStickX); - _gamepad->rightYAxis(255 - _outputs.rightStickY); - _gamepad->triggerLAnalog(_outputs.triggerLAnalog + 1); - _gamepad->triggerRAnalog(_outputs.triggerRAnalog + 1); + _gamepad.leftXAxis(_outputs.leftStickX); + _gamepad.leftYAxis(255 - _outputs.leftStickY); + _gamepad.rightXAxis(_outputs.rightStickX); + _gamepad.rightYAxis(255 - _outputs.rightStickY); + _gamepad.triggerLAnalog(_outputs.triggerLAnalog + 1); + _gamepad.triggerRAnalog(_outputs.triggerRAnalog + 1); // D-pad Hat Switch - _gamepad->hatSwitch(_outputs.dpadLeft, _outputs.dpadRight, _outputs.dpadDown, _outputs.dpadUp); + _gamepad.hatSwitch(_outputs.dpadLeft, _outputs.dpadRight, _outputs.dpadDown, _outputs.dpadUp); - _gamepad->sendState(); + _gamepad.sendState(); } diff --git a/HAL/pico/src/comms/GamecubeBackend.cpp b/HAL/pico/src/comms/GamecubeBackend.cpp index a5b1bdfb..2c8ee35a 100644 --- a/HAL/pico/src/comms/GamecubeBackend.cpp +++ b/HAL/pico/src/comms/GamecubeBackend.cpp @@ -7,6 +7,7 @@ #include GamecubeBackend::GamecubeBackend( + InputState &inputs, InputSource **input_sources, size_t input_source_count, uint data_pin, @@ -14,13 +15,13 @@ GamecubeBackend::GamecubeBackend( int sm, int offset ) - : CommunicationBackend(input_sources, input_source_count) { - _gamecube = new GamecubeConsole(data_pin, pio, sm, offset); + : CommunicationBackend(inputs, input_sources, input_source_count), + _gamecube(data_pin, pio, sm, offset) { _report = default_gc_report; } -GamecubeBackend::~GamecubeBackend() { - delete _gamecube; +CommunicationBackendId GamecubeBackend::BackendId() { + return COMMS_BACKEND_GAMECUBE; } void GamecubeBackend::SendReport() { @@ -29,7 +30,7 @@ void GamecubeBackend::SendReport() { ScanInputs(InputScanSpeed::MEDIUM); // Read inputs - _gamecube->WaitForPollStart(); + _gamecube.WaitForPollStart(); // Update fast inputs in response to poll. // But wait 40us first so that we read inputs at the start of the 3rd byte of the poll command @@ -63,11 +64,11 @@ void GamecubeBackend::SendReport() { _report.r_analog = _outputs.triggerRAnalog; // Send outputs to console unless poll command is invalid. - if (_gamecube->WaitForPollEnd() != PollStatus::ERROR) { - _gamecube->SendReport(&_report); + if (_gamecube.WaitForPollEnd() != PollStatus::ERROR) { + _gamecube.SendReport(&_report); } } int GamecubeBackend::GetOffset() { - return _gamecube->GetOffset(); + return _gamecube.GetOffset(); } diff --git a/HAL/pico/src/comms/N64Backend.cpp b/HAL/pico/src/comms/N64Backend.cpp index 58ed3030..f45d2032 100644 --- a/HAL/pico/src/comms/N64Backend.cpp +++ b/HAL/pico/src/comms/N64Backend.cpp @@ -6,6 +6,7 @@ #include N64Backend::N64Backend( + InputState &inputs, InputSource **input_sources, size_t input_source_count, uint data_pin, @@ -13,13 +14,13 @@ N64Backend::N64Backend( int sm, int offset ) - : CommunicationBackend(input_sources, input_source_count) { - _n64 = new N64Console(data_pin, pio, sm, offset); + : CommunicationBackend(inputs, input_sources, input_source_count), + _n64(data_pin, pio, sm, offset) { _report = default_n64_report; } -N64Backend::~N64Backend() { - delete _n64; +CommunicationBackendId N64Backend::BackendId() { + return COMMS_BACKEND_N64; } void N64Backend::SendReport() { @@ -28,7 +29,7 @@ void N64Backend::SendReport() { ScanInputs(InputScanSpeed::MEDIUM); // Read inputs - _n64->WaitForPoll(); + _n64.WaitForPoll(); // Update fast inputs in response to poll. ScanInputs(InputScanSpeed::FAST); @@ -58,9 +59,9 @@ void N64Backend::SendReport() { _report.stick_y = _outputs.leftStickY - 128; // Send outputs to console. - _n64->SendReport(&_report); + _n64.SendReport(&_report); } int N64Backend::GetOffset() { - return _n64->GetOffset(); + return _n64.GetOffset(); } diff --git a/HAL/pico/src/comms/NesBackend.cpp b/HAL/pico/src/comms/NesBackend.cpp new file mode 100644 index 00000000..c59063e0 --- /dev/null +++ b/HAL/pico/src/comms/NesBackend.cpp @@ -0,0 +1,42 @@ +#include "comms/NesBackend.hpp" + +NesBackend::NesBackend( + InputState &inputs, + InputSource **input_sources, + size_t input_source_count, + uint data_pin, + uint clock_pin, + uint latch_pin, + PIO pio, + int sm, + int offset +) + : CommunicationBackend(inputs, input_sources, input_source_count), + _nes(data_pin, clock_pin, latch_pin, pio, sm, offset) {} + +CommunicationBackendId NesBackend::BackendId() { + return COMMS_BACKEND_NES; +} + +void NesBackend::SendReport() { + ScanInputs(); + + // Run gamemode logic + UpdateOutputs(); + + // Digital outputs + _report.a = _outputs.a; + _report.b = _outputs.b; + _report.select = _outputs.select; + _report.start = _outputs.start; + _report.dpad_left = _outputs.dpadLeft || _outputs.leftStickX < 128; + _report.dpad_right = _outputs.dpadRight || _outputs.leftStickX > 128; + _report.dpad_down = _outputs.dpadDown || _outputs.leftStickY < 128; + _report.dpad_up = _outputs.dpadUp || _outputs.leftStickY > 128; + + _nes.SendReport(_report); +} + +int NesBackend::GetOffset() { + return _nes.GetOffset(); +} \ No newline at end of file diff --git a/HAL/pico/src/comms/NintendoSwitchBackend.cpp b/HAL/pico/src/comms/NintendoSwitchBackend.cpp index 3af69be8..d480424d 100644 --- a/HAL/pico/src/comms/NintendoSwitchBackend.cpp +++ b/HAL/pico/src/comms/NintendoSwitchBackend.cpp @@ -2,6 +2,7 @@ #include "core/CommunicationBackend.hpp" #include "core/state.hpp" +#include "util/analog_filters.hpp" #include #include @@ -48,19 +49,30 @@ HID_REPORT_SIZE ( 8 ) ,\ HID_REPORT_COUNT ( 4 ) ,\ HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ - /* Some output from the host idk */ \ + /* Some random vendor input idk */ \ HID_USAGE_PAGE_N ( HID_USAGE_PAGE_VENDOR, 2 ) ,\ HID_USAGE ( 0x20 ) ,\ + HID_REPORT_COUNT ( 1 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* Some output from the host idk */ \ + 0x0A, 0x21, 0x26, \ HID_REPORT_COUNT ( 8 ) ,\ HID_OUTPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ HID_COLLECTION_END // clang-format on -uint8_t NintendoSwitchBackend::_descriptor[] = { HID_REPORT_DESC() }; +const uint8_t NintendoSwitchBackend::_descriptor[] = { HID_REPORT_DESC() }; -NintendoSwitchBackend::NintendoSwitchBackend(InputSource **input_sources, size_t input_source_count) - : CommunicationBackend(input_sources, input_source_count) { +const uint8_t DEADZONE = 11; +const int RADIUS = 256; + +NintendoSwitchBackend::NintendoSwitchBackend( + InputState &inputs, + InputSource **input_sources, + size_t input_source_count +) + : CommunicationBackend(inputs, input_sources, input_source_count) { USBDevice.setManufacturerDescriptor("HORI CO.,LTD."); USBDevice.setProductDescriptor("POKKEN CONTROLLER"); USBDevice.setSerialDescriptor("1.0"); @@ -99,6 +111,10 @@ void NintendoSwitchBackend::RegisterDescriptor() { TUCompositeHID::addDescriptor(_descriptor, sizeof(_descriptor)); } +CommunicationBackendId NintendoSwitchBackend::BackendId() { + return COMMS_BACKEND_NINTENDO_SWITCH; +} + void NintendoSwitchBackend::SendReport() { ScanInputs(InputScanSpeed::SLOW); ScanInputs(InputScanSpeed::MEDIUM); @@ -127,10 +143,10 @@ void NintendoSwitchBackend::SendReport() { _report.home = _outputs.home; // Analog outputs - _report.lx = (_outputs.leftStickX - 128) * 1.25 + 128; - _report.ly = 255 - ((_outputs.leftStickY - 128) * 1.25 + 128); - _report.rx = (_outputs.rightStickX - 128) * 1.25 + 128; - _report.ry = 255 - ((_outputs.rightStickY - 128) * 1.25 + 128); + _report.lx = apply_radius(apply_deadzone(_outputs.leftStickX, DEADZONE, true), RADIUS); + _report.ly = 255 - apply_radius(apply_deadzone(_outputs.leftStickY, DEADZONE, true), RADIUS); + _report.rx = apply_radius(apply_deadzone(_outputs.rightStickX, DEADZONE, true), RADIUS); + _report.ry = 255 - apply_radius(apply_deadzone(_outputs.rightStickY, DEADZONE, true), RADIUS); // D-pad Hat Switch _report.hat = diff --git a/HAL/pico/src/comms/SnesBackend.cpp b/HAL/pico/src/comms/SnesBackend.cpp new file mode 100644 index 00000000..fab8bbfa --- /dev/null +++ b/HAL/pico/src/comms/SnesBackend.cpp @@ -0,0 +1,46 @@ +#include "comms/SnesBackend.hpp" + +SnesBackend::SnesBackend( + InputState &inputs, + InputSource **input_sources, + size_t input_source_count, + uint data_pin, + uint clock_pin, + uint latch_pin, + PIO pio, + int sm, + int offset +) + : CommunicationBackend(inputs, input_sources, input_source_count), + _snes(data_pin, clock_pin, latch_pin, pio, sm, offset) {} + +CommunicationBackendId SnesBackend::BackendId() { + return COMMS_BACKEND_SNES; +} + +void SnesBackend::SendReport() { + ScanInputs(); + + // Run gamemode logic + UpdateOutputs(); + + // Digital outputs + _report.a = _outputs.a; + _report.b = _outputs.b; + _report.x = _outputs.x; + _report.y = _outputs.y; + _report.l = _outputs.buttonL || _outputs.triggerLDigital; + _report.r = _outputs.buttonR || _outputs.triggerRDigital; + _report.select = _outputs.select; + _report.start = _outputs.start; + _report.dpad_left = _outputs.dpadLeft || _outputs.leftStickX < 128; + _report.dpad_right = _outputs.dpadRight || _outputs.leftStickX > 128; + _report.dpad_down = _outputs.dpadDown || _outputs.leftStickY < 128; + _report.dpad_up = _outputs.dpadUp || _outputs.leftStickY > 128; + + _snes.SendReport(_report); +} + +int SnesBackend::GetOffset() { + return _snes.GetOffset(); +} \ No newline at end of file diff --git a/HAL/pico/src/comms/XInputBackend.cpp b/HAL/pico/src/comms/XInputBackend.cpp index 55ab4ad7..068b7296 100644 --- a/HAL/pico/src/comms/XInputBackend.cpp +++ b/HAL/pico/src/comms/XInputBackend.cpp @@ -5,29 +5,29 @@ #include -XInputBackend::XInputBackend(InputSource **input_sources, size_t input_source_count) - : CommunicationBackend(input_sources, input_source_count) { +XInputBackend::XInputBackend( + InputState &inputs, + InputSource **input_sources, + size_t input_source_count +) + : CommunicationBackend(inputs, input_sources, input_source_count), + _xinput() { Serial.end(); - _xinput = new Adafruit_USBD_XInput(); - _xinput->begin(); + _xinput.begin(); Serial.begin(115200); TinyUSBDevice.setID(0x0738, 0x4726); - - while (!_xinput->ready()) { - delay(1); - } } -XInputBackend::~XInputBackend() { - delete _xinput; +CommunicationBackendId XInputBackend::BackendId() { + return COMMS_BACKEND_XINPUT; } void XInputBackend::SendReport() { ScanInputs(InputScanSpeed::SLOW); ScanInputs(InputScanSpeed::MEDIUM); - while (!_xinput->ready()) { + while (!_xinput.ready()) { tight_loop_contents(); } @@ -59,5 +59,5 @@ void XInputBackend::SendReport() { _report.rx = (_outputs.rightStickX - 128) * 65535 / 255 + 128; _report.ry = (_outputs.rightStickY - 128) * 65535 / 255 + 128; - _xinput->sendReport(&_report); + _xinput.sendReport(&_report); } \ No newline at end of file diff --git a/HAL/pico/src/comms/backend_init.cpp b/HAL/pico/src/comms/backend_init.cpp new file mode 100644 index 00000000..32e6f7f8 --- /dev/null +++ b/HAL/pico/src/comms/backend_init.cpp @@ -0,0 +1,252 @@ +#include "comms/backend_init.hpp" + +#include "comms/B0XXInputViewer.hpp" +#include "comms/ConfiguratorBackend.hpp" +#include "comms/DInputBackend.hpp" +#include "comms/GamecubeBackend.hpp" +#include "comms/N64Backend.hpp" +#include "comms/NesBackend.hpp" +#include "comms/NintendoSwitchBackend.hpp" +#include "comms/SnesBackend.hpp" +#include "comms/XInputBackend.hpp" +#include "core/CommunicationBackend.hpp" +#include "core/mode_selection.hpp" +#include "core/pinout.hpp" +#include "util/config_util.hpp" + +#include +#include +#include + +size_t initialize_backends( + CommunicationBackend **&backends, + InputState &inputs, + InputSource **input_sources, + size_t input_source_count, + Config &config, + const Pinout &pinout, + backend_config_selector_t get_backend_config, + usb_backend_getter_t get_usb_backend_config, + detect_console_t detect_console, + secondary_backend_initializer_t init_secondary_backends, + primary_backend_initializer_t init_primary_backend +) { + // Make sure required function pointers are not null. + if (get_backend_config == nullptr || get_usb_backend_config == nullptr || + init_primary_backend == nullptr || detect_console == nullptr) { + return 0; + } + + CommunicationBackendConfig backend_config = CommunicationBackendConfig_init_zero; + get_backend_config(backend_config, inputs, config); + + CommunicationBackend *primary_backend = nullptr; + + /* If no match found for button hold, use console/USB detection to select backend instead. */ + if (backend_config.backend_id == COMMS_BACKEND_UNSPECIFIED) { + /* Must check default USB backend here and initialize it before console detection, so that + * we can respond correctly to device descriptor requests from host. */ + CommunicationBackendConfig usb_backend_config; + get_usb_backend_config(usb_backend_config, config); + init_primary_backend( + primary_backend, + usb_backend_config.backend_id, + inputs, + input_sources, + input_source_count, + config, + pinout + ); + CommunicationBackendId detected_backend_id = COMMS_BACKEND_UNSPECIFIED; + detected_backend_id = detect_console(pinout); + if (detected_backend_id == COMMS_BACKEND_XINPUT) { + backend_config = usb_backend_config; + } else { + backend_config = backend_config_from_id( + detected_backend_id, + config.communication_backend_configs, + config.communication_backend_configs_count + ); + } + } + if (backend_config.backend_id == COMMS_BACKEND_UNSPECIFIED && + config.default_backend_config > 0) { + backend_config = config.communication_backend_configs[config.default_backend_config - 1]; + } + + init_primary_backend( + primary_backend, + backend_config.backend_id, + inputs, + input_sources, + input_source_count, + config, + pinout + ); + + size_t backend_count = 1; + if (init_secondary_backends != nullptr) { + backend_count = init_secondary_backends( + backends, + primary_backend, + backend_config.backend_id, + inputs, + input_sources, + input_source_count, + config, + pinout + ); + } + + if (backend_config.default_mode_config > 0) { + GameModeConfig &mode_config = + config.game_mode_configs[backend_config.default_mode_config - 1]; + for (size_t i = 0; i < backend_count; i++) { + set_mode(backends[i], mode_config, config); + } + } + + return backend_count; +} + +void init_primary_backend( + CommunicationBackend *&primary_backend, + CommunicationBackendId backend_id, + InputState &inputs, + InputSource **input_sources, + size_t input_source_count, + Config &config, + const Pinout &pinout +) { + switch (backend_id) { + case COMMS_BACKEND_DINPUT: + if (primary_backend == nullptr) { + TUGamepad::registerDescriptor(); + TUKeyboard::registerDescriptor(); + primary_backend = new DInputBackend(inputs, input_sources, input_source_count); + } + break; + case COMMS_BACKEND_NINTENDO_SWITCH: + if (primary_backend == nullptr) { + NintendoSwitchBackend::RegisterDescriptor(); + primary_backend = + new NintendoSwitchBackend(inputs, input_sources, input_source_count); + } + break; + case COMMS_BACKEND_XINPUT: + if (primary_backend == nullptr) { + primary_backend = new XInputBackend(inputs, input_sources, input_source_count); + } + break; + case COMMS_BACKEND_GAMECUBE: + delete primary_backend; + primary_backend = + new GamecubeBackend(inputs, input_sources, input_source_count, pinout.joybus_data); + break; + case COMMS_BACKEND_N64: + delete primary_backend; + primary_backend = + new N64Backend(inputs, input_sources, input_source_count, pinout.joybus_data); + break; + case COMMS_BACKEND_NES: + delete primary_backend; + primary_backend = new NesBackend( + inputs, + input_sources, + input_source_count, + pinout.nes_data, + pinout.nes_clock, + pinout.nes_latch + ); + break; + case COMMS_BACKEND_SNES: + delete primary_backend; + primary_backend = new SnesBackend( + inputs, + input_sources, + input_source_count, + pinout.nes_data, + pinout.nes_clock, + pinout.nes_latch + ); + break; + case COMMS_BACKEND_UNSPECIFIED: // Fall back to configurator if invalid backend selected. + case COMMS_BACKEND_CONFIGURATOR: + default: + delete primary_backend; + Serial.begin(115200); + primary_backend = + new ConfiguratorBackend(inputs, input_sources, input_source_count, config, Serial); + } +} + +size_t init_secondary_backends( + CommunicationBackend **&backends, + CommunicationBackend *&primary_backend, + CommunicationBackendId backend_id, + InputState &inputs, + InputSource **input_sources, + size_t input_source_count, + Config &config, + const Pinout &pinout +) { + size_t backend_count = 0; + + switch (backend_id) { + case COMMS_BACKEND_DINPUT: + case COMMS_BACKEND_XINPUT: + backend_count = 2; + backends = new CommunicationBackend *[backend_count] { + primary_backend, new B0XXInputViewer(inputs, input_sources, input_source_count) + }; + break; + default: + backend_count = 1; + backends = new CommunicationBackend *[backend_count] { primary_backend }; + } + + return backend_count; +} + +// clang-format off + +/* Default is to first check button holds for a matching comms backend config. */ +backend_config_selector_t get_backend_config_default = []( + CommunicationBackendConfig &backend_config, + const InputState &inputs, + Config &config +) { + if (watchdog_caused_reboot()) { + // Check watchdog SCRATCH0 register for temporarily set backend config index. + uint8_t temp_backend_index = watchdog_hw->scratch[0]; + if (temp_backend_index > 0 && + temp_backend_index <= config.communication_backend_configs_count) { + backend_config = config.communication_backend_configs[temp_backend_index - 1]; + config.default_usb_backend_config = temp_backend_index; + return; + } + } + + backend_config = backend_config_from_buttons( + inputs, + config.communication_backend_configs, + config.communication_backend_configs_count + ); +}; + +/* Default is to get default USB backend from config. */ +usb_backend_getter_t get_usb_backend_config_default = []( + CommunicationBackendConfig &backend_config, + const Config &config +) { + if (config.default_usb_backend_config > 0 && + config.default_usb_backend_config <= config.communication_backend_configs_count) { + backend_config = + config.communication_backend_configs[config.default_usb_backend_config - 1]; + } +}; + +// clang-format on + +primary_backend_initializer_t init_primary_backend_default = &init_primary_backend; +secondary_backend_initializer_t init_secondary_backends_default = &init_secondary_backends; \ No newline at end of file diff --git a/HAL/pico/src/comms/console_detection.cpp b/HAL/pico/src/comms/console_detection.cpp new file mode 100644 index 00000000..dd30e821 --- /dev/null +++ b/HAL/pico/src/comms/console_detection.cpp @@ -0,0 +1,50 @@ +#include "comms/console_detection.hpp" + +#include "core/pinout.hpp" + +#include +#include +#include +#include + +uint latch_pulses = 0; +uint clock_pulses = 0; + +void latch_irq_handler() { + latch_pulses++; +} + +void clock_irq_handler() { + clock_pulses++; +} + +CommunicationBackendId detect_console(const Pinout &pinout) { + if (pinout.nes_latch > -1 && pinout.nes_clock > -1) { + attachInterrupt(pinout.nes_latch, &latch_irq_handler, PinStatus::RISING); + attachInterrupt(pinout.nes_clock, &clock_irq_handler, PinStatus::FALLING); + } + + delay(500); + bool usb_connected = usb_hw->sie_status & USB_SIE_STATUS_CONNECTED_BITS; + + if (pinout.nes_latch > -1 && pinout.nes_clock > -1) { + detachInterrupt(pinout.nes_latch); + detachInterrupt(pinout.nes_clock); + } + + CommunicationBackendId result = COMMS_BACKEND_UNSPECIFIED; + + if (usb_connected) { + result = COMMS_BACKEND_XINPUT; + } else if (latch_pulses && (clock_pulses / latch_pulses) > 8) { + result = COMMS_BACKEND_SNES; + } else if (latch_pulses && (clock_pulses / latch_pulses) > 1) { + result = COMMS_BACKEND_NES; + } else if (GamecubeConsole(pinout.joybus_data).Detect()) { + result = COMMS_BACKEND_GAMECUBE; + } else if (N64Console(pinout.joybus_data).Detect()) { + result = COMMS_BACKEND_N64; + } + + return result; +} \ No newline at end of file diff --git a/HAL/pico/src/core/KeyboardMode.cpp b/HAL/pico/src/core/KeyboardMode.cpp index c87acf7c..a14f01d0 100644 --- a/HAL/pico/src/core/KeyboardMode.cpp +++ b/HAL/pico/src/core/KeyboardMode.cpp @@ -4,7 +4,7 @@ #include -KeyboardMode::KeyboardMode() { +KeyboardMode::KeyboardMode() : InputMode() { _keyboard = new TUKeyboard(); _keyboard->begin(); } @@ -15,12 +15,14 @@ KeyboardMode::~KeyboardMode() { delete _keyboard; } -void KeyboardMode::SendReport(InputState &inputs) { - HandleSocd(inputs); +void KeyboardMode::SendReport(const InputState &inputs) { + InputState remapped_inputs = inputs; + HandleRemap(inputs, remapped_inputs); + HandleSocd(remapped_inputs); UpdateKeys(inputs); _keyboard->sendState(); } void KeyboardMode::Press(uint8_t keycode, bool press) { _keyboard->setPressed(keycode, press); -} \ No newline at end of file +} diff --git a/HAL/pico/src/core/Persistence.cpp b/HAL/pico/src/core/Persistence.cpp new file mode 100644 index 00000000..907e6ca3 --- /dev/null +++ b/HAL/pico/src/core/Persistence.cpp @@ -0,0 +1,183 @@ +/* + * This file is part of HayBox + * Copyright (C) 2024 Jonathan Haylett + * + * HayBox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software. If not, see . + */ + +#include "core/Persistence.hpp" + +#include "stdlib.hpp" + +#include +#include +#include +#include +#include + +Persistence::Persistence() { + LittleFS.begin(); +} + +Persistence::~Persistence() { + LittleFS.end(); +} + +bool Persistence::SaveConfig(Config &config) { + // Make sure config encodes correctly. + size_t encoded_size; + if (!pb_get_encoded_size(&encoded_size, Config_fields, &config)) { + return false; + } + + // Open file to store config data in. + File config_file = LittleFS.open(config_filename, "w+"); + if (!config_file) { + return false; + } + + // Write empty header to start with. + ConfigHeader header = { .config_size = 0, .config_crc = 0 }; + config_file.write((uint8_t *)&header, sizeof(ConfigHeader)); + + // Encode Protobuf data directly into file body. + pb_ostream_t ostream = as_pb_ostream(config_file); + if (!pb_encode(&ostream, Config_fields, &config)) { + config_file.close(); + return false; + } + + // Calculate checksum. + config_file.seek(config_offset); + CRC32 crc; + int value; + while ((value = config_file.read()) != -1) { + crc.update((uint8_t)value); + } + + // Update header. + header.config_size = ostream.bytes_written; + header.config_crc = crc.finalize(); + config_file.seek(0); + config_file.write((uint8_t *)&header, sizeof(ConfigHeader)); + + // Persist changes. + config_file.close(); + + return true; +} + +bool Persistence::LoadConfig(Config &config) { + // Open file to load config data from. + File config_file = LittleFS.open(config_filename, "r"); + if (!config_file) { + return false; + } + + if (!CheckSavedConfig(config_file)) { + config_file.close(); + return false; + } + + // Seek to start of Protobuf data. + if (!config_file.seek(config_offset)) { + config_file.close(); + return false; + } + + // Reset config defaults first, so config is completely replaced rather than merged with + // defaults. + config = Config_init_default; + + // Decode streamed Protobuf data into config struct. + pb_istream_t istream = as_pb_istream(config_file, (size_t)config_file.available()); + if (!pb_decode(&istream, Config_fields, &config)) { + config_file.close(); + return false; + } + + config_file.close(); + return true; +} + +bool Persistence::CheckSavedConfig() { + // Open file to load config data from. + File config_file = LittleFS.open(config_filename, "r"); + if (!config_file) { + return false; + } + + bool is_valid = CheckSavedConfig(config_file); + config_file.close(); + return is_valid; +} + +size_t Persistence::LoadConfigRaw(Print &out, bool validate) { + // Open file to load config data from. + File config_file = LittleFS.open(config_filename, "r"); + if (!config_file) { + return false; + } + + // Optionally perform validation. + if (validate && !CheckSavedConfig(config_file)) { + config_file.close(); + return false; + } + + // Seek to start of Protobuf data. + if (!config_file.seek(config_offset)) { + config_file.close(); + return false; + } + + // Write raw Protobuf encoded data to output stream. + int value; + while ((value = config_file.read()) != -1) { + out.write((uint8_t)value); + } + + config_file.close(); + return true; +} + +bool Persistence::CheckSavedConfig(File &config_file) { + size_t file_size = config_file.size(); + + // Read file header. + ConfigHeader header; + size_t bytes_read = config_file.read((uint8_t *)&header, sizeof(ConfigHeader)); + if (bytes_read < sizeof(ConfigHeader)) { + return false; + } + + // Validate config length. + size_t config_size = file_size - config_offset; + if (config_size != header.config_size) { + return false; + } + + // Calculate CRC for file contents and compare with CRC in header. + CRC32 crc; + int value; + while ((value = config_file.read()) != -1) { + crc.update((uint8_t)value); + } + if (crc.finalize() != header.config_crc) { + return false; + } + + return true; +} + +Persistence persistence; \ No newline at end of file diff --git a/HAL/pico/src/display/ConfigMenu.cpp b/HAL/pico/src/display/ConfigMenu.cpp new file mode 100644 index 00000000..2a3e46e9 --- /dev/null +++ b/HAL/pico/src/display/ConfigMenu.cpp @@ -0,0 +1,107 @@ +#include "display/ConfigMenu.hpp" + +#include "core/Persistence.hpp" +#include "core/mode_selection.hpp" +#include "reboot.hpp" +#include "util/config_util.hpp" + +ConfigMenu::ConfigMenu(Config &config, CommunicationBackend **backends, size_t backends_count) + : _config(config), + _backends(backends), + _backends_count(backends_count) {} + +DisplayModeId ConfigMenu::GetId() { + return DISPLAY_MODE_CONFIG; +} + +void ConfigMenu::HandleControls( + IntegratedDisplay *instance, + const DisplayControls &controls, + Button button +) { + if (_current_menu_page == nullptr) { + _current_menu_page = _top_level_page; + _current_menu_offset = 0; + return; + } + + if (button == controls.up) { + _highlighted_menu_item = max(0, _highlighted_menu_item - 1); + } else if (button == controls.down) { + _highlighted_menu_item = + min(_current_menu_page->items_count - 1, _highlighted_menu_item + 1); + } else if (button == controls.enter) { + // Bounds check. + if (_highlighted_menu_item > _current_menu_page->items_count) { + _highlighted_menu_item = 0; + return; + } + + const MenuPage::MenuItem &selected_item = _current_menu_page->items[_highlighted_menu_item]; + + // If there's an action defined, perform it. + if (selected_item.action != nullptr) { + selected_item.action(instance, this, _config, selected_item.key); + + // Go back up a level. + if (_current_menu_page->parent != nullptr) { + _current_menu_page = _current_menu_page->parent; + _current_menu_offset = 0; + } + _highlighted_menu_item = 0; + return; + } + + // If there's a child page defined, drill into it. + if (selected_item.page != nullptr) { + _current_menu_page = selected_item.page; + _current_menu_offset = 0; + _highlighted_menu_item = 0; + return; + } + } else if (button == controls.back) { + // If at top-level page, go back to input viewer. + if (_current_menu_page->parent == nullptr) { + // Restore gamemode. + if (_backends[0] != nullptr) { + _backends[0]->SetGameMode(instance->CurrentGameMode()); + } + ReturnToDashboard(instance); + return; + } + + _current_menu_page = _current_menu_page->parent; + _highlighted_menu_item = 0; + } +} + +void ConfigMenu::UpdateDisplay(IntegratedDisplay *instance, Adafruit_GFX &display) { + // Unset gamemode to prevent menu button presses being sent to console. + if (_backends[0] != nullptr && _backends[0]->CurrentGameMode() != nullptr) { + instance->SetGameMode(_backends[0]->CurrentGameMode()); + _backends[0]->SetGameMode(nullptr); + } + + uint8_t font_width = instance->font_width; + uint8_t font_height = instance->font_height; + + if (_highlighted_menu_item - _current_menu_offset > max_visible_lines - 1) { + _current_menu_offset++; + } else if (_highlighted_menu_item < _current_menu_offset) { + _current_menu_offset--; + } + uint8_t last_item_to_display = + min(_current_menu_page->items_count - _current_menu_offset, max_visible_lines + 1); + for (size_t i = 0; i < last_item_to_display; i++) { + if (i + _current_menu_offset == _highlighted_menu_item) { + display.setCursor(0, i * (font_height + padding)); + display.print(highlight_string); + } + display.setCursor(font_width + padding, i * (font_height + padding)); + display.print(_current_menu_page->items[i + _current_menu_offset].text); + } +} + +void ConfigMenu::ReturnToDashboard(IntegratedDisplay *instance) { + instance->SetDisplayMode(DISPLAY_MODE_VIEWER); +} \ No newline at end of file diff --git a/HAL/pico/src/display/DefaultConfigMenu.cpp b/HAL/pico/src/display/DefaultConfigMenu.cpp new file mode 100644 index 00000000..a29901bc --- /dev/null +++ b/HAL/pico/src/display/DefaultConfigMenu.cpp @@ -0,0 +1,248 @@ +#include "display/DefaultConfigMenu.hpp" + +#include "core/Persistence.hpp" +#include "core/mode_selection.hpp" +#include "reboot.hpp" +#include "util/config_util.hpp" + +DefaultConfigMenu::DefaultConfigMenu( + Config &config, + CommunicationBackend **backends, + size_t backends_count +) + : ConfigMenu(config, backends, backends_count) { + /* Build default USB backends page */ + MenuPage::MenuItem *usb_backend_options = + new MenuPage::MenuItem[config.communication_backend_configs_count]; + + size_t usb_backend_options_count = 0; + for (size_t i = 0; i < config.communication_backend_configs_count; i++) { + CommunicationBackendConfig &backend_config = config.communication_backend_configs[i]; + MenuPage::MenuItem ¤t_option = usb_backend_options[usb_backend_options_count]; + + if (backend_config.backend_id != COMMS_BACKEND_XINPUT && + backend_config.backend_id != COMMS_BACKEND_DINPUT && + backend_config.backend_id != COMMS_BACKEND_NINTENDO_SWITCH && + backend_config.backend_id != COMMS_BACKEND_CONFIGURATOR) { + continue; + } + + strlcpy( + current_option.text, + backend_name(backend_config.backend_id), + sizeof(current_option.text) + ); + current_option.key = i; + current_option.action = &SetUsbBackend; + usb_backend_options_count++; + } + + _usb_backends_page = { + .items = usb_backend_options, + .items_count = usb_backend_options_count, + }; + + /* Build gamemodes page */ + MenuPage::MenuItem *gamemode_options = new MenuPage::MenuItem[config.game_mode_configs_count]; + + size_t gamemode_options_count = 0; + for (size_t i = 0; i < config.game_mode_configs_count; i++) { + GameModeConfig &mode_config = config.game_mode_configs[i]; + MenuPage::MenuItem ¤t_option = gamemode_options[gamemode_options_count]; + + // Don't show keyboard modes as a gamemode option unless using DInputBackend. + if (_backends_count > 0 && _backends[0] != nullptr) { + CommunicationBackendId primary_backend_id = _backends[0]->BackendId(); + if (primary_backend_id != COMMS_BACKEND_DINPUT && + mode_config.mode_id == MODE_KEYBOARD) { + continue; + } + } + + if (strnlen(mode_config.name, sizeof(mode_config.name)) > 0) { + strlcpy(current_option.text, mode_config.name, sizeof(current_option.text)); + } else { + strlcpy( + current_option.text, + gamemode_name(mode_config.mode_id), + sizeof(current_option.text) + ); + } + current_option.key = i; + current_option.action = &SetDefaultMode; + gamemode_options_count++; + } + + _gamemode_options_page = { + .items = gamemode_options, + .items_count = gamemode_options_count, + }; + + /* Build SOCD types page */ + static MenuPage::MenuItem socd_options[_SocdType_MAX] = {}; + for (uint8_t socd_type = SOCD_NEUTRAL; socd_type < _SocdType_ARRAYSIZE; socd_type++) { + MenuPage::MenuItem ¤t_option = socd_options[socd_type - 1]; + strlcpy(current_option.text, socd_name((SocdType)socd_type), sizeof(current_option.text)); + current_option.key = socd_type; + current_option.action = &SetSocdType; + } + + static MenuPage socd_page = { + .items = socd_options, + .items_count = sizeof(socd_options) / sizeof(MenuPage::MenuItem), + }; + + /* Build top-level page */ + // clang-format off + static MenuPage::MenuItem top_level_items[] = { + { + .text = "Profile", + .page = &_gamemode_options_page, + }, + { + .text = "USB Mode", + .page = &_usb_backends_page, + }, + { + .text = "SOCD Mode", + .page = &socd_page, + }, + { + .text = "RGB Brightness", + .action = []( + IntegratedDisplay *display_backend, + ConfigMenu *menu, + Config &config, + uint8_t key + ) { + display_backend->SetDisplayMode(DISPLAY_MODE_RGB_BRIGHTNESS); + }, + }, + { + .text = "Input Viewer", + .action = []( + IntegratedDisplay *display_backend, + ConfigMenu *menu, + Config &config, + uint8_t key + ) { + // Restore gamemode. + DefaultConfigMenu *config_menu = (DefaultConfigMenu*)menu; + if (config_menu->_backends[0] != nullptr) { + config_menu->_backends[0]->SetGameMode(display_backend->CurrentGameMode()); + } + display_backend->SetDisplayMode(DISPLAY_MODE_VIEWER); + }, + }, + { + .text = "Save changes", + .action = []( + IntegratedDisplay *display_backend, + ConfigMenu *menu, + Config &config, + uint8_t key + ) { + persistence.SaveConfig(config); + reboot_firmware(); + }, + }, + { + .text = "Discard changes", + .action = []( + IntegratedDisplay *display_backend, + ConfigMenu *menu, + Config &config, + uint8_t key + ) { + reboot_firmware(); + }, + }, + { + .text = "Firmware update", + .action = []( + IntegratedDisplay *display_backend, + ConfigMenu *menu, + Config &config, + uint8_t key + ) { + reboot_bootloader(); + } + } + }; + // clang-format on + + static MenuPage top_level_page = { + .items = top_level_items, + .items_count = sizeof(top_level_items) / sizeof(MenuPage::MenuItem), + }; + _top_level_page = &top_level_page; + + _usb_backends_page.parent = _top_level_page; + _gamemode_options_page.parent = _top_level_page; + socd_page.parent = _top_level_page; + + // Set initial page. + _current_menu_page = _top_level_page; +} + +DefaultConfigMenu::~DefaultConfigMenu() { + delete[] _usb_backends_page.items; + delete[] _gamemode_options_page.items; +} + +void DefaultConfigMenu::SetDefaultMode( + IntegratedDisplay *display_backend, + ConfigMenu *menu, + Config &config, + uint8_t mode_config_index +) { + if (mode_config_index < 0 || mode_config_index >= config.game_mode_configs_count) { + return; + } + + // Overwrite default game mode for all backend configs. + for (size_t i = 0; i < config.communication_backend_configs_count; i++) { + config.communication_backend_configs[i].default_mode_config = mode_config_index + 1; + } + + // Update mode for all backends. + DefaultConfigMenu *config_menu = (DefaultConfigMenu *)menu; + for (size_t i = 0; i < config_menu->_backends_count; i++) { + set_mode(config_menu->_backends[i], config.game_mode_configs[mode_config_index], config); + } + set_mode(display_backend, config.game_mode_configs[mode_config_index], config); +} + +void DefaultConfigMenu::SetUsbBackend( + IntegratedDisplay *display_backend, + ConfigMenu *menu, + Config &config, + uint8_t backend_config_index +) { + if (backend_config_index < 0 || + backend_config_index >= config.communication_backend_configs_count) { + return; + } + // Set backend in watchdog SCRATCH0 register and reboot. + watchdog_hw->scratch[0] = backend_config_index + 1; + reboot_firmware(); +} + +void DefaultConfigMenu::SetSocdType( + IntegratedDisplay *display_backend, + ConfigMenu *menu, + Config &config, + uint8_t socd_type +) { + if (socd_type <= SOCD_UNSPECIFIED || socd_type > _SocdType_MAX) { + return; + } + + // Overwrite SOCD type for all SOCD pairs of current gamemode's config. + GameModeConfig *mode_config = display_backend->CurrentGameMode()->GetConfig(); + if (mode_config != nullptr) { + for (size_t i = 0; i < mode_config->socd_pairs_count; i++) { + mode_config->socd_pairs[i].socd_type = (SocdType)socd_type; + } + } +} \ No newline at end of file diff --git a/HAL/pico/src/display/InputDisplay.cpp b/HAL/pico/src/display/InputDisplay.cpp new file mode 100644 index 00000000..f9889d90 --- /dev/null +++ b/HAL/pico/src/display/InputDisplay.cpp @@ -0,0 +1,71 @@ +#include "display/InputDisplay.hpp" + +#include "comms/IntegratedDisplay.hpp" +#include "util/config_util.hpp" +#include "util/state_util.hpp" + +InputDisplay::InputDisplay( + InputViewerButton *input_viewer_buttons, + size_t input_viewer_buttons_count, + const CommunicationBackendId backend_id +) + : _input_viewer_buttons(input_viewer_buttons), + _input_viewer_buttons_count(input_viewer_buttons_count), + _backend_id(backend_id) {} + +DisplayModeId InputDisplay::GetId() { + return DISPLAY_MODE_VIEWER; +} + +void InputDisplay::HandleControls( + IntegratedDisplay *instance, + const DisplayControls &controls, + Button button +) { + if (button == controls.back) { + instance->SetDisplayMode(DISPLAY_MODE_CONFIG); + } +} + +void InputDisplay::UpdateDisplay(IntegratedDisplay *instance, Adafruit_GFX &display) { + InputState &inputs = instance->GetInputs(); + uint8_t font_width = instance->font_width; + uint8_t color = instance->default_color; + + /* Gamemode text */ + display.setCursor(0, 0); + if (instance->CurrentGameMode() != nullptr) { + const GameModeConfig &mode_config = *instance->CurrentGameMode()->GetConfig(); + if (strnlen(mode_config.name, sizeof(mode_config.name)) > 0) { + display.print(mode_config.name); + } else { + display.print(gamemode_name(mode_config.mode_id)); + } + } + + /* Backend text */ + const char *backend_text = backend_name(_backend_id); + display.setCursor(display.width() - (strlen(backend_text) * font_width), 0); + display.print(backend_name(_backend_id)); + + /* Input display */ + if (_input_viewer_buttons == nullptr) { + return; + } + for (size_t i = 0; i < _input_viewer_buttons_count; i++) { + InputViewerButton mapping = _input_viewer_buttons[i]; + if (get_button(inputs.buttons, mapping.button)) { + display.fillCircle(mapping.center_x, mapping.center_y, mapping.radius, color); + } else { + display.drawCircle(mapping.center_x, mapping.center_y, mapping.radius, color); + } + } +} + +void InputDisplay::UpdateButtonLayout( + InputViewerButton *input_viewer_buttons, + size_t input_viewer_buttons_count +) { + _input_viewer_buttons = input_viewer_buttons; + _input_viewer_buttons_count = input_viewer_buttons_count; +} \ No newline at end of file diff --git a/HAL/pico/src/display/RgbBrightnessMenu.cpp b/HAL/pico/src/display/RgbBrightnessMenu.cpp new file mode 100644 index 00000000..a4545f73 --- /dev/null +++ b/HAL/pico/src/display/RgbBrightnessMenu.cpp @@ -0,0 +1,41 @@ +#include "display/RgbBrightnessMenu.hpp" + +#include "comms/IntegratedDisplay.hpp" + +RgbBrightnessMenu::RgbBrightnessMenu(Config &config) : _config(config) {} + +DisplayModeId RgbBrightnessMenu::GetId() { + return DISPLAY_MODE_RGB_BRIGHTNESS; +} + +void RgbBrightnessMenu::HandleControls( + IntegratedDisplay *instance, + const DisplayControls &controls, + Button button +) { + // const DisplayControls &controls = instance->_controls; + if (button == controls.up) { + _config.rgb_brightness++; + } else if (button == controls.down) { + _config.rgb_brightness--; + } else if (button == controls.back) { + instance->SetDisplayMode(DISPLAY_MODE_CONFIG); + } +} + +void RgbBrightnessMenu::UpdateDisplay(IntegratedDisplay *instance, Adafruit_GFX &display) { + uint8_t font_width = instance->font_width; + uint8_t font_height = instance->font_height; + + // Current brightness value. + display.setCursor(display.width() / 2 - (font_width * 3) / 2, display.height() / 2); + display.printf("%3d", _config.rgb_brightness); + + // Display control hints. + display.setCursor(5, display.height() - font_height); + display.print("Back"); + display.setCursor(45, display.getCursorY()); + display.print("-"); + display.setCursor(display.width() - 45 - font_width, display.getCursorY()); + display.print("+"); +} \ No newline at end of file diff --git a/HAL/pico/src/input/Pca9671Input.cpp b/HAL/pico/src/input/Pca9671Input.cpp new file mode 100644 index 00000000..fd501853 --- /dev/null +++ b/HAL/pico/src/input/Pca9671Input.cpp @@ -0,0 +1,47 @@ +#include "input/Pca9671Input.hpp" + +#include "util/state_util.hpp" + +Pca9671Input::Pca9671Input( + const Pca9671ButtonMapping *button_mappings, + size_t button_count, + TwoWire &wire, + int sda_pin, + int scl_pin, + uint8_t i2c_addr +) + : _pcf(i2c_addr, &wire) { + _button_mappings = button_mappings; + _button_count = button_count; + +#ifdef ARDUINO_PICO_REVISION + wire.setClock(3'000'000); + if (sda_pin > 0 && scl_pin > 0) { + wire.setSDA(sda_pin); + wire.setSCL(scl_pin); + } +#endif + + wire.begin(); + _pcf.begin(); +} + +InputScanSpeed Pca9671Input::ScanSpeed() { + return InputScanSpeed::FAST; +} + +void Pca9671Input::UpdateInputs(InputState &inputs) { + uint16_t pin_values = _pcf.read16(); + + for (size_t i = 0; i < _button_count; i++) { + UpdateButtonState(inputs, i, !(pin_values & (1 << _button_mappings[i].bit))); + } +} + +void Pca9671Input::UpdateButtonState( + InputState &inputs, + size_t button_mapping_index, + bool pressed +) { + set_button(inputs.buttons, _button_mappings[button_mapping_index].button, pressed); +} \ No newline at end of file diff --git a/HAL/pico/src/joybus_utils.cpp b/HAL/pico/src/joybus_utils.cpp deleted file mode 100644 index d4e14f98..00000000 --- a/HAL/pico/src/joybus_utils.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "joybus_utils.hpp" - -#include -#include - -#define VBUS_SENSE_PIN 24 - -ConnectedConsole detect_console(uint joybus_pin) { - gpio_init(VBUS_SENSE_PIN); - gpio_set_dir(VBUS_SENSE_PIN, GPIO_IN); - bool vbus_powered = gpio_get(VBUS_SENSE_PIN); - - if (GamecubeConsole(joybus_pin).Detect()) { - return ConnectedConsole::GAMECUBE; - } - // 5V is not connected when plugged into N64 so we check that first to save time. - if (!vbus_powered && N64Console(joybus_pin).Detect()) { - return ConnectedConsole::N64; - } - - return ConnectedConsole::NONE; -} \ No newline at end of file diff --git a/HAL/pico/src/reboot.cpp b/HAL/pico/src/reboot.cpp new file mode 100644 index 00000000..796c7f4b --- /dev/null +++ b/HAL/pico/src/reboot.cpp @@ -0,0 +1,11 @@ +#include "reboot.hpp" + +#include "stdlib.hpp" + +void reboot_firmware() { + rp2040.reboot(); +} + +void reboot_bootloader() { + rp2040.rebootToBootloader(); +} \ No newline at end of file diff --git a/README.md b/README.md index 11bdc7f1..26a9275f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,48 @@ -# HayBox +# SnekBox (configurator) +Forked version of HayBox focused on P+ Lucas + +Changes: +- Added Up2 functionality (BubbleBox, GRAM, etc.) +- Swapped A and C-stick down buttons +- Neutral SOCD for all modes +- Expanded Firefox angle range (now hold B for shallowest/steepest angle) +- Made ProjectM mode default (true Z press = true) + - Added snekdash angles (use mod X + L) + - Changed Mod X C-stick angling to enable angled C-stick forward tilts + - 45° notch + B outputs vertical special when grounded + - Mod X crouching (useful on platforms) + - True maximum length wavedashes (Mod X) + - Project M Bidou mode + - Mod X + Start + A + - Up2 acts as 2nd A button + - Project Nair mode + - Mod X + Start + RF7 + - Up2 acts as C-stick nair button (attack stick) +- D-Pad Modifier mode + - Mod X + Start + RF8 + - Up2 replaced with D-Pad modifier; hold it down to replace C-stick with D-Pad +- Added HDR mode (replaces Ultimate mode; now the default when X is held while plugging in) + - Added Mod Y + Down shield drops + - Set RF8 button to D-Pad Up for footstools + - Changed select/home buttons to activate when Mod X + Start/Mod Y + Start is pressed, respectively + - Changed Mod X C-stick angling to enable angled C-stick forward tilts + - 45° notch + B outputs vertical special when grounded + - Mod X crouching (useful on platforms) + - Added buffered turnaround vertical tilts (Mod Y) +- Rivals of Aether mode edits +- Rivals 2 mode optimized for Ranno +- Buffed Melee mode (Mod X + Start + LF5) to activate + - Added Mod Y + Down shield drops + - Light shield pinpoints 43 instead of 49 + - Parasol dash angle (airdodge diagonally upwards while holding X or Y) + - Mod X outputs longest wavedash angle + - Changed slideoff DI C-stick diagonal angles to the most optimal ones + - Added buffered turnaround vertical tilts (Mod Y) + - Added slight-angled forward tilt/smash angles (Mod X + Mod Y) + - Removed turnaround neutral B nerf + - Pikachu double up-B angle (Mod X + Mod Y + Up) + +## Original ReadMe below: HayBox is a modular, cross-platform firmware for digital or mixed analog/digital controllers, primarily targeted at [B0XX](https://b0xx.com)-style controllers. @@ -127,13 +171,17 @@ If you are adding a new device config/PlatformIO environment, you will have to a ### Default button holds +#### Config mode + +To enter config mode (Pico-based devices only), hold MB1 (Start) on plugin. + #### Pico bootsel mode -To reboot Pico-based controllers into bootsel mode, hold Start on plugin. +To reboot Pico-based controllers into bootsel mode, hold RT2 (C-Down) on plugin. #### Brook board passthrough mode -To switch to Brook board mode on GCCPCB2, GCCMX, B0XX R2, or LBX, hold B on +To switch to Brook board mode on GCCPCB2, GCCMX, B0XX R2, or LBX, hold RF1 (B) on plugin. #### Communication backends (console selection) @@ -145,14 +193,14 @@ On Pico/RP2040, USB vs GameCube vs Nintendo 64 is detected automatically. If not plugged into a console, the default is **XInput**, which should work plug-and-play with most PC games. Other backends are selected by holding one of the following buttons on plugin: -- X - Nintendo Switch USB mode (also sets initial game mode to Ultimate mode) -- Z - DInput mode (only recommended for games which don't support XInput) +- RF2 (X) - Nintendo Switch USB mode (also sets initial game mode to Ultimate mode) +- RF3 (Z) - DInput mode (only recommended for games which don't support XInput) On Arduino/AVR, the **DInput** backend is selected if a USB connection is detected. Otherwise, it defaults to GameCube backend, unless another backend is manually selected by holding one of the following buttons on plugin: -- A - GameCube backend with polling rate fix disabled (used for GCC adapters) -- C-Left - Nintendo 64 backend (60Hz polling rate) +- RT1 (A) - GameCube backend with polling rate fix disabled (used for GCC adapters) +- RT3 (C-Left) - Nintendo 64 backend (60Hz polling rate) #### Game mode selection @@ -163,14 +211,14 @@ switch game anyway. It also serves the purpose of reducing the number of buttons you have to hold with one hand while plugging in. The default controller mode button combinations are: -- Mod X + Start + L - Melee mode (default) -- Mod X + Start + Left - Project M/Project+ mode -- Mod X + Start + Down - Ultimate mode -- Mod X + Start + Right - FGC mode (Hitbox style fighting game layout) -- Mod X + Start + B - Rivals of Aether mode +- MB1 + LT1 + LF4 (Start + Mod X + L) - Melee mode (default) +- MB1 + LT1 + LF3 (Start + Mod X + Left) - Project M/Project+ mode +- MB1 + LT1 + LF2 (Start + Mod X + Down) - Ultimate mode +- MB1 + LT1 + LF1 (Start + Mod X + Right) - FGC mode (Hitbox style fighting game layout) +- MB1 + LT1 + RF1 (Start + Mod X + B) - Rivals of Aether mode Default keyboard mode button combinations (only available when using DInput backend, **not** with XInput): -- Mod Y + Start + L - Default keyboard mode +- MB1 + LT2 + LF4 (Start + Mod Y + L) - Default keyboard mode ### Dolphin setup diff --git a/SnekConfig.json b/SnekConfig.json new file mode 100644 index 00000000..1554d526 --- /dev/null +++ b/SnekConfig.json @@ -0,0 +1,447 @@ +{ + "gameModeConfigs": [ + { + "modeId": "MODE_MELEE", + "socdPairs": [ + { + "buttonDir1": "BTN_LF3", + "buttonDir2": "BTN_LF1", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_LF2", + "buttonDir2": "BTN_RF4", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_RT3", + "buttonDir2": "BTN_RT5", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_RT2", + "buttonDir2": "BTN_RT4", + "socdType": "SOCD_NEUTRAL" + } + ], + "activationBinding": [ + "BTN_LT1", + "BTN_MB1", + "BTN_LF4" + ] + }, + { + "modeId": "MODE_PROJECT_M", + "socdPairs": [ + { + "buttonDir1": "BTN_LF3", + "buttonDir2": "BTN_LF1", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_LF2", + "buttonDir2": "BTN_RF4", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_RT3", + "buttonDir2": "BTN_RT5", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_RT2", + "buttonDir2": "BTN_RT4", + "socdType": "SOCD_NEUTRAL" + } + ], + "buttonRemapping": [ + { + "physicalButton": "BTN_LF5", + "activates": "BTN_RF4" + } + ], + "activationBinding": [ + "BTN_LT1", + "BTN_MB1", + "BTN_LF3" + ] + }, + { + "modeId": "MODE_ULTIMATE", + "socdPairs": [ + { + "buttonDir1": "BTN_LF3", + "buttonDir2": "BTN_LF1", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_LF2", + "buttonDir2": "BTN_RF4", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_RT3", + "buttonDir2": "BTN_RT5", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_RT2", + "buttonDir2": "BTN_RT4", + "socdType": "SOCD_NEUTRAL" + } + ], + "buttonRemapping": [ + { + "physicalButton": "BTN_LF5", + "activates": "BTN_RF4" + } + ], + "activationBinding": [ + "BTN_LT1", + "BTN_MB1", + "BTN_LF2" + ] + }, + { + "modeId": "MODE_PROJECT_M", + "socdPairs": [ + { + "buttonDir1": "BTN_LF3", + "buttonDir2": "BTN_LF1", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_LF2", + "buttonDir2": "BTN_RF4", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_RT3", + "buttonDir2": "BTN_RT5", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_RT2", + "buttonDir2": "BTN_RT4", + "socdType": "SOCD_NEUTRAL" + } + ], + "buttonRemapping": [ + { + "physicalButton": "BTN_LF5", + "activates": "BTN_RT1" + } + ], + "activationBinding": [ + "BTN_LT1", + "BTN_MB1", + "BTN_RT1" + ] + }, + { + "modeId": "MODE_PROJECT_M", + "socdPairs": [ + { + "buttonDir1": "BTN_LF3", + "buttonDir2": "BTN_LF1", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_LF2", + "buttonDir2": "BTN_RF4", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_RT3", + "buttonDir2": "BTN_RT5", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_RT2", + "buttonDir2": "BTN_RT4", + "socdType": "SOCD_NEUTRAL" + } + ], + "buttonRemapping": [ + { + "physicalButton": "BTN_LF5", + "activates": "BTN_LF15" + } + ], + "activationBinding": [ + "BTN_LT1", + "BTN_MB1", + "BTN_RF7" + ] + }, + { + "modeId": "MODE_PROJECT_M", + "socdPairs": [ + { + "buttonDir1": "BTN_LF3", + "buttonDir2": "BTN_LF1", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_LF2", + "buttonDir2": "BTN_RF4", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_RT3", + "buttonDir2": "BTN_RT5", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_RT2", + "buttonDir2": "BTN_RT4", + "socdType": "SOCD_NEUTRAL" + } + ], + "buttonRemapping": [ + { + "physicalButton": "BTN_LF5", + "activates": "BTN_LF14" + }, + { + "physicalButton": "BTN_RF8", + "activates": "BTN_RF16" + } + ], + "activationBinding": [ + "BTN_LT1", + "BTN_MB1", + "BTN_RF8" + ] + }, + { + "modeId": "MODE_FGC", + "socdPairs": [ + { + "buttonDir1": "BTN_LF3", + "buttonDir2": "BTN_LF1", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_LF2", + "buttonDir2": "BTN_RF4", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_RT3", + "buttonDir2": "BTN_RT5", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_RT2", + "buttonDir2": "BTN_RT4", + "socdType": "SOCD_NEUTRAL" + } + ], + "buttonRemapping": [ + { + "physicalButton": "BTN_LF5", + "activates": "BTN_RF4" + } + ], + "activationBinding": [ + "BTN_LT1", + "BTN_MB1", + "BTN_LF5" + ] + }, + { + "modeId": "MODE_RIVALS_OF_AETHER", + "socdPairs": [ + { + "buttonDir1": "BTN_LF3", + "buttonDir2": "BTN_LF1", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_LF2", + "buttonDir2": "BTN_RF4", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_RT3", + "buttonDir2": "BTN_RT5", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_RT2", + "buttonDir2": "BTN_RT4", + "socdType": "SOCD_NEUTRAL" + } + ], + "buttonRemapping": [ + { + "physicalButton": "BTN_LF5", + "activates": "BTN_RF4" + } + ], + "activationBinding": [ + "BTN_LT1", + "BTN_MB1", + "BTN_RF1" + ] + }, + { + "modeId": "MODE_RIVALS_2", + "socdPairs": [ + { + "buttonDir1": "BTN_LF3", + "buttonDir2": "BTN_LF1", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_LF2", + "buttonDir2": "BTN_RF4", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_RT3", + "buttonDir2": "BTN_RT5", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_RT2", + "buttonDir2": "BTN_RT4", + "socdType": "SOCD_NEUTRAL" + } + ], + "buttonRemapping": [ + { + "physicalButton": "BTN_LF5", + "activates": "BTN_RF4" + } + ], + "activationBinding": [ + "BTN_LT1", + "BTN_MB1", + "BTN_RF5" + ] + }, + { + "modeId": "MODE_CUSTOM", + "socdPairs": [ + { + "buttonDir1": "BTN_LF3", + "buttonDir2": "BTN_LF1", + "socdType": "SOCD_NEUTRAL" + }, + { + "buttonDir1": "BTN_LF2", + "buttonDir2": "BTN_LT1", + "socdType": "SOCD_NEUTRAL" + } + ], + "buttonRemapping": [ + { + "physicalButton": "BTN_RT4", + "activates": "BTN_LT1" + }, + { + "physicalButton": "BTN_LF5", + "activates": "BTN_LT1" + } + ], + "activationBinding": [ + "BTN_LT1", + "BTN_MB1", + "BTN_LF1" + ], + "customModeConfig": 1 + } + ], + "communicationBackendConfigs": [ + { + "backendId": "COMMS_BACKEND_XINPUT", + "defaultModeConfig": 2 + }, + { + "backendId": "COMMS_BACKEND_DINPUT", + "defaultModeConfig": 2, + "activationBinding": [ + "BTN_RF3" + ] + }, + { + "backendId": "COMMS_BACKEND_NINTENDO_SWITCH", + "defaultModeConfig": 3, + "activationBinding": [ + "BTN_RF2" + ] + }, + { + "backendId": "COMMS_BACKEND_GAMECUBE", + "defaultModeConfig": 2 + }, + { + "backendId": "COMMS_BACKEND_N64", + "defaultModeConfig": 1 + }, + { + "backendId": "COMMS_BACKEND_NES", + "defaultModeConfig": 1, + "activationBinding": [ + "BTN_LT1" + ] + }, + { + "backendId": "COMMS_BACKEND_SNES", + "defaultModeConfig": 1, + "activationBinding": [ + "BTN_LT2" + ] + }, + { + "backendId": "COMMS_BACKEND_CONFIGURATOR", + "activationBinding": [ + "BTN_MB1" + ] + } + ], + "customModes": [ + { + "digitalButtonMappings": [ + "BTN_RF1", + "BTN_RF2", + "BTN_RF5", + "BTN_RF6", + "BTN_RF8", + "BTN_RF7", + "BTN_RF4", + "BTN_RF3", + "BTN_MB1", + "BTN_RT3", + "BTN_RT1", + "BTN_RT5", + "BTN_LT1", + "BTN_LF2", + "BTN_LF3", + "BTN_LF1", + "BTN_LT2", + "BTN_RT2" + ], + "analogTriggerMappings": [ + { + "button": "BTN_RF4", + "trigger": "TRIGGER_LT", + "value": 255 + }, + { + "button": "BTN_RF3", + "trigger": "TRIGGER_RT", + "value": 255 + } + ] + } + ], + "defaultBackendConfig": 1, + "defaultUsbBackendConfig": 1, + "projectMOptions": { + "trueZPress": true, + "disableLedgedashSocdOverride": true + } +} \ No newline at end of file diff --git a/config/arduino/config.cpp b/config/arduino/config.cpp index 033456d6..9ca816e6 100644 --- a/config/arduino/config.cpp +++ b/config/arduino/config.cpp @@ -1,100 +1,96 @@ -#include "comms/GamecubeBackend.hpp" -#include "comms/N64Backend.hpp" -#include "config/mode_selection.hpp" +#include "comms/backend_init.hpp" +#include "config_defaults.hpp" #include "core/CommunicationBackend.hpp" -#include "core/InputMode.hpp" #include "core/KeyboardMode.hpp" +#include "core/mode_selection.hpp" #include "core/pinout.hpp" -#include "core/socd.hpp" #include "core/state.hpp" #include "input/GpioButtonInput.hpp" -#include "input/NunchukInput.hpp" -#include "modes/Melee20Button.hpp" +#include "reboot.hpp" #include "stdlib.hpp" -CommunicationBackend **backends = nullptr; -size_t backend_count; -KeyboardMode *current_kb_mode = nullptr; +#include + +Config config = default_config; // Customise this to match your controller's pinout. -GpioButtonMapping button_mappings[] = { - {&InputState::l, 15}, - { &InputState::left, 16}, - { &InputState::down, 14}, - { &InputState::right, 1 }, - - { &InputState::mod_x, 12}, - { &InputState::mod_y, 0 }, - - { &InputState::select, 2 }, - { &InputState::start, 4 }, - { &InputState::home, 3 }, - - { &InputState::c_left, 8 }, - { &InputState::c_up, 10}, - { &InputState::c_down, 6 }, - { &InputState::a, 9 }, - { &InputState::c_right, 5 }, - - { &InputState::b, A2}, - { &InputState::x, A1}, - { &InputState::z, A0}, - { &InputState::up, 13}, - - { &InputState::r, 7 }, - { &InputState::y, A5}, - { &InputState::lightshield, A4}, - { &InputState::midshield, A3}, +const GpioButtonMapping button_mappings[] = { + { BTN_LF4, 15 }, + { BTN_LF3, 16 }, + { BTN_LF2, 14 }, + { BTN_LF1, 1 }, + + { BTN_LT1, 12 }, + { BTN_LT2, 0 }, + + { BTN_MB3, 2 }, + { BTN_MB1, 4 }, + { BTN_MB2, 3 }, + + { BTN_RT3, 8 }, + { BTN_RT4, 10 }, + { BTN_RT2, 6 }, + { BTN_RT1, 9 }, + { BTN_RT5, 5 }, + + { BTN_RF1, A2 }, + { BTN_RF2, A1 }, + { BTN_RF3, A0 }, + { BTN_RF4, 13 }, + + { BTN_RF5, 7 }, + { BTN_RF6, A5 }, + { BTN_RF7, A4 }, + { BTN_RF8, A3 }, }; -size_t button_count = sizeof(button_mappings) / sizeof(GpioButtonMapping); +const size_t button_count = sizeof(button_mappings) / sizeof(GpioButtonMapping); -Pinout pinout = { - .joybus_data = 13, +const Pinout pinout = { + .joybus_data = 8, + .nes_data = 0, + .nes_clock = 0, + .nes_latch = 0, .mux = -1, .nunchuk_detect = -1, .nunchuk_sda = -1, .nunchuk_scl = -1, }; +CommunicationBackend **backends = nullptr; +size_t backend_count; +KeyboardMode *current_kb_mode = nullptr; + void setup() { - // Create Nunchuk input source - must be done before GPIO input source otherwise it would - // disable the pullups on the i2c pins. - NunchukInput *nunchuk = new NunchukInput(); + static InputState inputs; // Create GPIO input source and use it to read button states for checking button holds. - GpioButtonInput *gpio_input = new GpioButtonInput(button_mappings, button_count); + static GpioButtonInput gpio_input(button_mappings, button_count); + gpio_input.UpdateInputs(inputs); - InputState button_holds; - gpio_input->UpdateInputs(button_holds); + // Check bootloader button hold as early as possible for safety. + if (inputs.rt2) { + Serial.begin(115200); + reboot_bootloader(); + } // Create array of input sources to be used. - static InputSource *input_sources[] = { gpio_input, nunchuk }; + static InputSource *input_sources[] = { &gpio_input }; size_t input_source_count = sizeof(input_sources) / sizeof(InputSource *); - CommunicationBackend *primary_backend = nullptr; - if (button_holds.a) { - // Hold A on plugin for GameCube adapter. - primary_backend = - new GamecubeBackend(input_sources, input_source_count, 0, pinout.joybus_data); - } else { - // Default to GameCube/Wii. - primary_backend = - new GamecubeBackend(input_sources, input_source_count, 125, pinout.joybus_data); - } - - backend_count = 1; - backends = new CommunicationBackend *[backend_count] { primary_backend }; + backend_count = + initialize_backends(backends, inputs, input_sources, input_source_count, config, pinout); - // Default to Melee mode. - primary_backend->SetGameMode( - new Melee20Button(socd::SOCD_2IP_NO_REAC, { .crouch_walk_os = false }) - ); + setup_mode_activation_bindings(config.game_mode_configs, config.game_mode_configs_count); } void loop() { - select_mode(backends[0]); + select_mode(backends, backend_count, config); for (size_t i = 0; i < backend_count; i++) { backends[i]->SendReport(); } + + if (current_kb_mode != nullptr) { + current_kb_mode->SendReport(backends[0]->GetInputs()); + } } diff --git a/config/arduino/env.ini b/config/arduino/env.ini index 630377b1..ef851b7b 100644 --- a/config/arduino/env.ini +++ b/config/arduino/env.ini @@ -1,20 +1,34 @@ [env:arduino_uno] extends = avr_nousb board = uno -build_src_filter = +build_src_filter = ${avr_nousb.build_src_filter} + [env:arduino_nano] extends = avr_nousb board = nanoatmega328 -build_src_filter = +build_src_filter = ${avr_nousb.build_src_filter} + [env:arduino_mega] extends = avr_nousb board = megaatmega2560 -build_src_filter = +build_src_filter = ${avr_nousb.build_src_filter} - + \ No newline at end of file + + + +[env:arduino_leonardo] +extends = avr_usb +board = leonardo +build_src_filter = + ${avr_usb.build_src_filter} + + + +[env:arduino_micro] +extends = avr_usb +board = micro +build_src_filter = + ${avr_usb.build_src_filter} + + diff --git a/config/arduino_nativeusb/config.cpp b/config/arduino_nativeusb/config.cpp deleted file mode 100644 index b615000f..00000000 --- a/config/arduino_nativeusb/config.cpp +++ /dev/null @@ -1,121 +0,0 @@ -#include "comms/B0XXInputViewer.hpp" -#include "comms/DInputBackend.hpp" -#include "comms/GamecubeBackend.hpp" -#include "comms/N64Backend.hpp" -#include "config/mode_selection.hpp" -#include "core/CommunicationBackend.hpp" -#include "core/InputMode.hpp" -#include "core/KeyboardMode.hpp" -#include "core/pinout.hpp" -#include "core/socd.hpp" -#include "core/state.hpp" -#include "input/GpioButtonInput.hpp" -#include "input/NunchukInput.hpp" -#include "modes/Melee20Button.hpp" -#include "stdlib.hpp" - -CommunicationBackend **backends = nullptr; -size_t backend_count; -KeyboardMode *current_kb_mode = nullptr; - -// Customise this to match your controller's pinout. -GpioButtonMapping button_mappings[] = { - {&InputState::l, 15}, - { &InputState::left, 16}, - { &InputState::down, 14}, - { &InputState::right, 1 }, - - { &InputState::mod_x, 12}, - { &InputState::mod_y, 0 }, - - { &InputState::select, 2 }, - { &InputState::start, 4 }, - { &InputState::home, 3 }, - - { &InputState::c_left, 8 }, - { &InputState::c_up, 10}, - { &InputState::c_down, 6 }, - { &InputState::a, 9 }, - { &InputState::c_right, 5 }, - - { &InputState::b, A2}, - { &InputState::x, A1}, - { &InputState::z, A0}, - { &InputState::up, 13}, - - { &InputState::r, 7 }, - { &InputState::y, A5}, - { &InputState::lightshield, A4}, - { &InputState::midshield, A3}, -}; -size_t button_count = sizeof(button_mappings) / sizeof(GpioButtonMapping); - -Pinout pinout = { - .joybus_data = 17, - .mux = -1, - .nunchuk_detect = -1, - .nunchuk_sda = -1, - .nunchuk_scl = -1, -}; - -void setup() { - // Create Nunchuk input source - must be done before GPIO input source otherwise it would - // disable the pullups on the i2c pins. - NunchukInput *nunchuk = new NunchukInput(); - - // Create GPIO input source and use it to read button states for checking button holds. - GpioButtonInput *gpio_input = new GpioButtonInput(button_mappings, button_count); - - InputState button_holds; - gpio_input->UpdateInputs(button_holds); - - // Create array of input sources to be used. - static InputSource *input_sources[] = { gpio_input, nunchuk }; - size_t input_source_count = sizeof(input_sources) / sizeof(InputSource *); - - CommunicationBackend *primary_backend = new DInputBackend(input_sources, input_source_count); - delay(500); - bool usb_connected = UDADDR & _BV(ADDEN); - - /* Select communication backend. */ - if (usb_connected) { - // Default to DInput mode if USB is connected. - // Input viewer only used when connected to PC i.e. when using DInput mode. - backend_count = 2; - backends = new CommunicationBackend *[backend_count] { - primary_backend, new B0XXInputViewer(input_sources, input_source_count) - }; - } else { - delete primary_backend; - if (button_holds.a) { - // Hold A on plugin for GameCube adapter. - primary_backend = - new GamecubeBackend(input_sources, input_source_count, 0, pinout.joybus_data); - } else { - // Default to GameCube/Wii. - primary_backend = - new GamecubeBackend(input_sources, input_source_count, 125, pinout.joybus_data); - } - - // If not DInput then only using 1 backend (no input viewer). - backend_count = 1; - backends = new CommunicationBackend *[backend_count] { primary_backend }; - } - - // Default to Melee mode. - primary_backend->SetGameMode( - new Melee20Button(socd::SOCD_2IP_NO_REAC, { .crouch_walk_os = false }) - ); -} - -void loop() { - select_mode(backends[0]); - - for (size_t i = 0; i < backend_count; i++) { - backends[i]->SendReport(); - } - - if (current_kb_mode != nullptr) { - current_kb_mode->SendReport(backends[0]->GetInputs()); - } -} diff --git a/config/arduino_nativeusb/env.ini b/config/arduino_nativeusb/env.ini deleted file mode 100644 index 08688de6..00000000 --- a/config/arduino_nativeusb/env.ini +++ /dev/null @@ -1,13 +0,0 @@ -[env:arduino_leonardo] -extends = avr_usb -board = leonardo -build_src_filter = - ${avr_usb.build_src_filter} - + - -[env:arduino_micro] -extends = avr_usb -board = micro -build_src_filter = - ${avr_usb.build_src_filter} - + \ No newline at end of file diff --git a/config/b0xx_r1/config.cpp b/config/b0xx_r1/config.cpp index 22a50744..713e2149 100644 --- a/config/b0xx_r1/config.cpp +++ b/config/b0xx_r1/config.cpp @@ -1,113 +1,93 @@ -#include "comms/B0XXInputViewer.hpp" -#include "comms/DInputBackend.hpp" -#include "comms/GamecubeBackend.hpp" -#include "comms/N64Backend.hpp" -#include "config/mode_selection.hpp" +#include "comms/backend_init.hpp" +#include "config_defaults.hpp" #include "core/CommunicationBackend.hpp" -#include "core/InputMode.hpp" +#include "core/KeyboardMode.hpp" +#include "core/mode_selection.hpp" #include "core/pinout.hpp" -#include "core/socd.hpp" #include "core/state.hpp" #include "input/GpioButtonInput.hpp" #include "input/NunchukInput.hpp" -#include "modes/Melee20Button.hpp" +#include "reboot.hpp" #include "stdlib.hpp" -CommunicationBackend **backends = nullptr; -size_t backend_count; -KeyboardMode *current_kb_mode = nullptr; +#include -GpioButtonMapping button_mappings[] = { - {&InputState::l, 7 }, - { &InputState::left, 15}, - { &InputState::down, 16}, - { &InputState::right, 14}, +Config config = default_config; - { &InputState::mod_x, 6 }, - { &InputState::mod_y, 8 }, +const GpioButtonMapping button_mappings[] = { + { BTN_LF4, 7 }, + { BTN_LF3, 15 }, + { BTN_LF2, 16 }, + { BTN_LF1, 14 }, - { &InputState::start, 12}, + { BTN_LT1, 6 }, + { BTN_LT2, 8 }, - { &InputState::c_left, A1}, - { &InputState::c_up, A2}, - { &InputState::c_down, 5 }, - { &InputState::a, 13}, - { &InputState::c_right, A0}, + { BTN_MB1, 12 }, - { &InputState::b, 4 }, - { &InputState::x, A5}, - { &InputState::z, A4}, - { &InputState::up, A3}, + { BTN_RT3, A1 }, + { BTN_RT4, A2 }, + { BTN_RT2, 5 }, + { BTN_RT1, 13 }, + { BTN_RT5, A0 }, - { &InputState::r, 0 }, - { &InputState::y, 1 }, + { BTN_RF1, 4 }, + { BTN_RF2, A5 }, + { BTN_RF3, A4 }, + { BTN_RF4, A3 }, + + { BTN_RF5, 0 }, + { BTN_RF6, 1 }, }; -size_t button_count = sizeof(button_mappings) / sizeof(GpioButtonMapping); +const size_t button_count = sizeof(button_mappings) / sizeof(GpioButtonMapping); -Pinout pinout = { +const Pinout pinout = { .joybus_data = 17, + .nes_data = 0, + .nes_clock = 0, + .nes_latch = 0, .mux = -1, .nunchuk_detect = -1, .nunchuk_sda = -1, .nunchuk_scl = -1, }; +CommunicationBackend **backends = nullptr; +size_t backend_count; +KeyboardMode *current_kb_mode = nullptr; + void setup() { + static InputState inputs; + // Create Nunchuk input source - must be done before GPIO input source otherwise it would // disable the pullups on the i2c pins. - NunchukInput *nunchuk = new NunchukInput(); + // static NunchukInput nunchuk; // Create GPIO input source and use it to read button states for checking button holds. - GpioButtonInput *gpio_input = new GpioButtonInput(button_mappings, button_count); + static GpioButtonInput gpio_input(button_mappings, button_count); + gpio_input.UpdateInputs(inputs); - InputState button_holds; - gpio_input->UpdateInputs(button_holds); + // Check bootloader button hold as early as possible for safety. + if (inputs.rt2) { + Serial.begin(115200); + reboot_bootloader(); + } // Create array of input sources to be used. - static InputSource *input_sources[] = { gpio_input, nunchuk }; + static InputSource *input_sources[] = { + &gpio_input, + // &nunchuk, + }; size_t input_source_count = sizeof(input_sources) / sizeof(InputSource *); - CommunicationBackend *primary_backend = new DInputBackend(input_sources, input_source_count); - delay(500); - bool usb_connected = UDADDR & _BV(ADDEN); - - /* Select communication backend. */ - if (usb_connected) { - // Default to DInput mode if USB is connected. - // Input viewer only used when connected to PC i.e. when using DInput mode. - backend_count = 2; - backends = new CommunicationBackend *[backend_count] { - primary_backend, new B0XXInputViewer(input_sources, input_source_count) - }; - } else { - delete primary_backend; - if (button_holds.c_left) { - // Hold C-Left on plugin for N64. - primary_backend = - new N64Backend(input_sources, input_source_count, 60, pinout.joybus_data); - } else if (button_holds.a) { - // Hold A on plugin for GameCube adapter. - primary_backend = - new GamecubeBackend(input_sources, input_source_count, 0, pinout.joybus_data); - } else { - // Default to GameCube/Wii. - primary_backend = - new GamecubeBackend(input_sources, input_source_count, 125, pinout.joybus_data); - } - - // If not DInput then only using 1 backend (no input viewer). - backend_count = 1; - backends = new CommunicationBackend *[backend_count] { primary_backend }; - } + backend_count = + initialize_backends(backends, inputs, input_sources, input_source_count, config, pinout); - // Default to Melee mode. - primary_backend->SetGameMode( - new Melee20Button(socd::SOCD_2IP_NO_REAC, { .crouch_walk_os = false }) - ); + setup_mode_activation_bindings(config.game_mode_configs, config.game_mode_configs_count); } void loop() { - select_mode(backends[0]); + select_mode(backends, backend_count, config); for (size_t i = 0; i < backend_count; i++) { backends[i]->SendReport(); diff --git a/config/b0xx_r2/config.cpp b/config/b0xx_r2/config.cpp index 1843a01b..c8710bb6 100644 --- a/config/b0xx_r2/config.cpp +++ b/config/b0xx_r2/config.cpp @@ -1,122 +1,102 @@ -#include "comms/B0XXInputViewer.hpp" -#include "comms/DInputBackend.hpp" -#include "comms/GamecubeBackend.hpp" -#include "comms/N64Backend.hpp" -#include "config/mode_selection.hpp" +#include "comms/backend_init.hpp" +#include "config_defaults.hpp" #include "core/CommunicationBackend.hpp" -#include "core/InputMode.hpp" +#include "core/KeyboardMode.hpp" +#include "core/mode_selection.hpp" #include "core/pinout.hpp" -#include "core/socd.hpp" #include "core/state.hpp" #include "input/GpioButtonInput.hpp" #include "input/NunchukInput.hpp" -#include "modes/Melee20Button.hpp" +#include "reboot.hpp" #include "stdlib.hpp" -CommunicationBackend **backends = nullptr; -size_t backend_count; -KeyboardMode *current_kb_mode = nullptr; +#include -GpioButtonMapping button_mappings[] = { - {&InputState::l, 9 }, - { &InputState::left, 15}, - { &InputState::down, 16}, - { &InputState::right, 14}, +Config config = default_config; - { &InputState::mod_x, 8 }, - { &InputState::mod_y, 6 }, +const GpioButtonMapping button_mappings[] = { + { BTN_LF4, 9 }, + { BTN_LF3, 15 }, + { BTN_LF2, 16 }, + { BTN_LF1, 14 }, - { &InputState::start, 12}, + { BTN_LT1, 8 }, + { BTN_LT2, 6 }, - { &InputState::c_left, A1}, - { &InputState::c_up, A2}, - { &InputState::c_down, 5 }, - { &InputState::a, 13}, - { &InputState::c_right, A0}, + { BTN_MB1, 12 }, - { &InputState::b, 4 }, - { &InputState::x, A5}, - { &InputState::z, A4}, - { &InputState::up, A3}, + { BTN_RT3, A1 }, + { BTN_RT4, A2 }, + { BTN_RT2, 5 }, + { BTN_RT1, 13 }, + { BTN_RT5, A0 }, - { &InputState::r, 0 }, - { &InputState::y, 1 }, - { &InputState::lightshield, 10}, - { &InputState::midshield, 11}, + { BTN_RF1, 4 }, + { BTN_RF2, A5 }, + { BTN_RF3, A4 }, + { BTN_RF4, A3 }, + + { BTN_RF5, 0 }, + { BTN_RF6, 1 }, + { BTN_RF7, 10 }, + { BTN_RF8, 11 }, }; -size_t button_count = sizeof(button_mappings) / sizeof(GpioButtonMapping); +const size_t button_count = sizeof(button_mappings) / sizeof(GpioButtonMapping); -Pinout pinout = { +const Pinout pinout = { .joybus_data = 17, + .nes_data = 0, + .nes_clock = 0, + .nes_latch = 0, .mux = 7, .nunchuk_detect = -1, .nunchuk_sda = -1, .nunchuk_scl = -1, }; +CommunicationBackend **backends = nullptr; +size_t backend_count; +KeyboardMode *current_kb_mode = nullptr; + void setup() { + static InputState inputs; + // Create Nunchuk input source - must be done before GPIO input source otherwise it would // disable the pullups on the i2c pins. - NunchukInput *nunchuk = new NunchukInput(); + // static NunchukInput nunchuk; // Create GPIO input source and use it to read button states for checking button holds. - GpioButtonInput *gpio_input = new GpioButtonInput(button_mappings, button_count); + static GpioButtonInput gpio_input(button_mappings, button_count); + gpio_input.UpdateInputs(inputs); - InputState button_holds; - gpio_input->UpdateInputs(button_holds); - - // Create array of input sources to be used. - static InputSource *input_sources[] = { gpio_input, nunchuk }; - size_t input_source_count = sizeof(input_sources) / sizeof(InputSource *); + // Check bootloader button hold as early as possible for safety. + if (inputs.rt2) { + Serial.begin(115200); + reboot_bootloader(); + } - // Hold B on plugin for Brook board mode. + // Hold RF1 (B) on plugin for Brook board mode. pinMode(pinout.mux, OUTPUT); - if (button_holds.b) + if (inputs.rf1) digitalWrite(pinout.mux, LOW); else digitalWrite(pinout.mux, HIGH); - CommunicationBackend *primary_backend = new DInputBackend(input_sources, input_source_count); - delay(500); - bool usb_connected = UDADDR & _BV(ADDEN); - - /* Select communication backend. */ - if (usb_connected) { - // Default to DInput mode if USB is connected. - // Input viewer only used when connected to PC i.e. when using DInput mode. - backend_count = 2; - backends = new CommunicationBackend *[backend_count] { - primary_backend, new B0XXInputViewer(input_sources, input_source_count) - }; - } else { - delete primary_backend; - if (button_holds.c_left) { - // Hold C-Left on plugin for N64. - primary_backend = - new N64Backend(input_sources, input_source_count, 60, pinout.joybus_data); - } else if (button_holds.a) { - // Hold A on plugin for GameCube adapter. - primary_backend = - new GamecubeBackend(input_sources, input_source_count, 0, pinout.joybus_data); - } else { - // Default to GameCube/Wii. - primary_backend = - new GamecubeBackend(input_sources, input_source_count, 125, pinout.joybus_data); - } - - // If not DInput then only using 1 backend (no input viewer). - backend_count = 1; - backends = new CommunicationBackend *[backend_count] { primary_backend }; - } + // Create array of input sources to be used. + static InputSource *input_sources[] = { + &gpio_input, + // &nunchuk, + }; + size_t input_source_count = sizeof(input_sources) / sizeof(InputSource *); + + backend_count = + initialize_backends(backends, inputs, input_sources, input_source_count, config, pinout); - // Default to Melee mode. - primary_backend->SetGameMode( - new Melee20Button(socd::SOCD_2IP_NO_REAC, { .crouch_walk_os = false }) - ); + setup_mode_activation_bindings(config.game_mode_configs, config.game_mode_configs_count); } void loop() { - select_mode(backends[0]); + select_mode(backends, backend_count, config); for (size_t i = 0; i < backend_count; i++) { backends[i]->SendReport(); diff --git a/config/b0xx_r4/config.cpp b/config/b0xx_r4/config.cpp new file mode 100644 index 00000000..fd3acc31 --- /dev/null +++ b/config/b0xx_r4/config.cpp @@ -0,0 +1,123 @@ +#include "comms/backend_init.hpp" +#include "config_defaults.hpp" +#include "core/CommunicationBackend.hpp" +#include "core/KeyboardMode.hpp" +#include "core/Persistence.hpp" +#include "core/mode_selection.hpp" +#include "core/pinout.hpp" +#include "core/state.hpp" +#include "input/DebouncedGpioButtonInput.hpp" +#include "input/NunchukInput.hpp" +#include "reboot.hpp" +#include "stdlib.hpp" + +#include + +Config config = default_config; + +GpioButtonMapping button_mappings[] = { + {BTN_LF1, 9 }, + { BTN_LF2, 8 }, + { BTN_LF3, 7 }, + { BTN_LF4, 6 }, + + { BTN_LT1, 10}, + { BTN_LT2, 11}, + + { BTN_MB1, 12}, + + { BTN_RT1, 28}, + { BTN_RT2, 27}, + { BTN_RT3, 14}, + { BTN_RT4, 13}, + { BTN_RT5, 15}, + + { BTN_RF1, 19}, + { BTN_RF2, 18}, + { BTN_RF3, 17}, + { BTN_RF4, 16}, + + { BTN_RF5, 26}, + { BTN_RF6, 22}, + { BTN_RF7, 21}, + { BTN_RF8, 20}, +}; +const size_t button_count = sizeof(button_mappings) / sizeof(GpioButtonMapping); + +const Pinout pinout = { + .joybus_data = 2, + .nes_data = -1, + .nes_clock = -1, + .nes_latch = -1, + .mux = -1, + .nunchuk_detect = 3, + .nunchuk_sda = 4, + .nunchuk_scl = 5, +}; + +DebouncedGpioButtonInput gpio_input(button_mappings); +NunchukInput *nunchuk = nullptr; + +CommunicationBackend **backends = nullptr; +size_t backend_count; +KeyboardMode *current_kb_mode = nullptr; + +void setup() { + static InputState inputs; + + // Read button states for checking button holds. + gpio_input.UpdateInputs(inputs); + + // Check bootsel button hold as early as possible for safety. + if (inputs.rt2) { + reboot_bootloader(); + } + + // Turn on LED to indicate firmware booted. + gpio_init(PICO_DEFAULT_LED_PIN); + gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT); + gpio_put(PICO_DEFAULT_LED_PIN, 1); + + // Attempt to load config, or write default config to flash if failed to load config. + if (!persistence.LoadConfig(config)) { + persistence.SaveConfig(config); + } + + // Create Nunchuk input source. + nunchuk = new NunchukInput(Wire, pinout.nunchuk_detect, pinout.nunchuk_sda, pinout.nunchuk_scl); + + // Create array of input sources to be used. + static InputSource *input_sources[] = { nunchuk }; + size_t input_source_count = sizeof(input_sources) / sizeof(InputSource *); + + backend_count = + initialize_backends(backends, inputs, input_sources, input_source_count, config, pinout); + + setup_mode_activation_bindings(config.game_mode_configs, config.game_mode_configs_count); +} + +void loop() { + select_mode(backends, backend_count, config); + + for (size_t i = 0; i < backend_count; i++) { + backends[i]->SendReport(); + } + + if (current_kb_mode != nullptr) { + current_kb_mode->SendReport(backends[0]->GetInputs()); + } +} + +/* Button inputs are read from the second core */ + +void setup1() { + while (backends == nullptr) { + tight_loop_contents(); + } +} + +void loop1() { + if (backends != nullptr) { + gpio_input.UpdateInputs(backends[0]->GetInputs()); + } +} diff --git a/config/b0xx_r4/env.ini b/config/b0xx_r4/env.ini new file mode 100644 index 00000000..8b6d2ea8 --- /dev/null +++ b/config/b0xx_r4/env.ini @@ -0,0 +1,5 @@ +[env:b0xx_r4] +extends = arduino_pico_base +build_src_filter = + ${arduino_pico_base.build_src_filter} + + diff --git a/config/c53/config.cpp b/config/c53/config.cpp index 78ee81a0..62f69811 100644 --- a/config/c53/config.cpp +++ b/config/c53/config.cpp @@ -1,61 +1,60 @@ -#include "comms/B0XXInputViewer.hpp" -#include "comms/DInputBackend.hpp" -#include "comms/GamecubeBackend.hpp" -#include "comms/N64Backend.hpp" -#include "comms/NintendoSwitchBackend.hpp" -#include "comms/XInputBackend.hpp" -#include "config/mode_selection.hpp" +#include "comms/backend_init.hpp" +#include "config_defaults.hpp" #include "core/CommunicationBackend.hpp" -#include "core/InputMode.hpp" #include "core/KeyboardMode.hpp" +#include "core/Persistence.hpp" +#include "core/mode_selection.hpp" #include "core/pinout.hpp" -#include "core/socd.hpp" #include "core/state.hpp" #include "input/SwitchMatrixInput.hpp" -#include "joybus_utils.hpp" -#include "modes/Melee20Button.hpp" +#include "reboot.hpp" #include "stdlib.hpp" -#include +#include -CommunicationBackend **backends; -size_t backend_count; -KeyboardMode *current_kb_mode = nullptr; +Config config = default_config; const size_t num_rows = 5; const size_t num_cols = 13; -uint row_pins[num_rows] = { 20, 19, 18, 17, 16 }; -uint col_pins[num_cols] = { 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; +const uint row_pins[num_rows] = { 20, 19, 18, 17, 16 }; +const uint col_pins[num_cols] = { 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; // clang-format off -SwitchMatrixElement matrix[num_rows][num_cols] = { - {NA, NA, NA, NA, NA, BTN(select), BTN(start), BTN(home), NA, BTN(r), BTN(y), BTN(lightshield), BTN(midshield)}, - { BTN(l), BTN(left), BTN(down), BTN(right), NA, NA, NA, NA, NA, BTN(b), BTN(x), BTN(z), BTN(up) }, - { NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA }, - { NA, NA, NA, NA, NA, NA, NA, NA, NA, BTN(c_left), BTN(c_up), BTN(c_right), NA }, - { NA, NA, BTN(mod_x), BTN(mod_y), NA, NA, NA, NA, NA, BTN(c_down), BTN(a), NA, NA }, +const Button matrix[num_rows][num_cols] = { + {BTN_LF8, BTN_LF7, BTN_LF6, BTN_LF5, NA, BTN_MB3, BTN_MB1, BTN_MB2, NA, BTN_RF5, BTN_RF6, BTN_RF7, BTN_RF8 }, + { BTN_LF4, BTN_LF3, BTN_LF2, BTN_LF1, NA, BTN_MB4, BTN_MB5, BTN_MB6, NA, BTN_RF1, BTN_RF2, BTN_RF3, BTN_RF4 }, + { BTN_LF12, BTN_LF11, BTN_LF10, BTN_LF9, NA, BTN_MB7, BTN_MB8, BTN_MB9, NA, BTN_RF9, BTN_RF10, BTN_RF11, BTN_RF12}, + { NA, BTN_LT5, BTN_LT4, BTN_LT3, NA, BTN_MB10, BTN_MB11, BTN_MB12, NA, BTN_RT3, BTN_RT4, BTN_RT5, NA }, + { NA, NA, BTN_LT1, BTN_LT2, NA, BTN_LT6, BTN_RT7, BTN_RT6, NA, BTN_RT2, BTN_RT1, NA, NA }, }; // clang-format on -DiodeDirection diode_direction = DiodeDirection::COL2ROW; +const DiodeDirection diode_direction = DiodeDirection::COL2ROW; const Pinout pinout = { .joybus_data = 22, + .nes_data = -1, + .nes_clock = -1, + .nes_latch = -1, .mux = -1, .nunchuk_detect = -1, .nunchuk_sda = -1, .nunchuk_scl = -1, }; +SwitchMatrixInput matrix_input(row_pins, col_pins, matrix, diode_direction); + +CommunicationBackend **backends; +size_t backend_count; +KeyboardMode *current_kb_mode = nullptr; + void setup() { - // Create switch matrix input source and use it to read button states for checking button holds. - SwitchMatrixInput *matrix_input = - new SwitchMatrixInput(row_pins, col_pins, matrix, diode_direction); + static InputState inputs; - InputState button_holds; - matrix_input->UpdateInputs(button_holds); + // Read button states for checking button holds. + matrix_input.UpdateInputs(inputs); // Bootsel button hold as early as possible for safety. - if (button_holds.start) { - reset_usb_boot(0, 0); + if (inputs.rt2) { + reboot_bootloader(); } // Turn on LED to indicate firmware booted. @@ -63,63 +62,18 @@ void setup() { gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT); gpio_put(PICO_DEFAULT_LED_PIN, 1); - // Create array of input sources to be used. - static InputSource *input_sources[] = { matrix_input }; - size_t input_source_count = sizeof(input_sources) / sizeof(InputSource *); - - ConnectedConsole console = detect_console(pinout.joybus_data); - - /* Select communication backend. */ - CommunicationBackend *primary_backend; - if (console == ConnectedConsole::NONE) { - if (button_holds.x) { - // If no console detected and X is held on plugin then use Switch USB backend. - NintendoSwitchBackend::RegisterDescriptor(); - backend_count = 1; - primary_backend = new NintendoSwitchBackend(input_sources, input_source_count); - backends = new CommunicationBackend *[backend_count] { primary_backend }; - - // Default to Ultimate mode on Switch. - primary_backend->SetGameMode(new Ultimate(socd::SOCD_2IP)); - return; - } else if (button_holds.z) { - // If no console detected and Z is held on plugin then use DInput backend. - TUGamepad::registerDescriptor(); - TUKeyboard::registerDescriptor(); - backend_count = 2; - primary_backend = new DInputBackend(input_sources, input_source_count); - backends = new CommunicationBackend *[backend_count] { - primary_backend, new B0XXInputViewer(input_sources, input_source_count) - }; - } else { - // Default to XInput mode if no console detected and no other mode forced. - backend_count = 2; - primary_backend = new XInputBackend(input_sources, input_source_count); - backends = new CommunicationBackend *[backend_count] { - primary_backend, new B0XXInputViewer(input_sources, input_source_count) - }; - } - } else { - if (console == ConnectedConsole::GAMECUBE) { - primary_backend = - new GamecubeBackend(input_sources, input_source_count, pinout.joybus_data); - } else if (console == ConnectedConsole::N64) { - primary_backend = new N64Backend(input_sources, input_source_count, pinout.joybus_data); - } - - // If console then only using 1 backend (no input viewer). - backend_count = 1; - backends = new CommunicationBackend *[backend_count] { primary_backend }; + // Attempt to load config, or write default config to flash if failed to load config. + if (!persistence.LoadConfig(config)) { + persistence.SaveConfig(config); } - // Default to Melee mode. - primary_backend->SetGameMode( - new Melee20Button(socd::SOCD_2IP_NO_REAC, { .crouch_walk_os = false }) - ); + backend_count = initialize_backends(backends, inputs, nullptr, 0, config, pinout); + + setup_mode_activation_bindings(config.game_mode_configs, config.game_mode_configs_count); } void loop() { - select_mode(backends[0]); + select_mode(backends, backend_count, config); for (size_t i = 0; i < backend_count; i++) { backends[i]->SendReport(); @@ -129,3 +83,17 @@ void loop() { current_kb_mode->SendReport(backends[0]->GetInputs()); } } + +/* Button inputs are read from the second core */ + +void setup1() { + while (backends == nullptr) { + tight_loop_contents(); + } +} + +void loop1() { + if (backends != nullptr) { + matrix_input.UpdateInputs(backends[0]->GetInputs()); + } +} \ No newline at end of file diff --git a/config/gccmx/config.cpp b/config/gccmx/config.cpp index 604331cb..5dbdcd19 100644 --- a/config/gccmx/config.cpp +++ b/config/gccmx/config.cpp @@ -1,118 +1,96 @@ -#include "comms/B0XXInputViewer.hpp" -#include "comms/DInputBackend.hpp" -#include "comms/GamecubeBackend.hpp" -#include "comms/N64Backend.hpp" -#include "config/mode_selection.hpp" +#include "comms/backend_init.hpp" +#include "config_defaults.hpp" #include "core/CommunicationBackend.hpp" -#include "core/InputMode.hpp" +#include "core/KeyboardMode.hpp" +#include "core/mode_selection.hpp" #include "core/pinout.hpp" -#include "core/socd.hpp" #include "core/state.hpp" #include "input/GpioButtonInput.hpp" -#include "modes/Melee20Button.hpp" +#include "reboot.hpp" #include "stdlib.hpp" -CommunicationBackend **backends = nullptr; -size_t backend_count; -KeyboardMode *current_kb_mode = nullptr; +#include + +Config config = default_config; + +const GpioButtonMapping button_mappings[] = { + { BTN_LF4, 15 }, + { BTN_LF3, 16 }, + { BTN_LF2, 14 }, + { BTN_LF1, 3 }, + + { BTN_LT1, 2 }, + { BTN_LT2, 0 }, -GpioButtonMapping button_mappings[] = { - {&InputState::l, 15}, - { &InputState::left, 16}, - { &InputState::down, 14}, - { &InputState::right, 3 }, - { &InputState::mod_x, 2 }, - { &InputState::mod_y, 0 }, - - { &InputState::select, 1 }, - { &InputState::start, 4 }, - { &InputState::home, 12}, - - { &InputState::c_left, 8 }, - { &InputState::c_up, 10}, - { &InputState::c_down, 6 }, - { &InputState::a, 9 }, - { &InputState::c_right, 5 }, - - { &InputState::b, A2}, - { &InputState::x, A1}, - { &InputState::z, A0}, - { &InputState::up, 13}, - - { &InputState::r, 7 }, - { &InputState::y, A5}, - { &InputState::lightshield, A4}, - { &InputState::midshield, A3}, + { BTN_MB3, 1 }, + { BTN_MB1, 4 }, + { BTN_MB2, 12 }, + + { BTN_RT3, 8 }, + { BTN_RT4, 10 }, + { BTN_RT2, 6 }, + { BTN_RT1, 9 }, + { BTN_RT5, 5 }, + + { BTN_RF1, A2 }, + { BTN_RF2, A1 }, + { BTN_RF3, A0 }, + { BTN_RF4, 13 }, + + { BTN_RF5, 7 }, + { BTN_RF6, A5 }, + { BTN_RF7, A4 }, + { BTN_RF8, A3 }, }; -size_t button_count = sizeof(button_mappings) / sizeof(GpioButtonMapping); +const size_t button_count = sizeof(button_mappings) / sizeof(GpioButtonMapping); -Pinout pinout = { +const Pinout pinout = { .joybus_data = 17, + .nes_data = 0, + .nes_clock = 0, + .nes_latch = 0, .mux = 11, .nunchuk_detect = -1, .nunchuk_sda = -1, .nunchuk_scl = -1, }; +CommunicationBackend **backends = nullptr; +size_t backend_count; +KeyboardMode *current_kb_mode = nullptr; + void setup() { - // Create GPIO input source and use it to read button states for checking button holds. - GpioButtonInput *gpio_input = new GpioButtonInput(button_mappings, button_count); + static InputState inputs; - InputState button_holds; - gpio_input->UpdateInputs(button_holds); + // Create GPIO input source and use it to read button states for checking button holds. + GpioButtonInput gpio_input(button_mappings, button_count); + gpio_input.UpdateInputs(inputs); - // Create array of input sources to be used. - static InputSource *input_sources[] = { gpio_input }; - size_t input_source_count = sizeof(input_sources) / sizeof(InputSource *); + // Check bootloader button hold as early as possible for safety. + if (inputs.rt2) { + Serial.begin(115200); + reboot_bootloader(); + } - // Hold B on plugin for Brook board mode. + // Hold RF1 (B) on plugin for Brook board mode. pinMode(pinout.mux, OUTPUT); - if (button_holds.b) + if (inputs.rf1) digitalWrite(pinout.mux, HIGH); else digitalWrite(pinout.mux, LOW); - CommunicationBackend *primary_backend = new DInputBackend(input_sources, input_source_count); - delay(500); - bool usb_connected = UDADDR & _BV(ADDEN); - - /* Select communication backend. */ - if (usb_connected) { - // Default to DInput mode if USB is connected. - // Input viewer only used when connected to PC i.e. when using DInput mode. - backend_count = 2; - backends = new CommunicationBackend *[backend_count] { - primary_backend, new B0XXInputViewer(input_sources, input_source_count) - }; - } else { - delete primary_backend; - if (button_holds.c_left) { - // Hold C-Left on plugin for N64. - primary_backend = - new N64Backend(input_sources, input_source_count, 60, pinout.joybus_data); - } else if (button_holds.a) { - // Hold A on plugin for GameCube adapter. - primary_backend = - new GamecubeBackend(input_sources, input_source_count, 0, pinout.joybus_data); - } else { - // Default to GameCube/Wii. - primary_backend = - new GamecubeBackend(input_sources, input_source_count, 125, pinout.joybus_data); - } - - // If not DInput then only using 1 backend (no input viewer). - backend_count = 1; - backends = new CommunicationBackend *[backend_count] { primary_backend }; - } + // Create array of input sources to be used. + static InputSource *input_sources[] = { &gpio_input }; + size_t input_source_count = sizeof(input_sources) / sizeof(InputSource *); + + backend_count = + initialize_backends(backends, inputs, input_sources, input_source_count, config, pinout); - // Default to Melee mode. - primary_backend->SetGameMode( - new Melee20Button(socd::SOCD_2IP_NO_REAC, { .crouch_walk_os = false }) - ); + setup_mode_activation_bindings(config.game_mode_configs, config.game_mode_configs_count); } void loop() { - select_mode(backends[0]); + select_mode(backends, backend_count, config); for (size_t i = 0; i < backend_count; i++) { backends[i]->SendReport(); diff --git a/config/gccpcb1/config.cpp b/config/gccpcb1/config.cpp index 072deeaf..d12b7adf 100644 --- a/config/gccpcb1/config.cpp +++ b/config/gccpcb1/config.cpp @@ -1,109 +1,87 @@ -#include "comms/B0XXInputViewer.hpp" -#include "comms/DInputBackend.hpp" -#include "comms/GamecubeBackend.hpp" -#include "comms/N64Backend.hpp" -#include "config/mode_selection.hpp" +#include "comms/backend_init.hpp" +#include "config_defaults.hpp" #include "core/CommunicationBackend.hpp" -#include "core/InputMode.hpp" +#include "core/KeyboardMode.hpp" +#include "core/mode_selection.hpp" #include "core/pinout.hpp" -#include "core/socd.hpp" #include "core/state.hpp" #include "input/GpioButtonInput.hpp" -#include "modes/Melee20Button.hpp" +#include "reboot.hpp" #include "stdlib.hpp" -CommunicationBackend **backends = nullptr; -size_t backend_count; -KeyboardMode *current_kb_mode = nullptr; +#include + +Config config = default_config; + +const GpioButtonMapping button_mappings[] = { + { BTN_LF4, 16 }, + { BTN_LF3, 1 }, + { BTN_LF2, 0 }, + { BTN_LF1, 4 }, + + { BTN_LT1, 5 }, + { BTN_LT2, 6 }, + + { BTN_MB1, 7 }, + + { BTN_RT3, 9 }, + { BTN_RT4, 8 }, + { BTN_RT2, 12 }, + { BTN_RT1, 15 }, + { BTN_RT5, 14 }, -GpioButtonMapping button_mappings[] = { - {&InputState::l, 16}, - { &InputState::left, 1 }, - { &InputState::down, 0 }, - { &InputState::right, 4 }, - { &InputState::mod_x, 5 }, - { &InputState::mod_y, 6 }, - - { &InputState::start, 7 }, - - { &InputState::c_left, 9 }, - { &InputState::c_up, 8 }, - { &InputState::c_down, 12}, - { &InputState::a, 15}, - { &InputState::c_right, 14}, - - { &InputState::b, A2}, - { &InputState::x, A1}, - { &InputState::z, A0}, - { &InputState::up, 13}, - - { &InputState::r, A4}, - { &InputState::y, A3}, - { &InputState::lightshield, 11}, - { &InputState::midshield, 10}, + { BTN_RF1, A2 }, + { BTN_RF2, A1 }, + { BTN_RF3, A0 }, + { BTN_RF4, 13 }, + + { BTN_RF5, A4 }, + { BTN_RF6, A3 }, + { BTN_RF7, 11 }, + { BTN_RF8, 10 }, }; -size_t button_count = sizeof(button_mappings) / sizeof(GpioButtonMapping); +const size_t button_count = sizeof(button_mappings) / sizeof(GpioButtonMapping); -Pinout pinout = { +const Pinout pinout = { .joybus_data = A5, + .nes_data = 0, + .nes_clock = 0, + .nes_latch = 0, .mux = -1, .nunchuk_detect = -1, .nunchuk_sda = -1, .nunchuk_scl = -1, }; +CommunicationBackend **backends = nullptr; +size_t backend_count; +KeyboardMode *current_kb_mode = nullptr; + void setup() { + static InputState inputs; + // Create GPIO input source and use it to read button states for checking button holds. - GpioButtonInput *gpio_input = new GpioButtonInput(button_mappings, button_count); + static GpioButtonInput gpio_input(button_mappings, button_count); + gpio_input.UpdateInputs(inputs); - InputState button_holds; - gpio_input->UpdateInputs(button_holds); + // Check bootloader button hold as early as possible for safety. + if (inputs.rt2) { + Serial.begin(115200); + reboot_bootloader(); + } // Create array of input sources to be used. - static InputSource *input_sources[] = { gpio_input }; + static InputSource *input_sources[] = { &gpio_input }; size_t input_source_count = sizeof(input_sources) / sizeof(InputSource *); - CommunicationBackend *primary_backend = new DInputBackend(input_sources, input_source_count); - delay(500); - bool usb_connected = UDADDR & _BV(ADDEN); - - /* Select communication backend. */ - if (usb_connected) { - // Default to DInput mode if USB is connected. - // Input viewer only used when connected to PC i.e. when using DInput mode. - backend_count = 2; - backends = new CommunicationBackend *[backend_count] { - primary_backend, new B0XXInputViewer(input_sources, input_source_count) - }; - } else { - delete primary_backend; - if (button_holds.c_left) { - // Hold C-Left on plugin for N64. - primary_backend = - new N64Backend(input_sources, input_source_count, 60, pinout.joybus_data); - } else if (button_holds.a) { - // Hold A on plugin for GameCube adapter. - primary_backend = - new GamecubeBackend(input_sources, input_source_count, 0, pinout.joybus_data); - } else { - // Default to GameCube/Wii. - primary_backend = - new GamecubeBackend(input_sources, input_source_count, 125, pinout.joybus_data); - } - - // If not DInput then only using 1 backend (no input viewer). - backend_count = 1; - backends = new CommunicationBackend *[backend_count] { primary_backend }; - } + backend_count = + initialize_backends(backends, inputs, input_sources, input_source_count, config, pinout); - // Default to Melee mode. - primary_backend->SetGameMode( - new Melee20Button(socd::SOCD_2IP_NO_REAC, { .crouch_walk_os = false }) - ); + setup_mode_activation_bindings(config.game_mode_configs, config.game_mode_configs_count); } void loop() { - select_mode(backends[0]); + select_mode(backends, backend_count, config); for (size_t i = 0; i < backend_count; i++) { backends[i]->SendReport(); diff --git a/config/gccpcb2/config.cpp b/config/gccpcb2/config.cpp index 9366e92f..9b29ab6e 100644 --- a/config/gccpcb2/config.cpp +++ b/config/gccpcb2/config.cpp @@ -1,119 +1,96 @@ -#include "comms/B0XXInputViewer.hpp" -#include "comms/DInputBackend.hpp" -#include "comms/GamecubeBackend.hpp" -#include "comms/N64Backend.hpp" -#include "config/mode_selection.hpp" +#include "comms/backend_init.hpp" +#include "config_defaults.hpp" #include "core/CommunicationBackend.hpp" -#include "core/InputMode.hpp" +#include "core/KeyboardMode.hpp" +#include "core/mode_selection.hpp" #include "core/pinout.hpp" -#include "core/socd.hpp" #include "core/state.hpp" #include "input/GpioButtonInput.hpp" -#include "modes/Melee20Button.hpp" +#include "reboot.hpp" #include "stdlib.hpp" -CommunicationBackend **backends = nullptr; -size_t backend_count; -KeyboardMode *current_kb_mode = nullptr; +#include + +Config config = default_config; + +const GpioButtonMapping button_mappings[] = { + { BTN_LF4, 2 }, + { BTN_LF3, 10 }, + { BTN_LF2, 13 }, + { BTN_LF1, 5 }, + + { BTN_LT1, 7 }, + { BTN_LT2, 3 }, -GpioButtonMapping button_mappings[] = { - {&InputState::l, 2 }, - { &InputState::left, 10}, - { &InputState::down, 13}, - { &InputState::right, 5 }, - - { &InputState::mod_x, 7 }, - { &InputState::mod_y, 3 }, - - { &InputState::select, 9 }, - { &InputState::start, 6 }, - { &InputState::home, 8 }, - - { &InputState::c_left, A1}, - { &InputState::c_up, A3}, - { &InputState::c_down, A0}, - { &InputState::a, A2}, - { &InputState::c_right, A4}, - - { &InputState::b, A5}, - { &InputState::x, 14}, - { &InputState::z, 16}, - { &InputState::up, 15}, - - { &InputState::r, 12}, - { &InputState::y, 4 }, - { &InputState::lightshield, 1 }, - { &InputState::midshield, 0 }, + { BTN_MB3, 9 }, + { BTN_MB1, 6 }, + { BTN_MB2, 8 }, + + { BTN_RT3, A1 }, + { BTN_RT4, A3 }, + { BTN_RT2, A0 }, + { BTN_RT1, A2 }, + { BTN_RT5, A4 }, + + { BTN_RF1, A5 }, + { BTN_RF2, 14 }, + { BTN_RF3, 16 }, + { BTN_RF4, 15 }, + + { BTN_RF5, 12 }, + { BTN_RF6, 4 }, + { BTN_RF7, 1 }, + { BTN_RF8, 0 }, }; -size_t button_count = sizeof(button_mappings) / sizeof(GpioButtonMapping); +const size_t button_count = sizeof(button_mappings) / sizeof(GpioButtonMapping); -Pinout pinout = { +const Pinout pinout = { .joybus_data = 17, + .nes_data = 0, + .nes_clock = 0, + .nes_latch = 0, .mux = 11, .nunchuk_detect = -1, .nunchuk_sda = -1, .nunchuk_scl = -1, }; +CommunicationBackend **backends = nullptr; +size_t backend_count; +KeyboardMode *current_kb_mode = nullptr; + void setup() { - // Create GPIO input source and use it to read button states for checking button holds. - GpioButtonInput *gpio_input = new GpioButtonInput(button_mappings, button_count); + static InputState inputs; - InputState button_holds; - gpio_input->UpdateInputs(button_holds); + // Create GPIO input source and use it to read button states for checking button holds. + GpioButtonInput gpio_input(button_mappings, button_count); + gpio_input.UpdateInputs(inputs); - // Create array of input sources to be used. - static InputSource *input_sources[] = { gpio_input }; - size_t input_source_count = sizeof(input_sources) / sizeof(InputSource *); + // Check bootloader button hold as early as possible for safety. + if (inputs.rt2) { + Serial.begin(115200); + reboot_bootloader(); + } - // Hold B on plugin for Brook board mode. + // Hold RF1 (B) on plugin for Brook board mode. pinMode(pinout.mux, OUTPUT); - if (button_holds.b) + if (inputs.rf1) digitalWrite(pinout.mux, HIGH); else digitalWrite(pinout.mux, LOW); - CommunicationBackend *primary_backend = new DInputBackend(input_sources, input_source_count); - delay(500); - bool usb_connected = UDADDR & _BV(ADDEN); - - /* Select communication backend. */ - if (usb_connected) { - // Default to DInput mode if USB is connected. - // Input viewer only used when connected to PC i.e. when using DInput mode. - backend_count = 2; - backends = new CommunicationBackend *[backend_count] { - primary_backend, new B0XXInputViewer(input_sources, input_source_count) - }; - } else { - delete primary_backend; - if (button_holds.c_left) { - // Hold C-Left on plugin for N64. - primary_backend = - new N64Backend(input_sources, input_source_count, 60, pinout.joybus_data); - } else if (button_holds.a) { - // Hold A on plugin for GameCube adapter. - primary_backend = - new GamecubeBackend(input_sources, input_source_count, 0, pinout.joybus_data); - } else { - // Default to GameCube/Wii. - primary_backend = - new GamecubeBackend(input_sources, input_source_count, 125, pinout.joybus_data); - } - - // If not DInput then only using 1 backend (no input viewer). - backend_count = 1; - backends = new CommunicationBackend *[backend_count] { primary_backend }; - } + // Create array of input sources to be used. + static InputSource *input_sources[] = { &gpio_input }; + size_t input_source_count = sizeof(input_sources) / sizeof(InputSource *); + + backend_count = + initialize_backends(backends, inputs, input_sources, input_source_count, config, pinout); - // Default to Melee mode. - primary_backend->SetGameMode( - new Melee20Button(socd::SOCD_2IP_NO_REAC, { .crouch_walk_os = false }) - ); + setup_mode_activation_bindings(config.game_mode_configs, config.game_mode_configs_count); } void loop() { - select_mode(backends[0]); + select_mode(backends, backend_count, config); for (size_t i = 0; i < backend_count; i++) { backends[i]->SendReport(); diff --git a/config/htangl_v1/config.cpp b/config/htangl_v1/config.cpp new file mode 100644 index 00000000..4964d808 --- /dev/null +++ b/config/htangl_v1/config.cpp @@ -0,0 +1,95 @@ +#include "comms/backend_init.hpp" +#include "config_defaults.hpp" +#include "core/CommunicationBackend.hpp" +#include "core/KeyboardMode.hpp" +#include "core/mode_selection.hpp" +#include "core/pinout.hpp" +#include "core/state.hpp" +#include "input/GpioButtonInput.hpp" +#include "reboot.hpp" +#include "stdlib.hpp" + +#include + +Config config = default_config; + +const GpioButtonMapping button_mappings[] = { + { BTN_LF4, 6 }, + { BTN_LF3, 4 }, + { BTN_LF2, 0 }, + { BTN_LF1, 1 }, + + { BTN_LT1, 14 }, + { BTN_LT2, 16 }, + + { BTN_MB3, 2 }, + { BTN_MB1, 3 }, + { BTN_MB2, 5 }, + + { BTN_RT3, A2 }, + { BTN_RT4, A3 }, + { BTN_RT2, 15 }, + { BTN_RT1, A5 }, + { BTN_RT5, A4 }, + + { BTN_RF1, 10 }, + { BTN_RF2, 11 }, + { BTN_RF3, 13 }, + { BTN_RF4, A1 }, + + { BTN_RF5, 7 }, + { BTN_RF6, 9 }, + { BTN_RF7, 12 }, + { BTN_RF8, A0 }, +}; +const size_t button_count = sizeof(button_mappings) / sizeof(GpioButtonMapping); + +const Pinout pinout = { + .joybus_data = 8, + .nes_data = 0, + .nes_clock = 0, + .nes_latch = 0, + .mux = -1, + .nunchuk_detect = -1, + .nunchuk_sda = -1, + .nunchuk_scl = -1, +}; + +CommunicationBackend **backends = nullptr; +size_t backend_count; +KeyboardMode *current_kb_mode = nullptr; + +void setup() { + static InputState inputs; + + // Create GPIO input source and use it to read button states for checking button holds. + static GpioButtonInput gpio_input(button_mappings, button_count); + gpio_input.UpdateInputs(inputs); + + // Check bootloader button hold as early as possible for safety. + if (inputs.rt2) { + Serial.begin(115200); + reboot_bootloader(); + } + + // Create array of input sources to be used. + static InputSource *input_sources[] = { &gpio_input }; + size_t input_source_count = sizeof(input_sources) / sizeof(InputSource *); + + backend_count = + initialize_backends(backends, inputs, input_sources, input_source_count, config, pinout); + + setup_mode_activation_bindings(config.game_mode_configs, config.game_mode_configs_count); +} + +void loop() { + select_mode(backends, backend_count, config); + + for (size_t i = 0; i < backend_count; i++) { + backends[i]->SendReport(); + } + + if (current_kb_mode != nullptr) { + current_kb_mode->SendReport(backends[0]->GetInputs()); + } +} diff --git a/config/htangl_v1/env.ini b/config/htangl_v1/env.ini new file mode 100644 index 00000000..9ef86037 --- /dev/null +++ b/config/htangl_v1/env.ini @@ -0,0 +1,6 @@ +[env:htangl_v1] +extends = avr_usb +board = leonardo +build_src_filter = + ${avr_usb.build_src_filter} + + \ No newline at end of file diff --git a/config/lbx/config.cpp b/config/lbx/config.cpp index a9c379a0..e9f7af3b 100644 --- a/config/lbx/config.cpp +++ b/config/lbx/config.cpp @@ -1,97 +1,106 @@ -#include "comms/B0XXInputViewer.hpp" -#include "comms/DInputBackend.hpp" -#include "comms/GamecubeBackend.hpp" -#include "comms/N64Backend.hpp" -#include "config/mode_selection.hpp" +#include "comms/backend_init.hpp" +#include "config_defaults.hpp" #include "core/CommunicationBackend.hpp" -#include "core/InputMode.hpp" +#include "core/KeyboardMode.hpp" +#include "core/mode_selection.hpp" #include "core/pinout.hpp" -#include "core/socd.hpp" #include "core/state.hpp" #include "input/GpioButtonInput.hpp" -#include "modes/Melee20Button.hpp" +#include "reboot.hpp" #include "stdlib.hpp" -const int brook_up_pin = 17; -const int brook_l_pin = 30; +#include -CommunicationBackend **backends = nullptr; -size_t backend_count; -KeyboardMode *current_kb_mode = nullptr; -bool brook_mode = false; +Config config = default_config; + +const GpioButtonMapping button_mappings[] = { + { BTN_LF4, 11 }, + { BTN_LF3, 15 }, + { BTN_LF2, 16 }, + { BTN_LF1, 14 }, -GpioButtonMapping button_mappings[] = { - {&InputState::l, 11}, - { &InputState::left, 15}, - { &InputState::down, 16}, - { &InputState::right, 14}, - - { &InputState::mod_x, 3 }, - { &InputState::mod_y, 0 }, - { &InputState::nunchuk_c, 2 }, // Dpad Toggle button - - { &InputState::start, A5}, - - { &InputState::c_left, 4 }, - { &InputState::c_up, 8 }, - { &InputState::c_down, 1 }, - { &InputState::a, 12}, - { &InputState::c_right, 6 }, - - { &InputState::b, 13}, - { &InputState::x, 5 }, - { &InputState::z, 10}, - { &InputState::up, 9 }, - - { &InputState::r, A0}, - { &InputState::y, A1}, - { &InputState::lightshield, A2}, - { &InputState::midshield, A3}, + { BTN_LT1, 3 }, + { BTN_LT2, 0 }, + // { &InputState::nunchuk_c, 2 }, // Dpad Toggle button + + { BTN_MB1, A5 }, + + { BTN_RT3, 4 }, + { BTN_RT4, 8 }, + { BTN_RT2, 1 }, + { BTN_RT1, 12 }, + { BTN_RT5, 6 }, + + { BTN_RF1, 13 }, + { BTN_RF2, 5 }, + { BTN_RF3, 10 }, + { BTN_RF4, 9 }, + + { BTN_RF5, A0 }, + { BTN_RF6, A1 }, + { BTN_RF7, A2 }, + { BTN_RF8, A3 }, }; -size_t button_count = sizeof(button_mappings) / sizeof(GpioButtonMapping); - -GpioButtonMapping brook_button_mappings[] = { - // These are the only buttons which aren't also bound on brook board directly. - // And so the only buttons which can be bound to dpad_up and l3 on brook - // WARNING: Bind as few of these as you need, since it increases latency - {&InputState::l, 11}, - - { &InputState::mod_x, 3 }, - { &InputState::mod_y, 0 }, - { &InputState::nunchuk_c, 2 }, - - { &InputState::c_left, 4 }, - { &InputState::c_up, 8 }, - { &InputState::c_down, 1 }, - { &InputState::a, 12}, - { &InputState::c_right, 6 }, +const size_t button_count = sizeof(button_mappings) / sizeof(GpioButtonMapping); + +const GpioButtonMapping brook_button_mappings[] = { + // These are the only buttons which aren't also bound on brook board directly. + // And so the only buttons which can be bound to dpad_up and l3 on brook + // WARNING: Bind as few of these as you need, since it increases latency + { BTN_LF4, 11 }, + + { BTN_LT1, 3 }, + { BTN_LT2, 0 }, + // { &InputState::nunchuk_c, 2 }, + + { BTN_RT3, 4 }, + { BTN_RT4, 8 }, + { BTN_RT2, 1 }, + { BTN_RT1, 12 }, + { BTN_RT5, 6 }, }; Pinout pinout = { .joybus_data = 7, + .nes_data = 0, + .nes_clock = 0, + .nes_latch = 0, .mux = A4, + .nunchuk_detect = -1, + .nunchuk_sda = -1, + .nunchuk_scl = -1, }; +const int brook_up_pin = 17; +const int brook_l_pin = 30; + +CommunicationBackend **backends = nullptr; +size_t backend_count; +KeyboardMode *current_kb_mode = nullptr; +bool brook_mode = false; + void setup() { - // Create GPIO input source and use it to read button states for checking button holds. - GpioButtonInput *gpio_input = new GpioButtonInput(button_mappings, button_count); + static InputState inputs; - InputState button_holds; - gpio_input->UpdateInputs(button_holds); + // Create GPIO input source and use it to read button states for checking button holds. + GpioButtonInput gpio_input(button_mappings, button_count); + gpio_input.UpdateInputs(inputs); - // Create array of input sources to be used. - static InputSource *input_sources[] = { gpio_input }; - size_t input_source_count = sizeof(input_sources) / sizeof(InputSource *); + // Check bootloader button hold as early as possible for safety. + if (inputs.rt2) { + Serial.begin(115200); + reboot_bootloader(); + } - // Hold B on plugin for Brook board mode. + // Hold RF1 (B) on plugin for Brook board mode. pinMode(pinout.mux, OUTPUT); - if (button_holds.b) { + if (inputs.rf1) { digitalWrite(pinout.mux, HIGH); brook_mode = true; return; // Remaining code is no-op if brook is enabled. // Brook Firmware takes control, so we can't control layout/gamemode/backend in this branch - // IN Addition, you can force the following brook modes by holding the corresponding button + // In addition, you can force the following brook modes by holding the corresponding button // on connecting. If none are held, brook will auto-detect. 1P/X = PS3 2P/Y = PS4 3P/RB = // XID-PC 4P/LB = Nintendo Switch These listed buttons correspond to the mapping in brook // mode (so can't be remapped) So in the case of the default layout for lbx these correspond @@ -100,43 +109,14 @@ void setup() { digitalWrite(pinout.mux, LOW); brook_mode = false; - CommunicationBackend *primary_backend = new DInputBackend(input_sources, input_source_count); - delay(500); - bool usb_connected = UDADDR & _BV(ADDEN); - - /* Select communication backend. */ - if (usb_connected) { - // Default to DInput mode if USB is connected. - // Input viewer only used when connected to PC i.e. when using DInput mode. - backend_count = 2; - backends = new CommunicationBackend *[backend_count] { - primary_backend, new B0XXInputViewer(input_sources, input_source_count) - }; - } else { - delete primary_backend; - if (button_holds.c_left) { - // Hold C-Left on plugin for N64. - primary_backend = - new N64Backend(input_sources, input_source_count, 60, pinout.joybus_data); - } else if (button_holds.a) { - // Hold A on plugin for GameCube adapter. - primary_backend = - new GamecubeBackend(input_sources, input_source_count, 0, pinout.joybus_data); - } else { - // Default to GameCube/Wii. - primary_backend = - new GamecubeBackend(input_sources, input_source_count, 125, pinout.joybus_data); - } - - // If not DInput then only using 1 backend (no input viewer). - backend_count = 1; - backends = new CommunicationBackend *[backend_count] { primary_backend }; - } + // Create array of input sources to be used. + static InputSource *input_sources[] = { &gpio_input }; + size_t input_source_count = sizeof(input_sources) / sizeof(InputSource *); + + backend_count = + initialize_backends(backends, inputs, input_sources, input_source_count, config, pinout); - // Default to Melee mode. - primary_backend->SetGameMode( - new Melee20Button(socd::SOCD_2IP_NO_REAC, { .crouch_walk_os = false }) - ); + setup_mode_activation_bindings(config.game_mode_configs, config.game_mode_configs_count); } void loop() { @@ -152,7 +132,7 @@ void loop() { return; } - select_mode(backends[0]); + select_mode(backends, backend_count, config); for (size_t i = 0; i < backend_count; i++) { backends[i]->SendReport(); diff --git a/config/mode_selection.hpp b/config/mode_selection.hpp deleted file mode 100644 index 181b7fec..00000000 --- a/config/mode_selection.hpp +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef _CONFIG_MODE_SELECTION_HPP -#define _CONFIG_MODE_SELECTION_HPP - -#include "core/state.hpp" -#include "modes/DefaultKeyboardMode.hpp" -#include "modes/FgcMode.hpp" -#include "modes/Melee20Button.hpp" -#include "modes/ProjectM.hpp" -#include "modes/RivalsOfAether.hpp" -#include "modes/Ultimate.hpp" - -extern KeyboardMode *current_kb_mode; - -void set_mode(CommunicationBackend *backend, ControllerMode *mode) { - // Delete keyboard mode in case one is set, so we don't end up getting both controller and - // keyboard inputs. - delete current_kb_mode; - current_kb_mode = nullptr; - - // Set new controller mode. - backend->SetGameMode(mode); -} - -void set_mode(CommunicationBackend *backend, KeyboardMode *mode) { - // Delete and reassign current keyboard mode. - delete current_kb_mode; - current_kb_mode = mode; - - // Unset the current controller mode so backend only gives neutral inputs. - backend->SetGameMode(nullptr); -} - -void select_mode(CommunicationBackend *backend) { - InputState &inputs = backend->GetInputs(); - if (inputs.mod_x && !inputs.mod_y && inputs.start) { - if (inputs.l) { - set_mode( - backend, - new Melee20Button(socd::SOCD_2IP_NO_REAC, { .crouch_walk_os = false }) - ); - } else if (inputs.left) { - set_mode( - backend, - new ProjectM( - socd::SOCD_2IP_NO_REAC, - { .true_z_press = false, .ledgedash_max_jump_traj = true } - ) - ); - } else if (inputs.down) { - set_mode(backend, new Ultimate(socd::SOCD_2IP)); - } else if (inputs.right) { - set_mode(backend, new FgcMode(socd::SOCD_NEUTRAL, socd::SOCD_NEUTRAL)); - } else if (inputs.b) { - set_mode(backend, new RivalsOfAether(socd::SOCD_2IP)); - } - } else if (inputs.mod_y && !inputs.mod_x && inputs.start) { - if (inputs.l) { - set_mode(backend, new DefaultKeyboardMode(socd::SOCD_2IP)); - } - } -} - -#endif diff --git a/config/pico/config.cpp b/config/pico/config.cpp index bf4f4a50..90393686 100644 --- a/config/pico/config.cpp +++ b/config/pico/config.cpp @@ -1,77 +1,78 @@ -#include "comms/B0XXInputViewer.hpp" -#include "comms/DInputBackend.hpp" -#include "comms/GamecubeBackend.hpp" -#include "comms/N64Backend.hpp" -#include "comms/NintendoSwitchBackend.hpp" -#include "comms/XInputBackend.hpp" -#include "config/mode_selection.hpp" +#include "comms/backend_init.hpp" +#include "config_defaults.hpp" #include "core/CommunicationBackend.hpp" -#include "core/InputMode.hpp" #include "core/KeyboardMode.hpp" +#include "core/Persistence.hpp" +#include "core/mode_selection.hpp" #include "core/pinout.hpp" -#include "core/socd.hpp" #include "core/state.hpp" -#include "input/GpioButtonInput.hpp" +#include "input/DebouncedGpioButtonInput.hpp" #include "input/NunchukInput.hpp" -#include "joybus_utils.hpp" -#include "modes/Melee20Button.hpp" +#include "reboot.hpp" #include "stdlib.hpp" -#include +#include -CommunicationBackend **backends = nullptr; -size_t backend_count; -KeyboardMode *current_kb_mode = nullptr; +Config config = default_config; GpioButtonMapping button_mappings[] = { - {&InputState::l, 5 }, - { &InputState::left, 4 }, - { &InputState::down, 3 }, - { &InputState::right, 2 }, - - { &InputState::mod_x, 6 }, - { &InputState::mod_y, 7 }, - - { &InputState::select, 10}, - { &InputState::start, 0 }, - { &InputState::home, 11}, - - { &InputState::c_left, 13}, - { &InputState::c_up, 12}, - { &InputState::c_down, 15}, - { &InputState::a, 14}, - { &InputState::c_right, 16}, - - { &InputState::b, 26}, - { &InputState::x, 21}, - { &InputState::z, 19}, - { &InputState::up, 17}, - - { &InputState::r, 27}, - { &InputState::y, 22}, - { &InputState::lightshield, 20}, - { &InputState::midshield, 18}, + { BTN_LF1, 2 }, + { BTN_LF2, 3 }, + { BTN_LF3, 4 }, + { BTN_LF4, 5 }, + { BTN_LF5, 1 }, + + { BTN_LT1, 6 }, + { BTN_LT2, 7 }, + + { BTN_MB1, 0 }, + { BTN_MB2, 10 }, + { BTN_MB3, 11 }, + + { BTN_RT1, 15 }, + { BTN_RT2, 14 }, + { BTN_RT3, 13 }, + { BTN_RT4, 12 }, + { BTN_RT5, 16 }, + + { BTN_RF1, 26 }, + { BTN_RF2, 21 }, + { BTN_RF3, 19 }, + { BTN_RF4, 17 }, + + { BTN_RF5, 27 }, + { BTN_RF6, 22 }, + { BTN_RF7, 20 }, + { BTN_RF8, 18 }, }; -size_t button_count = sizeof(button_mappings) / sizeof(GpioButtonMapping); +const size_t button_count = sizeof(button_mappings) / sizeof(GpioButtonMapping); + +DebouncedGpioButtonInput gpio_input(button_mappings); const Pinout pinout = { .joybus_data = 28, + .nes_data = -1, + .nes_clock = -1, + .nes_latch = -1, .mux = -1, .nunchuk_detect = -1, .nunchuk_sda = -1, .nunchuk_scl = -1, }; +CommunicationBackend **backends = nullptr; +size_t backend_count; +KeyboardMode *current_kb_mode = nullptr; + void setup() { - // Create GPIO input source and use it to read button states for checking button holds. - GpioButtonInput *gpio_input = new GpioButtonInput(button_mappings, button_count); + static InputState inputs; - InputState button_holds; - gpio_input->UpdateInputs(button_holds); + // Create GPIO input source and use it to read button states for checking button holds. + gpio_input.UpdateInputs(inputs); - // Bootsel button hold as early as possible for safety. - if (button_holds.start) { - reset_usb_boot(0, 0); + // Check bootsel button hold as early as possible for safety. + if (inputs.rt2) { + reboot_bootloader(); } // Turn on LED to indicate firmware booted. @@ -79,63 +80,23 @@ void setup() { gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT); gpio_put(PICO_DEFAULT_LED_PIN, 1); + // Attempt to load config, or write default config to flash if failed to load config. + if (!persistence.LoadConfig(config)) { + persistence.SaveConfig(config); + } + // Create array of input sources to be used. - static InputSource *input_sources[] = { gpio_input }; + static InputSource *input_sources[] = {}; size_t input_source_count = sizeof(input_sources) / sizeof(InputSource *); - ConnectedConsole console = detect_console(pinout.joybus_data); - - /* Select communication backend. */ - CommunicationBackend *primary_backend; - if (console == ConnectedConsole::NONE) { - if (button_holds.x) { - // If no console detected and X is held on plugin then use Switch USB backend. - NintendoSwitchBackend::RegisterDescriptor(); - backend_count = 1; - primary_backend = new NintendoSwitchBackend(input_sources, input_source_count); - backends = new CommunicationBackend *[backend_count] { primary_backend }; - - // Default to Ultimate mode on Switch. - primary_backend->SetGameMode(new Ultimate(socd::SOCD_2IP)); - return; - } else if (button_holds.z) { - // If no console detected and Z is held on plugin then use DInput backend. - TUGamepad::registerDescriptor(); - TUKeyboard::registerDescriptor(); - backend_count = 2; - primary_backend = new DInputBackend(input_sources, input_source_count); - backends = new CommunicationBackend *[backend_count] { - primary_backend, new B0XXInputViewer(input_sources, input_source_count) - }; - } else { - // Default to XInput mode if no console detected and no other mode forced. - backend_count = 2; - primary_backend = new XInputBackend(input_sources, input_source_count); - backends = new CommunicationBackend *[backend_count] { - primary_backend, new B0XXInputViewer(input_sources, input_source_count) - }; - } - } else { - if (console == ConnectedConsole::GAMECUBE) { - primary_backend = - new GamecubeBackend(input_sources, input_source_count, pinout.joybus_data); - } else if (console == ConnectedConsole::N64) { - primary_backend = new N64Backend(input_sources, input_source_count, pinout.joybus_data); - } - - // If console then only using 1 backend (no input viewer). - backend_count = 1; - backends = new CommunicationBackend *[backend_count] { primary_backend }; - } + backend_count = + initialize_backends(backends, inputs, input_sources, input_source_count, config, pinout); - // Default to Melee mode. - primary_backend->SetGameMode( - new Melee20Button(socd::SOCD_2IP_NO_REAC, { .crouch_walk_os = false }) - ); + setup_mode_activation_bindings(config.game_mode_configs, config.game_mode_configs_count); } void loop() { - select_mode(backends[0]); + select_mode(backends, backend_count, config); for (size_t i = 0; i < backend_count; i++) { backends[i]->SendReport(); @@ -146,21 +107,16 @@ void loop() { } } -/* Nunchuk code runs on the second core */ -NunchukInput *nunchuk = nullptr; +/* Button inputs are read from the second core */ void setup1() { while (backends == nullptr) { tight_loop_contents(); } - - // Create Nunchuk input source. - nunchuk = new NunchukInput(Wire, pinout.nunchuk_detect, pinout.nunchuk_sda, pinout.nunchuk_scl); } void loop1() { if (backends != nullptr) { - nunchuk->UpdateInputs(backends[0]->GetInputs()); - busy_wait_us(50); + gpio_input.UpdateInputs(backends[0]->GetInputs()); } } diff --git a/config/schism/config.cpp b/config/schism/config.cpp new file mode 100644 index 00000000..6f91e62c --- /dev/null +++ b/config/schism/config.cpp @@ -0,0 +1,127 @@ +#include "comms/backend_init.hpp" +#include "config_defaults.hpp" +#include "core/CommunicationBackend.hpp" +#include "core/KeyboardMode.hpp" +#include "core/Persistence.hpp" +#include "core/mode_selection.hpp" +#include "core/pinout.hpp" +#include "core/state.hpp" +#include "input/DebouncedGpioButtonInput.hpp" +#include "input/Pca9671Input.hpp" +#include "reboot.hpp" +#include "stdlib.hpp" + +#include + +Config config = default_config; + +GpioButtonMapping button_mappings[] = { + {BTN_LF1, 27}, + { BTN_LF2, 26}, + { BTN_LF3, 19}, + { BTN_LF4, 18}, + + { BTN_LT1, 3 }, + { BTN_LT2, 2 }, + { BTN_LT3, 0 }, + + { BTN_MB1, 1 }, +}; +const size_t button_count = sizeof(button_mappings) / sizeof(GpioButtonMapping); + +Pca9671ButtonMapping expander_button_mappings[] = { + {BTN_RT1, 14}, + { BTN_RT2, 15}, + { BTN_RT3, 13}, + { BTN_RT4, 11}, + { BTN_RT5, 12}, + + { BTN_RF1, 7 }, + { BTN_RF2, 3 }, + { BTN_RF3, 4 }, + { BTN_RF4, 6 }, + + { BTN_RF5, 0 }, + { BTN_RF6, 1 }, + { BTN_RF7, 2 }, + { BTN_RF8, 5 }, +}; +const size_t expander_button_count = + sizeof(expander_button_mappings) / sizeof(Pca9671ButtonMapping); + +DebouncedGpioButtonInput gpio_input(button_mappings); +Pca9671Input expander_input(expander_button_mappings, expander_button_count, Wire, 20, 21); + +const Pinout pinout = { + .joybus_data = 28, + .mux = -1, + .nunchuk_detect = -1, + .nunchuk_sda = -1, + .nunchuk_scl = -1, +}; + +CommunicationBackend **backends = nullptr; +size_t backend_count; +KeyboardMode *current_kb_mode = nullptr; + +void setup() { + static InputState inputs; + + // Create GPIO input source and use it to read button states for checking button holds. + gpio_input.UpdateInputs(inputs); + + // Check bootsel button hold as early as possible for safety. + if (inputs.rt2) { + reboot_bootloader(); + } + + // Also scan I/O expander for inputs, but after the bootloader check in case it causes a crash. + expander_input.UpdateInputs(inputs); + + // Turn on LED to indicate firmware booted. + gpio_init(PICO_DEFAULT_LED_PIN); + gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT); + gpio_put(PICO_DEFAULT_LED_PIN, 1); + + // Attempt to load config, or write default config to flash if failed to load config. + if (!persistence.LoadConfig(config)) { + persistence.SaveConfig(config); + } + + // Create array of input sources to be used. + static InputSource *input_sources[] = {}; + size_t input_source_count = sizeof(input_sources) / sizeof(InputSource *); + + backend_count = + initialize_backends(backends, inputs, input_sources, input_source_count, config, pinout); + + setup_mode_activation_bindings(config.game_mode_configs, config.game_mode_configs_count); +} + +void loop() { + select_mode(backends, backend_count, config); + + for (size_t i = 0; i < backend_count; i++) { + backends[i]->SendReport(); + } + + if (current_kb_mode != nullptr) { + current_kb_mode->SendReport(backends[0]->GetInputs()); + } +} + +/* Button inputs are read from the second core */ + +void setup1() { + while (backends == nullptr) { + tight_loop_contents(); + } +} + +void loop1() { + if (backends != nullptr) { + InputState &inputs = backends[0]->GetInputs(); + gpio_input.UpdateInputs(inputs); + expander_input.UpdateInputs(inputs); + } +} diff --git a/config/schism/env.ini b/config/schism/env.ini new file mode 100644 index 00000000..40c42504 --- /dev/null +++ b/config/schism/env.ini @@ -0,0 +1,6 @@ +[env:schism] +extends = arduino_pico_base +board_build.f_cpu = 130000000L +build_src_filter = + ${arduino_pico_base.build_src_filter} + + diff --git a/config/smashbox/config.cpp b/config/smashbox/config.cpp index ea54a6f4..54e19921 100644 --- a/config/smashbox/config.cpp +++ b/config/smashbox/config.cpp @@ -1,97 +1,94 @@ -#include "comms/GamecubeBackend.hpp" -#include "comms/N64Backend.hpp" -#include "config/mode_selection.hpp" +#include "comms/backend_init.hpp" +#include "config_defaults.hpp" #include "core/CommunicationBackend.hpp" -#include "core/InputMode.hpp" #include "core/KeyboardMode.hpp" +#include "core/mode_selection.hpp" #include "core/pinout.hpp" -#include "core/socd.hpp" #include "core/state.hpp" #include "input/GpioButtonInput.hpp" #include "input/NunchukInput.hpp" -#include "modes/Melee20Button.hpp" +#include "reboot.hpp" #include "stdlib.hpp" -CommunicationBackend **backends = nullptr; -size_t backend_count; -KeyboardMode *current_kb_mode = nullptr; +#include + +Config config = default_config; + +const GpioButtonMapping button_mappings[] = { + { BTN_LF4, 47 }, + { BTN_LF3, 24 }, + { BTN_LF2, 23 }, + { BTN_LF1, 25 }, + + { BTN_LT1, 28 }, + { BTN_LT2, 29 }, + { BTN_LT3, 30 }, + { BTN_LT4, 31 }, + + { BTN_MB1, 50 }, + + { BTN_RT3, 36 }, + { BTN_RT4, 34 }, + { BTN_RT2, 46 }, + { BTN_RT1, 35 }, + { BTN_RT5, 37 }, + + { BTN_RF1, 44 }, + { BTN_RF2, 42 }, + { BTN_RF3, 7 }, + { BTN_RF4, 45 }, -GpioButtonMapping button_mappings[] = { - {&InputState::l, 47}, - { &InputState::left, 24}, - { &InputState::down, 23}, - { &InputState::right, 25}, - - { &InputState::mod_x, 28}, - { &InputState::mod_y, 29}, - { &InputState::select, 30}, - { &InputState::home, 31}, - - { &InputState::start, 50}, - - { &InputState::c_left, 36}, - { &InputState::c_up, 34}, - { &InputState::c_down, 46}, - { &InputState::a, 35}, - { &InputState::c_right, 37}, - - { &InputState::b, 44}, - { &InputState::x, 42}, - { &InputState::z, 7 }, - { &InputState::up, 45}, - - { &InputState::r, 41}, - { &InputState::y, 43}, - { &InputState::lightshield, 40}, - { &InputState::midshield, 12}, + { BTN_RF5, 41 }, + { BTN_RF6, 43 }, + { BTN_RF7, 40 }, + { BTN_RF8, 12 }, }; -size_t button_count = sizeof(button_mappings) / sizeof(GpioButtonMapping); +const size_t button_count = sizeof(button_mappings) / sizeof(GpioButtonMapping); -Pinout pinout = { +const Pinout pinout = { .joybus_data = 52, + .nes_data = 0, + .nes_clock = 0, + .nes_latch = 0, .mux = -1, .nunchuk_detect = -1, .nunchuk_sda = -1, .nunchuk_scl = -1, }; +CommunicationBackend **backends = nullptr; +size_t backend_count; +KeyboardMode *current_kb_mode = nullptr; + void setup() { + static InputState inputs; + // Create Nunchuk input source - must be done before GPIO input source otherwise it would // disable the pullups on the i2c pins. - NunchukInput *nunchuk = new NunchukInput(); + static NunchukInput nunchuk; // Create GPIO input source and use it to read button states for checking button holds. - GpioButtonInput *gpio_input = new GpioButtonInput(button_mappings, button_count); + static GpioButtonInput gpio_input(button_mappings, button_count); + gpio_input.UpdateInputs(inputs); - InputState button_holds; - gpio_input->UpdateInputs(button_holds); + // Check bootloader button hold as early as possible for safety. + if (inputs.rt2) { + Serial.begin(115200); + reboot_bootloader(); + } // Create array of input sources to be used. - static InputSource *input_sources[] = { gpio_input, nunchuk }; + static InputSource *input_sources[] = { &gpio_input, &nunchuk }; size_t input_source_count = sizeof(input_sources) / sizeof(InputSource *); - CommunicationBackend *primary_backend = nullptr; - if (button_holds.a) { - // Hold A on plugin for GameCube adapter. - primary_backend = - new GamecubeBackend(input_sources, input_source_count, 0, pinout.joybus_data); - } else { - // Default to GameCube/Wii. - primary_backend = - new GamecubeBackend(input_sources, input_source_count, 125, pinout.joybus_data); - } - - backend_count = 1; - backends = new CommunicationBackend *[backend_count] { primary_backend }; + backend_count = + initialize_backends(backends, inputs, input_sources, input_source_count, config, pinout); - // Default to Melee mode. - primary_backend->SetGameMode( - new Melee20Button(socd::SOCD_2IP_NO_REAC, { .crouch_walk_os = false }) - ); + setup_mode_activation_bindings(config.game_mode_configs, config.game_mode_configs_count); } void loop() { - select_mode(backends[0]); + select_mode(backends, backend_count, config); for (size_t i = 0; i < backend_count; i++) { backends[i]->SendReport(); diff --git a/include/comms/B0XXInputViewer.hpp b/include/comms/B0XXInputViewer.hpp index b03f1fc5..47713956 100644 --- a/include/comms/B0XXInputViewer.hpp +++ b/include/comms/B0XXInputViewer.hpp @@ -12,7 +12,7 @@ enum reportState : byte { class B0XXInputViewer : public CommunicationBackend { public: - B0XXInputViewer(InputSource **input_sources, size_t input_source_count); + B0XXInputViewer(InputState &inputs, InputSource **input_sources, size_t input_source_count); ~B0XXInputViewer(); void SendReport(); diff --git a/include/comms/IntegratedDisplay.hpp b/include/comms/IntegratedDisplay.hpp new file mode 100644 index 00000000..1441ce74 --- /dev/null +++ b/include/comms/IntegratedDisplay.hpp @@ -0,0 +1,62 @@ +#ifndef _COMMS_INTEGRATEDDISPLAY_HPP +#define _COMMS_INTEGRATEDDISPLAY_HPP + +#include "core/CommunicationBackend.hpp" +#include "display/DisplayMode.hpp" + +#include +#include + +typedef struct _DisplayControls { + Button back; + Button down; + Button up; + Button enter; +} DisplayControls; + +class IntegratedDisplay : public CommunicationBackend { + public: + static constexpr uint8_t controls_count = 4; + static constexpr uint32_t button_cooldown_ms = 150; + static constexpr uint8_t font_width = 6; + static constexpr uint8_t font_height = 8; + static constexpr uint8_t default_color = 1; + + IntegratedDisplay( + InputState &inputs, + Adafruit_GFX &display, + void (*clear_display)(), + void (*update_display)(), + const DisplayControls controls, + DisplayMode **display_modes, + size_t display_modes_count + ); + ~IntegratedDisplay(); + virtual void SendReport(); + void SetDisplayMode(DisplayModeId display_mode); + + protected: + Adafruit_GFX &_display; + void (*_clear_display)(); + void (*_update_display)(); + const DisplayControls _controls; + const Button _controls_array[controls_count]; + DisplayModeId _display_mode = DISPLAY_MODE_VIEWER; + absolute_time_t _button_cooldown_end = 0; + + private: + DisplayMode **_display_modes; + size_t _display_modes_count; + + void HandleControls(DisplayMode *active_mode); + + /** + * @brief Get the current active DisplayMode instance. + * + * @return A pointer to the first DisplayMode instance in the _display_modes array whose + * DisplayModeId is the same as the current value of _display_mode in this class. + */ + DisplayMode *GetActiveDisplayMode(); +}; + +#endif \ No newline at end of file diff --git a/include/comms/backend_init.hpp b/include/comms/backend_init.hpp new file mode 100644 index 00000000..7d08aad2 --- /dev/null +++ b/include/comms/backend_init.hpp @@ -0,0 +1,124 @@ +#ifndef _COMMS_BACKEND_INIT_HPP +#define _COMMS_BACKEND_INIT_HPP + +#include "comms/console_detection.hpp" +#include "core/CommunicationBackend.hpp" +#include "core/InputSource.hpp" +#include "core/pinout.hpp" +#include "core/state.hpp" + +#include + +/** + * @brief Optionally defined function that allows a device config to hook into initialize_backends() + * and use a custom method for selecting backend config before console detection + * + * @param backend_config The reference to the current backend config after checking for button holds + * and connected console + * @param inputs Reference to the InputState struct to pass into each backend's constructor + * @param config Reference to the global Config struct + * @return true if the passed in backend config was altered, otherwise false + */ +typedef void (*backend_config_selector_t)( + CommunicationBackendConfig &backend_config, + const InputState &inputs, + Config &config +); + +/** + * @brief Optionally defined function that allows a device config to hook into initialize_backends() + * and use a custom method for selecting the default USB communication backend to initialize prior + * to console/USB detection + * + * @param backend_config The reference to the current backend config after checking for button holds + * and connected console + * @param config Reference to the global Config struct + * @return true if the passed in backend config was altered, otherwise false + */ +// bool usb_backend_config_custom(CommunicationBackendConfig &backend_config, const Config &config) +// __attribute__((weak)); +typedef void (*usb_backend_getter_t)( + CommunicationBackendConfig &backend_config, + const Config &config +); + +/** + * @brief Initialize primary backend based on the passed in backend id + * + * @param primary_backend The reference to the primary backend pointer to initialize + * @param backend_id The id of the backend to initialize + * @param backends The reference to assign to the created backends array + * @param inputs Reference to the InputState struct to pass into each backend's constructor + * @param input_sources Input sources array to pass into each backend's constructor + * @param input_source_count Number of elements in the input_sources array + * @param config Reference to global config struct + * @param pinout Pinout struct used for GameCube/N64 communication backends + */ +typedef void (*primary_backend_initializer_t)( + CommunicationBackend *&primary_backend, + CommunicationBackendId backend_id, + InputState &inputs, + InputSource **input_sources, + size_t input_source_count, + Config &config, + const Pinout &pinout +); + +/** + * @brief Initialize secondary backends and the backends array and return the number of elements in + * the array + * + * @param backends The reference to assign to the created backends array + * @param primary_backend The reference to the primary backend to insert first in the backends array + * @param backend_id The id indicating the primary backend + * @param inputs Reference to the InputState struct to pass into each backend's constructor + * @param input_sources Input sources array to pass into each backend's constructor + * @param input_source_count Number of elements in the input_sources array + * @param config Reference to global config struct + * @param pinout Pinout struct used for GameCube/N64 communication backends + * @return The number of backends in the array + */ +typedef size_t (*secondary_backend_initializer_t)( + CommunicationBackend **&backends, + CommunicationBackend *&primary_backend, + CommunicationBackendId backend_id, + InputState &inputs, + InputSource **input_sources, + size_t input_source_count, + Config &config, + const Pinout &pinout +); + +typedef CommunicationBackendId (*detect_console_t)(const Pinout &pinout); + +extern backend_config_selector_t get_backend_config_default; +extern usb_backend_getter_t get_usb_backend_config_default; +extern primary_backend_initializer_t init_primary_backend_default; +extern secondary_backend_initializer_t init_secondary_backends_default; + +/** + * @brief Initialize the backends array and return the number of elements in the array + * + * @param backends The reference to assign to the created backends array + * @param inputs Reference to the InputState struct to pass into each backend's constructor + * @param input_sources Input sources array to pass into each backend's constructor + * @param input_source_count Number of elements in the input_sources array + * @param config Reference to global config struct + * @param pinout Pinout struct used for GameCube/N64 communication backends + * @return size_t The number of backends in the array + */ +size_t initialize_backends( + CommunicationBackend **&backends, + InputState &inputs, + InputSource **input_sources, + size_t input_source_count, + Config &config, + const Pinout &pinout, + backend_config_selector_t get_backend_config_custom = get_backend_config_default, + usb_backend_getter_t get_usb_backend_config = get_usb_backend_config_default, + detect_console_t detect_console = &detect_console, + secondary_backend_initializer_t init_secondary_backends = init_secondary_backends_default, + primary_backend_initializer_t init_primary_backend = init_primary_backend_default +); + +#endif diff --git a/include/comms/console_detection.hpp b/include/comms/console_detection.hpp new file mode 100644 index 00000000..9ddea46d --- /dev/null +++ b/include/comms/console_detection.hpp @@ -0,0 +1,10 @@ +#ifndef _COMMS_CONSOLE_DETECTION_HPP +#define _COMMS_CONSOLE_DETECTION_HPP + +#include "core/pinout.hpp" + +#include + +CommunicationBackendId detect_console(const Pinout &pinout); + +#endif \ No newline at end of file diff --git a/include/core/CommunicationBackend.hpp b/include/core/CommunicationBackend.hpp index 33fd56d0..5d661430 100644 --- a/include/core/CommunicationBackend.hpp +++ b/include/core/CommunicationBackend.hpp @@ -7,25 +7,32 @@ class CommunicationBackend { public: - CommunicationBackend(InputSource **input_sources, size_t input_source_count); + CommunicationBackend( + InputState &inputs, + InputSource **input_sources, + size_t input_source_count + ); virtual ~CommunicationBackend(){}; InputState &GetInputs(); + OutputState &GetOutputs(); void ScanInputs(); void ScanInputs(InputScanSpeed input_source_filter); - void UpdateOutputs(); - virtual void SetGameMode(ControllerMode *gamemode); + virtual void UpdateOutputs(); + virtual CommunicationBackendId BackendId(); + virtual void SetGameMode(InputMode *gamemode); + virtual InputMode *CurrentGameMode(); virtual void SendReport() = 0; protected: - InputState _inputs; + InputState &_inputs; InputSource **_input_sources; size_t _input_source_count; OutputState _outputs; - ControllerMode *_gamemode; + InputMode *_gamemode = nullptr; private: void ResetOutputs(); diff --git a/include/core/ControllerMode.hpp b/include/core/ControllerMode.hpp index 05b776b0..8d3a927d 100644 --- a/include/core/ControllerMode.hpp +++ b/include/core/ControllerMode.hpp @@ -5,10 +5,12 @@ #include "core/socd.hpp" #include "core/state.hpp" +#include + class ControllerMode : public InputMode { public: ControllerMode(); - void UpdateOutputs(InputState &inputs, OutputState &outputs); + void UpdateOutputs(const InputState &inputs, OutputState &outputs); void ResetDirections(); virtual void UpdateDirections( bool lsLeft, @@ -29,8 +31,8 @@ class ControllerMode : public InputMode { StickDirections directions; private: - virtual void UpdateDigitalOutputs(InputState &inputs, OutputState &outputs) = 0; - virtual void UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) = 0; + virtual void UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs) = 0; + virtual void UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs) = 0; }; #endif diff --git a/include/core/InputMode.hpp b/include/core/InputMode.hpp index 2be670c2..0e4d7803 100644 --- a/include/core/InputMode.hpp +++ b/include/core/InputMode.hpp @@ -4,19 +4,23 @@ #include "socd.hpp" #include "state.hpp" +#include + class InputMode { public: InputMode(); - virtual ~InputMode(); + GameModeConfig *GetConfig(); + void SetConfig(GameModeConfig &config); + virtual void UpdateOutputs(const InputState &inputs, OutputState &outputs) = 0; protected: - socd::SocdPair *_socd_pairs = nullptr; - size_t _socd_pair_count = 0; + GameModeConfig *_config = nullptr; virtual void HandleSocd(InputState &inputs); + virtual void HandleRemap(const InputState &original_inputs, InputState &remapped_inputs); private: - socd::SocdState *_socd_states = nullptr; + socd::SocdState _socd_states[10] = {}; }; #endif diff --git a/include/core/mode_selection.hpp b/include/core/mode_selection.hpp new file mode 100644 index 00000000..79ee24de --- /dev/null +++ b/include/core/mode_selection.hpp @@ -0,0 +1,19 @@ +#ifndef _CORE_MODE_SELECTION_HPP +#define _CORE_MODE_SELECTION_HPP + +#include "core/CommunicationBackend.hpp" +#include "core/ControllerMode.hpp" +#include "core/KeyboardMode.hpp" + +#include + +extern KeyboardMode *current_kb_mode; + +void set_mode(CommunicationBackend *backend, ControllerMode *mode); +void set_mode(CommunicationBackend *backend, KeyboardMode *mode); +void set_mode(CommunicationBackend *backend, GameModeConfig &mode_config, Config &config); +void set_mode(CommunicationBackend *backend, GameModeId mode_id, Config &config); +void select_mode(CommunicationBackend **backends, size_t backends_count, Config &config); +void setup_mode_activation_bindings(const GameModeConfig *mode_configs, size_t mode_configs_count); + +#endif diff --git a/include/core/pinout.hpp b/include/core/pinout.hpp index 539cca18..7ba44310 100644 --- a/include/core/pinout.hpp +++ b/include/core/pinout.hpp @@ -5,6 +5,9 @@ typedef struct { uint8_t joybus_data; + int nes_data; + int nes_clock; + int nes_latch; int mux; int nunchuk_detect; int nunchuk_sda; diff --git a/include/core/socd.hpp b/include/core/socd.hpp index 78b6bddd..5d912b27 100644 --- a/include/core/socd.hpp +++ b/include/core/socd.hpp @@ -4,22 +4,9 @@ #include "state.hpp" #include "stdlib.hpp" -namespace socd { - typedef enum { - SOCD_NEUTRAL, - SOCD_2IP, - SOCD_2IP_NO_REAC, - SOCD_DIR1_PRIORITY, - SOCD_DIR2_PRIORITY, - SOCD_NONE, - } SocdType; - - typedef struct { - bool InputState::*input_dir1; - bool InputState::*input_dir2; - SocdType socd_type = SOCD_NEUTRAL; - } SocdPair; +#include +namespace socd { typedef struct { bool was_dir1 = false; bool was_dir2 = false; @@ -28,16 +15,22 @@ namespace socd { } SocdState; void second_input_priority_no_reactivation( - bool &input_dir1, - bool &input_dir2, + InputState &inputs, + Button button_dir1, + Button button_dir2, SocdState &socd_state ); - void second_input_priority(bool &input_dir1, bool &input_dir2, SocdState &socd_state); + void second_input_priority( + InputState &inputs, + Button button_dir1, + Button button_dir2, + SocdState &socd_state + ); - void neutral(bool &input_dir1, bool &input_dir2); + void neutral(InputState &inputs, Button button_dir1, Button button_dir2); - void dir1_priority(bool &input_dir1, bool &input_dir2); + void dir1_priority(InputState &inputs, Button button_dir1, Button button_dir2); } #endif diff --git a/include/core/state.hpp b/include/core/state.hpp index 5570e599..8a8e7e13 100644 --- a/include/core/state.hpp +++ b/include/core/state.hpp @@ -3,38 +3,90 @@ #include "stdlib.hpp" -// Button state. -typedef struct inputstate { +#include + +typedef struct _InputState { // Rectangle inputs. - bool left = false; - bool right = false; - bool down = false; - bool up = false; - bool c_left = false; - bool c_right = false; - bool c_down = false; - bool c_up = false; - bool a = false; - bool b = false; - bool x = false; - bool y = false; - bool l = false; - bool r = false; - bool z = false; - bool lightshield = false; - bool midshield = false; - bool select = false; - bool start = false; - bool home = false; - bool mod_x = false; - bool mod_y = false; + union { + uint64_t buttons = 0; + + struct { + bool lf1 : 1; + bool lf2 : 1; + bool lf3 : 1; + bool lf4 : 1; + bool lf5 : 1; + bool lf6 : 1; + bool lf7 : 1; + bool lf8 : 1; + bool lf9 : 1; + bool lf10 : 1; + bool lf11 : 1; + bool lf12 : 1; + bool lf13 : 1; + bool lf14 : 1; + bool lf15 : 1; + bool lf16 : 1; + bool rf1 : 1; + bool rf2 : 1; + bool rf3 : 1; + bool rf4 : 1; + bool rf5 : 1; + bool rf6 : 1; + bool rf7 : 1; + bool rf8 : 1; + bool rf9 : 1; + bool rf10 : 1; + bool rf11 : 1; + bool rf12 : 1; + bool rf13 : 1; + bool rf14 : 1; + bool rf15 : 1; + bool rf16 : 1; + bool lt1 : 1; + bool lt2 : 1; + bool lt3 : 1; + bool lt4 : 1; + bool lt5 : 1; + bool lt6 : 1; + bool lt7 : 1; + bool lt8 : 1; + bool rt1 : 1; + bool rt2 : 1; + bool rt3 : 1; + bool rt4 : 1; + bool rt5 : 1; + bool rt6 : 1; + bool rt7 : 1; + bool rt8 : 1; + bool mb1 : 1; + bool mb2 : 1; + bool mb3 : 1; + bool mb4 : 1; + bool mb5 : 1; + bool mb6 : 1; + bool mb7 : 1; + bool mb8 : 1; + bool mb9 : 1; + bool mb10 : 1; + bool mb11 : 1; + bool mb12 : 1; + }; + }; // Nunchuk inputs. - bool nunchuk_connected = false; + union { + uint8_t nunchuk_buttons = 0; + + struct { + bool nunchuk_connected : 1; + bool nunchuk_c : 1; + bool nunchuk_z : 1; + }; + }; + int8_t nunchuk_x = 0; int8_t nunchuk_y = 0; - bool nunchuk_c = false; - bool nunchuk_z = false; } InputState; // State describing stick direction at the quadrant level. @@ -49,33 +101,46 @@ typedef struct { } StickDirections; // Output state. -typedef struct outputstate { +typedef struct _OutputState { // Digital outputs. - bool a = false; - bool b = false; - bool x = false; - bool y = false; - bool buttonL = false; - bool buttonR = false; - bool triggerLDigital = false; - bool triggerRDigital = false; - bool start = false; - bool select = false; - bool home = false; - bool dpadUp = false; - bool dpadDown = false; - bool dpadLeft = false; - bool dpadRight = false; - bool leftStickClick = false; - bool rightStickClick = false; + union { + uint32_t buttons = 0; + + struct { + bool a : 1; + bool b : 1; + bool x : 1; + bool y : 1; + bool buttonL : 1; + bool buttonR : 1; + bool triggerLDigital : 1; + bool triggerRDigital : 1; + bool start : 1; + bool select : 1; + bool home : 1; + bool capture : 1; + bool dpadUp : 1; + bool dpadDown : 1; + bool dpadLeft : 1; + bool dpadRight : 1; + bool leftStickClick : 1; + bool rightStickClick : 1; + }; + }; // Analog outputs. - uint8_t leftStickX = 128; - uint8_t leftStickY = 128; - uint8_t rightStickX = 128; - uint8_t rightStickY = 128; - uint8_t triggerRAnalog = 0; - uint8_t triggerLAnalog = 0; + union { + uint8_t analog_axes[6] = { 128, 128, 128, 128, 0, 0 }; + + struct { + uint8_t leftStickX; + uint8_t leftStickY; + uint8_t rightStickX; + uint8_t rightStickY; + uint8_t triggerLAnalog; + uint8_t triggerRAnalog; + }; + }; } OutputState; #endif \ No newline at end of file diff --git a/include/input/GpioButtonInput.hpp b/include/input/GpioButtonInput.hpp index 4fefb4d6..fdc4c9b6 100644 --- a/include/input/GpioButtonInput.hpp +++ b/include/input/GpioButtonInput.hpp @@ -5,20 +5,25 @@ #include "core/state.hpp" #include "stdlib.hpp" +#include + typedef struct { - bool InputState::*button; + Button button; uint pin; } GpioButtonMapping; class GpioButtonInput : public InputSource { public: - GpioButtonInput(GpioButtonMapping *button_mappings, size_t button_count); + GpioButtonInput(const GpioButtonMapping *button_mappings, size_t button_count); InputScanSpeed ScanSpeed(); void UpdateInputs(InputState &inputs); protected: - GpioButtonMapping *_button_mappings; + const GpioButtonMapping *_button_mappings; size_t _button_count; + + private: + virtual void UpdateButtonState(InputState &inputs, size_t button_mapping_index, bool pressed); }; #endif \ No newline at end of file diff --git a/include/input/SwitchMatrixInput.hpp b/include/input/SwitchMatrixInput.hpp index 7da9f838..d499de64 100644 --- a/include/input/SwitchMatrixInput.hpp +++ b/include/input/SwitchMatrixInput.hpp @@ -4,23 +4,23 @@ #include "core/InputSource.hpp" #include "core/state.hpp" #include "gpio.hpp" +#include "util/state_util.hpp" -#define BTN(x) &InputState::x -#define NA nullptr +#include + +#define NA BTN_UNSPECIFIED enum class DiodeDirection { ROW2COL, COL2ROW, }; -typedef bool InputState::*SwitchMatrixElement; - template class SwitchMatrixInput : public InputSource { public: SwitchMatrixInput( - uint row_pins[num_rows], - uint col_pins[num_cols], - SwitchMatrixElement (&matrix)[num_rows][num_cols], + const uint row_pins[num_rows], + const uint col_pins[num_cols], + const Button (&matrix)[num_rows][num_cols], DiodeDirection direction ) : _matrix(matrix) { @@ -66,11 +66,12 @@ template class SwitchMatrixInput : public Inp // Read each cell in the column/row. for (size_t j = 0; j < _num_inputs; j++) { - SwitchMatrixElement button = - _direction == DiodeDirection::ROW2COL ? _matrix[j][i] : _matrix[i][j]; - if (button != nullptr) { - inputs.*button = !gpio::read_digital(_input_pins[j]); - } + UpdateButtonState( + inputs, + _direction == DiodeDirection::ROW2COL ? j : i, + _direction == DiodeDirection::ROW2COL ? i : j, + !gpio::read_digital(_input_pins[j]) + ); } // Deactivate the column/row. @@ -81,10 +82,21 @@ template class SwitchMatrixInput : public Inp protected: size_t _num_outputs; size_t _num_inputs; - uint *_output_pins; - uint *_input_pins; - SwitchMatrixElement (&_matrix)[num_rows][num_cols]; + const uint *_output_pins; + const uint *_input_pins; + const Button (&_matrix)[num_rows][num_cols]; DiodeDirection _direction; + + private: + virtual void UpdateButtonState( + InputState &inputs, + size_t col_index, + size_t row_index, + bool pressed + ) { + Button button = _matrix[col_index][row_index]; + set_button(inputs.buttons, button, pressed); + }; }; #endif \ No newline at end of file diff --git a/include/modes/CustomControllerMode.hpp b/include/modes/CustomControllerMode.hpp new file mode 100644 index 00000000..c322f0e0 --- /dev/null +++ b/include/modes/CustomControllerMode.hpp @@ -0,0 +1,28 @@ +#ifndef _MODES_CUSTOMCONTROLLERMODE_HPP +#define _MODES_CUSTOMCONTROLLERMODE_HPP + +#include "core/ControllerMode.hpp" +#include "core/state.hpp" + +#include + +class CustomControllerMode : public ControllerMode { + public: + CustomControllerMode(); + void SetConfig(GameModeConfig &config, const CustomModeConfig &custom_mode_config); + + protected: + void UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs); + void UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs); + + private: + const CustomModeConfig *_custom_mode_config; + uint64_t _modifier_button_masks[10]; + uint64_t _button_combo_mappings_masks[5]; + uint64_t _buttons_to_ignore = 0; + uint64_t _filtered_buttons = 0; + + Button GetDirectionButton(const Button *direction_buttons, StickDirectionButton direction); +}; + +#endif diff --git a/include/modes/CustomKeyboardMode.hpp b/include/modes/CustomKeyboardMode.hpp new file mode 100644 index 00000000..79a2725c --- /dev/null +++ b/include/modes/CustomKeyboardMode.hpp @@ -0,0 +1,20 @@ +#ifndef _MODES_CUSTOMKEYBOARDMODE_HPP +#define _MODES_CUSTOMKEYBOARDMODE_HPP + +#include "core/KeyboardMode.hpp" +#include "core/state.hpp" + +#include + +class CustomKeyboardMode : public KeyboardMode { + public: + CustomKeyboardMode(); + void SetConfig(GameModeConfig &config, const KeyboardModeConfig &keyboard_config); + + private: + const KeyboardModeConfig *_keyboard_config; + + void UpdateKeys(const InputState &inputs); +}; + +#endif \ No newline at end of file diff --git a/include/modes/DefaultKeyboardMode.hpp b/include/modes/DefaultKeyboardMode.hpp index 30de505f..cf1a0b3e 100644 --- a/include/modes/DefaultKeyboardMode.hpp +++ b/include/modes/DefaultKeyboardMode.hpp @@ -2,15 +2,14 @@ #define _MODES_DEFAULTKEYBOARDMODE_HPP #include "core/KeyboardMode.hpp" -#include "core/socd.hpp" #include "core/state.hpp" class DefaultKeyboardMode : public KeyboardMode { public: - DefaultKeyboardMode(socd::SocdType socd_type); + DefaultKeyboardMode(); private: - void UpdateKeys(InputState &inputs); + void UpdateKeys(const InputState &inputs); }; #endif diff --git a/include/modes/FgcMode.hpp b/include/modes/FgcMode.hpp index bc838827..d0bca9dd 100644 --- a/include/modes/FgcMode.hpp +++ b/include/modes/FgcMode.hpp @@ -2,16 +2,15 @@ #define _MODES_FGCMODE_HPP #include "core/ControllerMode.hpp" -#include "core/socd.hpp" #include "core/state.hpp" class FgcMode : public ControllerMode { public: - FgcMode(socd::SocdType horizontal_socd, socd::SocdType vertical_socd); + FgcMode(); private: - void UpdateDigitalOutputs(InputState &inputs, OutputState &outputs); - void UpdateAnalogOutputs(InputState &inputs, OutputState &outputs); + void UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs); + void UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs); }; #endif diff --git a/include/modes/Melee18Button.hpp b/include/modes/Melee18Button.hpp index 48843853..3095e5a7 100644 --- a/include/modes/Melee18Button.hpp +++ b/include/modes/Melee18Button.hpp @@ -2,7 +2,6 @@ #define _MODES_MELEE18BUTTON_HPP #include "core/ControllerMode.hpp" -#include "core/socd.hpp" #include "core/state.hpp" typedef struct { @@ -11,15 +10,15 @@ typedef struct { class Melee18Button : public ControllerMode { public: - Melee18Button(socd::SocdType socd_type, Melee18ButtonOptions options = {}); + Melee18Button(Melee18ButtonOptions options = {}); private: Melee18ButtonOptions _options; bool horizontal_socd; void HandleSocd(InputState &inputs); - void UpdateDigitalOutputs(InputState &inputs, OutputState &outputs); - void UpdateAnalogOutputs(InputState &inputs, OutputState &outputs); + void UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs); + void UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs); }; #endif diff --git a/include/modes/Melee20Button.hpp b/include/modes/Melee20Button.hpp index 12be2801..54f904f2 100644 --- a/include/modes/Melee20Button.hpp +++ b/include/modes/Melee20Button.hpp @@ -2,23 +2,21 @@ #define _MODES_MELEE20BUTTON_HPP #include "core/ControllerMode.hpp" -#include "core/socd.hpp" #include "core/state.hpp" -typedef struct { - bool crouch_walk_os = false; -} Melee20ButtonOptions; +#include class Melee20Button : public ControllerMode { public: - Melee20Button(socd::SocdType socd_type, Melee20ButtonOptions options = {}); + Melee20Button(); + void SetConfig(GameModeConfig &config, const MeleeOptions options); protected: - void UpdateDigitalOutputs(InputState &inputs, OutputState &outputs); - void UpdateAnalogOutputs(InputState &inputs, OutputState &outputs); + void UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs); + void UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs); private: - Melee20ButtonOptions _options; + MeleeOptions _options; bool _horizontal_socd; void HandleSocd(InputState &inputs); diff --git a/include/modes/ProjectM.hpp b/include/modes/ProjectM.hpp index ec46038d..0d84da8f 100644 --- a/include/modes/ProjectM.hpp +++ b/include/modes/ProjectM.hpp @@ -2,25 +2,22 @@ #define _MODES_PROJECTM_HPP #include "core/ControllerMode.hpp" -#include "core/socd.hpp" #include "core/state.hpp" -typedef struct { - bool true_z_press = false; - bool ledgedash_max_jump_traj = true; -} ProjectMOptions; +#include class ProjectM : public ControllerMode { public: - ProjectM(socd::SocdType socd_type, ProjectMOptions options = {}); + ProjectM(); + void SetConfig(GameModeConfig &config, const ProjectMOptions options); private: ProjectMOptions _options; bool _horizontal_socd; void HandleSocd(InputState &inputs); - void UpdateDigitalOutputs(InputState &inputs, OutputState &outputs); - void UpdateAnalogOutputs(InputState &inputs, OutputState &outputs); + void UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs); + void UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs); }; #endif diff --git a/include/modes/Rivals2.hpp b/include/modes/Rivals2.hpp new file mode 100644 index 00000000..5ccbbad3 --- /dev/null +++ b/include/modes/Rivals2.hpp @@ -0,0 +1,18 @@ +#ifndef _MODES_RIVALS_2_HPP +#define _MODES_RIVALS_2_HPP + +#include "core/ControllerMode.hpp" +#include "core/state.hpp" + +#include //this was not here, but doesn't seem to matter if I remove it from Melee20Button.hpp anyway + +class Rivals2 : public ControllerMode { + public: + Rivals2(); + + private: + void UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs); + void UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs); +}; + +#endif diff --git a/include/modes/RivalsOfAether.hpp b/include/modes/RivalsOfAether.hpp index 62ddfb83..69c832ae 100644 --- a/include/modes/RivalsOfAether.hpp +++ b/include/modes/RivalsOfAether.hpp @@ -2,16 +2,15 @@ #define _MODES_RIVALSOFAETHER_HPP #include "core/ControllerMode.hpp" -#include "core/socd.hpp" #include "core/state.hpp" class RivalsOfAether : public ControllerMode { public: - RivalsOfAether(socd::SocdType socd_type); + RivalsOfAether(); private: - void UpdateDigitalOutputs(InputState &inputs, OutputState &outputs); - void UpdateAnalogOutputs(InputState &inputs, OutputState &outputs); + void UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs); + void UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs); }; #endif diff --git a/include/modes/Ultimate.hpp b/include/modes/Ultimate.hpp index a49e225a..f02eb50d 100644 --- a/include/modes/Ultimate.hpp +++ b/include/modes/Ultimate.hpp @@ -2,16 +2,15 @@ #define _MODES_ULTIMATE_HPP #include "core/ControllerMode.hpp" -#include "core/socd.hpp" #include "core/state.hpp" class Ultimate : public ControllerMode { public: - Ultimate(socd::SocdType socd_type); + Ultimate(); private: - void UpdateDigitalOutputs(InputState &inputs, OutputState &outputs); - void UpdateAnalogOutputs(InputState &inputs, OutputState &outputs); + void UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs); + void UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs); }; #endif diff --git a/include/modes/extra/DarkSouls.hpp b/include/modes/extra/DarkSouls.hpp index 65c04851..5ff2722b 100644 --- a/include/modes/extra/DarkSouls.hpp +++ b/include/modes/extra/DarkSouls.hpp @@ -7,11 +7,11 @@ class DarkSouls : public ControllerMode { public: - DarkSouls(socd::SocdType socd_type); + DarkSouls(); private: - void UpdateDigitalOutputs(InputState &inputs, OutputState &outputs); - void UpdateAnalogOutputs(InputState &inputs, OutputState &outputs); + void UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs); + void UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs); }; #endif diff --git a/include/modes/extra/HollowKnight.hpp b/include/modes/extra/HollowKnight.hpp index 8a182ea4..7b395ffb 100644 --- a/include/modes/extra/HollowKnight.hpp +++ b/include/modes/extra/HollowKnight.hpp @@ -8,11 +8,11 @@ class HollowKnight : public ControllerMode { public: - HollowKnight(socd::SocdType socd_type); + HollowKnight(); private: - void UpdateDigitalOutputs(InputState &inputs, OutputState &outputs); - void UpdateAnalogOutputs(InputState &inputs, OutputState &outputs); + void UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs); + void UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs); }; #endif diff --git a/include/modes/extra/MKWii.hpp b/include/modes/extra/MKWii.hpp index 3b165f1b..7bc12a8e 100644 --- a/include/modes/extra/MKWii.hpp +++ b/include/modes/extra/MKWii.hpp @@ -7,11 +7,11 @@ class MKWii : public ControllerMode { public: - MKWii(socd::SocdType socd_type); + MKWii(); private: - void UpdateDigitalOutputs(InputState &inputs, OutputState &outputs); - void UpdateAnalogOutputs(InputState &inputs, OutputState &outputs); + void UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs); + void UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs); }; #endif diff --git a/include/modes/extra/MultiVersus.hpp b/include/modes/extra/MultiVersus.hpp index 2f7c510c..4dca8f9e 100644 --- a/include/modes/extra/MultiVersus.hpp +++ b/include/modes/extra/MultiVersus.hpp @@ -7,11 +7,11 @@ class MultiVersus : public ControllerMode { public: - MultiVersus(socd::SocdType socd_type); + MultiVersus(); protected: - virtual void UpdateDigitalOutputs(InputState &inputs, OutputState &outputs); - virtual void UpdateAnalogOutputs(InputState &inputs, OutputState &outputs); + virtual void UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs); + virtual void UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs); }; #endif diff --git a/include/modes/extra/RocketLeague.hpp b/include/modes/extra/RocketLeague.hpp index 4891b833..bd4e6013 100644 --- a/include/modes/extra/RocketLeague.hpp +++ b/include/modes/extra/RocketLeague.hpp @@ -7,12 +7,11 @@ class RocketLeague : public ControllerMode { public: - RocketLeague(socd::SocdType socd_type); + RocketLeague(); private: - void HandleSocd(InputState &inputs); - void UpdateDigitalOutputs(InputState &inputs, OutputState &outputs); - void UpdateAnalogOutputs(InputState &inputs, OutputState &outputs); + void UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs); + void UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs); }; #endif diff --git a/include/modes/extra/SaltAndSanctuary.hpp b/include/modes/extra/SaltAndSanctuary.hpp index 5ca60a3e..42466669 100644 --- a/include/modes/extra/SaltAndSanctuary.hpp +++ b/include/modes/extra/SaltAndSanctuary.hpp @@ -7,11 +7,11 @@ class SaltAndSanctuary : public ControllerMode { public: - SaltAndSanctuary(socd::SocdType socd_type); + SaltAndSanctuary(); private: - void UpdateDigitalOutputs(InputState &inputs, OutputState &outputs); - void UpdateAnalogOutputs(InputState &inputs, OutputState &outputs); + void UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs); + void UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs); }; #endif diff --git a/include/modes/extra/ShovelKnight.hpp b/include/modes/extra/ShovelKnight.hpp index a9bec744..b1ca38e6 100644 --- a/include/modes/extra/ShovelKnight.hpp +++ b/include/modes/extra/ShovelKnight.hpp @@ -7,11 +7,11 @@ class ShovelKnight : public ControllerMode { public: - ShovelKnight(socd::SocdType socd_type); + ShovelKnight(); private: - virtual void UpdateDigitalOutputs(InputState &inputs, OutputState &outputs); - virtual void UpdateAnalogOutputs(InputState &inputs, OutputState &outputs); + virtual void UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs); + virtual void UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs); }; #endif diff --git a/include/modes/extra/ToughLoveArena.hpp b/include/modes/extra/ToughLoveArena.hpp index bb843ab5..7f5d0fd0 100644 --- a/include/modes/extra/ToughLoveArena.hpp +++ b/include/modes/extra/ToughLoveArena.hpp @@ -7,10 +7,10 @@ class ToughLoveArena : public KeyboardMode { public: - ToughLoveArena(socd::SocdType socd_type); + ToughLoveArena(); private: - void UpdateKeys(InputState &inputs); + void UpdateKeys(const InputState &inputs); }; #endif diff --git a/include/modes/extra/Ultimate2.hpp b/include/modes/extra/Ultimate2.hpp index 3aff2506..14640e74 100644 --- a/include/modes/extra/Ultimate2.hpp +++ b/include/modes/extra/Ultimate2.hpp @@ -7,11 +7,11 @@ class Ultimate2 : public ControllerMode { public: - Ultimate2(socd::SocdType socd_type); + Ultimate2(); private: - void UpdateDigitalOutputs(InputState &inputs, OutputState &outputs); - void UpdateAnalogOutputs(InputState &inputs, OutputState &outputs); + void UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs); + void UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs); }; #endif diff --git a/include/reboot.hpp b/include/reboot.hpp new file mode 100644 index 00000000..506e0c57 --- /dev/null +++ b/include/reboot.hpp @@ -0,0 +1,7 @@ +#ifndef _REBOOT_HPP +#define _REBOOT_HPP + +void reboot_firmware(); +void reboot_bootloader(); + +#endif \ No newline at end of file diff --git a/include/util/analog_filters.hpp b/include/util/analog_filters.hpp new file mode 100644 index 00000000..55420a0e --- /dev/null +++ b/include/util/analog_filters.hpp @@ -0,0 +1,9 @@ +#ifndef _UTIL_ANALOG_FILTERS_HPP +#define _UTIL_ANALOG_FILTERS_HPP + +#include "stdlib.hpp" + +uint8_t apply_deadzone(uint8_t value, uint8_t deadzone, bool scale); +uint8_t apply_radius(uint8_t value, int radius); + +#endif \ No newline at end of file diff --git a/include/util/config_util.hpp b/include/util/config_util.hpp new file mode 100644 index 00000000..0812c516 --- /dev/null +++ b/include/util/config_util.hpp @@ -0,0 +1,93 @@ +#ifndef _CORE_CONFIG_UTILS_HPP +#define _CORE_CONFIG_UTILS_HPP + +#include "core/state.hpp" + +#include + +CommunicationBackendConfig backend_config_from_buttons( + const InputState &inputs, + const CommunicationBackendConfig *backend_configs, + size_t backend_configs_count +); + +CommunicationBackendConfig backend_config_from_id( + CommunicationBackendId backend_id, + const CommunicationBackendConfig *backend_configs, + size_t backend_configs_count +); + +uint8_t backend_config_id_from_backend_id( + CommunicationBackendId backend_id, + const CommunicationBackendConfig *backend_configs, + size_t backend_configs_count +); + +uint8_t mode_config_id_from_mode_id( + GameModeId mode_id, + const GameModeConfig *mode_configs, + size_t mode_configs_count +); + +constexpr const char *gamemode_name(GameModeId mode_id) { + switch (mode_id) { + case MODE_MELEE: + return "Melee"; + case MODE_PROJECT_M: + return "Project M"; + case MODE_ULTIMATE: + return "Ultimate"; + case MODE_FGC: + return "FGC"; + case MODE_RIVALS_OF_AETHER: + return "Rivals"; + case MODE_RIVALS_2: + return "Rivals 2"; + case MODE_KEYBOARD: + return "Keyboard"; + default: + return "Unknown"; + } +} + +constexpr const char *backend_name(CommunicationBackendId backend_id) { + switch (backend_id) { + case COMMS_BACKEND_DINPUT: + return "DInput"; + case COMMS_BACKEND_XINPUT: + return "XInput"; + case COMMS_BACKEND_GAMECUBE: + return "GameCube"; + case COMMS_BACKEND_N64: + return "N64"; + case COMMS_BACKEND_NES: + return "NES"; + case COMMS_BACKEND_SNES: + return "SNES"; + case COMMS_BACKEND_NINTENDO_SWITCH: + return "Switch"; + case COMMS_BACKEND_CONFIGURATOR: + return "Configurator"; + default: + return "Unknown"; + } +} + +constexpr const char *socd_name(SocdType socd_type) { + switch (socd_type) { + case SOCD_NEUTRAL: + return "Neutral"; + case SOCD_2IP: + return "2IP"; + case SOCD_2IP_NO_REAC: + return "2IP No Reactivation"; + case SOCD_DIR1_PRIORITY: + return "Dir 1 priority"; + case SOCD_DIR2_PRIORITY: + return "Dir 2 priority"; + default: + return "Unknown"; + } +} + +#endif diff --git a/lib/TUCompositeHID/include/TUCompositeHID.hpp b/lib/TUCompositeHID/include/TUCompositeHID.hpp index dce350df..e644e48b 100644 --- a/lib/TUCompositeHID/include/TUCompositeHID.hpp +++ b/lib/TUCompositeHID/include/TUCompositeHID.hpp @@ -7,7 +7,7 @@ namespace TUCompositeHID { extern Adafruit_USBD_HID _usb_hid; - bool addDescriptor(uint8_t *descriptor, size_t descriptor_len); + bool addDescriptor(const uint8_t *descriptor, size_t descriptor_len); } #endif \ No newline at end of file diff --git a/lib/TUCompositeHID/include/TUGamepad.hpp b/lib/TUCompositeHID/include/TUGamepad.hpp index 2e4c33a2..447cd689 100644 --- a/lib/TUCompositeHID/include/TUGamepad.hpp +++ b/lib/TUCompositeHID/include/TUGamepad.hpp @@ -67,7 +67,7 @@ class TUGamepad { protected: static const uint8_t _report_id = 1; - static uint8_t _descriptor[]; + static const uint8_t _descriptor[]; gamepad_report_t _report; diff --git a/lib/TUCompositeHID/include/TUKeyboard.hpp b/lib/TUCompositeHID/include/TUKeyboard.hpp index 7e430586..c4b9fc5f 100644 --- a/lib/TUCompositeHID/include/TUKeyboard.hpp +++ b/lib/TUCompositeHID/include/TUKeyboard.hpp @@ -20,7 +20,7 @@ class TUKeyboard { private: static const uint8_t _report_id = 2; - static uint8_t _descriptor[]; + static const uint8_t _descriptor[]; hid_keyboard_report_t _report; }; diff --git a/lib/TUCompositeHID/src/TUCompositeHID.cpp b/lib/TUCompositeHID/src/TUCompositeHID.cpp index acaa6b8c..e3fac0e5 100644 --- a/lib/TUCompositeHID/src/TUCompositeHID.cpp +++ b/lib/TUCompositeHID/src/TUCompositeHID.cpp @@ -16,7 +16,7 @@ namespace TUCompositeHID { false ); - bool addDescriptor(uint8_t *descriptor, size_t descriptor_len) { + bool addDescriptor(const uint8_t *descriptor, size_t descriptor_len) { if (_current_descriptor_len + descriptor_len > HID_DESCRIPTOR_BUFSIZE) { return false; } diff --git a/lib/TUCompositeHID/src/TUGamepad.cpp b/lib/TUCompositeHID/src/TUGamepad.cpp index aaefa094..f8b05af9 100644 --- a/lib/TUCompositeHID/src/TUGamepad.cpp +++ b/lib/TUCompositeHID/src/TUGamepad.cpp @@ -71,7 +71,7 @@ SOFTWARE. // clang-format on -uint8_t TUGamepad::_descriptor[] = { HID_REPORT_DESC(HID_REPORT_ID(_report_id)) }; +const uint8_t TUGamepad::_descriptor[] = { HID_REPORT_DESC(HID_REPORT_ID(_report_id)) }; TUGamepad::TUGamepad() {} diff --git a/lib/TUCompositeHID/src/TUKeyboard.cpp b/lib/TUCompositeHID/src/TUKeyboard.cpp index d3ac6cbc..b93887db 100644 --- a/lib/TUCompositeHID/src/TUKeyboard.cpp +++ b/lib/TUCompositeHID/src/TUKeyboard.cpp @@ -3,7 +3,9 @@ #include #include -uint8_t TUKeyboard::_descriptor[] = { TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(_report_id)) }; +// clang-format off +const uint8_t TUKeyboard::_descriptor[] = { TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(_report_id)) }; +// clang-format on #define MODIFIER_MASK(mod_kc) (1 << (mod_kc & 0x0F)) diff --git a/platformio.ini b/platformio.ini index cb36743c..bc216a34 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,86 +1,126 @@ -[platformio] -default_envs = pico -extra_configs = config/*/env.ini -src_dir = ./ - -[env] -build_type = release -lib_ldf_mode = chain+ -build_flags = - -I src/ - -I include/ -build_src_filter = - + - -[avr_base] -platform = atmelavr -framework = arduino -build_unflags = - -std=gnu++11 -build_flags = - -std=gnu++17 - -Os - -fdata-sections - -ffunction-sections - -fno-sized-deallocation - -Wl,--gc-sections - -I HAL/avr/include -build_src_filter = - ${env.build_src_filter} - + -lib_deps = - ${env.lib_deps} - nicohood/Nintendo@^1.4.0 - Wire - https://github.com/JonnyHaystack/arduino-nunchuk/archive/refs/tags/v1.0.1.zip - -[avr_nousb] -extends = avr_base -build_flags = - ${avr_base.build_flags} - -I HAL/avr/avr_nousb/include -build_src_filter = - ${avr_base.build_src_filter} - + - -[avr_usb] -extends = avr_base -build_flags = - ${avr_base.build_flags} - -I HAL/avr/avr_usb/include -build_src_filter = - ${avr_base.build_src_filter} - + -lib_deps = - ${avr_base.lib_deps} - mheironimus/Joystick@^2.1.1 - https://github.com/JonnyHaystack/ArduinoKeyboard/archive/refs/tags/1.0.5.zip - -[arduino_pico_base] -platform = https://github.com/maxgerhardt/platform-raspberrypi.git#5e87ae34ca025274df25b3303e9e9cb6c120123c -framework = arduino -board = pico -extra_scripts = pre:builder_scripts/arduino_pico.py -debug_tool = picoprobe -board_build.core = earlephilhower -board_build.f_cpu = 130000000L -build_unflags = -Os -build_flags = - ${env.build_flags} - -D USE_TINYUSB - -D CFG_TUSB_CONFIG_FILE=\"tusb_config_pico.h\" - -D NDEBUG - -O3 - -I HAL/pico/include -build_src_filter = - ${env.build_src_filter} - + -platform_packages = - framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#3.6.3 -lib_archive = no -lib_deps = - ${env.lib_deps} - https://github.com/JonnyHaystack/joybus-pio/archive/refs/tags/v1.2.3.zip - https://github.com/JonnyHaystack/arduino-nunchuk/archive/refs/tags/v1.0.1.zip - https://github.com/JonnyHaystack/Adafruit_TinyUSB_XInput - TUCompositeHID +[platformio] +name = HayBox +default_envs = pico +extra_configs = config/*/env.ini +src_dir = ./ + +[env] +build_type = release +custom_firmware_version = 3.0.0 +lib_ldf_mode = chain+ +build_flags = + -I src/ + -I include/ + -D 'DEVICE_NAME="${PIOENV}"' + -D 'FIRMWARE_NAME="${platformio.name}"' + -D 'FIRMWARE_VERSION="${this.custom_firmware_version}"' +build_src_filter = + + +custom_nanopb_protos = + +<.pio/libdeps/${PIOENV}/HayBox-proto/config.proto> +custom_nanopb_options = + --error-on-unmatched +lib_deps = + nanopb/Nanopb@^0.4.91 + https://github.com/JonnyHaystack/HayBox-proto/archive/refs/tags/v0.1.0.zip + +[avr_base] +platform = atmelavr +framework = arduino +build_unflags = + -std=gnu++11 +build_flags = + ${env.build_flags} + -std=gnu++17 + -Os + -fdata-sections + -ffunction-sections + -fno-sized-deallocation + -flto + -fshort-enums + -Wl,--gc-sections + -Wno-unused-variable + -I HAL/avr/include + -D PB_BUFFER_ONLY + -D PB_WITHOUT_64BIT + -D PB_NO_ERRMSG + -D PB_CONVERT_DOUBLE_FLOAT +build_src_filter = + ${env.build_src_filter} + + + - + - +custom_nanopb_options = + ${env.custom_nanopb_options} + --options-file ../../../../HAL/avr/proto/config.options +lib_deps = + ${env.lib_deps} + nicohood/Nintendo@^1.4.0 + Wire + https://github.com/JonnyHaystack/arduino-nunchuk/archive/refs/tags/v1.0.1.zip + +[avr_nousb] +extends = avr_base +build_flags = + ${avr_base.build_flags} + -I HAL/avr/avr_nousb/include +build_src_filter = + ${avr_base.build_src_filter} + + + +[avr_usb] +extends = avr_base +build_flags = + ${avr_base.build_flags} + -I HAL/avr/avr_usb/include +build_src_filter = + ${avr_base.build_src_filter} + + +lib_deps = + ${avr_base.lib_deps} + https://github.com/JonnyHaystack/ArduinoJoystickLibrary/archive/refs/tags/v0.0.1.zip + https://github.com/JonnyHaystack/ArduinoKeyboard/archive/refs/tags/1.0.5.zip + +[arduino_pico_base] +platform = https://github.com/maxgerhardt/platform-raspberrypi.git#5e87ae34ca025274df25b3303e9e9cb6c120123c +framework = arduino +board = pico +extra_scripts = pre:builder_scripts/arduino_pico.py +debug_tool = cmsis-dap +; upload_protocol = cmsis-dap +monitor_speed = 115200 +board_build.core = earlephilhower +board_build.f_cpu = 200000000L +board_build.filesystem_size = 0.5m +build_unflags = -Os +build_flags = + ${env.build_flags} + -O3 + -D USE_TINYUSB + -D CFG_TUSB_CONFIG_FILE=\"tusb_config_pico.h\" + -D NDEBUG + -D FASTLED_RP2040_CLOCKLESS_M0_FALLBACK=0 + -Wall + -Wstack-usage=1024 + -Wno-unused-variable + -I HAL/pico/include +build_src_filter = + ${env.build_src_filter} + + +platform_packages = + framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#3.6.3 +lib_archive = no +lib_deps = + ${env.lib_deps} + https://github.com/JonnyHaystack/joybus-pio/archive/refs/tags/v1.2.3.zip + https://github.com/JonnyHaystack/nes-pio#73417ba + https://github.com/JonnyHaystack/arduino-nunchuk/archive/refs/tags/v1.0.1.zip + https://github.com/JonnyHaystack/Adafruit_TinyUSB_XInput#4b5617b + https://github.com/FastLED/FastLED#6daa782 + https://github.com/JonnyHaystack/nanopb-arduino/archive/refs/tags/v1.1.1.zip + adafruit/Adafruit SSD1306@^2.5.9 + adafruit/Adafruit GFX Library@^1.11.9 + eric-wieser/PacketIO@^0.3.0 + bakercp/CRC32@^2.0.0 + robtillaart/PCF8575@^0.2.2 + TUCompositeHID diff --git a/src/comms/B0XXInputViewer.cpp b/src/comms/B0XXInputViewer.cpp index 89e1d9d9..e9b3dfe4 100644 --- a/src/comms/B0XXInputViewer.cpp +++ b/src/comms/B0XXInputViewer.cpp @@ -5,8 +5,12 @@ #define ASCII_BIT(x) (x ? '1' : '0'); -B0XXInputViewer::B0XXInputViewer(InputSource **input_sources, size_t input_source_count) - : CommunicationBackend(input_sources, input_source_count) { +B0XXInputViewer::B0XXInputViewer( + InputState &inputs, + InputSource **input_sources, + size_t input_source_count +) + : CommunicationBackend(inputs, input_sources, input_source_count) { serial::init(115200); } @@ -27,30 +31,26 @@ void B0XXInputViewer::SendReport() { } _clock = 0; - // Only scan fast input sources because we don't want to waste any more time than necessary - // on the input viewer and we can't afford to read from something like a Nunchuk twice. - ScanInputs(InputScanSpeed::FAST); - - _report[0] = ASCII_BIT(_inputs.start); - _report[1] = ASCII_BIT(_inputs.y); - _report[2] = ASCII_BIT(_inputs.x); - _report[3] = ASCII_BIT(_inputs.b); - _report[4] = ASCII_BIT(_inputs.a); - _report[5] = ASCII_BIT(_inputs.l); - _report[6] = ASCII_BIT(_inputs.r); - _report[7] = ASCII_BIT(_inputs.z); - _report[8] = ASCII_BIT(_inputs.up); - _report[9] = ASCII_BIT(_inputs.down); - _report[10] = ASCII_BIT(_inputs.right); - _report[11] = ASCII_BIT(_inputs.left); - _report[12] = ASCII_BIT(_inputs.mod_x); - _report[13] = ASCII_BIT(_inputs.mod_y); - _report[14] = ASCII_BIT(_inputs.c_left); - _report[15] = ASCII_BIT(_inputs.c_right); - _report[16] = ASCII_BIT(_inputs.c_up); - _report[17] = ASCII_BIT(_inputs.c_down); - _report[18] = ASCII_BIT(_inputs.lightshield); - _report[19] = ASCII_BIT(_inputs.midshield); + _report[0] = ASCII_BIT(_inputs.mb1); + _report[1] = ASCII_BIT(_inputs.rf6); + _report[2] = ASCII_BIT(_inputs.rf2); + _report[3] = ASCII_BIT(_inputs.rf1); + _report[4] = ASCII_BIT(_inputs.rt1); + _report[5] = ASCII_BIT(_inputs.lf4); + _report[6] = ASCII_BIT(_inputs.rf5); + _report[7] = ASCII_BIT(_inputs.rf3); + _report[8] = ASCII_BIT(_inputs.rf4); + _report[9] = ASCII_BIT(_inputs.lf2); + _report[10] = ASCII_BIT(_inputs.lf1); + _report[11] = ASCII_BIT(_inputs.lf3); + _report[12] = ASCII_BIT(_inputs.lt1); + _report[13] = ASCII_BIT(_inputs.lt2); + _report[14] = ASCII_BIT(_inputs.rt3); + _report[15] = ASCII_BIT(_inputs.rt5); + _report[16] = ASCII_BIT(_inputs.rt4); + _report[17] = ASCII_BIT(_inputs.rt2); + _report[18] = ASCII_BIT(_inputs.rf7); + _report[19] = ASCII_BIT(_inputs.rf8); _report[20] = ASCII_BIT(false); _report[21] = ASCII_BIT(false); _report[22] = ASCII_BIT(false); diff --git a/src/comms/IntegratedDisplay.cpp b/src/comms/IntegratedDisplay.cpp new file mode 100644 index 00000000..7e989324 --- /dev/null +++ b/src/comms/IntegratedDisplay.cpp @@ -0,0 +1,74 @@ +#include "comms/IntegratedDisplay.hpp" + +#include "core/Persistence.hpp" +#include "util/config_util.hpp" +#include "util/state_util.hpp" + +IntegratedDisplay::IntegratedDisplay( + InputState &inputs, + Adafruit_GFX &display, + void (*clear_display)(), + void (*update_display)(), + const DisplayControls controls, + DisplayMode **display_modes, + size_t display_modes_count +) + : CommunicationBackend(inputs, nullptr, 0), + _display(display), + _clear_display(clear_display), + _update_display(update_display), + _controls(controls), + _controls_array{ controls.back, controls.down, controls.up, controls.enter }, + _display_modes(display_modes), + _display_modes_count(display_modes_count) { + _display.setTextSize(1); + _display.setTextColor(default_color); +} + +IntegratedDisplay::~IntegratedDisplay() { + _clear_display(); + _update_display(); +} + +void IntegratedDisplay::SendReport() { + DisplayMode *active_mode = GetActiveDisplayMode(); + if (active_mode == nullptr) { + return; + } + + _clear_display(); + active_mode->UpdateDisplay(this, _display); + _update_display(); + + // This is done *after* display update so any side effects of HandleControls are not shown + // without active_mode also being up-to-date. + HandleControls(active_mode); +} + +void IntegratedDisplay::HandleControls(DisplayMode *active_mode) { + if (!time_reached(_button_cooldown_end)) { + return; + } + + for (uint8_t i = 0; i < controls_count; i++) { + Button button = _controls_array[i]; + if (get_button(_inputs.buttons, button)) { + _button_cooldown_end = make_timeout_time_ms(button_cooldown_ms); + active_mode->HandleControls(this, _controls, button); + return; + } + } +} + +void IntegratedDisplay::SetDisplayMode(DisplayModeId display_mode) { + _display_mode = display_mode; +} + +DisplayMode *IntegratedDisplay::GetActiveDisplayMode() { + for (size_t i = 0; i < _display_modes_count; i++) { + if (_display_modes[i]->GetId() == _display_mode) { + return _display_modes[i]; + } + } + return nullptr; +} \ No newline at end of file diff --git a/src/core/CommunicationBackend.cpp b/src/core/CommunicationBackend.cpp index dfc3e677..f38d5b5d 100644 --- a/src/core/CommunicationBackend.cpp +++ b/src/core/CommunicationBackend.cpp @@ -4,7 +4,14 @@ #include "core/InputSource.hpp" #include "core/state.hpp" -CommunicationBackend::CommunicationBackend(InputSource **input_sources, size_t input_source_count) { +#include + +CommunicationBackend::CommunicationBackend( + InputState &inputs, + InputSource **input_sources, + size_t input_source_count +) + : _inputs(inputs) { _gamemode = nullptr; _input_sources = input_sources; _input_source_count = input_source_count; @@ -14,6 +21,10 @@ InputState &CommunicationBackend::GetInputs() { return _inputs; } +OutputState &CommunicationBackend::GetOutputs() { + return _outputs; +} + void CommunicationBackend::ScanInputs() { for (size_t i = 0; i < _input_source_count; i++) { _input_sources[i]->UpdateInputs(_inputs); @@ -40,7 +51,14 @@ void CommunicationBackend::UpdateOutputs() { } } -void CommunicationBackend::SetGameMode(ControllerMode *gamemode) { - delete _gamemode; +CommunicationBackendId CommunicationBackend::BackendId() { + return COMMS_BACKEND_UNSPECIFIED; +} + +void CommunicationBackend::SetGameMode(InputMode *gamemode) { _gamemode = gamemode; } + +InputMode *CommunicationBackend::CurrentGameMode() { + return _gamemode; +} diff --git a/src/core/ControllerMode.cpp b/src/core/ControllerMode.cpp index af203165..6cb60552 100644 --- a/src/core/ControllerMode.cpp +++ b/src/core/ControllerMode.cpp @@ -1,14 +1,18 @@ #include "core/ControllerMode.hpp" -ControllerMode::ControllerMode() { +ControllerMode::ControllerMode() : InputMode() { // Set up initial state. ResetDirections(); } -void ControllerMode::UpdateOutputs(InputState &inputs, OutputState &outputs) { - HandleSocd(inputs); - UpdateDigitalOutputs(inputs, outputs); - UpdateAnalogOutputs(inputs, outputs); +void ControllerMode::UpdateOutputs(const InputState &inputs, OutputState &outputs) { + // Create a copy of the input state here so remapping can be many-to-one (many physical buttons + // to one activated button). + InputState remapped_inputs = inputs; + HandleRemap(inputs, remapped_inputs); + HandleSocd(remapped_inputs); + UpdateDigitalOutputs(remapped_inputs, outputs); + UpdateAnalogOutputs(remapped_inputs, outputs); } void ControllerMode::ResetDirections() { diff --git a/src/core/InputMode.cpp b/src/core/InputMode.cpp index 56afd594..f1388a19 100644 --- a/src/core/InputMode.cpp +++ b/src/core/InputMode.cpp @@ -2,53 +2,85 @@ #include "core/socd.hpp" #include "core/state.hpp" +#include "util/state_util.hpp" InputMode::InputMode() {} -InputMode::~InputMode() { - delete[] _socd_pairs; - delete[] _socd_states; +GameModeConfig *InputMode::GetConfig() { + return _config; +} + +void InputMode::SetConfig(GameModeConfig &config) { + _config = &config; } void InputMode::HandleSocd(InputState &inputs) { - if (_socd_pairs == nullptr) { + if (_config == nullptr) { return; } - - // Initialize SOCD states if they aren't initialized. - if (_socd_states == nullptr) { - _socd_states = new socd::SocdState[_socd_pair_count]; - } - // Handle SOCD resolution for each SOCD button pair. - for (size_t i = 0; i < _socd_pair_count; i++) { - socd::SocdPair pair = _socd_pairs[i]; + for (size_t i = 0; i < _config->socd_pairs_count; i++) { + const SocdPair &pair = _config->socd_pairs[i]; switch (pair.socd_type) { - case socd::SOCD_NEUTRAL: - socd::neutral(inputs.*(pair.input_dir1), inputs.*(pair.input_dir2)); + case SOCD_NEUTRAL: + socd::neutral(inputs, pair.button_dir1, pair.button_dir2); break; - case socd::SOCD_2IP: + case SOCD_2IP: socd::second_input_priority( - inputs.*(pair.input_dir1), - inputs.*(pair.input_dir2), + inputs, + pair.button_dir1, + pair.button_dir2, _socd_states[i] ); break; - case socd::SOCD_2IP_NO_REAC: + case SOCD_2IP_NO_REAC: socd::second_input_priority_no_reactivation( - inputs.*(pair.input_dir1), - inputs.*(pair.input_dir2), + inputs, + pair.button_dir1, + pair.button_dir2, _socd_states[i] ); break; - case socd::SOCD_DIR1_PRIORITY: - socd::dir1_priority(inputs.*(pair.input_dir1), inputs.*(pair.input_dir2)); + case SOCD_DIR1_PRIORITY: + socd::dir1_priority(inputs, pair.button_dir1, pair.button_dir2); break; - case socd::SOCD_DIR2_PRIORITY: - socd::dir1_priority(inputs.*(pair.input_dir2), inputs.*(pair.input_dir1)); + case SOCD_DIR2_PRIORITY: + socd::dir1_priority(inputs, pair.button_dir2, pair.button_dir1); break; - case socd::SOCD_NONE: + case SOCD_UNSPECIFIED: + default: break; } } } + +void InputMode::HandleRemap(const InputState &original_inputs, InputState &remapped_inputs) { + if (_config == nullptr) { + return; + } + remapped_inputs.buttons = 0; + + // Keep track of which buttons have been remapped so that we can prevent macro remapping. + uint64_t physical_buttons_already_remapped = 0; + for (size_t i = 0; i < _config->button_remapping_count; i++) { + const ButtonRemap &remapping = _config->button_remapping[i]; + // If this physical button was already remapped to something else, ignore this remapping. + // This is to prevent creating macro behaviour through remapping. + if (get_button(physical_buttons_already_remapped, remapping.physical_button)) { + continue; + } + + // Either use the value of the physical button, or if the physical button is not pressed, + // but the target button has another physical button remapped to it, and is considered to be + // pressed, leave it as pressed. + bool should_be_pressed = get_button(original_inputs.buttons, remapping.physical_button) || + get_button(remapped_inputs.buttons, remapping.activates); + set_button(remapped_inputs.buttons, remapping.activates, should_be_pressed); + + // Track which buttons have been remapped. + set_button(physical_buttons_already_remapped, remapping.physical_button, true); + } + + // Copy over original button states for buttons that were not remapped. + remapped_inputs.buttons |= original_inputs.buttons & ~physical_buttons_already_remapped; +} diff --git a/src/core/mode_selection.cpp b/src/core/mode_selection.cpp new file mode 100644 index 00000000..81a7e1d0 --- /dev/null +++ b/src/core/mode_selection.cpp @@ -0,0 +1,146 @@ +#include "core/mode_selection.hpp" + +#include "core/state.hpp" +#include "modes/CustomControllerMode.hpp" +#include "modes/CustomKeyboardMode.hpp" +#include "modes/FgcMode.hpp" +#include "modes/Melee20Button.hpp" +#include "modes/ProjectM.hpp" +#include "modes/Rivals2.hpp" +#include "modes/RivalsOfAether.hpp" +#include "modes/Ultimate.hpp" +#include "util/state_util.hpp" + +#include + +Melee20Button melee_mode; +ProjectM projectm_mode; +Ultimate ultimate_mode; +FgcMode fgc_mode; +RivalsOfAether rivals_mode; +Rivals2 rivals2_mode; +CustomKeyboardMode keyboard_mode; +CustomControllerMode custom_mode; + +uint64_t mode_activation_masks[10]; + +size_t current_mode_index = SIZE_MAX; + +void set_mode(CommunicationBackend *backend, ControllerMode *mode) { + // Delete keyboard mode in case one is set, so we don't end up getting both controller and + // keyboard inputs. + current_kb_mode = nullptr; + + // Set new controller mode. + backend->SetGameMode(mode); +} + +void set_mode(CommunicationBackend *backend, KeyboardMode *mode) { + // Only DInputBackend supports keyboard modes. + if (backend->BackendId() != COMMS_BACKEND_DINPUT) { + return; + } + + // Delete and reassign current keyboard mode. + current_kb_mode = mode; + + // Unset the current controller mode so backend only gives neutral inputs. + backend->SetGameMode(mode); +} + +void set_mode(CommunicationBackend *backend, GameModeConfig &mode_config, Config &config) { + switch (mode_config.mode_id) { + case MODE_MELEE: + melee_mode.SetConfig(mode_config, config.melee_options); + set_mode(backend, &melee_mode); + break; + case MODE_PROJECT_M: + projectm_mode.SetConfig(mode_config, config.project_m_options); + set_mode(backend, &projectm_mode); + break; + case MODE_ULTIMATE: + ultimate_mode.SetConfig(mode_config); + set_mode(backend, &ultimate_mode); + break; + case MODE_FGC: + fgc_mode.SetConfig(mode_config); + set_mode(backend, &fgc_mode); + break; + case MODE_RIVALS_OF_AETHER: + rivals_mode.SetConfig(mode_config); + set_mode(backend, &rivals_mode); + break; + case MODE_RIVALS_2: + rivals2_mode.SetConfig(mode_config); + set_mode(backend, &rivals2_mode); + break; + case MODE_KEYBOARD: + if (backend->BackendId() != COMMS_BACKEND_DINPUT || + mode_config.keyboard_mode_config < 1 || + mode_config.keyboard_mode_config > config.keyboard_modes_count) { + break; + } + keyboard_mode.SetConfig( + mode_config, + config.keyboard_modes[mode_config.keyboard_mode_config - 1] + ); + set_mode(backend, &keyboard_mode); + break; + case MODE_CUSTOM: + if (mode_config.custom_mode_config < 1 || + mode_config.custom_mode_config > config.custom_modes_count) { + break; + } + custom_mode.SetConfig( + mode_config, + config.custom_modes[mode_config.custom_mode_config - 1] + ); + set_mode(backend, &custom_mode); + break; + case MODE_UNSPECIFIED: + default: + break; + } +} + +// TODO: Maybe remove this overload in favour of looking up the gamemode outside of here using a +// config_utils function. +void set_mode(CommunicationBackend *backend, GameModeId mode_id, Config &config) { + // In this overload we only know the mode id so we need to find a mode config that matches this + // ID. + for (size_t i = 0; i < config.game_mode_configs_count; i++) { + GameModeConfig &mode = config.game_mode_configs[i]; + if (mode.mode_id == mode_id) { + set_mode(backend, mode, config); + return; + } + } +} + +void select_mode(CommunicationBackend **backends, size_t backends_count, Config &config) { + // TODO: Use a counter variable to only run the contents of this function every x iterations + // rather than on every single poll. + + InputState &inputs = backends[0]->GetInputs(); + + for (size_t i = 0; i < config.game_mode_configs_count; i++) { + GameModeConfig &mode_config = config.game_mode_configs[i]; + if (all_buttons_held(inputs.buttons, mode_activation_masks[i]) && i != current_mode_index) { + current_mode_index = i; + for (size_t i = 0; i < backends_count; i++) { + set_mode(backends[i], mode_config, config); + } + return; + } + } +} + +void setup_mode_activation_bindings(const GameModeConfig *mode_configs, size_t mode_configs_count) { + // Build bit masks for checking for matching button holds. + for (size_t i = 0; i < mode_configs_count; i++) { + mode_activation_masks[i] = make_button_mask( + mode_configs[i].activation_binding, + mode_configs[i].activation_binding_count + ); + } +} diff --git a/src/core/socd.cpp b/src/core/socd.cpp index 504f3500..81a0d53f 100644 --- a/src/core/socd.cpp +++ b/src/core/socd.cpp @@ -1,84 +1,96 @@ #include "core/socd.hpp" +#include "util/state_util.hpp" + void socd::second_input_priority_no_reactivation( - bool &input_dir1, - bool &input_dir2, + InputState &inputs, + Button button_dir1, + Button button_dir2, SocdState &socd_state ) { - bool is_dir1 = false; - bool is_dir2 = false; - if (input_dir1 && input_dir2) { + bool result_dir1 = false; + bool result_dir2 = false; + bool dir1_pressed = get_button(inputs.buttons, button_dir1); + bool dir2_pressed = get_button(inputs.buttons, button_dir2); + if (dir1_pressed && dir2_pressed) { if (socd_state.was_dir2) { - is_dir1 = true; - is_dir2 = false; + result_dir1 = true; + result_dir2 = false; socd_state.lock_dir2 = true; } if (socd_state.was_dir1) { - is_dir1 = false; - is_dir2 = true; + result_dir1 = false; + result_dir2 = true; socd_state.lock_dir1 = true; } } - if (!input_dir1 && input_dir2 && !socd_state.lock_dir2) { - is_dir1 = false; - is_dir2 = true; + if (!dir1_pressed && dir2_pressed && !socd_state.lock_dir2) { + result_dir1 = false; + result_dir2 = true; socd_state.was_dir2 = true; socd_state.was_dir1 = false; socd_state.lock_dir1 = false; } - if (input_dir1 && !input_dir2 && !socd_state.lock_dir1) { - is_dir1 = true; - is_dir2 = false; + if (dir1_pressed && !dir2_pressed && !socd_state.lock_dir1) { + result_dir1 = true; + result_dir2 = false; socd_state.was_dir1 = true; socd_state.was_dir2 = false; socd_state.lock_dir2 = false; } - if (!input_dir1 && !input_dir2) { + if (!dir1_pressed && !dir2_pressed) { socd_state.was_dir2 = false; socd_state.was_dir1 = false; socd_state.lock_dir1 = false; socd_state.lock_dir2 = false; } - input_dir1 = is_dir1; - input_dir2 = is_dir2; + set_button(inputs.buttons, button_dir1, result_dir1); + set_button(inputs.buttons, button_dir2, result_dir2); } -void socd::second_input_priority(bool &input_dir1, bool &input_dir2, SocdState &socd_state) { - bool is_dir1 = false; - bool is_dir2 = false; - if (input_dir1 && socd_state.was_dir2) { - is_dir1 = true; - is_dir2 = false; +void socd::second_input_priority( + InputState &inputs, + Button button_dir1, + Button button_dir2, + SocdState &socd_state +) { + bool result_dir1 = false; + bool result_dir2 = false; + bool dir1_pressed = get_button(inputs.buttons, button_dir1); + bool dir2_pressed = get_button(inputs.buttons, button_dir2); + if (dir1_pressed && socd_state.was_dir2) { + result_dir1 = true; + result_dir2 = false; } - if (input_dir2 && socd_state.was_dir1) { - is_dir1 = false; - is_dir2 = true; + if (dir2_pressed && socd_state.was_dir1) { + result_dir1 = false; + result_dir2 = true; } - if (!input_dir1 && input_dir2) { - is_dir1 = false; - is_dir2 = true; + if (!dir1_pressed && dir2_pressed) { + result_dir1 = false; + result_dir2 = true; socd_state.was_dir2 = true; socd_state.was_dir1 = false; } - if (input_dir1 && !input_dir2) { - is_dir1 = true; - is_dir2 = false; + if (dir1_pressed && !dir2_pressed) { + result_dir1 = true; + result_dir2 = false; socd_state.was_dir1 = true; socd_state.was_dir2 = false; } - input_dir1 = is_dir1; - input_dir2 = is_dir2; + set_button(inputs.buttons, button_dir1, result_dir1); + set_button(inputs.buttons, button_dir2, result_dir2); } -void socd::neutral(bool &input_dir1, bool &input_dir2) { - if (input_dir1 && input_dir2) { - input_dir1 = false; - input_dir2 = false; +void socd::neutral(InputState &inputs, Button button_dir1, Button button_dir2) { + if (get_button(inputs.buttons, button_dir1) && get_button(inputs.buttons, button_dir2)) { + set_button(inputs.buttons, button_dir1, false); + set_button(inputs.buttons, button_dir2, false); } } -void socd::dir1_priority(bool &input_dir1, bool &input_dir2) { - if (input_dir1 && input_dir2) { - input_dir2 = false; +void socd::dir1_priority(InputState &inputs, Button button_dir1, Button button_dir2) { + if (get_button(inputs.buttons, button_dir1) && get_button(inputs.buttons, button_dir2)) { + set_button(inputs.buttons, button_dir2, false); } } diff --git a/src/input/GpioButtonInput.cpp b/src/input/GpioButtonInput.cpp index de65476f..69fc2ee1 100644 --- a/src/input/GpioButtonInput.cpp +++ b/src/input/GpioButtonInput.cpp @@ -1,8 +1,9 @@ #include "input/GpioButtonInput.hpp" #include "gpio.hpp" +#include "util/state_util.hpp" -GpioButtonInput::GpioButtonInput(GpioButtonMapping *button_mappings, size_t button_count) { +GpioButtonInput::GpioButtonInput(const GpioButtonMapping *button_mappings, size_t button_count) { _button_mappings = button_mappings; _button_count = button_count; @@ -19,7 +20,14 @@ InputScanSpeed GpioButtonInput::ScanSpeed() { void GpioButtonInput::UpdateInputs(InputState &inputs) { for (size_t i = 0; i < _button_count; i++) { - GpioButtonMapping button_mapping = _button_mappings[i]; - inputs.*(button_mapping.button) = !gpio::read_digital(button_mapping.pin); + UpdateButtonState(inputs, i, !gpio::read_digital(_button_mappings[i].pin)); } } + +void GpioButtonInput::UpdateButtonState( + InputState &inputs, + size_t button_mapping_index, + bool pressed +) { + set_button(inputs.buttons, _button_mappings[button_mapping_index].button, pressed); +} diff --git a/src/modes/CustomControllerMode.cpp b/src/modes/CustomControllerMode.cpp new file mode 100644 index 00000000..f3599398 --- /dev/null +++ b/src/modes/CustomControllerMode.cpp @@ -0,0 +1,155 @@ +#include "modes/CustomControllerMode.hpp" + +#include "util/state_util.hpp" + +#define SIGNUM(x) ((x > 0) - (x < 0)) +#define ANALOG_STICK_NEUTRAL 128 + +CustomControllerMode::CustomControllerMode() : ControllerMode() {} + +void CustomControllerMode::SetConfig( + GameModeConfig &config, + const CustomModeConfig &custom_mode_config +) { + InputMode::SetConfig(config); + _custom_mode_config = &custom_mode_config; + for (size_t i = 0; i < custom_mode_config.modifiers_count; i++) { + _modifier_button_masks[i] = make_button_mask( + custom_mode_config.modifiers[i].buttons, + custom_mode_config.modifiers[i].buttons_count + ); + } + for (size_t i = 0; i < custom_mode_config.button_combo_mappings_count; i++) { + _button_combo_mappings_masks[i] = make_button_mask( + custom_mode_config.button_combo_mappings[i].buttons, + custom_mode_config.button_combo_mappings[i].buttons_count + ); + } +} + +void CustomControllerMode::UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs) { + if (_custom_mode_config == nullptr) { + return; + } + + // First check for button combo -> single output mappings, and lock out the normal function of + // the buttons in any buttons combos that are activated. + _buttons_to_ignore = 0; + const ButtonComboMapping *button_combo_mappings = _custom_mode_config->button_combo_mappings; + for (size_t i = 0; i < _custom_mode_config->button_combo_mappings_count; i++) { + const ButtonComboMapping &button_combo_mapping = button_combo_mappings[i]; + if (!all_buttons_held(inputs.buttons, _button_combo_mappings_masks[i])) { + continue; + } + + set_output(outputs.buttons, button_combo_mapping.digital_output, true); + _buttons_to_ignore |= _button_combo_mappings_masks[i]; + } + _filtered_buttons = inputs.buttons & ~_buttons_to_ignore; + + for (size_t output = 0; output < _custom_mode_config->digital_button_mappings_count; output++) { + Button input = _custom_mode_config->digital_button_mappings[output]; + set_output( + outputs.buttons, + (DigitalOutput)(output + 1), + get_button(_filtered_buttons, input) + ); + } + + if (inputs.nunchuk_connected) { + outputs.triggerLDigital |= inputs.nunchuk_z; + } +} + +void CustomControllerMode::UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs) { + if (_custom_mode_config == nullptr) { + return; + } + + const Button *direction_buttons = _custom_mode_config->stick_direction_mappings; + uint8_t stick_range = _custom_mode_config->stick_range; + UpdateDirections( + get_button(_filtered_buttons, GetDirectionButton(direction_buttons, SD_LSTICK_LEFT)), + get_button(_filtered_buttons, GetDirectionButton(direction_buttons, SD_LSTICK_RIGHT)), + get_button(_filtered_buttons, GetDirectionButton(direction_buttons, SD_LSTICK_DOWN)), + get_button(_filtered_buttons, GetDirectionButton(direction_buttons, SD_LSTICK_UP)), + get_button(_filtered_buttons, GetDirectionButton(direction_buttons, SD_RSTICK_LEFT)), + get_button(_filtered_buttons, GetDirectionButton(direction_buttons, SD_RSTICK_RIGHT)), + get_button(_filtered_buttons, GetDirectionButton(direction_buttons, SD_RSTICK_DOWN)), + get_button(_filtered_buttons, GetDirectionButton(direction_buttons, SD_RSTICK_UP)), + ANALOG_STICK_NEUTRAL - stick_range, + ANALOG_STICK_NEUTRAL, + ANALOG_STICK_NEUTRAL + stick_range, + outputs + ); + + const AnalogModifier *modifiers = _custom_mode_config->modifiers; + for (size_t i = 0; i < _custom_mode_config->modifiers_count; i++) { + const AnalogModifier &modifier = modifiers[i]; + if (modifier.axis == AXIS_UNSPECIFIED || modifier.axis > _AnalogAxis_MAX) { + continue; + } + if (!all_buttons_held(_filtered_buttons, _modifier_button_masks[i])) { + continue; + } + + uint8_t OutputState::*axis = axis_pointer(modifier.axis); + if (axis != nullptr) { + int8_t sign = 0; + switch (modifier.combination_mode) { + case COMBINATION_MODE_OVERRIDE: + sign = SIGNUM(outputs.*axis); + outputs.*axis = ANALOG_STICK_NEUTRAL + + _custom_mode_config->stick_range * modifier.multiplier * sign; + break; + case COMBINATION_MODE_COMPOUND: + case COMBINATION_MODE_UNSPECIFIED: + default: + outputs.*axis = ANALOG_STICK_NEUTRAL + + (outputs.*axis - ANALOG_STICK_NEUTRAL) * modifier.multiplier; + break; + } + } + } + + const AnalogTriggerMapping *analog_trigger_mappings = + _custom_mode_config->analog_trigger_mappings; + for (size_t i = 0; i < _custom_mode_config->analog_trigger_mappings_count; i++) { + const AnalogTriggerMapping &mapping = analog_trigger_mappings[i]; + if (get_button(_filtered_buttons, mapping.button)) { + switch (mapping.trigger) { + case TRIGGER_LT: + outputs.triggerLAnalog = mapping.value; + break; + case TRIGGER_RT: + outputs.triggerRAnalog = mapping.value; + break; + default: + break; + } + } + } + + if (outputs.triggerLDigital) { + outputs.triggerLAnalog = 255; + } + if (outputs.triggerRDigital) { + outputs.triggerRAnalog = 255; + } + + // Nunchuk overrides left stick. + if (inputs.nunchuk_connected) { + outputs.leftStickX = inputs.nunchuk_x; + outputs.leftStickY = inputs.nunchuk_y; + } +} + +Button CustomControllerMode::GetDirectionButton( + const Button *direction_buttons, + StickDirectionButton direction +) { + if (direction == SD_UNSPECIFIED || direction > _StickDirectionButton_MAX) { + return BTN_UNSPECIFIED; + } + return direction_buttons[direction - 1]; +} \ No newline at end of file diff --git a/src/modes/CustomKeyboardMode.cpp b/src/modes/CustomKeyboardMode.cpp new file mode 100644 index 00000000..60c7c69a --- /dev/null +++ b/src/modes/CustomKeyboardMode.cpp @@ -0,0 +1,31 @@ +#include "modes/CustomKeyboardMode.hpp" + +#include "core/state.hpp" +#include "util/state_util.hpp" + +#include + +CustomKeyboardMode::CustomKeyboardMode() : KeyboardMode() {} + +void CustomKeyboardMode::SetConfig( + GameModeConfig &config, + const KeyboardModeConfig &keyboard_config +) { + InputMode::SetConfig(config); + _keyboard_config = &keyboard_config; +} + +void CustomKeyboardMode::UpdateKeys(const InputState &inputs) { + if (_keyboard_config == nullptr) { + return; + } + + const ButtonToKeycodeMapping *keymap = _keyboard_config->buttons_to_keycodes; + size_t key_count = _keyboard_config->buttons_to_keycodes_count; + for (size_t i = 0; i < key_count; i++) { + if (keymap[i].button == BTN_UNSPECIFIED) { + continue; + } + Press(keymap[i].keycode, get_button(inputs.buttons, keymap[i].button)); + } +} \ No newline at end of file diff --git a/src/modes/DefaultKeyboardMode.cpp b/src/modes/DefaultKeyboardMode.cpp index 33960778..e5d16cb8 100644 --- a/src/modes/DefaultKeyboardMode.cpp +++ b/src/modes/DefaultKeyboardMode.cpp @@ -3,29 +3,17 @@ #include "core/socd.hpp" #include "core/state.hpp" -DefaultKeyboardMode::DefaultKeyboardMode(socd::SocdType socd_type) {} +DefaultKeyboardMode::DefaultKeyboardMode() : KeyboardMode() {} -void DefaultKeyboardMode::UpdateKeys(InputState &inputs) { - Press(HID_KEY_A, inputs.l); - Press(HID_KEY_B, inputs.left); - Press(HID_KEY_C, inputs.down); - Press(HID_KEY_D, inputs.right); - Press(HID_KEY_E, inputs.mod_x); - Press(HID_KEY_F, inputs.mod_y); - Press(HID_KEY_G, inputs.select); - Press(HID_KEY_H, inputs.start); - Press(HID_KEY_I, inputs.home); - Press(HID_KEY_J, inputs.r); - Press(HID_KEY_K, inputs.y); - Press(HID_KEY_L, inputs.lightshield); - Press(HID_KEY_M, inputs.midshield); - Press(HID_KEY_N, inputs.b); - Press(HID_KEY_O, inputs.x); - Press(HID_KEY_P, inputs.z); - Press(HID_KEY_Q, inputs.up); - Press(HID_KEY_R, inputs.c_up); - Press(HID_KEY_S, inputs.c_left); - Press(HID_KEY_T, inputs.c_right); - Press(HID_KEY_U, inputs.a); - Press(HID_KEY_V, inputs.c_down); +void DefaultKeyboardMode::UpdateKeys(const InputState &inputs) { + Press(HID_KEY_SHIFT_LEFT, inputs.rt2); + Press(HID_KEY_ARROW_LEFT, inputs.lf3); + Press(HID_KEY_ARROW_DOWN, inputs.lf2); + Press(HID_KEY_ARROW_RIGHT, inputs.lf1); + Press(HID_KEY_ARROW_UP, inputs.lf5); + Press(HID_KEY_ENTER, inputs.mb1); + Press(HID_KEY_SPACE, inputs.lt1); + Press(HID_KEY_X, inputs.rf1); + Press(HID_KEY_Z, inputs.rt1); + Press(HID_KEY_C, inputs.rf3); } diff --git a/src/modes/FgcMode.cpp b/src/modes/FgcMode.cpp index 4928a89e..9c7bb253 100644 --- a/src/modes/FgcMode.cpp +++ b/src/modes/FgcMode.cpp @@ -1,49 +1,280 @@ +// Replaced with Buffed Melee mode #include "modes/FgcMode.hpp" -FgcMode::FgcMode(socd::SocdType horizontal_socd, socd::SocdType vertical_socd) { - _socd_pair_count = 4; - _socd_pairs = new socd::SocdPair[_socd_pair_count]{ - socd::SocdPair{&InputState::left, &InputState::right, horizontal_socd }, - /* Mod X override C-Up input if both are pressed. Without this, neutral SOCD doesn't work - properly if Down and both Up buttons are pressed, because it first resolves Down + Mod X - to set both as unpressed, and then it sees C-Up as pressed but not Down, so you get an up - input instead of neutral. */ - socd::SocdPair{ &InputState::mod_x, &InputState::c_up, socd::SOCD_DIR1_PRIORITY}, - socd::SocdPair{ &InputState::down, &InputState::mod_x, vertical_socd }, - socd::SocdPair{ &InputState::down, &InputState::c_up, vertical_socd }, - }; +#define ANALOG_STICK_MIN 48 +#define ANALOG_STICK_NEUTRAL 128 +#define ANALOG_STICK_MAX 208 + +FgcMode::FgcMode() : ControllerMode() { + // horizontal_socd = false; } -void FgcMode::UpdateDigitalOutputs(InputState &inputs, OutputState &outputs) { - // Directions - outputs.dpadLeft = inputs.left; - outputs.dpadRight = inputs.right; - outputs.dpadDown = inputs.down; - outputs.dpadUp = inputs.mod_x || inputs.c_up; - - // Menu keys - outputs.start = inputs.start; - outputs.select = inputs.c_left; - outputs.home = inputs.c_down; - - // Right hand bottom row - outputs.a = inputs.b; - outputs.b = inputs.x; - outputs.triggerRDigital = inputs.z; - outputs.triggerLDigital = inputs.up; - - // Right hand top row - outputs.x = inputs.r; - outputs.y = inputs.y; - outputs.buttonR = inputs.lightshield; - outputs.buttonL = inputs.midshield; +/* void FgcMode::SetConfig(GameModeConfig &config, const MeleeOptions options) { + InputMode::SetConfig(config); + _options = options; } -void FgcMode::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { - outputs.leftStickX = 128; - outputs.leftStickY = 128; - outputs.rightStickX = 128; - outputs.rightStickY = 128; - outputs.triggerLAnalog = outputs.triggerLDigital ? 255 : 0; - outputs.triggerRAnalog = outputs.triggerRDigital ? 255 : 0; +void FgcMode::HandleSocd(InputState &inputs) { + _horizontal_socd = inputs.lf3 && inputs.lf1; + InputMode::HandleSocd(inputs); +} */ + +void FgcMode::UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs) { + outputs.a = inputs.rt1; + outputs.b = inputs.rf1; + outputs.x = inputs.rf2; + outputs.y = inputs.rf6; + outputs.buttonR = inputs.rf3; + if (inputs.nunchuk_connected) { + outputs.triggerLDigital = inputs.nunchuk_z; + } else { + outputs.triggerLDigital = inputs.lf4; + } + outputs.triggerRDigital = inputs.rf5; + outputs.start = inputs.mb1; + + // Activate D-Pad layer by holding Mod X + Mod Y or Nunchuk C button. + if ((inputs.lt1 && inputs.lt2) || inputs.nunchuk_c) { + outputs.dpadUp = inputs.rt4; + outputs.dpadDown = inputs.rt2; + outputs.dpadLeft = inputs.rt3; + outputs.dpadRight = inputs.rt5; + } + + if (inputs.mb3) + outputs.dpadLeft = true; + if (inputs.mb2) + outputs.dpadRight = true; } + +void FgcMode::UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs) { + // Coordinate calculations to make modifier handling simpler. + UpdateDirections( + inputs.lf3, // Left + inputs.lf1, // Right + inputs.lf2, // Down + inputs.rf4, // Up + inputs.rt3, // C-Left + inputs.rt5, // C-Right + inputs.rt2, // C-Down + inputs.rt4, // C-Up + ANALOG_STICK_MIN, + ANALOG_STICK_NEUTRAL, + ANALOG_STICK_MAX, + outputs + ); + + bool shield_button_pressed = inputs.lf4 || inputs.rf5 || inputs.rf7 || inputs.rf8; + + if (directions.diagonal) { + // q1/2 = 7000 7000 + outputs.leftStickX = 128 + (directions.x * 56); + outputs.leftStickY = 128 + (directions.y * 56); + // L, R, LS, and MS + q3/4 = 7000 6875 (For vanilla shield drop. Gives 44.5 + // degree wavedash). Also used as default q3/4 diagonal if crouch walk option select is + // enabled. + if (directions.y == -1) { + /* // DBooC + if (!inputs.rf1) { + outputs.leftStickX = 128 + (directions.x * 80); + outputs.leftStickY = 128 + (directions.y * 60); + } */ + + if (shield_button_pressed) { + outputs.leftStickX = 128 + (directions.x * 56); + outputs.leftStickY = 128 + (directions.y * 55); + } + } + } + + if (inputs.lt1) { + // MX + Horizontal (even if shield is held) = 6625 = 53 + if (directions.horizontal) { + outputs.leftStickX = 128 + (directions.x * 53); + } + // MX + Vertical (even if shield is held) = 5375 = 43 + if (directions.vertical) { + outputs.leftStickY = 128 + (directions.y * 43); + + // shine on platform without falling through + if (inputs.rf1) { + outputs.leftStickY = 128 + (directions.y * 45); + } + } + if (directions.diagonal && shield_button_pressed) { + // 9500 2875 = 76 23 + outputs.leftStickX = 128 + (directions.x * 76); + outputs.leftStickY = 128 + (directions.y * 23); + } + + if (directions.diagonal && !shield_button_pressed) { + // 22.9638 - 7375 3125 = 59 25 + outputs.leftStickX = 128 + (directions.x * 59); + outputs.leftStickY = 128 + (directions.y * 25); + + /* Up B Angles */ + if (inputs.rf1) { + outputs.leftStickX = 128 + (directions.x * 76); + outputs.leftStickY = 128 + (directions.y * 23); + } + if (inputs.rt2) { + outputs.leftStickX = 128 + (directions.x * 75); + outputs.leftStickY = 128 + (directions.y * 31); + } + if (inputs.rt3) { + outputs.leftStickX = 128 + (directions.x * 73); + outputs.leftStickY = 128 + (directions.y * 39); + } + if (inputs.rt4) { + outputs.leftStickX = 128 + (directions.x * 69); + outputs.leftStickY = 128 + (directions.y * 46); + } + if (inputs.rt5) { + outputs.leftStickX = 128 + (directions.x * 50); + outputs.leftStickY = 128 + (directions.y * 41); + } + + } + + // Angled fsmash + if (directions.cx != 0) { + // 8500 5250 = 68 42 + outputs.rightStickX = 128 + (directions.cx * 68); + outputs.rightStickY = 128 + (directions.y * 42); + } + } + + if (inputs.lt2) { + // MY + Horizontal (even if shield is held) = 3375 = 27 + if (directions.horizontal) { + outputs.leftStickX = 128 + (directions.x * 27); + } + // MY + Vertical = 7375 = 59 + if (directions.vertical) { + if (directions.y == 1) { + outputs.leftStickY = 128 + (directions.y * 52); + } + if (directions.y == -1) { + outputs.leftStickY = 128 + (directions.y * 59); + } + + if (shield_button_pressed) { + outputs.leftStickY = 128 + (directions.y * 55); + } + } + if (directions.diagonal && shield_button_pressed) { + // MY + L, R, LS, and MS + q1/2 = 4750 8750 = 38 70 + outputs.leftStickX = 128 + (directions.x * 30); + outputs.leftStickY = 128 + (directions.y * 55); + // MY + L, R, LS, and MS + q3/4 = 5000 8500 = 40 68 + + // Parasol dash + if (directions.y == 1 && (inputs.rf2 || inputs.rf6)) { + outputs.leftStickX = 128 + (directions.x * 40); + outputs.leftStickY = 128 + (directions.y * 68); + } + + /* // steepest wavedash + if (directions.y == -1) { + // 2875 9500 = 23 76 + outputs.leftStickX = 128 + (directions.x * 23); + outputs.leftStickY = 128 + (directions.y * 76); + } */ + } + + + if (directions.diagonal && !shield_button_pressed) { + // buffered turnaround vertical tilts + if (directions.y == -1) { + outputs.leftStickX = 128 + (directions.x * 23); + outputs.leftStickY = 128 + (directions.y * 53); + } + + if (directions.y == 1) { + outputs.leftStickX = 128 + (directions.x * 23); + outputs.leftStickY = 128 + (directions.y * 52); + } + + /* Up B Angles */ + if (inputs.rf1) { + outputs.leftStickX = 128 + (directions.x * 23); + outputs.leftStickY = 128 + (directions.y * 76); + } + if (inputs.rt2) { + outputs.leftStickX = 128 + (directions.x * 31); + outputs.leftStickY = 128 + (directions.y * 75); + } + if (inputs.rt3) { + outputs.leftStickX = 128 + (directions.x * 39); + outputs.leftStickY = 128 + (directions.y * 75); + } + if (inputs.rt4) { + outputs.leftStickX = 128 + (directions.x * 46); + outputs.leftStickY = 128 + (directions.y * 69); + } + if (inputs.rt5) { + outputs.leftStickX = 128 + (directions.x * 41); + outputs.leftStickY = 128 + (directions.y * 50); + } + } + + // Slight angled fsmash + if (directions.cx != 0) { + // 9500 2875 = 76 23 + outputs.rightStickX = 128 + (directions.cx * 76); + outputs.rightStickY = 128 + (directions.y * 23); + } + } + + /* if (inputs.lf5) { + // Up2 Nair + outputs.leftStickX = 128; + outputs.leftStickY = 128; + } */ + + // C-stick ASDI Slideoff angle overrides any other C-stick modifiers (such as + // angled fsmash). + if (directions.cx != 0 && directions.cy != 0) { + // 2875 9500 = 23 76 + outputs.rightStickX = 128 + (directions.cx * 23); + outputs.rightStickY = 128 + (directions.cy * 76); + } + + if (inputs.rf7) { + outputs.triggerRAnalog = 43; + } + if (inputs.rf8) { + outputs.triggerRAnalog = 94; + } + + if (outputs.triggerLDigital) { + outputs.triggerLAnalog = 140; + } + if (outputs.triggerRDigital) { + outputs.triggerRAnalog = 140; + } + + // Shut off C-stick when using D-Pad layer. + if ((inputs.lt1 && inputs.lt2) || inputs.nunchuk_c) { + outputs.rightStickX = 128; + outputs.rightStickY = 128; + + // slight angled ftilts + if (inputs.rt1 && directions.diagonal) { + // 9500 2875 = 76 23 + outputs.leftStickX = 128 + (directions.x * 76); + outputs.leftStickY = 128 + (directions.y * 23); + } + + // Pikachu/Pichu double up-special + if (inputs.rf1 && inputs.rf4) { + outputs.leftStickY = 128 - (directions.y * 40); + } + } + + // Nunchuk overrides left stick. + if (inputs.nunchuk_connected) { + outputs.leftStickX = inputs.nunchuk_x; + outputs.leftStickY = inputs.nunchuk_y; + } +} \ No newline at end of file diff --git a/src/modes/Melee18Button.cpp b/src/modes/Melee18Button.cpp index d9ac9f60..cb74101a 100644 --- a/src/modes/Melee18Button.cpp +++ b/src/modes/Melee18Button.cpp @@ -4,30 +4,22 @@ #define ANALOG_STICK_NEUTRAL 128 #define ANALOG_STICK_MAX 208 -Melee18Button::Melee18Button(socd::SocdType socd_type, Melee18ButtonOptions options) { - _socd_pair_count = 4; - _socd_pairs = new socd::SocdPair[_socd_pair_count]{ - socd::SocdPair{&InputState::left, &InputState::right, socd_type}, - socd::SocdPair{ &InputState::down, &InputState::up, socd_type}, - socd::SocdPair{ &InputState::c_left, &InputState::c_right, socd_type}, - socd::SocdPair{ &InputState::c_down, &InputState::c_up, socd_type}, - }; - +Melee18Button::Melee18Button(Melee18ButtonOptions options) : ControllerMode() { _options = options; horizontal_socd = false; } void Melee18Button::HandleSocd(InputState &inputs) { - horizontal_socd = inputs.left && inputs.right; + horizontal_socd = inputs.lf3 && inputs.lf1; InputMode::HandleSocd(inputs); } -void Melee18Button::UpdateDigitalOutputs(InputState &inputs, OutputState &outputs) { - outputs.a = inputs.a; - outputs.b = inputs.b; - outputs.x = inputs.x; - outputs.y = inputs.y; - outputs.buttonR = inputs.z; +void Melee18Button::UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs) { + outputs.a = inputs.rt1; + outputs.b = inputs.rf1; + outputs.x = inputs.rf2; + outputs.y = inputs.rf6; + outputs.buttonR = inputs.rf3; if (inputs.nunchuk_connected) { // Lightshield with C button. if (inputs.nunchuk_c) { @@ -35,50 +27,50 @@ void Melee18Button::UpdateDigitalOutputs(InputState &inputs, OutputState &output } outputs.triggerLDigital = inputs.nunchuk_z; } else { - outputs.triggerLDigital = inputs.l; + outputs.triggerLDigital = inputs.lf4; } - outputs.triggerRDigital = inputs.r; - outputs.start = inputs.start; + outputs.triggerRDigital = inputs.rf5; + outputs.start = inputs.mb1; // Activate D-Pad layer by holding Mod X + Mod Y. - if (inputs.mod_x && inputs.mod_y) { - outputs.dpadUp = inputs.c_up; - outputs.dpadDown = inputs.c_down; - outputs.dpadLeft = inputs.c_left; - outputs.dpadRight = inputs.c_right; + if (inputs.lt1 && inputs.lt2) { + outputs.dpadUp = inputs.rt4; + outputs.dpadDown = inputs.rt2; + outputs.dpadLeft = inputs.rt3; + outputs.dpadRight = inputs.rt5; } - if (inputs.select) + if (inputs.mb3) outputs.dpadLeft = true; - if (inputs.home) + if (inputs.mb2) outputs.dpadRight = true; } -void Melee18Button::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { +void Melee18Button::UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs) { // Coordinate calculations to make modifier handling simpler. UpdateDirections( - inputs.left, - inputs.right, - inputs.down, - inputs.up, - inputs.c_left, - inputs.c_right, - inputs.c_down, - inputs.c_up, + inputs.lf3, // Left + inputs.lf1, // Right + inputs.lf2, // Down + inputs.rf4, // Up + inputs.rt3, // C-Left + inputs.rt5, // C-Right + inputs.rt2, // C-Down + inputs.rt4, // C-Up ANALOG_STICK_MIN, ANALOG_STICK_NEUTRAL, ANALOG_STICK_MAX, outputs ); - bool shield_button_pressed = inputs.l || inputs.r; + bool shield_button_pressed = inputs.lf4 || inputs.rf5; if (directions.diagonal && directions.y == -1 && _options.crouch_walk_os) { outputs.leftStickX = 128 + (directions.x * 56); outputs.leftStickY = 128 + (directions.y * 55); } - if (inputs.mod_x) { + if (inputs.lt1) { if (directions.horizontal) { outputs.leftStickX = 128 + (directions.x * 53); } @@ -99,48 +91,48 @@ void Melee18Button::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs outputs.leftStickX = 128 + (directions.x * 59); outputs.leftStickY = 128 + (directions.y * 25); // 27.37104 - 7000 3625 (27.38) = 56 29 - if (inputs.c_down) { + if (inputs.rt2) { outputs.leftStickX = 128 + (directions.x * 56); outputs.leftStickY = 128 + (directions.y * 29); } // 31.77828 - 7875 4875 (31.76) = 63 39 - if (inputs.c_left) { + if (inputs.rt3) { outputs.leftStickX = 128 + (directions.x * 63); outputs.leftStickY = 128 + (directions.y * 39); } // 36.18552 - 7000 5125 (36.21) = 56 41 - if (inputs.c_up) { + if (inputs.rt4) { outputs.leftStickX = 128 + (directions.x * 56); outputs.leftStickY = 128 + (directions.y * 41); } // 40.59276 - 6125 5250 (40.6) = 49 42 - if (inputs.c_right) { + if (inputs.rt5) { outputs.leftStickX = 128 + (directions.x * 49); outputs.leftStickY = 128 + (directions.y * 42); } /* Extended Up B Angles */ - if (inputs.b) { + if (inputs.rf1) { // 22.9638 - 9125 3875 (23.0) = 73 31 outputs.leftStickX = 128 + (directions.x * 73); outputs.leftStickY = 128 + (directions.y * 31); // 27.37104 - 8750 4500 (27.2) = 70 36 - if (inputs.c_down) { + if (inputs.rt2) { outputs.leftStickX = 128 + (directions.x * 70); outputs.leftStickY = 128 + (directions.y * 36); } // 31.77828 - 8500 5250 (31.7) = 68 42 - if (inputs.c_left) { + if (inputs.rt3) { outputs.leftStickX = 128 + (directions.x * 68); outputs.leftStickY = 128 + (directions.y * 42); } // 36.18552 - 7375 5375 (36.1) = 59 43 - if (inputs.c_up) { + if (inputs.rt4) { outputs.leftStickX = 128 + (directions.x * 59); outputs.leftStickY = 128 + (directions.y * 43); } // 40.59276 - 6375 5375 (40.1) = 51 43 - if (inputs.c_right) { + if (inputs.rt5) { outputs.leftStickX = 128 + (directions.x * 51); outputs.leftStickY = 128 + (directions.y * 43); } @@ -148,7 +140,7 @@ void Melee18Button::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs } } - if (inputs.mod_y) { + if (inputs.lt2) { if (directions.horizontal) { outputs.leftStickX = 128 + (directions.x * 27); } @@ -157,7 +149,7 @@ void Melee18Button::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs } // Turnaround neutral B nerf - if (inputs.b) { + if (inputs.rf1) { outputs.leftStickX = 128 + (directions.x * 80); } @@ -167,48 +159,48 @@ void Melee18Button::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs outputs.leftStickX = 128 + (directions.x * 25); outputs.leftStickY = 128 + (directions.y * 59); // 62.62896 - 3625 7000 (62.62) = 29 56 - if (inputs.c_down) { + if (inputs.rt2) { outputs.leftStickX = 128 + (directions.x * 29); outputs.leftStickY = 128 + (directions.y * 56); } // 58.22172 - 4875 7875 (58.24) = 39 63 - if (inputs.c_left) { + if (inputs.rt3) { outputs.leftStickX = 128 + (directions.x * 39); outputs.leftStickY = 128 + (directions.y * 63); } // 53.81448 - 5125 7000 (53.79) = 41 56 - if (inputs.c_up) { + if (inputs.rt4) { outputs.leftStickX = 128 + (directions.x * 41); outputs.leftStickY = 128 + (directions.y * 56); } // 49.40724 - 6375 7625 (50.10) = 51 61 - if (inputs.c_right) { + if (inputs.rt5) { outputs.leftStickX = 128 + (directions.x * 51); outputs.leftStickY = 128 + (directions.y * 61); } /* Extended Up B Angles */ - if (inputs.b) { + if (inputs.rf1) { // 67.0362 - 3875 9125 = 31 73 outputs.leftStickX = 128 + (directions.x * 31); outputs.leftStickY = 128 + (directions.y * 73); // 62.62896 - 4500 8750 (62.8) = 36 70 - if (inputs.c_down) { + if (inputs.rt2) { outputs.leftStickX = 128 + (directions.x * 36); outputs.leftStickY = 128 + (directions.y * 70); } // 58.22172 - 5250 8500 (58.3) = 42 68 - if (inputs.c_left) { + if (inputs.rt3) { outputs.leftStickX = 128 + (directions.x * 42); outputs.leftStickY = 128 + (directions.y * 68); } // 53.81448 - 5875 8000 (53.7) = 47 64 - if (inputs.c_up) { + if (inputs.rt4) { outputs.leftStickX = 128 + (directions.x * 47); outputs.leftStickY = 128 + (directions.y * 64); } // 49.40724 - 5875 7125 (50.49) = 47 57 - if (inputs.c_right) { + if (inputs.rt5) { outputs.leftStickX = 128 + (directions.x * 47); outputs.leftStickY = 128 + (directions.y * 57); } @@ -216,7 +208,7 @@ void Melee18Button::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs } } - if (inputs.l) { + if (inputs.lf4) { // L overrides modifiers, both for wavedash nerf and so MX/MY can give midshield/lightshield // without forcing shield tilt. if (directions.horizontal) { @@ -235,7 +227,7 @@ void Melee18Button::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs } // L + Mod X = midshield - if (inputs.mod_x) { + if (inputs.lt1) { outputs.triggerLDigital = false; outputs.triggerRAnalog = 94; @@ -245,7 +237,7 @@ void Melee18Button::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs } } // L + Mod Y = lightshield - if (inputs.mod_y) { + if (inputs.lt2) { outputs.triggerLDigital = false; outputs.triggerRAnalog = 49; @@ -257,7 +249,7 @@ void Melee18Button::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs } // Holding R gives special shield tilt and wavedash coordinates. - if (inputs.r) { + if (inputs.rf5) { if (directions.horizontal) { outputs.leftStickX = 128 + (directions.x * 51); } @@ -266,11 +258,11 @@ void Melee18Button::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs } if (directions.diagonal) { outputs.leftStickX = 128 + (directions.x * 43); - if (inputs.mod_x) { + if (inputs.lt1) { outputs.leftStickX = 128 + (directions.x * 51); outputs.leftStickY = 128 + (directions.y * 30); } - if (inputs.mod_y) { + if (inputs.lt2) { outputs.leftStickX = 128 + (directions.x * 40); outputs.leftStickY = 128 + (directions.y * 68); } @@ -287,12 +279,12 @@ void Melee18Button::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs // Horizontal SOCD overrides X-axis modifiers (for ledgedash maximum jump // trajectory). - if (!inputs.r && horizontal_socd && !directions.vertical) { + if (!inputs.rf5 && horizontal_socd && !directions.vertical) { outputs.leftStickX = 128 + (directions.x * 80); } // Shut off C-stick when using D-Pad layer. - if ((inputs.mod_x && inputs.mod_y) || inputs.nunchuk_c) { + if ((inputs.lt1 && inputs.lt2) || inputs.nunchuk_c) { outputs.rightStickX = 128; outputs.rightStickY = 128; } diff --git a/src/modes/Melee20Button.cpp b/src/modes/Melee20Button.cpp index cfb47075..766e609d 100644 --- a/src/modes/Melee20Button.cpp +++ b/src/modes/Melee20Button.cpp @@ -4,70 +4,66 @@ #define ANALOG_STICK_NEUTRAL 128 #define ANALOG_STICK_MAX 208 -Melee20Button::Melee20Button(socd::SocdType socd_type, Melee20ButtonOptions options) { - _socd_pair_count = 4; - _socd_pairs = new socd::SocdPair[_socd_pair_count]{ - socd::SocdPair{&InputState::left, &InputState::right, socd_type}, - socd::SocdPair{ &InputState::down, &InputState::up, socd_type}, - socd::SocdPair{ &InputState::c_left, &InputState::c_right, socd_type}, - socd::SocdPair{ &InputState::c_down, &InputState::c_up, socd_type}, - }; +Melee20Button::Melee20Button() : ControllerMode() { + _horizontal_socd = false; +} +void Melee20Button::SetConfig(GameModeConfig &config, const MeleeOptions options) { + InputMode::SetConfig(config); _options = options; - _horizontal_socd = false; } void Melee20Button::HandleSocd(InputState &inputs) { - _horizontal_socd = inputs.left && inputs.right; + _horizontal_socd = inputs.lf3 && inputs.lf1; InputMode::HandleSocd(inputs); } -void Melee20Button::UpdateDigitalOutputs(InputState &inputs, OutputState &outputs) { - outputs.a = inputs.a; - outputs.b = inputs.b; - outputs.x = inputs.x; - outputs.y = inputs.y; - outputs.buttonR = inputs.z; +void Melee20Button::UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs) { + outputs.a = inputs.rt1; + outputs.b = inputs.rf1; + outputs.x = inputs.rf2; + outputs.y = inputs.rf6; + outputs.buttonR = inputs.rf3; if (inputs.nunchuk_connected) { outputs.triggerLDigital = inputs.nunchuk_z; } else { - outputs.triggerLDigital = inputs.l; + outputs.triggerLDigital = inputs.lf4; } - outputs.triggerRDigital = inputs.r; - outputs.start = inputs.start; + outputs.triggerRDigital = inputs.rf5; + outputs.start = inputs.mb1; // Activate D-Pad layer by holding Mod X + Mod Y or Nunchuk C button. - if ((inputs.mod_x && inputs.mod_y) || inputs.nunchuk_c) { - outputs.dpadUp = inputs.c_up; - outputs.dpadDown = inputs.c_down; - outputs.dpadLeft = inputs.c_left; - outputs.dpadRight = inputs.c_right; + if ((inputs.lt1 && inputs.lt2) || inputs.nunchuk_c) { + outputs.dpadUp = inputs.rt4; + outputs.dpadDown = inputs.rt2; + outputs.dpadLeft = inputs.rt3; + outputs.dpadRight = inputs.rt5; } - if (inputs.select) + if (inputs.mb3) outputs.dpadLeft = true; - if (inputs.home) + if (inputs.mb2) outputs.dpadRight = true; } -void Melee20Button::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { +void Melee20Button::UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs) { // Coordinate calculations to make modifier handling simpler. UpdateDirections( - inputs.left, - inputs.right, - inputs.down, - inputs.up, - inputs.c_left, - inputs.c_right, - inputs.c_down, - inputs.c_up, + inputs.lf3, // Left + inputs.lf1, // Right + inputs.lf2, // Down + inputs.rf4, // Up + inputs.rt3, // C-Left + inputs.rt5, // C-Right + inputs.rt2, // C-Down + inputs.rt4, // C-Up ANALOG_STICK_MIN, ANALOG_STICK_NEUTRAL, ANALOG_STICK_MAX, outputs ); - bool shield_button_pressed = inputs.l || inputs.r || inputs.lightshield || inputs.midshield; + bool shield_button_pressed = inputs.lf4 || inputs.rf5 || inputs.rf7 || inputs.rf8; if (directions.diagonal) { // q1/2 = 7000 7000 outputs.leftStickX = 128 + (directions.x * 56); @@ -81,7 +77,8 @@ void Melee20Button::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs } } - if (inputs.mod_x) { + /* Mod X */ + if (inputs.lt1) { // MX + Horizontal (even if shield is held) = 6625 = 53 if (directions.horizontal) { outputs.leftStickX = 128 + (directions.x * 53); @@ -91,9 +88,15 @@ void Melee20Button::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs outputs.leftStickY = 128 + (directions.y * 43); } if (directions.diagonal && shield_button_pressed) { - // MX + L, R, LS, and MS + q1/2/3/4 = 6375 3750 = 51 30 - outputs.leftStickX = 128 + (directions.x * 51); - outputs.leftStickY = 128 + (directions.y * 30); + // Use custom airdodge angle if set, otherwise B0XX standard default. + if (_options.has_custom_airdodge) { + outputs.leftStickX = 128 + (directions.x * _options.custom_airdodge.x); + outputs.leftStickY = 128 + (directions.y * _options.custom_airdodge.y); + } else { + // MX + L, R, LS, and MS + q1/2/3/4 = 6375 3750 = 51 30 + outputs.leftStickX = 128 + (directions.x * 51); + outputs.leftStickY = 128 + (directions.y * 30); + } } /* Up B angles */ @@ -102,48 +105,48 @@ void Melee20Button::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs outputs.leftStickX = 128 + (directions.x * 59); outputs.leftStickY = 128 + (directions.y * 25); // 27.37104 - 7000 3625 (27.38) = 56 29 - if (inputs.c_down) { + if (inputs.rt2) { outputs.leftStickX = 128 + (directions.x * 56); outputs.leftStickY = 128 + (directions.y * 29); } // 31.77828 - 7875 4875 (31.76) = 63 39 - if (inputs.c_left) { + if (inputs.rt3) { outputs.leftStickX = 128 + (directions.x * 63); outputs.leftStickY = 128 + (directions.y * 39); } // 36.18552 - 7000 5125 (36.21) = 56 41 - if (inputs.c_up) { + if (inputs.rt4) { outputs.leftStickX = 128 + (directions.x * 56); outputs.leftStickY = 128 + (directions.y * 41); } // 40.59276 - 6125 5250 (40.6) = 49 42 - if (inputs.c_right) { + if (inputs.rt5) { outputs.leftStickX = 128 + (directions.x * 49); outputs.leftStickY = 128 + (directions.y * 42); } /* Extended Up B Angles */ - if (inputs.b) { + if (inputs.rf1) { // 22.9638 - 9125 3875 (23.0) = 73 31 outputs.leftStickX = 128 + (directions.x * 73); outputs.leftStickY = 128 + (directions.y * 31); // 27.37104 - 8750 4500 (27.2) = 70 36 - if (inputs.c_down) { + if (inputs.rt2) { outputs.leftStickX = 128 + (directions.x * 70); outputs.leftStickY = 128 + (directions.y * 36); } // 31.77828 - 8500 5250 (31.7) = 68 42 - if (inputs.c_left) { + if (inputs.rt3) { outputs.leftStickX = 128 + (directions.x * 68); outputs.leftStickY = 128 + (directions.y * 42); } // 36.18552 - 7375 5375 (36.1) = 59 43 - if (inputs.c_up) { + if (inputs.rt4) { outputs.leftStickX = 128 + (directions.x * 59); outputs.leftStickY = 128 + (directions.y * 43); } // 40.59276 - 6375 5375 (40.1) = 51 43 - if (inputs.c_right) { + if (inputs.rt5) { outputs.leftStickX = 128 + (directions.x * 51); outputs.leftStickY = 128 + (directions.y * 43); } @@ -158,7 +161,8 @@ void Melee20Button::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs } } - if (inputs.mod_y) { + /* Mod Y */ + if (inputs.lt2) { // MY + Horizontal (even if shield is held) = 3375 = 27 if (directions.horizontal) { outputs.leftStickX = 128 + (directions.x * 27); @@ -179,7 +183,7 @@ void Melee20Button::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs } // Turnaround neutral B nerf - if (inputs.b) { + if (inputs.rf1) { outputs.leftStickX = 128 + (directions.x * 80); } @@ -189,48 +193,48 @@ void Melee20Button::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs outputs.leftStickX = 128 + (directions.x * 25); outputs.leftStickY = 128 + (directions.y * 59); // 62.62896 - 3625 7000 (62.62) = 29 56 - if (inputs.c_down) { + if (inputs.rt2) { outputs.leftStickX = 128 + (directions.x * 29); outputs.leftStickY = 128 + (directions.y * 56); } // 58.22172 - 4875 7875 (58.24) = 39 63 - if (inputs.c_left) { + if (inputs.rt3) { outputs.leftStickX = 128 + (directions.x * 39); outputs.leftStickY = 128 + (directions.y * 63); } // 53.81448 - 5125 7000 (53.79) = 41 56 - if (inputs.c_up) { + if (inputs.rt4) { outputs.leftStickX = 128 + (directions.x * 41); outputs.leftStickY = 128 + (directions.y * 56); } // 49.40724 - 6375 7625 (50.10) = 51 61 - if (inputs.c_right) { + if (inputs.rt5) { outputs.leftStickX = 128 + (directions.x * 51); outputs.leftStickY = 128 + (directions.y * 61); } /* Extended Up B Angles */ - if (inputs.b) { + if (inputs.rf1) { // 67.0362 - 3875 9125 = 31 73 outputs.leftStickX = 128 + (directions.x * 31); outputs.leftStickY = 128 + (directions.y * 73); // 62.62896 - 4500 8750 (62.8) = 36 70 - if (inputs.c_down) { + if (inputs.rt2) { outputs.leftStickX = 128 + (directions.x * 36); outputs.leftStickY = 128 + (directions.y * 70); } // 58.22172 - 5250 8500 (58.3) = 42 68 - if (inputs.c_left) { + if (inputs.rt3) { outputs.leftStickX = 128 + (directions.x * 42); outputs.leftStickY = 128 + (directions.y * 68); } // 53.81448 - 5875 8000 (53.7) = 47 64 - if (inputs.c_up) { + if (inputs.rt4) { outputs.leftStickX = 128 + (directions.x * 47); outputs.leftStickY = 128 + (directions.y * 64); } // 49.40724 - 5875 7125 (50.49) = 47 57 - if (inputs.c_right) { + if (inputs.rt5) { outputs.leftStickX = 128 + (directions.x * 47); outputs.leftStickY = 128 + (directions.y * 57); } @@ -248,14 +252,14 @@ void Melee20Button::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs // Horizontal SOCD overrides X-axis modifiers (for ledgedash maximum jump // trajectory). - if (_horizontal_socd && !directions.vertical) { + if (!_options.disable_ledgedash_socd_override && _horizontal_socd && !directions.vertical) { outputs.leftStickX = 128 + (directions.x * 80); } - if (inputs.lightshield) { + if (inputs.rf7) { outputs.triggerRAnalog = 49; } - if (inputs.midshield) { + if (inputs.rf8) { outputs.triggerRAnalog = 94; } @@ -267,7 +271,7 @@ void Melee20Button::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs } // Shut off C-stick when using D-Pad layer. - if ((inputs.mod_x && inputs.mod_y) || inputs.nunchuk_c) { + if ((inputs.lt1 && inputs.lt2) || inputs.nunchuk_c) { outputs.rightStickX = 128; outputs.rightStickY = 128; } diff --git a/src/modes/ProjectM.cpp b/src/modes/ProjectM.cpp index 704ea041..e5f912c5 100644 --- a/src/modes/ProjectM.cpp +++ b/src/modes/ProjectM.cpp @@ -4,175 +4,215 @@ #define ANALOG_STICK_NEUTRAL 128 #define ANALOG_STICK_MAX 228 -ProjectM::ProjectM(socd::SocdType socd_type, ProjectMOptions options) { - _socd_pair_count = 4; - _socd_pairs = new socd::SocdPair[_socd_pair_count]{ - socd::SocdPair{&InputState::left, &InputState::right, socd_type}, - socd::SocdPair{ &InputState::down, &InputState::up, socd_type}, - socd::SocdPair{ &InputState::c_left, &InputState::c_right, socd_type}, - socd::SocdPair{ &InputState::c_down, &InputState::c_up, socd_type}, - }; +ProjectM::ProjectM() : ControllerMode() { + _horizontal_socd = false; +} +void ProjectM::SetConfig(GameModeConfig &config, const ProjectMOptions options) { + InputMode::SetConfig(config); _options = options; - _horizontal_socd = false; } void ProjectM::HandleSocd(InputState &inputs) { - _horizontal_socd = inputs.left && inputs.right; + _horizontal_socd = inputs.lf3 && inputs.lf1; InputMode::HandleSocd(inputs); } -void ProjectM::UpdateDigitalOutputs(InputState &inputs, OutputState &outputs) { - outputs.a = inputs.a; - outputs.b = inputs.b; - outputs.x = inputs.x; - outputs.y = inputs.y; +void ProjectM::UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs) { + outputs.a = inputs.rt1; + outputs.b = inputs.rf1; + outputs.x = inputs.rf2; + outputs.y = inputs.rf6; // True Z press vs macro lightshield + A. - if (_options.true_z_press || inputs.mod_x) { - outputs.buttonR = inputs.z; + if (_options.true_z_press || inputs.lt1) { + outputs.buttonR = inputs.rf3; } else { - outputs.a = inputs.a || inputs.z; + outputs.a = inputs.rt1 || inputs.rf3; } if (inputs.nunchuk_connected) { outputs.triggerLDigital = inputs.nunchuk_z; } else { - outputs.triggerLDigital = inputs.l; + outputs.triggerLDigital = inputs.lf4; } - outputs.triggerRDigital = inputs.r; - outputs.start = inputs.start; - - // Activate D-Pad layer by holding Mod X + Mod Y or Nunchuk C button. - if ((inputs.mod_x && inputs.mod_y) || inputs.nunchuk_c) { - outputs.dpadUp = inputs.c_up; - outputs.dpadDown = inputs.c_down; - outputs.dpadLeft = inputs.c_left; - outputs.dpadRight = inputs.c_right; + outputs.triggerRDigital = inputs.rf5; + outputs.start = inputs.mb1; + + // Activate D-Pad layer by holding Mod X + Mod Y, Nunchuk C or DP* button. + if ((inputs.lt1 && inputs.lt2) || inputs.nunchuk_c || inputs.lf14) { + outputs.dpadUp = inputs.rt4; + outputs.dpadDown = inputs.rt2; + outputs.dpadLeft = inputs.rt3; + outputs.dpadRight = inputs.rt5; } - // Don't override dpad up if it's already pressed using the MX + MY dpad - // layer. - outputs.dpadUp = outputs.dpadUp || inputs.midshield; + // Don't override dpad if it's already pressed using the D-Pad layer. + outputs.dpadUp = outputs.dpadUp || inputs.rf8; + outputs.dpadDown = outputs.dpadDown || inputs.rf16; + + // D-Pad Neutral SOCD + if ((inputs.lt1 && inputs.lt2) || inputs.nunchuk_c || inputs.lf14) { + if ((inputs.rt2 && inputs.rf8) || (inputs.rt4 && inputs.rf16)) { + outputs.dpadUp = false; + outputs.dpadDown = false; + } + } + - if (inputs.select) + if (inputs.mb3) outputs.dpadLeft = true; - if (inputs.home) + if (inputs.mb2) outputs.dpadRight = true; } -void ProjectM::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { +void ProjectM::UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs) { UpdateDirections( - inputs.left, - inputs.right, - inputs.down, - inputs.up, - inputs.c_left, - inputs.c_right, - inputs.c_down, - inputs.c_up, + inputs.lf3, // Left + inputs.lf1, // Right + inputs.lf2, // Down + inputs.rf4, // Up + inputs.rt3, // C-Left + inputs.rt5, // C-Right + inputs.rt2, // C-Down + inputs.rt4, // C-Up ANALOG_STICK_MIN, ANALOG_STICK_NEUTRAL, ANALOG_STICK_MAX, outputs ); - bool shield_button_pressed = inputs.l || inputs.lightshield; + bool shield_button_pressed = inputs.lf4 || inputs.rf5 || inputs.rf7; if (directions.diagonal) { - if (directions.y == 1) { + // Up smash without letting go of forward + if ((inputs.rf3 || inputs.rt1) && directions.y == 1) { outputs.leftStickX = 128 + (directions.x * 83); outputs.leftStickY = 128 + (directions.y * 93); } + + // 45° notch + B outputs vertical special when grounded + if (inputs.rf1) { + outputs.leftStickX = 128 + (directions.x * 57); + outputs.leftStickY = 128 + (directions.y * 57); + } } - if (inputs.mod_x) { + /* Mod X */ + if (inputs.lt1) { if (directions.horizontal) { outputs.leftStickX = 128 + (directions.x * 70); } + + // Crouch on platforms if (directions.vertical) { - outputs.leftStickY = 128 + (directions.y * 60); + outputs.leftStickY = 128 + (directions.y * 61); } + // Angled tilt stick if (directions.cx != 0) { outputs.rightStickX = 128 + (directions.cx * 65); - outputs.rightStickY = 128 + (directions.y * 23); + outputs.rightStickY = 128 + (directions.y * 44); } if (directions.diagonal) { + // Default MX Diagonal outputs.leftStickX = 128 + (directions.x * 70); outputs.leftStickY = 128 + (directions.y * 34); - if (inputs.b) { - outputs.leftStickX = 128 + (directions.x * 85); - outputs.leftStickY = 128 + (directions.y * 31); + // Airdodge angle + if (inputs.rf5) { + if (_options.has_custom_airdodge) { + outputs.leftStickX = 128 + (directions.x * _options.custom_airdodge.x); + outputs.leftStickY = 128 + (directions.y * _options.custom_airdodge.y); + } else { + outputs.leftStickX = 128 + (directions.x * 96); + outputs.leftStickY = 128 + (directions.y * 28); + } } - if (inputs.r) { - outputs.leftStickX = 128 + (directions.x * 82); - outputs.leftStickY = 128 + (directions.y * 35); + // MX Firefox angles + if (inputs.rf1) { + outputs.leftStickX = 128 + (directions.x * 96); + outputs.leftStickY = 128 + (directions.y * 28); } - if (inputs.c_up) { - outputs.leftStickX = 128 + (directions.x * 77); - outputs.leftStickY = 128 + (directions.y * 55); + if (inputs.rt2) { + outputs.leftStickX = 128 + (directions.x * 99); + outputs.leftStickY = 128 + (directions.y * 40); + } + + if (inputs.rt3) { + outputs.leftStickX = 128 + (directions.x * 95); + outputs.leftStickY = 128 + (directions.y * 50); } - if (inputs.c_down) { - outputs.leftStickX = 128 + (directions.x * 82); - outputs.leftStickY = 128 + (directions.y * 36); + if (inputs.rt4) { + outputs.leftStickX = 128 + (directions.x * 86); + outputs.leftStickY = 128 + (directions.y * 57); } - if (inputs.c_left) { - outputs.leftStickX = 128 + (directions.x * 84); - outputs.leftStickY = 128 + (directions.y * 50); + if (inputs.rt5) { + outputs.leftStickX = 128 + (directions.x * 93); + outputs.leftStickY = 128 + (directions.y * 76); } - if (inputs.c_right) { - outputs.leftStickX = 128 + (directions.x * 72); - outputs.leftStickY = 128 + (directions.y * 61); + // snekdash angle + if (inputs.lf4 && directions.y == 1) { + outputs.leftStickX = 128 + (directions.x * 90); + outputs.leftStickY = 128 + (directions.y * 63); } } } - if (inputs.mod_y) { + /* Mod Y */ + if (inputs.lt2) { if (directions.horizontal) { outputs.leftStickX = 128 + (directions.x * 35); } + + // No tap jump if (directions.vertical) { - outputs.leftStickY = 128 + (directions.y * 70); + outputs.leftStickY = 128 + (directions.y * 67); + + // (Shield) drop through platforms + if (directions.y == -1) { + outputs.leftStickY = 128 + (directions.y * 69); + } } if (directions.diagonal) { outputs.leftStickX = 128 + (directions.x * 28); outputs.leftStickY = 128 + (directions.y * 58); - if (inputs.b) { - outputs.leftStickX = 128 + (directions.x * 28); - outputs.leftStickY = 128 + (directions.y * 85); + // Diagonal shield drop, wavedash + if (shield_button_pressed && directions.y == -1) { + outputs.leftStickX = 128 + (directions.x * 45); + outputs.leftStickY = 128 + (directions.y * 72); } - if (inputs.r) { - outputs.leftStickX = 128 + (directions.x * 51); - outputs.leftStickY = 128 + (directions.y * 82); + // MY Firefox angles + if (inputs.rf1) { + outputs.leftStickX = 128 + (directions.x * 28); + outputs.leftStickY = 128 + (directions.y * 96); } - - if (inputs.c_up) { - outputs.leftStickX = 128 + (directions.x * 55); - outputs.leftStickY = 128 + (directions.y * 77); + + if (inputs.rt2) { + outputs.leftStickX = 128 + (directions.x * 40); + outputs.leftStickY = 128 + (directions.y * 99); } - - if (inputs.c_down) { - outputs.leftStickX = 128 + (directions.x * 34); - outputs.leftStickY = 128 + (directions.y * 82); + + if (inputs.rt3) { + outputs.leftStickX = 128 + (directions.x * 50); + outputs.leftStickY = 128 + (directions.y * 95); } - if (inputs.c_left) { - outputs.leftStickX = 128 + (directions.x * 40); - outputs.leftStickY = 128 + (directions.y * 84); + if (inputs.rt4) { + outputs.leftStickX = 128 + (directions.x * 57); + outputs.leftStickY = 128 + (directions.y * 86); } - if (inputs.c_right) { - outputs.leftStickX = 128 + (directions.x * 62); - outputs.leftStickY = 128 + (directions.y * 72); + if (inputs.rt5) { + outputs.leftStickX = 128 + (directions.x * 76); + outputs.leftStickY = 128 + (directions.y * 93); } } } @@ -189,17 +229,17 @@ void ProjectM::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { // Horizontal SOCD overrides X-axis modifiers (for ledgedash maximum jump // trajectory). - if (_options.ledgedash_max_jump_traj && _horizontal_socd && !directions.vertical && + if (!_options.disable_ledgedash_socd_override && _horizontal_socd && !directions.vertical && !shield_button_pressed) { outputs.leftStickX = 128 + (directions.x * 100); } - if (inputs.lightshield) { + if (inputs.rf7) { outputs.triggerRAnalog = 49; } // Send lightshield input if we are using Z = lightshield + A macro. - if (inputs.z && !(inputs.mod_x || _options.true_z_press)) { + if (inputs.rf3 && !(inputs.lt1 || _options.true_z_press)) { outputs.triggerRAnalog = 49; } @@ -212,7 +252,7 @@ void ProjectM::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { } // Shut off C-stick when using D-Pad layer. - if ((inputs.mod_x && inputs.mod_y) || inputs.nunchuk_c) { + if ((inputs.lt1 && inputs.lt2) || inputs.nunchuk_c || inputs.lf14) { outputs.rightStickX = 128; outputs.rightStickY = 128; } @@ -222,4 +262,10 @@ void ProjectM::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { outputs.leftStickX = inputs.nunchuk_x; outputs.leftStickY = inputs.nunchuk_y; } + + // Attack stick nair + if (inputs.lf15) { + outputs.rightStickX = 88; + outputs.rightStickY = 88; + } } diff --git a/src/modes/Rivals2.cpp b/src/modes/Rivals2.cpp new file mode 100644 index 00000000..0d019440 --- /dev/null +++ b/src/modes/Rivals2.cpp @@ -0,0 +1,197 @@ +#include "modes/Rivals2.hpp" + +#define ANALOG_STICK_MIN 1 +#define ANALOG_STICK_NEUTRAL 128 +#define ANALOG_STICK_MAX 255 + +Rivals2::Rivals2() : ControllerMode() {} + +void Rivals2::UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs) { + outputs.a = inputs.rt1; + outputs.b = inputs.rf1; + outputs.x = inputs.rf2; + outputs.y = inputs.rf6; + outputs.buttonR = inputs.rf3; + if (inputs.nunchuk_connected) { + // Lightshield with C button. + if (inputs.nunchuk_c) { + outputs.triggerLAnalog = 49; + } + outputs.triggerLDigital = inputs.nunchuk_z; + } else { + outputs.triggerLDigital = inputs.lf4; + } + outputs.triggerRDigital = inputs.rf5; + outputs.start = inputs.mb1; + outputs.select = inputs.mb3; + outputs.home = inputs.mb2; + outputs.buttonL = inputs.rf7; + outputs.leftStickClick = inputs.rf8; + outputs.rightStickClick = inputs.lt1 || inputs.lt2; + + // Activate D-Pad layer by holding Mod X + Mod Y. + if (inputs.lt1 && inputs.lt2) { + outputs.dpadUp = inputs.rt4; + outputs.dpadDown = inputs.rt2; + outputs.dpadLeft = inputs.rt3; + outputs.dpadRight = inputs.rt5; + } + + // Activate select by holding Mod X + if ((inputs.lt1)) outputs.select = inputs.mb1; + // Activate home by holding Mod Y + if ((inputs.lt2)) outputs.home = inputs.mb1; +} + +void Rivals2::UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs) { + // Coordinate calculations to make modifier handling simpler. + UpdateDirections( + inputs.lf3, // Left + inputs.lf1, // Right + inputs.lf2, // Down + inputs.rf4, // Up + inputs.rt3, // C-Left + inputs.rt5, // C-Right + inputs.rt2, // C-Down + inputs.rt4, // C-Up + ANALOG_STICK_MIN, + ANALOG_STICK_NEUTRAL, + ANALOG_STICK_MAX, + outputs + ); + + bool shield_button_pressed = inputs.lf4 || inputs.rf5; + + // Joystick accurate diagonals [joystick outputs (0.87~, 0.87~) rather than (+/- 1.2, 1.2)] + if (directions.diagonal && !shield_button_pressed) { + // X = 92 (0.78 in-game); [x < 0.8] allows crouch tilts/crouch turn-around tilts + outputs.leftStickX = 128 + (directions.x * 92); + // Y = 96 (0.83 in-game); [y > 0.8] allows fast fall + outputs.leftStickY = 128 + (directions.y * 96); + } + + if (directions.diagonal && shield_button_pressed) { + // (0.77~, 0.77~) to prevent spot dodging when inputting lower 45° notch while grounded + outputs.leftStickX = 128 + (directions.x * 92); + outputs.leftStickY = 128 + (directions.y * 92); + } + + if (inputs.lt1) { + /* if (directions.horizontal) { + outputs.leftStickX = 128 + (directions.x * 76); + // MX Horizontal Tilts + if (inputs.rt1) { + outputs.leftStickX = 128 + (directions.x * 44); + } + } + + if(directions.vertical) { + outputs.leftStickY = 128 + (directions.y * 73); + } */ + + // Extra DI, Air Dodge, and Up B angles + if (directions.diagonal) { + if (shield_button_pressed) { + outputs.leftStickX = 128 + (directions.x * 90); + outputs.leftStickY = 128 + (directions.y * 42); + } + + /* // Angled F-tilts + if (inputs.rt1) { + outputs.leftStickX = 128 + (directions.x * 69); + outputs.leftStickY = 128 + (directions.y * 53); + } */ + + // Angles just for DI and Up B + if (inputs.rf1) { + outputs.leftStickX = 128 + (directions.x * 96); + outputs.leftStickY = 128 + (directions.y * 28); + } + + if (inputs.rt2) { + outputs.leftStickX = 128 + (directions.x * 84); + outputs.leftStickY = 128 + (directions.y * 34); + } + + if (inputs.rt3) { + outputs.leftStickX = 128 + (directions.x * 76); + outputs.leftStickY = 128 + (directions.y * 40); + } + + if (inputs.rt4) { + outputs.leftStickX = 128 + (directions.x * 86); + outputs.leftStickY = 128 + (directions.y * 57); + } + + if (inputs.rt5) { + outputs.leftStickX = 128 + (directions.x * 82); + outputs.leftStickY = 128 + (directions.y * 67); + } + } + } + + if (inputs.lt2) { + if (directions.horizontal) { + outputs.leftStickX = 128 + (directions.x * 41); + } + + /* if(directions.vertical) { + outputs.leftStickY = 128 + (directions.y * 78); + } */ + + // Extra DI, Air Dodge, and Up B angles + if (directions.diagonal) { + outputs.leftStickX = 128 + (directions.x * 41); + outputs.leftStickY = 128 + (directions.y * 76); + + + // Angles just for DI and Up B + if (inputs.rf1) { + outputs.leftStickX = 128 + (directions.x * 28); + outputs.leftStickY = 128 + (directions.y * 96); + } + + if (inputs.rt2) { + outputs.leftStickX = 128 + (directions.x * 34); + outputs.leftStickY = 128 + (directions.y * 84); + } + + if (inputs.rt3) { + outputs.leftStickX = 128 + (directions.x * 40); + outputs.leftStickY = 128 + (directions.y * 76); + } + + if (inputs.rt4) { + outputs.leftStickX = 128 + (directions.x * 57); + outputs.leftStickY = 128 + (directions.y * 86); + } + + if (inputs.rt5) { + outputs.leftStickX = 128 + (directions.x * 67); + outputs.leftStickY = 128 + (directions.y * 82); + } + } + } + + // Shut off C-stick when using D-Pad layer. + if (inputs.lt1 && inputs.lt2) { + outputs.rightStickX = 128; + outputs.rightStickY = 128; + + if (directions.horizontal) { + // "perfect" walking speed (matches animation) + outputs.leftStickX = 128 + (directions.x * 61); + } + } + + // Nunchuk overrides left stick. + if (inputs.nunchuk_connected) { + outputs.leftStickX = inputs.nunchuk_x; + outputs.leftStickY = inputs.nunchuk_y; + } + + // Turns off Start when holding Mod X or Mod Y + if ((inputs.lt1 || inputs.lt2)) { + outputs.start = false; + } +} diff --git a/src/modes/RivalsOfAether.cpp b/src/modes/RivalsOfAether.cpp index b43aed8b..e5648aa6 100644 --- a/src/modes/RivalsOfAether.cpp +++ b/src/modes/RivalsOfAether.cpp @@ -1,25 +1,17 @@ #include "modes/RivalsOfAether.hpp" -#define ANALOG_STICK_MIN 28 +#define ANALOG_STICK_MIN 1 #define ANALOG_STICK_NEUTRAL 128 -#define ANALOG_STICK_MAX 228 - -RivalsOfAether::RivalsOfAether(socd::SocdType socd_type) { - _socd_pair_count = 4; - _socd_pairs = new socd::SocdPair[_socd_pair_count]{ - socd::SocdPair{&InputState::left, &InputState::right, socd_type}, - socd::SocdPair{ &InputState::down, &InputState::up, socd_type}, - socd::SocdPair{ &InputState::c_left, &InputState::c_right, socd_type}, - socd::SocdPair{ &InputState::c_down, &InputState::c_up, socd_type}, - }; -} +#define ANALOG_STICK_MAX 255 + +RivalsOfAether::RivalsOfAether() : ControllerMode() {} -void RivalsOfAether::UpdateDigitalOutputs(InputState &inputs, OutputState &outputs) { - outputs.a = inputs.a; - outputs.b = inputs.b; - outputs.x = inputs.x; - outputs.y = inputs.y; - outputs.buttonR = inputs.z; +void RivalsOfAether::UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs) { + outputs.a = inputs.rt1; + outputs.b = inputs.rf1; + outputs.x = inputs.rf2; + outputs.y = inputs.rf6; + outputs.buttonR = inputs.rf3; if (inputs.nunchuk_connected) { // Lightshield with C button. if (inputs.nunchuk_c) { @@ -27,134 +19,166 @@ void RivalsOfAether::UpdateDigitalOutputs(InputState &inputs, OutputState &outpu } outputs.triggerLDigital = inputs.nunchuk_z; } else { - outputs.triggerLDigital = inputs.l; + outputs.triggerLDigital = inputs.lf4; } - outputs.triggerRDigital = inputs.r; - outputs.start = inputs.start; - outputs.select = inputs.select; - outputs.home = inputs.home; - outputs.leftStickClick = inputs.lightshield; - outputs.rightStickClick = inputs.midshield; + outputs.triggerRDigital = inputs.rf5; + outputs.start = inputs.mb1; + outputs.select = inputs.mb3; + outputs.home = inputs.mb2; + outputs.buttonL = inputs.rf7; + outputs.leftStickClick = inputs.rf8; // Activate D-Pad layer by holding Mod X + Mod Y. - if (inputs.mod_x && inputs.mod_y) { - outputs.dpadUp = inputs.c_up; - outputs.dpadDown = inputs.c_down; - outputs.dpadLeft = inputs.c_left; - outputs.dpadRight = inputs.c_right; + if (inputs.lt1 && inputs.lt2) { + outputs.dpadUp = inputs.rt4; + outputs.dpadDown = inputs.rt2; + outputs.dpadLeft = inputs.rt3; + outputs.dpadRight = inputs.rt5; } + + // Activate select by holding Mod X + if ((inputs.lt1)) outputs.select = inputs.mb1; + // Activate home by holding Mod Y + if ((inputs.lt2)) outputs.home = inputs.mb1; } -void RivalsOfAether::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { +void RivalsOfAether::UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs) { // Coordinate calculations to make modifier handling simpler. UpdateDirections( - inputs.left, - inputs.right, - inputs.down, - inputs.up, - inputs.c_left, - inputs.c_right, - inputs.c_down, - inputs.c_up, + inputs.lf3, // Left + inputs.lf1, // Right + inputs.lf2, // Down + inputs.rf4, // Up + inputs.rt3, // C-Left + inputs.rt5, // C-Right + inputs.rt2, // C-Down + inputs.rt4, // C-Up ANALOG_STICK_MIN, ANALOG_STICK_NEUTRAL, ANALOG_STICK_MAX, outputs ); - bool shield_button_pressed = inputs.l || inputs.r; + bool shield_button_pressed = inputs.lf4 || inputs.rf5; + // Joystick accurate diagonals [joystick outputs (0.87~, 0.87~) rather than (+/- 1.2, 1.2)] + if (directions.diagonal && !shield_button_pressed) { + // X = 92 (0.78 in-game); [x < 0.8] allows crouch tilts/crouch turn-around tilts + outputs.leftStickX = 128 + (directions.x * 92); + // Y = 96 (0.83 in-game); [y > 0.8] allows fast fall + outputs.leftStickY = 128 + (directions.y * 96); + } - // 48 total DI angles, 24 total Up b angles, 16 total airdodge angles + if (directions.diagonal && shield_button_pressed) { + // (0.77~, 0.77~) to prevent spot dodging when inputting lower 45° notch while grounded + outputs.leftStickX = 128 + (directions.x * 92); + outputs.leftStickY = 128 + (directions.y * 92); + } - if (inputs.mod_x) { + if (inputs.lt1) { if (directions.horizontal) { - outputs.leftStickX = 128 + (directions.x * 66); + outputs.leftStickX = 128 + (directions.x * 76); // MX Horizontal Tilts - if (inputs.a) { + if (inputs.rt1) { outputs.leftStickX = 128 + (directions.x * 44); } } if(directions.vertical) { - outputs.leftStickY = 128 + (directions.y * 44); - // MX Vertical Tilts - if (inputs.a) { - outputs.leftStickY = 128 + (directions.y * 67); - } + outputs.leftStickY = 128 + (directions.y * 73); } - /* Extra DI, Air Dodge, and Up B angles */ + // Extra DI, Air Dodge, and Up B angles if (directions.diagonal) { - outputs.leftStickX = 128 + (directions.x * 59); - outputs.leftStickY = 128 + (directions.y * 23); + outputs.leftStickX = 128 + (directions.x * 90); + outputs.leftStickY = 128 + (directions.y * 42); + + // Angled F-tilts + if (inputs.rt1) { + outputs.leftStickX = 128 + (directions.x * 69); + outputs.leftStickY = 128 + (directions.y * 53); + } // Angles just for DI and Up B - if (inputs.c_down) { - outputs.leftStickX = 128 + (directions.x * 49); - outputs.leftStickY = 128 + (directions.y * 24); + if (inputs.rf1) { + outputs.leftStickX = 128 + (directions.x * 96); + outputs.leftStickY = 128 + (directions.y * 28); } - // Angles just for DI - if (inputs.c_left) { - outputs.leftStickX = 128 + (directions.x * 52); - outputs.leftStickY = 128 + (directions.y * 31); + if (inputs.rt2) { + outputs.leftStickX = 128 + (directions.x * 84); + outputs.leftStickY = 128 + (directions.y * 34); } - - if (inputs.c_up) { - outputs.leftStickX = 128 + (directions.x * 49); - outputs.leftStickY = 128 + (directions.y * 35); + + if (inputs.rt3) { + outputs.leftStickX = 128 + (directions.x * 76); + outputs.leftStickY = 128 + (directions.y * 40); } - - if (inputs.c_right) { - outputs.leftStickX = 128 + (directions.x * 51); - outputs.leftStickY = 128 + (directions.y * 43); + + if (inputs.rt4) { + outputs.leftStickX = 128 + (directions.x * 86); + outputs.leftStickY = 128 + (directions.y * 57); + } + + if (inputs.rt5) { + outputs.leftStickX = 128 + (directions.x * 82); + outputs.leftStickY = 128 + (directions.y * 67); } } } - if (inputs.mod_y) { + if (inputs.lt2) { if (directions.horizontal) { - outputs.leftStickX = 128 + (directions.x * 44); + outputs.leftStickX = 128 + (directions.x * 41); } if(directions.vertical) { - outputs.leftStickY = 128 + (directions.y * 67); + outputs.leftStickY = 128 + (directions.y * 78); } - /* Extra DI, Air Dodge, and Up B angles */ + // Extra DI, Air Dodge, and Up B angles if (directions.diagonal) { - outputs.leftStickX = 128 + (directions.x * 44); - outputs.leftStickY = 128 + (directions.y * 113); + outputs.leftStickX = 128 + (directions.x * 41); + outputs.leftStickY = 128 + (directions.y * 76); + // Angles just for DI and Up B - if (inputs.c_down) { - outputs.leftStickX = 128 + (directions.x * 44); - outputs.leftStickY = 128 + (directions.y * 90); + if (inputs.rf1) { + outputs.leftStickX = 128 + (directions.x * 28); + outputs.leftStickY = 128 + (directions.y * 96); } - // Angles just for DI - if (inputs.c_left) { - outputs.leftStickX = 128 + (directions.x * 44); - outputs.leftStickY = 128 + (directions.y * 74); + if (inputs.rt2) { + outputs.leftStickX = 128 + (directions.x * 34); + outputs.leftStickY = 128 + (directions.y * 84); } - - if (inputs.c_up) { - outputs.leftStickX = 128 + (directions.x * 45); - outputs.leftStickY = 128 + (directions.y * 63); + + if (inputs.rt3) { + outputs.leftStickX = 128 + (directions.x * 40); + outputs.leftStickY = 128 + (directions.y * 76); } - - if (inputs.c_right) { - outputs.leftStickX = 128 + (directions.x * 47); - outputs.leftStickY = 128 + (directions.y * 57); + + if (inputs.rt4) { + outputs.leftStickX = 128 + (directions.x * 57); + outputs.leftStickY = 128 + (directions.y * 86); + } + + if (inputs.rt5) { + outputs.leftStickX = 128 + (directions.x * 67); + outputs.leftStickY = 128 + (directions.y * 82); } } } // Shut off C-stick when using D-Pad layer. - if (inputs.mod_x && inputs.mod_y) { + if (inputs.lt1 && inputs.lt2) { outputs.rightStickX = 128; outputs.rightStickY = 128; + + if (directions.horizontal) { + // "perfect" walking speed (matches animation) + outputs.leftStickX = 128 + (directions.x * 61); + } } // Nunchuk overrides left stick. @@ -162,4 +186,9 @@ void RivalsOfAether::UpdateAnalogOutputs(InputState &inputs, OutputState &output outputs.leftStickX = inputs.nunchuk_x; outputs.leftStickY = inputs.nunchuk_y; } + + // Turns off Start when holding Mod X or Mod Y + if ((inputs.lt1 || inputs.lt2)) { + outputs.start = false; + } } diff --git a/src/modes/Ultimate.cpp b/src/modes/Ultimate.cpp index c23289ad..f1446d0c 100644 --- a/src/modes/Ultimate.cpp +++ b/src/modes/Ultimate.cpp @@ -1,276 +1,218 @@ -/* Ultimate profile by Taker */ +/* CONVERTED TO HDR MODE */ #include "modes/Ultimate.hpp" #define ANALOG_STICK_MIN 28 #define ANALOG_STICK_NEUTRAL 128 #define ANALOG_STICK_MAX 228 -Ultimate::Ultimate(socd::SocdType socd_type) { - _socd_pair_count = 4; - _socd_pairs = new socd::SocdPair[_socd_pair_count]{ - socd::SocdPair{&InputState::left, &InputState::right, socd_type}, - socd::SocdPair{ &InputState::down, &InputState::up, socd_type}, - socd::SocdPair{ &InputState::c_left, &InputState::c_right, socd_type}, - socd::SocdPair{ &InputState::c_down, &InputState::c_up, socd_type}, - }; -} - -void Ultimate::UpdateDigitalOutputs(InputState &inputs, OutputState &outputs) { - outputs.a = inputs.a; - outputs.b = inputs.b; - outputs.x = inputs.x; - outputs.y = inputs.y; - outputs.buttonL = inputs.lightshield; - outputs.buttonR = inputs.z || inputs.midshield; - outputs.triggerLDigital = inputs.l; - outputs.triggerRDigital = inputs.r; - outputs.start = inputs.start; - outputs.select = inputs.select; - outputs.home = inputs.home; +Ultimate::Ultimate() : ControllerMode() {} + +void Ultimate::UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs) { + outputs.a = inputs.rt1; + outputs.b = inputs.rf1; + outputs.x = inputs.rf2; + outputs.y = inputs.rf6; + outputs.buttonL = inputs.rf7; + outputs.buttonR = inputs.rf3; + outputs.triggerLDigital = inputs.lf4; + outputs.triggerRDigital = inputs.rf5; + outputs.start = inputs.mb1; + outputs.select = inputs.mb3; + outputs.home = inputs.mb2; // Turn on D-Pad layer by holding Mod X + Mod Y or Nunchuk C button. - if ((inputs.mod_x && inputs.mod_y) || inputs.nunchuk_c) { - outputs.dpadUp = inputs.c_up; - outputs.dpadDown = inputs.c_down; - outputs.dpadLeft = inputs.c_left; - outputs.dpadRight = inputs.c_right; + if ((inputs.lt1 && inputs.lt2) || inputs.nunchuk_c) { + outputs.dpadUp = inputs.rt4; + outputs.dpadDown = inputs.rt2; + outputs.dpadLeft = inputs.rt3; + outputs.dpadRight = inputs.rt5; + } + + // Activate select by holding Mod X + if ((inputs.lt1)) { + outputs.select = inputs.mb1; + } + // Activate home by holding Mod Y + if ((inputs.lt2)) { + outputs.home = inputs.mb1; + } + // Don't override dpad up if it's already pressed using the MX + MY dpad + // layer. + outputs.dpadUp = outputs.dpadUp || inputs.rf8; + + // D-Pad Neutral SOCD + if ((inputs.lt1 && inputs.lt2) || inputs.nunchuk_c) { + if (inputs.rt2 && inputs.rf8) { + outputs.dpadUp = false; + outputs.dpadDown = false; + } } } -void Ultimate::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { +void Ultimate::UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs) { // Coordinate calculations to make modifier handling simpler. UpdateDirections( - inputs.left, - inputs.right, - inputs.down, - inputs.up, - inputs.c_left, - inputs.c_right, - inputs.c_down, - inputs.c_up, + inputs.lf3, + inputs.lf1, + inputs.lf2, + inputs.rf4, + inputs.rt3, + inputs.rt5, + inputs.rt2, + inputs.rt4, ANALOG_STICK_MIN, ANALOG_STICK_NEUTRAL, ANALOG_STICK_MAX, outputs ); - bool shield_button_pressed = inputs.l || inputs.r; + bool shield_button_pressed = inputs.lf4 || inputs.rf5 || inputs.rf7; - if (inputs.mod_x) { - // MX + Horizontal = 6625 = 53 + if (directions.diagonal) { + // 45° notch + B outputs vertical special when grounded + if (inputs.rf1) { + outputs.leftStickX = 128 + (directions.x * 28); + outputs.leftStickY = 128 + (directions.y * 85); + } + } + + if (inputs.lt1) { if (directions.horizontal) { - outputs.leftStickX = 128 + (directions.x * 53); - // Horizontal Shield tilt = 51 - if (shield_button_pressed) { - outputs.leftStickX = 128 + (directions.x * 51); - } - // Horizontal Tilts = 36 - if (inputs.a) { - outputs.leftStickX = 128 + (directions.x * 36); - } + outputs.leftStickX = 128 + (directions.x * 49); } - // MX + Vertical = 44 if (directions.vertical) { - outputs.leftStickY = 128 + (directions.y * 44); - // Vertical Shield Tilt = 51 - if (shield_button_pressed) { - outputs.leftStickY = 128 + (directions.y * 51); - } - } - if (directions.diagonal) { - // MX + q1/2/3/4 = 53 35 - outputs.leftStickX = 128 + (directions.x * 53); - outputs.leftStickY = 128 + (directions.y * 35); - if (shield_button_pressed) { - // MX + L, R, LS, and MS + q1/2/3/4 = 6375 3750 = 51 30 - outputs.leftStickX = 128 + (directions.x * 51); - outputs.leftStickY = 128 + (directions.y * 30); - } + outputs.leftStickY = 128 + (directions.y * 49); } - // Angled fsmash/ftilt with C-Stick + MX if (directions.cx != 0) { - outputs.rightStickX = 128 + (directions.cx * 127); - outputs.rightStickY = 128 + (directions.y * 59); + outputs.rightStickX = 128 + (directions.cx * 65); + outputs.rightStickY = 128 + (directions.y * 44); } - /* Up B angles */ - if (directions.diagonal && !shield_button_pressed) { - // (33.44) = 53 35 - outputs.leftStickX = 128 + (directions.x * 53); - outputs.leftStickY = 128 + (directions.y * 35); - // (39.05) = 53 43 - if (inputs.c_down) { - outputs.leftStickX = 128 + (directions.x * 53); + if (directions.diagonal) { + outputs.leftStickX = 128 + (directions.x * 49); + outputs.leftStickY = 128 + (directions.y * 32); + + // Angled tilts + if (inputs.rt1) { + outputs.leftStickX = 128 + (directions.x * 47); outputs.leftStickY = 128 + (directions.y * 43); } - // (36.35) = 53 39 - if (inputs.c_left) { - outputs.leftStickX = 128 + (directions.x * 53); - outputs.leftStickY = 128 + (directions.y * 39); + + // MX Firefox angles + if (inputs.rf1) { + outputs.leftStickX = 128 + (directions.x * 96); + outputs.leftStickY = 128 + (directions.y * 28); } - // (30.32) = 56 41 - if (inputs.c_up) { - outputs.leftStickX = 128 + (directions.x * 53); - outputs.leftStickY = 128 + (directions.y * 31); + + if (inputs.rt2) { + outputs.leftStickX = 128 + (directions.x * 99); + outputs.leftStickY = 128 + (directions.y * 40); } - // (27.85) = 49 42 - if (inputs.c_right) { - outputs.leftStickX = 128 + (directions.x * 53); - outputs.leftStickY = 128 + (directions.y * 28); + + if (inputs.rt3) { + outputs.leftStickX = 128 + (directions.x * 95); + outputs.leftStickY = 128 + (directions.y * 50); } - /* Extended Up B Angles */ - if (inputs.b) { - // (33.29) = 67 44 - outputs.leftStickX = 128 + (directions.x * 67); - outputs.leftStickY = 128 + (directions.y * 44); - // (39.38) = 67 55 - if (inputs.c_down) { - outputs.leftStickX = 128 + (directions.x * 67); - outputs.leftStickY = 128 + (directions.y * 55); - } - // (36.18) = 67 49 - if (inputs.c_left) { - outputs.leftStickX = 128 + (directions.x * 67); - outputs.leftStickY = 128 + (directions.y * 49); - } - // (30.2) = 67 39 - if (inputs.c_up) { - outputs.leftStickX = 128 + (directions.x * 67); - outputs.leftStickY = 128 + (directions.y * 39); - } - // (27.58) = 67 35 - if (inputs.c_right) { - outputs.leftStickX = 128 + (directions.x * 67); - outputs.leftStickY = 128 + (directions.y * 35); - } + if (inputs.rt4) { + outputs.leftStickX = 128 + (directions.x * 86); + outputs.leftStickY = 128 + (directions.y * 57); } - // Angled Ftilts - if (inputs.a) { - outputs.leftStickX = 128 + (directions.x * 36); - outputs.leftStickY = 128 + (directions.y * 26); + if (inputs.rt5) { + outputs.leftStickX = 128 + (directions.x * 72); + outputs.leftStickY = 128 + (directions.y * 61); } } } - if (inputs.mod_y) { - // MY + Horizontal (even if shield is held) = 41 + if (inputs.lt2) { if (directions.horizontal) { - outputs.leftStickX = 128 + (directions.x * 41); - // MY Horizontal Tilts - if (inputs.a) { - outputs.leftStickX = 128 + (directions.x * 36); - } + outputs.leftStickX = 128 + (directions.x * 35); } - // MY + Vertical (even if shield is held) = 53 if (directions.vertical) { - outputs.leftStickY = 128 + (directions.y * 53); - // MY Vertical Tilts - if (inputs.a) { - outputs.leftStickY = 128 + (directions.y * 36); - } - } - if (directions.diagonal) { - // MY + q1/2/3/4 = 35 59 - outputs.leftStickX = 128 + (directions.x * 35); - outputs.leftStickY = 128 + (directions.y * 53); + outputs.leftStickY = 128 + (directions.y * 49); + + // Shield drop if (shield_button_pressed) { - // MY + L, R, LS, and MS + q1/2 = 38 70 - outputs.leftStickX = 128 + (directions.x * 38); - outputs.leftStickY = 128 + (directions.y * 70); - // MY + L, R, LS, and MS + q3/4 = 40 68 - if (directions.x == -1) { - outputs.leftStickX = 128 + (directions.x * 40); - outputs.leftStickY = 128 + (directions.y * 68); - } + outputs.leftStickY = 128 + (directions.y * 53); } } - /* Up B angles */ - if (directions.diagonal && !shield_button_pressed) { - // (56.56) = 35 53 - outputs.leftStickX = 128 + (directions.x * 35); - outputs.leftStickY = 128 + (directions.y * 53); - // (50.95) = 43 53 - if (inputs.c_down) { - outputs.leftStickX = 128 + (directions.x * 43); + if (directions.diagonal) { + outputs.leftStickX = 128 + (directions.x * 38); + outputs.leftStickY = 128 + (directions.y * 49); + + // Shield drop + if (shield_button_pressed && directions.y == -1) { + outputs.leftStickX = 128 + (directions.x * 36); outputs.leftStickY = 128 + (directions.y * 53); } - // (53.65) = 39 53 - if (inputs.c_left) { - outputs.leftStickX = 128 + (directions.x * 49); - outputs.leftStickY = 128 + (directions.y * 53); + + // MY Firefox angles + if (inputs.rf1) { + outputs.leftStickX = 128 + (directions.x * 28); + outputs.leftStickY = 128 + (directions.y * 96); } - // (59.68) = 31 53 - if (inputs.c_up) { - outputs.leftStickX = 128 + (directions.x * 31); - outputs.leftStickY = 128 + (directions.y * 53); + + if (inputs.rt2) { + outputs.leftStickX = 128 + (directions.x * 40); + outputs.leftStickY = 128 + (directions.y * 99); } - // (62.15) = 28 53 - if (inputs.c_right) { - outputs.leftStickX = 128 + (directions.x * 28); - outputs.leftStickY = 128 + (directions.y * 53); + + if (inputs.rt3) { + outputs.leftStickX = 128 + (directions.x * 50); + outputs.leftStickY = 128 + (directions.y * 95); } - /* Extended Up B Angles */ - if (inputs.b) { - // (56.71) = 44 67 - outputs.leftStickX = 128 + (directions.x * 44); - outputs.leftStickY = 128 + (directions.y * 67); - // (50.62) = 55 67 - if (inputs.c_down) { - outputs.leftStickX = 128 + (directions.x * 55); - outputs.leftStickY = 128 + (directions.y * 67); - } - // (53.82) = 49 67 - if (inputs.c_left) { - outputs.leftStickX = 128 + (directions.x * 49); - outputs.leftStickY = 128 + (directions.y * 67); - } - // (59.8) = 39 67 - if (inputs.c_up) { - outputs.leftStickX = 128 + (directions.x * 39); - outputs.leftStickY = 128 + (directions.y * 67); - } - // (62.42) = 35 67 - if (inputs.c_right) { - outputs.leftStickX = 128 + (directions.x * 35); - outputs.leftStickY = 128 + (directions.y * 67); - } + if (inputs.rt4) { + outputs.leftStickX = 128 + (directions.x * 57); + outputs.leftStickY = 128 + (directions.y * 86); } - // MY Pivot Uptilt/Dtilt - if (inputs.a) { - outputs.leftStickX = 128 + (directions.x * 34); - outputs.leftStickY = 128 + (directions.y * 38); + if (inputs.rt5) { + outputs.leftStickX = 128 + (directions.x * 61); + outputs.leftStickY = 128 + (directions.y * 72); } } } + /* if (inputs.lf5) { + // Up2 Nair + outputs.leftStickX = 128; + outputs.leftStickY = 128; + } */ + // C-stick ASDI Slideoff angle overrides any other C-stick modifiers (such as // angled fsmash). - if (directions.cx != 0 && directions.cy != 0) { - // 5250 8500 = 42 68 - outputs.rightStickX = 128 + (directions.cx * 42); - outputs.rightStickY = 128 + (directions.cy * 68); + // We don't apply this for c-up + c-left/c-right in case we want to implement + // C-stick nair somehow. + if (directions.cx != 0 && directions.cy == -1) { + // 3000 9875 + outputs.rightStickX = 128 + (directions.cx * 35); + outputs.rightStickY = 128 + (directions.cy * 98); } - if (inputs.l) { + if (inputs.lf4) { outputs.triggerLAnalog = 140; } - if (inputs.r) { + if (inputs.rf5) { outputs.triggerRAnalog = 140; } // Shut off C-stick when using D-Pad layer. - if ((inputs.mod_x && inputs.mod_y) || inputs.nunchuk_c) { + if ((inputs.lt1 && inputs.lt2) || inputs.nunchuk_c) { outputs.rightStickX = 128; outputs.rightStickY = 128; } + // Turns off Start when holding Mod X or Mod Y + if ((inputs.lt1 || inputs.lt2)) { + outputs.start = false; + } + // Nunchuk overrides left stick. if (inputs.nunchuk_connected) { outputs.leftStickX = inputs.nunchuk_x; diff --git a/src/modes/extra/DarkSouls.cpp b/src/modes/extra/DarkSouls.cpp index 3e432120..62298aa3 100644 --- a/src/modes/extra/DarkSouls.cpp +++ b/src/modes/extra/DarkSouls.cpp @@ -4,58 +4,50 @@ #define ANALOG_STICK_NEUTRAL 128 #define ANALOG_STICK_MAX 255 -DarkSouls::DarkSouls(socd::SocdType socd_type) { - _socd_pair_count = 4; - _socd_pairs = new socd::SocdPair[_socd_pair_count]{ - socd::SocdPair{&InputState::left, &InputState::right, socd_type}, - socd::SocdPair{ &InputState::down, &InputState::mod_x, socd_type}, - socd::SocdPair{ &InputState::c_left, &InputState::c_right, socd_type}, - socd::SocdPair{ &InputState::c_down, &InputState::c_up, socd_type}, - }; -} +DarkSouls::DarkSouls() : ControllerMode() {} -void DarkSouls::UpdateDigitalOutputs(InputState &inputs, OutputState &outputs) { - outputs.y = inputs.y; - outputs.x = inputs.r; +void DarkSouls::UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs) { + outputs.y = inputs.rf6; + outputs.x = inputs.rf5; // Base layer. - bool layer0 = !inputs.x && !inputs.nunchuk_c; + bool layer0 = !inputs.rf2 && !inputs.nunchuk_c; // Secondary layer when X is held. - bool layerX = inputs.x && !inputs.nunchuk_c; + bool layerX = inputs.rf2 && !inputs.nunchuk_c; // DPad layer when Nunchuk C is held. bool layerC = inputs.nunchuk_c; if (layer0) { - outputs.a = inputs.a; - outputs.b = inputs.b; - outputs.buttonR = inputs.z; - outputs.buttonL = inputs.up; - outputs.start = inputs.start | inputs.nunchuk_z; + outputs.a = inputs.rt1; + outputs.b = inputs.rf1; + outputs.buttonR = inputs.rf3; + outputs.buttonL = inputs.rf4; + outputs.start = inputs.mb1 | inputs.nunchuk_z; } else if (layerX) { - outputs.rightStickClick = inputs.a; - outputs.triggerRDigital = inputs.z; - outputs.triggerLDigital = inputs.up; - outputs.select = inputs.start; + outputs.rightStickClick = inputs.rt1; + outputs.triggerRDigital = inputs.rf3; + outputs.triggerLDigital = inputs.rf4; + outputs.select = inputs.mb1; } else if (layerC) { - outputs.a = inputs.a; - outputs.dpadLeft = inputs.b; - outputs.dpadDown = inputs.x; - outputs.dpadUp = inputs.z; - outputs.dpadRight = inputs.up; + outputs.a = inputs.rt1; + outputs.dpadLeft = inputs.rf1; + outputs.dpadDown = inputs.rf2; + outputs.dpadUp = inputs.rf3; + outputs.dpadRight = inputs.rf4; outputs.select = inputs.nunchuk_z; } } -void DarkSouls::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { +void DarkSouls::UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs) { UpdateDirections( - inputs.left, - inputs.right, - inputs.down, - inputs.mod_x, - inputs.c_left, - inputs.c_right, - inputs.c_down, - inputs.c_up, + inputs.lf3, + inputs.lf1, + inputs.lf2, + inputs.lt1, + inputs.rt3, + inputs.rt5, + inputs.rt2, + inputs.rt4, ANALOG_STICK_MIN, ANALOG_STICK_NEUTRAL, ANALOG_STICK_MAX, diff --git a/src/modes/extra/HollowKnight.cpp b/src/modes/extra/HollowKnight.cpp index 5cd66e83..ef339b9b 100644 --- a/src/modes/extra/HollowKnight.cpp +++ b/src/modes/extra/HollowKnight.cpp @@ -4,40 +4,32 @@ #define ANALOG_STICK_NEUTRAL 128 #define ANALOG_STICK_MAX 255 -HollowKnight::HollowKnight(socd::SocdType socd_type) { - _socd_pair_count = 4; - _socd_pairs = new socd::SocdPair[_socd_pair_count]{ - socd::SocdPair{&InputState::left, &InputState::right, socd_type}, - socd::SocdPair{ &InputState::down, &InputState::mod_x, socd_type}, - socd::SocdPair{ &InputState::c_left, &InputState::c_right, socd_type}, - socd::SocdPair{ &InputState::c_down, &InputState::c_up, socd_type}, - }; -} +HollowKnight::HollowKnight() : ControllerMode() {} -void HollowKnight::UpdateDigitalOutputs(InputState &inputs, OutputState &outputs) { - outputs.a = inputs.a; // Attack - outputs.b = inputs.b; // Dash - outputs.x = inputs.x; // Jump - outputs.y = inputs.mod_y; // Quick Cast - outputs.triggerLDigital = inputs.r; // Focus / Cast - outputs.triggerRDigital = inputs.z; // C-Dash - outputs.buttonR = inputs.up; // Dream Nail +void HollowKnight::UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs) { + outputs.a = inputs.rt1; // Attack + outputs.b = inputs.rf1; // Dash + outputs.x = inputs.rf2; // Jump + outputs.y = inputs.lt2; // Quick Cast + outputs.triggerLDigital = inputs.rf5; // Focus / Cast + outputs.triggerRDigital = inputs.rf3; // C-Dash + outputs.buttonR = inputs.rf4; // Dream Nail - outputs.buttonL = inputs.lightshield; // Map - outputs.select = inputs.midshield; // Inventory - outputs.start = inputs.start; // Pause + outputs.buttonL = inputs.rf7; // Map + outputs.select = inputs.rf8; // Inventory + outputs.start = inputs.mb1; // Pause } -void HollowKnight::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { +void HollowKnight::UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs) { UpdateDirections( - inputs.left, - inputs.right, - inputs.down, - inputs.mod_x, - inputs.c_left, - inputs.c_right, - inputs.c_down, - inputs.c_up, + inputs.lf3, + inputs.lf1, + inputs.lf2, + inputs.lt1, + inputs.rt3, + inputs.rt5, + inputs.rt2, + inputs.rt4, ANALOG_STICK_MIN, ANALOG_STICK_NEUTRAL, ANALOG_STICK_MAX, diff --git a/src/modes/extra/MKWii.cpp b/src/modes/extra/MKWii.cpp index 8bef561a..ca8bd31b 100644 --- a/src/modes/extra/MKWii.cpp +++ b/src/modes/extra/MKWii.cpp @@ -4,32 +4,24 @@ #define ANALOG_STICK_NEUTRAL 128 #define ANALOG_STICK_MAX 255 -MKWii::MKWii(socd::SocdType socd_type) { - _socd_pair_count = 4; - _socd_pairs = new socd::SocdPair[_socd_pair_count]{ - socd::SocdPair{&InputState::left, &InputState::right, socd_type}, - socd::SocdPair{ &InputState::l, &InputState::down, socd_type}, - socd::SocdPair{ &InputState::l, &InputState::mod_x, socd_type}, - socd::SocdPair{ &InputState::l, &InputState::mod_y, socd_type}, - }; -} +MKWii::MKWii() : ControllerMode() {} -void MKWii::UpdateDigitalOutputs(InputState &inputs, OutputState &outputs) { - outputs.a = inputs.b; - outputs.b = inputs.x; - outputs.triggerLDigital = inputs.z; - outputs.buttonR = inputs.up; - outputs.dpadUp = inputs.a; - outputs.start = inputs.start; +void MKWii::UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs) { + outputs.a = inputs.rf1; + outputs.b = inputs.rf2; + outputs.triggerLDigital = inputs.rf3; + outputs.buttonR = inputs.rf4; + outputs.dpadUp = inputs.rt1; + outputs.start = inputs.mb1; } -void MKWii::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { - bool up = inputs.down || inputs.mod_x || inputs.mod_y; +void MKWii::UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs) { + bool up = inputs.lf2 || inputs.lt1 || inputs.lt2; UpdateDirections( - inputs.left, - inputs.right, - inputs.l, + inputs.lf3, + inputs.lf1, + inputs.lf4, up, false, false, @@ -41,7 +33,7 @@ void MKWii::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { outputs ); - if (inputs.z) { + if (inputs.rf3) { outputs.triggerLAnalog = 140; } diff --git a/src/modes/extra/Melee20Button [buffed].cpp b/src/modes/extra/Melee20Button [buffed].cpp new file mode 100644 index 00000000..84a1dae3 --- /dev/null +++ b/src/modes/extra/Melee20Button [buffed].cpp @@ -0,0 +1,279 @@ +// #include "modes/Melee20Button.hpp" + +// #define ANALOG_STICK_MIN 48 +// #define ANALOG_STICK_NEUTRAL 128 +// #define ANALOG_STICK_MAX 208 + +// Melee20Button::Melee20Button() : ControllerMode() { +// _horizontal_socd = false; +// } + +// void Melee20Button::SetConfig(GameModeConfig &config, const MeleeOptions options) { +// InputMode::SetConfig(config); +// _options = options; +// } + +// void Melee20Button::HandleSocd(InputState &inputs) { +// _horizontal_socd = inputs.lf3 && inputs.lf1; +// InputMode::HandleSocd(inputs); +// } + +// void Melee20Button::UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs) { +// outputs.a = inputs.rt1; +// outputs.b = inputs.rf1; +// outputs.x = inputs.rf2; +// outputs.y = inputs.rf6; +// outputs.buttonR = inputs.rf3; +// if (inputs.nunchuk_connected) { +// outputs.triggerLDigital = inputs.nunchuk_z; +// } else { +// outputs.triggerLDigital = inputs.lf4; +// } +// outputs.triggerRDigital = inputs.rf5; +// outputs.start = inputs.mb1; + +// // Activate D-Pad layer by holding Mod X + Mod Y or Nunchuk C button. +// if ((inputs.lt1 && inputs.lt2) || inputs.nunchuk_c) { +// outputs.dpadUp = inputs.rt4; +// outputs.dpadDown = inputs.rt2; +// outputs.dpadLeft = inputs.rt3; +// outputs.dpadRight = inputs.rt5; +// } + +// if (inputs.mb3) +// outputs.dpadLeft = true; +// if (inputs.mb2) +// outputs.dpadRight = true; +// } + +// void Melee20Button::UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs) { +// // Coordinate calculations to make modifier handling simpler. +// UpdateDirections( +// inputs.lf3, // Left +// inputs.lf1, // Right +// inputs.lf2, // Down +// inputs.rf4, // Up +// inputs.rt3, // C-Left +// inputs.rt5, // C-Right +// inputs.rt2, // C-Down +// inputs.rt4, // C-Up +// ANALOG_STICK_MIN, +// ANALOG_STICK_NEUTRAL, +// ANALOG_STICK_MAX, +// outputs +// ); + +// bool shield_button_pressed = inputs.lf4 || inputs.rf5 || inputs.rf7 || inputs.rf8; + +// if (directions.diagonal) { +// // q1/2 = 7000 7000 +// outputs.leftStickX = 128 + (directions.x * 56); +// outputs.leftStickY = 128 + (directions.y * 56); +// // L, R, LS, and MS + q3/4 = 7000 6875 (For vanilla shield drop. Gives 44.5 +// // degree wavedash). Also used as default q3/4 diagonal if crouch walk option select is +// // enabled. +// if (directions.y == -1) { +// /* // DBooC +// if (!inputs.rf1) { +// outputs.leftStickX = 128 + (directions.x * 80); +// outputs.leftStickY = 128 + (directions.y * 60); +// } */ + +// if (shield_button_pressed) { +// outputs.leftStickX = 128 + (directions.x * 56); +// outputs.leftStickY = 128 + (directions.y * 55); +// } +// } +// } + +// if (inputs.lt1) { +// // MX + Horizontal (even if shield is held) = 6625 = 53 +// if (directions.horizontal) { +// outputs.leftStickX = 128 + (directions.x * 53); +// } +// // MX + Vertical (even if shield is held) = 5375 = 43 +// if (directions.vertical) { +// outputs.leftStickY = 128 + (directions.y * 43); + +// // shine on platform without falling through +// if (inputs.rf1) { +// outputs.leftStickY = 128 + (directions.y * 45); +// } +// } +// if (directions.diagonal && shield_button_pressed) { +// // 9500 2875 = 76 23 +// outputs.leftStickX = 128 + (directions.x * 76); +// outputs.leftStickY = 128 + (directions.y * 23); +// } + +// if (directions.diagonal && !shield_button_pressed) { +// // 22.9638 - 7375 3125 = 59 25 +// outputs.leftStickX = 128 + (directions.x * 59); +// outputs.leftStickY = 128 + (directions.y * 25); + +// /* Up B Angles */ +// if (inputs.rf1) { +// outputs.leftStickX = 128 + (directions.x * 76); +// outputs.leftStickY = 128 + (directions.y * 23); +// } +// if (inputs.rt2) { +// outputs.leftStickX = 128 + (directions.x * 75); +// outputs.leftStickY = 128 + (directions.y * 31); +// } +// if (inputs.rt3) { +// outputs.leftStickX = 128 + (directions.x * 73); +// outputs.leftStickY = 128 + (directions.y * 39); +// } +// if (inputs.rt4) { +// outputs.leftStickX = 128 + (directions.x * 69); +// outputs.leftStickY = 128 + (directions.y * 46); +// } +// if (inputs.rt5) { +// outputs.leftStickX = 128 + (directions.x * 50); +// outputs.leftStickY = 128 + (directions.y * 41); +// } + +// } + +// // Angled fsmash +// if (directions.cx != 0) { +// // 8500 5250 = 68 42 +// outputs.rightStickX = 128 + (directions.cx * 68); +// outputs.rightStickY = 128 + (directions.y * 42); +// } +// } + +// if (inputs.lt2) { +// // MY + Horizontal (even if shield is held) = 3375 = 27 +// if (directions.horizontal) { +// outputs.leftStickX = 128 + (directions.x * 27); +// } +// // MY + Vertical = 7375 = 59 +// if (directions.vertical) { +// if (directions.y == 1) { +// outputs.leftStickY = 128 + (directions.y * 52); +// } +// if (directions.y == -1) { +// outputs.leftStickY = 128 + (directions.y * 59); +// } + +// if (shield_button_pressed) { +// outputs.leftStickY = 128 + (directions.y * 55); +// } +// } +// if (directions.diagonal && shield_button_pressed) { +// // MY + L, R, LS, and MS + q1/2 = 4750 8750 = 38 70 +// outputs.leftStickX = 128 + (directions.x * 30); +// outputs.leftStickY = 128 + (directions.y * 55); +// // MY + L, R, LS, and MS + q3/4 = 5000 8500 = 40 68 + +// // Parasol dash +// if (directions.y == 1 && (inputs.rf2 || inputs.rf6)) { +// outputs.leftStickX = 128 + (directions.x * 40); +// outputs.leftStickY = 128 + (directions.y * 68); +// } + +// /* // steepest wavedash +// if (directions.y == -1) { +// // 2875 9500 = 23 76 +// outputs.leftStickX = 128 + (directions.x * 23); +// outputs.leftStickY = 128 + (directions.y * 76); +// } */ +// } + + +// if (directions.diagonal && !shield_button_pressed) { +// // buffered turnaround vertical tilts +// if (directions.y == -1) { +// outputs.leftStickX = 128 + (directions.x * 23); +// outputs.leftStickY = 128 + (directions.y * 53); +// } + +// if (directions.y == 1) { +// outputs.leftStickX = 128 + (directions.x * 23); +// outputs.leftStickY = 128 + (directions.y * 52); +// } + +// /* Up B Angles */ +// if (inputs.rf1) { +// outputs.leftStickX = 128 + (directions.x * 23); +// outputs.leftStickY = 128 + (directions.y * 76); +// } +// if (inputs.rt2) { +// outputs.leftStickX = 128 + (directions.x * 31); +// outputs.leftStickY = 128 + (directions.y * 75); +// } +// if (inputs.rt3) { +// outputs.leftStickX = 128 + (directions.x * 39); +// outputs.leftStickY = 128 + (directions.y * 75); +// } +// if (inputs.rt4) { +// outputs.leftStickX = 128 + (directions.x * 46); +// outputs.leftStickY = 128 + (directions.y * 69); +// } +// if (inputs.rt5) { +// outputs.leftStickX = 128 + (directions.x * 41); +// outputs.leftStickY = 128 + (directions.y * 50); +// } +// } + +// // Slight angled fsmash +// if (directions.cx != 0) { +// // 9500 2875 = 76 23 +// outputs.rightStickX = 128 + (directions.cx * 76); +// outputs.rightStickY = 128 + (directions.y * 23); +// } +// } + +// /* if (inputs.lf5) { +// // Up2 Nair +// outputs.leftStickX = 128; +// outputs.leftStickY = 128; +// } */ + +// // C-stick ASDI Slideoff angle overrides any other C-stick modifiers (such as +// // angled fsmash). +// if (directions.cx != 0 && directions.cy != 0) { +// // 2875 9500 = 23 76 +// outputs.rightStickX = 128 + (directions.cx * 23); +// outputs.rightStickY = 128 + (directions.cy * 76); +// } + +// if (inputs.rf7) { +// outputs.triggerRAnalog = 43; +// } +// if (inputs.rf8) { +// outputs.triggerRAnalog = 94; +// } + +// if (outputs.triggerLDigital) { +// outputs.triggerLAnalog = 140; +// } +// if (outputs.triggerRDigital) { +// outputs.triggerRAnalog = 140; +// } + +// // Shut off C-stick when using D-Pad layer. +// if ((inputs.lt1 && inputs.lt2) || inputs.nunchuk_c) { +// outputs.rightStickX = 128; +// outputs.rightStickY = 128; + +// // slight angled ftilts +// if (inputs.rt1 && directions.diagonal) { +// // 9500 2875 = 76 23 +// outputs.leftStickX = 128 + (directions.x * 76); +// outputs.leftStickY = 128 + (directions.y * 23); +// } + +// // Pikachu/Pichu double up-special +// if (inputs.rf1 && inputs.rf4) { +// outputs.leftStickY = 128 - (directions.y * 40); +// } +// } + +// // Nunchuk overrides left stick. +// if (inputs.nunchuk_connected) { +// outputs.leftStickX = inputs.nunchuk_x; +// outputs.leftStickY = inputs.nunchuk_y; +// } +// } \ No newline at end of file diff --git a/src/modes/extra/MultiVersus.cpp b/src/modes/extra/MultiVersus.cpp index 83ffe989..3fe3b8df 100644 --- a/src/modes/extra/MultiVersus.cpp +++ b/src/modes/extra/MultiVersus.cpp @@ -4,95 +4,87 @@ #define ANALOG_STICK_NEUTRAL 128 #define ANALOG_STICK_MAX 255 -MultiVersus::MultiVersus(socd::SocdType socd_type) { - _socd_pair_count = 4; - _socd_pairs = new socd::SocdPair[_socd_pair_count]{ - socd::SocdPair{&InputState::left, &InputState::right, socd_type}, - socd::SocdPair{ &InputState::down, &InputState::up, socd_type}, - socd::SocdPair{ &InputState::c_left, &InputState::c_right, socd_type}, - socd::SocdPair{ &InputState::c_down, &InputState::c_up, socd_type}, - }; -} +MultiVersus::MultiVersus() : ControllerMode() {} -void MultiVersus::UpdateDigitalOutputs(InputState &inputs, OutputState &outputs) { +void MultiVersus::UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs) { // Bind X and Y to "jump" in-game. - outputs.x = inputs.x; - outputs.y = inputs.y; + outputs.x = inputs.rf2; + outputs.y = inputs.rf6; - outputs.start = !inputs.mod_y && inputs.start; + outputs.start = !inputs.lt2 && inputs.mb1; // Select, MS, or MY + Start for "Reset" in the Lab. Not supported by GameCube adapter. - outputs.select = inputs.select || inputs.midshield || (inputs.mod_y && inputs.start); + outputs.select = inputs.mb3 || inputs.rf8 || (inputs.lt2 && inputs.mb1); // Home not supported by GameCube adapter. - outputs.home = inputs.home; + outputs.home = inputs.mb2; // L or Nunchuk Z = LT. Bind to "dodge" in-game. if (inputs.nunchuk_connected) { outputs.triggerLDigital = inputs.nunchuk_z; } else { - outputs.triggerLDigital = inputs.l; + outputs.triggerLDigital = inputs.lf4; } // R = RT. Can be bound to "pickup item" or left unbound. - outputs.triggerRDigital = inputs.r; + outputs.triggerRDigital = inputs.rf5; - if (!inputs.mod_x) { + if (!inputs.lt1) { // Bind A to "attack" in-game. - outputs.a = inputs.a; + outputs.a = inputs.rt1; // Bind B to "special" in-game. - outputs.b = inputs.b; + outputs.b = inputs.rf1; // Z = RB. Bind to "dodge" in-game. - outputs.buttonR = inputs.z; + outputs.buttonR = inputs.rf3; // LS = LB. Not supported by GameCube adapter. - outputs.buttonL = inputs.lightshield; + outputs.buttonL = inputs.rf7; } // MX activates a layer for "neutral" binds. Uses D-Pad buttons. - if (inputs.mod_x && !inputs.mod_y) { + if (inputs.lt1 && !inputs.lt2) { // MX + A = D-Pad Left. Bind to "neutral attack" in-game. - outputs.dpadLeft = inputs.a; + outputs.dpadLeft = inputs.rt1; // MX + B = D-Pad Right. Bind to "neutral special" in-game. - outputs.dpadRight = inputs.b; + outputs.dpadRight = inputs.rf1; // MX + Z = D-Pad Down. Bind to "neutral evade" in-game. - outputs.dpadDown = inputs.z; + outputs.dpadDown = inputs.rf3; // MX + LS = D-Pad Up. Bind to "taunt 1" in-game. - outputs.dpadUp = inputs.lightshield; + outputs.dpadUp = inputs.rf7; } // MY activates C-Stick to D-Pad conversion. - if (inputs.mod_y && !inputs.mod_x) { - outputs.dpadLeft = inputs.c_left; - outputs.dpadRight = inputs.c_right; - outputs.dpadDown = inputs.c_down; - outputs.dpadUp = inputs.c_up; + if (inputs.lt2 && !inputs.lt1) { + outputs.dpadLeft = inputs.rt3; + outputs.dpadRight = inputs.rt5; + outputs.dpadDown = inputs.rt2; + outputs.dpadUp = inputs.rt4; } } -void MultiVersus::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { +void MultiVersus::UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs) { // Coordinate calculations to make modifier handling simpler. UpdateDirections( - inputs.left, - inputs.right, - inputs.down, - inputs.up, - inputs.c_left, - inputs.c_right, - inputs.c_down, - inputs.c_up, + inputs.lf3, // Left + inputs.lf1, // Right + inputs.lf2, // Down + inputs.rf4, // Up + inputs.rt3, // C-Left + inputs.rt5, // C-Right + inputs.rt2, // C-Down + inputs.rt4, // C-Up ANALOG_STICK_MIN, ANALOG_STICK_NEUTRAL, ANALOG_STICK_MAX, outputs ); - if (inputs.mod_y && !inputs.mod_x) { + if (inputs.lt2 && !inputs.lt1) { // MY slows down the cursor for easier menu navigation. // Menu cursor speed can also be turned down in-game under "Interface" settings. // 128 ± 76 results in the slowest cursor that still actuates directional inputs in-game. diff --git a/src/modes/extra/RocketLeague.cpp b/src/modes/extra/RocketLeague.cpp index e10299ec..0706f22d 100644 --- a/src/modes/extra/RocketLeague.cpp +++ b/src/modes/extra/RocketLeague.cpp @@ -4,67 +4,59 @@ #define ANALOG_STICK_NEUTRAL 128 #define ANALOG_STICK_MAX 255 -RocketLeague::RocketLeague(socd::SocdType socd_type) { - _socd_pair_count = 4; - _socd_pairs = new socd::SocdPair[_socd_pair_count]{ - socd::SocdPair{&InputState::left, &InputState::right, socd_type }, - socd::SocdPair{ &InputState::down, &InputState::mod_x, socd::SOCD_DIR2_PRIORITY}, - socd::SocdPair{ &InputState::c_left, &InputState::c_right, socd_type }, - socd::SocdPair{ &InputState::c_down, &InputState::c_up, socd_type }, - }; -} +RocketLeague::RocketLeague() : ControllerMode() {} -void RocketLeague::UpdateDigitalOutputs(InputState &inputs, OutputState &outputs) { - outputs.a = inputs.a; - outputs.b = inputs.b; - outputs.x = inputs.midshield; - outputs.y = inputs.up; - outputs.buttonL = inputs.l; - outputs.buttonR = inputs.lightshield; - outputs.triggerLDigital = inputs.z; - outputs.triggerRDigital = inputs.x; - outputs.leftStickClick = inputs.r; +void RocketLeague::UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs) { + outputs.a = inputs.rt1; + outputs.b = inputs.rf1; + outputs.x = inputs.rf8; + outputs.y = inputs.rf4; + outputs.buttonL = inputs.lf4; + outputs.buttonR = inputs.rf7; + outputs.triggerLDigital = inputs.rf3; + outputs.triggerRDigital = inputs.rf2; + outputs.leftStickClick = inputs.rf5; // Hold accelerate and reverse simultaneously for rear view. - if (inputs.z && inputs.x) { + if (inputs.rf3 && inputs.rf2) { outputs.rightStickClick = true; // Override (deactivate) accelerator. outputs.triggerRDigital = false; } // MX + Start = Select - if (inputs.mod_x) - outputs.select = inputs.start; + if (inputs.lt1) + outputs.select = inputs.mb1; else - outputs.start = inputs.start; + outputs.start = inputs.mb1; // D-Pad - if (inputs.mod_x && inputs.mod_y) { - outputs.dpadUp = inputs.c_up; - outputs.dpadDown = inputs.c_down; - outputs.dpadLeft = inputs.c_left; - outputs.dpadRight = inputs.c_right; + if (inputs.lt1 && inputs.lt2) { + outputs.dpadUp = inputs.rt4; + outputs.dpadDown = inputs.rt2; + outputs.dpadLeft = inputs.rt3; + outputs.dpadRight = inputs.rt5; } } -void RocketLeague::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { +void RocketLeague::UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs) { // Coordinate calculations to make modifier handling simpler. UpdateDirections( - inputs.left, - inputs.right, - inputs.down, - inputs.mod_x, - inputs.c_left, - inputs.c_right, - inputs.c_down, - inputs.c_up, + inputs.lf3, + inputs.lf1, + inputs.lf2, + inputs.lt1, + inputs.rt3, + inputs.rt5, + inputs.rt2, + inputs.rt4, ANALOG_STICK_MIN, ANALOG_STICK_NEUTRAL, ANALOG_STICK_MAX, outputs ); - if (inputs.mod_y) { + if (inputs.lt2) { if (directions.diagonal) { outputs.leftStickX = ANALOG_STICK_NEUTRAL + (directions.x * 70); // outputs.leftStickY = @@ -80,7 +72,7 @@ void RocketLeague::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) } // Shut off right stick when using dpad layer. - if (inputs.mod_x && inputs.mod_y) { + if (inputs.lt1 && inputs.lt2) { outputs.rightStickX = ANALOG_STICK_NEUTRAL; outputs.rightStickY = ANALOG_STICK_NEUTRAL; } diff --git a/src/modes/extra/SaltAndSanctuary.cpp b/src/modes/extra/SaltAndSanctuary.cpp index d1581817..5e6f1ccc 100644 --- a/src/modes/extra/SaltAndSanctuary.cpp +++ b/src/modes/extra/SaltAndSanctuary.cpp @@ -4,46 +4,38 @@ #define ANALOG_STICK_NEUTRAL 128 #define ANALOG_STICK_MAX 255 -SaltAndSanctuary::SaltAndSanctuary(socd::SocdType socd_type) { - _socd_pair_count = 4; - _socd_pairs = new socd::SocdPair[_socd_pair_count]{ - socd::SocdPair{&InputState::left, &InputState::right, socd_type}, - socd::SocdPair{ &InputState::down, &InputState::mod_x, socd_type}, - socd::SocdPair{ &InputState::c_left, &InputState::c_right, socd_type}, - socd::SocdPair{ &InputState::c_down, &InputState::c_up, socd_type}, - }; -} +SaltAndSanctuary::SaltAndSanctuary() : ControllerMode() {} -void SaltAndSanctuary::UpdateDigitalOutputs(InputState &inputs, OutputState &outputs) { - outputs.dpadRight = inputs.l; // Block - outputs.b = inputs.b; // Roll - outputs.a = inputs.a; // Attack - outputs.y = inputs.z; // Strong - outputs.dpadDown = inputs.mod_y; // Use - outputs.x = inputs.x; // Jump +void SaltAndSanctuary::UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs) { + outputs.dpadRight = inputs.lf4; // Block + outputs.b = inputs.rf1; // Roll + outputs.a = inputs.rt1; // Attack + outputs.y = inputs.rf3; // Strong + outputs.dpadDown = inputs.lt2; // Use + outputs.x = inputs.rf2; // Jump - outputs.buttonL = inputs.r; // Previous item - outputs.buttonR = inputs.y; // Next item - outputs.triggerLDigital = inputs.lightshield; // Use item + outputs.buttonL = inputs.rf5; // Previous item + outputs.buttonR = inputs.rf6; // Next item + outputs.triggerLDigital = inputs.rf7; // Use item - outputs.triggerRDigital = inputs.midshield; // Use torch + outputs.triggerRDigital = inputs.rf8; // Use torch - outputs.dpadLeft = inputs.up; // Switch loadout + outputs.dpadLeft = inputs.rf4; // Switch loadout - outputs.start = inputs.start; // Inventory + outputs.start = inputs.mb1; // Inventory } -void SaltAndSanctuary::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { +void SaltAndSanctuary::UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs) { // Coordinate calculations to make modifier handling simpler. UpdateDirections( - inputs.left, - inputs.right, - inputs.down, - inputs.mod_x, - inputs.c_left, - inputs.c_right, - inputs.c_down, - inputs.c_up, + inputs.lf3, + inputs.lf1, + inputs.lf2, + inputs.lt1, + inputs.rt3, + inputs.rt5, + inputs.rt2, + inputs.rt4, ANALOG_STICK_MIN, ANALOG_STICK_NEUTRAL, ANALOG_STICK_MAX, diff --git a/src/modes/extra/ShovelKnight.cpp b/src/modes/extra/ShovelKnight.cpp index e956a7bf..14ebd16f 100644 --- a/src/modes/extra/ShovelKnight.cpp +++ b/src/modes/extra/ShovelKnight.cpp @@ -4,43 +4,35 @@ #define ANALOG_STICK_NEUTRAL 128 #define ANALOG_STICK_MAX 255 -ShovelKnight::ShovelKnight(socd::SocdType socd_type) { - _socd_pair_count = 4; - _socd_pairs = new socd::SocdPair[_socd_pair_count]{ - socd::SocdPair{&InputState::left, &InputState::right, socd_type}, - socd::SocdPair{ &InputState::down, &InputState::mod_x, socd_type}, - socd::SocdPair{ &InputState::c_left, &InputState::c_right, socd_type}, - socd::SocdPair{ &InputState::c_down, &InputState::c_up, socd_type}, - }; -} +ShovelKnight::ShovelKnight() : ControllerMode() {} -void ShovelKnight::UpdateDigitalOutputs(InputState &inputs, OutputState &outputs) { - outputs.dpadLeft = inputs.left; - outputs.dpadRight = inputs.right; - outputs.dpadDown = inputs.down; - outputs.dpadUp = inputs.mod_x; +void ShovelKnight::UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs) { + outputs.dpadLeft = inputs.lf3; + outputs.dpadRight = inputs.lf1; + outputs.dpadDown = inputs.lf2; + outputs.dpadUp = inputs.lt1; - outputs.b = inputs.x; // Jump - outputs.a = inputs.a; // Attack - outputs.y = inputs.b; // Attack - outputs.x = inputs.z; // Subweapon - outputs.buttonL = inputs.r; // Subweapon prev - outputs.buttonR = inputs.y; // Subweapon next + outputs.b = inputs.rf2; // Jump + outputs.a = inputs.rt1; // Attack + outputs.y = inputs.rf1; // Attack + outputs.x = inputs.rf3; // Subweapon + outputs.buttonL = inputs.rf5; // Subweapon prev + outputs.buttonR = inputs.rf6; // Subweapon next - outputs.select = inputs.lightshield; // Inventory - outputs.start = inputs.start; // Pause + outputs.select = inputs.rf7; // Inventory + outputs.start = inputs.mb1; // Pause } -void ShovelKnight::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { +void ShovelKnight::UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs) { UpdateDirections( - inputs.left, - inputs.right, - inputs.down, - inputs.mod_x, - inputs.c_left, - inputs.c_right, - inputs.c_down, - inputs.c_up, + inputs.lf3, + inputs.lf1, + inputs.lf2, + inputs.lt1, + inputs.rt3, + inputs.rt5, + inputs.rt2, + inputs.rt4, ANALOG_STICK_MIN, ANALOG_STICK_NEUTRAL, ANALOG_STICK_MAX, diff --git a/src/modes/extra/ToughLoveArena.cpp b/src/modes/extra/ToughLoveArena.cpp index e1d765d3..3e20768a 100644 --- a/src/modes/extra/ToughLoveArena.cpp +++ b/src/modes/extra/ToughLoveArena.cpp @@ -1,16 +1,11 @@ #include "modes/extra/ToughLoveArena.hpp" -ToughLoveArena::ToughLoveArena(socd::SocdType socd_type) { - _socd_pair_count = 1; - _socd_pairs = new socd::SocdPair[_socd_pair_count]{ - socd::SocdPair{&InputState::left, &InputState::right, socd_type}, - }; -} +ToughLoveArena::ToughLoveArena() : KeyboardMode() {} -void ToughLoveArena::UpdateKeys(InputState &inputs) { - Press('s', inputs.left); - Press('d', inputs.right); - Press('j', inputs.b); - Press('k', inputs.x); - Press('l', inputs.z); +void ToughLoveArena::UpdateKeys(const InputState &inputs) { + Press(HID_KEY_S, inputs.lf3); + Press(HID_KEY_D, inputs.lf1); + Press(HID_KEY_J, inputs.rf1); + Press(HID_KEY_K, inputs.rf2); + Press(HID_KEY_L, inputs.rf3); } diff --git a/src/modes/extra/Ultimate2.cpp b/src/modes/extra/Ultimate2.cpp index 666dd94c..c48b9bcb 100644 --- a/src/modes/extra/Ultimate2.cpp +++ b/src/modes/extra/Ultimate2.cpp @@ -5,60 +5,52 @@ #define ANALOG_STICK_NEUTRAL 128 #define ANALOG_STICK_MAX 228 -Ultimate2::Ultimate2(socd::SocdType socd_type) { - _socd_pair_count = 4; - _socd_pairs = new socd::SocdPair[_socd_pair_count]{ - socd::SocdPair{&InputState::left, &InputState::right, socd_type}, - socd::SocdPair{ &InputState::down, &InputState::up, socd_type}, - socd::SocdPair{ &InputState::c_left, &InputState::c_right, socd_type}, - socd::SocdPair{ &InputState::c_down, &InputState::c_up, socd_type}, - }; -} - -void Ultimate2::UpdateDigitalOutputs(InputState &inputs, OutputState &outputs) { - outputs.a = inputs.a; - outputs.b = inputs.b; - outputs.x = inputs.x; - outputs.y = inputs.y; - outputs.buttonR = inputs.z; - outputs.triggerLDigital = inputs.l; - outputs.triggerRDigital = inputs.r; - outputs.start = inputs.start; +Ultimate2::Ultimate2() : ControllerMode() {} + +void Ultimate2::UpdateDigitalOutputs(const InputState &inputs, OutputState &outputs) { + outputs.a = inputs.rt1; + outputs.b = inputs.rf1; + outputs.x = inputs.rf2; + outputs.y = inputs.rf6; + outputs.buttonR = inputs.rf3; + outputs.triggerLDigital = inputs.lf4; + outputs.triggerRDigital = inputs.rf5; + outputs.start = inputs.mb1; // Turn on D-Pad layer by holding Mod X + Mod Y, or Nunchuk C button. - if ((inputs.mod_x && inputs.mod_y) || inputs.nunchuk_c) { - outputs.dpadUp = inputs.c_up; - outputs.dpadDown = inputs.c_down; - outputs.dpadLeft = inputs.c_left; - outputs.dpadRight = inputs.c_right; + if ((inputs.lt1 && inputs.lt2) || inputs.nunchuk_c) { + outputs.dpadUp = inputs.rt4; + outputs.dpadDown = inputs.rt2; + outputs.dpadLeft = inputs.rt3; + outputs.dpadRight = inputs.rt5; } - if (inputs.select) + if (inputs.mb3) outputs.dpadLeft = true; - if (inputs.home) + if (inputs.mb2) outputs.dpadRight = true; } -void Ultimate2::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { +void Ultimate2::UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs) { // Coordinate calculations to make modifier handling simpler. UpdateDirections( - inputs.left, - inputs.right, - inputs.down, - inputs.up, - inputs.c_left, - inputs.c_right, - inputs.c_down, - inputs.c_up, + inputs.lf3, // Left + inputs.lf1, // Right + inputs.lf2, // Down + inputs.rf4, // Up + inputs.rt3, // C-Left + inputs.rt5, // C-Right + inputs.rt2, // C-Down + inputs.rt4, // C-Up ANALOG_STICK_MIN, ANALOG_STICK_NEUTRAL, ANALOG_STICK_MAX, outputs ); - bool shield_button_pressed = inputs.l || inputs.r || inputs.lightshield || inputs.midshield; + bool shield_button_pressed = inputs.lf4 || inputs.rf5 || inputs.rf7 || inputs.rf8; - if (inputs.mod_x) { + if (inputs.lt1) { // MX + Horizontal = 6625 = 53 if (directions.horizontal) { outputs.leftStickX = 128 + (directions.x * 53); @@ -67,7 +59,7 @@ void Ultimate2::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { outputs.leftStickX = 128 + (directions.x * 51); } // Horizontal Tilts = 36 - if (inputs.a) { + if (inputs.rt1) { outputs.leftStickX = 128 + (directions.x * 36); } } @@ -103,19 +95,19 @@ void Ultimate2::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { outputs.leftStickY = 128 + (directions.y * 40); // Angled Ftilts - if (inputs.a) { + if (inputs.rt1) { outputs.leftStickX = 128 + (directions.x * 36); outputs.leftStickY = 128 + (directions.y * 26); } } } - if (inputs.mod_y) { + if (inputs.lt2) { // MY + Horizontal (even if shield is held) = 41 if (directions.horizontal) { outputs.leftStickX = 128 + (directions.x * 41); // MY Horizontal Tilts - if (inputs.a) { + if (inputs.rt1) { outputs.leftStickX = 128 + (directions.x * 36); } } @@ -123,7 +115,7 @@ void Ultimate2::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { if (directions.vertical) { outputs.leftStickY = 128 + (directions.y * 44); // MY Vertical Tilts - if (inputs.a) { + if (inputs.rt1) { outputs.leftStickY = 128 + (directions.y * 36); } } @@ -150,7 +142,7 @@ void Ultimate2::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { outputs.leftStickY = 128 + (directions.y * 44); // MY Pivot Uptilt/Dtilt - if (inputs.a) { + if (inputs.rt1) { outputs.leftStickX = 128 + (directions.x * 34); outputs.leftStickY = 128 + (directions.y * 38); } @@ -165,16 +157,16 @@ void Ultimate2::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { outputs.rightStickY = 128 + (directions.cy * 68); } - if (inputs.l) { + if (inputs.lf4) { outputs.triggerLAnalog = 140; } - if (inputs.r) { + if (inputs.rf5) { outputs.triggerRAnalog = 140; } // Shut off C-stick when using D-Pad layer. - if ((inputs.mod_x && inputs.mod_y) || inputs.nunchuk_c) { + if ((inputs.lt1 && inputs.lt2) || inputs.nunchuk_c) { outputs.rightStickX = 128; outputs.rightStickY = 128; } diff --git a/src/util/analog_filters.cpp b/src/util/analog_filters.cpp new file mode 100644 index 00000000..74e619df --- /dev/null +++ b/src/util/analog_filters.cpp @@ -0,0 +1,29 @@ +#include "util/analog_filters.hpp" + +#include + +#define SIGNUM(x) ((x > 0) - (x < 0)) + +uint8_t apply_deadzone(uint8_t value, uint8_t deadzone, bool scale) { + int8_t value_signed = value - 128; + if (abs(value_signed) > deadzone) { + // If outside deadzone, must subtract deadzone from result so that axis values start from 1 + // instead of having lower values cut off. + int8_t post_deadzone = value_signed - deadzone * SIGNUM(value_signed); + // If a radius value is passed in, scale up the values linearly so that the same effective + // value is given on the rim. + if (scale) { + int8_t sign = SIGNUM(post_deadzone); + int8_t post_scaling = min(127, abs(post_deadzone) * 128.0 / (128 - deadzone)) * sign; + return post_scaling + 128; + } + return post_deadzone + 128; + } + return 128; +} + +uint8_t apply_radius(uint8_t value, int radius) { + int8_t value_signed = value - 128; + int8_t sign = SIGNUM(value_signed); + return min(127, (int)(abs(value_signed) * radius / 128.0)) * sign + 128; +} \ No newline at end of file diff --git a/src/util/config_util.cpp b/src/util/config_util.cpp new file mode 100644 index 00000000..3afabc3e --- /dev/null +++ b/src/util/config_util.cpp @@ -0,0 +1,79 @@ +#include "util/config_util.hpp" + +#include "core/state.hpp" +#include "util/state_util.hpp" + +#include + +CommunicationBackendConfig backend_config_from_buttons( + const InputState &inputs, + const CommunicationBackendConfig *backend_configs, + size_t backend_configs_count +) { + for (size_t i = 0; i < backend_configs_count; i++) { + const CommunicationBackendConfig &backend_config = backend_configs[i]; + // Build bit mask for checking for matching button hold. + uint64_t button_hold_mask = make_button_mask( + backend_config.activation_binding, + backend_config.activation_binding_count + ); + + // Check if button hold matches. + if (all_buttons_held(inputs.buttons, button_hold_mask)) { + return backend_config; + } + } + + return CommunicationBackendConfig{ + .backend_id = COMMS_BACKEND_UNSPECIFIED, + .default_mode_config = 0, + }; +} + +CommunicationBackendConfig backend_config_from_id( + CommunicationBackendId backend_id, + const CommunicationBackendConfig *backend_configs, + size_t backend_configs_count +) { + for (size_t i = 0; i < backend_configs_count; i++) { + const CommunicationBackendConfig &backend_config = backend_configs[i]; + if (backend_config.backend_id == backend_id) { + return backend_config; + } + } + + return CommunicationBackendConfig{ + .backend_id = backend_id, + .default_mode_config = 0, + }; +} + +uint8_t backend_config_id_from_backend_id( + CommunicationBackendId backend_id, + const CommunicationBackendConfig *backend_configs, + size_t backend_configs_count +) { + for (size_t i = 0; i < backend_configs_count; i++) { + const CommunicationBackendConfig &backend_config = backend_configs[i]; + if (backend_config.backend_id == backend_id) { + return i + 1; + } + } + + return 0; +} + +uint8_t mode_config_id_from_mode_id( + GameModeId mode_id, + const GameModeConfig *mode_configs, + size_t mode_configs_count +) { + for (size_t i = 0; i < mode_configs_count; i++) { + const GameModeConfig &mode = mode_configs[i]; + if (mode.mode_id == mode_id) { + return i + 1; + } + } + + return 0; +} \ No newline at end of file