From adc7dd050f3a2fd13b85b2d799154061d196ef09 Mon Sep 17 00:00:00 2001 From: MegaMech Date: Sun, 20 Jul 2025 17:13:35 -0600 Subject: [PATCH] Add Rulesets (#494) --- src/code_800029B0.c | 2 +- src/code_800029B0.h | 1 + src/engine/Rulesets.cpp | 48 +++++++++++++++++++ src/engine/Rulesets.h | 11 +++++ src/engine/courses/KalimariDesert.cpp | 4 ++ src/engine/courses/ToadsTurnpike.cpp | 5 ++ src/engine/objects/BombKart.cpp | 5 ++ src/port/Game.cpp | 5 ++ src/port/ui/PortMenu.cpp | 67 +++++++++++++++++++++++++++ src/port/ui/PortMenu.h | 1 + src/racing/memory.c | 8 ++-- src/racing/memory.h | 2 - 12 files changed, 152 insertions(+), 7 deletions(-) create mode 100644 src/engine/Rulesets.cpp create mode 100644 src/engine/Rulesets.h diff --git a/src/code_800029B0.c b/src/code_800029B0.c index 32473d14b..562a9fa3a 100644 --- a/src/code_800029B0.c +++ b/src/code_800029B0.c @@ -65,7 +65,7 @@ bool gIsEditorPaused = false; u8* pAppNmiBuffer = (u8*) &osAppNmiBuffer; s32 gIsMirrorMode = 0; -f32 vtxStretchY = 1.0f; +Vec3f gVtxStretch = {1.0f, 1.0f, 1.0f}; Lights1 D_800DC610[] = { gdSPDefLights1(175, 175, 175, 255, 255, 255, 0, 0, 120), //! @todo impl lighting in custom track origin value 115 instead of 209. Hack fix for lighting for now diff --git a/src/code_800029B0.h b/src/code_800029B0.h index 8556248d8..85fa36b6e 100644 --- a/src/code_800029B0.h +++ b/src/code_800029B0.h @@ -66,6 +66,7 @@ extern u8* pAppNmiBuffer; extern s32 gIsMirrorMode; // D_800DC604 extern s16 gCreditsCourseId; extern s16 gPlaceItemBoxes; +extern Vec3f gVtxStretch; extern CollisionTriangle* gCollisionMesh; extern u16* gCollisionIndices; diff --git a/src/engine/Rulesets.cpp b/src/engine/Rulesets.cpp new file mode 100644 index 000000000..acdbfde2a --- /dev/null +++ b/src/engine/Rulesets.cpp @@ -0,0 +1,48 @@ +#include "Rulesets.h" +#include "objects/Thwomp.h" +#include "objects/Trophy.h" + +extern "C" { +#include "code_800029B0.h" +#include "memory.h" +} + +// Before the game is loaded, and before vertices and displaylists are unpacked. +// Only runs a single time at the beginning of a track. +void Rulesets::PreLoad() { +} + +// Just before BeginPlay() (used to spawn actors) is ran. +// Only runs a single time at the beginning of a track. +void Rulesets::PreInit() { + if (CVarGetInteger("gDisableItemboxes", false) == true) { + gPlaceItemBoxes = false; + } else { + gPlaceItemBoxes = true; + } +} + +// Just after BeginPlay() is ran. +// Only runs a single time at the beginning of a track. +void Rulesets::PostInit() { + 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->State = OThwomp::States::JAILED; + } + } + } + + if (CVarGetInteger("gAllBombKartsChase", false) == true) { + for (auto object : gWorldInstance.Objects) { + if (OBombKart* kart = dynamic_cast(object)) { + kart->State = OBombKart::States::CHASE; + } + } + } + + if (CVarGetInteger("gGoFish", false) == true) { + gWorldInstance.AddObject(new OTrophy(FVector(0,0,0), OTrophy::TrophyType::GOLD, OTrophy::Behaviour::GO_FISH)); + } +} diff --git a/src/engine/Rulesets.h b/src/engine/Rulesets.h new file mode 100644 index 000000000..26e67b594 --- /dev/null +++ b/src/engine/Rulesets.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include "World.h" + +class Rulesets { +public: + virtual void PreLoad(); + virtual void PreInit(); + virtual void PostInit(); +}; diff --git a/src/engine/courses/KalimariDesert.cpp b/src/engine/courses/KalimariDesert.cpp index 369828581..3ecb3f03d 100644 --- a/src/engine/courses/KalimariDesert.cpp +++ b/src/engine/courses/KalimariDesert.cpp @@ -192,6 +192,10 @@ void KalimariDesert::BeginPlay() { s32 centerWaypoint = 160; + _numTrains = CVarGetInteger("gNumTrains", 2); + _numCarriages = CVarGetInteger("gNumCarriages", 5); + _tender = (ATrain::TenderStatus)CVarGetInteger("gHasTender", 1); + // Spawn two trains for (size_t i = 0; i < _numTrains; ++i) { uint32_t waypoint = CalculateWaypointDistribution(i, _numTrains, gVehicle2DPathLength, centerWaypoint); diff --git a/src/engine/courses/ToadsTurnpike.cpp b/src/engine/courses/ToadsTurnpike.cpp index 0d79cd942..c9f8b9918 100644 --- a/src/engine/courses/ToadsTurnpike.cpp +++ b/src/engine/courses/ToadsTurnpike.cpp @@ -176,6 +176,11 @@ void ToadsTurnpike::BeginPlay() { a /= 2; // Normally vehicle logic is only ran every 2 frames. This slows the vehicles down to match. b /= 2; + _numTrucks = CVarGetInteger("gNumTrucks", 7); + _numBuses = CVarGetInteger("gNumBuses", 7); + _numTankerTrucks = CVarGetInteger("gNumTankerTrucks", 7); + _numCars = CVarGetInteger("gNumCars", 7); + // Other game modes spawn seven of each vehicle if (gModeSelection == TIME_TRIALS) { _numTrucks = 8; diff --git a/src/engine/objects/BombKart.cpp b/src/engine/objects/BombKart.cpp index a9dd3a22e..78d4762a9 100644 --- a/src/engine/objects/BombKart.cpp +++ b/src/engine/objects/BombKart.cpp @@ -253,6 +253,11 @@ void OBombKart::Tick() { } break; case States::CHASE: + // Prevents chasing during race staging + if (gRaceState != RACE_IN_PROGRESS) { + return; + } + if (!_target) { _target = OBombKart::FindTarget(); } else { diff --git a/src/port/Game.cpp b/src/port/Game.cpp index 068651bff..62dec7a16 100644 --- a/src/port/Game.cpp +++ b/src/port/Game.cpp @@ -48,6 +48,7 @@ #include "engine/editor/Editor.h" #include "engine/editor/EditorMath.h" #include "engine/editor/SceneManager.h" +#include "engine/Rulesets.h" #ifdef _WIN32 #include @@ -84,6 +85,7 @@ Cup* gBattleCup; ModelLoader gModelLoader; HarbourMastersIntro gMenuIntro; +Rulesets gRulesets; Editor::Editor gEditor; @@ -236,6 +238,7 @@ const char* GetCupName(void) { void LoadCourse() { if (gWorldInstance.CurrentCourse) { + gRulesets.PreLoad(); gWorldInstance.CurrentCourse->Load(); } } @@ -379,6 +382,7 @@ void CM_BeginPlay() { auto course = gWorldInstance.CurrentCourse; if (course) { + gRulesets.PreInit(); // Do not spawn finishline in credits or battle mode. And if bSpawnFinishline. if ((gGamestate != CREDITS_SEQUENCE) && (gModeSelection != BATTLE)) { if (course->bSpawnFinishline) { @@ -388,6 +392,7 @@ void CM_BeginPlay() { gEditor.AddLight("Sun", nullptr, D_800DC610[1].l->l.dir); course->BeginPlay(); + gRulesets.PostInit(); } } diff --git a/src/port/ui/PortMenu.cpp b/src/port/ui/PortMenu.cpp index ba180d5b9..13ec40f51 100644 --- a/src/port/ui/PortMenu.cpp +++ b/src/port/ui/PortMenu.cpp @@ -10,6 +10,10 @@ #include #include "ResolutionEditor.h" +#include "courses/Course.h" +#include "courses/KalimariDesert.h" +#include "courses/ToadsTurnpike.h" + #ifdef __SWITCH__ #include #endif @@ -383,6 +387,8 @@ void PortMenu::AddEnhancements() { .CVar("gShowSpaghettiVersion") .Options(CheckboxOptions().Tooltip("Show the Spaghetti Kart version on the Mario Kart menu").DefaultValue(true)); + AddRulesets(); + path = { "Enhancements", "Cheats", SECTION_COLUMN_1 }; AddSidebarEntry("Enhancements", "Cheats", 3); AddWidget(path, "Moon Jump", WIDGET_CVAR_CHECKBOX).CVar("gEnableMoonJump"); @@ -424,6 +430,67 @@ static const std::unordered_map switchCPUProfiles = { }; #endif +void PortMenu::AddRulesets() { + WidgetPath path = { "Enhancements", "Rulesets", SECTION_COLUMN_1 }; + AddSidebarEntry("Enhancements", "Rulesets", 3); + + // Requires more testing + // AddWidget(path, "Number of Laps", WIDGET_CVAR_SLIDER_INT) + // .CVar("gNumLaps") + // .Options(UIWidgets::IntSliderOptions().Min().Max(20).Step(1).DefaultValue(3)); + + AddWidget(path, "No Itemboxes", WIDGET_CVAR_CHECKBOX) + .CVar("gDisableItemboxes") + .Options(CheckboxOptions().Tooltip( + "Prevents Itemboxes from spawning")); + AddWidget(path, "All Thwomps are Marty", WIDGET_CVAR_CHECKBOX) + .CVar("gAllThwompsAreMarty") + .Options(CheckboxOptions().Tooltip( + "All Thwomps are Marty")); + AddWidget(path, "All Bomb Karts in Chase Mode", WIDGET_CVAR_CHECKBOX) + .CVar("gAllBombKartsChase") + .Options(CheckboxOptions().Tooltip( + "These karts will chase you!!!")); + AddWidget(path, "Get the trophies!", WIDGET_CVAR_CHECKBOX) + .CVar("gGoFish") + .Options(CheckboxOptions().Tooltip( + "Collect as many trophies as you can. Racer with the most trophies wins!")); + AddWidget(path, "Track X Stretch", WIDGET_SLIDER_FLOAT) + .ValuePointer(&gVtxStretch[0]) + .Options(UIWidgets::FloatSliderOptions().Min(0.1f).Max(10.0f).Step(0.1f).Format("%.2f")); + AddWidget(path, "Track Y Stretch", WIDGET_SLIDER_FLOAT) + .ValuePointer(&gVtxStretch[1]) + .Options(UIWidgets::FloatSliderOptions().Min(0.1f).Max(10.0f).Step(0.1f).Format("%.2f")); + AddWidget(path, "Track Z Stretch", WIDGET_SLIDER_FLOAT) + .ValuePointer(&gVtxStretch[2]) + .Options(UIWidgets::FloatSliderOptions().Min(0.1f).Max(10.0f).Step(0.1f).Format("%.2f")); + + + AddWidget(path, "Trains", WIDGET_CVAR_SLIDER_INT) + .CVar("gNumTrains") + .Options(UIWidgets::IntSliderOptions().Min(0).Max(19).Step(1).DefaultValue(2)); + AddWidget(path, "Carriages", WIDGET_CVAR_SLIDER_INT) + .CVar("gNumCarriages") + .Options(UIWidgets::IntSliderOptions().Min(0).Max(74).Step(1).DefaultValue(5)); + AddWidget(path, "Train has a tender", WIDGET_CVAR_CHECKBOX) + .CVar("gHasTender") + .Options(UIWidgets::CheckboxOptions().DefaultValue(1) + .Tooltip("This option is only valid if there are no carriages on the train")); + + AddWidget(path, "Trucks", WIDGET_CVAR_SLIDER_INT) + .CVar("gNumTrucks") + .Options(UIWidgets::IntSliderOptions().Min(0).Max(50).Step(1).DefaultValue(7)); + AddWidget(path, "Buses", WIDGET_CVAR_SLIDER_INT) + .CVar("gNumBuses") + .Options(UIWidgets::IntSliderOptions().Min(0).Max(50).Step(1).DefaultValue(7)); + AddWidget(path, "Tanker Trucks", WIDGET_CVAR_SLIDER_INT) + .CVar("gNumTankerTrucks") + .Options(UIWidgets::IntSliderOptions().Min(0).Max(50).Step(1).DefaultValue(7)); + AddWidget(path, "Cars", WIDGET_CVAR_SLIDER_INT) + .CVar("gNumCars") + .Options(UIWidgets::IntSliderOptions().Min(0).Max(50).Step(1).DefaultValue(7)); +} + void PortMenu::AddDevTools() { AddMenuEntry("Developer", "gSettings.Menu.DevToolsSidebarSection"); AddSidebarEntry("Developer", "General", 3); diff --git a/src/port/ui/PortMenu.h b/src/port/ui/PortMenu.h index a96e3570e..32cb9148e 100644 --- a/src/port/ui/PortMenu.h +++ b/src/port/ui/PortMenu.h @@ -65,6 +65,7 @@ class PortMenu : public Ship::Menu { WidgetInfo& AddWidget(WidgetPath& pathInfo, std::string widgetName, WidgetType widgetType); void AddSettings(); void AddEnhancements(); + void AddRulesets(); void AddDevTools(); }; } // namespace BenGui diff --git a/src/racing/memory.c b/src/racing/memory.c index 679bbf69e..088f7e3f7 100644 --- a/src/racing/memory.c +++ b/src/racing/memory.c @@ -612,19 +612,19 @@ void func_802A86A8(CourseVtx* data, Vtx* vtx, size_t arg1) { // s32 to uintptr_t comparison required for matching. for (i = 0; i < arg1; i++) { if (gIsMirrorMode) { - vtx->v.ob[0] = -courseVtx->ob[0]; + vtx->v.ob[0] = -(courseVtx->ob[0] * gVtxStretch[0]); } else { - vtx->v.ob[0] = courseVtx->ob[0]; + vtx->v.ob[0] = courseVtx->ob[0] * gVtxStretch[0]; } - vtx->v.ob[1] = (courseVtx->ob[1] * vtxStretchY); + vtx->v.ob[1] = (courseVtx->ob[1] * gVtxStretch[1]); temp_a0 = courseVtx->ca[0]; temp_a3 = courseVtx->ca[1]; flags = temp_a0 & 3; flags |= (temp_a3 << 2) & 0xC; - vtx->v.ob[2] = courseVtx->ob[2]; + vtx->v.ob[2] = courseVtx->ob[2] * gVtxStretch[2]; vtx->v.tc[0] = courseVtx->tc[0]; vtx->v.tc[1] = courseVtx->tc[1]; vtx->v.cn[0] = (temp_a0 & 0xFC); diff --git a/src/racing/memory.h b/src/racing/memory.h index 557d9f02f..72ecd0eb5 100644 --- a/src/racing/memory.h +++ b/src/racing/memory.h @@ -41,8 +41,6 @@ struct AllocOnlyPool { #define ALIGN4(val) (((val) + 0x3) & ~0x3) -extern f32 vtxStretchY; - u8* load_lakitu_tlut_x64(const char** textureList, size_t length); void* get_next_available_memory_addr(uintptr_t); uintptr_t set_segment_base_addr(s32, void*);