diff options
| author | Lauri Koskela <luryus@users.noreply.github.com> | 2022-11-25 19:52:28 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-11-25 12:52:28 -0500 |
| commit | b57f2162ee71b23643fd4e4133f7455905c2969a (patch) | |
| tree | 72253c04906f043ec0815ddf8c9ee9c6e5b6f893 /src/code | |
| parent | 9c35716fe2c0ff6447525c23a1364694e6ade01f (diff) | |
Document NPC talking and player tracking (#1426)
* Rename npc dialog state variable
* Rename and doc NPC actor talking funtion
* Introduce NpcTalkState enum
* Rename NPC_TALK_STATE enum values
* Document NpcPlayerInteractionState and related functions
* Rename player tracking opt enum variants
* Rename npc functions, interact info
* Minor npc actor function tweaks
* Minor comment fixes for npc
* Generalize NPC player tracking to point tracking
* Change unused NpcInteractInfo field type and name
Co-authored-by: engineer124 <47598039+engineer124@users.noreply.github.com>
* Rename headRot and torsoRot
* Rename GetTalkState to UpdateTalkState
* Minor comment fixes
* Rename rotateActorShape and clarify related comments
* Remove unneeded parentheses in z_en_heishi4.c
* Reformat
* Remove unclear comment
* Rename yPosOffset to yOffset
Co-authored-by: engineer124 <47598039+engineer124@users.noreply.github.com>
Diffstat (limited to 'src/code')
| -rw-r--r-- | src/code/z_actor.c | 316 |
1 files changed, 206 insertions, 110 deletions
diff --git a/src/code/z_actor.c b/src/code/z_actor.c index 618f80f97..0f30255d8 100644 --- a/src/code/z_actor.c +++ b/src/code/z_actor.c @@ -3659,24 +3659,40 @@ Hilite* func_8003435C(Vec3f* object, PlayState* play) { return func_8002EB44(object, &play->view.eye, &lightDir, play->state.gfxCtx); } -s32 func_800343CC(PlayState* play, Actor* actor, s16* arg2, f32 interactRange, callback1_800343CC unkFunc1, - callback2_800343CC unkFunc2) { +/** + * 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) { s16 x; s16 y; if (Actor_ProcessTalkRequest(actor, play)) { - *arg2 = 1; + *talkState = NPC_TALK_STATE_TALKING; return true; } - if (*arg2 != 0) { - *arg2 = unkFunc2(play, actor); + if (*talkState != NPC_TALK_STATE_IDLE) { + *talkState = updateTalkState(play, actor); return false; } Actor_GetScreenPos(play, actor, &x, &y); - if ((x < 0) || (x > SCREEN_WIDTH) || (y < 0) || (y > SCREEN_HEIGHT)) { + // Actor is offscreen return false; } @@ -3684,162 +3700,242 @@ s32 func_800343CC(PlayState* play, Actor* actor, s16* arg2, f32 interactRange, c return false; } - actor->textId = unkFunc1(play, actor); + 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_80116130_0; // size = 0x10 + /* 0x00 */ s16 maxHeadYaw; + /* 0x02 */ s16 minHeadPitch; + /* 0x04 */ s16 maxHeadPitch; + /* 0x06 */ s16 maxTorsoYaw; + /* 0x08 */ s16 minTorsoPitch; + /* 0x0A */ s16 maxTorsoPitch; + /* 0x0C */ u8 rotateYaw; +} NpcTrackingRotLimits; // size = 0x10 typedef struct { - /* 0x00 */ struct_80116130_0 sub_00; - /* 0x10 */ f32 unk_10; - /* 0x14 */ s16 unk_14; -} struct_80116130; // size = 0x18 - -static struct_80116130 D_80116130[] = { - { { 0x2AA8, 0xF1C8, 0x18E2, 0x1554, 0x0000, 0x0000, 1 }, 170.0f, 0x3FFC }, - { { 0x2AA8, 0xEAAC, 0x1554, 0x1554, 0xF8E4, 0x0E38, 1 }, 170.0f, 0x3FFC }, - { { 0x31C4, 0xE390, 0x0E38, 0x0E38, 0xF1C8, 0x071C, 1 }, 170.0f, 0x3FFC }, - { { 0x1554, 0xF1C8, 0x0000, 0x071C, 0xF8E4, 0x0000, 1 }, 170.0f, 0x3FFC }, - { { 0x2AA8, 0xF8E4, 0x071C, 0x0E38, 0xD558, 0x2AA8, 1 }, 170.0f, 0x3FFC }, - { { 0x0000, 0xE390, 0x2AA8, 0x3FFC, 0xF1C8, 0x0E38, 1 }, 170.0f, 0x3FFC }, - { { 0x2AA8, 0xF1C8, 0x0E38, 0x0E38, 0x0000, 0x0000, 1 }, 0.0f, 0x0000 }, - { { 0x2AA8, 0xF1C8, 0x0000, 0x0E38, 0x0000, 0x1C70, 1 }, 0.0f, 0x0000 }, - { { 0x2AA8, 0xF1C8, 0xF1C8, 0x0000, 0x0000, 0x0000, 1 }, 0.0f, 0x0000 }, - { { 0x071C, 0xF1C8, 0x0E38, 0x1C70, 0x0000, 0x0000, 1 }, 0.0f, 0x0000 }, - { { 0x0E38, 0xF1C8, 0x0000, 0x1C70, 0x0000, 0x0E38, 1 }, 0.0f, 0x0000 }, - { { 0x2AA8, 0xE390, 0x1C70, 0x0E38, 0xF1C8, 0x0E38, 1 }, 0.0f, 0x0000 }, - { { 0x18E2, 0xF1C8, 0x0E38, 0x0E38, 0x0000, 0x0000, 1 }, 0.0f, 0x0000 }, + /* 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 + */ +static NpcTrackingParams sNpcTrackingPresets[] = { + { { 0x2AA8, -0x0E38, 0x18E2, 0x1554, 0x0000, 0x0000, true }, 170.0f, 0x3FFC }, + { { 0x2AA8, -0x1554, 0x1554, 0x1554, -0x071C, 0x0E38, true }, 170.0f, 0x3FFC }, + { { 0x31C4, -0x1C70, 0x0E38, 0x0E38, -0x0E38, 0x071C, true }, 170.0f, 0x3FFC }, + { { 0x1554, -0x0E38, 0x0000, 0x071C, -0x071C, 0x0000, true }, 170.0f, 0x3FFC }, + { { 0x2AA8, -0x071C, 0x071C, 0x0E38, -0x2AA8, 0x2AA8, true }, 170.0f, 0x3FFC }, + { { 0x0000, -0x1C70, 0x2AA8, 0x3FFC, -0x0E38, 0x0E38, true }, 170.0f, 0x3FFC }, + { { 0x2AA8, -0x0E38, 0x0E38, 0x0E38, 0x0000, 0x0000, true }, 0.0f, 0x0000 }, + { { 0x2AA8, -0x0E38, 0x0000, 0x0E38, 0x0000, 0x1C70, true }, 0.0f, 0x0000 }, + { { 0x2AA8, -0x0E38, -0x0E38, 0x0000, 0x0000, 0x0000, true }, 0.0f, 0x0000 }, + { { 0x071C, -0x0E38, 0x0E38, 0x1C70, 0x0000, 0x0000, true }, 0.0f, 0x0000 }, + { { 0x0E38, -0x0E38, 0x0000, 0x1C70, 0x0000, 0x0E38, true }, 0.0f, 0x0000 }, + { { 0x2AA8, -0x1C70, 0x1C70, 0x0E38, -0x0E38, 0x0E38, true }, 0.0f, 0x0000 }, + { { 0x18E2, -0x0E38, 0x0E38, 0x0E38, 0x0000, 0x0000, true }, 0.0f, 0x0000 }, }; -void func_800344BC(Actor* actor, struct_80034A14_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, 2000, 1); - temp1 = (ABS(sp40) >= 0x8000) ? 0 : ABS(sp40); - arg1->unk_08.y = CLAMP(arg1->unk_08.y, -temp1, temp1); + temp = (ABS(bodyYawDiff) >= 0x8000) ? 0 : ABS(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, 2000, 1); - temp1 = (ABS(sp40) >= 0x8000) ? 0 : ABS(sp40); - arg1->unk_0E.y = CLAMP(arg1->unk_0E.y, -temp1, temp1); + temp = (ABS(bodyYawDiff) >= 0x8000) ? 0 : ABS(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, 2000, 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, 2000, 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, 2000, 1); } -s16 func_800347E8(s16 arg0) { - return D_80116130[arg0].unk_14; +s16 Npc_GetTrackingPresetMaxPlayerYaw(s16 presetIndex) { + return sNpcTrackingPresets[presetIndex].maxYawForPlayerTracking; } -s16 func_80034810(Actor* actor, struct_80034A14_arg1* arg1, f32 arg2, s16 arg3, s16 arg4) { +/** + * 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 var; - s16 abs_var; + s16 yaw; + s16 yawDiff; - if (arg4 != 0) { - return arg4; + if (trackingMode != NPC_TRACKING_PLAYER_AUTO_TURN) { + return trackingMode; } - if (arg1->unk_00 != 0) { - return 4; + if (interactInfo->talkState != NPC_TALK_STATE_IDLE) { + // When talking, always fully turn to face the player + return NPC_TRACKING_FULL_BODY; } - if (arg2 < Math_Vec3f_DistXYZ(&actor->world.pos, &arg1->unk_18)) { - arg1->unk_04 = 0; - arg1->unk_06 = 0; - return 1; + 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; } - var = Math_Vec3f_Yaw(&actor->world.pos, &arg1->unk_18); - abs_var = ABS((s16)((f32)var - actor->shape.rot.y)); - if (arg3 >= abs_var) { - arg1->unk_04 = 0; - arg1->unk_06 = 0; - return 2; + yaw = Math_Vec3f_Yaw(&actor->world.pos, &interactInfo->trackPos); + yawDiff = ABS((s16)((f32)yaw - actor->shape.rot.y)); + 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; } - if (DECR(arg1->unk_04) != 0) { - return arg1->unk_02; + // 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 (arg1->unk_06) { + switch (interactInfo->autoTurnState) { case 0: case 2: - arg1->unk_04 = Rand_S16Offset(30, 30); - arg1->unk_06++; - return 1; + // Just stand still, not tracking the player + interactInfo->autoTurnTimer = Rand_S16Offset(30, 30); + interactInfo->autoTurnState++; + return NPC_TRACKING_NONE; case 1: - arg1->unk_04 = Rand_S16Offset(10, 10); - arg1->unk_06++; - return 3; + // Glance at the player by only turning the head + interactInfo->autoTurnTimer = Rand_S16Offset(10, 10); + interactInfo->autoTurnState++; + return NPC_TRACKING_HEAD; } - return 4; + // Auto-turn sequence complete, turn towards the player + return NPC_TRACKING_FULL_BODY; } -void func_80034A14(Actor* actor, struct_80034A14_arg1* arg1, s16 arg2, s16 arg3) { - struct_80116130_0 sp38; +/** + * 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; - arg1->unk_02 = func_80034810(actor, arg1, D_80116130[arg2].unk_10, D_80116130[arg2].unk_14, arg3); + interactInfo->trackingMode = + Npc_UpdateAutoTurn(actor, interactInfo, sNpcTrackingPresets[presetIndex].autoTurnDistanceRange, + sNpcTrackingPresets[presetIndex].maxYawForPlayerTracking, trackingMode); - sp38 = D_80116130[arg2].sub_00; + rotLimits = sNpcTrackingPresets[presetIndex].rotLimits; - switch (arg1->unk_02) { - case 1: - sp38.unk_00 = 0; - sp38.unk_04 = 0; - sp38.unk_02 = 0; + switch (interactInfo->trackingMode) { + case NPC_TRACKING_NONE: + rotLimits.maxHeadYaw = 0; + rotLimits.maxHeadPitch = 0; + rotLimits.minHeadPitch = 0; FALLTHROUGH; - case 3: - sp38.unk_06 = 0; - sp38.unk_0A = 0; - sp38.unk_08 = 0; + case NPC_TRACKING_HEAD: + rotLimits.maxTorsoYaw = 0; + rotLimits.maxTorsoPitch = 0; + rotLimits.minTorsoPitch = 0; FALLTHROUGH; - case 2: - sp38.unk_0C = 0; + case NPC_TRACKING_HEAD_AND_TORSO: + rotLimits.rotateYaw = false; break; } - func_800344BC(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* func_80034B28(GraphicsContext* gfxCtx) { |
