Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions soh/soh/Enhancements/randomizer/LakeHyliaWaterControl.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#include <libultraship/libultraship.h>
#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<Actor*>(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<Actor*>(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<Actor*>(actorRef);
if (actor->params != 2 /* LHO_WATER_PLANE */ || !LINK_IS_ADULT) {
return;
}
BgSpot06Objects* waterPlane = reinterpret_cast<BgSpot06Objects*>(actor);

if (sLock != nullptr) {
BgSpot06Objects* lockObj = reinterpret_cast<BgSpot06Objects*>(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<DynaPolyActor*>(sSwitchMain);
DynaPolyActor* islandSwitch = reinterpret_cast<DynaPolyActor*>(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" });
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;

Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -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;
}
}

/**
Expand Down
Loading