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..94afa079 --- /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..d62fdece 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,44 +9,53 @@ 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: c53 + bin_ext: uf2 + - env: b0xx_r4 + bin_ext: uf2 + - env: pico + bin_ext: uf2 + - env: schism + 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: htangl_v1 + bin_ext: hex + - env: lbx + bin_ext: hex + - env: smashbox + bin_ext: hex 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 +64,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/.vscode/extensions.json b/.vscode/extensions.json index 0f0d7401..e80666bf 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,7 +1,7 @@ -{ - // 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" + ] +} 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..0b4a47ef --- /dev/null +++ b/HAL/avr/avr_nousb/include/config_defaults.hpp @@ -0,0 +1,135 @@ +#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 = 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 = 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 = false, + .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..0871884f --- /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/config_utils.hpp" +#include "core/mode_selection.hpp" +#include "core/pinout.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..aedbf71f --- /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 = false, + .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..63d28814 --- /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/config_utils.hpp" +#include "core/mode_selection.hpp" +#include "core/pinout.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/comms/backend_init.hpp b/HAL/avr/include/comms/backend_init.hpp new file mode 100644 index 00000000..7d08aad2 --- /dev/null +++ b/HAL/avr/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/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..0d16ac39 100644 --- a/HAL/pico/include/comms/NintendoSwitchBackend.hpp +++ b/HAL/pico/include/comms/NintendoSwitchBackend.hpp @@ -41,11 +41,16 @@ 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: 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/comms/backend_init.hpp b/HAL/pico/include/comms/backend_init.hpp new file mode 100644 index 00000000..7d08aad2 --- /dev/null +++ b/HAL/pico/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/HAL/pico/include/config_defaults.hpp b/HAL/pico/include/config_defaults.hpp new file mode 100644 index 00000000..96c9f971 --- /dev/null +++ b/HAL/pico/include/config_defaults.hpp @@ -0,0 +1,193 @@ +#ifndef _CONFIG_DEFAULTS_HPP +#define _CONFIG_DEFAULTS_HPP + +#include +#include + +// clang-format off + +const Config default_config = { + .game_mode_configs_count = 7, + .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_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, + .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_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, + .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_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, + .activation_binding_count = 3, + .activation_binding = { BTN_LT1, BTN_MB1, BTN_LF2 }, + }, + GameModeConfig { + .mode_id = MODE_FGC, + .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 = 1, + .button_remapping = { + ButtonRemap { .physical_button = BTN_RT4, .activates = BTN_LT1 }, + }, + .activation_binding_count = 3, + .activation_binding = { BTN_LT1, BTN_MB1, BTN_LF1 }, + }, + 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_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, + .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_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, + .activation_binding_count = 3, + .activation_binding = { BTN_LT1, BTN_MB1, BTN_RF5 }, + }, + GameModeConfig { + .mode_id = MODE_KEYBOARD, + .socd_pairs_count = 2, + .socd_pairs = { + 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, + .activation_binding_count = 3, + .activation_binding = { BTN_LT2, BTN_MB1, BTN_LF4 }, + .keyboard_mode_config = 1, + }, + }, + .communication_backend_configs_count = 8, + .communication_backend_configs = { + CommunicationBackendConfig { + .backend_id = COMMS_BACKEND_XINPUT, + .default_mode_config = 1, + }, + CommunicationBackendConfig { + .backend_id = COMMS_BACKEND_DINPUT, + .default_mode_config = 1, + .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 = 1, + }, + CommunicationBackendConfig { + .backend_id = COMMS_BACKEND_N64, + .default_mode_config = 1, + }, + 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_RT2 }, + } + }, + .keyboard_modes_count = 1, + .keyboard_modes = { + KeyboardModeConfig { + 0, + 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 }, + }, + }, + }, + .default_backend_config = 1, + .default_usb_backend_config = 1, + .melee_options = { + .crouch_walk_os = false, + .disable_ledgedash_socd_override = false, + }, + .project_m_options = { + .true_z_press = false, + .disable_ledgedash_socd_override = false, + }, +}; + +// 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..c4aab389 100644 --- a/HAL/pico/src/comms/NintendoSwitchBackend.cpp +++ b/HAL/pico/src/comms/NintendoSwitchBackend.cpp @@ -48,9 +48,13 @@ 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 @@ -59,8 +63,12 @@ uint8_t NintendoSwitchBackend::_descriptor[] = { HID_REPORT_DESC() }; -NintendoSwitchBackend::NintendoSwitchBackend(InputSource **input_sources, size_t input_source_count) - : CommunicationBackend(input_sources, input_source_count) { +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 +107,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); 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..f7b65d11 --- /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/config_utils.hpp" +#include "core/mode_selection.hpp" +#include "core/pinout.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..9fb52d1f --- /dev/null +++ b/HAL/pico/src/display/ConfigMenu.cpp @@ -0,0 +1,107 @@ +#include "display/ConfigMenu.hpp" + +#include "core/Persistence.hpp" +#include "core/config_utils.hpp" +#include "core/mode_selection.hpp" +#include "reboot.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..aa783fcf --- /dev/null +++ b/HAL/pico/src/display/DefaultConfigMenu.cpp @@ -0,0 +1,248 @@ +#include "display/DefaultConfigMenu.hpp" + +#include "core/Persistence.hpp" +#include "core/config_utils.hpp" +#include "core/mode_selection.hpp" +#include "reboot.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..c3ccd582 --- /dev/null +++ b/HAL/pico/src/display/InputDisplay.cpp @@ -0,0 +1,71 @@ +#include "display/InputDisplay.hpp" + +#include "comms/IntegratedDisplay.hpp" +#include "core/config_utils.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/config/arduino/config.cpp b/config/arduino/config.cpp index 033456d6..4041caed 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.mb1) { + 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..5829bb5b 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.mb1) { + 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..e2c7e4e2 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.mb1) { + 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..b3042533 --- /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.mb1) { + 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..fed8becd 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.mb1) { + 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..62cf2bfa 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.mb1) { + 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..ec00fb10 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.mb1) { + 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..9e5f9118 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.mb1) { + 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..927eeaeb --- /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.mb1) { + 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..70b5eb6f 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.mb1) { + 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..cdb0dc6d 100644 --- a/config/pico/config.cpp +++ b/config/pico/config.cpp @@ -1,77 +1,77 @@ -#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_LT1, 6 }, + { BTN_LT2, 7 }, + + { BTN_MB1, 0 }, + { BTN_MB2, 10 }, + { BTN_MB3, 11 }, + + { BTN_RT1, 14 }, + { BTN_RT2, 15 }, + { 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.mb1) { + reboot_bootloader(); } // Turn on LED to indicate firmware booted. @@ -79,63 +79,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 +106,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..7fa11bd6 --- /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.mb1) { + 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..7fa0a0aa --- /dev/null +++ b/config/schism/env.ini @@ -0,0 +1,5 @@ +[env:schism] +extends = arduino_pico_base +build_src_filter = + ${arduino_pico_base.build_src_filter} + + diff --git a/config/smashbox/config.cpp b/config/smashbox/config.cpp index ea54a6f4..17803feb 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.mb1) { + 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/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/config_utils.hpp b/include/core/config_utils.hpp new file mode 100644 index 00000000..0812c516 --- /dev/null +++ b/include/core/config_utils.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/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/platformio.ini b/platformio.ini index cb36743c..93e77235 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#bbd0862 + +[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..1274ba6c --- /dev/null +++ b/src/comms/IntegratedDisplay.cpp @@ -0,0 +1,74 @@ +#include "comms/IntegratedDisplay.hpp" + +#include "core/Persistence.hpp" +#include "core/config_utils.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/config_utils.cpp b/src/core/config_utils.cpp new file mode 100644 index 00000000..beeb1202 --- /dev/null +++ b/src/core/config_utils.cpp @@ -0,0 +1,79 @@ +#include "core/config_utils.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 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..c016527a 100644 --- a/src/modes/DefaultKeyboardMode.cpp +++ b/src/modes/DefaultKeyboardMode.cpp @@ -3,29 +3,29 @@ #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_A, inputs.lf4); + Press(HID_KEY_B, inputs.lf3); + Press(HID_KEY_C, inputs.lf2); + Press(HID_KEY_D, inputs.lf1); + Press(HID_KEY_E, inputs.lt1); + Press(HID_KEY_F, inputs.lt2); + Press(HID_KEY_G, inputs.mb3); + Press(HID_KEY_H, inputs.mb1); + Press(HID_KEY_I, inputs.mb2); + Press(HID_KEY_J, inputs.rf5); + Press(HID_KEY_K, inputs.rf6); + Press(HID_KEY_L, inputs.rf7); + Press(HID_KEY_M, inputs.rf8); + Press(HID_KEY_N, inputs.rf1); + Press(HID_KEY_O, inputs.rf2); + Press(HID_KEY_P, inputs.rf3); + Press(HID_KEY_Q, inputs.rf4); + Press(HID_KEY_R, inputs.rt4); + Press(HID_KEY_S, inputs.rt3); + Press(HID_KEY_T, inputs.rt5); + Press(HID_KEY_U, inputs.rt1); + Press(HID_KEY_V, inputs.rt2); } diff --git a/src/modes/FgcMode.cpp b/src/modes/FgcMode.cpp index 4928a89e..c51f0eb1 100644 --- a/src/modes/FgcMode.cpp +++ b/src/modes/FgcMode.cpp @@ -1,45 +1,35 @@ #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 }, - }; -} +FgcMode::FgcMode() : ControllerMode() {} -void FgcMode::UpdateDigitalOutputs(InputState &inputs, OutputState &outputs) { +void FgcMode::UpdateDigitalOutputs(const 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; + outputs.dpadLeft = inputs.lf3; + outputs.dpadRight = inputs.lf1; + outputs.dpadDown = inputs.lf2; + outputs.dpadUp = inputs.lt1; // Menu keys - outputs.start = inputs.start; - outputs.select = inputs.c_left; - outputs.home = inputs.c_down; + outputs.start = inputs.mb1; + outputs.select = inputs.rt3; + outputs.home = inputs.rt2; + outputs.leftStickClick = inputs.lt2; + outputs.rightStickClick = inputs.rt1; // Right hand bottom row - outputs.a = inputs.b; - outputs.b = inputs.x; - outputs.triggerRDigital = inputs.z; - outputs.triggerLDigital = inputs.up; + outputs.a = inputs.rf1; + outputs.b = inputs.rf2; + outputs.triggerRDigital = inputs.rf3; + outputs.triggerLDigital = inputs.rf4; // Right hand top row - outputs.x = inputs.r; - outputs.y = inputs.y; - outputs.buttonR = inputs.lightshield; - outputs.buttonL = inputs.midshield; + outputs.x = inputs.rf5; + outputs.y = inputs.rf6; + outputs.buttonR = inputs.rf7; + outputs.buttonL = inputs.rf8; } -void FgcMode::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { +void FgcMode::UpdateAnalogOutputs(const InputState &inputs, OutputState &outputs) { outputs.leftStickX = 128; outputs.leftStickY = 128; outputs.rightStickX = 128; 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..5246bbca 100644 --- a/src/modes/ProjectM.cpp +++ b/src/modes/ProjectM.cpp @@ -4,78 +4,74 @@ #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; + 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; } // Don't override dpad up if it's already pressed using the MX + MY dpad // layer. - outputs.dpadUp = outputs.dpadUp || inputs.midshield; + outputs.dpadUp = outputs.dpadUp || inputs.rf8; - 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.rf7; if (directions.diagonal) { if (directions.y == 1) { @@ -84,7 +80,8 @@ void ProjectM::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { } } - if (inputs.mod_x) { + /* Mod X */ + if (inputs.lt1) { if (directions.horizontal) { outputs.leftStickX = 128 + (directions.x * 70); } @@ -98,42 +95,50 @@ void ProjectM::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { } if (directions.diagonal) { + // Default MX Diagonal outputs.leftStickX = 128 + (directions.x * 70); outputs.leftStickY = 128 + (directions.y * 34); - if (inputs.b) { + if (inputs.rf1) { outputs.leftStickX = 128 + (directions.x * 85); outputs.leftStickY = 128 + (directions.y * 31); } - if (inputs.r) { - outputs.leftStickX = 128 + (directions.x * 82); - outputs.leftStickY = 128 + (directions.y * 35); + // 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 * 82); + outputs.leftStickY = 128 + (directions.y * 35); + } } - if (inputs.c_up) { + if (inputs.rt4) { outputs.leftStickX = 128 + (directions.x * 77); outputs.leftStickY = 128 + (directions.y * 55); } - if (inputs.c_down) { + if (inputs.rt2) { outputs.leftStickX = 128 + (directions.x * 82); outputs.leftStickY = 128 + (directions.y * 36); } - if (inputs.c_left) { + if (inputs.rt3) { outputs.leftStickX = 128 + (directions.x * 84); outputs.leftStickY = 128 + (directions.y * 50); } - if (inputs.c_right) { + if (inputs.rt5) { outputs.leftStickX = 128 + (directions.x * 72); outputs.leftStickY = 128 + (directions.y * 61); } } } - if (inputs.mod_y) { + /* Mod Y */ + if (inputs.lt2) { if (directions.horizontal) { outputs.leftStickX = 128 + (directions.x * 35); } @@ -145,32 +150,32 @@ void ProjectM::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { outputs.leftStickX = 128 + (directions.x * 28); outputs.leftStickY = 128 + (directions.y * 58); - if (inputs.b) { + if (inputs.rf1) { outputs.leftStickX = 128 + (directions.x * 28); outputs.leftStickY = 128 + (directions.y * 85); } - if (inputs.r) { + if (inputs.rf5) { outputs.leftStickX = 128 + (directions.x * 51); outputs.leftStickY = 128 + (directions.y * 82); } - if (inputs.c_up) { + if (inputs.rt4) { outputs.leftStickX = 128 + (directions.x * 55); outputs.leftStickY = 128 + (directions.y * 77); } - if (inputs.c_down) { + if (inputs.rt2) { outputs.leftStickX = 128 + (directions.x * 34); outputs.leftStickY = 128 + (directions.y * 82); } - if (inputs.c_left) { + if (inputs.rt3) { outputs.leftStickX = 128 + (directions.x * 40); outputs.leftStickY = 128 + (directions.y * 84); } - if (inputs.c_right) { + if (inputs.rt5) { outputs.leftStickX = 128 + (directions.x * 62); outputs.leftStickY = 128 + (directions.y * 72); } @@ -189,17 +194,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 +217,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) { outputs.rightStickX = 128; outputs.rightStickY = 128; } diff --git a/src/modes/Rivals2.cpp b/src/modes/Rivals2.cpp new file mode 100644 index 00000000..eb8eb483 --- /dev/null +++ b/src/modes/Rivals2.cpp @@ -0,0 +1,260 @@ +#include "modes/Rivals2.hpp" + +#define ANALOG_STICK_MIN 0 +#define ANALOG_STICK_NEUTRAL 128 +#define ANALOG_STICK_MAX 255 + +bool input_persist; // for angled tilts +int timer = 0; // for angled tilts + +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.leftStickClick = inputs.rf7; + outputs.buttonL = inputs.rf8; //changed from rightStickClick to buttonL + //only because buttonL is a default mapping in Rivals 2 (doesn't really matter) + + // 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; + } +} + +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; // if L or R are pressed + + if (directions.diagonal && !shield_button_pressed) { //added this conditional to give joystick accurate diagonals rather than (+/- 1.2, 1.2) should be (0.87~, 0.87~) + outputs.leftStickX = 128 + (directions.x * 92); // 92 (0.78 in-game), reduced below 0.8 to allow crouch tilts/crouch turn-around tilts + outputs.leftStickY = 128 + (directions.y * 96);//Y value 0.83. >0.8 allows fast fall + } + + if (directions.diagonal && shield_button_pressed) { + outputs.leftStickX = 128 + (directions.x * 92); // (0.77~, 0.77~) to prevent spot dodging when pressing diagonal on the ground + outputs.leftStickY = 128 + (directions.y * 92); + } + + // For MX Angled Tilts when input_persist is true + //(x, y), (69, 53), (~0.506, ~0.31) [coords, code_values, in-game values] + if (input_persist) { //input_persist becomes true if ModX + diagonal + A + timer++; + outputs.leftStickX = 128 + (directions.x * 69); + outputs.leftStickY = 128 + (directions.y * 53); + } + + if (timer == 150) { // 150 has a 90% success rate on pico + timer = 0; + input_persist = false; + } + + + + if (inputs.lt1) { //if ModX is held + if (directions.horizontal) { + outputs.leftStickX = 128 + (directions.x * 76); //76 gives 0.58~ in-game for a medium speed walk. will also do tilts + } + + if(directions.vertical) { + outputs.leftStickY = 128 + (directions.y * 53); // 48 (0.31~ in-game), 0.3 allows tilts and shield drop + } + + if (directions.diagonal && shield_button_pressed) { //for max-length diagonal wavedash while holding ModX + outputs.leftStickX = 128 + (directions.x * 120); + outputs.leftStickY = 128 + (directions.y * 42); + } + + if (directions.diagonal && !shield_button_pressed) { + /* 100% Magnitude UpB when holding B */ + if (inputs.rf1 && !inputs.rf3) { + // (x, y), (123, 51), (1.14~, 0.29~) [coords, code_values, in-game values] + outputs.leftStickX = 128 + (directions.x * 123); + outputs.leftStickY = 128 + (directions.y * 51); + // (x, y), (120, 61), (1.1~, 0.41~) [coords, code_values, in-game values] + if (inputs.rt2) { //C-Down + outputs.leftStickX = 128 + (directions.x * 120); + outputs.leftStickY = 128 + (directions.y * 61); + } + // (x, y), (115, 69), (1.04~, 0.51~) [coords, code_values, in-game values] + if (inputs.rt3) { //C-Left + outputs.leftStickX = 128 + (directions.x * 115); + outputs.leftStickY = 128 + (directions.y * 69); + } + // (x, y), (110, 78), (0.98~, 0.61~) [coords, code_values, in-game values] + if (inputs.rt4) { //C-Up + outputs.leftStickX = 128 + (directions.x * 110); + outputs.leftStickY = 128 + (directions.y * 78); + } + // (x, y), (103, 87), (0.9~, 0.71~) [coords, code_values, in-game values] + if (inputs.rt5) { //C-Right + outputs.leftStickX = 128 + (directions.x * 103); + outputs.leftStickY = 128 + (directions.y * 87); + } + } + /* 60% Magnitude UpB when not holding B nor Z*/ + if (!inputs.rf3 && !inputs.rf1 && !input_persist) { + // (x, y), (68, 42), (~0.49, ~0.188) [coords, code_values, in-game values] + outputs.leftStickX = 128 + (directions.x * 68); + outputs.leftStickY = 128 + (directions.y * 42); + // (x, y), (71, 47), (~0.52, ~0.24) [coords, code_values, in-game values] + if (inputs.rt2) { //C-Down + outputs.leftStickX = 128 + (directions.x * 71); + outputs.leftStickY = 128 + (directions.y * 47); + } + // (x, y), (71, 51), (~0.52, 0.29~) [coords, code_values, in-game values] + if (inputs.rt3) { //C-Left + outputs.leftStickX = 128 + (directions.x * 71); + outputs.leftStickY = 128 + (directions.y * 51); + } + // (x, y), (69, 55), (~0.51, ~0.34) [coords, code_values, in-game values] + if (inputs.rt4) { //C-Up + outputs.leftStickX = 128 + (directions.x * 69); + outputs.leftStickY = 128 + (directions.y * 55); + } + // (x, y), (64, 60), (, ~0.38) [coords, code_values, in-game values] + if (inputs.rt5) { //C-Right + outputs.leftStickX = 128 + (directions.x * 64); + outputs.leftStickY = 128 + (directions.y * 60); + } + } + /* Shortest UpB when holding Z*/ + if (inputs.rf3) { + // (x, y), (53, 68), (~0.31, ~0.188) [coords, code_values, in-game values] + outputs.leftStickX = 128 + (directions.x * 53); + outputs.leftStickY = 128 + (directions.y * 42); + } + /*ModX Angled Tilts*/ + if (inputs.rt1) { + input_persist = true; + timer = 0; + outputs.leftStickX = 128 + (directions.x * 69); + outputs.leftStickY = 128 + (directions.y * 53); + } + } + } + + + if (inputs.lt2) { // if ModY is held + if (directions.horizontal) { + outputs.leftStickX = 128 + (directions.x * 53); //53 equates to 0.318~ in-game. 0.3 is min to achieve a walk + } + + if(directions.vertical) { + outputs.leftStickY = 128 + (directions.y * 90); // 0.75~ in-game. will shield drop and tap jump; will not fast fall + } + + if (directions.diagonal && !shield_button_pressed) { + /* 100% Magnitude UpB when holding B*/ + if (inputs.rf1 && !inputs.rf3) { + // (x, y), (51, 123), (~0.29, ~1.14) [coords, code_values, in-game values] + outputs.leftStickX = 128 + (directions.x * 51); + outputs.leftStickY = 128 + (directions.y * 123); + // (x, y), (61, 120), (~0.41, ~1.1) [coords, code_values, in-game values] + if (inputs.rt2) { //C-Down + outputs.leftStickX = 128 + (directions.x * 61); + outputs.leftStickY = 128 + (directions.y * 120); + } + // (x, y), (69, 115), (~0.51, 1.04~) [coords, code_values, in-game values] + if (inputs.rt3) { //C-Left + outputs.leftStickX = 128 + (directions.x * 69); + outputs.leftStickY = 128 + (directions.y * 115); + } + // (x, y), (78, 110), (~0.61, 0.98~) [coords, code_values, in-game values] + if (inputs.rt4) { //C-Up + outputs.leftStickX = 128 + (directions.x * 78); + outputs.leftStickY = 128 + (directions.y * 110); + } + // (x, y), (87, 103), (~0.71, 0.9~) [coords, code_values, in-game values] + if (inputs.rt5) { //C-Right + outputs.leftStickX = 128 + (directions.x * 87); + outputs.leftStickY = 128 + (directions.y * 103); + } + } + /* 60% Magnitude UpB when not holding B nor Z*/ + if (!inputs.rf3 && !inputs.rf1) { + // (x, y), (42, 68), (~0.188, ~0.49) [coords, code_values, in-game values] + outputs.leftStickX = 128 + (directions.x * 42); + outputs.leftStickY = 128 + (directions.y * 68); + // (x, y), (47, 71), (~0.24, ~0.52) [coords, code_values, in-game values] + if (inputs.rt2) { //C-Down + outputs.leftStickX = 128 + (directions.x * 47); + outputs.leftStickY = 128 + (directions.y * 71); + } + // (x, y), (51, 71), (~0.29, ~0.52) [coords, code_values, in-game values] + if (inputs.rt3) { //C-Left + outputs.leftStickX = 128 + (directions.x * 51); + outputs.leftStickY = 128 + (directions.y * 71); + } + // (x, y), (55, 69), (~0.34, ~0.51) [coords, code_values, in-game values] + if (inputs.rt4) { //C-Up + outputs.leftStickX = 128 + (directions.x * 55); + outputs.leftStickY = 128 + (directions.y * 69); + } + // (x, y), (60, 64), (~0.38, ~0.) [coords, code_values, in-game values] + if (inputs.rt5) { //C-Right + outputs.leftStickX = 128 + (directions.x * 60); + outputs.leftStickY = 128 + (directions.y * 64); + } + } + /* Shortest UpB when holding Z*/ + if (inputs.rf3) { + // (x, y), (42, 53), (~0.188, ~0.31) [coords, code_values, in-game values] + outputs.leftStickX = 128 + (directions.x * 42); + outputs.leftStickY = 128 + (directions.y * 53); + } + /* For buffered turnaround up-tilt/down-tilt with ModY + Diagonal */ + if (inputs.rt1) { + outputs.leftStickX = 128 + (directions.x * 69); + outputs.leftStickY = 128 + (directions.y * 89); + } + } + } + + + // Shut off C-stick when using D-Pad layer. + if (inputs.lt1 && inputs.lt2) { + outputs.rightStickX = 128; + outputs.rightStickY = 128; + } + + // Nunchuk overrides left stick. + if (inputs.nunchuk_connected) { + outputs.leftStickX = inputs.nunchuk_x; + outputs.leftStickY = inputs.nunchuk_y; + } +} diff --git a/src/modes/RivalsOfAether.cpp b/src/modes/RivalsOfAether.cpp index b43aed8b..172d6607 100644 --- a/src/modes/RivalsOfAether.cpp +++ b/src/modes/RivalsOfAether.cpp @@ -4,22 +4,14 @@ #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}, - }; -} - -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; +RivalsOfAether::RivalsOfAether() : ControllerMode() {} + +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,51 +19,50 @@ 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.leftStickClick = inputs.rf7; + outputs.rightStickClick = 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; } } -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; // 48 total DI angles, 24 total Up b angles, 16 total airdodge angles - if (inputs.mod_x) { + if (inputs.lt1) { if (directions.horizontal) { outputs.leftStickX = 128 + (directions.x * 66); // MX Horizontal Tilts - if (inputs.a) { + if (inputs.rt1) { outputs.leftStickX = 128 + (directions.x * 44); } } @@ -79,7 +70,7 @@ void RivalsOfAether::UpdateAnalogOutputs(InputState &inputs, OutputState &output if(directions.vertical) { outputs.leftStickY = 128 + (directions.y * 44); // MX Vertical Tilts - if (inputs.a) { + if (inputs.rt1) { outputs.leftStickY = 128 + (directions.y * 67); } } @@ -90,30 +81,30 @@ void RivalsOfAether::UpdateAnalogOutputs(InputState &inputs, OutputState &output outputs.leftStickY = 128 + (directions.y * 23); // Angles just for DI and Up B - if (inputs.c_down) { + if (inputs.rt2) { outputs.leftStickX = 128 + (directions.x * 49); outputs.leftStickY = 128 + (directions.y * 24); } // Angles just for DI - if (inputs.c_left) { + if (inputs.rt3) { outputs.leftStickX = 128 + (directions.x * 52); outputs.leftStickY = 128 + (directions.y * 31); } - - if (inputs.c_up) { + + if (inputs.rt4) { outputs.leftStickX = 128 + (directions.x * 49); outputs.leftStickY = 128 + (directions.y * 35); } - - if (inputs.c_right) { + + if (inputs.rt5) { outputs.leftStickX = 128 + (directions.x * 51); outputs.leftStickY = 128 + (directions.y * 43); } } } - if (inputs.mod_y) { + if (inputs.lt2) { if (directions.horizontal) { outputs.leftStickX = 128 + (directions.x * 44); } @@ -128,23 +119,23 @@ void RivalsOfAether::UpdateAnalogOutputs(InputState &inputs, OutputState &output outputs.leftStickY = 128 + (directions.y * 113); // Angles just for DI and Up B - if (inputs.c_down) { + if (inputs.rt2) { outputs.leftStickX = 128 + (directions.x * 44); outputs.leftStickY = 128 + (directions.y * 90); } // Angles just for DI - if (inputs.c_left) { + if (inputs.rt3) { outputs.leftStickX = 128 + (directions.x * 44); outputs.leftStickY = 128 + (directions.y * 74); } - - if (inputs.c_up) { + + if (inputs.rt4) { outputs.leftStickX = 128 + (directions.x * 45); outputs.leftStickY = 128 + (directions.y * 63); } - - if (inputs.c_right) { + + if (inputs.rt5) { outputs.leftStickX = 128 + (directions.x * 47); outputs.leftStickY = 128 + (directions.y * 57); } @@ -152,7 +143,7 @@ void RivalsOfAether::UpdateAnalogOutputs(InputState &inputs, OutputState &output } // 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; } diff --git a/src/modes/Ultimate.cpp b/src/modes/Ultimate.cpp index c23289ad..b52a64a3 100644 --- a/src/modes/Ultimate.cpp +++ b/src/modes/Ultimate.cpp @@ -5,58 +5,50 @@ #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}, - }; -} +Ultimate::Ultimate() : ControllerMode() {} -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; +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 || inputs.rf8; + 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; } } -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, // 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 (inputs.mod_x) { + if (inputs.lt1) { // MX + Horizontal = 6625 = 53 if (directions.horizontal) { outputs.leftStickX = 128 + (directions.x * 53); @@ -65,7 +57,7 @@ void Ultimate::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); } } @@ -77,90 +69,85 @@ void Ultimate::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { 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); - } - } - - // Angled fsmash/ftilt with C-Stick + MX - if (directions.cx != 0) { - outputs.rightStickX = 128 + (directions.cx * 127); - outputs.rightStickY = 128 + (directions.y * 59); + 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); } /* Up B angles */ if (directions.diagonal && !shield_button_pressed) { - // (33.44) = 53 35 + // MX + q1/2/3/4 = 33.44 degrees | 53 35 outputs.leftStickX = 128 + (directions.x * 53); outputs.leftStickY = 128 + (directions.y * 35); // (39.05) = 53 43 - if (inputs.c_down) { + if (inputs.rt2) { outputs.leftStickX = 128 + (directions.x * 53); outputs.leftStickY = 128 + (directions.y * 43); } // (36.35) = 53 39 - if (inputs.c_left) { + if (inputs.rt3) { outputs.leftStickX = 128 + (directions.x * 53); outputs.leftStickY = 128 + (directions.y * 39); } // (30.32) = 56 41 - if (inputs.c_up) { + if (inputs.rt4) { outputs.leftStickX = 128 + (directions.x * 53); outputs.leftStickY = 128 + (directions.y * 31); } // (27.85) = 49 42 - if (inputs.c_right) { + if (inputs.rt5) { outputs.leftStickX = 128 + (directions.x * 53); outputs.leftStickY = 128 + (directions.y * 28); } /* Extended Up B Angles */ - if (inputs.b) { + if (inputs.rf1) { // (33.29) = 67 44 outputs.leftStickX = 128 + (directions.x * 67); outputs.leftStickY = 128 + (directions.y * 44); // (39.38) = 67 55 - if (inputs.c_down) { + if (inputs.rt2) { outputs.leftStickX = 128 + (directions.x * 67); outputs.leftStickY = 128 + (directions.y * 55); } // (36.18) = 67 49 - if (inputs.c_left) { + if (inputs.rt3) { outputs.leftStickX = 128 + (directions.x * 67); outputs.leftStickY = 128 + (directions.y * 49); } // (30.2) = 67 39 - if (inputs.c_up) { + if (inputs.rt4) { outputs.leftStickX = 128 + (directions.x * 67); outputs.leftStickY = 128 + (directions.y * 39); } // (27.58) = 67 35 - if (inputs.c_right) { + if (inputs.rt5) { outputs.leftStickX = 128 + (directions.x * 67); outputs.leftStickY = 128 + (directions.y * 35); } } + // Angled fsmash/ftilt with C-Stick + MX + if (directions.cx != 0) { + outputs.rightStickX = 128 + (directions.cx * 127); + outputs.rightStickY = 128 + (directions.y * 59); + } + // 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); } } @@ -168,7 +155,7 @@ void Ultimate::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { if (directions.vertical) { outputs.leftStickY = 128 + (directions.y * 53); // MY Vertical Tilts - if (inputs.a) { + if (inputs.rt1) { outputs.leftStickY = 128 + (directions.y * 36); } } @@ -194,55 +181,55 @@ void Ultimate::UpdateAnalogOutputs(InputState &inputs, OutputState &outputs) { outputs.leftStickX = 128 + (directions.x * 35); outputs.leftStickY = 128 + (directions.y * 53); // (50.95) = 43 53 - if (inputs.c_down) { + if (inputs.rt2) { outputs.leftStickX = 128 + (directions.x * 43); outputs.leftStickY = 128 + (directions.y * 53); } // (53.65) = 39 53 - if (inputs.c_left) { + if (inputs.rt3) { outputs.leftStickX = 128 + (directions.x * 49); outputs.leftStickY = 128 + (directions.y * 53); } // (59.68) = 31 53 - if (inputs.c_up) { + if (inputs.rt4) { outputs.leftStickX = 128 + (directions.x * 31); outputs.leftStickY = 128 + (directions.y * 53); } // (62.15) = 28 53 - if (inputs.c_right) { + if (inputs.rt5) { outputs.leftStickX = 128 + (directions.x * 28); outputs.leftStickY = 128 + (directions.y * 53); } /* Extended Up B Angles */ - if (inputs.b) { + if (inputs.rf1) { // (56.71) = 44 67 outputs.leftStickX = 128 + (directions.x * 44); outputs.leftStickY = 128 + (directions.y * 67); // (50.62) = 55 67 - if (inputs.c_down) { + if (inputs.rt2) { outputs.leftStickX = 128 + (directions.x * 55); outputs.leftStickY = 128 + (directions.y * 67); } // (53.82) = 49 67 - if (inputs.c_left) { + if (inputs.rt3) { outputs.leftStickX = 128 + (directions.x * 49); outputs.leftStickY = 128 + (directions.y * 67); } // (59.8) = 39 67 - if (inputs.c_up) { + if (inputs.rt4) { outputs.leftStickX = 128 + (directions.x * 39); outputs.leftStickY = 128 + (directions.y * 67); } // (62.42) = 35 67 - if (inputs.c_right) { + if (inputs.rt5) { outputs.leftStickX = 128 + (directions.x * 35); outputs.leftStickY = 128 + (directions.y * 67); } } // MY Pivot Uptilt/Dtilt - if (inputs.a) { + if (inputs.rt1) { outputs.leftStickX = 128 + (directions.x * 34); outputs.leftStickY = 128 + (directions.y * 38); } @@ -257,16 +244,16 @@ void Ultimate::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/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/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; }