diff --git a/soh/soh/Enhancements/AlwaysOnFixes.cpp b/soh/soh/Enhancements/AlwaysOnFixes.cpp index aa1436f7b7..365d8377da 100644 --- a/soh/soh/Enhancements/AlwaysOnFixes.cpp +++ b/soh/soh/Enhancements/AlwaysOnFixes.cpp @@ -6,73 +6,78 @@ extern "C" { #include "functions.h" #include "variables.h" #include "src/overlays/actors/ovl_En_Go2/z_en_go2.h" +#include "src/overlays/actors/ovl_En_Test/z_en_test.h" 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 -// shouldn't ever influence vanilla play regardless. -void RegisterFixOutsideTotCrash() { +// Actor_FindNearby also matches actors killed this same frame (update==NULL) that are +// still in the list, so when the last Stalfos of a group dies alongside its siblings, +// vanilla thinks an enemy remains and BGM never restores (seen in MQ Water Temple). +// Re-check against living actors only. +static bool EnTest_HasLivingNearby(Actor* refActor) { + Actor* actor = gPlayState->actorCtx.actorLists[ACTORCAT_ENEMY].head; + while (actor != NULL) { + if (actor != refActor && actor->id == ACTOR_EN_TEST && actor->update != NULL && + Actor_WorldDistXYZToActor(refActor, actor) <= 8000.0f) { + return true; + } + actor = actor->next; + } + return false; +} + +void RegisterAlwaysOnFixes() { + // Crash on death/Din's Fire outside Temple of Time (crowd control, Sail, unrestricted items). camId -1 path won't + // affect vanilla. COND_VB_SHOULD(VB_SHOULD_LOAD_BG_IMAGE, true, { int32_t* camId = va_arg(args, int*); if (*camId == -1) { *should = false; } }); -} -// Vanilla bug: `Actor_Item_Shield` (dropped Deku Shield when burning) assumes that segment 12 -// contains Link display list `gCullBackDList`. If an actor is drawn between player and shield -// that uses segment 12 (such as Jabu-Jabu tentacles), the game will crash on Deku Shield drop. -// Fix: Re-set segment 12 to the required display list. -void RegisterFixDekuShieldDropCrash() { + // Actor_Item_Shield (dropped Deku Shield) assumes segment 12 still holds Link's + // gCullBackDList; an intermediate actor using segment 12 (e.g. Jabu tentacles) + // overwrites it and crashes. Re-set segment 12 before drawing. COND_VB_SHOULD(VB_ITEMSHIELD_DRAW, true, { GraphicsContext* __gfxCtx = gPlayState->state.gfxCtx; gSPSegment(POLY_OPA_DISP++, 0x0C, (uintptr_t)SEGMENTED_TO_VIRTUAL(gCullBackDList)); }); -} -// 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() { + // Hookshot not spawning softlocks player (child use, memory full). Clear item on no + // spawn (Player_InitItemAction removes the ranged weapon state elsewhere). 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() { + // Non-hookshot parent causes fly-land-fly loop (e.g. Moblin grab in water, or Like + // Like eating player then despawning falling through En_Holl). Validate ACTOR_ARMS_HOOK parent. COND_VB_SHOULD(VB_PREVENT_HOOKSHOT_PARENT_SOFTLOCK, true, { s16* parentId = va_arg(args, s16*); if (*parentId != ACTOR_ARMS_HOOK) { *should = false; } }); -} -// Vanilla bug: If player starts talking with Goron Link for the first time (before getting tunic) -// but moves out of range, player will softlock because the text cannot progress to the question -// choice textbox when Goron Link is asleep (UpdateTalkState cannot run). -// Fix: Allow updating talkState even when Goron Link is asleep. -void RegisterPreventGoronLinkSoftlock() { + // Goron Link asleep softlocks talk state after leaving range before tunic, since + // UpdateTalkState cannot run to progress to the question textbox. Force the update. COND_VB_SHOULD(VB_PREVENT_GORON_LINK_SOFTLOCK, true, { EnGo2* GoronLink = va_arg(args, EnGo2*); if (GoronLink->interactInfo.talkState == NPC_TALK_STATE_TALKING) { *should = true; } }); + + COND_ID_HOOK(OnActorDestroy, ACTOR_EN_TEST, true, [](void* refActor) { + Actor* actor = reinterpret_cast(refActor); + if (actor->params != STALFOS_TYPE_2 && !EnTest_HasLivingNearby(actor)) { + func_800F5B58(); + } + }); } -static RegisterShipInitFunc initFuncFixOutsideTotCrash(RegisterFixOutsideTotCrash, { "" }); -static RegisterShipInitFunc initFuncFixDekuShieldDropCrash(RegisterFixDekuShieldDropCrash, { "" }); -static RegisterShipInitFunc initFuncHookshotNospawnSoftlock(RegisterPreventHookshotNoSpawnSoftlock, { "" }); -static RegisterShipInitFunc initFuncHookshotParentSoftlock(RegisterPreventHookshotParentSoftlock, { "" }); -static RegisterShipInitFunc initFuncGoronLinkSoftlock(RegisterPreventGoronLinkSoftlock, { "" }); +static RegisterShipInitFunc initAlwaysOnFixes(RegisterAlwaysOnFixes, { "" });