Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions soh/soh/Enhancements/randomizer/option_descriptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ void Settings::CreateOptionDescriptions() {
"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_RANDOMIZE_SETTINGS] =
"Randomize settings each time a seed is generated using the seed RNG.\n\n"
"Off - Use the exact settings you selected.\n"
"On (No Entrance Rando) - Randomize settings, excluding entrance shuffle settings.\n"
"On (Entrance Rando) - Also randomize entrance shuffle settings.\n"
"On (Entrance Rando + Decoupled) - Same as above but Decouple Entrance Setting is also randomized.\n\n"
"Logic, Excluded Locations, Starting Items and Tricks are never randomized. Starting Age is always randomized";
mOptionDescriptions[RSK_RANDOMIZE_SETTINGS_INCLUDE_MQ] =
"When Randomize Settings Per Seed is enabled, this also includes MQ dungeon-related settings in that "
"randomization.";
mOptionDescriptions[RSK_GERUDO_FORTRESS] =
"Sets the state of the carpenters captured by Gerudo "
"in Gerudo Fortress, and with it the number of guards that spawn.\n"
Expand Down
10 changes: 10 additions & 0 deletions soh/soh/Enhancements/randomizer/randomizerTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -6667,6 +6667,8 @@ typedef enum {
RSK_LOCK_OVERWORLD_DOORS,
RSK_SHUFFLE_GRASS,
RSK_ROCS_FEATHER,
RSK_RANDOMIZE_SETTINGS,
RSK_RANDOMIZE_SETTINGS_INCLUDE_MQ,
RSK_MAX
} RandomizerSettingKey;

Expand Down Expand Up @@ -6729,6 +6731,14 @@ typedef enum {
RO_AGE_RANDOM,
} RandoOptionStartingAge;

// Randomize Settings settings (off, on with entrance handling modes)
typedef enum {
RO_RANDOMIZE_SETTINGS_OFF,
RO_RANDOMIZE_SETTINGS_EXCLUDE_ENTRANCES,
RO_RANDOMIZE_SETTINGS_INCLUDE_ENTRANCES,
RO_RANDOMIZE_SETTINGS_INCLUDE_ENTRANCES_DECOUPLED,
} RandoOptionRandomizeSettings;

// Fortress Carpenters settings (normal, fast, free)
typedef enum {
RO_GF_CARPENTERS_NORMAL,
Expand Down
98 changes: 98 additions & 0 deletions soh/soh/Enhancements/randomizer/settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,13 @@ void Settings::CreateOptions() {
OPT_U8(RSK_TRIAL_COUNT, "Ganon's Trials Count", {NumOpts(0, 6)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("GanonTrialCount"), mOptionDescriptions[RSK_TRIAL_COUNT], WIDGET_CVAR_SLIDER_INT, 6, true);
OPT_BOOL(RSK_MEDALLION_LOCKED_TRIALS, "Medallion Locked Trials", CVAR_RANDOMIZER_SETTING("MedallionLockedTrials"), mOptionDescriptions[RSK_MEDALLION_LOCKED_TRIALS]);
OPT_U8(RSK_STARTING_AGE, "Starting Age", {"Child", "Adult", "Random"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("StartingAge"), mOptionDescriptions[RSK_STARTING_AGE], WIDGET_CVAR_COMBOBOX, RO_AGE_CHILD);
OPT_U8(RSK_RANDOMIZE_SETTINGS, "Randomize Settings Per Seed",
{"Off", "On", "On + Entrance Rando", "On + Entrance Rando + Decoupled"},
OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("RandomizeSettings"),
mOptionDescriptions[RSK_RANDOMIZE_SETTINGS], WIDGET_CVAR_COMBOBOX, RO_RANDOMIZE_SETTINGS_OFF);
OPT_BOOL(RSK_RANDOMIZE_SETTINGS_INCLUDE_MQ, "Include MQ Dungeon Settings",
CVAR_RANDOMIZER_SETTING("RandomizeSettingsIncludeMQ"),
mOptionDescriptions[RSK_RANDOMIZE_SETTINGS_INCLUDE_MQ], IMFLAG_NONE, WIDGET_CVAR_CHECKBOX, false);
OPT_U8(RSK_SELECTED_STARTING_AGE, "Selected Starting Age", {"Child", "Adult"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("SelectedStartingAge"), mOptionDescriptions[RSK_STARTING_AGE], WIDGET_CVAR_COMBOBOX, RO_AGE_CHILD);
OPT_BOOL(RSK_SHUFFLE_ENTRANCES, "Shuffle Entrances");
OPT_U8(RSK_SHUFFLE_DUNGEON_ENTRANCES, "Dungeon Entrances", {"Off", "On", "On + Ganon"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleDungeonsEntrances"), mOptionDescriptions[RSK_SHUFFLE_DUNGEON_ENTRANCES], WIDGET_CVAR_COMBOBOX, RO_DUNGEON_ENTRANCE_SHUFFLE_OFF);
Expand Down Expand Up @@ -1280,6 +1287,7 @@ void Settings::CreateOptions() {
OPT_U8(RSK_DAMAGE_MULTIPLIER, "Damage Multiplier", {"x1/2", "x1", "x2", "x4", "x8", "x16", "OHKO"}, OptionCategory::Setting, "", "", WIDGET_CVAR_SLIDER_INT, RO_DAMAGE_MULTIPLIER_DEFAULT);
// Don't show any MQ options if both quests aren't available
if (!(OTRGlobals::Instance->HasMasterQuest() && OTRGlobals::Instance->HasOriginal())) {
mOptions[RSK_RANDOMIZE_SETTINGS_INCLUDE_MQ].Disable("This Options has been disabled because only one type of OTR has been loaded");
mOptions[RSK_MQ_DUNGEON_RANDOM].Disable("This Options has been disabled because only one type of OTR has been loaded");
mOptions[RSK_MQ_DUNGEON_COUNT].Disable("This Options has been disabled because only one type of OTR has been loaded");
mOptions[RSK_MQ_DUNGEON_SET].Disable("This Options has been disabled because only one type of OTR has been loaded");
Expand All @@ -1297,6 +1305,7 @@ void Settings::CreateOptions() {
mOptions[RSK_MQ_GANONS_CASTLE].Disable("This Options has been disabled because only one type of OTR has been loaded");
} else {
// If any MQ Options are available, show the MQ Dungeon Randomization Combobox
mOptions[RSK_RANDOMIZE_SETTINGS_INCLUDE_MQ].Enable();
mOptions[RSK_MQ_DUNGEON_RANDOM].Enable();
mOptions[RSK_MQ_DUNGEON_COUNT].Enable();
mOptions[RSK_MQ_DUNGEON_SET].Enable();
Expand Down Expand Up @@ -2972,6 +2981,95 @@ void Settings::UpdateAllOptions() {

void Context::FinalizeSettings(const std::set<RandomizerCheck>& excludedLocations,
const std::set<RandomizerTrick>& enabledTricks) {

// Randomize settings based on selected mode.
const uint8_t randomizeSettingsMode = mOptions[RSK_RANDOMIZE_SETTINGS].Get();
if (randomizeSettingsMode != RO_RANDOMIZE_SETTINGS_OFF) {
const bool includeEntranceSettings = randomizeSettingsMode >= RO_RANDOMIZE_SETTINGS_INCLUDE_ENTRANCES;
const bool includeDecoupledEntrances =
randomizeSettingsMode == RO_RANDOMIZE_SETTINGS_INCLUDE_ENTRANCES_DECOUPLED;
const bool includeMqSettings = mOptions[RSK_RANDOMIZE_SETTINGS_INCLUDE_MQ] &&
OTRGlobals::Instance->HasMasterQuest() && OTRGlobals::Instance->HasOriginal();

auto settings = Rando::Settings::GetInstance();
const auto addGroupKeys = [settings](std::set<RandomizerSettingKey>& keySet,
const RandomizerSettingGroupKey groupKey) {
for (const auto* option : settings->GetOptionGroup(groupKey).GetOptions()) {
keySet.insert(option->GetKey());
}
};

std::set<RandomizerSettingKey> alwaysRandomizedSettingKeys;
const std::array<RandomizerSettingGroupKey, 7> alwaysRandomizedGroups = {
RSG_MENU_SECTION_WINCON, RSG_MENU_SECTION_AREA_ACCESS, RSG_MENU_SECTION_DUNGEON_ITEMS,
RSG_MENU_SECTION_KEYRINGS, RSG_MENU_SECTION_BASIC_SHUFFLES, RSG_MENU_SECTION_SHOP_SHUFFLES,
RSG_MENU_SECTION_ADDITIONAL_ITEMS
};
for (const auto groupKey : alwaysRandomizedGroups) {
addGroupKeys(alwaysRandomizedSettingKeys, groupKey);
}
alwaysRandomizedSettingKeys.insert(RSK_STARTING_AGE);

std::set<RandomizerSettingKey> mqSettingKeys;
addGroupKeys(mqSettingKeys, RSG_MENU_SECTION_MQ);

std::set<RandomizerSettingKey> entranceSettingKeys;
addGroupKeys(entranceSettingKeys, RSG_MENU_SECTION_ENTRANCES);
if (!includeDecoupledEntrances) {
entranceSettingKeys.erase(RSK_DECOUPLED_ENTRANCES);
mOptions[RSK_DECOUPLED_ENTRANCES].Set(RO_GENERIC_OFF); // Prevents setting leak if decoupled is not in use
}

if (!includeEntranceSettings) { // Prevents setting leak if entrance rando is not in use
mOptions[RSK_SHUFFLE_DUNGEON_ENTRANCES].Set(RO_DUNGEON_ENTRANCE_SHUFFLE_OFF);
mOptions[RSK_SHUFFLE_BOSS_ENTRANCES].Set(RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF);
mOptions[RSK_SHUFFLE_GANONS_TOWER_ENTRANCE].Set(RO_GENERIC_OFF);
mOptions[RSK_SHUFFLE_OVERWORLD_ENTRANCES].Set(RO_GENERIC_OFF);
mOptions[RSK_SHUFFLE_INTERIOR_ENTRANCES].Set(RO_INTERIOR_ENTRANCE_SHUFFLE_OFF);
mOptions[RSK_SHUFFLE_THIEVES_HIDEOUT_ENTRANCES].Set(RO_GENERIC_OFF);
mOptions[RSK_SHUFFLE_GROTTO_ENTRANCES].Set(RO_GENERIC_OFF);
mOptions[RSK_SHUFFLE_OWL_DROPS].Set(RO_GENERIC_OFF);
mOptions[RSK_SHUFFLE_WARP_SONGS].Set(RO_GENERIC_OFF);
mOptions[RSK_SHUFFLE_OVERWORLD_SPAWNS].Set(RO_GENERIC_OFF);
mOptions[RSK_MIXED_ENTRANCE_POOLS].Set(RO_GENERIC_OFF);
mOptions[RSK_MIX_DUNGEON_ENTRANCES].Set(RO_GENERIC_OFF);
mOptions[RSK_MIX_BOSS_ENTRANCES].Set(RO_GENERIC_OFF);
mOptions[RSK_MIX_OVERWORLD_ENTRANCES].Set(RO_GENERIC_OFF);
mOptions[RSK_MIX_INTERIOR_ENTRANCES].Set(RO_GENERIC_OFF);
mOptions[RSK_MIX_THIEVES_HIDEOUT_ENTRANCES].Set(RO_GENERIC_OFF);
mOptions[RSK_MIX_GROTTO_ENTRANCES].Set(RO_GENERIC_OFF);
mOptions[RSK_DECOUPLED_ENTRANCES].Set(RO_GENERIC_OFF);
}

for (size_t i = 0; i < RSK_MAX; i++) {
const auto key = static_cast<RandomizerSettingKey>(i);

const bool isEntranceSetting = entranceSettingKeys.contains(key);
if (isEntranceSetting && !includeEntranceSettings) {
continue;
}

const bool shouldRandomizeThisSetting = alwaysRandomizedSettingKeys.contains(key) ||
(isEntranceSetting && includeEntranceSettings) ||
(includeMqSettings && mqSettingKeys.contains(key));
if (!shouldRandomizeThisSetting) {
continue;
}

auto& setting = settings->GetOption(key);
if (!setting.IsCategory(OptionCategory::Setting)) {
continue;
}

const size_t optionCount = setting.GetOptionCount();
if (optionCount <= 1) {
continue;
}

mOptions[i].Set(Random(0, static_cast<uint32_t>(optionCount)));
}
}

// 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 (mOptions[RSK_SKIP_CHILD_ZELDA]) {
Expand Down
34 changes: 34 additions & 0 deletions soh/soh/SohGui/SohMenuRandomizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ static const std::map<int32_t, const char*> skipGetItemAnimationOptions = {
{ SGIA_ALL, "All Items" },
};

static const std::map<int32_t, const char*> randomizeSettingsModeOptions = {
{ RO_RANDOMIZE_SETTINGS_OFF, "Off" },
{ RO_RANDOMIZE_SETTINGS_EXCLUDE_ENTRANCES, "On" },
{ RO_RANDOMIZE_SETTINGS_INCLUDE_ENTRANCES, "On + Entrance Rando" },
{ RO_RANDOMIZE_SETTINGS_INCLUDE_ENTRANCES_DECOUPLED, "On + Entrance Rando + Decoupled" },
};

static bool locationsDirty = true;
static bool tricksDirty = true;
static int32_t prevMQDungeonSetting;
Expand Down Expand Up @@ -530,6 +537,33 @@ void SohMenu::AddMenuRandomizer() {
WIDGET_TEXT)
.Options(TextOptions().Color(UIWidgets::Colors::Gray));
AddWidget(path, "Seed Entry", WIDGET_SEPARATOR_TEXT);
AddWidget(path, "Randomize Settings Per Seed", WIDGET_CVAR_COMBOBOX)
.CVar(CVAR_RANDOMIZER_SETTING("RandomizeSettings"))
.Options(
ComboboxOptions()
.ComboMap(randomizeSettingsModeOptions)
.DefaultIndex(RO_RANDOMIZE_SETTINGS_OFF)
.Tooltip(
"Randomize settings each time a seed is generated.\n\n"
"Off - Settings are not randomized.\n"
"On - Randomize settings.\n"
"On + Entrance Rando - Randomize settings + entrances.\n"
"On + Entrance Rando + Decoupled - Same as above, plus Decoupled Entrances can be randomized.\n\n"
"Logic, Excluded Locations, Starting Items and Tricks are never randomized.\n"
"Starting age is always randomized."));
AddWidget(path, "Include MQ Dungeon Settings", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR_RANDOMIZER_SETTING("RandomizeSettingsIncludeMQ"))
.PreFunc([](WidgetInfo& info) {
const bool hasBothOtrs = OTRGlobals::Instance->HasMasterQuest() && OTRGlobals::Instance->HasOriginal();
const bool randomizeSettingsEnabled =
CVarGetInteger(CVAR_RANDOMIZER_SETTING("RandomizeSettings"), RO_RANDOMIZE_SETTINGS_OFF) !=
RO_RANDOMIZE_SETTINGS_OFF;
info.isHidden = !hasBothOtrs || !randomizeSettingsEnabled;
if (info.isHidden) {
CVarSetInteger(CVAR_RANDOMIZER_SETTING("RandomizeSettingsIncludeMQ"), 0);
}
})
.Options(CheckboxOptions().DefaultValue(false).Tooltip("If enabled, MQ dungeon settings are also randomized."));
AddWidget(path, "Manual seed entry", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR_RANDOMIZER_SETTING("ManualSeedEntry"))
.Options(CheckboxOptions().DefaultValue(true));
Expand Down
Loading