From ba9dc8e9badec54fd1bc9a2e01ab0573a2e3ec9a Mon Sep 17 00:00:00 2001 From: CraftyBoss Date: Tue, 19 May 2026 16:13:50 -0700 Subject: [PATCH] finish up rando config ui, remove randomizerEnabled CVar, fix seed strings not saving to config i would like to have someone more aquainted with the UI system to look over the config menu at some point, as i do think it could be better. --- include/dusk/settings.h | 1 - src/dusk/imgui/ImGuiMenuRandomizer.hpp | 2 +- .../randomizer/game/randomizer_context.cpp | 6 + src/dusk/randomizer/generator/randomizer.cpp | 4 - src/dusk/settings.cpp | 3 - src/dusk/ui/rando_config.cpp | 378 ++++++++++++------ src/dusk/ui/rando_config.hpp | 4 +- 7 files changed, 261 insertions(+), 137 deletions(-) diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 03d87045eb..bb7bfba191 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -214,7 +214,6 @@ struct UserSettings { ConfigVar recordingMode; // Misc - ConfigVar randomizerEnabled; ConfigVar showInputViewer; ConfigVar showInputViewerGyro; } game; diff --git a/src/dusk/imgui/ImGuiMenuRandomizer.hpp b/src/dusk/imgui/ImGuiMenuRandomizer.hpp index b11684a021..b9e3b051e8 100644 --- a/src/dusk/imgui/ImGuiMenuRandomizer.hpp +++ b/src/dusk/imgui/ImGuiMenuRandomizer.hpp @@ -13,7 +13,7 @@ public: void windowRandoGeneration(); private: - bool m_showRandoStats{true}; + bool m_showRandoStats{false}; bool m_showRandoGeneration{false}; }; } diff --git a/src/dusk/randomizer/game/randomizer_context.cpp b/src/dusk/randomizer/game/randomizer_context.cpp index 81fcf9384b..5333b3fdd0 100644 --- a/src/dusk/randomizer/game/randomizer_context.cpp +++ b/src/dusk/randomizer/game/randomizer_context.cpp @@ -116,6 +116,12 @@ std::optional RandomizerContext::WriteToFile() { std::optional RandomizerContext::LoadFromHash(const std::string& hash) { this->mHash = hash; + if (!std::filesystem::exists(this->GetSeedDataPath())) { + DuskLog.error("Failed to load Hash: {}", hash); + mHash.clear(); + return std::nullopt; + } + auto in = LoadYAML(this->GetSeedDataPath()); // Necessary settings diff --git a/src/dusk/randomizer/generator/randomizer.cpp b/src/dusk/randomizer/generator/randomizer.cpp index ac2a1895a2..3b341d8ba4 100644 --- a/src/dusk/randomizer/generator/randomizer.cpp +++ b/src/dusk/randomizer/generator/randomizer.cpp @@ -41,10 +41,6 @@ namespace randomizer utility::time::ScopedTimer<"Seed generation took ", std::chrono::milliseconds> timer; this->_config.LoadFromFile(GetConfigPath(), GetPrefPath()); - const std::string& configSeed = this->_config.GetSeed(); - std::string hashStr = configSeed.empty() ? seedgen::seed::GenerateSeed() : configSeed; - _config.SetSeed(hashStr); - utility::platform::Log(std::string("Seed: ") + this->_config.GetSeed()); seedgen::config::SeedRNG(this->_config, true, false); diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index f32d0daf32..5861241fe3 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -124,7 +124,6 @@ UserSettings g_userSettings = { .liveSplitEnabled {"game.liveSplitEnabled", false}, .showSpeedrunRTATimer {"game.showSpeedrunRTATimer", true}, .recordingMode {"game.recordingMode", false}, - .randomizerEnabled {"game.randomizerEnabled", false}, .showInputViewer {"game.showInputViewer", false}, .showInputViewerGyro {"game.showInputViewerGyro", false} }, @@ -278,8 +277,6 @@ void registerSettings() { Register(g_userSettings.game.debugFlyCamLockEvents); Register(g_userSettings.game.allowBackgroundInput); - Register(g_userSettings.game.randomizerEnabled); - Register(g_userSettings.backend.isoPath); Register(g_userSettings.backend.isoVerification); Register(g_userSettings.backend.graphicsBackend); diff --git a/src/dusk/ui/rando_config.cpp b/src/dusk/ui/rando_config.cpp index 93a8e367bd..5b09b6816a 100644 --- a/src/dusk/ui/rando_config.cpp +++ b/src/dusk/ui/rando_config.cpp @@ -6,6 +6,7 @@ #include "SDL3/SDL_filesystem.h" #include "bool_button.hpp" +#include "modal.hpp" #include "dusk/app_info.hpp" #include "dusk/config.hpp" #include "dusk/data.hpp" @@ -13,9 +14,10 @@ #include "number_button.hpp" #include "pane.hpp" #include "string_button.hpp" +#include "dusk/randomizer/game/tools.h" +#include "dusk/randomizer/generator/seedgen/seed.hpp" namespace dusk::ui { - struct ConfigBoolProps { Rml::String key; Rml::String icon; @@ -26,50 +28,14 @@ struct ConfigBoolProps { static bool generatingSeed = false; static std::string generationStatusMsg{}; -static std::mutex generationStatusMsgMutex{}; static void StartSeedGeneration() { - if (generatingSeed) { - return; - } - - generatingSeed = true; - std::lock_guard lock(generationStatusMsgMutex); GenerateAndWriteSeed(generationStatusMsg); generatingSeed = false; DuskLog.debug("{}", generationStatusMsg); } -// ripped straight from settings window -SelectButton& config_bool_select( - Pane& leftPane, Pane& rightPane, ConfigVar& var, ConfigBoolProps props) { - auto& button = leftPane.add_child(BoolButton::Props{ - .key = std::move(props.key), - .icon = std::move(props.icon), - .getValue = [&var] { return var.getValue(); }, - .setValue = - [&var, callback = std::move(props.onChange)](bool value) { - if (value == var.getValue()) { - return; - } - var.setValue(value); - config::Save(); - if (callback) { - callback(value); - } - }, - .isDisabled = std::move(props.isDisabled), - .isModified = [&var] { return var.getValue() != var.getDefaultValue(); }, - }); - leftPane.register_control( - button, rightPane, [helpText = std::move(props.helpText)](Pane& pane) { - pane.clear(); - pane.add_rml(helpText); - }); - return button; -} - - randomizer::seedgen::settings::Setting* FindSetting(const std::string& key) { +randomizer::seedgen::settings::Setting* FindSetting(const std::string& key) { if (key.empty()) { DuskLog.fatal("Key is empty! Unable to find setting."); } @@ -87,7 +53,49 @@ void SaveConfig() { GetRandomizerConfig().WriteToFile(GetRandomizerSettingsPath(), GetRandomizerPreferencesPath()); } -void rando_config_group(Pane& leftPane, Pane& rightPane, std::string settingKey, std::function onSelected = nullptr) { +bool TryCreateRandomSeed() { + auto& config = GetRandomizerConfig(); + + const std::string& configSeed = config.GetSeed(); + if (configSeed.empty()) { + config.SetSeed(randomizer::seedgen::seed::GenerateSeed()); + SaveConfig(); + return true; + } + return false; +} + +// ripped straight from settings window +SelectButton& config_bool_select( + Pane& leftPane, Pane& rightPane, ConfigVar& var, ConfigBoolProps props) { + auto& button = leftPane.add_child(BoolButton::Props{ + .key = std::move(props.key), + .icon = std::move(props.icon), + .getValue = [&var] { return var.getValue(); }, + .setValue = + [&var, callback = std::move(props.onChange)](bool value) { + if (value == var.getValue()) { + return; + } + var.setValue(value); + config::Save(); + if (callback) { + callback(value); + } + }, + .isDisabled = std::move(props.isDisabled), + .isModified = [&var] { return var.getValue() != var.getDefaultValue(); }, + }); + leftPane.register_control( + button, rightPane, [helpText = std::move(props.helpText)](Pane& pane) { + pane.clear(); + pane.add_rml(helpText); + }); + return button; +} + +void rando_config_group(Pane& leftPane, Pane& rightPane, std::string settingKey, + std::function onSelected = nullptr) { auto randoSettings = randomizer::seedgen::settings::GetAllSettingsInfo(); auto& settingData = randoSettings->at(settingKey); @@ -97,38 +105,40 @@ void rando_config_group(Pane& leftPane, Pane& rightPane, std::string settingKey, auto curSetting = FindSetting(settingKey); leftPane.register_control( - leftPane.add_select_button({ - .key = settingKey, - .getValue = - [curSetting] { return Rml::String{curSetting->GetCurrentOption()}; }, - }), - rightPane, [curSetting, onSelected](Pane& pane) { - auto curSelIdx = curSetting->GetCurrentOptionIndex(); - auto settingInfo = curSetting->GetInfo(); + leftPane.add_select_button({ + .key = settingKey, + .getValue = + [curSetting] { return Rml::String{curSetting->GetCurrentOption()}; }, + }), + rightPane, [curSetting, onSelected](Pane& pane) { + auto curSelIdx = curSetting->GetCurrentOptionIndex(); + auto settingInfo = curSetting->GetInfo(); - Rml::Element* text_elem = pane.add_rml(settingInfo->GetDescriptions().at(curSelIdx)); + Rml::Element* text_elem = pane.add_rml(settingInfo->GetDescriptions().at(curSelIdx)); - for (int i = 0; i < settingInfo->GetOptions().size(); ++i) { - pane.add_button( - { - .text = settingInfo->GetOptions()[i], - .isSelected = [curSetting, i] { return curSetting->GetCurrentOptionIndex() == i; }, - }) - .on_pressed([i, text_elem, curSetting] { - auto settingInfo = curSetting->GetInfo(); + for (int i = 0; i < settingInfo->GetOptions().size(); ++i) { + pane.add_button( + { + .text = settingInfo->GetOptions()[i], + .isSelected = [curSetting, i] { + return curSetting->GetCurrentOptionIndex() == i; + }, + }) + .on_pressed([i, text_elem, curSetting] { + auto settingInfo = curSetting->GetInfo(); - mDoAud_seStartMenu(kSoundItemChange); - curSetting->SetCurrentOption(i); - text_elem->SetInnerRML(settingInfo->GetDescriptions().at(i)); + mDoAud_seStartMenu(kSoundItemChange); + curSetting->SetCurrentOption(i); + text_elem->SetInnerRML(settingInfo->GetDescriptions().at(i)); - SaveConfig(); - }); - } + SaveConfig(); + }); + } - if (onSelected) { - onSelected(curSetting->GetCurrentOption(), pane); - } - }); + if (onSelected) { + onSelected(curSetting->GetCurrentOption(), pane); + } + }); } SelectButton& rando_config_toggle( @@ -139,16 +149,16 @@ SelectButton& rando_config_toggle( .key = settingKey, .getValue = [setting] { return setting->GetCurrentOptionIndex() != 0; }, .setValue = - [setting](bool value) { - auto idx = setting->GetCurrentOptionIndex(); - if (idx == value) { - return; - } + [setting](bool value) { + auto idx = setting->GetCurrentOptionIndex(); + if (idx == value) { + return; + } - setting->SetCurrentOption(value); + setting->SetCurrentOption(value); - SaveConfig(); - }, + SaveConfig(); + }, }); auto& comp = leftPane.register_control( button, rightPane, [setting](Pane& pane) { @@ -168,8 +178,9 @@ SelectButton& rando_config_toggle( return button; } -NumberButton* rando_add_optional_setting(std::string optionValue, std::string optionsKeyPrefix, Pane& pane) { - std::string fullOptionalKey = fmt::format("{} {}", optionsKeyPrefix, optionValue); +NumberButton* rando_add_optional_setting(std::string optionValue, std::string optionsKeyPrefix, + Pane& pane) { + std::string fullOptionalKey = fmt::format("{} {}", optionsKeyPrefix, optionValue); // check if setting exists auto randoSettings = randomizer::seedgen::settings::GetAllSettingsInfo(); @@ -183,69 +194,172 @@ NumberButton* rando_add_optional_setting(std::string optionValue, std::string op return &pane.add_child(NumberButton::Props{ .key = fmt::format("{} Count", optionValue), .getValue = [curSetting] { return curSetting->GetCurrentOptionAsNumber(); }, - .setValue = [curSetting](int value) { curSetting->SetCurrentOption(std::to_string(value)); }, + .setValue = [curSetting](int value) { + curSetting->SetCurrentOption(std::to_string(value)); + }, .min = std::stoi(options.front()), .max = std::stoi(options.back()), }); } +Document* show_seed_gen_modal(std::string_view message) { + auto* modal = &push_document(std::make_unique(Modal::Props{ + .title = "Randomizer", + .bodyRml = escape(message), + .onDismiss = [](Modal& modal) { + mDoAud_seStartMenu(kSoundWindowClose); + modal.pop(); + }, + .icon = "verifying", + })); + + if (auto* doc = top_document()) { + doc->focus(); + } + + return modal; +} + RandomizerWindow::RandomizerWindow() { add_tab("Seed Management", [this](Rml::Element* content) { auto& leftPane = add_child(content, Pane::Type::Controlled); auto& rightPane = add_child(content, Pane::Type::Controlled); - config_bool_select(leftPane, rightPane, getSettings().game.randomizerEnabled, { - .key = "Randomizer Enabled", - .helpText = "Determines if a new Save will load as a regular save or randomized save. (Doesn't do anything currently)" + leftPane.register_control( + leftPane.add_select_button({ + .key = "Selected Seed", + .getValue = + [] { + return randomizer_GetContext().mHash.empty() ? + "None" : + randomizer_GetContext().mHash; + }, + }), + rightPane, [](Pane& pane) { + std::filesystem::path seedDirectory = + dusk::data::configured_data_path() / "randomizer" / "seeds"; + + if (!std::filesystem::exists(seedDirectory)) { + std::filesystem::create_directories(seedDirectory); + } + + if (std::filesystem::is_empty(seedDirectory)) { + pane.add_rml( + "No seeds generated! Check out the options tab and generate a seed using the button below."); + return; + } + + pane.add_rml( + "Apply a seed generated using the randomizer generator. (Note: must be done before selecting a save file)."); + + for (const auto& entry : std::filesystem::directory_iterator(seedDirectory)) { + if (entry.is_directory()) { + std::string hash = entry.path().filename().string(); + + pane.add_button( + { + .text = hash, + .isSelected = [hash] { + return randomizer_GetContext().mHash == hash; + }, + }) + .on_pressed([hash] { + randomizer_GetContext() = RandomizerContext(); + randomizer_GetContext().LoadFromHash(hash); + }); + } + } + }); + + leftPane.register_control( + leftPane.add_button("Delete Seeds"), + rightPane, [](Pane& pane) { + std::filesystem::path seedDirectory = + dusk::data::configured_data_path() / "randomizer" / "seeds"; + + if (!std::filesystem::exists(seedDirectory)) { + std::filesystem::create_directories(seedDirectory); + } + + if (std::filesystem::is_empty(seedDirectory)) { + pane.add_rml( + "No seeds generated."); + return; + } + + pane.add_rml( + "Delete any seed currently not active in the randomizer."); + + for (const auto& entry : std::filesystem::directory_iterator(seedDirectory)) { + if (entry.is_directory()) { + std::string hash = entry.path().filename().string(); + + // TODO: our ui lib doesnt have an easy way to either refresh or remove values from the right pane + pane.add_button( + { + .text = hash, + .isDisabled = [hash] { + return !playerIsOnTitleScreen() || randomizer_GetContext().mHash == hash; + } + }) + .on_pressed([hash, entry] { + if (randomizer_GetContext().mHash != hash) { + std::filesystem::remove_all(entry); + } else if (!randomizer_IsActive()){ + // If the user selected the currently seed, but it's not active, we'll allow the delete + std::filesystem::remove_all(entry); + randomizer_GetContext() = RandomizerContext(); + } + }); + } + } + }); + + leftPane.register_control(leftPane.add_button(ControlledButton::Props{ + .text = "Deactivate Seed", + .isDisabled = [] { return randomizer_GetContext().mHash.empty() || !playerIsOnTitleScreen(); } + }).on_pressed([] { + if (!randomizer_IsActive()) { + randomizer_GetContext() = RandomizerContext(); + } + }), rightPane, [](Pane& pane) { + pane.clear(); + pane.add_rml("Disables currently chosen seed."); }); - leftPane.add_button("Generate Seed").on_pressed([this] { + leftPane.register_control(leftPane.add_button("Generate Seed").on_pressed( + [this, &rightPane] { + if (TryCreateRandomSeed()) { + DuskLog.info("Created new Seed for generator."); + } + std::thread randoGenerationThread(StartSeedGeneration); randoGenerationThread.detach(); - m_showRandoGeneration = true; - }); - leftPane.add_button("Load Last Generated Seed").on_pressed([] { - std::string seedDirectory = - std::string(SDL_GetPrefPath(dusk::OrgName, dusk::AppName)) + "randomizer/seeds"; + generatingSeed = true; + m_genSeedModal = show_seed_gen_modal("Generating Seed..."); - if (!std::filesystem::exists(seedDirectory)) { - DuskLog.error("No Files in Directory: {}", seedDirectory); - return; - } - - std::string hash = ""; - for (const auto& entry : std::filesystem::directory_iterator(seedDirectory)) { - if (entry.is_directory()) { - hash = entry.path().filename().string(); - break; - } - } - - if (!randomizer_IsActive() && !hash.empty()) { - DuskLog.info("Attempting to load Hash: {}", hash); - - randomizer_GetContext() = RandomizerContext(); - randomizer_GetContext().LoadFromHash(hash); - }else { - DuskLog.warn("No seeds found in generated folder: {}", hash); - } + }),rightPane, [](Pane& pane) { + pane.clear(); + pane.add_rml( + "Generate a Randomizer seed using the current configuration options, and the supplied seed string."); }); leftPane.register_control(leftPane.add_child(StringButton::Props{ - .key = "Seed String", - .getValue = [] { - return GetRandomizerConfig().GetSeed(); - }, - .setValue = [](Rml::String value) { - GetRandomizerConfig().SetSeed(value); - SaveConfig(); - }, - .maxLength = 32, - }), + .key = "Seed String", + .getValue = [] { + return GetRandomizerConfig().GetSeed(); + }, + .setValue = [](Rml::String value) { + GetRandomizerConfig().SetSeed(value); + SaveConfig(); + }, + .maxLength = 32, + }), rightPane, [](Pane& pane) { pane.clear(); - pane.add_rml("Current value of the seed used by the randomizer for generation. Leave blank for a random value."); + pane.add_rml( + "Current value of the seed used by the randomizer for generation. Leave blank for a random value."); }); }); @@ -259,9 +373,10 @@ RandomizerWindow::RandomizerWindow() { leftPane.add_section("Access Options"); - rando_config_group(leftPane, rightPane, "Hyrule Barrier Requirements", [](const std::string& value, Pane& pane) { - return rando_add_optional_setting(value, "Hyrule Barrier", pane); - }); + rando_config_group(leftPane, rightPane, "Hyrule Barrier Requirements", + [](const std::string& value, Pane& pane) { + return rando_add_optional_setting(value, "Hyrule Barrier", pane); + }); rando_config_group(leftPane, rightPane, "Palace of Twilight Requirements"); rando_config_group(leftPane, rightPane, "Faron Woods Logic"); @@ -286,9 +401,10 @@ RandomizerWindow::RandomizerWindow() { rando_config_group(leftPane, rightPane, "Small Keys"); rando_config_group(leftPane, rightPane, "Big Keys"); rando_config_group(leftPane, rightPane, "Maps and Compasses"); - rando_config_group(leftPane, rightPane, "Hyrule Castle Big Key Requirements", [](const std::string& value, Pane& pane) { - return rando_add_optional_setting(value, "Hyrule Castle Big Key", pane); - }); + rando_config_group(leftPane, rightPane, "Hyrule Castle Big Key Requirements", + [](const std::string& value, Pane& pane) { + return rando_add_optional_setting(value, "Hyrule Castle Big Key", pane); + }); rando_config_toggle(leftPane, rightPane, "Dungeon Rewards Can Be Anywhere"); rando_config_toggle(leftPane, rightPane, "No Small Keys on Bosses"); @@ -345,6 +461,15 @@ RandomizerWindow::RandomizerWindow() { }); } +void RandomizerWindow::update() { + Window::update(); + + if (m_genSeedModal && !generatingSeed) { + m_genSeedModal->pop(); + m_genSeedModal = nullptr; + } +} + std::filesystem::path GetRandomizerSettingsPath() { return data::configured_data_path() / "randomizer" / "settings.yaml"; } @@ -354,7 +479,8 @@ std::filesystem::path GetRandomizerPreferencesPath() { } randomizer::seedgen::config::Config& GetRandomizerConfig() { - static randomizer::seedgen::config::Config s_config{GetRandomizerSettingsPath(), GetRandomizerPreferencesPath()}; + static randomizer::seedgen::config::Config s_config{GetRandomizerSettingsPath(), + GetRandomizerPreferencesPath()}; return s_config; } -} +} \ No newline at end of file diff --git a/src/dusk/ui/rando_config.hpp b/src/dusk/ui/rando_config.hpp index d2cd353c8f..111c7c756d 100644 --- a/src/dusk/ui/rando_config.hpp +++ b/src/dusk/ui/rando_config.hpp @@ -15,8 +15,8 @@ namespace dusk::ui { class RandomizerWindow : public Window { public: RandomizerWindow(); - + void update() override; private: - bool m_showRandoGeneration = false; + Document* m_genSeedModal = nullptr; }; }