From a2db4ca60eacfe23dab76133b3393f2679b70018 Mon Sep 17 00:00:00 2001 From: Ishan Chawla Date: Wed, 30 Oct 2024 20:46:01 -0700 Subject: [PATCH] Add Checkpoints --- source/egg/math/Vector.cc | 6 + source/egg/math/Vector.hh | 4 +- source/game/system/CourseMap.cc | 30 ++++- source/game/system/CourseMap.hh | 13 ++- source/game/system/map/MapdataCheckPoint.cc | 117 ++++++++++++++++++++ source/game/system/map/MapdataCheckPoint.hh | 100 +++++++++++++++++ 6 files changed, 260 insertions(+), 10 deletions(-) create mode 100644 source/game/system/map/MapdataCheckPoint.cc create mode 100644 source/game/system/map/MapdataCheckPoint.hh diff --git a/source/egg/math/Vector.cc b/source/egg/math/Vector.cc index 43da5f8f..5577513d 100644 --- a/source/egg/math/Vector.cc +++ b/source/egg/math/Vector.cc @@ -36,6 +36,12 @@ f32 Vector2f::normalise() { return len; } +/// @brief Constructs a Vector2f by reading 8 bytes from the stream. +void Vector2f::read(Stream &stream) { + x = stream.read_f32(); + y = stream.read_f32(); +} + /// @brief The dot product between the vector and itself. f32 Vector3f::dot() const { return x * x + y * y + z * z; diff --git a/source/egg/math/Vector.hh b/source/egg/math/Vector.hh index d97f635c..351ec347 100644 --- a/source/egg/math/Vector.hh +++ b/source/egg/math/Vector.hh @@ -36,7 +36,9 @@ struct Vector2f { [[nodiscard]] f32 dot(const Vector2f &rhs) const; [[nodiscard]] f32 dot() const; [[nodiscard]] f32 length() const; - [[nodiscard]] f32 normalise(); + f32 normalise(); + + void read(Stream &stream); f32 x; f32 y; diff --git a/source/game/system/CourseMap.cc b/source/game/system/CourseMap.cc index f835a263..6ee99605 100644 --- a/source/game/system/CourseMap.cc +++ b/source/game/system/CourseMap.cc @@ -1,6 +1,7 @@ #include "CourseMap.hh" #include "game/system/map/MapdataCannonPoint.hh" +#include "game/system/map/MapdataCheckPoint.hh" #include "game/system/map/MapdataFileAccessor.hh" #include "game/system/map/MapdataGeoObj.hh" #include "game/system/map/MapdataStageInfo.hh" @@ -17,11 +18,13 @@ void CourseMap::init() { new MapdataFileAccessor(reinterpret_cast(buffer)); constexpr u32 CANNON_POINT_SIGNATURE = 0x434e5054; + constexpr u32 CHECK_POINT_SIGNATURE = 0x434b5054; constexpr u32 GEO_OBJ_SIGNATURE = 0x474f424a; constexpr u32 START_POINT_SIGNATURE = 0x4b545054; constexpr u32 STAGE_INFO_SIGNATURE = 0x53544749; m_startPoint = parseStartPoint(START_POINT_SIGNATURE); + m_checkPoint = parseCheckPoint(CHECK_POINT_SIGNATURE); m_geoObj = parseGeoObj(GEO_OBJ_SIGNATURE); m_cannonPoint = parseCannonPoint(CANNON_POINT_SIGNATURE); m_stageInfo = parseStageInfo(STAGE_INFO_SIGNATURE); @@ -43,7 +46,7 @@ void CourseMap::init() { } /// @addr{0x80512FA4} -MapdataCannonPointAccessor *CourseMap::parseCannonPoint(u32 sectionName) { +MapdataCannonPointAccessor *CourseMap::parseCannonPoint(u32 sectionName) const { const MapSectionHeader *sectionPtr = m_course->findSection(sectionName); MapdataCannonPointAccessor *accessor = nullptr; @@ -54,8 +57,20 @@ MapdataCannonPointAccessor *CourseMap::parseCannonPoint(u32 sectionName) { return accessor; } +/// @addr{0x80513640} +MapdataCheckPointAccessor *CourseMap::parseCheckPoint(u32 sectionName) const { + const MapSectionHeader *sectionPtr = m_course->findSection(sectionName); + + MapdataCheckPointAccessor *accessor = nullptr; + if (sectionPtr) { + accessor = new MapdataCheckPointAccessor(sectionPtr); + } + + return accessor; +} + /// @addr{0x805134C8} -MapdataGeoObjAccessor *CourseMap::parseGeoObj(u32 sectionName) { +MapdataGeoObjAccessor *CourseMap::parseGeoObj(u32 sectionName) const { const MapSectionHeader *sectionPtr = m_course->findSection(sectionName); MapdataGeoObjAccessor *accessor = nullptr; @@ -67,7 +82,7 @@ MapdataGeoObjAccessor *CourseMap::parseGeoObj(u32 sectionName) { } /// @addr{0x80512D64} -MapdataStageInfoAccessor *CourseMap::parseStageInfo(u32 sectionName) { +MapdataStageInfoAccessor *CourseMap::parseStageInfo(u32 sectionName) const { const MapSectionHeader *sectionPtr = m_course->findSection(sectionName); MapdataStageInfoAccessor *accessor = nullptr; @@ -79,7 +94,7 @@ MapdataStageInfoAccessor *CourseMap::parseStageInfo(u32 sectionName) { } /// @addr{0x80513F5C} -MapdataStartPointAccessor *CourseMap::parseStartPoint(u32 sectionName) { +MapdataStartPointAccessor *CourseMap::parseStartPoint(u32 sectionName) const { const MapSectionHeader *sectionPtr = m_course->findSection(sectionName); MapdataStartPointAccessor *accessor = nullptr; @@ -90,11 +105,16 @@ MapdataStartPointAccessor *CourseMap::parseStartPoint(u32 sectionName) { return accessor; } -/// @addr{0x80518AF8} +/// @addr{0x80518AE0} MapdataCannonPoint *CourseMap::getCannonPoint(u16 i) const { return m_cannonPoint && m_cannonPoint->size() != 0 ? m_cannonPoint->get(i) : nullptr; } +/// @addr{0x80515C24} +MapdataCheckPoint *CourseMap::getCheckPoint(u16 i) const { + return m_checkPoint && m_checkPoint->size() != 0 ? m_checkPoint->get(i) : nullptr; +} + /// @addr{0x80514148} MapdataGeoObj *CourseMap::getGeoObj(u16 i) const { return i < getGeoObjCount() ? m_geoObj->get(i) : nullptr; diff --git a/source/game/system/CourseMap.hh b/source/game/system/CourseMap.hh index a50de6ed..f6e3ac7e 100644 --- a/source/game/system/CourseMap.hh +++ b/source/game/system/CourseMap.hh @@ -8,6 +8,8 @@ namespace System { class MapdataCannonPoint; class MapdataCannonPointAccessor; +class MapdataCheckPoint; +class MapdataCheckPointAccessor; class MapdataFileAccessor; class MapdataGeoObj; class MapdataGeoObjAccessor; @@ -22,13 +24,15 @@ class MapdataStartPointAccessor; class CourseMap : EGG::Disposer { public: void init(); - [[nodiscard]] MapdataCannonPointAccessor *parseCannonPoint(u32 sectionName); - [[nodiscard]] MapdataGeoObjAccessor *parseGeoObj(u32 sectionName); - [[nodiscard]] MapdataStageInfoAccessor *parseStageInfo(u32 sectionName); - [[nodiscard]] MapdataStartPointAccessor *parseStartPoint(u32 sectionName); + [[nodiscard]] MapdataCannonPointAccessor *parseCannonPoint(u32 sectionName) const; + [[nodiscard]] MapdataCheckPointAccessor *parseCheckPoint(u32 sectionName) const; + [[nodiscard]] MapdataGeoObjAccessor *parseGeoObj(u32 sectionName) const; + [[nodiscard]] MapdataStageInfoAccessor *parseStageInfo(u32 sectionName) const; + [[nodiscard]] MapdataStartPointAccessor *parseStartPoint(u32 sectionName) const; /// @beginGetters [[nodiscard]] MapdataCannonPoint *getCannonPoint(u16 i) const; + [[nodiscard]] MapdataCheckPoint *getCheckPoint(u16 i) const; [[nodiscard]] MapdataGeoObj *getGeoObj(u16 i) const; [[nodiscard]] MapdataStageInfo *getStageInfo() const; [[nodiscard]] MapdataStartPoint *getStartPoint(u16 i) const; @@ -51,6 +55,7 @@ private: MapdataFileAccessor *m_course; MapdataStartPointAccessor *m_startPoint; + MapdataCheckPointAccessor *m_checkPoint; MapdataGeoObjAccessor *m_geoObj; MapdataCannonPointAccessor *m_cannonPoint; MapdataStageInfoAccessor *m_stageInfo; diff --git a/source/game/system/map/MapdataCheckPoint.cc b/source/game/system/map/MapdataCheckPoint.cc new file mode 100644 index 00000000..f4826c65 --- /dev/null +++ b/source/game/system/map/MapdataCheckPoint.cc @@ -0,0 +1,117 @@ +#include "MapdataCheckPoint.hh" + +#include "game/system/CourseMap.hh" + +namespace System { + +/// @addr{0x805154E4} +MapdataCheckPoint::MapdataCheckPoint(const SData *data) + : m_rawData(data), m_nextCount(0), m_prevCount(0) { + u8 *unsafeData = reinterpret_cast(const_cast(data)); + EGG::RamStream stream = EGG::RamStream(unsafeData, sizeof(SData)); + read(stream); + m_midpoint = EGG::Vector2f((left().x + right().x) / 2.0f, (left().y + right().y) / 2.0f); + m_dir = EGG::Vector2f(left().y - right().y, right().x - left().x); + m_dir.normalise(); +} + +void MapdataCheckPoint::read(EGG::Stream &stream) { + m_left.read(stream); + m_right.read(stream); + m_jugemIndex = stream.read_u8(); + m_type = stream.read_s8(); + m_prevPt = stream.read_u8(); + m_nextPt = stream.read_u8(); +} + +void MapdataCheckPoint::setPlayerFlags(s32 /* playerIdx */) { + m_flag = true; +} + +const EGG::Vector2f &MapdataCheckPoint::left() const { + return m_left; +} + +const EGG::Vector2f &MapdataCheckPoint::right() const { + return m_right; +} + +u8 MapdataCheckPoint::jugemIndex() const { + return m_jugemIndex; +} + +s8 MapdataCheckPoint::type() const { + return m_type; +} + +bool MapdataCheckPoint::isNormalCheckpoint() const { + return m_type == NORMAL_CHECKPOINT; +} + +bool MapdataCheckPoint::isFinishLine() const { + return m_type == FINISH_LINE; +} + +/// @brief Computes @ref m_prevKcpId for each checkpoint. +/// @details @ref m_flag indicates that a checkpoint has been visited, to prevent infinite +/// recursion. +/// @addr{0x80515A6C} +void MapdataCheckPoint::linkPrevKcpIds(u8 prevKcpId) { + m_prevKcpId = m_type == NORMAL_CHECKPOINT ? prevKcpId : m_type; + + setPlayerFlags(0); + for (size_t i = 0; i < m_nextCount; i++) { + MapdataCheckPoint *next = m_nextPoints[i].checkpoint; + if (!next->m_flag) { + next->linkPrevKcpIds(m_prevKcpId); + } + } +} + +/// @brief Finds indices of the finish line and last key checkpoint; also calls initCheckpointLinks +/// for all checkpoints +/// @addr{Inlined in 0x80515244} fake. not real. it's not in the base game. in the base +/// game, it's inlined into `init()`. i, kooshnoo, split it out because i wanted to. +// I'm not sure if this should be split; it feels like a separate function, +// but idk if i can somehow prove nintendo wrote it as a separate function, +// and i don't know if we even care what nintendo extracted to a separate function and what they +// inlined anyways. would the PR reviewer kindly please advise, thank you +void MapdataCheckPointAccessor::findFinishAndLastKcp() { + s8 lastKcpType = -1; + s16 finishLineCheckpointId = -1; + + for (size_t ckptId = 0; ckptId < size(); ckptId++) { + MapdataCheckPoint *checkpoint = get(ckptId); + // @todo implement initCheckpointLinks + // checkpoint->initCheckpointLinks(*this, ckptId); + checkpoint = get(ckptId); + + if (checkpoint->isFinishLine()) { + finishLineCheckpointId = ckptId; + } + + lastKcpType = std::max(lastKcpType, checkpoint->type()); + } + + m_lastKcpType = lastKcpType; + m_finishLineCheckpointId = finishLineCheckpointId; +} + +/// @addr{0x80515244} +void MapdataCheckPointAccessor::init() { + findFinishAndLastKcp(); + MapdataCheckPoint *finishLine = get(m_finishLineCheckpointId); + finishLine->linkPrevKcpIds(0); +} + +MapdataCheckPointAccessor::MapdataCheckPointAccessor(const MapSectionHeader *header) + : MapdataAccessorBase(header) { + MapdataAccessorBase::init( + reinterpret_cast(m_sectionHeader + 1), + parse(m_sectionHeader->count)); + init(); +} + +MapdataCheckPointAccessor::~MapdataCheckPointAccessor() = default; + +} // namespace System diff --git a/source/game/system/map/MapdataCheckPoint.hh b/source/game/system/map/MapdataCheckPoint.hh new file mode 100644 index 00000000..31335110 --- /dev/null +++ b/source/game/system/map/MapdataCheckPoint.hh @@ -0,0 +1,100 @@ +#pragma once + +#include "game/system/map/MapdataAccessorBase.hh" + +#include + +namespace System { + +class MapdataCheckPoint; +class MapdataCheckPointAccessor; + +struct LinkedCheckpoint { + MapdataCheckPoint *checkpoint; + EGG::Vector2f p0diff; + EGG::Vector2f p1diff; + f32 distance; +}; + +class MapdataCheckPoint { +public: + struct SData { + EGG::Vector2f left; + EGG::Vector2f right; + u8 jugemIndex; + s8 lapCheck; + u8 prevPt; + u8 nextPt; + }; + + enum class SectorOccupancy { + InsideSector, ///< If the player is inside the checkpoint quad + OutsideSector, ///< If the player is not between the sides of the quad (may still be between + ///< this checkpoint and next); the player is likely in a different + ///< checkpoint group + OutsideSector_BetweenSides, ///< If the player is between the sides of the quad, but NOT + ///< between this checkpoint and next; the player is likely in + ///< the same checkpoint group + }; + + MapdataCheckPoint(const SData *data); + void read(EGG::Stream &stream); + void setPlayerFlags(s32 playerIdx); + + /// @beginGetters + [[nodiscard]] const EGG::Vector2f &left() const; + [[nodiscard]] const EGG::Vector2f &right() const; + [[nodiscard]] u8 jugemIndex() const; + [[nodiscard]] s8 type() const; + [[nodiscard]] bool isNormalCheckpoint() const; + [[nodiscard]] bool isFinishLine() const; + /// @endGetters + + void linkPrevKcpIds(u8 prevKcpId); + +private: + const SData *m_rawData; + EGG::Vector2f m_left; + EGG::Vector2f m_right; + u8 m_jugemIndex; ///< Index of respawn point associated with this checkpoint. Players who die + ///< here will be respawned at this point. + /// Either: + /// - a @ref `NORMAL_CHECKPOINT` (0) used to calulate respawns, + /// - a @ref `FINISH_LINE` (-1) which updates the lap count when crossed, or + /// - a "key checkpoint" (1-127) used to ensure racers travel around the entire + /// course before proceeding to the next lap. the type value represents the index, + /// i.e. racers must pass checkpoint with @ref `m_type` 1, then 2, then 3 etc.. + s8 m_type; + u8 m_prevPt; + u8 m_nextPt; + u16 m_nextCount; + u16 m_prevCount; + EGG::Vector2f m_midpoint; + EGG::Vector2f m_dir; + bool m_flag; ///< visited flag, for recursive functions. + u16 m_id; + u8 m_prevKcpId; ///< @unused + MapdataCheckPoint *m_prevPoints[6]; + LinkedCheckpoint m_nextPoints[6]; + + static constexpr s8 NORMAL_CHECKPOINT = -1; ///< Only used for picking respawn position + static constexpr s8 FINISH_LINE = 0; ///< Triggers a lap change +}; + +class MapdataCheckPointAccessor + : public MapdataAccessorBase { +public: + MapdataCheckPointAccessor(const MapSectionHeader *header); + ~MapdataCheckPointAccessor() override; + + [[nodiscard]] s8 lastKcpType() const; + +private: + void findFinishAndLastKcp(); + void init(); + + s8 m_lastKcpType; + u16 m_finishLineCheckpointId; +}; + +} // namespace System