From 6a51720194a6ffca9d059c60bfe0b48debd45d05 Mon Sep 17 00:00:00 2001 From: Jameriquiah <42100286+Jameriquiah@users.noreply.github.com> Date: Sun, 21 Jun 2026 16:46:47 -0500 Subject: [PATCH] Custom Hand DL's for each tunic take 3 (#6754) --- .../object_custom_equip/object_custom_equip.h | 12 ++++ soh/soh/Enhancements/customequipment.cpp | 21 ++++++ soh/soh/ResourceManagerHelpers.cpp | 64 +++++++++++++++++++ 3 files changed, 97 insertions(+) diff --git a/soh/assets/objects/object_custom_equip/object_custom_equip.h b/soh/assets/objects/object_custom_equip/object_custom_equip.h index dd4cfb1f76..f86061a9a5 100644 --- a/soh/assets/objects/object_custom_equip/object_custom_equip.h +++ b/soh/assets/objects/object_custom_equip/object_custom_equip.h @@ -128,9 +128,21 @@ static const ALIGN_ASSET(2) char gCustomMirrorShieldOnBackDL[] = dgCustomMirrorS #define dgCustomAdultFPSHandDL "__OTR__objects/object_custom_equip/gCustomAdultFPSHandDL" static const ALIGN_ASSET(2) char gCustomAdultFPSHandDL[] = dgCustomAdultFPSHandDL; +#define dgCustomAdultGoronFPSHandDL "__OTR__objects/object_custom_equip/gCustomAdultGoronFPSHandDL" +static const ALIGN_ASSET(2) char gCustomAdultGoronFPSHandDL[] = dgCustomAdultGoronFPSHandDL; + +#define dgCustomAdultZoraFPSHandDL "__OTR__objects/object_custom_equip/gCustomAdultZoraFPSHandDL" +static const ALIGN_ASSET(2) char gCustomAdultZoraFPSHandDL[] = dgCustomAdultZoraFPSHandDL; + #define dgCustomChildFPSHandDL "__OTR__objects/object_custom_equip/gCustomChildFPSHandDL" static const ALIGN_ASSET(2) char gCustomChildFPSHandDL[] = dgCustomChildFPSHandDL; +#define dgCustomChildGoronFPSHandDL "__OTR__objects/object_custom_equip/gCustomChildGoronFPSHandDL" +static const ALIGN_ASSET(2) char gCustomChildGoronFPSHandDL[] = dgCustomChildGoronFPSHandDL; + +#define dgCustomChildZoraFPSHandDL "__OTR__objects/object_custom_equip/gCustomChildZoraFPSHandDL" +static const ALIGN_ASSET(2) char gCustomChildZoraFPSHandDL[] = dgCustomChildZoraFPSHandDL; + #endif // OBJECTS_OBJECT_CUSTOM_EQUIP_H diff --git a/soh/soh/Enhancements/customequipment.cpp b/soh/soh/Enhancements/customequipment.cpp index 282bccc2e9..4038c5bc85 100644 --- a/soh/soh/Enhancements/customequipment.cpp +++ b/soh/soh/Enhancements/customequipment.cpp @@ -25,6 +25,26 @@ static const char* ResolveCustomChain(std::initializer_list paths) return fallback; } +static const char* ResolveCustomFPSHand(const char* path) { + const bool isAdult = path == gCustomAdultFPSHandDL; + const bool isChild = path == gCustomChildFPSHandDL; + + if (!isAdult && !isChild) { + return path; + } + + switch (TUNIC_EQUIP_TO_PLAYER(CUR_EQUIP_VALUE(EQUIP_TYPE_TUNIC))) { + case PLAYER_TUNIC_GORON: + return ResolveCustomChain( + { isAdult ? gCustomAdultGoronFPSHandDL : gCustomChildGoronFPSHandDL, path, nullptr }); + case PLAYER_TUNIC_ZORA: + return ResolveCustomChain( + { isAdult ? gCustomAdultZoraFPSHandDL : gCustomChildZoraFPSHandDL, path, nullptr }); + default: + return path; + } +} + static Gfx* LoadGfxByName(const char* path) { return path ? ResourceMgr_LoadGfxByName(path) : nullptr; } @@ -32,6 +52,7 @@ static Gfx* LoadGfxByName(const char* path) { static Gfx* LoadCustomGfx(const char* path) { if (!path) return nullptr; + path = ResolveCustomFPSHand(path); if (!ResourceGetIsCustomByName(path) && !ResourceMgr_FileAltExists(path)) return nullptr; return ResourceMgr_LoadGfxByName(path); diff --git a/soh/soh/ResourceManagerHelpers.cpp b/soh/soh/ResourceManagerHelpers.cpp index 618ac98f67..0d745dd084 100644 --- a/soh/soh/ResourceManagerHelpers.cpp +++ b/soh/soh/ResourceManagerHelpers.cpp @@ -2,6 +2,7 @@ #include "OTRGlobals.h" #include "variables.h" #include "z64.h" +#include "macros.h" #include "cvar_prefixes.h" #include "Enhancements/enhancementTypes.h" #include "Enhancements/randomizer/dungeon.h" @@ -20,6 +21,68 @@ extern "C" PlayState* gPlayState; +struct LinkTunicDListCacheKey { + size_t operator()(const std::pair& key) const { + return std::hash{}(key.first) ^ std::hash{}(key.second); + } +}; + +static const char* ResourceMgr_ResolveLinkTunicDListPath(const char* path) { + if (path == nullptr) { + return nullptr; + } + + const char* originalPath = path; + constexpr std::string_view adultPrefix = "__OTR__objects/object_link_boy/"; + constexpr std::string_view childPrefix = "__OTR__objects/object_link_child/"; + + std::string_view objectPrefix; + const char* objectFolder; + + if (std::string_view(originalPath).starts_with(adultPrefix)) { + objectPrefix = adultPrefix; + objectFolder = "object_link_boy"; + } else if (std::string_view(originalPath).starts_with(childPrefix)) { + objectPrefix = childPrefix; + objectFolder = "object_link_child"; + } else { + return path; + } + + const char* tunicSuffix = nullptr; + switch (TUNIC_EQUIP_TO_PLAYER(CUR_EQUIP_VALUE(EQUIP_TYPE_TUNIC))) { + case PLAYER_TUNIC_KOKIRI: + tunicSuffix = "kokiri"; + break; + case PLAYER_TUNIC_GORON: + tunicSuffix = "goron"; + break; + case PLAYER_TUNIC_ZORA: + tunicSuffix = "zora"; + break; + default: + return path; + } + + static std::unordered_map, std::string, LinkTunicDListCacheKey> + sResolvedLinkTunicDListPaths; + std::pair cacheKey{ originalPath, tunicSuffix }; + if (auto it = sResolvedLinkTunicDListPaths.find(cacheKey); it != sResolvedLinkTunicDListPaths.end()) { + return it->second.c_str(); + } + + const std::string candidate = + fmt::format("__OTR__objects/{}_{}/{}", objectFolder, tunicSuffix, originalPath + objectPrefix.size()); + + if (!ResourceGetIsCustomByName(candidate.c_str()) && !ResourceMgr_FileExists(candidate.c_str()) && + !(ResourceMgr_IsAltAssetsEnabled() && ResourceMgr_FileAltExists(candidate.c_str()))) { + return path; + } + + auto it = sResolvedLinkTunicDListPaths.emplace(std::move(cacheKey), candidate).first; + return it->second.c_str(); +} + extern "C" uint32_t ResourceMgr_GetNumGameVersions() { return static_cast( Ship::Context::GetRawInstance()->GetResourceManager()->GetArchiveManager()->GetGameVersions().size()); @@ -307,6 +370,7 @@ extern "C" void ResourceMgr_PushCurrentDirectory(char* path) { } extern "C" Gfx* ResourceMgr_LoadGfxByName(const char* path) { + path = ResourceMgr_ResolveLinkTunicDListPath(path); // When an alt resource exists for the DL, we need to unload the original asset // to clear the cache so the alt asset will be loaded instead // OTRTODO: If Alt loading over original cache is fixed, this line can most likely be removed