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
This commit is contained in:
Christopher Leggett 2023-12-10 11:20:47 -05:00 committed by GitHub
parent 4925abdd67
commit dad4ae0095
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 2277 additions and 2017 deletions

View File

@ -21,7 +21,7 @@ std::vector<std::string> presetEntries;
Rando::Option* currentSetting;
} // namespace
std::string GenerateRandomizer(std::unordered_map<RandomizerSettingKey, uint8_t> cvarSettings, std::set<RandomizerCheck> excludedLocations, std::set<RandomizerTrick> enabledTricks,
std::string GenerateRandomizer(std::set<RandomizerCheck> excludedLocations, std::set<RandomizerTrick> enabledTricks,
std::string seedString) {
auto ctx = Rando::Context::GetInstance();
@ -38,7 +38,7 @@ std::string GenerateRandomizer(std::unordered_map<RandomizerSettingKey, uint8_t>
} 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<RandomizerSettingKey, uint8_t>
uint32_t seedHash = boost::hash_32<std::string>{}(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 "

View File

@ -25,4 +25,4 @@
// #define CYAN "\x1b[36m"
// #define WHITE "\x1b[37m"
std::string GenerateRandomizer(std::unordered_map<RandomizerSettingKey, uint8_t> cvarSetting, std::set<RandomizerCheck> excludedLocations, std::set<RandomizerTrick> enabledTricks, std::string seedInput);
std::string GenerateRandomizer(std::set<RandomizerCheck> excludedLocations, std::set<RandomizerTrick> enabledTricks, std::string seedInput);

View File

@ -13,7 +13,7 @@
namespace Playthrough {
int Playthrough_Init(uint32_t seed, std::unordered_map<RandomizerSettingKey, uint8_t> cvarSettings, std::set<RandomizerCheck> excludedLocations, std::set<RandomizerTrick> enabledTricks) {
int Playthrough_Init(uint32_t seed, std::set<RandomizerCheck> excludedLocations, std::set<RandomizerTrick> 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_map<RandomizerSettingKey, uin
ctx->HintReset();
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<RandomizerSettingKey, uin
//TODO: Handle different types of file output (i.e. Spoiler Log, Plando Template, Patch Files, Race Files, etc.)
// write logs
printf("\x1b[11;10HWriting Spoiler Log...");
if (SpoilerLog_Write(cvarSettings[RSK_LANGUAGE])) {
if (SpoilerLog_Write()) {
printf("Done");
} else {
printf("Failed");
@ -85,7 +85,7 @@ int Playthrough_Init(uint32_t seed, std::unordered_map<RandomizerSettingKey, uin
}
// used for generating a lot of seeds at once
int Playthrough_Repeat(std::unordered_map<RandomizerSettingKey, uint8_t> cvarSettings, std::set<RandomizerCheck> excludedLocations, std::set<RandomizerTrick> enabledTricks, int count /*= 1*/) {
int Playthrough_Repeat(std::set<RandomizerCheck> excludedLocations, std::set<RandomizerTrick> 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<RandomizerSettingKey, uint8_t> 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);
}

View File

@ -4,6 +4,6 @@
#include "../context.h"
namespace Playthrough {
int Playthrough_Init(uint32_t seed, std::unordered_map<RandomizerSettingKey, uint8_t> cvarSettings, std::set<RandomizerCheck> excludedLocations, std::set<RandomizerTrick> enabledTricks);
int Playthrough_Repeat(std::unordered_map<RandomizerSettingKey, uint8_t> cvarSettings, std::set<RandomizerCheck> excludedLocations, std::set<RandomizerTrick> enabledTricks, int count = 1);
int Playthrough_Init(uint32_t seed, std::set<RandomizerCheck> excludedLocations, std::set<RandomizerTrick> enabledTricks);
int Playthrough_Repeat(std::set<RandomizerCheck> excludedLocations, std::set<RandomizerTrick> enabledTricks, int count = 1);
}

View File

@ -10,14 +10,14 @@
#include <Context.h>
#include <libultraship/libultra/types.h>
void RandoMain::GenerateRando(std::unordered_map<RandomizerSettingKey, u8> cvarSettings, std::set<RandomizerCheck> excludedLocations, std::set<RandomizerTrick> enabledTricks,
void RandoMain::GenerateRando(std::set<RandomizerCheck> excludedLocations, std::set<RandomizerTrick> 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();

View File

@ -1,5 +1,7 @@
#pragma once
#include "soh/Enhancements/randomizer/item.h"
#include <set>
namespace RandoMain {
void GenerateRando(std::unordered_map<RandomizerSettingKey, uint8_t> cvarSettings, std::set<RandomizerCheck> excludedLocations, std::set<RandomizerTrick> enabledTricks, std::string seedInput);
void GenerateRando(std::set<RandomizerCheck> excludedLocations, std::set<RandomizerTrick> enabledTricks, std::string seedInput);
}

View File

@ -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"));

View File

@ -111,5 +111,5 @@ const RandomizerHash& GetRandomizerHash();
void WriteIngameSpoilerLog();
const char* SpoilerLog_Write(int language);
const char* SpoilerLog_Write();
const SpoilerData& GetSpoilerData();

View File

@ -14,6 +14,7 @@
namespace Rando {
std::weak_ptr<Context> 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::vector<RandomizerChe
void Context::GenerateLocationPool() {
allLocations.clear();
AddLocation(RC_LINKS_POCKET);
if (mSettings->Setting(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 {

View File

@ -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;

View File

@ -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;

View File

@ -1,18 +1,33 @@
#include "option.h"
#include "libultraship/bridge.h"
#include <Context.h>
#include <ImGui/imgui.h>
#include "soh/UIWidgets.hpp"
namespace Rando {
Option Option::Bool(std::string name_, std::vector<std::string> 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<std::string> options_, const OptionCategory category_,
const uint8_t defaultOption_, const bool defaultHidden_) {
return {static_cast<uint8_t>(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<uint8_t>(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<bool>(var)) {
var.emplace<bool>(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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<bool>(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<int>(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<int>(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<int>(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<Option*> 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<OptionGroup*> 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<Option*> options, const bool printInSpoiler) {
return {std::move(name), std::move(options), OptionGroupType::SUBGROUP, printInSpoiler};
OptionGroup OptionGroup::SubGroup(std::string name, std::vector<Option*> 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<OptionGroup*> subGroups, const bool printInSpoiler) {
return {std::move(name), std::move(subGroups), OptionGroupType::SUBGROUP, printInSpoiler, OptionGroupType::SUBGROUP};
OptionGroup OptionGroup::SubGroup(std::string name, std::vector<OptionGroup*> 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<int>(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

View File

@ -1,5 +1,7 @@
#pragma once
#include "soh/UIWidgets.hpp"
#include <cstdint>
#include <string>
#include <vector>
@ -7,26 +9,136 @@
#include <type_traits>
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<std::string> 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<std::string> 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 <typename T> T Value() const {
return std::get<T>(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 <typename T> bool Is(T other) const {
static_assert(std::is_integral_v<T> || std::is_enum_v<T>, "T must be an integral type or an enum.");
if constexpr ((std::is_integral_v<T> && !std::is_same_v<bool, T>) || std::is_enum_v<T>) {
@ -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 <typename T> 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<std::string> 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<std::string> 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<std::string> 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<bool, uint8_t> var;
std::string name;
std::vector<std::string> 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<Option*> options, OptionGroupType groupType = OptionGroupType::DEFAULT, bool printInSpoiler = true, OptionGroupType containsType = OptionGroupType::DEFAULT);
OptionGroup(std::string name, std::vector<OptionGroup*> subGroups, OptionGroupType groupType = OptionGroupType::DEFAULT, bool printInSpoiler = true, OptionGroupType containsType = OptionGroupType::SUBGROUP);
static OptionGroup SubGroup(std::string name, std::vector<Option*> options, bool printInSpoiler = true);
static OptionGroup SubGroup(std::string name, std::vector<OptionGroup*> 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<Option*> 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<OptionGroup*> 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<Option*> 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<OptionGroup*> 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<Option*>&
*/
const std::vector<Option*>& GetOptions() const;
/**
* @brief Get the list of `OptionGroup`s contained within this `OptionGroup`.
*
* @return const std::vector<OptionGroup*>&
*/
const std::vector<OptionGroup*>& 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<Option*> 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

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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,

View File

@ -1,10 +1,18 @@
#include <libultraship/libultraship.h>
namespace Rando {
class Settings;
}
class RandomizerSettingsWindow : public LUS::GuiWindow {
public:
using GuiWindow::GuiWindow;
void InitElement() override;
void DrawElement() override;
void UpdateElement() override {};
};
void UpdateElement() override;
private:
bool mNeedsUpdate = false;
std::shared_ptr<Rando::Settings> mSettings;
};

File diff suppressed because it is too large Load Diff

View File

@ -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<Option, RSK_MAX>&
*/
const std::array<Option, RSK_MAX>& GetAllOptions() const;
/**
* @brief Get a list of Location Exclude `Option`s for the given
* SpoilerCollectionCheckGroup
*
* @param group
* @return std::vector<Option*>&
*/
std::vector<Option*>& GetExcludeOptionsForGroup(SpoilerCollectionCheckGroup group);
/**
* @brief Get a reference to all of the Exclude Location `Option` lists.
*
* @return const std::vector<std::vector<Option*>>&
*/
const std::vector<std::vector<Option*>>& 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<OptionGroup, RSG_MAX>&
*/
const std::array<OptionGroup, RSG_MAX>& 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<RandomizerSettingKey, uint8_t> cvarSettings,
const std::set<RandomizerCheck>& excludedLocations, const std::set<RandomizerTrick>& 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<RandomizerCheck>& excludedLocations, const std::set<RandomizerTrick>& enabledTricks);
/**
* @brief Parse Options from a JSON file.
*
* @param spoilerFileJson
*/
void ParseJson(nlohmann::json spoilerFileJson);
std::vector<Option*> VanillaLogicDefaults = {};
private:
/**
* @brief Create the list of description strings for `Option`s.
*/
void CreateOptionDescriptions();
std::array<Option, RSK_MAX> mOptions = {};
std::array<std::string, RSK_MAX> mOptionDescriptions = {};
std::array<OptionGroup, RSG_MAX> mOptionGroups = {};
std::array<Option, RT_MAX> mTrickOptions = {};
std::vector<std::vector<Option*>> mExcludeLocationsOptionsGroups = {};