diff options
| author | MegaMech <MegaMech@users.noreply.github.com> | 2025-12-07 21:00:06 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-12-07 21:00:06 -0700 |
| commit | dbae0c3aca33a022e8c1d511fe521407375057b2 (patch) | |
| tree | 889ebe9e5c03261d92304545c5beae13892831f2 /src/engine/tracks/Track.cpp | |
| parent | 04ca19a9757752676450364e998d6af0d90e73ef (diff) | |
Name ScreenContext, Refactor Course class to Track (#583)
* Rename Screen Contexts
* typedef ScreenContext struct
* Fix Compile
* Rename Course.cpp to Track.cpp
* Refactor Course to Track
* A few renames
* General Cleanup
* More Rename
* More names
* Move TrackSections struct to Track.h
---------
Co-authored-by: MegaMech <7255464+MegaMech@users.noreply.github.com>
Diffstat (limited to 'src/engine/tracks/Track.cpp')
| -rw-r--r-- | src/engine/tracks/Track.cpp | 589 |
1 files changed, 589 insertions, 0 deletions
diff --git a/src/engine/tracks/Track.cpp b/src/engine/tracks/Track.cpp new file mode 100644 index 000000000..ddcbcd9ff --- /dev/null +++ b/src/engine/tracks/Track.cpp @@ -0,0 +1,589 @@ +#include <libultraship.h> +#include <unordered_set> +#include <string> +#include <cstring> + +#include "Track.h" +#include "MarioRaceway.h" +#include "ChocoMountain.h" +#include "port/Game.h" +#include "port/resource/type/TrackPathPointData.h" +#include "engine/editor/SceneManager.h" +#include "Registry.h" +#include "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; +} + +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; + } +} + +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(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(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 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)); + // Props.Cup = FLOWER_CUP; + // Props.CupIndex = 3; + Id = ""; + 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; + Props.Sequence = MusicSeq::MUSIC_SEQ_UNKNOWN; +} + +// Load custom track from code +void Track::Load(Vtx* vtx, Gfx* gfx) { + Track::Init(); +} + +void Track::UnLoad() { +} + +void Track::LoadO2R(std::string trackPath) { + if (!trackPath.empty()) { + SceneFilePtr = (trackPath + "/scene.json"); + TrackSectionsPtr = (trackPath + "/data_track_sections"); + + std::string path_file = (trackPath + "/data_paths").c_str(); + + auto res = std::dynamic_pointer_cast<MK64::Paths>(ResourceLoad(path_file.c_str())); + + if (res != nullptr) { + auto& paths = res->PathList; + + size_t i = 0; + u16* ptr = &Props.PathSizes.unk0; + for (auto& path : paths) { + if (i >= ARRAY_COUNT(Props.PathTable2)) { + printf("[Track.cpp] The game can only import 5 paths. Found more than 5. Skipping the rest\n"); + break; // Only 5 paths allowed. 4 track, 1 vehicle + } + ptr[i] = path.size(); + Props.PathTable2[i] = (TrackPathPoint*) path.data(); + + i += 1; + } + } + gVehiclePathSize = Props.PathSizes.unk0; // This is likely incorrect. + + } else { + printf("Track.cpp: LoadO2R: trackPath str is empty\n"); + } +} + +// Load stock and o2r tracks +void Track::Load() { + // Re-load scenefile in-case changes were made in the editor + if (!SceneFilePtr.empty()) { + Editor::LoadLevel(this, SceneFilePtr); + } + + // Load from O2R + if (!TrackSectionsPtr.empty()) { + bIsMod = true; + // auto res = std::dynamic_pointer_cast<MK64::TrackSectionsO2RClass>(ResourceLoad(TrackSectionsPtr.c_str())); + + TrackSections* sections = (TrackSections*) LOAD_ASSET_RAW(TrackSectionsPtr.c_str()); + size_t size = ResourceGetSizeByName(TrackSectionsPtr.c_str()); + + if (sections != nullptr) { + Track::Init(); + ParseTrackSections(sections, size); + func_80295C6C(); + + if (Props.WaterLevel == FLT_MAX) { + Props.WaterLevel = gTrackMinY - 10.0f; + } + } else { + printf("Track.cpp: Custom track sections are invalid\n"); + } + return; + } + + Track::Init(); +} + +// C++ version of parse_track_displaylists() +void Track::ParseTrackSections(TrackSections* sections, size_t size) { + printf("\n[Track] Generating Collision Meshes...\n"); + for (size_t i = 0; i < (size / sizeof(TrackSections)); i++) { + if (sections[i].flags & 0x8000) { + D_8015F59C = 1; // single-sided wall + } else { + D_8015F59C = 0; + } + if (sections[i].flags & 0x2000) { + D_8015F5A0 = 1; // surface + } else { + D_8015F5A0 = 0; + } + if (sections[i].flags & 0x4000) { + D_8015F5A4 = 1; // double-sided wall + } else { + D_8015F5A4 = 0; + } + const char* name = ResourceGetNameByCrc(sections[i].crc); + printf(" %s\n", name); + generate_collision_mesh((Gfx*) ResourceGetDataByCrc(sections[i].crc), sections[i].surfaceType, + sections[i].sectionId); + } + printf("[Track] Collision Mesh Generation Complete!\n\n"); +} + +void Track::TestPath() { + // DEBUG ONLY TO VISUALIZE PATH + return; + s16 x; + s16 y; + s16 z; + Vec3s rot = { 0, 0, 0 }; + Vec3f vel = { 0, 0, 0 }; + + for (size_t i = 0; i < gPathCountByPathIndex[0]; i++) { + x = gTrackPaths[0][i].x; + y = gTrackPaths[0][i].y; + z = gTrackPaths[0][i].z; + + if (((x & 0xFFFF) == 0x8000) && ((y & 0xFFFF) == 0x8000) && ((z & 0xFFFF) == 0x8000)) { + break; + } + + f32 height = spawn_actor_on_surface(x, 2000.0f, z); + Vec3f itemPos = { (f32) x, height, (f32) z }; + add_actor_to_empty_slot(itemPos, rot, vel, ACTOR_ITEM_BOX); + } +} + +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_800DC5BC = 0; + D_800DC5C8 = 0; +} + +void Track::BeginPlay() { + printf("[Track] BeginPlay\n"); + TestPath(); + this->SpawnActors(); +} + +// Spawns actors from SpawnParams set by the scene file in SceneManager.cpp +void Track::SpawnActors() { + for (const auto& actor : SpawnList) { + auto it = gActorRegistry.find(actor.Name); + if (it != gActorRegistry.end() && it->second.spawnFunc) { + it->second.spawnFunc(actor); + } else { + printf("Actor not found %s\n", actor.Name.c_str()); + } + } +} + +void Track::InitClouds() { + if (this->Props.Clouds) { + init_clouds(this->Props.Clouds); + } +} + +void Track::TickClouds(s32 arg0, Camera* camera) { + s32 cloudIndex; + s32 objectIndex; + CloudData* cloud; + + if (this->Props.CloudList) { + for (cloudIndex = 0; cloudIndex < D_8018D1F0; cloudIndex++) { + cloud = &this->Props.CloudList[cloudIndex]; + objectIndex = D_8018CC80[arg0 + cloudIndex]; + func_800788F8(objectIndex, cloud->rotY, camera); + } + } +} + +// 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) { + if (!TrackSectionsPtr.empty()) { + gSPSetGeometryMode(gDisplayListHead++, G_SHADING_SMOOTH); + gSPClearGeometryMode(gDisplayListHead++, G_LIGHTING); + 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); + // d_course_big_donut_packed_dl_DE8 + } + + TrackSections* sections = (TrackSections*) LOAD_ASSET_RAW(TrackSectionsPtr.c_str()); + size_t size = ResourceGetSizeByName(TrackSectionsPtr.c_str()); + for (size_t i = 0; i < (size / sizeof(TrackSections)); 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 : gWorldInstance.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 : gWorldInstance.GetTrack()->Props.WaterLevel; +} + +void Track::ScrollingTextures() { +} +void Track::DrawWater(ScreenContext* screen, uint16_t pathCounter, uint16_t cameraRot, + uint16_t playerDirection) { +} + +void Track::Destroy() { +} + +bool Track::IsMod() { + return bIsMod; +} |
