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
This commit is contained in:
MegaMech 2025-11-14 14:50:33 -07:00 committed by GitHub
parent c2755aee40
commit fffd3f7fe9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 183 additions and 97 deletions

View File

@ -1,6 +1,9 @@
#include "GarbageCollector.h" #include "GarbageCollector.h"
#include "World.h" #include "World.h"
/**
* Removes objects if they are marked for deletion
*/
void RunGarbageCollector() { void RunGarbageCollector() {
CleanActors(); CleanActors();
CleanObjects(); CleanObjects();

View File

@ -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<OThwomp*>(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<OBombKart*>(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() {
}

31
src/engine/RaceManager.h Normal file
View File

@ -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;
};

View File

@ -21,14 +21,17 @@ extern "C" {
#include "mario_raceway_data.h" #include "mario_raceway_data.h"
} }
World::World() {}
World::~World() {
CM_CleanWorld();
}
std::shared_ptr<Course> CurrentCourse; std::shared_ptr<Course> CurrentCourse;
Cup* CurrentCup; Cup* CurrentCup;
World::World() {
RaceManagerInstance = std::make_unique<RaceManager>(*this);
}
World::~World() {
ClearWorld();
}
std::shared_ptr<Course> World::AddCourse(std::shared_ptr<Course> course) { std::shared_ptr<Course> World::AddCourse(std::shared_ptr<Course> course) {
gWorldInstance.Courses.push_back(course); gWorldInstance.Courses.push_back(course);
return course; return course;
@ -252,13 +255,39 @@ Object* World::GetObjectByIndex(size_t index) {
return nullptr; // Or handle the error as needed return nullptr; // Or handle the error as needed
} }
// Deletes all objects from the world
void World::ClearWorld(void) { 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
} }

View File

@ -9,6 +9,7 @@
#include "TrainCrossing.h" #include "TrainCrossing.h"
#include <memory> #include <memory>
#include <unordered_map> #include <unordered_map>
#include "RaceManager.h"
#include "Actor.h" #include "Actor.h"
#include "StaticMeshActor.h" #include "StaticMeshActor.h"
#include "particles/ParticleEmitter.h" #include "particles/ParticleEmitter.h"
@ -50,6 +51,9 @@ public:
explicit World(); explicit World();
~World(); ~World();
RaceManager& GetRaceManager() { return *RaceManagerInstance; }
void SetRaceManager(std::unique_ptr<RaceManager> manager) { RaceManagerInstance = std::move(manager); }
std::shared_ptr<Course> AddCourse(std::shared_ptr<Course> course); std::shared_ptr<Course> AddCourse(std::shared_ptr<Course> course);
AActor* AddActor(AActor* actor); AActor* AddActor(AActor* actor);
@ -130,7 +134,7 @@ public:
std::vector<std::shared_ptr<Course>> Courses; std::vector<std::shared_ptr<Course>> Courses;
size_t CourseIndex = 0; // For browsing courses. size_t CourseIndex = 0; // For browsing courses.
private: private:
std::unique_ptr<RaceManager> RaceManagerInstance;
}; };
extern World gWorldInstance; extern World gWorldInstance;

View File

@ -77,7 +77,11 @@ AText::AText(const SpawnParams& params) : AActor(params) {
* But these need to be checked thoroughly before white-listing. * But these need to be checked thoroughly before white-listing.
*/ */
std::string AText::ValidateString(const std::string_view& s) { 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(); Text.clear();
@ -100,6 +104,11 @@ std::string AText::ValidateString(const std::string_view& s) {
return Text; 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 * Most changes during runtime require a refresh because the text is generated statically
* with the intention of this code being somewhat performant * 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)); FrameInterpolation_RecordOpenChild("actor_text", TAG_LETTER(this));
gSPDisplayList(gDisplayListHead++, (Gfx*)D_020077A8); gSPDisplayList(gDisplayListHead++, (Gfx*)D_020077A8);
switch (1) {
for (CharacterList& tex : TextureList) {
switch (tex.mode) {
case 1: case 1:
gSPDisplayList(gDisplayListHead++, (Gfx*)D_020077F8); gSPDisplayList(gDisplayListHead++, (Gfx*)D_020077F8);
break; break;
@ -361,8 +372,6 @@ void AText::DrawText3D(Camera* camera) { // Based on func_80095BD0
gSPDisplayList(gDisplayListHead++, (Gfx*)D_02007818); gSPDisplayList(gDisplayListHead++, (Gfx*)D_02007818);
break; 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); //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, 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, 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) {
} }
} }
} }
} }

View File

@ -118,6 +118,8 @@ public:
void DrawColourEditor(bool* updated); void DrawColourEditor(bool* updated);
void FollowPlayer(); void FollowPlayer();
void SetText(std::string text);
std::string ValidateString(const std::string_view& text); std::string ValidateString(const std::string_view& text);
void Refresh(); void Refresh();
void Print3D(char* text, s32 tracking, s32 mode); void Print3D(char* text, s32 tracking, s32 mode);

View File

@ -90,7 +90,6 @@ Cup* gBattleCup;
ModelLoader gModelLoader; ModelLoader gModelLoader;
HarbourMastersIntro gMenuIntro; HarbourMastersIntro gMenuIntro;
Rulesets gRulesets;
Editor::Editor gEditor; Editor::Editor gEditor;
@ -194,14 +193,6 @@ void HM_DrawIntro() {
gMenuIntro.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 // Set default course; mario raceway
void SetMarioRaceway(void) { void SetMarioRaceway(void) {
SetCourseById(0); SetCourseById(0);
@ -239,10 +230,7 @@ const char* GetCupName(void) {
} }
void LoadCourse() { void LoadCourse() {
if (gWorldInstance.CurrentCourse) { gWorldInstance.GetRaceManager().Load();
gRulesets.PreLoad();
gWorldInstance.CurrentCourse->Load();
}
} }
size_t GetCourseIndex() { size_t GetCourseIndex() {
@ -383,28 +371,15 @@ void CM_DrawStaticMeshActors() {
} }
void CM_BeginPlay() { void CM_BeginPlay() {
auto course = gWorldInstance.CurrentCourse; if (gWorldInstance.CurrentCourse) {
// This line should likely be moved.
if (course) { // It's here so PreInit is after the scene file has been loaded
Editor::LoadLevel(course.get(), course->SceneFilePtr); // It used to be at the start of BeginPlay
Editor::LoadLevel(gWorldInstance.CurrentCourse.get(), gWorldInstance.CurrentCourse->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();
} }
gWorldInstance.GetRaceManager().PreInit();
gWorldInstance.GetRaceManager().BeginPlay();
gWorldInstance.GetRaceManager().PostInit();
} }
void CM_TickObjects() { 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<AFinishline*>(actor)) {
return true;
}
}
return false;
}
void CM_InitClouds() { void CM_InitClouds() {
if (gWorldInstance.CurrentCourse) { if (gWorldInstance.CurrentCourse) {
gWorldInstance.CurrentCourse->InitClouds(); gWorldInstance.CurrentCourse->InitClouds();
@ -661,36 +626,7 @@ void CM_DeleteActor(size_t index) {
* Clean up actors and other game objects. * Clean up actors and other game objects.
*/ */
void CM_CleanWorld(void) { void CM_CleanWorld(void) {
printf("[Game.cpp] Clean World\n"); gWorldInstance.ClearWorld();
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();
} }
struct Actor* CM_AddBaseActor() { struct Actor* CM_AddBaseActor() {

View File

@ -30,8 +30,6 @@ void HM_InitIntro(void);
void HM_TickIntro(void); void HM_TickIntro(void);
void HM_DrawIntro(void); void HM_DrawIntro(void);
void CM_SpawnFromLevelProps();
void CM_DisplayBattleBombKart(s32 playerId, s32 primAlpha); void CM_DisplayBattleBombKart(s32 playerId, s32 primAlpha);
void CM_DrawBattleBombKarts(s32 cameraId); void CM_DrawBattleBombKarts(s32 cameraId);
@ -68,8 +66,6 @@ void CM_ActivateSecondLapLakitu(s32 playerId);
void CM_ActivateFinalLapLakitu(s32 playerId); void CM_ActivateFinalLapLakitu(s32 playerId);
void CM_ActivateReverseLakitu(s32 playerId); void CM_ActivateReverseLakitu(s32 playerId);
bool CM_DoesFinishlineExist();
void CM_InitClouds(); void CM_InitClouds();
void CM_DrawActors(Camera* camera); void CM_DrawActors(Camera* camera);
@ -224,4 +220,4 @@ void CM_RunGarbageCollector(void);
} }
#endif #endif
#endif // _GAME_H #endif // _GAME_H

View File

@ -1318,7 +1318,6 @@ void init_actors_and_load_textures(void) {
destroy_all_actors(); destroy_all_actors();
CM_CleanWorld(); CM_CleanWorld();
CM_SpawnFromLevelProps();
CM_BeginPlay(); CM_BeginPlay();
spawn_course_actors(); spawn_course_actors();
} }
@ -2896,4 +2895,4 @@ const char* get_actor_resource_location_name(s32 id) {
default: default:
return "mk:actor"; return "mk:actor";
} }
} }