From 2e5a985745580a1f2285689357b0defa00971a78 Mon Sep 17 00:00:00 2001 From: Garrett Cox Date: Wed, 31 Dec 2025 17:31:55 -0600 Subject: [PATCH] Add support for warp points in the dev tools, as well as a boot to warp point option (#6037) --- .../Enhancements/BootToDebugWarpScreen.cpp | 46 ---- soh/soh/Enhancements/Warping.cpp | 207 ++++++++++++++++++ .../cosmetics/CustomLogoTitle.cpp | 9 +- soh/soh/SohGui/SohMenuDevTools.cpp | 55 +++-- 4 files changed, 253 insertions(+), 64 deletions(-) delete mode 100644 soh/soh/Enhancements/BootToDebugWarpScreen.cpp create mode 100644 soh/soh/Enhancements/Warping.cpp diff --git a/soh/soh/Enhancements/BootToDebugWarpScreen.cpp b/soh/soh/Enhancements/BootToDebugWarpScreen.cpp deleted file mode 100644 index dc0f1899b..000000000 --- a/soh/soh/Enhancements/BootToDebugWarpScreen.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include -#include "soh/Enhancements/game-interactor/GameInteractor.h" -#include "soh/ShipInit.hpp" -#include "functions.h" - -extern "C" { -#include "z64.h" -#include "overlays/gamestates/ovl_file_choose/file_choose.h" -} - -static constexpr int32_t CVAR_DEBUG_ENABLED_DEFAULT = 0; -#define CVAR_DEBUG_ENABLED_NAME CVAR_DEVELOPER_TOOLS("DebugEnabled") -#define CVAR_DEBUG_ENABLED_VALUE CVarGetInteger(CVAR_DEBUG_ENABLED_NAME, CVAR_DEBUG_ENABLED_DEFAULT) - -static constexpr int32_t CVAR_BOOT_TO_DEBUG_WARP_SCREEN_DEFAULT = 0; -#define CVAR_BOOT_TO_DEBUG_WARP_SCREEN_NAME CVAR_DEVELOPER_TOOLS("BootToDebugWarpScreen") -#define CVAR_BOOT_TO_DEBUG_WARP_SCREEN_VALUE \ - CVarGetInteger(CVAR_BOOT_TO_DEBUG_WARP_SCREEN_NAME, CVAR_BOOT_TO_DEBUG_WARP_SCREEN_DEFAULT) - -void OnFileChooseMainBootToDebugWarpScreen(void* gameState) { - FileChooseContext* fileChooseContext = (FileChooseContext*)gameState; - fileChooseContext->buttonIndex = 0xFF; - fileChooseContext->menuMode = FS_MENU_MODE_SELECT; - fileChooseContext->selectMode = SM_LOAD_GAME; -} - -void OnZTitleUpdateBootToDebugWarpScreen(void* gameState) { - TitleContext* titleContext = (TitleContext*)gameState; - - gSaveContext.seqId = (u8)NA_BGM_DISABLED; - gSaveContext.natureAmbienceId = 0xFF; - gSaveContext.gameMode = GAMEMODE_FILE_SELECT; - titleContext->state.running = false; - - SET_NEXT_GAMESTATE(&titleContext->state, FileChoose_Init, FileChooseContext); -} - -void RegisterBootToDebugWarpScreen() { - COND_HOOK(OnFileChooseMain, CVAR_DEBUG_ENABLED_VALUE && CVAR_BOOT_TO_DEBUG_WARP_SCREEN_VALUE, - OnFileChooseMainBootToDebugWarpScreen); - COND_HOOK(OnZTitleUpdate, CVAR_DEBUG_ENABLED_VALUE && CVAR_BOOT_TO_DEBUG_WARP_SCREEN_VALUE, - OnZTitleUpdateBootToDebugWarpScreen); -} - -static RegisterShipInitFunc initFunc(RegisterBootToDebugWarpScreen, - { CVAR_DEBUG_ENABLED_NAME, CVAR_BOOT_TO_DEBUG_WARP_SCREEN_NAME }); diff --git a/soh/soh/Enhancements/Warping.cpp b/soh/soh/Enhancements/Warping.cpp new file mode 100644 index 000000000..3df636c31 --- /dev/null +++ b/soh/soh/Enhancements/Warping.cpp @@ -0,0 +1,207 @@ +#include +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" +#include "functions.h" +#include "soh/SohGui/MenuTypes.h" +#include "soh/util.h" + +extern "C" { +#include "z64.h" +#include "overlays/gamestates/ovl_file_choose/file_choose.h" +void Sram_InitDebugSave(void); +void Select_LoadGame(SelectContext* selectContext, s32 entranceIndex); +} + +static constexpr int32_t CVAR_DEBUG_ENABLED_DEFAULT = 0; +#define CVAR_DEBUG_ENABLED_NAME CVAR_DEVELOPER_TOOLS("DebugEnabled") +#define CVAR_DEBUG_ENABLED_VALUE CVarGetInteger(CVAR_DEBUG_ENABLED_NAME, CVAR_DEBUG_ENABLED_DEFAULT) + +static constexpr int32_t CVAR_BOOT_TO_DEBUG_WARP_SCREEN_DEFAULT = 0; +#define CVAR_BOOT_TO_DEBUG_WARP_SCREEN_NAME CVAR_DEVELOPER_TOOLS("BootToDebugWarpScreen") +#define CVAR_BOOT_TO_DEBUG_WARP_SCREEN_VALUE \ + CVarGetInteger(CVAR_BOOT_TO_DEBUG_WARP_SCREEN_NAME, CVAR_BOOT_TO_DEBUG_WARP_SCREEN_DEFAULT) + +typedef struct WarpPoint { + s32 entranceId; + s8 roomNum; + Vec3f pos; + s16 rotY; + bool bootToPoint; +} WarpPoint; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Vec3f, x, y, z) +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(WarpPoint, entranceId, roomNum, pos, rotY, bootToPoint) +std::map warpPoints; + +void LoadConfig() { + auto allConfig = Ship::Context::GetInstance()->GetConfig()->GetNestedJson(); + if (allConfig.find("WarpPoints") == allConfig.end() || !allConfig["WarpPoints"].is_object()) { + allConfig["WarpPoints"] = nlohmann::json::object(); + } + warpPoints = allConfig["WarpPoints"]; +} + +void SaveConfig() { + auto allConfig = Ship::Context::GetInstance()->GetConfig()->GetNestedJson(); + allConfig["WarpPoints"] = warpPoints; + Ship::Context::GetInstance()->GetConfig()->SetBlock("WarpPoints", warpPoints); + Ship::Context::GetInstance()->GetConfig()->Save(); +} + +void Warp(WarpPoint& warpPoint) { + SPDLOG_INFO("PLAYSTATE IS NULL: {}", gPlayState == NULL); + if (gPlayState == NULL) { + // If gPlayState is NULL, it means the the user opted into BootToWarpPoint and the game is starting up. + gSaveContext.gameMode = GAMEMODE_NORMAL; + gSaveContext.fileNum = 0xFE; // temporary file so that this will respect debug save file option + Sram_InitDebugSave(); + gSaveContext.fileNum = 0xFF; + gSaveContext.sceneSetupIndex = 0; + gSaveContext.cutsceneIndex = 0; + + // Copied from Select_LoadGame + for (int buttonIndex = 0; buttonIndex < ARRAY_COUNT(gSaveContext.buttonStatus); buttonIndex++) { + gSaveContext.buttonStatus[buttonIndex] = BTN_ENABLED; + } + gSaveContext.forceRisingButtonAlphas = gSaveContext.unk_13E8 = gSaveContext.unk_13EA = gSaveContext.unk_13EC = + 0; + Audio_QueueSeqCmd(SEQ_PLAYER_BGM_MAIN << 24 | NA_BGM_STOP); + gSaveContext.entranceIndex = warpPoint.entranceId; + + gSaveContext.seqId = (u8)NA_BGM_DISABLED; + gSaveContext.natureAmbienceId = 0xFF; + gSaveContext.showTitleCard = true; + gWeatherMode = 0; + gGameState->running = false; + SET_NEXT_GAMESTATE(gGameState, Play_Init, PlayState); + + GameInteractor_ExecuteOnLoadGame(gSaveContext.fileNum); + } else { + gPlayState->nextEntranceIndex = warpPoint.entranceId; + gPlayState->transitionTrigger = TRANS_TRIGGER_START; + gPlayState->transitionType = TRANS_TYPE_INSTANT; + } + gSaveContext.respawn[RESPAWN_MODE_DOWN].entranceIndex = warpPoint.entranceId; + gSaveContext.respawn[RESPAWN_MODE_DOWN].roomIndex = warpPoint.roomNum; + gSaveContext.respawn[RESPAWN_MODE_DOWN].pos = warpPoint.pos; + gSaveContext.respawn[RESPAWN_MODE_DOWN].yaw = warpPoint.rotY; + gSaveContext.respawn[RESPAWN_MODE_DOWN].playerParams = 0xDFF; + gSaveContext.nextTransitionType = TRANS_TYPE_FADE_BLACK_FAST; + gSaveContext.respawnFlag = 1; + static HOOK_ID hookId = 0; + hookId = REGISTER_VB_SHOULD(VB_INFLICT_VOID_DAMAGE, { + *should = false; + GameInteractor::Instance->UnregisterGameHookForID(hookId); + }); +} + +static std::string warpNameInput = ""; + +void WarpPointsWidget(WidgetInfo& info) { + ImGui::SeparatorText("Warp Points"); + if (gPlayState != NULL && GET_PLAYER(gPlayState) != NULL) { + UIWidgets::InputString("##WarpPointNameInput", &warpNameInput, + { + .size = ImVec2(ImGui::GetContentRegionAvail().x - 50.0f, 0.0f), + .placeholder = "Enter warp point name...", + }); + + ImGui::SameLine(); + bool isEmpty = warpNameInput.empty(); + if (isEmpty) { + ImGui::BeginDisabled(); + } + + if (UIWidgets::Button(ICON_FA_PLUS)) { + Player* player = GET_PLAYER(gPlayState); + + std::string warpName = SohUtils::GetSceneName(gPlayState->sceneNum); + if (gPlayState->roomCtx.curRoom.num != 0) { + warpName += " (" + std::to_string(gPlayState->roomCtx.curRoom.num) + ")"; + } + + warpPoints[warpNameInput] = WarpPoint{ + .entranceId = gSaveContext.entranceIndex, + .roomNum = gPlayState->roomCtx.curRoom.num, + .pos = player->actor.world.pos, + .rotY = player->actor.shape.rot.y, + }; + SaveConfig(); + warpNameInput = ""; + } + if (isEmpty) { + ImGui::EndDisabled(); + } + } + // List of warp points, showing just their name, a button to warp and a button to delete + for (auto it = warpPoints.begin(); it != warpPoints.end();) { + ImGui::PushID(it->first.c_str()); + + ImGui::AlignTextToFramePadding(); + ImGui::Text("%s", it->first.c_str()); + if (it->second.bootToPoint) { + ImGui::SameLine(); + ImGui::TextColored(ImVec4(0.85f, 0.55f, 0.0f, 1.0f), "[Boot]"); + } + ImGui::SameLine(ImGui::GetContentRegionAvail().x - 115.0f); + if (gPlayState == NULL) + ImGui::BeginDisabled(); + if (UIWidgets::Button(ICON_FA_PLANE, { .size = UIWidgets::Sizes::Inline })) { + // Warp to this point + Warp(it->second); + } + if (gPlayState == NULL) + ImGui::EndDisabled(); + ImGui::SameLine(); + if (UIWidgets::Button(ICON_FA_REFRESH, + { .size = UIWidgets::Sizes::Inline, .color = UIWidgets::Colors::Orange })) { + for (auto& wp : warpPoints) { + wp.second.bootToPoint = false; + } + it->second.bootToPoint = true; + SaveConfig(); + } + ImGui::SameLine(); + if (UIWidgets::Button(ICON_FA_TRASH, { .size = UIWidgets::Sizes::Inline, .color = UIWidgets::Colors::Red })) { + it = warpPoints.erase(it); + SaveConfig(); + ImGui::PopID(); + continue; + ; + } + ImGui::PopID(); + + ++it; + } +} + +void RegisterWarping() { + static bool loadedConfig = false; + if (!loadedConfig) { + LoadConfig(); + loadedConfig = true; + } + + COND_HOOK(OnZTitleUpdate, CVAR_DEBUG_ENABLED_VALUE && CVAR_BOOT_TO_DEBUG_WARP_SCREEN_VALUE == 1, + [](void* gameState) { + TitleContext* titleContext = (TitleContext*)gameState; + + gSaveContext.seqId = (u8)NA_BGM_DISABLED; + gSaveContext.natureAmbienceId = 0xFF; + gSaveContext.gameMode = GAMEMODE_NORMAL; + titleContext->state.running = false; + SET_NEXT_GAMESTATE(&titleContext->state, Select_Init, SelectContext); + }); + + COND_HOOK(OnZTitleUpdate, CVAR_DEBUG_ENABLED_VALUE && CVAR_BOOT_TO_DEBUG_WARP_SCREEN_VALUE == 2, + [](void* gameState) { + for (auto& wp : warpPoints) { + if (wp.second.bootToPoint) { + Warp(wp.second); + break; + } + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterWarping, { CVAR_DEBUG_ENABLED_NAME, CVAR_BOOT_TO_DEBUG_WARP_SCREEN_NAME }); diff --git a/soh/soh/Enhancements/cosmetics/CustomLogoTitle.cpp b/soh/soh/Enhancements/cosmetics/CustomLogoTitle.cpp index 3ec872368..876313f7a 100644 --- a/soh/soh/Enhancements/cosmetics/CustomLogoTitle.cpp +++ b/soh/soh/Enhancements/cosmetics/CustomLogoTitle.cpp @@ -213,10 +213,15 @@ void OnZTitleUpdateSkipToFileSelect(void* gameState) { } void RegisterCustomLogoTitleBootsequence() { - COND_HOOK(OnZTitleUpdate, CVAR_BOOTSEQUENCE_VALUE == BOOTSEQUENCE_FILESELECT, OnZTitleUpdateSkipToFileSelect); + COND_HOOK(OnZTitleUpdate, + CVAR_BOOTSEQUENCE_VALUE == BOOTSEQUENCE_FILESELECT && + CVarGetInteger(CVAR_DEVELOPER_TOOLS("BootToDebugWarpScreen"), 0) == 0, + OnZTitleUpdateSkipToFileSelect); } -static RegisterShipInitFunc registerTitleBootSequence(RegisterCustomLogoTitleBootsequence, { CVAR_BOOTSEQUENCE_NAME }); +static RegisterShipInitFunc registerTitleBootSequence(RegisterCustomLogoTitleBootsequence, + { CVAR_BOOTSEQUENCE_NAME, + CVAR_DEVELOPER_TOOLS("BootToDebugWarpScreen") }); // // // // // // // Let it Snow diff --git a/soh/soh/SohGui/SohMenuDevTools.cpp b/soh/soh/SohGui/SohMenuDevTools.cpp index 288d002f2..f3a88f0c3 100644 --- a/soh/soh/SohGui/SohMenuDevTools.cpp +++ b/soh/soh/SohGui/SohMenuDevTools.cpp @@ -1,4 +1,7 @@ #include "SohMenu.h" +#include "SohGui.hpp" + +void WarpPointsWidget(WidgetInfo& info); namespace SohGui { @@ -10,6 +13,11 @@ static const std::unordered_map logLevels = { { DEBUG_LOG_WARN, "Warn" }, { DEBUG_LOG_ERROR, "Error" }, { DEBUG_LOG_CRITICAL, "Critical" }, { DEBUG_LOG_OFF, "Off" }, }; +static std::unordered_map bootToOptions = { + { 0, "Disabled" }, + { 1, "Debug Warp Screen" }, + { 2, "Warp Point" }, +}; #ifdef _DEBUG DebugLogOption defaultLogLevel = DEBUG_LOG_TRACE; @@ -39,12 +47,6 @@ void SohMenu::AddMenuDevTools() { .Options( CheckboxOptions().Tooltip("Enables Debug Mode, allowing you to select maps with L + R + Z, noclip " "with L + D-pad Right, and open the debug menu with L on the pause screen.")); - AddWidget(path, "Boot To Debug Warp Screen", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_DEVELOPER_TOOLS("BootToDebugWarpScreen")) - .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0); }) - .Options( - CheckboxOptions().Tooltip("Automatically shows Debug Warp Screen when starting or resetting the game.\n" - "This option takes precedence over \"Boot Sequence\" option.")); AddWidget(path, "OoT Registry Editor", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_DEVELOPER_TOOLS("RegEditEnabled")) .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0); }) @@ -61,19 +63,10 @@ void SohMenu::AddMenuDevTools() { .ComboMap(debugSaveFileModes)); AddWidget(path, "OoT Skulltula Debug", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_DEVELOPER_TOOLS("SkulltulaDebugEnabled")) + .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0); }) .Options(CheckboxOptions().Tooltip("Enables Skulltula Debug, when moving the cursor in the menu above various " "map icons (boss key, compass, map screen locations, etc.) will set the GS " "bits in that area.\nUSE WITH CAUTION AS IT DOES NOT UPDATE THE GS COUNT!")); - AddWidget(path, "Better Debug Warp Screen", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_DEVELOPER_TOOLS("BetterDebugWarpScreen")) - .Options(CheckboxOptions() - .Tooltip("Optimized Debug Warp Screen, with the added ability to chose entrances and time of day.") - .DefaultValue(true)); - AddWidget(path, "Debug Warp Screen Translation", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_DEVELOPER_TOOLS("DebugWarpScreenTranslation")) - .Options(CheckboxOptions() - .Tooltip("Translate the Debug Warp Screen based on the game language.") - .DefaultValue(true)); AddWidget(path, "Resource logging", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_DEVELOPER_TOOLS("ResourceLogging")) .Options(CheckboxOptions().Tooltip("Logs some resources as XML when they're loaded in binary format.")); @@ -124,6 +117,36 @@ void SohMenu::AddMenuDevTools() { }) .PreFunc([](WidgetInfo& info) { info.isHidden = mSohMenu->disabledMap.at(DISABLE_FOR_DEBUG_MODE_OFF).active; }); + path.column = SECTION_COLUMN_2; + AddWidget(path, "Warping", WIDGET_SEPARATOR_TEXT).PreFunc([](WidgetInfo& info) { + info.isHidden = !CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0); + }); + AddWidget(path, "Better Debug Warp Screen", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_DEVELOPER_TOOLS("BetterDebugWarpScreen")) + .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0); }) + .Options(CheckboxOptions() + .Tooltip("Optimized Debug Warp Screen, with the added ability to chose entrances and time of day.") + .DefaultValue(true)); + AddWidget(path, "Debug Warp Screen Translation", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_DEVELOPER_TOOLS("DebugWarpScreenTranslation")) + .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0); }) + .Options(CheckboxOptions() + .Tooltip("Translate the Debug Warp Screen based on the game language.") + .DefaultValue(true)); + AddWidget(path, "Boot To:", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_DEVELOPER_TOOLS("BootToDebugWarpScreen")) + .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0); }) + .Options(ComboboxOptions() + .DefaultIndex(0) + .ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far) + .Color(THEME_COLOR) + .ComboMap(bootToOptions) + .Tooltip("Automatically boots to Debug Warp Screen or custom Warp Point when starting or " + "resetting the game.\n" + "This option takes precedence over \"Boot Sequence\" option.")); + AddWidget(path, "Warp Points", WIDGET_CUSTOM).CustomFunction(WarpPointsWidget).HideInSearch(true); + // Stats path.sidebarName = "Stats"; AddSidebarEntry("Dev Tools", path.sidebarName, 1);