From bca911e05fc7bec9a064d7ba1e69537744d13356 Mon Sep 17 00:00:00 2001 From: djevangelia Date: Tue, 23 Jun 2026 13:24:30 +0200 Subject: [PATCH 1/3] Vanilla bugfix, ladder cutscene softlock --- soh/include/z64player.h | 3 +- soh/soh/Enhancements/AlwaysOnFixes.cpp | 17 +++++- .../vanilla-behavior/GIVanillaBehavior.h | 8 +++ .../actors/ovl_player_actor/z_player.c | 60 ++++++++++--------- 4 files changed, 59 insertions(+), 29 deletions(-) diff --git a/soh/include/z64player.h b/soh/include/z64player.h index 7e051e6eaec..661e1f4fadc 100644 --- a/soh/include/z64player.h +++ b/soh/include/z64player.h @@ -845,7 +845,7 @@ typedef struct Player { /* 0x06A4 */ f32 closestSecretDistSq; /* 0x06A8 */ Actor* unk_6A8; /* 0x06AC */ s8 idleType; - /* 0x06AD */ u8 unk_6AD; + /* 0x06AD */ u8 unk_6AD; // Camera related. 0 = normal, 1 = first person without weapon, 2 = first person with weapon, 3 = cutscene action, 4 = cutscene items /* 0x06AE */ u16 unk_6AE_rotFlags; // See `UNK6AE_ROT_` macros. If its flag isn't set, a rot steps to 0. /* 0x06B0 */ s16 upperLimbYawSecondary; /* 0x06B2 */ char unk_6B4[0x004]; @@ -885,6 +885,7 @@ typedef struct Player { s16 bonked; // Player_Action_Roll: set to true after bonking into a wall or an actor s16 startedTextbox; // Player_Action_SwingBottle: set to true when the textbox is started s16 inWater; // Player_Action_SwingBottle: true if a bottle is swung in water. Used to determine which bottle swing animation to use. + s16 dismountDown; // Player_Action_DismountLadder: True if player is dismounting the ladder downwards } av2; // "Action Variable 2": context dependent variable that has different meanings depending on what action is currently running /* 0x0854 */ f32 unk_854; diff --git a/soh/soh/Enhancements/AlwaysOnFixes.cpp b/soh/soh/Enhancements/AlwaysOnFixes.cpp index c2746a2c7e9..0e790d4baa8 100644 --- a/soh/soh/Enhancements/AlwaysOnFixes.cpp +++ b/soh/soh/Enhancements/AlwaysOnFixes.cpp @@ -15,4 +15,19 @@ void RegisterFixOutsideTotCrash() { }); } -static RegisterShipInitFunc initFunc(RegisterFixOutsideTotCrash, { "" }); +// `Player_Action_DismountLadder`: Dismounting a ladder in a cutscene or when using +// a cutscene item (possible using restricted items glitch), i.e. `player->unk_6AD` is 3 or 4, +// causes the animation in `Player_Action_DismountLadder` to never finish because +// `Player_TryActionInterrupt` always returns true and player cannot exit the action. +// Fix: Disregard result of `Player_TryActionInterrupt` if `unk_6AD` >= 3. +void RegisterFixLadderCutsceneSoftlock() { + COND_VB_SHOULD(VB_LADDER_CUTSCENE_FLAG, true, { + u8* unk_6AD = va_arg(args, u8*); + if (*unk_6AD >= 3) { + *should = false; + } + }); +} + +static RegisterShipInitFunc initFuncFixOutsideTotCrash(RegisterFixOutsideTotCrash, { "" }); +static RegisterShipInitFunc initFuncFixLadderCutsceneSoftlock(RegisterFixLadderCutsceneSoftlock, { "" }); diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index db841e8745d..320b7eb64a1 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -3056,6 +3056,14 @@ typedef enum { // - `*EnItem00` VB_ITEM00_KILL, + // #### `result` + // ```c + // false if `player->unk_6AD` >= 3 + // ``` + // #### `args` + // - `*u8 (&player->unk_6AD)` + VB_LADDER_CUTSCENE_FLAG, + // true // ``` // #### `args` 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 ba523a70539..67ccb8ed782 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -300,7 +300,7 @@ void Player_Action_8084B9E4(Player* this, PlayState* play); void Player_Action_8084BBE4(Player* this, PlayState* play); void Player_Action_8084BDFC(Player* this, PlayState* play); void Player_Action_8084BF1C(Player* this, PlayState* play); -void Player_Action_8084C5F8(Player* this, PlayState* play); +void Player_Action_DismountLadder(Player* this, PlayState* play); void Player_Action_8084C760(Player* this, PlayState* play); void Player_Action_8084C81C(Player* this, PlayState* play); void Player_Action_8084CC98(Player* this, PlayState* play); @@ -7600,8 +7600,8 @@ s32 func_8083EC18(Player* this, PlayState* play, u32 wallFlags) { return false; } -void func_8083F070(Player* this, LinkAnimationHeader* anim, PlayState* play) { - Player_SetupActionPreserveAnimMovement(play, this, Player_Action_8084C5F8, 0); +void Player_SetupDismountLadder(Player* this, LinkAnimationHeader* anim, PlayState* play) { + Player_SetupActionPreserveAnimMovement(play, this, Player_Action_DismountLadder, 0); LinkAnimation_PlayOnceSetSpeed(play, &this->skelAnime, anim, (4.0f / 3.0f)); } @@ -13322,7 +13322,7 @@ void Player_Action_8084BF1C(Player* this, PlayState* play) { func_8083A9B8(this, &gPlayerAnim_link_normal_jump_climb_up_free, play); this->stateFlags1 |= PLAYER_STATE1_CLIMBING_LEDGE; } else { - func_8083F070(this, this->ageProperties->unk_CC[this->av2.actionVar2], play); + Player_SetupDismountLadder(this, this->ageProperties->unk_CC[this->av2.actionVar2], play); } } else { this->skelAnime.prevTransl = this->ageProperties->unk_4A[sp68]; @@ -13336,7 +13336,7 @@ void Player_Action_8084BF1C(Player* this, PlayState* play) { if (this->av2.actionVar2 != 0) { this->skelAnime.prevTransl = this->ageProperties->unk_44; } - func_8083F070(this, this->ageProperties->unk_C4[this->av2.actionVar2], play); + Player_SetupDismountLadder(this, this->ageProperties->unk_C4[this->av2.actionVar2], play); this->av2.actionVar2 = 1; } } else { @@ -13385,50 +13385,56 @@ void Player_Action_8084BF1C(Player* this, PlayState* play) { } } -static f32 D_80854898[] = { 10.0f, 20.0f }; -static f32 D_808548A0[] = { 40.0f, 50.0f }; +static f32 sUpDismountLadderFrames[] = { 10.0f, 20.0f }; +static f32 sDownDismountLadderFrames[] = { 40.0f, 50.0f }; -static AnimSfxEntry D_808548A8[] = { +static AnimSfxEntry sDownDismountLadderAnimSfx[] = { { NA_SE_PL_WALK_LADDER, ANIMSFX_DATA(ANIMSFX_TYPE_GENERAL, 10) }, { NA_SE_PL_WALK_LADDER, ANIMSFX_DATA(ANIMSFX_TYPE_GENERAL, 20) }, { NA_SE_PL_WALK_LADDER, -ANIMSFX_DATA(ANIMSFX_TYPE_GENERAL, 30) }, }; -void Player_Action_8084C5F8(Player* this, PlayState* play) { - s32 temp; - f32* sp38; - CollisionPoly* sp34; - s32 sp30; - Vec3f sp24; +/** + * Dismounting ladders, both upwards and downwards. + * `actionVar2` (dismountDown) true if dismounting down + */ +void Player_Action_DismountLadder(Player* this, PlayState* play) { + s32 interruptResult; + f32* frame; + CollisionPoly* groundPoly; + s32 bgId; + Vec3f raycastPos; this->stateFlags2 |= PLAYER_STATE2_DISABLE_ROTATION_ALWAYS; - temp = Player_TryActionInterrupt(play, this, &this->skelAnime, 4.0f); + interruptResult = Player_TryActionInterrupt(play, this, &this->skelAnime, 4.0f); - if (temp == 0) { + // SoH: Check for `this->unk_6AD` >= 3 to fix ladder dismount softlock with cutscenes and restricted items. + // See `RegisterFixLadderCutsceneSoftlock()` + if (interruptResult == PLAYER_INTERRUPT_NEW_ACTION && GameInteractor_Should(VB_LADDER_CUTSCENE_FLAG, true, &this->unk_6AD)) { this->stateFlags1 &= ~PLAYER_STATE1_CLIMBING_LADDER; return; } - if ((temp > 0) || LinkAnimation_Update(play, &this->skelAnime)) { + if ((interruptResult >= PLAYER_INTERRUPT_MOVE) || LinkAnimation_Update(play, &this->skelAnime)) { func_8083C0E8(this, play); this->stateFlags1 &= ~PLAYER_STATE1_CLIMBING_LADDER; return; } - sp38 = D_80854898; + frame = sUpDismountLadderFrames; - if (this->av2.actionVar2 != 0) { - Player_ProcessAnimSfxList(this, D_808548A8); - sp38 = D_808548A0; + if (this->av2.dismountDown != false) { + Player_ProcessAnimSfxList(this, sDownDismountLadderAnimSfx); + frame = sDownDismountLadderFrames; } - if (LinkAnimation_OnFrame(&this->skelAnime, sp38[0]) || LinkAnimation_OnFrame(&this->skelAnime, sp38[1])) { - sp24.x = this->actor.world.pos.x; - sp24.y = this->actor.world.pos.y + 20.0f; - sp24.z = this->actor.world.pos.z; - if (BgCheck_EntityRaycastFloor3(&play->colCtx, &sp34, &sp30, &sp24) != 0.0f) { - this->floorSfxOffset = func_80041F10(&play->colCtx, sp34, sp30); + if (LinkAnimation_OnFrame(&this->skelAnime, frame[0]) || LinkAnimation_OnFrame(&this->skelAnime, frame[1])) { + raycastPos.x = this->actor.world.pos.x; + raycastPos.y = this->actor.world.pos.y + 20.0f; + raycastPos.z = this->actor.world.pos.z; + if (BgCheck_EntityRaycastFloor3(&play->colCtx, &groundPoly, &bgId, &raycastPos) != 0.0f) { + this->floorSfxOffset = func_80041F10(&play->colCtx, groundPoly, bgId); Player_PlayLandingSfx(this); } } From 23c02869c8199fe38275519667c40b911d8d466a Mon Sep 17 00:00:00 2001 From: djevangelia Date: Tue, 23 Jun 2026 16:26:17 +0200 Subject: [PATCH 2/3] Ladder softlock bugfix, review edit --- soh/include/z64player.h | 1 - soh/src/overlays/actors/ovl_player_actor/z_player.c | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/soh/include/z64player.h b/soh/include/z64player.h index 661e1f4fadc..bd01e81bd5a 100644 --- a/soh/include/z64player.h +++ b/soh/include/z64player.h @@ -885,7 +885,6 @@ typedef struct Player { s16 bonked; // Player_Action_Roll: set to true after bonking into a wall or an actor s16 startedTextbox; // Player_Action_SwingBottle: set to true when the textbox is started s16 inWater; // Player_Action_SwingBottle: true if a bottle is swung in water. Used to determine which bottle swing animation to use. - s16 dismountDown; // Player_Action_DismountLadder: True if player is dismounting the ladder downwards } av2; // "Action Variable 2": context dependent variable that has different meanings depending on what action is currently running /* 0x0854 */ f32 unk_854; 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 67ccb8ed782..d366e6bae78 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -13396,7 +13396,7 @@ static AnimSfxEntry sDownDismountLadderAnimSfx[] = { /** * Dismounting ladders, both upwards and downwards. - * `actionVar2` (dismountDown) true if dismounting down + * `actionVar2` true if dismounting down */ void Player_Action_DismountLadder(Player* this, PlayState* play) { s32 interruptResult; @@ -13411,7 +13411,8 @@ void Player_Action_DismountLadder(Player* this, PlayState* play) { // SoH: Check for `this->unk_6AD` >= 3 to fix ladder dismount softlock with cutscenes and restricted items. // See `RegisterFixLadderCutsceneSoftlock()` - if (interruptResult == PLAYER_INTERRUPT_NEW_ACTION && GameInteractor_Should(VB_LADDER_CUTSCENE_FLAG, true, &this->unk_6AD)) { + if (interruptResult == PLAYER_INTERRUPT_NEW_ACTION && + GameInteractor_Should(VB_LADDER_CUTSCENE_FLAG, true, &this->unk_6AD)) { this->stateFlags1 &= ~PLAYER_STATE1_CLIMBING_LADDER; return; } @@ -13424,7 +13425,7 @@ void Player_Action_DismountLadder(Player* this, PlayState* play) { frame = sUpDismountLadderFrames; - if (this->av2.dismountDown != false) { + if (this->av2.actionVar2 != false) { Player_ProcessAnimSfxList(this, sDownDismountLadderAnimSfx); frame = sDownDismountLadderFrames; } From 6e66c97d782251aba8fd21957536da2b600f83ca Mon Sep 17 00:00:00 2001 From: djevangelia Date: Tue, 14 Jul 2026 11:36:16 +0200 Subject: [PATCH 3/3] Ladder cutscene, format --- soh/src/overlays/actors/ovl_player_actor/z_player.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 e0a47f4e5f5..14366029f8e 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -13321,7 +13321,8 @@ void Player_Action_DismountLadder(Player* this, PlayState* play) { interruptResult = Player_TryActionInterrupt(play, this, &this->skelAnime, 4.0f); // SoH: Prevent ladder cutscene softlock - if (GameInteractor_Should(VB_INTERRUPT_LADDER_DISMOUNT, interruptResult == PLAYER_INTERRUPT_NEW_ACTION, &this->unk_6AD)) { + if (GameInteractor_Should(VB_INTERRUPT_LADDER_DISMOUNT, interruptResult == PLAYER_INTERRUPT_NEW_ACTION, + &this->unk_6AD)) { this->stateFlags1 &= ~PLAYER_STATE1_CLIMBING_LADDER; return; }