summaryrefslogtreecommitdiff
path: root/src/code
diff options
context:
space:
mode:
Diffstat (limited to 'src/code')
-rw-r--r--src/code/game.c2
-rw-r--r--src/code/z_actor.c99
-rw-r--r--src/code/z_player_lib.c214
3 files changed, 241 insertions, 74 deletions
diff --git a/src/code/game.c b/src/code/game.c
index 290f474a9..00539001a 100644
--- a/src/code/game.c
+++ b/src/code/game.c
@@ -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;
diff --git a/src/code/z_actor.c b/src/code/z_actor.c
index e54fc18e7..954e786ed 100644
--- a/src/code/z_actor.c
+++ b/src/code/z_actor.c
@@ -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);
- }
-
- if (info->blinkTimer - arg3 > 0) {
- info->eyeTexIndex = 0;
- } else if ((info->blinkTimer - arg3 >= -1) || (info->blinkTimer < 2)) {
- info->eyeTexIndex = 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 >= -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) {
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];