diff --git a/soh/assets/objects/object_link_boy/object_link_boy_flipbook_DL.h b/soh/assets/objects/object_link_boy/object_link_boy_flipbook_DL.h new file mode 100644 index 00000000000..c57b7e7ffae --- /dev/null +++ b/soh/assets/objects/object_link_boy/object_link_boy_flipbook_DL.h @@ -0,0 +1,42 @@ +#ifndef OBJECTS_OBJECT_LINK_BOY_FACE_DLS_H +#define OBJECTS_OBJECT_LINK_BOY_FACE_DLS_H + +#include "align_asset_macro.h" + +#define dgLinkAdultEyesOpenDL "__OTR__objects/object_link_boy/gLinkAdultEyesOpenDL" +static const ALIGN_ASSET(2) char gLinkAdultEyesOpenDL[] = dgLinkAdultEyesOpenDL; + +#define dgLinkAdultEyesHalfDL "__OTR__objects/object_link_boy/gLinkAdultEyesHalfDL" +static const ALIGN_ASSET(2) char gLinkAdultEyesHalfDL[] = dgLinkAdultEyesHalfDL; + +#define dgLinkAdultEyesClosedfDL "__OTR__objects/object_link_boy/gLinkAdultEyesClosedfDL" +static const ALIGN_ASSET(2) char gLinkAdultEyesClosedfDL[] = dgLinkAdultEyesClosedfDL; + +#define dgLinkAdultEyesRollLeftDL "__OTR__objects/object_link_boy/gLinkAdultEyesRollLeftDL" +static const ALIGN_ASSET(2) char gLinkAdultEyesRollLeftDL[] = dgLinkAdultEyesRollLeftDL; + +#define dgLinkAdultEyesRollRightDL "__OTR__objects/object_link_boy/gLinkAdultEyesRollRightDL" +static const ALIGN_ASSET(2) char gLinkAdultEyesRollRightDL[] = dgLinkAdultEyesRollRightDL; + +#define dgLinkAdultEyesShockDL "__OTR__objects/object_link_boy/gLinkAdultEyesShockDL" +static const ALIGN_ASSET(2) char gLinkAdultEyesShockDL[] = dgLinkAdultEyesShockDL; + +#define dgLinkAdultEyesUnk1DL "__OTR__objects/object_link_boy/gLinkAdultEyesUnk1DL" +static const ALIGN_ASSET(2) char gLinkAdultEyesUnk1DL[] = dgLinkAdultEyesUnk1DL; + +#define dgLinkAdultEyesUnk2DL "__OTR__objects/object_link_boy/gLinkAdultEyesUnk2DL" +static const ALIGN_ASSET(2) char gLinkAdultEyesUnk2DL[] = dgLinkAdultEyesUnk2DL; + +#define dgLinkAdultMouth1DL "__OTR__objects/object_link_boy/gLinkAdultMouth1DL" +static const ALIGN_ASSET(2) char gLinkAdultMouth1DL[] = dgLinkAdultMouth1DL; + +#define dgLinkAdultMouth2DL "__OTR__objects/object_link_boy/gLinkAdultMouth2DL" +static const ALIGN_ASSET(2) char gLinkAdultMouth2DL[] = dgLinkAdultMouth2DL; + +#define dgLinkAdultMouth3DL "__OTR__objects/object_link_boy/gLinkAdultMouth3DL" +static const ALIGN_ASSET(2) char gLinkAdultMouth3DL[] = dgLinkAdultMouth3DL; + +#define dgLinkAdultMouth4DL "__OTR__objects/object_link_boy/gLinkAdultMouth4DL" +static const ALIGN_ASSET(2) char gLinkAdultMouth4DL[] = dgLinkAdultMouth4DL; + +#endif // OBJECTS_OBJECT_LINK_BOY_FACE_DLS_H diff --git a/soh/assets/objects/object_link_child/object_link_child_flipbook_DL.h b/soh/assets/objects/object_link_child/object_link_child_flipbook_DL.h new file mode 100644 index 00000000000..14f26facaad --- /dev/null +++ b/soh/assets/objects/object_link_child/object_link_child_flipbook_DL.h @@ -0,0 +1,42 @@ +#ifndef OBJECTS_OBJECT_LINK_CHILD_FACE_DLS_H +#define OBJECTS_OBJECT_LINK_CHILD_FACE_DLS_H + +#include "align_asset_macro.h" + +#define dgLinkChildEyesOpenDL "__OTR__objects/object_link_child/gLinkChildEyesOpenDL" +static const ALIGN_ASSET(2) char gLinkChildEyesOpenDL[] = dgLinkChildEyesOpenDL; + +#define dgLinkChildEyesHalfDL "__OTR__objects/object_link_child/gLinkChildEyesHalfDL" +static const ALIGN_ASSET(2) char gLinkChildEyesHalfDL[] = dgLinkChildEyesHalfDL; + +#define dgLinkChildEyesClosedfDL "__OTR__objects/object_link_child/gLinkChildEyesClosedfDL" +static const ALIGN_ASSET(2) char gLinkChildEyesClosedfDL[] = dgLinkChildEyesClosedfDL; + +#define dgLinkChildEyesRollLeftDL "__OTR__objects/object_link_child/gLinkChildEyesRollLeftDL" +static const ALIGN_ASSET(2) char gLinkChildEyesRollLeftDL[] = dgLinkChildEyesRollLeftDL; + +#define dgLinkChildEyesRollRightDL "__OTR__objects/object_link_child/gLinkChildEyesRollRightDL" +static const ALIGN_ASSET(2) char gLinkChildEyesRollRightDL[] = dgLinkChildEyesRollRightDL; + +#define dgLinkChildEyesShockDL "__OTR__objects/object_link_child/gLinkChildEyesShockDL" +static const ALIGN_ASSET(2) char gLinkChildEyesShockDL[] = dgLinkChildEyesShockDL; + +#define dgLinkChildEyesUnk1DL "__OTR__objects/object_link_child/gLinkChildEyesUnk1DL" +static const ALIGN_ASSET(2) char gLinkChildEyesUnk1DL[] = dgLinkChildEyesUnk1DL; + +#define dgLinkChildEyesUnk2DL "__OTR__objects/object_link_child/gLinkChildEyesUnk2DL" +static const ALIGN_ASSET(2) char gLinkChildEyesUnk2DL[] = dgLinkChildEyesUnk2DL; + +#define dgLinkChildMouth1DL "__OTR__objects/object_link_child/gLinkChildMouth1DL" +static const ALIGN_ASSET(2) char gLinkChildMouth1DL[] = dgLinkChildMouth1DL; + +#define dgLinkChildMouth2DL "__OTR__objects/object_link_child/gLinkChildMouth2DL" +static const ALIGN_ASSET(2) char gLinkChildMouth2DL[] = dgLinkChildMouth2DL; + +#define dgLinkChildMouth3DL "__OTR__objects/object_link_child/gLinkChildMouth3DL" +static const ALIGN_ASSET(2) char gLinkChildMouth3DL[] = dgLinkChildMouth3DL; + +#define dgLinkChildMouth4DL "__OTR__objects/object_link_child/gLinkChildMouth4DL" +static const ALIGN_ASSET(2) char gLinkChildMouth4DL[] = dgLinkChildMouth4DL; + +#endif // OBJECTS_OBJECT_LINK_CHILD_FACE_DLS_H diff --git a/soh/soh/Enhancements/cosmetics/FlipbookDisplayLists.cpp b/soh/soh/Enhancements/cosmetics/FlipbookDisplayLists.cpp new file mode 100644 index 00000000000..283a0cef95e --- /dev/null +++ b/soh/soh/Enhancements/cosmetics/FlipbookDisplayLists.cpp @@ -0,0 +1,153 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ResourceManagerHelpers.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "macros.h" +#include "objects/object_link_boy/object_link_boy_flipbook_DL.h" +#include "objects/object_link_child/object_link_child_flipbook_DL.h" +#include "variables.h" +#include "functions.h" +#include "z64animation.h" +#include "z64player.h" +extern PlayState* gPlayState; +} + +extern "C" uint8_t Player_IsCustomLinkModel(); + +// File-local helpers +// Set this limb index to the head limb in your custom player skeleton. +// Use -1 to disable. Limb index must be < player->skelAnime.limbCount. +static const s16 kHeadLimbIndex = 11; + +// Use the texture name conventions and append "DL" for display lists. +static const char* kEyeDlPaths[2][8] = { + { + gLinkAdultEyesOpenDL, + gLinkAdultEyesHalfDL, + gLinkAdultEyesClosedfDL, + gLinkAdultEyesRollLeftDL, + gLinkAdultEyesRollRightDL, + gLinkAdultEyesShockDL, + gLinkAdultEyesUnk1DL, + gLinkAdultEyesUnk2DL, + }, + { + gLinkChildEyesOpenDL, + gLinkChildEyesHalfDL, + gLinkChildEyesClosedfDL, + gLinkChildEyesRollLeftDL, + gLinkChildEyesRollRightDL, + gLinkChildEyesShockDL, + gLinkChildEyesUnk1DL, + gLinkChildEyesUnk2DL, + }, +}; + +static const char* kMouthDlPaths[2][4] = { + { + gLinkAdultMouth1DL, + gLinkAdultMouth2DL, + gLinkAdultMouth3DL, + gLinkAdultMouth4DL, + }, + { + gLinkChildMouth1DL, + gLinkChildMouth2DL, + gLinkChildMouth3DL, + gLinkChildMouth4DL, + }, +}; + +static const u8 kEyeMouthIndexes[16][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 }, +}; + +static bool HasFaceDlConfigured() { + return kHeadLimbIndex >= 0; +} + +static s16 sLastEyeIndex = -1; +static s16 sLastMouthIndex = -1; +static s16 sLastFaceIndex = -1; +static s16 sLastLinkAge = -1; + +static void UpdateFaceFlipbookDlists(s16 eyeIndex, s16 mouthIndex, s16 faceIndex, s16 linkAge) { + if (!GameInteractor::IsSaveLoaded(true) || gPlayState == nullptr) { + return; + } + + Player* player = GET_PLAYER(gPlayState); + if (eyeIndex < 0 || mouthIndex < 0) { + const s32 face = CLAMP(faceIndex, 0, static_cast(ARRAY_COUNT(kEyeMouthIndexes) - 1)); + if (eyeIndex < 0) { + eyeIndex = kEyeMouthIndexes[face][0]; + } + if (mouthIndex < 0) { + mouthIndex = kEyeMouthIndexes[face][1]; + } + } + + sLastEyeIndex = eyeIndex; + sLastMouthIndex = mouthIndex; + sLastFaceIndex = faceIndex; + sLastLinkAge = linkAge; +} + +extern "C" void DrawFaceFlipbookDlists(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) { + Player* player = (Player*)thisx; + + if (player == nullptr || limbIndex != kHeadLimbIndex) { + return; + } + + if (!Player_IsCustomLinkModel() || !HasFaceDlConfigured()) { + return; + } + + s16 eyeIndex = sLastEyeIndex; + s16 mouthIndex = sLastMouthIndex; + s16 faceIndex = sLastFaceIndex; + s16 linkAge = sLastLinkAge; + + if (eyeIndex < 0 || mouthIndex < 0) { + const s32 face = CLAMP(faceIndex, 0, static_cast(ARRAY_COUNT(kEyeMouthIndexes) - 1)); + if (eyeIndex < 0) { + eyeIndex = kEyeMouthIndexes[face][0]; + } + if (mouthIndex < 0) { + mouthIndex = kEyeMouthIndexes[face][1]; + } + } + + eyeIndex = CLAMP(eyeIndex, 0, static_cast(ARRAY_COUNT(kEyeDlPaths[0]) - 1)); + mouthIndex = CLAMP(mouthIndex, 0, static_cast(ARRAY_COUNT(kMouthDlPaths[0]) - 1)); + + const s32 ageIndex = CLAMP(linkAge, 0, 1); + const char* eyeDl = kEyeDlPaths[ageIndex][eyeIndex]; + const char* mouthDl = kMouthDlPaths[ageIndex][mouthIndex]; + + OPEN_DISPS(play->state.gfxCtx); + + const bool hasEyeDl = eyeDl != nullptr && ResourceMgr_IsAltAssetsEnabled() && ResourceMgr_FileAltExists(eyeDl); + const bool hasMouthDl = + mouthDl != nullptr && ResourceMgr_IsAltAssetsEnabled() && ResourceMgr_FileAltExists(mouthDl); + + if (hasEyeDl) { + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)eyeDl); + } + + if (hasMouthDl) { + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)mouthDl); + } + + CLOSE_DISPS(play->state.gfxCtx); +} + +static void RegisterFaceFlipbookDlists() { + COND_HOOK(OnPlayerFaceUpdate, true, UpdateFaceFlipbookDlists); + COND_HOOK(OnPlayerPostLimbDraw, true, DrawFaceFlipbookDlists); +} + +static RegisterShipInitFunc initFunc(RegisterFaceFlipbookDlists); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h index 93cee420ea2..2eb5c5cc379 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h @@ -26,6 +26,8 @@ DEFINE_HOOK(OnSceneSpawnActors, ()); DEFINE_HOOK(OnLinkSkeletonInit, ()); DEFINE_HOOK(OnLinkEquipmentChange, ()); DEFINE_HOOK(OnPlayerUpdate, ()); +DEFINE_HOOK(OnPlayerFaceUpdate, (s16 eyeIndex, s16 mouthIndex, s16 faceIndex, s16 linkAge)); +DEFINE_HOOK(OnPlayerPostLimbDraw, (PlayState * play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx)); DEFINE_HOOK(OnSetDoAction, (uint16_t action)); DEFINE_HOOK(OnPlayerSfx, (u16 sfxId)); DEFINE_HOOK(OnOcarinaSongAction, ()); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp index 480b981c45f..160ada036f5 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp @@ -102,6 +102,29 @@ void GameInteractor_ExecuteOnPlayerUpdate() { GameInteractor::Instance->ExecuteHooks(); } +void GameInteractor_ExecuteOnPlayerFaceUpdate(s16 eyeIndex, s16 mouthIndex, s16 faceIndex, s16 linkAge) { + static s16 lastEyeIndex = -1; + static s16 lastMouthIndex = -1; + static s16 lastFaceIndex = -1; + static s16 lastLinkAge = -1; + + if (lastEyeIndex == eyeIndex && lastMouthIndex == mouthIndex && lastFaceIndex == faceIndex && + lastLinkAge == linkAge) { + return; + } + + lastEyeIndex = eyeIndex; + lastMouthIndex = mouthIndex; + lastFaceIndex = faceIndex; + lastLinkAge = linkAge; + GameInteractor::Instance->ExecuteHooks(eyeIndex, mouthIndex, faceIndex, + linkAge); +} + +void GameInteractor_ExecuteOnPlayerPostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) { + GameInteractor::Instance->ExecuteHooks(play, limbIndex, dList, rot, thisx); +} + void GameInteractor_ExecuteOnSetDoAction(uint16_t action) { GameInteractor::Instance->ExecuteHooks(action); } diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h index 6bcd0dc9765..3f306d926f7 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h @@ -29,6 +29,8 @@ void GameInteractor_ExecuteOnSceneSpawnActors(); void GameInteractor_ExecuteOnLinkSkeletonInit(); void GameInteractor_ExecuteOnLinkEquipmentChange(); void GameInteractor_ExecuteOnPlayerUpdate(); +void GameInteractor_ExecuteOnPlayerFaceUpdate(s16 eyeIndex, s16 mouthIndex, s16 faceIndex, s16 linkAge); +void GameInteractor_ExecuteOnPlayerPostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx); void GameInteractor_ExecuteOnSetDoAction(uint16_t action); void GameInteractor_ExecuteOnPlayerSfx(u16 sfxId); void GameInteractor_ExecuteOnOcarinaSongAction(); diff --git a/soh/src/code/z_player_lib.c b/soh/src/code/z_player_lib.c index aa3623b6a72..cada13607d1 100644 --- a/soh/src/code/z_player_lib.c +++ b/soh/src/code/z_player_lib.c @@ -1063,6 +1063,8 @@ void Player_DrawImpl(PlayState* play, void** skeleton, Vec3s* jointTable, s32 dL if (mouthIndex > 3) mouthIndex = 3; + GameInteractor_ExecuteOnPlayerFaceUpdate(eyeIndex, mouthIndex, face, gSaveContext.linkAge); + #if defined(MODDING) || defined(_MSC_VER) || defined(__GNUC__) gSPSegment(POLY_OPA_DISP++, 0x09, SEGMENTED_TO_VIRTUAL(sMouthTextures[gSaveContext.linkAge][mouthIndex])); #else @@ -2025,6 +2027,8 @@ void Player_PostLimbDrawGameplay(PlayState* play, s32 limbIndex, Gfx** dList, Ve Actor_SetFeetPos(&this->actor, limbIndex, PLAYER_LIMB_L_FOOT, vec, PLAYER_LIMB_R_FOOT, vec); } } + + GameInteractor_ExecuteOnPlayerPostLimbDraw(play, limbIndex, dList, rot, thisx); } u32 func_80091738(PlayState* play, u8* segment, SkelAnime* skelAnime) {