From fffd3f7fe92c6eb45b68c0ca522066bf9c54abb2 Mon Sep 17 00:00:00 2001 From: MegaMech Date: Fri, 14 Nov 2025 14:50:33 -0700 Subject: [PATCH] RaceManager class (#562) * Create RaceManager.cpp * Create RaceManager class for race lifecycle management Added RaceManager class to manage race events lifecycle. * Refactor World class and implement ClearWorld method Refactor World class constructor and destructor. Implement ClearWorld method to delete all objects and reset state. * Add RaceManager to World class * Update ValidateString for editor mode checks Refactor ValidateString to handle editor mode and empty strings. * Update Text.cpp * Add SetText method to Text class * Document RunGarbageCollector function Added documentation for the RunGarbageCollector function. * Refactor Game.cpp by removing dead code Removed unused ruleset handling and clean-up code. * Update Game.h * Remove CM_SpawnFromLevelProps call * Update Text.cpp * Update World.cpp * Add Clean method to RaceManager class * Update RaceManager.cpp * Update World.cpp * Update World.h * Update World.cpp --- src/engine/GarbageCollector.cpp | 3 ++ src/engine/RaceManager.cpp | 77 ++++++++++++++++++++++++++++++ src/engine/RaceManager.h | 31 ++++++++++++ src/engine/World.cpp | 49 +++++++++++++++---- src/engine/World.h | 6 ++- src/engine/actors/Text.cpp | 19 ++++++-- src/engine/actors/Text.h | 2 + src/port/Game.cpp | 84 ++++----------------------------- src/port/Game.h | 6 +-- src/racing/actors.c | 3 +- 10 files changed, 183 insertions(+), 97 deletions(-) create mode 100644 src/engine/RaceManager.cpp create mode 100644 src/engine/RaceManager.h diff --git a/src/engine/GarbageCollector.cpp b/src/engine/GarbageCollector.cpp index e7f2d9975..d356602d3 100644 --- a/src/engine/GarbageCollector.cpp +++ b/src/engine/GarbageCollector.cpp @@ -1,6 +1,9 @@ #include "GarbageCollector.h" #include "World.h" +/** + * Removes objects if they are marked for deletion + */ void RunGarbageCollector() { CleanActors(); CleanObjects(); diff --git a/src/engine/RaceManager.cpp b/src/engine/RaceManager.cpp new file mode 100644 index 000000000..ae1ef828f --- /dev/null +++ b/src/engine/RaceManager.cpp @@ -0,0 +1,77 @@ +#include "RaceManager.h" + +#include "AllActors.h" +#include "World.h" +#include "port/Game.h" +#include "engine/editor/Editor.h" +#include "engine/editor/SceneManager.h" + +extern "C" { +#include "render_courses.h" +} + +RaceManager::RaceManager(World& world) : WorldContext(world) { +} + +void RaceManager::Load() { + if (WorldContext.CurrentCourse) { + WorldContext.CurrentCourse->Load(); + } +} + +void RaceManager::PreInit() { + // Ruleset options + if (CVarGetInteger("gDisableItemboxes", false) == true) { + gPlaceItemBoxes = false; + } else { + gPlaceItemBoxes = true; + } +} + +void RaceManager::BeginPlay() { + auto course = WorldContext.CurrentCourse; + + if (course) { + // Do not spawn finishline in credits or battle mode. And if bSpawnFinishline. + if ((gGamestate != CREDITS_SEQUENCE) && (gModeSelection != BATTLE)) { + if (course->bSpawnFinishline) { + if (course->FinishlineSpawnPoint.has_value()) { + AFinishline::Spawn(course->FinishlineSpawnPoint.value(), IRotator(0, 0, 0)); + } else { + AFinishline::Spawn(); + } + + } + } + gEditor.AddLight("Sun", nullptr, D_800DC610[1].l->l.dir); + + course->BeginPlay(); + } +} + +void RaceManager::PostInit() { + // Ruleset options + if (CVarGetInteger("gAllThwompsAreMarty", false) == true) { + for (auto object : gWorldInstance.Objects) { + if (OThwomp* thwomp = dynamic_cast(object)) { + gObjectList[thwomp->_objectIndex].unk_0D5 = OThwomp::States::JAILED; // Sets all the thwomp behaviour flags to marty + thwomp->Behaviour = OThwomp::States::JAILED; + } + } + } + + if (CVarGetInteger("gAllBombKartsChase", false) == true) { + for (auto object : gWorldInstance.Objects) { + if (OBombKart* kart = dynamic_cast(object)) { + kart->Behaviour = OBombKart::States::CHASE; + } + } + } + + if (CVarGetInteger("gGoFish", false) == true) { + OTrophy::Spawn(FVector(0,0,0), OTrophy::TrophyType::GOLD, OTrophy::Behaviour::GO_FISH); + } +} + +void RaceManager::Clean() { +} diff --git a/src/engine/RaceManager.h b/src/engine/RaceManager.h new file mode 100644 index 000000000..fb38bb33c --- /dev/null +++ b/src/engine/RaceManager.h @@ -0,0 +1,31 @@ +#pragma once + +class World; + +/** + * This may eventually become GameMode class + * + * The RaceManager orchestrates the event lifecycle + * It defines when a race begins, actors spawn, and the race ends. + * + * It does not define the game mode itself. + * + * You must call the base function if you want the + * default functionality to run + * Example: + * MyRaceManager::BeginPlay() { + * RaceManager::BeginPlay() // <-- Calls default functionality + * // My code here + * } + */ +class RaceManager { +public: + RaceManager(World& world); + virtual void Load(); // virtual required here in the base class to allow inherited classes to override + virtual void PreInit(); + virtual void BeginPlay(); + virtual void PostInit(); + virtual void Clean(); +protected: + World& WorldContext; +}; diff --git a/src/engine/World.cpp b/src/engine/World.cpp index 0430edf6d..cca7a2199 100644 --- a/src/engine/World.cpp +++ b/src/engine/World.cpp @@ -21,14 +21,17 @@ extern "C" { #include "mario_raceway_data.h" } -World::World() {} -World::~World() { - CM_CleanWorld(); -} - std::shared_ptr CurrentCourse; Cup* CurrentCup; +World::World() { + RaceManagerInstance = std::make_unique(*this); +} + +World::~World() { + ClearWorld(); +} + std::shared_ptr World::AddCourse(std::shared_ptr course) { gWorldInstance.Courses.push_back(course); return course; @@ -252,13 +255,39 @@ Object* World::GetObjectByIndex(size_t index) { return nullptr; // Or handle the error as needed } +// Deletes all objects from the world void World::ClearWorld(void) { - CM_CleanWorld(); + printf("[Game.cpp] Clean World\n"); + World* world = &gWorldInstance; + for (auto& actor : world->Actors) { + delete actor; + } - // for (size_t i = 0; i < ARRAY_COUNT(gCollisionMesh); i++) { + gWorldInstance.Reset(); // Reset OObjects + for (auto& object : world->Objects) { + delete object; + } - // } + for (auto& emitter : world->Emitters) { + delete emitter; + } + + for (auto& actor : world->StaticMeshActors) { + delete actor; + } + + for (size_t i = 0; i < ARRAY_COUNT(gWorldInstance.playerBombKart); i++) { + gWorldInstance.playerBombKart[i].state = PlayerBombKart::PlayerBombKartState::DISABLED; + gWorldInstance.playerBombKart[i]._primAlpha = 0; + } + + gEditor.ClearObjects(); + gWorldInstance.Actors.clear(); + gWorldInstance.StaticMeshActors.clear(); + gWorldInstance.Objects.clear(); + gWorldInstance.Emitters.clear(); + gWorldInstance.Lakitus.clear(); + + gWorldInstance.GetRaceManager().Clean(); - // gCollisionMesh - // Paths } diff --git a/src/engine/World.h b/src/engine/World.h index 52a307be5..4e1e6e06e 100644 --- a/src/engine/World.h +++ b/src/engine/World.h @@ -9,6 +9,7 @@ #include "TrainCrossing.h" #include #include +#include "RaceManager.h" #include "Actor.h" #include "StaticMeshActor.h" #include "particles/ParticleEmitter.h" @@ -50,6 +51,9 @@ public: explicit World(); ~World(); + RaceManager& GetRaceManager() { return *RaceManagerInstance; } + void SetRaceManager(std::unique_ptr manager) { RaceManagerInstance = std::move(manager); } + std::shared_ptr AddCourse(std::shared_ptr course); AActor* AddActor(AActor* actor); @@ -130,7 +134,7 @@ public: std::vector> Courses; size_t CourseIndex = 0; // For browsing courses. private: - + std::unique_ptr RaceManagerInstance; }; extern World gWorldInstance; diff --git a/src/engine/actors/Text.cpp b/src/engine/actors/Text.cpp index 2d2fab0b0..06a74c54d 100644 --- a/src/engine/actors/Text.cpp +++ b/src/engine/actors/Text.cpp @@ -77,7 +77,11 @@ AText::AText(const SpawnParams& params) : AActor(params) { * But these need to be checked thoroughly before white-listing. */ std::string AText::ValidateString(const std::string_view& s) { - if (s.empty()) { return "Blank Text"; } + if (CVarGetInteger("gIsEditorEnabled", false) == true) { + if (s.empty()) { return "Blank Text"; } + } else { + if (s.empty()) { return ""; } + } Text.clear(); @@ -100,6 +104,11 @@ std::string AText::ValidateString(const std::string_view& s) { return Text; } +void AText::SetText(std::string text) { + AText::ValidateString(text); + Refresh(); +} + /* * Most changes during runtime require a refresh because the text is generated statically * with the intention of this code being somewhat performant @@ -353,7 +362,9 @@ void AText::DrawText3D(Camera* camera) { // Based on func_80095BD0 FrameInterpolation_RecordOpenChild("actor_text", TAG_LETTER(this)); gSPDisplayList(gDisplayListHead++, (Gfx*)D_020077A8); - switch (1) { + + for (CharacterList& tex : TextureList) { + switch (tex.mode) { case 1: gSPDisplayList(gDisplayListHead++, (Gfx*)D_020077F8); break; @@ -361,8 +372,6 @@ void AText::DrawText3D(Camera* camera) { // Based on func_80095BD0 gSPDisplayList(gDisplayListHead++, (Gfx*)D_02007818); break; } - - for (CharacterList& tex : TextureList) { //printf("tex texture %p width %d height %d mode %d col %f\n", tex.Texture, tex.width, tex.height, tex.mode, tex.column); gDPLoadTextureTile_4b(gDisplayListHead++, (Gfx*)tex.Texture, G_IM_FMT_I, tex.width, 0, 0, 0, tex.width, tex.height + 2, 0, G_TX_NOMIRROR | G_TX_CLAMP, G_TX_NOMIRROR | G_TX_CLAMP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, @@ -585,4 +594,4 @@ void AText::DrawColourEditor(bool* updated) { } } } -} \ No newline at end of file +} diff --git a/src/engine/actors/Text.h b/src/engine/actors/Text.h index 7a9ac7407..e0b436709 100644 --- a/src/engine/actors/Text.h +++ b/src/engine/actors/Text.h @@ -118,6 +118,8 @@ public: void DrawColourEditor(bool* updated); void FollowPlayer(); + void SetText(std::string text); + std::string ValidateString(const std::string_view& text); void Refresh(); void Print3D(char* text, s32 tracking, s32 mode); diff --git a/src/port/Game.cpp b/src/port/Game.cpp index 37282346d..d3fb061e0 100644 --- a/src/port/Game.cpp +++ b/src/port/Game.cpp @@ -90,7 +90,6 @@ Cup* gBattleCup; ModelLoader gModelLoader; HarbourMastersIntro gMenuIntro; -Rulesets gRulesets; Editor::Editor gEditor; @@ -194,14 +193,6 @@ void HM_DrawIntro() { gMenuIntro.HM_DrawIntro(); } -void CM_SpawnFromLevelProps() { - // Spawning actors needs to be delayed to the correct time. - // And loadlevel needs to happen asap - - //Editor::LoadLevel(nullptr); - // Editor::SpawnFromLevelProps(); -} - // Set default course; mario raceway void SetMarioRaceway(void) { SetCourseById(0); @@ -239,10 +230,7 @@ const char* GetCupName(void) { } void LoadCourse() { - if (gWorldInstance.CurrentCourse) { - gRulesets.PreLoad(); - gWorldInstance.CurrentCourse->Load(); - } + gWorldInstance.GetRaceManager().Load(); } size_t GetCourseIndex() { @@ -383,28 +371,15 @@ void CM_DrawStaticMeshActors() { } void CM_BeginPlay() { - auto course = gWorldInstance.CurrentCourse; - - if (course) { - Editor::LoadLevel(course.get(), course->SceneFilePtr); - - gRulesets.PreInit(); - // Do not spawn finishline in credits or battle mode. And if bSpawnFinishline. - if ((gGamestate != CREDITS_SEQUENCE) && (gModeSelection != BATTLE)) { - if (course->bSpawnFinishline) { - if (course->FinishlineSpawnPoint.has_value()) { - AFinishline::Spawn(course->FinishlineSpawnPoint.value(), IRotator(0, 0, 0)); - } else { - AFinishline::Spawn(); - } - - } - } - gEditor.AddLight("Sun", nullptr, D_800DC610[1].l->l.dir); - - course->BeginPlay(); - gRulesets.PostInit(); + if (gWorldInstance.CurrentCourse) { + // This line should likely be moved. + // It's here so PreInit is after the scene file has been loaded + // It used to be at the start of BeginPlay + Editor::LoadLevel(gWorldInstance.CurrentCourse.get(), gWorldInstance.CurrentCourse->SceneFilePtr); } + gWorldInstance.GetRaceManager().PreInit(); + gWorldInstance.GetRaceManager().BeginPlay(); + gWorldInstance.GetRaceManager().PostInit(); } void CM_TickObjects() { @@ -451,16 +426,6 @@ void CM_DrawParticles(s32 cameraId) { } } -// Helps prevents users from forgetting to add a finishline to their course -bool CM_DoesFinishlineExist() { - for (AActor* actor : gWorldInstance.Actors) { - if (dynamic_cast(actor)) { - return true; - } - } - return false; -} - void CM_InitClouds() { if (gWorldInstance.CurrentCourse) { gWorldInstance.CurrentCourse->InitClouds(); @@ -661,36 +626,7 @@ void CM_DeleteActor(size_t index) { * Clean up actors and other game objects. */ void CM_CleanWorld(void) { - printf("[Game.cpp] Clean World\n"); - World* world = &gWorldInstance; - for (auto& actor : world->Actors) { - delete actor; - } - - gWorldInstance.Reset(); // Reset OObjects - for (auto& object : world->Objects) { - delete object; - } - - for (auto& emitter : world->Emitters) { - delete emitter; - } - - for (auto& actor : world->StaticMeshActors) { - delete actor; - } - - for (size_t i = 0; i < ARRAY_COUNT(gWorldInstance.playerBombKart); i++) { - gWorldInstance.playerBombKart[i].state = PlayerBombKart::PlayerBombKartState::DISABLED; - gWorldInstance.playerBombKart[i]._primAlpha = 0; - } - - gEditor.ClearObjects(); - gWorldInstance.Actors.clear(); - gWorldInstance.StaticMeshActors.clear(); - gWorldInstance.Objects.clear(); - gWorldInstance.Emitters.clear(); - gWorldInstance.Lakitus.clear(); + gWorldInstance.ClearWorld(); } struct Actor* CM_AddBaseActor() { diff --git a/src/port/Game.h b/src/port/Game.h index 062b9aafb..0982d649b 100644 --- a/src/port/Game.h +++ b/src/port/Game.h @@ -30,8 +30,6 @@ void HM_InitIntro(void); void HM_TickIntro(void); void HM_DrawIntro(void); -void CM_SpawnFromLevelProps(); - void CM_DisplayBattleBombKart(s32 playerId, s32 primAlpha); void CM_DrawBattleBombKarts(s32 cameraId); @@ -68,8 +66,6 @@ void CM_ActivateSecondLapLakitu(s32 playerId); void CM_ActivateFinalLapLakitu(s32 playerId); void CM_ActivateReverseLakitu(s32 playerId); -bool CM_DoesFinishlineExist(); - void CM_InitClouds(); void CM_DrawActors(Camera* camera); @@ -224,4 +220,4 @@ void CM_RunGarbageCollector(void); } #endif -#endif // _GAME_H \ No newline at end of file +#endif // _GAME_H diff --git a/src/racing/actors.c b/src/racing/actors.c index 11bd79203..d9a59400a 100644 --- a/src/racing/actors.c +++ b/src/racing/actors.c @@ -1318,7 +1318,6 @@ void init_actors_and_load_textures(void) { destroy_all_actors(); CM_CleanWorld(); - CM_SpawnFromLevelProps(); CM_BeginPlay(); spawn_course_actors(); } @@ -2896,4 +2895,4 @@ const char* get_actor_resource_location_name(s32 id) { default: return "mk:actor"; } -} \ No newline at end of file +}