summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilip Dubé <159546+serprex@users.noreply.github.com>2026-09-17 00:15:19 +0000
committerGitHub <noreply@github.com>2026-09-17 00:15:19 +0000
commit01a4f3693d111695e3bcf02e554c5d3dbdb9799a (patch)
treec7096652c00aeda8d56499792fb9dbfe7631963d
parentc9e3ef765224e97089fc3f43913c23bfe3f64a24 (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.cpp29
-rw-r--r--soh/soh/Enhancements/FileSelectEnhancements.h2
-rw-r--r--soh/soh/SohGui/SohMenuEnhancements.cpp73
-rw-r--r--soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c94
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);