diff --git a/soh/assets/objects/object_custom_equip/object_custom_equip.h b/soh/assets/objects/object_custom_equip/object_custom_equip.h new file mode 100644 index 000000000..380116a0f --- /dev/null +++ b/soh/assets/objects/object_custom_equip/object_custom_equip.h @@ -0,0 +1,118 @@ +#ifndef OBJECTS_OBJECT_CUSTOM_EQUIP_H +#define OBJECTS_OBJECT_CUSTOM_EQUIP 1 + +#include "align_asset_macro.h" + +// Custom Equipment Display Lists +#define dgCustomBowDL "__OTR__objects/object_custom_equip/gCustomBowDL" +static const ALIGN_ASSET(2) char gCustomBowDL[] = dgCustomBowDL; + +#define dgCustomHammerDL "__OTR__objects/object_custom_equip/gCustomHammerDL" +static const ALIGN_ASSET(2) char gCustomHammerDL[] = dgCustomHammerDL; + +#define dgCustomHookshotDL "__OTR__objects/object_custom_equip/gCustomHookshotDL" +static const ALIGN_ASSET(2) char gCustomHookshotDL[] = dgCustomHookshotDL; + +#define dgCustomLongshotDL "__OTR__objects/object_custom_equip/gCustomLongshotDL" +static const ALIGN_ASSET(2) char gCustomLongshotDL[] = dgCustomLongshotDL; + +#define dgCustomHookshotTipDL "__OTR__objects/object_custom_equip/gCustomHookshotTipDL" +static const ALIGN_ASSET(2) char gCustomHookshotTipDL[] = dgCustomHookshotTipDL; + +#define dgCustomHookshotChainDL "__OTR__objects/object_custom_equip/gCustomHookshotChainDL" +static const ALIGN_ASSET(2) char gCustomHookshotChainDL[] = dgCustomHookshotChainDL; + +#define dgCustomSlingshotDL "__OTR__objects/object_custom_equip/gCustomSlingshotDL" +static const ALIGN_ASSET(2) char gCustomSlingshotDL[] = dgCustomSlingshotDL; + +#define dgCustomFairyOcarinaDL "__OTR__objects/object_custom_equip/gCustomFairyOcarinaDL" +static const ALIGN_ASSET(2) char gCustomFairyOcarinaDL[] = dgCustomFairyOcarinaDL; + +#define dgCustomOcarinaOfTimeDL "__OTR__objects/object_custom_equip/gCustomOcarinaOfTimeDL" +static const ALIGN_ASSET(2) char gCustomOcarinaOfTimeDL[] = dgCustomOcarinaOfTimeDL; + +#define dgCustomFairyOcarinaAdultDL "__OTR__objects/object_custom_equip/gCustomFairyOcarinaAdultDL" +static const ALIGN_ASSET(2) char gCustomFairyOcarinaAdultDL[] = dgCustomFairyOcarinaAdultDL; + +#define dgCustomOcarinaOfTimeAdultDL "__OTR__objects/object_custom_equip/gCustomOcarinaOfTimeAdultDL" +static const ALIGN_ASSET(2) char gCustomOcarinaOfTimeAdultDL[] = dgCustomOcarinaOfTimeAdultDL; + +#define dgCustomBoomerangDL "__OTR__objects/object_custom_equip/gCustomBoomerangDL" +static const ALIGN_ASSET(2) char gCustomBoomerangDL[] = dgCustomBoomerangDL; + +#define dgCustomKokiriSwordDL "__OTR__objects/object_custom_equip/gCustomKokiriSwordDL" +static const ALIGN_ASSET(2) char gCustomKokiriSwordDL[] = dgCustomKokiriSwordDL; + +#define dgCustomKokiriSwordSheathDL "__OTR__objects/object_custom_equip/gCustomKokiriSwordSheathDL" +static const ALIGN_ASSET(2) char gCustomKokiriSwordSheathDL[] = dgCustomKokiriSwordSheathDL; + +#define dgCustomKokiriSwordInSheathDL "__OTR__objects/object_custom_equip/gCustomKokiriSwordInSheathDL" +static const ALIGN_ASSET(2) char gCustomKokiriSwordInSheathDL[] = dgCustomKokiriSwordInSheathDL; + +#define dgCustomMasterSwordDL "__OTR__objects/object_custom_equip/gCustomMasterSwordDL" +static const ALIGN_ASSET(2) char gCustomMasterSwordDL[] = dgCustomMasterSwordDL; + +#define dgCustomMasterSwordSheathDL "__OTR__objects/object_custom_equip/gCustomMasterSwordSheathDL" +static const ALIGN_ASSET(2) char gCustomMasterSwordSheathDL[] = dgCustomMasterSwordSheathDL; + +#define dgCustomMasterSwordInSheathDL "__OTR__objects/object_custom_equip/gCustomMasterSwordInSheathDL" +static const ALIGN_ASSET(2) char gCustomMasterSwordInSheathDL[] = dgCustomMasterSwordInSheathDL; + +#define dgCustomLongswordDL "__OTR__objects/object_custom_equip/gCustomLongswordDL" +static const ALIGN_ASSET(2) char gCustomLongswordDL[] = dgCustomLongswordDL; + +#define dgCustomBreakableLongswordDL "__OTR__objects/object_custom_equip/gCustomBreakableLongswordDL" +static const ALIGN_ASSET(2) char gCustomBreakableLongswordDL[] = dgCustomBreakableLongswordDL; + +#define dgCustomBrokenLongswordDL "__OTR__objects/object_custom_equip/gCustomBrokenLongswordDL" +static const ALIGN_ASSET(2) char gCustomBrokenLongswordDL[] = dgCustomBrokenLongswordDL; + +#define dgCustomLongswordSheathDL "__OTR__objects/object_custom_equip/gCustomLongswordSheathDL" +static const ALIGN_ASSET(2) char gCustomLongswordSheathDL[] = dgCustomLongswordSheathDL; + +#define dgCustomLongswordInSheathDL "__OTR__objects/object_custom_equip/gCustomLongswordInSheathDL" +static const ALIGN_ASSET(2) char gCustomLongswordInSheathDL[] = dgCustomLongswordInSheathDL; + +#define dgCustomBreakableLongswordSheathDL "__OTR__objects/object_custom_equip/gCustomBreakableLongswordSheathDL" +static const ALIGN_ASSET(2) char gCustomBreakableLongswordSheathDL[] = dgCustomBreakableLongswordSheathDL; + +#define dgCustomBreakableLongswordInSheathDL "__OTR__objects/object_custom_equip/gCustomBreakableLongswordInSheathDL" +static const ALIGN_ASSET(2) char gCustomBreakableLongswordInSheathDL[] = dgCustomBreakableLongswordInSheathDL; + +#define dgCustomBrokenLongswordSheathDL "__OTR__objects/object_custom_equip/gCustomBrokenLongswordSheathDL" +static const ALIGN_ASSET(2) char gCustomBrokenLongswordSheathDL[] = dgCustomBrokenLongswordSheathDL; + +#define dgCustomBrokenLongswordInSheathDL "__OTR__objects/object_custom_equip/gCustomBrokenLongswordInSheathDL" +static const ALIGN_ASSET(2) char gCustomBrokenLongswordInSheathDL[] = dgCustomBrokenLongswordInSheathDL; + +#define dgCustomDekuShieldDL "__OTR__objects/object_custom_equip/gCustomDekuShieldDL" +static const ALIGN_ASSET(2) char gCustomDekuShieldDL[] = dgCustomDekuShieldDL; + +#define dgCustomDekuShieldOnBackDL "__OTR__objects/object_custom_equip/gCustomDekuShieldOnBackDL" +static const ALIGN_ASSET(2) char gCustomDekuShieldOnBackDL[] = dgCustomDekuShieldOnBackDL; + +#define dgCustomHylianShieldDL "__OTR__objects/object_custom_equip/gCustomHylianShieldDL" +static const ALIGN_ASSET(2) char gCustomHylianShieldDL[] = dgCustomHylianShieldDL; + +#define dgCustomHylianShieldOnBackDL "__OTR__objects/object_custom_equip/gCustomHylianShieldOnBackDL" +static const ALIGN_ASSET(2) char gCustomHylianShieldOnBackDL[] = dgCustomHylianShieldOnBackDL; + +#define dgCustomHylianShieldOnChildBackDL "__OTR__objects/object_custom_equip/gCustomHylianShieldOnChildBackDL" +static const ALIGN_ASSET(2) char gCustomHylianShieldOnChildBackDL[] = dgCustomHylianShieldOnChildBackDL; + +#define dgCustomMirrorShieldDL "__OTR__objects/object_custom_equip/gCustomMirrorShieldDL" +static const ALIGN_ASSET(2) char gCustomMirrorShieldDL[] = dgCustomMirrorShieldDL; + +#define dgCustomMirrorShieldOnBackDL "__OTR__objects/object_custom_equip/gCustomMirrorShieldOnBackDL" +static const ALIGN_ASSET(2) char gCustomMirrorShieldOnBackDL[] = dgCustomMirrorShieldOnBackDL; + +// Custom First Person Hand Display Lists +#define dgCustomAdultFPSHandDL "__OTR__objects/object_custom_equip/gCustomAdultFPSHandDL" +static const ALIGN_ASSET(2) char gCustomAdultFPSHandDL[] = dgCustomAdultFPSHandDL; + +#define dgCustomChildFPSHandDL "__OTR__objects/object_custom_equip/gCustomChildFPSHandDL" +static const ALIGN_ASSET(2) char gCustomChildFPSHandDL[] = dgCustomChildFPSHandDL; + + + +#endif // OBJECTS_OBJECT_CUSTOM_EQUIP_H diff --git a/soh/soh/Enhancements/customequipment.cpp b/soh/soh/Enhancements/customequipment.cpp new file mode 100644 index 000000000..9834836fb --- /dev/null +++ b/soh/soh/Enhancements/customequipment.cpp @@ -0,0 +1,536 @@ +#include +#include "src/overlays/actors/ovl_En_Elf/z_en_elf.h" +#include "objects/object_link_boy/object_link_boy.h" +#include "objects/object_link_child/object_link_child.h" +#include "objects/object_custom_equip/object_custom_equip.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/ShipInit.hpp" +#include "soh/ResourceManagerHelpers.h" +#include "soh_assets.h" +#include "kaleido.h" +#include "soh/cvar_prefixes.h" + +extern SaveContext gSaveContext; +extern PlayState* gPlayState; +extern void Overlay_DisplayText(float duration, const char* text); +void DummyPlayer_Update(Actor* actor, PlayState* play); + +static void UpdatePatchCustomEquipmentDlists(); +static void RefreshCustomEquipment(); +static bool HasDummyPlayers(); + +static const char* ResolveCustomChain(std::initializer_list paths) { + const char* fallback = nullptr; + for (auto path : paths) { + if (path != nullptr) { + fallback = path; + if (ResourceMgr_FileExists(path)) { + return path; + } + } + } + return fallback; +} + +static const char* GetBreakableLongswordDL() { + return ResolveCustomChain({ gCustomBreakableLongswordDL, gCustomLongswordDL }); +} + +static const char* GetBreakableLongswordSheathDL() { + return ResolveCustomChain({ gCustomBreakableLongswordSheathDL, gCustomLongswordSheathDL }); +} + +static const char* GetBreakableLongswordInSheathDL() { + return ResolveCustomChain({ gCustomBreakableLongswordInSheathDL, gCustomLongswordInSheathDL }); +} + +static const char* GetBrokenLongswordSheathDL() { + return ResolveCustomChain( + { gCustomBrokenLongswordSheathDL, gCustomBreakableLongswordSheathDL, gCustomLongswordSheathDL }); +} + +static const char* GetBrokenLongswordInSheathDL() { + return ResolveCustomChain( + { gCustomBrokenLongswordInSheathDL, gCustomBreakableLongswordInSheathDL, gCustomLongswordInSheathDL }); +} + +static void UpdateCustomEquipmentSetModel(Player* player, u8 ModelGroup) { + (void)ModelGroup; + + if (player == nullptr || gPlayState == nullptr || player != GET_PLAYER(gPlayState)) { + return; + } + + RefreshCustomEquipment(); +} + +static void UpdateCustomEquipment() { + if (!GameInteractor::IsSaveLoaded() || gPlayState == nullptr) { + return; + } + + Player* player = GET_PLAYER(gPlayState); + if (player == nullptr || player->actor.update == DummyPlayer_Update) { + return; + } + + const bool altAssetsRuntime = ResourceMgr_IsAltAssetsEnabled(); + + // If multiplayer dummy actors are present, skip patching shared resources to avoid corrupting them. + if (HasDummyPlayers() && altAssetsRuntime) { + return; + } + + RefreshCustomEquipment(); +} + +static void PatchCustomEquipment() { + COND_HOOK(OnPlayerSetModels, true, UpdateCustomEquipmentSetModel); + COND_HOOK(OnLinkEquipmentChange, true, UpdateCustomEquipment); + COND_HOOK(OnLinkSkeletonInit, true, UpdateCustomEquipment); + COND_HOOK(OnAssetAltChange, true, UpdateCustomEquipment); +} + +static RegisterShipInitFunc initFunc(PatchCustomEquipment); + +static void RefreshCustomEquipment() { + if (!GameInteractor::IsSaveLoaded() || gPlayState == NULL || GET_PLAYER(gPlayState) == nullptr) { + return; + } + + const bool hasDummyPlayers = HasDummyPlayers(); + const bool altAssetsRuntime = ResourceMgr_IsAltAssetsEnabled(); + + // Keep custom patches off dummy players, but still allow unpatching when alt assets are disabled so we can + // restore vanilla display lists. + if (hasDummyPlayers && altAssetsRuntime) { + return; + } + + UpdatePatchCustomEquipmentDlists(); +} + +void PatchOrUnpatch(const char* resource, const char* gfx, const char* dlist1, const char* dlist2, const char* dlist3, + const char* alternateDL) { + if (resource == NULL || gfx == NULL || dlist1 == NULL || dlist2 == NULL) { + return; + } + + const bool altAssetsRuntime = ResourceMgr_IsAltAssetsEnabled(); + + if (!altAssetsRuntime) { + // Alt assets are off; ensure any prior patches using these names are reverted. + ResourceMgr_UnpatchGfxByName(resource, dlist1); + ResourceMgr_UnpatchGfxByName(resource, dlist2); + if (dlist3 != NULL) { + ResourceMgr_UnpatchGfxByName(resource, dlist3); + } + // Drop any cached version of the resource so it reloads clean (unpatched) next use. + ResourceMgr_UnloadResource(resource); + return; + } + + if (!ResourceGetIsCustomByName(gfx)) { + return; + } + + if (alternateDL == NULL || ResourceGetIsCustomByName(alternateDL) || ResourceMgr_FileExists(alternateDL)) { + ResourceMgr_PatchCustomGfxByName(resource, dlist1, 0, gsSPDisplayListOTRFilePath(gfx)); + if (dlist3 == NULL) { + ResourceMgr_PatchCustomGfxByName(resource, dlist2, 1, gsSPEndDisplayList()); + } else { + ResourceMgr_PatchCustomGfxByName(resource, dlist2, 1, gsSPDisplayListOTRFilePath(alternateDL)); + } + if (dlist3 != NULL) { + ResourceMgr_PatchCustomGfxByName(resource, dlist3, 2, gsSPEndDisplayList()); + } + } +} + +struct PatchEntry { + const char* resource; + const char* gfx; + const char* dlist1; + const char* dlist2; + const char* dlist3; + const char* alternateDL; +}; + +static void ApplyPatchEntries(std::initializer_list entries) { + for (const auto& entry : entries) { + PatchOrUnpatch(entry.resource, entry.gfx, entry.dlist1, entry.dlist2, entry.dlist3, entry.alternateDL); + } +} + +static void UnpatchGroup(const char* resource, std::initializer_list dlistNames) { + for (const char* name : dlistNames) { + ResourceMgr_UnpatchGfxByName(resource, name); + } +} + +static void ApplySwordlessChildPatches() { + ApplyPatchEntries({ + { gLinkChildDekuShieldWithMatrixDL, gCustomDekuShieldOnBackDL, "customChildShieldOnly1", + "customChildShieldOnly2", nullptr, nullptr }, + { gLinkChildHylianShieldSwordAndSheathNearDL, gCustomHylianShieldOnChildBackDL, "customChildHylianShieldOnly1", + "customChildHylianShieldOnly2", nullptr, nullptr }, + { gLinkAdultMirrorShieldSwordAndSheathNearDL, gCustomMirrorShieldOnBackDL, "customAdultMirrorOnly1", + "customAdultMirrorOnly2", nullptr, nullptr }, + }); + + UnpatchGroup(gLinkChildSwordAndSheathNearDL, { "customKokiriSwordSheath1", "customKokiriSwordSheath2" }); + UnpatchGroup(gLinkChildSheathNearDL, { "customKokiriSheath1", "customKokiriSheath2" }); + UnpatchGroup(gLinkChildDekuShieldSwordAndSheathNearDL, + { "customDekuShieldSword1", "customDekuShieldSword2", "customDekuShieldSword3" }); + UnpatchGroup(gLinkChildHylianShieldSwordAndSheathNearDL, + { "customChildHylianShieldSword1", "customChildHylianShieldSword2", "customChildHylianShieldSword3" }); +} + +static void ApplySwordlessAdultPatches() { + ApplyPatchEntries({ + { gLinkAdultHylianShieldSwordAndSheathNearDL, gCustomHylianShieldOnBackDL, "customAdultShieldOnly1", + "customAdultShieldOnly2", nullptr, nullptr }, + { gLinkAdultMirrorShieldSwordAndSheathNearDL, gCustomMirrorShieldOnBackDL, "customAdultMirrorOnly1", + "customAdultMirrorOnly2", nullptr, nullptr }, + { gLinkChildDekuShieldSwordAndSheathNearDL, gCustomDekuShieldOnBackDL, "customDekuShieldSword1", + "customDekuShieldSword2", nullptr, nullptr }, + }); + + UnpatchGroup(gLinkAdultMasterSwordAndSheathNearDL, { "customMasterSwordSheath1", "customMasterSwordSheath2" }); +} + +static void ApplyKokiriSwordPatches() { + ApplyPatchEntries({ + { gLinkChildSheathNearDL, gCustomKokiriSwordSheathDL, "customKokiriSheath1", "customKokiriSheath2", nullptr, + nullptr }, + { gLinkChildSwordAndSheathNearDL, gCustomKokiriSwordInSheathDL, "customKokiriSwordSheath1", + "customKokiriSwordSheath2", nullptr, nullptr }, + { gLinkChildDekuShieldSwordAndSheathNearDL, gCustomKokiriSwordInSheathDL, "customDekuShieldSword1", + "customDekuShieldSword2", "customDekuShieldSword3", gCustomDekuShieldOnBackDL }, + { gLinkChildDekuShieldAndSheathNearDL, gCustomKokiriSwordSheathDL, "customDekuShieldSheath1", + "customDekuShieldSheath2", "customDekuShieldSheath3", gCustomDekuShieldOnBackDL }, + { gLinkChildHylianShieldSwordAndSheathNearDL, gCustomKokiriSwordInSheathDL, "customChildHylianShieldSword1", + "customChildHylianShieldSword2", "customChildHylianShieldSword3", gCustomHylianShieldOnChildBackDL }, + { gLinkChildHylianShieldAndSheathNearDL, gCustomKokiriSwordSheathDL, "customChildHylianShieldSheath1", + "customChildHylianShieldSheath2", "customChildHylianShieldSheath3", gCustomHylianShieldOnChildBackDL }, + { gLinkAdultSheathNearDL, gCustomKokiriSwordSheathDL, "customSheath1", "customSheath2", nullptr, nullptr }, + { gLinkAdultHylianShieldSwordAndSheathNearDL, gCustomKokiriSwordInSheathDL, "customHylianShieldSword1", + "customHylianShieldSword2", "customHylianShieldSword3", gCustomHylianShieldOnBackDL }, + { gLinkAdultMasterSwordAndSheathNearDL, gCustomKokiriSwordInSheathDL, "customMasterSwordSheath1", + "customMasterSwordSheath2", nullptr, nullptr }, + { gLinkAdultHylianShieldAndSheathNearDL, gCustomKokiriSwordSheathDL, "customHylianShieldSheath1", + "customHylianShieldSheath2", "customHylianShieldSheath3", gCustomHylianShieldOnBackDL }, + { gLinkAdultMirrorShieldSwordAndSheathNearDL, gCustomKokiriSwordInSheathDL, "customMirrorShieldSword1", + "customMirrorShieldSword2", "customMirrorShieldSword3", gCustomMirrorShieldOnBackDL }, + }); +} + +static void ApplyMasterSwordPatches() { + ApplyPatchEntries({ + { gLinkChildDekuShieldWithMatrixDL, gCustomMasterSwordInSheathDL, "customDekuShieldBack1", + "customDekuShieldBack2", "customDekuShieldBack2", gCustomDekuShieldOnBackDL }, + { gLinkChildHylianShieldAndSheathNearDL, gCustomMasterSwordSheathDL, "customChildHylianShieldSheath1", + "customChildHylianShieldSheath2", "customChildHylianShieldSheath3", gCustomHylianShieldOnChildBackDL }, + { gLinkChildSheathNearDL, gCustomMasterSwordSheathDL, "customKokiriSheath1", "customKokiriSheath2", nullptr, + nullptr }, + { gLinkChildSwordAndSheathNearDL, gCustomMasterSwordInSheathDL, "customKokiriSwordSheath1", + "customKokiriSwordSheath2", nullptr, nullptr }, + { gLinkChildDekuShieldSwordAndSheathNearDL, gCustomMasterSwordInSheathDL, "customDekuShieldSword1", + "customDekuShieldSword2", "customDekuShieldSword3", gCustomDekuShieldOnBackDL }, + { gLinkChildDekuShieldAndSheathNearDL, gCustomMasterSwordSheathDL, "customDekuShieldSheath1", + "customDekuShieldSheath2", "customDekuShieldSheath3", gCustomDekuShieldOnBackDL }, + { gLinkChildHylianShieldSwordAndSheathNearDL, gCustomMasterSwordInSheathDL, "customChildHylianShieldSword1", + "customChildHylianShieldSword2", "customChildHylianShieldSword3", gCustomHylianShieldOnChildBackDL }, + { gLinkAdultSheathNearDL, gCustomMasterSwordSheathDL, "customSheath1", "customSheath2", nullptr, nullptr }, + { gLinkAdultMasterSwordAndSheathNearDL, gCustomMasterSwordInSheathDL, "customMasterSwordSheath1", + "customMasterSwordSheath2", nullptr, nullptr }, + { gLinkAdultHylianShieldSwordAndSheathNearDL, gCustomMasterSwordInSheathDL, "customHylianShieldSword1", + "customHylianShieldSword2", "customHylianShieldSword3", gCustomHylianShieldOnBackDL }, + { gLinkAdultHylianShieldAndSheathNearDL, gCustomMasterSwordSheathDL, "customHylianShieldSheath1", + "customHylianShieldSheath2", "customHylianShieldSheath3", gCustomHylianShieldOnBackDL }, + { gLinkAdultMirrorShieldSwordAndSheathNearDL, gCustomMasterSwordInSheathDL, "customMirrorShieldSword1", + "customMirrorShieldSword2", "customMirrorShieldSword3", gCustomMirrorShieldOnBackDL }, + }); +} + +static void ApplyBiggoronSwordPatches() { + if (gPlayState != nullptr && GET_PLAYER(gPlayState)->sheathType == PLAYER_MODELTYPE_SHEATH_19) { + PatchOrUnpatch(gLinkChildDekuShieldWithMatrixDL, gCustomLongswordSheathDL, "customDekuShieldBack1", + "customDekuShieldBack2", "customDekuShieldBack2", gCustomDekuShieldOnBackDL); + } else { + PatchOrUnpatch(gLinkChildDekuShieldWithMatrixDL, gCustomLongswordInSheathDL, "customDekuShieldBack1", + "customDekuShieldBack2", "customDekuShieldBack2", gCustomDekuShieldOnBackDL); + } + + ApplyPatchEntries({ + { gLinkChildHylianShieldAndSheathNearDL, gCustomLongswordSheathDL, "customChildHylianShieldSheath1", + "customChildHylianShieldSheath2", "customChildHylianShieldSheath3", gCustomHylianShieldOnChildBackDL }, + { gLinkChildDekuShieldAndSheathNearDL, gCustomLongswordSheathDL, "customDekuShieldSheath1", + "customDekuShieldSheath2", "customDekuShieldSheath3", gCustomDekuShieldOnBackDL }, + { gLinkAdultLeftHandHoldingBgsNearDL, gCustomLongswordDL, "customBGS1", "customBGS2", "customBGS3", + gLinkAdultLeftHandClosedNearDL }, + { gLinkAdultMasterSwordAndSheathNearDL, gCustomLongswordInSheathDL, "customMasterSwordSheath1", + "customMasterSwordSheath2", nullptr, nullptr }, + { gLinkChildSheathNearDL, gCustomLongswordSheathDL, "customKokiriSheath1", "customKokiriSheath2", nullptr, + nullptr }, + { gLinkChildSwordAndSheathNearDL, gCustomLongswordInSheathDL, "customKokiriSwordSheath1", + "customKokiriSwordSheath2", nullptr, nullptr }, + { gLinkChildDekuShieldSwordAndSheathNearDL, gCustomLongswordInSheathDL, "customDekuShieldSword1", + "customDekuShieldSword2", "customDekuShieldSword3", gCustomDekuShieldOnBackDL }, + { gLinkChildHylianShieldSwordAndSheathNearDL, gCustomLongswordInSheathDL, "customChildHylianShieldSword1", + "customChildHylianShieldSword2", "customChildHylianShieldSword3", gCustomHylianShieldOnChildBackDL }, + { gLinkAdultSheathNearDL, gCustomLongswordSheathDL, "customSheath1", "customSheath2", nullptr, nullptr }, + { gLinkAdultHylianShieldSwordAndSheathNearDL, gCustomLongswordInSheathDL, "customHylianShieldSword1", + "customHylianShieldSword2", "customHylianShieldSword3", gCustomHylianShieldOnBackDL }, + { gLinkAdultHylianShieldAndSheathNearDL, gCustomLongswordSheathDL, "customHylianShieldSheath1", + "customHylianShieldSheath2", "customHylianShieldSheath3", gCustomHylianShieldOnBackDL }, + { gLinkAdultMirrorShieldSwordAndSheathNearDL, gCustomLongswordInSheathDL, "customMirrorShieldSword1", + "customMirrorShieldSword2", "customMirrorShieldSword3", gCustomMirrorShieldOnBackDL }, + { gLinkAdultMirrorShieldAndSheathNearDL, gCustomLongswordSheathDL, "customMirrorShieldSheath1", + "customMirrorShieldSheath2", "customMirrorShieldSheath3", gCustomMirrorShieldOnBackDL }, + }); +} + +static void ApplyBreakableLongswordPatches() { + if (gPlayState != nullptr && GET_PLAYER(gPlayState)->sheathType == PLAYER_MODELTYPE_SHEATH_19) { + PatchOrUnpatch(gLinkChildDekuShieldWithMatrixDL, GetBreakableLongswordSheathDL(), "customDekuShieldBack1", + "customDekuShieldBack2", "customDekuShieldBack2", gCustomDekuShieldOnBackDL); + } else { + PatchOrUnpatch(gLinkChildDekuShieldWithMatrixDL, GetBreakableLongswordInSheathDL(), "customDekuShieldBack1", + "customDekuShieldBack2", "customDekuShieldBack2", gCustomDekuShieldOnBackDL); + } + + ApplyPatchEntries({ + { gLinkChildHylianShieldAndSheathNearDL, GetBreakableLongswordSheathDL(), "customChildHylianShieldSheath1", + "customChildHylianShieldSheath2", "customChildHylianShieldSheath3", gCustomHylianShieldOnChildBackDL }, + { gLinkChildDekuShieldAndSheathNearDL, GetBreakableLongswordSheathDL(), "customDekuShieldSheath1", + "customDekuShieldSheath2", "customDekuShieldSheath3", gCustomDekuShieldOnBackDL }, + { gLinkAdultLeftHandHoldingBgsNearDL, GetBreakableLongswordDL(), "customGK1", "customGK2", "customGK3", + gLinkAdultLeftHandClosedNearDL }, + { gLinkAdultMasterSwordAndSheathNearDL, GetBreakableLongswordInSheathDL(), "customMasterSwordSheath1", + "customMasterSwordSheath2", nullptr, nullptr }, + { gLinkChildSheathNearDL, GetBreakableLongswordSheathDL(), "customKokiriSheath1", "customKokiriSheath2", + nullptr, nullptr }, + { gLinkChildSwordAndSheathNearDL, GetBreakableLongswordInSheathDL(), "customKokiriSwordSheath1", + "customKokiriSwordSheath2", nullptr, nullptr }, + { gLinkChildDekuShieldSwordAndSheathNearDL, GetBreakableLongswordInSheathDL(), "customDekuShieldSword1", + "customDekuShieldSword2", "customDekuShieldSword3", gCustomDekuShieldOnBackDL }, + { gLinkChildHylianShieldSwordAndSheathNearDL, GetBreakableLongswordInSheathDL(), + "customChildHylianShieldSword1", "customChildHylianShieldSword2", "customChildHylianShieldSword3", + gCustomHylianShieldOnChildBackDL }, + { gLinkAdultSheathNearDL, GetBreakableLongswordSheathDL(), "customSheath1", "customSheath2", nullptr, nullptr }, + { gLinkAdultHylianShieldSwordAndSheathNearDL, GetBreakableLongswordInSheathDL(), "customHylianShieldSword1", + "customHylianShieldSword2", "customHylianShieldSword3", gCustomHylianShieldOnBackDL }, + { gLinkAdultHylianShieldAndSheathNearDL, GetBreakableLongswordSheathDL(), "customHylianShieldSheath1", + "customHylianShieldSheath2", "customHylianShieldSheath3", gCustomHylianShieldOnBackDL }, + { gLinkAdultMirrorShieldSwordAndSheathNearDL, GetBreakableLongswordInSheathDL(), "customMirrorShieldSword1", + "customMirrorShieldSword2", "customMirrorShieldSword3", gCustomMirrorShieldOnBackDL }, + { gLinkAdultMirrorShieldAndSheathNearDL, GetBreakableLongswordSheathDL(), "customMirrorShieldSheath1", + "customMirrorShieldSheath2", "customMirrorShieldSheath3", gCustomMirrorShieldOnBackDL }, + }); +} + +static void ApplyBrokenKnifePatches() { + if (gPlayState != nullptr && GET_PLAYER(gPlayState)->sheathType == PLAYER_MODELTYPE_SHEATH_19) { + PatchOrUnpatch(gLinkChildDekuShieldWithMatrixDL, GetBrokenLongswordSheathDL(), "customDekuShieldBack1", + "customDekuShieldBack2", "customDekuShieldBack2", gCustomDekuShieldOnBackDL); + } else { + PatchOrUnpatch(gLinkChildDekuShieldWithMatrixDL, GetBrokenLongswordInSheathDL(), "customDekuShieldBack1", + "customDekuShieldBack2", "customDekuShieldBack2", gCustomDekuShieldOnBackDL); + } + + ApplyPatchEntries({ + { gLinkChildHylianShieldAndSheathNearDL, GetBrokenLongswordSheathDL(), "customChildHylianShieldSheath1", + "customChildHylianShieldSheath2", "customChildHylianShieldSheath3", gCustomHylianShieldOnChildBackDL }, + { gLinkChildDekuShieldAndSheathNearDL, GetBrokenLongswordSheathDL(), "customDekuShieldSheath1", + "customDekuShieldSheath2", "customDekuShieldSheath3", gCustomDekuShieldOnBackDL }, + { gLinkAdultMasterSwordAndSheathNearDL, GetBrokenLongswordInSheathDL(), "customMasterSwordSheath1", + "customMasterSwordSheath2", nullptr, nullptr }, + { gLinkChildSheathNearDL, GetBrokenLongswordSheathDL(), "customKokiriSheath1", "customKokiriSheath2", nullptr, + nullptr }, + { gLinkChildSwordAndSheathNearDL, GetBrokenLongswordInSheathDL(), "customKokiriSwordSheath1", + "customKokiriSwordSheath2", nullptr, nullptr }, + { gLinkChildDekuShieldSwordAndSheathNearDL, GetBrokenLongswordInSheathDL(), "customDekuShieldSword1", + "customDekuShieldSword2", "customDekuShieldSword3", gCustomDekuShieldOnBackDL }, + { gLinkChildHylianShieldSwordAndSheathNearDL, GetBrokenLongswordInSheathDL(), "customChildHylianShieldSword1", + "customChildHylianShieldSword2", "customChildHylianShieldSword3", gCustomHylianShieldOnChildBackDL }, + { gLinkAdultSheathNearDL, GetBrokenLongswordSheathDL(), "customSheath1", "customSheath2", nullptr, nullptr }, + { gLinkAdultHylianShieldSwordAndSheathNearDL, GetBrokenLongswordInSheathDL(), "customHylianShieldSword1", + "customHylianShieldSword2", "customHylianShieldSword3", gCustomHylianShieldOnBackDL }, + { gLinkAdultHylianShieldAndSheathNearDL, GetBrokenLongswordSheathDL(), "customHylianShieldSheath1", + "customHylianShieldSheath2", "customHylianShieldSheath3", gCustomHylianShieldOnBackDL }, + { gLinkAdultMirrorShieldSwordAndSheathNearDL, GetBrokenLongswordInSheathDL(), "customMirrorShieldSword1", + "customMirrorShieldSword2", "customMirrorShieldSword3", gCustomMirrorShieldOnBackDL }, + { gLinkAdultMirrorShieldAndSheathNearDL, GetBrokenLongswordSheathDL(), "customMirrorShieldSheath1", + "customMirrorShieldSheath2", "customMirrorShieldSheath3", gCustomMirrorShieldOnBackDL }, + }); +} + +static void ApplyCommonEquipmentPatches() { + const bool isChild = LINK_IS_CHILD; + const char* rightHandClosed = isChild ? gLinkChildRightHandClosedNearDL : gLinkAdultRightHandClosedNearDL; + const char* leftHandClosed = isChild ? gLinkChildLeftFistNearDL : gLinkAdultLeftHandClosedNearDL; + const char* fpsHand = isChild ? gCustomChildFPSHandDL : gCustomAdultFPSHandDL; + const char* rightHandNear = isChild ? gLinkChildRightHandNearDL : gLinkAdultRightHandNearDL; + + ApplyPatchEntries({ + { gLinkAdultLeftHandHoldingMasterSwordNearDL, gCustomMasterSwordDL, "customMasterSword1", "customMasterSword2", + "customMasterSword3", leftHandClosed }, + { gLinkAdultRightHandHoldingHylianShieldNearDL, gCustomHylianShieldDL, "customHylianShield1", + "customHylianShield2", "customHylianShield3", rightHandClosed }, + { gLinkAdultRightHandHoldingMirrorShieldNearDL, gCustomMirrorShieldDL, "customMirrorShield1", + "customMirrorShield2", "customMirrorShield3", rightHandClosed }, + { gLinkAdultHandHoldingBrokenGiantsKnifeDL, gCustomBrokenLongswordDL, "customBrokenBGS1", "customBrokenBGS2", + "customBrokenBGS3", leftHandClosed }, + { gLinkChildLeftFistAndKokiriSwordNearDL, gCustomKokiriSwordDL, "customKokiriSword1", "customKokiriSword2", + "customKokiriSword3", leftHandClosed }, + { gLinkChildRightFistAndDekuShieldNearDL, gCustomDekuShieldDL, "customDekuShield1", "customDekuShield2", + "customDekuShield3", rightHandClosed }, + }); + + if (INV_CONTENT(ITEM_HOOKSHOT) == ITEM_HOOKSHOT) { + ApplyPatchEntries({ + { gLinkAdultRightHandHoldingHookshotNearDL, gCustomHookshotDL, "customHookshot1", "customHookshot2", + "customHookshot3", rightHandClosed }, + { gLinkAdultRightHandHoldingHookshotFarDL, gCustomHookshotDL, "customHookshotFPS1", "customHookshotFPS2", + "customHookshotFPS3", fpsHand }, + }); + } + + if (INV_CONTENT(ITEM_LONGSHOT) == ITEM_LONGSHOT) { + ApplyPatchEntries({ + { gLinkAdultRightHandHoldingHookshotNearDL, gCustomLongshotDL, "customHookshot1", "customHookshot2", + "customHookshot3", rightHandClosed }, + { gLinkAdultRightHandHoldingHookshotFarDL, gCustomLongshotDL, "customHookshotFPS1", "customHookshotFPS2", + "customHookshotFPS3", fpsHand }, + }); + } + + ApplyPatchEntries({ + { gLinkAdultHookshotTipDL, gCustomHookshotTipDL, "customHookshotTip1", "customHookshotTip2", nullptr, nullptr }, + { gLinkAdultHookshotChainDL, gCustomHookshotChainDL, "customHookshotChain1", "customHookshotChain2", nullptr, + nullptr }, + }); + + if (INV_CONTENT(ITEM_OCARINA_FAIRY) == ITEM_OCARINA_FAIRY) { + ApplyPatchEntries({ + { gLinkAdultRightHandHoldingOotNearDL, isChild ? gCustomFairyOcarinaDL : gCustomFairyOcarinaAdultDL, + "customOcarina1", "customOcarina2", "customOcarina3", rightHandNear }, + }); + } + + if (INV_CONTENT(ITEM_OCARINA_TIME) == ITEM_OCARINA_TIME) { + ApplyPatchEntries({ + { gLinkAdultRightHandHoldingOotNearDL, isChild ? gCustomOcarinaOfTimeDL : gCustomOcarinaOfTimeAdultDL, + "customOcarina1", "customOcarina2", "customOcarina3", rightHandNear }, + }); + } + + ApplyPatchEntries({ + { gLinkChildRightHandHoldingFairyOcarinaNearDL, gCustomFairyOcarinaDL, "customFairyOcarina1", + "customFairyOcarina2", "customFairyOcarina3", rightHandNear }, + { gLinkChildRightHandAndOotNearDL, gCustomOcarinaOfTimeDL, "customChildOcarina1", "customChildOcarina2", + "customChildOcarina3", rightHandNear }, + { gLinkAdultRightHandHoldingBowNearDL, gCustomBowDL, "customBow1", "customBow2", "customBow3", + rightHandClosed }, + { gLinkAdultRightHandHoldingBowFirstPersonDL, gCustomBowDL, "customBowFPS1", "customBowFPS2", "customBowFPS3", + fpsHand }, + { gLinkAdultLeftHandHoldingHammerNearDL, gCustomHammerDL, "customHammer1", "customHammer2", "customHammer3", + leftHandClosed }, + { gLinkChildLeftFistAndBoomerangNearDL, gCustomBoomerangDL, "customBoomerang1", "customBoomerang2", + "customBoomerang3", leftHandClosed }, + { gLinkChildRightHandHoldingSlingshotNearDL, gCustomSlingshotDL, "customSlingshot1", "customSlingshot2", + "customSlingshot3", rightHandClosed }, + { gLinkChildRightArmStretchedSlingshotDL, gCustomSlingshotDL, "customSlingshotFPS1", "customSlingshotFPS2", + "customSlingshotFPS3", fpsHand }, + }); + + ApplyPatchEntries({ + { gLinkChildRightHandHoldingFairyOcarinaNearDL, gCustomFairyOcarinaDL, "customFairyOcarina1", + "customFairyOcarina2", "customFairyOcarina3", rightHandNear }, + { gLinkChildRightHandAndOotNearDL, gCustomOcarinaOfTimeDL, "customChildOcarina1", "customChildOcarina2", + "customChildOcarina3", rightHandNear }, + { gLinkChildLeftFistAndBoomerangNearDL, gCustomBoomerangDL, "customBoomerang1", "customBoomerang2", + "customBoomerang3", leftHandClosed }, + { gLinkChildRightHandHoldingSlingshotNearDL, gCustomSlingshotDL, "customSlingshot1", "customSlingshot2", + "customSlingshot3", rightHandClosed }, + { gLinkChildRightArmStretchedSlingshotDL, gCustomSlingshotDL, "customSlingshotFPS1", "customSlingshotFPS2", + "customSlingshotFPS3", fpsHand }, + }); + + const bool equipmentAlwaysVisible = CVarGetInteger(CVAR_ENHANCEMENT("EquipmentAlwaysVisible"), 0) != 0; + + if (LINK_IS_CHILD && equipmentAlwaysVisible) { + ApplyPatchEntries({ + { gCustomAdultFPSHandDL, gCustomChildFPSHandDL, "patchChildFPSHand1", "patchChildFPSHand2", nullptr, + nullptr }, + { gLinkAdultRightHandClosedNearDL, gLinkChildRightHandClosedNearDL, "customChildRightHand1", + "customChildRightHand2", nullptr, nullptr }, + { gLinkAdultLeftHandClosedNearDL, gLinkChildLeftFistNearDL, "customChildLeftHand1", "customChildLeftHand2", + nullptr, nullptr }, + }); + } + + if (LINK_IS_ADULT && equipmentAlwaysVisible) { + ApplyPatchEntries({ + { gCustomChildFPSHandDL, gCustomAdultFPSHandDL, "patchAdultFPSHand1", "patchAdultFPSHand2", nullptr, + nullptr }, + { gLinkChildRightHandClosedNearDL, gLinkAdultRightHandClosedNearDL, "customAdultRightHand1", + "customAdultRightHand2", nullptr, nullptr }, + { gLinkChildLeftFistNearDL, gLinkAdultLeftHandClosedNearDL, "customAdultLeftHand1", "customAdultLeftHand2", + nullptr, nullptr }, + }); + } +} + +void UpdatePatchCustomEquipmentDlists() { + const u8 equippedSword = gSaveContext.equips.buttonItems[0]; + + if (equippedSword == ITEM_NONE) { + if (LINK_IS_CHILD) { + ApplySwordlessChildPatches(); + } + if (LINK_IS_ADULT) { + ApplySwordlessAdultPatches(); + } + } + + switch (equippedSword) { + case ITEM_SWORD_KOKIRI: + ApplyKokiriSwordPatches(); + break; + case ITEM_SWORD_MASTER: + ApplyMasterSwordPatches(); + break; + case ITEM_SWORD_BGS: + if (gSaveContext.bgsFlag) { + ApplyBiggoronSwordPatches(); + } else { + ApplyBreakableLongswordPatches(); + } + break; + case ITEM_SWORD_KNIFE: + ApplyBrokenKnifePatches(); + break; + default: + break; + } + + ApplyCommonEquipmentPatches(); +} + +static bool HasDummyPlayers() { + if (gPlayState == nullptr) { + return false; + } + + Actor* actor = gPlayState->actorCtx.actorLists[ACTORCAT_NPC].head; + while (actor != nullptr) { + if (actor->id == ACTOR_EN_OE2 && actor->update == DummyPlayer_Update) { + return true; + } + actor = actor->next; + } + + return false; +} diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h index b961d0ff6..d5fa7221e 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h @@ -43,6 +43,7 @@ DEFINE_HOOK(OnEnemyDefeat, (void* actor)); DEFINE_HOOK(OnBossDefeat, (void* actor)); DEFINE_HOOK(OnTimestamp, (u8 item)); DEFINE_HOOK(OnPlayerBonk, ()); +DEFINE_HOOK(OnPlayerSetModels, (Player * player, u8 modelGroup)); DEFINE_HOOK(OnPlayerHealthChange, (int16_t amount)); DEFINE_HOOK(OnPlayerBottleUpdate, (int16_t contents)); DEFINE_HOOK(OnPlayerHoldUpShield, ()); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp index f096139b2..e3e570fe4 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp @@ -201,6 +201,10 @@ void GameInteractor_ExecuteOnPlayerBonk() { GameInteractor::Instance->ExecuteHooks(); } +void GameInteractor_ExecuteOnPlayerSetModels(Player* player, u8 modelGroup) { + GameInteractor::Instance->ExecuteHooks(player, modelGroup); +} + void GameInteractor_ExecuteOnPlayerHealthChange(int16_t amount) { GameInteractor::Instance->ExecuteHooks(amount); } diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h index 4964c896a..5aafd93de 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h @@ -44,6 +44,7 @@ void GameInteractor_ExecuteOnEnemyDefeat(void* actor); void GameInteractor_ExecuteOnBossDefeat(void* actor); void GameInteractor_ExecuteOnTimestamp(u8 item); void GameInteractor_ExecuteOnPlayerBonk(); +void GameInteractor_ExecuteOnPlayerSetModels(Player* player, u8 modelGroup); void GameInteractor_ExecuteOnPlayerHealthChange(int16_t amount); void GameInteractor_ExecuteOnPlayerBottleUpdate(int16_t contents); void GameInteractor_ExecuteOnPlayerHoldUpShield(); diff --git a/soh/soh/ResourceManagerHelpers.cpp b/soh/soh/ResourceManagerHelpers.cpp index 127378da7..329aed6a6 100644 --- a/soh/soh/ResourceManagerHelpers.cpp +++ b/soh/soh/ResourceManagerHelpers.cpp @@ -314,6 +314,9 @@ extern "C" uint8_t ResourceMgr_FileIsCustomByName(const char* path) { typedef struct { int index; Gfx instruction; + const void* instructionsPtr; + size_t instructionCount; + bool isCustom; } GfxPatch; std::unordered_map> originalGfx; @@ -324,6 +327,10 @@ extern "C" void ResourceMgr_PatchGfxByName(const char* path, const char* patchNa auto res = std::static_pointer_cast( Ship::Context::GetInstance()->GetResourceManager()->LoadResource(path)); + if (res == nullptr || static_cast(index) >= res->Instructions.size()) { + return; + } + // Leaving this here for people attempting to find the correct Dlist index to patch /*if (strcmp("__OTR__objects/object_gi_longsword/gGiBiggoronSwordDL", path) == 0) { for (int i = 0; i < res->instructions.size(); i++) { @@ -351,7 +358,8 @@ extern "C" void ResourceMgr_PatchGfxByName(const char* path, const char* patchNa Gfx* gfx = (Gfx*)&res->Instructions[index]; if (!originalGfx.contains(path) || !originalGfx[path].contains(patchName)) { - originalGfx[path][patchName] = { index, *gfx }; + originalGfx[path][patchName] = { index, *gfx, res->Instructions.data(), res->Instructions.size(), + res->GetInitData()->IsCustom }; } *gfx = instruction; @@ -362,6 +370,11 @@ extern "C" void ResourceMgr_PatchGfxCopyCommandByName(const char* path, const ch auto res = std::static_pointer_cast( Ship::Context::GetInstance()->GetResourceManager()->LoadResource(path)); + if (res == nullptr || static_cast(destinationIndex) >= res->Instructions.size() || + static_cast(sourceIndex) >= res->Instructions.size()) { + return; + } + // Do not patch custom assets as they most likely do not have the same instructions as authentic assets if (res->GetInitData()->IsCustom) { return; @@ -371,19 +384,62 @@ extern "C" void ResourceMgr_PatchGfxCopyCommandByName(const char* path, const ch Gfx sourceGfx = *(Gfx*)&res->Instructions[sourceIndex]; if (!originalGfx.contains(path) || !originalGfx[path].contains(patchName)) { - originalGfx[path][patchName] = { destinationIndex, *destinationGfx }; + originalGfx[path][patchName] = { destinationIndex, *destinationGfx, res->Instructions.data(), + res->Instructions.size(), res->GetInitData()->IsCustom }; } *destinationGfx = sourceGfx; } +extern "C" void ResourceMgr_PatchCustomGfxByName(const char* path, const char* patchName, int index, Gfx instruction) { + auto res = std::static_pointer_cast( + Ship::Context::GetInstance()->GetResourceManager()->LoadResource(path)); + + if (res == nullptr || static_cast(index) >= res->Instructions.size()) { + return; + } + + Gfx* gfx = (Gfx*)&res->Instructions[index]; + + if (!originalGfx.contains(path) || !originalGfx[path].contains(patchName)) { + originalGfx[path][patchName] = { index, *gfx, res->Instructions.data(), res->Instructions.size(), + res->GetInitData()->IsCustom }; + } + + *gfx = instruction; +} + extern "C" void ResourceMgr_UnpatchGfxByName(const char* path, const char* patchName) { if (originalGfx.contains(path) && originalGfx[path].contains(patchName)) { auto res = std::static_pointer_cast( Ship::Context::GetInstance()->GetResourceManager()->LoadResource(path)); - Gfx* gfx = (Gfx*)&res->Instructions[originalGfx[path][patchName].index]; - *gfx = originalGfx[path][patchName].instruction; + // If the resource is unavailable (e.g. swapped out when toggling alt assets), clean up the record and bail. + if (res == nullptr) { + ResourceMgr_UnloadResource(path); + originalGfx[path].erase(patchName); + return; + } + + const GfxPatch& patch = originalGfx[path][patchName]; + // Skip and clean up if the backing resource changed since we recorded the patch (e.g. alt<->vanilla swap) + // to avoid writing instructions from a different asset onto the current one. + if (res->Instructions.data() != patch.instructionsPtr || res->Instructions.size() != patch.instructionCount || + res->GetInitData()->IsCustom != patch.isCustom) { + ResourceMgr_UnloadResource(path); + originalGfx[path].erase(patchName); + return; + } + + // Skip and clean up if the loaded resource is smaller than the recorded patch index (can happen when alt assets + // swap in shorter display lists). + if (static_cast(patch.index) >= res->Instructions.size()) { + originalGfx[path].erase(patchName); + return; + } + + Gfx* gfx = (Gfx*)&res->Instructions[patch.index]; + *gfx = patch.instruction; originalGfx[path].erase(patchName); } diff --git a/soh/soh/ResourceManagerHelpers.h b/soh/soh/ResourceManagerHelpers.h index 8562a3e0e..1dfd34449 100644 --- a/soh/soh/ResourceManagerHelpers.h +++ b/soh/soh/ResourceManagerHelpers.h @@ -47,6 +47,7 @@ Gfx* ResourceMgr_LoadGfxByCRC(uint64_t crc); Gfx* ResourceMgr_LoadGfxByName(const char* path); uint8_t ResourceMgr_FileIsCustomByName(const char* path); void ResourceMgr_PatchGfxByName(const char* path, const char* patchName, int index, Gfx instruction); +void ResourceMgr_PatchCustomGfxByName(const char* path, const char* patchName, int index, Gfx instruction); void ResourceMgr_UnpatchGfxByName(const char* path, const char* patchName); char* ResourceMgr_LoadArrayByNameAsVec3s(const char* path); Vtx* ResourceMgr_LoadVtxByCRC(uint64_t crc); @@ -65,4 +66,4 @@ int ResourceMgr_OTRSigCheck(char* imgData); char* ResourceMgr_GetResourceDataByNameHandlingMQ(const char* path); #ifdef __cplusplus } -#endif // __cplusplus \ No newline at end of file +#endif // __cplusplus diff --git a/soh/src/code/z_player_lib.c b/soh/src/code/z_player_lib.c index 49d9380bf..0502559f0 100644 --- a/soh/src/code/z_player_lib.c +++ b/soh/src/code/z_player_lib.c @@ -650,6 +650,7 @@ void Player_SetModels(Player* this, s32 modelGroup) { this->waistDLists = &sPlayerDListGroups[gPlayerModelTypes[modelGroup][4]][gSaveContext.linkAge]; Player_SetModelsForHoldingShield(this); + GameInteractor_ExecuteOnPlayerSetModels(this, modelGroup); } void Player_SetModelGroup(Player* this, s32 modelGroup) { @@ -1481,7 +1482,6 @@ s32 Player_OverrideLimbDrawGameplayFirstPerson(PlayState* play, s32 limbIndex, G *dList = NULL; } } - return false; }