Skip to content

Commit

Permalink
Add support for host controllers
Browse files Browse the repository at this point in the history
  • Loading branch information
vabold committed Oct 25, 2024
1 parent 723d089 commit 8c8cd7c
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 30 deletions.
166 changes: 161 additions & 5 deletions source/game/system/KPadController.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@

namespace System {

/// @brief Converts a raw stick input into an input usable by the state.
/// @param rawStick The raw stick input to convert.
/// @return The converted input.
static constexpr f32 RawStickToState(u8 rawStick) {
return (static_cast<f32>(rawStick) - 7.0f) / 7.0f;
}

/// @addr{0x8051EBA8}
KPadController::KPadController() : m_connected(false) {}

/// @addr{0x8051CE7C}
ControlSource KPadController::controlSource() {
ControlSource KPadController::controlSource() const {
return ControlSource::Unknown;
}

Expand Down Expand Up @@ -41,7 +48,7 @@ KPadGhostController::KPadGhostController() : m_acceptingInputs(false) {
KPadGhostController::~KPadGhostController() = default;

/// @addr{0x8052282C}
ControlSource KPadGhostController::controlSource() {
ControlSource KPadGhostController::controlSource() const {
return ControlSource::Ghost;
}

Expand Down Expand Up @@ -104,9 +111,8 @@ void KPadGhostController::calcImpl() {
u8 sticks = m_buttonsStreams[1]->readFrame();
m_raceInputState.stickXRaw = sticks >> 4 & 0xF;
m_raceInputState.stickYRaw = sticks & 0xF;
m_raceInputState.stick =
EGG::Vector2f((static_cast<f32>(m_raceInputState.stickXRaw) - 7.0f) / 7.0f,
(static_cast<f32>(m_raceInputState.stickYRaw) - 7.0f) / 7.0f);
m_raceInputState.stick = EGG::Vector2f(RawStickToState(m_raceInputState.stickXRaw),
RawStickToState(m_raceInputState.stickYRaw));
m_raceInputState.trickRaw = m_buttonsStreams[2]->readFrame();

switch (m_raceInputState.trickRaw >> 4) {
Expand Down Expand Up @@ -147,6 +153,71 @@ void RaceInputState::reset() {
trickRaw = 0;
}

/// @brief Checks if the input state is valid.
/// @return If the input state is valid.
bool RaceInputState::isValid() const {
if (!isButtonsValid()) {
return false;
}

if (!isStickValid(stick.x) || !isStickValid(stick.y)) {
return false;
}

if (!isTrickValid()) {
return false;
}

return true;
}

/// @brief Checks if there are any invalid buttons.
/// @details Validatation with the previous input state doesn't happen because it doesn't exist.
/// Therefore, we cannot check here if e.g. the drift button is pressed when it shouldn't be.
/// @return If no invalid buttons are present.
bool RaceInputState::isButtonsValid() const {
return !(buttons & ~0xf);
}

/// @brief Checks if the stick values are within the domain of the physics engine.
/// @details The set of valid stick values is \f$\{\frac{x-7}{7}|0\leq x\leq 14,\in\mathbb{Z}\}\f$.
/// It's possible for the stick input to be 8/7 with x = 15, but only with ghost controllers.
/// @return If the stick values are valid.
bool RaceInputState::isStickValid(f32 stick) const {
if (stick > 1.0f || stick < -1.0f) {
return false;
}

for (size_t i = 0; i <= 14; ++i) {
auto cond = stick <=> RawStickToState(i);
ASSERT(cond != std::partial_ordering::unordered);

if (cond == std::partial_ordering::equivalent) {
return true;
} else if (cond == std::partial_ordering::less) {
return false;
}
}

// This is unreachable
return false;
}

/// @brief Checks if the trick input is valid.
/// @return If the trick input is valid.
bool RaceInputState::isTrickValid() const {
switch (trick) {
case Trick::None:
case Trick::Up:
case Trick::Down:
case Trick::Left:
case Trick::Right:
return true;
default:
return false;
}
}

bool RaceInputState::accelerate() const {
return !!(buttons & 0x1);
}
Expand Down Expand Up @@ -236,6 +307,86 @@ u8 KPadGhostTrickButtonsStream::readVal() const {
return currentSequence >> 0x8 & ~0x80;
}

/* ================================ *
* HOST CONTROLLER
* ================================ */

KPadHostController::KPadHostController() = default;

KPadHostController::~KPadHostController() = default;

ControlSource KPadHostController::controlSource() const {
return ControlSource::Host;
}

void KPadHostController::reset(bool driftIsAuto) {
m_driftIsAuto = driftIsAuto;
m_raceInputState.reset();
m_connected = true;
}

/// @brief Sets the inputs of the controller.
/// @param state The specified inputs packaged in the state. Only buttons, stick, and trick matter.
/// @return Input state validity.
bool KPadHostController::setInputs(const RaceInputState &state) {
return setInputs(state.buttons, state.stick, state.trick);
}

/// @brief Sets the inputs of the controller.
/// @param buttons The button inputs.
/// @param stick The stick inputs, as a 2D vector.
/// @param trick The trick input.
/// @return Input state validity.
bool KPadHostController::setInputs(u16 buttons, const EGG::Vector2f &stick, Trick trick) {
m_raceInputState.buttons = buttons;
m_raceInputState.stick = stick;
m_raceInputState.trick = trick;

return m_raceInputState.isValid();
}

/// @brief Sets the inputs of the controller.
/// @param buttons The button inputs.
/// @param stickX The stick input on the X axis.
/// @param stickY The stick input on the Y axis.
/// @param trick The trick input.
/// @return Input state validity.
bool KPadHostController::setInputs(u16 buttons, f32 stickX, f32 stickY, Trick trick) {
m_raceInputState.buttons = buttons;
m_raceInputState.stick.x = stickX;
m_raceInputState.stick.y = stickY;
m_raceInputState.trick = trick;

return m_raceInputState.isValid();
}

/// @brief Sets the inputs of the controller.
/// @details A different name is specified to avoid any ambiguity with the parameters.
/// @param buttons The button inputs.
/// @param stickXRaw The 7-centered raw stick input on the X axis.
/// @param stickYRaw The 7-centered raw stick input on the Y axis.
/// @param trick The trick input.
/// @return Input state validity.
bool KPadHostController::setInputsRawStick(u16 buttons, u8 stickXRaw, u8 stickYRaw, Trick trick) {
return setInputs(buttons, RawStickToState(stickXRaw), RawStickToState(stickYRaw), trick);
}

/// @brief Sets the inputs of the controller.
/// @details A different name is specified to avoid any ambiguity with the parameters.
/// @param buttons The button inputs.
/// @param stickXRaw The 0-centered raw stick input on the X axis.
/// @param stickYRaw The 0-centered raw stick input on the Y axis.
/// @param trick The trick input.
/// @return Input state validity.
bool KPadHostController::setInputsRawStickZeroCenter(u16 buttons, s8 stickXRaw, s8 stickYRaw,
Trick trick) {
return setInputsRawStick(buttons, stickXRaw + 7, stickYRaw + 7, trick);
}

/* ================================ *
* PADS
* ================================ */

/// @addr{0x80520F64}
KPad::KPad() : m_controller(nullptr) {
reset();
Expand Down Expand Up @@ -287,6 +438,11 @@ void KPadPlayer::setGhostController(KPadGhostController *controller, const u8 *i
controller->readGhostBuffer(m_ghostBuffer, driftIsAuto);
}

void KPadPlayer::setHostController(KPadHostController *controller, bool driftIsAuto) {
m_controller = controller;
m_controller->setDriftIsAuto(driftIsAuto);
}

/// @addr{0x805215D4}
void KPadPlayer::startGhostProxy() {
if (!m_controller || m_controller->controlSource() != ControlSource::Ghost) {
Expand Down
30 changes: 27 additions & 3 deletions source/game/system/KPadController.hh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ enum class ControlSource {
Gamecube = 3,
Ghost = 4,
AI = 5,
Host = 6, // Added in Kinoko, represents an external program
};

enum class Trick {
Expand All @@ -31,6 +32,11 @@ struct RaceInputState {

void reset();

[[nodiscard]] bool isValid() const;
[[nodiscard]] bool isButtonsValid() const;
[[nodiscard]] bool isStickValid(f32 stick) const;
[[nodiscard]] bool isTrickValid() const;

[[nodiscard]] bool accelerate() const;
[[nodiscard]] bool brake() const;
[[nodiscard]] bool item() const;
Expand Down Expand Up @@ -110,7 +116,7 @@ public:
KPadController();
virtual ~KPadController() {}

[[nodiscard]] virtual ControlSource controlSource();
[[nodiscard]] virtual ControlSource controlSource() const;
virtual void reset(bool /*driftIsAuto*/) {}
virtual void calcImpl() {}

Expand All @@ -133,9 +139,9 @@ protected:
class KPadGhostController : public KPadController {
public:
KPadGhostController();
~KPadGhostController();
~KPadGhostController() override;

[[nodiscard]] ControlSource controlSource() override;
[[nodiscard]] ControlSource controlSource() const override;
void reset(bool driftIsAuto) override;

void readGhostBuffer(const u8 *buffer, bool driftIsAuto);
Expand All @@ -150,6 +156,23 @@ private:
bool m_acceptingInputs;
};

/// @brief The abstraction of a controller object but for external usage.
/// @details The input state is managed externally by programs interfacing with Kinoko.
class KPadHostController : public KPadController {
public:
KPadHostController();
~KPadHostController() override;

[[nodiscard]] ControlSource controlSource() const override;
void reset(bool driftIsAuto) override;

bool setInputs(const RaceInputState &state);
bool setInputs(u16 buttons, const EGG::Vector2f &stick, Trick trick);
bool setInputs(u16 buttons, f32 stickX, f32 stickY, Trick trick);
bool setInputsRawStick(u16 buttons, u8 stickXRaw, u8 stickYRaw, Trick trick);
bool setInputsRawStickZeroCenter(u16 buttons, s8 stickXRaw, s8 stickYRaw, Trick trick);
};

class KPad {
public:
KPad();
Expand All @@ -175,6 +198,7 @@ public:
~KPadPlayer();

void setGhostController(KPadGhostController *controller, const u8 *inputs, bool driftIsAuto);
void setHostController(KPadHostController *controller, bool driftIsAuto);

void startGhostProxy(); ///< Signals to start reading ghost data after fade-in.
void endGhostProxy(); ///< Signals to stop reading ghost data after race completion.
Expand Down
6 changes: 6 additions & 0 deletions source/game/system/KPadDirector.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ void KPadDirector::calc() {
/// @addr{0x805237E8}
void KPadDirector::calcPads() {
m_ghostController->calc();
m_hostController->calc();
}

/// @addr{0x80523724}
Expand Down Expand Up @@ -40,6 +41,10 @@ void KPadDirector::setGhostPad(const u8 *inputs, bool driftIsAuto) {
m_playerInput.setGhostController(m_ghostController, inputs, driftIsAuto);
}

void KPadDirector::setHostPad(bool driftIsAuto) {
m_playerInput.setHostController(m_hostController, driftIsAuto);
}

/// @addr{0x8052313C}
KPadDirector *KPadDirector::CreateInstance() {
ASSERT(!s_instance);
Expand All @@ -61,6 +66,7 @@ KPadDirector *KPadDirector::Instance() {
/// @addr{0x805232F0}
KPadDirector::KPadDirector() {
m_ghostController = new KPadGhostController;
m_hostController = new KPadHostController;
}

/// @addr{0x805231DC}
Expand Down
2 changes: 2 additions & 0 deletions source/game/system/KPadDirector.hh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public:
[[nodiscard]] const KPadPlayer &playerInput() const;

void setGhostPad(const u8 *inputs, bool driftIsAuto);
void setHostPad(bool driftIsAuto);

static KPadDirector *CreateInstance();
static void DestroyInstance();
Expand All @@ -29,6 +30,7 @@ private:

KPadPlayer m_playerInput;
KPadGhostController *m_ghostController;
KPadHostController *m_hostController;

static KPadDirector *s_instance; ///< @addr{0x809BD70C}
};
Expand Down
Loading

0 comments on commit 8c8cd7c

Please sign in to comment.