diff --git a/include/player/controller.h b/include/player/controller.h index 755db95..40cad52 100644 --- a/include/player/controller.h +++ b/include/player/controller.h @@ -4,6 +4,7 @@ #include #include "player/controller-properties.h" +#include "player/rumble.h" namespace Player { @@ -33,6 +34,8 @@ namespace Player virtual AnalogStick leftAnalog() = 0; virtual AnalogStick rightAnalog() = 0; + virtual void rumble(RumbleOptions option) = 0; + virtual const uint8_t rawButtonState(const ControllerButton button) const = 0; virtual const bool wasPressed(const ControllerButton button) const = 0; virtual const bool wasPressedAndReleased(const ControllerButton button) const = 0; diff --git a/include/player/ps3-controller.h b/include/player/ps3-controller.h index f0ffd78..e59ee75 100644 --- a/include/player/ps3-controller.h +++ b/include/player/ps3-controller.h @@ -3,6 +3,7 @@ #include #include "player/controller.h" +#include "player/rumble.h" namespace Player { @@ -34,6 +35,8 @@ namespace Player AnalogStick leftAnalog() override; AnalogStick rightAnalog() override; + void rumble(RumbleOptions option) override; + const uint8_t rawButtonState(const ControllerButton button) const; const bool wasPressed(const ControllerButton button) const; const bool wasPressedAndReleased(const ControllerButton button) const; @@ -45,12 +48,20 @@ namespace Player ::Ps3Controller *controller; static Ps3Controller *instance; + static void playRumblePattern(RumblePattern pattern); + static void rumbleTask(void *pvParameters); + void triggerRumble(const RumblePattern &pattern); void handleOnConnect(); static void onConnect() { if (instance) instance->handleOnConnect(); } + struct RumbleTaskParams + { + Ps3Controller *instance; + const RumblePattern *pattern; + }; }; } #endif \ No newline at end of file diff --git a/include/player/rumble.h b/include/player/rumble.h new file mode 100644 index 0000000..0eb11b8 --- /dev/null +++ b/include/player/rumble.h @@ -0,0 +1,54 @@ +#pragma once + +#include + +#include "common.h" + +namespace Player +{ + enum class RumbleOptions + { + SingleQuickPulse, + DoubleQuickPulse, + DeathPulse + }; + + struct RumbleStep + { + int intensity; + int duration; + }; + + class RumblePattern + { + public: + template + RumblePattern(const RumbleStep (&steps)[N]) : steps(steps), count(N) {} + + const RumbleStep *getSteps() const { return steps; } + size_t getCount() const { return count; } + + private: + const RumbleStep *steps; + size_t count; + }; + + static const RumbleStep singleQuickSteps[] = { + {200, 100}}; + static const RumbleStep doubleQuickSteps[] = { + {255, 100}, + {0, 100}, + {200, 100}}; + static const RumbleStep deathSteps[] = { + {255, 100}, + {0, 100}, + {255, 400}, + {0, 100}, + {255, 100}, + {0, 100}, + {255, 400}}; + static const RumblePattern singleQuickRumbleSeq{singleQuickSteps}; + static const RumblePattern doubleQuickRumbleSeq{doubleQuickSteps}; + static const RumblePattern deathRumbleSeq{deathSteps}; + +} \ No newline at end of file diff --git a/src/games/demo/driver.cpp b/src/games/demo/driver.cpp index ec8f0d4..0832919 100644 --- a/src/games/demo/driver.cpp +++ b/src/games/demo/driver.cpp @@ -10,6 +10,7 @@ namespace Games::Demo { incrementCurrentScore(); contextManager->stateManager.displayShouldUpdate = true; + contextManager->controller.rumble(::Player::RumbleOptions::SingleQuickPulse); logf("Current score: %d", getCurrentScore()); } auto leftInput = contextManager->controller.leftAnalog(); diff --git a/src/games/phase-evasion/driver.cpp b/src/games/phase-evasion/driver.cpp index a8ec1be..96f604b 100644 --- a/src/games/phase-evasion/driver.cpp +++ b/src/games/phase-evasion/driver.cpp @@ -129,6 +129,7 @@ namespace Games::PhaseEvasion flare.impacted = true; state.current = Actions::MuzzleFlash; gameOverPhaseShift = static_cast(((SystemCore::Configuration::numLeds() / 2) + player.getPosition()) % SystemCore::Configuration::numLeds()); + contextManager->controller.rumble(::Player::RumbleOptions::DeathPulse); wait(20); } } @@ -158,6 +159,7 @@ namespace Games::PhaseEvasion gem.capture(); contextManager->stateManager.getPhaseEvasionGameState().gemsCaptured++; contextManager->stateManager.displayShouldUpdate = true; + contextManager->controller.rumble(::Player::RumbleOptions::DoubleQuickPulse); gem.wait(gemRespawnDelay); } } diff --git a/src/games/phase-evasion/flare-manager.cpp b/src/games/phase-evasion/flare-manager.cpp index 6aca3a9..5875544 100644 --- a/src/games/phase-evasion/flare-manager.cpp +++ b/src/games/phase-evasion/flare-manager.cpp @@ -34,6 +34,7 @@ namespace Games::PhaseEvasion { contextManager->stateManager.getPhaseEvasionGameState().flaresEvaded++; contextManager->stateManager.displayShouldUpdate = true; + contextManager->controller.rumble(Player::RumbleOptions::SingleQuickPulse); flare.completedCycle = false; } } diff --git a/src/games/recall/driver.cpp b/src/games/recall/driver.cpp index 249ea74..314b1ca 100644 --- a/src/games/recall/driver.cpp +++ b/src/games/recall/driver.cpp @@ -115,6 +115,7 @@ namespace Games::Recall state.current = Actions::PlayerResponseEvaluation; sequenceIndex = 0; contextManager->controller.reset(); + contextManager->controller.rumble(::Player::RumbleOptions::SingleQuickPulse); successFadeawayAnimation = 1; log("Ready for User playback"); return; @@ -136,6 +137,7 @@ namespace Games::Recall state.incrementScore(); contextManager->controller.reset(); + contextManager->controller.rumble(::Player::RumbleOptions::DoubleQuickPulse); wait(gameplaySpeedIlluminated * 2); return; } @@ -160,6 +162,7 @@ namespace Games::Recall } logf("User provided the incorrect answer. Entering game over sequence."); + contextManager->controller.rumble(::Player::RumbleOptions::DeathPulse); state.current = Actions::GameOver; } } diff --git a/src/player/ps3-controller.cpp b/src/player/ps3-controller.cpp index ef14e97..ecc672c 100644 --- a/src/player/ps3-controller.cpp +++ b/src/player/ps3-controller.cpp @@ -41,6 +41,28 @@ namespace Player return joystick; } + void Ps3Controller::rumble(RumbleOptions option) + { + const RumblePattern *pattern = nullptr; + + switch (option) + { + case RumbleOptions::DoubleQuickPulse: + pattern = &doubleQuickRumbleSeq; + break; + case RumbleOptions::SingleQuickPulse: + pattern = &singleQuickRumbleSeq; + break; + case RumbleOptions::DeathPulse: + pattern = &deathRumbleSeq; + break; + default: + break; + } + + triggerRumble(*pattern); + } + const uint8_t Ps3Controller::rawButtonState(const ControllerButton button) const { if (!instance->connection) @@ -160,5 +182,44 @@ namespace Player instance->reset(); instance->poll(); } + + void Ps3Controller::playRumblePattern(RumblePattern pattern) + { + const RumbleStep *steps = pattern.getSteps(); + size_t count = pattern.getCount(); + + for (size_t i = 0; i < count; i++) + { + Ps3.setRumble(steps[i].intensity, steps[i].duration); + vTaskDelay(pdMS_TO_TICKS(steps[i].duration)); + } + } + + void Ps3Controller::rumbleTask(void *pvParameters) + { + auto *params = (RumbleTaskParams *)pvParameters; + + Ps3Controller *self = params->instance; + const RumblePattern *pattern = params->pattern; + + self->playRumblePattern(*pattern); + + delete params; + vTaskDelete(NULL); + } + void Ps3Controller::triggerRumble(const RumblePattern &pattern) + { + auto *params = new RumbleTaskParams{ + this, + &pattern}; + xTaskCreatePinnedToCore( + Ps3Controller::rumbleTask, + "RumbleTask", + 2048, + (void *)params, + 1, + NULL, + 0); + } } #endif \ No newline at end of file