diff --git a/soh/soh/Enhancements/AlwaysOnFixes.cpp b/soh/soh/Enhancements/AlwaysOnFixes.cpp index c2746a2c7e..16feeaf75c 100644 --- a/soh/soh/Enhancements/AlwaysOnFixes.cpp +++ b/soh/soh/Enhancements/AlwaysOnFixes.cpp @@ -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, { "" }); diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index 4af469cbc4..96f8bb8528 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -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` 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 ba523a7053..9b6712e32e 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -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);