summaryrefslogtreecommitdiff
path: root/src/code
diff options
context:
space:
mode:
authorengineer124 <47598039+engineer124@users.noreply.github.com>2023-03-07 21:33:16 -0500
committerGitHub <noreply@github.com>2023-03-07 23:33:16 -0300
commite8f906100b73872174004a6ca354ba459a3e9bb4 (patch)
tree2b949878a35fcc91a2f845690ac1d6708de9b65a /src/code
parente50ab59cf789deb7aef4ccc3e4e5c69eb217b3c2 (diff)
NPC Tracking (#1184)
* npc tracking * fix func declaration * more cleanup * rm named var * more cleanup
Diffstat (limited to 'src/code')
-rw-r--r--src/code/z_actor.c365
1 files changed, 240 insertions, 125 deletions
diff --git a/src/code/z_actor.c b/src/code/z_actor.c
index f6039a8ed..d00061bf5 100644
--- a/src/code/z_actor.c
+++ b/src/code/z_actor.c
@@ -4015,170 +4015,285 @@ void Actor_GetClosestPosOnPath(Vec3s* points, s32 numPoints, Vec3f* srcPos, Vec3
}
}
-// unused
-s32 func_800BD2B4(PlayState* play, Actor* actor, s16* arg2, f32 arg3, u16 (*textIdCallback)(PlayState*, Actor*),
- s16 (*arg5)(PlayState*, Actor*)) {
+/**
+ * Updates NPC talking state. Checks for a talk request and updates
+ * the talkState parameter when a dialog is ongoing. Otherwise checks if
+ * the actor is onscreen, advertises the interaction in a range and sets
+ * the current text id if necessary.
+ *
+ * The talk state values are defined in the NpcTalkState enum.
+ *
+ * @see NpcTalkState
+ *
+ * @param[in,out] talkState Talk state
+ * @param interactRange The interact (talking) range for the actor
+ * @param getTextId Callback for getting the next text id
+ * @param updateTalkState Callback for getting the next talkState value
+ * @return True if a new dialog was started (player talked to the actor). False otherwise.
+ */
+s32 Npc_UpdateTalking(PlayState* play, Actor* actor, s16* talkState, f32 interactRange, NpcGetTextIdFunc getTextId,
+ NpcUpdateTalkStateFunc updateTalkState) {
if (Actor_ProcessTalkRequest(actor, &play->state)) {
- *arg2 = true;
+ *talkState = NPC_TALK_STATE_TALKING;
return true;
- } else if (*arg2) {
- *arg2 = arg5(play, actor);
- return false;
- } else if (!Actor_OnScreen(play, actor)) {
+ }
+
+ if (*talkState != NPC_TALK_STATE_IDLE) {
+ *talkState = updateTalkState(play, actor);
return false;
- } else if (!func_800B8614(actor, play, arg3)) {
+ }
+
+ if (!Actor_OnScreen(play, actor)) {
return false;
- } else {
- actor->textId = textIdCallback(play, actor);
+ }
+
+ if (!func_800B8614(actor, play, interactRange)) {
return false;
}
+
+ actor->textId = getTextId(play, actor);
+
+ return false;
}
typedef struct {
- /* 0x00 */ s16 unk_00;
- /* 0x02 */ s16 unk_02;
- /* 0x04 */ s16 unk_04;
- /* 0x06 */ s16 unk_06;
- /* 0x08 */ s16 unk_08;
- /* 0x0A */ s16 unk_0A;
- /* 0x0C */ u8 unk_0C;
-} struct_801AEE38_0; // size = 0x10
+ /* 0x0 */ s16 maxHeadYaw;
+ /* 0x2 */ s16 minHeadPitch;
+ /* 0x4 */ s16 maxHeadPitch;
+ /* 0x6 */ s16 maxTorsoYaw;
+ /* 0x8 */ s16 minTorsoPitch;
+ /* 0xA */ s16 maxTorsoPitch;
+ /* 0xC */ u8 rotateYaw;
+} NpcTrackingRotLimits; // size = 0x10
typedef struct {
- /* 0x00 */ struct_801AEE38_0 sub_00;
- /* 0x10 */ f32 unk_10;
- /* 0x14 */ s16 unk_14;
-} struct_801AEE38; // size = 0x18
-
-struct_801AEE38 D_801AEE38[] = {
- { { 0x1C20, 0xE390, 0x1C70, 0x1554, 0x0000, 0x0000, 0x0000 }, 170.0f, 0x3FFC },
- { { 0x2AA8, 0xEAAC, 0x1554, 0x1554, 0xF8E4, 0x0E38, 0x0001 }, 170.0f, 0x3FFC },
- { { 0x31C4, 0xE390, 0x0E38, 0x0E38, 0xF1C8, 0x071C, 0x0001 }, 170.0f, 0x3FFC },
- { { 0x1554, 0xF1C8, 0x0000, 0x071C, 0xF8E4, 0x0000, 0x0001 }, 170.0f, 0x3FFC },
- { { 0x2AA8, 0xF8E4, 0x071C, 0x0E38, 0xD558, 0x2AA8, 0x0001 }, 170.0f, 0x3FFC },
- { { 0x0000, 0xE390, 0x2AA8, 0x3FFC, 0xF1C8, 0x0E38, 0x0001 }, 170.0f, 0x3FFC },
- { { 0x2AA8, 0xF1C8, 0x0E38, 0x0E38, 0x0000, 0x0000, 0x0001 }, 0.0f, 0x0000 },
- { { 0x2AA8, 0xF1C8, 0x0000, 0x0E38, 0x0000, 0x1C70, 0x0001 }, 0.0f, 0x0000 },
- { { 0x2AA8, 0xF1C8, 0xF1C8, 0x0000, 0x0000, 0x0000, 0x0001 }, 0.0f, 0x0000 },
- { { 0x071C, 0xF1C8, 0x0E38, 0x1C70, 0x0000, 0x0000, 0x0001 }, 0.0f, 0x0000 },
- { { 0x0E38, 0xF1C8, 0x0000, 0x1C70, 0x0000, 0x0E38, 0x0001 }, 0.0f, 0x0000 },
- { { 0x2AA8, 0xE390, 0x1C70, 0x0E38, 0xF1C8, 0x0E38, 0x0001 }, 0.0f, 0x0000 },
- { { 0x18E2, 0xF1C8, 0x0E38, 0x0E38, 0x0000, 0x0000, 0x0001 }, 0.0f, 0x0000 },
- { { 0x2A6C, 0xE390, 0x1C70, 0x1554, 0x0000, 0x0000, 0x0000 }, 170.0f, 0x3FFC },
+ /* 0x00 */ NpcTrackingRotLimits rotLimits;
+ // Fields specific to NPC_TRACKING_PLAYER_AUTO_TURN mode
+ /* 0x10 */ f32 autoTurnDistanceRange; // Max distance to player to enable tracking and auto-turn
+ /* 0x14 */ s16 maxYawForPlayerTracking; // Player is tracked if within this yaw
+} NpcTrackingParams; // size = 0x18
+
+/**
+ * Npc tracking angle limit presets to use with Npc_TrackPoint.
+ *
+ * @see Npc_TrackPoint
+ */
+NpcTrackingParams sNpcTrackingPresets[] = {
+ { { 0x1C20, 0xE390, 0x1C70, 0x1554, 0x0000, 0x0000, false }, 170.0f, 0x3FFC },
+ { { 0x2AA8, 0xEAAC, 0x1554, 0x1554, 0xF8E4, 0x0E38, true }, 170.0f, 0x3FFC },
+ { { 0x31C4, 0xE390, 0x0E38, 0x0E38, 0xF1C8, 0x071C, true }, 170.0f, 0x3FFC },
+ { { 0x1554, 0xF1C8, 0x0000, 0x071C, 0xF8E4, 0x0000, true }, 170.0f, 0x3FFC },
+ { { 0x2AA8, 0xF8E4, 0x071C, 0x0E38, 0xD558, 0x2AA8, true }, 170.0f, 0x3FFC },
+ { { 0x0000, 0xE390, 0x2AA8, 0x3FFC, 0xF1C8, 0x0E38, true }, 170.0f, 0x3FFC },
+ { { 0x2AA8, 0xF1C8, 0x0E38, 0x0E38, 0x0000, 0x0000, true }, 0.0f, 0x0000 },
+ { { 0x2AA8, 0xF1C8, 0x0000, 0x0E38, 0x0000, 0x1C70, true }, 0.0f, 0x0000 },
+ { { 0x2AA8, 0xF1C8, 0xF1C8, 0x0000, 0x0000, 0x0000, true }, 0.0f, 0x0000 },
+ { { 0x071C, 0xF1C8, 0x0E38, 0x1C70, 0x0000, 0x0000, true }, 0.0f, 0x0000 },
+ { { 0x0E38, 0xF1C8, 0x0000, 0x1C70, 0x0000, 0x0E38, true }, 0.0f, 0x0000 },
+ { { 0x2AA8, 0xE390, 0x1C70, 0x0E38, 0xF1C8, 0x0E38, true }, 0.0f, 0x0000 },
+ { { 0x18E2, 0xF1C8, 0x0E38, 0x0E38, 0x0000, 0x0000, true }, 0.0f, 0x0000 },
+ { { 0x2A6C, 0xE390, 0x1C70, 0x1554, 0x0000, 0x0000, false }, 170.0f, 0x3FFC },
};
-void func_800BD384(Actor* actor, struct_800BD888_arg1* arg1, s16 arg2, s16 arg3, s16 arg4, s16 arg5, s16 arg6, s16 arg7,
- u8 arg8) {
- s16 sp46;
- s16 sp44;
- s16 temp2;
- s16 sp40;
- s16 temp1;
- Vec3f sp30;
+/**
+ * Smoothly turns the actor's whole body and updates torso and head rotations in
+ * NpcInteractInfo so that the actor tracks the point specified in NpcInteractInfo.trackPos.
+ * Rotations are limited to specified angles.
+ *
+ * Head and torso rotation angles are determined by calculating the pitch and yaw
+ * from the actor position to the given target position.
+ *
+ * The y position of the actor is offset by NpcInteractInfo.yOffset
+ * before calculating the angles. It can be used to configure the height difference
+ * between the actor and the target.
+ *
+ * @param maxHeadYaw maximum head yaw difference from neutral position
+ * @param maxHeadPitch maximum head pitch angle
+ * @param minHeadPitch minimum head pitch angle
+ * @param maxTorsoYaw maximum torso yaw difference from neutral position
+ * @param maxTorsoPitch maximum torso pitch angle
+ * @param minTorsoPitch minimum torso pitch angle
+ * @param rotateYaw if true, the actor's yaw (shape.rot.y) is updated to turn the actor's whole body
+ */
+void Npc_TrackPointWithLimits(Actor* actor, NpcInteractInfo* interactInfo, s16 maxHeadYaw, s16 maxHeadPitch,
+ s16 minHeadPitch, s16 maxTorsoYaw, s16 maxTorsoPitch, s16 minTorsoPitch, u8 rotateYaw) {
+ s16 pitchTowardsTarget;
+ s16 yawTowardsTarget;
+ s16 torsoPitch;
+ s16 bodyYawDiff;
+ s16 temp;
+ Vec3f offsetActorPos;
- sp30.x = actor->world.pos.x;
- sp30.y = actor->world.pos.y + arg1->unk_14;
- sp30.z = actor->world.pos.z;
+ offsetActorPos.x = actor->world.pos.x;
+ offsetActorPos.y = actor->world.pos.y + interactInfo->yOffset;
+ offsetActorPos.z = actor->world.pos.z;
- sp46 = Math_Vec3f_Pitch(&sp30, &arg1->unk_18);
- sp44 = Math_Vec3f_Yaw(&sp30, &arg1->unk_18);
- sp40 = Math_Vec3f_Yaw(&actor->world.pos, &arg1->unk_18) - actor->shape.rot.y;
+ pitchTowardsTarget = Math_Vec3f_Pitch(&offsetActorPos, &interactInfo->trackPos);
+ yawTowardsTarget = Math_Vec3f_Yaw(&offsetActorPos, &interactInfo->trackPos);
+ bodyYawDiff = Math_Vec3f_Yaw(&actor->world.pos, &interactInfo->trackPos) - actor->shape.rot.y;
- temp1 = CLAMP(sp40, -arg2, arg2);
- Math_SmoothStepToS(&arg1->unk_08.y, temp1, 6, 2000, 1);
+ temp = CLAMP(bodyYawDiff, -maxHeadYaw, maxHeadYaw);
+ Math_SmoothStepToS(&interactInfo->headRot.y, temp, 6, 0x7D0, 1);
- temp1 = (ABS_ALT(sp40) >= 0x8000) ? 0 : ABS_ALT(sp40);
- arg1->unk_08.y = CLAMP(arg1->unk_08.y, -temp1, temp1);
+ temp = (ABS_ALT(bodyYawDiff) >= 0x8000) ? 0 : ABS_ALT(bodyYawDiff);
+ interactInfo->headRot.y = CLAMP(interactInfo->headRot.y, -temp, temp);
- sp40 -= arg1->unk_08.y;
+ bodyYawDiff -= interactInfo->headRot.y;
- temp1 = CLAMP(sp40, -arg5, arg5);
- Math_SmoothStepToS(&arg1->unk_0E.y, temp1, 6, 2000, 1);
+ temp = CLAMP(bodyYawDiff, -maxTorsoYaw, maxTorsoYaw);
+ Math_SmoothStepToS(&interactInfo->torsoRot.y, temp, 6, 0x7D0, 1);
- temp1 = (ABS_ALT(sp40) >= 0x8000) ? 0 : ABS_ALT(sp40);
- arg1->unk_0E.y = CLAMP(arg1->unk_0E.y, -temp1, temp1);
+ temp = (ABS_ALT(bodyYawDiff) >= 0x8000) ? 0 : ABS_ALT(bodyYawDiff);
+ interactInfo->torsoRot.y = CLAMP(interactInfo->torsoRot.y, -temp, temp);
- if (arg8) {
- Math_SmoothStepToS(&actor->shape.rot.y, sp44, 6, 2000, 1);
+ if (rotateYaw) {
+ Math_SmoothStepToS(&actor->shape.rot.y, yawTowardsTarget, 6, 0x7D0, 1);
}
- temp1 = CLAMP(sp46, arg4, (s16)(u16)arg3);
- Math_SmoothStepToS(&arg1->unk_08.x, temp1, 6, 2000, 1);
+ temp = CLAMP(pitchTowardsTarget, minHeadPitch, (s16)(u16)maxHeadPitch);
+ Math_SmoothStepToS(&interactInfo->headRot.x, temp, 6, 0x7D0, 1);
- temp2 = sp46 - arg1->unk_08.x;
+ torsoPitch = pitchTowardsTarget - interactInfo->headRot.x;
- temp1 = CLAMP(temp2, arg7, arg6);
- Math_SmoothStepToS(&arg1->unk_0E.x, temp1, 6, 2000, 1);
+ temp = CLAMP(torsoPitch, minTorsoPitch, maxTorsoPitch);
+ Math_SmoothStepToS(&interactInfo->torsoRot.x, temp, 6, 0x7D0, 1);
}
// unused
-s16 func_800BD6B8(s16 arg0) {
- return D_801AEE38[arg0].unk_14;
+s16 Npc_GetTrackingPresetMaxPlayerYaw(s16 presetIndex) {
+ return sNpcTrackingPresets[presetIndex].maxYawForPlayerTracking;
}
-s16 func_800BD6E4(Actor* actor, struct_800BD888_arg1* arg1, f32 arg2, s16 arg3, s16 flag) {
+/**
+ * Handles NPC tracking modes and auto-turning towards the player when
+ * NPC_TRACKING_PLAYER_AUTO_TURN tracking mode is used.
+ *
+ * Returns a tracking mode that will determine which actor limbs
+ * will be rotated towards the target.
+ *
+ * When the player is behind the actor (i.e. not in the yaw range in front of the actor
+ * defined by maxYawForPlayerTracking), the actor will start an auto-turn sequence:
+ * - look forward for 30-60 frames
+ * - turn head to look at the player for 10-20 frames
+ * - look forward for 30-60 frames
+ * - turn the entire body to face the player
+ *
+ * @param distanceRange Max distance to player that tracking and auto-turning will be active for
+ * @param maxYawForPlayerTracking Maximum angle for tracking the player.
+ * @param trackingMode The tracking mode selected by the actor. If this is not
+ * NPC_TRACKING_PLAYER_AUTO_TURN this function does nothing
+ *
+ * @return The tracking mode (NpcTrackingMode) to use for the current frame.
+ */
+s16 Npc_UpdateAutoTurn(Actor* actor, NpcInteractInfo* interactInfo, f32 distanceRange, s16 maxYawForPlayerTracking,
+ s16 trackingMode) {
s32 pad;
+ s16 yaw;
+ s16 yawDiff;
- if (flag) {
- return flag;
- } else if (arg1->unk_00 != 0) {
- return 4;
- } else if (arg2 < Math_Vec3f_DistXYZ(&actor->world.pos, &arg1->unk_18)) {
- arg1->unk_04 = 0;
- arg1->unk_06 = 0;
- return 1;
- } else {
- s16 yaw = Math_Vec3f_Yaw(&actor->world.pos, &arg1->unk_18);
- s16 phi_a0 = ABS_ALT(BINANG_SUB(yaw, actor->shape.rot.y));
-
- if (arg3 >= phi_a0) {
- arg1->unk_04 = 0;
- arg1->unk_06 = 0;
- return 2;
- } else if (DECR(arg1->unk_04) != 0) {
- return arg1->unk_02;
- } else {
- switch (arg1->unk_06) {
- case 0:
- case 2:
- arg1->unk_04 = Rand_S16Offset(30, 30);
- arg1->unk_06++;
- return 1;
-
- case 1:
- arg1->unk_04 = Rand_S16Offset(10, 10);
- arg1->unk_06++;
- return 3;
-
- default:
- return 4;
- }
- }
+ if (trackingMode != NPC_TRACKING_PLAYER_AUTO_TURN) {
+ return trackingMode;
+ }
+
+ if (interactInfo->talkState != NPC_TALK_STATE_IDLE) {
+ // When talking, always fully turn to face the player
+ return NPC_TRACKING_FULL_BODY;
}
-}
-// This function is very similar to OoT's func_80034A14
-void func_800BD888(Actor* actor, struct_800BD888_arg1* arg1, s16 arg2, s16 arg3) {
- struct_801AEE38_0 sp38;
+ if (distanceRange < Math_Vec3f_DistXYZ(&actor->world.pos, &interactInfo->trackPos)) {
+ // Player is too far away, do not track
+ interactInfo->autoTurnTimer = 0;
+ interactInfo->autoTurnState = 0;
+ return NPC_TRACKING_NONE;
+ }
- arg1->unk_02 = func_800BD6E4(actor, arg1, D_801AEE38[arg2].unk_10, D_801AEE38[arg2].unk_14, arg3);
- sp38 = D_801AEE38[arg2].sub_00;
+ yaw = Math_Vec3f_Yaw(&actor->world.pos, &interactInfo->trackPos);
+ yawDiff = ABS_ALT(BINANG_SUB(yaw, actor->shape.rot.y));
- switch (arg1->unk_02) {
- case 1:
- sp38.unk_00 = 0;
- sp38.unk_04 = 0;
- sp38.unk_02 = 0;
- case 3:
- sp38.unk_06 = 0;
- sp38.unk_0A = 0;
- sp38.unk_08 = 0;
+ if (maxYawForPlayerTracking >= yawDiff) {
+ // Player is in front of the actor, track with the head and the torso
+ interactInfo->autoTurnTimer = 0;
+ interactInfo->autoTurnState = 0;
+ return NPC_TRACKING_HEAD_AND_TORSO;
+ }
+
+ // Player is behind the actor, run the auto-turn sequence.
+
+ if (DECR(interactInfo->autoTurnTimer) != 0) {
+ // While the timer is still running, return the previous tracking mode
+ return interactInfo->trackingMode;
+ }
+
+ switch (interactInfo->autoTurnState) {
+ case 0:
case 2:
- sp38.unk_0C = 0;
+ // Just stand still, not tracking the player
+ interactInfo->autoTurnTimer = Rand_S16Offset(30, 30);
+ interactInfo->autoTurnState++;
+ return NPC_TRACKING_NONE;
+
+ case 1:
+ // Glance at the player by only turning the head
+ interactInfo->autoTurnTimer = Rand_S16Offset(10, 10);
+ interactInfo->autoTurnState++;
+ return NPC_TRACKING_HEAD;
+
+ default:
+ // Auto-turn sequence complete, turn towards the player
+ return NPC_TRACKING_FULL_BODY;
+ }
+}
+
+/**
+ * Rotates the actor's whole body, torso and head tracking the point specified in NpcInteractInfo.trackPos.
+ * Uses angle limits from a preset selected from from sNpcTrackingPresets.
+ *
+ * The trackingMode parameter controls whether the head and torso are turned towards the target.
+ * If not, they are smoothly turned towards zero. Setting the parameter to NPC_TRACKING_FULL_BODY
+ * causes the actor's whole body to be rotated to face the target.
+ *
+ * If NPC_TRACKING_PLAYER_AUTO_TURN is used, the actor will track the player with its head and torso as long
+ * as the player is in front of the actor (within a yaw angle specified in the option preset).
+ * If the player is outside of this angle, the actor will turn to face the player after a while.
+ *
+ * @see Npc_UpdateAutoTurn
+ * @see sNpcTrackingPresets
+ * @see NpcTrackingMode
+ *
+ * @param presetIndex The index to a preset in sNpcTrackingPresets
+ * @param trackingMode A value from NpcTrackingMode enum
+ */
+void Npc_TrackPoint(Actor* actor, NpcInteractInfo* interactInfo, s16 presetIndex, s16 trackingMode) {
+ NpcTrackingRotLimits rotLimits;
+
+ interactInfo->trackingMode =
+ Npc_UpdateAutoTurn(actor, interactInfo, sNpcTrackingPresets[presetIndex].autoTurnDistanceRange,
+ sNpcTrackingPresets[presetIndex].maxYawForPlayerTracking, trackingMode);
+
+ rotLimits = sNpcTrackingPresets[presetIndex].rotLimits;
+
+ switch (interactInfo->trackingMode) {
+ case NPC_TRACKING_NONE:
+ rotLimits.maxHeadYaw = 0;
+ rotLimits.maxHeadPitch = 0;
+ rotLimits.minHeadPitch = 0;
+ // fallthrough
+ case NPC_TRACKING_HEAD:
+ rotLimits.maxTorsoYaw = 0;
+ rotLimits.maxTorsoPitch = 0;
+ rotLimits.minTorsoPitch = 0;
+ // fallthrough
+ case NPC_TRACKING_HEAD_AND_TORSO:
+ rotLimits.rotateYaw = false;
+ break;
+
+ default:
+ break;
}
- func_800BD384(actor, arg1, sp38.unk_00, sp38.unk_04, sp38.unk_02, sp38.unk_06, sp38.unk_0A, sp38.unk_08,
- sp38.unk_0C);
+ Npc_TrackPointWithLimits(actor, interactInfo, rotLimits.maxHeadYaw, rotLimits.maxHeadPitch, rotLimits.minHeadPitch,
+ rotLimits.maxTorsoYaw, rotLimits.maxTorsoPitch, rotLimits.minTorsoPitch,
+ rotLimits.rotateYaw);
}
Gfx D_801AEF88[] = {