Refactor file select quest visibility, fix single quest handling (#7210)

Select single visible quest when only single quest visible

Fix bad operator precedence
This commit is contained in:
Philip Dubé
2026-09-17 00:15:19 +00:00
committed by GitHub
parent c9e3ef7652
commit 01a4f3693d
4 changed files with 88 additions and 108 deletions
@@ -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();
}
@@ -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
+19 -54
View File
@@ -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."));
@@ -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;
// 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];
// Needed to come back to main menu
this->prevConfigMode = CM_MAIN_MENU;
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);