Vanilla bugfix, Hookshot with parent + Hookshot nospawn (#6805)

This commit is contained in:
djevangelia
2026-06-24 20:32:03 +02:00
committed by GitHub
parent 8cf4ff5f14
commit 53b9db5b92
3 changed files with 54 additions and 2 deletions
+32 -1
View File
@@ -2,6 +2,11 @@
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ShipInit.hpp"
extern "C" {
extern void Player_UseItem(PlayState*, Player*, s32);
extern PlayState* gPlayState;
}
// Dying or using Din's Fire in the Outside Temple of Time area crashes the game.
// In vanilla this can never happen, but with CrowdControl, Sail, Unrestricted Items
// and others this *can* happen. Because it checks for a camId of -1, this code path
@@ -15,4 +20,30 @@ void RegisterFixOutsideTotCrash() {
});
}
static RegisterShipInitFunc initFunc(RegisterFixOutsideTotCrash, { "" });
// Vanilla bug: If Hookshot doesn't spawn, player is softlocked. (eg. use as child, no memory left)
// Fix: Change item to none if no spawn. (Ranged weapon state is removed by `Player_InitItemAction`)
void RegisterPreventHookshotNoSpawnSoftlock() {
COND_VB_SHOULD(VB_INIT_HOOKSHOT_IA, true, {
Player* player = va_arg(args, Player*);
if (player->heldActor == NULL) {
Player_UseItem(gPlayState, player, 0xFF);
}
});
}
// Vanilla bug: When pulling out Hookshot, if `this->actor.parent` is set but not Hookshot, player
// is locked into repeated fly-land-fly. Possible with enemies that grab player and set themselves
// as parent (such as Moblin in water, eaten by Like like that despawns falling through En_Holl).
// Fix: Ensure that parent actor has Hookshot actor ID before starting flying.
void RegisterPreventHookshotParentSoftlock() {
COND_VB_SHOULD(VB_PREVENT_HOOKSHOT_PARENT_SOFTLOCK, true, {
s16* parentId = va_arg(args, s16*);
if (*parentId != ACTOR_ARMS_HOOK) {
*should = false;
}
});
}
static RegisterShipInitFunc initFuncFixOutsideTotCrash(RegisterFixOutsideTotCrash, { "" });
static RegisterShipInitFunc initFuncHookshotNospawnSoftlock(RegisterPreventHookshotNoSpawnSoftlock, { "" });
static RegisterShipInitFunc initFuncHookshotParentSoftlock(RegisterPreventHookshotParentSoftlock, { "" });
@@ -3066,6 +3066,22 @@ typedef enum {
// - `*EnItem00`
VB_ITEM00_KILL,
// #### `result`
// ```c
// true
// ```
// #### `args`
// - `*Player`
VB_INIT_HOOKSHOT_IA,
// #### `result`
// ```c
// !(this->stateFlags1 & PLAYER_STATE1_ON_HORSE) && Player_HoldsHookshot(this)
// ```
// #### `args`
// - `s16* (&this->actor.parent->id)`
VB_PREVENT_HOOKSHOT_PARENT_SOFTLOCK,
// true
// ```
// #### `args`
@@ -2300,6 +2300,8 @@ void Player_InitHookshotIA(PlayState* play, Player* this) {
this->heldActor =
Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_ARMS_HOOK, this->actor.world.pos.x,
this->actor.world.pos.y, this->actor.world.pos.z, 0, this->actor.shape.rot.y, 0, 0);
GameInteractor_Should(VB_INIT_HOOKSHOT_IA, true, this);
}
void Player_InitBoomerangIA(PlayState* play, Player* this) {
@@ -3572,7 +3574,10 @@ int Player_CanUpdateItems(Player* this) {
* depending on some conditions. See details below.
*/
s32 Player_UpdateUpperBody(Player* this, PlayState* play) {
if (!(this->stateFlags1 & PLAYER_STATE1_ON_HORSE) && (this->actor.parent != NULL) && Player_HoldsHookshot(this)) {
if (this->actor.parent != NULL &&
GameInteractor_Should(VB_PREVENT_HOOKSHOT_PARENT_SOFTLOCK,
!(this->stateFlags1 & PLAYER_STATE1_ON_HORSE) && Player_HoldsHookshot(this),
&this->actor.parent->id)) {
Player_SetupAction(play, this, Player_Action_80850AEC, 1);
this->stateFlags3 |= PLAYER_STATE3_FLYING_WITH_HOOKSHOT;
Player_AnimPlayOnce(play, this, &gPlayerAnim_link_hook_fly_start);