diff options
| author | engineer124 <47598039+engineer124@users.noreply.github.com> | 2025-06-04 11:59:25 +1000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-06-03 18:59:25 -0700 |
| commit | d31ceacfdf468456d1052ca8af981f06040e0ac4 (patch) | |
| tree | 77ace096ed16ddcb9e88bfd264f722e2fadf1833 /src/code/z_player_lib.c | |
| parent | ace4043d1a244185423837cb85829024f5e79a2c (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.c | 214 |
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]; |
