diff --git a/src/engine/courses/Course.cpp b/src/engine/courses/Course.cpp index ae70dd3e1..4418e757c 100644 --- a/src/engine/courses/Course.cpp +++ b/src/engine/courses/Course.cpp @@ -29,9 +29,13 @@ extern StaffGhost* d_mario_raceway_staff_ghost; Course::Course() { // Props.Name = "Course Name"; // Props.DebugName = "CName"; - // Props.CourseLength = "567m"; + Props.SetText(Props.CourseLength, "100m", sizeof(Props.CourseLength)); // Props.Cup = FLOWER_CUP; // Props.CupIndex = 3; + Props.Id = ""; + Props.MinimapTexture = gTextureCourseOutlineMarioRaceway; + Props.MinimapDimensions = IVector2D(ResourceGetTexWidthByName(Props.MinimapTexture), ResourceGetTexHeightByName(Props.MinimapTexture)); + Props.LakituTowType = (s32) OLakitu::LakituTowType::NORMAL; Props.AIBehaviour = D_0D008F28; Props.AIMaximumSeparation = 50.0f; diff --git a/src/engine/courses/Course.h b/src/engine/courses/Course.h index 340c3c1cf..af1fc48ef 100644 --- a/src/engine/courses/Course.h +++ b/src/engine/courses/Course.h @@ -122,6 +122,8 @@ typedef struct Properties { // j["Skybox"] = Skybox; // Implement your serialization logic here j["Sequence"] = static_cast(Sequence); + j["WaterLevel"] = static_cast(WaterLevel); + return j; } @@ -194,6 +196,7 @@ typedef struct Properties { MinimapFinishlineY = j.at("MinimapFinishlineY").get(); //textures = nullptr; // Deserialize textures if present Sequence = static_cast(j.at("Sequence").get()); + WaterLevel = static_cast(j.at("WaterLevel").get()); } void SetText(char* name, const char* title, size_t bufferSize) { // Copy the title into the name buffer, ensuring it's null-terminated and within bounds diff --git a/src/engine/editor/SceneManager.cpp b/src/engine/editor/SceneManager.cpp index 677548660..7efa7081b 100644 --- a/src/engine/editor/SceneManager.cpp +++ b/src/engine/editor/SceneManager.cpp @@ -46,17 +46,14 @@ namespace Editor { } } - void LoadLevel(std::string sceneFile) { - printf("LOAD TRACK PROPS %s\n", sceneFile.c_str()); - + void LoadLevel(std::shared_ptr archive, Course* course, std::string sceneFile) { nlohmann::json data; - CurrentArchive = GameEngine::Instance->context->GetResourceManager()->GetArchiveManager()->GetArchiveFromFile(sceneFile); SceneFile = sceneFile; - if (CurrentArchive) { + if (archive) { auto initData = std::make_shared(); - initData->Parent = CurrentArchive; + initData->Parent = archive; initData->Format = RESOURCE_FORMAT_BINARY; initData->ByteOrder = Ship::Endianness::Little; initData->Type = static_cast(Ship::ResourceType::Json); @@ -72,7 +69,7 @@ namespace Editor { // Load the Props (deserialize it) if (data.contains("Props")) { auto& propsJson = data["Props"]; - gWorldInstance.CurrentCourse->Props.from_json(propsJson); // Assuming you have a `from_json` function + course->Props.from_json(propsJson); // Assuming you have a `from_json` function } else { std::cerr << "Props data not found in the JSON file!" << std::endl; } @@ -102,8 +99,8 @@ namespace Editor { GameObject::CollisionType::BOUNDING_BOX, 20.0f, (int32_t*) &actor->bPendingDestroy, (int32_t) 1); } - void SetSceneFile(std::string archive, std::string sceneFile) { - CurrentArchive = GameEngine::Instance->context->GetResourceManager()->GetArchiveManager()->GetArchiveFromFile(archive); - SceneFile = sceneFile+"/scene.json"; + void SetSceneFile(std::shared_ptr archive, std::string sceneFile) { + CurrentArchive = archive; + SceneFile = sceneFile; } } diff --git a/src/engine/editor/SceneManager.h b/src/engine/editor/SceneManager.h index 8d3abbda4..c9fc97c6d 100644 --- a/src/engine/editor/SceneManager.h +++ b/src/engine/editor/SceneManager.h @@ -1,11 +1,11 @@ #include -#include "Course.h" +#include "engine/courses/Course.h" namespace Editor { void SaveLevel(); - void LoadLevel(std::string sceneFile); + void LoadLevel(std::shared_ptr archive, Course* course, std::string sceneFile); void Load_AddStaticMeshActor(const nlohmann::json& actorJson); - void SetSceneFile(std::string archive, std::string sceneFile); + void SetSceneFile(std::shared_ptr archive, std::string sceneFile); extern std::shared_ptr CurrentArchive; // This is used to retrieve and write the scene data file extern std::string SceneFile; diff --git a/src/port/ui/ContentBrowser.cpp b/src/port/ui/ContentBrowser.cpp index 0bafcffbf..ac876ddca 100644 --- a/src/port/ui/ContentBrowser.cpp +++ b/src/port/ui/ContentBrowser.cpp @@ -33,10 +33,9 @@ namespace Editor { // Query content in o2r and add them to Content if (Refresh) { Refresh = false; + RemoveCustomTracksFromTrackList(); Tracks.clear(); Content.clear(); - TrackAssetMap.clear(); - TrackPath.clear(); FindTracks(); FindContent(); return; @@ -123,28 +122,21 @@ namespace Editor { void ContentBrowserWindow::AddTrackContent() { size_t i_track = 0; - for (const auto& track : Tracks) { - std::string label = fmt::format("{}##{}", track, i_track); - bool foundTrackFile = false; - - for (auto& asset : TrackAssetMap[track]) { - if (asset.ends_with("scene.json")) { - foundTrackFile = true; - if (ImGui::Button(label.c_str())) { - gWorldInstance.Courses.push_back(new Course()); - SetCourseByClass(gWorldInstance.Courses.back()); - LoadLevel(asset); - gWorldInstance.CurrentCourse->LoadO2R(TrackPath[track]); - gGamestateNext = RACING; - break; - } - } - } - - if (!foundTrackFile) { - std::string label = fmt::format("{} {}", ICON_FA_EXCLAMATION_TRIANGLE, track); + for (auto& track : Tracks) { + if (!track.SceneFile.empty()) { // has scene file + std::string label = fmt::format("{}##{}", track.Name, i_track); if (ImGui::Button(label.c_str())) { - SetSceneFile(TrackAssetMap[track][0], TrackPath[track]); + SetCourseByClass(track.course); + gGamestateNext = RACING; + SetSceneFile(track.Archive, track.SceneFile); + break; + } + } else { // no scene file + std::string label = fmt::format("{} {}", ICON_FA_EXCLAMATION_TRIANGLE, track.Name); + if (ImGui::Button(label.c_str())) { + track.SceneFile = track.Dir + "/scene.json"; + SetCourseByClass(track.course); + SetSceneFile(track.Archive, track.SceneFile); SaveLevel(); Refresh = true; } @@ -154,6 +146,20 @@ namespace Editor { } } + void ContentBrowserWindow::RemoveCustomTracksFromTrackList() { + for (auto& track : Tracks) { + auto it = gWorldInstance.Courses.begin(); + while (it != gWorldInstance.Courses.end()) { + if (track.course == *it) { + delete *it; + it = gWorldInstance.Courses.erase(it); + } else { + ++it; + } + } + } + } + void ContentBrowserWindow::AddActorContent() { FVector pos = GetPositionAheadOfCamera(300.0f); @@ -216,43 +222,41 @@ namespace Editor { // Finds modded archives only. For discovering tracks void ContentBrowserWindow::FindTracks() { + auto manager = GameEngine::Instance->context->GetResourceManager()->GetArchiveManager(); - // ListFiles(whitelist, blacklist); - auto ptr2 = GameEngine::Instance->context->GetResourceManager()->GetArchiveManager()->ListFiles({"tracks/*/scene.json"}, {""}); + auto ptr2 = manager->ListDirectories("tracks/*"); if (ptr2) { - auto files = *ptr2; - std::set uniqueTracks; - - for (const auto& file : files) { - //printf("TRACK FILES: %s\n", file.c_str()); - - // Find "tracks/" - size_t pos = file.find("tracks/"); - if (pos == std::string::npos) continue; // Skip if not found - - // Extract track name - size_t start = pos + 7; // Move past "tracks/" - size_t end = file.find('/', start); // Find next '/' - std::string trackName = file.substr(start, end - start); + auto dirs = *ptr2; - TrackAssetMap[trackName].push_back(file); - - // Insert into set (ensuring uniqueness) - uniqueTracks.insert(trackName); + for (const std::string& dir : dirs) { + std::string name = dir.substr(dir.find_last_of('/') + 1); + std::string sceneFile = dir + "/scene.json"; + if (manager->HasFile(sceneFile)) { + auto archive = manager->GetArchiveFromFile(sceneFile); + + Course* course = new Course(); + course->LoadO2R(dir); + gWorldInstance.Courses.push_back(course); + LoadLevel(archive, course, sceneFile); + Tracks.push_back({course, sceneFile, name, dir, archive}); + } else { + const std::string file = dir + "/data_track_sections"; + + if (manager->HasFile(file)) { - // Extract directory path (remove filename) - size_t lastSlash = file.find_last_of('/'); - printf("file %s\n", file.c_str()); + Course* course = new Course(); + course->Props.Id = (std::string("mods:") + name).c_str(); + course->Props.SetText(course->Props.Name, name.c_str(), sizeof(course->Props.Name)); + course->Props.SetText(course->Props.DebugName, name.c_str(), sizeof(course->Props.Name)); - std::string directoryPath = (lastSlash != std::string::npos) ? file.substr(0, lastSlash) : file; - printf("dir %s\n", directoryPath.c_str()); + auto archive = manager->GetArchiveFromFile(file); + Tracks.push_back({course, "", name, dir, archive}); + } else { + printf("ContentBrowser.cpp: Track '%s' missing required track files. Cannot add to game\n Missing %s/data_track_sections file\n", name.c_str(), dir.c_str()); + } - // Store in TrackPath - TrackPath[trackName] = directoryPath; + } } - - // Move unique tracks into the vector - Tracks.assign(uniqueTracks.begin(), uniqueTracks.end()); } } diff --git a/src/port/ui/ContentBrowser.h b/src/port/ui/ContentBrowser.h index 988947185..f077f7283 100644 --- a/src/port/ui/ContentBrowser.h +++ b/src/port/ui/ContentBrowser.h @@ -1,6 +1,7 @@ #pragma once #include +#include "engine/courses/Course.h" namespace Editor { class ContentBrowserWindow : public Ship::GuiWindow { @@ -8,10 +9,17 @@ public: using Ship::GuiWindow::GuiWindow; ~ContentBrowserWindow(); + struct Tracks { + Course* course; + std::string SceneFile; + std::string Name; + std::string Dir; // Directory + std::shared_ptr Archive; + }; + + std::vector Tracks; + std::vector Content; - std::vector Tracks; // Contains modded archives in mods/ - std::unordered_map> TrackAssetMap; - std::unordered_map TrackPath; bool Refresh = true; @@ -24,6 +32,7 @@ protected: void DrawElement() override; void UpdateElement() override {}; void AddTrackContent(); + void RemoveCustomTracksFromTrackList(); // Prevents duplicate courses being added to World->Courses array void AddActorContent(); void AddObjectContent(); void AddCustomContent();