summaryrefslogtreecommitdiff
path: root/src/code/z_player_lib.c
diff options
context:
space:
mode:
authorengineer124 <47598039+engineer124@users.noreply.github.com>2025-06-04 11:59:25 +1000
committerGitHub <noreply@github.com>2025-06-03 18:59:25 -0700
commitd31ceacfdf468456d1052ca8af981f06040e0ac4 (patch)
tree77ace096ed16ddcb9e88bfd264f722e2fadf1833 /src/code/z_player_lib.c
parentace4043d1a244185423837cb85829024f5e79a2c (diff)
Document Player's Face and z_actor FaceChange functions (#1777)
* player face docs * more docs * cleanup * toto * more comments * fix bss, names * better comment * PR, fix comment * fix bss * sEyeTextures comment * FaceChange_UpdateBlinkingNonHuman --------- Co-authored-by: Anghelo Carvajal <angheloalf95@gmail.com> Co-authored-by: Derek Hensley <hensley.derek58@gmail.com>
Diffstat (limited to 'src/code/z_player_lib.c')
-rw-r--r--src/code/z_player_lib.c214
1 files changed, 170 insertions, 44 deletions
diff --git a/src/code/z_player_lib.c b/src/code/z_player_lib.c
index 483d371c9..85d224010 100644
--- a/src/code/z_player_lib.c
+++ b/src/code/z_player_lib.c
@@ -1837,46 +1837,156 @@ Gfx gCullFrontDList[] = {
gsSPEndDisplayList(),
};
-TexturePtr sPlayerEyesTextures[PLAYER_EYES_MAX] = {
- gLinkHumanEyesOpenTex, // PLAYER_EYES_OPEN
- gLinkHumanEyesHalfTex, // PLAYER_EYES_HALF
- gLinkHumanEyesClosedTex, // PLAYER_EYES_CLOSED
- gLinkHumanEyesRollRightTex, // PLAYER_EYES_ROLL_RIGHT
- gLinkHumanEyesRollLeftTex, // PLAYER_EYES_ROLL_LEFT
- gLinkHumanEyesRollUpTex, // PLAYER_EYES_ROLL_UP
- gLinkHumanEyesRollDownTex, // PLAYER_EYES_ROLL_DOWN
- object_link_child_Tex_003800, // PLAYER_EYES_7
-};
-
-TexturePtr sPlayerMouthTextures[PLAYER_MOUTH_MAX] = {
+/**
+ * Link's eyes and mouth textures are placed at the exact same place in all player form's respective object files.
+ * This allows the array to only contain the symbols for one file and have it apply to all of them. 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 human Link's object are used here.
+ *
+ * Note that some player forms do not use the eyes and mouth textures loaded into segments 0x08 and 0x09 respectively.
+ * Therefore, the segment will point at garbage data, but this does not cause issues as the data is not read from.
+ */
+#ifndef AVOID_UB
+static TexturePtr sEyeTextures[PLAYER_EYES_MAX] = {
+ gLinkHumanEyesOpenTex, // PLAYER_EYES_OPEN
+ gLinkHumanEyesHalfTex, // PLAYER_EYES_HALF
+ gLinkHumanEyesClosedTex, // PLAYER_EYES_CLOSED
+ gLinkHumanEyesRightTex, // PLAYER_EYES_RIGHT
+ gLinkHumanEyesLeftTex, // PLAYER_EYES_LEFT
+ gLinkHumanEyesUpTex, // PLAYER_EYES_UP
+ gLinkHumanEyesDownTex, // PLAYER_EYES_DOWN
+ gLinkHumanEyesWincingTex, // PLAYER_EYES_WINCING
+};
+
+static TexturePtr sMouthTextures[PLAYER_MOUTH_MAX] = {
gLinkHumanMouthClosedTex, // PLAYER_MOUTH_CLOSED
- gLinkHumanMouthTeethTex, // PLAYER_MOUTH_TEETH
- gLinkHumanMouthAngryTex, // PLAYER_MOUTH_ANGRY
- gLinkHumanMouthHappyTex, // PLAYER_MOUTH_HAPPY
-};
-
-typedef struct PlayerFaceIndices {
- /* 0x0 */ u8 eyeIndex;
- /* 0x1 */ u8 mouthIndex;
-} PlayerFaceIndices; // size = 0x2
-
-PlayerFaceIndices sPlayerFaces[] = {
- { PLAYER_EYES_OPEN, PLAYER_MOUTH_CLOSED }, // PLAYER_FACE_0
- { PLAYER_EYES_HALF, PLAYER_MOUTH_CLOSED }, // PLAYER_FACE_1
- { PLAYER_EYES_CLOSED, PLAYER_MOUTH_CLOSED }, // PLAYER_FACE_2
- { PLAYER_EYES_OPEN, PLAYER_MOUTH_CLOSED }, // PLAYER_FACE_3
- { PLAYER_EYES_HALF, PLAYER_MOUTH_CLOSED }, // PLAYER_FACE_4
- { PLAYER_EYES_CLOSED, PLAYER_MOUTH_CLOSED }, // PLAYER_FACE_5
- { PLAYER_EYES_ROLL_LEFT, PLAYER_MOUTH_CLOSED }, // PLAYER_FACE_6
- { PLAYER_EYES_ROLL_UP, PLAYER_MOUTH_TEETH }, // PLAYER_FACE_7
- { PLAYER_EYES_7, PLAYER_MOUTH_ANGRY }, // PLAYER_FACE_8
- { PLAYER_EYES_OPEN, PLAYER_MOUTH_ANGRY }, // PLAYER_FACE_9
- { PLAYER_EYES_ROLL_RIGHT, PLAYER_MOUTH_CLOSED }, // PLAYER_FACE_10
- { PLAYER_EYES_ROLL_LEFT, PLAYER_MOUTH_CLOSED }, // PLAYER_FACE_11
- { PLAYER_EYES_CLOSED, PLAYER_MOUTH_ANGRY }, // PLAYER_FACE_12
- { PLAYER_EYES_HALF, PLAYER_MOUTH_TEETH }, // PLAYER_FACE_13
- { PLAYER_EYES_OPEN, PLAYER_MOUTH_ANGRY }, // PLAYER_FACE_14
- { PLAYER_EYES_OPEN, PLAYER_MOUTH_HAPPY }, // PLAYER_FACE_15
+ gLinkHumanMouthHalfTex, // PLAYER_MOUTH_HALF
+ gLinkHumanMouthOpenTex, // PLAYER_MOUTH_OPEN
+ gLinkHumanMouthSmileTex, // PLAYER_MOUTH_SMILE
+};
+#else
+static TexturePtr sEyeTextures[PLAYER_FORM_MAX][PLAYER_EYES_MAX] = {
+ // PLAYER_FORM_FIERCE_DEITY
+ {
+ NULL, // PLAYER_EYES_OPEN
+ NULL, // PLAYER_EYES_HALF
+ NULL, // PLAYER_EYES_CLOSED
+ NULL, // PLAYER_EYES_RIGHT
+ NULL, // PLAYER_EYES_LEFT
+ NULL, // PLAYER_EYES_UP
+ NULL, // PLAYER_EYES_DOWN
+ NULL, // PLAYER_EYES_WINCING
+ },
+ // PLAYER_FORM_GORON
+ // Note: use PLAYER_EYES_WINCING to access `gLinkGoronEyesSurprisedTex`. See `Player_DrawImpl`.
+ {
+ gLinkGoronEyesOpenTex, // PLAYER_EYES_OPEN
+ gLinkGoronEyesHalfTex, // PLAYER_EYES_HALF
+ gLinkGoronEyesClosedTex, // PLAYER_EYES_CLOSED
+ gLinkGoronEyesSurprisedTex, // PLAYER_EYES_RIGHT
+ NULL, // PLAYER_EYES_LEFT
+ NULL, // PLAYER_EYES_UP
+ NULL, // PLAYER_EYES_DOWN
+ NULL, // PLAYER_EYES_WINCING
+ },
+ // PLAYER_FORM_ZORA
+ {
+ gLinkZoraEyesOpenTex, // PLAYER_EYES_OPEN
+ gLinkZoraEyesHalfTex, // PLAYER_EYES_HALF
+ gLinkZoraEyesClosedTex, // PLAYER_EYES_CLOSED
+ gLinkZoraEyesRightTex, // PLAYER_EYES_RIGHT
+ gLinkZoraEyesLeftTex, // PLAYER_EYES_LEFT
+ gLinkZoraEyesUpTex, // PLAYER_EYES_UP
+ gLinkZoraEyesDownTex, // PLAYER_EYES_DOWN
+ gLinkZoraEyesWincingTex, // PLAYER_EYES_WINCING
+ },
+ // PLAYER_FORM_DEKU
+ {
+ NULL, // PLAYER_EYES_OPEN
+ NULL, // PLAYER_EYES_HALF
+ NULL, // PLAYER_EYES_CLOSED
+ NULL, // PLAYER_EYES_RIGHT
+ NULL, // PLAYER_EYES_LEFT
+ NULL, // PLAYER_EYES_UP
+ NULL, // PLAYER_EYES_DOWN
+ NULL, // PLAYER_EYES_WINCING
+ },
+ // PLAYER_FORM_HUMAN
+ {
+ gLinkHumanEyesOpenTex, // PLAYER_EYES_OPEN
+ gLinkHumanEyesHalfTex, // PLAYER_EYES_HALF
+ gLinkHumanEyesClosedTex, // PLAYER_EYES_CLOSED
+ gLinkHumanEyesRightTex, // PLAYER_EYES_RIGHT
+ gLinkHumanEyesLeftTex, // PLAYER_EYES_LEFT
+ gLinkHumanEyesUpTex, // PLAYER_EYES_UP
+ gLinkHumanEyesDownTex, // PLAYER_EYES_DOWN
+ gLinkHumanEyesWincingTex, // PLAYER_EYES_WINCING
+ },
+};
+
+static TexturePtr sMouthTextures[PLAYER_FORM_MAX][PLAYER_MOUTH_MAX] = {
+ // PLAYER_FORM_FIERCE_DEITY
+ {
+ NULL, // PLAYER_MOUTH_CLOSED
+ NULL, // PLAYER_MOUTH_HALF
+ NULL, // PLAYER_MOUTH_OPEN
+ NULL, // PLAYER_MOUTH_SMILE
+ },
+ // PLAYER_FORM_GORON
+ {
+ NULL, // PLAYER_MOUTH_CLOSED
+ NULL, // PLAYER_MOUTH_HALF
+ NULL, // PLAYER_MOUTH_OPEN
+ NULL, // PLAYER_MOUTH_SMILE
+ },
+ // PLAYER_FORM_ZORA
+ {
+ gLinkZoraMouthClosedTex, // PLAYER_MOUTH_CLOSED
+ gLinkZoraMouthHalfTex, // PLAYER_MOUTH_HALF
+ gLinkZoraMouthOpenTex, // PLAYER_MOUTH_OPEN
+ gLinkZoraMouthSmileTex, // PLAYER_MOUTH_SMILE
+ },
+ // PLAYER_FORM_DEKU
+ {
+ NULL, // PLAYER_MOUTH_CLOSED
+ NULL, // PLAYER_MOUTH_HALF
+ NULL, // PLAYER_MOUTH_OPEN
+ NULL, // PLAYER_MOUTH_SMILE
+ },
+ // PLAYER_FORM_HUMAN
+ {
+ gLinkHumanMouthClosedTex, // PLAYER_MOUTH_CLOSED
+ gLinkHumanMouthHalfTex, // PLAYER_MOUTH_HALF
+ gLinkHumanMouthOpenTex, // PLAYER_MOUTH_OPEN
+ gLinkHumanMouthSmileTex, // PLAYER_MOUTH_SMILE
+ },
+};
+#endif
+
+PlayerFaceIndices sPlayerFaces[PLAYER_FACE_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
+
+ // Additional faces. Most faces are encoded within animations.
+ { PLAYER_EYES_LEFT, PLAYER_MOUTH_CLOSED }, // PLAYER_FACE_LOOK_LEFT
+ { PLAYER_EYES_UP, 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_RIGHT, PLAYER_MOUTH_CLOSED }, // PLAYER_FACE_LOOK_RIGHT
+ { PLAYER_EYES_LEFT, PLAYER_MOUTH_CLOSED }, // PLAYER_FACE_LOOK_LEFT_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_SMILE }, // PLAYER_FACE_SMILE
};
// Note the correct pointer to pass as the jointTable is the jointTable pointer from the SkelAnime struct, not the
@@ -1892,25 +2002,41 @@ void Player_DrawImpl(PlayState* play, void** skeleton, Vec3s* jointTable, s32 dL
gfx = POLY_OPA_DISP;
+ // If the eyes index provided by the animation is negative, use the value provided by the `face` argument instead
if (eyeIndex < 0) {
eyeIndex = sPlayerFaces[face].eyeIndex;
}
if (playerForm == PLAYER_FORM_GORON) {
- if ((eyeIndex >= PLAYER_EYES_ROLL_RIGHT) && (eyeIndex <= PLAYER_EYES_ROLL_DOWN)) {
+ // Goron does not have the eye textures to look in different directions
+ if ((eyeIndex >= PLAYER_EYES_RIGHT) && (eyeIndex <= PLAYER_EYES_DOWN)) {
eyeIndex = PLAYER_EYES_OPEN;
- } else if (eyeIndex == PLAYER_EYES_7) {
- eyeIndex = PLAYER_EYES_ROLL_RIGHT;
+ } else if (eyeIndex == PLAYER_EYES_WINCING) {
+ // Goron form puts a surpised expression where the eyes-right normally goes
+ eyeIndex = PLAYER_EYES_RIGHT;
}
}
- gSPSegment(&gfx[0], 0x08, Lib_SegmentedToVirtual(sPlayerEyesTextures[eyeIndex]));
+ // Only Human, Zora, and Goron will read the eye textures in the head limb display list.
+ // Fierce Deity and Deku will point this segment to garbage data, but it will be unread from.
+#ifndef AVOID_UB
+ gSPSegment(&gfx[0], 0x08, Lib_SegmentedToVirtual(sEyeTextures[eyeIndex]));
+#else
+ gSPSegment(&gfx[0], 0x08, Lib_SegmentedToVirtual(sEyeTextures[playerForm][eyeIndex]));
+#endif
+ // If the mouth index provided by the animation is negative, use the value provided by the `face` argument instead
if (mouthIndex < 0) {
mouthIndex = sPlayerFaces[face].mouthIndex;
}
- gSPSegment(&gfx[1], 0x09, Lib_SegmentedToVirtual(sPlayerMouthTextures[mouthIndex]));
+ // Only Human and Zora will read the mouth textures in the head limb display list.
+ // Goron, Fierce Deity, and Deku will point this segment to garbage data, but it will be unread from.
+#ifndef AVOID_UB
+ gSPSegment(&gfx[1], 0x09, Lib_SegmentedToVirtual(sMouthTextures[mouthIndex]));
+#else
+ gSPSegment(&gfx[1], 0x09, Lib_SegmentedToVirtual(sMouthTextures[playerForm][mouthIndex]));
+#endif
POLY_OPA_DISP = &gfx[2];