From dad4ae00957e4960556f7f0cce94a3354105708b Mon Sep 17 00:00:00 2001 From: Christopher Leggett Date: Sun, 10 Dec 2023 11:20:47 -0500 Subject: [PATCH] Rando settings streamline and auto render (#3391) * Removes cvarSettings map. Options now link with CVar names directly. So instead of passing the cvarSettings map, the Option class can check a corresponding CVar if a cvarName was provided during construction. Of note, it does not automatically sync the Option selected index with the CVar value, as we would not want this to happen in all cases, for example when dragging a spoiler file, we don't want to overwrite all the CVars with the Options from the spoiler file. Currently all Options are set to the value of the CVar they are linked to right before generating a new seed, unless a spoiler file has been dropped in which case those settings are used instead. * Early version of ImGui Render function Currently only the slider variant. Will allow for auto rendering of options in ImGui, with tooltips and automatic display of the values in each Option's options array while keeping the CVars at the selected index, preventing Off By One Errors. * Implementation of Checkbox and Combobox rendering. Currently only in use for a couple of items, future commit will implement for all options. * Auto-render entire first tab of Randomizer Settings * Switch remaining tabs to auto-render * Implements disabling options * Cleanup/Documentation * Auto-render entire table columns * Implement OptionGroup rendering for "Sections" * Automates the rendering of tables in the Settings window. With the exception of the Locations and Tricks tabs, those are special and will need a lot more work. * Adds ability for option groups to have descriptions, These descriptions will automatically display as tooltips in ImGui, if the widget container type accounts for it. * Fix as many IDE warnings as possible in option.h/cpp Trying out CLion Nova, and it highlighted some things I decided to fix, some from CLion itself and some from CLang-Tidy. Oddly, it didn't like a conversion from size_t to int whether I left it implicit or added a static_cast, so I guess that warning is staying. * Fixes some simple bugs * fix another small oopsie * Fixes parsing some of the option changes Specifically we went from storing the actual value in the CVar to storing an index, meaning sliders that started with 1 now have the index offset by 1. This is currently only Big Poe Count, Triforce Hunt total/required, and starting hearts. Everything else either already started at 0, or in the case of LACS/Bridge counts, we were starting the sliders at 1 but they would have always worked at 0 according to the 3drando logic. * Fix bug with status of skip child stealth * Renames the Settings::Setting function to GetOption * Add `Settings` pointer as a member of `RandomizerSettingsWindow`. * Replaces ctx->GetOption with direct access to mOptions This is equivalent, the access through ctx in this case was completely unnecessary and a muscle-memory mistake on my part. * Implements a few IDE/Linter suggestions --- .../Enhancements/randomizer/3drando/menu.cpp | 6 +- .../Enhancements/randomizer/3drando/menu.hpp | 2 +- .../randomizer/3drando/playthrough.cpp | 10 +- .../randomizer/3drando/playthrough.hpp | 4 +- .../randomizer/3drando/rando_main.cpp | 4 +- .../randomizer/3drando/rando_main.hpp | 4 +- .../randomizer/3drando/spoiler_log.cpp | 18 +- .../randomizer/3drando/spoiler_log.hpp | 2 +- soh/soh/Enhancements/randomizer/context.cpp | 7 +- .../Enhancements/randomizer/item_location.cpp | 4 +- .../Enhancements/randomizer/item_location.h | 2 +- soh/soh/Enhancements/randomizer/option.cpp | 323 ++- soh/soh/Enhancements/randomizer/option.h | 402 +++- .../randomizer/option_descriptions.cpp | 523 +++++ .../Enhancements/randomizer/randomizer.cpp | 1792 +---------------- .../Enhancements/randomizer/randomizerTypes.h | 20 + .../randomizer/randomizer_settings_window.h | 12 +- soh/soh/Enhancements/randomizer/settings.cpp | 1012 ++++++++-- soh/soh/Enhancements/randomizer/settings.h | 147 +- 19 files changed, 2277 insertions(+), 2017 deletions(-) create mode 100644 soh/soh/Enhancements/randomizer/option_descriptions.cpp diff --git a/soh/soh/Enhancements/randomizer/3drando/menu.cpp b/soh/soh/Enhancements/randomizer/3drando/menu.cpp index 9356ceeda..e01098c7d 100644 --- a/soh/soh/Enhancements/randomizer/3drando/menu.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/menu.cpp @@ -21,7 +21,7 @@ std::vector presetEntries; Rando::Option* currentSetting; } // namespace -std::string GenerateRandomizer(std::unordered_map cvarSettings, std::set excludedLocations, std::set enabledTricks, +std::string GenerateRandomizer(std::set excludedLocations, std::set enabledTricks, std::string seedString) { auto ctx = Rando::Context::GetInstance(); @@ -38,7 +38,7 @@ std::string GenerateRandomizer(std::unordered_map } catch (std::out_of_range &e) { count = 1; } - Playthrough::Playthrough_Repeat(cvarSettings, excludedLocations, enabledTricks, count); + Playthrough::Playthrough_Repeat(excludedLocations, enabledTricks, count); return ""; } @@ -46,7 +46,7 @@ std::string GenerateRandomizer(std::unordered_map uint32_t seedHash = boost::hash_32{}(ctx->GetSettings()->GetSeedString()); ctx->GetSettings()->SetSeed(seedHash & 0xFFFFFFFF); - int ret = Playthrough::Playthrough_Init(ctx->GetSettings()->GetSeed(), cvarSettings, excludedLocations, enabledTricks); + int ret = Playthrough::Playthrough_Init(ctx->GetSettings()->GetSeed(), excludedLocations, enabledTricks); if (ret < 0) { if (ret == -1) { // Failed to generate after 5 tries printf("\n\nFailed to generate after 5 tries.\nPress B to go back to the menu.\nA different seed might be " diff --git a/soh/soh/Enhancements/randomizer/3drando/menu.hpp b/soh/soh/Enhancements/randomizer/3drando/menu.hpp index 8079ab2e9..77228e835 100644 --- a/soh/soh/Enhancements/randomizer/3drando/menu.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/menu.hpp @@ -25,4 +25,4 @@ // #define CYAN "\x1b[36m" // #define WHITE "\x1b[37m" -std::string GenerateRandomizer(std::unordered_map cvarSetting, std::set excludedLocations, std::set enabledTricks, std::string seedInput); \ No newline at end of file +std::string GenerateRandomizer(std::set excludedLocations, std::set enabledTricks, std::string seedInput); \ No newline at end of file diff --git a/soh/soh/Enhancements/randomizer/3drando/playthrough.cpp b/soh/soh/Enhancements/randomizer/3drando/playthrough.cpp index 15c5736e6..68200f470 100644 --- a/soh/soh/Enhancements/randomizer/3drando/playthrough.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/playthrough.cpp @@ -13,7 +13,7 @@ namespace Playthrough { -int Playthrough_Init(uint32_t seed, std::unordered_map cvarSettings, std::set excludedLocations, std::set enabledTricks) { +int Playthrough_Init(uint32_t seed, std::set excludedLocations, std::set enabledTricks) { // initialize the RNG with just the seed incase any settings need to be // resolved to something random Random_Init(seed); @@ -25,7 +25,7 @@ int Playthrough_Init(uint32_t seed, std::unordered_mapHintReset(); Areas::AccessReset(); - ctx->GetSettings()->UpdateSettings(cvarSettings, excludedLocations, enabledTricks); + ctx->GetSettings()->FinalizeSettings(excludedLocations, enabledTricks); // once the settings have been finalized turn them into a string for hashing std::string settingsStr; for (const Rando::OptionGroup& optionGroup : ctx->GetSettings()->GetOptionGroups()) { @@ -63,7 +63,7 @@ int Playthrough_Init(uint32_t seed, std::unordered_map cvarSettings, std::set excludedLocations, std::set enabledTricks, int count /*= 1*/) { +int Playthrough_Repeat(std::set excludedLocations, std::set enabledTricks, int count /*= 1*/) { printf("\x1b[0;0HGENERATING %d SEEDS", count); auto ctx = Rando::Context::GetInstance(); uint32_t repeatedSeed = 0; @@ -95,7 +95,7 @@ int Playthrough_Repeat(std::unordered_map cvarSet ctx->GetSettings()->SetSeed(repeatedSeed % 0xFFFFFFFF); //CitraPrint("testing seed: " + std::to_string(Settings::seed)); ClearProgress(); - Playthrough_Init(ctx->GetSettings()->GetSeed(), cvarSettings, excludedLocations, enabledTricks); + Playthrough_Init(ctx->GetSettings()->GetSeed(), excludedLocations, enabledTricks); printf("\x1b[15;15HSeeds Generated: %d\n", i + 1); } diff --git a/soh/soh/Enhancements/randomizer/3drando/playthrough.hpp b/soh/soh/Enhancements/randomizer/3drando/playthrough.hpp index 9b59b8f5e..1dbbc7842 100644 --- a/soh/soh/Enhancements/randomizer/3drando/playthrough.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/playthrough.hpp @@ -4,6 +4,6 @@ #include "../context.h" namespace Playthrough { - int Playthrough_Init(uint32_t seed, std::unordered_map cvarSettings, std::set excludedLocations, std::set enabledTricks); - int Playthrough_Repeat(std::unordered_map cvarSettings, std::set excludedLocations, std::set enabledTricks, int count = 1); + int Playthrough_Init(uint32_t seed, std::set excludedLocations, std::set enabledTricks); + int Playthrough_Repeat(std::set excludedLocations, std::set enabledTricks, int count = 1); } diff --git a/soh/soh/Enhancements/randomizer/3drando/rando_main.cpp b/soh/soh/Enhancements/randomizer/3drando/rando_main.cpp index 2c2ea3158..833a6fc42 100644 --- a/soh/soh/Enhancements/randomizer/3drando/rando_main.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/rando_main.cpp @@ -10,14 +10,14 @@ #include #include -void RandoMain::GenerateRando(std::unordered_map cvarSettings, std::set excludedLocations, std::set enabledTricks, +void RandoMain::GenerateRando(std::set excludedLocations, std::set enabledTricks, std::string seedString) { HintTable_Init(); // std::string settingsFileName = "./randomizer/latest_settings.json"; // CVarSetString("gLoadedPreset", settingsFileName.c_str()); - std::string fileName = LUS::Context::GetPathRelativeToAppDirectory(GenerateRandomizer(cvarSettings, excludedLocations, enabledTricks, seedString).c_str()); + std::string fileName = LUS::Context::GetPathRelativeToAppDirectory(GenerateRandomizer(excludedLocations, enabledTricks, seedString).c_str()); CVarSave(); CVarLoad(); diff --git a/soh/soh/Enhancements/randomizer/3drando/rando_main.hpp b/soh/soh/Enhancements/randomizer/3drando/rando_main.hpp index 54ebdf6f8..8ac22069b 100644 --- a/soh/soh/Enhancements/randomizer/3drando/rando_main.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/rando_main.hpp @@ -1,5 +1,7 @@ #pragma once #include "soh/Enhancements/randomizer/item.h" + +#include namespace RandoMain { -void GenerateRando(std::unordered_map cvarSettings, std::set excludedLocations, std::set enabledTricks, std::string seedInput); +void GenerateRando(std::set excludedLocations, std::set enabledTricks, std::string seedInput); } diff --git a/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp b/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp index 6561951a0..6c9f83ab1 100644 --- a/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp @@ -598,7 +598,7 @@ Rando::ItemLocation* GetItemLocation(RandomizerGet item) { } // Writes the hints to the spoiler log, if they are enabled. -static void WriteHints(int language) { +static void WriteHints() { auto ctx = Rando::Context::GetInstance(); std::string unformattedGanonText; std::string unformattedGanonHintText; @@ -607,7 +607,7 @@ static void WriteHints(int language) { std::string unformattedSheikText; std::string unformattedSariaText; - switch (language) { + switch (ctx->GetOption(RSK_LANGUAGE).GetSelectedOptionIndex()) { case 0: default: unformattedGanonText = GetGanonText().GetEnglish(); @@ -721,7 +721,7 @@ static void WriteHints(int language) { Rando::Hint* hint = ctx->GetHint((RandomizerHintKey)(key - RC_COLOSSUS_GOSSIP_STONE + 1)); Rando::ItemLocation* hintedLocation = ctx->GetItemLocation(hint->GetHintedLocation()); std::string hintTextString; - switch (language) { + switch (ctx->GetOption(RSK_LANGUAGE).GetSelectedOptionIndex()) { case 0: default: hintTextString = hint->GetText().GetEnglish(); @@ -748,13 +748,13 @@ static void WriteHints(int language) { } } -static void WriteAllLocations(int language) { +static void WriteAllLocations() { auto ctx = Rando::Context::GetInstance(); for (const RandomizerCheck key : ctx->allLocations) { Rando::ItemLocation* location = ctx->GetItemLocation(key); std::string placedItemName; - switch (language) { + switch (ctx->GetOption(RSK_LANGUAGE).GetSelectedOptionIndex()) { case 0: default: placedItemName = location->GetPlacedItemName().english; @@ -785,7 +785,7 @@ static void WriteAllLocations(int language) { } if (location->GetPlacedRandomizerGet() == RG_ICE_TRAP) { - switch (language) { + switch (ctx->GetOption(RSK_LANGUAGE).GetSelectedOptionIndex()) { case 0: default: jsonData["locations"][Rando::StaticData::GetLocation(location->GetRandomizerCheck())->GetName()]["model"] = @@ -804,7 +804,7 @@ static void WriteAllLocations(int language) { } } -const char* SpoilerLog_Write(int language) { +const char* SpoilerLog_Write() { auto ctx = Rando::Context::GetInstance(); auto spoilerLog = tinyxml2::XMLDocument(false); spoilerLog.InsertEndChild(spoilerLog.NewDeclaration()); @@ -839,9 +839,9 @@ const char* SpoilerLog_Write(int language) { ctx->playthroughLocations.clear(); ctx->playthroughBeatable = false; - WriteHints(language); + WriteHints(); WriteShuffledEntrances(); - WriteAllLocations(language); + WriteAllLocations(); if (!std::filesystem::exists(LUS::Context::GetPathRelativeToAppDirectory("Randomizer"))) { std::filesystem::create_directory(LUS::Context::GetPathRelativeToAppDirectory("Randomizer")); diff --git a/soh/soh/Enhancements/randomizer/3drando/spoiler_log.hpp b/soh/soh/Enhancements/randomizer/3drando/spoiler_log.hpp index 622c15814..8ad3212f8 100644 --- a/soh/soh/Enhancements/randomizer/3drando/spoiler_log.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/spoiler_log.hpp @@ -111,5 +111,5 @@ const RandomizerHash& GetRandomizerHash(); void WriteIngameSpoilerLog(); -const char* SpoilerLog_Write(int language); +const char* SpoilerLog_Write(); const SpoilerData& GetSpoilerData(); \ No newline at end of file diff --git a/soh/soh/Enhancements/randomizer/context.cpp b/soh/soh/Enhancements/randomizer/context.cpp index b9420ace4..945f62a57 100644 --- a/soh/soh/Enhancements/randomizer/context.cpp +++ b/soh/soh/Enhancements/randomizer/context.cpp @@ -14,6 +14,7 @@ namespace Rando { std::weak_ptr Context::mContext; + Context::Context() { for (auto& location : StaticData::GetLocationTable()) { mSpoilerfileCheckNameToEnum[location.GetName()] = location.GetRandomizerCheck(); @@ -135,7 +136,7 @@ void Context::PlaceItemInLocation(const RandomizerCheck locKey, const Randomizer SPDLOG_DEBUG(StaticData::GetLocation(locKey)->GetName()); SPDLOG_DEBUG("\n\n"); - if (applyEffectImmediately || mSettings->Setting(RSK_LOGIC_RULES).Is(RO_LOGIC_GLITCHLESS) || mSettings->Setting(RSK_LOGIC_RULES).Is(RO_LOGIC_VANILLA)) { + if (applyEffectImmediately || mSettings->GetOption(RSK_LOGIC_RULES).Is(RO_LOGIC_GLITCHLESS) || mSettings->GetOption(RSK_LOGIC_RULES).Is(RO_LOGIC_VANILLA)) { StaticData::RetrieveItem(item).ApplyEffect(); } @@ -175,7 +176,7 @@ void Context::AddLocations(const Container& locations, std::vectorSetting(RSK_TRIFORCE_HUNT)) { + if (mSettings->GetOption(RSK_TRIFORCE_HUNT)) { AddLocation(RC_TRIFORCE_COMPLETED); } AddLocations(StaticData::overworldLocations); @@ -584,7 +585,7 @@ Sprite* Context::GetSeedTexture(const uint8_t index) { } Option& Context::GetOption(const RandomizerSettingKey key) const { - return mSettings->Setting(key); + return mSettings->GetOption(key); } Option& Context::GetTrickOption(const RandomizerTrick key) const { diff --git a/soh/soh/Enhancements/randomizer/item_location.cpp b/soh/soh/Enhancements/randomizer/item_location.cpp index 468a01ec1..323c6159b 100644 --- a/soh/soh/Enhancements/randomizer/item_location.cpp +++ b/soh/soh/Enhancements/randomizer/item_location.cpp @@ -140,13 +140,13 @@ Option* ItemLocation::GetExcludedOption() { void ItemLocation::AddExcludeOption() { if (const std::string name = StaticData::GetLocation(rc)->GetName(); name.length() < 23) { - excludedOption = Option::Bool(name, {"Include", "Exclude"}); + excludedOption = Option::Bool(name, {"Include", "Exclude"}, OptionCategory::Setting, "", "", WidgetType::Checkbox, RO_LOCATION_INCLUDE); } else { const size_t lastSpace = name.rfind(' ', 23); std::string settingText = name; settingText.replace(lastSpace, 1, "\n "); - excludedOption = Option::Bool(settingText, {"Include", "Exclude"}); + excludedOption = Option::Bool(settingText, {"Include", "Exclude"}, OptionCategory::Setting, "", "", WidgetType::Checkbox, RO_LOCATION_INCLUDE); } // RANDOTODO: this without string compares and loops bool alreadyAdded = false; diff --git a/soh/soh/Enhancements/randomizer/item_location.h b/soh/soh/Enhancements/randomizer/item_location.h index 79ceaec01..a4b60a5d3 100644 --- a/soh/soh/Enhancements/randomizer/item_location.h +++ b/soh/soh/Enhancements/randomizer/item_location.h @@ -58,7 +58,7 @@ class ItemLocation { bool addedToPool = false; RandomizerGet placedItem = RG_NONE; RandomizerGet delayedItem = RG_NONE; - Option excludedOption = Option::Bool(StaticData::GetLocation(rc)->GetName(), {"Include", "Exclude"}); + Option excludedOption = Option::Bool(StaticData::GetLocation(rc)->GetName(), {"Include", "Exclude"}, OptionCategory::Setting, "", "", WidgetType::Checkbox, RO_LOCATION_INCLUDE); uint16_t price = 0; RandomizerRegion parentRegion = RR_NONE; RandomizerArea area = RA_NONE; diff --git a/soh/soh/Enhancements/randomizer/option.cpp b/soh/soh/Enhancements/randomizer/option.cpp index a692ef712..a7b4340c9 100644 --- a/soh/soh/Enhancements/randomizer/option.cpp +++ b/soh/soh/Enhancements/randomizer/option.cpp @@ -1,18 +1,33 @@ #include "option.h" +#include "libultraship/bridge.h" +#include +#include +#include "soh/UIWidgets.hpp" namespace Rando { Option Option::Bool(std::string name_, std::vector options_, const OptionCategory category_, - const uint8_t defaultOption_, const bool defaultHidden_) { - return {false, std::move(name_), std::move(options_), category_, defaultOption_, defaultHidden_}; + std::string cvarName_, std::string description_, WidgetType widgetType_, const uint8_t defaultOption_, + const bool defaultHidden_, int imFlags_) { + return {false, std::move(name_), std::move(options_), category_, std::move(cvarName_), std::move(description_), + widgetType_, defaultOption_, defaultHidden_, imFlags_}; +} + +Option Option::Bool(std::string name_, std::string cvarName_, std::string description_, const int imFlags_, + const WidgetType widgetType_, const bool defaultOption_) { + return Option(false, std::move(name_), {"Off", "On"}, OptionCategory::Setting, std::move(cvarName_), + std::move(description_), widgetType_, defaultOption_, false, imFlags_); } Option Option::U8(std::string name_, std::vector options_, const OptionCategory category_, - const uint8_t defaultOption_, const bool defaultHidden_) { - return {static_cast(0), std::move(name_), std::move(options_), category_, defaultOption_, defaultHidden_}; + std::string cvarName_, std::string description_, WidgetType widgetType_, const uint8_t defaultOption_, + const bool defaultHidden_, int imFlags_) { + return {static_cast(0), std::move(name_), std::move(options_), category_, std::move(cvarName_), + std::move(description_), widgetType_, defaultOption_, defaultHidden_, imFlags_}; } Option Option::LogicTrick(std::string name_) { - return Option(false, std::move(name_), { "Disabled", "Enabled" }, OptionCategory::Setting, 0, false); + return Option(false, std::move(name_), { "Disabled", "Enabled" }, OptionCategory::Setting, "", + "", WidgetType::Checkbox, 0, false, IMFLAG_NONE); } Option::operator bool() const { @@ -38,6 +53,10 @@ const std::string& Option::GetSelectedOptionText() const { return options[selectedOption]; } +const std::string& Option::GetCVarName() const { + return cvarName; +} + void Option::SetVariable() { if (std::holds_alternative(var)) { var.emplace(selectedOption != 0); @@ -46,6 +65,18 @@ void Option::SetVariable() { } } +void Option::SetCVar() const { + if (!cvarName.empty()) { + CVarSetInteger(cvarName.c_str(), GetSelectedOptionIndex()); + } +} + +void Option::SetFromCVar() { + if (!cvarName.empty()) { + SetSelectedIndex(CVarGetInteger(cvarName.c_str(), defaultOption)); + } +} + void Option::SetDelayedOption() { delayedOption = selectedOption; } @@ -75,45 +106,221 @@ bool Option::IsHidden() const { return hidden; } +void Option::ChangeOptions(std::vector opts) { + if (selectedOption >= opts.size()) { + selectedOption = opts.size() - 1; + } + options = std::move(opts); +} + +void Option::Enable() { + disabled = false; +} + +void Option::Disable(std::string text, const UIWidgets::CheckboxGraphics graphic) { + if (!disabled || disabledText != text || disabledGraphic != graphic) { + disabled = true; + disabledText = std::move(text); + disabledGraphic = graphic; + } +} + bool Option::IsCategory(const OptionCategory category) const { return category == this->category; } -Option::Option(uint8_t var_, std::string name_, std::vector options_, const OptionCategory category_, - const uint8_t defaultOption_, const bool defaultHidden_) +bool Option::RenderImGui() const { + bool changed = false; + ImGui::BeginGroup(); + switch (widgetType) { + case WidgetType::Checkbox: + changed = RenderCheckbox(); + break; + case WidgetType::Combobox: + changed = RenderCombobox(); + break; + case WidgetType::Slider: + changed = RenderSlider(); + break; + } + UIWidgets::Spacer(0); + ImGui::EndGroup(); + return changed; +} + +bool Option::HasFlag(const int imFlag_) const { + return imFlag_ & imFlags; +} + +void Option::AddFlag(const int imFlag_) { + imFlags |= imFlag_; +} + +void Option::SetFlag(const int imFlag_) { + imFlags = imFlag_; +} + +void Option::RemoveFlag(const int imFlag_) { + imFlags &= ~imFlag_; +} + +Option::Option(uint8_t var_, std::string name_, std::vector options_, OptionCategory category_, + std::string cvarName_, std::string description_, WidgetType widgetType_, uint8_t defaultOption_, + bool defaultHidden_, int imFlags_) : var(var_), name(std::move(name_)), options(std::move(options_)), category(category_), - defaultOption(defaultOption_), defaultHidden(defaultHidden_) { + cvarName(std::move(cvarName_)), description(std::move(description_)), widgetType(widgetType_), + defaultOption(defaultOption_), defaultHidden(defaultHidden_), imFlags(imFlags_) { selectedOption = defaultOption; hidden = defaultHidden; SetVariable(); } Option::Option(bool var_, std::string name_, std::vector options_, const OptionCategory category_, - const uint8_t defaultOption_, const bool defaultHidden_) + std::string cvarName_, std::string description_, WidgetType widgetType_, const uint8_t defaultOption_, + const bool defaultHidden_, int imFlags_) : var(var_), name(std::move(name_)), options(std::move(options_)), category(category_), - defaultOption(defaultOption_), defaultHidden(defaultHidden_) { + cvarName(std::move(cvarName_)), description(std::move(description_)), widgetType(widgetType_), + defaultOption(defaultOption_), defaultHidden(defaultHidden_), imFlags(imFlags_) { selectedOption = defaultOption; hidden = defaultHidden; SetVariable(); } +bool Option::RenderCheckbox() const { + bool changed = false; + if (disabled) { + UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); + } + bool val = static_cast(CVarGetInteger(cvarName.c_str(), defaultOption)); + if (CustomCheckbox(name.c_str(), &val, disabled, disabledGraphic)) { + CVarSetInteger(cvarName.c_str(), val); + changed = true; + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + } + if (!description.empty()) { + UIWidgets::InsertHelpHoverText(description.c_str()); + } + if (disabled) { + UIWidgets::ReEnableComponent(disabledText.c_str()); + } + return changed; +} + +bool Option::RenderCombobox() const { + bool changed = false; + if (disabled) { + UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); + } + ImGui::Text("%s", name.c_str()); + uint8_t selected = CVarGetInteger(cvarName.c_str(), defaultOption); + if (selected >= options.size()) { + selected--; + CVarSetInteger(cvarName.c_str(), selected); + changed = true; + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + } + if (!description.empty()) { + UIWidgets::InsertHelpHoverText(description.c_str()); + } + ImGui::BeginGroup(); + const std::string comboName = std::string("##") + std::string(cvarName); + if (ImGui::BeginCombo(comboName.c_str(), options[selected].c_str())) { + for (size_t i = 0; i < options.size(); i++) { + if (!options[i].empty()) { + if (ImGui::Selectable(options[i].c_str(), i == selected)) { + CVarSetInteger(cvarName.c_str(), static_cast(i)); + changed = true; + selected = i; + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + } + } + } + ImGui::EndCombo(); + } + ImGui::EndGroup(); + if (disabled) { + UIWidgets::ReEnableComponent(disabledText.c_str()); + } + return changed; +} + +bool Option::RenderSlider() const { + bool changed = false; + int val = CVarGetInteger(cvarName.c_str(), defaultOption); + if (val >= options.size()) { + val--; + changed = true; + } + if (disabled) { + UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); + } + const std::string formatName = name + ": %s"; + ImGui::Text(formatName.c_str(), options[val].c_str()); + if (!description.empty()) { + UIWidgets::InsertHelpHoverText(description.c_str()); + } + UIWidgets::Spacer(0); + ImGui::BeginGroup(); + const std::string MinusBTNName = " - ##" + cvarName; + if (ImGui::Button(MinusBTNName.c_str())) { + val--; + changed = true; + } + ImGui::SameLine(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); + ImGui::PushItemWidth(std::min(ImGui::GetContentRegionAvail().x - 30.0f, 260.0f)); + const std::string id = "##Slider" + cvarName; + if (ImGui::SliderInt(id.c_str(), &val, 0, static_cast(options.size()) - 1, "", ImGuiSliderFlags_AlwaysClamp)) { + changed = true; + } + ImGui::PopItemWidth(); + const std::string PlusBTNName = " + ##" + cvarName; + ImGui::SameLine(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); + if (ImGui::Button(PlusBTNName.c_str())) { + val++; + changed = true; + } + ImGui::EndGroup(); + if (disabled) { + UIWidgets::ReEnableComponent(disabledText.c_str()); + } + if (val < 0) { + val = 0; + changed = true; + } + if (val > options.size() - 1) { + val = static_cast(options.size() - 1); + changed = true; + } + if (changed) { + CVarSetInteger(cvarName.c_str(), val); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + } + return changed; +} + OptionGroup::OptionGroup(std::string name, std::vector options, const OptionGroupType groupType, - const bool printInSpoiler, const OptionGroupType containsType) + const bool printInSpoiler, const WidgetContainerType containerType, std::string description) : mName(std::move(name)), mOptions(std::move(options)), mGroupType(groupType), mPrintInSpoiler(printInSpoiler), - mContainsType(containsType) { + mContainerType(containerType), mDescription(std::move(description)) { } OptionGroup::OptionGroup(std::string name, std::vector subGroups, const OptionGroupType groupType, - const bool printInSpoiler, const OptionGroupType containsType) + const bool printInSpoiler, const WidgetContainerType containerType, std::string description) : mName(std::move(name)), mSubGroups(std::move(subGroups)), mGroupType(groupType), mPrintInSpoiler(printInSpoiler), - mContainsType(containsType) { + mContainsType(OptionGroupType::SUBGROUP), mContainerType(containerType), mDescription(std::move(description)) { } -OptionGroup OptionGroup::SubGroup(std::string name, std::vector options, const bool printInSpoiler) { - return {std::move(name), std::move(options), OptionGroupType::SUBGROUP, printInSpoiler}; +OptionGroup OptionGroup::SubGroup(std::string name, std::vector options, const bool printInSpoiler, + const WidgetContainerType containerType, std::string description) { + return {std::move(name), std::move(options), OptionGroupType::SUBGROUP, printInSpoiler, containerType, + std::move(description)}; } -OptionGroup OptionGroup::SubGroup(std::string name, std::vector subGroups, const bool printInSpoiler) { - return {std::move(name), std::move(subGroups), OptionGroupType::SUBGROUP, printInSpoiler, OptionGroupType::SUBGROUP}; +OptionGroup OptionGroup::SubGroup(std::string name, std::vector subGroups, const bool printInSpoiler, + const WidgetContainerType containerType, std::string description) { + return {std::move(name), std::move(subGroups), OptionGroupType::SUBGROUP, printInSpoiler, containerType, + std::move(description)}; } const std::string& OptionGroup::GetName() const { @@ -139,4 +346,84 @@ OptionGroupType OptionGroup::GetGroupType() const { OptionGroupType OptionGroup::GetContainsType() const { return mContainsType; } + +WidgetContainerType OptionGroup::GetContainerType() const { + return mContainerType; +} + +const std::string& OptionGroup::GetDescription() const { + return mDescription; +} + +bool OptionGroup::RenderImGui() const { // NOLINT(*-no-recursion) + ImGuiWindow* window = ImGui::GetCurrentWindow(); + bool changed = false; + if (mContainerType == WidgetContainerType::TABLE) { + if (ImGui::BeginTable(mName.c_str(), static_cast(mSubGroups.size()), ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV)) { + for (const auto column : mSubGroups) { + if (column->GetContainerType() == WidgetContainerType::COLUMN) { + ImGui::TableSetupColumn(column->GetName().c_str(), ImGuiTableColumnFlags_WidthStretch, 200.0f); + } + } + ImGui::PushItemFlag(ImGuiItemFlags_NoNav, true); + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + for (int i = 0; i < mSubGroups.size(); i++) { + ImGui::TableSetColumnIndex(i); + ImGui::TableHeader(mSubGroups[i]->GetName().c_str()); + if (!mSubGroups[i]->GetDescription().empty()) { + UIWidgets::SetLastItemHoverText(mSubGroups[i]->GetDescription().c_str()); + } + } + ImGui::PopItemFlag(); + ImGui::TableNextRow(); + } + } + if (mContainerType == WidgetContainerType::SECTION && !mName.empty()) { + UIWidgets::PaddedSeparator(); + ImGui::Text("%s", mName.c_str()); + if (!mDescription.empty()) { + UIWidgets::InsertHelpHoverText(mDescription.c_str()); + } + UIWidgets::PaddedSeparator(); + } + if (mContainerType == WidgetContainerType::COLUMN) { + ImGui::TableNextColumn(); + window->DC.CurrLineTextBaseOffset = 0.0f; + ImGui::BeginChild(mName.c_str(), ImVec2(0, -8)); + ImGui::PushItemWidth(-FLT_MIN); + } + if (mContainsType == OptionGroupType::SUBGROUP) { + for (const auto optionGroup : mSubGroups) { + if (optionGroup->RenderImGui()) { + changed = true; + } + } + } else { + for (const auto option : mOptions) { + if (option->IsHidden()) { + continue; + } + if (option->HasFlag(IMFLAG_INDENT)) { + ImGui::Indent(); + } + // If any options changed, changed will end up being true + if (option->RenderImGui()) { + changed = true; + } + if (option->HasFlag(IMFLAG_UNINDENT)) { + ImGui::Unindent(); + } + if (option->HasFlag(IMFLAG_SEPARATOR_BOTTOM)) { + UIWidgets::PaddedSeparator(); + } + } + } + if (mContainerType == WidgetContainerType::COLUMN) { + ImGui::EndChild(); + } + if (mContainerType == WidgetContainerType::TABLE) { + ImGui::EndTable(); + } + return changed; +} } // namespace Rando \ No newline at end of file diff --git a/soh/soh/Enhancements/randomizer/option.h b/soh/soh/Enhancements/randomizer/option.h index aff38c714..8e0fd0713 100644 --- a/soh/soh/Enhancements/randomizer/option.h +++ b/soh/soh/Enhancements/randomizer/option.h @@ -1,5 +1,7 @@ #pragma once +#include "soh/UIWidgets.hpp" + #include #include #include @@ -7,26 +9,136 @@ #include namespace Rando { -enum class OptionCategory { - Setting, - Toggle, +enum ImGuiMenuFlags { + IMFLAG_NONE = 0, + IMFLAG_SEPARATOR_BOTTOM = 1 << 0, /** Adds a padded separator below the widget. */ + IMFLAG_SEPARATOR_TOP = 1 << 1, /** Adds a padded separator above the widget. */ + IMFLAG_INDENT = 1 << 2, /** Indents this widget and all proceeding widgets. */ + IMFLAG_UNINDENT = 1 << 3, /** Unindents this widget and all proceeding widgets. */ }; +/** + * @brief Affects how options are handled when writing a spoiler/patch file + */ +enum class OptionCategory { + Setting, /** An option that typically affects the logic/item pool/etc. of the seed. Typically gets written out to the spoiler file. */ + Toggle, /** An option that typically affects other options rather than affecting the seed directly. i.e. A toggle for randomizing the values of other options. */ +}; + +/** + * @brief Controls how this option is rendered in the menu. + */ +enum class WidgetType { + Checkbox, /** Default for Bools, not compatible if options.size() > 2. */ + Combobox, /** Default for U8s, works with U8s and Bools. */ + Slider, /** Compatible with U8s. If constructed with NumOpts, consider using this. Technically can be used for Bool or non-NumOpts options but it would be a bit weird semantically. */ +}; + +/** + * @brief A class describing the state of a single option/setting, such as its name, + * options, current value, whether or not it is interactable in the menu, or the CVar, + * it is linked to. + */ class Option { public: Option() = default; + + /** + * @brief Constructs a boolean option. This overload of this function typically requires more + * options to be specified rather than left as default. + * + * @param name_ The name of the option. Appears in the spoiler/patch file. + * @param options_ A vector of value names for this Option. This vector should have a size of 2. + * The name corresponding to the selected index for this option will be printed to the spoiler/patch file. + * @param category_ The desired `OptionCategory` for this option. + * @param cvarName_ The name ofthe CVar this option should correspond with. Set as an empty string to not + * link to any Cvar. + * @param description_ A description of what this option affects. Will be rendered in a toolip in ImGui. + * Can be left as an empty string if desired, no tooltip will be rendered. + * @param widgetType_ What type of widget should be rendered. Should probably be `Checkbox` but technically + * `Combobox` or `Slider` would render and function correctly. + * @param defaultOption_ The default index that should be selected. + * @param defaultHidden_ Whether or not to display the option (can be changed at runtime later). + * @param imFlags_ (see ImGuiMenuFlags type) flags that can modify how this option is rendered. + * @return Option + */ static Option Bool(std::string name_, std::vector options_ = { "Off", "On" }, - OptionCategory category_ = OptionCategory::Setting, uint8_t defaultOption_ = 0, - bool defaultHidden_ = false); + OptionCategory category_ = OptionCategory::Setting, std::string cvarName_ = "", + std::string description_ = "", WidgetType widgetType_ = WidgetType::Checkbox, + uint8_t defaultOption_ = 0, bool defaultHidden_ = false, int imFlags_ = IMFLAG_SEPARATOR_BOTTOM); + + /** + * @brief Constructs a boolean option. This constructor was added later for convenience so that a cvarName + * could be specified without needing to fill in options that were previously left at default in + * existing calls to the other overload of this function. The options vector will be { "Off", "On" } + * when using this overload. If you want your option to have different value names, use the other overload. + * + * @param name_ The name of the option. Appears in the spoiler/patch file. + * @param cvarName_ The name of the CVar this option should correspond with. Set as an empty string to not + * link to any CVar. + * @param description_ A description of what this option affects. Will be rendered in a tooltip in ImGui. + * Can be left as an empty string if desired, no tooltip will be rendered. + * @param imFlags_ (see ImGuiMenuFlags type) flags that can modify how this option is rendered. + * @param widgetType_ What type of widget should be rendered. Should probably be `Checkbox` but technically + * `Combobox` or `Slider` would render and function correctly. + * @param defaultOption_ The defaulted selected index for this Option. + * @return Option + */ + static Option Bool(std::string name_, std::string cvarName_, std::string description_ = "", + int imFlags_ = IMFLAG_SEPARATOR_BOTTOM, WidgetType widgetType_ = WidgetType::Checkbox, + bool defaultOption_ = false); + + /** + * @brief Constructs a U8 Option. + * + * @param name_ The name of this Option. Appears in the spoiler/patch file. + * @param options_ A vector of value names for this Option. The name corresponding to the selected + * index for this option will be printed to the spoiler/patch file. + * @param category_ The desired `OptionCategory` for this option. + * @param cvarName_ The name ofthe CVar this option should correspond with. Set as an empty string to not + * link to any Cvar. + * @param description_ A description of what this option affects. Will be rendered in a toolip in ImGui. + * Can be left as an empty string if desired, no tooltip will be rendered. + * @param widgetType_ What type of widget should be rendered. Defaults to `Combobox`, but if you use NumOpts + * to make the `options_` vector you should probably set this to `Slider`. `Slider` will technically work for + * any value of `options_` but may be odd/unclear semantically speaking. + * This should not be set for `Checkbox` if options_ has more than 2 values. + * @param defaultOption_ The default index that should be selected. + * @param defaultHidden_ Whether or not to display the option (can be changed at runtime later). + * @param imFlags_ (see ImGuiMenuFlags type) flags that can modify how this option is rendered. + * @return Option + */ static Option U8(std::string name_, std::vector options_, - OptionCategory category_ = OptionCategory::Setting, uint8_t defaultOption_ = 0, - bool defaultHidden_ = false); + OptionCategory category_ = OptionCategory::Setting, std::string cvarName_ = "", + std::string description_ = "", WidgetType widgetType_ = WidgetType::Combobox, + uint8_t defaultOption_ = 0, bool defaultHidden_ = false, int imFlags_ = IMFLAG_SEPARATOR_BOTTOM); + + /** + * @brief A convenience function for constructing the Option for a trick. + * + * @param name_ The name of the trick. Appears in the spoiler/patch file. + * @return Option + */ static Option LogicTrick(std::string name_); + /** + * @brief Gets the selected index or boolean value of the Option. + * + * @tparam T uint8_t or bool, depending on how the option was constructed. + * @return T + */ template T Value() const { return std::get(var); } + /** + * @brief Determines if the value/selected index of this Option matches the provided value. + * + * @tparam T uint8_t, bool, or an enum (which will be cast to uint8_t). + * @param other The value to compare. + * @return true + * @return false + */ template bool Is(T other) const { static_assert(std::is_integral_v || std::is_enum_v, "T must be an integral type or an enum."); if constexpr ((std::is_integral_v && !std::is_same_v) || std::is_enum_v) { @@ -36,29 +148,170 @@ class Option { } } + /** + * @brief Determines if the value/selected index of this Option does not match the provided value. + * + * @tparam T uint8_t, book, or an enum (which will be cast to uint8_t). + * @param other The value to compare. + * @return true + * @return false + */ template bool IsNot(T other) const { return !Is(other); } + /** + * @brief Allows the option to be used as a boolean value directly. + * + * @return true + * @return false + */ explicit operator bool() const; + + /** + * @brief Get the size of the options array. + * + * @return size_t + */ size_t GetOptionCount() const; + + /** + * @brief Get the name of the Option. + * + * @return const std::string& + */ const std::string& GetName() const; + + /** + * @brief Get the value name corresponding to the selected index. + * + * @return const std::string& + */ const std::string& GetSelectedOptionText() const; + + /** + * @brief Get the CVar name for this Option. + * + * @return const std::string& + */ + const std::string& GetCVarName() const; + + /** + * @brief Get the selected index for this Option. + * + * @return uint8_t + */ uint8_t GetSelectedOptionIndex() const; + + /** + * @brief Sets the variable to the currently selected index for this Option. + */ void SetVariable(); + + /** + * @brief Sets the CVar corresponding to the property `cvarName` equal to the value + * of the property `selectedValue`. + */ + void SetCVar() const; + + /** + * @brief Sets the value of property `selectedValue` equal to the CVar corresponding + * to the property `cvarName`. + */ + void SetFromCVar(); + + /** + * @brief Set the delayedOption to the currently selected index so it can be restored later. + */ void SetDelayedOption(); + + /** + * @brief Restores the delayedOption back to the selected index. + */ void RestoreDelayedOption(); + + /** + * @brief Set the selected index for this Option. Also calls `SetVariable()`. + * + * @param idx the index to set as the selected index. + */ void SetSelectedIndex(size_t idx); + + /** + * @brief Hides this Option in the menu. (Not currently being used afaik, we prefer to + * display all the Options and visually disable the ones that aren't applicable.) + */ void Hide(); + + /** + * @brief Shows this Option in the menu if it was previously hidden. + */ void Unhide(); + + /** + * @brief Whether or not this Option is currently hidden. + * + * @return true + * @return false + */ + bool IsHidden() const; + /** + * @brief Replaces the `options` vector for this Option with a new one. + * If the new vector is smaller than the old one and the current selected + * index is out of range, this function changes the selected index to the + * last index of the new vector. + * + * @param opts The new vector of options. + */ + void ChangeOptions(std::vector opts); + + /** + * @brief Enables interaction with this option. + * + * "Enable" in this context refers to the ability to change the option in the + * settings menu. The actual value of the option is not decided by whether or not + * the option is "Enabled". + */ + void Enable(); + + /** + * @brief Disables interaction with this option. + * + * "Disable" in this context refers to the ability to change the option in the + * settings menu. The actual value of the option is not decided by whether or not + * the option is "Disabled". + * + * @param text The tooltip text explaining why the option is disabled. + * @param graphic What graphic to display in a disabled checkbox. Defaults to an + * "X" symbol. + */ + void Disable(std::string text, UIWidgets::CheckboxGraphics graphic = UIWidgets::CheckboxGraphics::Cross); bool IsCategory(OptionCategory category) const; + /** + * @brief Automatically renders a widget for this option in ImGui, based on the various + * properties of this Option. Typically, Bool options are rendered as Checkboxes and + * U8 options are rendered as Comboboxes, but this can be overriden during construction with + * the `widgetType` property. + */ + bool RenderImGui() const; + + bool HasFlag(int imFlag_) const; + void AddFlag(int imFlag_); + void SetFlag(int imFlag_); + void RemoveFlag(int imFlag_); + private: Option(uint8_t var_, std::string name_, std::vector options_, OptionCategory category_, - uint8_t defaultOption_, bool defaultHidden_); + std::string cvarName_, std::string description_, WidgetType widgetType_, uint8_t defaultOption_, + bool defaultHidden_, int imFlags_); Option(bool var_, std::string name_, std::vector options_, OptionCategory category_, - uint8_t defaultOption_, bool defaultHidden_); + std::string cvarName_, std::string description_, WidgetType widgetType_, uint8_t defaultOption_, + bool defaultHidden_, int imFlags_); + bool RenderCheckbox() const; + bool RenderCombobox() const; + bool RenderSlider() const; std::variant var; std::string name; std::vector options; @@ -66,8 +319,15 @@ class Option { uint8_t delayedOption = 0; bool hidden = false; OptionCategory category = OptionCategory::Setting; + std::string cvarName; + std::string description; + WidgetType widgetType = WidgetType::Checkbox; uint8_t defaultOption = false; bool defaultHidden = false; + int imFlags = IMFLAG_NONE; + bool disabled = false; + UIWidgets::CheckboxGraphics disabledGraphic = UIWidgets::CheckboxGraphics::Cross; + std::string disabledText; }; enum class OptionGroupType { @@ -75,20 +335,134 @@ enum class OptionGroupType { SUBGROUP, }; +enum class WidgetContainerType { + BASIC, /** Barebones container, just lists the options within. */ + SECTION, /** Similar to Barebones, but has a header with the section name. */ + COLUMN, /** Signifies the container should be the start of new column within a table. */ + TABLE, /** Signifies the container is a table (should contain other subgroups with type column)*/ + TABBED, /** Signifies this container's contents should be contained within a tabbed interface. */ +}; + class OptionGroup { public: OptionGroup() = default; - OptionGroup(std::string name, std::vector options, OptionGroupType groupType = OptionGroupType::DEFAULT, bool printInSpoiler = true, OptionGroupType containsType = OptionGroupType::DEFAULT); - OptionGroup(std::string name, std::vector subGroups, OptionGroupType groupType = OptionGroupType::DEFAULT, bool printInSpoiler = true, OptionGroupType containsType = OptionGroupType::SUBGROUP); - static OptionGroup SubGroup(std::string name, std::vector options, bool printInSpoiler = true); - static OptionGroup SubGroup(std::string name, std::vector subGroups, bool printInSpoiler = true); + + /** + * @brief Construct a new Option Group containing a list of `Option` pointers. + * + * @param name The name of this Option Group. Appears in the spoiler/patch file in front each option it contains. + * @param options A vector of Option pointers + * @param groupType `DEFAULT` if this group is not contained within any other groups, `SUBGROUP` if it is a + * subgroup of another group. + * @param printInSpoiler Whether or not to print the contents of this group to the spoiler/patch file. + * @param containerType Specifies the type of container this widget should render as in ImGui. + * @param description A description that can appear in a tooltip in ImGui. + */ + OptionGroup(std::string name, std::vector options, OptionGroupType groupType = OptionGroupType::DEFAULT, + bool printInSpoiler = true, WidgetContainerType containerType = WidgetContainerType::BASIC, + std::string description = ""); + + /** + * @brief Construct a new Option Group containing a list of `OptionGroup` pointers. + * + * @param name The name of this option group. Appears in the spoiler/patch file. + * @param subGroups A vector of OptionGroup pointers that will be subgroups of this group. + * @param groupType `DEFAULT` if this group is not contained within any other groups, `SUBGROUP` if it is a + * subgroup of another group. + * @param printInSpoiler Whether or not to print the contents of this group to spoiler/patch file. + * @param containerType Specifies the type of container this widget should render as in ImGui. + * @param description A description that can appear in a tooltip in ImGui. + */ + OptionGroup(std::string name, std::vector subGroups, OptionGroupType groupType = OptionGroupType::DEFAULT, + bool printInSpoiler = true, WidgetContainerType containerType = WidgetContainerType::BASIC, + std::string description = ""); + + /** + * @brief Convenience function for constructing an OptionGroup of groupType `SUBGROUP` with + * containsType of `DEFAULT` (contains `Option`s). + * + * @param name The name of this option group. Appears in the spoiler/patch file. + * @param options A vector of Option pointers. + * @param printInSpoiler Whether or not to print the options of this group to the spoiler/patch file. + * @param containerType Specifies the type of container this widget should render as in ImGui. + * @param description A description that can appear in a tooltip in ImGui. + * @return OptionGroup + */ + static OptionGroup SubGroup(std::string name, std::vector options, bool printInSpoiler = true, + WidgetContainerType containerType = WidgetContainerType::BASIC, + std::string description = ""); + + /** + * @brief Convenience function for constructing an OptionGroup of groupType `SUBGROUP` with + * containsType of `SUBGROUP` (contains other `OptionGroup`s) + * + * @param name The name of this option group. Appears in the spoiler/patch file. + * @param subGroups A vector of OptionGroup pointers. + * @param printInSpoiler Whether or not to print the options of this group to the spoiler/patch file. + * @param containerType Specifies the type of container this widget should render as in ImGui. + * @param description A description that can appear in a tooltip in ImGui. + * @return OptionGroup + */ + static OptionGroup SubGroup(std::string name, std::vector subGroups, bool printInSpoiler = true, + WidgetContainerType containerType = WidgetContainerType::BASIC, + std::string description = ""); + + /** + * @brief Get the name of the OptionGroup. + * + * @return const std::string& + */ const std::string& GetName() const; + + /** + * @brief Get the list of `Option`s contained within this `OptionGroup`. + * + * @return const std::vector& + */ const std::vector& GetOptions() const; + + /** + * @brief Get the list of `OptionGroup`s contained within this `OptionGroup`. + * + * @return const std::vector& + */ const std::vector& GetSubGroups() const; + + /** + * @brief Returns whether or not this `OptionGroup`'s contents should be printed to the + * spoiler/patch file. + * + * @return true + * @return false + */ bool PrintInSpoiler() const; + + /** + * @brief Get the Group Type of this `OptionGroup`. `DEFAULT` means this group is not contained + * within any other groups, while `SUBGROUP` means that it is contained within at least one other. + * `OptionGroup`. + * + * @return OptionGroupType + */ OptionGroupType GetGroupType() const; + + /** + * @brief Get the type of values contained in this `OptionGroup`. `DEFAULT` means this group contains + * `Options`, and `SUBGROUP` means this group contains other `OptionGroup`s. + * + * @return OptionGroupType + */ OptionGroupType GetContainsType() const; + WidgetContainerType GetContainerType() const; + + const std::string& GetDescription() const; + + /** + * @brief Renders all of the options contained within this `OptionGroup` in the ImGui menu. + */ + bool RenderImGui() const; + private: std::string mName; std::vector mOptions; @@ -96,5 +470,7 @@ class OptionGroup { OptionGroupType mGroupType = OptionGroupType::DEFAULT; bool mPrintInSpoiler = true; OptionGroupType mContainsType = OptionGroupType::DEFAULT; + WidgetContainerType mContainerType = WidgetContainerType::BASIC; + std::string mDescription; }; } // namespace Rando \ No newline at end of file diff --git a/soh/soh/Enhancements/randomizer/option_descriptions.cpp b/soh/soh/Enhancements/randomizer/option_descriptions.cpp new file mode 100644 index 000000000..83f8b12a0 --- /dev/null +++ b/soh/soh/Enhancements/randomizer/option_descriptions.cpp @@ -0,0 +1,523 @@ +#include "settings.h" + +namespace Rando { +void Settings::CreateOptionDescriptions() { + mOptionDescriptions[RSK_FOREST] = "Closed - Kokiri sword & shield are required to access " + "the Deku Tree, and completing the Deku Tree is required to " + "access the Hyrule Field exit.\n" + "\n" + "Closed Deku - Kokiri boy no longer blocks the path to Hyrule " + "Field but Mido still requires the Kokiri sword and Deku shield " + "to access the tree.\n" + "\n" + "Open - Mido no longer blocks the path to the Deku Tree. Kokiri " + "boy no longer blocks the path out of the forest."; + mOptionDescriptions[RSK_KAK_GATE] = "Closed - The gate will remain closed until Zelda's letter " + "is shown to the guard.\n" + "\n" + "Open - The gate is always open. The happy mask shop " + "will open immediately after obtaining Zelda's letter."; + mOptionDescriptions[RSK_DOOR_OF_TIME] = "Closed - The Ocarina of Time, the Song of Time and all " + "three spiritual stones are required to open the Door of Time.\n" + "\n" + "Song only - Play the Song of Time in front of the Door of " + "Time to open it.\n" + "\n" + "Open - The Door of Time is permanently open with no requirements."; + mOptionDescriptions[RSK_ZORAS_FOUNTAIN] = "Closed - King Zora obstructs the way to Zora's Fountain. " + "Ruto's letter must be shown as child Link in order to move " + "him in both time periods.\n" + "\n" + "Closed as child - Ruto's Letter is only required to move King Zora " + "as child Link. Zora's Fountain starts open as adult.\n" + "\n" + "Open - King Zora has already mweeped out of the way in both " + "time periods. Ruto's Letter is removed from the item pool."; + mOptionDescriptions[RSK_STARTING_AGE] = + "Choose which age Link will start as.\n\n" + "Starting as adult means you start with the Master Sword in your inventory.\n" + "The child option is forcefully set if it would conflict with other options."; + mOptionDescriptions[RSK_GERUDO_FORTRESS] = "Sets the amount of carpenters required to repair the bridge " + "in Gerudo Valley.\n" + "\n" + "Normal - All 4 carpenters are required to be saved.\n" + "\n" + "Fast - Only the bottom left carpenter requires rescuing.\n" + "\n" + "Open - The bridge is repaired from the start.\n" + "\n" + "Only \"Normal\" is compatible with Gerudo Fortress Key Rings."; + mOptionDescriptions[RSK_RAINBOW_BRIDGE] = + "Alters the requirements to open the bridge to Ganon's Castle.\n" + "\n" + "Vanilla - Obtain the Shadow Medallion, Spirit Medallion and Light Arrows.\n" + "\n" + "Always open - No requirements.\n" + "\n" + "Stones - Obtain the specified amount of spiritual stones.\n" + "\n" + "Medallions - Obtain the specified amount of medallions.\n" + "\n" + "Dungeon rewards - Obtain the specified total sum of spiritual " + "stones or medallions.\n" + "\n" + "Dungeons - Complete the specified amount of dungeons. Dungeons " + "are considered complete after stepping in to the blue warp after " + "the boss.\n" + "\n" + "Tokens - Obtain the specified amount of Skulltula tokens.\n" + "\n" + "Greg - Find Greg the Green Rupee."; + mOptionDescriptions[RSK_BRIDGE_OPTIONS] = + "Standard Rewards - Greg does not change logic, Greg does not help open the bridge, max " + "number of rewards on slider does not change.\n" + "\n" + "Greg as Reward - Greg does change logic (can be part of expected path for opening " + "bridge), Greg helps open bridge, max number of rewards on slider increases by 1 to " + "account for Greg. \n" + "\n" + "Greg as Wildcard - Greg does not change logic, Greg helps open the bridge, max number of " + "rewards on slider does not change."; + mOptionDescriptions[RSK_GANONS_TRIALS] = + "Sets the number of Ganon's Trials required to dispel the barrier.\n" + "\n" + "Skip - No Trials are required and the barrier is already dispelled.\n" + "\n" + "Set Number - Select a number of trials that will be required from the" + "slider below. Which specific trials you need to complete will be random.\n" + "\n" + "Random Number - A Random number and set of trials will be required."; + mOptionDescriptions[RSK_TRIAL_COUNT] = "Set the number of trials required to enter Ganon's Tower."; + mOptionDescriptions[RSK_MQ_DUNGEON_RANDOM] = + "Sets the number of Master Quest Dungeons that are shuffled into the pool.\n" + "\n" + "None - All Dungeons will be their Vanilla versions.\n" + "\n" + "Set Number - Select a number of dungeons that will be their Master Quest versions " + "using the slider below. Which dungeons are set to be the Master Quest variety will be random.\n" + "\n" + "Random Number - A Random number and set of dungeons will be their Master Quest varieties.\n" + "\n" + "Selection Only - Specify which dungeons are Vanilla or Master Quest."; + mOptionDescriptions[RSK_MQ_DUNGEON_SET] = + "Choose specific Dungeons to be Master Quest or Vanilla.\n" + "\n" + "If Master Quest Dungeons is set to Set Number or Random, the dungeons chosen " + "to be Master Quest here will count towards that total. Any Dungeons set to Vanilla " + "here will be guaranteed to be Vanilla. If Set Number is higher than the amount of dungeons " + "set to either MQ or Random here, you will have fewer MQ Dungeons than the number you " + "set."; + mOptionDescriptions[RSK_TRIFORCE_HUNT] = + "Pieces of the Triforce of Courage have been scattered across the world. Find them all to finish the game!\n\n" + "When the required amount of pieces have been found, the game is saved and Ganon's Boss key is given " + "to you when you load back into the game if you desire to beat Ganon afterwards.\n\n" + "Keep in mind Ganon might not be logically beatable when \"All Locations Reachable\" is turned off."; + mOptionDescriptions[RSK_TRIFORCE_HUNT_PIECES_TOTAL] = + "The amount of Triforce pieces that will be placed in the world. " + "Keep in mind seed generation can fail if more pieces are placed than there are junk items in the item pool."; + mOptionDescriptions[RSK_TRIFORCE_HUNT_PIECES_REQUIRED] = "The amount of Triforce pieces required to win the game."; + mOptionDescriptions[RSK_SHUFFLE_DUNGEON_ENTRANCES] = + "Shuffle the pool of dungeon entrances, including Bottom of the Well, Ice Cavern and Gerudo Training Grounds.\n" + "\n" + "Shuffling Ganon's Castle can be enabled separately.\n" + "\n" + "Additionally, the entrances of Deku Tree, Fire Temple, Bottom of the Well and Gerudo Training Ground are " + "opened for both child and adult.\n" + "\n" + "- Deku Tree will be open for adult after Mido has seen child Link with a sword and shield.\n" + "- Bottom of the Well will be open for adult after playing Song of Storms to the Windmill guy as child.\n" + "- Gerudo Training Ground will be open for child after adult has paid to open the gate once."; + mOptionDescriptions[RSK_SHUFFLE_BOSS_ENTRANCES] = + "Shuffle the pool of dungeon boss entrances. This affects the boss rooms of all stone and medallion dungeons.\n" + "\n" + "Age Restricted - Shuffle the entrances of child and adult boss rooms separately.\n" + "\n" + "Full - Shuffle the entrances of all boss rooms together. Child may be expected to defeat Phantom Ganon and/or " + "Bongo Bongo."; + mOptionDescriptions[RSK_SHUFFLE_OVERWORLD_ENTRANCES] = + "Shuffle the pool of Overworld entrances, which corresponds to almost all loading zones between overworld " + "areas.\n" + "\n" + "Some entrances are unshuffled to avoid issues:\n" + "- Hyrule Castle Courtyard and Garden entrance\n" + "- Both Market Back Alley entrances\n" + "- Gerudo Valley to Lake Hylia (unless entrances are decoupled)"; + mOptionDescriptions[RSK_SHUFFLE_INTERIOR_ENTRANCES] = + "Shuffle the pool of interior entrances which contains most Houses and all Great Fairies.\n" + "\n" + "All - An extended version of 'Simple' with some extra places:\n" + "- Windmill\n" + "- Link's House\n" + "- Temple of Time\n" + "- Kakariko Potion Shop"; + mOptionDescriptions[RSK_SHUFFLE_GROTTO_ENTRANCES] = + "Shuffle the pool of grotto entrances, including all graves, small Fairy fountains and the Deku Theatre."; + mOptionDescriptions[RSK_SHUFFLE_OWL_DROPS] = "Randomize where Kaepora Gaebora (the Owl) drops you at when you talk " + "to him at Lake Hylia or at the top of Death Mountain Trail."; + mOptionDescriptions[RSK_SHUFFLE_WARP_SONGS] = "Randomize where each of the 6 warp songs leads to."; + mOptionDescriptions[RSK_SHUFFLE_OVERWORLD_SPAWNS] = + "Randomize where you start as Child or Adult when loading a save in the Overworld. This " + "means you may not necessarily spawn inside Link's House or Temple of Time.\n" + "\n" + "This stays consistent after saving and loading the game again.\n" + "\n" + "Keep in mind you may need to temporarily disable the \"Remember Save Location\" time saver to " + "be able use the spawn positions, especially if they are the only logical way to get to certain areas."; + mOptionDescriptions[RSK_DECOUPLED_ENTRANCES] = + "Decouple entrances when shuffling them. This means you are no longer guaranteed " + "to end up back where you came from when you go back through an entrance.\n" + "\n" + "This also adds the one-way entrance from Gerudo Valley to Lake Hylia in the pool of " + "overworld entrances when they are shuffled."; + mOptionDescriptions[RSK_MIXED_ENTRANCE_POOLS] = + "Shuffle entrances into a mixed pool instead of separate ones.\n" + "\n" + "For example, enabling the settings to shuffle grotto, dungeon, and overworld entrances and " + "selecting grotto and dungeon entrances here will allow a dungeon to be inside a grotto or " + "vice versa, while overworld entrances are shuffled in their own separate pool and indoors stay vanilla."; + mOptionDescriptions[RSK_MIX_DUNGEON_ENTRANCES] = "Dungeon entrances will be part of the mixed pool"; + mOptionDescriptions[RSK_MIX_OVERWORLD_ENTRANCES] = "Overworld entrances will be part of the mixed pool"; + mOptionDescriptions[RSK_MIX_INTERIOR_ENTRANCES] = "Interior entrances will be part of the mixed pool"; + mOptionDescriptions[RSK_MIX_GROTTO_ENTRANCES] = "Grotto entrances will be part of the mixed pool"; + mOptionDescriptions[RSK_SHUFFLE_SONGS] = + "Song locations - Songs will only appear at locations that normally teach songs.\n" + "\n" + "Dungeon rewards - Songs appear after beating a major dungeon boss.\n" + "The 4 remaining songs are located at:\n" + " - Zelda's lullaby location\n" + " - Ice Cavern's Serenade of Water location\n" + " - Bottom of the Well Lens of Truth location\n" + " - Gerudo Training Ground's Ice Arrows location\n" + "\n" + "Anywhere - Songs can appear at any location."; + mOptionDescriptions[RSK_SHUFFLE_TOKENS] = "Shuffles Golden Skulltula Tokens into the item pool. This means " + "Golden Skulltulas can contain other items as well.\n" + "\n" + "Off - GS tokens will not be shuffled.\n" + "\n" + "Dungeons - Only shuffle GS tokens that are within dungeons.\n" + "\n" + "Overworld - Only shuffle GS tokens that are outside of dungeons.\n" + "\n" + "All Tokens - Shuffle all 100 GS tokens."; + mOptionDescriptions[RSK_SKULLS_SUNS_SONG] = "All Golden Skulltulas that require nighttime to appear will only be " + "expected to be collected after getting Sun's Song."; + mOptionDescriptions[RSK_SHUFFLE_KOKIRI_SWORD] = + "Shuffles the Kokiri Sword into the item pool.\n" + "\n" + "This will require the use of sticks until the Kokiri Sword is found."; + mOptionDescriptions[RSK_SHUFFLE_MASTER_SWORD] = + "Shuffles the Master Sword into the item pool.\n" + "\n" + "Adult Link will start with a second free item instead of the Master Sword.\n" + "If you haven't found the Master Sword before facing Ganon, you won't receive it during the fight."; + mOptionDescriptions[RSK_SHUFFLE_OCARINA] = + "Enabling this shuffles the Fairy Ocarina and the Ocarina of Time into the item pool.\n" + "\n" + "This will require finding an Ocarina before being able to play songs."; + mOptionDescriptions[RSK_SHUFFLE_WEIRD_EGG] = "Shuffles the Weird Egg from Malon in to the item pool. Enabling " + "\"Skip Child Zelda\" disables this feature.\n" + "\n" + "The Weird Egg is required to unlock several events:\n" + " - Zelda's Lullaby from Impa\n" + " - Saria's song in Sacred Forest Meadow\n" + " - Epona's song and chicken minigame at Lon Lon Ranch\n" + " - Zelda's letter for Kakariko gate (if set to closed)\n" + " - Happy Mask Shop sidequest\n"; + mOptionDescriptions[RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD] = + "Shuffles the Gerudo Membership Card into the item pool.\n" + "\n" + "The Gerudo Card is required to enter the Gerudo Training Grounds, opening " + "the gate to Haunted Wasteland and the Horseback Archery minigame."; + mOptionDescriptions[RSK_SHOPSANITY] = "Off - All shop items will be the same as vanilla.\n" + "\n" + "0 Items - Vanilla shop items will be shuffled among different shops.\n" + "\n" + "1-4 Items - Vanilla shop items will be shuffled among different shops, and " + "each shop will contain 1-4 non-vanilla shop items.\n" + "\n" + "Random - Vanilla shop items will be shuffled among different shops, and " + "each shop will contain a random number(1-4) of non-vanilla shop items.\n"; + mOptionDescriptions[RSK_SHOPSANITY_PRICES] = + "Balanced - The default randomization. Shop prices for shopsanity items will range between 0 to 300 rupees, " + "with a bias towards values slightly below the middle of the range, in multiples of 5.\n " + "\n" + "X Wallet - Randomized between 5 and the wallet's max size, in multiples of 5"; + mOptionDescriptions[RSK_SHOPSANITY_PRICES_AFFORDABLE] = + "Affordable prices per tier: starter = 10, adult = 105, giant = 205, tycoon = 505\n\n" + "Use this to enable wallet tier locking, but make shop items not as expensive as they could be."; + mOptionDescriptions[RSK_SHUFFLE_SCRUBS] = + "Off - Scrubs will not be shuffled. The 3 Scrubs that give one-time items in the vanilla game " + "(PoH, Deku Nut capacity, and Deku Stick capacity) will have random items.\n" + "\n" + "Affordable - Scrubs will be shuffled and their item will cost 10 rupees.\n" + "\n" + "Expensive - Scrubs will be shuffled and their item will cost the vanilla price.\n" + "\n" + "Random - Scrubs will be shuffled and their item will cost will be between 0-95 rupees.\n"; + mOptionDescriptions[RSK_SHUFFLE_COWS] = + "Cows give a randomized item from the pool upon performing Epona's Song in front of them."; + mOptionDescriptions[RSK_SHUFFLE_MAGIC_BEANS] = + "Enabling this adds a pack of 10 beans to the item pool and changes the Magic Bean " + "Salesman to sell a random item at a price of 60 rupees."; + mOptionDescriptions[RSK_SHUFFLE_MERCHANTS] = + "Enabling this changes Medigoron, Granny and the Carpet Salesman to sell a random item " + "once at a high price (100 for Granny, 200 for the others).\n" + "A Giant's Knife and a pack of Bombchus will be added to the item pool, and " + "one of the bottles will contain a Blue Potion.\n\n" + "On (no hints) - Salesmen will be included but won't tell you what you'll get.\n" + "On (with hints) - Salesmen will be included and you'll know what you're buying.\n" + "\n" + "Granny's item will only be offered after you have traded in the Odd Mushroom when Shuffle Adult Trade is on. " + "Otherwise when off, you will need to have found the Claim Check to buy her item (simulating the trade quest " + "is complete)."; + mOptionDescriptions[RSK_SHUFFLE_FROG_SONG_RUPEES] = "Shuffles 5 Purple Rupees into to the item pool, and allows\n" + "you to earn items by playing songs at the Frog Choir.\n" + "\n" + "This setting does not effect the item earned from playing\n" + "the Song of Storms and the frog song minigame."; + mOptionDescriptions[RSK_SHUFFLE_ADULT_TRADE] = + "Adds all of the adult trade quest items into the pool, each of which " + "can be traded for a unique reward.\n" + "\n" + "You will be able to choose which of your owned adult trade items is visible " + "in the inventory by selecting the item with A and using the control stick or " + "D-pad.\n" + "\n" + "If disabled, only the Claim Check will be found in the pool."; + mOptionDescriptions[RSK_SHUFFLE_100_GS_REWARD] = + "Shuffle the item the cursed rich man in the House of Skulltula gives when you " + "have collected all 100 Gold Skulltula Tokens.\n" + "\n" + "You can still talk to him multiple times to get Huge Rupees."; + mOptionDescriptions[RSK_SHUFFLE_DUNGEON_REWARDS] = + "Shuffles the location of spiritual stones and medallions.\n" + "\n" + "End of dungeons - Spiritual stones and medallions will be given as rewards " + "for beating major dungeons. Link will always start with one stone or medallion.\n" + "\n" + "Any dungeon - Spiritual stones and medallions can be found inside any dungeon.\n" + "\n" + "Overworld - Spiritual stones and medallions can only be found outside of dungeons.\n" + "\n" + "Anywhere - Spiritual stones and medallions can appear anywhere."; + mOptionDescriptions[RSK_SHUFFLE_MAPANDCOMPASS] = + "Start with - You will start with Maps & Compasses from all dungeons.\n" + "\n" + "Vanilla - Maps & Compasses will appear in their vanilla locations.\n" + "\n" + "Own dungeon - Maps & Compasses can only appear in their respective dungeon.\n" + "\n" + "Any dungeon - Maps & Compasses can only appear inside of any dungon.\n" + "\n" + "Overworld - Maps & Compasses can only appear outside of dungeons.\n" + "\n" + "Anywhere - Maps & Compasses can appear anywhere in the world."; + mOptionDescriptions[RSK_KEYSANITY] = + "Start with - You will start with all Small Keys from all dungeons.\n" + "\n" + "Vanilla - Small Keys will appear in their vanilla locations. " + "You start with 3 keys in Spirit Temple MQ because the vanilla key layout is not beatable in logic.\n" + "\n" + "Own dungeon - Small Keys can only appear in their respective dungeon. " + "If Fire Temple is not a Master Quest dungeon, the door to the Boss Key chest will be unlocked.\n" + "\n" + "Any dungeon - Small Keys can only appear inside of any dungon.\n" + "\n" + "Overworld - Small Keys can only appear outside of dungeons.\n" + "\n" + "Anywhere - Small Keys can appear anywhere in the world."; + mOptionDescriptions[RSK_KEYRINGS] = + "Keyrings will replace all small keys from a particular dungeon with a single keyring that awards all keys for " + "it's associated dungeon\n" + "\n" + "Off - No dungeons will have their keys replaced with keyrings.\n" + "\n" + "Random - A random amount of dungeons(0-8 or 9) will have their keys replaced with keyrings.\n" + "\n" + "Count - A specified amount of randomly selected dungeons will have their keys replaced with keyrings.\n" + "\n" + "Selection - Hand select which dungeons will have their keys replaced with keyrings.\n" + "\n" + "Selecting key ring for dungeons will have no effect if Small Keys are set to Start With or Vanilla.\n" + "\n" + "If Gerudo Fortress Carpenters is set to Normal, and Gerudo Fortress Keys is set to anything " + "other than Vanilla, then the maximum amount of Key Rings that can be selected by Random or " + "Count will be 9. Otherwise, the maximum amount of Key Rings will be 8."; + mOptionDescriptions[RSK_GERUDO_KEYS] = "Vanilla - Thieve's Hideout Keys will appear in their vanilla locations.\n" + "\n" + "Any dungeon - Thieve's Hideout Keys can only appear inside of any dungon.\n" + "\n" + "Overworld - Thieve's Hideout Keys can only appear outside of dungeons.\n" + "\n" + "Anywhere - Thieve's Hideout Keys can appear anywhere in the world."; + mOptionDescriptions[RSK_BOSS_KEYSANITY] = "Start with - You will start with Boss keys from all dungeons.\n" + "\n" + "Vanilla - Boss Keys will appear in their vanilla locations.\n" + "\n" + "Own dungeon - Boss Keys can only appear in their respective dungeon.\n" + "\n" + "Any dungeon - Boss Keys can only appear inside of any dungon.\n" + "\n" + "Overworld - Boss Keys can only appear outside of dungeons.\n" + "\n" + "Anywhere - Boss Keys can appear anywhere in the world."; + mOptionDescriptions[RSK_GANONS_BOSS_KEY] = + "Vanilla - Ganon's Boss Key will appear in the vanilla location.\n" + "\n" + "Own dungeon - Ganon's Boss Key can appear anywhere inside Ganon's Castle.\n" + "\n" + "Start with - Places Ganon's Boss Key in your starting inventory." + "\n" + "Any dungeon - Ganon's Boss Key Key can only appear inside of any dungon.\n" + "\n" + "Overworld - Ganon's Boss Key Key can only appear outside of dungeons.\n" + "\n" + "Anywhere - Ganon's Boss Key Key can appear anywhere in the world.\n" + "\n" + "LACS - These settings put the boss key on the Light Arrow Cutscene location, from Zelda in Temple of Time as " + "adult, with differing requirements:\n" + "- Vanilla: Obtain the Shadow Medallion and Spirit Medallion\n" + "- Stones: Obtain the specified amount of spiritual stones.\n" + "- Medallions: Obtain the specified amount of medallions.\n" + "- Dungeon rewards: Obtain the specified total sum of spiritual stones or medallions.\n" + "- Dungeons: Complete the specified amount of dungeons. Dungeons are considered complete after stepping in to " + "the blue warp after the boss.\n" + "- Tokens: Obtain the specified amount of Skulltula tokens.\n" + "\n" + "100 GS Reward - Ganon's Boss Key will be awarded by the cursed rich man after you collect 100 Gold Skulltula " + "Tokens."; + mOptionDescriptions[RSK_LACS_OPTIONS] = + "Standard Rewards - Greg does not change logic, Greg does not help obtain GBK, max " + "number of rewards on slider does not change.\n" + "\n" + "Greg as Reward - Greg does change logic (can be part of expected path for obtaining " + "GBK), Greg helps obtain GBK, max number of rewards on slider increases by 1 to " + "account for Greg. \n" + "\n" + "Greg as Wildcard - Greg does not change logic, Greg helps obtain GBK, max number of " + "rewards on slider does not change."; + mOptionDescriptions[RSK_CUCCO_COUNT] = "The amount of cuccos needed to claim the reward from Anju the cucco lady"; + mOptionDescriptions[RSK_BIG_POE_COUNT] = "The Poe collector will give a reward for turning in this many Big Poes."; + mOptionDescriptions[RSK_SKIP_CHILD_STEALTH] = + "The crawlspace into Hyrule Castle goes straight to Zelda, skipping the guards."; + mOptionDescriptions[RSK_SKIP_CHILD_ZELDA] = + "Start with Zelda's Letter and the item Impa would normally give you and skip the sequence up " + "until after meeting Zelda. Disables the ability to shuffle Weird Egg."; + mOptionDescriptions[RSK_SKIP_EPONA_RACE] = "Epona can be summoned with Epona's Song without needing to race Ingo."; + mOptionDescriptions[RSK_SKIP_TOWER_ESCAPE] = + "The tower escape sequence between Ganondorf and Ganon will be skipped."; + mOptionDescriptions[RSK_COMPLETE_MASK_QUEST] = + "Once the happy mask shop is opened, all masks will be available to be borrowed."; + mOptionDescriptions[RSK_SKIP_SCARECROWS_SONG] = + "Start with the ability to summon Pierre the scarecrow. Pulling out an ocarina in the usual locations will " + "automatically summon him."; + mOptionDescriptions[RSK_ITEM_POOL] = "Sets how many major items appear in the item pool.\n" + "\n" + "Plentiful - Extra major items are added to the pool.\n" + "\n" + "Balanced - Original item pool.\n" + "\n" + "Scarce - Some excess items are removed, including health upgrades.\n" + "\n" + "Minimal - Most excess items are removed."; + mOptionDescriptions[RSK_ICE_TRAPS] = "Sets how many items are replaced by ice traps.\n" + "\n" + "Off - No ice traps.\n" + "\n" + "Normal - Only Ice Traps from the base item pool are shuffled in.\n" + "\n" + "Extra - Chance to replace added junk items with additional ice traps.\n" + "\n" + "Mayhem - All added junk items will be Ice Traps.\n" + "\n" + "Onslaught - All junk items will be replaced by Ice Traps, even those " + "in the base pool."; + mOptionDescriptions[RSK_GOSSIP_STONE_HINTS] = + "Allows Gossip Stones to provide hints on item locations. Hints mentioning " + "\"Way of the Hero\" indicate a location that holds an item required to beat " + "the seed.\n" + "\n" + "No hints - No hints will be given at all.\n" + "\n" + "Need Nothing - Hints are always available from Gossip Stones.\n" + "\n" + "Need Stone of Agony - Hints are only available after obtaining the Stone of Agony.\n" + "\n" + "Need Mask of Truth - Hints are only available whilst wearing the Mask of Truth.\n"; + mOptionDescriptions[RSK_HINT_CLARITY] = + "Sets the difficulty of hints.\n" + "\n" + "Obscure - Hints are unique for each item, but the writing may be cryptic.\n" + "Ex: Kokiri Sword > a butter knife\n" + "\n" + "Ambiguous - Hints are clearly written, but may refer to more than one item.\n" + "Ex: Kokiri Sword > a sword\n" + "\n" + "Clear - Hints are clearly written and are unique for each item.\n" + "Ex: Kokiri Sword > the Kokiri Sword"; + mOptionDescriptions[RSK_HINT_DISTRIBUTION] = "Sets how many hints will be useful.\n" + "\n" + "Useless - Only junk hints.\n" + "\n" + "Balanced - Recommended hint spread.\n" + "\n" + "Strong - More useful hints.\n" + "\n" + "Very Strong - Many powerful hints."; + mOptionDescriptions[RSK_TOT_ALTAR_HINT] = + "Reading the Temple of Time altar as child will tell you the locations of the Spiritual Stones.\n" + "Reading the Temple of Time altar as adult will tell you the locations of the Medallions, as well as the " + "conditions for building the Rainbow Bridge and getting the Boss Key for Ganon's Castle."; + mOptionDescriptions[RSK_LIGHT_ARROWS_HINT] = + "Talking to Ganondorf in his boss room or Sheik inside Ganon's Castle (when trials are enabled) will tell you " + "the location of the Light Arrows." + "If this option is enabled and Ganondorf is reachable without Light Arrows, Gossip Stones will never hint the " + "Light Arrows."; + mOptionDescriptions[RSK_DAMPES_DIARY_HINT] = + "Reading the diary of Dampé the gravekeeper as adult will tell you the location of one of the Hookshots."; + mOptionDescriptions[RSK_GREG_HINT] = + "Talking to the chest game owner after buying a key will tell you the location of Greg the Green Rupee."; + mOptionDescriptions[RSK_SARIA_HINT] = "Talking to Saria either in person or through Saria's Song will tell you the " + "location of a progressive magic meter."; + mOptionDescriptions[RSK_FROGS_HINT] = "Standing near the pedestal for the frogs in Zora's River will tell you the " + "reward for the frogs' ocarina game."; + mOptionDescriptions[RSK_WARP_SONG_HINTS] = "Standing near the pedestal for the frogs in Zora's River will tell you " + "the reward for the frogs' ocarina game."; + mOptionDescriptions[RSK_SCRUB_TEXT_HINT] = "Business scrubs will reveal the identity of what they're selling."; + mOptionDescriptions[RSK_FULL_WALLETS] = "Start with a full wallet. All wallet upgrades come filled with rupees."; + mOptionDescriptions[RSK_BOMBCHUS_IN_LOGIC] = + "Bombchus are properly considered in logic.\n" + "\n" + "The first Bombchu pack will always be 20, and subsequent packs will be " + "5 or 10 based on how many you have.\n" + "Once found, they can be replenished at the Bombchu shop.\n" + "\n" + "Bombchu Bowling is opened by obtaining Bombchus."; + mOptionDescriptions[RSK_ENABLE_BOMBCHU_DROPS] = "Once you obtain bombchus for the first time, refills can be found " + "in bushes and other places where bomb drops can normally spawn."; + mOptionDescriptions[RSK_BLUE_FIRE_ARROWS] = + "Ice Arrows act like Blue Fire, making them able to melt red ice. " + "Item placement logic will respect this option, so it might be required to use this to progress."; + mOptionDescriptions[RSK_LIGHT_ARROWS_HINT] = + "Light Arrows can be used to light up the sun switches instead of using the Mirror Shield. " + "Item placement logic will respect this option, so it might be required to use this to progress."; + mOptionDescriptions[RSK_LOGIC_RULES] = + "Glitchless - No glitches are required, but may require some minor tricks. Additional tricks may be enabled " + "and disabled below.\n" + "\n" + //"Glitched - Glitches may be required to beat the game. You can disable and enable glitches below.\n" + //"\n" + "No logic - Item placement is completely random. MAY BE IMPOSSIBLE TO BEAT."; + mOptionDescriptions[RSK_ALL_LOCATIONS_REACHABLE] = "When this options is enabled, the randomizer will " + "guarantee that every item is obtainable and every " + "location is reachable. When disabled, only " + "required items and locations to beat the game " + "will be guaranteed reachable."; + mOptionDescriptions[RSK_ENABLE_GLITCH_CUTSCENES] = + "The cutscenes of the Poes in Forest Temple and Darunia in Fire Temple will not be skipped. " + "These cutscenes are only useful for glitched gameplay and can be safely skipped otherwise."; + mOptionDescriptions[RSK_SHUFFLE_BOSS_SOULS] = "Shuffles 8 boss souls (one for each blue warp dungeon). A boss will not appear until you collect its respective soul." + "\n\"On + Ganon\" will also hide Ganon and Ganondorf behind a boss soul."; +} +} // namespace Rando \ No newline at end of file diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index 079a14e51..6d1d76e30 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -1642,198 +1642,8 @@ std::thread randoThread; void GenerateRandomizerImgui(std::string seed = "") { CVarSetInteger("gRandoGenerating", 1); CVarSave(); - - std::unordered_map cvarSettings; - cvarSettings[RSK_LOGIC_RULES] = CVarGetInteger("gRandomizeLogicRules", RO_LOGIC_GLITCHLESS); - cvarSettings[RSK_ALL_LOCATIONS_REACHABLE] = CVarGetInteger("gRandomizeAllLocationsReachable", RO_GENERIC_ON); - cvarSettings[RSK_FOREST] = CVarGetInteger("gRandomizeForest", RO_FOREST_CLOSED); - cvarSettings[RSK_KAK_GATE] = CVarGetInteger("gRandomizeKakarikoGate", RO_KAK_GATE_CLOSED); - cvarSettings[RSK_DOOR_OF_TIME] = CVarGetInteger("gRandomizeDoorOfTime", RO_DOOROFTIME_CLOSED); - cvarSettings[RSK_ZORAS_FOUNTAIN] = CVarGetInteger("gRandomizeZorasFountain", 0); - cvarSettings[RSK_STARTING_AGE] = CVarGetInteger("gRandomizeStartingAge", RO_AGE_CHILD); - cvarSettings[RSK_GERUDO_FORTRESS] = CVarGetInteger("gRandomizeGerudoFortress", RO_GF_NORMAL); - cvarSettings[RSK_RAINBOW_BRIDGE] = CVarGetInteger("gRandomizeRainbowBridge", RO_BRIDGE_VANILLA); - cvarSettings[RSK_RAINBOW_BRIDGE_STONE_COUNT] = CVarGetInteger("gRandomizeStoneCount", 3); - cvarSettings[RSK_RAINBOW_BRIDGE_MEDALLION_COUNT] = CVarGetInteger("gRandomizeMedallionCount", 6); - cvarSettings[RSK_RAINBOW_BRIDGE_REWARD_COUNT] = CVarGetInteger("gRandomizeRewardCount", 9); - cvarSettings[RSK_RAINBOW_BRIDGE_DUNGEON_COUNT] = CVarGetInteger("gRandomizeDungeonCount", 8); - cvarSettings[RSK_RAINBOW_BRIDGE_TOKEN_COUNT] = CVarGetInteger("gRandomizeTokenCount", 100); - cvarSettings[RSK_BRIDGE_OPTIONS] = CVarGetInteger("gRandomizeBridgeRewardOptions", 0); - cvarSettings[RSK_GANONS_TRIALS] = CVarGetInteger("gRandomizeGanonTrial", RO_GANONS_TRIALS_SET_NUMBER); - cvarSettings[RSK_TRIAL_COUNT] = CVarGetInteger("gRandomizeGanonTrialCount", 6); - cvarSettings[RSK_STARTING_OCARINA] = CVarGetInteger("gRandomizeStartingOcarina", 0); - cvarSettings[RSK_SHUFFLE_OCARINA] = CVarGetInteger("gRandomizeShuffleOcarinas", 0) || - CVarGetInteger("gRandomizeStartingOcarina", 0); - cvarSettings[RSK_STARTING_KOKIRI_SWORD] = CVarGetInteger("gRandomizeStartingKokiriSword", 0); - cvarSettings[RSK_SHUFFLE_KOKIRI_SWORD] = CVarGetInteger("gRandomizeShuffleKokiriSword", 0) || - CVarGetInteger("gRandomizeStartingKokiriSword", 0); - cvarSettings[RSK_SHUFFLE_MASTER_SWORD] = CVarGetInteger("gRandomizeShuffleMasterSword", 0); - cvarSettings[RSK_STARTING_DEKU_SHIELD] = CVarGetInteger("gRandomizeStartingDekuShield", 0); - cvarSettings[RSK_STARTING_ZELDAS_LULLABY] = CVarGetInteger("gRandomizeStartingZeldasLullaby", 0); - cvarSettings[RSK_STARTING_EPONAS_SONG] = CVarGetInteger("gRandomizeStartingEponasSong", 0); - cvarSettings[RSK_STARTING_SARIAS_SONG] = CVarGetInteger("gRandomizeStartingSariasSong", 0); - cvarSettings[RSK_STARTING_SUNS_SONG] = CVarGetInteger("gRandomizeStartingSunsSong", 0); - cvarSettings[RSK_STARTING_SONG_OF_TIME] = CVarGetInteger("gRandomizeStartingSongOfTime", 0); - cvarSettings[RSK_STARTING_SONG_OF_STORMS] = CVarGetInteger("gRandomizeStartingSongOfStorms", 0); - cvarSettings[RSK_STARTING_MINUET_OF_FOREST] = CVarGetInteger("gRandomizeStartingMinuetOfForest", 0); - cvarSettings[RSK_STARTING_BOLERO_OF_FIRE] = CVarGetInteger("gRandomizeStartingBoleroOfFire", 0); - cvarSettings[RSK_STARTING_SERENADE_OF_WATER] = CVarGetInteger("gRandomizeStartingSerenadeOfWater", 0); - cvarSettings[RSK_STARTING_REQUIEM_OF_SPIRIT] = CVarGetInteger("gRandomizeStartingRequiemOfSpirit", 0); - cvarSettings[RSK_STARTING_NOCTURNE_OF_SHADOW] = CVarGetInteger("gRandomizeStartingNocturneOfShadow", 0); - cvarSettings[RSK_STARTING_PRELUDE_OF_LIGHT] = CVarGetInteger("gRandomizeStartingPreludeOfLight", 0); - cvarSettings[RSK_STARTING_SKULLTULA_TOKEN] = CVarGetInteger("gRandomizeStartingSkulltulaToken", 0); - cvarSettings[RSK_SHUFFLE_MAPANDCOMPASS] = CVarGetInteger("gRandomizeStartingMapsCompasses", RO_DUNGEON_ITEM_LOC_OWN_DUNGEON); - cvarSettings[RSK_SHUFFLE_DUNGEON_REWARDS] = CVarGetInteger("gRandomizeShuffleDungeonReward", RO_DUNGEON_REWARDS_END_OF_DUNGEON); - cvarSettings[RSK_SHUFFLE_SONGS] = CVarGetInteger("gRandomizeShuffleSongs", RO_SONG_SHUFFLE_SONG_LOCATIONS); - cvarSettings[RSK_SHUFFLE_TOKENS] = CVarGetInteger("gRandomizeShuffleTokens", RO_TOKENSANITY_OFF); - cvarSettings[RSK_SHOPSANITY] = CVarGetInteger("gRandomizeShopsanity", RO_SHOPSANITY_OFF); - cvarSettings[RSK_SHOPSANITY_PRICES] = CVarGetInteger("gRandomizeShopsanityPrices", RO_SHOPSANITY_PRICE_BALANCED); - cvarSettings[RSK_SHOPSANITY_PRICES_AFFORDABLE] = CVarGetInteger("gRandomizeShopsanityPricesAffordable", RO_SHOPSANITY_OFF); - cvarSettings[RSK_SHUFFLE_SCRUBS] = CVarGetInteger("gRandomizeShuffleScrubs", RO_SCRUBS_OFF); - cvarSettings[RSK_SHUFFLE_COWS] = CVarGetInteger("gRandomizeShuffleCows", 0); - cvarSettings[RSK_SHUFFLE_ADULT_TRADE] = CVarGetInteger("gRandomizeShuffleAdultTrade", 0); - cvarSettings[RSK_SHUFFLE_MAGIC_BEANS] = CVarGetInteger("gRandomizeShuffleBeans", 0); - cvarSettings[RSK_SHUFFLE_MERCHANTS] = CVarGetInteger("gRandomizeShuffleMerchants", RO_SHUFFLE_MERCHANTS_OFF); - cvarSettings[RSK_SHUFFLE_100_GS_REWARD] = CVarGetInteger("gRandomizeShuffle100GSReward", RO_GENERIC_OFF); - cvarSettings[RSK_SHUFFLE_BOSS_SOULS] = CVarGetInteger("gRandomizeShuffleBossSouls", RO_BOSS_SOULS_OFF); - cvarSettings[RSK_ENABLE_BOMBCHU_DROPS] = CVarGetInteger("gRandomizeEnableBombchuDrops", 0); - cvarSettings[RSK_BOMBCHUS_IN_LOGIC] = CVarGetInteger("gRandomizeBombchusInLogic", 0); - cvarSettings[RSK_SKIP_CHILD_ZELDA] = CVarGetInteger("gRandomizeSkipChildZelda", 0); - - // if we skip child zelda, we start with zelda's letter, and malon starts - // at the ranch, so we should *not* shuffle the weird egg - cvarSettings[RSK_SHUFFLE_WEIRD_EGG] = ((CVarGetInteger("gRandomizeSkipChildZelda", 0) == 0) && - CVarGetInteger("gRandomizeShuffleWeirdEgg", 0)); - cvarSettings[RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD] = CVarGetInteger("gRandomizeShuffleGerudoToken", 0); - cvarSettings[RSK_SHUFFLE_FROG_SONG_RUPEES] = CVarGetInteger("gRandomizeShuffleFrogSongRupees", 0); - cvarSettings[RSK_ITEM_POOL] = CVarGetInteger("gRandomizeItemPool", RO_ITEM_POOL_BALANCED); - cvarSettings[RSK_ICE_TRAPS] = CVarGetInteger("gRandomizeIceTraps", RO_ICE_TRAPS_NORMAL); - cvarSettings[RSK_TOT_ALTAR_HINT] = CVarGetInteger("gRandomizeAltarHint", RO_GENERIC_ON); - cvarSettings[RSK_LIGHT_ARROWS_HINT] = CVarGetInteger("gRandomizeLAHint", RO_GENERIC_ON); - cvarSettings[RSK_DAMPES_DIARY_HINT] = CVarGetInteger("gRandomizeDampeHint", RO_GENERIC_OFF); - cvarSettings[RSK_GREG_HINT] = CVarGetInteger("gRandomizeGregHint", RO_GENERIC_OFF); - cvarSettings[RSK_SARIA_HINT] = CVarGetInteger("gRandomizeSariaHint", RO_GENERIC_OFF); - cvarSettings[RSK_FROGS_HINT] = CVarGetInteger("gRandomizeFrogsHint", RO_GENERIC_OFF); - cvarSettings[RSK_WARP_SONG_HINTS] = CVarGetInteger("gRandomizeWarpSongText", RO_GENERIC_ON); - cvarSettings[RSK_SCRUB_TEXT_HINT] = CVarGetInteger("gRandomizeScrubText", RO_GENERIC_OFF); - cvarSettings[RSK_KAK_10_SKULLS_HINT] = CVarGetInteger("gRandomize10GSHint", RO_GENERIC_OFF); - cvarSettings[RSK_KAK_20_SKULLS_HINT] = CVarGetInteger("gRandomize20GSHint", RO_GENERIC_OFF); - cvarSettings[RSK_KAK_30_SKULLS_HINT] = CVarGetInteger("gRandomize30GSHint", RO_GENERIC_OFF); - cvarSettings[RSK_KAK_40_SKULLS_HINT] = CVarGetInteger("gRandomize40GSHint", RO_GENERIC_OFF); - cvarSettings[RSK_KAK_50_SKULLS_HINT] = CVarGetInteger("gRandomize50GSHint", RO_GENERIC_OFF); - cvarSettings[RSK_GOSSIP_STONE_HINTS] = CVarGetInteger("gRandomizeGossipStoneHints", RO_GOSSIP_STONES_NEED_NOTHING); - cvarSettings[RSK_HINT_CLARITY] = CVarGetInteger("gRandomizeHintClarity", RO_HINT_CLARITY_CLEAR); - cvarSettings[RSK_HINT_DISTRIBUTION] = CVarGetInteger("gRandomizeHintDistribution", RO_HINT_DIST_BALANCED); - cvarSettings[RSK_BLUE_FIRE_ARROWS] = CVarGetInteger("gRandomizeBlueFireArrows", 0); - cvarSettings[RSK_SUNLIGHT_ARROWS] = CVarGetInteger("gRandomizeSunlightArrows", 0); - cvarSettings[RSK_KEYSANITY] = CVarGetInteger("gRandomizeKeysanity", RO_DUNGEON_ITEM_LOC_OWN_DUNGEON); - cvarSettings[RSK_GERUDO_KEYS] = CVarGetInteger("gRandomizeGerudoKeys", RO_GERUDO_KEYS_VANILLA); - cvarSettings[RSK_KEYRINGS] = CVarGetInteger("gRandomizeShuffleKeyRings", RO_KEYRINGS_OFF); - int maxKeyringCount = (CVarGetInteger("gRandomizeGerudoFortress", RO_GF_NORMAL) == RO_GF_NORMAL && - CVarGetInteger("gRandomizeGerudoKeys", RO_GERUDO_KEYS_VANILLA) != RO_GERUDO_KEYS_VANILLA) ? 9 : 8; - cvarSettings[RSK_KEYRINGS_RANDOM_COUNT] = std::min(CVarGetInteger("gRandomizeShuffleKeyRingsRandomCount", maxKeyringCount), maxKeyringCount); - // Don't allow this to be on if Gerudo Fortress Carpenters is anything other than Normal - cvarSettings[RSK_KEYRINGS_GERUDO_FORTRESS] = - (CVarGetInteger("gRandomizeGerudoFortress", RO_GF_NORMAL) == RO_GF_NORMAL && - CVarGetInteger("gRandomizeGerudoKeys", RO_GERUDO_KEYS_VANILLA) != RO_GERUDO_KEYS_VANILLA) - ? CVarGetInteger("gRandomizeShuffleKeyRingsGerudoFortress", RO_GENERIC_OFF) : RO_GENERIC_OFF; - cvarSettings[RSK_KEYRINGS_FOREST_TEMPLE] = CVarGetInteger("gRandomizeShuffleKeyRingsForestTemple", 0); - cvarSettings[RSK_KEYRINGS_FIRE_TEMPLE] = CVarGetInteger("gRandomizeShuffleKeyRingsFireTemple", 0); - cvarSettings[RSK_KEYRINGS_WATER_TEMPLE] = CVarGetInteger("gRandomizeShuffleKeyRingsWaterTemple", 0); - cvarSettings[RSK_KEYRINGS_SPIRIT_TEMPLE] = CVarGetInteger("gRandomizeShuffleKeyRingsSpiritTemple", 0); - cvarSettings[RSK_KEYRINGS_SHADOW_TEMPLE] = CVarGetInteger("gRandomizeShuffleKeyRingsShadowTemple", 0); - cvarSettings[RSK_KEYRINGS_BOTTOM_OF_THE_WELL] = CVarGetInteger("gRandomizeShuffleKeyRingsBottomOfTheWell", 0); - cvarSettings[RSK_KEYRINGS_GTG] = CVarGetInteger("gRandomizeShuffleKeyRingsGTG", 0); - cvarSettings[RSK_KEYRINGS_GANONS_CASTLE] = CVarGetInteger("gRandomizeShuffleKeyRingsGanonsCastle", 0); - cvarSettings[RSK_BOSS_KEYSANITY] = CVarGetInteger("gRandomizeBossKeysanity", RO_DUNGEON_ITEM_LOC_OWN_DUNGEON); - cvarSettings[RSK_GANONS_BOSS_KEY] = CVarGetInteger("gRandomizeShuffleGanonBossKey", RO_GANON_BOSS_KEY_VANILLA); - cvarSettings[RSK_LACS_STONE_COUNT] = CVarGetInteger("gRandomizeLacsStoneCount", 3); - cvarSettings[RSK_LACS_MEDALLION_COUNT] = CVarGetInteger("gRandomizeLacsMedallionCount", 6); - cvarSettings[RSK_LACS_REWARD_COUNT] = CVarGetInteger("gRandomizeLacsRewardCount", 9); - cvarSettings[RSK_LACS_DUNGEON_COUNT] = CVarGetInteger("gRandomizeLacsDungeonCount", 8); - cvarSettings[RSK_LACS_TOKEN_COUNT] = CVarGetInteger("gRandomizeLacsTokenCount", 100); - cvarSettings[RSK_LACS_OPTIONS] = CVarGetInteger("gRandomizeLacsRewardOptions", 0); - cvarSettings[RSK_STARTING_CONSUMABLES] = CVarGetInteger("gRandomizeStartingConsumables", 0); - cvarSettings[RSK_FULL_WALLETS] = CVarGetInteger("gRandomizeFullWallets", 0); - - // RANDOTODO implement chest minigame shuffle with keysanity - cvarSettings[RSK_SHUFFLE_CHEST_MINIGAME] = false; - - cvarSettings[RSK_LANGUAGE] = CVarGetInteger("gLanguages", 0); - - cvarSettings[RSK_CUCCO_COUNT] = CVarGetInteger("gRandomizeCuccosToReturn", 7); - cvarSettings[RSK_BIG_POE_COUNT] = CVarGetInteger("gRandomizeBigPoeTargetCount", 10); - - // If we skip child zelda, skip child stealth is pointless, so this needs to be reflected in the spoiler log - cvarSettings[RSK_SKIP_CHILD_STEALTH] = !CVarGetInteger("gRandomizeSkipChildZelda", 0) && CVarGetInteger("gRandomizeSkipChildStealth", 0); - - cvarSettings[RSK_SKIP_EPONA_RACE] = CVarGetInteger("gRandomizeSkipEponaRace", 0); - cvarSettings[RSK_SKIP_TOWER_ESCAPE] = CVarGetInteger("gRandomizeSkipTowerEscape", 0); - cvarSettings[RSK_COMPLETE_MASK_QUEST] = CVarGetInteger("gRandomizeCompleteMaskQuest", 0); - cvarSettings[RSK_SKIP_SCARECROWS_SONG] = CVarGetInteger("gRandomizeSkipScarecrowsSong", 0); - cvarSettings[RSK_ENABLE_GLITCH_CUTSCENES] = CVarGetInteger("gRandomizeEnableGlitchCutscenes", 0); - - cvarSettings[RSK_SKULLS_SUNS_SONG] = CVarGetInteger("gRandomizeGsExpectSunsSong", 0); - // Link's Pocket has to have a dungeon reward if the other rewards are shuffled to end of dungeon. - cvarSettings[RSK_LINKS_POCKET] = CVarGetInteger("gRandomizeShuffleDungeonReward", RO_DUNGEON_REWARDS_END_OF_DUNGEON) != RO_DUNGEON_REWARDS_END_OF_DUNGEON ? - CVarGetInteger("gRandomizeLinksPocket", RO_LINKS_POCKET_DUNGEON_REWARD) : RO_LINKS_POCKET_DUNGEON_REWARD; - - if (OTRGlobals::Instance->HasMasterQuest() && OTRGlobals::Instance->HasOriginal()) { - // If both OTRs are loaded. - cvarSettings[RSK_MQ_DUNGEON_RANDOM] = CVarGetInteger("gRandomizeMqDungeons", RO_MQ_DUNGEONS_NONE); - cvarSettings[RSK_MQ_DUNGEON_COUNT] = CVarGetInteger("gRandomizeMqDungeonCount", 12); - } else if (OTRGlobals::Instance->HasMasterQuest()) { - // If only Master Quest is loaded. - cvarSettings[RSK_MQ_DUNGEON_RANDOM] = RO_MQ_DUNGEONS_SET_NUMBER; - cvarSettings[RSK_MQ_DUNGEON_COUNT] = 12; - } else { - // If only Original Quest is loaded. - cvarSettings[RSK_MQ_DUNGEON_RANDOM] = RO_MQ_DUNGEONS_NONE; - cvarSettings[RSK_MQ_DUNGEON_COUNT] = 0; - } - - cvarSettings[RSK_TRIFORCE_HUNT] = CVarGetInteger("gRandomizeTriforceHunt", 0); - cvarSettings[RSK_TRIFORCE_HUNT_PIECES_TOTAL] = CVarGetInteger("gRandomizeTriforceHuntTotalPieces", 30); - cvarSettings[RSK_TRIFORCE_HUNT_PIECES_REQUIRED] = CVarGetInteger("gRandomizeTriforceHuntRequiredPieces", 20); - - cvarSettings[RSK_MQ_DEKU_TREE] = CVarGetInteger("gRandomizeMqDungeonsDekuTree", RO_MQ_SET_VANILLA); - cvarSettings[RSK_MQ_DODONGOS_CAVERN] = CVarGetInteger("gRandomizeMqDungeonsDodongosCavern", RO_MQ_SET_VANILLA); - cvarSettings[RSK_MQ_JABU_JABU] = CVarGetInteger("gRandomizeMqDungeonsJabuJabu", RO_MQ_SET_VANILLA); - cvarSettings[RSK_MQ_FOREST_TEMPLE] = CVarGetInteger("gRandomizeMqDungeonsForestTemple", RO_MQ_SET_VANILLA); - cvarSettings[RSK_MQ_FIRE_TEMPLE] = CVarGetInteger("gRandomizeMqDungeonsFireTemple", RO_MQ_SET_VANILLA); - cvarSettings[RSK_MQ_WATER_TEMPLE] = CVarGetInteger("gRandomizeMqDungeonsWaterTemple", RO_MQ_SET_VANILLA); - cvarSettings[RSK_MQ_SPIRIT_TEMPLE] = CVarGetInteger("gRandomizeMqDungeonsSpiritTemple", RO_MQ_SET_VANILLA); - cvarSettings[RSK_MQ_SHADOW_TEMPLE] = CVarGetInteger("gRandomizeMqDungeonsShadowTemple", RO_MQ_SET_VANILLA); - cvarSettings[RSK_MQ_BOTTOM_OF_THE_WELL] = CVarGetInteger("gRandomizeMqDungeonsBottomOfTheWell", RO_MQ_SET_VANILLA); - cvarSettings[RSK_MQ_ICE_CAVERN] = CVarGetInteger("gRandomizeMqDungeonsIceCavern", RO_MQ_SET_VANILLA); - cvarSettings[RSK_MQ_GTG] = CVarGetInteger("gRandomizeMqDungeonsGTG", RO_MQ_SET_VANILLA); - cvarSettings[RSK_MQ_GANONS_CASTLE] = CVarGetInteger("gRandomizeMqDungeonsGanonsCastle", RO_MQ_SET_VANILLA); - - // Enable if any of the entrance rando options are enabled. - cvarSettings[RSK_SHUFFLE_ENTRANCES] = CVarGetInteger("gRandomizeShuffleDungeonsEntrances", RO_DUNGEON_ENTRANCE_SHUFFLE_OFF) || - CVarGetInteger("gRandomizeShuffleBossEntrances", RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF) || - CVarGetInteger("gRandomizeShuffleOverworldEntrances", RO_GENERIC_OFF) || - CVarGetInteger("gRandomizeShuffleInteriorsEntrances", RO_INTERIOR_ENTRANCE_SHUFFLE_OFF) || - CVarGetInteger("gRandomizeShuffleGrottosEntrances", RO_GENERIC_OFF) || - CVarGetInteger("gRandomizeShuffleOwlDrops", RO_GENERIC_OFF) || - CVarGetInteger("gRandomizeShuffleWarpSongs", RO_GENERIC_OFF) || - CVarGetInteger("gRandomizeShuffleOverworldSpawns", RO_GENERIC_OFF); - - cvarSettings[RSK_SHUFFLE_DUNGEON_ENTRANCES] = CVarGetInteger("gRandomizeShuffleDungeonsEntrances", RO_DUNGEON_ENTRANCE_SHUFFLE_OFF); - cvarSettings[RSK_SHUFFLE_BOSS_ENTRANCES] = CVarGetInteger("gRandomizeShuffleBossEntrances", RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF); - cvarSettings[RSK_SHUFFLE_OVERWORLD_ENTRANCES] = CVarGetInteger("gRandomizeShuffleOverworldEntrances", RO_GENERIC_OFF); - cvarSettings[RSK_SHUFFLE_INTERIOR_ENTRANCES] = CVarGetInteger("gRandomizeShuffleInteriorsEntrances", RO_INTERIOR_ENTRANCE_SHUFFLE_OFF); - cvarSettings[RSK_SHUFFLE_GROTTO_ENTRANCES] = CVarGetInteger("gRandomizeShuffleGrottosEntrances", RO_GENERIC_OFF); - cvarSettings[RSK_SHUFFLE_OWL_DROPS] = CVarGetInteger("gRandomizeShuffleOwlDrops", RO_GENERIC_OFF); - cvarSettings[RSK_SHUFFLE_WARP_SONGS] = CVarGetInteger("gRandomizeShuffleWarpSongs", RO_GENERIC_OFF); - cvarSettings[RSK_SHUFFLE_OVERWORLD_SPAWNS] = CVarGetInteger("gRandomizeShuffleOverworldSpawns", RO_GENERIC_OFF); - cvarSettings[RSK_MIXED_ENTRANCE_POOLS] = CVarGetInteger("gRandomizeMixedEntrances", RO_GENERIC_OFF); - cvarSettings[RSK_MIX_DUNGEON_ENTRANCES] = CVarGetInteger("gRandomizeMixDungeons", RO_GENERIC_OFF); - cvarSettings[RSK_MIX_OVERWORLD_ENTRANCES] = CVarGetInteger("gRandomizeMixOverworld", RO_GENERIC_OFF); - cvarSettings[RSK_MIX_INTERIOR_ENTRANCES] = CVarGetInteger("gRandomizeMixInteriors", RO_GENERIC_OFF); - cvarSettings[RSK_MIX_GROTTO_ENTRANCES] = CVarGetInteger("gRandomizeMixGrottos", RO_GENERIC_OFF); - cvarSettings[RSK_DECOUPLED_ENTRANCES] = CVarGetInteger("gRandomizeDecoupleEntrances", RO_GENERIC_OFF); - + auto ctx = Rando::Context::GetInstance(); + ctx->GetSettings()->SetAllFromCVar(); // todo: this efficently when we build out cvar array support std::set excludedLocations; std::stringstream excludedLocationStringStream(CVarGetString("gRandomizeExcludedLocations", "")); @@ -1856,7 +1666,6 @@ void GenerateRandomizerImgui(std::string seed = "") { RandomizerCheckObjects::UpdateImGuiVisibility(); // Remove excludes for locations that are no longer allowed to be excluded - auto ctx = Rando::Context::GetInstance(); for (auto& location : Rando::StaticData::GetLocationTable()) { auto elfound = excludedLocations.find(location.GetRandomizerCheck()); if (!ctx->GetItemLocation(location.GetRandomizerCheck())->IsVisible() && elfound != excludedLocations.end()) { @@ -1864,7 +1673,7 @@ void GenerateRandomizerImgui(std::string seed = "") { } } - RandoMain::GenerateRando(cvarSettings, excludedLocations, enabledTricks, seed); + RandoMain::GenerateRando(excludedLocations, enabledTricks, seed); CVarSetInteger("gRandoGenerating", 0); CVarSave(); @@ -1892,73 +1701,6 @@ void RandomizerSettingsWindow::DrawElement() { randoThread.join(); } - // Randomizer settings - // Logic Settings - static const char* randoLogicRules[2] = { "Glitchless", "No logic" }; - - // Open Settings - static const char* randoForest[3] = { "Closed", "Closed Deku", "Open" }; - static const char* randoKakarikoGate[2] = { "Closed", "Open" }; - static const char* randoDoorOfTime[3] = { "Closed", "Song only", "Open" }; - static const char* randoZorasFountain[3] = { "Closed", "Closed as child", "Open" }; - static const char* randoGerudoFortress[3] = { "Normal", "Fast", "Open" }; - static const char* randoRainbowBridge[8] = { "Vanilla", "Always open", "Stones", "Medallions", - "Dungeon rewards", "Dungeons", "Tokens", "Greg" }; - static const char* randoBridgeRewardOptions[3] = { "Standard Rewards", "Greg as Reward", "Greg as Wildcard" }; - static const char* randoGanonsTrial[3] = { "Skip", "Set Number", "Random Number" }; - static const char* randoMqDungeons[4] = { "None", "Set Number", "Random Number", "Selection Only" }; - static const char* randoMqDungeonOptions[3] = {"Vanilla", "Master Quest", "Random"}; - - // World Settings - static const char* randoStartingAge[3] = { "Child", "Adult", "Random" }; - static const char* randoShuffleDungeonsEntrances[3] = { "Off", "On", "On + Ganon" }; - static const char* randoShuffleBossEntrances[3] = { "Off", "Age Restricted", "Full" }; - static const char* randoShuffleInteriorsEntrances[3] = { "Off", "Simple", "All" }; - static const char* randoBombchusInLogic[2] = { "Off", "On" }; - static const char* randoAmmoDrops[3] = { "On + Bombchu", "Off", "On" }; - static const char* randoHeartDropsAndRefills[4] = { "On", "No Drop", "No Refill", "Off" }; - - // Shuffle Settings - static const char* randoShuffleDungeonRewards[4] = { "End of dungeons", "Any dungeon", "Overworld", "Anywhere" }; - static const char* randoLinksPocket[4] = { "Dungeon Reward", "Advancement", "Anything", "Nothing" }; - static const char* randoShuffleSongs[3] = { "Song Locations", "Dungeon Rewards", "Anywhere" }; - static const char* randoShopsanity[7] = { "Off", "0 Items", "1 Item", "2 Items", "3 Items", "4 Items", "Random" }; - static const char* randoShopsanityPrices[5] = { "Balanced", "Starter Wallet", "Adult Wallet", "Giant's Wallet", "Tycoon's Wallet" }; - static const char* randoTokensanity[4] = { "Off", "Dungeons", "Overworld", "All Tokens" }; - static const char* randoShuffleScrubs[4] = { "Off", "Affordable", "Expensive", "Random Prices" }; - static const char* randoShuffleMerchants[3] = { "Off", "On (no hints)", "On (with hints)" }; - static const char* randoShuffleBossSouls[3] = { "Off", "On", "On + Ganon"}; - - // Shuffle Dungeon Items Settings - static const char* randoShuffleMapsAndCompasses[6] = { "Start With", "Vanilla", "Own Dungeon", - "Any Dungeon", "Overworld", "Anywhere" }; - static const char* randoShuffleSmallKeys[6] = { "Start With", "Vanilla", "Own Dungeon", - "Any Dungeon", "Overworld", "Anywhere" }; - static const char* randoShuffleGerudoFortressKeys[4] = { "Vanilla", "Any Dungeon", "Overworld", "Anywhere" }; - static const char* randoShuffleBossKeys[6] = { "Start With", "Vanilla", "Own Dungeon", - "Any Dungeon", "Overworld", "Anywhere" }; - static const char* randoShuffleGanonsBossKey[13] = {"Vanilla", "Own dungeon", "Start with", - "Any Dungeon", "Overworld", "Anywhere", - "LACS-Vanilla", "LACS-Stones", "LACS-Medallions", - "LACS-Rewards", "LACS-Dungeons", "LACS-Tokens", - "100 GS Reward"}; - static const char* randoLACSRewardOptions[3] = { "Standard Reward", "Greg as Reward", "Greg as Wildcard" }; - static const char* randoShuffleKeyRings[4] = { "Off", "Random", "Count", "Selection" }; - - // Misc Settings - static const char* randoGossipStoneHints[4] = { "No Hints", "Need Nothing", "Mask of Truth", "Stone of Agony" }; - static const char* randoHintClarity[3] = { "Obscure", "Ambiguous", "Clear" }; - static const char* randoHintDistribution[4] = { "Useless", "Balanced", "Strong", "Very Strong" }; - static const char* randoStartingTime[2] = { "Day", "Night" }; - static const char* randoRandomTrapDamage[3] = { "Basic", "Advanced", "Off" }; - - // Item Pool Settings - static const char* randoItemPool[4] = { "Plentiful", "Balanced", "Scarce", "Minimal" }; - static const char* randoIceTraps[5] = { "Off", "Normal", "Extra", "Mayhem", "Onslaught" }; - - static int maxKeyringCount; - static bool disableGFKeyring = false; - ImGui::SetNextWindowSize(ImVec2(920, 600), ImGuiCond_FirstUseEver); if (!ImGui::Begin("Randomizer Editor", &mIsVisible, ImGuiWindowFlags_NoFocusOnAppearing)) { ImGui::End(); @@ -2013,567 +1755,8 @@ void RandomizerSettingsWindow::DrawElement() { if (ImGui::BeginTabBar("Randomizer Settings", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { if (ImGui::BeginTabItem("World")) { ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, cellPadding); - if (ImGui::BeginTable("tableRandoWorld", 3, ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV)) { - ImGui::TableSetupColumn("Area Access", ImGuiTableColumnFlags_WidthStretch, 200.0f); - ImGui::TableSetupColumn("World Settings", ImGuiTableColumnFlags_WidthStretch, 200.0f); - ImGui::TableSetupColumn("Shuffle Entrances", ImGuiTableColumnFlags_WidthStretch, 200.0f); - ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); - ImGui::TableHeadersRow(); - ImGui::PopItemFlag(); - ImGui::TableNextRow(); - - // COLUMN 1 - Area Access - ImGui::TableNextColumn(); - window->DC.CurrLineTextBaseOffset = 0.0f; - ImGui::BeginChild("ChildOpenSettings", ImVec2(0, -8)); - ImGui::PushItemWidth(-FLT_MIN); - - // Forest - ImGui::Text("%s", ctx->GetOption(RSK_FOREST).GetName().c_str()); - UIWidgets::InsertHelpHoverText( - "Closed - Kokiri sword & shield are required to access " - "the Deku Tree, and completing the Deku Tree is required to " - "access the Hyrule Field exit.\n" - "\n" - "Closed Deku - Kokiri boy no longer blocks the path to Hyrule " - "Field but Mido still requires the Kokiri sword and Deku shield " - "to access the tree.\n" - "\n" - "Open - Mido no longer blocks the path to the Deku Tree. Kokiri " - "boy no longer blocks the path out of the forest." - ); - UIWidgets::EnhancementCombobox("gRandomizeForest", randoForest, RO_FOREST_CLOSED); - - UIWidgets::PaddedSeparator(); - - // Kakariko Gate - ImGui::Text("%s", ctx->GetOption(RSK_KAK_GATE).GetName().c_str()); - UIWidgets::InsertHelpHoverText( - "Closed - The gate will remain closed until Zelda's letter " - "is shown to the guard.\n" - "\n" - "Open - The gate is always open. The happy mask shop " - "will open immediately after obtaining Zelda's letter." - ); - UIWidgets::EnhancementCombobox("gRandomizeKakarikoGate", randoKakarikoGate, RO_KAK_GATE_CLOSED); - - UIWidgets::PaddedSeparator(); - - // Door of Time - ImGui::Text("%s", ctx->GetOption(RSK_DOOR_OF_TIME).GetName().c_str()); - UIWidgets::InsertHelpHoverText( - "Closed - The Ocarina of Time, the Song of Time and all " - "three spiritual stones are required to open the Door of Time.\n" - "\n" - "Song only - Play the Song of Time in front of the Door of " - "Time to open it.\n" - "\n" - "Open - The Door of Time is permanently open with no requirements." - ); - UIWidgets::EnhancementCombobox("gRandomizeDoorOfTime", randoDoorOfTime, RO_DOOROFTIME_CLOSED); - - UIWidgets::PaddedSeparator(); - - // Zora's Fountain - ImGui::Text("%s", ctx->GetOption(RSK_ZORAS_FOUNTAIN).GetName().c_str()); - UIWidgets::InsertHelpHoverText( - "Closed - King Zora obstructs the way to Zora's Fountain. " - "Ruto's letter must be shown as child Link in order to move " - "him in both time periods.\n" - "\n" - "Closed as child - Ruto's Letter is only required to move King Zora " - "as child Link. Zora's Fountain starts open as adult.\n" - "\n" - "Open - King Zora has already mweeped out of the way in both " - "time periods. Ruto's Letter is removed from the item pool." - ); - UIWidgets::EnhancementCombobox("gRandomizeZorasFountain", randoZorasFountain, RO_ZF_CLOSED); - - UIWidgets::PaddedSeparator(); - - ImGui::EndChild(); - - // COLUMN 2 - World Settings - ImGui::TableNextColumn(); - window->DC.CurrLineTextBaseOffset = 0.0f; - ImGui::BeginChild("ChildMiscWorldSettings", ImVec2(0,-8)); - ImGui::PushItemWidth(-FLT_MIN); - - //Starting Age - //Disabled when Forest is set to Closed or under very specific conditions - bool disableRandoStartingAge = CVarGetInteger("gRandomizeForest", RO_FOREST_CLOSED) == RO_FOREST_CLOSED || - ((CVarGetInteger("gRandomizeDoorOfTime", RO_DOOROFTIME_CLOSED) == RO_DOOROFTIME_CLOSED) && - (CVarGetInteger("gRandomizeShuffleOcarinas", RO_GENERIC_OFF) == RO_GENERIC_OFF)); // closed door of time with ocarina shuffle off - - static const char* disableRandoStartingAgeText = "This option is disabled due to other options making the game unbeatable."; - ImGui::Text("%s", ctx->GetOption(RSK_STARTING_AGE).GetName().c_str()); - UIWidgets::InsertHelpHoverText( - "Choose which age Link will start as.\n\n" - "Starting as adult means you start with the Master Sword in your inventory.\n" - "The child option is forcefully set if it would conflict with other options." - ); - UIWidgets::EnhancementCombobox("gRandomizeStartingAge", randoStartingAge, RO_AGE_CHILD, disableRandoStartingAge, disableRandoStartingAgeText, RO_AGE_CHILD); - - UIWidgets::PaddedSeparator(); - - // Gerudo Fortress - ImGui::Text("Gerudo Fortress Carpenters"); - UIWidgets::InsertHelpHoverText( - "Sets the amount of carpenters required to repair the bridge " - "in Gerudo Valley.\n" - "\n" - "Normal - All 4 carpenters are required to be saved.\n" - "\n" - "Fast - Only the bottom left carpenter requires rescuing.\n" - "\n" - "Open - The bridge is repaired from the start.\n" - "\n" - "Only \"Normal\" is compatible with Gerudo Fortress Key Rings." - ); - UIWidgets::EnhancementCombobox("gRandomizeGerudoFortress", randoGerudoFortress, RO_GF_NORMAL); - - UIWidgets::PaddedSeparator(); - - // Rainbow Bridge - ImGui::Text("Rainbow Bridge Requirements"); - UIWidgets::InsertHelpHoverText( - "Alters the requirements to open the bridge to Ganon's Castle.\n" - "\n" - "Vanilla - Obtain the Shadow Medallion, Spirit Medallion and Light Arrows.\n" - "\n" - "Always open - No requirements.\n" - "\n" - "Stones - Obtain the specified amount of spiritual stones.\n" - "\n" - "Medallions - Obtain the specified amount of medallions.\n" - "\n" - "Dungeon rewards - Obtain the specified total sum of spiritual " - "stones or medallions.\n" - "\n" - "Dungeons - Complete the specified amount of dungeons. Dungeons " - "are considered complete after stepping in to the blue warp after " - "the boss.\n" - "\n" - "Tokens - Obtain the specified amount of Skulltula tokens.\n" - "\n" - "Greg - Find Greg the Green Rupee." - ); - - UIWidgets::EnhancementCombobox("gRandomizeRainbowBridge", randoRainbowBridge, RO_BRIDGE_VANILLA); - ImGui::PopItemWidth(); - switch (CVarGetInteger("gRandomizeRainbowBridge", RO_BRIDGE_VANILLA)) { - case RO_BRIDGE_ALWAYS_OPEN: - break; - case RO_BRIDGE_VANILLA: - break; - case RO_BRIDGE_STONES: - ImGui::Text("Reward Options"); - UIWidgets::InsertHelpHoverText( - "Standard Rewards - Greg does not change logic, Greg does not help open the bridge, max " - "number of rewards on slider does not change.\n" - "\n" - "Greg as Reward - Greg does change logic (can be part of expected path for opening " - "bridge), Greg helps open bridge, max number of rewards on slider increases by 1 to " - "account for Greg. \n" - "\n" - "Greg as Wildcard - Greg does not change logic, Greg helps open the bridge, max number of " - "rewards on slider does not change."); - - UIWidgets::EnhancementCombobox("gRandomizeBridgeRewardOptions", randoBridgeRewardOptions, RO_BRIDGE_STANDARD_REWARD); - switch (CVarGetInteger("gRandomizeBridgeRewardOptions", RO_BRIDGE_STANDARD_REWARD)) { - case RO_BRIDGE_STANDARD_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Stone Count: %d", "##RandoStoneCount", - "gRandomizeStoneCount", 1, 3, "", 3, true, true, false); - break; - case RO_BRIDGE_GREG_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Stone Count: %d", "##RandoStoneCount", - "gRandomizeStoneCount", 1, 4, "", 4, true, true, false); - break; - case RO_BRIDGE_WILDCARD_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Stone Count: %d", "##RandoStoneCount", - "gRandomizeStoneCount", 1, 3, "", 3, true, true, false); - break; - } - break; - case RO_BRIDGE_MEDALLIONS: - ImGui::Text("Reward Options"); - UIWidgets::InsertHelpHoverText( - "Standard Rewards - Greg does not change logic, Greg does not help open the bridge, max " - "number of rewards on slider does not change.\n" - "\n" - "Greg as Reward - Greg does change logic (can be part of expected path for opening " - "bridge), Greg helps open bridge, max number of rewards on slider increases by 1 to " - "account for Greg. \n" - "\n" - "Greg as Wildcard - Greg does not change logic, Greg helps open the bridge, max number of " - "rewards on slider does not change."); - - UIWidgets::EnhancementCombobox("gRandomizeBridgeRewardOptions", randoBridgeRewardOptions, RO_BRIDGE_STANDARD_REWARD); - switch (CVarGetInteger("gRandomizeBridgeRewardOptions", RO_BRIDGE_STANDARD_REWARD)) { - case RO_BRIDGE_STANDARD_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Medallion Count: %d", "##RandoMedallionCount", - "gRandomizeMedallionCount", 1, 6, "", 6, true, true, false); - break; - case RO_BRIDGE_GREG_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Medallion Count: %d", "##RandoMedallionCount", - "gRandomizeMedallionCount", 1, 7, "", 7, true, true, false); - break; - case RO_BRIDGE_WILDCARD_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Medallion Count: %d", "##RandoMedallionCount", - "gRandomizeMedallionCount", 1, 6, "", 6, true, true, false); - break; - } - break; - case RO_BRIDGE_DUNGEON_REWARDS: - ImGui::Text("Reward Options"); - UIWidgets::InsertHelpHoverText( - "Standard Rewards - Greg does not change logic, Greg does not help open the bridge, max " - "number of rewards on slider does not change.\n" - "\n" - "Greg as Reward - Greg does change logic (can be part of expected path for opening " - "bridge), Greg helps open bridge, max number of rewards on slider increases by 1 to " - "account for Greg. \n" - "\n" - "Greg as Wildcard - Greg does not change logic, Greg helps open the bridge, max number of " - "rewards on slider does not change."); - - UIWidgets::EnhancementCombobox("gRandomizeBridgeRewardOptions", randoBridgeRewardOptions, RO_BRIDGE_STANDARD_REWARD); - switch (CVarGetInteger("gRandomizeBridgeRewardOptions", RO_BRIDGE_STANDARD_REWARD)) { - case RO_BRIDGE_STANDARD_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Reward Count: %d", "##RandoRewardCount", - "gRandomizeRewardCount", 1, 9, "", 9, true, true, false); - break; - case RO_BRIDGE_GREG_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Reward Count: %d", "##RandoRewardCount", - "gRandomizeRewardCount", 1, 10, "", 10, true, true, false); - break; - case RO_BRIDGE_WILDCARD_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Reward Count: %d", "##RandoRewardCount", - "gRandomizeRewardCount", 1, 9, "", 9, true, true, false); - - break; - } - break; - case RO_BRIDGE_DUNGEONS: - ImGui::Text("Reward Options"); - UIWidgets::InsertHelpHoverText( - "Standard Rewards - Greg does not change logic, Greg does not help open the bridge, max " - "number of rewards on slider does not change.\n" - "\n" - "Greg as Reward - Greg does change logic (can be part of expected path for opening " - "bridge), Greg helps open bridge, max number of rewards on slider increases by 1 to " - "account for Greg. \n" - "\n" - "Greg as Wildcard - Greg does not change logic, Greg helps open the bridge, max number of " - "rewards on slider does not change."); - - UIWidgets::EnhancementCombobox("gRandomizeBridgeRewardOptions", randoBridgeRewardOptions, RO_BRIDGE_STANDARD_REWARD); - switch (CVarGetInteger("gRandomizeBridgeRewardOptions", RO_BRIDGE_STANDARD_REWARD)) { - case RO_BRIDGE_STANDARD_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Dungeon Count: %d", "##RandoDungeonCount", - "gRandomizeDungeonCount", 1, 8, "", 8, true, true, false); - break; - case RO_BRIDGE_GREG_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Dungeon Count: %d", "##RandoDungeonCount", - "gRandomizeDungeonCount", 1, 9, "", 9, true, true, false); - break; - case RO_BRIDGE_WILDCARD_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Dungeon Count: %d", "##RandoDungeonCount", - "gRandomizeDungeonCount", 1, 8, "", 8, true, true, false); - break; - } - break; - case RO_BRIDGE_TOKENS: - UIWidgets::PaddedEnhancementSliderInt("Token Count: %d", "##RandoTokenCount", - "gRandomizeTokenCount", 1, 100, "", 100, true, true, false); - break; - case RO_BRIDGE_GREG: - break; - } - - UIWidgets::PaddedSeparator(); - - // Ganon's Trials - ImGui::PushItemWidth(-FLT_MIN); - ImGui::Text("Ganon's Trials"); - UIWidgets::InsertHelpHoverText( - "Sets the number of Ganon's Trials required to dispel the barrier.\n" - "\n" - "Skip - No Trials are required and the barrier is already dispelled.\n" - "\n" - "Set Number - Select a number of trials that will be required from the" - "slider below. Which specific trials you need to complete will be random.\n" - "\n" - "Random Number - A Random number and set of trials will be required." - ); - UIWidgets::EnhancementCombobox("gRandomizeGanonTrial", randoGanonsTrial, RO_GANONS_TRIALS_SET_NUMBER); - ImGui::PopItemWidth(); - if (CVarGetInteger("gRandomizeGanonTrial", RO_GANONS_TRIALS_SET_NUMBER) == RO_GANONS_TRIALS_SET_NUMBER) { - UIWidgets::PaddedEnhancementSliderInt("Ganon's Trial Count: %d", "##RandoTrialCount", - "gRandomizeGanonTrialCount", 1, 6, "", 6, true, true, false); - UIWidgets::InsertHelpHoverText("Set the number of trials required to enter Ganon's Tower."); - } - - UIWidgets::PaddedSeparator(); - - // Master Quest Dungeons - if (OTRGlobals::Instance->HasMasterQuest() && OTRGlobals::Instance->HasOriginal()) { - ImGui::PushItemWidth(-FLT_MIN); - ImGui::Text("Master Quest Dungeons"); - UIWidgets::InsertHelpHoverText( - "Sets the number of Master Quest Dungeons that are shuffled into the pool.\n" - "\n" - "None - All Dungeons will be their Vanilla versions.\n" - "\n" - "Set Number - Select a number of dungeons that will be their Master Quest versions " - "using the slider below. Which dungeons are set to be the Master Quest variety will be random.\n" - "\n" - "Random Number - A Random number and set of dungeons will be their Master Quest varieties.\n" - "\n" - "Selection Only - Specify which dungeons are Vanilla or Master Quest." - ); - UIWidgets::EnhancementCombobox("gRandomizeMqDungeons", randoMqDungeons, RO_MQ_DUNGEONS_NONE); - ImGui::PopItemWidth(); - if (CVarGetInteger("gRandomizeMqDungeons", RO_MQ_DUNGEONS_NONE) == RO_MQ_DUNGEONS_SET_NUMBER) { - UIWidgets::PaddedEnhancementSliderInt( - "Master Quest Dungeon Count: %d", "##RandoMqDungeonCount", "gRandomizeMqDungeonCount", 1, - 12, "", CVarGetInteger("gRandomizeMqDungeonCount", 12), true, true, false); - } - if (CVarGetInteger("gRandomizeMqDungeons", RO_MQ_DUNGEONS_NONE) != RO_MQ_DUNGEONS_NONE) { - UIWidgets::EnhancementCheckbox( - ctx->GetOption(RSK_MQ_DUNGEON_SET).GetName().c_str(), "gRandomizeMqDungeonsSelection", - CVarGetInteger("gRandomizeMqDungeons", RO_MQ_DUNGEONS_NONE) == RO_MQ_DUNGEONS_SELECTION, - "This option is enabled because Master Quest Dungeons is set to Selection Only", - UIWidgets::CheckboxGraphics::Checkmark); - UIWidgets::InsertHelpHoverText( - "Choose specific Dungeons to be Master Quest or Vanilla.\n" - "\n" - "If Master Quest Dungeons is set to Set Number or Random, the dungeons chosen " - "to be Master Quest here will count towards that total. Any Dungeons set to Vanilla " - "here will be guaranteed to be Vanilla. If Set Number is higher than the amount of dungeons " - "set to either MQ or Random here, you will have fewer MQ Dungeons than the number you " - "set."); - if (CVarGetInteger("gRandomizeMqDungeonsSelection", RO_GENERIC_OFF) == RO_GENERIC_ON) { - UIWidgets::PaddedText("Deku Tree", true, false); - UIWidgets::EnhancementCombobox("gRandomizeMqDungeonsDekuTree", randoMqDungeonOptions, RO_MQ_SET_VANILLA); - UIWidgets::PaddedText("Dodongo's Cavern", true, false); - UIWidgets::EnhancementCombobox("gRandomizeMqDungeonsDodongosCavern", randoMqDungeonOptions, RO_MQ_SET_VANILLA); - UIWidgets::PaddedText("Jabu Jabu's Belly", true, false); - UIWidgets::EnhancementCombobox("gRandomizeMqDungeonsJabuJabu", randoMqDungeonOptions, RO_MQ_SET_VANILLA); - UIWidgets::PaddedText("Forest Temple", true, false); - UIWidgets::EnhancementCombobox("gRandomizeMqDungeonsForestTemple", randoMqDungeonOptions, RO_MQ_SET_VANILLA); - UIWidgets::PaddedText("Fire Temple", true, false); - UIWidgets::EnhancementCombobox("gRandomizeMqDungeonsFireTemple", randoMqDungeonOptions, RO_MQ_SET_VANILLA); - UIWidgets::PaddedText("Water Temple", true, false); - UIWidgets::EnhancementCombobox("gRandomizeMqDungeonsWaterTemple", randoMqDungeonOptions, RO_MQ_SET_VANILLA); - UIWidgets::PaddedText("Spirit Temple", true, false); - UIWidgets::EnhancementCombobox("gRandomizeMqDungeonsSpiritTemple", randoMqDungeonOptions, RO_MQ_SET_VANILLA); - UIWidgets::PaddedText("Shadow Temple", true, false); - UIWidgets::EnhancementCombobox("gRandomizeMqDungeonsShadowTemple", randoMqDungeonOptions, RO_MQ_SET_VANILLA); - UIWidgets::PaddedText("Bottom of the Well", true, false); - UIWidgets::EnhancementCombobox("gRandomizeMqDungeonsBottomOfTheWell", randoMqDungeonOptions, RO_MQ_SET_VANILLA); - UIWidgets::PaddedText("Ice Cavern", true, false); - UIWidgets::EnhancementCombobox("gRandomizeMqDungeonsIceCavern", randoMqDungeonOptions, RO_MQ_SET_VANILLA); - UIWidgets::PaddedText("Gerudo Training Grounds", true, false); - UIWidgets::EnhancementCombobox("gRandomizeMqDungeonsGTG", randoMqDungeonOptions, RO_MQ_SET_VANILLA); - UIWidgets::PaddedText("Ganon's Castle", true, false); - UIWidgets::EnhancementCombobox("gRandomizeMqDungeonsGanonsCastle", randoMqDungeonOptions, RO_MQ_SET_VANILLA); - } - } - - UIWidgets::PaddedSeparator(); - } - - // Triforce Hunt - UIWidgets::EnhancementCheckbox("Triforce Hunt", "gRandomizeTriforceHunt"); - UIWidgets::InsertHelpHoverText( - "Pieces of the Triforce of Courage have been scattered across the world. Find them all to finish the game!\n\n" - "When the required amount of pieces have been found, the game is saved and Ganon's Boss key is given " - "to you when you load back into the game if you desire to beat Ganon afterwards.\n\n" - "Keep in mind Ganon might not be logically beatable when \"All Locations Reachable\" is turned off." - ); - - if (CVarGetInteger("gRandomizeTriforceHunt", 0)) { - // Triforce Hunt (total pieces) - UIWidgets::Spacer(0); - int totalPieces = CVarGetInteger("gRandomizeTriforceHuntTotalPieces", 30); - ImGui::Text("Triforce Pieces in the world: %d", totalPieces); - UIWidgets::InsertHelpHoverText( - "The amount of Triforce pieces that will be placed in the world. " - "Keep in mind seed generation can fail if more pieces are placed than there are junk items in the item pool." - ); - ImGui::SameLine(); - UIWidgets::EnhancementSliderInt("", "##TriforceHuntTotalPieces", "gRandomizeTriforceHuntTotalPieces", 1, 100, "", 30); - - // Triforce Hunt (required pieces) - int requiredPieces = CVarGetInteger("gRandomizeTriforceHuntRequiredPieces", 20); - ImGui::Text("Triforce Pieces to win: %d", requiredPieces); - UIWidgets::InsertHelpHoverText( - "The amount of Triforce pieces required to win the game." - ); - ImGui::SameLine(); - UIWidgets::EnhancementSliderInt("", "##TriforceHuntRequiredPieces", "gRandomizeTriforceHuntRequiredPieces", 1, totalPieces, "", 20); - } - - UIWidgets::PaddedSeparator(); - - ImGui::EndChild(); - - // COLUMN 3 - Shuffle Entrances - ImGui::TableNextColumn(); - window->DC.CurrLineTextBaseOffset = 0.0f; - ImGui::BeginChild("ChildShuffleEntrances", ImVec2(0, -8)); - ImGui::PushItemWidth(-FLT_MIN); - - // Shuffle Dungeon Entrances - ImGui::Text("Shuffle Dungeon Entrances"); - UIWidgets::InsertHelpHoverText( - "Shuffle the pool of dungeon entrances, including Bottom of the Well, Ice Cavern and Gerudo Training Grounds.\n" - "\n" - "Shuffling Ganon's Castle can be enabled separately.\n" - "\n" - "Additionally, the entrances of Deku Tree, Fire Temple, Bottom of the Well and Gerudo Training Ground are opened for both child and adult.\n" - "\n" - "- Deku Tree will be open for adult after Mido has seen child Link with a sword and shield.\n" - "- Bottom of the Well will be open for adult after playing Song of Storms to the Windmill guy as child.\n" - "- Gerudo Training Ground will be open for child after adult has paid to open the gate once." - ); - UIWidgets::EnhancementCombobox("gRandomizeShuffleDungeonsEntrances", randoShuffleDungeonsEntrances, RO_DUNGEON_ENTRANCE_SHUFFLE_OFF); - - UIWidgets::PaddedSeparator(); - - // Shuffle Boss Entrances - ImGui::Text("Shuffle Boss Entrances"); - UIWidgets::InsertHelpHoverText( - "Shuffle the pool of dungeon boss entrances. This affects the boss rooms of all stone and medallion dungeons.\n" - "\n" - "Age Restricted - Shuffle the entrances of child and adult boss rooms separately.\n" - "\n" - "Full - Shuffle the entrances of all boss rooms together. Child may be expected to defeat Phantom Ganon and/or Bongo Bongo." - ); - UIWidgets::EnhancementCombobox("gRandomizeShuffleBossEntrances", randoShuffleBossEntrances, RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF); - - UIWidgets::PaddedSeparator(); - - // Shuffle Overworld Entrances - UIWidgets::EnhancementCheckbox("Shuffle Overworld Entrances", "gRandomizeShuffleOverworldEntrances"); - UIWidgets::InsertHelpHoverText( - "Shuffle the pool of Overworld entrances, which corresponds to almost all loading zones between overworld areas.\n" - "\n" - "Some entrances are unshuffled to avoid issues:\n" - "- Hyrule Castle Courtyard and Garden entrance\n" - "- Both Market Back Alley entrances\n" - "- Gerudo Valley to Lake Hylia (unless entrances are decoupled)" - ); - - UIWidgets::PaddedSeparator(); - - // Shuffle Interior Entrances - ImGui::Text("Shuffle Interior Entrances"); - UIWidgets::InsertHelpHoverText( - "Shuffle the pool of interior entrances which contains most Houses and all Great Fairies.\n" - "\n" - "All - An extended version of 'Simple' with some extra places:\n" - "- Windmill\n" - "- Link's House\n" - "- Temple of Time\n" - "- Kakariko Potion Shop" - ); - UIWidgets::EnhancementCombobox("gRandomizeShuffleInteriorsEntrances", randoShuffleInteriorsEntrances, RO_INTERIOR_ENTRANCE_SHUFFLE_OFF); - - UIWidgets::PaddedSeparator(); - - // Shuffle Grotto Entrances - UIWidgets::EnhancementCheckbox("Shuffle Grotto Entrances", "gRandomizeShuffleGrottosEntrances"); - UIWidgets::InsertHelpHoverText( - "Shuffle the pool of grotto entrances, including all graves, small Fairy fountains and the Deku Theatre." - ); - - UIWidgets::PaddedSeparator(); - - // Shuffle Owl Drops - UIWidgets::EnhancementCheckbox("Shuffle Owl Drops", "gRandomizeShuffleOwlDrops"); - UIWidgets::InsertHelpHoverText( - "Randomize where Kaepora Gaebora (the Owl) drops you at when you talk " - "to him at Lake Hylia or at the top of Death Mountain Trail." - ); - - UIWidgets::PaddedSeparator(); - - // Shuffle Warp Songs - UIWidgets::EnhancementCheckbox("Shuffle Warp Songs", "gRandomizeShuffleWarpSongs"); - UIWidgets::InsertHelpHoverText( - "Randomize where each of the 6 warp songs leads to." - ); - - UIWidgets::PaddedSeparator(); - - // Shuffle Overworld Spawns - UIWidgets::EnhancementCheckbox("Shuffle Overworld Spawns", "gRandomizeShuffleOverworldSpawns"); - UIWidgets::InsertHelpHoverText( - "Randomize where you start as Child or Adult when loading a save in the Overworld. This " - "means you may not necessarily spawn inside Link's House or Temple of Time.\n" - "\n" - "This stays consistent after saving and loading the game again.\n" - "\n" - "Keep in mind you may need to temporarily disable the \"Remember Save Location\" time saver to " - "be able use the spawn positions, especially if they are the only logical way to get to certain areas." - ); - - UIWidgets::PaddedSeparator(); - - // Decouple Entrances - UIWidgets::EnhancementCheckbox("Decouple Entrances", "gRandomizeDecoupleEntrances"); - UIWidgets::InsertHelpHoverText( - "Decouple entrances when shuffling them. This means you are no longer guaranteed " - "to end up back where you came from when you go back through an entrance.\n" - "\n" - "This also adds the one-way entrance from Gerudo Valley to Lake Hylia in the pool of " - "overworld entrances when they are shuffled." - ); - - UIWidgets::PaddedSeparator(); - - // Mixed Entrance Pools - UIWidgets::EnhancementCheckbox("Mixed Entrance Pools", "gRandomizeMixedEntrances"); - UIWidgets::InsertHelpHoverText( - "Shuffle entrances into a mixed pool instead of separate ones.\n" - "\n" - "For example, enabling the settings to shuffle grotto, dungeon, and overworld entrances and " - "selecting grotto and dungeon entrances here will allow a dungeon to be inside a grotto or " - "vice versa, while overworld entrances are shuffled in their own separate pool and indoors stay vanilla." - ); - - if (CVarGetInteger("gRandomizeMixedEntrances", RO_GENERIC_OFF)) { - if (CVarGetInteger("gRandomizeShuffleDungeonsEntrances", RO_GENERIC_OFF)) { - UIWidgets::Spacer(0); - ImGui::SetCursorPosX(20); - UIWidgets::EnhancementCheckbox("Mix Dungeons", "gRandomizeMixDungeons"); - UIWidgets::InsertHelpHoverText("Dungeon entrances will be part of the mixed pool"); - } - if (CVarGetInteger("gRandomizeShuffleOverworldEntrances", RO_GENERIC_OFF)) { - UIWidgets::Spacer(0); - ImGui::SetCursorPosX(20); - UIWidgets::EnhancementCheckbox("Mix Overworld", "gRandomizeMixOverworld"); - UIWidgets::InsertHelpHoverText("Overworld entrances will be part of the mixed pool"); - } - if (CVarGetInteger("gRandomizeShuffleInteriorsEntrances", RO_GENERIC_OFF)) { - UIWidgets::Spacer(0); - ImGui::SetCursorPosX(20); - UIWidgets::EnhancementCheckbox("Mix Interiors", "gRandomizeMixInteriors"); - UIWidgets::InsertHelpHoverText("Interior entrances will be part of the mixed pool"); - } - if (CVarGetInteger("gRandomizeShuffleGrottosEntrances", RO_GENERIC_OFF)) { - UIWidgets::Spacer(0); - ImGui::SetCursorPosX(20); - UIWidgets::EnhancementCheckbox("Mix Grottos", "gRandomizeMixGrottos"); - UIWidgets::InsertHelpHoverText("Grotto entrances will be part of the mixed pool"); - } - } - - ImGui::PopItemWidth(); - ImGui::EndChild(); - ImGui::EndTable(); + if (mSettings->GetOptionGroup(RSG_WORLD_IMGUI_TABLE).RenderImGui()) { + mNeedsUpdate = true; } ImGui::PopStyleVar(1); ImGui::EndTabItem(); @@ -2581,594 +1764,8 @@ void RandomizerSettingsWindow::DrawElement() { if (ImGui::BeginTabItem("Items")) { ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, cellPadding); - if (ImGui::BeginTable("tableRandoStartingInventory", 3, ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV)) { - ImGui::TableSetupColumn("Shuffle Items", ImGuiTableColumnFlags_WidthStretch, 200.0f); - ImGui::TableSetupColumn("Shuffle NPCs & Merchants", ImGuiTableColumnFlags_WidthStretch, 200.0f); - ImGui::TableSetupColumn("Shuffle Dungeon Items", ImGuiTableColumnFlags_WidthStretch, 200.0f); - ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); - ImGui::TableHeadersRow(); - ImGui::PopItemFlag(); - ImGui::TableNextRow(); - - // COLUMN 1 - Shuffle Items - ImGui::TableNextColumn(); - window->DC.CurrLineTextBaseOffset = 0.0f; - ImGui::BeginChild("ChildShuffleItems", ImVec2(0, -8)); - ImGui::PushItemWidth(-FLT_MIN); - - // Shuffle Songs - ImGui::Text("%s", ctx->GetOption(RSK_SHUFFLE_SONGS).GetName().c_str()); - UIWidgets::InsertHelpHoverText( - "Song locations - Songs will only appear at locations that normally teach songs.\n" - "\n" - "Dungeon rewards - Songs appear after beating a major dungeon boss.\n" - "The 4 remaining songs are located at:\n" - " - Zelda's lullaby location\n" - " - Ice Cavern's Serenade of Water location\n" - " - Bottom of the Well Lens of Truth location\n" - " - Gerudo Training Ground's Ice Arrows location\n" - "\n" - "Anywhere - Songs can appear at any location." - ); - UIWidgets::EnhancementCombobox("gRandomizeShuffleSongs", randoShuffleSongs, RO_SONG_SHUFFLE_SONG_LOCATIONS); - - UIWidgets::PaddedSeparator(); - - // Shuffle Tokens - ImGui::Text("%s", ctx->GetOption(RSK_SHUFFLE_TOKENS).GetName().c_str()); - UIWidgets::InsertHelpHoverText( - "Shuffles Golden Skulltula Tokens into the item pool. This means " - "Golden Skulltulas can contain other items as well.\n" - "\n" - "Off - GS tokens will not be shuffled.\n" - "\n" - "Dungeons - Only shuffle GS tokens that are within dungeons.\n" - "\n" - "Overworld - Only shuffle GS tokens that are outside of dungeons.\n" - "\n" - "All Tokens - Shuffle all 100 GS tokens." - ); - UIWidgets::EnhancementCombobox("gRandomizeShuffleTokens", randoTokensanity, RO_TOKENSANITY_OFF); - - UIWidgets::PaddedEnhancementCheckbox("Nighttime GS expect Sun's Song", "gRandomizeGsExpectSunsSong", true, false); - UIWidgets::InsertHelpHoverText( - "All Golden Skulltulas that require nighttime to appear will only be " - "expected to be collected after getting Sun's Song." - ); - - UIWidgets::PaddedSeparator(); - - // Shuffle Kokiri Sword - // Disabled when Start with Kokiri Sword is active - bool disableShuffleKokiriSword = CVarGetInteger("gRandomizeStartingKokiriSword", 0); - static const char* disableShuffleKokiriSwordText = "This option is disabled because \"Start with Kokiri Sword\" is enabled."; - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_SHUFFLE_KOKIRI_SWORD).GetName().c_str(), "gRandomizeShuffleKokiriSword", - disableShuffleKokiriSword, disableShuffleKokiriSwordText); - UIWidgets::InsertHelpHoverText( - "Shuffles the Kokiri Sword into the item pool.\n" - "\n" - "This will require the use of sticks until the Kokiri Sword is found." - ); - - UIWidgets::PaddedSeparator(); - - //Shuffle Master Sword - //RANDOTODO: Disable when Start with Master Sword is active - // bool disableShuffleMasterSword = CvarGetInteger("gRandomizeStartingMasterSword", 0); - // static const char* disableShuffleMasterSwordText = "This option is disabled because \"Start with Master Sword\" is enabled."; - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_SHUFFLE_MASTER_SWORD).GetName().c_str(), "gRandomizeShuffleMasterSword"); - UIWidgets::InsertHelpHoverText( - "Shuffles the Master Sword into the item pool.\n" - "\n" - "Adult Link will start with a second free item instead of the Master Sword.\n" - "If you haven't found the Master Sword before facing Ganon, you won't receive it during the fight." - ); - - UIWidgets::PaddedSeparator(); - - // Shuffle Ocarinas - // Disabled when Start with Ocarina is active - bool disableShuffleOcarinas = CVarGetInteger("gRandomizeStartingOcarina", 0); - static const char* disableShuffleOcarinasText = "This option is disabled because \"Start with Fairy Ocarina\" is enabled."; - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_SHUFFLE_OCARINA).GetName().c_str(), "gRandomizeShuffleOcarinas", - disableShuffleOcarinas, disableShuffleOcarinasText); - UIWidgets::InsertHelpHoverText( - "Enabling this shuffles the Fairy Ocarina and the Ocarina of Time into the item pool.\n" - "\n" - "This will require finding an Ocarina before being able to play songs." - ); - - UIWidgets::PaddedSeparator(); - - // Shuffle Weird Egg - // Disabled when Skip Child Zelda is active - bool disableShuffleWeirdEgg = CVarGetInteger("gRandomizeSkipChildZelda", 0); - static const char* disableShuffleWeirdEggText = "This option is disabled because \"Skip Child Zelda\" is enabled."; - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_SHUFFLE_WEIRD_EGG).GetName().c_str(), "gRandomizeShuffleWeirdEgg", - disableShuffleWeirdEgg, disableShuffleWeirdEggText); - UIWidgets::InsertHelpHoverText( - "Shuffles the Weird Egg from Malon in to the item pool. Enabling " - "\"Skip Child Zelda\" disables this feature.\n" - "\n" - "The Weird Egg is required to unlock several events:\n" - " - Zelda's Lullaby from Impa\n" - " - Saria's song in Sacred Forest Meadow\n" - " - Epona's song and chicken minigame at Lon Lon Ranch\n" - " - Zelda's letter for Kakariko gate (if set to closed)\n" - " - Happy Mask Shop sidequest\n" - ); - - UIWidgets::PaddedSeparator(); - - // Shuffle Gerudo Membership Card - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD).GetName().c_str(), "gRandomizeShuffleGerudoToken"); - UIWidgets::InsertHelpHoverText( - "Shuffles the Gerudo Membership Card into the item pool.\n" - "\n" - "The Gerudo Card is required to enter the Gerudo Training Grounds, opening " - "the gate to Haunted Wasteland and the Horseback Archery minigame." - ); - - UIWidgets::PaddedSeparator(); - - ImGui::PopItemWidth(); - ImGui::EndChild(); - - // COLUMN 2 - Shuffle NPCs & Merchants - ImGui::TableNextColumn(); - window->DC.CurrLineTextBaseOffset = 0.0f; - ImGui::BeginChild("ChildShuffleNpcs", ImVec2(0, -8)); - ImGui::PushItemWidth(-FLT_MIN); - - // Shopsanity - ImGui::Text("%s", ctx->GetOption(RSK_SHOPSANITY).GetName().c_str()); - UIWidgets::InsertHelpHoverText( - "Off - All shop items will be the same as vanilla.\n" - "\n" - "0 Items - Vanilla shop items will be shuffled among different shops.\n" - "\n" - "1-4 Items - Vanilla shop items will be shuffled among different shops, and each shop will contain 1-4 non-vanilla shop items.\n" - "\n" - "Random - Vanilla shop items will be shuffled among different shops, and each shop will contain a random number(1-4) of non-vanilla shop items.\n" - ); - UIWidgets::EnhancementCombobox("gRandomizeShopsanity", randoShopsanity, RO_SHOPSANITY_OFF); - - // Shopsanity Prices - switch (CVarGetInteger("gRandomizeShopsanity", RO_SHOPSANITY_OFF)) { - case RO_SHOPSANITY_OFF: - case RO_SHOPSANITY_ZERO_ITEMS: // no need to show it if there aren't shop slots in the pool - break; - default: - ImGui::Text("%s", ctx->GetOption(RSK_SHOPSANITY_PRICES).GetName().c_str()); - UIWidgets::InsertHelpHoverText( - "Balanced - The default randomization. Shop prices for shopsanity items will range between 0 to 300 rupees, " - "with a bias towards values slightly below the middle of the range, in multiples of 5.\n " - "\n" - "X Wallet - Randomized between 5 and the wallet's max size, in multiples of 5" - ); - UIWidgets::EnhancementCombobox("gRandomizeShopsanityPrices", randoShopsanityPrices, RO_SHOPSANITY_PRICE_BALANCED); - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_SHOPSANITY_PRICES_AFFORDABLE).GetName().c_str(), "gRandomizeShopsanityPricesAffordable", - CVarGetInteger("gRandomizeShopsanityPrices", RO_SHOPSANITY_PRICE_BALANCED) == RO_SHOPSANITY_PRICE_BALANCED, - "This can only apply to a wallet range."); - UIWidgets::InsertHelpHoverText("Random selection between the selected wallet tier's affordable price and the affordable prices of the preceding wallet tiers.\n\n" - "Affordable prices per tier: starter = 10, adult = 105, giant = 205, tycoon = 505\n\n" - "Use this to enable wallet tier locking, but make shop items not as expensive as they could be."); - } - - UIWidgets::PaddedSeparator(); - - // Shuffle Scrubs - ImGui::Text("%s", ctx->GetOption(RSK_SHUFFLE_SCRUBS).GetName().c_str()); - UIWidgets::InsertHelpHoverText( - "Off - Scrubs will not be shuffled. The 3 Scrubs that give one-time items in the vanilla game " - "(PoH, Deku Nut capacity, and Deku Stick capacity) will have random items.\n" - "\n" - "Affordable - Scrubs will be shuffled and their item will cost 10 rupees.\n" - "\n" - "Expensive - Scrubs will be shuffled and their item will cost the vanilla price.\n" - "\n" - "Random - Scrubs will be shuffled and their item will cost will be between 0-95 rupees.\n"); - UIWidgets::EnhancementCombobox("gRandomizeShuffleScrubs", randoShuffleScrubs, RO_SCRUBS_OFF); - - UIWidgets::PaddedSeparator(); - - // Shuffle Cows - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_SHUFFLE_COWS).GetName().c_str(), "gRandomizeShuffleCows"); - UIWidgets::InsertHelpHoverText("Cows give a randomized item from the pool upon performing Epona's Song in front of them."); - - UIWidgets::PaddedSeparator(); - - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_SHUFFLE_MAGIC_BEANS).GetName().c_str(), "gRandomizeShuffleBeans"); - UIWidgets::InsertHelpHoverText( - "Enabling this adds a pack of 10 beans to the item pool and changes the Magic Bean " - "Salesman to sell a random item at a price of 60 rupees." - ); - - UIWidgets::PaddedSeparator(); - - // Shuffle Merchants - ImGui::Text("%s", ctx->GetOption(RSK_SHUFFLE_MERCHANTS).GetName().c_str()); - UIWidgets::InsertHelpHoverText( - "Enabling this changes Medigoron, Granny and the Carpet Salesman to sell a random item " - "once at a high price (100 for Granny, 200 for the others).\n" - "A Giant's Knife and a pack of Bombchus will be added to the item pool, and " - "one of the bottles will contain a Blue Potion.\n\n" - "On (no hints) - Salesmen will be included but won't tell you what you'll get.\n" - "On (with hints) - Salesmen will be included and you'll know what you're buying.\n" - "\n" - "Granny's item will only be offered after you have traded in the Odd Mushroom when Shuffle Adult Trade is on. " - "Otherwise when off, you will need to have found the Claim Check to buy her item (simulating the trade quest is complete)." - ); - UIWidgets::EnhancementCombobox("gRandomizeShuffleMerchants", randoShuffleMerchants, RO_SHUFFLE_MERCHANTS_OFF); - - UIWidgets::PaddedSeparator(); - - // Shuffle Frog Song Rupees - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_SHUFFLE_FROG_SONG_RUPEES).GetName().c_str(), "gRandomizeShuffleFrogSongRupees"); - UIWidgets::InsertHelpHoverText( - "Shuffles 5 Purple Rupees into to the item pool, and allows\n" - "you to earn items by playing songs at the Frog Choir.\n" - "\n" - "This setting does not effect the item earned from playing\n" - "the Song of Storms and the frog song minigame." - ); - - UIWidgets::PaddedSeparator(); - - // Shuffle Adult Trade Quest - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_SHUFFLE_ADULT_TRADE).GetName().c_str(), "gRandomizeShuffleAdultTrade"); - UIWidgets::InsertHelpHoverText( - "Adds all of the adult trade quest items into the pool, each of which " - "can be traded for a unique reward.\n" - "\n" - "You will be able to choose which of your owned adult trade items is visible " - "in the inventory by selecting the item with A and using the control stick or " - "D-pad.\n" - "\n" - "If disabled, only the Claim Check will be found in the pool." - ); - - UIWidgets::PaddedSeparator(); - - // Shuffle 100 GS Reward - // Forcefully enabled if Ganon's Boss Key is on the cursed man - bool forceEnable100GSShuffle = - (CVarGetInteger("gRandomizeShuffleGanonBossKey", RO_GANON_BOSS_KEY_VANILLA) == RO_GANON_BOSS_KEY_KAK_TOKENS); - static const char* disable100GSRewardText = "This option is forcefully enabled because \"Ganon's Boss Key\" is set to \"100 GS Reward.\""; - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_SHUFFLE_100_GS_REWARD).GetName().c_str(), "gRandomizeShuffle100GSReward", - forceEnable100GSShuffle, disable100GSRewardText, UIWidgets::CheckboxGraphics::Checkmark); - UIWidgets::InsertHelpHoverText( - "Shuffle the item the cursed rich man in the House of Skulltula gives when you " - "have collected all 100 Gold Skulltula Tokens.\n" - "\n" - "You can still talk to him multiple times to get Huge Rupees." - ); - - UIWidgets::PaddedSeparator(); - - // Shuffle Boss Souls - // Forces players to find a boss's soul before defeating them in their lair. - ImGui::Text("%s", ctx->GetOption(RSK_SHUFFLE_BOSS_SOULS).GetName().c_str()); - UIWidgets::InsertHelpHoverText("Shuffles 8 boss souls (one for each blue warp dungeon). A boss will not appear until you collect its respective soul." - "\n\"On + Ganon\" will also hide Ganon and Ganondorf behind a boss soul."); - UIWidgets::EnhancementCombobox("gRandomizeShuffleBossSouls", randoShuffleBossSouls, RO_BOSS_SOULS_OFF); - - UIWidgets::PaddedSeparator(); - - ImGui::PopItemWidth(); - ImGui::EndChild(); - - // COLUMN 3 - Shuffle Dungeon Items - ImGui::TableNextColumn(); - window->DC.CurrLineTextBaseOffset = 0.0f; - ImGui::BeginChild("ChildShuffleDungeonItems", ImVec2(0, -8)); - ImGui::PushItemWidth(-FLT_MIN); - - // Shuffle Dungeon Rewards - ImGui::Text("%s", ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).GetName().c_str()); - UIWidgets::InsertHelpHoverText( - "Shuffles the location of spiritual stones and medallions.\n" - "\n" - "End of dungeons - Spiritual stones and medallions will be given as rewards " - "for beating major dungeons. Link will always start with one stone or medallion.\n" - "\n" - "Any dungeon - Spiritual stones and medallions can be found inside any dungeon.\n" - "\n" - "Overworld - Spiritual stones and medallions can only be found outside of dungeons.\n" - "\n" - "Anywhere - Spiritual stones and medallions can appear anywhere." - ); - UIWidgets::EnhancementCombobox("gRandomizeShuffleDungeonReward", randoShuffleDungeonRewards, RO_DUNGEON_REWARDS_END_OF_DUNGEON); - - UIWidgets::PaddedSeparator(); - - // Maps & Compasses - ImGui::Text("%s", ctx->GetOption(RSK_SHUFFLE_MAPANDCOMPASS).GetName().c_str()); - UIWidgets::InsertHelpHoverText( - "Start with - You will start with Maps & Compasses from all dungeons.\n" - "\n" - "Vanilla - Maps & Compasses will appear in their vanilla locations.\n" - "\n" - "Own dungeon - Maps & Compasses can only appear in their respective dungeon.\n" - "\n" - "Any dungeon - Maps & Compasses can only appear inside of any dungon.\n" - "\n" - "Overworld - Maps & Compasses can only appear outside of dungeons.\n" - "\n" - "Anywhere - Maps & Compasses can appear anywhere in the world." - ); - UIWidgets::EnhancementCombobox("gRandomizeStartingMapsCompasses", randoShuffleMapsAndCompasses, RO_DUNGEON_ITEM_LOC_OWN_DUNGEON); - - UIWidgets::PaddedSeparator(); - - // Keysanity - ImGui::Text("%s", ctx->GetOption(RSK_KEYSANITY).GetName().c_str()); - UIWidgets::InsertHelpHoverText( - "Start with - You will start with all Small Keys from all dungeons.\n" - "\n" - "Vanilla - Small Keys will appear in their vanilla locations. " - "You start with 3 keys in Spirit Temple MQ because the vanilla key layout is not beatable in logic.\n" - "\n" - "Own dungeon - Small Keys can only appear in their respective dungeon. " - "If Fire Temple is not a Master Quest dungeon, the door to the Boss Key chest will be unlocked.\n" - "\n" - "Any dungeon - Small Keys can only appear inside of any dungon.\n" - "\n" - "Overworld - Small Keys can only appear outside of dungeons.\n" - "\n" - "Anywhere - Small Keys can appear anywhere in the world." - ); - UIWidgets::EnhancementCombobox("gRandomizeKeysanity", randoShuffleSmallKeys, RO_DUNGEON_ITEM_LOC_OWN_DUNGEON); - - UIWidgets::PaddedSeparator(); - - // Key Rings - ImGui::Text("%s", ctx->GetOption(RSK_KEYRINGS).GetName().c_str()); - UIWidgets::InsertHelpHoverText( - "Keyrings will replace all small keys from a particular dungeon with a single keyring that awards all keys for it's associated dungeon\n" - "\n" - "Off - No dungeons will have their keys replaced with keyrings.\n" - "\n" - "Random - A random amount of dungeons(0-8 or 9) will have their keys replaced with keyrings.\n" - "\n" - "Count - A specified amount of randomly selected dungeons will have their keys replaced with keyrings.\n" - "\n" - "Selection - Hand select which dungeons will have their keys replaced with keyrings.\n" - "\n" - "Selecting key ring for dungeons will have no effect if Small Keys are set to Start With or Vanilla.\n" - "\n" - "If Gerudo Fortress Carpenters is set to Normal, and Gerudo Fortress Keys is set to anything " - "other than Vanilla, then the maximum amount of Key Rings that can be selected by Random or " - "Count will be 9. Otherwise, the maximum amount of Key Rings will be 8." - ); - UIWidgets::EnhancementCombobox("gRandomizeShuffleKeyRings", randoShuffleKeyRings, RO_KEYRINGS_OFF); - ImGui::PopItemWidth(); - switch (CVarGetInteger("gRandomizeShuffleKeyRings", RO_KEYRINGS_OFF)) { - case RO_KEYRINGS_COUNT: - maxKeyringCount = (CVarGetInteger("gRandomizeGerudoFortress", RO_GF_NORMAL) == RO_GF_NORMAL && - CVarGetInteger("gRandomizeGerudoKeys", RO_GERUDO_KEYS_VANILLA) != RO_GERUDO_KEYS_VANILLA) ? 9 : 8; - UIWidgets::PaddedEnhancementSliderInt("Key Ring Count: %d", - "##RandomizeShuffleKeyRingsRandomCount", - "gRandomizeShuffleKeyRingsRandomCount", 1, - maxKeyringCount, "", maxKeyringCount, true, true, false); - break; - case RO_KEYRINGS_SELECTION: - disableGFKeyring = - CVarGetInteger("gRandomizeGerudoFortress", RO_GF_NORMAL) != RO_GF_NORMAL || CVarGetInteger("gRandomizeGerudoKeys", RO_GERUDO_KEYS_VANILLA) == RO_GERUDO_KEYS_VANILLA; - UIWidgets::EnhancementCheckbox("Gerudo Fortress##RandomizeShuffleKeyRings", "gRandomizeShuffleKeyRingsGerudoFortress", - disableGFKeyring, "Disabled because the currently selected Gerudo Fortress Carpenters\n setting and/or Gerudo Fortress Keys setting is incompatible with \nhaving a Gerudo Fortress keyring."); - UIWidgets::EnhancementCheckbox("Forest Temple##RandomizeShuffleKeyRings", "gRandomizeShuffleKeyRingsForestTemple"); - UIWidgets::EnhancementCheckbox("Fire Temple##RandomizeShuffleKeyRings", "gRandomizeShuffleKeyRingsFireTemple"); - UIWidgets::EnhancementCheckbox("Water Temple##RandomizeShuffleKeyRings", "gRandomizeShuffleKeyRingsWaterTemple"); - UIWidgets::EnhancementCheckbox("Spirit Temple##RandomizeShuffleKeyRings", "gRandomizeShuffleKeyRingsSpiritTemple"); - UIWidgets::EnhancementCheckbox("Shadow Temple##RandomizeShuffleKeyRings", "gRandomizeShuffleKeyRingsShadowTemple"); - UIWidgets::EnhancementCheckbox("Bottom of the Well##RandomizeShuffleKeyRings", "gRandomizeShuffleKeyRingsBottomOfTheWell"); - UIWidgets::EnhancementCheckbox("Gerudo Training Grounds##RandomizeShuffleKeyRings", "gRandomizeShuffleKeyRingsGTG"); - UIWidgets::EnhancementCheckbox("Ganon's Castle##RandomizeShuffleKeyRings", "gRandomizeShuffleKeyRingsGanonsCastle"); - break; - default: - break; - } - ImGui::PushItemWidth(-FLT_MIN); - - UIWidgets::PaddedSeparator(); - - // Gerudo Keys - ImGui::Text("%s", ctx->GetOption(RSK_GERUDO_KEYS).GetName().c_str()); - UIWidgets::InsertHelpHoverText( - "Vanilla - Thieve's Hideout Keys will appear in their vanilla locations.\n" - "\n" - "Any dungeon - Thieve's Hideout Keys can only appear inside of any dungon.\n" - "\n" - "Overworld - Thieve's Hideout Keys can only appear outside of dungeons.\n" - "\n" - "Anywhere - Thieve's Hideout Keys can appear anywhere in the world." - ); - UIWidgets::EnhancementCombobox("gRandomizeGerudoKeys", randoShuffleGerudoFortressKeys, RO_GERUDO_KEYS_VANILLA); - - UIWidgets::PaddedSeparator(); - - // Boss Keysanity - ImGui::Text("%s", ctx->GetOption(RSK_BOSS_KEYSANITY).GetName().c_str()); - UIWidgets::InsertHelpHoverText( - "Start with - You will start with Boss keys from all dungeons.\n" - "\n" - "Vanilla - Boss Keys will appear in their vanilla locations.\n" - "\n" - "Own dungeon - Boss Keys can only appear in their respective dungeon.\n" - "\n" - "Any dungeon - Boss Keys can only appear inside of any dungon.\n" - "\n" - "Overworld - Boss Keys can only appear outside of dungeons.\n" - "\n" - "Anywhere - Boss Keys can appear anywhere in the world." - ); - UIWidgets::EnhancementCombobox("gRandomizeBossKeysanity", randoShuffleBossKeys, RO_DUNGEON_ITEM_LOC_OWN_DUNGEON); - - UIWidgets::PaddedSeparator(); - - // Ganon's Boss Key - ImGui::Text("%s", ctx->GetOption(RSK_GANONS_BOSS_KEY).GetName().c_str()); - UIWidgets::InsertHelpHoverText( - "Vanilla - Ganon's Boss Key will appear in the vanilla location.\n" - "\n" - "Own dungeon - Ganon's Boss Key can appear anywhere inside Ganon's Castle.\n" - "\n" - "Start with - Places Ganon's Boss Key in your starting inventory." - "\n" - "Any dungeon - Ganon's Boss Key Key can only appear inside of any dungon.\n" - "\n" - "Overworld - Ganon's Boss Key Key can only appear outside of dungeons.\n" - "\n" - "Anywhere - Ganon's Boss Key Key can appear anywhere in the world.\n" - "\n" - "LACS - These settings put the boss key on the Light Arrow Cutscene location, from Zelda in Temple of Time as adult, with differing requirements:\n" - "- Vanilla: Obtain the Shadow Medallion and Spirit Medallion\n" - "- Stones: Obtain the specified amount of spiritual stones.\n" - "- Medallions: Obtain the specified amount of medallions.\n" - "- Dungeon rewards: Obtain the specified total sum of spiritual stones or medallions.\n" - "- Dungeons: Complete the specified amount of dungeons. Dungeons are considered complete after stepping in to the blue warp after the boss.\n" - "- Tokens: Obtain the specified amount of Skulltula tokens.\n" - "\n" - "100 GS Reward - Ganon's Boss Key will be awarded by the cursed rich man after you collect 100 Gold Skulltula Tokens." - ); - bool disableGBK = CVarGetInteger("gRandomizeTriforceHunt", 0); - static const char* disableGBKText = "This option is disabled because Triforce Hunt is enabled. Ganon's Boss key\nwill instead be given to you after Triforce Hunt completion."; - UIWidgets::EnhancementCombobox("gRandomizeShuffleGanonBossKey", randoShuffleGanonsBossKey, - RO_GANON_BOSS_KEY_VANILLA, disableGBK, disableGBKText, - RO_GANON_BOSS_KEY_VANILLA); - ImGui::PopItemWidth(); - switch (CVarGetInteger("gRandomizeShuffleGanonBossKey", RO_GANON_BOSS_KEY_VANILLA)) { - case RO_GANON_BOSS_KEY_LACS_STONES: - ImGui::Text("Reward Options"); - UIWidgets::InsertHelpHoverText( - "Standard Rewards - Greg does not change logic, Greg does not help obtain GBK, max " - "number of rewards on slider does not change.\n" - "\n" - "Greg as Reward - Greg does change logic (can be part of expected path for obtaining " - "GBK), Greg helps obtain GBK, max number of rewards on slider increases by 1 to " - "account for Greg. \n" - "\n" - "Greg as Wildcard - Greg does not change logic, Greg helps obtain GBK, max number of " - "rewards on slider does not change."); - - UIWidgets::EnhancementCombobox("gRandomizeLacsRewardOptions", randoLACSRewardOptions, RO_LACS_STANDARD_REWARD); - switch (CVarGetInteger("gRandomizeLacsRewardOptions", RO_LACS_STANDARD_REWARD)) { - case RO_LACS_STANDARD_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Stone Count: %d", "##RandoLacsStoneCount", - "gRandomizeLacsStoneCount", 1, 3, "", 3, true, true, false); - break; - case RO_LACS_GREG_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Stone Count: %d", "##RandoLacsStoneCount", - "gRandomizeLacsStoneCount", 1, 4, "", 4, true, true, false); - break; - case RO_LACS_WILDCARD_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Stone Count: %d", "##RandoLacsStoneCount", - "gRandomizeLacsStoneCount", 1, 3, "", 3, true, true, false); - break; - } - break; - case RO_GANON_BOSS_KEY_LACS_MEDALLIONS: - ImGui::Text("Reward Options"); - UIWidgets::InsertHelpHoverText( - "Standard Rewards - Greg does not change logic, Greg does not help obtain GBK, max " - "number of rewards on slider does not change.\n" - "\n" - "Greg as Reward - Greg does change logic (can be part of expected path for obtaining " - "GBK), Greg helps obtain GBK, max number of rewards on slider increases by 1 to " - "account for Greg. \n" - "\n" - "Greg as Wildcard - Greg does not change logic, Greg helps obtain GBK, max number of " - "rewards on slider does not change."); - - UIWidgets::EnhancementCombobox("gRandomizeLacsRewardOptions", randoLACSRewardOptions, RO_LACS_STANDARD_REWARD); - switch (CVarGetInteger("gRandomizeLacsRewardOptions", RO_LACS_STANDARD_REWARD)) { - case RO_LACS_STANDARD_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Medallion Count: %d", "##RandoLacsMedallionCount", - "gRandomizeLacsMedallionCount", 1, 6, "", 6, true, true, false); - break; - case RO_LACS_GREG_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Medallion Count: %d", "##RandoLacsMedallionCount", - "gRandomizeLacsMedallionCount", 1, 7, "", 7, true, true, false); - break; - case RO_LACS_WILDCARD_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Medallion Count: %d", "##RandoLacsMedallionCount", - "gRandomizeLacsMedallionCount", 1, 6, "", 6, true, true, false); - break; - } - break; - case RO_GANON_BOSS_KEY_LACS_REWARDS: - ImGui::Text("Reward Options"); - UIWidgets::InsertHelpHoverText( - "Standard Rewards - Greg does not change logic, Greg does not help obtain GBK, max " - "number of rewards on slider does not change.\n" - "\n" - "Greg as Reward - Greg does change logic (can be part of expected path for obtaining " - "GBK), Greg helps obtain GBK, max number of rewards on slider increases by 1 to " - "account for Greg. \n" - "\n" - "Greg as Wildcard - Greg does not change logic, Greg helps obtain GBK, max number of " - "rewards on slider does not change."); - - UIWidgets::EnhancementCombobox("gRandomizeLacsRewardOptions", randoLACSRewardOptions, RO_LACS_STANDARD_REWARD); - switch (CVarGetInteger("gRandomizeLacsRewardOptions", RO_LACS_STANDARD_REWARD)) { - case RO_LACS_STANDARD_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Reward Count: %d", "##RandoLacsRewardCount", - "gRandomizeLacsRewardCount", 1, 9, "", 9, true, true, false); - break; - case RO_LACS_GREG_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Reward Count: %d", "##RandoLacsRewardCount", - "gRandomizeLacsRewardCount", 1, 10, "", 10, true, true, false); - break; - case RO_LACS_WILDCARD_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Reward Count: %d", "##RandoLacsRewardCount", - "gRandomizeLacsRewardCount", 1, 9, "", 9, true, true, false); - break; - } - break; - case RO_GANON_BOSS_KEY_LACS_DUNGEONS: - ImGui::Text("Reward Options"); - UIWidgets::InsertHelpHoverText( - "Standard Rewards - Greg does not change logic, Greg does not help obtain GBK, max " - "number of rewards on slider does not change.\n" - "\n" - "Greg as Reward - Greg does change logic (can be part of expected path for obtaining " - "GBK), Greg helps obtain GBK, max number of rewards on slider increases by 1 to " - "account for Greg. \n" - "\n" - "Greg as Wildcard - Greg does not change logic, Greg helps obtain GBK, max number of " - "rewards on slider does not change."); - - UIWidgets::EnhancementCombobox("gRandomizeLacsRewardOptions", randoLACSRewardOptions, RO_LACS_STANDARD_REWARD); - switch (CVarGetInteger("gRandomizeLacsRewardOptions", RO_LACS_STANDARD_REWARD)) { - case RO_LACS_STANDARD_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Dungeon Count: %d", "##RandoLacsDungeonCount", - "gRandomizeLacsDungeonCount", 1, 8, "", 8, true, true, false); - break; - case RO_LACS_GREG_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Dungeon Count: %d", "##RandoLacsDungeonCount", - "gRandomizeLacsDungeonCount", 1, 9, "", 9, true, true, false); - break; - case RO_LACS_WILDCARD_REWARD: - UIWidgets::PaddedEnhancementSliderInt("Dungeon Count: %d", "##RandoLacsDungeonCount", - "gRandomizeLacsDungeonCount", 1, 8, "", 8, true, true, false); - break; - } - break; - case RO_GANON_BOSS_KEY_LACS_TOKENS: - UIWidgets::PaddedEnhancementSliderInt("Token Count: %d", "##RandoLacsTokenCount", - "gRandomizeLacsTokenCount", 1, 100, "", 100, true, true, false); - break; - default: - break; - } - ImGui::PushItemWidth(-FLT_MIN); - - UIWidgets::PaddedSeparator(); - - ImGui::PopItemWidth(); - ImGui::EndChild(); - ImGui::EndTable(); + if (mSettings->GetOptionGroup(RSG_ITEMS_IMGUI_TABLE).RenderImGui()) { + mNeedsUpdate = true; } ImGui::PopStyleVar(1); ImGui::EndTabItem(); @@ -3176,269 +1773,8 @@ void RandomizerSettingsWindow::DrawElement() { if (ImGui::BeginTabItem("Gameplay")) { ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, cellPadding); - if (ImGui::BeginTable("tableRandoGameplay", 3, ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV)) { - ImGui::TableSetupColumn("Timesavers", ImGuiTableColumnFlags_WidthStretch, 200.0f); - ImGui::TableSetupColumn("Item Pool & Hints", ImGuiTableColumnFlags_WidthStretch, 200.0f); - ImGui::TableSetupColumn("Additional Features", ImGuiTableColumnFlags_WidthStretch, 200.0f); - ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); - ImGui::TableHeadersRow(); - ImGui::PopItemFlag(); - ImGui::TableNextRow(); - - // COLUMN 1 - TIME SAVERS - ImGui::TableNextColumn(); - window->DC.CurrLineTextBaseOffset = 0.0f; - ImGui::BeginChild("ChildTimeSavers", ImVec2(0, -8)); - - // Cuccos to return - int cuccos = CVarGetInteger("gRandomizeCuccosToReturn", 7); - ImGui::Text("Cuccos to return: %d", cuccos); - UIWidgets::InsertHelpHoverText("The amount of cuccos needed to claim the reward from Anju the cucco lady"); - ImGui::SameLine(); - UIWidgets::EnhancementSliderInt("", "##RandoCuccosToReturn", "gRandomizeCuccosToReturn", 0, 7, "", 7); - UIWidgets::PaddedSeparator(); - - // Big Poe Target Count - int poes = CVarGetInteger("gRandomizeBigPoeTargetCount", 10); - ImGui::Text("Big Poe Target Count: %d", poes); - UIWidgets::InsertHelpHoverText("The Poe collector will give a reward for turning in this many Big Poes."); - ImGui::SameLine(); - UIWidgets::EnhancementSliderInt("", "##RandoBigPoeTargetCount", "gRandomizeBigPoeTargetCount", 1, 10, "", 10); - UIWidgets::PaddedSeparator(); - - // Skip child stealth - // Disabled when Skip Child Zelda is active - bool disableChildStealth = CVarGetInteger("gRandomizeSkipChildZelda", 0); - static const char* disableChildStealthText = "This option is disabled because \"Skip Child Zelda\" is enabled"; - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_SKIP_CHILD_STEALTH).GetName().c_str(), "gRandomizeSkipChildStealth", disableChildStealth, disableChildStealthText); - UIWidgets::InsertHelpHoverText("The crawlspace into Hyrule Castle goes straight to Zelda, skipping the guards."); - - UIWidgets::PaddedSeparator(); - - // Skip child zelda - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_SKIP_CHILD_ZELDA).GetName().c_str(), "gRandomizeSkipChildZelda"); - UIWidgets::InsertHelpHoverText("Start with Zelda's Letter and the item Impa would normally give you and skip the sequence up " - "until after meeting Zelda. Disables the ability to shuffle Weird Egg."); - - UIWidgets::PaddedSeparator(); - - // Skip Epona race - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_SKIP_EPONA_RACE).GetName().c_str(), "gRandomizeSkipEponaRace"); - UIWidgets::InsertHelpHoverText("Epona can be summoned with Epona's Song without needing to race Ingo."); - - UIWidgets::PaddedSeparator(); - - // Skip tower escape - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_SKIP_TOWER_ESCAPE).GetName().c_str(), "gRandomizeSkipTowerEscape"); - UIWidgets::InsertHelpHoverText("The tower escape sequence between Ganondorf and Ganon will be skipped."); - - UIWidgets::PaddedSeparator(); - - // Complete mask quest - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_COMPLETE_MASK_QUEST).GetName().c_str(), "gRandomizeCompleteMaskQuest"); - UIWidgets::InsertHelpHoverText("Once the happy mask shop is opened, all masks will be available to be borrowed."); - - UIWidgets::PaddedSeparator(); - - // Skip Scarecrow Song - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_SKIP_SCARECROWS_SONG).GetName().c_str(), "gRandomizeSkipScarecrowsSong"); - UIWidgets::InsertHelpHoverText( - "Start with the ability to summon Pierre the scarecrow. Pulling out an ocarina in the usual locations will automatically summon him." - ); - - UIWidgets::PaddedSeparator(); - - ImGui::EndChild(); - - // COLUMN 2 - Item Pool & Hint Settings - ImGui::TableNextColumn(); - window->DC.CurrLineTextBaseOffset = 0.0f; - ImGui::BeginChild("ChildItemPoolHintSettings", ImVec2(0, -8)); - ImGui::PushItemWidth(-FLT_MIN); - - // Item Pool Settings - ImGui::Text("%s", ctx->GetOption(RSK_ITEM_POOL).GetName().c_str()); - UIWidgets::InsertHelpHoverText( - "Sets how many major items appear in the item pool.\n" - "\n" - "Plentiful - Extra major items are added to the pool.\n" - "\n" - "Balanced - Original item pool.\n" - "\n" - "Scarce - Some excess items are removed, including health upgrades.\n" - "\n" - "Minimal - Most excess items are removed." - ); - UIWidgets::EnhancementCombobox("gRandomizeItemPool", randoItemPool, RO_ITEM_POOL_BALANCED); - UIWidgets::PaddedSeparator(); - - // Ice Traps - ImGui::Text("%s", ctx->GetOption(RSK_ICE_TRAPS).GetName().c_str()); - UIWidgets::InsertHelpHoverText( - "Sets how many items are replaced by ice traps.\n" - "\n" - "Off - No ice traps.\n" - "\n" - "Normal - Only Ice Traps from the base item pool are shuffled in.\n" - "\n" - "Extra - Chance to replace added junk items with additional ice traps.\n" - "\n" - "Mayhem - All added junk items will be Ice Traps.\n" - "\n" - "Onslaught - All junk items will be replaced by Ice Traps, even those " - "in the base pool." - ); - UIWidgets::EnhancementCombobox("gRandomizeIceTraps", randoIceTraps, RO_ICE_TRAPS_NORMAL); - - UIWidgets::PaddedSeparator(); - - // Gossip Stone Hints - ImGui::Text("%s", ctx->GetOption(RSK_GOSSIP_STONE_HINTS).GetName().c_str()); - UIWidgets::InsertHelpHoverText( - "Allows Gossip Stones to provide hints on item locations. Hints mentioning " - "\"Way of the Hero\" indicate a location that holds an item required to beat " - "the seed.\n" - "\n" - "No hints - No hints will be given at all.\n" - "\n" - "Need Nothing - Hints are always available from Gossip Stones.\n" - "\n" - "Need Stone of Agony - Hints are only available after obtaining the Stone of Agony.\n" - "\n" - "Need Mask of Truth - Hints are only available whilst wearing the Mask of Truth.\n"); - UIWidgets::EnhancementCombobox("gRandomizeGossipStoneHints", randoGossipStoneHints, RO_GOSSIP_STONES_NEED_NOTHING); - if (CVarGetInteger("gRandomizeGossipStoneHints", RO_GOSSIP_STONES_NEED_NOTHING) != RO_GOSSIP_STONES_NONE) { - // Hint Clarity - UIWidgets::Spacer(0); - ImGui::Indent(); - ImGui::Text("%s", ctx->GetOption(RSK_HINT_CLARITY).GetName().c_str()); - UIWidgets::InsertHelpHoverText( - "Sets the difficulty of hints.\n" - "\n" - "Obscure - Hints are unique for each item, but the writing may be cryptic.\n" - "Ex: Kokiri Sword > a butter knife\n" - "\n" - "Ambiguous - Hints are clearly written, but may refer to more than one item.\n" - "Ex: Kokiri Sword > a sword\n" - "\n" - "Clear - Hints are clearly written and are unique for each item.\n" - "Ex: Kokiri Sword > the Kokiri Sword" - ); - UIWidgets::EnhancementCombobox("gRandomizeHintClarity", randoHintClarity, RO_HINT_CLARITY_CLEAR); - - // Hint Distribution - UIWidgets::Spacer(0); - ImGui::Text("%s", ctx->GetOption(RSK_HINT_DISTRIBUTION).GetName().c_str()); - UIWidgets::InsertHelpHoverText( - "Sets how many hints will be useful.\n" - "\n" - "Useless - Only junk hints.\n" - "\n" - "Balanced - Recommended hint spread.\n" - "\n" - "Strong - More useful hints.\n" - "\n" - "Very Strong - Many powerful hints." - ); - UIWidgets::EnhancementCombobox("gRandomizeHintDistribution", randoHintDistribution, RO_HINT_DIST_BALANCED); - ImGui::Unindent(); - } - - UIWidgets::PaddedSeparator(); - - //Extra Hints - ImGui::Text("Extra Hints"); - UIWidgets::InsertHelpHoverText( - "This setting adds some hints at locations other than Gossip Stones.\n\n" - "House of Skulltula: # - Talking to a cursed House of Skulltula resident will tell you the reward they will give you for obtaining that many tokens." - ); - - ImGui::Indent(); - //Altar, Light Arrows, and Warp Songs are enabled by default - UIWidgets::PaddedEnhancementCheckbox("Altar Text", "gRandomizeAltarHint", true, false, false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::InsertHelpHoverText("Reading the Temple of Time altar as child will tell you the locations of the Spiritual Stones.\n" - "Reading the Temple of Time altar as adult will tell you the locations of the Medallions, as well as the conditions for building the Rainbow Bridge and getting the Boss Key for Ganon's Castle."); - UIWidgets::PaddedEnhancementCheckbox("Light Arrows", "gRandomizeLAHint", true, false, false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::InsertHelpHoverText("Talking to Ganondorf in his boss room or Sheik inside Ganon's Castle (when trials are enabled) will tell you the location of the Light Arrows." - "If this option is enabled and Ganondorf is reachable without Light Arrows, Gossip Stones will never hint the Light Arrows."); - UIWidgets::PaddedEnhancementCheckbox("Dampe's Diary (Hookshot)", "gRandomizeDampeHint", true, false); - UIWidgets::InsertHelpHoverText("Reading the diary of Dampé the gravekeeper as adult will tell you the location of one of the Hookshots."); - UIWidgets::PaddedEnhancementCheckbox("Greg the Green Rupee", "gRandomizeGregHint", true, false); - UIWidgets::InsertHelpHoverText("Talking to the chest game owner after buying a key will tell you the location of Greg the Green Rupee."); - UIWidgets::PaddedEnhancementCheckbox("Saria (Magic)", "gRandomizeSariaHint", true, false); - UIWidgets::InsertHelpHoverText("Talking to Saria either in person or through Saria's Song will tell you the location of a progressive magic meter."); - UIWidgets::PaddedEnhancementCheckbox("Frog Ocarina Game", "gRandomizeFrogsHint", true, false); - UIWidgets::InsertHelpHoverText("Standing near the pedestal for the frogs in Zora's River will tell you the reward for the frogs' ocarina game."); - UIWidgets::PaddedEnhancementCheckbox("Warp Song text", "gRandomizeWarpSongText", true, false, !CVarGetInteger("gRandomizeShuffleWarpSongs", RO_GENERIC_OFF), - "This option is disabled since warp songs are not shuffled.", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::InsertHelpHoverText("Playing a warp song will tell you where it leads. (If warp song destinations are vanilla, this is always enabled.)"); - UIWidgets::PaddedEnhancementCheckbox("Scrub Item text", "gRandomizeScrubText", true, false, false, "", UIWidgets::CheckboxGraphics::Cross, false); - UIWidgets::InsertHelpHoverText("Business scrubs will reveal the identity of what they're selling."); - UIWidgets::PaddedEnhancementCheckbox("House of Skulltula: 10", "gRandomize10GSHint", true, false); - UIWidgets::PaddedEnhancementCheckbox("House of Skulltula: 20", "gRandomize20GSHint", true, false); - UIWidgets::PaddedEnhancementCheckbox("House of Skulltula: 30", "gRandomize30GSHint", true, false); - UIWidgets::PaddedEnhancementCheckbox("House of Skulltula: 40", "gRandomize40GSHint", true, false); - UIWidgets::PaddedEnhancementCheckbox("House of Skulltula: 50", "gRandomize50GSHint", true, false); - ImGui::Unindent(); - - - UIWidgets::PaddedSeparator(); - - ImGui::PopItemWidth(); - ImGui::EndChild(); - - // COLUMN 3 - Additional Features - ImGui::TableNextColumn(); - window->DC.CurrLineTextBaseOffset = 0.0f; - ImGui::BeginChild("ChildAdditionalFeatures", ImVec2(0, -8)); - ImGui::PushItemWidth(-FLT_MIN); - - UIWidgets::EnhancementCheckbox("Full Wallets", "gRandomizeFullWallets"); - UIWidgets::InsertHelpHoverText("Start with a full wallet. All wallet upgrades come filled with rupees."); - - UIWidgets::PaddedSeparator(); - - // Bombchus in Logic - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_BOMBCHUS_IN_LOGIC).GetName().c_str(), "gRandomizeBombchusInLogic"); - UIWidgets::InsertHelpHoverText( - "Bombchus are properly considered in logic.\n" - "\n" - "The first Bombchu pack will always be 20, and subsequent packs will be " - "5 or 10 based on how many you have.\n" - "Once found, they can be replenished at the Bombchu shop.\n" - "\n" - "Bombchu Bowling is opened by obtaining Bombchus." - ); - - UIWidgets::PaddedSeparator(); - - // Enable Bombchu Drops - UIWidgets::EnhancementCheckbox("Enable Bombchu Drops", "gRandomizeEnableBombchuDrops"); - UIWidgets::InsertHelpHoverText( - "Once you obtain bombchus for the first time, refills can be found in bushes and other places where bomb drops can normally spawn." - ); - - UIWidgets::PaddedSeparator(); - - UIWidgets::EnhancementCheckbox("Blue Fire Arrows", "gRandomizeBlueFireArrows"); - UIWidgets::InsertHelpHoverText( - "Ice Arrows act like Blue Fire, making them able to melt red ice. " - "Item placement logic will respect this option, so it might be required to use this to progress." - ); - - UIWidgets::PaddedSeparator(); - - UIWidgets::EnhancementCheckbox("Sunlight Arrows", "gRandomizeSunlightArrows"); - UIWidgets::InsertHelpHoverText( - "Light Arrows can be used to light up the sun switches instead of using the Mirror Shield. " - "Item placement logic will respect this option, so it might be required to use this to progress." - ); - - UIWidgets::PaddedSeparator(); - - ImGui::PopItemWidth(); - ImGui::EndChild(); - ImGui::EndTable(); + if (mSettings->GetOptionGroup(RSG_GAMEPLAY_IMGUI_TABLE).RenderImGui()) { + mNeedsUpdate = true; } ImGui::PopStyleVar(1); ImGui::EndTabItem(); @@ -3589,35 +1925,22 @@ void RandomizerSettingsWindow::DrawElement() { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::PushItemWidth(170.0); - ImGui::Text("Logic Rules"); - UIWidgets::InsertHelpHoverText( - "Glitchless - No glitches are required, but may require some minor tricks. Additional tricks may be enabled and disabled below.\n" - "\n" - //"Glitched - Glitches may be required to beat the game. You can disable and enable glitches below.\n" - //"\n" - "No logic - Item placement is completely random. MAY BE IMPOSSIBLE TO BEAT." - ); - UIWidgets::EnhancementCombobox("gRandomizeLogicRules", randoLogicRules, RO_LOGIC_GLITCHLESS); + if (mSettings->GetOption(RSK_LOGIC_RULES).RenderImGui()) { + mNeedsUpdate = true; + } if (CVarGetInteger("gRandomizeLogicRules", RO_LOGIC_GLITCHLESS) != RO_LOGIC_NO_LOGIC) { ImGui::SameLine(); - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_ALL_LOCATIONS_REACHABLE).GetName().c_str(), "gRandomizeAllLocationsReachable", false, "", UIWidgets::CheckboxGraphics::Cross, RO_GENERIC_ON); - UIWidgets::InsertHelpHoverText( - "When this options is enabled, the randomizer will " - "guarantee that every item is obtainable and every " - "location is reachable. When disabled, only " - "required items and locations to beat the game " - "will be guaranteed reachable." - ); + if (mSettings->GetOption(RSK_ALL_LOCATIONS_REACHABLE).RenderImGui()) { + mNeedsUpdate = true; + } } UIWidgets::PaddedSeparator(); // Enable Glitch-Useful Cutscenes - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_ENABLE_GLITCH_CUTSCENES).GetName().c_str(), "gRandomizeEnableGlitchCutscenes"); - UIWidgets::InsertHelpHoverText( - "The cutscenes of the Poes in Forest Temple and Darunia in Fire Temple will not be skipped. " - "These cutscenes are only useful for glitched gameplay and can be safely skipped otherwise."); - + if (mSettings->GetOption(RSK_ENABLE_GLITCH_CUTSCENES).RenderImGui()) { + mNeedsUpdate = true; + } ImGui::PopItemWidth(); ImGui::EndTable(); } @@ -4002,73 +2325,8 @@ void RandomizerSettingsWindow::DrawElement() { if (ImGui::BeginTabItem("Starting Inventory")) { ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, cellPadding); - if (ImGui::BeginTable("tableRandoStartingInventory", 3, ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV)) { - ImGui::TableSetupColumn("Starting Equipment", ImGuiTableColumnFlags_WidthStretch, 200.0f); - ImGui::TableSetupColumn("Starting Items", ImGuiTableColumnFlags_WidthStretch, 200.0f); - ImGui::TableSetupColumn("Starting Songs", ImGuiTableColumnFlags_WidthStretch, 200.0f); - ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); - ImGui::TableHeadersRow(); - ImGui::PopItemFlag(); - ImGui::TableNextRow(); - - // COLUMN 1 - STARTING EQUIPMENT - ImGui::TableNextColumn(); - window->DC.CurrLineTextBaseOffset = 0.0f; - ImGui::BeginChild("ChildStartingEquipment", ImVec2(0, -8)); - // Don't display this option if Dungeon Rewards are Shuffled to End of Dungeon. - // TODO: Show this but disabled when we have options for disabled Comboboxes. - if (CVarGetInteger("gRandomizeShuffleDungeonReward", RO_DUNGEON_REWARDS_END_OF_DUNGEON) != RO_DUNGEON_REWARDS_END_OF_DUNGEON) { - ImGui::Text("%s", ctx->GetOption(RSK_LINKS_POCKET).GetName().c_str()); - UIWidgets::EnhancementCombobox("gRandomizeLinksPocket", randoLinksPocket, RO_LINKS_POCKET_DUNGEON_REWARD); - UIWidgets::PaddedSeparator(); - } - - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_STARTING_KOKIRI_SWORD).GetName().c_str(), "gRandomizeStartingKokiriSword"); - UIWidgets::PaddedSeparator(); - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_STARTING_DEKU_SHIELD).GetName().c_str(), "gRandomizeStartingDekuShield"); - - UIWidgets::PaddedSeparator(); - - ImGui::EndChild(); - - // COLUMN 2 - STARTING ITEMS - ImGui::TableNextColumn(); - window->DC.CurrLineTextBaseOffset = 0.0f; - ImGui::BeginChild("ChildStartingItems", ImVec2(0, -8)); - - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_STARTING_OCARINA).GetName().c_str(), "gRandomizeStartingOcarina"); - UIWidgets::PaddedSeparator(); - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_STARTING_CONSUMABLES).GetName().c_str(), "gRandomizeStartingConsumables"); - UIWidgets::PaddedSeparator(); - UIWidgets::EnhancementSliderInt("Gold Skulltula Tokens: %d", "##RandoStartingSkulltulaToken", "gRandomizeStartingSkulltulaToken", 0, 100, "", 0); - UIWidgets::PaddedSeparator(); - - ImGui::EndChild(); - - // COLUMN 3 - STARTING SONGS - ImGui::TableNextColumn(); - window->DC.CurrLineTextBaseOffset = 0.0f; - ImGui::BeginChild("ChildStartingSongs", ImVec2(0, -8)); - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_STARTING_ZELDAS_LULLABY).GetName().c_str(), "gRandomizeStartingZeldasLullaby"); - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_STARTING_EPONAS_SONG).GetName().c_str(), "gRandomizeStartingEponasSong"); - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_STARTING_SARIAS_SONG).GetName().c_str(), "gRandomizeStartingSariasSong"); - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_STARTING_SUNS_SONG).GetName().c_str(), "gRandomizeStartingSunsSong"); - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_STARTING_SONG_OF_TIME).GetName().c_str(), "gRandomizeStartingSongOfTime"); - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_STARTING_SONG_OF_STORMS).GetName().c_str(), "gRandomizeStartingSongOfStorms"); - UIWidgets::PaddedSeparator(); - - ImGui::Text("Warp Songs"); - UIWidgets::PaddedSeparator(); - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_STARTING_MINUET_OF_FOREST).GetName().c_str(), "gRandomizeStartingMinuetOfForest"); - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_STARTING_BOLERO_OF_FIRE).GetName().c_str(), "gRandomizeStartingBoleroOfFire"); - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_STARTING_SERENADE_OF_WATER).GetName().c_str(), "gRandomizeStartingSerenadeOfWater"); - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_STARTING_REQUIEM_OF_SPIRIT).GetName().c_str(), "gRandomizeStartingRequiemOfSpirit"); - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_STARTING_NOCTURNE_OF_SHADOW).GetName().c_str(), "gRandomizeStartingNocturneOfShadow"); - UIWidgets::EnhancementCheckbox(ctx->GetOption(RSK_STARTING_PRELUDE_OF_LIGHT).GetName().c_str(), "gRandomizeStartingPreludeOfLight"); - UIWidgets::PaddedSeparator(); - - ImGui::EndChild(); - ImGui::EndTable(); + if (mSettings->GetOptionGroup(RSG_STARTING_INVENTORY_IMGUI_TABLE).RenderImGui()) { + mNeedsUpdate = true; } ImGui::PopStyleVar(1); ImGui::EndTabItem(); @@ -4083,6 +2341,12 @@ void RandomizerSettingsWindow::DrawElement() { ImGui::End(); } +void RandomizerSettingsWindow::UpdateElement() { + if (mNeedsUpdate) { + mSettings->UpdateOptionProperties(); + } +} + CustomMessage Randomizer::GetWarpSongMessage(u16 textId, bool mysterious) { CustomMessage messageEntry = CustomMessageManager::Instance->RetrieveMessage( Randomizer::hintMessageTableID, TEXT_WARP_RANDOM_REPLACED_TEXT); @@ -4981,7 +3245,7 @@ void Randomizer::CreateCustomMessages() { GIMESSAGE_UNTRANSLATED(RG_BONGO_BONGO_SOUL, ITEM_BIG_POE, "You found the soul for %pBongo&Bongo%w!"), GIMESSAGE_UNTRANSLATED(RG_TWINROVA_SOUL, ITEM_BIG_POE, "You found the soul for %yTwinrova%w!"), GIMESSAGE_UNTRANSLATED(RG_GANON_SOUL, ITEM_BIG_POE, "You found the soul for %cGanon%w!"), - + }}; CreateGetItemMessages(&getItemMessages); CreateRupeeMessages(); @@ -5004,6 +3268,8 @@ class ExtendedVanillaTableInvalidItemIdException: public std::exception { }; void RandomizerSettingsWindow::InitElement() { + mSettings = Rando::Context::GetInstance()->GetSettings(); Randomizer::CreateCustomMessages(); seedString = (char*)calloc(MAX_SEED_STRING_SIZE, sizeof(char)); + Rando::Context::GetInstance()->GetSettings()->UpdateOptionProperties(); } diff --git a/soh/soh/Enhancements/randomizer/randomizerTypes.h b/soh/soh/Enhancements/randomizer/randomizerTypes.h index c91d0633b..52a86f649 100644 --- a/soh/soh/Enhancements/randomizer/randomizerTypes.h +++ b/soh/soh/Enhancements/randomizer/randomizerTypes.h @@ -3317,6 +3317,26 @@ typedef enum { RSG_EXCLUDES, RSG_TRICKS, RSG_GLITCHES, + RSG_AREA_ACCESS_IMGUI, + RSG_WORLD_IMGUI, + RSG_SHUFFLE_ENTRANCES_IMGUI, + RSG_WORLD_IMGUI_TABLE, + RSG_SHUFFLE_ITEMS_IMGUI, + RSG_SHUFFLE_NPCS_IMGUI, + RSG_SHUFFLE_DUNGEON_ITEMS_IMGUI, + RSG_ITEMS_IMGUI_TABLE, + RSG_TIMESAVERS_IMGUI, + RSG_ITEM_POOL_HINTS_IMGUI, + RSG_EXTRA_HINTS_IMGUI, + RSG_ITEM_POOL_HINTS_IMGUI_COLUMN, + RSG_ADDITIONAL_FEATURES_IMGUI, + RSG_GAMEPLAY_IMGUI_TABLE, + RSG_STARTING_EQUIPMENT_IMGUI, + RSG_STARTING_ITEMS_IMGUI, + RSG_STARTING_NORMAL_SONGS_IMGUI, + RSG_STARTING_WARP_SONGS_IMGUI, + RSG_STARTING_SONGS_IMGUI, + RSG_STARTING_INVENTORY_IMGUI_TABLE, RSG_OPEN, RSG_WORLD, RSG_SHUFFLE, diff --git a/soh/soh/Enhancements/randomizer/randomizer_settings_window.h b/soh/soh/Enhancements/randomizer/randomizer_settings_window.h index bbb55ca3c..01ce3b083 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_settings_window.h +++ b/soh/soh/Enhancements/randomizer/randomizer_settings_window.h @@ -1,10 +1,18 @@ #include +namespace Rando { +class Settings; +} + class RandomizerSettingsWindow : public LUS::GuiWindow { public: using GuiWindow::GuiWindow; void InitElement() override; void DrawElement() override; - void UpdateElement() override {}; -}; \ No newline at end of file + void UpdateElement() override; + + private: + bool mNeedsUpdate = false; + std::shared_ptr mSettings; +}; diff --git a/soh/soh/Enhancements/randomizer/settings.cpp b/soh/soh/Enhancements/randomizer/settings.cpp index 65c98e251..114f2201a 100644 --- a/soh/soh/Enhancements/randomizer/settings.cpp +++ b/soh/soh/Enhancements/randomizer/settings.cpp @@ -3,6 +3,8 @@ #include "dungeon.h" #include "context.h" +#include "soh/OTRGlobals.h" + #include #include @@ -36,157 +38,160 @@ Settings::Settings() : mExcludeLocationsOptionsGroups(SPOILER_COLLECTION_GROUP_C } void Settings::CreateOptions() { - //clang-format off - mOptions[RSK_FOREST] = Option::U8("Forest", {"Closed", "Closed Deku", "Open"}, OptionCategory::Setting, RO_FOREST_CLOSED); - mOptions[RSK_KAK_GATE] = Option::U8("Kakariko Gate", {"Close", "Open"}); - mOptions[RSK_DOOR_OF_TIME] = Option::U8("Door of Time", {"Closed", "Song only", "Open"}); - mOptions[RSK_ZORAS_FOUNTAIN] = Option::U8("Zora's Fountain", {"Closed", "Closed as child", "Open"}); - mOptions[RSK_GERUDO_FORTRESS] = Option::U8("Gerudo Fortress", {"Normal", "Fast", "Open"}); - mOptions[RSK_RAINBOW_BRIDGE] = Option::U8("Rainbow Bridge", {"Vanilla", "Always open", "Stones", "Medallions", "Dungeons", "Tokens", "Greg"}, OptionCategory::Setting, RO_BRIDGE_VANILLA); - mOptions[RSK_RAINBOW_BRIDGE_STONE_COUNT] = Option::U8("Stone Count", {NumOpts(0, 4)}, OptionCategory::Setting, 1, true); - mOptions[RSK_RAINBOW_BRIDGE_MEDALLION_COUNT] = Option::U8("Medallion Count", {NumOpts(0, 7)}, OptionCategory::Setting, 1, true); - mOptions[RSK_RAINBOW_BRIDGE_REWARD_COUNT] = Option::U8("Reward Count", {NumOpts(0, 10)}, OptionCategory::Setting, 1, true); - mOptions[RSK_RAINBOW_BRIDGE_DUNGEON_COUNT] = Option::U8("Dungeon Count", {NumOpts(0, 9)}, OptionCategory::Setting, 1, true); - mOptions[RSK_RAINBOW_BRIDGE_TOKEN_COUNT] = Option::U8("Token Count", {NumOpts(0, 100)}, OptionCategory::Setting, 1, true); - mOptions[RSK_BRIDGE_OPTIONS] = Option::U8("Bridge Reward Options", {"Standard Rewards", "Greg as Reward", "Greg as Wildcard"}); - mOptions[RSK_GANONS_TRIALS] = Option::U8("Ganon's Trials", {"Skip", "Set Number", "Random Number"}, OptionCategory::Setting, RO_GANONS_TRIALS_SKIP); - mOptions[RSK_TRIAL_COUNT] = Option::U8("Ganon's Trials Count", {NumOpts(1, 6)}, OptionCategory::Setting, 1, true); - mOptions[RSK_STARTING_AGE] = Option::U8("Starting Age", {"Child", "Adult", "Random"}, OptionCategory::Setting, RO_AGE_CHILD); + CreateOptionDescriptions(); + // clang-format off + mOptions[RSK_FOREST] = Option::U8("Forest", {"Closed", "Closed Deku", "Open"}, OptionCategory::Setting, "gRandomizeForest", mOptionDescriptions[RSK_FOREST], WidgetType::Combobox, RO_FOREST_CLOSED); + mOptions[RSK_KAK_GATE] = Option::U8("Kakariko Gate", {"Close", "Open"}, OptionCategory::Setting, "gRandomizeKakarikoGate", mOptionDescriptions[RSK_KAK_GATE]); + mOptions[RSK_DOOR_OF_TIME] = Option::U8("Door of Time", {"Closed", "Song only", "Open"}, OptionCategory::Setting, "gRandomizeDoorOfTime", mOptionDescriptions[RSK_DOOR_OF_TIME], WidgetType::Combobox); + mOptions[RSK_ZORAS_FOUNTAIN] = Option::U8("Zora's Fountain", {"Closed", "Closed as child", "Open"}, OptionCategory::Setting, "gRandomizeZorasFountain", mOptionDescriptions[RSK_ZORAS_FOUNTAIN]); + mOptions[RSK_GERUDO_FORTRESS] = Option::U8("Gerudo Fortress", {"Normal", "Fast", "Open"}, OptionCategory::Setting, "gRandomizeGerudoFortress", mOptionDescriptions[RSK_GERUDO_FORTRESS]); + mOptions[RSK_RAINBOW_BRIDGE] = Option::U8("Rainbow Bridge", {"Vanilla", "Always open", "Stones", "Medallions", "Dungeon rewards", "Dungeons", "Tokens", "Greg"}, OptionCategory::Setting, "gRandomizeRainbowBridge", mOptionDescriptions[RSK_RAINBOW_BRIDGE], WidgetType::Combobox, RO_BRIDGE_VANILLA, false, IMFLAG_NONE); + mOptions[RSK_RAINBOW_BRIDGE_STONE_COUNT] = Option::U8("Stone Count", {NumOpts(0, 4)}, OptionCategory::Setting, "gRandomizeStoneCount", "", WidgetType::Slider, 3, true); + mOptions[RSK_RAINBOW_BRIDGE_MEDALLION_COUNT] = Option::U8("Medallion Count", {NumOpts(0, 7)}, OptionCategory::Setting, "gRandomizeMedallionCount", "", WidgetType::Slider, 6, true); + mOptions[RSK_RAINBOW_BRIDGE_REWARD_COUNT] = Option::U8("Reward Count", {NumOpts(0, 10)}, OptionCategory::Setting, "gRandomizeRewardCount", "", WidgetType::Slider, 9, true); + mOptions[RSK_RAINBOW_BRIDGE_DUNGEON_COUNT] = Option::U8("Dungeon Count", {NumOpts(0, 9)}, OptionCategory::Setting, "gRandomizeDungeonCount", "", WidgetType::Slider, 8, true); + mOptions[RSK_RAINBOW_BRIDGE_TOKEN_COUNT] = Option::U8("Token Count", {NumOpts(0, 100)}, OptionCategory::Setting, "gRandomizeTokenCount", "", WidgetType::Slider, 100, true); + mOptions[RSK_BRIDGE_OPTIONS] = Option::U8("Bridge Reward Options", {"Standard Rewards", "Greg as Reward", "Greg as Wildcard"}, OptionCategory::Setting, "gRandomizeBridgeRewardOptions", mOptionDescriptions[RSK_BRIDGE_OPTIONS], WidgetType::Combobox, RO_BRIDGE_STANDARD_REWARD, false, IMFLAG_NONE); + mOptions[RSK_GANONS_TRIALS] = Option::U8("Ganon's Trials", {"Skip", "Set Number", "Random Number"}, OptionCategory::Setting, "gRandomizeGanonTrial", mOptionDescriptions[RSK_GANONS_TRIALS], WidgetType::Combobox, RO_GANONS_TRIALS_SET_NUMBER); + mOptions[RSK_TRIAL_COUNT] = Option::U8("Ganon's Trials Count", {NumOpts(0, 6)}, OptionCategory::Setting, "gRandomizeGanonTrialCount", mOptionDescriptions[RSK_TRIAL_COUNT], WidgetType::Slider, 6, true); + mOptions[RSK_STARTING_AGE] = Option::U8("Starting Age", {"Child", "Adult", "Random"}, OptionCategory::Setting, "gRandomizeStartingAge", mOptionDescriptions[RSK_STARTING_AGE], WidgetType::Combobox, RO_AGE_CHILD); mOptions[RSK_SHUFFLE_ENTRANCES] = Option::Bool("Shuffle Entrances"); - mOptions[RSK_SHUFFLE_DUNGEON_ENTRANCES] = Option::U8("Dungeon Entrances", {"Off", "On", "On + Ganon"}); - mOptions[RSK_SHUFFLE_BOSS_ENTRANCES] = Option::U8("Boss Entrances", {"Off", "Age Restricted", "Full"}); - mOptions[RSK_SHUFFLE_OVERWORLD_ENTRANCES] = Option::Bool("Overworld Entrances"); - mOptions[RSK_SHUFFLE_INTERIOR_ENTRANCES] = Option::U8("Interior Entrances", {"Off", "Simple", "All"}); - mOptions[RSK_SHUFFLE_GROTTO_ENTRANCES] = Option::Bool("Grottos Entrances"); - mOptions[RSK_SHUFFLE_OWL_DROPS] = Option::Bool("Owl Drops"); - mOptions[RSK_SHUFFLE_WARP_SONGS] = Option::Bool("Warp Songs"); - mOptions[RSK_SHUFFLE_OVERWORLD_SPAWNS] = Option::Bool("Overworld Spawns"); - mOptions[RSK_MIXED_ENTRANCE_POOLS] = Option::Bool("Mixed Entrance Pools"); - mOptions[RSK_MIX_DUNGEON_ENTRANCES] = Option::Bool("Mix Dungeons"); - mOptions[RSK_MIX_OVERWORLD_ENTRANCES] = Option::Bool("Mix Overworld"); - mOptions[RSK_MIX_INTERIOR_ENTRANCES] = Option::Bool("Mix Interiors"); - mOptions[RSK_MIX_GROTTO_ENTRANCES] = Option::Bool("Mix Grottos"); - mOptions[RSK_DECOUPLED_ENTRANCES] = Option::Bool("Decouple Entrances"); - mOptions[RSK_BOMBCHUS_IN_LOGIC] = Option::Bool("Bombchus in Logic"); - mOptions[RSK_ENABLE_BOMBCHU_DROPS] = Option::U8("Bombchu Drops", {"No", "Yes"}); + mOptions[RSK_SHUFFLE_DUNGEON_ENTRANCES] = Option::U8("Dungeon Entrances", {"Off", "On", "On + Ganon"}, OptionCategory::Setting, "gRandomizeShuffleDungeonsEntrances", mOptionDescriptions[RSK_SHUFFLE_DUNGEON_ENTRANCES], WidgetType::Combobox, RO_DUNGEON_ENTRANCE_SHUFFLE_OFF); + mOptions[RSK_SHUFFLE_BOSS_ENTRANCES] = Option::U8("Boss Entrances", {"Off", "Age Restricted", "Full"}, OptionCategory::Setting, "gRandomizeShuffleBossEntrances", mOptionDescriptions[RSK_SHUFFLE_BOSS_ENTRANCES], WidgetType::Combobox, RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF); + mOptions[RSK_SHUFFLE_OVERWORLD_ENTRANCES] = Option::Bool("Overworld Entrances", "gRandomizeShuffleOverworldEntrances", mOptionDescriptions[RSK_SHUFFLE_OVERWORLD_ENTRANCES]); + mOptions[RSK_SHUFFLE_INTERIOR_ENTRANCES] = Option::U8("Interior Entrances", {"Off", "Simple", "All"}, OptionCategory::Setting, "gRandomizeShuffleInteriorsEntrances", mOptionDescriptions[RSK_SHUFFLE_INTERIOR_ENTRANCES], WidgetType::Combobox, RO_INTERIOR_ENTRANCE_SHUFFLE_OFF); + mOptions[RSK_SHUFFLE_GROTTO_ENTRANCES] = Option::Bool("Grottos Entrances", "gRandomizeShuffleGrottosEntrances", mOptionDescriptions[RSK_SHUFFLE_GROTTO_ENTRANCES]); + mOptions[RSK_SHUFFLE_OWL_DROPS] = Option::Bool("Owl Drops", "gRandomizeShuffleOwlDrops", mOptionDescriptions[RSK_SHUFFLE_OWL_DROPS]); + mOptions[RSK_SHUFFLE_WARP_SONGS] = Option::Bool("Warp Songs", "gRandomizeShuffleWarpSongs", mOptionDescriptions[RSK_SHUFFLE_WARP_SONGS]); + mOptions[RSK_SHUFFLE_OVERWORLD_SPAWNS] = Option::Bool("Overworld Spawns", "gRandomizeShuffleOverworldSpanws", mOptionDescriptions[RSK_SHUFFLE_OVERWORLD_SPAWNS]); + mOptions[RSK_MIXED_ENTRANCE_POOLS] = Option::Bool("Mixed Entrance Pools", "gRandomizeMixedEntrances", mOptionDescriptions[RSK_MIXED_ENTRANCE_POOLS]); + mOptions[RSK_MIX_DUNGEON_ENTRANCES] = Option::Bool("Mix Dungeons", "gRandomizeMixDungeons", mOptionDescriptions[RSK_MIX_DUNGEON_ENTRANCES], IMFLAG_NONE); + mOptions[RSK_MIX_OVERWORLD_ENTRANCES] = Option::Bool("Mix Overworld", "gRandomizeMixOverworld", mOptionDescriptions[RSK_MIX_OVERWORLD_ENTRANCES], IMFLAG_NONE); + mOptions[RSK_MIX_INTERIOR_ENTRANCES] = Option::Bool("Mix Interiors", "gRandomizeMixInteriors", mOptionDescriptions[RSK_MIX_INTERIOR_ENTRANCES], IMFLAG_NONE); + mOptions[RSK_MIX_GROTTO_ENTRANCES] = Option::Bool("Mix Grottos", "gRandomizeMixGrottos", mOptionDescriptions[RSK_MIX_GROTTO_ENTRANCES]); + mOptions[RSK_DECOUPLED_ENTRANCES] = Option::Bool("Decouple Entrances", "gRandomizeDecoupleEntrances", mOptionDescriptions[RSK_DECOUPLED_ENTRANCES]); + mOptions[RSK_BOMBCHUS_IN_LOGIC] = Option::Bool("Bombchus in Logic", "gRandomizeBombchusInLogic", mOptionDescriptions[RSK_BOMBCHUS_IN_LOGIC]); + mOptions[RSK_ENABLE_BOMBCHU_DROPS] = Option::U8("Bombchu Drops", {"No", "Yes"}, OptionCategory::Setting, "gRandomizeEnableBombchuDrops", mOptionDescriptions[RSK_ENABLE_BOMBCHU_DROPS], WidgetType::Combobox, RO_AMMO_DROPS_ON); // TODO: AmmoDrops and/or HeartDropRefill, combine with/separate Ammo Drops from Bombchu Drops? - mOptions[RSK_TRIFORCE_HUNT] = Option::Bool("Triforce Hunt"); - mOptions[RSK_TRIFORCE_HUNT_PIECES_TOTAL] = Option::U8("Triforce Hunt Total Pieces", {NumOpts(0, 100)}); - mOptions[RSK_TRIFORCE_HUNT_PIECES_REQUIRED] = Option::U8("Triforce Hunt Required Pieces", {NumOpts(0, 100)}); - mOptions[RSK_MQ_DUNGEON_RANDOM] = Option::U8("MQ Dungeon Setting", {"None", "Set Number", "Random", "Selection Only"}); - mOptions[RSK_MQ_DUNGEON_COUNT] = Option::U8("MQ Dungeon Count", {NumOpts(0, 12)}); - mOptions[RSK_MQ_DUNGEON_SET] = Option::Bool("Set Dungeon Quests"); - mOptions[RSK_MQ_DEKU_TREE] = Option::U8("Deku Tree", {"Vanilla", "Master Quest", "Random"}); - mOptions[RSK_MQ_DODONGOS_CAVERN] = Option::U8("Dodongo's Cavern", {"Vanilla", "Master Quest", "Random"}); - mOptions[RSK_MQ_JABU_JABU] = Option::U8("Jabu-Jabu's Belly", {"Vanilla", "Master Quest", "Random"}); - mOptions[RSK_MQ_FOREST_TEMPLE] = Option::U8("Forest Temple", {"Vanilla", "Master Quest", "Random"}); - mOptions[RSK_MQ_FIRE_TEMPLE] = Option::U8("Fire Temple", {"Vanilla", "Master Quest", "Random"}); - mOptions[RSK_MQ_WATER_TEMPLE] = Option::U8("Water Temple", {"Vanilla", "Master Quest", "Random"}); - mOptions[RSK_MQ_SPIRIT_TEMPLE] = Option::U8("Spirit Temple", {"Vanilla", "Master Quest", "Random"}); - mOptions[RSK_MQ_SHADOW_TEMPLE] = Option::U8("Shadow Temple", {"Vanilla", "Master Quest", "Random"}); - mOptions[RSK_MQ_BOTTOM_OF_THE_WELL] = Option::U8("Bottom of the Well", {"Vanilla", "Master Quest", "Random"}); - mOptions[RSK_MQ_ICE_CAVERN] = Option::U8("Ice Cavern", {"Vanilla", "Master Quest", "Random"}); - mOptions[RSK_MQ_GTG] = Option::U8("Gerudo Training Grounds", {"Vanilla", "Master Quest", "Random"}); - mOptions[RSK_MQ_GANONS_CASTLE] = Option::U8("Ganon's Castle", {"Vanilla", "Master Quest", "Random"}); - mOptions[RSK_SHUFFLE_DUNGEON_REWARDS] = Option::U8("Shuffle Dungeon Rewards", {"End of Dungeons", "Any Dungeon", "Overworld", "Anywhere"}); - mOptions[RSK_LINKS_POCKET] = Option::U8("Link's Pocket", {"Dungeon Reward", "Advancement", "Anything", "Nothing"}); - mOptions[RSK_SHUFFLE_SONGS] = Option::U8("Shuffle Songs", {"Song Locations", "Dungeon Rewards", "Anywhere"}); - mOptions[RSK_SHOPSANITY] = Option::U8("Shopsanity", {"Off", "0 Items", "1 Item", "2 Items", "3 Items", "4 Items", "Random"}); - mOptions[RSK_SHOPSANITY_PRICES] = Option::U8("Shopsanity Prices", {"Balanced", "Starting Wallet", "Adult Wallet", "Giant's Wallet", "Tycoon's Wallet"}); - mOptions[RSK_SHOPSANITY_PRICES_AFFORDABLE] = Option::Bool("Affordable Prices"); // TODO: Can this be added as a selection for above option? - mOptions[RSK_SHUFFLE_TOKENS] = Option::U8("Tokensanity", {"Off", "Dungeons", "Overworld", "All Tokens"}); - mOptions[RSK_SHUFFLE_SCRUBS] = Option::U8("Scrub Shuffle", {"Off", "Affordable", "Expensive", "Random Prices"}); - mOptions[RSK_SHUFFLE_COWS] = Option::Bool("Shuffle Cows"); - mOptions[RSK_SHUFFLE_KOKIRI_SWORD] = Option::Bool("Shuffle Kokiri Sword"); - mOptions[RSK_SHUFFLE_MASTER_SWORD] = Option::Bool("Shuffle Master Sword"); - mOptions[RSK_SHUFFLE_OCARINA] = Option::Bool("Shuffle Ocarinas"); - mOptions[RSK_SHUFFLE_WEIRD_EGG] = Option::Bool("Shuffle Weird Egg"); - mOptions[RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD] = Option::Bool("Shuffle Gerudo Membership Card"); - mOptions[RSK_SHUFFLE_MAGIC_BEANS] = Option::Bool("Shuffle Magic Beans"); - mOptions[RSK_SHUFFLE_MERCHANTS] = Option::U8("Shuffle Merchants", {"Off", "On (No Hints)", "On (With Hints)"}); - mOptions[RSK_SHUFFLE_FROG_SONG_RUPEES] = Option::Bool("Shuffle Frog Song Rupees"); - mOptions[RSK_SHUFFLE_ADULT_TRADE] = Option::Bool("Shuffle Adult Trade"); + mOptions[RSK_TRIFORCE_HUNT] = Option::Bool("Triforce Hunt", "gRandomizeTriforceHunt", mOptionDescriptions[RSK_TRIFORCE_HUNT], IMFLAG_NONE); + mOptions[RSK_TRIFORCE_HUNT_PIECES_TOTAL] = Option::U8("Triforce Hunt Total Pieces", {NumOpts(1, 100)}, OptionCategory::Setting, "gRandomizeTriforceHuntTotalPieces", mOptionDescriptions[RSK_TRIFORCE_HUNT_PIECES_TOTAL], WidgetType::Slider, 29, false, IMFLAG_NONE); + mOptions[RSK_TRIFORCE_HUNT_PIECES_REQUIRED] = Option::U8("Triforce Hunt Required Pieces", {NumOpts(1, 100)}, OptionCategory::Setting, "gRandomizeTriforceHuntRequiredPieces", mOptionDescriptions[RSK_TRIFORCE_HUNT_PIECES_REQUIRED], WidgetType::Slider, 19); + mOptions[RSK_MQ_DUNGEON_RANDOM] = Option::U8("MQ Dungeon Setting", {"None", "Set Number", "Random", "Selection Only"}, OptionCategory::Setting, "gRandomizeMqDungeons", mOptionDescriptions[RSK_MQ_DUNGEON_RANDOM], WidgetType::Combobox, RO_MQ_DUNGEONS_NONE, true, IMFLAG_NONE); + mOptions[RSK_MQ_DUNGEON_COUNT] = Option::U8("MQ Dungeon Count", {NumOpts(0, 12)}, OptionCategory::Setting, "gRandomizeMqDungeonCount", "", WidgetType::Slider, 12, true, IMFLAG_NONE); + mOptions[RSK_MQ_DUNGEON_SET] = Option::Bool("Set Dungeon Quests", {"Off", "On"}, OptionCategory::Setting, "gRandomizeMqDungeonsSelection", mOptionDescriptions[RSK_MQ_DUNGEON_SET], WidgetType::Checkbox, false, false, IMFLAG_NONE); + mOptions[RSK_MQ_DEKU_TREE] = Option::U8("Deku Tree", {"Vanilla", "Master Quest", "Random"}, OptionCategory::Setting, "gRandomizeMqDungeonsDekuTree", "", WidgetType::Combobox, RO_MQ_SET_VANILLA, false, IMFLAG_NONE); + mOptions[RSK_MQ_DODONGOS_CAVERN] = Option::U8("Dodongo's Cavern", {"Vanilla", "Master Quest", "Random"}, OptionCategory::Setting, "gRandomizeMqDungeonsDodongosCavern", "", WidgetType::Combobox, RO_MQ_SET_VANILLA, false, IMFLAG_NONE); + mOptions[RSK_MQ_JABU_JABU] = Option::U8("Jabu-Jabu's Belly", {"Vanilla", "Master Quest", "Random"}, OptionCategory::Setting, "gRandomizeMqDungeonsJabuJabu", "", WidgetType::Combobox, RO_MQ_SET_VANILLA, false, IMFLAG_NONE); + mOptions[RSK_MQ_FOREST_TEMPLE] = Option::U8("Forest Temple", {"Vanilla", "Master Quest", "Random"}, OptionCategory::Setting, "gRandomizeMqDungeonsForestTemple", "", WidgetType::Combobox, RO_MQ_SET_VANILLA, false, IMFLAG_NONE); + mOptions[RSK_MQ_FIRE_TEMPLE] = Option::U8("Fire Temple", {"Vanilla", "Master Quest", "Random"}, OptionCategory::Setting, "gRandomizeMqDungeonsFireTemple", "", WidgetType::Combobox, RO_MQ_SET_VANILLA, false, IMFLAG_NONE); + mOptions[RSK_MQ_WATER_TEMPLE] = Option::U8("Water Temple", {"Vanilla", "Master Quest", "Random"}, OptionCategory::Setting, "gRandomizeMqDungeonsWaterTemple", "", WidgetType::Combobox, RO_MQ_SET_VANILLA, false, IMFLAG_NONE); + mOptions[RSK_MQ_SPIRIT_TEMPLE] = Option::U8("Spirit Temple", {"Vanilla", "Master Quest", "Random"}, OptionCategory::Setting, "gRandomizeMqDungeonsSpiritTemple", "", WidgetType::Combobox, RO_MQ_SET_VANILLA, false, IMFLAG_NONE); + mOptions[RSK_MQ_SHADOW_TEMPLE] = Option::U8("Shadow Temple", {"Vanilla", "Master Quest", "Random"}, OptionCategory::Setting, "gRandomizeMqDungeonsShadowTemple", "", WidgetType::Combobox, RO_MQ_SET_VANILLA, false, IMFLAG_NONE); + mOptions[RSK_MQ_BOTTOM_OF_THE_WELL] = Option::U8("Bottom of the Well", {"Vanilla", "Master Quest", "Random"}, OptionCategory::Setting, "gRandomizeMqDungeonsBottomOfTheWell", "", WidgetType::Combobox, RO_MQ_SET_VANILLA, false, IMFLAG_NONE); + mOptions[RSK_MQ_ICE_CAVERN] = Option::U8("Ice Cavern", {"Vanilla", "Master Quest", "Random"}, OptionCategory::Setting, "gRandomizeMqDungeonsIceCavern", "", WidgetType::Combobox, RO_MQ_SET_VANILLA, false, IMFLAG_NONE); + mOptions[RSK_MQ_GTG] = Option::U8("Gerudo Training Grounds", {"Vanilla", "Master Quest", "Random"}, OptionCategory::Setting, "gRandomizeMqDungeonsGTG", "", WidgetType::Combobox, RO_MQ_SET_VANILLA, false, IMFLAG_NONE); + mOptions[RSK_MQ_GANONS_CASTLE] = Option::U8("Ganon's Castle", {"Vanilla", "Master Quest", "Random"}, OptionCategory::Setting, "gRandomizeMqDungeonsGanonsCastle", "", WidgetType::Combobox, RO_MQ_SET_VANILLA); + mOptions[RSK_SHUFFLE_DUNGEON_REWARDS] = Option::U8("Shuffle Dungeon Rewards", {"End of Dungeons", "Any Dungeon", "Overworld", "Anywhere"}, OptionCategory::Setting, "gRandomizeShuffleDungeonReward", mOptionDescriptions[RSK_SHUFFLE_DUNGEON_REWARDS], WidgetType::Combobox, RO_DUNGEON_REWARDS_END_OF_DUNGEON); + mOptions[RSK_LINKS_POCKET] = Option::U8("Link's Pocket", {"Dungeon Reward", "Advancement", "Anything", "Nothing"}, OptionCategory::Setting, "gRandomizeLinksPocket", "", WidgetType::Combobox, RO_LINKS_POCKET_DUNGEON_REWARD, true); + mOptions[RSK_SHUFFLE_SONGS] = Option::U8("Shuffle Songs", {"Song Locations", "Dungeon Rewards", "Anywhere"}, OptionCategory::Setting, "gRandomizeShuffleSongs", mOptionDescriptions[RSK_SHUFFLE_SONGS], WidgetType::Combobox, RO_SONG_SHUFFLE_SONG_LOCATIONS); + mOptions[RSK_SHOPSANITY] = Option::U8("Shopsanity", {"Off", "0 Items", "1 Item", "2 Items", "3 Items", "4 Items", "Random"}, OptionCategory::Setting, "gRandomizeShopsanity", mOptionDescriptions[RSK_SHOPSANITY], WidgetType::Combobox, RO_SHOPSANITY_OFF); + mOptions[RSK_SHOPSANITY_PRICES] = Option::U8("Shopsanity Prices", {"Balanced", "Starting Wallet", "Adult Wallet", "Giant's Wallet", "Tycoon's Wallet"}, OptionCategory::Setting, "gRandomizeShopsanityPrices", mOptionDescriptions[RSK_SHOPSANITY_PRICES], WidgetType::Combobox, RO_SHOPSANITY_PRICE_BALANCED, false, IMFLAG_NONE); + mOptions[RSK_SHOPSANITY_PRICES_AFFORDABLE] = Option::Bool("Affordable Prices", "gRandomizeShopsanityPricesAffordable", mOptionDescriptions[RSK_SHOPSANITY_PRICES_AFFORDABLE]); + mOptions[RSK_SHUFFLE_TOKENS] = Option::U8("Tokensanity", {"Off", "Dungeons", "Overworld", "All Tokens"}, OptionCategory::Setting, "gRandomizeShuffleTokens", mOptionDescriptions[RSK_SHUFFLE_TOKENS], WidgetType::Combobox, RO_TOKENSANITY_OFF); + mOptions[RSK_SHUFFLE_SCRUBS] = Option::U8("Scrub Shuffle", {"Off", "Affordable", "Expensive", "Random Prices"}, OptionCategory::Setting, "gRandomizeShuffleScrubs", mOptionDescriptions[RSK_SHUFFLE_SCRUBS], WidgetType::Combobox, RO_SCRUBS_OFF); + mOptions[RSK_SHUFFLE_COWS] = Option::Bool("Shuffle Cows", "gRandomizeShuffleCows", mOptionDescriptions[RSK_SHUFFLE_COWS]); + mOptions[RSK_SHUFFLE_KOKIRI_SWORD] = Option::Bool("Shuffle Kokiri Sword", "gRandomizeShuffleKokiriSword", mOptionDescriptions[RSK_SHUFFLE_KOKIRI_SWORD]); + mOptions[RSK_SHUFFLE_MASTER_SWORD] = Option::Bool("Shuffle Master Sword", "gRandomizeShuffleMasterSword", mOptionDescriptions[RSK_SHUFFLE_MASTER_SWORD]); + mOptions[RSK_SHUFFLE_OCARINA] = Option::Bool("Shuffle Ocarinas", "gRandomizeShuffleOcarinas", mOptionDescriptions[RSK_SHUFFLE_OCARINA]); + mOptions[RSK_SHUFFLE_WEIRD_EGG] = Option::Bool("Shuffle Weird Egg", "gRandomizeShuffleWeirdEgg", mOptionDescriptions[RSK_SHUFFLE_WEIRD_EGG]); + mOptions[RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD] = Option::Bool("Shuffle Gerudo Membership Card", "gRandomizeShuffleGerudoToken", mOptionDescriptions[RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD]); + mOptions[RSK_SHUFFLE_MAGIC_BEANS] = Option::Bool("Shuffle Magic Beans", "gRandomizeShuffleBeans", mOptionDescriptions[RSK_SHUFFLE_MAGIC_BEANS]); + mOptions[RSK_SHUFFLE_MERCHANTS] = Option::U8("Shuffle Merchants", {"Off", "On (No Hints)", "On (With Hints)"}, OptionCategory::Setting, "gRandomizeShuffleMerchants", mOptionDescriptions[RSK_SHUFFLE_MERCHANTS], WidgetType::Combobox, RO_SHUFFLE_MERCHANTS_OFF); + mOptions[RSK_SHUFFLE_FROG_SONG_RUPEES] = Option::Bool("Shuffle Frog Song Rupees", "gRandomizeShuffleFrogSongRupees", mOptionDescriptions[RSK_SHUFFLE_FROG_SONG_RUPEES]); + mOptions[RSK_SHUFFLE_ADULT_TRADE] = Option::Bool("Shuffle Adult Trade", "gRandomizeShuffleAdultTrade", mOptionDescriptions[RSK_SHUFFLE_ADULT_TRADE]); mOptions[RSK_SHUFFLE_CHEST_MINIGAME] = Option::U8("Shuffle Chest Minigame", {"Off", "On (Separate)", "On (Pack)"}); - mOptions[RSK_SHUFFLE_100_GS_REWARD] = Option::Bool("Shuffle 100 GS Reward"); - mOptions[RSK_SHUFFLE_BOSS_SOULS] = Option::U8("Shuffle Boss Souls", {"Off", "On", "On + Ganon"}); - mOptions[RSK_SHUFFLE_MAPANDCOMPASS] = Option::U8("Maps/Compasses", {"Start With", "Vanilla", "Own Dungeon", "Any Dungeon", "Overworld", "Anywhere"}, OptionCategory::Setting, RO_DUNGEON_ITEM_LOC_OWN_DUNGEON); - mOptions[RSK_KEYSANITY] = Option::U8("Small Keys", {"Start With", "Vanilla", "Own Dungeon", "Any Dungeon", "Overworld", "Anywhere"}, OptionCategory::Setting, RO_DUNGEON_ITEM_LOC_OWN_DUNGEON); - mOptions[RSK_GERUDO_KEYS] = Option::U8("Gerudo Fortress Keys", {"Vanilla", "Any Dungeon", "Overworld", "Anywhere"}); - mOptions[RSK_BOSS_KEYSANITY] = Option::U8("Boss Keys", {"Start With", "Vanilla", "Own Dungeon", "Any Dungeon", "Overworld", "Anywhere"}, OptionCategory::Setting, RO_DUNGEON_ITEM_LOC_OWN_DUNGEON); - mOptions[RSK_GANONS_BOSS_KEY] = Option::U8("Ganon's Boss Key", {"Vanilla", "Own Dungeon", "Start With", "Any Dungeon", "Overworld", "Anywhere", "LACS-Vanilla", "LACS-Stones", "LACS-Medallions", "LACS-Rewards", "LACS-Dungeons", "LACS-Tokens", "Triforce Hunt"}, OptionCategory::Setting, RO_GANON_BOSS_KEY_VANILLA); - mOptions[RSK_LACS_STONE_COUNT] = Option::U8("Stone Count", {NumOpts(0, 4)}, OptionCategory::Setting, 1, true); - mOptions[RSK_LACS_MEDALLION_COUNT] = Option::U8("Medallion Count", {NumOpts(0, 7)}, OptionCategory::Setting, 1, true); - mOptions[RSK_LACS_REWARD_COUNT] = Option::U8("Reward Count", {NumOpts(0, 10)}, OptionCategory::Setting, 1, true); - mOptions[RSK_LACS_DUNGEON_COUNT] = Option::U8("Dungeon Count", {NumOpts(0, 9)}, OptionCategory::Setting, 1, true); - mOptions[RSK_LACS_TOKEN_COUNT] = Option::U8("Token Count", {NumOpts(0, 100)}, OptionCategory::Setting, 1, true); - mOptions[RSK_LACS_OPTIONS] = Option::U8("LACS Reward Options", {"Standard Reward", "Greg as Reward", "Greg as Wildcard"}); - mOptions[RSK_KEYRINGS] = Option::U8("Key Rings", {"Off", "Random", "Count", "Selection"}); - mOptions[RSK_KEYRINGS_RANDOM_COUNT] = Option::U8("Keyring Dungeon Count", {NumOpts(0, 9)}, OptionCategory::Setting, 1); - mOptions[RSK_KEYRINGS_GERUDO_FORTRESS] = Option::Bool("Gerudo Fortress"); - mOptions[RSK_KEYRINGS_FOREST_TEMPLE] = Option::Bool("Forest Temple"); - mOptions[RSK_KEYRINGS_FIRE_TEMPLE] = Option::Bool("Fire Temple"); - mOptions[RSK_KEYRINGS_WATER_TEMPLE] = Option::Bool("Water Temple"); - mOptions[RSK_KEYRINGS_SPIRIT_TEMPLE] = Option::Bool("Spirit Temple"); - mOptions[RSK_KEYRINGS_SHADOW_TEMPLE] = Option::Bool("Shadow Temple"); - mOptions[RSK_KEYRINGS_BOTTOM_OF_THE_WELL] = Option::Bool("Bottom of the Well"); - mOptions[RSK_KEYRINGS_GTG] = Option::Bool("Gerudo Training Grounds"); - mOptions[RSK_KEYRINGS_GANONS_CASTLE] = Option::Bool("Ganon's Castle"); - mOptions[RSK_SKIP_CHILD_STEALTH] = Option::Bool("Skip Child Stealth", {"Don't Skip", "Skip"}, OptionCategory::Setting, RO_GENERIC_SKIP); - mOptions[RSK_SKIP_CHILD_ZELDA] = Option::Bool("Skip Child Zelda", {"Don't Skip", "Skip"}, OptionCategory::Setting, RO_GENERIC_DONT_SKIP); - mOptions[RSK_SKIP_TOWER_ESCAPE] = Option::Bool("Skip Tower Escape", {"Don't Skip", "Skip"}); - mOptions[RSK_SKIP_EPONA_RACE] = Option::Bool("Skip Epona Race", {"Don't Skip", "Skip"}); - mOptions[RSK_SKIP_SCARECROWS_SONG] = Option::Bool("Skip Scarecrow's Song"); - mOptions[RSK_BIG_POE_COUNT] = Option::U8("Big Poe Target Count", {NumOpts(1, 10)}); - mOptions[RSK_CUCCO_COUNT] = Option::U8("Cuccos to return", {NumOpts(0, 7)}); - mOptions[RSK_COMPLETE_MASK_QUEST] = Option::Bool("Complete Mask Quest"); - mOptions[RSK_ENABLE_GLITCH_CUTSCENES] = Option::Bool("Enable Glitch-Useful Cutscenes"); - mOptions[RSK_GOSSIP_STONE_HINTS] = Option::U8("Gossip Stone Hints", {"No Hints", "Need Nothing", "Mask of Truth", "Stone of Agony"}, OptionCategory::Setting, RO_GOSSIP_STONES_NEED_NOTHING); - mOptions[RSK_HINT_CLARITY] = Option::U8("Hint Clarity", {"Obscure", "Ambiguous", "Clear"}); - mOptions[RSK_HINT_DISTRIBUTION] = Option::U8("Hint Distribution", {"Useless", "Balanced", "Strong", "Very Strong"}, OptionCategory::Setting, RO_HINT_DIST_BALANCED); - mOptions[RSK_TOT_ALTAR_HINT] = Option::Bool("ToT Altar Hint", {"Off", "On"}, OptionCategory::Setting, RO_GENERIC_ON); - mOptions[RSK_LIGHT_ARROWS_HINT] = Option::Bool("Light Arrow Hint", {"Off", "On"}, OptionCategory::Setting, RO_GENERIC_ON); - mOptions[RSK_DAMPES_DIARY_HINT] = Option::Bool("Dampe's Diary Hint"); - mOptions[RSK_GREG_HINT] = Option::Bool("Greg the Green Rupee Hint"); - mOptions[RSK_SARIA_HINT] = Option::Bool("Saria's Hint"); - mOptions[RSK_FROGS_HINT] = Option::Bool("Frog Ocarina Game Hint"); - mOptions[RSK_WARP_SONG_HINTS] = Option::Bool("Warp Song Hints"); - mOptions[RSK_KAK_10_SKULLS_HINT] = Option::Bool("10 GS Hint"); - mOptions[RSK_KAK_20_SKULLS_HINT] = Option::Bool("20 GS Hint"); - mOptions[RSK_KAK_30_SKULLS_HINT] = Option::Bool("30 GS Hint"); - mOptions[RSK_KAK_40_SKULLS_HINT] = Option::Bool("40 GS Hint"); - mOptions[RSK_KAK_50_SKULLS_HINT] = Option::Bool("50 GS Hint"); - mOptions[RSK_SCRUB_TEXT_HINT] = Option::Bool("Scrub Hint Text"); + mOptions[RSK_SHUFFLE_100_GS_REWARD] = Option::Bool("Shuffle 100 GS Reward", "gRandomizeShuffle100GSReward", mOptionDescriptions[RSK_SHUFFLE_100_GS_REWARD], IMFLAG_SEPARATOR_BOTTOM, WidgetType::Checkbox, RO_GENERIC_OFF); + mOptions[RSK_SHUFFLE_BOSS_SOULS] = Option::U8("Shuffle Boss Souls", {"Off", "On", "On + Ganon"}, OptionCategory::Setting, "gRandomizeShuffleBossSouls", mOptionDescriptions[RSK_SHUFFLE_BOSS_SOULS], WidgetType::Combobox); + mOptions[RSK_SHUFFLE_MAPANDCOMPASS] = Option::U8("Maps/Compasses", {"Start With", "Vanilla", "Own Dungeon", "Any Dungeon", "Overworld", "Anywhere"}, OptionCategory::Setting, "gRandomizeStartingMapsCompasses", mOptionDescriptions[RSK_SHUFFLE_MAPANDCOMPASS], WidgetType::Combobox, RO_DUNGEON_ITEM_LOC_OWN_DUNGEON); + mOptions[RSK_KEYSANITY] = Option::U8("Small Keys", {"Start With", "Vanilla", "Own Dungeon", "Any Dungeon", "Overworld", "Anywhere"}, OptionCategory::Setting, "gRandomizeKeysanity", mOptionDescriptions[RSK_KEYSANITY], WidgetType::Combobox, RO_DUNGEON_ITEM_LOC_OWN_DUNGEON); + mOptions[RSK_GERUDO_KEYS] = Option::U8("Gerudo Fortress Keys", {"Vanilla", "Any Dungeon", "Overworld", "Anywhere"}, OptionCategory::Setting, "gRandomizeGerudoKeys", mOptionDescriptions[RSK_GERUDO_KEYS], WidgetType::Combobox, RO_GERUDO_KEYS_VANILLA); + mOptions[RSK_BOSS_KEYSANITY] = Option::U8("Boss Keys", {"Start With", "Vanilla", "Own Dungeon", "Any Dungeon", "Overworld", "Anywhere"}, OptionCategory::Setting, "gRandomizeBossKeysanity", mOptionDescriptions[RSK_BOSS_KEYSANITY], WidgetType::Combobox, RO_DUNGEON_ITEM_LOC_OWN_DUNGEON); + mOptions[RSK_GANONS_BOSS_KEY] = Option::U8("Ganon's Boss Key", {"Vanilla", "Own Dungeon", "Start With", "Any Dungeon", "Overworld", "Anywhere", "LACS-Vanilla", "LACS-Stones", "LACS-Medallions", "LACS-Rewards", "LACS-Dungeons", "LACS-Tokens", "Triforce Hunt"}, OptionCategory::Setting, "gRandomizeShuffleGanonBossKey", mOptionDescriptions[RSK_GANONS_BOSS_KEY], WidgetType::Combobox, RO_GANON_BOSS_KEY_VANILLA); + mOptions[RSK_LACS_STONE_COUNT] = Option::U8("Stone Count", {NumOpts(0, 4)}, OptionCategory::Setting, "gRandomizeLacsStoneCount", "", WidgetType::Slider, 3, true); + mOptions[RSK_LACS_MEDALLION_COUNT] = Option::U8("Medallion Count", {NumOpts(0, 7)}, OptionCategory::Setting, "gRandomizeLacsMedallionCount", "", WidgetType::Slider, 6, true); + mOptions[RSK_LACS_REWARD_COUNT] = Option::U8("Reward Count", {NumOpts(0, 10)}, OptionCategory::Setting, "gRandomizeLacsRewardCount", "", WidgetType::Slider, 9, true); + mOptions[RSK_LACS_DUNGEON_COUNT] = Option::U8("Dungeon Count", {NumOpts(0, 9)}, OptionCategory::Setting, "gRandomizeLacsDungeonCount", "", WidgetType::Slider, 8, true); + mOptions[RSK_LACS_TOKEN_COUNT] = Option::U8("Token Count", {NumOpts(0, 100)}, OptionCategory::Setting, "gRandomizeLacsTokenCount", "", WidgetType::Slider, 100, true); + mOptions[RSK_LACS_OPTIONS] = Option::U8("LACS Reward Options", {"Standard Reward", "Greg as Reward", "Greg as Wildcard"}, OptionCategory::Setting, "gRandomizeLacsRewardOptions", "", WidgetType::Combobox, RO_LACS_STANDARD_REWARD); + mOptions[RSK_KEYRINGS] = Option::U8("Key Rings", {"Off", "Random", "Count", "Selection"}, OptionCategory::Setting, "gRandomizeShuffleKeyRings", mOptionDescriptions[RSK_KEYRINGS], WidgetType::Combobox, RO_KEYRINGS_OFF); + mOptions[RSK_KEYRINGS_RANDOM_COUNT] = Option::U8("Keyring Dungeon Count", {NumOpts(0, 9)}, OptionCategory::Setting, "gRandomizeShuffleKeyRingsRandomCount", "", WidgetType::Slider, 8); + mOptions[RSK_KEYRINGS_GERUDO_FORTRESS] = Option::Bool("Gerudo Fortress", "gRandomizeShuffleKeyRingsGerudoFortress", "", IMFLAG_NONE); + mOptions[RSK_KEYRINGS_FOREST_TEMPLE] = Option::Bool("Forest Temple", "gRandomizeShuffleKeyRingsForestTemple", "", IMFLAG_NONE); + mOptions[RSK_KEYRINGS_FIRE_TEMPLE] = Option::Bool("Fire Temple", "gRandomizeShuffleKeyRingsFireTemple", "", IMFLAG_NONE); + mOptions[RSK_KEYRINGS_WATER_TEMPLE] = Option::Bool("Water Temple", "gRandomizeShuffleKeyRingsWaterTemple", "", IMFLAG_NONE); + mOptions[RSK_KEYRINGS_SPIRIT_TEMPLE] = Option::Bool("Spirit Temple", "gRandomizeShuffleKeyRingsSpiritTemple", "", IMFLAG_NONE); + mOptions[RSK_KEYRINGS_SHADOW_TEMPLE] = Option::Bool("Shadow Temple", "gRandomizeShuffleKeyRingsShadowTemple", "", IMFLAG_NONE); + mOptions[RSK_KEYRINGS_BOTTOM_OF_THE_WELL] = Option::Bool("Bottom of the Well", "gRandomizeShuffleKeyRingsBottomOfTheWell", "", IMFLAG_NONE); + mOptions[RSK_KEYRINGS_GTG] = Option::Bool("Gerudo Training Grounds", "gRandomizeShuffleKeyRingsGTG", "", IMFLAG_NONE); + mOptions[RSK_KEYRINGS_GANONS_CASTLE] = Option::Bool("Ganon's Castle", "gRandomizeShuffleKeyRingsGanonsCastle"); + mOptions[RSK_SKIP_CHILD_STEALTH] = Option::Bool("Skip Child Stealth", {"Don't Skip", "Skip"}, OptionCategory::Setting, "gRandomizeSkipChildZelda", mOptionDescriptions[RSK_SKIP_CHILD_STEALTH], WidgetType::Checkbox, RO_GENERIC_DONT_SKIP); + mOptions[RSK_SKIP_CHILD_ZELDA] = Option::Bool("Skip Child Zelda", {"Don't Skip", "Skip"}, OptionCategory::Setting, "gRandomizeSkipChildZelda", mOptionDescriptions[RSK_SKIP_CHILD_ZELDA], WidgetType::Checkbox, RO_GENERIC_DONT_SKIP); + mOptions[RSK_SKIP_TOWER_ESCAPE] = Option::Bool("Skip Tower Escape", {"Don't Skip", "Skip"}, OptionCategory::Setting, "gRandomizeSkipTowerEscape", mOptionDescriptions[RSK_SKIP_TOWER_ESCAPE], WidgetType::Checkbox, RO_GENERIC_DONT_SKIP); + mOptions[RSK_SKIP_EPONA_RACE] = Option::Bool("Skip Epona Race", {"Don't Skip", "Skip"}, OptionCategory::Setting, "gRandomizeSkipEponaRace", mOptionDescriptions[RSK_SKIP_EPONA_RACE], WidgetType::Checkbox, RO_GENERIC_DONT_SKIP); + mOptions[RSK_SKIP_SCARECROWS_SONG] = Option::Bool("Skip Scarecrow's Song", "gRandomizeSkipScarecrowsSong", mOptionDescriptions[RSK_SKIP_SCARECROWS_SONG]); + mOptions[RSK_BIG_POE_COUNT] = Option::U8("Big Poe Target Count", {NumOpts(1, 10)}, OptionCategory::Setting, "gRandomizeBigPoeTargetCount", mOptionDescriptions[RSK_BIG_POE_COUNT], WidgetType::Slider, 9); + mOptions[RSK_CUCCO_COUNT] = Option::U8("Cuccos to return", {NumOpts(0, 7)}, OptionCategory::Setting, "gRandomizeCuccosToReturn", mOptionDescriptions[RSK_CUCCO_COUNT], WidgetType::Slider, 7); + mOptions[RSK_COMPLETE_MASK_QUEST] = Option::Bool("Complete Mask Quest", "gRandomizeCompleteMaskQuest", mOptionDescriptions[RSK_COMPLETE_MASK_QUEST]); + mOptions[RSK_ENABLE_GLITCH_CUTSCENES] = Option::Bool("Enable Glitch-Useful Cutscenes", "gRandomizeEnableGlitchCutscenes", mOptionDescriptions[RSK_ENABLE_GLITCH_CUTSCENES]); + mOptions[RSK_GOSSIP_STONE_HINTS] = Option::U8("Gossip Stone Hints", {"No Hints", "Need Nothing", "Mask of Truth", "Stone of Agony"}, OptionCategory::Setting, "gRandomizeGossipStoneHints", mOptionDescriptions[RSK_GOSSIP_STONE_HINTS], WidgetType::Combobox, RO_GOSSIP_STONES_NEED_NOTHING, false, IMFLAG_NONE); + mOptions[RSK_HINT_CLARITY] = Option::U8("Hint Clarity", {"Obscure", "Ambiguous", "Clear"}, OptionCategory::Setting, "gRandomizeHintClarity", mOptionDescriptions[RSK_HINT_CLARITY], WidgetType::Combobox, RO_HINT_CLARITY_CLEAR, true, IMFLAG_INDENT); + mOptions[RSK_HINT_DISTRIBUTION] = Option::U8("Hint Distribution", {"Useless", "Balanced", "Strong", "Very Strong"}, OptionCategory::Setting, "gRandomizeHintDistribution", mOptionDescriptions[RSK_HINT_DISTRIBUTION], WidgetType::Combobox, RO_HINT_DIST_BALANCED, true, IMFLAG_UNINDENT); + mOptions[RSK_TOT_ALTAR_HINT] = Option::Bool("ToT Altar Hint", {"Off", "On"}, OptionCategory::Setting, "gRandomizeAltarHint", mOptionDescriptions[RSK_TOT_ALTAR_HINT], WidgetType::Checkbox, RO_GENERIC_ON, false, IMFLAG_INDENT); + mOptions[RSK_LIGHT_ARROWS_HINT] = Option::Bool("Light Arrow Hint", {"Off", "On"}, OptionCategory::Setting, "gRandomizeLAHint", mOptionDescriptions[RSK_LIGHT_ARROWS_HINT], WidgetType::Checkbox, RO_GENERIC_ON, false, IMFLAG_NONE); + mOptions[RSK_DAMPES_DIARY_HINT] = Option::Bool("Dampe's Diary Hint", "gRandomizeDampeHint", mOptionDescriptions[RSK_DAMPES_DIARY_HINT], IMFLAG_NONE); + mOptions[RSK_GREG_HINT] = Option::Bool("Greg the Green Rupee Hint", "gRandomizeGregHint", mOptionDescriptions[RSK_GREG_HINT], IMFLAG_NONE); + mOptions[RSK_SARIA_HINT] = Option::Bool("Saria's Hint", "gRandomizeSariaHint", mOptionDescriptions[RSK_SARIA_HINT], IMFLAG_NONE); + mOptions[RSK_FROGS_HINT] = Option::Bool("Frog Ocarina Game Hint", "gRandomizeFrogsHint", mOptionDescriptions[RSK_FROGS_HINT], IMFLAG_NONE); + mOptions[RSK_WARP_SONG_HINTS] = Option::Bool("Warp Song Hints", "gRandomizeWarpSongText", mOptionDescriptions[RSK_WARP_SONG_HINTS], IMFLAG_NONE, WidgetType::Checkbox, RO_GENERIC_ON); + mOptions[RSK_KAK_10_SKULLS_HINT] = Option::Bool("10 GS Hint", "gRandomize10GSHint", "", IMFLAG_NONE); + mOptions[RSK_KAK_20_SKULLS_HINT] = Option::Bool("20 GS Hint", "gRandomize20GSHint", "", IMFLAG_NONE); + mOptions[RSK_KAK_30_SKULLS_HINT] = Option::Bool("30 GS Hint", "gRandomize30GSHint", "", IMFLAG_NONE); + mOptions[RSK_KAK_40_SKULLS_HINT] = Option::Bool("40 GS Hint", "gRandomize40GSHint", "", IMFLAG_NONE); + mOptions[RSK_KAK_50_SKULLS_HINT] = Option::Bool("50 GS Hint", "gRandomize50GSHint"); + mOptions[RSK_SCRUB_TEXT_HINT] = Option::Bool("Scrub Hint Text", "gRandomizeScrubText", mOptionDescriptions[RSK_SCRUB_TEXT_HINT], IMFLAG_NONE); // TODO: Compasses show rewards/woth, maps show dungeon mode - mOptions[RSK_BLUE_FIRE_ARROWS] = Option::Bool("Blue Fire Arrows"); - mOptions[RSK_SUNLIGHT_ARROWS] = Option::Bool("Sunlight Arrows"); - mOptions[RSK_ITEM_POOL] = Option::U8("Item Pool", {"Plentiful", "Balanced", "Scarce", "Minimal"}, OptionCategory::Setting, RO_ITEM_POOL_BALANCED); - mOptions[RSK_ICE_TRAPS] = Option::U8("Ice Traps", {"Off", "Normal", "Extra", "Mayhem", "Onslaught"}, OptionCategory::Setting, RO_ICE_TRAPS_NORMAL); + mOptions[RSK_BLUE_FIRE_ARROWS] = Option::Bool("Blue Fire Arrows", "gRandomizeBlueFireArrows", mOptionDescriptions[RSK_BLUE_FIRE_ARROWS]); + mOptions[RSK_SUNLIGHT_ARROWS] = Option::Bool("Sunlight Arrows", "gRandomizeSunlightArrows", mOptionDescriptions[RSK_SUNLIGHT_ARROWS]); + mOptions[RSK_ITEM_POOL] = Option::U8("Item Pool", {"Plentiful", "Balanced", "Scarce", "Minimal"}, OptionCategory::Setting, "gRandomizeItemPool", mOptionDescriptions[RSK_ITEM_POOL], WidgetType::Combobox, RO_ITEM_POOL_BALANCED); + mOptions[RSK_ICE_TRAPS] = Option::U8("Ice Traps", {"Off", "Normal", "Extra", "Mayhem", "Onslaught"}, OptionCategory::Setting, "gRandomizeIceTraps", mOptionDescriptions[RSK_ICE_TRAPS], WidgetType::Combobox, RO_ICE_TRAPS_NORMAL); // TODO: Remove Double Defense, Progressive Goron Sword - mOptions[RSK_STARTING_OCARINA] = Option::U8("Start with Ocarina", {"Off", "Fairy Ocarina", "Ocarina of Time"}); - mOptions[RSK_STARTING_DEKU_SHIELD] = Option::Bool("Start with Deku Shield"); - mOptions[RSK_STARTING_KOKIRI_SWORD] = Option::Bool("Start with Kokiri Sword"); - mOptions[RSK_STARTING_MASTER_SWORD] = Option::Bool("Start with Master Sword"); - mOptions[RSK_STARTING_CONSUMABLES] = Option::Bool("Start with Consumables", {"No", "Yes"}); - mOptions[RSK_FULL_WALLETS] = Option::Bool("Full Wallets", {"No", "Yes"}); - mOptions[RSK_STARTING_ZELDAS_LULLABY] = Option::Bool("Start with Zelda's Lullaby"); - mOptions[RSK_STARTING_EPONAS_SONG] = Option::Bool("Start with Epona's Song"); - mOptions[RSK_STARTING_SARIAS_SONG] = Option::Bool("Start with Epona's Song"); - mOptions[RSK_STARTING_SUNS_SONG] = Option::Bool("Start with Sun's Song"); - mOptions[RSK_STARTING_SONG_OF_TIME] = Option::Bool("Start with Song of Time"); - mOptions[RSK_STARTING_SONG_OF_STORMS] = Option::Bool("Start with Song of Storms"); - mOptions[RSK_STARTING_MINUET_OF_FOREST] = Option::Bool("Start with Minuet of Forest"); - mOptions[RSK_STARTING_BOLERO_OF_FIRE] = Option::Bool("Start with Bolero of Fire"); - mOptions[RSK_STARTING_SERENADE_OF_WATER] = Option::Bool("Start with Serenade of Water"); - mOptions[RSK_STARTING_REQUIEM_OF_SPIRIT] = Option::Bool("Start with Requiem of Spirit"); - mOptions[RSK_STARTING_NOCTURNE_OF_SHADOW] = Option::Bool("Start with Nocturne of Shadow"); - mOptions[RSK_STARTING_PRELUDE_OF_LIGHT] = Option::Bool("Start with Prelude of Light"); - mOptions[RSK_STARTING_SKULLTULA_TOKEN] = Option::U8("Gold Skulltula Tokens", {NumOpts(0, 100)}); - mOptions[RSK_STARTING_HEARTS] = Option::U8("Hearts", {NumOpts(1, 20)}, OptionCategory::Setting, 2); + mOptions[RSK_STARTING_OCARINA] = Option::U8("Start with Ocarina", {"Off", "Fairy Ocarina", "Ocarina of Time"}, OptionCategory::Setting, "gRandomizeStartingOcarina", "", WidgetType::Combobox, RO_STARTING_OCARINA_OFF); + mOptions[RSK_STARTING_DEKU_SHIELD] = Option::Bool("Start with Deku Shield", "gRandomizeStartingDekuShield"); + mOptions[RSK_STARTING_KOKIRI_SWORD] = Option::Bool("Start with Kokiri Sword", "gRandomizeStartingKokiriSword"); + mOptions[RSK_STARTING_MASTER_SWORD] = Option::Bool("Start with Master Sword", "gRandomizeStartingMasterSword"); + mOptions[RSK_STARTING_CONSUMABLES] = Option::Bool("Start with Consumables", {"No", "Yes"}, OptionCategory::Setting, "gRandomizeStartingConsumables", "", WidgetType::Checkbox, RO_GENERIC_OFF); + mOptions[RSK_FULL_WALLETS] = Option::Bool("Full Wallets", {"No", "Yes"}, OptionCategory::Setting, "gRandomizeFullWallets", mOptionDescriptions[RSK_FULL_WALLETS], WidgetType::Checkbox, RO_GENERIC_OFF); + mOptions[RSK_STARTING_ZELDAS_LULLABY] = Option::Bool("Start with Zelda's Lullaby", "gRandomizeStartingZeldasLullaby", "", IMFLAG_NONE); + mOptions[RSK_STARTING_EPONAS_SONG] = Option::Bool("Start with Epona's Song", "gRandomizeStartingEponasSong", "", IMFLAG_NONE); + mOptions[RSK_STARTING_SARIAS_SONG] = Option::Bool("Start with Epona's Song", "gRandomizeStartingSariasSong", "", IMFLAG_NONE); + mOptions[RSK_STARTING_SUNS_SONG] = Option::Bool("Start with Sun's Song", "gRandomizeStartingSunsSong", "", IMFLAG_NONE); + mOptions[RSK_STARTING_SONG_OF_TIME] = Option::Bool("Start with Song of Time", "gRandomizeStartingSongOfTime", "", IMFLAG_NONE); + mOptions[RSK_STARTING_SONG_OF_STORMS] = Option::Bool("Start with Song of Storms", "gRandomizeStartingSongOfStorms", "", IMFLAG_NONE); + mOptions[RSK_STARTING_MINUET_OF_FOREST] = Option::Bool("Start with Minuet of Forest", "gRandomizeStartingMinuetOfForest", "", IMFLAG_NONE); + mOptions[RSK_STARTING_BOLERO_OF_FIRE] = Option::Bool("Start with Bolero of Fire", "gRandomizeStartingBoleroOfFire", "", IMFLAG_NONE); + mOptions[RSK_STARTING_SERENADE_OF_WATER] = Option::Bool("Start with Serenade of Water", "gRandomizeStartingSerenadeOfWater", "", IMFLAG_NONE); + mOptions[RSK_STARTING_REQUIEM_OF_SPIRIT] = Option::Bool("Start with Requiem of Spirit", "gRandomizeStartingRequiemOfSpirit", "", IMFLAG_NONE); + mOptions[RSK_STARTING_NOCTURNE_OF_SHADOW] = Option::Bool("Start with Nocturne of Shadow", "gRandomizeStartingNocturneOfShadow", "", IMFLAG_NONE); + mOptions[RSK_STARTING_PRELUDE_OF_LIGHT] = Option::Bool("Start with Prelude of Light", "gRandomizeStartingPreludeOfLight"); + mOptions[RSK_STARTING_SKULLTULA_TOKEN] = Option::U8("Gold Skulltula Tokens", {NumOpts(0, 100)}, OptionCategory::Setting, "", "gRandomizeStartingSkulltulaToken", WidgetType::Slider); + mOptions[RSK_STARTING_HEARTS] = Option::U8("Hearts", {NumOpts(1, 20)}, OptionCategory::Setting, "", "", WidgetType::Slider, 2); // TODO: Remainder of Starting Items - mOptions[RSK_LOGIC_RULES] = Option::U8("Logic", {"Glitchless", "Glitched", "No Logic", "Vanilla"}); - mOptions[RSK_ALL_LOCATIONS_REACHABLE] = Option::Bool("All Locations Reachable", {"Off", "On"}, OptionCategory::Setting, RO_GENERIC_ON); - mOptions[RSK_SKULLS_SUNS_SONG] = Option::Bool("Night Skulltula's Expect Sun's Song"); - mOptions[RSK_DAMAGE_MULTIPLIER] = Option::U8("Damage Multiplier", {"x1/2", "x1", "x2", "x4", "x8", "x16", "OHKO"}, OptionCategory::Setting, RO_DAMAGE_MULTIPLIER_DEFAULT); + mOptions[RSK_LOGIC_RULES] = Option::U8("Logic", {"Glitchless", "Glitched", "No Logic", "Vanilla"}, OptionCategory::Setting, "gRandomizeLogicRules", mOptionDescriptions[RSK_LOGIC_RULES], WidgetType::Combobox, RO_LOGIC_GLITCHLESS); + mOptions[RSK_ALL_LOCATIONS_REACHABLE] = Option::Bool("All Locations Reachable", {"Off", "On"}, OptionCategory::Setting, "gRandomizeAllLocationsReachable", mOptionDescriptions[RSK_ALL_LOCATIONS_REACHABLE], WidgetType::Checkbox, RO_GENERIC_ON); + mOptions[RSK_SKULLS_SUNS_SONG] = Option::Bool("Night Skulltula's Expect Sun's Song", "gRandomizeGsExpectSunsSong", mOptionDescriptions[RSK_SKULLS_SUNS_SONG]); + mOptions[RSK_DAMAGE_MULTIPLIER] = Option::U8("Damage Multiplier", {"x1/2", "x1", "x2", "x4", "x8", "x16", "OHKO"}, OptionCategory::Setting, "", "", WidgetType::Slider, RO_DAMAGE_MULTIPLIER_DEFAULT); + + mOptions[RSK_LANGUAGE] = Option::U8("Language", {"English", "German", "French"}, OptionCategory::Setting, "gLanguages", "", WidgetType::Combobox, LANGUAGE_ENG); // clang-format on mExcludeLocationsOptionsGroups.reserve(SPOILER_COLLECTION_GROUP_COUNT); @@ -543,6 +548,201 @@ void Settings::CreateOptions() { &mTrickOptions[RT_GANON_MQ_LIGHT_TRIAL], }, false); // TODO: Glitches + mOptionGroups[RSG_AREA_ACCESS_IMGUI] = OptionGroup::SubGroup("Area Access", { + &mOptions[RSK_FOREST], + &mOptions[RSK_KAK_GATE], + &mOptions[RSK_DOOR_OF_TIME], + &mOptions[RSK_ZORAS_FOUNTAIN], + }, false, WidgetContainerType::COLUMN); + mOptionGroups[RSG_WORLD_IMGUI] = OptionGroup::SubGroup("World Settings", { + &mOptions[RSK_STARTING_AGE], + &mOptions[RSK_GERUDO_FORTRESS], + &mOptions[RSK_RAINBOW_BRIDGE], + &mOptions[RSK_BRIDGE_OPTIONS], + &mOptions[RSK_RAINBOW_BRIDGE_STONE_COUNT], + &mOptions[RSK_RAINBOW_BRIDGE_MEDALLION_COUNT], + &mOptions[RSK_RAINBOW_BRIDGE_REWARD_COUNT], + &mOptions[RSK_RAINBOW_BRIDGE_DUNGEON_COUNT], + &mOptions[RSK_RAINBOW_BRIDGE_TOKEN_COUNT], + &mOptions[RSK_GANONS_TRIALS], + &mOptions[RSK_TRIAL_COUNT], + &mOptions[RSK_MQ_DUNGEON_RANDOM], + &mOptions[RSK_MQ_DUNGEON_COUNT], + &mOptions[RSK_MQ_DUNGEON_SET], + &mOptions[RSK_MQ_DEKU_TREE], + &mOptions[RSK_MQ_DODONGOS_CAVERN], + &mOptions[RSK_MQ_JABU_JABU], + &mOptions[RSK_MQ_FOREST_TEMPLE], + &mOptions[RSK_MQ_FIRE_TEMPLE], + &mOptions[RSK_MQ_WATER_TEMPLE], + &mOptions[RSK_MQ_SPIRIT_TEMPLE], + &mOptions[RSK_MQ_SHADOW_TEMPLE], + &mOptions[RSK_MQ_BOTTOM_OF_THE_WELL], + &mOptions[RSK_MQ_ICE_CAVERN], + &mOptions[RSK_MQ_GTG], + &mOptions[RSK_MQ_GANONS_CASTLE], + &mOptions[RSK_TRIFORCE_HUNT], + &mOptions[RSK_TRIFORCE_HUNT_PIECES_TOTAL], + &mOptions[RSK_TRIFORCE_HUNT_PIECES_REQUIRED] + }, false, WidgetContainerType::COLUMN); + mOptionGroups[RSG_SHUFFLE_ENTRANCES_IMGUI] = OptionGroup::SubGroup("Shuffle Entrances", { + &mOptions[RSK_SHUFFLE_DUNGEON_ENTRANCES], + &mOptions[RSK_SHUFFLE_BOSS_ENTRANCES], + &mOptions[RSK_SHUFFLE_OVERWORLD_ENTRANCES], + &mOptions[RSK_SHUFFLE_INTERIOR_ENTRANCES], + &mOptions[RSK_SHUFFLE_GROTTO_ENTRANCES], + &mOptions[RSK_SHUFFLE_OWL_DROPS], + &mOptions[RSK_SHUFFLE_WARP_SONGS], + &mOptions[RSK_SHUFFLE_OVERWORLD_SPAWNS], + &mOptions[RSK_DECOUPLED_ENTRANCES], + &mOptions[RSK_MIXED_ENTRANCE_POOLS], + &mOptions[RSK_MIX_DUNGEON_ENTRANCES], + &mOptions[RSK_MIX_OVERWORLD_ENTRANCES], + &mOptions[RSK_MIX_INTERIOR_ENTRANCES], + &mOptions[RSK_MIX_GROTTO_ENTRANCES] + }, false, WidgetContainerType::COLUMN); + mOptionGroups[RSG_WORLD_IMGUI_TABLE] = OptionGroup::SubGroup("World", { + &mOptionGroups[RSG_AREA_ACCESS_IMGUI], + &mOptionGroups[RSG_WORLD_IMGUI], + &mOptionGroups[RSG_SHUFFLE_ENTRANCES_IMGUI], + }, false, WidgetContainerType::TABLE); + mOptionGroups[RSG_SHUFFLE_ITEMS_IMGUI] = OptionGroup::SubGroup("Shuffle Items", { + &mOptions[RSK_SHUFFLE_SONGS], + &mOptions[RSK_SHUFFLE_TOKENS], + &mOptions[RSK_SKULLS_SUNS_SONG], + &mOptions[RSK_SHUFFLE_KOKIRI_SWORD], + &mOptions[RSK_SHUFFLE_MASTER_SWORD], + &mOptions[RSK_SHUFFLE_OCARINA], + &mOptions[RSK_SHUFFLE_WEIRD_EGG], + &mOptions[RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD] + }, false, WidgetContainerType::COLUMN); + mOptionGroups[RSG_SHUFFLE_NPCS_IMGUI] = OptionGroup::SubGroup("Shuffle NPCs & Merchants", { + &mOptions[RSK_SHOPSANITY], + &mOptions[RSK_SHOPSANITY_PRICES], + &mOptions[RSK_SHOPSANITY_PRICES_AFFORDABLE], + &mOptions[RSK_SHUFFLE_SCRUBS], + &mOptions[RSK_SHUFFLE_COWS], + &mOptions[RSK_SHUFFLE_MAGIC_BEANS], + &mOptions[RSK_SHUFFLE_MERCHANTS], + &mOptions[RSK_SHUFFLE_ADULT_TRADE], + &mOptions[RSK_SHUFFLE_100_GS_REWARD], + &mOptions[RSK_SHUFFLE_BOSS_SOULS], + }, false, WidgetContainerType::COLUMN); + mOptionGroups[RSG_SHUFFLE_DUNGEON_ITEMS_IMGUI] = OptionGroup::SubGroup("Shuffle Dungeon Items", { + &mOptions[RSK_SHUFFLE_MAPANDCOMPASS], + &mOptions[RSK_KEYSANITY], + &mOptions[RSK_GERUDO_KEYS], + &mOptions[RSK_BOSS_KEYSANITY], + &mOptions[RSK_GANONS_BOSS_KEY], + &mOptions[RSK_LACS_STONE_COUNT], + &mOptions[RSK_LACS_MEDALLION_COUNT], + &mOptions[RSK_LACS_DUNGEON_COUNT], + &mOptions[RSK_LACS_REWARD_COUNT], + &mOptions[RSK_LACS_TOKEN_COUNT], + &mOptions[RSK_LACS_OPTIONS], + &mOptions[RSK_KEYRINGS], + &mOptions[RSK_KEYRINGS_RANDOM_COUNT], + &mOptions[RSK_KEYRINGS_GERUDO_FORTRESS], + &mOptions[RSK_KEYRINGS_FOREST_TEMPLE], + &mOptions[RSK_KEYRINGS_FIRE_TEMPLE], + &mOptions[RSK_KEYRINGS_WATER_TEMPLE], + &mOptions[RSK_KEYRINGS_SPIRIT_TEMPLE], + &mOptions[RSK_KEYRINGS_SHADOW_TEMPLE], + &mOptions[RSK_KEYRINGS_BOTTOM_OF_THE_WELL], + &mOptions[RSK_KEYRINGS_GTG], + &mOptions[RSK_KEYRINGS_GANONS_CASTLE], + }, false, WidgetContainerType::COLUMN); + mOptionGroups[RSG_ITEMS_IMGUI_TABLE] = OptionGroup::SubGroup("Items", { + &mOptionGroups[RSG_SHUFFLE_ITEMS_IMGUI], + &mOptionGroups[RSG_SHUFFLE_NPCS_IMGUI], + &mOptionGroups[RSG_SHUFFLE_DUNGEON_ITEMS_IMGUI], + }, false, WidgetContainerType::TABLE); + mOptionGroups[RSG_TIMESAVERS_IMGUI] = OptionGroup::SubGroup("Timesavers", { + &mOptions[RSK_CUCCO_COUNT], + &mOptions[RSK_BIG_POE_COUNT], + &mOptions[RSK_SKIP_CHILD_STEALTH], + &mOptions[RSK_SKIP_CHILD_ZELDA], + &mOptions[RSK_SKIP_EPONA_RACE], + &mOptions[RSK_SKIP_TOWER_ESCAPE], + &mOptions[RSK_COMPLETE_MASK_QUEST], + &mOptions[RSK_SKIP_SCARECROWS_SONG] + }, false, WidgetContainerType::COLUMN); + mOptionGroups[RSG_ITEM_POOL_HINTS_IMGUI] = OptionGroup::SubGroup("", { + &mOptions[RSK_ITEM_POOL], + &mOptions[RSK_ICE_TRAPS], + &mOptions[RSK_GOSSIP_STONE_HINTS], + &mOptions[RSK_HINT_CLARITY], + &mOptions[RSK_HINT_DISTRIBUTION], + }, false, WidgetContainerType::SECTION); + mOptionGroups[RSG_EXTRA_HINTS_IMGUI] = OptionGroup::SubGroup("Extra Hints", { + &mOptions[RSK_TOT_ALTAR_HINT], + &mOptions[RSK_LIGHT_ARROWS_HINT], + &mOptions[RSK_DAMPES_DIARY_HINT], + &mOptions[RSK_GREG_HINT], + &mOptions[RSK_SARIA_HINT], + &mOptions[RSK_FROGS_HINT], + &mOptions[RSK_WARP_SONG_HINTS], + &mOptions[RSK_SCRUB_TEXT_HINT], + &mOptions[RSK_KAK_10_SKULLS_HINT], + &mOptions[RSK_KAK_20_SKULLS_HINT], + &mOptions[RSK_KAK_30_SKULLS_HINT], + &mOptions[RSK_KAK_40_SKULLS_HINT], + &mOptions[RSK_KAK_50_SKULLS_HINT] + }, false, WidgetContainerType::SECTION, "This setting adds some hints at locations other than Gossip Stones.\n\n" + "House of Skulltula: # - Talking to a cursed House of Skulltula resident will tell you the reward" + "they will give you for obtaining that many tokens."); + mOptionGroups[RSG_ITEM_POOL_HINTS_IMGUI_COLUMN] = OptionGroup::SubGroup("Item Pool & Hints", std::initializer_list{ + &mOptionGroups[RSG_ITEM_POOL_HINTS_IMGUI], + &mOptionGroups[RSG_EXTRA_HINTS_IMGUI], + }, false, WidgetContainerType::COLUMN); + mOptionGroups[RSG_ADDITIONAL_FEATURES_IMGUI] = OptionGroup::SubGroup("Additional Features", { + &mOptions[RSK_FULL_WALLETS], + &mOptions[RSK_BOMBCHUS_IN_LOGIC], + &mOptions[RSK_ENABLE_BOMBCHU_DROPS], + &mOptions[RSK_BLUE_FIRE_ARROWS], + &mOptions[RSK_SUNLIGHT_ARROWS] + }, false, WidgetContainerType::COLUMN); + mOptionGroups[RSG_GAMEPLAY_IMGUI_TABLE] = OptionGroup::SubGroup("Gameplay", { + &mOptionGroups[RSG_TIMESAVERS_IMGUI], + &mOptionGroups[RSG_ITEM_POOL_HINTS_IMGUI_COLUMN], + &mOptionGroups[RSG_ADDITIONAL_FEATURES_IMGUI] + }, false, WidgetContainerType::TABLE); + mOptionGroups[RSG_STARTING_EQUIPMENT_IMGUI] = OptionGroup::SubGroup("Starting Equipment", { + &mOptions[RSK_LINKS_POCKET], + &mOptions[RSK_STARTING_KOKIRI_SWORD], + &mOptions[RSK_STARTING_MASTER_SWORD], + &mOptions[RSK_STARTING_DEKU_SHIELD] + }, false, WidgetContainerType::COLUMN); + mOptionGroups[RSG_STARTING_ITEMS_IMGUI] = OptionGroup::SubGroup("Starting Items", { + &mOptions[RSK_STARTING_OCARINA], + &mOptions[RSK_STARTING_CONSUMABLES], + &mOptions[RSK_STARTING_SKULLTULA_TOKEN], + }, false, WidgetContainerType::COLUMN); + mOptionGroups[RSG_STARTING_NORMAL_SONGS_IMGUI] = OptionGroup::SubGroup("Normal Songs", { + &mOptions[RSK_STARTING_ZELDAS_LULLABY], + &mOptions[RSK_STARTING_EPONAS_SONG], + &mOptions[RSK_STARTING_SARIAS_SONG], + &mOptions[RSK_STARTING_SUNS_SONG], + &mOptions[RSK_STARTING_SONG_OF_TIME], + &mOptions[RSK_STARTING_SONG_OF_STORMS], + }, false, WidgetContainerType::SECTION); + mOptionGroups[RSG_STARTING_WARP_SONGS_IMGUI] = OptionGroup::SubGroup("Warp Songs", { + &mOptions[RSK_STARTING_MINUET_OF_FOREST], + &mOptions[RSK_STARTING_BOLERO_OF_FIRE], + &mOptions[RSK_STARTING_SERENADE_OF_WATER], + &mOptions[RSK_STARTING_REQUIEM_OF_SPIRIT], + &mOptions[RSK_STARTING_NOCTURNE_OF_SHADOW], + &mOptions[RSK_STARTING_PRELUDE_OF_LIGHT] + }, false, WidgetContainerType::SECTION); + mOptionGroups[RSG_STARTING_SONGS_IMGUI] = OptionGroup::SubGroup("Starting Songs", std::initializer_list({ + &mOptionGroups[RSG_STARTING_NORMAL_SONGS_IMGUI], + &mOptionGroups[RSG_STARTING_WARP_SONGS_IMGUI], + }), false, WidgetContainerType::COLUMN); + mOptionGroups[RSG_STARTING_INVENTORY_IMGUI_TABLE] = OptionGroup::SubGroup("Starting Inventory", { + &mOptionGroups[RSG_STARTING_EQUIPMENT_IMGUI], + &mOptionGroups[RSG_STARTING_ITEMS_IMGUI], + &mOptionGroups[RSG_STARTING_SONGS_IMGUI] + }, false, WidgetContainerType::TABLE); mOptionGroups[RSG_OPEN] = OptionGroup("Open Settings", { &mOptions[RSK_FOREST], &mOptions[RSK_KAK_GATE], @@ -707,11 +907,10 @@ void Settings::CreateOptions() { &mOptions[RSK_BLUE_FIRE_ARROWS], &mOptions[RSK_SUNLIGHT_ARROWS], }); - // TODO: For some reason this group and only this group is an ambiguous constructor call, despite - // the initializer list only having Option* in it. For now explictly declare and initialize - // a vector of Option* and construct the group with that. - const std::vector itemPoolOptions = {&mOptions[RSK_ITEM_POOL], &mOptions[RSK_ICE_TRAPS]}; - mOptionGroups[RSG_ITEM_POOL] = OptionGroup("Item Pool Settings", itemPoolOptions); + mOptionGroups[RSG_ITEM_POOL] = OptionGroup("Item Pool Settings", std::initializer_list({ + &mOptions[RSK_ITEM_POOL], + &mOptions[RSK_ICE_TRAPS] + })); // TODO: Progressive Goron Sword, Remove Double Defense mOptionGroups[RSG_EXCLUDES_KOKIRI_FOREST] = OptionGroup::SubGroup("Kokiri Forest", mExcludeLocationsOptionsGroups[GROUP_KOKIRI_FOREST], false); mOptionGroups[RSG_EXCLUDES_LOST_WOODS] = OptionGroup::SubGroup("Lost Woods", mExcludeLocationsOptionsGroups[GROUP_LOST_WOODS], false); @@ -930,7 +1129,7 @@ void Settings::CreateOptions() { }; } -Option& Settings::Setting(const RandomizerSettingKey key) { +Option& Settings::GetOption(const RandomizerSettingKey key) { return mOptions[key]; } @@ -990,51 +1189,482 @@ const OptionGroup& Settings::GetOptionGroup(const RandomizerSettingGroupKey key) return mOptionGroups[key]; } -void Settings::UpdateSettings(std::unordered_map cvarSettings, const std::set& excludedLocations, const std::set& enabledTricks) { - const auto ctx = Context::GetInstance(); +void Settings::SetAllFromCVar() { + for (auto& option : mOptions) { + option.SetFromCVar(); + } +} + +void Settings::UpdateOptionProperties() { + // Starting Age - Disabled when Forest is set to Closed or under very specific conditions + if (CVarGetInteger("gRandomizeForest", RO_FOREST_CLOSED) == RO_FOREST_CLOSED || + (CVarGetInteger("gRandomizeDoorOfTime", RO_DOOROFTIME_CLOSED) == RO_DOOROFTIME_CLOSED && + CVarGetInteger("gRandomizeShuffleOcarinas", RO_GENERIC_OFF) == RO_GENERIC_OFF)) /* closed door of time with ocarina shuffle off */ { + mOptions[RSK_STARTING_AGE].Disable("This option is disabled due to other optionos making the game unbeatable"); + } else { + mOptions[RSK_STARTING_AGE].Enable(); + } + // Default to hiding bridge opts and the extra sliders. + mOptions[RSK_RAINBOW_BRIDGE].AddFlag(IMFLAG_SEPARATOR_BOTTOM); + mOptions[RSK_BRIDGE_OPTIONS].Hide(); + mOptions[RSK_RAINBOW_BRIDGE_STONE_COUNT].Hide(); + mOptions[RSK_RAINBOW_BRIDGE_MEDALLION_COUNT].Hide(); + mOptions[RSK_RAINBOW_BRIDGE_REWARD_COUNT].Hide(); + mOptions[RSK_RAINBOW_BRIDGE_DUNGEON_COUNT].Hide(); + mOptions[RSK_RAINBOW_BRIDGE_TOKEN_COUNT].Hide(); + const uint8_t bridgeOpt = CVarGetInteger("gRandomizeBridgeRewardOptions", RO_BRIDGE_STANDARD_REWARD); + switch (CVarGetInteger("gRandomizeRainbowBridge", RO_BRIDGE_VANILLA)) { + case RO_BRIDGE_STONES: + // Show Bridge Options and Stone Count slider + mOptions[RSK_RAINBOW_BRIDGE].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM); + mOptions[RSK_BRIDGE_OPTIONS].Unhide(); + mOptions[RSK_RAINBOW_BRIDGE_STONE_COUNT].Unhide(); + if (bridgeOpt == RO_BRIDGE_GREG_REWARD) { + if (mOptions[RSK_RAINBOW_BRIDGE_STONE_COUNT].GetOptionCount() == 4) { + mOptions[RSK_RAINBOW_BRIDGE_STONE_COUNT].ChangeOptions(NumOpts(0, 4)); + } + } else { + if (mOptions[RSK_RAINBOW_BRIDGE_STONE_COUNT].GetOptionCount() == 5) { + mOptions[RSK_RAINBOW_BRIDGE_STONE_COUNT].ChangeOptions(NumOpts(0, 3)); + } + } + break; + case RO_BRIDGE_MEDALLIONS: + // Show Bridge Options and Medallion Count Slider + mOptions[RSK_RAINBOW_BRIDGE].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM); + mOptions[RSK_BRIDGE_OPTIONS].Unhide(); + mOptions[RSK_RAINBOW_BRIDGE_REWARD_COUNT].Unhide(); + if (bridgeOpt == RO_BRIDGE_GREG_REWARD) { + if (mOptions[RSK_RAINBOW_BRIDGE_MEDALLION_COUNT].GetOptionCount() == 7) { + mOptions[RSK_RAINBOW_BRIDGE_MEDALLION_COUNT].ChangeOptions(NumOpts(0, 7)); + } + } else { + if (mOptions[RSK_RAINBOW_BRIDGE_MEDALLION_COUNT].GetOptionCount() == 8) { + mOptions[RSK_RAINBOW_BRIDGE_MEDALLION_COUNT].ChangeOptions(NumOpts(0, 6)); + } + } + break; + case RO_BRIDGE_DUNGEON_REWARDS: + // Show Bridge Options and Dungeon Reward Count Slider + mOptions[RSK_RAINBOW_BRIDGE].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM); + mOptions[RSK_BRIDGE_OPTIONS].Unhide(); + mOptions[RSK_RAINBOW_BRIDGE_REWARD_COUNT].Unhide(); + if (bridgeOpt == RO_BRIDGE_GREG_REWARD) { + if (mOptions[RSK_RAINBOW_BRIDGE_REWARD_COUNT].GetOptionCount() == 10) { + mOptions[RSK_RAINBOW_BRIDGE_REWARD_COUNT].ChangeOptions(NumOpts(0, 10)); + } + } else { + if (mOptions[RSK_RAINBOW_BRIDGE_REWARD_COUNT].GetOptionCount() == 11) { + mOptions[RSK_RAINBOW_BRIDGE_REWARD_COUNT].ChangeOptions(NumOpts(0, 9)); + } + } + break; + case RO_BRIDGE_DUNGEONS: + // Show Bridge Options and Dungeon Count Slider + mOptions[RSK_RAINBOW_BRIDGE].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM); + mOptions[RSK_BRIDGE_OPTIONS].Unhide(); + mOptions[RSK_RAINBOW_BRIDGE_DUNGEON_COUNT].Unhide(); + if (bridgeOpt == RO_BRIDGE_GREG_REWARD) { + if (mOptions[RSK_RAINBOW_BRIDGE_DUNGEON_COUNT].GetOptionCount() == 9) { + mOptions[RSK_RAINBOW_BRIDGE_DUNGEON_COUNT].ChangeOptions(NumOpts(0, 9)); + } + } else { + if (mOptions[RSK_RAINBOW_BRIDGE_DUNGEON_COUNT].GetOptionCount() == 10) { + mOptions[RSK_RAINBOW_BRIDGE_DUNGEON_COUNT].ChangeOptions(NumOpts(0, 8)); + } + } + break; + case RO_BRIDGE_TOKENS: + // Show token count slider (not bridge options) + mOptions[RSK_RAINBOW_BRIDGE].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM); + mOptions[RSK_BRIDGE_OPTIONS].Hide(); + mOptions[RSK_RAINBOW_BRIDGE_TOKEN_COUNT].Unhide(); + break; + default: + break; + } + // Only show the trial count slider if Trials is set to Set Number + if (CVarGetInteger("gRandomizeGanonTrial", RO_GANONS_TRIALS_SKIP) == RO_GANONS_TRIALS_SET_NUMBER) { + mOptions[RSK_GANONS_TRIALS].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM); + mOptions[RSK_TRIAL_COUNT].Unhide(); + } else { + mOptions[RSK_GANONS_TRIALS].AddFlag(IMFLAG_SEPARATOR_BOTTOM); + mOptions[RSK_TRIAL_COUNT].Hide(); + } + // Don't show any MQ options if both quests aren't available + if (!(OTRGlobals::Instance->HasMasterQuest() && OTRGlobals::Instance->HasOriginal())) { + mOptions[RSK_MQ_DUNGEON_RANDOM].Hide(); + mOptions[RSK_MQ_DUNGEON_COUNT].Hide(); + mOptions[RSK_MQ_DUNGEON_SET].Hide(); + mOptions[RSK_MQ_DEKU_TREE].Hide(); + mOptions[RSK_MQ_DODONGOS_CAVERN].Hide(); + mOptions[RSK_MQ_JABU_JABU].Hide(); + mOptions[RSK_MQ_FOREST_TEMPLE].Hide(); + mOptions[RSK_MQ_FIRE_TEMPLE].Hide(); + mOptions[RSK_MQ_WATER_TEMPLE].Hide(); + mOptions[RSK_MQ_SPIRIT_TEMPLE].Hide(); + mOptions[RSK_MQ_SHADOW_TEMPLE].Hide(); + mOptions[RSK_MQ_BOTTOM_OF_THE_WELL].Hide(); + mOptions[RSK_MQ_ICE_CAVERN].Hide(); + mOptions[RSK_MQ_GTG].Hide(); + mOptions[RSK_MQ_GANONS_CASTLE].Hide(); + } else { + // If any MQ Options are available, show the MQ Dungeon Randomization Combobox + mOptions[RSK_MQ_DUNGEON_RANDOM].Unhide(); + switch(CVarGetInteger("gRandomizeMqDungeons", RO_MQ_DUNGEONS_NONE)) { + // If No MQ Dungeons, add a separator after the combobx and hide + // the count slider and the toggle for individual dungeon selections. + case RO_MQ_DUNGEONS_NONE: + mOptions[RSK_MQ_DUNGEON_RANDOM].AddFlag(IMFLAG_SEPARATOR_BOTTOM); + mOptions[RSK_MQ_DUNGEON_COUNT].Hide(); + mOptions[RSK_MQ_DUNGEON_SET].Hide(); + break; + // If Set Number, remove the separator and show both the count slider and the + // individual dungeon selection toggle. + case RO_MQ_DUNGEONS_SET_NUMBER: + mOptions[RSK_MQ_DUNGEON_RANDOM].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM); + mOptions[RSK_MQ_DUNGEON_COUNT].Unhide(); + mOptions[RSK_MQ_DUNGEON_SET].Unhide(); + break; + // else if random number or selection only, remove the separator and only show + // the individual dungeon selection toggle. + case RO_MQ_DUNGEONS_RANDOM_NUMBER: + case RO_MQ_DUNGEONS_SELECTION: + mOptions[RSK_MQ_DUNGEON_RANDOM].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM); + mOptions[RSK_MQ_DUNGEON_COUNT].Hide(); + mOptions[RSK_MQ_DUNGEON_SET].Unhide(); + break; + default: + break; + } + // Controls whether or not to show the selectors for individual dungeons. + if (CVarGetInteger("gRandomizeMqDungeons", RO_MQ_DUNGEONS_NONE) != RO_MQ_DUNGEONS_NONE && + CVarGetInteger("gRandomizeMqDungeonsSelection", RO_GENERIC_OFF) == RO_GENERIC_ON) { + // if showing the dungeon selectors, remove the separator after the Set Dungeons checkbox. + mOptions[RSK_MQ_DUNGEON_SET].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM); + mOptions[RSK_MQ_DEKU_TREE].Unhide(); + mOptions[RSK_MQ_DODONGOS_CAVERN].Unhide(); + mOptions[RSK_MQ_JABU_JABU].Unhide(); + mOptions[RSK_MQ_FOREST_TEMPLE].Unhide(); + mOptions[RSK_MQ_FIRE_TEMPLE].Unhide(); + mOptions[RSK_MQ_WATER_TEMPLE].Unhide(); + mOptions[RSK_MQ_SPIRIT_TEMPLE].Unhide(); + mOptions[RSK_MQ_SHADOW_TEMPLE].Unhide(); + mOptions[RSK_MQ_BOTTOM_OF_THE_WELL].Unhide(); + mOptions[RSK_MQ_ICE_CAVERN].Unhide(); + mOptions[RSK_MQ_GTG].Unhide(); + mOptions[RSK_MQ_GANONS_CASTLE].Unhide(); + } else { + // If those are not shown, add a separator after the Set Dungeons checkbox. + mOptions[RSK_MQ_DUNGEON_SET].AddFlag(IMFLAG_SEPARATOR_BOTTOM); + mOptions[RSK_MQ_DEKU_TREE].Hide(); + mOptions[RSK_MQ_DODONGOS_CAVERN].Hide(); + mOptions[RSK_MQ_JABU_JABU].Hide(); + mOptions[RSK_MQ_FOREST_TEMPLE].Hide(); + mOptions[RSK_MQ_FIRE_TEMPLE].Hide(); + mOptions[RSK_MQ_WATER_TEMPLE].Hide(); + mOptions[RSK_MQ_SPIRIT_TEMPLE].Hide(); + mOptions[RSK_MQ_SHADOW_TEMPLE].Hide(); + mOptions[RSK_MQ_BOTTOM_OF_THE_WELL].Hide(); + mOptions[RSK_MQ_ICE_CAVERN].Hide(); + mOptions[RSK_MQ_GTG].Hide(); + mOptions[RSK_MQ_GANONS_CASTLE].Hide(); + } + } + // Disable interaction with Set Dungeons checkbox if MQ Dungeon Randomization is set to Selection Only. + if (CVarGetInteger("gRandomizeMqDungeons", RO_MQ_DUNGEONS_NONE) == RO_MQ_DUNGEONS_SELECTION) { + mOptions[RSK_MQ_DUNGEON_SET].Disable("This option is force-enabled because Master Quest Dungeons is set to Selection Only", UIWidgets::CheckboxGraphics::Checkmark); + } else { + mOptions[RSK_MQ_DUNGEON_SET].Enable(); + } + // Remove the pieces required/total sliders and add a separator after Tirforce Hunt if Triforce Hunt is off + if (CVarGetInteger("gRandomizeTriforceHunt", RO_GENERIC_OFF) == RO_GENERIC_OFF) { + mOptions[RSK_TRIFORCE_HUNT_PIECES_REQUIRED].Hide(); + mOptions[RSK_TRIFORCE_HUNT_PIECES_TOTAL].Hide(); + mOptions[RSK_TRIFORCE_HUNT].AddFlag(IMFLAG_SEPARATOR_BOTTOM); + } else { + mOptions[RSK_TRIFORCE_HUNT_PIECES_REQUIRED].Unhide(); + mOptions[RSK_TRIFORCE_HUNT_PIECES_TOTAL].Unhide(); + mOptions[RSK_TRIFORCE_HUNT].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM); + } + // Update triforce pieces required to be capped at the current value for pieces total. + const uint8_t triforceTotal = CVarGetInteger("gRandomizeTriforceHuntTotalPieces", 30); + if (mOptions[RSK_TRIFORCE_HUNT_PIECES_REQUIRED].GetOptionCount() != triforceTotal + 1) { + mOptions[RSK_TRIFORCE_HUNT_PIECES_REQUIRED].ChangeOptions(NumOpts(1, triforceTotal + 1)); + } + // Show mixed entrance pool options if mixed entrance pools are enabled at all. + if (CVarGetInteger("gRandomizeMixedEntrances", RO_GENERIC_OFF)) { + mOptions[RSK_MIXED_ENTRANCE_POOLS].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM); + mOptions[RSK_MIX_DUNGEON_ENTRANCES].Unhide(); + mOptions[RSK_MIX_OVERWORLD_ENTRANCES].Unhide(); + mOptions[RSK_MIX_INTERIOR_ENTRANCES].Unhide(); + mOptions[RSK_MIX_GROTTO_ENTRANCES].Unhide(); + } else { + mOptions[RSK_MIXED_ENTRANCE_POOLS].AddFlag(IMFLAG_SEPARATOR_BOTTOM); + mOptions[RSK_MIX_DUNGEON_ENTRANCES].Hide(); + mOptions[RSK_MIX_OVERWORLD_ENTRANCES].Hide(); + mOptions[RSK_MIX_INTERIOR_ENTRANCES].Hide(); + mOptions[RSK_MIX_GROTTO_ENTRANCES].Hide(); + } + // Shuffle Kokiri Sword - Disabled when Start with Kokiri Sword is active + if (CVarGetInteger("gRandomizeStartingKokiriSword", RO_GENERIC_OFF)) { + mOptions[RSK_SHUFFLE_KOKIRI_SWORD].Disable("This option is disabled because \"Start with Kokiri Sword\" is enabled."); + } else { + mOptions[RSK_SHUFFLE_KOKIRI_SWORD].Enable(); + } + // Shuffle Master Sword - Disabled when Start with Master Sword is active + if (CVarGetInteger("gRandomizeStartingMasterSword", RO_GENERIC_OFF)) { + mOptions[RSK_SHUFFLE_MASTER_SWORD].Disable("This option is disabled because \"Start with Master Sword\" is enabled"); + } else { + mOptions[RSK_SHUFFLE_MASTER_SWORD].Enable(); + } + // Shuffle Ocarinas - Disabled when Start with Ocarina is active + if (CVarGetInteger("gRandomizeStartingOcarina", RO_STARTING_OCARINA_OFF)) { + mOptions[RSK_SHUFFLE_OCARINA].Disable("This option is disabled because \"Start with Fairy Ocarina\" is enabled."); + } else { + mOptions[RSK_SHUFFLE_OCARINA].Enable(); + } + // Shuffle Weird Egg - Disabled when Skip Child Zelda is active + if (CVarGetInteger("gRandomizeSkipChildZelda", RO_GENERIC_DONT_SKIP)) { + mOptions[RSK_SHUFFLE_WEIRD_EGG].Disable("This option is disabled because \"Skip Child Zelda\" is enabled."); + } else { + mOptions[RSK_SHUFFLE_WEIRD_EGG].Enable(); + } + // Hide shopsanity prices if shopsanity is off or zero + switch (CVarGetInteger("gRandomizeShopsanity", RO_SHOPSANITY_OFF)) { + case RO_SHOPSANITY_OFF: + case RO_SHOPSANITY_ZERO_ITEMS: + mOptions[RSK_SHOPSANITY].AddFlag(IMFLAG_SEPARATOR_BOTTOM); + mOptions[RSK_SHOPSANITY_PRICES].Hide(); + mOptions[RSK_SHOPSANITY_PRICES_AFFORDABLE].Hide(); + break; + default: + mOptions[RSK_SHOPSANITY].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM); + mOptions[RSK_SHOPSANITY_PRICES].Unhide(); + mOptions[RSK_SHOPSANITY_PRICES_AFFORDABLE].Unhide(); + break; + } + // Shuffle 100 GS Reward - Force-Enabled if Ganon's Boss Key is on the 100 GS Reward + if (CVarGetInteger("gRandomizeShuffleGanonBossKey", RO_GANON_BOSS_KEY_VANILLA) == RO_GANON_BOSS_KEY_KAK_TOKENS) { + mOptions[RSK_SHUFFLE_100_GS_REWARD].Disable("This option is force-enabled because \"Ganon's Boss Key\" is set to \"100 GS Reward.\"", UIWidgets::CheckboxGraphics::Checkmark); + } else { + mOptions[RSK_SHUFFLE_100_GS_REWARD].Enable(); + } + // Default state for Keyrings GUI + mOptions[RSK_KEYRINGS].AddFlag(IMFLAG_SEPARATOR_BOTTOM); + mOptions[RSK_KEYRINGS_RANDOM_COUNT].Hide(); + mOptions[RSK_KEYRINGS_GERUDO_FORTRESS].Hide(); + mOptions[RSK_KEYRINGS_FOREST_TEMPLE].Hide(); + mOptions[RSK_KEYRINGS_FIRE_TEMPLE].Hide(); + mOptions[RSK_KEYRINGS_WATER_TEMPLE].Hide(); + mOptions[RSK_KEYRINGS_SPIRIT_TEMPLE].Hide(); + mOptions[RSK_KEYRINGS_SHADOW_TEMPLE].Hide(); + mOptions[RSK_KEYRINGS_BOTTOM_OF_THE_WELL].Hide(); + mOptions[RSK_KEYRINGS_GTG].Hide(); + mOptions[RSK_KEYRINGS_GANONS_CASTLE].Hide(); + switch (CVarGetInteger("gRandomizeShuffleKeyRings", RO_KEYRINGS_OFF)) { + case RO_KEYRINGS_COUNT: + // Show count slider. + mOptions[RSK_KEYRINGS].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM); + mOptions[RSK_KEYRINGS_RANDOM_COUNT].Unhide(); + break; + case RO_KEYRINGS_SELECTION: + // Show checkboxes for each dungeon with keys. + mOptions[RSK_KEYRINGS].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM); + mOptions[RSK_KEYRINGS_GERUDO_FORTRESS].Unhide(); + mOptions[RSK_KEYRINGS_FOREST_TEMPLE].Unhide(); + mOptions[RSK_KEYRINGS_FIRE_TEMPLE].Unhide(); + mOptions[RSK_KEYRINGS_WATER_TEMPLE].Unhide(); + mOptions[RSK_KEYRINGS_SPIRIT_TEMPLE].Unhide(); + mOptions[RSK_KEYRINGS_SHADOW_TEMPLE].Unhide(); + mOptions[RSK_KEYRINGS_BOTTOM_OF_THE_WELL].Unhide(); + mOptions[RSK_KEYRINGS_GTG].Unhide(); + mOptions[RSK_KEYRINGS_GANONS_CASTLE].Unhide(); + default: + break; + } + const uint8_t maxKeyringCount = (CVarGetInteger("gRandomizeGerudoFortress", RO_GF_NORMAL) == RO_GF_NORMAL && + CVarGetInteger("gRandomizeGerudoKeys", RO_GERUDO_KEYS_VANILLA) != RO_GERUDO_KEYS_VANILLA) ? 9 : 8; + if (mOptions[RSK_KEYRINGS_RANDOM_COUNT].GetOptionCount() != maxKeyringCount + 1) { + mOptions[RSK_KEYRINGS_RANDOM_COUNT].ChangeOptions(NumOpts(0, maxKeyringCount)); + } + if (CVarGetInteger("gRandomizeGerudoFortress", RO_GF_NORMAL) != RO_GF_NORMAL || + CVarGetInteger("gRandomizeGerudoKeys", RO_GERUDO_KEYS_VANILLA) == RO_GERUDO_KEYS_VANILLA) { + mOptions[RSK_KEYRINGS_GERUDO_FORTRESS].Disable("Disabled because the currently selected Gerudo Fortress Carpenters\n" + "setting and/or Gerudo Fortress Keys setting is incompatible with\n" + "having a Gerudo Fortress Keyring."); + } else { + mOptions[RSK_KEYRINGS_GERUDO_FORTRESS].Enable(); + } + if (CVarGetInteger("gRandomizeTriforceHunt", RO_GENERIC_OFF)) { + mOptions[RSK_GANONS_BOSS_KEY].Disable("This option is disabled because Triforcce Hunt is enabled." + "Ganon's Boss key\nwill instead be given to you after Triforce Hunt completion."); + } else { + mOptions[RSK_GANONS_BOSS_KEY].Enable(); + } + mOptions[RSK_GANONS_BOSS_KEY].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM); + mOptions[RSK_LACS_OPTIONS].Hide(); + mOptions[RSK_LACS_STONE_COUNT].Hide(); + mOptions[RSK_LACS_MEDALLION_COUNT].Hide(); + mOptions[RSK_LACS_REWARD_COUNT].Hide(); + mOptions[RSK_LACS_DUNGEON_COUNT].Hide(); + mOptions[RSK_LACS_TOKEN_COUNT].Hide(); + const uint8_t lacsOpts = CVarGetInteger("gRandomizeLacsRewardOptions", RO_LACS_STANDARD_REWARD); + switch (CVarGetInteger("gRandomizeShuffleGanonBossKey", RO_GANON_BOSS_KEY_VANILLA)) { + case RO_GANON_BOSS_KEY_LACS_STONES: + mOptions[RSK_LACS_OPTIONS].Unhide(); + mOptions[RSK_LACS_STONE_COUNT].Unhide(); + if (lacsOpts == RO_LACS_GREG_REWARD) { + if (mOptions[RSK_LACS_STONE_COUNT].GetOptionCount() == 4) { + mOptions[RSK_LACS_STONE_COUNT].ChangeOptions(NumOpts(0, 4)); + } + } else { + if (mOptions[RSK_LACS_STONE_COUNT].GetOptionCount() == 5) { + mOptions[RSK_LACS_STONE_COUNT].ChangeOptions(NumOpts(0, 3)); + } + } + break; + case RO_GANON_BOSS_KEY_LACS_MEDALLIONS: + mOptions[RSK_LACS_OPTIONS].Unhide(); + mOptions[RSK_LACS_MEDALLION_COUNT].Unhide(); + if (lacsOpts == RO_LACS_GREG_REWARD) { + if (mOptions[RSK_LACS_MEDALLION_COUNT].GetOptionCount() == 7) { + mOptions[RSK_LACS_MEDALLION_COUNT].ChangeOptions(NumOpts(0, 7)); + } + } else { + if (mOptions[RSK_LACS_MEDALLION_COUNT].GetOptionCount() == 8) { + mOptions[RSK_LACS_MEDALLION_COUNT].ChangeOptions(NumOpts(0, 6)); + } + } + break; + case RO_GANON_BOSS_KEY_LACS_REWARDS: + mOptions[RSK_LACS_OPTIONS].Unhide(); + mOptions[RSK_LACS_REWARD_COUNT].Unhide(); + if (lacsOpts == RO_LACS_GREG_REWARD) { + if (mOptions[RSK_LACS_REWARD_COUNT].GetOptionCount() == 10) { + mOptions[RSK_LACS_REWARD_COUNT].ChangeOptions(NumOpts(0, 10)); + } + } else { + if (mOptions[RSK_LACS_REWARD_COUNT].GetOptionCount() == 11) { + mOptions[RSK_LACS_REWARD_COUNT].ChangeOptions(NumOpts(0, 9)); + } + } + break; + case RO_GANON_BOSS_KEY_LACS_DUNGEONS: + mOptions[RSK_LACS_OPTIONS].Unhide(); + mOptions[RSK_LACS_DUNGEON_COUNT].Unhide(); + if (lacsOpts == RO_LACS_GREG_REWARD) { + if (mOptions[RSK_LACS_DUNGEON_COUNT].GetOptionCount() == 9) { + mOptions[RSK_LACS_DUNGEON_COUNT].ChangeOptions(NumOpts(0, 9)); + } + } else { + if (mOptions[RSK_LACS_DUNGEON_COUNT].GetOptionCount() == 10) { + mOptions[RSK_LACS_DUNGEON_COUNT].ChangeOptions(NumOpts(0, 8)); + } + } + break; + case RO_GANON_BOSS_KEY_LACS_TOKENS: + mOptions[RSK_LACS_TOKEN_COUNT].Unhide(); + break; + default: + mOptions[RSK_GANONS_BOSS_KEY].AddFlag(IMFLAG_SEPARATOR_BOTTOM); + break; + } + // Skip Child Stealth - Disabled when Skip Child Zelda is active + if (CVarGetInteger("gRandomizeSkipChildZelda", RO_GENERIC_DONT_SKIP)) { + mOptions[RSK_SKIP_CHILD_STEALTH].Disable("This option is disabled because \"Skip Child Zelda\" is enabled."); + } else { + mOptions[RSK_SKIP_CHILD_STEALTH].Enable(); + } + if (CVarGetInteger("gRandomizeGossipStoneHints", RO_GOSSIP_STONES_NEED_NOTHING) == RO_GOSSIP_STONES_NONE) { + mOptions[RSK_HINT_CLARITY].Hide(); + mOptions[RSK_HINT_DISTRIBUTION].Hide(); + } else { + mOptions[RSK_HINT_CLARITY].Unhide(); + mOptions[RSK_HINT_DISTRIBUTION].Unhide(); + } + // Link's Pocket - Disabled when Dungeon Rewards are shuffled to End of Dungeon + if (CVarGetInteger("gRandomizeShuffleDungeonReward", RO_DUNGEON_REWARDS_END_OF_DUNGEON) == RO_DUNGEON_REWARDS_END_OF_DUNGEON) { + mOptions[RSK_LINKS_POCKET].Disable("This option is disabled because \"Dungeon Rewards\" are shuffled to \"End of Dungeons\"."); + } else { + mOptions[RSK_LINKS_POCKET].Enable(); + } +} + +void Settings::FinalizeSettings(const std::set& excludedLocations, const std::set& enabledTricks) { + const auto ctx = Rando::Context::GetInstance(); if (!ctx->IsSpoilerLoaded()) { // If we've loaded a spoiler file, the settings have already been populated, so we // only need to do things like resolve the starting age or determine MQ dungeons. // Any logic dependent on cvarSettings should go in this if statement - for (auto [key, value] : cvarSettings) { - mOptions[key].SetSelectedIndex(value); - } - switch (cvarSettings[RSK_LOGIC_RULES]) { - case RO_LOGIC_GLITCHLESS: - mOptions[RSK_LOGIC_RULES].SetSelectedIndex(RO_LOGIC_GLITCHLESS); - break; - case RO_LOGIC_NO_LOGIC: - mOptions[RSK_LOGIC_RULES].SetSelectedIndex(RO_LOGIC_NO_LOGIC); - break; - default: - mOptions[RSK_LOGIC_RULES].SetSelectedIndex(RO_LOGIC_GLITCHLESS); - break; - } // if we skip child zelda, we start with zelda's letter, and malon starts // at the ranch, so we should *not* shuffle the weird egg - if (cvarSettings[RSK_SKIP_CHILD_ZELDA]) { - mOptions[RSK_SKIP_CHILD_ZELDA].SetSelectedIndex(true); - mOptions[RSK_SHUFFLE_WEIRD_EGG].SetSelectedIndex(0); - } else { - mOptions[RSK_SKIP_CHILD_ZELDA].SetSelectedIndex(false); - mOptions[RSK_SHUFFLE_WEIRD_EGG].SetSelectedIndex(cvarSettings[RSK_SHUFFLE_WEIRD_EGG]); - } - // Force 100 GS Shuffle if that's where Ganon's Boss Key is - if (cvarSettings[RSK_GANONS_BOSS_KEY] == RO_GANON_BOSS_KEY_KAK_TOKENS) { - mOptions[RSK_SHUFFLE_100_GS_REWARD].SetSelectedIndex(1); - } else { - mOptions[RSK_SHUFFLE_100_GS_REWARD].SetSelectedIndex(cvarSettings[RSK_SHUFFLE_100_GS_REWARD]); + if (mOptions[RSK_SKIP_CHILD_ZELDA]) { + mOptions[RSK_SHUFFLE_WEIRD_EGG].SetSelectedIndex(RO_GENERIC_OFF); } - if (cvarSettings[RSK_TRIFORCE_HUNT]) { - mOptions[RSK_GANONS_BOSS_KEY].SetSelectedIndex(RO_GANON_BOSS_KEY_TRIFORCE_HUNT); - } else { - mOptions[RSK_GANONS_BOSS_KEY].SetSelectedIndex(cvarSettings[RSK_GANONS_BOSS_KEY]); + // With certain access settings, the seed is only beatable if Starting Age is set to Child. + if (mOptions[RSK_FOREST].Is(RO_FOREST_CLOSED) || (mOptions[RSK_DOOR_OF_TIME].Is(RO_DOOROFTIME_CLOSED) && + !mOptions[RSK_SHUFFLE_OCARINA])) { + mOptions[RSK_STARTING_AGE].SetSelectedIndex(RO_AGE_CHILD); } - // ImGui has a slider which returns the actual number, which is off by one since - // the ImGui slider can't go down to 0 (the first index aka 1 big poe). - mOptions[RSK_BIG_POE_COUNT].SetSelectedIndex(cvarSettings[RSK_BIG_POE_COUNT] - 1); + + if (mOptions[RSK_TRIFORCE_HUNT]) { + mOptions[RSK_GANONS_BOSS_KEY].SetSelectedIndex(RO_GANON_BOSS_KEY_TRIFORCE_HUNT); + } + + // Force 100 GS Shuffle if that's where Ganon's Boss Key is + if (mOptions[RSK_GANONS_BOSS_KEY].Is(RO_GANON_BOSS_KEY_KAK_TOKENS)) { + mOptions[RSK_SHUFFLE_100_GS_REWARD].SetSelectedIndex(1); + } + + // If we only have MQ, set all dungeons to MQ + if (OTRGlobals::Instance->HasMasterQuest() && !OTRGlobals::Instance->HasOriginal()) { + mOptions[RSK_MQ_DUNGEON_RANDOM].SetSelectedIndex(RO_MQ_DUNGEONS_SET_NUMBER); + mOptions[RSK_MQ_DUNGEON_COUNT].SetSelectedIndex(12); + mOptions[RSK_MQ_DUNGEON_SET].SetSelectedIndex(RO_GENERIC_OFF); + } + + // If we don't have MQ, set all dungeons to Vanilla + if (OTRGlobals::Instance->HasOriginal() && !OTRGlobals::Instance->HasMasterQuest()) { + mOptions[RSK_MQ_DUNGEON_RANDOM].SetSelectedIndex(RO_MQ_DUNGEONS_NONE); + } + + if (mOptions[RSK_MQ_DUNGEON_RANDOM].Is(RO_MQ_DUNGEONS_NONE)) { + mOptions[RSK_MQ_DUNGEON_COUNT].SetSelectedIndex(0); + mOptions[RSK_MQ_DUNGEON_SET].SetSelectedIndex(RO_GENERIC_OFF); + } + + // TODO: Historically we have forced the shuffle settings off when we have the corresponding start + // with setting on, but 3drando doesn't seem to actually do that. It seems like you can both shuffle + // them in the pool and start with them. + if (mOptions[RSK_STARTING_KOKIRI_SWORD]) { + mOptions[RSK_SHUFFLE_KOKIRI_SWORD].SetSelectedIndex(RO_GENERIC_OFF); + } + + if (mOptions[RSK_STARTING_MASTER_SWORD]) { + mOptions[RSK_SHUFFLE_MASTER_SWORD].SetSelectedIndex(RO_GENERIC_OFF); + } + + if (mOptions[RSK_STARTING_OCARINA].IsNot(RO_STARTING_OCARINA_OFF)) { + mOptions[RSK_SHUFFLE_OCARINA].SetSelectedIndex(RO_GENERIC_OFF); + } + + // If any of the individual shuffle settings are on, turn on the main Shuffle Entrances option + if (mOptions[RSK_SHUFFLE_DUNGEON_ENTRANCES].IsNot(RO_DUNGEON_ENTRANCE_SHUFFLE_OFF) + || mOptions[RSK_SHUFFLE_BOSS_ENTRANCES].IsNot(RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF) + || mOptions[RSK_SHUFFLE_OVERWORLD_ENTRANCES] + || mOptions[RSK_SHUFFLE_INTERIOR_ENTRANCES].IsNot(RO_INTERIOR_ENTRANCE_SHUFFLE_OFF) + || mOptions[RSK_SHUFFLE_GROTTO_ENTRANCES] || mOptions[RSK_SHUFFLE_OWL_DROPS] + || mOptions[RSK_SHUFFLE_WARP_SONGS] || mOptions[RSK_SHUFFLE_OVERWORLD_SPAWNS]) { + mOptions[RSK_SHUFFLE_ENTRANCES].SetSelectedIndex(RO_GENERIC_ON); + } + + if (mOptions[RSK_SHUFFLE_DUNGEON_REWARDS].Is(RO_DUNGEON_REWARDS_END_OF_DUNGEON)) { + mOptions[RSK_LINKS_POCKET].SetSelectedIndex(RO_LINKS_POCKET_DUNGEON_REWARD); + } + ctx->AddExcludedOptions(); for (const auto locationKey : ctx->everyPossibleLocation) { if (const auto location = ctx->GetItemLocation(locationKey); excludedLocations.contains(location->GetRandomizerCheck())) { @@ -1366,14 +1996,19 @@ void Settings::ParseJson(nlohmann::json spoilerFileJson) { case RSK_LACS_DUNGEON_COUNT: case RSK_LACS_TOKEN_COUNT: case RSK_KEYRINGS_RANDOM_COUNT: - case RSK_BIG_POE_COUNT: case RSK_CUCCO_COUNT: case RSK_STARTING_SKULLTULA_TOKEN: + numericValueString = it.value(); + mOptions[index].SetSelectedIndex(std::stoi(numericValueString)); + break; + // Same as the above section, but the indexes are off by one from the text + // (i.e. 10 Big Poes is index 9). + case RSK_BIG_POE_COUNT: case RSK_TRIFORCE_HUNT_PIECES_TOTAL: case RSK_TRIFORCE_HUNT_PIECES_REQUIRED: case RSK_STARTING_HEARTS: numericValueString = it.value(); - mOptions[index].SetSelectedIndex(std::stoi(numericValueString)); + mOptions[index].SetSelectedIndex(std::stoi(numericValueString) - 1); break; case RSK_GANONS_TRIALS: if (it.value() == "Skip") { @@ -1412,6 +2047,7 @@ void Settings::ParseJson(nlohmann::json spoilerFileJson) { } else if (it.value() == "Tycoon's Wallet") { mOptions[index].SetSelectedIndex(RO_SHOPSANITY_PRICE_TYCOON); } + break; case RSK_SHUFFLE_SCRUBS: if (it.value() == "Off") { mOptions[index].SetSelectedIndex(RO_SCRUBS_OFF); diff --git a/soh/soh/Enhancements/randomizer/settings.h b/soh/soh/Enhancements/randomizer/settings.h index b84dacad5..3e60e7bfb 100644 --- a/soh/soh/Enhancements/randomizer/settings.h +++ b/soh/soh/Enhancements/randomizer/settings.h @@ -13,29 +13,170 @@ namespace Rando { class Settings { public: Settings(); + + /** + * @brief Creates the `Option` and `OptionGroup` objects. This happens after construction because certain + * other events in the codebase need to happen before all of the `Option`s can be created. + */ void CreateOptions(); - Option& Setting(RandomizerSettingKey key); + + /** + * @brief Get a reference to the `Option` corresponding to the provided RandomizerSettingKey. + * + * @param key + * @return Option& + */ + Option& GetOption(RandomizerSettingKey key); + + /** + * @brief Get a reference to the `Option` corresponding to the provided RandomizerTrick key. + * + * @param key + * @return Option& + */ Option& GetTrickOption(RandomizerTrick key); + + /** + * @brief Returns a reference to the entire array of options. + * + * @return const std::array& + */ const std::array& GetAllOptions() const; + + /** + * @brief Get a list of Location Exclude `Option`s for the given + * SpoilerCollectionCheckGroup + * + * @param group + * @return std::vector& + */ std::vector& GetExcludeOptionsForGroup(SpoilerCollectionCheckGroup group); + + /** + * @brief Get a reference to all of the Exclude Location `Option` lists. + * + * @return const std::vector>& + */ const std::vector>& GetExcludeLocationsOptions() const; + + /** + * @brief Gets the resolved Starting Age. Represents the actual starting age when the + * RSK_STARTING_AGE option is set to Random. + * + * @return RandoOptionStartingAge + */ RandoOptionStartingAge ResolvedStartingAge() const; + + /** + * @brief Gets the resolved Light Arrow CutScene check condition. + * There is no direct option for this, it is inferred based on the value of a few other options. + * + * @return RandoOptionLACSCondition + */ RandoOptionLACSCondition LACSCondition() const; + + /** + * @brief Get the hash for the current seed. + * + * @return std::string + */ std::string GetHash() const; + + /** + * @brief Get the Seed String + * + * @return const std::string& + */ const std::string& GetSeedString() const; + + /** + * @brief Set the Seed String + * + * @param seedString + */ void SetSeedString(std::string seedString); + + /** + * @brief Get the Seed + * + * @return const uint32_t + */ uint32_t GetSeed() const; + + /** + * @brief Set the Seed + * + * @param seed + */ void SetSeed(uint32_t seed); + + /** + * @brief Set the Seed Hash for the current seed. + * + * @param hash + */ void SetHash(std::string hash); + + /** + * @brief Get the list of `OptionGroup`s. + * + * @return const std::array& + */ const std::array& GetOptionGroups(); + + /** + * @brief Get the `OptionGroup` corresponding to the provided RandomizerSettingGroupKey + * + * @param key + * @return const OptionGroup& + */ const OptionGroup& GetOptionGroup(RandomizerSettingGroupKey key); - void UpdateSettings(std::unordered_map cvarSettings, - const std::set& excludedLocations, const std::set& enabledTricks); + + /** + * @brief sets the `selectedOption` of all Options to the value of the CVar + * corresponding to their `cvarName`s. + */ + void SetAllFromCVar(); + + /** + * @brief Updates various properties of options based on the value of other options. + * Used to update visibility, whether or not interaction is disabled, and what the + * actual option values are. Actually changing option values should be handled in + * `FinalizeSettings` + * + * For example, this function handles setting the maximum possible keyring count to 9 + * when Gerudo's Fortress options are set such that a keyring is possible for that + * dungeon. + */ + void UpdateOptionProperties(); + + /** + * @brief Runs before seed generation to ensure all options are compatible with each + * other and resolve options that have been set to random (such as random trial count, + * or starting age). + * + * @param excludedLocations Set of locations that should be forced to have junk items. + * @param enabledTricks Set of tricks that should be considered logically possible. Tricks + * are things that are possible to do in gameplay but are difficult, not intuitive or that + * require more extensive game knowledge, i.e. opening invisible chests without the Lens of Truth. + */ + void FinalizeSettings(const std::set& excludedLocations, const std::set& enabledTricks); + + /** + * @brief Parse Options from a JSON file. + * + * @param spoilerFileJson + */ void ParseJson(nlohmann::json spoilerFileJson); std::vector VanillaLogicDefaults = {}; private: + /** + * @brief Create the list of description strings for `Option`s. + */ + void CreateOptionDescriptions(); std::array mOptions = {}; + std::array mOptionDescriptions = {}; std::array mOptionGroups = {}; std::array mTrickOptions = {}; std::vector> mExcludeLocationsOptionsGroups = {};