diff --git a/src/dusk/imgui/ImGuiMenuRandomizer.cpp b/src/dusk/imgui/ImGuiMenuRandomizer.cpp index 9dddd3ae20..190b8ed5e6 100644 --- a/src/dusk/imgui/ImGuiMenuRandomizer.cpp +++ b/src/dusk/imgui/ImGuiMenuRandomizer.cpp @@ -1,7 +1,5 @@ #include "imgui.h" -#include "d/d_stage.h" - #include "ImGuiConsole.hpp" #include "ImGuiMenuRandomizer.hpp" @@ -9,7 +7,6 @@ #include "dusk/logging.h" #include "dusk/randomizer/game/randomizer_context.hpp" #include "dusk/randomizer/game/tools.h" -#include "dusk/randomizer/generator/randomizer.hpp" #include "SDL3/SDL_filesystem.h" @@ -17,237 +14,20 @@ #include #include -#include "dusk/randomizer/generator/utility/endian.hpp" -#include "dusk/randomizer/generator/utility/string.hpp" - namespace dusk { static bool generatingSeed = false; static std::string generationStatusMsg{}; static std::mutex generationStatusMsgMutex{}; - void GenerateSeed() { - std::lock_guard lock(generationStatusMsgMutex); - const auto result = SDL_GetPrefPath(dusk::OrgName, dusk::AppName); - if (!result) { - DuskLog.fatal("Unable to get PrefPath: {}", SDL_GetError()); - } - randomizer::Randomizer r; - r.SetBaseOutputPath(result); - auto generationResult = r.Generate(); - if (generationResult.has_value()) { - generationStatusMsg = fmt::format("Generation failed with the following error:\n{}", generationResult.value()); - return; - } - - const auto& world = r.GetWorlds()[0]; - - RandomizerContext randoData{}; - - // Settings we need to check ingame - for (const auto& [setting, info] : *randomizer::seedgen::settings::GetAllSettingsInfo()) { - if (info->NeedInGame()) { - randoData.mSettings[setting] = world->Setting(setting).GetCurrentOption(); - } - } - - // Set data for all locations - for (const auto& location : world->GetAllLocations()) { - const auto& metaData = location->GetMetadata(); - - // Chest Overrides - if (location->HasCategories("Chest")) { - const auto& stage = metaData[0]["Stage"].as(); - const auto& tboxId = metaData[0]["Tbox ID"].as(); - const auto& itemId = location->GetCurrentItem()->GetID(); - randoData.mTreasureChestOverrides[stage][tboxId] = itemId; - } - - // Freestanding Overrides - if (location->HasCategories("Freestanding Item")) { - u8 stage = metaData[0]["Stage"].as(); - u8 flag = metaData[0]["Flag"].as(); - u8 itemId = location->GetCurrentItem()->GetID(); - randoData.mFreestandingItemOverrides[stage][flag] = itemId; - } - - // Bug Rewards - if (location->HasCategories("Bug Reward")) { - u8 bugItemId = metaData[0]["Bug Item Id"].as(); - u8 itemId = location->GetCurrentItem()->GetID(); - randoData.mBugRewardOverrides[bugItemId] = itemId; - } - - // Items that we lookup just by calling their location name - if (location->HasCategories("Location Name Lookup")) { - const auto& locationName = metaData.as(); - const int itemId = location->GetCurrentItem()->GetID(); - randoData.mItemLocations[locationName] = itemId; - } - } - - // Set starting inventory - for (const auto& item: world->GetStartingItemPool()) { - randoData.mStartingInventory.push_back(item->GetID()); - } - - // Set starting flags - auto startFlags = LoadYAML(RANDO_DATA_PATH "startflags.yaml"); - // Event Flags - for (const auto& flagNode : startFlags["EventFlags"]) { - if (flagNode.IsScalar()) { - const auto& flag = flagNode.as(); - randoData.mStartEventFlags.push_back(flag); - } else if (flagNode.IsMap()) { - const auto& condition = flagNode.begin()->first.as(); - if (world->EvaluateSettingCondition(condition)) { - DuskLog.debug("Setting flags for {}", condition); - for (const auto& conditionalFlag : flagNode.begin()->second) { - const auto& flag = conditionalFlag.as(); - randoData.mStartEventFlags.push_back(flag); - } - } - } - } - - // Region Flags - for (const auto& regionNode : startFlags["RegionFlags"]) { - const auto& region = regionNode.first.as(); - const auto& index = regionNode.second["Index"].as(); - const auto& flags = regionNode.second["Flags"]; - DuskLog.debug("Setting region flags for {}", region); - // This seems kinda scuffed so maybe we change it later - for (const auto& flagNode : flags) { - if (flagNode.IsScalar()) { - const auto& flag = flagNode.as(); - randoData.mStartRegionFlags[index].push_back(flag); - } else if (flagNode.IsMap()) { - const auto& condition = flagNode.begin()->first.as(); - if (world->EvaluateSettingCondition(condition)) { - for (const auto& conditionalFlag : flagNode.begin()->second) { - const auto& flag = conditionalFlag.as(); - randoData.mStartRegionFlags[index].push_back(flag); - } - } - } - } - } - - if (world->Setting("Unlock Map Regions") == "On") - { - auto& bits = randoData.mMapBits; - bits = 0x20; - if (world->Setting("Snowpeak Does Not Require Reekfish Scent") == "On") {bits |= 0x40;} - if (world->Setting("Lanayru Twilight Cleared") == "On") {bits |= 0x10;} - if (world->Setting("Eldin Twilight Cleared") == "On") {bits |= 0x08;} - if (world->Setting("Faron Twilight Cleared") == "On") {bits |= 0x04;} - if (world->Setting("Skip Prologue") == "On") {bits |= 0x02;} - } - - // Set starting time of day - const auto startTimeSetting = world->Setting("Starting Time of Day"); - if (startTimeSetting == "Morning") - randoData.mStartHour = 6; - else if (startTimeSetting == "Noon") - randoData.mStartHour = 12; - else if (startTimeSetting == "Evening") - randoData.mStartHour = 18; - else if (startTimeSetting == "Night") - randoData.mStartHour = 24; - - // Actor Patches - auto actorPatches = LoadYAML(RANDO_DATA_PATH "actor_patches.yaml"); - for (const auto& stageNode : actorPatches) { - const auto& stageName = stageNode.first.as(); - for (const auto& roomNode : stageNode.second) { - u8 roomNo = roomNode.first.as(); - for (const auto& actorNode : roomNode.second) { - using namespace Utility::Endian; - // Get all the data for the actor (with endian shenanigans) - stage_actor_data_class actor{}; - const auto& actorName = actorNode["name"].as(); - strncpy(actor.name, actorName.c_str(), 8); - actor.base.parameters = toPlatform(target, actorNode["parameters"].as()); - actor.base.position.x = toPlatform(target, actorNode["position"]["x"].as()); - actor.base.position.y = toPlatform(target, actorNode["position"]["y"].as()); - actor.base.position.z = toPlatform(target, actorNode["position"]["z"].as()); - // Have to retrieve as u16 and then cast as s16 because otherwise yaml-cpp - // complains about values over 32767 not fitting in s16 - actor.base.angle.x = toPlatform(target, static_cast(actorNode["angle"]["x"].as())); - actor.base.angle.y = toPlatform(target, static_cast(actorNode["angle"]["y"].as())); - actor.base.angle.z = toPlatform(target, static_cast(actorNode["angle"]["z"].as())); - - // Create unique hash based off of actor data - u32 actorCRC32 = getActorCRC32(&actor); - - // Then override the actor with whatever parts are being patched - const auto& patchNode = actorNode["patch"]; - if (patchNode["name"]) { - const auto& newName = patchNode["name"].as(); - strncpy(actor.name, newName.c_str(), 8); - } - if (patchNode["parameters"]) { - actor.base.parameters = toPlatform(target, patchNode["parameters"].as()); - } - if (auto patchPosition = patchNode["position"]) { - if (patchPosition["x"]) { - actor.base.position.x = toPlatform(target, patchPosition["x"].as()); - } - if (patchPosition["y"]) { - actor.base.position.y = toPlatform(target, patchPosition["y"].as()); - } - if (patchPosition["z"]) { - actor.base.position.z = toPlatform(target, patchPosition["z"].as()); - } - } - if (auto patchAngle = patchNode["angle"]) { - // Have to retrieve as u16 and then cast as s16 because otherwise yaml-cpp - // complains about values over 32767 not fitting in s16 - if (patchAngle["x"]) { - actor.base.angle.x = toPlatform(target, static_cast(patchAngle["x"].as())); - } - if (patchAngle["y"]) { - actor.base.angle.y = toPlatform(target, static_cast(patchAngle["y"].as())); - } - if (patchAngle["z"]) { - actor.base.angle.z = toPlatform(target, static_cast(patchAngle["z"].as())); - } - } - - // Insert the actor patch into the context with our crc32 as the key and the - // raw actor patch data as the value - std::array patchedActorData{}; - std::memcpy(patchedActorData.data(), &actor, RandomizerContext::ACTOR_CRC_SIZE); - for (const auto& layerNode : actorNode["layers"]) { - u8 layerNo = layerNode.as(); - // Create key based off of stage index, room, and layer - u32 stageRoomLayerKey{}; - stageRoomLayerKey |= getStageID(stageName.c_str()) << 16; - stageRoomLayerKey |= roomNo << 8; - stageRoomLayerKey |= layerNo; - randoData.mActorPatches[stageRoomLayerKey][actorCRC32] = patchedActorData; - } - } - } - } - - randoData.mHash = r.GetConfig().GetHash(); - auto writeToFileResult = randoData.WriteToFile(); - if (writeToFileResult.has_value()) { - generationStatusMsg = - fmt::format("Failed to write seed data. Reason: {}", writeToFileResult.value()); - return; - } - - generationStatusMsg = fmt::format("Seed generated! Hash: {}", randoData.mHash); - } - static void StartSeedGeneration() { if (generatingSeed) { return; } + generatingSeed = true; - GenerateSeed(); + std::lock_guard lock(generationStatusMsgMutex); + GenerateAndWriteSeed(generationStatusMsg); generatingSeed = false; DuskLog.debug("{}", generationStatusMsg); } @@ -259,6 +39,7 @@ namespace dusk { if (ImGui::BeginMenu("Generate")) { if (ImGui::MenuItem("Generate Seed", nullptr, false, !generatingSeed)) { + // Put seed generation on a separate thread so it doesn't freeze the game std::thread randoGenerationThread(StartSeedGeneration); randoGenerationThread.detach(); m_showRandoGeneration = true; diff --git a/src/dusk/randomizer/game/randomizer_context.cpp b/src/dusk/randomizer/game/randomizer_context.cpp index 64affa21c1..c6f7ef8686 100644 --- a/src/dusk/randomizer/game/randomizer_context.cpp +++ b/src/dusk/randomizer/game/randomizer_context.cpp @@ -1,12 +1,12 @@ #include "randomizer_context.hpp" -#include "../generator/utility/yaml.hpp" - #include "dusk/app_info.hpp" #include "dusk/logging.h" #include "dusk/main.h" #include "dusk/randomizer/game/tools.h" #include "dusk/randomizer/generator/utility/endian.hpp" +#include "dusk/randomizer/generator/utility/yaml.hpp" +#include "dusk/randomizer/generator/randomizer.hpp" #include "SDL3/SDL_filesystem.h" #include @@ -15,15 +15,6 @@ #include "d/actor/d_a_alink.h" -RandomizerContext& randomizer_GetContext() { - static RandomizerContext instance; - return instance; -} - -bool randomizer_IsActive() { - return dusk::IsGameLaunched && (!playerIsOnTitleScreen() || randomizer_GetContext().mCreatingSave) && !randomizer_GetContext().mHash.empty(); -} - std::optional RandomizerContext::WriteToFile() { std::ofstream seedData(this->GetSeedDataPath()); @@ -179,6 +170,15 @@ std::string RandomizerContext::GetSeedDataPath() const { return std::string(SDL_GetPrefPath(dusk::OrgName, dusk::AppName)) + "randomizer/seeds/" + this->mHash + "/seed.dat"; } +RandomizerContext& randomizer_GetContext() { + static RandomizerContext instance; + return instance; +} + +bool randomizer_IsActive() { + return dusk::IsGameLaunched && (!playerIsOnTitleScreen() || randomizer_GetContext().mCreatingSave) && !randomizer_GetContext().mHash.empty(); +} + std::vector HexToBytes(std::string hex) { std::vector bytes; // Strip "0x" if present @@ -206,4 +206,219 @@ u32 getActorPatchesCurrentStageKey() { u32 getActorCRC32(stage_actor_data_class* actor) { return zng_crc32(0, reinterpret_cast(actor), RandomizerContext::ACTOR_CRC_SIZE); +} + +void GenerateAndWriteSeed(std::string& generationStatusMsg) { + const auto result = SDL_GetPrefPath(dusk::OrgName, dusk::AppName); + if (!result) { + DuskLog.fatal("Unable to get PrefPath: {}", SDL_GetError()); + } + randomizer::Randomizer r; + r.SetBaseOutputPath(result); + auto generationResult = r.Generate(); + if (generationResult.has_value()) { + generationStatusMsg = fmt::format("Generation failed with the following error:\n{}", generationResult.value()); + return; + } + + const auto& world = r.GetWorlds()[0]; + + RandomizerContext randoData{}; + + // Settings we need to check ingame + for (const auto& [setting, info] : *randomizer::seedgen::settings::GetAllSettingsInfo()) { + if (info->NeedInGame()) { + randoData.mSettings[setting] = world->Setting(setting).GetCurrentOption(); + } + } + + // Set data for all locations + for (const auto& location : world->GetAllLocations()) { + const auto& metaData = location->GetMetadata(); + + // Chest Overrides + if (location->HasCategories("Chest")) { + const auto& stage = metaData[0]["Stage"].as(); + const auto& tboxId = metaData[0]["Tbox ID"].as(); + const auto& itemId = location->GetCurrentItem()->GetID(); + randoData.mTreasureChestOverrides[stage][tboxId] = itemId; + } + + // Freestanding Overrides + if (location->HasCategories("Freestanding Item")) { + u8 stage = metaData[0]["Stage"].as(); + u8 flag = metaData[0]["Flag"].as(); + u8 itemId = location->GetCurrentItem()->GetID(); + randoData.mFreestandingItemOverrides[stage][flag] = itemId; + } + + // Bug Rewards + if (location->HasCategories("Bug Reward")) { + u8 bugItemId = metaData[0]["Bug Item Id"].as(); + u8 itemId = location->GetCurrentItem()->GetID(); + randoData.mBugRewardOverrides[bugItemId] = itemId; + } + + // Items that we lookup just by calling their location name + if (location->HasCategories("Location Name Lookup")) { + const auto& locationName = metaData.as(); + const int itemId = location->GetCurrentItem()->GetID(); + randoData.mItemLocations[locationName] = itemId; + } + } + + // Set starting inventory + for (const auto& item: world->GetStartingItemPool()) { + randoData.mStartingInventory.push_back(item->GetID()); + } + + // Set starting flags + auto startFlags = LoadYAML(RANDO_DATA_PATH "startflags.yaml"); + // Event Flags + for (const auto& flagNode : startFlags["EventFlags"]) { + if (flagNode.IsScalar()) { + const auto& flag = flagNode.as(); + randoData.mStartEventFlags.push_back(flag); + } else if (flagNode.IsMap()) { + const auto& condition = flagNode.begin()->first.as(); + if (world->EvaluateSettingCondition(condition)) { + DuskLog.debug("Setting flags for {}", condition); + for (const auto& conditionalFlag : flagNode.begin()->second) { + const auto& flag = conditionalFlag.as(); + randoData.mStartEventFlags.push_back(flag); + } + } + } + } + + // Region Flags + for (const auto& regionNode : startFlags["RegionFlags"]) { + const auto& region = regionNode.first.as(); + const auto& index = regionNode.second["Index"].as(); + const auto& flags = regionNode.second["Flags"]; + DuskLog.debug("Setting region flags for {}", region); + // This seems kinda scuffed so maybe we change it later + for (const auto& flagNode : flags) { + if (flagNode.IsScalar()) { + const auto& flag = flagNode.as(); + randoData.mStartRegionFlags[index].push_back(flag); + } else if (flagNode.IsMap()) { + const auto& condition = flagNode.begin()->first.as(); + if (world->EvaluateSettingCondition(condition)) { + for (const auto& conditionalFlag : flagNode.begin()->second) { + const auto& flag = conditionalFlag.as(); + randoData.mStartRegionFlags[index].push_back(flag); + } + } + } + } + } + + if (world->Setting("Unlock Map Regions") == "On") + { + auto& bits = randoData.mMapBits; + bits = 0x20; + if (world->Setting("Snowpeak Does Not Require Reekfish Scent") == "On") {bits |= 0x40;} + if (world->Setting("Lanayru Twilight Cleared") == "On") {bits |= 0x10;} + if (world->Setting("Eldin Twilight Cleared") == "On") {bits |= 0x08;} + if (world->Setting("Faron Twilight Cleared") == "On") {bits |= 0x04;} + if (world->Setting("Skip Prologue") == "On") {bits |= 0x02;} + } + + // Set starting time of day + const auto startTimeSetting = world->Setting("Starting Time of Day"); + if (startTimeSetting == "Morning") + randoData.mStartHour = 6; + else if (startTimeSetting == "Noon") + randoData.mStartHour = 12; + else if (startTimeSetting == "Evening") + randoData.mStartHour = 18; + else if (startTimeSetting == "Night") + randoData.mStartHour = 24; + + // Actor Patches + auto actorPatches = LoadYAML(RANDO_DATA_PATH "actor_patches.yaml"); + for (const auto& stageNode : actorPatches) { + const auto& stageName = stageNode.first.as(); + for (const auto& roomNode : stageNode.second) { + u8 roomNo = roomNode.first.as(); + for (const auto& actorNode : roomNode.second) { + using namespace Utility::Endian; + // Get all the data for the actor (with endian shenanigans) + stage_actor_data_class actor{}; + const auto& actorName = actorNode["name"].as(); + strncpy(actor.name, actorName.c_str(), 8); + actor.base.parameters = toPlatform(target, actorNode["parameters"].as()); + actor.base.position.x = toPlatform(target, actorNode["position"]["x"].as()); + actor.base.position.y = toPlatform(target, actorNode["position"]["y"].as()); + actor.base.position.z = toPlatform(target, actorNode["position"]["z"].as()); + // Have to retrieve as u16 and then cast as s16 because otherwise yaml-cpp + // complains about values over 32767 not fitting in s16 + actor.base.angle.x = toPlatform(target, static_cast(actorNode["angle"]["x"].as())); + actor.base.angle.y = toPlatform(target, static_cast(actorNode["angle"]["y"].as())); + actor.base.angle.z = toPlatform(target, static_cast(actorNode["angle"]["z"].as())); + + // Create unique hash based off of actor data + u32 actorCRC32 = getActorCRC32(&actor); + + // Then override the actor with whatever parts are being patched + const auto& patchNode = actorNode["patch"]; + if (patchNode["name"]) { + const auto& newName = patchNode["name"].as(); + strncpy(actor.name, newName.c_str(), 8); + } + if (patchNode["parameters"]) { + actor.base.parameters = toPlatform(target, patchNode["parameters"].as()); + } + if (auto patchPosition = patchNode["position"]) { + if (patchPosition["x"]) { + actor.base.position.x = toPlatform(target, patchPosition["x"].as()); + } + if (patchPosition["y"]) { + actor.base.position.y = toPlatform(target, patchPosition["y"].as()); + } + if (patchPosition["z"]) { + actor.base.position.z = toPlatform(target, patchPosition["z"].as()); + } + } + if (auto patchAngle = patchNode["angle"]) { + // Have to retrieve as u16 and then cast as s16 because otherwise yaml-cpp + // complains about values over 32767 not fitting in s16 + if (patchAngle["x"]) { + actor.base.angle.x = toPlatform(target, static_cast(patchAngle["x"].as())); + } + if (patchAngle["y"]) { + actor.base.angle.y = toPlatform(target, static_cast(patchAngle["y"].as())); + } + if (patchAngle["z"]) { + actor.base.angle.z = toPlatform(target, static_cast(patchAngle["z"].as())); + } + } + + // Insert the actor patch into the context with our crc32 as the key and the + // raw actor patch data as the value + std::array patchedActorData{}; + std::memcpy(patchedActorData.data(), &actor, RandomizerContext::ACTOR_CRC_SIZE); + for (const auto& layerNode : actorNode["layers"]) { + u8 layerNo = layerNode.as(); + // Create key based off of stage index, room, and layer + u32 stageRoomLayerKey{}; + stageRoomLayerKey |= getStageID(stageName.c_str()) << 16; + stageRoomLayerKey |= roomNo << 8; + stageRoomLayerKey |= layerNo; + randoData.mActorPatches[stageRoomLayerKey][actorCRC32] = patchedActorData; + } + } + } + } + + randoData.mHash = r.GetConfig().GetHash(); + auto writeToFileResult = randoData.WriteToFile(); + if (writeToFileResult.has_value()) { + generationStatusMsg = + fmt::format("Failed to write seed data. Reason: {}", writeToFileResult.value()); + return; + } + + generationStatusMsg = fmt::format("Seed generated! Hash: {}", randoData.mHash); } \ No newline at end of file diff --git a/src/dusk/randomizer/game/randomizer_context.hpp b/src/dusk/randomizer/game/randomizer_context.hpp index 26ce604ab1..83c1e36d38 100644 --- a/src/dusk/randomizer/game/randomizer_context.hpp +++ b/src/dusk/randomizer/game/randomizer_context.hpp @@ -88,4 +88,5 @@ class stage_actor_data_class; */ u32 getActorCRC32(stage_actor_data_class*); +void GenerateAndWriteSeed(std::string& generationStatusMsg); #endif //DUSK_RANDOMIZER_CONTEXT_HPP