Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zora's River waterfall always open, take two #4459

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a24ee18
Zora's River waterfall always open, take two
JordanLongstaff Oct 21, 2024
21b81ac
Remove improper, redundant checks in hook
JordanLongstaff Nov 4, 2024
3a87d3b
Merge branch 'develop' into waterfall-always-open
JordanLongstaff Nov 4, 2024
24530bf
Move all checks into update hook
JordanLongstaff Nov 4, 2024
c9e0e32
Add Randomizer setting for keeping Sleeping Waterfall open
JordanLongstaff Nov 1, 2024
5eedae0
Change header exports to extern exports
JordanLongstaff Nov 5, 2024
76cc904
Merge branch 'develop' into waterfall-always-open
JordanLongstaff Nov 8, 2024
30f9c70
Merge branch 'develop' into waterfall-always-open
JordanLongstaff Nov 9, 2024
40a2a44
Remove "closed as child" option for rando setting
JordanLongstaff Nov 9, 2024
2ba18a6
Oops, missed a spot
JordanLongstaff Nov 9, 2024
2b6543c
A bit more cleanup: simplify a redundant condition
JordanLongstaff Nov 10, 2024
d712ead
Unify hook handlers
JordanLongstaff Nov 10, 2024
139e9b2
Oopsie, fix build error
JordanLongstaff Nov 10, 2024
69d8c97
Add "play only once" option
JordanLongstaff Nov 10, 2024
410131f
Force Sleeping Waterfall enhancement in rando mode
JordanLongstaff Nov 15, 2024
caaea54
Force enhancement only if waterfall is Open in rando
JordanLongstaff Nov 19, 2024
b7339ea
Restore forced-open waterfall in rando
JordanLongstaff Nov 20, 2024
9daa924
Merge branch 'develop' into waterfall-always-open
JordanLongstaff Nov 27, 2024
9e5e860
Fix rando condition in hook
JordanLongstaff Nov 30, 2024
310f540
Fix? rando entrance logic for OI
JordanLongstaff Nov 30, 2024
617e7c6
Merge branch 'develop' into waterfall-always-open
JordanLongstaff Nov 30, 2024
dc74e76
Fix build errors
JordanLongstaff Nov 30, 2024
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
9 changes: 7 additions & 2 deletions soh/soh/Enhancements/presets.h
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ const std::vector<const char*> enhancementsCvars = {
CVAR_ENHANCEMENT("TimeSavers.SkipChildStealth"),
CVAR_ENHANCEMENT("TimeSavers.SkipTowerEscape"),
CVAR_ENHANCEMENT("TimeSavers.SkipForcedDialog"),
CVAR_ENHANCEMENT("TimeSavers.SleepingWaterfall"),
CVAR_ENHANCEMENT("SlowTextSpeed"),
};

Expand Down Expand Up @@ -549,6 +550,7 @@ const std::vector<const char*> randomizerCvars = {
CVAR_RANDOMIZER_SETTING("SkipChildZelda"),
CVAR_RANDOMIZER_SETTING("SkipEponaRace"),
CVAR_RANDOMIZER_SETTING("SkipScarecrowsSong"),
CVAR_RANDOMIZER_SETTING("SleepingWaterfall"),
CVAR_RANDOMIZER_SETTING("StartingAge"),
CVAR_RANDOMIZER_SETTING("StartingBoleroOfFire"),
CVAR_RANDOMIZER_SETTING("StartingConsumables"),
Expand Down Expand Up @@ -1175,12 +1177,13 @@ const std::vector<PresetEntry> s6PresetEntries = {
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("SkipChildZelda"), 1),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("SkipEponaRace"), 1),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("SkipTowerEscape"), 1),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("SleepingWaterfall"), RO_WATERFALL_CLOSED),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("StartingAge"), RO_AGE_RANDOM),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("StartingConsumables"), 1),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("StartingDekuShield"), 1),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("StartingMapsCompasses"), RO_DUNGEON_ITEM_LOC_STARTWITH),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("StartingOcarina"), 1),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("ZorasFountain"), 0),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("ZorasFountain"), RO_ZF_CLOSED),
};

const std::vector<PresetEntry> hellModePresetEntries = {
Expand Down Expand Up @@ -1235,16 +1238,18 @@ const std::vector<PresetEntry> hellModePresetEntries = {
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("SkipEponaRace"), 1),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("SkipScarecrowsSong"), 1),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("SkipTowerEscape"), 1),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("SleepingWaterfall"), RO_WATERFALL_OPEN),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("StartingAge"), RO_AGE_RANDOM),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("StartingMapsCompasses"), RO_DUNGEON_ITEM_LOC_ANYWHERE),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("SunlightArrows"), 1),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("ZorasFountain"), 2),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("ZorasFountain"), RO_ZF_OPEN),
};

const std::vector<PresetEntry> BenchmarkPresetEntries = {
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("Forest"), RO_FOREST_CLOSED_DEKU),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("KakarikoGate"), RO_KAK_GATE_OPEN),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("DoorOfTime"), RO_DOOROFTIME_SONGONLY),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("SleepingWaterfall"), RO_WATERFALL_CLOSED),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("ZorasFountain"), RO_ZF_CLOSED),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("GerudoFortress"), RO_GF_NORMAL),
PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("RainbowBridge"), RO_BRIDGE_DUNGEON_REWARDS),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ void RegionTable_Init_ZorasDomain() {
Entrance(RR_ZR_FAIRY_GROTTO, {[]{return Here(RR_ZORAS_RIVER, []{return logic->BlastOrSmash();});}}),
Entrance(RR_THE_LOST_WOODS, {[]{return logic->HasItem(RG_SILVER_SCALE) || logic->CanUse(RG_IRON_BOOTS);}}),
Entrance(RR_ZR_STORMS_GROTTO, {[]{return logic->CanOpenStormsGrotto();}}),
Entrance(RR_ZR_BEHIND_WATERFALL, {[]{return logic->CanUse(RG_ZELDAS_LULLABY) || (logic->IsChild && ctx->GetTrickOption(RT_ZR_CUCCO)) || (logic->IsAdult && logic->CanUse(RG_HOVER_BOOTS) && ctx->GetTrickOption(RT_ZR_HOVERS));}}),
Entrance(RR_ZR_BEHIND_WATERFALL, {[]{
return ctx->GetOption(RSK_SLEEPING_WATERFALL).Is(RO_WATERFALL_OPEN) ||
Here(RR_ZORAS_RIVER, []{return logic->CanUse(RG_ZELDAS_LULLABY);}) ||
(logic->IsChild && ctx->GetTrickOption(RT_ZR_CUCCO)) ||
(logic->IsAdult && logic->CanUse(RG_HOVER_BOOTS) && ctx->GetTrickOption(RT_ZR_HOVERS));
}}),
});

areaTable[RR_ZR_BEHIND_WATERFALL] = Region("ZR Behind Waterfall", "Zora River", {RA_ZORAS_RIVER}, DAY_NIGHT_CYCLE, {}, {}, {
Expand Down
6 changes: 6 additions & 0 deletions soh/soh/Enhancements/randomizer/option_descriptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ void Settings::CreateOptionDescriptions() {
"\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_SLEEPING_WATERFALL] = "Closed - Sleeping Waterfall obstructs the entrance to Zora's "
"Domain. Zelda's Lullaby must be played in order to open it "
"(but only once; then it stays open in both time periods).\n"
"\n"
"Open - Sleeping Waterfall is always open. "
"Link may always enter Zora's Domain.";
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"
Expand Down
7 changes: 7 additions & 0 deletions soh/soh/Enhancements/randomizer/randomizerTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -3947,6 +3947,7 @@ typedef enum {
RSK_KAK_GATE,
RSK_DOOR_OF_TIME,
RSK_ZORAS_FOUNTAIN,
RSK_SLEEPING_WATERFALL,
RSK_STARTING_AGE,
RSK_GERUDO_FORTRESS,
RSK_RAINBOW_BRIDGE,
Expand Down Expand Up @@ -4184,6 +4185,12 @@ typedef enum {
RO_ZF_OPEN,
} RandoOptionZorasFountain;

//Sleeping Waterfall settings (closed, open)
typedef enum {
RO_WATERFALL_CLOSED,
RO_WATERFALL_OPEN,
} RandoOptionSleepingWaterfall;

//Starting Age settings (child, adult, random)
typedef enum {
RO_AGE_CHILD,
Expand Down
11 changes: 11 additions & 0 deletions soh/soh/Enhancements/randomizer/settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ void Settings::CreateOptions() {
mOptions[RSK_KAK_GATE] = Option::U8("Kakariko Gate", {"Closed", "Open"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("KakarikoGate"), mOptionDescriptions[RSK_KAK_GATE]);
mOptions[RSK_DOOR_OF_TIME] = Option::U8("Door of Time", {"Closed", "Song only", "Open"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("DoorOfTime"), mOptionDescriptions[RSK_DOOR_OF_TIME], WidgetType::Combobox);
mOptions[RSK_ZORAS_FOUNTAIN] = Option::U8("Zora's Fountain", {"Closed", "Closed as child", "Open"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ZorasFountain"), mOptionDescriptions[RSK_ZORAS_FOUNTAIN]);
mOptions[RSK_SLEEPING_WATERFALL] = Option::U8("Sleeping Waterfall", {"Closed", "Open"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("SleepingWaterfall"), mOptionDescriptions[RSK_SLEEPING_WATERFALL]);
mOptions[RSK_GERUDO_FORTRESS] = Option::U8("Gerudo Fortress", {"Normal", "Fast", "Open"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("GerudoFortress"), mOptionDescriptions[RSK_GERUDO_FORTRESS]);
mOptions[RSK_RAINBOW_BRIDGE] = Option::U8("Rainbow Bridge", {"Vanilla", "Always open", "Stones", "Medallions", "Dungeon rewards", "Dungeons", "Tokens", "Greg"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("RainbowBridge"), mOptionDescriptions[RSK_RAINBOW_BRIDGE], WidgetType::Combobox, RO_BRIDGE_VANILLA, false, IMFLAG_NONE);
mOptions[RSK_RAINBOW_BRIDGE_STONE_COUNT] = Option::U8("Stone Count", {NumOpts(0, 4)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("StoneCount"), "", WidgetType::Slider, 3, true);
Expand Down Expand Up @@ -696,6 +697,7 @@ void Settings::CreateOptions() {
&mOptions[RSK_KAK_GATE],
&mOptions[RSK_DOOR_OF_TIME],
&mOptions[RSK_ZORAS_FOUNTAIN],
&mOptions[RSK_SLEEPING_WATERFALL],
}, false, WidgetContainerType::COLUMN);
mOptionGroups[RSG_WORLD_IMGUI] = OptionGroup::SubGroup("World Settings", {
&mOptions[RSK_STARTING_AGE],
Expand Down Expand Up @@ -945,6 +947,7 @@ void Settings::CreateOptions() {
&mOptions[RSK_KAK_GATE],
&mOptions[RSK_DOOR_OF_TIME],
&mOptions[RSK_ZORAS_FOUNTAIN],
&mOptions[RSK_SLEEPING_WATERFALL],
&mOptions[RSK_GERUDO_FORTRESS],
&mOptions[RSK_RAINBOW_BRIDGE],
&mOptions[RSK_RAINBOW_BRIDGE_STONE_COUNT],
Expand Down Expand Up @@ -1262,6 +1265,7 @@ void Settings::CreateOptions() {
{ "Open Settings:Kakariko Gate", RSK_KAK_GATE },
{ "Open Settings:Door of Time", RSK_DOOR_OF_TIME },
{ "Open Settings:Zora's Fountain", RSK_ZORAS_FOUNTAIN },
{ "Open Settings:Sleeping Waterfall", RSK_SLEEPING_WATERFALL },
{ "World Settings:Starting Age", RSK_STARTING_AGE },
{ "Open Settings:Gerudo Fortress", RSK_GERUDO_FORTRESS },
{ "Open Settings:Rainbow Bridge", RSK_RAINBOW_BRIDGE },
Expand Down Expand Up @@ -2625,6 +2629,13 @@ void Settings::ParseJson(nlohmann::json spoilerFileJson) {
mOptions[index].SetContextIndex(RO_ZF_OPEN);
}
break;
case RSK_SLEEPING_WATERFALL:
if (it.value() == "Closed") {
mOptions[index].SetContextIndex(RO_WATERFALL_CLOSED);
} else if (it.value() == "Open") {
mOptions[index].SetContextIndex(RO_WATERFALL_OPEN);
}
break;
case RSK_STARTING_AGE:
if (it.value() == "Child") {
mOptions[index].SetContextIndex(RO_AGE_CHILD);
Expand Down
67 changes: 67 additions & 0 deletions soh/soh/Enhancements/timesaver_hook_handlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ extern SaveContext gSaveContext;
extern PlayState* gPlayState;
extern int32_t D_8011D3AC;

extern void func_808ADEF0(BgSpot03Taki* bgSpot03Taki, PlayState* play);
extern void BgSpot03Taki_ApplyOpeningAlpha(BgSpot03Taki* bgSpot03Taki, s32 bufferIndex);

extern void func_80AF36EC(EnRu2* enRu2, PlayState* play);
}

Expand Down Expand Up @@ -96,6 +99,9 @@ void EnDntDemo_JudgeSkipToReward(EnDntDemo* enDntDemo, PlayState* play) {
}
}

void BgSpot03Taki_KeepOpen(BgSpot03Taki* bgSpot03Taki, PlayState* play) {
}

static int successChimeCooldown = 0;
void RateLimitedSuccessChime() {
if (successChimeCooldown == 0) {
Expand Down Expand Up @@ -692,6 +698,8 @@ static uint32_t enFuUpdateHook = 0;
static uint32_t enFuKillHook = 0;
static uint32_t bgSpot02UpdateHook = 0;
static uint32_t bgSpot02KillHook = 0;
static uint32_t bgSpot03UpdateHook = 0;
static uint32_t bgSpot03KillHook = 0;
static uint32_t enPoSistersUpdateHook = 0;
static uint32_t enPoSistersKillHook = 0;
void TimeSaverOnActorInitHandler(void* actorRef) {
Expand Down Expand Up @@ -747,6 +755,10 @@ void TimeSaverOnActorInitHandler(void* actorRef) {
});
}

if (actor->id == ACTOR_EN_OWL && gPlayState->sceneNum == SCENE_ZORAS_RIVER && CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SleepingWaterfall"), 0) == 2) {
Actor_Kill(actor);
}

if (actor->id == ACTOR_BG_SPOT02_OBJECTS && actor->params == 2) {
bgSpot02UpdateHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorUpdate>([](void* innerActorRef) mutable {
Actor* innerActor = static_cast<Actor*>(innerActorRef);
Expand All @@ -769,6 +781,61 @@ void TimeSaverOnActorInitHandler(void* actorRef) {
});
}

if (actor->id == ACTOR_BG_SPOT03_TAKI) {
bgSpot03UpdateHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorUpdate>([](void* innerActorRef) mutable {
Actor* innerActor = static_cast<Actor*>(innerActorRef);

if (innerActor->id != ACTOR_BG_SPOT03_TAKI) {
return;
}

bool shouldKeepOpen;
switch (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SleepingWaterfall"), 0)) {
case 1:
shouldKeepOpen = Flags_GetEventChkInf(EVENTCHKINF_OPENED_ZORAS_DOMAIN);
break;
case 2:
if (IS_RANDO && RAND_GET_OPTION(RSK_SLEEPING_WATERFALL) == RO_WATERFALL_OPEN) {
shouldKeepOpen = true;
} else {
shouldKeepOpen = CHECK_QUEST_ITEM(QUEST_SONG_LULLABY) &&
(INV_CONTENT(ITEM_OCARINA_TIME) == ITEM_OCARINA_TIME ||
INV_CONTENT(ITEM_OCARINA_FAIRY) == ITEM_OCARINA_FAIRY);
}
break;
default:
shouldKeepOpen = false;
break;
}

if (!shouldKeepOpen) {
return;
}

BgSpot03Taki* bgSpot03 = static_cast<BgSpot03Taki*>(innerActorRef);
if (bgSpot03->actionFunc == func_808ADEF0) {
bgSpot03->actionFunc = BgSpot03Taki_KeepOpen;
bgSpot03->state = WATERFALL_OPENED;
bgSpot03->openingAlpha = 0.0f;
Flags_SetSwitch(gPlayState, bgSpot03->switchFlag);
func_8003EBF8(gPlayState, &gPlayState->colCtx.dyna, bgSpot03->dyna.bgId);
BgSpot03Taki_ApplyOpeningAlpha(bgSpot03, 0);
BgSpot03Taki_ApplyOpeningAlpha(bgSpot03, 1);

GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorUpdate>(bgSpot03UpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(bgSpot03KillHook);
bgSpot03UpdateHook = 0;
bgSpot03KillHook = 0;
}
});
bgSpot03KillHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>([](int16_t sceneNum) mutable {
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorUpdate>(bgSpot03UpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(bgSpot03KillHook);
bgSpot03UpdateHook = 0;
bgSpot03KillHook = 0;
});
}

if (actor->id == ACTOR_EN_DNT_DEMO && (IS_RANDO || CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO))) {
EnDntDemo* enDntDemo = static_cast<EnDntDemo*>(actorRef);
enDntDemo->actionFunc = EnDntDemo_JudgeSkipToReward;
Expand Down
23 changes: 20 additions & 3 deletions soh/soh/SohMenuBar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ static const char* imguiScaleOptions[4] = { "Small", "Normal", "Large", "X-Large
static const char* chestStyleMatchesContentsOptions[4] = { "Disabled", "Both", "Texture Only", "Size Only" };
static const char* skipGetItemAnimationOptions[3] = { "Disabled", "Junk Items", "All Items" };
static const char* skipForcedDialogOptions[4] = { "None", "Navi Only", "NPCs Only", "All" };
static const char* sleepingWaterfallOptions[3] = { "Always", "Once", "Never" };
static const char* bunnyHoodOptions[3] = { "Disabled", "Faster Run & Longer Jump", "Faster Run" };
static const char* mirroredWorldModes[9] = {
"Disabled", "Always", "Random", "Random (Seeded)", "Dungeons",
Expand Down Expand Up @@ -123,7 +124,7 @@ static const char* imguiScaleOptions[4] = { "Small", "Normal", "Large", "X-Large
CVAR_ENHANCEMENT("InjectItemCounts.HeartPiece"),
CVAR_ENHANCEMENT("InjectItemCounts.HeartContainer"),
};
static const char* itemCountMessageOptions[sizeof(itemCountMessageCVars) / sizeof(const char*)] = {
static const char* itemCountMessageOptions[ARRAY_COUNT(itemCountMessageCVars)] = {
"Gold Skulltula Tokens",
"Pieces of Heart",
"Heart Containers",
Expand Down Expand Up @@ -796,7 +797,23 @@ void DrawEnhancementsMenu() {
UIWidgets::PaddedEnhancementCheckbox("Skip Scarecrow Song", CVAR_ENHANCEMENT("InstantScarecrow"), true, false,
forceSkipScarecrow, forceSkipScarecrowText, UIWidgets::CheckboxGraphics::Checkmark);
UIWidgets::Tooltip("Pierre appears when Ocarina is pulled out. Requires learning scarecrow song.");

bool forceSleepingWaterfallEnhancement =
IS_RANDO && OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SLEEPING_WATERFALL) == RO_WATERFALL_OPEN;
uint8_t forceSleepingWaterfallValue = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SLEEPING_WATERFALL) + 1;
static const char* forceSleepingWaterfallText =
"This setting is forcefully enabled because a randomizer savefile with \"Sleeping Waterfall: Open\" is loaded.";
UIWidgets::PaddedText("Play Zelda's Lullaby to open Sleeping Waterfall", true, false);
UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("TimeSavers.SleepingWaterfall"),
sleepingWaterfallOptions, 0, forceSleepingWaterfallEnhancement,
forceSleepingWaterfallText, forceSleepingWaterfallValue);
UIWidgets::Tooltip(
"Always: Link must always play Zelda's Lullaby to open "
"the waterfall entrance to Zora's Domain.\n"
"Once: Link only needs to play Zelda's Lullaby once to "
"open the waterfall; after that, it stays open permanently.\n"
"Never: Link never needs to play Zelda's Lullaby to open the "
"waterfall; he only needs to have learned it and have an ocarina."
);

ImGui::EndTable();
ImGui::EndMenu();
Expand Down Expand Up @@ -867,7 +884,7 @@ void DrawEnhancementsMenu() {
UIWidgets::Spacer(0);

if (ImGui::BeginMenu("Item Count Messages")) {
int numOptions = sizeof(itemCountMessageCVars) / sizeof(const char*);
int numOptions = ARRAY_COUNT(itemCountMessageCVars);
bool allItemCountsChecked = std::all_of(itemCountMessageCVars, itemCountMessageCVars + numOptions,
[](const char* cvar) { return CVarGetInteger(cvar, 0); });
bool someItemCountsChecked = std::any_of(itemCountMessageCVars, itemCountMessageCVars + numOptions,
Expand Down