From 9b74a09955d745f97ac7ee2b33971596cd18cfdd Mon Sep 17 00:00:00 2001 From: Jordan Longstaff Date: Sat, 30 Nov 2024 20:59:38 -0500 Subject: [PATCH] Zora's River waterfall always open, take two (#4459) * Zora's River waterfall always open, take two * Remove improper, redundant checks in hook * Move all checks into update hook * Add Randomizer setting for keeping Sleeping Waterfall open * Change header exports to extern exports * Remove "closed as child" option for rando setting * Oops, missed a spot * A bit more cleanup: simplify a redundant condition * Unify hook handlers * Oopsie, fix build error * Add "play only once" option * Force Sleeping Waterfall enhancement in rando mode * Force enhancement only if waterfall is Open in rando * Restore forced-open waterfall in rando * Fix rando condition in hook * Fix? rando entrance logic for OI * Fix build errors --- soh/soh/Enhancements/presets.h | 9 ++- .../location_access/locacc_zoras_domain.cpp | 7 +- .../randomizer/option_descriptions.cpp | 6 ++ .../Enhancements/randomizer/randomizerTypes.h | 7 ++ soh/soh/Enhancements/randomizer/settings.cpp | 11 +++ .../Enhancements/timesaver_hook_handlers.cpp | 67 +++++++++++++++++++ soh/soh/SohMenuBar.cpp | 23 ++++++- 7 files changed, 124 insertions(+), 6 deletions(-) diff --git a/soh/soh/Enhancements/presets.h b/soh/soh/Enhancements/presets.h index 96b39ba30..63fd6efc6 100644 --- a/soh/soh/Enhancements/presets.h +++ b/soh/soh/Enhancements/presets.h @@ -304,6 +304,7 @@ const std::vector enhancementsCvars = { CVAR_ENHANCEMENT("TimeSavers.SkipChildStealth"), CVAR_ENHANCEMENT("TimeSavers.SkipTowerEscape"), CVAR_ENHANCEMENT("TimeSavers.SkipForcedDialog"), + CVAR_ENHANCEMENT("TimeSavers.SleepingWaterfall"), CVAR_ENHANCEMENT("SlowTextSpeed"), }; @@ -549,6 +550,7 @@ const std::vector 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"), @@ -1175,12 +1177,13 @@ const std::vector 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 hellModePresetEntries = { @@ -1235,16 +1238,18 @@ const std::vector 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 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), diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_zoras_domain.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_zoras_domain.cpp index 6ad5ec758..b0be901d9 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_zoras_domain.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_zoras_domain.cpp @@ -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, {}, {}, { diff --git a/soh/soh/Enhancements/randomizer/option_descriptions.cpp b/soh/soh/Enhancements/randomizer/option_descriptions.cpp index 7de98b3d0..d119c5e80 100644 --- a/soh/soh/Enhancements/randomizer/option_descriptions.cpp +++ b/soh/soh/Enhancements/randomizer/option_descriptions.cpp @@ -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" diff --git a/soh/soh/Enhancements/randomizer/randomizerTypes.h b/soh/soh/Enhancements/randomizer/randomizerTypes.h index fe8910c6d..252fc0b28 100644 --- a/soh/soh/Enhancements/randomizer/randomizerTypes.h +++ b/soh/soh/Enhancements/randomizer/randomizerTypes.h @@ -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, @@ -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, diff --git a/soh/soh/Enhancements/randomizer/settings.cpp b/soh/soh/Enhancements/randomizer/settings.cpp index df3d31c76..f1fa221da 100644 --- a/soh/soh/Enhancements/randomizer/settings.cpp +++ b/soh/soh/Enhancements/randomizer/settings.cpp @@ -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); @@ -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], @@ -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], @@ -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 }, @@ -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); diff --git a/soh/soh/Enhancements/timesaver_hook_handlers.cpp b/soh/soh/Enhancements/timesaver_hook_handlers.cpp index b7b4c9de6..c271b6cb3 100644 --- a/soh/soh/Enhancements/timesaver_hook_handlers.cpp +++ b/soh/soh/Enhancements/timesaver_hook_handlers.cpp @@ -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); } @@ -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) { @@ -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) { @@ -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([](void* innerActorRef) mutable { Actor* innerActor = static_cast(innerActorRef); @@ -769,6 +781,61 @@ void TimeSaverOnActorInitHandler(void* actorRef) { }); } + if (actor->id == ACTOR_BG_SPOT03_TAKI) { + bgSpot03UpdateHook = GameInteractor::Instance->RegisterGameHook([](void* innerActorRef) mutable { + Actor* innerActor = static_cast(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(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(bgSpot03UpdateHook); + GameInteractor::Instance->UnregisterGameHook(bgSpot03KillHook); + bgSpot03UpdateHook = 0; + bgSpot03KillHook = 0; + } + }); + bgSpot03KillHook = GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) mutable { + GameInteractor::Instance->UnregisterGameHook(bgSpot03UpdateHook); + GameInteractor::Instance->UnregisterGameHook(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(actorRef); enDntDemo->actionFunc = EnDntDemo_JudgeSkipToReward; diff --git a/soh/soh/SohMenuBar.cpp b/soh/soh/SohMenuBar.cpp index a133a8ef9..2435af1f8 100644 --- a/soh/soh/SohMenuBar.cpp +++ b/soh/soh/SohMenuBar.cpp @@ -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", @@ -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", @@ -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(); @@ -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,