summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--soh/assets/objects/object_custom_equip/object_custom_equip.h118
-rw-r--r--soh/soh/Enhancements/customequipment.cpp536
-rw-r--r--soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h1
-rw-r--r--soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp4
-rw-r--r--soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h1
-rw-r--r--soh/soh/ResourceManagerHelpers.cpp64
-rw-r--r--soh/soh/ResourceManagerHelpers.h3
-rw-r--r--soh/src/code/z_player_lib.c2
8 files changed, 723 insertions, 6 deletions
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 <initializer_list>
+#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<const char*> 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<PatchEntry> 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<const char*> 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<GameInteractor::OnPlayerBonk>();
}
+void GameInteractor_ExecuteOnPlayerSetModels(Player* player, u8 modelGroup) {
+ GameInteractor::Instance->ExecuteHooks<GameInteractor::OnPlayerSetModels>(player, modelGroup);
+}
+
void GameInteractor_ExecuteOnPlayerHealthChange(int16_t amount) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnPlayerHealthChange>(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<std::string, std::unordered_map<std::string, GfxPatch>> originalGfx;
@@ -324,6 +327,10 @@ extern "C" void ResourceMgr_PatchGfxByName(const char* path, const char* patchNa
auto res = std::static_pointer_cast<Fast::DisplayList>(
Ship::Context::GetInstance()->GetResourceManager()->LoadResource(path));
+ if (res == nullptr || static_cast<size_t>(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<Fast::DisplayList>(
Ship::Context::GetInstance()->GetResourceManager()->LoadResource(path));
+ if (res == nullptr || static_cast<size_t>(destinationIndex) >= res->Instructions.size() ||
+ static_cast<size_t>(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<Fast::DisplayList>(
+ Ship::Context::GetInstance()->GetResourceManager()->LoadResource(path));
+
+ if (res == nullptr || static_cast<size_t>(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<Fast::DisplayList>(
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<size_t>(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;
}