mirror of
https://github.com/zeldaret/mm.git
synced 2026-09-02 09:51:44 -04:00
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>
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
#include "z64vimode.h"
|
||||
#include "z64vis.h"
|
||||
|
||||
#pragma increment_block_number "n64-us:128"
|
||||
|
||||
s32 gFramerateDivisor = 1;
|
||||
f32 gFramerateDivisorF = 1.0f;
|
||||
f32 gFramerateDivisorHalf = 1.0f / 2.0f;
|
||||
|
||||
+67
-28
@@ -3831,51 +3831,90 @@ void Enemy_StartFinishingBlow(PlayState* play, Actor* actor) {
|
||||
SoundSource_PlaySfxAtFixedWorldPos(play, &actor->world.pos, 20, NA_SE_EN_LAST_DAMAGE);
|
||||
}
|
||||
|
||||
// blinking routine
|
||||
s16 func_800BBAC0(BlinkInfo* info, s16 arg1, s16 arg2, s16 arg3) {
|
||||
if (DECR(info->blinkTimer) == 0) {
|
||||
info->blinkTimer = Rand_S16Offset(arg1, arg2);
|
||||
/**
|
||||
* 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 (info->blinkTimer - arg3 > 0) {
|
||||
info->eyeTexIndex = 0;
|
||||
} else if ((info->blinkTimer - arg3 >= -1) || (info->blinkTimer < 2)) {
|
||||
info->eyeTexIndex = 1;
|
||||
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 >= -1) || (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 {
|
||||
info->eyeTexIndex = 2;
|
||||
// If both conditions above fail, the only possibility left is the "eyes closed" face
|
||||
faceChange->face = 2;
|
||||
}
|
||||
|
||||
return info->eyeTexIndex;
|
||||
return faceChange->face;
|
||||
}
|
||||
|
||||
// blinking routine
|
||||
s16 func_800BBB74(BlinkInfo* info, s16 arg1, s16 arg2, s16 arg3) {
|
||||
if (DECR(info->blinkTimer) == 0) {
|
||||
info->blinkTimer = Rand_S16Offset(arg1, arg2);
|
||||
/**
|
||||
* 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_UpdateBlinkingNonHuman(FaceChange* faceChange, s16 blinkIntervalBase, s16 blinkIntervalRandRange,
|
||||
s16 blinkDuration) {
|
||||
if (DECR(faceChange->timer) == 0) {
|
||||
faceChange->timer = Rand_S16Offset(blinkIntervalBase, blinkIntervalRandRange);
|
||||
}
|
||||
|
||||
if (info->blinkTimer - arg3 > 0) {
|
||||
info->eyeTexIndex = 0;
|
||||
} else if (info->blinkTimer - arg3 == 0) {
|
||||
info->eyeTexIndex = 1;
|
||||
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 == 0) {
|
||||
faceChange->face = 1;
|
||||
} else {
|
||||
info->eyeTexIndex = 2;
|
||||
// If both conditions above fail, the only possibility left is the "eyes closed" face
|
||||
faceChange->face = 2;
|
||||
}
|
||||
|
||||
return info->eyeTexIndex;
|
||||
return faceChange->face;
|
||||
}
|
||||
|
||||
// unused blinking routine
|
||||
s16 func_800BBC20(BlinkInfo* info, s16 arg1, s16 arg2, s16 arg3) {
|
||||
if (DECR(info->blinkTimer) == 0) {
|
||||
info->blinkTimer = Rand_S16Offset(arg1, arg2);
|
||||
info->eyeTexIndex++;
|
||||
if ((info->eyeTexIndex % 3) == 0) {
|
||||
info->eyeTexIndex = (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) {
|
||||
faceChange->face = (s32)(Rand_ZeroOne() * faceSetRange) * 3;
|
||||
}
|
||||
}
|
||||
|
||||
return info->eyeTexIndex;
|
||||
return faceChange->face;
|
||||
}
|
||||
|
||||
void Actor_SpawnBodyParts(Actor* actor, PlayState* play, s32 partParams, Gfx** dList) {
|
||||
|
||||
+165
-39
@@ -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
|
||||
/**
|
||||
* 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
|
||||
};
|
||||
|
||||
TexturePtr sPlayerMouthTextures[PLAYER_MOUTH_MAX] = {
|
||||
static TexturePtr sMouthTextures[PLAYER_MOUTH_MAX] = {
|
||||
gLinkHumanMouthClosedTex, // PLAYER_MOUTH_CLOSED
|
||||
gLinkHumanMouthTeethTex, // PLAYER_MOUTH_TEETH
|
||||
gLinkHumanMouthAngryTex, // PLAYER_MOUTH_ANGRY
|
||||
gLinkHumanMouthHappyTex, // PLAYER_MOUTH_HAPPY
|
||||
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
|
||||
},
|
||||
};
|
||||
|
||||
typedef struct PlayerFaceIndices {
|
||||
/* 0x0 */ u8 eyeIndex;
|
||||
/* 0x1 */ u8 mouthIndex;
|
||||
} PlayerFaceIndices; // size = 0x2
|
||||
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_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
|
||||
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];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user