diff --git a/soh/include/attributes.h b/soh/include/attributes.h new file mode 100644 index 000000000..f58cc8122 --- /dev/null +++ b/soh/include/attributes.h @@ -0,0 +1,12 @@ +#ifndef ATTRIBUTES_H +#define ATTRIBUTES_H + +#if !defined(__GNUC__) && !defined(__attribute__) +#define __attribute__(x) +#endif + +#define UNUSED __attribute__((unused)) +#define FALLTHROUGH __attribute__((fallthrough)) +#define NORETURN __attribute__((noreturn)) + +#endif diff --git a/soh/include/functions.h b/soh/include/functions.h index 41b325236..8a1f6572c 100644 --- a/soh/include/functions.h +++ b/soh/include/functions.h @@ -1536,9 +1536,9 @@ void KaleidoScopeCall_Init(PlayState* play); void KaleidoScopeCall_Destroy(PlayState* play); void KaleidoScopeCall_Update(PlayState* play); void KaleidoScopeCall_Draw(PlayState* play); -void func_800BC490(PlayState* play, s16 point); -s32 func_800BC56C(PlayState* play, s16 arg1); -void func_800BC590(PlayState* play); +void Play_SetViewpoint(PlayState* play, s16 viewpoint); +s32 Play_CheckViewpoint(PlayState* play, s16 viewpoint); +void Play_SetShopBrowsingViewpoint(PlayState* play); void Gameplay_SetupTransition(PlayState* play, s32 arg1); Gfx* Play_SetFog(PlayState* play, Gfx* gfx); void Play_Destroy(GameState* thisx); @@ -1552,7 +1552,7 @@ u8 CheckLACSRewardCount(); s32 Play_InCsMode(PlayState* play); f32 func_800BFCB8(PlayState* play, MtxF* mf, Vec3f* vec); void* Play_LoadFile(PlayState* play, RomFile* file); -void Play_SpawnScene(PlayState* play, s32 sceneNum, s32 spawn); +void Play_SpawnScene(PlayState* play, s32 sceneId, s32 spawn); void func_800C016C(PlayState* play, Vec3f* src, Vec3f* dest); s16 Play_CreateSubCamera(PlayState* play); s16 Play_GetActiveCamId(PlayState* play); diff --git a/soh/include/variables.h b/soh/include/variables.h index 5c97b26b8..25fb304d3 100644 --- a/soh/include/variables.h +++ b/soh/include/variables.h @@ -120,7 +120,7 @@ extern "C" extern KaleidoMgrOverlay gKaleidoMgrOverlayTable[KALEIDO_OVL_MAX]; extern KaleidoMgrOverlay* gKaleidoMgrCurOvl; extern u8 gBossMarkState; - extern void* D_8012D1F0; + extern void* gDebugCutsceneScript; extern s32 gScreenWidth; extern s32 gScreenHeight; extern Mtx gMtxClear; diff --git a/soh/include/z64.h b/soh/include/z64.h index f4cec6e82..00a67c844 100644 --- a/soh/include/z64.h +++ b/soh/include/z64.h @@ -3,6 +3,7 @@ #include #include "unk.h" // this used to get pulled in via ultra64.h +#include "attributes.h" #include "z64save.h" #include "z64light.h" #include "z64bgcheck.h" diff --git a/soh/include/z64save.h b/soh/include/z64save.h index 0233819ce..bf0d20697 100644 --- a/soh/include/z64save.h +++ b/soh/include/z64save.h @@ -361,7 +361,7 @@ typedef enum { /* 4 */ SCENE_LAYER_CUTSCENE_FIRST } SceneLayer; -#define IS_CUTSCENE_LAYER (gSaveContext.sceneLayer >= SCENE_LAYER_CUTSCENE_FIRST) +#define IS_CUTSCENE_LAYER (gSaveContext.sceneSetupIndex >= SCENE_LAYER_CUTSCENE_FIRST) typedef enum { /* 0 */ LINK_AGE_ADULT, diff --git a/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipBlueWarp.cpp b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipBlueWarp.cpp index e94556aac..0bb65a8de 100644 --- a/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipBlueWarp.cpp +++ b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipBlueWarp.cpp @@ -3,35 +3,43 @@ #include "soh/OTRGlobals.h" extern "C" { - #include "macros.h" - #include "src/overlays/actors/ovl_En_Ko/z_en_ko.h" - #include "z64save.h" - #include "functions.h" - #include "variables.h" +#include "macros.h" +#include "src/overlays/actors/ovl_En_Ko/z_en_ko.h" +#include "z64save.h" +#include "functions.h" +#include "variables.h" } #define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).GetSelectedOptionIndex() +static bool sEnteredBlueWarp = false; + /** * This will override the transitions into the blue warp cutscenes, set any appropriate flags, and * set the entrance index to where you would normally end up after the blue warp cutscene. This * should also account for the difference between your first and following visits to the blue warp. */ void SkipBlueWarp_ShouldPlayTransitionCS(GIVanillaBehavior _, bool* should, va_list originalArgs) { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { - uint8_t isBlueWarpCutscene = 0; + bool overrideBlueWarpDestinations = + IS_RANDO && (RAND_GET_OPTION(RSK_SHUFFLE_DUNGEON_ENTRANCES) != RO_DUNGEON_ENTRANCE_SHUFFLE_OFF || + RAND_GET_OPTION(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF); + + // Force blue warp skip on when ER needs to place Link somewhere else. + // This is preferred over having story cutscenes play in the overworld and then reloading Link somewhere else after. + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO) || overrideBlueWarpDestinations) { + bool isBlueWarpCutscene = false; // Deku Tree Blue warp if (gSaveContext.entranceIndex == ENTR_KOKIRI_FOREST_0 && gSaveContext.cutsceneIndex == 0xFFF1) { gSaveContext.entranceIndex = ENTR_KOKIRI_FOREST_DEKU_TREE_BLUE_WARP; - isBlueWarpCutscene = 1; + isBlueWarpCutscene = true; // Dodongo's Cavern Blue warp } else if (gSaveContext.entranceIndex == ENTR_DEATH_MOUNTAIN_TRAIL_BOTTOM_EXIT && gSaveContext.cutsceneIndex == 0xFFF1) { gSaveContext.entranceIndex = ENTR_DEATH_MOUNTAIN_TRAIL_DODONGO_BLUE_WARP; - isBlueWarpCutscene = 1; + isBlueWarpCutscene = true; // Jabu Jabu's Blue warp } else if (gSaveContext.entranceIndex == ENTR_ZORAS_FOUNTAIN_JABU_JABU_BLUE_WARP && gSaveContext.cutsceneIndex == 0xFFF0) { gSaveContext.entranceIndex = ENTR_ZORAS_FOUNTAIN_JABU_JABU_BLUE_WARP; - isBlueWarpCutscene = 1; + isBlueWarpCutscene = true; // Forest Temple Blue warp } else if (gSaveContext.entranceIndex == ENTR_CHAMBER_OF_THE_SAGES_0 && gSaveContext.cutsceneIndex == 0x0 && gSaveContext.chamberCutsceneNum == CHAMBER_CS_FOREST) { // Normally set in the blue warp cutscene @@ -43,14 +51,14 @@ void SkipBlueWarp_ShouldPlayTransitionCS(GIVanillaBehavior _, bool* should, va_l gSaveContext.entranceIndex = ENTR_KOKIRI_FOREST_12; } - isBlueWarpCutscene = 1; + isBlueWarpCutscene = true; // Fire Temple Blue warp } else if (gSaveContext.entranceIndex == ENTR_KAKARIKO_VILLAGE_FRONT_GATE && gSaveContext.cutsceneIndex == 0xFFF3) { // Normally set in the blue warp cutscene Flags_SetEventChkInf(EVENTCHKINF_DEATH_MOUNTAIN_ERUPTED); gSaveContext.entranceIndex = ENTR_DEATH_MOUNTAIN_CRATER_FIRE_TEMPLE_BLUE_WARP; - isBlueWarpCutscene = 1; + isBlueWarpCutscene = true; // Water Temple Blue warp } else if (gSaveContext.entranceIndex == ENTR_CHAMBER_OF_THE_SAGES_0 && gSaveContext.cutsceneIndex == 0x0 && gSaveContext.chamberCutsceneNum == CHAMBER_CS_WATER) { // Normally set in the blue warp cutscene @@ -58,15 +66,15 @@ void SkipBlueWarp_ShouldPlayTransitionCS(GIVanillaBehavior _, bool* should, va_l Flags_SetEventChkInf(EVENTCHKINF_RAISED_LAKE_HYLIA_WATER); gSaveContext.entranceIndex = ENTR_LAKE_HYLIA_WATER_TEMPLE_BLUE_WARP; - isBlueWarpCutscene = 1; + isBlueWarpCutscene = true; // Spirit Temple Blue warp } else if (gSaveContext.entranceIndex == ENTR_CHAMBER_OF_THE_SAGES_0 && gSaveContext.cutsceneIndex == 0x0 && gSaveContext.chamberCutsceneNum == CHAMBER_CS_SPIRIT) { gSaveContext.entranceIndex = ENTR_DESERT_COLOSSUS_SPIRIT_TEMPLE_BLUE_WARP; - isBlueWarpCutscene = 1; + isBlueWarpCutscene = true; // Shadow Temple Blue warp } else if (gSaveContext.entranceIndex == ENTR_CHAMBER_OF_THE_SAGES_0 && gSaveContext.cutsceneIndex == 0x0 && gSaveContext.chamberCutsceneNum == CHAMBER_CS_SHADOW) { gSaveContext.entranceIndex = ENTR_GRAVEYARD_SHADOW_TEMPLE_BLUE_WARP; - isBlueWarpCutscene = 1; + isBlueWarpCutscene = true; } if (isBlueWarpCutscene) { @@ -80,10 +88,20 @@ void SkipBlueWarp_ShouldPlayTransitionCS(GIVanillaBehavior _, bool* should, va_l } // This is outside the above condition because we want to handle both first and following visits to the blue warp - if (IS_RANDO && (RAND_GET_OPTION(RSK_SHUFFLE_DUNGEON_ENTRANCES) != RO_DUNGEON_ENTRANCE_SHUFFLE_OFF || RAND_GET_OPTION(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF)) { + if (sEnteredBlueWarp && overrideBlueWarpDestinations) { Entrance_OverrideBlueWarp(); } } + + sEnteredBlueWarp = false; +} + +/** + * Using this hook to simply observe that Link has entered a bluewarp + * This way we know to allow entrance rando overrides to be processed on the next tranisition hook + */ +void SkipBlueWarp_ShouldPlayBlueWarpCS(GIVanillaBehavior _, bool* should, va_list originalArgs) { + sEnteredBlueWarp = true; } /** @@ -143,6 +161,7 @@ void SkipBlueWarp_ShouldDekuJrConsiderForestTempleFinished(GIVanillaBehavior _, void SkipBlueWarp_Register() { GameInteractor::Instance->RegisterGameHookForID(ACTOR_EN_KO, SkipBlueWarp_OnActorUpdate); GameInteractor::Instance->RegisterGameHookForID(VB_PLAY_TRANSITION_CS, SkipBlueWarp_ShouldPlayTransitionCS); + GameInteractor::Instance->RegisterGameHookForID(VB_PLAY_BLUE_WARP_CS, SkipBlueWarp_ShouldPlayBlueWarpCS); GameInteractor::Instance->RegisterGameHookForID(VB_DEKU_JR_CONSIDER_FOREST_TEMPLE_FINISHED, SkipBlueWarp_ShouldDekuJrConsiderForestTempleFinished); GameInteractor::Instance->RegisterGameHookForID(VB_GIVE_ITEM_FROM_BLUE_WARP, SkipBlueWarp_ShouldGiveItem); } diff --git a/soh/soh/Enhancements/debugger/colViewer.cpp b/soh/soh/Enhancements/debugger/colViewer.cpp index 8ce5f8b50..5139576ce 100644 --- a/soh/soh/Enhancements/debugger/colViewer.cpp +++ b/soh/soh/Enhancements/debugger/colViewer.cpp @@ -8,6 +8,7 @@ #include #include #include "soh/OTRGlobals.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" extern "C" { #include @@ -276,11 +277,6 @@ void CreateSphereData() { sphereGfx.push_back(gsSPEndDisplayList()); } -void ColViewerWindow::InitElement() { - CreateCylinderData(); - CreateSphereData(); -} - // Initializes the display list for a ColRenderSetting void InitGfx(std::vector& gfx, ColRenderSetting setting) { uint32_t rm; @@ -689,3 +685,10 @@ extern "C" void DrawColViewer() { CLOSE_DISPS(gPlayState->state.gfxCtx); } + +void ColViewerWindow::InitElement() { + CreateCylinderData(); + CreateSphereData(); + + GameInteractor::Instance->RegisterGameHook(DrawColViewer); +} diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor.h b/soh/soh/Enhancements/game-interactor/GameInteractor.h index 15a74b181..61cfbe72e 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.h @@ -342,6 +342,7 @@ typedef enum { VB_SHOULD_GIVE_VANILLA_FISHING_PRIZE, VB_GIVE_RANDO_FISHING_PRIZE, VB_PLAY_THROW_ANIMATION, + VB_INFLICT_VOID_DAMAGE, /*** Give Items ***/ diff --git a/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp index 9a0e80a51..9ecc0e163 100644 --- a/soh/soh/Enhancements/mods.cpp +++ b/soh/soh/Enhancements/mods.cpp @@ -64,15 +64,6 @@ static const ALIGN_ASSET(2) char tokinoma_room_0DL_007A70[] = dtokinoma_room_0DL #define dtokinoma_room_0DL_007FD0 "__OTR__scenes/shared/tokinoma_scene/tokinoma_room_0DL_007FD0" static const ALIGN_ASSET(2) char tokinoma_room_0DL_007FD0[] = dtokinoma_room_0DL_007FD0; -// TODO: When there's more uses of something like this, create a new GI::RawAction? -void ReloadSceneTogglingLinkAge() { - gPlayState->nextEntranceIndex = gSaveContext.entranceIndex; - gPlayState->transitionTrigger = TRANS_TRIGGER_START; - gPlayState->transitionType = TRANS_TYPE_CIRCLE(TCA_WAVE, TCC_WHITE, TCS_FAST); // Fade Out - gSaveContext.nextTransitionType = TRANS_TYPE_CIRCLE(TCA_WAVE, TCC_WHITE, TCS_FAST); - gPlayState->linkAgeOnLoad ^= 1; // toggle linkAgeOnLoad -} - void RegisterInfiniteMoney() { GameInteractor::Instance->RegisterGameHook([]() { if (!GameInteractor::IsSaveLoaded(true)) return; @@ -219,42 +210,43 @@ void RegisterFreezeTime() { } /// Switches Link's age and respawns him at the last entrance he entered. -void RegisterSwitchAge() { - GameInteractor::Instance->RegisterGameHook([]() { - static bool warped = false; +void SwitchAge() { + if (gPlayState == NULL) return; - if (!GameInteractor::IsSaveLoaded(true)) { - CVarClear(CVAR_GENERAL("SwitchAge")); - warped = false; - return; + Player* player = GET_PLAYER(gPlayState); + + // Hyrule Castle: Very likely to fall through floor, so we force a specific entrance + if (gPlayState->sceneNum == SCENE_HYRULE_CASTLE || gPlayState->sceneNum == SCENE_OUTSIDE_GANONS_CASTLE) { + gPlayState->nextEntranceIndex = ENTR_CASTLE_GROUNDS_SOUTH_EXIT; + } else { + gSaveContext.respawnFlag = 1; + gPlayState->nextEntranceIndex = gSaveContext.entranceIndex; + + // Preserve the player's position and orientation + gSaveContext.respawn[RESPAWN_MODE_DOWN].entranceIndex = gPlayState->nextEntranceIndex; + gSaveContext.respawn[RESPAWN_MODE_DOWN].roomIndex = gPlayState->roomCtx.curRoom.num; + gSaveContext.respawn[RESPAWN_MODE_DOWN].pos = player->actor.world.pos; + gSaveContext.respawn[RESPAWN_MODE_DOWN].yaw = player->actor.shape.rot.y; + + if (gPlayState->roomCtx.curRoom.behaviorType2 < 4) { + gSaveContext.respawn[RESPAWN_MODE_DOWN].playerParams = 0x0DFF; + } else { + // Scenes with static backgrounds use a special camera we need to preserve + Camera* camera = GET_ACTIVE_CAM(gPlayState); + s16 camId = camera->camDataIdx; + gSaveContext.respawn[RESPAWN_MODE_DOWN].playerParams = 0x0D00 | camId; } + } - static Vec3f playerPos; - static int16_t playerYaw; - static RoomContext* roomCtx; - static s32 roomNum; + gPlayState->transitionTrigger = TRANS_TRIGGER_START; + gPlayState->transitionType = TRANS_TYPE_INSTANT; + gSaveContext.nextTransitionType = TRANS_TYPE_FADE_BLACK_FAST; + gPlayState->linkAgeOnLoad ^= 1; - if (CVarGetInteger(CVAR_GENERAL("SwitchAge"), 0) && !warped) { - playerPos = GET_PLAYER(gPlayState)->actor.world.pos; - playerYaw = GET_PLAYER(gPlayState)->actor.shape.rot.y; - roomCtx = &gPlayState->roomCtx; - roomNum = roomCtx->curRoom.num; - ReloadSceneTogglingLinkAge(); - warped = true; - } - - if (warped && gPlayState->transitionTrigger != TRANS_TRIGGER_START && - gSaveContext.nextTransitionType == TRANS_NEXT_TYPE_DEFAULT) { - GET_PLAYER(gPlayState)->actor.shape.rot.y = playerYaw; - GET_PLAYER(gPlayState)->actor.world.pos = playerPos; - if (roomNum != roomCtx->curRoom.num) { - func_8009728C(gPlayState, roomCtx, roomNum); //load original room - //func_800973FC(gPlayState, &gPlayState->roomCtx); // commit to room load? - func_80097534(gPlayState, roomCtx); // load map for new room (unloading the previous room) - } - warped = false; - CVarClear(CVAR_GENERAL("SwitchAge")); - } + static HOOK_ID hookId = 0; + hookId = REGISTER_VB_SHOULD(VB_INFLICT_VOID_DAMAGE, { + *should = false; + GameInteractor::Instance->UnregisterGameHookForID(hookId); }); } @@ -262,8 +254,7 @@ void RegisterSwitchAge() { void RegisterOcarinaTimeTravel() { GameInteractor::Instance->RegisterGameHook([]() { - if (!GameInteractor::IsSaveLoaded(true)) { - CVarClear(CVAR_ENHANCEMENT("TimeTravel")); + if (!GameInteractor::IsSaveLoaded(true) || !CVarGetInteger(CVAR_ENHANCEMENT("TimeTravel"), 0)) { return; } @@ -273,22 +264,14 @@ void RegisterOcarinaTimeTravel() { Actor* nearbyOcarinaSpot = Actor_FindNearby(gPlayState, player, ACTOR_EN_OKARINA_TAG, ACTORCAT_PROP, 120.0f); Actor* nearbyDoorOfTime = Actor_FindNearby(gPlayState, player, ACTOR_DOOR_TOKI, ACTORCAT_BG, 500.0f); Actor* nearbyFrogs = Actor_FindNearby(gPlayState, player, ACTOR_EN_FR, ACTORCAT_NPC, 300.0f); - uint8_t hasMasterSword = CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_MASTER); - uint8_t hasOcarinaOfTime = (INV_CONTENT(ITEM_OCARINA_TIME) == ITEM_OCARINA_TIME); - // If TimeTravel + Player have the Ocarina of Time + Have Master Sword + is in proper range + bool justPlayedSoT = gPlayState->msgCtx.lastPlayedSong == OCARINA_SONG_TIME; + bool notNearAnySource = !nearbyTimeBlockEmpty && !nearbyTimeBlock && !nearbyOcarinaSpot && !nearbyDoorOfTime && !nearbyFrogs; + bool hasOcarinaOfTime = (INV_CONTENT(ITEM_OCARINA_TIME) == ITEM_OCARINA_TIME); + bool doesntNeedOcarinaOfTime = CVarGetInteger(CVAR_ENHANCEMENT("TimeTravel"), 0) == 2; + bool hasMasterSword = CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_MASTER); // TODO: Once Swordless Adult is fixed: Remove the Master Sword check - if (((CVarGetInteger(CVAR_ENHANCEMENT("TimeTravel"), 0) == 1 && hasOcarinaOfTime) || CVarGetInteger(CVAR_ENHANCEMENT("TimeTravel"), 0) == 2) && hasMasterSword && - gPlayState->msgCtx.lastPlayedSong == OCARINA_SONG_TIME && !nearbyTimeBlockEmpty && !nearbyTimeBlock && - !nearbyOcarinaSpot && !nearbyFrogs) { - - if (IS_RANDO) { - CVarSetInteger(CVAR_GENERAL("SwitchTimeline"), 1); - } else if (!IS_RANDO && !nearbyDoorOfTime) { - // This check is made for when Link is learning the Song Of Time in a vanilla save file that load a - // Temple of Time scene where the only object present is the Door of Time - CVarSetInteger(CVAR_GENERAL("SwitchTimeline"), 1); - } - ReloadSceneTogglingLinkAge(); + if (justPlayedSoT && notNearAnySource && (hasOcarinaOfTime || doesntNeedOcarinaOfTime) && hasMasterSword) { + SwitchAge(); } }); } @@ -1429,7 +1412,6 @@ void InitMods() { RegisterEzQPA(); RegisterUnrestrictedItems(); RegisterFreezeTime(); - RegisterSwitchAge(); RegisterOcarinaTimeTravel(); RegisterAutoSave(); RegisterDaytimeGoldSkultullas(); diff --git a/soh/soh/Enhancements/mods.h b/soh/soh/Enhancements/mods.h index b70cda286..930522250 100644 --- a/soh/soh/Enhancements/mods.h +++ b/soh/soh/Enhancements/mods.h @@ -17,6 +17,7 @@ void UpdateHyperEnemiesState(); void UpdateHyperBossesState(); void InitMods(); void UpdatePatchHand(); +void SwitchAge(); #ifdef __cplusplus } diff --git a/soh/soh/Enhancements/nametag.cpp b/soh/soh/Enhancements/nametag.cpp index 8435b6b26..fa88c9aeb 100644 --- a/soh/soh/Enhancements/nametag.cpp +++ b/soh/soh/Enhancements/nametag.cpp @@ -201,7 +201,7 @@ extern "C" void NameTag_RegisterForActorWithOptions(Actor* actor, const char* te processedText.erase(std::remove_if(processedText.begin(), processedText.end(), [](const char& c) { // 172 is max supported texture for the in-game font system, // and filter anything less than a space but not the newline or nul characters - return c > (s8)172 || (c < ' ' && c != '\n' && c != '\0'); + return (unsigned char)c > 172 || (c < ' ' && c != '\n' && c != '\0'); }), processedText.end()); int16_t numChar = processedText.length(); diff --git a/soh/soh/Enhancements/presets.h b/soh/soh/Enhancements/presets.h index 232ed83a9..1c2966b69 100644 --- a/soh/soh/Enhancements/presets.h +++ b/soh/soh/Enhancements/presets.h @@ -354,8 +354,6 @@ const std::vector cheatCvars = { CVAR_DEVELOPER_TOOLS("SaveFileID"), CVAR_CHEAT("EnableBetaQuest"), CVAR_DEVELOPER_TOOLS("BetterDebugWarpScreen"), - CVAR_GENERAL("SwitchAge"), - CVAR_GENERAL("SwitchTimeline"), CVAR_CHEAT("NoRedeadFreeze"), CVAR_CHEAT("NoKeeseGuayTarget"), CVAR_CHEAT("BombTimerMultiplier"), diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_water_temple.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_water_temple.cpp index a02e51e83..3a2559474 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_water_temple.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_water_temple.cpp @@ -10,9 +10,9 @@ void RegionTable_Init_WaterTemple() { ---------------------------*/ areaTable[RR_WATER_TEMPLE_ENTRYWAY] = Region("Water Temple Entryway", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { //Exits - Entrance(RR_WATER_TEMPLE_LOBBY, {[]{return logic->HasItem(RG_BRONZE_SCALE) && ctx->GetDungeon(WATER_TEMPLE)->IsVanilla();}}), - Entrance(RR_WATER_TEMPLE_MQ_LOBBY, {[]{return logic->HasItem(RG_BRONZE_SCALE) && ctx->GetDungeon(WATER_TEMPLE)->IsMQ();}}), - Entrance(RR_LAKE_HYLIA, {[]{return true;}}), + Entrance(RR_WATER_TEMPLE_LOBBY, {[]{return logic->HasItem(RG_BRONZE_SCALE) && ctx->GetDungeon(WATER_TEMPLE)->IsVanilla();}}), + Entrance(RR_WATER_TEMPLE_MQ_3F_CENTRAL, {[]{return logic->HasItem(RG_BRONZE_SCALE) && ctx->GetDungeon(WATER_TEMPLE)->IsMQ();}}), + Entrance(RR_LAKE_HYLIA, {[]{return true;}}), }); /*-------------------------- @@ -23,29 +23,29 @@ void RegionTable_Init_WaterTemple() { areaTable[RR_WATER_TEMPLE_LOBBY] = Region("Water Temple Lobby", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { //Exits Entrance(RR_WATER_TEMPLE_ENTRYWAY, {[]{return true;}}), - Entrance(RR_WATER_TEMPLE_EAST_LOWER, {[]{return logic->WaterTempleLow || ((ctx->GetTrickOption(RT_FEWER_TUNIC_REQUIREMENTS) || logic->CanUse(RG_ZORA_TUNIC)) && (logic->CanUse(RG_IRON_BOOTS) || (logic->CanUse(RG_LONGSHOT) && ctx->GetTrickOption(RT_WATER_LONGSHOT_TORCH))));}}), - Entrance(RR_WATER_TEMPLE_NORTH_LOWER, {[]{return logic->WaterTempleLow || ((ctx->GetTrickOption(RT_FEWER_TUNIC_REQUIREMENTS) || logic->CanUse(RG_ZORA_TUNIC)) && logic->CanUse(RG_IRON_BOOTS));}}), - Entrance(RR_WATER_TEMPLE_SOUTH_LOWER, {[]{return logic->WaterTempleLow && logic->HasExplosives() && (logic->HasItem(RG_SILVER_SCALE) || logic->CanUse(RG_IRON_BOOTS)) && (ctx->GetTrickOption(RT_FEWER_TUNIC_REQUIREMENTS) || logic->CanUse(RG_ZORA_TUNIC));}}), - Entrance(RR_WATER_TEMPLE_WEST_LOWER, {[]{return logic->WaterTempleLow && logic->HasItem(RG_GORONS_BRACELET) && (logic->IsChild || logic->HasItem(RG_SILVER_SCALE) || logic->CanUse(RG_IRON_BOOTS)) && (ctx->GetTrickOption(RT_FEWER_TUNIC_REQUIREMENTS) || logic->CanUse(RG_ZORA_TUNIC));}}), - Entrance(RR_WATER_TEMPLE_CENTRAL_PILLAR_LOWER, {[]{return logic->WaterTempleLow && logic->SmallKeys(RR_WATER_TEMPLE, 5);}}), - Entrance(RR_WATER_TEMPLE_CENTRAL_PILLAR_UPPER, {[]{return (logic->WaterTempleLow || logic->WaterTempleMiddle) && (logic->HasFireSourceWithTorch() || logic->CanUse(RG_FAIRY_BOW));}}), - Entrance(RR_WATER_TEMPLE_EAST_MIDDLE, {[]{return (logic->WaterTempleLow || logic->WaterTempleMiddle || (logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 16)) && logic->CanUse(RG_HOOKSHOT);}}), - Entrance(RR_WATER_TEMPLE_WEST_MIDDLE, {[]{return logic->WaterTempleMiddle;}}), + Entrance(RR_WATER_TEMPLE_EAST_LOWER, {[]{return logic->CanWaterTempleLowFromHigh || ((ctx->GetTrickOption(RT_FEWER_TUNIC_REQUIREMENTS) || logic->CanUse(RG_ZORA_TUNIC)) && (logic->CanUse(RG_IRON_BOOTS) || (logic->CanUse(RG_LONGSHOT) && ctx->GetTrickOption(RT_WATER_LONGSHOT_TORCH))));}}), + Entrance(RR_WATER_TEMPLE_NORTH_LOWER, {[]{return logic->CanWaterTempleLowFromHigh || ((ctx->GetTrickOption(RT_FEWER_TUNIC_REQUIREMENTS) || logic->CanUse(RG_ZORA_TUNIC)) && logic->CanUse(RG_IRON_BOOTS));}}), + Entrance(RR_WATER_TEMPLE_SOUTH_LOWER, {[]{return logic->CanWaterTempleLowFromHigh && logic->HasExplosives() && (logic->HasItem(RG_SILVER_SCALE) || logic->CanUse(RG_IRON_BOOTS)) && (ctx->GetTrickOption(RT_FEWER_TUNIC_REQUIREMENTS) || logic->CanUse(RG_ZORA_TUNIC));}}), + Entrance(RR_WATER_TEMPLE_WEST_LOWER, {[]{return logic->CanWaterTempleLowFromHigh && logic->HasItem(RG_GORONS_BRACELET) && (logic->IsChild || logic->HasItem(RG_SILVER_SCALE) || logic->CanUse(RG_IRON_BOOTS)) && (ctx->GetTrickOption(RT_FEWER_TUNIC_REQUIREMENTS) || logic->CanUse(RG_ZORA_TUNIC));}}), + Entrance(RR_WATER_TEMPLE_CENTRAL_PILLAR_LOWER, {[]{return logic->CanWaterTempleLowFromHigh && logic->SmallKeys(RR_WATER_TEMPLE, 5);}}), + Entrance(RR_WATER_TEMPLE_CENTRAL_PILLAR_UPPER, {[]{return (logic->CanWaterTempleLowFromHigh || logic->CanWaterTempleMiddle) && (logic->HasFireSourceWithTorch() || logic->CanUse(RG_FAIRY_BOW));}}), + Entrance(RR_WATER_TEMPLE_EAST_MIDDLE, {[]{return (logic->CanWaterTempleLowFromHigh || logic->CanWaterTempleMiddle || (logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 16)) && logic->CanUse(RG_HOOKSHOT);}}), + Entrance(RR_WATER_TEMPLE_WEST_MIDDLE, {[]{return logic->CanWaterTempleMiddle;}}), Entrance(RR_WATER_TEMPLE_HIGH_WATER, {[]{return logic->IsAdult && (logic->CanUse(RG_HOVER_BOOTS) || (ctx->GetTrickOption(RT_DAMAGE_BOOST) && logic->CanUse(RG_BOMB_BAG) && logic->TakeDamage()));}}), - Entrance(RR_WATER_TEMPLE_BLOCK_CORRIDOR, {[]{return (logic->WaterTempleLow || logic->WaterTempleMiddle) && (logic->CanUse(RG_FAIRY_SLINGSHOT) || logic->CanUse(RG_FAIRY_BOW)) && (logic->CanUse(RG_LONGSHOT) || logic->CanUse(RG_HOVER_BOOTS) || (ctx->GetTrickOption(RT_WATER_CENTRAL_BOW) && (logic->IsAdult || logic->WaterTempleMiddle)));}}), - Entrance(RR_WATER_TEMPLE_FALLING_PLATFORM_ROOM, {[]{return logic->WaterTempleHigh && logic->SmallKeys(RR_WATER_TEMPLE, 4);}}), - Entrance(RR_WATER_TEMPLE_PRE_BOSS_ROOM, {[]{return logic->WaterTempleHigh && logic->CanUse(RG_LONGSHOT);}}), + Entrance(RR_WATER_TEMPLE_BLOCK_CORRIDOR, {[]{return (logic->CanWaterTempleLowFromHigh || logic->CanWaterTempleMiddle) && (logic->CanUse(RG_FAIRY_SLINGSHOT) || logic->CanUse(RG_FAIRY_BOW)) && (logic->CanUse(RG_LONGSHOT) || logic->CanUse(RG_HOVER_BOOTS) || (ctx->GetTrickOption(RT_WATER_CENTRAL_BOW) && (logic->IsAdult || logic->CanWaterTempleMiddle)));}}), + Entrance(RR_WATER_TEMPLE_FALLING_PLATFORM_ROOM, {[]{return logic->CanWaterTempleHigh && logic->SmallKeys(RR_WATER_TEMPLE, 4);}}), + Entrance(RR_WATER_TEMPLE_PRE_BOSS_ROOM, {[]{return logic->CanWaterTempleHigh && logic->CanUse(RG_LONGSHOT);}}), }); areaTable[RR_WATER_TEMPLE_EAST_LOWER] = Region("Water Temple East Lower", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, { //Events - EventAccess(&logic->WaterTempleLow, {[]{return logic->WaterTempleLow || logic->CanUse(RG_ZELDAS_LULLABY);}}), + EventAccess(&logic->CanWaterTempleLowFromHigh, {[]{return logic->CanWaterTempleLowFromHigh || logic->CanUse(RG_ZELDAS_LULLABY);}}), }, {}, { //Exits - Entrance(RR_WATER_TEMPLE_LOBBY, {[]{return logic->WaterTempleLow || ((ctx->GetTrickOption(RT_FEWER_TUNIC_REQUIREMENTS) || logic->CanUse(RG_ZORA_TUNIC)) && logic->CanUse(RG_IRON_BOOTS));}}), - Entrance(RR_WATER_TEMPLE_MAP_ROOM, {[]{return logic->WaterTempleHigh;}}), - Entrance(RR_WATER_TEMPLE_CRACKED_WALL, {[]{return logic->WaterTempleMiddle || (logic->WaterTempleHigh && logic->WaterTempleLow && ((logic->CanUse(RG_HOVER_BOOTS) && ctx->GetTrickOption(RT_WATER_CRACKED_WALL_HOVERS)) || ctx->GetTrickOption(RT_WATER_CRACKED_WALL)));}}), - Entrance(RR_WATER_TEMPLE_TORCH_ROOM, {[]{return logic->WaterTempleLow && (logic->HasFireSourceWithTorch() || logic->CanUse(RG_FAIRY_BOW));}}), + Entrance(RR_WATER_TEMPLE_LOBBY, {[]{return logic->CanWaterTempleLowFromHigh || ((ctx->GetTrickOption(RT_FEWER_TUNIC_REQUIREMENTS) || logic->CanUse(RG_ZORA_TUNIC)) && logic->CanUse(RG_IRON_BOOTS));}}), + Entrance(RR_WATER_TEMPLE_MAP_ROOM, {[]{return logic->CanWaterTempleHigh;}}), + Entrance(RR_WATER_TEMPLE_CRACKED_WALL, {[]{return logic->CanWaterTempleMiddle || (logic->CanWaterTempleHigh && logic->CanWaterTempleLowFromHigh && ((logic->CanUse(RG_HOVER_BOOTS) && ctx->GetTrickOption(RT_WATER_CRACKED_WALL_HOVERS)) || ctx->GetTrickOption(RT_WATER_CRACKED_WALL)));}}), + Entrance(RR_WATER_TEMPLE_TORCH_ROOM, {[]{return logic->CanWaterTempleLowFromHigh && (logic->HasFireSourceWithTorch() || logic->CanUse(RG_FAIRY_BOW));}}), }); areaTable[RR_WATER_TEMPLE_MAP_ROOM] = Region("Water Temple Map Room", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, { @@ -145,15 +145,15 @@ void RegionTable_Init_WaterTemple() { //Exits Entrance(RR_WATER_TEMPLE_LOBBY, {[]{return logic->SmallKeys(RR_WATER_TEMPLE, 5);}}), Entrance(RR_WATER_TEMPLE_CENTRAL_PILLAR_UPPER, {[]{return logic->CanUse(RG_HOOKSHOT);}}), - Entrance(RR_WATER_TEMPLE_CENTRAL_PILLAR_BASEMENT, {[]{return logic->WaterTempleMiddle && logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 40;}}), + Entrance(RR_WATER_TEMPLE_CENTRAL_PILLAR_BASEMENT, {[]{return logic->CanWaterTempleMiddle && logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 40;}}), }); areaTable[RR_WATER_TEMPLE_CENTRAL_PILLAR_UPPER] = Region("Water Temple Central Pillar Upper", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, { //Events - EventAccess(&logic->WaterTempleMiddle, {[]{return logic->WaterTempleMiddle || logic->CanUse(RG_ZELDAS_LULLABY);}}), + EventAccess(&logic->CanWaterTempleMiddle, {[]{return logic->CanWaterTempleMiddle || logic->CanUse(RG_ZELDAS_LULLABY);}}), }, { //Locations - LOCATION(RC_WATER_TEMPLE_GS_CENTRAL_PILLAR, logic->CanUse(RG_LONGSHOT) || (((ctx->GetTrickOption(RT_WATER_FW_CENTRAL_GS) && logic->CanUse(RG_FARORES_WIND) && (logic->CanUse(RG_FAIRY_BOW) || logic->CanUse(RG_DINS_FIRE) || logic->SmallKeys(RR_WATER_TEMPLE, 5))) || (ctx->GetTrickOption(RT_WATER_IRONS_CENTRAL_GS) && logic->CanUse(RG_IRON_BOOTS) && ((logic->CanUse(RG_HOOKSHOT) && logic->CanUse(RG_FAIRY_BOW)) || (logic->CanUse(RG_DINS_FIRE))))) && logic->WaterTempleHigh && logic->HookshotOrBoomerang())), + LOCATION(RC_WATER_TEMPLE_GS_CENTRAL_PILLAR, logic->CanUse(RG_LONGSHOT) || (((ctx->GetTrickOption(RT_WATER_FW_CENTRAL_GS) && logic->CanUse(RG_FARORES_WIND) && (logic->CanUse(RG_FAIRY_BOW) || logic->CanUse(RG_DINS_FIRE) || logic->SmallKeys(RR_WATER_TEMPLE, 5))) || (ctx->GetTrickOption(RT_WATER_IRONS_CENTRAL_GS) && logic->CanUse(RG_IRON_BOOTS) && ((logic->CanUse(RG_HOOKSHOT) && logic->CanUse(RG_FAIRY_BOW)) || (logic->CanUse(RG_DINS_FIRE))))) && logic->CanWaterTempleHigh && logic->HookshotOrBoomerang())), }, { //Exits Entrance(RR_WATER_TEMPLE_LOBBY, {[]{return true;}}), @@ -184,7 +184,7 @@ void RegionTable_Init_WaterTemple() { areaTable[RR_WATER_TEMPLE_HIGH_WATER] = Region("Water Temple High Water", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, { //Events - EventAccess(&logic->WaterTempleHigh, {[]{return logic->WaterTempleHigh || logic->CanUse(RG_ZELDAS_LULLABY);}}), + EventAccess(&logic->CanWaterTempleHigh, {[]{return logic->CanWaterTempleHigh || logic->CanUse(RG_ZELDAS_LULLABY);}}), }, {}, { //Exits Entrance(RR_WATER_TEMPLE_LOBBY, {[]{return true;}}), @@ -192,7 +192,7 @@ void RegionTable_Init_WaterTemple() { areaTable[RR_WATER_TEMPLE_BLOCK_CORRIDOR] = Region("Water Temple Block Corridor", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, { //Locations - LOCATION(RC_WATER_TEMPLE_CENTRAL_BOW_TARGET_CHEST, logic->HasItem(RG_GORONS_BRACELET) && (logic->WaterTempleLow || logic->WaterTempleMiddle)), + LOCATION(RC_WATER_TEMPLE_CENTRAL_BOW_TARGET_CHEST, logic->HasItem(RG_GORONS_BRACELET) && (logic->CanWaterTempleLowFromHigh || logic->CanWaterTempleMiddle)), }, { //Exits Entrance(RR_WATER_TEMPLE_LOBBY, {[]{return logic->CanUse(RG_HOOKSHOT);}}), @@ -251,53 +251,407 @@ void RegionTable_Init_WaterTemple() { | MASTER QUEST DUNGEON | ---------------------------*/ if (ctx->GetDungeon(WATER_TEMPLE)->IsMQ()) { - areaTable[RR_WATER_TEMPLE_MQ_LOBBY] = Region("Water Temple MQ Lobby", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { + areaTable[RR_WATER_TEMPLE_MQ_3F_SOUTH_LEDGE] = Region("Water Temple MQ 3F South Ledge", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { //Exits - Entrance(RR_WATER_TEMPLE_ENTRYWAY, {[]{return true;}}), - Entrance(RR_WATER_TEMPLE_MQ_DIVE, {[]{return logic->IsAdult && logic->WaterTimer() >= 24 && logic->CanUse(RG_IRON_BOOTS);}}), - Entrance(RR_WATER_TEMPLE_MQ_DARK_LINK_REGION, {[]{return logic->SmallKeys(RR_WATER_TEMPLE, 1) && logic->IsAdult && logic->CanUse(RG_LONGSHOT) && logic->CanJumpslashExceptHammer();}}), - Entrance(RR_WATER_TEMPLE_BOSS_ENTRYWAY, {[]{return logic->HasItem(RG_WATER_TEMPLE_BOSS_KEY) && logic->IsAdult && logic->CanJumpslashExceptHammer() && logic->CanUse(RG_LONGSHOT);}}), + Entrance(RR_WATER_TEMPLE_ENTRYWAY, {[]{return logic->HasItem(RG_BRONZE_SCALE) || logic->CanUse(RG_IRON_BOOTS);}}), + Entrance(RR_WATER_TEMPLE_MQ_MAIN, {[]{return true;}}), + //If we are not on WL_HIGH, we reach RR_WATER_TEMPLE_MQ_3F_MAIN with hookshot via 2F, otherwise we can reach the platform + Entrance(RR_WATER_TEMPLE_MQ_3F_CENTRAL, {[]{return logic->CanUse(RG_HOOKSHOT) || logic->CanUse(RG_HOVER_BOOTS);}}), + Entrance(RR_WATER_TEMPLE_MQ_2F_CENTRAL, {[]{return logic->MQWaterLevel(WL_LOW_OR_MID);}}), }); - areaTable[RR_WATER_TEMPLE_MQ_DIVE] = Region("Water Temple MQ Dive", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, { - //Locations - LOCATION(RC_WATER_TEMPLE_MQ_MAP_CHEST, logic->HasFireSource() && logic->IsAdult && logic->CanUse(RG_HOOKSHOT)), - LOCATION(RC_WATER_TEMPLE_MQ_CENTRAL_PILLAR_CHEST, logic->IsAdult && logic->CanUse(RG_ZORA_TUNIC) && logic->CanUse(RG_HOOKSHOT) && ((ctx->GetTrickOption(RT_WATER_MQ_CENTRAL_PILLAR) && logic->CanUse(RG_FIRE_ARROWS)) || (logic->CanUse(RG_DINS_FIRE) && logic->CanUse(RG_SONG_OF_TIME)))), - //Trick: logic->IsAdult && logic->CanUse(RG_ZORA_TUNIC) && logic->CanUse(RG_HOOKSHOT) && ((LogicWaterMQCentralPillar && logic->CanUse(RG_FIRE_ARROWS)) || (logic->CanUse(RG_DINS_FIRE) && logic->CanUse(RG_SONG_OF_TIME))) +//This region covers simply existing in the area around the central pillar without being on a specific platform, either swimming or walking on the lakebed +//Entry should only include being in the correct area, taking any possible fall damage, and floating up to the surface of WL_HIGH if coming from below +//This area then leads to others based on level and worst-case water timers for follow-up exits from the water's surface +//remember that any solution that works for any level doesn't need to be given a level, even if that solution is overkill for a lower level + areaTable[RR_WATER_TEMPLE_MQ_MAIN] = Region("Water Temple MQ Main", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_3F_SOUTH_LEDGE, {[]{return logic->HasItem(RG_BRONZE_SCALE) && logic->MQWaterLevel(WL_HIGH);}}), + //Jumping across is possible but a trick due to the janky ledge + Entrance(RR_WATER_TEMPLE_MQ_EAST_TOWER, {[]{return (logic->WaterTimer() >= 24 && logic->CanUse(RG_IRON_BOOTS)) || + (logic->MQWaterLevel(WL_MID) && logic->HasItem(RG_GOLDEN_SCALE) && logic->WaterTimer() >= 16) || + logic->MQWaterLevel(WL_LOW);}}), + Entrance(RR_WATER_TEMPLE_MQ_3F_CENTRAL, {[]{return logic->MQWaterLevel(WL_HIGH) && logic->HasItem(RG_BRONZE_SCALE);}}), + //First water timer uses the hook to go from the top of center to storage room/central pillar as coming from the bottom + //Second water timer is simply diving down and entering the door as fast as possible from the surface + Entrance(RR_WATER_TEMPLE_MQ_2F_CENTRAL, {[]{return ((logic->MQWaterLevel(WL_LOW) || (logic->CanUse(RG_IRON_BOOTS) && (logic->MQWaterLevel(WL_MID) || logic->WaterTimer() >= 16))) && logic->CanUse(RG_LONGSHOT)) || + ((logic->MQWaterLevel(WL_MID) || (logic->MQWaterLevel(WL_HIGH_OR_MID) && logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 8)) && logic->HasItem(RG_BRONZE_SCALE));}}), + Entrance(RR_WATER_TEMPLE_MQ_CENTRAL_PILLAR_1F, {[]{return logic->MQWaterLevel(WL_LOW);}}), + //A special entry as we can't set it to high after entering at a lower height + Entrance(RR_WATER_TEMPLE_MQ_CENTRAL_PILLAR_HIGH, {[]{return logic->MQWaterLevel(WL_HIGH) && logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 16 && (logic->HasItem(RG_BRONZE_SCALE) || logic->CanUse(RG_LONGSHOT));}}), + Entrance(RR_WATER_TEMPLE_MQ_2F_SOUTH, {[]{return (logic->MQWaterLevel(WL_MID) || (logic->MQWaterLevel(WL_HIGH_OR_MID) && logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 16)) && logic->HasItem(RG_BRONZE_SCALE);}}), + Entrance(RR_WATER_TEMPLE_MQ_B1_GATE_SWITCH, {[]{return logic->MQWaterB1Switch && (logic->MQWaterLevel(WL_LOW) || ((logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 24) && logic->HasItem(RG_BRONZE_SCALE)));}}), + Entrance(RR_WATER_TEMPLE_MQ_TRIANGLE_TORCH_ROOM, {[]{return logic->MQWaterB1Switch && + ((logic->MQWaterLevel(WL_LOW) && logic->HasItem(RG_SILVER_SCALE)) || + (logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 16 && (logic->HasItem(RG_BRONZE_SCALE) || logic->CanUse(RG_LONGSHOT))));}}), + //Adult needs to jump in instead of dive for swim access, but you just hold forward. RT_WATER_BK_REGION Isn't relevant unless the Dark Link loop can be done without longshot with other tricks + Entrance(RR_WATER_TEMPLE_MQ_CRATES_WHIRLPOOLS_ROOM, {[]{return logic->MQWaterB1Switch && + ((logic->MQWaterLevel(WL_LOW) && logic->HasItem(RG_BRONZE_SCALE)) || (logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 16 && logic->CanUse(RG_HOOKSHOT))) && + (logic->CanUse(RG_LONGSHOT) || (ctx->GetTrickOption(RT_WATER_BK_REGION) && logic->CanUse(RG_HOVER_BOOTS)));}}), + }); + +//This region specifically covers the topmost platform around central pillar + areaTable[RR_WATER_TEMPLE_MQ_3F_CENTRAL] = Region("Water Temple MQ 3F Central", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_MAIN, {[]{return true;}}), + Entrance(RR_WATER_TEMPLE_MQ_3F_SOUTH_LEDGE, {[]{return logic->CanUse(RG_LONGSHOT) || logic->CanUse(RG_HOVER_BOOTS);}}), + Entrance(RR_WATER_TEMPLE_MQ_2F_CENTRAL, {[]{return (logic->MQWaterLevel(WL_LOW_OR_MID) || (logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 16)) && logic->CanUse(RG_HOOKSHOT);}}), + Entrance(RR_WATER_TEMPLE_MQ_CENTRAL_PILLAR_HIGH, {[]{return logic->MQWaterLevel(WL_HIGH) && logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 16 && logic->CanUse(RG_HOOKSHOT);}}), + Entrance(RR_WATER_TEMPLE_MQ_3F_NORTH_LEDGE, {[]{return logic->MQWaterLevel(WL_HIGH) && logic->CanUse(RG_LONGSHOT);}}), + //Jumping across is possible but a trick due to the janky ledge + Entrance(RR_WATER_TEMPLE_MQ_HIGH_EMBLEM, {[]{return logic->CanUse(RG_HOOKSHOT) || (logic->IsAdult && logic->CanUse(RG_HOVER_BOOTS));}}), + //room access is (logic->IsAdult || (logic->HasItem(RG_BRONZE_SCALE) || logic->CanUse(RG_HOVER_BOOTS) || logic->CanUse(RG_HOOKSHOT))) + Entrance(RR_WATER_TEMPLE_MQ_WATERFALL, {[]{return logic->SmallKeys(RR_WATER_TEMPLE, 1) && logic->MQWaterLevel(WL_HIGH) && logic->CanUse(RG_LONGSHOT);}}), + //this swimless jump with irons may be a trick as you have to put irons on quite late. + Entrance(RR_WATER_TEMPLE_MQ_2F_SOUTH, {[]{return (logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 16) || logic->MQWaterLevel(WL_LOW_OR_MID);}}), + }); + +//This region specifically covers walking on the lower platform around central pillar. This is underwater when WL_HIGH +//RR_WATER_TEMPLE_MQ_CENTRAL_PILLAR_HIGH should be accessed directly to use the central pillar door while at WL_HIGH + areaTable[RR_WATER_TEMPLE_MQ_2F_CENTRAL] = Region("Water Temple MQ 2F Central", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_MAIN, {[]{return true;}}), + Entrance(RR_WATER_TEMPLE_MQ_3F_CENTRAL, {[]{return logic->CanUse(RG_HOOKSHOT);}}), + Entrance(RR_WATER_TEMPLE_MQ_CENTRAL_PILLAR_2F, {[]{return logic->MQWaterLevel(WL_LOW_OR_MID);}}), + Entrance(RR_WATER_TEMPLE_MQ_STORAGE_ROOM, {[]{return logic->CanUse(RG_HOOKSHOT);}}), + Entrance(RR_WATER_TEMPLE_MQ_BEHIND_BLUE_SWITCH_2F, {[]{return logic->MQWaterLevel(WL_LOW_OR_MID) && (logic->IsAdult || logic->CanUse(RG_HOVER_BOOTS)) && logic->CanUse(RG_HOOKSHOT);}}), + Entrance(RR_WATER_TEMPLE_MQ_2F_SOUTH, {[]{return logic->MQWaterLevel(WL_LOW_OR_MID) && logic->CanUse(RG_HOVER_BOOTS);}}), + }); + + areaTable[RR_WATER_TEMPLE_MQ_HIGH_EMBLEM] = Region("Water Temple MQ High Emblem", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, { + //Events + EventAccess(&logic->ReachedWaterHighEmblem, {[]{return true;}}), + EventAccess(&logic->CanWaterTempleHigh, {[]{return logic->CanUse(RG_ZELDAS_LULLABY);}}), + }, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_3F_CENTRAL, {[]{return true;}}), + Entrance(RR_WATER_TEMPLE_MQ_MAIN, {[]{return true;}}), + }); + + areaTable[RR_WATER_TEMPLE_MQ_3F_NORTH_LEDGE] = Region("Water Temple MQ 3F North Ledge", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + //what we need if WL_LOW, we can't guarantee repeated access otherwise. + Entrance(RR_WATER_TEMPLE_MQ_MAIN, {[]{return logic->HasItem(RG_BRONZE_SCALE) || logic->TakeDamage();}}), + Entrance(RR_WATER_TEMPLE_MQ_3F_CENTRAL, {[]{return logic->CanUse(RG_LONGSHOT);}}), + Entrance(RR_WATER_TEMPLE_MQ_BOSS_DOOR, {[]{return logic->CanUse(RG_LONGSHOT) || logic->CanUse(RG_ICE_ARROWS) || logic->CanUse(RG_NAYRUS_LOVE);}}), + }); + + areaTable[RR_WATER_TEMPLE_MQ_BOSS_DOOR] = Region("Water Temple MQ Main", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_3F_NORTH_LEDGE, {[]{return logic->CanUse(RG_ICE_ARROWS) || logic->TakeDamage();}}), + Entrance(RR_WATER_TEMPLE_BOSS_ENTRYWAY, {[]{return logic->HasItem(RG_WATER_TEMPLE_BOSS_KEY);}}), + }); + + areaTable[RR_WATER_TEMPLE_MQ_EAST_TOWER] = Region("Water Temple MQ East Tower", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, { + //Events + //if we can't reach these, we can't move the water at all, so no need to specify level or account for WL_LOW access here + //review is some way to play ocarina underwater exists + EventAccess(&logic->CouldWaterTempleLow, {[]{return true;}}), + EventAccess(&logic->CanWaterTempleLowFromHigh, {[]{return logic->CanUse(RG_ZELDAS_LULLABY);}}), + //Reserved for glitches/tricks that could do this + //EventAccess(&logic->CanWaterTempleLowFromMid, {[]{return false;}}), }, { - //Exits - Entrance(RR_WATER_TEMPLE_MQ_LOWERED_WATER_LEVELS, {[]{return logic->CanUse(RG_ZELDAS_LULLABY);}}), + //Locations + LOCATION(RC_WATER_TEMPLE_MQ_MAP_CHEST, logic->MQWaterLevel(WL_HIGH) && logic->HasFireSource() && logic->CanUse(RG_HOOKSHOT)), + //easy to get at WL_HIGH with the hook-the-underwater-chest glitch + LOCATION(RC_WATER_TEMPLE_MQ_LONGSHOT_CHEST, logic->MQWaterLevel(WL_MID) && logic->CanUse(RG_HOOKSHOT)), + }, { + Entrance(RR_WATER_TEMPLE_MQ_EAST_TOWER_1F_ROOM, {[]{return logic->MQWaterLevel(WL_LOW) && (logic->CanUse(RG_FAIRY_BOW) || logic->CanUse(RG_DINS_FIRE) || logic->CanUse(RG_STICKS));}}), }); - areaTable[RR_WATER_TEMPLE_MQ_LOWERED_WATER_LEVELS] = Region("Water Temple MQ Lowered Water Levels", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, { + //Raising the targets by clearing this room achieves nothing logically because it requires WL_LOW to do and hookshot to use, which implies access to WL_MID and WL_HIGH already + areaTable[RR_WATER_TEMPLE_MQ_EAST_TOWER_1F_ROOM] = Region("Water Temple MQ East Tower 1F Room", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, { //Locations - LOCATION(RC_WATER_TEMPLE_MQ_COMPASS_CHEST, ((logic->IsAdult && logic->CanUse(RG_FAIRY_BOW)) || logic->CanUse(RG_DINS_FIRE) || Here(RR_WATER_TEMPLE_MQ_LOBBY, []{return logic->IsChild && logic->CanUse(RG_STICKS) && logic->HasExplosives();})) && - (logic->CanJumpslashExceptHammer() || logic->CanUseProjectile())), - LOCATION(RC_WATER_TEMPLE_MQ_LONGSHOT_CHEST, logic->IsAdult && logic->CanUse(RG_HOOKSHOT)), - LOCATION(RC_WATER_TEMPLE_MQ_GS_LIZALFOS_HALLWAY, logic->CanUse(RG_DINS_FIRE)), - LOCATION(RC_WATER_TEMPLE_MQ_GS_BEFORE_UPPER_WATER_SWITCH, logic->IsAdult && logic->CanUse(RG_LONGSHOT)), + LOCATION(RC_WATER_TEMPLE_MQ_COMPASS_CHEST, logic->CanKillEnemy(RE_LIZALFOS) && logic->CanKillEnemy(RE_SPIKE)), + }, { + Entrance(RR_WATER_TEMPLE_MQ_EAST_TOWER, {[]{return true;}}), + }); + +//This area assumes we entered through the lower door, so water is low and cannot be changed without leaving. + areaTable[RR_WATER_TEMPLE_MQ_CENTRAL_PILLAR_1F] = Region("Water Temple MQ Central Pillar 1F", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, { + //Events + //This is harder than the other possibilities as you have to move between shots on top of the extra range, but there's basically no universe this should matter. + EventAccess(&logic->MQWaterB1Switch, {[]{return ctx->GetTrickOption(RT_WATER_MQ_CENTRAL_PILLAR) && logic->CanUse(RG_FIRE_ARROWS);}}), + }, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_CENTRAL_PILLAR_HIGH, {[]{return logic->MQWaterLevel(WL_HIGH) && ctx->GetTrickOption(RT_WATER_FW_CENTRAL_GS) && logic->CanUse(RG_FARORES_WIND) && logic->HasItem(RG_BRONZE_SCALE);}}), + //I don't know if this FW trick can ever matter but maybe it's needed to get child to CENTRAL_2F or something + Entrance(RR_WATER_TEMPLE_MQ_CENTRAL_PILLAR_2F, {[]{return logic->CanUse(RG_HOOKSHOT) || + (logic->MQWaterLevel(WL_MID) && ctx->GetTrickOption(RT_WATER_FW_CENTRAL_GS) && logic->CanUse(RG_FARORES_WIND) && logic->HasItem(RG_BRONZE_SCALE));}}), + //if the gate is open, you sink straight in, so you can't climb up this way in logic without swimming + Entrance(RR_WATER_TEMPLE_MQ_CENTRAL_PILLAR_B1, {[]{return logic->MQWaterOpenedPillarB1 && logic->MQWaterLevel(WL_HIGH_OR_MID) && + ctx->GetTrickOption(RT_WATER_FW_CENTRAL_GS) && logic->CanUse(RG_FARORES_WIND) && + logic->CanUse(RG_IRON_BOOTS) && logic->CanUse(RG_ZORA_TUNIC);}}), + }); + +//If we enter here in WL_HIGH, go to RR_WATER_TEMPLE_MQ_CENTRAL_PILLAR_HIGH instead, Assumes WL_MID_OR_LOW + areaTable[RR_WATER_TEMPLE_MQ_CENTRAL_PILLAR_2F] = Region("Water Temple MQ Central Pillar 2F", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, { + //Events + EventAccess(&logic->CouldWaterTempleMiddle, {[]{return true;}}), + EventAccess(&logic->CanWaterTempleMiddle, {[]{return logic->CanUse(RG_ZELDAS_LULLABY);}}), + //It's possible to do this even on low water, but more awkward. I'm not sure if it's even possible for it to be relevant though. + EventAccess(&logic->MQWaterOpenedPillarB1, {[]{return ctx->GetTrickOption(RT_WATER_MQ_CENTRAL_PILLAR) && logic->CanUse(RG_FIRE_ARROWS);}}), + //this could theoretically matter once OI and equip swap is in logic, as one age may be able to get here dry and not wet, and the other may not be able to OI, but as you can OI with hookshot it probably never happens + //EventAccess(&logic->MQWaterPillarSoTBlock, {[]{return logic->CanUse(RG_HOOKSHOT) && logic->CanUse(RG_SONG_OF_TIME);}}), + }, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_CENTRAL_PILLAR_HIGH, {[]{return logic->MQWaterLevel(WL_HIGH) && logic->CanUse(RG_FARORES_WIND) && (logic->HasItem(RG_BRONZE_SCALE) || logic->CanUse(RG_IRON_BOOTS));}}), + Entrance(RR_WATER_TEMPLE_MQ_CENTRAL_PILLAR_B1, {[]{return logic->MQWaterOpenedPillarB1 && logic->MQWaterLevel(WL_MID) && logic->CanUse(RG_IRON_BOOTS) && logic->CanUse(RG_ZORA_TUNIC);}}), + }); + + areaTable[RR_WATER_TEMPLE_MQ_CENTRAL_PILLAR_HIGH] = Region("Water Temple MQ Central Pillar High", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, { + //Events + EventAccess(&logic->MQWaterOpenedPillarB1, {[]{return ((logic->CanUse(RG_SONG_OF_TIME) && logic->CanUse(RG_DINS_FIRE)) || (ctx->GetTrickOption(RT_WATER_MQ_CENTRAL_PILLAR) && logic->CanUse(RG_FIRE_ARROWS))) && + (logic->HasItem(RG_BRONZE_SCALE) || (logic->CanUse(RG_IRON_BOOTS) && logic->CanUse(RG_LONGSHOT) && logic->CanJumpslash()));}}), + }, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_CENTRAL_PILLAR_B1, {[]{return logic->MQWaterB1Switch && logic->CanUse(RG_IRON_BOOTS) && logic->CanUse(RG_ZORA_TUNIC);}}), + }); + +//Assuming tunic and irons was checked on entry + areaTable[RR_WATER_TEMPLE_MQ_CENTRAL_PILLAR_B1] = Region("Water Temple MQ Central Pillar B1", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + //Can't know water level, so we'll just assume any possibility and skip to MAIN + Entrance(RR_WATER_TEMPLE_MQ_MAIN, {[]{return logic->MQWaterOpenedPillarB1 && logic->CanUse(RG_IRON_BOOTS) && logic->CanUse(RG_BRONZE_SCALE);}}), + //Child needs to release irons for height to push down the larger "peg", however they can push the lower one down by climbing and then hit the switch through the larger peg, but it's a trick + Entrance(RR_WATER_TEMPLE_MQ_CENTRAL_PILLAR_B1_FINAL, {[]{return ((logic->IsAdult && logic->CanUse(RG_LONGSHOT)) || (logic->CanUse(RG_HOOKSHOT) && logic->HasItem(RG_BRONZE_SCALE)));}}), + }); + + areaTable[RR_WATER_TEMPLE_MQ_CENTRAL_PILLAR_B1_FINAL] = Region("Water Temple MQ Central Pillar B1 Final", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, { + //Locations + LOCATION(RC_WATER_TEMPLE_MQ_CENTRAL_PILLAR_CHEST, logic->CanUse(RG_HOOKSHOT)), }, {}); - areaTable[RR_WATER_TEMPLE_MQ_DARK_LINK_REGION] = Region("Water Temple MQ Dark Link Region", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, { +//Region exists to add crate/pot/box locations + areaTable[RR_WATER_TEMPLE_MQ_STORAGE_ROOM] = Region("Water Temple MQ Storage Room", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_MAIN, {[]{return logic->MQWaterLevel(WL_LOW_OR_MID) || (logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 8);}}), + }); + + areaTable[RR_WATER_TEMPLE_MQ_BEHIND_BLUE_SWITCH_2F] = Region("Water Temple MQ Behind Blue Switch 2F", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_MAIN, {[]{return true;}}), + Entrance(RR_WATER_TEMPLE_MQ_BEHIND_BLUE_SWITCH_3F, {[]{return logic->CanUse(RG_LONGSHOT);}}), + }); + + areaTable[RR_WATER_TEMPLE_MQ_BEHIND_BLUE_SWITCH_3F] = Region("Water Temple MQ Behind Blue Switch 2F", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, { + //Locations + LOCATION(RC_WATER_TEMPLE_MQ_GS_BEFORE_UPPER_WATER_SWITCH, logic->CanGetEnemyDrop(RE_GOLD_SKULLTULA)), + }, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_BEHIND_BLUE_SWITCH_2F, {[]{return true;}}), + Entrance(RR_WATER_TEMPLE_MQ_HIGH_EMBLEM, {[]{return true;}}), + }); + + areaTable[RR_WATER_TEMPLE_MQ_2F_SOUTH] = Region("Water Temple MQ Lowered Water Levels", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_2F_SOUTH_CAGE, {[]{return logic->MQWaterLevel(WL_LOW_OR_MID) && logic->CanUse(RG_DINS_FIRE);}}), + //this technically exists, but only complicates things, uncomment if some edge case/glitch can use RR_WATER_TEMPLE_MQ_2F_SOUTH to reach RR_WATER_TEMPLE_MQ_3F_CENTRAL, or if a void warp goes here + /*Entrance(RR_WATER_TEMPLE_MQ_3F_EAST_LEDGE, {[]{return (logic->CanUse(RG_HOOKSHOT) && (logic->HasItem(RG_BRONZE_SCALE) || logic->CanUse(RG_IRON_BOOTS))) || + (logic->MQWaterLevel(WL_LOW_OR_MID) && logic->CanUse(RG_HOOKSHOT)) || + logic->MQWaterLevel(WL_HIGH) && (logic->HasItem(RG_BRONZE_SCALE));}}), + }); + + areaTable[RR_WATER_TEMPLE_MQ_3F_EAST_LEDGE] = Region("Water Temple MQ 3F East Ledge", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_MAIN, {[]{return true;}}), + Entrance(RR_WATER_TEMPLE_MQ_3F_CENTRAL, {[]{return logic->CanUse(RG_HOOKSHOT) || logic->CanUse(RG_HOVER_BOOTS);}}),*/ + }); + + areaTable[RR_WATER_TEMPLE_MQ_2F_SOUTH_CAGE] = Region("Water Temple MQ Lowered Water Levels", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, { + //Locations + LOCATION(RC_WATER_TEMPLE_MQ_GS_LIZALFOS_HALLWAY, logic->CanKillEnemy(RE_GOLD_SKULLTULA)), + }, {}); + +//This room exists to hold the wonderitems that drop from the emblems here. Specifically this assumes you are standing on the final ledge + areaTable[RR_WATER_TEMPLE_MQ_WATERFALL] = Region("Water Temple Waterfall", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_3F_CENTRAL, {[]{return logic->SmallKeys(RR_WATER_TEMPLE, 1) && logic->CanUse(RG_LONGSHOT);}}), + Entrance(RR_WATER_TEMPLE_MQ_STALFOS_PIT, {[]{return true;}}), + Entrance(RR_WATER_TEMPLE_MQ_STALFOS_PIT_POTS, {[]{return (logic->MQWaterStalfosPit && logic->IsAdult && logic->CanUse(RG_HOOKSHOT)) || logic->CanUse(RG_HOVER_BOOTS);}}), + Entrance(RR_WATER_TEMPLE_MQ_STALFOS_PIT_UPPER, {[]{return logic->MQWaterStalfosPit && logic->CanUse(RG_LONGSHOT);}}), + }); + + areaTable[RR_WATER_TEMPLE_MQ_STALFOS_PIT] = Region("Water Temple MQ Stalfos Pit", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, { + //Events + EventAccess(&logic->MQWaterStalfosPit, {[]{return ((logic->IsAdult && logic->CanKillEnemy(RE_STALFOS, ED_CLOSE, true, 3, false, true)) || + (logic->CanUse(RG_IRON_BOOTS) && logic->CanUse(RG_HOOKSHOT) && logic->CanKillEnemy(RE_STALFOS, ED_BOOMERANG, true, 3, false, true)));}}), + }, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_WATERFALL, {[]{return logic->MQWaterStalfosPit && logic->CanUse(RG_HOOKSHOT) && (logic->IsAdult || logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 8);}}), + Entrance(RR_WATER_TEMPLE_MQ_STALFOS_PIT_POTS, {[]{return (logic->IsAdult && logic->CanUse(RG_HOOKSHOT)) || + (logic->CanUse(RG_HOOKSHOT) && (logic->IsAdult || logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 8) && (logic->CanUse(RG_HOVER_BOOTS) || logic->MQWaterStalfosPit));}}), + Entrance(RR_WATER_TEMPLE_MQ_STALFOS_PIT_UPPER, {[]{return logic->MQWaterStalfosPit && (logic->IsAdult || logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 8) && logic->CanUse(RG_HOOKSHOT);}}), + }); + + //also includes the suns fairy in the middle + areaTable[RR_WATER_TEMPLE_MQ_STALFOS_PIT_POTS] = Region("Water Temple MQ Stalfos Pit Pots", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, { //Events EventAccess(&logic->FairyPot, {[]{return true;}}), EventAccess(&logic->NutPot, {[]{return true;}}), - }, { - //Locations - LOCATION(RC_WATER_TEMPLE_MQ_BOSS_KEY_CHEST, logic->IsAdult && logic->WaterTimer() >= 24 && logic->CanUse(RG_DINS_FIRE) && (ctx->GetTrickOption(RT_WATER_DRAGON_JUMP_DIVE) || logic->HasItem(RG_SILVER_SCALE) || logic->CanUse(RG_IRON_BOOTS))), - LOCATION(RC_WATER_TEMPLE_MQ_GS_RIVER, true), - }, { + }, {}, { //Exits - Entrance(RR_WATER_TEMPLE_MQ_BASEMENT_GATED_AREAS, {[]{return logic->IsAdult && logic->WaterTimer() >= 24 && logic->CanUse(RG_DINS_FIRE) && logic->CanUse(RG_IRON_BOOTS);}}), + Entrance(RR_WATER_TEMPLE_MQ_WATERFALL, {[]{return logic->MQWaterStalfosPit && (logic->CanUse(RG_HOVER_BOOTS) || logic->CanUse(RG_LONGSHOT));}}), + Entrance(RR_WATER_TEMPLE_MQ_STALFOS_PIT, {[]{return true;}}), + Entrance(RR_WATER_TEMPLE_MQ_STALFOS_PIT_UPPER, {[]{return logic->MQWaterStalfosPit && logic->CanUse(RG_HOOKSHOT);}}), }); - areaTable[RR_WATER_TEMPLE_MQ_BASEMENT_GATED_AREAS] = Region("Water Temple MQ Basement Gated Areas", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, { +//specifically the area past the spikes + areaTable[RR_WATER_TEMPLE_MQ_STALFOS_PIT_UPPER] = Region("Water Temple MQ Stalfos Pit Upper", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_STALFOS_PIT, {[]{return logic->IsAdult || logic->TakeDamage();}}), + Entrance(RR_WATER_TEMPLE_MQ_STALFOS_PIT_POTS, {[]{return logic->IsAdult || logic->TakeDamage();}}), + Entrance(RR_WATER_TEMPLE_MQ_AFTER_DARK_LINK, {[]{return logic->CanKillEnemy(RE_DARK_LINK);}}), + }); + + areaTable[RR_WATER_TEMPLE_MQ_AFTER_DARK_LINK] = Region("Water Temple MQ After Dark Link", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, { + //Events + EventAccess(&logic->FairyPot, {[]{return true;}}), + }, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_STALFOS_PIT_UPPER, {[]{return logic->CanKillEnemy(RE_DARK_LINK);}}), + Entrance(RR_WATER_TEMPLE_MQ_RIVER_SKULL, {[]{return logic->CanUse(RG_HOOKSHOT) && (logic->HasItem(RG_BRONZE_SCALE) || (logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 8) || logic->CanUse(RG_LONGSHOT));}}), + }); + +//if we can use hookshot, we are standing on the targets, otherwise assume we're in the water + areaTable[RR_WATER_TEMPLE_MQ_RIVER_SKULL] = Region("Water Temple MQ River Skull", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, { //Locations - LOCATION(RC_WATER_TEMPLE_MQ_FREESTANDING_KEY, logic->CanUse(RG_HOVER_BOOTS) || logic->CanUse(RG_SCARECROW) || ctx->GetTrickOption(RT_WATER_NORTH_BASEMENT_LEDGE_JUMP)), - LOCATION(RC_WATER_TEMPLE_MQ_GS_TRIPLE_WALL_TORCH, logic->CanUse(RG_FIRE_ARROWS) && (logic->CanUse(RG_HOVER_BOOTS) || logic->CanUse(RG_SCARECROW))), - LOCATION(RC_WATER_TEMPLE_MQ_GS_FREESTANDING_KEY_AREA, ctx->GetTrickOption(RT_WATER_MQ_LOCKED_GS) || (logic->SmallKeys(RR_WATER_TEMPLE, 2) && (logic->CanUse(RG_HOVER_BOOTS) || logic->CanUse(RG_SCARECROW) || ctx->GetTrickOption(RT_WATER_NORTH_BASEMENT_LEDGE_JUMP)) && logic->CanJumpslashExceptHammer())), - //Trick: LogicWaterMQLockedGS || (logic->SmallKeys(RR_WATER_TEMPLE, 2) && (logic->CanUse(RG_HOVER_BOOTS) || logic->CanUse(RG_SCARECROW) || LogicWaterNorthBasementLedgeJump)) + LOCATION(RC_WATER_TEMPLE_MQ_GS_RIVER, logic->CanUse(RG_LONGSHOT) || (logic->CanUse(RG_IRON_BOOTS) && logic->CanUse(RG_HOOKSHOT))), + }, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_RIVER_POTS, {[]{return logic->HasItem(RG_BRONZE_SCALE) || logic->CanUse(RG_LONGSHOT);}}), + }); + + areaTable[RR_WATER_TEMPLE_MQ_RIVER_POTS] = Region("Water Temple MQ River Pots", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, { + //Events + EventAccess(&logic->FairyPot, {[]{return true;}}), + }, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_RIVER_SKULL, {[]{return logic->CanUse(RG_LONGSHOT) || (logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 8 && logic->CanUse(RG_HOOKSHOT));}}), + //You don't need to swim for this if you put irons on in midair and hold forward while aiming for the tunnel with a tight angle, but if you miss you have to void unless you have a hook. It's only relevant with glitches anyway + Entrance(RR_WATER_TEMPLE_MQ_DRAGON_ROOM_TUNNEL, {[]{return logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 16;}}), + Entrance(RR_WATER_TEMPLE_MQ_DRAGON_ROOM_ALCOVE, {[]{return logic->HasItem(RG_SILVER_SCALE) || (logic->IsAdult && logic->HasItem(RG_BRONZE_SCALE) && ctx->GetTrickOption(RT_WATER_DRAGON_JUMP_DIVE));}}), + Entrance(RR_WATER_TEMPLE_MQ_DRAGON_ROOM_DOOR, {[]{return logic->HasItem(RG_BRONZE_SCALE) || logic->CanUse(RG_LONGSHOT) || (logic->CanUse(RG_HOVER_BOOTS) && logic->CanJumpslash());}}), + }); + +//This region assumes Iron boots to access + areaTable[RR_WATER_TEMPLE_MQ_DRAGON_ROOM_TUNNEL] = Region("Water Temple MQ Dragon Room Tunnel", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_RIVER_POTS, {[]{return logic->CanUse(RG_LONGSHOT);}}), + Entrance(RR_WATER_TEMPLE_MQ_DRAGON_ROOM_ALCOVE, {[]{return logic->HasItem(RG_BRONZE_SCALE) || logic->CanUse(RG_HOOKSHOT);}}), + Entrance(RR_WATER_TEMPLE_MQ_DRAGON_ROOM_DOOR, {[]{return logic->HasItem(RG_BRONZE_SCALE) || logic->CanUse(RG_LONGSHOT);}}), + }); + + areaTable[RR_WATER_TEMPLE_MQ_DRAGON_ROOM_ALCOVE] = Region("Water Temple MQ Dragon Room Alcove", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, { + //Events + EventAccess(&logic->MQWaterDragonTorches, {[]{return true;}}), + }, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_DRAGON_ROOM_TUNNEL, {[]{return logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 16;}}), + Entrance(RR_WATER_TEMPLE_MQ_DRAGON_ROOM_DOOR, {[]{return logic->HasItem(RG_SILVER_SCALE);}}), + }); + + areaTable[RR_WATER_TEMPLE_MQ_DRAGON_ROOM_DOOR] = Region("Water Temple MQ Dragon Room Door", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_RIVER_POTS, {[]{return logic->CanUse(RG_LONGSHOT);}}), + Entrance(RR_WATER_TEMPLE_MQ_DRAGON_ROOM_TUNNEL, {[]{return logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 16 && logic->CanUse(RG_HOOKSHOT);}}), + Entrance(RR_WATER_TEMPLE_MQ_DRAGON_ROOM_ALCOVE, {[]{return logic->HasItem(RG_SILVER_SCALE);}}), + Entrance(RR_WATER_TEMPLE_MQ_BOSS_KEY_ROOM_SWITCH, {[]{return logic->MQWaterDragonTorches;}}), + }); + + areaTable[RR_WATER_TEMPLE_MQ_BOSS_KEY_ROOM_SWITCH] = Region("Water Temple MQ Boss Key Room Switch", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_DRAGON_ROOM_DOOR, {[]{return true;}}), + Entrance(RR_WATER_TEMPLE_MQ_BOSS_KEY_ROOM_PIT, {[]{return true;}}), + Entrance(RR_WATER_TEMPLE_MQ_BOSS_KEY_ROOM_CHEST, {[]{return logic->CanHitSwitch() && Here(RR_WATER_TEMPLE_MQ_BOSS_KEY_ROOM_SWITCH, []{return logic->CanUse(RG_DINS_FIRE);});}}), + }); + +//this exists for the crates in preparation for clips through the grate + areaTable[RR_WATER_TEMPLE_MQ_BOSS_KEY_ROOM_PIT] = Region("Water Temple MQ Boss Key Room Pit", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_BOSS_KEY_ROOM_SWITCH, {[]{return logic->CanHitSwitch(ED_BOOMERANG);}}), + }); + + areaTable[RR_WATER_TEMPLE_MQ_BOSS_KEY_ROOM_CHEST] = Region("Water Temple MQ Boss Key Room Chest", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, { + //Locations + LOCATION(RC_WATER_TEMPLE_MQ_BOSS_KEY_CHEST, true), + }, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_BOSS_KEY_ROOM_SWITCH, {[]{return logic->CanHitSwitch(ED_BOOMERANG) || logic->CanUse(RG_HOVER_BOOTS);}}), + Entrance(RR_WATER_TEMPLE_MQ_BOSS_KEY_ROOM_PIT, {[]{return true;}}), + Entrance(RR_WATER_TEMPLE_MQ_B1_GATE_SWITCH, {[]{return logic->HasItem(RG_SILVER_SCALE) || (logic->CanUse(RG_IRON_BOOTS) && (logic->HasItem(RG_BRONZE_SCALE) || (logic->WaterTimer() >= 24 && logic->CanUse(RG_LONGSHOT))));}}), + }); + + areaTable[RR_WATER_TEMPLE_MQ_B1_GATE_SWITCH] = Region("Water Temple MQ B1 Gate Switch", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, { + //Events + //If the water is low, the switch is underwater and needs irons to press, otherwise, the water is too low to climb up and you need irons to hookshot a target + //If a glitch clips through the gate on low, have it logically press the switch and let entrance logic enter + EventAccess(&logic->MQWaterB1Switch, {[]{return logic->CanUse(RG_IRON_BOOTS);}}), + }, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_MAIN, {[]{return logic->MQWaterB1Switch && (logic->MQWaterLevel(WL_LOW) || (logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 16));}}), + Entrance(RR_WATER_TEMPLE_MQ_BOSS_KEY_ROOM_CHEST, {[]{return logic->CanUse(RG_IRON_BOOTS) && logic->HasItem(RG_BRONZE_SCALE) && (logic->MQWaterLevel(WL_LOW) || logic->WaterTimer() >= 24);}}) + }); + + areaTable[RR_WATER_TEMPLE_MQ_TRIANGLE_TORCH_ROOM] = Region("Water Temple MQ Triangle Torch Room", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_MAIN, {[]{return logic->MQWaterB1Switch && + ((logic->MQWaterLevel(WL_LOW) && logic->HasItem(RG_GOLDEN_SCALE)) || + (logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 40 && (logic->HasItem(RG_BRONZE_SCALE) || logic->CanUse(RG_LONGSHOT))));}}), + Entrance(RR_WATER_TEMPLE_MQ_TRIANGLE_TORCH_CAGE, {[]{return logic->CanUse(RG_FIRE_ARROWS) && + ((logic->IsAdult && logic->CanUse(RG_HOVER_BOOTS)) || (logic->CanUse(RG_LONGSHOT) && Here(RR_WATER_TEMPLE_MQ_TRIANGLE_TORCH_CAGE, []{return logic->ScarecrowsSong();})));}}) + }); + + areaTable[RR_WATER_TEMPLE_MQ_TRIANGLE_TORCH_CAGE] = Region("Water Temple MQ Triangle Torch Cage", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, { + //Locations + LOCATION(RC_WATER_TEMPLE_MQ_GS_TRIPLE_WALL_TORCH, logic->CanGetEnemyDrop(RE_GOLD_SKULLTULA, ED_BOOMERANG)), }, {}); + + areaTable[RR_WATER_TEMPLE_MQ_CRATES_WHIRLPOOLS_ROOM] = Region("Water Temple MQ Crates Whirlpools Room", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + //we can backflip over the spikes, but land in water. + Entrance(RR_WATER_TEMPLE_MQ_MAIN, {[]{return logic->MQWaterB1Switch && logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 24 && (logic->CanUse(RG_LONGSHOT) || logic->HasItem(RG_BRONZE_SCALE));}}), + //Child can use the crate to get the height to make it with hovers, but it's annoyingly tight so would be a trick + Entrance(RR_WATER_TEMPLE_MQ_SINGLE_STALFOS_ROOM, {[]{return logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 8 && + //We're putting the requirement to get out of the water here as the scarecrow method in includes hook which satisfies it + ((logic->IsAdult && (logic->CanUse(RG_HOVER_BOOTS) || ctx->GetTrickOption(RT_WATER_NORTH_BASEMENT_LEDGE_JUMP)) && (logic->CanUse(RG_HOOKSHOT) || logic->CanUse(RG_BRONZE_SCALE))) || + (Here(RR_WATER_TEMPLE_MQ_CRATES_WHIRLPOOLS_ROOM, []{return logic->ScarecrowsSong();}) && logic->CanUse(RG_HOOKSHOT)));}}), + Entrance(RR_WATER_TEMPLE_MQ_4_TORCH_ROOM, {[]{return logic->IsAdult && + (logic->CanUse(RG_HOVER_BOOTS) || ctx->GetTrickOption(RT_WATER_NORTH_BASEMENT_LEDGE_JUMP) || + (Here(RR_WATER_TEMPLE_MQ_CRATES_WHIRLPOOLS_ROOM, []{return logic->ScarecrowsSong();}) && logic->CanUse(RG_HOOKSHOT)));}}), + Entrance(RR_WATER_TEMPLE_MQ_CRATES_WHIRLPOOLS_CAGE, {[]{return ctx->GetTrickOption(RT_WATER_MQ_LOCKED_GS) && (logic->CanUse(RG_IRON_BOOTS) && logic->CanUse(RG_HOOKSHOT));}}), + }); + + areaTable[RR_WATER_TEMPLE_MQ_SINGLE_STALFOS_ROOM] = Region("Water Temple MQ Single Stalfos Room", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, { + //Locations + LOCATION(RC_WATER_TEMPLE_MQ_FREESTANDING_KEY, true), + }, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_CRATES_WHIRLPOOLS_ROOM, {[]{return logic->HasItem(RG_SILVER_SCALE) || (logic->IsChild && logic->HasItem(RG_BRONZE_SCALE)) || + (logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 8 && (logic->HasItem(RG_BRONZE_SCALE) || logic->CanUse(RG_HOOKSHOT)));}}) + }); + + areaTable[RR_WATER_TEMPLE_MQ_4_TORCH_ROOM] = Region("Water Temple MQ 4 Torch Room", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_CRATES_WHIRLPOOLS_ROOM, {[]{return (logic->IsAdult && (logic->CanUse(RG_HOVER_BOOTS) || logic->CanJumpslash())) || + (logic->HasItem(RG_BRONZE_SCALE) || (logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 8 && logic->CanUse(RG_HOOKSHOT) ));}}), + Entrance(RR_WATER_TEMPLE_MQ_DODONGO_ROOM, {[]{return logic->CanHitSwitch() && logic->HasFireSource();}}) + }); + + areaTable[RR_WATER_TEMPLE_MQ_DODONGO_ROOM] = Region("Water Temple MQ Dodongo Room", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(RR_WATER_TEMPLE_MQ_4_TORCH_ROOM, {[]{return (logic->CanUse(RG_HOOKSHOT) || logic->CanUse(RG_HOVER_BOOTS)) && + Here(RR_WATER_TEMPLE_MQ_DODONGO_ROOM, []{return logic->CanKillEnemy(RE_DODONGO, ED_CLOSE, true, 5);});}}), + Entrance(RR_WATER_TEMPLE_MQ_CRATES_WHIRLPOOLS_CAGE, {[]{return (logic->CanUse(RG_HOOKSHOT) || logic->CanUse(RG_HOVER_BOOTS)) && + Here(RR_WATER_TEMPLE_MQ_DODONGO_ROOM, []{return logic->CanKillEnemy(RE_DODONGO, ED_CLOSE, true, 5);});}}) + }); + + areaTable[RR_WATER_TEMPLE_MQ_CRATES_WHIRLPOOLS_CAGE] = Region("Water Temple MQ Basement Gated Areas", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, { + //Locations + LOCATION(RC_WATER_TEMPLE_MQ_GS_FREESTANDING_KEY_AREA, logic->CanGetEnemyDrop(RE_GOLD_SKULLTULA)), + }, { + Entrance(RR_WATER_TEMPLE_MQ_DODONGO_ROOM, {[]{return true;}}) + }); } /*--------------------------- @@ -307,9 +661,9 @@ void RegionTable_Init_WaterTemple() { Region("Water Temple Boss Entryway", "Water Temple", {RA_WATER_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { // Exits - Entrance(RR_WATER_TEMPLE_PRE_BOSS_ROOM, { [] { return ctx->GetDungeon(WATER_TEMPLE)->IsVanilla() && false; } }), - Entrance(RR_WATER_TEMPLE_MQ_LOBBY, { [] { return ctx->GetDungeon(WATER_TEMPLE)->IsMQ() && false; } }), - Entrance(RR_WATER_TEMPLE_BOSS_ROOM, { [] { return true; } }), + Entrance(RR_WATER_TEMPLE_PRE_BOSS_ROOM, {[]{return ctx->GetDungeon(WATER_TEMPLE)->IsVanilla() && false;}}), + Entrance(RR_WATER_TEMPLE_MQ_BOSS_DOOR, {[]{return ctx->GetDungeon(WATER_TEMPLE)->IsMQ() && false;}}), + Entrance(RR_WATER_TEMPLE_BOSS_ROOM, {[]{return true;}}), }); areaTable[RR_WATER_TEMPLE_BOSS_ROOM] = Region("Water Temple Boss Room", "Water Temple", {}, NO_DAY_NIGHT_CYCLE, diff --git a/soh/soh/Enhancements/randomizer/entrance.cpp b/soh/soh/Enhancements/randomizer/entrance.cpp index 4866c4f4a..bc9cb5513 100644 --- a/soh/soh/Enhancements/randomizer/entrance.cpp +++ b/soh/soh/Enhancements/randomizer/entrance.cpp @@ -923,7 +923,7 @@ int EntranceShuffler::ShuffleAllEntrances() { { EntranceType::Interior, RR_MARKET_POTION_SHOP, RR_THE_MARKET, ENTR_MARKET_DAY_OUTSIDE_POTION_SHOP } }, { { EntranceType::Interior, RR_THE_MARKET, RR_MARKET_TREASURE_CHEST_GAME, ENTR_TREASURE_BOX_SHOP_0 }, { EntranceType::Interior, RR_MARKET_TREASURE_CHEST_GAME, RR_THE_MARKET, ENTR_MARKET_DAY_OUTSIDE_TREASURE_BOX_SHOP } }, - { { EntranceType::Interior, RR_MARKET_BACK_ALLEY, RR_MARKET_BOMBCHU_SHOP, ENTR_BOMBCHU_SHOP_0 }, + { { EntranceType::Interior, RR_MARKET_BACK_ALLEY, RR_MARKET_BOMBCHU_SHOP, ENTR_BOMBCHU_SHOP_1 }, { EntranceType::Interior, RR_MARKET_BOMBCHU_SHOP, RR_MARKET_BACK_ALLEY, ENTR_BACK_ALLEY_DAY_OUTSIDE_BOMBCHU_SHOP } }, { { EntranceType::Interior, RR_MARKET_BACK_ALLEY, RR_MARKET_MAN_IN_GREEN_HOUSE, ENTR_BACK_ALLEY_MAN_IN_GREEN_HOUSE }, { EntranceType::Interior, RR_MARKET_MAN_IN_GREEN_HOUSE, RR_MARKET_BACK_ALLEY, ENTR_BACK_ALLEY_DAY_OUTSIDE_MAN_IN_GREEN_HOUSE } }, diff --git a/soh/soh/Enhancements/randomizer/logic.cpp b/soh/soh/Enhancements/randomizer/logic.cpp index 11ffd0766..4b159b941 100644 --- a/soh/soh/Enhancements/randomizer/logic.cpp +++ b/soh/soh/Enhancements/randomizer/logic.cpp @@ -59,6 +59,7 @@ namespace Rando { case RG_PROGRESSIVE_NUT_UPGRADE: case RG_NUTS: return CurrentUpgrade(UPG_NUTS); + //RANDOTODO handle cases where the scarecrow is persistent between age better when OI is added case RG_SCARECROW: return ScarecrowsSong() && CanUse(RG_HOOKSHOT); case RG_DISTANT_SCARECROW: @@ -450,6 +451,7 @@ namespace Rando { } return killed; case RE_DODONGO: + return CanUse(RG_KOKIRI_SWORD) || CanUse(RG_MASTER_SWORD) || CanUse(RG_BIGGORON_SWORD) || (quantity <= 5 && CanUse(RG_STICKS)) || HasExplosives() || CanUse(RG_FAIRY_SLINGSHOT) || CanUse(RG_FAIRY_BOW); case RE_LIZALFOS: return CanJumpslash() || HasExplosives() || CanUse(RG_FAIRY_SLINGSHOT) || CanUse(RG_FAIRY_BOW); case RE_KEESE: @@ -495,8 +497,31 @@ namespace Rando { return CanDamage(); case RE_STALFOS: //RANDOTODO Add trick to kill stalfos with sticks, and a second one for bombs without stunning. Higher ammo logic for bombs is also plausible - return CanUse(RG_KOKIRI_SWORD) || CanUse(RG_MASTER_SWORD) || CanUse(RG_BIGGORON_SWORD) || CanUse(RG_MEGATON_HAMMER) || CanUse(RG_FAIRY_BOW) || CanUse(RG_BOMBCHU_5) || - (quantity <= 2 && !timer && (CanUse(RG_NUTS) || HookshotOrBoomerang()) && CanUse(RG_BOMB_BAG)) || (quantity <= 1 && CanUse(RG_STICKS)); + switch (distance){ + case ED_CLOSE: + case ED_SHORT_JUMPSLASH: + killed = CanUse(RG_MEGATON_HAMMER) || CanUse(RG_KOKIRI_SWORD); + [[fallthrough]]; + case ED_MASTER_SWORD_JUMPSLASH: + killed = killed || CanUse(RG_MASTER_SWORD); + [[fallthrough]]; + case ED_LONG_JUMPSLASH: + killed = killed || CanUse(RG_BIGGORON_SWORD) || (quantity <= 1 && CanUse(RG_STICKS)); + [[fallthrough]]; + case ED_BOOMERANG: + //RANDOTODO test dins, bomb and chu range in a practical example + killed = killed || (quantity <= 2 && !timer && !inWater && (CanUse(RG_NUTS) || HookshotOrBoomerang()) && CanUse(RG_BOMB_BAG)); + [[fallthrough]]; + case ED_HOOKSHOT: + //RANDOTODO test dins, bomb and chu range in a practical example + killed = killed || (wallOrFloor && CanUse(RG_BOMBCHU_5)); + [[fallthrough]]; + case ED_LONGSHOT: + case ED_FAR: + killed = killed || CanUse(RG_FAIRY_BOW); + break; + } + return killed; //Needs 16 bombs, but is in default logic in N64, probably because getting the hits is quite easy. //bow and sling can wake them and damage after they shed their armour, so could reduce ammo requirements for explosives to 10. //requires 8 sticks to kill so would be a trick unless we apply higher stick bag logic @@ -562,6 +587,9 @@ namespace Rando { case RE_BIG_OCTO: //If chasing octo is annoying but with rolls you can catch him, and you need rang to get into this room without shenanigains anyway. Bunny makes it free return CanUse(RG_KOKIRI_SWORD) || CanUse(RG_STICKS) || CanUse(RG_MASTER_SWORD); + case RE_DARK_LINK: + //RNADOTODO Dark link is buggy right now, retest when he is not + return CanJumpslash() || CanUse(RG_FAIRY_BOW); default: SPDLOG_ERROR("CanKillEnemy reached `default`."); assert(false); @@ -597,6 +625,7 @@ namespace Rando { case RE_ARMOS: case RE_FREEZARD: case RE_SPIKE: + case RE_DARK_LINK: return true; case RE_BIG_SKULLTULA: //hammer jumpslash can pass, but only on flat land where you can kill with hammer swing @@ -648,6 +677,7 @@ namespace Rando { case RE_SPIKE: case RE_BIG_OCTO: case RE_GIBDO: + case RE_DARK_LINK: return true; case RE_MAD_SCRUB: case RE_KEESE: @@ -725,6 +755,35 @@ namespace Rando { return CanDetonateBombFlowers() || HasItem(RG_GORONS_BRACELET); } + bool Logic::MQWaterLevel(RandoWaterLevel level) { + //For ease of reading, I will call the triforce emblem that sets the water to WL_LOW the "Low Emblem", the one that sets it to WL_MID the "Mid Emblem", and the one that sets it to WL_HIGH the "High Emblem" + switch(level){ + //While you have to go through WL_LOW to get to Mid, the requirements for WL_LOW are stricter than WL_MID because you can always go up to WL_MID and then could need to go back to WL_HIGH to reach the Low Emblem again + //Thanks to this caveat you need to be able to reach and play ZL to both the High and Low Emblems to have WL_LOW in logic. + //Alternativly a way to reach WL_LOW from WL_MID could exist, but all glitchless methods need you to do a Low-locked action + case WL_LOW: + return (CanWaterTempleHigh && CanWaterTempleLowFromHigh) || (CanWaterTempleLowFromMid && CanWaterTempleLowFromHigh); + case WL_LOW_OR_MID: + return (CanWaterTempleHigh && CanWaterTempleLowFromHigh) || (CanWaterTempleLowFromHigh && CanWaterTempleMiddle) || (CanWaterTempleLowFromMid && CanWaterTempleLowFromHigh); + //If we can set it to High out of logic we can just repeat what we did to lower the water in the first place as High is the default. + //Because of this you only need to be able to use the Low and Mid Emblems, WL_LOW could be skipped if it was ever possible to play ZL underwater. + case WL_MID: + return CanWaterTempleLowFromHigh && CanWaterTempleMiddle; + //Despite being the initial state of water temple, WL_HIGH has the extra requirement of making sure that, if we were to lower the water out of logic, we could put it back to WL_HIGH + //However because it is the default state, we do not need to check if we can actually change the water level, only to make sure we can return to WL_HIGH if we found the means to play ZL out of logic. + //There are 2 methods to lock yourself out after playing ZL already: Not being able to reach the High Emblem and being unable to replay ZL. (I will be ignoring other-age-access shenanigains) + //The former check would simply be a check to see if we can reach High Emblem, but we assume the water is WL_MID (as if we can set it to WL_LOW, we can set it to WL_MID, as Mid Emblem has no requirements) + //The latter check can be assumed for now but will want a revisit once OI tricks are added. + case WL_HIGH: + return ReachedWaterHighEmblem; + case WL_HIGH_OR_MID: + return ReachedWaterHighEmblem || (CanWaterTempleLowFromHigh && CanWaterTempleMiddle); + } + SPDLOG_ERROR("MQWaterLevel reached `return false;`. Missing case for a Water Level"); + assert(false); + return false; + } + Logic::Logic() { } @@ -2007,9 +2066,10 @@ namespace Rando { GCWoodsWarpOpen = false; GCDaruniasDoorOpenChild = false; StopGCRollingGoronAsAdult = false; - WaterTempleLow = false; - WaterTempleMiddle = false; - WaterTempleHigh = false; + CanWaterTempleLowFromHigh = false; + CanWaterTempleLowFromMid = false; + CanWaterTempleMiddle = false; + CanWaterTempleHigh = false; KakarikoVillageGateOpen = false; KingZoraThawed = false; ForestTempleJoelle = false; @@ -2045,6 +2105,11 @@ namespace Rando { MQJabuLiftRoomCow = false; MQShadowFloorSpikeRupees = false; ShadowShortcutBlock = false; + MQWaterStalfosPit = false; + MQWaterDragonTorches = false; + MQWaterB1Switch = false; + //MQWaterPillarSoTBlock = false; + MQWaterOpenedPillarB1 = false; StopPerformanceTimer(PT_LOGIC_RESET); } diff --git a/soh/soh/Enhancements/randomizer/logic.h b/soh/soh/Enhancements/randomizer/logic.h index 4b57835e0..141cc4139 100644 --- a/soh/soh/Enhancements/randomizer/logic.h +++ b/soh/soh/Enhancements/randomizer/logic.h @@ -118,9 +118,13 @@ class Logic { bool GCWoodsWarpOpen = false; bool GCDaruniasDoorOpenChild = false; bool StopGCRollingGoronAsAdult = false; - bool WaterTempleLow = false; - bool WaterTempleMiddle = false; - bool WaterTempleHigh = false; + bool CanWaterTempleLowFromHigh = false; + bool CanWaterTempleMiddle = false; + bool CanWaterTempleHigh = false; + bool CanWaterTempleLowFromMid = false; + bool CouldWaterTempleLow = false; + bool CouldWaterTempleMiddle = false; + bool ReachedWaterHighEmblem = false; bool KakarikoVillageGateOpen = false; bool KingZoraThawed = false; bool ForestTempleJoelle = false; @@ -157,6 +161,11 @@ class Logic { bool MQJabuLiftRoomCow = false; bool MQShadowFloorSpikeRupees = false; bool ShadowShortcutBlock = false; + bool MQWaterStalfosPit = false; + bool MQWaterDragonTorches = false; + bool MQWaterB1Switch = false; + //bool MQWaterPillarSoTBlock = false; should be irrelevant. SHOULD. + bool MQWaterOpenedPillarB1 = false; /* --- END OF HELPERS AND LOCATION ACCESS --- */ @@ -180,6 +189,7 @@ class Logic { bool CanHitEyeTargets(); bool CanDetonateBombFlowers(); bool CanDetonateUprightBombFlower(); + bool MQWaterLevel(RandoWaterLevel level); uint8_t BottleCount(); uint8_t OcarinaButtons(); bool HasBottle(); diff --git a/soh/soh/Enhancements/randomizer/randomizerTypes.h b/soh/soh/Enhancements/randomizer/randomizerTypes.h index b7607f8ae..eb0996ef0 100644 --- a/soh/soh/Enhancements/randomizer/randomizerTypes.h +++ b/soh/soh/Enhancements/randomizer/randomizerTypes.h @@ -751,11 +751,48 @@ typedef enum { RR_WATER_TEMPLE_RIVER, RR_WATER_TEMPLE_PRE_BOSS_ROOM, - RR_WATER_TEMPLE_MQ_LOBBY, - RR_WATER_TEMPLE_MQ_DIVE, - RR_WATER_TEMPLE_MQ_LOWERED_WATER_LEVELS, - RR_WATER_TEMPLE_MQ_DARK_LINK_REGION, - RR_WATER_TEMPLE_MQ_BASEMENT_GATED_AREAS, + RR_WATER_TEMPLE_MQ_3F_SOUTH_LEDGE, + RR_WATER_TEMPLE_MQ_MAIN, + RR_WATER_TEMPLE_MQ_3F_CENTRAL, + RR_WATER_TEMPLE_MQ_2F_CENTRAL, + RR_WATER_TEMPLE_MQ_2F_CENTRAL_HIGH, + RR_WATER_TEMPLE_MQ_HIGH_EMBLEM, + RR_WATER_TEMPLE_MQ_3F_NORTH_LEDGE, + RR_WATER_TEMPLE_MQ_BOSS_DOOR, + RR_WATER_TEMPLE_MQ_EAST_TOWER, + RR_WATER_TEMPLE_MQ_EAST_TOWER_1F_ROOM, + RR_WATER_TEMPLE_MQ_CENTRAL_PILLAR_1F, + RR_WATER_TEMPLE_MQ_CENTRAL_PILLAR_2F, + RR_WATER_TEMPLE_MQ_CENTRAL_PILLAR_HIGH, + RR_WATER_TEMPLE_MQ_CENTRAL_PILLAR_B1, + RR_WATER_TEMPLE_MQ_CENTRAL_PILLAR_B1_FINAL, + RR_WATER_TEMPLE_MQ_BEHIND_BLUE_SWITCH_2F, + RR_WATER_TEMPLE_MQ_BEHIND_BLUE_SWITCH_3F, + RR_WATER_TEMPLE_MQ_STORAGE_ROOM, + RR_WATER_TEMPLE_MQ_2F_SOUTH, + RR_WATER_TEMPLE_MQ_2F_SOUTH_CAGE, + RR_WATER_TEMPLE_MQ_3F_EAST_LEDGE, + RR_WATER_TEMPLE_MQ_WATERFALL, + RR_WATER_TEMPLE_MQ_STALFOS_PIT, + RR_WATER_TEMPLE_MQ_STALFOS_PIT_POTS, + RR_WATER_TEMPLE_MQ_STALFOS_PIT_UPPER, + RR_WATER_TEMPLE_MQ_AFTER_DARK_LINK, + RR_WATER_TEMPLE_MQ_RIVER_SKULL, + RR_WATER_TEMPLE_MQ_RIVER_POTS, + RR_WATER_TEMPLE_MQ_DRAGON_ROOM_DOOR, + RR_WATER_TEMPLE_MQ_DRAGON_ROOM_TUNNEL, + RR_WATER_TEMPLE_MQ_DRAGON_ROOM_ALCOVE, + RR_WATER_TEMPLE_MQ_BOSS_KEY_ROOM_SWITCH, + RR_WATER_TEMPLE_MQ_BOSS_KEY_ROOM_PIT, + RR_WATER_TEMPLE_MQ_BOSS_KEY_ROOM_CHEST, + RR_WATER_TEMPLE_MQ_B1_GATE_SWITCH, + RR_WATER_TEMPLE_MQ_TRIANGLE_TORCH_ROOM, + RR_WATER_TEMPLE_MQ_TRIANGLE_TORCH_CAGE, + RR_WATER_TEMPLE_MQ_CRATES_WHIRLPOOLS_ROOM, + RR_WATER_TEMPLE_MQ_SINGLE_STALFOS_ROOM, + RR_WATER_TEMPLE_MQ_4_TORCH_ROOM, + RR_WATER_TEMPLE_MQ_DODONGO_ROOM, + RR_WATER_TEMPLE_MQ_CRATES_WHIRLPOOLS_CAGE, RR_WATER_TEMPLE_BOSS_ENTRYWAY, RR_WATER_TEMPLE_BOSS_ROOM, @@ -4533,6 +4570,7 @@ typedef enum { RE_STINGER, RE_BIG_OCTO, RE_GIBDO, + RE_DARK_LINK, } RandomizerEnemy; //RANDOTODO compare child long jumpslash range with adult short @@ -4549,6 +4587,14 @@ typedef enum { ED_FAR, } EnemyDistance; +typedef enum { + WL_LOW, + WL_MID, + WL_HIGH, + WL_LOW_OR_MID, + WL_HIGH_OR_MID +} RandoWaterLevel; + #define ENTRANCE_GROTTO_LOAD_START 0x0700 #define ENTRANCE_GROTTO_EXIT_START 0x0800 diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance.c b/soh/soh/Enhancements/randomizer/randomizer_entrance.c index e441f3aa5..5c0f8971f 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance.c +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance.c @@ -198,7 +198,7 @@ void Entrance_Init(void) { bossSceneSaveDeathWarps[bossScene - SCENE_DEKU_TREE_BOSS] = saveWarpEntrance; } - //Overwrite grotto related indices + // Overwrite grotto related indices if (originalIndex >= ENTRANCE_GROTTO_EXIT_START) { Grotto_SetExitOverride(originalIndex, overrideIndex); continue; @@ -226,7 +226,7 @@ void Entrance_Init(void) { s16 indicesToSilenceBackgroundMusic[2] = { // The lost woods music playing near the GC Woods Warp keeps playing - // in the next area if the bvackground music is allowed to keep playing + // in the next area if the background music is allowed to keep playing entranceOverrideTable[ENTR_LOST_WOODS_TUNNEL_SHORTCUT], // Goron City -> Lost Woods override // If Malon is singing at night, then her singing will be transferred @@ -452,7 +452,19 @@ void Entrance_SetWarpSongEntrance(void) { } void Entrance_OverrideBlueWarp(void) { - // Handles first time entering bluewarp (with item give) + // Remap child 2nd visits in adult dungeons for warp pad -> bluewarp + if (gSaveContext.entranceIndex == ENTR_SACRED_FOREST_MEADOW_WARP_PAD) { + gSaveContext.entranceIndex = ENTR_SACRED_FOREST_MEADOW_FOREST_TEMPLE_BLUE_WARP; + } else if (gSaveContext.entranceIndex == ENTR_DEATH_MOUNTAIN_CRATER_WARP_PAD) { + gSaveContext.entranceIndex = ENTR_DEATH_MOUNTAIN_CRATER_FIRE_TEMPLE_BLUE_WARP; + } else if (gSaveContext.entranceIndex == ENTR_LAKE_HYLIA_WARP_PAD) { + gSaveContext.entranceIndex = ENTR_LAKE_HYLIA_WATER_TEMPLE_BLUE_WARP; + } else if (gSaveContext.entranceIndex == ENTR_DESERT_COLOSSUS_WARP_PAD) { + gSaveContext.entranceIndex = ENTR_DESERT_COLOSSUS_SPIRIT_TEMPLE_BLUE_WARP; + } else if (gSaveContext.entranceIndex == ENTR_GRAVEYARD_WARP_PAD) { + gSaveContext.entranceIndex = ENTR_GRAVEYARD_SHADOW_TEMPLE_BLUE_WARP; + } + switch (gSaveContext.entranceIndex) { case ENTR_KOKIRI_FOREST_DEKU_TREE_BLUE_WARP: // Gohma blue warp case ENTR_DEATH_MOUNTAIN_TRAIL_DODONGO_BLUE_WARP: // KD blue warp @@ -465,20 +477,6 @@ void Entrance_OverrideBlueWarp(void) { gSaveContext.entranceIndex = Entrance_OverrideNextIndex(gSaveContext.entranceIndex); return; } - - // Handles second+ times entering bluewarp - switch (gPlayState->nextEntranceIndex) { - case ENTR_KOKIRI_FOREST_DEKU_TREE_BLUE_WARP: // Gohma blue warp - case ENTR_DEATH_MOUNTAIN_TRAIL_DODONGO_BLUE_WARP: // KD blue warp - case ENTR_ZORAS_FOUNTAIN_JABU_JABU_BLUE_WARP: // Barinade blue warp - case ENTR_SACRED_FOREST_MEADOW_FOREST_TEMPLE_BLUE_WARP: // Phantom Ganon blue warp - case ENTR_DEATH_MOUNTAIN_CRATER_FIRE_TEMPLE_BLUE_WARP: // Volvagia blue warp - case ENTR_LAKE_HYLIA_WATER_TEMPLE_BLUE_WARP: // Morpha blue warp - case ENTR_DESERT_COLOSSUS_SPIRIT_TEMPLE_BLUE_WARP: // Bongo-Bongo blue warp - case ENTR_GRAVEYARD_SHADOW_TEMPLE_BLUE_WARP: // Twinrova blue warp - gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(gPlayState->nextEntranceIndex); - return; - } } void Entrance_EnableFW(void) { diff --git a/soh/soh/Enhancements/randomizer/randomizer_grotto.c b/soh/soh/Enhancements/randomizer/randomizer_grotto.c index be10e5ec4..cb8eacb1a 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_grotto.c +++ b/soh/soh/Enhancements/randomizer/randomizer_grotto.c @@ -99,6 +99,10 @@ void Grotto_InitExitAndLoadLists(void) { grottoLoadList[i] = ENTRANCE_GROTTO_LOAD_START + i; grottoExitList[i] = ENTRANCE_GROTTO_EXIT_START + i; } + + grottoId = 0xFF; + lastEntranceType = NOT_GROTTO; + overridingNextEntrance = false; } void Grotto_SetExitOverride(s16 originalIndex, s16 overrideIndex) { diff --git a/soh/soh/Enhancements/randomizer/settings.cpp b/soh/soh/Enhancements/randomizer/settings.cpp index f3f1e43e1..3050658a5 100644 --- a/soh/soh/Enhancements/randomizer/settings.cpp +++ b/soh/soh/Enhancements/randomizer/settings.cpp @@ -446,6 +446,7 @@ void Settings::CreateOptions() { mTrickOptions[RT_WATER_BK_REGION] = TrickOption::LogicTrick(RCQUEST_VANILLA, RA_WATER_TEMPLE, {Tricks::Tag::INTERMEDIATE}, false, "Water Temple Boss Key Region with Hover Boots", "With precise Hover Boots movement it is possible to reach the boss key chest's region without needing the Longshot. It is not necessary to take damage from the spikes. The Gold Skulltula Token in the following room can also be obtained with just the Hover Boots."); mTrickOptions[RT_WATER_NORTH_BASEMENT_LEDGE_JUMP] = TrickOption::LogicTrick(RCQUEST_BOTH, RA_WATER_TEMPLE, {Tricks::Tag::INTERMEDIATE}, false, "Water Temple North Basement Ledge with Precise Jump", "In the northern basement there's a ledge from where, in vanilla Water Temple, boulders roll out into the room. Normally to jump directly to this ledge logically requires the Hover Boots, but with precise jump, it can be done without them. This trick applies to both Vanilla and Master Quest."); mTrickOptions[RT_WATER_BK_JUMP_DIVE] = TrickOption::LogicTrick(RCQUEST_VANILLA, RA_WATER_TEMPLE, {Tricks::Tag::NOVICE}, false, "Water Temple Boss Key Jump Dive", "Stand on the very edge of the raised corridor leading from the push block room to the rolling boulder corridor. Face the gold skulltula on the waterfall and jump over the boulder corridor floor into the pool of water, swimming right once underwater. This allows access to the boss key room without Iron boots."); + //Also used in MQ logic, but won't be relevent unl;ess a way to enter tower without irons exists (likely a clip + swim) mTrickOptions[RT_WATER_FW_CENTRAL_GS] = TrickOption::LogicTrick(RCQUEST_VANILLA, RA_WATER_TEMPLE, {Tricks::Tag::NOVICE}, false, "Water Temple Central Pillar GS with Farore\'s Wind", "If you set Farore's Wind inside the central pillar and then return to that warp point after raising the water to the highest level, you can obtain this Skulltula Token with Hookshot or Boomerang."); mTrickOptions[RT_WATER_IRONS_CENTRAL_GS] = TrickOption::LogicTrick(RCQUEST_VANILLA, RA_WATER_TEMPLE, {Tricks::Tag::NOVICE}, false, "Water Temple Central Pillar GS with Iron Boots", "After opening the middle water level door into the central pillar, the door will stay unbarred so long as you do not leave the room -- even if you were to raise the water up to the highest level. With the Iron Boots to go through the door after the water has been raised, you can obtain the Skulltula Token with the Hookshot."); mTrickOptions[RT_WATER_CENTRAL_BOW] = TrickOption::LogicTrick(RCQUEST_VANILLA, RA_WATER_TEMPLE, {Tricks::Tag::ADVANCED}, false, "Water Temple Central Bow Target without Longshot or Hover Boots", "A very precise Bow shot can hit the eye switch from the floor above. Then, you can jump down into the hallway and make through it before the gate closes. It can also be done as child, using the Slingshot instead of the Bow."); diff --git a/soh/soh/Enhancements/timesaver_hook_handlers.cpp b/soh/soh/Enhancements/timesaver_hook_handlers.cpp index 6b610a999..3111b8cea 100644 --- a/soh/soh/Enhancements/timesaver_hook_handlers.cpp +++ b/soh/soh/Enhancements/timesaver_hook_handlers.cpp @@ -14,6 +14,7 @@ extern "C" { #include "src/overlays/actors/ovl_En_Owl/z_en_owl.h" #include "src/overlays/actors/ovl_En_Ko/z_en_ko.h" #include "src/overlays/actors/ovl_En_Ma1/z_en_ma1.h" +#include "src/overlays/actors/ovl_En_Ru2/z_en_ru2.h" #include "src/overlays/actors/ovl_En_Zl4/z_en_zl4.h" #include "src/overlays/actors/ovl_En_Box/z_en_box.h" #include "src/overlays/actors/ovl_Demo_Im/z_demo_im.h" @@ -37,6 +38,8 @@ extern int32_t D_8011D3AC; extern void func_808ADEF0(BgSpot03Taki* bgSpot03Taki, PlayState* play); extern void BgSpot03Taki_ApplyOpeningAlpha(BgSpot03Taki* bgSpot03Taki, s32 bufferIndex); extern void BgSpot03Taki_KeepOpen(BgSpot03Taki* bgSpot03Taki, PlayState* play); + +extern void func_80AF36EC(EnRu2* enRu2, PlayState* play); } #define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).GetSelectedOptionIndex() @@ -852,6 +855,15 @@ void TimeSaverOnActorInitHandler(void* actorRef) { Actor_Kill(actor); } } + + // Water Temple Ruto cutscene + if (actor->id == ACTOR_EN_RU2 && gPlayState->sceneNum == SCENE_WATER_TEMPLE) { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { + EnRu2* enRu2 = (EnRu2*)actor; + func_80AF36EC(enRu2, gPlayState); + Actor_Kill(actor); + } + } } void TimeSaverOnSceneInitHandler(int16_t sceneNum) { diff --git a/soh/soh/SohMenuBar.cpp b/soh/soh/SohMenuBar.cpp index f82b76b5d..beb4553ba 100644 --- a/soh/soh/SohMenuBar.cpp +++ b/soh/soh/SohMenuBar.cpp @@ -1746,7 +1746,7 @@ void DrawCheatsMenu() { UIWidgets::EnhancementSliderFloat("Hookshot Reach Multiplier: %.2fx", "##gCheatHookshotReachMultiplier", CVAR_CHEAT("HookshotReachMultiplier"), 1.0f, 5.0f, "", 1.0f, false); UIWidgets::Spacer(2.0f); if (ImGui::Button("Change Age")) { - CVarSetInteger(CVAR_GENERAL("SwitchAge"), 1); + SwitchAge(); } UIWidgets::Tooltip("Switches Link's age and reloads the area."); UIWidgets::Spacer(2.0f); diff --git a/soh/soh/z_play_otr.cpp b/soh/soh/z_play_otr.cpp index 729e3badb..22345af88 100644 --- a/soh/soh/z_play_otr.cpp +++ b/soh/soh/z_play_otr.cpp @@ -19,19 +19,19 @@ Ship::IResource* OTRPlay_LoadFile(PlayState* play, const char* fileName) return res.get(); } -extern "C" void OTRPlay_SpawnScene(PlayState* play, s32 sceneNum, s32 spawn) { - SceneTableEntry* scene = &gSceneTable[sceneNum]; +extern "C" void OTRPlay_SpawnScene(PlayState* play, s32 sceneId, s32 spawn) { + SceneTableEntry* scene = &gSceneTable[sceneId]; scene->unk_13 = 0; play->loadedScene = scene; - play->sceneNum = sceneNum; + play->sceneNum = sceneId; play->sceneConfig = scene->config; //osSyncPrintf("\nSCENE SIZE %fK\n", (scene->sceneFile.vromEnd - scene->sceneFile.vromStart) / 1024.0f); // Scenes considered "dungeon" with a MQ variant - int16_t inNonSharedScene = (sceneNum >= SCENE_DEKU_TREE && sceneNum <= SCENE_ICE_CAVERN) || - sceneNum == SCENE_GERUDO_TRAINING_GROUND || sceneNum == SCENE_INSIDE_GANONS_CASTLE; + int16_t inNonSharedScene = (sceneId >= SCENE_DEKU_TREE && sceneId <= SCENE_ICE_CAVERN) || + sceneId == SCENE_GERUDO_TRAINING_GROUND || sceneId == SCENE_INSIDE_GANONS_CASTLE; std::string sceneVersion = "shared"; if (inNonSharedScene) { diff --git a/soh/src/code/graph.c b/soh/src/code/graph.c index 6a49117dd..f691c2619 100644 --- a/soh/src/code/graph.c +++ b/soh/src/code/graph.c @@ -300,7 +300,6 @@ void Graph_Update(GraphicsContext* gfxCtx, GameState* gameState) { GameState_ReqPadData(gameState); GameState_Update(gameState); - DrawColViewer(); OPEN_DISPS(gfxCtx); diff --git a/soh/src/code/z_demo.c b/soh/src/code/z_demo.c index 5d44a14af..decc18f39 100644 --- a/soh/src/code/z_demo.c +++ b/soh/src/code/z_demo.c @@ -307,7 +307,7 @@ void func_80064824(PlayState* play, CutsceneContext* csCtx, CsCmdBase* cmd) { break; case 14: if (sp3F != 0) { - func_800BC490(play, 1); + Play_SetViewpoint(play, 1); } break; case 15: @@ -2052,7 +2052,7 @@ void func_80068C3C(PlayState* play, CutsceneContext* csCtx) { csCtx->frames++; if (dREG(95) != 0) { - Cutscene_ProcessCommands(play, csCtx, D_8012D1F0); + Cutscene_ProcessCommands(play, csCtx, gDebugCutsceneScript); } else { Cutscene_ProcessCommands(play, csCtx, play->csCtx.segment); } diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index b519c84a3..6227a8b4b 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -1607,10 +1607,8 @@ void Inventory_SwapAgeEquipment(void) { s16 i; u16 shieldEquipValue; - // Mod Enhancments can utilise the rando flow path - if (IS_RANDO || CVarGetInteger(CVAR_GENERAL("SwitchAge"), 0) || CVarGetInteger(CVAR_GENERAL("SwitchTimeline"), 0)) { + if (IS_RANDO) { Rando_Inventory_SwapAgeEquipment(); - CVarSetInteger(CVAR_GENERAL("SwitchTimeline"), 0); return; } diff --git a/soh/src/code/z_play.c b/soh/src/code/z_play.c index ae6d987a9..0ba9d8759 100644 --- a/soh/src/code/z_play.c +++ b/soh/src/code/z_play.c @@ -18,53 +18,70 @@ #include #include -void* D_8012D1F0 = NULL; -//UNK_TYPE D_8012D1F4 = 0; // unused -Input* D_8012D1F8 = NULL; - TransitionUnk sTrnsnUnk; s32 gTrnsnUnkState; -VisMono D_80161498; +VisMono gPlayVisMono; Color_RGBA8_u32 D_801614B0; + FaultClient D_801614B8; -s16 D_801614C8; -#if 0 -u64 D_801614D0[0xA00]; -#endif + +s16 sTransitionFillTimer; + +void* gDebugCutsceneScript = NULL; +UNK_TYPE D_8012D1F4 = 0; // unused + +Input* D_8012D1F8 = NULL; PlayState* gPlayState; s16 firstInit = 0; - s16 gEnPartnerId; -void OTRPlay_SpawnScene(PlayState* play, s32 sceneNum, s32 spawn); +void Play_SpawnScene(PlayState* play, s32 sceneId, s32 spawn); + +// This macro prints the number "1" with a file and line number if R_ENABLE_PLAY_LOGS is enabled. +// For example, it can be used to trace the play state execution at a high level. +// SOHTODO: Revert log statements everywhere back to authentic, and deal with dynamic line/file names via macro +#define PLAY_LOG(line) \ + do { \ + if (1 & HREG(63)) { \ + LOG_NUM("1", 1 /*, "../z_play.c", line */); \ + } \ + } while (0) void enableBetaQuest(); void disableBetaQuest(); -void func_800BC450(PlayState* play) { +void OTRPlay_SpawnScene(PlayState* play, s32 sceneId, s32 spawn); + +void Play_RequestViewpointBgCam(PlayState* play) { Camera_ChangeDataIdx(GET_ACTIVE_CAM(play), play->unk_1242B - 1); } -void func_800BC490(PlayState* play, s16 point) { - assert(point == 1 || point == 2); +void Play_SetViewpoint(PlayState* play, s16 viewpoint) { + assert(viewpoint == 1 || viewpoint == 2); - play->unk_1242B = point; + play->unk_1242B = viewpoint; if ((YREG(15) != 0x10) && (gSaveContext.cutsceneIndex < 0xFFF0)) { - Audio_PlaySoundGeneral((point == 1) ? NA_SE_SY_CAMERA_ZOOM_DOWN : NA_SE_SY_CAMERA_ZOOM_UP, &D_801333D4, 4, + Audio_PlaySoundGeneral((viewpoint == 1) ? NA_SE_SY_CAMERA_ZOOM_DOWN : NA_SE_SY_CAMERA_ZOOM_UP, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8); } - func_800BC450(play); + Play_RequestViewpointBgCam(play); } -s32 func_800BC56C(PlayState* play, s16 arg1) { - return (arg1 == play->unk_1242B); +/** + * @return true if the currently set viewpoint is the same as the one provided in the argument + */ +s32 Play_CheckViewpoint(PlayState* play, s16 viewpoint) { + return (viewpoint == play->unk_1242B); } -// original name: "Game_play_shop_pr_vr_switch_set" -void func_800BC590(PlayState* play) { +/** + * If the scene is a shop, set the viewpoint that will set the bgCamIndex + * to toggle the camera into a "browsing item selection" setting. + */ +void Play_SetShopBrowsingViewpoint(PlayState* play) { osSyncPrintf("Game_play_shop_pr_vr_switch_set()\n"); if (YREG(15) == 0x10) { @@ -75,7 +92,7 @@ void func_800BC590(PlayState* play) { void Gameplay_SetupTransition(PlayState* play, s32 transitionType) { TransitionContext* transitionCtx = &play->transitionCtx; - memset(transitionCtx,0, sizeof(TransitionContext)); + memset(transitionCtx, 0, sizeof(TransitionContext)); transitionCtx->transitionType = transitionType; @@ -103,6 +120,7 @@ void Gameplay_SetupTransition(PlayState* play, s32 transitionType) { transitionCtx->setColor = TransitionTriforce_SetColor; transitionCtx->setEnvColor = NULL; break; + case TRANS_TYPE_WIPE: case TRANS_TYPE_WIPE_FAST: transitionCtx->init = TransitionWipe_Init; @@ -115,6 +133,7 @@ void Gameplay_SetupTransition(PlayState* play, s32 transitionType) { transitionCtx->setColor = TransitionWipe_SetColor; transitionCtx->setEnvColor = NULL; break; + case TRANS_TYPE_FADE_BLACK: case TRANS_TYPE_FADE_WHITE: case TRANS_TYPE_FADE_BLACK_FAST: @@ -135,25 +154,32 @@ void Gameplay_SetupTransition(PlayState* play, s32 transitionType) { transitionCtx->setColor = TransitionFade_SetColor; transitionCtx->setEnvColor = NULL; break; + case TRANS_TYPE_FILL_WHITE2: case TRANS_TYPE_FILL_WHITE: play->transitionMode = TRANS_MODE_FILL_WHITE_INIT; break; + case TRANS_TYPE_INSTANT: play->transitionMode = TRANS_MODE_INSTANT; break; + case TRANS_TYPE_FILL_BROWN: play->transitionMode = TRANS_MODE_FILL_BROWN_INIT; break; + case TRANS_TYPE_SANDSTORM_PERSIST: play->transitionMode = TRANS_MODE_SANDSTORM_INIT; break; + case TRANS_TYPE_SANDSTORM_END: play->transitionMode = TRANS_MODE_SANDSTORM_END_INIT; break; + case TRANS_TYPE_CS_BLACK_FILL: play->transitionMode = TRANS_MODE_CS_BLACK_FILL_INIT; break; + default: Fault_AddHungupAndCrash(__FILE__, __LINE__); break; @@ -166,8 +192,8 @@ void func_800BC88C(PlayState* play) { } Gfx* Play_SetFog(PlayState* play, Gfx* gfx) { - return Gfx_SetFog2(gfx, play->lightCtx.fogColor[0], play->lightCtx.fogColor[1], - play->lightCtx.fogColor[2], 0, play->lightCtx.fogNear, 1000); + return Gfx_SetFog2(gfx, play->lightCtx.fogColor[0], play->lightCtx.fogColor[1], play->lightCtx.fogColor[2], 0, + play->lightCtx.fogNear, 1000); } void Play_Destroy(GameState* thisx) { @@ -183,6 +209,7 @@ void Play_Destroy(GameState* thisx) { play->state.gfxCtx->callback = NULL; play->state.gfxCtx->callbackParam = 0; + SREG(91) = 0; R_PAUSE_MENU_MODE = 0; @@ -204,7 +231,7 @@ void Play_Destroy(GameState* thisx) { ShrinkWindow_Destroy(); TransitionFade_Destroy(&play->transitionFade); - VisMono_Destroy(&D_80161498); + VisMono_Destroy(&gPlayVisMono); if (gSaveContext.linkAge != play->linkAgeOnLoad) { Inventory_SwapAgeEquipment(); @@ -216,8 +243,11 @@ void Play_Destroy(GameState* thisx) { KaleidoScopeCall_Destroy(play); KaleidoManager_Destroy(); ZeldaArena_Cleanup(); + Fault_RemoveClient(&D_801614B8); + disableBetaQuest(); + gPlayState = NULL; } @@ -346,18 +376,17 @@ u8 CheckLACSRewardCount() { void Play_Init(GameState* thisx) { PlayState* play = (PlayState*)thisx; GraphicsContext* gfxCtx = play->state.gfxCtx; - enableBetaQuest(); - gPlayState = play; - //play->state.gfxCtx = NULL; uintptr_t zAlloc; uintptr_t zAllocAligned; size_t zAllocSize; Player* player; - s32 playerStartCamId; + s32 playerStartBgCamIndex; s32 i; - u8 tempSetupIndex; + u8 baseSceneLayer; s32 pad[2]; + enableBetaQuest(); + // Properly initialize the frame counter so it doesn't use garbage data if (!firstInit) { play->gameplayFrames = 0; @@ -373,7 +402,10 @@ void Play_Init(GameState* thisx) { return; } + gPlayState = play; + SystemArena_Display(); + // OTRTODO allocate double the normal amount of memory // This is to avoid some parts of the game, like loading actors, causing OoM // This is potionally unavoidable due to struct size differences, but is x2 the right amount? @@ -399,6 +431,7 @@ void Play_Init(GameState* thisx) { play->cameraPtrs[MAIN_CAM]->uid = 0; play->activeCamera = MAIN_CAM; func_8005AC48(&play->mainCamera, 0xFF); + // Sram_Init(this, &this->sramCtx); Regs_InitData(play); Message_Init(play); GameOver_Init(play); @@ -431,41 +464,53 @@ void Play_Init(GameState* thisx) { Cutscene_HandleConditionalTriggers(play); - if (gSaveContext.gameMode != 0 || gSaveContext.cutsceneIndex >= 0xFFF0) { + if (gSaveContext.gameMode != GAMEMODE_NORMAL || gSaveContext.cutsceneIndex >= 0xFFF0) { gSaveContext.nayrusLoveTimer = 0; Magic_Reset(play); - gSaveContext.sceneSetupIndex = (gSaveContext.cutsceneIndex & 0xF) + 4; + gSaveContext.sceneSetupIndex = SCENE_LAYER_CUTSCENE_FIRST + (gSaveContext.cutsceneIndex & 0xF); } else if (!LINK_IS_ADULT && IS_DAY) { - gSaveContext.sceneSetupIndex = 0; + gSaveContext.sceneSetupIndex = SCENE_LAYER_CHILD_DAY; } else if (!LINK_IS_ADULT && !IS_DAY) { - gSaveContext.sceneSetupIndex = 1; + gSaveContext.sceneSetupIndex = SCENE_LAYER_CHILD_NIGHT; } else if (LINK_IS_ADULT && IS_DAY) { - gSaveContext.sceneSetupIndex = 2; + gSaveContext.sceneSetupIndex = SCENE_LAYER_ADULT_DAY; } else { - gSaveContext.sceneSetupIndex = 3; + gSaveContext.sceneSetupIndex = SCENE_LAYER_ADULT_NIGHT; } - tempSetupIndex = gSaveContext.sceneSetupIndex; + // save the base scene layer (before accounting for the special cases below) to use later for the transition type + baseSceneLayer = gSaveContext.sceneSetupIndex; + if ((gEntranceTable[((void)0, gSaveContext.entranceIndex)].scene == SCENE_HYRULE_FIELD) && !LINK_IS_ADULT && - gSaveContext.sceneSetupIndex < 4) { + !IS_CUTSCENE_LAYER) { if (CHECK_QUEST_ITEM(QUEST_KOKIRI_EMERALD) && CHECK_QUEST_ITEM(QUEST_GORON_RUBY) && CHECK_QUEST_ITEM(QUEST_ZORA_SAPPHIRE)) { gSaveContext.sceneSetupIndex = 1; } else { gSaveContext.sceneSetupIndex = 0; } - } else if ((gEntranceTable[((void)0, gSaveContext.entranceIndex)].scene == SCENE_KOKIRI_FOREST) && LINK_IS_ADULT && - gSaveContext.sceneSetupIndex < 4) { + } else if ((gEntranceTable[((void)0, gSaveContext.entranceIndex)].scene == SCENE_KOKIRI_FOREST) && + LINK_IS_ADULT && !IS_CUTSCENE_LAYER) { gSaveContext.sceneSetupIndex = (Flags_GetEventChkInf(EVENTCHKINF_USED_FOREST_TEMPLE_BLUE_WARP)) ? 3 : 2; } Play_SpawnScene( - play, - gEntranceTable[((void)0, gSaveContext.entranceIndex) + ((void)0, gSaveContext.sceneSetupIndex)].scene, + play, gEntranceTable[((void)0, gSaveContext.entranceIndex) + ((void)0, gSaveContext.sceneSetupIndex)].scene, gEntranceTable[((void)0, gSaveContext.sceneSetupIndex) + ((void)0, gSaveContext.entranceIndex)].spawn); osSyncPrintf("\nSCENE_NO=%d COUNTER=%d\n", ((void)0, gSaveContext.entranceIndex), gSaveContext.sceneSetupIndex); +#if 0 + // When entering Gerudo Valley in the credits, trigger the GC emulator to play the ending movie. + // The emulator constantly checks whether PC is 0x81000000, so this works even though it's not a valid address. + if ((gEntranceTable[((void)0, gSaveContext.save.entranceIndex)].sceneId == SCENE_GERUDO_VALLEY) && + gSaveContext.sceneLayer == 6) { + PRINTF("エンディングはじまるよー\n"); // "The ending starts" + ((void (*)(void))0x81000000)(); + PRINTF("出戻り?\n"); // "Return?" + } +#endif + Cutscene_HandleEntranceTriggers(play); KaleidoScopeCall_Init(play); func_801109B0(play); @@ -475,10 +520,12 @@ void Play_Init(GameState* thisx) { gSaveContext.totalDays++; gSaveContext.bgsDayCount++; gSaveContext.dogIsLost = true; + if (Inventory_ReplaceItem(play, ITEM_WEIRD_EGG, ITEM_CHICKEN) || Inventory_HatchPocketCucco(play)) { Message_StartTextbox(play, 0x3066, NULL); } + gSaveContext.nextDayTime = 0xFFFE; } else { gSaveContext.nextDayTime = 0xFFFD; @@ -488,11 +535,10 @@ void Play_Init(GameState* thisx) { SREG(91) = -1; R_PAUSE_MENU_MODE = 0; PreRender_Init(&play->pauseBgPreRender); - PreRender_SetValuesSave(&play->pauseBgPreRender, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 0); - PreRender_SetValues(&play->pauseBgPreRender, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0); + PreRender_SetValuesSave(&play->pauseBgPreRender, SCREEN_WIDTH, SCREEN_HEIGHT, NULL, NULL, NULL); + PreRender_SetValues(&play->pauseBgPreRender, SCREEN_WIDTH, SCREEN_HEIGHT, NULL, NULL); gTrnsnUnkState = 0; play->transitionMode = TRANS_MODE_OFF; - FrameAdvance_Init(&play->frameAdvCtx); Rand_Seed((u32)osGetTime()); Matrix_Init(&play->state); @@ -501,12 +547,12 @@ void Play_Init(GameState* thisx) { play->transitionTrigger = TRANS_TRIGGER_END; play->unk_11E16 = 0xFF; play->unk_11E18 = 0; - play->unk_11DE9 = 0; + play->unk_11DE9 = false; - if (gSaveContext.gameMode != 1) { + if (gSaveContext.gameMode != GAMEMODE_TITLE_SCREEN) { if (gSaveContext.nextTransitionType == TRANS_NEXT_TYPE_DEFAULT) { play->transitionType = ENTRANCE_INFO_END_TRANS_TYPE( - gEntranceTable[((void)0, gSaveContext.entranceIndex) + tempSetupIndex].field); // Fade In + gEntranceTable[((void)0, gSaveContext.entranceIndex) + baseSceneLayer].field); // Fade In } else { play->transitionType = gSaveContext.nextTransitionType; gSaveContext.nextTransitionType = TRANS_NEXT_TYPE_DEFAULT; @@ -520,20 +566,21 @@ void Play_Init(GameState* thisx) { TransitionFade_SetType(&play->transitionFade, 3); TransitionFade_SetColor(&play->transitionFade, RGBA8(160, 160, 160, 255)); TransitionFade_Start(&play->transitionFade); - VisMono_Init(&D_80161498); + VisMono_Init(&gPlayVisMono); D_801614B0.a = 0; Flags_UnsetAllEnv(play); osSyncPrintf("ZELDA ALLOC SIZE=%x\n", THA_GetSize(&play->state.tha)); zAllocSize = THA_GetSize(&play->state.tha); - zAlloc = GAMESTATE_ALLOC_MC(&play->state, zAllocSize); + zAlloc = (uintptr_t)GAMESTATE_ALLOC_MC(&play->state, zAllocSize); zAllocAligned = (zAlloc + 8) & ~0xF; - ZeldaArena_Init(zAllocAligned, zAllocSize - zAllocAligned + zAlloc); + ZeldaArena_Init((void*)zAllocAligned, zAllocSize - (zAllocAligned - zAlloc)); // "Zelda Heap" osSyncPrintf("ゼルダヒープ %08x-%08x\n", zAllocAligned, - (s32)(zAllocAligned + zAllocSize) - (s32)(zAllocAligned - zAlloc)); + (u8*)zAllocAligned + zAllocSize - (s32)(zAllocAligned - zAlloc)); Fault_AddClient(&D_801614B8, ZeldaArena_Display, NULL, NULL); + // In order to keep masks equipped on first load, we need to pre-set the age reqs for the item and slot if (CVarGetInteger(CVAR_ENHANCEMENT("AdultMasks"), 0) || CVarGetInteger(CVAR_CHEAT("TimelessEquipment"), 0)) { for (int i = ITEM_MASK_KEATON; i <= ITEM_MASK_TRUTH; i += 1) { @@ -548,6 +595,7 @@ void Play_Init(GameState* thisx) { } gSlotAgeReqs[SLOT_TRADE_CHILD] = AGE_REQ_CHILD; } + func_800304DC(play, &play->actorCtx, play->linkActorEntry); while (!func_800973FC(play, &play->roomCtx)) { @@ -565,16 +613,17 @@ void Play_Init(GameState* thisx) { { CollisionHeader* colHeader = BgCheck_GetCollisionHeader(&play->colCtx, BGCHECK_SCENE); + u8 camId = player->actor.params & 0xFF; // If the player's start cam is out of bounds, set it to 0xFF so it isn't used. - if (colHeader != NULL && ((player->actor.params & 0xFF) >= colHeader->cameraDataListLen)) { + if (colHeader != NULL && (camId != 0xFF) && (camId >= colHeader->cameraDataListLen)) { player->actor.params |= 0xFF; } } - playerStartCamId = player->actor.params & 0xFF; - if (playerStartCamId != 0xFF) { - osSyncPrintf("player has start camera ID (" VT_FGCOL(BLUE) "%d" VT_RST ")\n", playerStartCamId); - Camera_ChangeDataIdx(&play->mainCamera, playerStartCamId); + playerStartBgCamIndex = player->actor.params & 0xFF; + if (playerStartBgCamIndex != 0xFF) { + osSyncPrintf("player has start camera ID (" VT_FGCOL(BLUE) "%d" VT_RST ")\n", playerStartBgCamIndex); + Camera_ChangeDataIdx(&play->mainCamera, playerStartBgCamIndex); } if (YREG(15) == 32) { @@ -591,7 +640,9 @@ void Play_Init(GameState* thisx) { gSaveContext.natureAmbienceId = play->sequenceCtx.natureAmbienceId; func_8002DF18(play, GET_PLAYER(play)); AnimationContext_Update(play, &play->animationCtx); + gSaveContext.respawnFlag = 0; + // #region SOH [Stats] if (gSaveContext.sohStats.sceneNum != gPlayState->sceneNum) { u16 idx = gSaveContext.sohStats.tsIdx; gSaveContext.sohStats.sceneTimestamps[idx].sceneTime = gSaveContext.sohStats.sceneTimer / 2; @@ -618,14 +669,20 @@ void Play_Init(GameState* thisx) { gSaveContext.sohStats.sceneNum = gPlayState->sceneNum; gSaveContext.sohStats.roomNum = gPlayState->roomCtx.curRoom.num; - gSaveContext.respawnFlag = 0; - #if 0 - if (dREG(95) != 0) { - D_8012D1F0 = D_801614D0; - osSyncPrintf("\nkawauso_data=[%x]", D_8012D1F0); - DmaMgr_DmaRomToRam(0x03FEB000, D_8012D1F0, sizeof(D_801614D0)); + // #endregion + +#if 0 + if (R_USE_DEBUG_CUTSCENE) { + static u64 sDebugCutsceneScriptBuf[0xA00]; + + gDebugCutsceneScript = sDebugCutsceneScriptBuf; + PRINTF("\nkawauso_data=[%x]", gDebugCutsceneScript); + + // This hardcoded ROM address extends past the end of the ROM file. + // Presumably the ROM was larger at a previous point in development when this debug feature was used. + DmaMgr_DmaRomToRam(0x03FEB000, gDebugCutsceneScript, sizeof(sDebugCutsceneScriptBuf)); } - #endif +#endif if (CVarGetInteger(CVAR_ENHANCEMENT("IvanCoopModeEnabled"), 0)) { Actor_Spawn(&play->actorCtx, play, gEnPartnerId, GET_PLAYER(play)->actor.world.pos.x, @@ -635,13 +692,9 @@ void Play_Init(GameState* thisx) { } void Play_Update(PlayState* play) { + Input* input = play->state.input; + s32 isPaused; s32 pad1; - s32 sp80; - Input* input; - u32 i; - s32 pad2; - - input = play->state.input; if ((SREG(1) < 0) || (DREG(0) != 0)) { SREG(1) = 0; @@ -649,20 +702,26 @@ void Play_Update(PlayState* play) { } if ((HREG(80) == 18) && (HREG(81) < 0)) { + u32 i; + s32 pad2; + HREG(81) = 0; osSyncPrintf("object_exchange_rom_address %u\n", gObjectTableSize); osSyncPrintf("RomStart RomEnd Size\n"); + for (i = 0; i < gObjectTableSize; i++) { ptrdiff_t size = gObjectTable[i].vromEnd - gObjectTable[i].vromStart; osSyncPrintf("%08x-%08x %08x(%8.3fKB)\n", gObjectTable[i].vromStart, gObjectTable[i].vromEnd, size, size / 1024.0f); } + osSyncPrintf("\n"); } if ((HREG(81) == 18) && (HREG(82) < 0)) { HREG(82) = 0; + // ActorOverlayTable_LogPrint(); } if (CVarGetInteger(CVAR_SETTING("FreeLook.Enabled"), 0) && Player_InCsMode(play)) { @@ -678,7 +737,7 @@ void Play_Update(PlayState* play) { play->transitionMode = TRANS_MODE_SETUP; } - // Gameplay stats: Count button presses + // #region SOH [Stats] Gameplay stats: Count button presses if (!gSaveContext.sohStats.gameComplete) { if (CHECK_BTN_ALL(input[0].press.button, BTN_A)) {gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_A]++;} if (CHECK_BTN_ALL(input[0].press.button, BTN_B)) {gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_B]++;} @@ -703,6 +762,7 @@ void Play_Update(PlayState* play) { gSaveContext.sohStats.fileCreatedAt = GetUnixTimestamp(); } } + // #endregion if (gTrnsnUnkState != 0) { switch (gTrnsnUnkState) { @@ -722,18 +782,20 @@ void Play_Update(PlayState* play) { } } - if (play->transitionMode) { + if ((u32)play->transitionMode != TRANS_MODE_OFF) { switch (play->transitionMode) { case TRANS_MODE_SETUP: if (play->transitionTrigger != TRANS_TRIGGER_END) { - s16 sp6E = 0; + s16 sceneLayer = 0; Interface_ChangeAlpha(1); if (gSaveContext.cutsceneIndex >= 0xFFF0) { - sp6E = (gSaveContext.cutsceneIndex & 0xF) + 4; + sceneLayer = SCENE_LAYER_CUTSCENE_FIRST + (gSaveContext.cutsceneIndex & 0xF); } - if (!(gEntranceTable[play->nextEntranceIndex + sp6E].field & ENTRANCE_INFO_CONTINUE_BGM_FLAG)) { // Continue BGM Off + // fade out bgm if "continue bgm" flag is not set + if (!(gEntranceTable[play->nextEntranceIndex + sceneLayer].field & + ENTRANCE_INFO_CONTINUE_BGM_FLAG)) { // "Sound initalized. 111" osSyncPrintf("\n\n\nサウンドイニシャル来ました。111"); if ((play->transitionType < TRANS_TYPE_MAX) && !Environment_IsForcedSequenceDisabled()) { @@ -753,25 +815,30 @@ void Play_Update(PlayState* play) { } if (play->transitionMode >= TRANS_MODE_FILL_WHITE_INIT) { + // non-instance modes break out of this switch break; } - + FALLTHROUGH; case TRANS_MODE_INSTANCE_INIT: play->transitionCtx.init(&play->transitionCtx.data); // Circle Transition Types if ((play->transitionCtx.transitionType >> 5) == 1) { play->transitionCtx.setType(&play->transitionCtx.data, - play->transitionCtx.transitionType | TC_SET_PARAMS); + play->transitionCtx.transitionType | TC_SET_PARAMS); } gSaveContext.transWipeSpeed = 14; + if ((play->transitionCtx.transitionType == TRANS_TYPE_WIPE_FAST) || (play->transitionCtx.transitionType == TRANS_TYPE_FILL_WHITE2)) { + //! @bug TRANS_TYPE_FILL_WHITE2 will never reach this code. + //! It is a non-instance type transition which doesn't run this case. gSaveContext.transWipeSpeed = 28; } gSaveContext.transFadeDuration = 60; + if ((play->transitionCtx.transitionType == TRANS_TYPE_FADE_BLACK_FAST) || (play->transitionCtx.transitionType == TRANS_TYPE_FADE_WHITE_FAST)) { gSaveContext.transFadeDuration = 20; @@ -788,24 +855,28 @@ void Play_Update(PlayState* play) { (play->transitionCtx.transitionType == TRANS_TYPE_FADE_WHITE_CS_DELAYED) || (play->transitionCtx.transitionType == TRANS_TYPE_FADE_WHITE_INSTANT)) { play->transitionCtx.setColor(&play->transitionCtx.data, RGBA8(160, 160, 160, 255)); + if (play->transitionCtx.setEnvColor != NULL) { play->transitionCtx.setEnvColor(&play->transitionCtx.data, - RGBA8(160, 160, 160, 255)); + RGBA8(160, 160, 160, 255)); } } else if (play->transitionCtx.transitionType == TRANS_TYPE_FADE_GREEN) { play->transitionCtx.setColor(&play->transitionCtx.data, RGBA8(140, 140, 100, 255)); + if (play->transitionCtx.setEnvColor != NULL) { play->transitionCtx.setEnvColor(&play->transitionCtx.data, - RGBA8(140, 140, 100, 255)); + RGBA8(140, 140, 100, 255)); } } else if (play->transitionCtx.transitionType == TRANS_TYPE_FADE_BLUE) { play->transitionCtx.setColor(&play->transitionCtx.data, RGBA8(70, 100, 110, 255)); + if (play->transitionCtx.setEnvColor != NULL) { play->transitionCtx.setEnvColor(&play->transitionCtx.data, - RGBA8(70, 100, 110, 255)); + RGBA8(70, 100, 110, 255)); } } else { play->transitionCtx.setColor(&play->transitionCtx.data, RGBA8(0, 0, 0, 0)); + if (play->transitionCtx.setEnvColor != NULL) { play->transitionCtx.setEnvColor(&play->transitionCtx.data, RGBA8(0, 0, 0, 0)); } @@ -827,18 +898,20 @@ void Play_Update(PlayState* play) { break; case TRANS_MODE_INSTANCE_RUNNING: - if (play->transitionCtx.isDone(&play->transitionCtx) != 0) { + if (play->transitionCtx.isDone(&play->transitionCtx.data)) { if (play->transitionCtx.transitionType >= TRANS_TYPE_MAX) { if (play->transitionTrigger == TRANS_TRIGGER_END) { - play->transitionCtx.destroy(&play->transitionCtx); + play->transitionCtx.destroy(&play->transitionCtx.data); func_800BC88C(play); play->transitionMode = TRANS_MODE_OFF; } } else if (play->transitionTrigger != TRANS_TRIGGER_END) { - play->state.running = 0; - if (gSaveContext.gameMode != 2) { + play->state.running = false; + + if (gSaveContext.gameMode != GAMEMODE_FILE_SELECT) { SET_NEXT_GAMESTATE(&play->state, Play_Init, PlayState); gSaveContext.entranceIndex = play->nextEntranceIndex; + if (gSaveContext.minigameState == 1) { gSaveContext.minigameState = 3; } @@ -846,18 +919,20 @@ void Play_Update(PlayState* play) { SET_NEXT_GAMESTATE(&play->state, FileChoose_Init, FileChooseContext); } } else { - play->transitionCtx.destroy(&play->transitionCtx); + play->transitionCtx.destroy(&play->transitionCtx.data); func_800BC88C(play); play->transitionMode = TRANS_MODE_OFF; + if (gTrnsnUnkState == 3) { TransitionUnk_Destroy(&sTrnsnUnk); gTrnsnUnkState = 0; R_UPDATE_RATE = 3; } - + // Transition end for standard transitions GameInteractor_ExecuteOnTransitionEndHooks(play->sceneNum); } + play->transitionTrigger = TRANS_TRIGGER_OFF; } else { play->transitionCtx.update(&play->transitionCtx.data, R_UPDATE_RATE); @@ -865,13 +940,15 @@ void Play_Update(PlayState* play) { break; } + // update non-instance transitions switch (play->transitionMode) { case TRANS_MODE_FILL_WHITE_INIT: - D_801614C8 = 0; + sTransitionFillTimer = 0; play->envCtx.fillScreen = true; play->envCtx.screenFillColor[0] = 160; play->envCtx.screenFillColor[1] = 160; play->envCtx.screenFillColor[2] = 160; + if (play->transitionTrigger != TRANS_TRIGGER_END) { play->envCtx.screenFillColor[3] = 0; play->transitionMode = TRANS_MODE_FILL_IN; @@ -882,37 +959,40 @@ void Play_Update(PlayState* play) { break; case TRANS_MODE_FILL_IN: - play->envCtx.screenFillColor[3] = (D_801614C8 / 20.0f) * 255.0f; - if (D_801614C8 >= 20 && 1) { - play->state.running = 0; + play->envCtx.screenFillColor[3] = (sTransitionFillTimer / 20.0f) * 255.0f; + + if (sTransitionFillTimer >= 20) { + play->state.running = false; SET_NEXT_GAMESTATE(&play->state, Play_Init, PlayState); gSaveContext.entranceIndex = play->nextEntranceIndex; play->transitionTrigger = TRANS_TRIGGER_OFF; play->transitionMode = TRANS_MODE_OFF; } else { - D_801614C8++; + sTransitionFillTimer++; } break; case TRANS_MODE_FILL_OUT: - play->envCtx.screenFillColor[3] = (1 - D_801614C8 / 20.0f) * 255.0f; - if (D_801614C8 >= 20 && 1) { + play->envCtx.screenFillColor[3] = (1 - sTransitionFillTimer / 20.0f) * 255.0f; + + if (sTransitionFillTimer >= 20) { gTrnsnUnkState = 0; R_UPDATE_RATE = 3; play->transitionTrigger = TRANS_TRIGGER_OFF; play->transitionMode = TRANS_MODE_OFF; play->envCtx.fillScreen = false; } else { - D_801614C8++; + sTransitionFillTimer++; } break; case TRANS_MODE_FILL_BROWN_INIT: - D_801614C8 = 0; + sTransitionFillTimer = 0; play->envCtx.fillScreen = true; play->envCtx.screenFillColor[0] = 170; play->envCtx.screenFillColor[1] = 160; play->envCtx.screenFillColor[2] = 150; + if (play->transitionTrigger != TRANS_TRIGGER_END) { play->envCtx.screenFillColor[3] = 0; play->transitionMode = TRANS_MODE_FILL_IN; @@ -956,8 +1036,9 @@ void Play_Update(PlayState* play) { break; case TRANS_MODE_SANDSTORM: - Audio_PlaySoundGeneral(NA_SE_EV_SAND_STORM - SFX_FLAG, &D_801333D4, 4, &D_801333E0, &D_801333E0, - &D_801333E8); + Audio_PlaySoundGeneral(NA_SE_EV_SAND_STORM - SFX_FLAG, &D_801333D4, 4, + &D_801333E0, &D_801333E0, &D_801333E8); + if (play->transitionTrigger == TRANS_TRIGGER_END) { if (play->envCtx.sandstormPrimA < 110) { gTrnsnUnkState = 0; @@ -970,7 +1051,7 @@ void Play_Update(PlayState* play) { } } else { if (play->envCtx.sandstormEnvA == 255) { - play->state.running = 0; + play->state.running = false; SET_NEXT_GAMESTATE(&play->state, Play_Init, PlayState); gSaveContext.entranceIndex = play->nextEntranceIndex; play->transitionTrigger = TRANS_TRIGGER_OFF; @@ -993,8 +1074,9 @@ void Play_Update(PlayState* play) { break; case TRANS_MODE_SANDSTORM_END: - Audio_PlaySoundGeneral(NA_SE_EV_SAND_STORM - SFX_FLAG, &D_801333D4, 4, &D_801333E0, &D_801333E0, - &D_801333E8); + Audio_PlaySoundGeneral(NA_SE_EV_SAND_STORM - SFX_FLAG, &D_801333D4, 4, + &D_801333E0, &D_801333E0, &D_801333E8); + if (play->transitionTrigger == TRANS_TRIGGER_END) { if (play->envCtx.sandstormPrimA <= 0) { gTrnsnUnkState = 0; @@ -1009,7 +1091,7 @@ void Play_Update(PlayState* play) { break; case TRANS_MODE_CS_BLACK_FILL_INIT: - D_801614C8 = 0; + sTransitionFillTimer = 0; play->envCtx.fillScreen = true; play->envCtx.screenFillColor[0] = 0; play->envCtx.screenFillColor[1] = 0; @@ -1021,7 +1103,8 @@ void Play_Update(PlayState* play) { case TRANS_MODE_CS_BLACK_FILL: if (gSaveContext.cutsceneTransitionControl != 0) { play->envCtx.screenFillColor[3] = gSaveContext.cutsceneTransitionControl; - if (gSaveContext.cutsceneTransitionControl < 0x65) { + + if (gSaveContext.cutsceneTransitionControl <= 100) { gTrnsnUnkState = 0; R_UPDATE_RATE = 3; play->transitionTrigger = TRANS_TRIGGER_OFF; @@ -1032,48 +1115,33 @@ void Play_Update(PlayState* play) { } } - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } + PLAY_LOG(3533); if (1 && (gTrnsnUnkState != 3)) { - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } + PLAY_LOG(3542); - if ((gSaveContext.gameMode == 0) && (play->msgCtx.msgMode == MSGMODE_NONE) && + if ((gSaveContext.gameMode == GAMEMODE_NORMAL) && (play->msgCtx.msgMode == MSGMODE_NONE) && (play->gameOverCtx.state == GAMEOVER_INACTIVE)) { KaleidoSetup_Update(play); } - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } - - sp80 = (play->pauseCtx.state != 0) || (play->pauseCtx.debugState != 0); - - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } + PLAY_LOG(3551); + isPaused = (play->pauseCtx.state != 0) || (play->pauseCtx.debugState != 0); + PLAY_LOG(3555); AnimationContext_Reset(&play->animationCtx); - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } - + PLAY_LOG(3561); Object_UpdateBank(&play->objectCtx); - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } + PLAY_LOG(3577); - if ((sp80 == 0) && (IREG(72) == 0)) { - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } + if (!isPaused && (IREG(72) == 0)) { + PLAY_LOG(3580); play->gameplayFrames++; + func_800AA178(true); + // Gameplay stat tracking if (!gSaveContext.sohStats.gameComplete && (!IS_BOSS_RUSH || !gSaveContext.isBossRushPaused)) { @@ -1086,12 +1154,10 @@ void Play_Update(PlayState* play) { } } - func_800AA178(1); - if (play->actorCtx.freezeFlashTimer && (play->actorCtx.freezeFlashTimer-- < 5)) { osSyncPrintf("FINISH=%d\n", play->actorCtx.freezeFlashTimer); - if ((play->actorCtx.freezeFlashTimer > 0) && - ((play->actorCtx.freezeFlashTimer % 2) != 0)) { + + if ((play->actorCtx.freezeFlashTimer > 0) && ((play->actorCtx.freezeFlashTimer % 2) != 0)) { play->envCtx.fillScreen = true; play->envCtx.screenFillColor[0] = play->envCtx.screenFillColor[1] = play->envCtx.screenFillColor[2] = 150; @@ -1100,91 +1166,52 @@ void Play_Update(PlayState* play) { play->envCtx.fillScreen = false; } } else { - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } - + PLAY_LOG(3606); func_800973FC(play, &play->roomCtx); - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } - + PLAY_LOG(3612); CollisionCheck_AT(play, &play->colChkCtx); - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } - + PLAY_LOG(3618); CollisionCheck_OC(play, &play->colChkCtx); - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } - + PLAY_LOG(3624); CollisionCheck_Damage(play, &play->colChkCtx); - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } - + PLAY_LOG(3631); CollisionCheck_ClearContext(play, &play->colChkCtx); - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } + PLAY_LOG(3637); - if (play->unk_11DE9 == 0) { + if (!play->unk_11DE9) { Actor_UpdateAll(play, &play->actorCtx); } - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } - + PLAY_LOG(3643); func_80064558(play, &play->csCtx); - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } - + PLAY_LOG(3648); func_800645A0(play, &play->csCtx); - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } - + PLAY_LOG(3651); Effect_UpdateAll(play); - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } - + PLAY_LOG(3657); EffectSs_UpdateAll(play); - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } + PLAY_LOG(3662); } } else { - func_800AA178(0); - } - - if (1 && HREG(63)) { - LOG_NUM("1", 1); + func_800AA178(false); } + PLAY_LOG(3672); func_80095AA0(play, &play->roomCtx.curRoom, &input[1], 0); - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } - + PLAY_LOG(3675); func_80095AA0(play, &play->roomCtx.prevRoom, &input[1], 1); - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } + PLAY_LOG(3677); if (play->unk_1242B != 0) { if (CHECK_BTN_ALL(input[0].press.button, BTN_CUP)) { @@ -1195,124 +1222,82 @@ void Play_Update(PlayState* play) { // "Changing viewpoint is prohibited during the cutscene" osSyncPrintf(VT_FGCOL(CYAN) "デモ中につき視点変更を禁止しております\n" VT_RST); } else if (YREG(15) == 0x10) { - Audio_PlaySoundGeneral(NA_SE_SY_ERROR, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8); + Audio_PlaySoundGeneral(NA_SE_SY_ERROR, &D_801333D4, 4, &D_801333E0, + &D_801333E0, &D_801333E8); } else { - func_800BC490(play, play->unk_1242B ^ 3); + // C-Up toggle for houses, move between pivot camera and fixed camera + // Toggle viewpoint between VIEWPOINT_LOCKED and VIEWPOINT_PIVOT + Play_SetViewpoint(play, play->unk_1242B ^ 3); } } - func_800BC450(play); - } - - if (1 && HREG(63)) { - LOG_NUM("1", 1); + + Play_RequestViewpointBgCam(play); } + PLAY_LOG(3708); SkyboxDraw_Update(&play->skyboxCtx); - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } + PLAY_LOG(3716); if ((play->pauseCtx.state != 0) || (play->pauseCtx.debugState != 0)) { - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } - + PLAY_LOG(3721); KaleidoScopeCall_Update(play); } else if (play->gameOverCtx.state != GAMEOVER_INACTIVE) { - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } - + PLAY_LOG(3727); GameOver_Update(play); } else { - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } - + PLAY_LOG(3733); Message_Update(play); } - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } - - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } + PLAY_LOG(3737); + PLAY_LOG(3742); Interface_Update(play); - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } - + PLAY_LOG(3765); AnimationContext_Update(play, &play->animationCtx); - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } - + PLAY_LOG(3771); SoundSource_UpdateAll(play); - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } - + PLAY_LOG(3777); ShrinkWindow_Update(R_UPDATE_RATE); - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } - + PLAY_LOG(3783); TransitionFade_Update(&play->transitionFade, R_UPDATE_RATE); } else { goto skip; } } - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } + PLAY_LOG(3799); skip: - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } + PLAY_LOG(3801); - if ((sp80 == 0) || (gDbgCamEnabled)) { - s32 pad3[5]; + if (!isPaused || gDbgCamEnabled) { s32 i; play->nextCamera = play->activeCamera; - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } + PLAY_LOG(3806); for (i = 0; i < NUM_CAMS; i++) { if ((i != play->nextCamera) && (play->cameraPtrs[i] != NULL)) { - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } - + PLAY_LOG(3809); Camera_Update(play->cameraPtrs[i]); } } Camera_Update(play->cameraPtrs[play->nextCamera]); - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } + PLAY_LOG(3814); } - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } - - Environment_Update(play, &play->envCtx, &play->lightCtx, &play->pauseCtx, &play->msgCtx, - &play->gameOverCtx, play->state.gfxCtx); + PLAY_LOG(3816); + Environment_Update(play, &play->envCtx, &play->lightCtx, &play->pauseCtx, &play->msgCtx, &play->gameOverCtx, + play->state.gfxCtx); } void Play_DrawOverlayElements(PlayState* play) { @@ -1320,7 +1305,7 @@ void Play_DrawOverlayElements(PlayState* play) { KaleidoScopeCall_Draw(play); } - if (gSaveContext.gameMode == 0) { + if (gSaveContext.gameMode == GAMEMODE_NORMAL) { Interface_Draw(play); } @@ -1405,12 +1390,11 @@ void Play_Draw(PlayState* play) { Matrix_Mult(&play->billboardMtxF, MTXMODE_APPLY); Matrix_Get(&play->viewProjectionMtxF); play->billboardMtxF.mf[0][3] = play->billboardMtxF.mf[1][3] = play->billboardMtxF.mf[2][3] = - play->billboardMtxF.mf[3][0] = play->billboardMtxF.mf[3][1] = play->billboardMtxF.mf[3][2] = - 0.0f; + play->billboardMtxF.mf[3][0] = play->billboardMtxF.mf[3][1] = play->billboardMtxF.mf[3][2] = 0.0f; // This transpose is where the viewing matrix is properly converted into a billboard matrix Matrix_Transpose(&play->billboardMtxF); play->billboardMtx = Matrix_MtxFToMtx(MATRIX_CHECKFLOATS(&play->billboardMtxF), - Graph_Alloc(gfxCtx, sizeof(Mtx))); + Graph_Alloc(gfxCtx, sizeof(Mtx))); gSPSegment(POLY_OPA_DISP++, 0x01, play->billboardMtx); @@ -1422,7 +1406,8 @@ void Play_Draw(PlayState* play) { gSPDisplayList(OVERLAY_DISP++, gfxP); gSPGrayscale(gfxP++, false); - if ((play->transitionMode == TRANS_MODE_INSTANCE_RUNNING) || (play->transitionMode == TRANS_MODE_INSTANCE_WAIT) || + if ((play->transitionMode == TRANS_MODE_INSTANCE_RUNNING) || + (play->transitionMode == TRANS_MODE_INSTANCE_WAIT) || (play->transitionCtx.transitionType >= TRANS_TYPE_MAX)) { View view; @@ -1438,6 +1423,8 @@ void Play_Draw(PlayState* play) { TransitionFade_Draw(&play->transitionFade, &gfxP); if (D_801614B0.a > 0) { + // gPlayVisMono.vis.primColor.rgba = D_801614B0.rgba; + // VisMono_Draw(&gPlayVisMono, &gfxP); gDPSetGrayscaleColor(gfxP++, D_801614B0.r, D_801614B0.g, D_801614B0.b, D_801614B0.a); gSPGrayscale(gfxP++, true); } @@ -1453,189 +1440,191 @@ void Play_Draw(PlayState* play) { TransitionUnk_Draw(&sTrnsnUnk, &sp88); POLY_OPA_DISP = sp88; goto Play_Draw_DrawOverlayElements; - } else { - PreRender_SetValues(&play->pauseBgPreRender, SCREEN_WIDTH, SCREEN_HEIGHT, gfxCtx->curFrameBuffer, - gZBuffer); + } - if (R_PAUSE_MENU_MODE == 2) { - MsgEvent_SendNullTask(); - PreRender_Calc(&play->pauseBgPreRender); - R_PAUSE_MENU_MODE = 3; - } else if (R_PAUSE_MENU_MODE >= 4) { - R_PAUSE_MENU_MODE = 0; - } + PreRender_SetValues(&play->pauseBgPreRender, SCREEN_WIDTH, SCREEN_HEIGHT, gfxCtx->curFrameBuffer, gZBuffer); - if (R_PAUSE_MENU_MODE == 3) { - Gfx* sp84 = POLY_OPA_DISP; + if (R_PAUSE_MENU_MODE == 2) { + // Wait for the previous frame's display list to be processed, + // so that `pauseBgPreRender.fbufSave` and `pauseBgPreRender.cvgSave` are filled with the appropriate + // content and can be used by `PreRender_ApplyFilters` below. + MsgEvent_SendNullTask(); - // SOH [Port] Draw game framebuffer using our custom handling - //func_800C24BC(&play->pauseBgPreRender, &sp84); - FB_DrawFromFramebuffer(&sp84, gPauseFrameBuffer, 255); - POLY_OPA_DISP = sp84; + PreRender_Calc(&play->pauseBgPreRender); - goto Play_Draw_DrawOverlayElements; - } else { - s32 sp80; + R_PAUSE_MENU_MODE = 3; + } else if (R_PAUSE_MENU_MODE >= 4) { + R_PAUSE_MENU_MODE = 0; + } - if ((HREG(80) != 10) || (HREG(83) != 0)) { - if (play->skyboxId && (play->skyboxId != SKYBOX_UNSET_1D) && - !play->envCtx.skyboxDisabled) { - if ((play->skyboxId == SKYBOX_NORMAL_SKY) || - (play->skyboxId == SKYBOX_CUTSCENE_MAP)) { - Environment_UpdateSkybox(play, play->skyboxId, &play->envCtx, &play->skyboxCtx); + if (R_PAUSE_MENU_MODE == 3) { + Gfx* gfxP = POLY_OPA_DISP; - SkyboxDraw_Draw(&play->skyboxCtx, gfxCtx, play->skyboxId, - play->envCtx.skyboxBlend, play->view.eye.x, play->view.eye.y, - play->view.eye.z); - } else if (play->skyboxCtx.unk_140 == 0) { - SkyboxDraw_Draw(&play->skyboxCtx, gfxCtx, play->skyboxId, 0, - play->view.eye.x, play->view.eye.y, play->view.eye.z); - } - } - } + // SOH [Port] Draw game framebuffer using our custom handling + //func_800C24BC(&play->pauseBgPreRender, &gfxP); + FB_DrawFromFramebuffer(&gfxP, gPauseFrameBuffer, 255); + POLY_OPA_DISP = gfxP; - if ((HREG(80) != 10) || (HREG(90) & 2)) { - if (!play->envCtx.sunMoonDisabled) { - Environment_DrawSunAndMoon(play); - } - } + goto Play_Draw_DrawOverlayElements; + } - if ((HREG(80) != 10) || (HREG(90) & 1)) { - Environment_DrawSkyboxFilters(play); - } - - if ((HREG(80) != 10) || (HREG(90) & 4)) { - Environment_UpdateLightningStrike(play); - Environment_DrawLightning(play, 0); - } - - if ((HREG(80) != 10) || (HREG(90) & 8)) { - sp228 = LightContext_NewLights(&play->lightCtx, gfxCtx); - Lights_BindAll(sp228, play->lightCtx.listHead, NULL); - Lights_Draw(sp228, gfxCtx); - } - - if ((HREG(80) != 10) || (HREG(84) != 0)) { - if (VREG(94) == 0) { - if (HREG(80) != 10) { - sp80 = 3; - } else { - sp80 = HREG(84); - } - Scene_Draw(play); - Room_Draw(play, &play->roomCtx.curRoom, sp80 & 3); - Room_Draw(play, &play->roomCtx.prevRoom, sp80 & 3); - } - } - - if ((HREG(80) != 10) || (HREG(83) != 0)) { - if ((play->skyboxCtx.unk_140 != 0) && - (GET_ACTIVE_CAM(play)->setting != CAM_SET_PREREND_FIXED)) { - Vec3f sp74; - - Camera_GetSkyboxOffset(&sp74, GET_ACTIVE_CAM(play)); - SkyboxDraw_Draw(&play->skyboxCtx, gfxCtx, play->skyboxId, 0, - play->view.eye.x + sp74.x, play->view.eye.y + sp74.y, - play->view.eye.z + sp74.z); - } - } - - if (play->envCtx.unk_EE[1] != 0) { - Environment_DrawRain(play, &play->view, gfxCtx); - } - - if ((HREG(80) != 10) || (HREG(84) != 0)) { - Environment_FillScreen(gfxCtx, 0, 0, 0, play->unk_11E18, FILL_SCREEN_OPA); - } - - if ((HREG(80) != 10) || (HREG(85) != 0)) { - func_800315AC(play, &play->actorCtx); - } - - if ((HREG(80) != 10) || (HREG(86) != 0)) { - if (!play->envCtx.sunMoonDisabled) { - sp21C.x = play->view.eye.x + play->envCtx.sunPos.x; - sp21C.y = play->view.eye.y + play->envCtx.sunPos.y; - sp21C.z = play->view.eye.z + play->envCtx.sunPos.z; - Environment_DrawSunLensFlare(play, &play->envCtx, &play->view, gfxCtx, sp21C, 0); - } - Environment_DrawCustomLensFlare(play); - } - - if ((HREG(80) != 10) || (HREG(87) != 0)) { - if (MREG(64) != 0) { - Environment_FillScreen(gfxCtx, MREG(65), MREG(66), MREG(67), MREG(68), - FILL_SCREEN_OPA | FILL_SCREEN_XLU); - } - - switch (play->envCtx.fillScreen) { - case 1: - Environment_FillScreen( - gfxCtx, play->envCtx.screenFillColor[0], play->envCtx.screenFillColor[1], - play->envCtx.screenFillColor[2], play->envCtx.screenFillColor[3], - FILL_SCREEN_OPA | FILL_SCREEN_XLU); - break; - default: - break; - } - } - - if ((HREG(80) != 10) || (HREG(88) != 0)) { - if (play->envCtx.sandstormState != SANDSTORM_OFF) { - Environment_DrawSandstorm(play, play->envCtx.sandstormState); - } - } - - if ((HREG(80) != 10) || (HREG(93) != 0)) { - DebugDisplay_DrawObjects(play); - } - - if ((R_PAUSE_MENU_MODE == 1) || (gTrnsnUnkState == 1)) { - Gfx* sp70 = OVERLAY_DISP; - - play->pauseBgPreRender.fbuf = gfxCtx->curFrameBuffer; - play->pauseBgPreRender.fbufSave = (u16*)gZBuffer; - // SOH [Port] Use our custom copy method instead of the prerender system - // func_800C1F20(&play->pauseBgPreRender, &sp70); - if (R_PAUSE_MENU_MODE == 1) { - play->pauseBgPreRender.cvgSave = (u8*)gfxCtx->curFrameBuffer; - // func_800C20B4(&play->pauseBgPreRender, &sp70); - R_PAUSE_MENU_MODE = 2; - - // #region SOH [Port] Custom handling for pause prerender background capture - lastPauseWidth = OTRGetGameRenderWidth(); - lastPauseHeight = OTRGetGameRenderHeight(); - hasCapturedPauseBuffer = false; - - FB_CopyToFramebuffer(&sp70, 0, gPauseFrameBuffer, false, &hasCapturedPauseBuffer); - - // Set the state back to ready after the recapture is done - if (recapturePauseBuffer) { - R_PAUSE_MENU_MODE = 3; - } - // #endregion - } else { - gTrnsnUnkState = 2; - } - OVERLAY_DISP = sp70; - play->unk_121C7 = 2; - SREG(33) |= 1; - - // 2S2H [Port] Continue to render the post world for pausing to avoid flashing the HUD - if (!(gTrnsnUnkState == 1)) { - goto Play_Draw_DrawOverlayElements; - } - } else if (R_PAUSE_MENU_MODE != 3) { - Play_Draw_DrawOverlayElements: - if ((HREG(80) != 10) || (HREG(89) != 0)) { - Play_DrawOverlayElements(play); - } + if ((HREG(80) != 10) || (HREG(83) != 0)) { + if (play->skyboxId && (play->skyboxId != SKYBOX_UNSET_1D) && !play->envCtx.skyboxDisabled) { + if ((play->skyboxId == SKYBOX_NORMAL_SKY) || (play->skyboxId == SKYBOX_CUTSCENE_MAP)) { + Environment_UpdateSkybox(play, play->skyboxId, &play->envCtx, &play->skyboxCtx); + SkyboxDraw_Draw(&play->skyboxCtx, gfxCtx, play->skyboxId, play->envCtx.skyboxBlend, + play->view.eye.x, play->view.eye.y, play->view.eye.z); + } else if (play->skyboxCtx.unk_140 == 0) { + SkyboxDraw_Draw(&play->skyboxCtx, gfxCtx, play->skyboxId, 0, play->view.eye.x, play->view.eye.y, + play->view.eye.z); } } } + if ((HREG(80) != 10) || (HREG(90) & 2)) { + if (!play->envCtx.sunMoonDisabled) { + Environment_DrawSunAndMoon(play); + } + } + + if ((HREG(80) != 10) || (HREG(90) & 1)) { + Environment_DrawSkyboxFilters(play); + } + + if ((HREG(80) != 10) || (HREG(90) & 4)) { + Environment_UpdateLightningStrike(play); + Environment_DrawLightning(play, 0); + } + + if ((HREG(80) != 10) || (HREG(90) & 8)) { + sp228 = LightContext_NewLights(&play->lightCtx, gfxCtx); + Lights_BindAll(sp228, play->lightCtx.listHead, NULL); + Lights_Draw(sp228, gfxCtx); + } + + if ((HREG(80) != 10) || (HREG(84) != 0)) { + if (VREG(94) == 0) { + s32 roomDrawFlags; + + if (HREG(80) != 10) { + roomDrawFlags = 3; + } else { + roomDrawFlags = HREG(84); + } + Scene_Draw(play); + Room_Draw(play, &play->roomCtx.curRoom, roomDrawFlags & 3); + Room_Draw(play, &play->roomCtx.prevRoom, roomDrawFlags & 3); + } + } + + if ((HREG(80) != 10) || (HREG(83) != 0)) { + if ((play->skyboxCtx.unk_140 != 0) && + (GET_ACTIVE_CAM(play)->setting != CAM_SET_PREREND_FIXED)) { + Vec3f quakeOffset; + + Camera_GetSkyboxOffset(&quakeOffset, GET_ACTIVE_CAM(play)); + SkyboxDraw_Draw(&play->skyboxCtx, gfxCtx, play->skyboxId, 0, play->view.eye.x + quakeOffset.x, + play->view.eye.y + quakeOffset.y, play->view.eye.z + quakeOffset.z); + } + } + + if (play->envCtx.unk_EE[1] != 0) { + Environment_DrawRain(play, &play->view, gfxCtx); + } + + if ((HREG(80) != 10) || (HREG(84) != 0)) { + Environment_FillScreen(gfxCtx, 0, 0, 0, play->unk_11E18, FILL_SCREEN_OPA); + } + + if ((HREG(80) != 10) || (HREG(85) != 0)) { + func_800315AC(play, &play->actorCtx); + } + + if ((HREG(80) != 10) || (HREG(86) != 0)) { + if (!play->envCtx.sunMoonDisabled) { + sp21C.x = play->view.eye.x + play->envCtx.sunPos.x; + sp21C.y = play->view.eye.y + play->envCtx.sunPos.y; + sp21C.z = play->view.eye.z + play->envCtx.sunPos.z; + Environment_DrawSunLensFlare(play, &play->envCtx, &play->view, gfxCtx, sp21C, 0); + } + Environment_DrawCustomLensFlare(play); + } + + if ((HREG(80) != 10) || (HREG(87) != 0)) { + if (MREG(64) != 0) { + Environment_FillScreen(gfxCtx, MREG(65), MREG(66), MREG(67), MREG(68), + FILL_SCREEN_OPA | FILL_SCREEN_XLU); + } + + switch (play->envCtx.fillScreen) { + case 1: + Environment_FillScreen(gfxCtx, play->envCtx.screenFillColor[0], play->envCtx.screenFillColor[1], + play->envCtx.screenFillColor[2], play->envCtx.screenFillColor[3], + FILL_SCREEN_OPA | FILL_SCREEN_XLU); + break; + default: + break; + } + } + + if ((HREG(80) != 10) || (HREG(88) != 0)) { + if (play->envCtx.sandstormState != SANDSTORM_OFF) { + Environment_DrawSandstorm(play, play->envCtx.sandstormState); + } + } + + if ((HREG(80) != 10) || (HREG(93) != 0)) { + DebugDisplay_DrawObjects(play); + } + + if ((R_PAUSE_MENU_MODE == 1) || (gTrnsnUnkState == 1)) { + Gfx* gfxP = OVERLAY_DISP; + + // Copy the frame buffer contents at this point in the display list to the zbuffer + // The zbuffer must then stay untouched until unpausing + play->pauseBgPreRender.fbuf = gfxCtx->curFrameBuffer; + play->pauseBgPreRender.fbufSave = (u16*)gZBuffer; + // SOH [Port] Use our custom copy method instead of the prerender system + // func_800C1F20(&play->pauseBgPreRender, &gfxP); + if (R_PAUSE_MENU_MODE == 1) { + play->pauseBgPreRender.cvgSave = (u8*)gfxCtx->curFrameBuffer; + // func_800C20B4(&play->pauseBgPreRender, &gfxP); + R_PAUSE_MENU_MODE = 2; + + // #region SOH [Port] Custom handling for pause prerender background capture + lastPauseWidth = OTRGetGameRenderWidth(); + lastPauseHeight = OTRGetGameRenderHeight(); + hasCapturedPauseBuffer = false; + + FB_CopyToFramebuffer(&gfxP, 0, gPauseFrameBuffer, false, &hasCapturedPauseBuffer); + + // Set the state back to ready after the recapture is done + if (recapturePauseBuffer) { + R_PAUSE_MENU_MODE = 3; + } + // #endregion + } else { + gTrnsnUnkState = 2; + } + OVERLAY_DISP = gfxP; + play->unk_121C7 = 2; + SREG(33) |= 1; + + // 2S2H [Port] Continue to render the post world for pausing to avoid flashing the HUD + if (gTrnsnUnkState == 2) { + goto Play_Draw_skip; + } + } + + // Draw Enhancements that need to be placed in the world. This happens before the PostWorldDraw + // so that they aren't drawn when the pause menu is up (e.g. collision viewer, actor name tags) GameInteractor_ExecuteOnPlayDrawEnd(); + Play_Draw_DrawOverlayElements: + if ((HREG(80) != 10) || (HREG(89) != 0)) { + Play_DrawOverlayElements(play); + } + // Reset the inverted culling if (CVarGetInteger(CVAR_ENHANCEMENT("MirroredWorld"), 0)) { gSPClearExtraGeometryMode(POLY_OPA_DISP++, G_EX_INVERT_CULLING); @@ -1643,13 +1632,14 @@ void Play_Draw(PlayState* play) { } } +Play_Draw_skip: + if (play->view.unk_124 != 0) { Camera_Update(GET_ACTIVE_CAM(play)); func_800AB944(&play->view); play->view.unk_124 = 0; if (play->skyboxId && (play->skyboxId != SKYBOX_UNSET_1D) && !play->envCtx.skyboxDisabled) { - SkyboxDraw_UpdateMatrix(&play->skyboxCtx, play->view.eye.x, play->view.eye.y, - play->view.eye.z); + SkyboxDraw_UpdateMatrix(&play->skyboxCtx, play->view.eye.x, play->view.eye.y, play->view.eye.z); } } @@ -1689,9 +1679,7 @@ void Play_Main(GameState* thisx) { DebugDisplay_Init(); - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } + PLAY_LOG(4556); if ((HREG(80) == 10) && (HREG(94) != 10)) { HREG(81) = 1; @@ -1714,18 +1702,14 @@ void Play_Main(GameState* thisx) { Play_Update(play); } - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } + PLAY_LOG(4583); FrameInterpolation_StartRecord(); Play_Draw(play); FrameInterpolation_StopRecord(); - if (1 && HREG(63)) { - LOG_NUM("1", 1); - } - + PLAY_LOG(4587); + if (CVarGetInteger(CVAR_CHEAT("TimeSync"), 0)) { const int maxRealDaySeconds = 86400; const int maxInGameDayTicks = 65536; @@ -1750,23 +1734,18 @@ s32 Play_InCsMode(PlayState* play) { return (play->csCtx.state != CS_STATE_IDLE) || Player_InCsMode(play); } -f32 func_800BFCB8(PlayState* play, MtxF* mf, Vec3f* vec) { +f32 func_800BFCB8(PlayState* play, MtxF* mf, Vec3f* pos) { CollisionPoly poly; f32 temp1; f32 temp2; f32 temp3; - f32 floorY; - f32 nx; - f32 ny; - f32 nz; - s32 pad[5]; - - floorY = BgCheck_AnyRaycastFloor1(&play->colCtx, &poly, vec); + f32 floorY = BgCheck_AnyRaycastFloor1(&play->colCtx, &poly, pos); if (floorY > BGCHECK_Y_MIN) { - nx = COLPOLY_GET_NORMAL(poly.normal.x); - ny = COLPOLY_GET_NORMAL(poly.normal.y); - nz = COLPOLY_GET_NORMAL(poly.normal.z); + f32 nx = COLPOLY_GET_NORMAL(poly.normal.x); + f32 ny = COLPOLY_GET_NORMAL(poly.normal.y); + f32 nz = COLPOLY_GET_NORMAL(poly.normal.z); + s32 pad[5]; temp1 = sqrtf(1.0f - SQ(nx)); @@ -1790,9 +1769,9 @@ f32 func_800BFCB8(PlayState* play, MtxF* mf, Vec3f* vec) { mf->wy = 0.0f; mf->xz = 0.0f; mf->wz = 0.0f; - mf->xw = vec->x; + mf->xw = pos->x; mf->yw = floorY; - mf->zw = vec->z; + mf->zw = pos->z; mf->ww = 1.0f; } else { mf->xy = 0.0f; @@ -1807,9 +1786,9 @@ f32 func_800BFCB8(PlayState* play, MtxF* mf, Vec3f* vec) { mf->yz = 0.0f; mf->zy = 0.0f; mf->yy = 1.0f; - mf->xw = vec->x; - mf->yw = vec->y; - mf->zw = vec->z; + mf->xw = pos->x; + mf->yw = pos->y; + mf->zw = pos->z; mf->ww = 1.0f; } @@ -1832,16 +1811,18 @@ void Play_InitEnvironment(PlayState* play, s16 skyboxId) { Environment_Init(play, &play->envCtx, 0); } -void Play_InitScene(PlayState* play, s32 spawn) -{ +void Play_InitScene(PlayState* play, s32 spawn) { play->curSpawn = spawn; + play->linkActorEntry = NULL; play->unk_11DFC = NULL; play->setupEntranceList = NULL; play->setupExitList = NULL; play->cUpElfMsgs = NULL; play->setupPathList = NULL; + play->numSetupActors = 0; + Object_InitBank(play, &play->objectCtx); LightContext_Init(play, &play->lightCtx); TransitionActor_InitContext(&play->state, &play->transiActorCtx); @@ -1852,29 +1833,28 @@ void Play_InitScene(PlayState* play, s32 spawn) Play_InitEnvironment(play, play->skyboxId); } -void Play_SpawnScene(PlayState* play, s32 sceneNum, s32 spawn) { +void Play_SpawnScene(PlayState* play, s32 sceneId, s32 spawn) { uint8_t mqMode = CVarGetInteger(CVAR_GENERAL("BetterDebugWarpScreenMQMode"), WARP_MODE_OVERRIDE_OFF); int16_t mqModeScene = CVarGetInteger(CVAR_GENERAL("BetterDebugWarpScreenMQModeScene"), -1); - if (mqMode != WARP_MODE_OVERRIDE_OFF && sceneNum != mqModeScene) { + if (mqMode != WARP_MODE_OVERRIDE_OFF && sceneId != mqModeScene) { CVarClear(CVAR_GENERAL("BetterDebugWarpScreenMQMode")); CVarClear(CVAR_GENERAL("BetterDebugWarpScreenMQModeScene")); } - OTRPlay_SpawnScene(play, sceneNum, spawn); + OTRPlay_SpawnScene(play, sceneId, spawn); } void func_800C016C(PlayState* play, Vec3f* src, Vec3f* dest) { - f32 temp; + f32 w; Matrix_Mult(&play->viewProjectionMtxF, MTXMODE_NEW); Matrix_MultVec3f(src, dest); - temp = play->viewProjectionMtxF.ww + - (play->viewProjectionMtxF.wx * src->x + play->viewProjectionMtxF.wy * src->y + - play->viewProjectionMtxF.wz * src->z); + w = play->viewProjectionMtxF.ww + (play->viewProjectionMtxF.wx * src->x + play->viewProjectionMtxF.wy * src->y + + play->viewProjectionMtxF.wz * src->z); - dest->x = 160.0f + ((dest->x / temp) * 160.0f); - dest->y = 120.0f + ((dest->y / temp) * 120.0f); + dest->x = (SCREEN_WIDTH / 2) + ((dest->x / w) * (SCREEN_WIDTH / 2)); + dest->y = (SCREEN_HEIGHT / 2) - ((dest->y / w) * (SCREEN_HEIGHT / 2)); } s16 Play_CreateSubCamera(PlayState* play) { @@ -2097,8 +2077,8 @@ void Play_SaveSceneFlags(PlayState* play) { savedSceneFlags->collect = play->actorCtx.flags.collect; } -void Play_SetRespawnData(PlayState* play, s32 respawnMode, s16 entranceIndex, s32 roomIndex, - s32 playerParams, Vec3f* pos, s16 yaw) { +void Play_SetRespawnData(PlayState* play, s32 respawnMode, s16 entranceIndex, s32 roomIndex, s32 playerParams, + Vec3f* pos, s16 yaw) { RespawnData* respawnData = &gSaveContext.respawn[respawnMode]; respawnData->entranceIndex = entranceIndex; @@ -2118,8 +2098,8 @@ void Play_SetupRespawnPoint(PlayState* play, s32 respawnMode, s32 playerParams) if ((play->sceneNum != SCENE_FAIRYS_FOUNTAIN) && (play->sceneNum != SCENE_GROTTOS)) { roomIndex = play->roomCtx.curRoom.num; entranceIndex = gSaveContext.entranceIndex; - Play_SetRespawnData(play, respawnMode, entranceIndex, roomIndex, playerParams, - &player->actor.world.pos, player->actor.shape.rot.y); + Play_SetRespawnData(play, respawnMode, entranceIndex, roomIndex, playerParams, &player->actor.world.pos, + player->actor.shape.rot.y); } } @@ -2136,12 +2116,15 @@ void Play_LoadToLastEntrance(PlayState* play) { gSaveContext.respawnFlag = -1; play->transitionTrigger = TRANS_TRIGGER_START; - if ((play->sceneNum == SCENE_GANONS_TOWER_COLLAPSE_INTERIOR) || (play->sceneNum == SCENE_GANONS_TOWER_COLLAPSE_EXTERIOR) || + if ((play->sceneNum == SCENE_GANONS_TOWER_COLLAPSE_INTERIOR) || + (play->sceneNum == SCENE_GANONS_TOWER_COLLAPSE_EXTERIOR) || (play->sceneNum == SCENE_INSIDE_GANONS_CASTLE_COLLAPSE) || (play->sceneNum == SCENE_GANON_BOSS)) { play->nextEntranceIndex = ENTR_GANONS_TOWER_COLLAPSE_EXTERIOR_0; Item_Give(play, ITEM_SWORD_MASTER); - } else if ((gSaveContext.entranceIndex == ENTR_HYRULE_FIELD_11) || (gSaveContext.entranceIndex == ENTR_HYRULE_FIELD_12) || - (gSaveContext.entranceIndex == ENTR_HYRULE_FIELD_13) || (gSaveContext.entranceIndex == ENTR_HYRULE_FIELD_15)) { + } else if ((gSaveContext.entranceIndex == ENTR_HYRULE_FIELD_11) || + (gSaveContext.entranceIndex == ENTR_HYRULE_FIELD_12) || + (gSaveContext.entranceIndex == ENTR_HYRULE_FIELD_13) || + (gSaveContext.entranceIndex == ENTR_HYRULE_FIELD_15)) { play->nextEntranceIndex = ENTR_HYRULE_FIELD_CENTER_EXIT; } else { play->nextEntranceIndex = gSaveContext.entranceIndex; diff --git a/soh/src/overlays/actors/ovl_En_Elf/z_en_elf.c b/soh/src/overlays/actors/ovl_En_Elf/z_en_elf.c index 477d60d64..9d8a3b2e0 100644 --- a/soh/src/overlays/actors/ovl_En_Elf/z_en_elf.c +++ b/soh/src/overlays/actors/ovl_En_Elf/z_en_elf.c @@ -1097,7 +1097,7 @@ void func_80A0461C(EnElf* this, PlayState* play) { } else { arrowPointedActor = play->actorCtx.targetCtx.arrowPointedActor; - if ((player->stateFlags1 & PLAYER_STATE1_GETTING_ITEM) || ((YREG(15) & 0x10) && func_800BC56C(play, 2))) { + if ((player->stateFlags1 & PLAYER_STATE1_GETTING_ITEM) || ((YREG(15) & 0x10) && Play_CheckViewpoint(play, 2))) { temp = 12; this->unk_2C0 = 100; } else if (arrowPointedActor == NULL || arrowPointedActor->category == ACTORCAT_NPC) { diff --git a/soh/src/overlays/actors/ovl_En_Ossan/z_en_ossan.c b/soh/src/overlays/actors/ovl_En_Ossan/z_en_ossan.c index 5049a05d6..171a6405d 100644 --- a/soh/src/overlays/actors/ovl_En_Ossan/z_en_ossan.c +++ b/soh/src/overlays/actors/ovl_En_Ossan/z_en_ossan.c @@ -679,7 +679,7 @@ void EnOssan_EndInteraction(PlayState* play, EnOssan* this) { play->msgCtx.msgMode = MSGMODE_TEXT_CLOSING; play->msgCtx.stateTimer = 4; player->stateFlags2 &= ~PLAYER_STATE2_DISABLE_DRAW; - func_800BC490(play, 1); + Play_SetViewpoint(play, 1); Interface_ChangeAlpha(50); this->drawCursor = 0; this->stickLeftPrompt.isEnabled = false; @@ -763,7 +763,7 @@ void EnOssan_State_Idle(EnOssan* this, PlayState* play, Player* player) { // "Start conversation!!" osSyncPrintf(VT_FGCOL(YELLOW) "★★★ 会話開始!! ★★★" VT_RST "\n"); player->stateFlags2 |= PLAYER_STATE2_DISABLE_DRAW; - func_800BC590(play); + Play_SetShopBrowsingViewpoint(play); EnOssan_SetStateStartShopping(play, this, false); } else if (this->actor.xzDistToPlayer < 100.0f) { func_8002F2CC(&this->actor, play, 100); @@ -1392,7 +1392,7 @@ void EnOssan_GiveItemWithFanfare(PlayState* play, EnOssan* this) { play->msgCtx.msgMode = MSGMODE_TEXT_CLOSING; play->msgCtx.stateTimer = 4; player->stateFlags2 &= ~PLAYER_STATE2_DISABLE_DRAW; - func_800BC490(play, 1); + Play_SetViewpoint(play, 1); Interface_ChangeAlpha(50); this->drawCursor = 0; EnOssan_UpdateCameraDirection(this, play, 0.0f); @@ -1770,7 +1770,7 @@ void EnOssan_State_ContinueShoppingPrompt(EnOssan* this, PlayState* play, Player osSyncPrintf(VT_FGCOL(YELLOW) "★★★ 続けるよ!! ★★★" VT_RST "\n"); player->actor.shape.rot.y += 0x8000; player->stateFlags2 |= PLAYER_STATE2_DISABLE_DRAW; - func_800BC490(play, 2); + Play_SetViewpoint(play, 2); Message_StartTextbox(play, this->actor.textId, &this->actor); EnOssan_SetStateStartShopping(play, this, true); func_8002F298(&this->actor, play, 100.0f, -1); @@ -1789,7 +1789,7 @@ void EnOssan_State_ContinueShoppingPrompt(EnOssan* this, PlayState* play, Player selectedItem->updateStockedItemFunc(play, selectedItem); player->actor.shape.rot.y += 0x8000; player->stateFlags2 |= PLAYER_STATE2_DISABLE_DRAW; - func_800BC490(play, 2); + Play_SetViewpoint(play, 2); Message_StartTextbox(play, this->actor.textId, &this->actor); EnOssan_SetStateStartShopping(play, this, true); func_8002F298(&this->actor, play, 100.0f, -1); diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index 4d4e9480d..c11609c5d 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -10811,7 +10811,7 @@ void Player_Init(Actor* thisx, PlayState* play2) { if (respawnFlag == -3) { thisx->params = gSaveContext.respawn[RESPAWN_MODE_RETURN].playerParams; } else { - if ((respawnFlag == 1) || (respawnFlag == -1)) { + if (GameInteractor_Should(VB_INFLICT_VOID_DAMAGE, (respawnFlag == 1) || (respawnFlag == -1), respawnFlag)) { this->unk_A86 = -2; }