diff --git a/configure.py b/configure.py index 36765934..13f3ea2c 100755 --- a/configure.py +++ b/configure.py @@ -3,6 +3,7 @@ from glob import glob import io import os +import shutil import sys from vendor.ninja_syntax import Writer @@ -147,3 +148,5 @@ with open('build.ninja', 'w') as out_file: out_file.write(out_buf.getvalue()) n.close() + +shutil.copytree("samples", "out/Tests", dirs_exist_ok=True) diff --git a/include/Common.hh b/include/Common.hh index b8f74c85..d8dce0c2 100644 --- a/include/Common.hh +++ b/include/Common.hh @@ -164,6 +164,93 @@ enum class Character { Max = 48, }; +enum class WeightClass { + Invalid = -1, + Light = 0, + Medium = 1, + Heavy = 2, +}; + +static constexpr WeightClass CharacterToWeight(Character character) { + switch (character) { + case Character::Baby_Peach: + case Character::Baby_Daisy... Character::Baby_Mario: + case Character::Toad: + case Character::Baby_Luigi... Character::Koopa_Troopa: + case Character::Small_Mii_Outfit_A_Male... Character::Small_Mii_Outfit_C_Female: + case Character::Small_Mii: + return WeightClass::Light; + case Character::Mario: + case Character::Luigi: + case Character::Yoshi: + case Character::Daisy... Character::Diddy_Kong: + case Character::Bowser_Jr: + case Character::Medium_Mii_Outfit_A_Male... Character::Medium_Mii_Outfit_C_Female: + case Character::Medium_Mii: + case Character::Peach_Biker_Outfit: + case Character::Daisy_Biker_Outfit: + return WeightClass::Medium; + case Character::Waluigi: + case Character::Bowser: + case Character::Donkey_Kong: + case Character::Wario: + case Character::King_Boo: + case Character::Dry_Bowser... Character::Rosalina: + case Character::Large_Mii_Outfit_A_Male... Character::Large_Mii_Outfit_C_Female: + case Character::Large_Mii: + case Character::Rosalina_Biker_Outfit: + return WeightClass::Heavy; + default: + return WeightClass::Invalid; + } +} + +static constexpr WeightClass VehicleToWeight(Vehicle vehicle) { + switch (vehicle) { + case Vehicle::Standard_Kart_S: + case Vehicle::Baby_Booster: + case Vehicle::Mini_Beast: + case Vehicle::Cheep_Charger: + case Vehicle::Tiny_Titan: + case Vehicle::Blue_Falcon: + case Vehicle::Standard_Bike_S: + case Vehicle::Bullet_Bike: + case Vehicle::Bit_Bike: + case Vehicle::Quacker: + case Vehicle::Magikruiser: + case Vehicle::Jet_Bubble: + return WeightClass::Light; + case Vehicle::Standard_Kart_M: + case Vehicle::Classic_Dragster: + case Vehicle::Wild_Wing: + case Vehicle::Super_Blooper: + case Vehicle::Daytripper: + case Vehicle::Sprinter: + case Vehicle::Standard_Bike_M: + case Vehicle::Mach_Bike: + case Vehicle::Sugarscoot: + case Vehicle::Zip_Zip: + case Vehicle::Sneakster: + case Vehicle::Dolphin_Dasher: + return WeightClass::Medium; + case Vehicle::Standard_Kart_L: + case Vehicle::Offroader: + case Vehicle::Flame_Flyer: + case Vehicle::Piranha_Prowler: + case Vehicle::Jetsetter: + case Vehicle::Honeycoupe: + case Vehicle::Standard_Bike_L: + case Vehicle::Flame_Runner: + case Vehicle::Wario_Bike: + case Vehicle::Shooting_Star: + case Vehicle::Spear: + case Vehicle::Phantom: + return WeightClass::Heavy; + default: + return WeightClass::Invalid; + } +} + extern const char *const COURSE_NAMES[59]; extern const char *const VEHICLE_NAMES[36]; diff --git a/samples/rmc3-rta-1-17-843.krkg b/samples/rmc3-rta-1-17-843.krkg new file mode 100644 index 00000000..4c10d1cd Binary files /dev/null and b/samples/rmc3-rta-1-17-843.krkg differ diff --git a/samples/rmc3-rta-1-17-843.rkg b/samples/rmc3-rta-1-17-843.rkg new file mode 100644 index 00000000..f04779c8 Binary files /dev/null and b/samples/rmc3-rta-1-17-843.rkg differ diff --git a/source/game/kart/KartParam.hh b/source/game/kart/KartParam.hh index 8aefd871..eb688b86 100644 --- a/source/game/kart/KartParam.hh +++ b/source/game/kart/KartParam.hh @@ -72,12 +72,6 @@ public: Inside_Drift_Bike = 2, }; - enum class WeightClass { - Light = 0, - Medium = 1, - Heavy = 2, - }; - Stats(); Stats(EGG::RamStream &stream); diff --git a/source/game/scene/GameScene.cc b/source/game/scene/GameScene.cc index cb17f929..abbf3d64 100644 --- a/source/game/scene/GameScene.cc +++ b/source/game/scene/GameScene.cc @@ -1,6 +1,6 @@ #include "GameScene.hh" -#include "game/system/InputManager.hh" +#include "game/system/KPadDirector.hh" #include @@ -13,7 +13,7 @@ GameScene::GameScene() { GameScene::~GameScene() = default; void GameScene::calc() { - System::InputManager::Instance()->calc(); + System::KPadDirector::Instance()->calc(); calcEngines(); } @@ -40,6 +40,7 @@ void GameScene::appendResource(System::MultiDvdArchive * /*arc*/, s32 /*id*/) {} void GameScene::initScene() { createEngines(); + System::KPadDirector::Instance()->reset(); initEngines(); } @@ -49,7 +50,6 @@ void GameScene::deinitScene() { } destroyEngines(); - System::InputManager::Instance()->clear(); } } // namespace Scene diff --git a/source/game/scene/RootScene.cc b/source/game/scene/RootScene.cc index 6bd49759..d29e2076 100644 --- a/source/game/scene/RootScene.cc +++ b/source/game/scene/RootScene.cc @@ -1,6 +1,6 @@ #include "RootScene.hh" -#include "game/system/InputManager.hh" +#include "game/system/KPadDirector.hh" #include "game/system/RaceConfig.hh" #include "game/system/ResourceManager.hh" @@ -21,13 +21,12 @@ void RootScene::enter() { void RootScene::allocate() { System::ResourceManager::CreateInstance(); - System::InputManager::CreateInstance(); + System::KPadDirector::CreateInstance(); System::RaceConfig::CreateInstance(); } void RootScene::init() { System::RaceConfig::Instance()->init(); - System::InputManager::Instance()->init(); } } // namespace Scene diff --git a/source/game/system/GhostFile.cc b/source/game/system/GhostFile.cc new file mode 100644 index 00000000..edd8905c --- /dev/null +++ b/source/game/system/GhostFile.cc @@ -0,0 +1,163 @@ +#include "GhostFile.hh" + +#include + +#include + +namespace System { + +GhostFile::GhostFile(RawGhostFile *raw) { + read(raw); +} + +GhostFile::~GhostFile() = default; + +void GhostFile::read(RawGhostFile *raw) { + readHeader(raw); + m_inputSize = raw->parseAt(0xE); + m_inputs = raw->buffer() + RKG_HEADER_SIZE; +} + +void GhostFile::readHeader(RawGhostFile *raw) { + memcpy(m_userData.data(), raw->buffer() + RKG_USER_DATA_OFFSET, RKG_USER_DATA_SIZE); + m_userData[10] = L'\0'; + + u32 data = raw->parseAt(0x8); + m_year = (data >> 0xD) & 0x7F; + m_month = (data >> 0x9) & 0xF; + m_day = (data >> 0x4) & 0x1F; + + memcpy(m_miiData.data(), raw->buffer() + RKG_MII_DATA_OFFSET, RKG_MII_DATA_SIZE); + + m_lapCount = raw->parseAt(0x10); + + // TODO: Do we want to add a bounds check here? + for (u8 lap = 0; lap < m_lapCount; ++lap) { + m_lapTimes[lap] = Timer(raw->parseAt(0x11 + lap * 3)); + } + + m_raceTime = Timer(raw->parseAt(0x4)); + + m_course = static_cast(raw->parseAt(0x4) >> 0x2 & 0x3F); + m_character = static_cast(raw->parseAt(0x8) >> 0x4 & 0x3F); + m_vehicle = static_cast(raw->parseAt(0x8) >> 0x2); + m_controllerId = raw->parseAt(0xB) & 0xF; + m_type = raw->parseAt(0xC) >> 0x2 & 0x7F; + m_location = raw->parseAt(0x34); + m_driftIsAuto = raw->parseAt(0xD) >> 1 & 0x1; +} + +Character GhostFile::character() const { + return m_character; +} + +Vehicle GhostFile::vehicle() const { + return m_vehicle; +} + +Course GhostFile::course() const { + return m_course; +} + +const u8 *GhostFile::inputs() const { + return m_inputs; +} + +bool GhostFile::driftIsAuto() const { + return m_driftIsAuto; +} + +RawGhostFile::RawGhostFile(const u8 *rkg) { + if (!isValid(rkg)) { + K_PANIC("Invalid RKG header"); + } + + if (compressed(rkg)) { + if (!decompress(rkg)) { + K_PANIC("Failed to decompress RKG!"); + } + } else { + memcpy(m_buffer, rkg, RKG_UNCOMPRESSED_FILE_SIZE); + } +} + +RawGhostFile::~RawGhostFile() = default; + +bool RawGhostFile::decompress(const u8 *rkg) { + memcpy(m_buffer, rkg, RKG_HEADER_SIZE); + + // Unset compressed flag + *(m_buffer + 0xC) &= 0xF7; + + // Get uncompressed size. Skip past 0x4 bytes which represents the size of the compressed data + s32 uncompressedSize = EGG::Decomp::GetExpandSize(rkg + RKG_HEADER_SIZE + 0x4); + + if (uncompressedSize <= 0 || + static_cast(uncompressedSize) > RKG_UNCOMPRESSED_INPUT_DATA_SECTION_SIZE) { + return false; + } + + EGG::Decomp::DecodeSZS(rkg + RKG_HEADER_SIZE + 0x4, m_buffer + RKG_HEADER_SIZE); + + // Set input data section length + *reinterpret_cast(m_buffer + 0xE) = static_cast(uncompressedSize); + + return true; +} + +bool RawGhostFile::isValid(const u8 *rkg) const { + if (strncmp(reinterpret_cast(rkg), "RKGD", 4) != 0) { + K_PANIC("RKG header malformed"); + return false; + } + + u32 ids = parse(*reinterpret_cast(rkg + 0x8)); + Vehicle vehicle = static_cast(ids >> 0x1a); + Character character = static_cast((ids >> 0x14) & 0x3f); + u8 year = (ids >> 0xd) & 0x7f; + u8 day = (ids >> 0x4) & 0x1f; + u8 month = (ids >> 0x9) & 0xf; + + if (vehicle >= Vehicle::Max || character >= Character::Max) { + return false; + } + + if (year >= 100 || day >= 32 || month > 12) { + return false; + } + + // Validate weight class match + WeightClass charWeight = CharacterToWeight(character); + WeightClass vehicleWeight = VehicleToWeight(vehicle); + + if (charWeight == WeightClass::Invalid) { + K_PANIC("Invalid character weight class!"); + } + if (vehicleWeight == WeightClass::Invalid) { + K_PANIC("Invalid vehicle weight class!"); + } + if (charWeight != vehicleWeight) { + K_PANIC("Character/Bike weight class mismatch!"); + } + + // TODO: + // - Check for valid controller type?? + // - Check lap times sum to race time?? + + return true; +} + +const u8 *RawGhostFile::buffer() const { + return m_buffer; +} + +template +T RawGhostFile::parseAt(size_t offset) const { + return parse(*reinterpret_cast(m_buffer + offset)); +} + +bool RawGhostFile::compressed(const u8 *rkg) const { + return ((*(rkg + 0xC) >> 3) & 1) == 1; +} + +} // namespace System diff --git a/source/game/system/GhostFile.hh b/source/game/system/GhostFile.hh new file mode 100644 index 00000000..99840bd3 --- /dev/null +++ b/source/game/system/GhostFile.hh @@ -0,0 +1,75 @@ +#pragma once + +#include "game/system/Timer.hh" + +#include + +namespace System { + +static constexpr size_t RKG_HEADER_SIZE = 0x88; +static constexpr size_t RKG_INPUT_DATA_HEADER_SIZE = 0x8; +static constexpr size_t RKG_UNCOMPRESSED_INPUT_DATA_SECTION_SIZE = 0x2774; +static constexpr size_t RKG_INPUT_DATA_SIZE = + RKG_UNCOMPRESSED_INPUT_DATA_SECTION_SIZE - RKG_INPUT_DATA_HEADER_SIZE; +static constexpr size_t RKG_UNCOMPRESSED_FILE_SIZE = + RKG_HEADER_SIZE + RKG_UNCOMPRESSED_INPUT_DATA_SECTION_SIZE; +static constexpr size_t RKG_USER_DATA_OFFSET = 0x20; +static constexpr size_t RKG_USER_DATA_SIZE = 0x14; +static constexpr size_t RKG_MII_DATA_OFFSET = 0x3C; +static constexpr size_t RKG_MII_DATA_SIZE = 0x4A; + +class RawGhostFile { +public: + RawGhostFile(const u8 *rkg); + ~RawGhostFile(); + + bool decompress(const u8 *rkg); + bool isValid(const u8 *rkg) const; + + const u8 *buffer() const; + + template + T parseAt(size_t offset) const; + +private: + bool compressed(const u8 *rkg) const; + + u8 m_buffer[RKG_UNCOMPRESSED_FILE_SIZE]; +}; +static_assert(sizeof(RawGhostFile) == RKG_UNCOMPRESSED_FILE_SIZE); + +class GhostFile { +public: + GhostFile(RawGhostFile *raw); + ~GhostFile(); + + void read(RawGhostFile *raw); + void readHeader(RawGhostFile *raw); + + Character character() const; + Vehicle vehicle() const; + Course course() const; + const u8 *inputs() const; + bool driftIsAuto() const; + +private: + std::array m_userData; + std::array m_miiData; + u8 m_lapCount; + std::array m_lapTimes; + Timer m_raceTime; + Character m_character; + Vehicle m_vehicle; + Course m_course; + u32 m_controllerId; + u8 m_year; + u8 m_month; + u8 m_day; + u32 m_type; + bool m_driftIsAuto; + u32 m_location; + u16 m_inputSize; + const u8 *m_inputs; +}; + +} // namespace System diff --git a/source/game/system/InputManager.cc b/source/game/system/InputManager.cc deleted file mode 100644 index 6657bb9c..00000000 --- a/source/game/system/InputManager.cc +++ /dev/null @@ -1,34 +0,0 @@ -#include "InputManager.hh" - -namespace System { - -void InputManager::calc() {} - -void InputManager::clear() {} - -void InputManager::init() {} - -void InputManager::setGhostPad() {} - -InputManager *InputManager::CreateInstance() { - assert(!s_instance); - return s_instance = new InputManager; -} - -void InputManager::DestroyInstance() { - assert(s_instance); - delete s_instance; - s_instance = nullptr; -} - -InputManager *InputManager::Instance() { - return s_instance; -} - -InputManager::InputManager() = default; - -InputManager::~InputManager() = default; - -InputManager *InputManager::s_instance = nullptr; - -} // namespace System diff --git a/source/game/system/InputManager.hh b/source/game/system/InputManager.hh deleted file mode 100644 index a2ce0a8f..00000000 --- a/source/game/system/InputManager.hh +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include - -namespace System { - -// TODO: full implementation -class InputManager { -public: - void calc(); - void clear(); - void init(); - - void setGhostPad(); - - static InputManager *CreateInstance(); - static void DestroyInstance(); - static InputManager *Instance(); - -private: - InputManager(); - ~InputManager(); - - static InputManager *s_instance; -}; - -} // namespace System diff --git a/source/game/system/KPadController.cc b/source/game/system/KPadController.cc new file mode 100644 index 00000000..dfebd4ad --- /dev/null +++ b/source/game/system/KPadController.cc @@ -0,0 +1,226 @@ +#include "KPadController.hh" + +#include + +namespace System { + +KPadController::KPadController() : m_connected(false) {} + +ControlSource KPadController::controlSource() { + return ControlSource::Unknown; +} + +void KPadController::calc() { + calcImpl(); +} + +const RaceInputState &KPadController::raceInputState() const { + return m_raceInputState; +} + +void KPadController::setDriftIsAuto(bool driftIsAuto) { + m_driftIsAuto = driftIsAuto; +} + +bool KPadController::driftIsAuto() const { + return m_driftIsAuto; +} + +KPadGhostController::KPadGhostController() : m_acceptingInputs(false) { + m_buttonsStreams[0] = new KPadGhostFaceButtonsStream; + m_buttonsStreams[1] = new KPadGhostDirectionButtonsStream; + m_buttonsStreams[2] = new KPadGhostTrickButtonsStream; +} + +KPadGhostController::~KPadGhostController() = default; + +ControlSource KPadGhostController::controlSource() { + return ControlSource::Ghost; +} + +void KPadGhostController::reset(bool driftIsAuto) { + m_driftIsAuto = driftIsAuto; + m_raceInputState.reset(); + + for (auto &stream : m_buttonsStreams) { + stream->sequenceCount = -1; + stream->readSequenceFrames = 0; + stream->state = 1; + } + + m_acceptingInputs = false; + m_connected = true; +} + +void KPadGhostController::readGhostBuffer(const u16 *buffer, bool driftIsAuto) { + constexpr u32 SEQUENCE_SIZE = 0x2; + + m_ghostBuffer = buffer; + m_driftIsAuto = driftIsAuto; + + u16 faceCount = parse(*buffer); + u16 directionCount = parse(*(buffer + 1)); + u16 trickCount = parse(*(buffer + 2)); + + buffer += 0x4; + + m_buttonsStreams[0]->buffer = buffer; + m_buttonsStreams[0]->size = faceCount * SEQUENCE_SIZE; + buffer += faceCount; + + m_buttonsStreams[1]->buffer = buffer; + m_buttonsStreams[1]->size = directionCount * SEQUENCE_SIZE; + buffer += directionCount; + + m_buttonsStreams[2]->buffer = buffer; + m_buttonsStreams[2]->size = trickCount * SEQUENCE_SIZE; +} + +void KPadGhostController::calcImpl() { + if (!m_ghostBuffer || !m_acceptingInputs) { + return; + } + + m_raceInputState.buttons = m_buttonsStreams[0]->readFrame() & 0xFF; + u16 sticks = m_buttonsStreams[1]->readFrame(); + m_raceInputState.stickXRaw = sticks >> 4 & 0xF; + m_raceInputState.stickYRaw = sticks & 0xF; + m_raceInputState.stick = + EGG::Vector2f((static_cast(m_raceInputState.stickXRaw) - 7.0f) / 7.0f, + (static_cast(m_raceInputState.stickYRaw) - 7.0f) / 7.0f); + m_raceInputState.trickRaw = m_buttonsStreams[2]->readFrame() >> 0x4; + m_raceInputState.trick = m_raceInputState.trickRaw; +} + +void KPadGhostController::setAcceptingInputs(bool set) { + m_acceptingInputs = set; +} + +RaceInputState::RaceInputState() { + reset(); +} + +void RaceInputState::reset() { + buttons = 0; + buttonsRaw = 0; + stick = EGG::Vector2f::zero; + stickXRaw = 7; + stickYRaw = 7; + trick = 0; + trickRaw = 0; +} + +KPadGhostButtonsStream::KPadGhostButtonsStream() + : buffer(nullptr), sequenceCount(-1), size(RKG_INPUT_DATA_SIZE), state(2) {} + +KPadGhostButtonsStream::~KPadGhostButtonsStream() = default; + +bool KPadGhostButtonsStream::readIsNewSequence(const u16 *sequence) const { + return readSequenceFrames >= (*sequence >> 0x8); +} + +u16 KPadGhostButtonsStream::readVal(const u16 *sequence) const { + return *sequence; +} + +u16 KPadGhostButtonsStream::readFrame() { + constexpr u32 SEQUENCE_SIZE = 0x2; + + if (state != 1) { + return 0; + } + + bool bVar1 = false; + const u16 *sequence = nullptr; + + if (sequenceCount >= 0) { + sequence = buffer + sequenceCount; + + if (readIsNewSequence(sequence)) { + bVar1 = true; + if (size <= ++sequenceCount * SEQUENCE_SIZE) { + state = 2; + } + } + } else { + sequenceCount = 0; + bVar1 = true; + } + + if (state != 1) { + return 0; + } + + sequence = buffer + sequenceCount; + + if (bVar1) { + readSequenceFrames = 0; + } + + ++readSequenceFrames; + + return readVal(sequence); +} + +KPadGhostFaceButtonsStream::KPadGhostFaceButtonsStream() = default; + +KPadGhostFaceButtonsStream::~KPadGhostFaceButtonsStream() = default; + +KPadGhostDirectionButtonsStream::KPadGhostDirectionButtonsStream() = default; + +KPadGhostDirectionButtonsStream::~KPadGhostDirectionButtonsStream() = default; + +KPadGhostTrickButtonsStream::KPadGhostTrickButtonsStream() = default; + +KPadGhostTrickButtonsStream::~KPadGhostTrickButtonsStream() = default; + +bool KPadGhostTrickButtonsStream::readIsNewSequence(const u16 *sequence) const { + return (*sequence & 0xFFF) <= readSequenceFrames; +} + +u16 KPadGhostTrickButtonsStream::readVal(const u16 *sequence) const { + return *sequence >> 0xC; +} + +KPad::KPad() : m_controller(nullptr) { + reset(); +} + +KPad::~KPad() = default; + +void KPad::calc() { + m_lastInputState = m_currentInputState; + m_currentInputState = m_controller->raceInputState(); +} + +void KPad::reset() { + if (m_controller) { + m_controller->reset(m_controller->driftIsAuto()); + } +} + +KPadPlayer::KPadPlayer() = default; + +KPadPlayer::~KPadPlayer() = default; + +void KPadPlayer::setGhostController(KPadGhostController *controller, const u8 *inputs, + bool driftIsAuto) { + m_controller = controller; + + if (inputs) { + memcpy(m_ghostBuffer, inputs, RKG_UNCOMPRESSED_INPUT_DATA_SECTION_SIZE); + } + + controller->readGhostBuffer(reinterpret_cast(m_ghostBuffer), driftIsAuto); +} + +void KPadPlayer::startGhostProxy() { + if (!m_controller || m_controller->controlSource() != ControlSource::Ghost) { + return; + } + + KPadGhostController *ghostController = reinterpret_cast(m_controller); + ghostController->setAcceptingInputs(true); +} + +} // namespace System diff --git a/source/game/system/KPadController.hh b/source/game/system/KPadController.hh new file mode 100644 index 00000000..ca25e9d6 --- /dev/null +++ b/source/game/system/KPadController.hh @@ -0,0 +1,137 @@ +#pragma once + +#include "game/system/GhostFile.hh" + +#include + +namespace System { + +enum class ControlSource { + Unknown = -1, + Core = 0, // WiiMote + Freestyle = 1, // WiiMote + Nunchuk + Classic = 2, + Gamecube = 3, + Ghost = 4, + AI = 5, +}; + +struct RaceInputState { + RaceInputState(); + virtual ~RaceInputState() {} + + void reset(); + + u16 buttons; + u16 buttonsRaw; + EGG::Vector2f stick; + u8 stickXRaw; + u8 stickYRaw; + u8 trick; + u8 trickRaw; +}; + +struct KPadGhostButtonsStream { + KPadGhostButtonsStream(); + ~KPadGhostButtonsStream(); + + virtual u16 readFrame(); + virtual bool readIsNewSequence(const u16 *sequence) const; + virtual u16 readVal(const u16 *sequence) const; + + const u16 *buffer; + s32 sequenceCount; + u32 size; + u16 readSequenceFrames; + u32 state; +}; + +struct KPadGhostFaceButtonsStream : public KPadGhostButtonsStream { + KPadGhostFaceButtonsStream(); + ~KPadGhostFaceButtonsStream(); +}; + +struct KPadGhostDirectionButtonsStream : public KPadGhostButtonsStream { + KPadGhostDirectionButtonsStream(); + ~KPadGhostDirectionButtonsStream(); +}; + +struct KPadGhostTrickButtonsStream : public KPadGhostButtonsStream { + KPadGhostTrickButtonsStream(); + ~KPadGhostTrickButtonsStream(); + + bool readIsNewSequence(const u16 *sequence) const override; + u16 readVal(const u16 *sequence) const override; +}; + +class KPadController { +public: + KPadController(); + virtual ~KPadController() {} + + virtual ControlSource controlSource(); + virtual void reset(bool /*driftIsAuto*/) {} + virtual void calcImpl() {} + + void calc(); + + const RaceInputState &raceInputState() const; + + void setDriftIsAuto(bool driftIsAuto); + + bool driftIsAuto() const; + +protected: + RaceInputState m_raceInputState; + bool m_connected; + bool m_driftIsAuto; +}; + +class KPadGhostController : public KPadController { +public: + KPadGhostController(); + ~KPadGhostController(); + + ControlSource controlSource() override; + void reset(bool driftIsAuto) override; + + void readGhostBuffer(const u16 *buffer, bool driftIsAuto); + + void calcImpl() override; + + void setAcceptingInputs(bool set); + +private: + const u16 *m_ghostBuffer; + std::array m_buttonsStreams; + bool m_acceptingInputs; +}; + +class KPad { +public: + KPad(); + ~KPad(); + + void calc(); + void reset(); + +protected: + KPadController *m_controller; + RaceInputState m_currentInputState; + RaceInputState m_lastInputState; +}; + +class KPadPlayer : public KPad { +public: + KPadPlayer(); + ~KPadPlayer(); + + void setGhostController(KPadGhostController *controller, const u8 *inputs, bool driftIsAuto); + + void startGhostProxy(); + +private: + u8 m_ghostBuffer[RKG_UNCOMPRESSED_INPUT_DATA_SECTION_SIZE]; +}; + +} // namespace System diff --git a/source/game/system/KPadDirector.cc b/source/game/system/KPadDirector.cc new file mode 100644 index 00000000..191e273f --- /dev/null +++ b/source/game/system/KPadDirector.cc @@ -0,0 +1,53 @@ +#include "KPadDirector.hh" + +namespace System { + +void KPadDirector::calc() { + calcPads(); + m_playerInput.calc(); +} + +void KPadDirector::calcPads() { + m_ghostController->calc(); +} + +void KPadDirector::reset() { + m_playerInput.reset(); +} + +void KPadDirector::startGhostProxies() { + m_playerInput.startGhostProxy(); +} + +const KPadPlayer &KPadDirector::playerInput() const { + return m_playerInput; +} + +void KPadDirector::setGhostPad(const u8 *inputs, bool driftIsAuto) { + m_playerInput.setGhostController(m_ghostController, inputs, driftIsAuto); +} + +KPadDirector *KPadDirector::CreateInstance() { + assert(!s_instance); + return s_instance = new KPadDirector; +} + +void KPadDirector::DestroyInstance() { + assert(s_instance); + delete s_instance; + s_instance = nullptr; +} + +KPadDirector *KPadDirector::Instance() { + return s_instance; +} + +KPadDirector::KPadDirector() { + m_ghostController = new KPadGhostController; +} + +KPadDirector::~KPadDirector() = default; + +KPadDirector *KPadDirector::s_instance = nullptr; + +} // namespace System diff --git a/source/game/system/KPadDirector.hh b/source/game/system/KPadDirector.hh new file mode 100644 index 00000000..07078236 --- /dev/null +++ b/source/game/system/KPadDirector.hh @@ -0,0 +1,33 @@ +#pragma once + +#include "game/system/KPadController.hh" + +namespace System { + +// TODO: full implementation +class KPadDirector { +public: + void calc(); + void calcPads(); + void reset(); + void startGhostProxies(); + + const KPadPlayer &playerInput() const; + + void setGhostPad(const u8 *inputs, bool driftIsAuto); + + static KPadDirector *CreateInstance(); + static void DestroyInstance(); + static KPadDirector *Instance(); + +private: + KPadDirector(); + ~KPadDirector(); + + KPadPlayer m_playerInput; + KPadGhostController *m_ghostController; + + static KPadDirector *s_instance; +}; + +} // namespace System diff --git a/source/game/system/RaceConfig.cc b/source/game/system/RaceConfig.cc index 4e11721f..dfd10fef 100644 --- a/source/game/system/RaceConfig.cc +++ b/source/game/system/RaceConfig.cc @@ -1,5 +1,9 @@ #include "RaceConfig.hh" +#include "game/system/GhostFile.hh" +#include "game/system/KPadDirector.hh" + +#include #include namespace System { @@ -18,6 +22,23 @@ void RaceConfig::initRace() { player.character = Character::Daisy; player.vehicle = Vehicle::Mach_Bike; player.type = Player::Type::Ghost; + + size_t size; + u8 *rkg = Abstract::File::Load("Tests/rmc3-rta-1-17-843.rkg", size); + m_raceScenario.ghost = new RawGhostFile(rkg); + + initControllers(); +} + +void RaceConfig::initControllers() { + // No need for raw validation, as we already do this + GhostFile ghost(m_raceScenario.ghost); + + if (ghost.course() != m_raceScenario.course) { + K_PANIC("Ghost is playing on wrong track! %u", ghost.course()); + } + + KPadDirector::Instance()->setGhostPad(ghost.inputs(), ghost.driftIsAuto()); } RaceConfig *RaceConfig::CreateInstance() { diff --git a/source/game/system/RaceConfig.hh b/source/game/system/RaceConfig.hh index 462892cd..69f002d6 100644 --- a/source/game/system/RaceConfig.hh +++ b/source/game/system/RaceConfig.hh @@ -2,6 +2,8 @@ #include +#include "game/system/GhostFile.hh" + namespace System { // TODO: elaborate on implementation @@ -32,10 +34,12 @@ public: std::array players; u8 playerCount; Course course; + RawGhostFile *ghost; }; void init(); void initRace(); + void initControllers(); const Scenario &raceScenario() const { return m_raceScenario; diff --git a/source/game/system/RaceManager.cc b/source/game/system/RaceManager.cc index ae5184e3..892dbfe7 100644 --- a/source/game/system/RaceManager.cc +++ b/source/game/system/RaceManager.cc @@ -1,6 +1,7 @@ #include "RaceManager.hh" #include "game/system/CourseMap.hh" +#include "game/system/KPadDirector.hh" #include "game/system/map/MapdataStartPoint.hh" namespace System { @@ -69,6 +70,10 @@ RaceManager::RaceManager() : m_stage(Stage::Intro), m_introTimer(0), m_timer(0) RaceManager::~RaceManager() = default; +RaceManagerPlayer::RaceManagerPlayer() { + m_inputs = &KPadDirector::Instance()->playerInput(); +} + RaceManager *RaceManager::s_instance = nullptr; } // namespace System diff --git a/source/game/system/RaceManager.hh b/source/game/system/RaceManager.hh index e84fc4aa..45e14e8d 100644 --- a/source/game/system/RaceManager.hh +++ b/source/game/system/RaceManager.hh @@ -1,9 +1,20 @@ #pragma once +#include "game/system/KPadController.hh" + #include namespace System { +class RaceManagerPlayer { +public: + RaceManagerPlayer(); + virtual ~RaceManagerPlayer() {} + +private: + const KPad *m_inputs; +}; + class RaceManager { public: enum class Stage { @@ -28,6 +39,7 @@ private: RaceManager(); ~RaceManager(); + RaceManagerPlayer m_player; Stage m_stage; u16 m_introTimer; u32 m_timer; diff --git a/source/game/system/Timer.cc b/source/game/system/Timer.cc new file mode 100644 index 00000000..5e002615 --- /dev/null +++ b/source/game/system/Timer.cc @@ -0,0 +1,15 @@ +#include "Timer.hh" + +namespace System { + +Timer::Timer() : min(0), sec(0), mil(0) {} + +Timer::Timer(u32 data) { + min = static_cast(data >> 0x19); + sec = static_cast(data >> 0x12) & 0x7F; + mil = static_cast(data >> 8) & 0x3FF; +} + +Timer::~Timer() = default; + +} // namespace System diff --git a/source/game/system/Timer.hh b/source/game/system/Timer.hh new file mode 100644 index 00000000..3f121dce --- /dev/null +++ b/source/game/system/Timer.hh @@ -0,0 +1,17 @@ +#pragma once + +#include "Common.hh" + +namespace System { + +struct Timer { + Timer(); + Timer(u32 data); + ~Timer(); + + u16 min; + u8 sec; + u16 mil; +}; + +} // namespace System