summaryrefslogtreecommitdiff
path: root/src/code
diff options
context:
space:
mode:
authorfig02 <fig02srl@gmail.com>2024-03-23 12:21:45 -0400
committerGitHub <noreply@github.com>2024-03-23 12:21:45 -0400
commitbd0941405d116ba14efacb6aeccea067958366fe (patch)
tree390c14cb27ea407063fd1780bac8d080d65a643e /src/code
parent07505dae371a99abef82f7ba3b7acc0d5253eeca (diff)
Document Player's Face and z_actor FaceChange functions (#1928)
* create some enums * gonna try struct instead of array * struct works. add docs too * inline function comments * fix function comment * name faces, move enums * rename textures * outnames * remove comments * change comment slightly * fixup face comments * review * offset comments * add and use PLAYER_FACE_MAX * typo * more comment on blinkDuration * another change to the comment
Diffstat (limited to 'src/code')
-rw-r--r--src/code/z_actor.c69
-rw-r--r--src/code/z_player_lib.c119
2 files changed, 140 insertions, 48 deletions
diff --git a/src/code/z_actor.c b/src/code/z_actor.c
index d9b7f579c..128fa94d8 100644
--- a/src/code/z_actor.c
+++ b/src/code/z_actor.c
@@ -3189,33 +3189,64 @@ void Enemy_StartFinishingBlow(PlayState* play, Actor* actor) {
SfxSource_PlaySfxAtFixedWorldPos(play, &actor->world.pos, 20, NA_SE_EN_LAST_DAMAGE);
}
-s16 func_80032CB4(s16* arg0, s16 arg1, s16 arg2, s16 arg3) {
- if (DECR(arg0[1]) == 0) {
- arg0[1] = Rand_S16Offset(arg1, arg2);
- }
-
- if ((arg0[1] - arg3) > 0) {
- arg0[0] = 0;
- } else if (((arg0[1] - arg3) > -2) || (arg0[1] < 2)) {
- arg0[0] = 1;
+/**
+ * Updates `FaceChange` data for a blinking pattern.
+ * This system expects that the actor using the system has defined 3 faces in this exact order:
+ * "eyes open", "eyes half open", "eyes closed".
+ *
+ * @param faceChange pointer to an actor's faceChange data
+ * @param blinkIntervalBase The base number of frames between blinks
+ * @param blinkIntervalRandRange The range for a random number of frames that can be added to `blinkIntervalBase`
+ * @param blinkDuration The number of frames it takes for a single blink to occur
+ */
+s16 FaceChange_UpdateBlinking(FaceChange* faceChange, s16 blinkIntervalBase, s16 blinkIntervalRandRange,
+ s16 blinkDuration) {
+ if (DECR(faceChange->timer) == 0) {
+ faceChange->timer = Rand_S16Offset(blinkIntervalBase, blinkIntervalRandRange);
+ }
+
+ if ((faceChange->timer - blinkDuration) > 0) {
+ // `timer - duration` is positive so this is the default state: "eyes open" face
+ faceChange->face = 0;
+ } else if (((faceChange->timer - blinkDuration) > -2) || (faceChange->timer < 2)) {
+ // This condition aims to catch both cases where the "eyes half open" face is needed.
+ // Note that the comparison assumes the duration of the "eyes half open" phase is 2 frames, irrespective of the
+ // value of `blinkDuration`. The duration for the "eyes closed" phase is `blinkDuration - 4`.
+ // For Player's use case `blinkDuration` is 6, so the "eyes closed" phase happens to have
+ // the same duration as each "eyes half open" phase.
+ faceChange->face = 1;
} else {
- arg0[0] = 2;
+ // If both conditions above fail, the only possibility left is the "eyes closed" face
+ faceChange->face = 2;
}
- return arg0[0];
+ return faceChange->face;
}
-s16 func_80032D60(s16* arg0, s16 arg1, s16 arg2, s16 arg3) {
- if (DECR(arg0[1]) == 0) {
- arg0[1] = Rand_S16Offset(arg1, arg2);
- arg0[0]++;
-
- if ((arg0[0] % 3) == 0) {
- arg0[0] = (s32)(Rand_ZeroOne() * arg3) * 3;
+/**
+ * Updates `FaceChange` data for randomly selected face sets.
+ * Each set contains 3 faces. After the timer runs out, the next face in the set is used.
+ * After the third face in a set is used, a new face set is randomly chosen.
+ *
+ * @param faceChange pointer to an actor's faceChange data
+ * @param changeTimerBase The base number of frames between each face change
+ * @param changeTimerRandRange The range for a random number of frames that can be added to `changeTimerBase`
+ * @param faceSetRange The max number of face sets that will be chosen from
+ */
+s16 FaceChange_UpdateRandomSet(FaceChange* faceChange, s16 changeTimerBase, s16 changeTimerRandRange,
+ s16 faceSetRange) {
+ if (DECR(faceChange->timer) == 0) {
+ faceChange->timer = Rand_S16Offset(changeTimerBase, changeTimerRandRange);
+ faceChange->face++;
+
+ if ((faceChange->face % 3) == 0) {
+ // Randomly chose a "set number", then multiply by 3 because each set has 3 faces.
+ // This will use the first face in the newly chosen set.
+ faceChange->face = (s32)(Rand_ZeroOne() * faceSetRange) * 3;
}
}
- return arg0[0];
+ return faceChange->face;
}
void BodyBreak_Alloc(BodyBreak* bodyBreak, s32 count, PlayState* play) {
diff --git a/src/code/z_player_lib.c b/src/code/z_player_lib.c
index 5fc1c4863..77f0cd035 100644
--- a/src/code/z_player_lib.c
+++ b/src/code/z_player_lib.c
@@ -813,41 +813,94 @@ s32 Player_GetEnvironmentalHazard(PlayState* play) {
return envHazard + 1;
}
-u8 sEyeMouthIndices[][2] = {
- { 0, 0 }, { 1, 0 }, { 2, 0 }, { 0, 0 }, { 1, 0 }, { 2, 0 }, { 4, 0 }, { 5, 1 },
- { 7, 2 }, { 0, 2 }, { 3, 0 }, { 4, 0 }, { 2, 2 }, { 1, 1 }, { 0, 2 }, { 0, 0 },
+u8 sPlayerFaces[PLAYER_FACE_MAX][PLAYER_FACEPART_MAX] = {
+ // The first 6 faces defined must be default blinking faces. See relevant code in `Player_UpdateCommon`.
+ { PLAYER_EYES_OPEN, PLAYER_MOUTH_CLOSED }, // PLAYER_FACE_NEUTRAL
+ { PLAYER_EYES_HALF, PLAYER_MOUTH_CLOSED }, // PLAYER_FACE_NEUTRAL_BLINKING_HALF
+ { PLAYER_EYES_CLOSED, PLAYER_MOUTH_CLOSED }, // PLAYER_FACE_NEUTRAL_BLINKING_CLOSED
+
+ // This duplicate set of blinking faces is defined because Player will choose between the first and second set
+ // based on gameplayFrames. See relevant code in `Player_UpdateCommon`.
+ // This, in theory, allows for psuedo-random variance in the faces used. But in practice, duplicate faces are used.
+ { PLAYER_EYES_OPEN, PLAYER_MOUTH_CLOSED }, // PLAYER_FACE_NEUTRAL_2
+ { PLAYER_EYES_HALF, PLAYER_MOUTH_CLOSED }, // PLAYER_FACE_NEUTRAL_BLINKING_HALF_2
+ { PLAYER_EYES_CLOSED, PLAYER_MOUTH_CLOSED }, // PLAYER_FACE_NEUTRAL_BLINKING_CLOSED_2
+
+ // The rest of these faces go unused. Face data encoded within animations handles all other faces.
+ { PLAYER_EYES_RIGHT, PLAYER_MOUTH_CLOSED }, // PLAYER_FACE_LOOK_RIGHT
+ { PLAYER_EYES_WIDE, PLAYER_MOUTH_HALF }, // PLAYER_FACE_SURPRISED
+ { PLAYER_EYES_WINCING, PLAYER_MOUTH_OPEN }, // PLAYER_FACE_HURT
+ { PLAYER_EYES_OPEN, PLAYER_MOUTH_OPEN }, // PLAYER_FACE_GASP
+ { PLAYER_EYES_LEFT, PLAYER_MOUTH_CLOSED }, // PLAYER_FACE_LOOK_LEFT
+ { PLAYER_EYES_RIGHT, PLAYER_MOUTH_CLOSED }, // PLAYER_FACE_LOOK_RIGHT_2
+ { PLAYER_EYES_CLOSED, PLAYER_MOUTH_OPEN }, // PLAYER_FACE_EYES_CLOSED_MOUTH_OPEN
+ { PLAYER_EYES_HALF, PLAYER_MOUTH_HALF }, // PLAYER_FACE_OPENING
+ { PLAYER_EYES_OPEN, PLAYER_MOUTH_OPEN }, // PLAYER_FACE_EYES_AND_MOUTH_OPEN
+ { PLAYER_EYES_OPEN, PLAYER_MOUTH_CLOSED }, // PLAYER_FACE_NEUTRAL_3
};
/**
- * Link's eye and mouth textures are placed at the exact same place in adult and child Link's respective object files.
+ * Link's eyes and mouth textures are placed at the exact same place in adult and child Link's respective object files.
* This allows the array to only contain the symbols for one file and have it apply to both. This is a problem for
* shiftability, and changes will need to be made in the code to account for this in a modding scenario. The symbols
* from adult Link's object are used here.
*/
#ifndef AVOID_UB
-void* sEyeTextures[] = {
- gLinkAdultEyesOpenTex, gLinkAdultEyesHalfTex, gLinkAdultEyesClosedfTex, gLinkAdultEyesRollLeftTex,
- gLinkAdultEyesRollRightTex, gLinkAdultEyesShockTex, gLinkAdultEyesUnk1Tex, gLinkAdultEyesUnk2Tex,
+void* sEyeTextures[PLAYER_EYES_MAX] = {
+ gLinkAdultEyesOpenTex, // PLAYER_EYES_OPEN
+ gLinkAdultEyesHalfTex, // PLAYER_EYES_HALF
+ gLinkAdultEyesClosedfTex, // PLAYER_EYES_CLOSED
+ gLinkAdultEyesLeftTex, // PLAYER_EYES_LEFT
+ gLinkAdultEyesRightTex, // PLAYER_EYES_RIGHT
+ gLinkAdultEyesWideTex, // PLAYER_EYES_WIDE
+ gLinkAdultEyesDownTex, // PLAYER_EYES_DOWN
+ gLinkAdultEyesWincingTex, // PLAYER_EYES_WINCING
};
-void* sMouthTextures[] = {
- gLinkAdultMouth1Tex,
- gLinkAdultMouth2Tex,
- gLinkAdultMouth3Tex,
- gLinkAdultMouth4Tex,
+void* sMouthTextures[PLAYER_MOUTH_MAX] = {
+ gLinkAdultMouthClosedTex, // PLAYER_MOUTH_CLOSED
+ gLinkAdultMouthHalfTex, // PLAYER_MOUTH_HALF
+ gLinkAdultMouthOpenTex, // PLAYER_MOUTH_OPEN
+ gLinkAdultMouthSmileTex, // PLAYER_MOUTH_SMILE
};
#else
// Defining `AVOID_UB` will use a 2D array instead and properly use the child link pointers to allow for shifting.
-void* sEyeTextures[][8] = {
- { gLinkAdultEyesOpenTex, gLinkAdultEyesHalfTex, gLinkAdultEyesClosedfTex, gLinkAdultEyesRollLeftTex,
- gLinkAdultEyesRollRightTex, gLinkAdultEyesShockTex, gLinkAdultEyesUnk1Tex, gLinkAdultEyesUnk2Tex },
- { gLinkChildEyesOpenTex, gLinkChildEyesHalfTex, gLinkChildEyesClosedfTex, gLinkChildEyesRollLeftTex,
- gLinkChildEyesRollRightTex, gLinkChildEyesShockTex, gLinkChildEyesUnk1Tex, gLinkChildEyesUnk2Tex },
+void* sEyeTextures[][PLAYER_EYES_MAX] = {
+ {
+ gLinkAdultEyesOpenTex, // PLAYER_EYES_OPEN
+ gLinkAdultEyesHalfTex, // PLAYER_EYES_HALF
+ gLinkAdultEyesClosedfTex, // PLAYER_EYES_CLOSED
+ gLinkAdultEyesLeftTex, // PLAYER_EYES_LEFT
+ gLinkAdultEyesRightTex, // PLAYER_EYES_RIGHT
+ gLinkAdultEyesWideTex, // PLAYER_EYES_WIDE
+ gLinkAdultEyesDownTex, // PLAYER_EYES_DOWN
+ gLinkAdultEyesWincingTex, // PLAYER_EYES_WINCING
+ },
+ {
+ gLinkChildEyesOpenTex, // PLAYER_EYES_OPEN
+ gLinkChildEyesHalfTex, // PLAYER_EYES_HALF
+ gLinkChildEyesClosedfTex, // PLAYER_EYES_CLOSED
+ gLinkChildEyesLeftTex, // PLAYER_EYES_LEFT
+ gLinkChildEyesRightTex, // PLAYER_EYES_RIGHT
+ gLinkChildEyesWideTex, // PLAYER_EYES_WIDE
+ gLinkChildEyesDownTex, // PLAYER_EYES_DOWN
+ gLinkChildEyesWincingTex, // PLAYER_EYES_WINCING
+ },
};
-void* sMouthTextures[][4] = {
- { gLinkAdultMouth1Tex, gLinkAdultMouth2Tex, gLinkAdultMouth3Tex, gLinkAdultMouth4Tex },
- { gLinkChildMouth1Tex, gLinkChildMouth2Tex, gLinkChildMouth3Tex, gLinkChildMouth4Tex },
+void* sMouthTextures[][PLAYER_MOUTH_MAX] = {
+ {
+ gLinkAdultMouthClosedTex, // PLAYER_MOUTH_CLOSED
+ gLinkAdultMouthHalfTex, // PLAYER_MOUTH_HALF
+ gLinkAdultMouthOpenTex, // PLAYER_MOUTH_OPEN
+ gLinkAdultMouthSmileTex, // PLAYER_MOUTH_SMILE
+ },
+ {
+ gLinkChildMouthClosedTex, // PLAYER_MOUTH_CLOSED
+ gLinkChildMouthHalfTex, // PLAYER_MOUTH_HALF
+ gLinkChildMouthOpenTex, // PLAYER_MOUTH_OPEN
+ gLinkChildMouthSmileTex, // PLAYER_MOUTH_SMILE
+ },
};
#endif
@@ -870,23 +923,31 @@ Gfx* sBootDListGroups[][2] = {
void Player_DrawImpl(PlayState* play, void** skeleton, Vec3s* jointTable, s32 dListCount, s32 lod, s32 tunic, s32 boots,
s32 face, OverrideLimbDrawOpa overrideLimbDraw, PostLimbDrawOpa postLimbDraw, void* data) {
Color_RGB8* color;
- s32 eyeIndex = (jointTable[22].x & 0xF) - 1;
- s32 mouthIndex = (jointTable[22].x >> 4) - 1;
+ s32 eyesIndex;
+ s32 mouthIndex;
+
+ // Player's animation data includes eyes and mouth indices for which texture to use on a given frame.
+ // Despite being accessed as "the x component of the 22nd limb", the eyes and mouth indices are stored in 2
+ // additional bytes tacked onto the end of the limb rotation data for a given animation frame.
+ eyesIndex = (jointTable[22].x & 0xF) - 1;
+ mouthIndex = (jointTable[22].x >> 4) - 1;
OPEN_DISPS(play->state.gfxCtx, "../z_player_lib.c", 1721);
- if (eyeIndex < 0) {
- eyeIndex = sEyeMouthIndices[face][0];
+ // If the eyes index provided by the animation is negative, use the value provided by the `face` argument instead
+ if (eyesIndex < 0) {
+ eyesIndex = sPlayerFaces[face][PLAYER_FACEPART_EYES];
}
#ifndef AVOID_UB
- gSPSegment(POLY_OPA_DISP++, 0x08, SEGMENTED_TO_VIRTUAL(sEyeTextures[eyeIndex]));
+ gSPSegment(POLY_OPA_DISP++, 0x08, SEGMENTED_TO_VIRTUAL(sEyeTextures[eyesIndex]));
#else
- gSPSegment(POLY_OPA_DISP++, 0x08, SEGMENTED_TO_VIRTUAL(sEyeTextures[gSaveContext.save.linkAge][eyeIndex]));
+ gSPSegment(POLY_OPA_DISP++, 0x08, SEGMENTED_TO_VIRTUAL(sEyeTextures[gSaveContext.save.linkAge][eyesIndex]));
#endif
+ // If the mouth index provided by the animation is negative, use the value provided by the `face` argument instead
if (mouthIndex < 0) {
- mouthIndex = sEyeMouthIndices[face][1];
+ mouthIndex = sPlayerFaces[face][PLAYER_FACEPART_MOUTH];
}
#ifndef AVOID_UB
@@ -1810,8 +1871,8 @@ void Player_DrawPauseImpl(PlayState* play, void* gameplayKeep, void* linkObject,
gSPSegment(POLY_OPA_DISP++, 0x0C, gCullBackDList);
- Player_DrawImpl(play, skelAnime->skeleton, skelAnime->jointTable, skelAnime->dListCount, 0, tunic, boots, 0,
- Player_OverrideLimbDrawPause, NULL, &playerSwordAndShield);
+ Player_DrawImpl(play, skelAnime->skeleton, skelAnime->jointTable, skelAnime->dListCount, 0, tunic, boots,
+ PLAYER_FACE_NEUTRAL, Player_OverrideLimbDrawPause, NULL, &playerSwordAndShield);
gSPEndDisplayList(POLY_OPA_DISP++);
gSPEndDisplayList(POLY_XLU_DISP++);