diff --git a/include/z64actor.h b/include/z64actor.h index 04217cbdcb..1e81bab9b7 100644 --- a/include/z64actor.h +++ b/include/z64actor.h @@ -153,8 +153,13 @@ typedef struct ActorShape { // Actor will not shake when a quake occurs #define ACTOR_FLAG_IGNORE_QUAKE (1 << 12) +// The hookshot is currently attached to this actor. +// The behavior that occurs after attachment is determined by `ACTOR_FLAG_9` and `ACTOR_FLAG_10`. +// If neither of those flags are set attachment cannot occur, and the hookshot will simply act as a damage source. // -#define ACTOR_FLAG_13 (1 << 13) +// This flag is also reused to indicate that an actor is attached to the boomerang. +// This only has an effect for Gold Skulltula Tokens (EN_SI) which has overlapping behavior for hookshot and boomerang +#define ACTOR_FLAG_HOOKSHOT_ATTACHED (1 << 13) // When hit by an arrow, the actor will be able to attach to the arrow and fly with it in the air #define ACTOR_FLAG_CAN_ATTACH_TO_ARROW (1 << 14) @@ -806,7 +811,7 @@ int func_8002DD6C(struct Player* player); int func_8002DD78(struct Player* player); s32 func_8002DDE4(struct PlayState* play); s32 func_8002DDF4(struct PlayState* play); -void func_8002DE04(struct PlayState* play, Actor* actorA, Actor* actorB); +void Actor_SwapHookshotAttachment(struct PlayState* play, Actor* srcActor, Actor* destActor); void func_8002DE74(struct PlayState* play, struct Player* player); void Actor_MountHorse(struct PlayState* play, struct Player* player, Actor* horse); int func_8002DEEC(struct Player* player); diff --git a/src/code/z_actor.c b/src/code/z_actor.c index 2fde9f8a92..b5e96201df 100644 --- a/src/code/z_actor.c +++ b/src/code/z_actor.c @@ -1119,15 +1119,24 @@ s32 func_8002DDF4(PlayState* play) { return player->stateFlags2 & PLAYER_STATE2_12; } -void func_8002DE04(PlayState* play, Actor* actorA, Actor* actorB) { +/** + * Swap hookshot attachment state from one actor to another. + * + * Note: There is no safety check for a NULL hookshot pointer. + * The responsibility is on the caller to make sure the hookshot exists. + */ +void Actor_SwapHookshotAttachment(PlayState* play, Actor* srcActor, Actor* destActor) { ArmsHook* hookshot = (ArmsHook*)Actor_Find(&play->actorCtx, ACTOR_ARMS_HOOK, ACTORCAT_ITEMACTION); - hookshot->grabbed = actorB; - hookshot->grabbedDistDiff.x = 0.0f; - hookshot->grabbedDistDiff.y = 0.0f; - hookshot->grabbedDistDiff.z = 0.0f; - actorB->flags |= ACTOR_FLAG_13; - actorA->flags &= ~ACTOR_FLAG_13; + hookshot->attachedActor = destActor; + + // The hookshot will attach at exactly the actors world position with 0 offset + hookshot->attachPointOffset.x = 0.0f; + hookshot->attachPointOffset.y = 0.0f; + hookshot->attachPointOffset.z = 0.0f; + + destActor->flags |= ACTOR_FLAG_HOOKSHOT_ATTACHED; + srcActor->flags &= ~ACTOR_FLAG_HOOKSHOT_ATTACHED; } void func_8002DE74(PlayState* play, Player* player) { diff --git a/src/overlays/actors/ovl_Arms_Hook/z_arms_hook.c b/src/overlays/actors/ovl_Arms_Hook/z_arms_hook.c index 63f929d43e..6baa55be3f 100644 --- a/src/overlays/actors/ovl_Arms_Hook/z_arms_hook.c +++ b/src/overlays/actors/ovl_Arms_Hook/z_arms_hook.c @@ -74,9 +74,10 @@ void ArmsHook_Init(Actor* thisx, PlayState* play) { void ArmsHook_Destroy(Actor* thisx, PlayState* play) { ArmsHook* this = (ArmsHook*)thisx; - if (this->grabbed != NULL) { - this->grabbed->flags &= ~ACTOR_FLAG_13; + if (this->attachedActor != NULL) { + this->attachedActor->flags &= ~ACTOR_FLAG_HOOKSHOT_ATTACHED; } + Collider_DestroyQuad(play, &this->collider); } @@ -109,10 +110,10 @@ s32 ArmsHook_AttachToPlayer(ArmsHook* this, Player* player) { return false; } -void ArmsHook_DetachHookFromActor(ArmsHook* this) { - if (this->grabbed != NULL) { - this->grabbed->flags &= ~ACTOR_FLAG_13; - this->grabbed = NULL; +void ArmsHook_DetachFromActor(ArmsHook* this) { + if (this->attachedActor != NULL) { + this->attachedActor->flags &= ~ACTOR_FLAG_HOOKSHOT_ATTACHED; + this->attachedActor = NULL; } } @@ -123,7 +124,7 @@ s32 ArmsHook_CheckForCancel(ArmsHook* this) { if ((player->itemAction != player->heldItemAction) || (player->actor.flags & ACTOR_FLAG_TALK) || ((player->stateFlags1 & (PLAYER_STATE1_DEAD | PLAYER_STATE1_26)))) { this->timer = 0; - ArmsHook_DetachHookFromActor(this); + ArmsHook_DetachFromActor(this); Math_Vec3f_Copy(&this->actor.world.pos, &player->unk_3C8); return 1; } @@ -131,17 +132,17 @@ s32 ArmsHook_CheckForCancel(ArmsHook* this) { return 0; } -void ArmsHook_AttachHookToActor(ArmsHook* this, Actor* actor) { - actor->flags |= ACTOR_FLAG_13; - this->grabbed = actor; - Math_Vec3f_Diff(&actor->world.pos, &this->actor.world.pos, &this->grabbedDistDiff); +void ArmsHook_AttachToActor(ArmsHook* this, Actor* actor) { + actor->flags |= ACTOR_FLAG_HOOKSHOT_ATTACHED; + this->attachedActor = actor; + Math_Vec3f_Diff(&actor->world.pos, &this->actor.world.pos, &this->attachPointOffset); } void ArmsHook_Shoot(ArmsHook* this, PlayState* play) { Player* player = GET_PLAYER(play); if ((this->actor.parent == NULL) || (!Player_HoldsHookshot(player))) { - ArmsHook_DetachHookFromActor(this); + ArmsHook_DetachFromActor(this); Actor_Kill(&this->actor); return; } @@ -155,7 +156,8 @@ void ArmsHook_Shoot(ArmsHook* this, PlayState* play) { if ((touchedActor->update != NULL) && (touchedActor->flags & (ACTOR_FLAG_9 | ACTOR_FLAG_10))) { if (this->collider.elem.atHitElem->acElemFlags & ACELEM_HOOKABLE) { - ArmsHook_AttachHookToActor(this, touchedActor); + ArmsHook_AttachToActor(this, touchedActor); + if (CHECK_FLAG_ALL(touchedActor->flags, ACTOR_FLAG_10)) { func_80865044(this); } @@ -168,41 +170,50 @@ void ArmsHook_Shoot(ArmsHook* this, PlayState* play) { } if (DECR(this->timer) == 0) { - Actor* grabbed; + Actor* attachedActor; Vec3f bodyDistDiffVec; Vec3f newPos; f32 bodyDistDiff; f32 phi_f16; s32 pad1; - f32 curGrabbedDist; - f32 grabbedDist; + f32 curActorOffsetXYZ; + f32 attachPointOffsetXYZ; f32 velocity; - grabbed = this->grabbed; - if (grabbed != NULL) { - if ((grabbed->update == NULL) || !CHECK_FLAG_ALL(grabbed->flags, ACTOR_FLAG_13)) { - grabbed = NULL; - this->grabbed = NULL; + attachedActor = this->attachedActor; + + if (attachedActor != NULL) { + if ((attachedActor->update == NULL) || + !CHECK_FLAG_ALL(attachedActor->flags, ACTOR_FLAG_HOOKSHOT_ATTACHED)) { + attachedActor = NULL; + this->attachedActor = NULL; } else if (this->actor.child != NULL) { - curGrabbedDist = Actor_WorldDistXYZToActor(&this->actor, grabbed); - grabbedDist = - sqrtf(SQ(this->grabbedDistDiff.x) + SQ(this->grabbedDistDiff.y) + SQ(this->grabbedDistDiff.z)); - Math_Vec3f_Diff(&grabbed->world.pos, &this->grabbedDistDiff, &this->actor.world.pos); - if ((curGrabbedDist - grabbedDist) > 50.0f) { - ArmsHook_DetachHookFromActor(this); - grabbed = NULL; + curActorOffsetXYZ = Actor_WorldDistXYZToActor(&this->actor, attachedActor); + attachPointOffsetXYZ = sqrtf(SQ(this->attachPointOffset.x) + SQ(this->attachPointOffset.y) + + SQ(this->attachPointOffset.z)); + + // Keep the hookshot actor at the same relative offset as the initial attachment even if the actor moves + Math_Vec3f_Diff(&attachedActor->world.pos, &this->attachPointOffset, &this->actor.world.pos); + + // If the actor the hookshot is attached to is moving, the hookshot's current relative + // position will be different than the initial attachment position. + // If the distance between those two points is larger than 50 units, detach the hookshot. + if ((curActorOffsetXYZ - attachPointOffsetXYZ) > 50.0f) { + ArmsHook_DetachFromActor(this); + attachedActor = NULL; } } } bodyDistDiff = Math_Vec3f_DistXYZAndStoreDiff(&player->unk_3C8, &this->actor.world.pos, &bodyDistDiffVec); + if (bodyDistDiff < 30.0f) { velocity = 0.0f; phi_f16 = 0.0f; } else { if (this->actor.child != NULL) { velocity = 30.0f; - } else if (grabbed != NULL) { + } else if (attachedActor != NULL) { velocity = 50.0f; } else { velocity = 200.0f; @@ -219,13 +230,13 @@ void ArmsHook_Shoot(ArmsHook* this, PlayState* play) { newPos.z = bodyDistDiffVec.z * velocity; if (this->actor.child == NULL) { - if ((grabbed != NULL) && (grabbed->id == ACTOR_BG_SPOT06_OBJECTS)) { - Math_Vec3f_Diff(&grabbed->world.pos, &this->grabbedDistDiff, &this->actor.world.pos); + if ((attachedActor != NULL) && (attachedActor->id == ACTOR_BG_SPOT06_OBJECTS)) { + Math_Vec3f_Diff(&attachedActor->world.pos, &this->attachPointOffset, &this->actor.world.pos); phi_f16 = 1.0f; } else { Math_Vec3f_Sum(&player->unk_3C8, &newPos, &this->actor.world.pos); - if (grabbed != NULL) { - Math_Vec3f_Sum(&this->actor.world.pos, &this->grabbedDistDiff, &grabbed->world.pos); + if (attachedActor != NULL) { + Math_Vec3f_Sum(&this->actor.world.pos, &this->attachPointOffset, &attachedActor->world.pos); } } } else { @@ -235,7 +246,7 @@ void ArmsHook_Shoot(ArmsHook* this, PlayState* play) { } if (phi_f16 < 50.0f) { - ArmsHook_DetachHookFromActor(this); + ArmsHook_DetachFromActor(this); if (phi_f16 == 0.0f) { ArmsHook_SetupAction(this, ArmsHook_Wait); if (ArmsHook_AttachToPlayer(this, player)) { @@ -274,8 +285,9 @@ void ArmsHook_Shoot(ArmsHook* this, PlayState* play) { if (bgId != BGCHECK_SCENE) { dynaPolyActor = DynaPoly_GetActor(&play->colCtx, bgId); + if (dynaPolyActor != NULL) { - ArmsHook_AttachHookToActor(this, &dynaPolyActor->actor); + ArmsHook_AttachToActor(this, &dynaPolyActor->actor); } } func_80865044(this); diff --git a/src/overlays/actors/ovl_Arms_Hook/z_arms_hook.h b/src/overlays/actors/ovl_Arms_Hook/z_arms_hook.h index b624599a53..c1c5790fd9 100644 --- a/src/overlays/actors/ovl_Arms_Hook/z_arms_hook.h +++ b/src/overlays/actors/ovl_Arms_Hook/z_arms_hook.h @@ -14,8 +14,8 @@ typedef struct ArmsHook { /* 0x01CC */ WeaponInfo hookInfo; /* 0x01E8 */ Vec3f unk_1E8; /* 0x01F4 */ Vec3f unk_1F4; - /* 0x0200 */ Actor* grabbed; - /* 0x0204 */ Vec3f grabbedDistDiff; + /* 0x0200 */ Actor* attachedActor; + /* 0x0204 */ Vec3f attachPointOffset; // Distance from the hookshot attach point to world pos of `attachedActor` /* 0x0210 */ s16 timer; /* 0x0214 */ ArmsHookActionFunc actionFunc; } ArmsHook; // size = 0x0218 diff --git a/src/overlays/actors/ovl_Bg_Spot06_Objects/z_bg_spot06_objects.c b/src/overlays/actors/ovl_Bg_Spot06_Objects/z_bg_spot06_objects.c index aa5b56024b..75618fb746 100644 --- a/src/overlays/actors/ovl_Bg_Spot06_Objects/z_bg_spot06_objects.c +++ b/src/overlays/actors/ovl_Bg_Spot06_Objects/z_bg_spot06_objects.c @@ -335,7 +335,7 @@ void BgSpot06Objects_LockPullOutward(BgSpot06Objects* this, PlayState* play) { if (this->timer == 0) { this->dyna.actor.velocity.y = 0.5f; - this->dyna.actor.flags &= ~ACTOR_FLAG_13; + this->dyna.actor.flags &= ~ACTOR_FLAG_HOOKSHOT_ATTACHED; this->actionFunc = BgSpot06Objects_LockSwimToSurface; } diff --git a/src/overlays/actors/ovl_Boss_Sst/z_boss_sst.c b/src/overlays/actors/ovl_Boss_Sst/z_boss_sst.c index 85b20fa613..7f4a23e375 100644 --- a/src/overlays/actors/ovl_Boss_Sst/z_boss_sst.c +++ b/src/overlays/actors/ovl_Boss_Sst/z_boss_sst.c @@ -894,7 +894,7 @@ void BossSst_HeadVulnerable(BossSst* this, PlayState* play) { Math_StepToF(&sHandOffsets[RIGHT].z, 600.0f, 20.0f); Math_StepToF(&sHandOffsets[LEFT].x, 200.0f, 20.0f); Math_StepToF(&sHandOffsets[RIGHT].x, -200.0f, 20.0f); - if (CHECK_FLAG_ALL(this->actor.flags, ACTOR_FLAG_13)) { + if (CHECK_FLAG_ALL(this->actor.flags, ACTOR_FLAG_HOOKSHOT_ATTACHED)) { this->timer += 2; this->timer = CLAMP_MAX(this->timer, 50); } else { diff --git a/src/overlays/actors/ovl_En_Boom/z_en_boom.c b/src/overlays/actors/ovl_En_Boom/z_en_boom.c index 56f6bd3452..932e84f68a 100644 --- a/src/overlays/actors/ovl_En_Boom/z_en_boom.c +++ b/src/overlays/actors/ovl_En_Boom/z_en_boom.c @@ -160,7 +160,7 @@ void EnBoom_Fly(EnBoom* this, PlayState* play) { if (((this->collider.base.at->id == ACTOR_EN_ITEM00) || (this->collider.base.at->id == ACTOR_EN_SI))) { this->grabbed = this->collider.base.at; if (this->collider.base.at->id == ACTOR_EN_SI) { - this->collider.base.at->flags |= ACTOR_FLAG_13; + this->collider.base.at->flags |= ACTOR_FLAG_HOOKSHOT_ATTACHED; } } } @@ -183,7 +183,7 @@ void EnBoom_Fly(EnBoom* this, PlayState* play) { target->gravity = -0.9f; target->bgCheckFlags &= ~(BGCHECKFLAG_GROUND | BGCHECKFLAG_GROUND_TOUCH); } else { - target->flags &= ~ACTOR_FLAG_13; + target->flags &= ~ACTOR_FLAG_HOOKSHOT_ATTACHED; } } // Set player flags and kill the boomerang beacause Link caught it. diff --git a/src/overlays/actors/ovl_En_Fd/z_en_fd.c b/src/overlays/actors/ovl_En_Fd/z_en_fd.c index 7a88080ae2..4f1bb687a1 100644 --- a/src/overlays/actors/ovl_En_Fd/z_en_fd.c +++ b/src/overlays/actors/ovl_En_Fd/z_en_fd.c @@ -228,8 +228,8 @@ s32 EnFd_SpawnCore(EnFd* this, PlayState* play) { this->actor.child->colChkInfo.health = 8; } - if (CHECK_FLAG_ALL(this->actor.flags, ACTOR_FLAG_13)) { - func_8002DE04(play, &this->actor, this->actor.child); + if (CHECK_FLAG_ALL(this->actor.flags, ACTOR_FLAG_HOOKSHOT_ATTACHED)) { + Actor_SwapHookshotAttachment(play, &this->actor, this->actor.child); } this->coreActive = true; @@ -668,15 +668,14 @@ void EnFd_Update(Actor* thisx, PlayState* play) { EnFd_SpawnDot(this, play); } - if (CHECK_FLAG_ALL(this->actor.flags, ACTOR_FLAG_13)) { - // has been hookshoted + if (CHECK_FLAG_ALL(this->actor.flags, ACTOR_FLAG_HOOKSHOT_ATTACHED)) { if (EnFd_SpawnCore(this, play)) { this->actor.flags &= ~ACTOR_FLAG_ATTENTION_ENABLED; this->invincibilityTimer = 30; Actor_PlaySfx(&this->actor, NA_SE_EN_FLAME_DAMAGE); Enemy_StartFinishingBlow(play, &this->actor); } else { - this->actor.flags &= ~ACTOR_FLAG_13; + this->actor.flags &= ~ACTOR_FLAG_HOOKSHOT_ATTACHED; } } else if (this->actionFunc != EnFd_WaitForCore) { EnFd_ColliderCheck(this, play); diff --git a/src/overlays/actors/ovl_En_Fw/z_en_fw.c b/src/overlays/actors/ovl_En_Fw/z_en_fw.c index 50bc3e3b7b..c8e36e1add 100644 --- a/src/overlays/actors/ovl_En_Fw/z_en_fw.c +++ b/src/overlays/actors/ovl_En_Fw/z_en_fw.c @@ -361,8 +361,7 @@ void EnFw_Update(Actor* thisx, PlayState* play) { EnFw* this = (EnFw*)thisx; SkelAnime_Update(&this->skelAnime); - if (!CHECK_FLAG_ALL(this->actor.flags, ACTOR_FLAG_13)) { - // not attached to hookshot. + if (!CHECK_FLAG_ALL(this->actor.flags, ACTOR_FLAG_HOOKSHOT_ATTACHED)) { Actor_MoveXZGravity(&this->actor); Actor_UpdateBgCheckInfo(play, &this->actor, 10.0f, 20.0f, 0.0f, UPDBGCHECKINFO_FLAG_0 | UPDBGCHECKINFO_FLAG_2); this->actionFunc(this, play); diff --git a/src/overlays/actors/ovl_En_Si/z_en_si.c b/src/overlays/actors/ovl_En_Si/z_en_si.c index f6619976d8..80b265fbc7 100644 --- a/src/overlays/actors/ovl_En_Si/z_en_si.c +++ b/src/overlays/actors/ovl_En_Si/z_en_si.c @@ -80,7 +80,7 @@ s32 func_80AFB748(EnSi* this, PlayState* play) { void func_80AFB768(EnSi* this, PlayState* play) { Player* player = GET_PLAYER(play); - if (CHECK_FLAG_ALL(this->actor.flags, ACTOR_FLAG_13)) { + if (CHECK_FLAG_ALL(this->actor.flags, ACTOR_FLAG_HOOKSHOT_ATTACHED)) { this->actionFunc = func_80AFB89C; } else { Math_SmoothStepToF(&this->actor.scale.x, 0.25f, 0.4f, 1.0f, 0.0f); @@ -113,7 +113,7 @@ void func_80AFB89C(EnSi* this, PlayState* play) { Actor_SetScale(&this->actor, this->actor.scale.x); this->actor.shape.rot.y += 0x400; - if (!CHECK_FLAG_ALL(this->actor.flags, ACTOR_FLAG_13)) { + if (!CHECK_FLAG_ALL(this->actor.flags, ACTOR_FLAG_HOOKSHOT_ATTACHED)) { Item_Give(play, ITEM_SKULL_TOKEN); player->actor.freezeTimer = 10; Message_StartTextbox(play, 0xB4, NULL);