Files
SpaghettiKart/src/engine/tracks/Track.cpp
T
MegaMech 548ccf0063 Impl Sky and SkyActors (#630)
* Create Cloud.cpp

* Create Cloud.h

* Fix the cloud

* Cleanup

* More cleanup

* Update Track.h

* Refactor SkyboxCloud position calculations

* Update SkyboxStar.cpp

* Update SkyboxStar.cpp

* Refactor SkyboxStar.cpp by removing redundant code

* Update SkyboxCloud.cpp

* Refactor SkyboxSnow.cpp by reordering includes

* Update SkyboxCloud.h

* Refactor SkyboxStar.h for improved formatting

* Impl skyboxcloud

* Update comment

* update comment

* Work now

* Fully impl Sky

* Fix define

* Fix args
2026-02-24 14:51:25 -07:00

578 lines
17 KiB
C++

#include <libultraship.h>
#include <unordered_set>
#include <string>
#include <cstring>
#include "Track.h"
#include "MarioRaceway.h"
#include "ChocoMountain.h"
#include "port/Game.h"
#include "engine/editor/SceneManager.h"
#include "engine/registry/Registry.h"
#include "libultraship/bridge/resourcebridge.h"
#include "align_asset_macro.h"
extern "C" {
#include "main.h"
#include "memory.h"
#include "common_structs.h"
#include "course_offsets.h"
#include "some_data.h"
#include "code_8006E9C0.h"
#include "code_8003DC40.h"
#include "assets/models/common_data.h"
#include "render_objects.h"
#include "save.h"
#include "replays.h"
#include "code_800029B0.h"
#include "render_courses.h"
#include "collision.h"
#include "actors.h"
#include "math_util.h"
#include "code_80005FD0.h"
extern StaffGhost* d_mario_raceway_staff_ghost;
extern s8 gPlayerCount;
}
void ResizeMinimap(MinimapProps* minimap) {
if (minimap->Height < minimap->Width) {
minimap->Width = (minimap->Width * 64) / minimap->Height;
minimap->Height = 64;
} else {
minimap->Height = (minimap->Height * 64) / minimap->Width;
minimap->Width = 64;
}
}
static std::unordered_set<std::string> gModifiedGfxSet;
static void SwapTriangleVertices(Gfx* cmd) {
int8_t opcode = cmd->words.w0 >> 24;
if (opcode == G_TRI1) {
uint32_t w1 = cmd->words.w1;
uint8_t v0 = (w1 >> 16) & 0xFF;
uint8_t v1 = (w1 >> 8) & 0xFF;
uint8_t v2 = w1 & 0xFF;
cmd->words.w1 = (v0 << 16) | (v2 << 8) | v1;
}
else if (opcode == G_TRI1_OTR) {
uint32_t w0 = cmd->words.w0;
uint32_t w1 = cmd->words.w1;
uint16_t v0 = w0 & 0xFFFF;
uint16_t v1 = (w1 >> 16) & 0xFFFF;
uint16_t v2 = w1 & 0xFFFF;
cmd->words.w0 = (opcode << 24) | v0;
cmd->words.w1 = (v2 << 16) | v1;
}
else if (opcode == G_TRI2 || opcode == G_QUAD) {
uint32_t w0 = cmd->words.w0;
uint8_t v0_a = (w0 >> 16) & 0xFF;
uint8_t v1_a = (w0 >> 8) & 0xFF;
uint8_t v2_a = w0 & 0xFF;
uint32_t w1 = cmd->words.w1;
uint8_t v0_b = (w1 >> 16) & 0xFF;
uint8_t v1_b = (w1 >> 8) & 0xFF;
uint8_t v2_b = w1 & 0xFF;
cmd->words.w0 = (opcode << 24) | (v0_a << 16) | (v2_a << 8) | v1_a;
cmd->words.w1 = (v0_b << 16) | (v2_b << 8) | v1_b;
}
}
// Remove all occurrences of the marker "__OTR__" from a name string.
static std::string StripOtrPrefix(const char* s) {
if (s == nullptr) {
return std::string();
}
std::string out(s);
const std::string marker = "__OTR__";
size_t pos = 0;
while ((pos = out.find(marker, pos)) != std::string::npos) {
out.erase(pos, marker.length());
}
return out;
}
static void InvertTriangleWindingInternal(Gfx* gfx, const char* gfxName, bool shouldSwap) {
if (gfx == nullptr) {
return;
}
if (GameEngine_OTRSigCheck((char*)gfx)) {
gfx = (Gfx*)LOAD_ASSET(gfx);
}
if (gfxName != nullptr) {
std::string nameStr = StripOtrPrefix(gfxName);
if (gModifiedGfxSet.find(nameStr) != gModifiedGfxSet.end()) {
return;
}
gModifiedGfxSet.insert(nameStr);
}
Gfx* cmd = gfx;
for (int i = 0; i < 0x1FFF; i++) {
int8_t opcode = cmd->words.w0 >> 24;
switch (opcode) {
case G_TRI1:
case G_TRI1_OTR:
case G_TRI2:
case G_QUAD:
if (shouldSwap) {
SwapTriangleVertices(cmd);
}
break;
case G_DL: {
Gfx* subDL = (Gfx*)(uintptr_t)cmd->words.w1;
InvertTriangleWindingInternal(subDL, nullptr, shouldSwap);
break;
}
case G_DL_OTR_HASH: {
cmd++;
i++;
uint64_t hash = ((uint64_t)cmd->words.w0 << 32) | cmd->words.w1;
const char* name = ResourceGetNameByCrc(hash);
if (name != nullptr) {
bool subShouldSwap = (strstr(name, "packed") != nullptr);
Gfx* subDL = (Gfx*)ResourceGetDataByCrc(hash);
InvertTriangleWindingInternal(subDL, name, subShouldSwap);
}
break;
}
case G_DL_OTR_FILEPATH: {
const char* name = (const char*)(uintptr_t)cmd->words.w1;
if (name != nullptr) {
bool subShouldSwap = (strstr(name, "packed") != nullptr);
Gfx* subDL = (Gfx*)ResourceGetDataByName(name);
InvertTriangleWindingInternal(subDL, name, subShouldSwap);
}
break;
}
case G_VTX_OTR_FILEPATH:
case G_VTX_OTR_HASH:
cmd++;
i++;
break;
case G_MARKER: {
cmd++;
i++;
uint64_t hash = ((uint64_t)cmd->words.w0 << 32) | cmd->words.w1;
const char* name = ResourceGetNameByCrc(hash);
if (name != nullptr && gfxName == nullptr) {
std::string nameStr = StripOtrPrefix(name);
if (gModifiedGfxSet.find(nameStr) != gModifiedGfxSet.end()) {
return;
}
gModifiedGfxSet.insert(nameStr);
shouldSwap = (strstr(name, "packed") != nullptr);
}
break;
}
case G_MTX_OTR:
case G_SETTIMG_OTR_HASH:
cmd++;
i++;
break;
case G_ENDDL:
return;
default:
break;
}
cmd++;
}
}
void InvertTriangleWindingModdedInternal(Gfx* gfx, const char* gfxName) {
if (gfx == nullptr) {
return;
}
if (GameEngine_OTRSigCheck((char*)gfx)) {
gfx = (Gfx*)LOAD_ASSET(gfx);
}
if (gfxName != nullptr) {
std::string nameStr = StripOtrPrefix(gfxName);
if (gModifiedGfxSet.find(nameStr) != gModifiedGfxSet.end()) {
return;
}
gModifiedGfxSet.insert(nameStr);
}
Gfx* cmd = gfx;
for (int i = 0; i < 0x1FFF; i++) {
int8_t opcode = cmd->words.w0 >> 24;
switch (opcode) {
case G_TRI1:
case G_TRI1_OTR:
case G_TRI2:
case G_QUAD:
SwapTriangleVertices(cmd);
break;
case G_DL: {
Gfx* subDL = (Gfx*)(uintptr_t)cmd->words.w1;
InvertTriangleWindingModdedInternal(subDL, nullptr);
break;
}
case G_DL_OTR_HASH: {
cmd++;
i++;
uint64_t hash = ((uint64_t)cmd->words.w0 << 32) | cmd->words.w1;
const char* name = ResourceGetNameByCrc(hash);
if (name != nullptr) {
Gfx* subDL = (Gfx*)ResourceGetDataByCrc(hash);
InvertTriangleWindingModdedInternal(subDL, name);
}
break;
}
case G_DL_OTR_FILEPATH: {
const char* name = (const char*)(uintptr_t)cmd->words.w1;
if (name != nullptr) {
Gfx* subDL = (Gfx*)ResourceGetDataByName(name);
InvertTriangleWindingModdedInternal(subDL, name);
}
break;
}
case G_VTX_OTR_FILEPATH:
case G_VTX_OTR_HASH:
cmd++;
i++;
break;
case G_MARKER: {
cmd++;
i++;
uint64_t hash = ((uint64_t)cmd->words.w0 << 32) | cmd->words.w1;
const char* name = ResourceGetNameByCrc(hash);
if (name != nullptr) {
std::string nameStr = StripOtrPrefix(name);
if (gModifiedGfxSet.find(nameStr) != gModifiedGfxSet.end()) {
return;
}
gModifiedGfxSet.insert(nameStr);
}
break;
}
case G_MTX_OTR:
case G_SETTIMG_OTR_HASH:
cmd++;
i++;
break;
case G_ENDDL:
return;
default:
break;
}
cmd++;
}
}
void InvertTriangleWinding(Gfx* gfx) {
InvertTriangleWindingInternal(gfx, nullptr, false);
}
void InvertTriangleWindingByName(const char* name) {
if (name == nullptr) {
return;
}
Gfx* gfx = (Gfx*)ResourceGetDataByName(name);
bool shouldSwap = (strstr(name, "packed") != nullptr);
InvertTriangleWindingInternal(gfx, name, shouldSwap);
}
void RestoreTriangleWinding() {
for (const std::string& name : gModifiedGfxSet) {
if (strstr(name.c_str(), "packed") == nullptr) {
continue;
}
Gfx* gfx = (Gfx*)ResourceGetDataByName(name.c_str());
if (gfx == nullptr) {
continue;
}
Gfx* cmd = gfx;
for (int i = 0; i < 0x1FFF; i++) {
int8_t opcode = cmd->words.w0 >> 24;
switch (opcode) {
case G_TRI1:
case G_TRI1_OTR:
case G_TRI2:
case G_QUAD:
SwapTriangleVertices(cmd);
break;
case G_DL_OTR_HASH:
case G_VTX_OTR_FILEPATH:
case G_VTX_OTR_HASH:
case G_MARKER:
case G_MTX_OTR:
case G_SETTIMG_OTR_HASH:
cmd++;
i++;
break;
case G_ENDDL:
goto next_gfx;
default:
break;
}
cmd++;
}
next_gfx:;
}
gModifiedGfxSet.clear();
}
bool IsTriangleWindingInverted() {
return !gModifiedGfxSet.empty();
}
Track::Track() {
Props.SetText(Props.Name, "Blank Track", sizeof(Props.Name));
Props.SetText(Props.DebugName, "blnktrck", sizeof(Props.DebugName));
Props.SetText(Props.TrackLength, "100m", sizeof(Props.TrackLength));
ResourceName = "mod:blank_track";
Props.Minimap.Texture = minimap_mario_raceway;
Props.Minimap.Width = ResourceGetTexWidthByName(Props.Minimap.Texture);
Props.Minimap.Height = ResourceGetTexHeightByName(Props.Minimap.Texture);
Props.Minimap.Pos[0].X = 257;
Props.Minimap.Pos[0].Y = 170;
Props.Minimap.PlayerX = 0;
Props.Minimap.PlayerY = 0;
Props.Minimap.PlayerScaleFactor = 0.22f;
Props.Minimap.FinishlineX = 0;
Props.Minimap.FinishlineY = 0;
Props.Minimap.Colour = { 255, 255, 255 };
Props.WaterLevel = FLT_MAX;
Props.LakituTowType = (s32) OLakitu::LakituTowType::NORMAL;
Props.AIBehaviour = D_0D008F28;
Props.AIMaximumSeparation = 50.0f;
Props.AIMinimumSeparation = 0.3f;
Props.AIDistance = gMarioRacewayAIDistances;
Props.AISteeringSensitivity = 48;
Props.NearPersp = 3.0f;
Props.FarPersp = 6800.0f;
Props.PathSizes = { 600, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0 };
Props.CurveTargetSpeed[0] = 4.1666665f;
Props.CurveTargetSpeed[1] = 5.5833334f;
Props.CurveTargetSpeed[2] = 6.1666665f;
Props.CurveTargetSpeed[3] = 6.75f;
Props.NormalTargetSpeed[0] = 3.75f;
Props.NormalTargetSpeed[1] = 5.1666665f;
Props.NormalTargetSpeed[2] = 5.75f;
Props.NormalTargetSpeed[3] = 6.3333334f;
Props.D_0D0096B8[0] = 3.3333332f;
Props.D_0D0096B8[1] = 3.9166667f;
Props.D_0D0096B8[2] = 4.5f;
Props.D_0D0096B8[3] = 5.0833334f;
Props.OffTrackTargetSpeed[0] = 3.75f;
Props.OffTrackTargetSpeed[1] = 5.1666665f;
Props.OffTrackTargetSpeed[2] = 5.75f;
Props.OffTrackTargetSpeed[3] = 6.3333334f;
Props.PathTable[0] = NULL;
Props.PathTable[1] = NULL;
Props.PathTable[2] = NULL;
Props.PathTable[3] = NULL;
Props.PathTable2[0] = NULL;
Props.PathTable2[1] = NULL;
Props.PathTable2[2] = NULL;
Props.PathTable2[3] = NULL;
Props.PathTable2[4] = NULL;
Props.Clouds = NULL;
Props.CloudList = NULL;
mCloudType = CloudType::CLOUDS;
Props.Sequence = MusicSeq::MUSIC_SEQ_UNKNOWN;
bFog = false;
gFogColour.r = 0;
gFogColour.g = 0;
gFogColour.b = 0;
gFogColour.a = 255;
gFogMin = 995;
gFogMax = 1000;
}
// Load custom track from code
void Track::Load(Vtx* vtx, Gfx* gfx) {
Track::Init();
}
// Load stock tracks
void Track::Load() {
printf("[Track] Loading... %s\n", ResourceName.c_str());
const TrackInfo* info = gTrackRegistry.GetInfo(ResourceName);
if (nullptr == info) {
printf("[Track] Could not find TrackInfo for %s\n", ResourceName.c_str());
return;
}
Track::Init();
}
void Track::Init() {
gNumActors = 0;
gTrackMinX = 0;
gTrackMinY = 0;
gTrackMinZ = 0;
gTrackMaxX = 0;
gTrackMaxY = 0;
gTrackMaxZ = 0;
D_8015F59C = 0;
D_8015F5A0 = 0;
func_80295D6C();
D_8015F58C = 0;
gCollisionMeshCount = 0;
gCollisionMesh = (CollisionTriangle*) gNextFreeMemoryAddress;
D_800DC5C8 = 0;
}
void Track::BeginPlay() {
printf("[Track] BeginPlay\n");
this->SpawnActors();
}
// Spawns actors from SpawnParams set by the scene file in SceneManager.cpp
void Track::SpawnActors() {
for (const auto& params : SpawnList) {
gActorRegistry.Invoke(params.Name, params);
}
}
// Adjusts player speed on steep hills
void Track::SomeCollisionThing(Player* player, Vec3f arg1, Vec3f arg2, Vec3f arg3, f32* arg4, f32* arg5, f32* arg6,
f32* arg7) {
func_8003E048(player, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
}
void Track::InitTrackObjects() {
}
void Track::TickTrackObjects() {
}
void Track::DrawTrackObjects(s32 cameraId) {
}
// Implemented for the first cup of each track plus Koopa Beach
void Track::SomeSounds() {
}
void Track::CreditsSpawnActors() {
}
void Track::WhatDoesThisDo(Player* player, int8_t playerId) {
}
void Track::WhatDoesThisDoAI(Player* player, int8_t playerId) {
}
void Track::SetStaffGhost() {
bCourseGhostDisabled = 1;
D_80162DF4 = 1;
}
void Track::Waypoints(Player* player, int8_t playerId) {
player->nearestPathPointId = gNearestPathPointByPlayerId[playerId];
if (player->nearestPathPointId < 0) {
player->nearestPathPointId = gPathCountByPathIndex[0] + player->nearestPathPointId;
}
}
void Track::Draw(ScreenContext* arg0) {
gSPSetGeometryMode(gDisplayListHead++, G_SHADING_SMOOTH);
gSPClearGeometryMode(gDisplayListHead++, G_LIGHTING);
if (bFog) {
gDPSetCycleType(gDisplayListHead++, G_CYC_2CYCLE);
gDPSetRenderMode(gDisplayListHead++, G_RM_FOG_SHADE_A, G_RM_AA_ZB_OPA_SURF2);
gSPSetGeometryMode(gDisplayListHead++, G_FOG);
gDPSetFogColor(gDisplayListHead++, gFogColour.r, gFogColour.g, gFogColour.b, gFogColour.a);
gSPFogPosition(gDisplayListHead++, gFogMin, gFogMax);
gDPPipeSync(gDisplayListHead++);
} else {
gSPClearGeometryMode(gDisplayListHead++, G_FOG);
}
set_track_light_direction(D_800DC610, D_802B87D4, 0, 1);
gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON);
gSPSetGeometryMode(gDisplayListHead++, G_SHADING_SMOOTH);
if (func_80290C20(arg0->camera) == 1) {
gDPSetCombineMode(gDisplayListHead++, G_CC_SHADE, G_CC_SHADE);
gDPSetRenderMode(gDisplayListHead++, G_RM_AA_ZB_OPA_SURF, G_RM_AA_ZB_OPA_SURF2);
}
const TrackInfo* info = gTrackRegistry.GetInfo(ResourceName);
if (nullptr == info) {
printf("[Track] [Draw] Resource name did not return a valid TrackInfo %s\n", ResourceName.c_str());
return;
}
std::string res = info->Path + "/data_track_sections";
TrackSections* sections = (TrackSections*) LOAD_ASSET_RAW(res.c_str());
size_t size = ResourceGetSizeByName(res.c_str());
size_t totalSections = size / sizeof(TrackSections);
for (size_t i = 0; i < totalSections; i++) {
gSPDisplayList(gDisplayListHead++, (Gfx*) ResourceGetDataByCrc(sections[i].crc));
}
}
void Track::DrawCredits() {
}
f32 Track::GetWaterLevel(FVector pos, Collision* collision) {
float highestWater = -FLT_MAX;
bool found = false;
for (const auto& volume : GetWorld()->GetTrack()->WaterVolumes) {
if (pos.x >= volume.MinX && pos.x <= volume.MaxX && pos.z >= volume.MinZ && pos.z <= volume.MaxZ) {
// Choose the highest water volume the player is over
if (!found || volume.Height > highestWater) {
highestWater = volume.Height;
found = true;
}
}
}
// If player is not over-top of a water volume then return the tracks default water level
return found ? highestWater : GetWorld()->GetTrack()->Props.WaterLevel;
}
void Track::Tick() {
}
void Track::DrawTransparency(ScreenContext* screen, uint16_t pathCounter, uint16_t cameraRot, uint16_t playerDirection) {
}
void Track::Destroy() {
}