diff options
| author | Philip Dubé <159546+serprex@users.noreply.github.com> | 2026-09-17 00:15:19 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-17 00:15:19 +0000 |
| commit | 01a4f3693d111695e3bcf02e554c5d3dbdb9799a (patch) | |
| tree | c7096652c00aeda8d56499792fb9dbfe7631963d | |
| parent | c9e3ef765224e97089fc3f43913c23bfe3f64a24 (diff) | |
Refactor file select quest visibility, fix single quest handling (#7210)
Select single visible quest when only single quest visible
Fix bad operator precedence
| -rw-r--r-- | soh/soh/Enhancements/FileSelectEnhancements.cpp | 29 | ||||
| -rw-r--r-- | soh/soh/Enhancements/FileSelectEnhancements.h | 2 | ||||
| -rw-r--r-- | soh/soh/SohGui/SohMenuEnhancements.cpp | 73 | ||||
| -rw-r--r-- | soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c | 94 |
4 files changed, 89 insertions, 109 deletions
diff --git a/soh/soh/Enhancements/FileSelectEnhancements.cpp b/soh/soh/Enhancements/FileSelectEnhancements.cpp index 489633dac..0900d202f 100644 --- a/soh/soh/Enhancements/FileSelectEnhancements.cpp +++ b/soh/soh/Enhancements/FileSelectEnhancements.cpp @@ -3,6 +3,7 @@ #include <ship/Context.h> #include "soh/OTRGlobals.h" +#include "soh/ResourceManagerHelpers.h" #include "soh/SohGui/SohModals.h" #include "soh/SohGui/SohGui.hpp" #include "soh/SohGui/UIWidgets.hpp" @@ -67,6 +68,34 @@ std::array<std::string, LANGUAGE_MAX> RandomizerSettingsMenuText[RSM_MAX] = { }, }; +// Quests are hidden when their O2R is missing or their File Select "Hide" option is on +bool SohFileSelect_IsQuestHidden(u8 quest) { + switch (quest) { + case QUEST_NORMAL: + return !ResourceMgr_GameHasOriginal() || CVarGetInteger(CVAR_ENHANCEMENT("FileSelect.HideNormalQuest"), 0); + case QUEST_MASTER: + return !ResourceMgr_GameHasMasterQuest() || + CVarGetInteger(CVAR_ENHANCEMENT("FileSelect.HideMasterQuest"), 0); + case QUEST_RANDOMIZER: + return CVarGetInteger(CVAR_ENHANCEMENT("FileSelect.HideRandomizerQuest"), 0); + case QUEST_BOSSRUSH: + return CVarGetInteger(CVAR_ENHANCEMENT("FileSelect.HideBossRushQuest"), 0); + default: + return false; + } +} + +u8 SohFileSelect_CountVisibleQuests() { + u8 count = 0; + for (u8 quest = QUEST_NORMAL; quest <= QUEST_BOSSRUSH; ++quest) { + if (!SohFileSelect_IsQuestHidden(quest)) { + count++; + } + } + + return count; +} + const char* SohFileSelect_GetSettingText(uint8_t optionIndex, uint8_t language) { return RandomizerSettingsMenuText[optionIndex][language].c_str(); } diff --git a/soh/soh/Enhancements/FileSelectEnhancements.h b/soh/soh/Enhancements/FileSelectEnhancements.h index 6ee38de50..62a67d51b 100644 --- a/soh/soh/Enhancements/FileSelectEnhancements.h +++ b/soh/soh/Enhancements/FileSelectEnhancements.h @@ -7,6 +7,8 @@ extern "C" { #endif const char* SohFileSelect_GetSettingText(u8 optionIndex, u8 language); void SohFileSelect_ShowPresetModal(); +bool SohFileSelect_IsQuestHidden(u8 quest); +u8 SohFileSelect_CountVisibleQuests(); #ifdef __cplusplus }; #endif diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index 64b57b2ee..7d0579981 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -3,6 +3,7 @@ #include "soh/Enhancements/SwitchAge.h" #include "soh/Enhancements/AdultMasks.h" #include "soh/Enhancements/BunnyHood.h" +#include "soh/Enhancements/FileSelectEnhancements.h" #include <soh/Enhancements/game-interactor/GameInteractor.h> #include <soh/OTRGlobals.h> #include <soh/Enhancements/cosmetics/authenticGfxPatches.h> @@ -177,26 +178,20 @@ static const std::map<int32_t, const char*> mirroredWorldModes = { { MIRRORED_WORLD_DUNGEONS_RANDOM_SEEDED, "Dungeons Random (Seeded)" }, }; -static uint8_t CountVisibleFileSelectQuests() { - uint8_t count = 0; - - if (ResourceMgr_GameHasOriginal() && !CVarGetInteger(CVAR_ENHANCEMENT("FileSelect.HideNormalQuest"), 0)) { - count++; - } - - if (ResourceMgr_GameHasMasterQuest() && !CVarGetInteger(CVAR_ENHANCEMENT("FileSelect.HideMasterQuest"), 0)) { - count++; - } - - if (!CVarGetInteger(CVAR_ENHANCEMENT("FileSelect.HideRandomizerQuest"), 0)) { - count++; - } - - if (!CVarGetInteger(CVAR_ENHANCEMENT("FileSelect.HideBossRushQuest"), 0)) { - count++; - } - - return count; +// Disables a File Select "Hide" checkbox when its O2R is missing, or when it would hide the last visible quest +static WidgetFunc HideQuestPreFunc(Quest quest) { + return [quest](WidgetInfo& info) { + if (quest == QUEST_NORMAL && !ResourceMgr_GameHasOriginal()) { + info.options->disabled = true; + info.options->disabledTooltip = "This option requires a loaded original O2R."; + } else if (quest == QUEST_MASTER && !ResourceMgr_GameHasMasterQuest()) { + info.options->disabled = true; + info.options->disabledTooltip = "This option requires a loaded Master Quest O2R."; + } else if (!SohFileSelect_IsQuestHidden(quest) && SohFileSelect_CountVisibleQuests() <= 1) { + info.options->disabled = true; + info.options->disabledTooltip = "At least one quest type must remain visible."; + } + }; } void SohMenu::AddMenuEnhancements() { @@ -806,55 +801,25 @@ void SohMenu::AddMenuEnhancements() { AddWidget(path, "Hide Original", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("FileSelect.HideNormalQuest")) .RaceDisable(false) - .PreFunc([](const WidgetInfo& info) { - if (!ResourceMgr_GameHasOriginal()) { - info.options->disabled = true; - info.options->disabledTooltip = "This option requires a loaded original O2R."; - } else if (CountVisibleFileSelectQuests() <= 1 && - !CVarGetInteger(CVAR_ENHANCEMENT("FileSelect.HideNormalQuest"), 0)) { - info.options->disabled = true; - info.options->disabledTooltip = "At least one quest type must remain visible."; - } - }) + .PreFunc(HideQuestPreFunc(QUEST_NORMAL)) .Options(CheckboxOptions().Tooltip( "Hides the original game when selecting a quest type on the File Select screen.")); AddWidget(path, "Hide Master Quest", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("FileSelect.HideMasterQuest")) .RaceDisable(false) - .PreFunc([](const WidgetInfo& info) { - if (!ResourceMgr_GameHasMasterQuest()) { - info.options->disabled = true; - info.options->disabledTooltip = "This option requires a loaded Master Quest O2R."; - } else if (CountVisibleFileSelectQuests() <= 1 && - !CVarGetInteger(CVAR_ENHANCEMENT("FileSelect.HideMasterQuest"), 0)) { - info.options->disabled = true; - info.options->disabledTooltip = "At least one quest type must remain visible."; - } - }) + .PreFunc(HideQuestPreFunc(QUEST_MASTER)) .Options(CheckboxOptions().Tooltip( "Hides the Master Quest option when selecting a quest type on the File Select screen.")); AddWidget(path, "Hide Randomizer", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("FileSelect.HideRandomizerQuest")) .RaceDisable(false) - .PreFunc([](const WidgetInfo& info) { - if (CountVisibleFileSelectQuests() <= 1 && - !CVarGetInteger(CVAR_ENHANCEMENT("FileSelect.HideRandomizerQuest"), 0)) { - info.options->disabled = true; - info.options->disabledTooltip = "At least one quest type must remain visible."; - } - }) + .PreFunc(HideQuestPreFunc(QUEST_RANDOMIZER)) .Options(CheckboxOptions().Tooltip( "Hides the Randomizer option when selecting a quest type on the File Select screen.")); AddWidget(path, "Hide Boss Rush", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("FileSelect.HideBossRushQuest")) .RaceDisable(false) - .PreFunc([](const WidgetInfo& info) { - if (CountVisibleFileSelectQuests() <= 1 && - !CVarGetInteger(CVAR_ENHANCEMENT("FileSelect.HideBossRushQuest"), 0)) { - info.options->disabled = true; - info.options->disabledTooltip = "At least one quest type must remain visible."; - } - }) + .PreFunc(HideQuestPreFunc(QUEST_BOSSRUSH)) .Options(CheckboxOptions().Tooltip( "Hides the Boss Rush option when selecting a quest type on the File Select screen.")); diff --git a/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c index 5278cff47..266c0114e 100644 --- a/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c +++ b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c @@ -31,31 +31,22 @@ #define MAX_QUEST QUEST_BOSSRUSH // #region SOH [Enhancement] - Hide Quest Modes -static bool IsQuestSkipped(uint8_t quest) { - switch (quest) { - case QUEST_NORMAL: - return !ResourceMgr_GameHasOriginal() || CVarGetInteger(CVAR_ENHANCEMENT("FileSelect.HideNormalQuest"), 0); - case QUEST_MASTER: - return !ResourceMgr_GameHasMasterQuest() || - CVarGetInteger(CVAR_ENHANCEMENT("FileSelect.HideMasterQuest"), 0); - case QUEST_RANDOMIZER: - return CVarGetInteger(CVAR_ENHANCEMENT("FileSelect.HideRandomizerQuest"), 0); - case QUEST_BOSSRUSH: - return CVarGetInteger(CVAR_ENHANCEMENT("FileSelect.HideBossRushQuest"), 0); - default: - return false; - } -} - -static uint8_t CountVisibleQuests(void) { - uint8_t count = 0; - for (int32_t quest = MIN_QUEST; quest <= MAX_QUEST; ++quest) { - if (!IsQuestSkipped(quest)) { - count++; +// Step from quest in the given direction (1 or -1) to the next visible quest, wrapping around at the ends +static s8 NextVisibleQuest(s8 quest, s8 dir) { + // Try each quest at most once, so this can't loop forever if every quest is hidden + for (int32_t tries = MAX_QUEST - MIN_QUEST + 1; tries > 0; --tries) { + quest += dir; + if (quest > MAX_QUEST) { + quest = MIN_QUEST; + } else if (quest < MIN_QUEST) { + quest = MAX_QUEST; + } + if (!SohFileSelect_IsQuestHidden(quest)) { + break; } } - return count; + return quest; } // #endregion @@ -706,35 +697,20 @@ void FileChoose_UpdateQuestMenu(GameState* thisx) { // #region SOH [Enhancement] - Hide Quest Modes // If the current quest type was hidden after being selected (i.e., CVar changed while on the quest menu), advance // to the next visible one. - if (CountVisibleQuests() > 0) { - while (IsQuestSkipped(this->questType[this->buttonIndex])) { - this->questType[this->buttonIndex]++; - if (this->questType[this->buttonIndex] > MAX_QUEST) { - this->questType[this->buttonIndex] = MIN_QUEST; - } - } + if (SohFileSelect_IsQuestHidden(this->questType[this->buttonIndex])) { + this->questType[this->buttonIndex] = NextVisibleQuest(this->questType[this->buttonIndex], 1); } // #endregion // #region SOH [Enhancement] - Hide Quest Modes - if (CountVisibleQuests() > 1 && ABS(this->stickRelX) > 30 || - (dpad && CHECK_BTN_ANY(input->press.button, BTN_DLEFT | BTN_DRIGHT))) { + if (SohFileSelect_CountVisibleQuests() > 1 && + (ABS(this->stickRelX) > 30 || (dpad && CHECK_BTN_ANY(input->press.button, BTN_DLEFT | BTN_DRIGHT)))) { // Cycle through quest types, skipping any that are hidden (i.e., Master Quest without O2R, // Randomizer/Boss Rush when their CVars are set). Wraps around if past min/max. if (this->stickRelX > 30 || (dpad && CHECK_BTN_ANY(input->press.button, BTN_DRIGHT))) { - do { - this->questType[this->buttonIndex]++; - if (this->questType[this->buttonIndex] > MAX_QUEST) { - this->questType[this->buttonIndex] = MIN_QUEST; - } - } while (IsQuestSkipped(this->questType[this->buttonIndex])); + this->questType[this->buttonIndex] = NextVisibleQuest(this->questType[this->buttonIndex], 1); } else if (this->stickRelX < -30 || (dpad && CHECK_BTN_ANY(input->press.button, BTN_DLEFT))) { - do { - this->questType[this->buttonIndex]--; - if (this->questType[this->buttonIndex] < MIN_QUEST) { - this->questType[this->buttonIndex] = MAX_QUEST; - } - } while (IsQuestSkipped(this->questType[this->buttonIndex])); + this->questType[this->buttonIndex] = NextVisibleQuest(this->questType[this->buttonIndex], -1); } // #endregion @@ -934,7 +910,7 @@ void FileChoose_RotateToQuest(GameState* thisx) { this->windowRot -= VREG(16); if (this->windowRot <= 314.0f) { - if (CountVisibleQuests() > 1) { + if (SohFileSelect_CountVisibleQuests() > 1) { this->windowRot = 314.0f; this->configMode = CM_START_QUEST_MENU; } else { @@ -946,21 +922,29 @@ void FileChoose_RotateToQuest(GameState* thisx) { this->windowRot += VREG(16); if (this->windowRot >= 314.0f) { - if (CountVisibleQuests() > 1) { + if (SohFileSelect_CountVisibleQuests() > 1) { this->windowRot = 314.0f; this->configMode = CM_START_QUEST_MENU; } else { this->windowRot = 628.0f; - if (!IsQuestSkipped(QUEST_RANDOMIZER)) { - this->configMode = CM_START_RANDOMIZER_SETTINGS_MENU; - } else if (!IsQuestSkipped(QUEST_BOSSRUSH)) { - this->configMode = CM_START_BOSS_RUSH_MENU; - } else { - this->configMode = CM_START_NAME_ENTRY; - - // Needed to come back to main menu - this->prevConfigMode = CM_MAIN_MENU; + // Only one quest is visible, so select it as if it was picked on the quest menu + this->questType[this->buttonIndex] = NextVisibleQuest(MAX_QUEST, 1); + gSaveContext.ship.quest.id = this->questType[this->buttonIndex]; + + switch (this->questType[this->buttonIndex]) { + case QUEST_RANDOMIZER: + this->configMode = CM_START_RANDOMIZER_SETTINGS_MENU; + break; + case QUEST_BOSSRUSH: + this->configMode = CM_START_BOSS_RUSH_MENU; + break; + default: + this->configMode = CM_START_NAME_ENTRY; + + // Needed to come back to main menu + this->prevConfigMode = CM_MAIN_MENU; + break; } } } @@ -1754,7 +1738,7 @@ void FileChoose_DrawWindowContents(GameState* thisx) { this->configMode == CM_NAME_ENTRY_TO_RANDOMIZER_SETTINGS_MENU) { // #region SOH [Enhancement] - Hide Quest Modes // Only draw the control stick prompts and arrows when there's more than one quest to cycle through. - if (CountVisibleQuests() > 1) { + if (SohFileSelect_CountVisibleQuests() > 1) { // #endregion // draw control stick prompts. Gfx_SetupDL_39Opa(this->state.gfxCtx); |
