From 93881517a72215564835a322e1a267be2f75a323 Mon Sep 17 00:00:00 2001 From: Christopher Flanagan Date: Sun, 26 Apr 2026 12:22:35 -0400 Subject: [PATCH] Add 2nd water control switch and sync floating key --- .../randomizer/LakeHyliaWaterControl.cpp | 151 ++++++++++++++++++ .../z_bg_spot06_objects.c | 86 ---------- 2 files changed, 151 insertions(+), 86 deletions(-) create mode 100644 soh/soh/Enhancements/randomizer/LakeHyliaWaterControl.cpp diff --git a/soh/soh/Enhancements/randomizer/LakeHyliaWaterControl.cpp b/soh/soh/Enhancements/randomizer/LakeHyliaWaterControl.cpp new file mode 100644 index 00000000000..17903949ab4 --- /dev/null +++ b/soh/soh/Enhancements/randomizer/LakeHyliaWaterControl.cpp @@ -0,0 +1,151 @@ +#include +#include "soh/OTRGlobals.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" +#include "soh/Enhancements/custom-message/CustomMessageTypes.h" + +extern "C" { +extern PlayState* gPlayState; +#include "macros.h" +#include "functions.h" +#include "variables.h" +#include "src/overlays/actors/ovl_Bg_Spot06_Objects/z_bg_spot06_objects.h" + +extern s32 Object_Spawn(ObjectContext* objectCtx, s16 objectId); +extern void BgSpot06Objects_WaterPlaneCutsceneRise(BgSpot06Objects*, PlayState*); +extern void BgSpot06Objects_WaterPlaneCutsceneLower(BgSpot06Objects*, PlayState*); +extern void BgSpot06Objects_LockFloat(BgSpot06Objects*, PlayState*); +} + +#define WATER_LEVEL_RAISED (-1313) + +// Main water control switch +static Actor* sSwitchMain = nullptr; +// Alternate control switch on fishing island +static Actor* sSwitchIsland = nullptr; +static Actor* sLock = nullptr; +static u8 sPrevFlagState = 0; + +static void SpawnSwitches(PlayState* play) { + // Object containing floor switch data (and ice block data) + Object_Spawn(&play->objectCtx, OBJECT_GAMEPLAY_DANGEON_KEEP); + + bool waterTempleCleared = Flags_GetEventChkInf(EVENTCHKINF_USED_WATER_TEMPLE_BLUE_WARP); + bool waterLowered = !Flags_GetEventChkInf(EVENTCHKINF_RAISED_LAKE_HYLIA_WATER); + // Persist the water level across scene reloads. + if (waterTempleCleared && waterLowered) { + Flags_SetSwitch(play, 0x3E); + } + + s16 switchParams; + if (waterTempleCleared) { + // Toggle-able floor switch + switchParams = 0x3E10; + } else { + // Frozen rusty switch, same flag as above. It's glitched and can't be pressed + switchParams = 0x3E81; + } + + sSwitchMain = + // Spawn a floor switch + Actor_Spawn(&play->actorCtx, play, ACTOR_OBJ_SWITCH, -896.0f, -1243.0f, 6953.0f, 0, 0, 0, switchParams); + // Spawn a sign + Actor_Spawn(&play->actorCtx, play, ACTOR_EN_KANBAN, -970.0f, -1242.0f, 6954.0f, 0, 0, 0, + 0x0000 | (TEXT_LAKE_HYLIA_WATER_SWITCH_SIGN & 0xFF)); + if (!waterTempleCleared) { + // Spawn a Navi check spot when Water Temple isn't cleared + Actor_Spawn(&play->actorCtx, play, ACTOR_ELF_MSG2, -896.0f, -1243.0f, 6953.0f, 0, 0, 0, + 0x3D00 | (TEXT_LAKE_HYLIA_WATER_SWITCH_NAVI & 0xFF)); + } + + // Second switch on the fishing pond island. Up against the wall + sSwitchIsland = + // Spawn a floor switch + Actor_Spawn(&play->actorCtx, play, ACTOR_OBJ_SWITCH, 1320.0f, -1218.7f, 4025.0f, 0, 0, 0, switchParams); + // Spawn a sign + Actor_Spawn(&play->actorCtx, play, ACTOR_EN_KANBAN, 1320.0f, -1217.7f, 3951.0f, 0, -0x4000, 0, + 0x0000 | (TEXT_LAKE_HYLIA_WATER_SWITCH_SIGN & 0xFF)); + if (!waterTempleCleared) { + // Spawn a Navi check spot when Water Temple isn't cleared + Actor_Spawn(&play->actorCtx, play, ACTOR_ELF_MSG2, 1320.0f, -1218.7f, 4025.0f, 0, 0, 0, + 0x3D00 | (TEXT_LAKE_HYLIA_WATER_SWITCH_NAVI & 0xFF)); + } + + sPrevFlagState = Flags_GetSwitch(play, 0x3E) != 0; +} + +void RegisterLakeHyliaWaterControl() { + COND_HOOK(OnSceneSpawnActors, IS_RANDO, []() { + // Bail early for water control system for child, non-rando, or wrong scene + if (LINK_IS_ADULT && gPlayState->sceneNum == SCENE_LAKE_HYLIA) { + SpawnSwitches(gPlayState); + } + }); + + // Strip the ice-block bit so melting it doesn't toggle flag 0x3E. + COND_ID_HOOK(OnActorInit, ACTOR_OBJ_SWITCH, IS_RANDO, [](void* actorRef) { + Actor* actor = static_cast(actorRef); + if (actor == sSwitchMain || actor == sSwitchIsland) { + actor->params &= ~0x80; + } + }); + + // Keep track of the floating lock + COND_ID_HOOK(OnActorInit, ACTOR_BG_SPOT06_OBJECTS, IS_RANDO, [](void* actorRef) { + Actor* actor = static_cast(actorRef); + if (actor->params == 1 /* LHO_WATER_TEMPLE_ENTRANCE_LOCK */) { + sLock = actor; + } + }); + + COND_ID_HOOK(OnActorUpdate, ACTOR_BG_SPOT06_OBJECTS, IS_RANDO, [](void* actorRef) { + Actor* actor = static_cast(actorRef); + if (actor->params != 2 /* LHO_WATER_PLANE */ || !LINK_IS_ADULT) { + return; + } + BgSpot06Objects* waterPlane = reinterpret_cast(actor); + + if (sLock != nullptr) { + BgSpot06Objects* lockObj = reinterpret_cast(sLock); + if (lockObj->actionFunc == BgSpot06Objects_LockFloat) { + // If we're in LockFloat, change the Y position to track the water surface + sLock->home.pos.y = waterPlane->lakeHyliaWaterLevel + WATER_LEVEL_RAISED; + } + } + + u8 flagState = Flags_GetSwitch(gPlayState, 0x3E) != 0; + if (sPrevFlagState == flagState) { + return; + } + sPrevFlagState = flagState; + waterPlane->actionFunc = + flagState ? BgSpot06Objects_WaterPlaneCutsceneLower : BgSpot06Objects_WaterPlaneCutsceneRise; + }); + + // Synchronize pressed states of both main and island switches + COND_HOOK(OnPlayerUpdate, IS_RANDO, []() { + if (gPlayState->sceneNum != SCENE_LAKE_HYLIA) { + return; + } + if (sSwitchMain == nullptr || sSwitchIsland == nullptr) { + return; + } + DynaPolyActor* mainSwitch = reinterpret_cast(sSwitchMain); + DynaPolyActor* islandSwitch = reinterpret_cast(sSwitchIsland); + u32 merged = (mainSwitch->interactFlags | islandSwitch->interactFlags) & DYNA_INTERACT_PLAYER_ON_TOP; + if (merged == 0) { + return; + } + mainSwitch->interactFlags |= merged; + islandSwitch->interactFlags |= merged; + }); + + COND_HOOK(OnPlayDestroy, IS_RANDO, []() { + sSwitchMain = nullptr; + sSwitchIsland = nullptr; + sLock = nullptr; + sPrevFlagState = 0; + }); +} + +static RegisterShipInitFunc registerLakeHyliaWaterControl(RegisterLakeHyliaWaterControl, { "IS_RANDO" }); diff --git a/soh/src/overlays/actors/ovl_Bg_Spot06_Objects/z_bg_spot06_objects.c b/soh/src/overlays/actors/ovl_Bg_Spot06_Objects/z_bg_spot06_objects.c index 7b7030ef477..eef23e1eae2 100644 --- a/soh/src/overlays/actors/ovl_Bg_Spot06_Objects/z_bg_spot06_objects.c +++ b/soh/src/overlays/actors/ovl_Bg_Spot06_Objects/z_bg_spot06_objects.c @@ -6,7 +6,6 @@ #include "z_bg_spot06_objects.h" #include "objects/object_spot06_objects/object_spot06_objects.h" -#include "soh/Enhancements/custom-message/CustomMessageTypes.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS ACTOR_FLAG_HOOKSHOT_PULLS_ACTOR @@ -49,8 +48,6 @@ void BgSpot06Objects_WaterPlaneCutsceneWait(BgSpot06Objects* this, PlayState* pl void BgSpot06Objects_WaterPlaneCutsceneRise(BgSpot06Objects* this, PlayState* play); void BgSpot06Objects_WaterPlaneCutsceneLower(BgSpot06Objects* this, PlayState* play); -s32 Object_Spawn(ObjectContext* objectCtx, s16 objectId); - const ActorInit Bg_Spot06_Objects_InitVars = { ACTOR_BG_SPOT06_OBJECTS, ACTORCAT_PROP, @@ -194,12 +191,6 @@ void BgSpot06Objects_Init(Actor* thisx, PlayState* play) { } } -static u8 actionCounter = 0; // Used to perform some actions on subsequent frames -static s8 waterMovement = 0; // Used to control the water change direction -static u8 switchPressed = 0; // Used to track when the water fill switch is pressed/depressed -static u8 prevSwitchState = 0; // Used to track the previous state of the water fill switch -static Actor* lakeControlFloorSwitch; - void BgSpot06Objects_Destroy(Actor* thisx, PlayState* play) { BgSpot06Objects* this = (BgSpot06Objects*)thisx; @@ -217,17 +208,6 @@ void BgSpot06Objects_Destroy(Actor* thisx, PlayState* play) { // Due to Ships resource caching, the water box collisions for the river have to be manually reset play->colCtx.colHeader->waterBoxes[LHWB_GERUDO_VALLEY_RIVER_LOWER].zMin = WATER_LEVEL_RIVER_LOWER_Z; - - if (IS_RANDO && Flags_GetEventChkInf(EVENTCHKINF_USED_WATER_TEMPLE_BLUE_WARP)) { - // For randomizer when leaving lake hylia while the water level is lowered, - // reset the "raise lake hylia water" flag back to on if the water temple is cleared - Flags_SetEventChkInf(EVENTCHKINF_RAISED_LAKE_HYLIA_WATER); - } - - actionCounter = 0; - waterMovement = 0; - switchPressed = 0; - prevSwitchState = 0; } /** @@ -450,72 +430,6 @@ void BgSpot06Objects_Update(Actor* thisx, PlayState* play) { if (thisx->params == LHO_WATER_TEMPLE_ENTRANCE_LOCK) { CollisionCheck_SetOC(play, &play->colChkCtx, &this->collider.base); } - - // Bail early for water control system for child or non-rando - if (LINK_IS_CHILD || !IS_RANDO) { - return; - } - - // Begin setup for Lake Hylia water control system - if (actionCounter == 0) { - // Object containing floor switch data (and ice block data) - Object_Spawn(&play->objectCtx, OBJECT_GAMEPLAY_DANGEON_KEEP); - - s16 switchParams; - if (Flags_GetEventChkInf(EVENTCHKINF_USED_WATER_TEMPLE_BLUE_WARP)) { - // Toggle-able floor switch, - // linked to temp_switch 0x1E (room temporary, cleared when room unloads) - switchParams = 0x3E10; - } else { - // Frozen rusty switch, same flag as above. It's glitched and can't be pressed - switchParams = 0x3E81; - } - - // Spawn a floor switch - lakeControlFloorSwitch = - Actor_Spawn(&play->actorCtx, play, ACTOR_OBJ_SWITCH, -896.0f, -1243.0f, 6953.0f, 0, 0, 0, switchParams); - // Spawn a sign - Actor_Spawn(&play->actorCtx, play, ACTOR_EN_KANBAN, -970.0f, -1242.0f, 6954.0f, 0, 0, 0, - 0x0000 | (TEXT_LAKE_HYLIA_WATER_SWITCH_SIGN & 0xFF)); - - // Spawn a Navi check spot when Water Temple isn't cleared - if (!Flags_GetEventChkInf(EVENTCHKINF_USED_WATER_TEMPLE_BLUE_WARP)) { - Actor_Spawn(&play->actorCtx, play, ACTOR_ELF_MSG2, -896.0f, -1243.0f, 6953.0f, 0, 0, 0, - 0x3D00 | (TEXT_LAKE_HYLIA_WATER_SWITCH_NAVI & 0xFF)); - } - - actionCounter++; - return; - } else if (actionCounter == 1) { - if (!Flags_GetEventChkInf(EVENTCHKINF_USED_WATER_TEMPLE_BLUE_WARP)) { - // Remove the link to ice block so melting it doesn't set the flag - lakeControlFloorSwitch->params = 0x3E01; - } - - actionCounter++; - return; - } - - // Detect when the switch is pressed - if (prevSwitchState != (Flags_GetSwitch(play, 0x3E) != 0)) { - prevSwitchState = !prevSwitchState; - switchPressed = 1; - } - - // When pressed, assign the corresponding action func to the water plane and water movement direction - if (switchPressed == 1 && thisx->params == LHO_WATER_PLANE) { - // Lower water - if (waterMovement >= 0) { - waterMovement = -1; - this->actionFunc = BgSpot06Objects_WaterPlaneCutsceneLower; - // Raise water - } else { - waterMovement = 1; - this->actionFunc = BgSpot06Objects_WaterPlaneCutsceneRise; - } - - switchPressed = 0; - } } /**