From 5a9828fcfd9a6a355f2f85ffe843bba4a2ab5806 Mon Sep 17 00:00:00 2001 From: nclok1405 <155463060+nclok1405@users.noreply.github.com> Date: Thu, 19 Jun 2025 16:42:56 +0900 Subject: [PATCH 01/10] Ported "Child Link Can Hold Hylian Shield" by Patrick12115 (#3887) to modern codebase --- soh/soh/SohGui/SohMenuEnhancements.cpp | 7 +++++++ soh/src/code/z_player_lib.c | 20 ++++++++++++++----- .../actors/ovl_En_Nutsball/z_en_nutsball.c | 4 +++- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index 30cf7d5494f..dce441eb623 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -1698,6 +1698,13 @@ void SohMenu::AddMenuEnhancements() { .CVar(CVAR_CHEAT("TimelessEquipment")) .Options(CheckboxOptions().Tooltip("Allows any item to be equipped, regardless of age.\n" "Also allows child to use adult strength upgrades.")); + AddWidget(path, "Hold Hylian Shield as Child Link", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ChildHoldsHylianShield")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("EquipmentAlwaysVisible"), 0) == 0; + }) + .Options(CheckboxOptions().Tooltip( + "Allows Child Link to hold the Hylian Shield the same way as the rest of the shields.")); AddWidget(path, "Unrestricted Items", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_CHEAT("NoRestrictItems")) .Options(CheckboxOptions().Tooltip("Allows you to use any item at any location")); diff --git a/soh/src/code/z_player_lib.c b/soh/src/code/z_player_lib.c index 0502559f0ef..40e0e59a4a9 100644 --- a/soh/src/code/z_player_lib.c +++ b/soh/src/code/z_player_lib.c @@ -526,7 +526,12 @@ s32 Player_CheckHostileLockOn(Player* this) { } s32 Player_IsChildWithHylianShield(Player* this) { - return gSaveContext.linkAge != 0 && (this->currentShield == PLAYER_SHIELD_HYLIAN); + if (CVarGetInteger(CVAR_ENHANCEMENT("ChildHoldsHylianShield"), 0)) { + return false; // Skip vanilla check for making child Link have the Hylian Shield on his back, allowing for it to + // be used in hand + } else { + return gSaveContext.linkAge != 0 && (this->currentShield == PLAYER_SHIELD_HYLIAN); + } } s32 Player_ActionToModelGroup(Player* this, s32 actionParam) { @@ -547,8 +552,11 @@ void Player_SetModelsForHoldingShield(Player* this) { !Player_HoldsTwoHandedWeapon(this)) && !Player_IsChildWithHylianShield(this)) { this->rightHandType = PLAYER_MODELTYPE_RH_SHIELD; - if (LINK_IS_CHILD && (CVarGetInteger(CVAR_ENHANCEMENT("EquipmentAlwaysVisible"), 0)) && - (this->currentShield == PLAYER_SHIELD_MIRROR)) { + if (LINK_IS_CHILD && CVarGetInteger(CVAR_ENHANCEMENT("ChildHoldsHylianShield"), 0) && + this->currentShield == PLAYER_SHIELD_HYLIAN) { + this->rightHandDLists = &sPlayerDListGroups[PLAYER_MODELTYPE_RH_SHIELD][LINK_AGE_ADULT]; + } else if (LINK_IS_CHILD && (CVarGetInteger(CVAR_ENHANCEMENT("EquipmentAlwaysVisible"), 0)) && + (this->currentShield == PLAYER_SHIELD_MIRROR)) { this->rightHandDLists = &sPlayerDListGroups[PLAYER_MODELTYPE_RH_SHIELD][0]; } else if (LINK_IS_ADULT && (CVarGetInteger(CVAR_ENHANCEMENT("EquipmentAlwaysVisible"), 0)) && (this->currentShield == PLAYER_SHIELD_DEKU)) { @@ -599,8 +607,10 @@ void Player_SetModels(Player* this, s32 modelGroup) { this->rightHandType = gPlayerModelTypes[modelGroup][PLAYER_MODELGROUPENTRY_RIGHT_HAND]; this->rightHandDLists = &sPlayerDListGroups[this->rightHandType][gSaveContext.linkAge]; - this->rightHandType = gPlayerModelTypes[modelGroup][PLAYER_MODELGROUPENTRY_RIGHT_HAND]; - this->rightHandDLists = &sPlayerDListGroups[this->rightHandType][gSaveContext.linkAge]; + if (LINK_IS_CHILD && CVarGetInteger(CVAR_ENHANCEMENT("ChildHoldsHylianShield"), 0) && + this->rightHandType == PLAYER_MODELTYPE_RH_SHIELD && this->currentShield == PLAYER_SHIELD_HYLIAN) { + this->rightHandDLists = &sPlayerDListGroups[this->rightHandType][LINK_AGE_ADULT]; + } if (CVarGetInteger(CVAR_ENHANCEMENT("EquipmentAlwaysVisible"), 0)) { if (LINK_IS_CHILD && diff --git a/soh/src/overlays/actors/ovl_En_Nutsball/z_en_nutsball.c b/soh/src/overlays/actors/ovl_En_Nutsball/z_en_nutsball.c index cf70efe9f84..b470a5fb6c7 100644 --- a/soh/src/overlays/actors/ovl_En_Nutsball/z_en_nutsball.c +++ b/soh/src/overlays/actors/ovl_En_Nutsball/z_en_nutsball.c @@ -123,7 +123,9 @@ void func_80ABBBA8(EnNutsball* this, PlayState* play) { // Checking if the player is using a shield that reflects projectiles // And if so, reflects the projectile on impact if ((player->currentShield == PLAYER_SHIELD_DEKU) || - ((player->currentShield == PLAYER_SHIELD_HYLIAN) && LINK_IS_ADULT)) { + ((player->currentShield == PLAYER_SHIELD_HYLIAN) && LINK_IS_ADULT) || + (CVarGetInteger(CVAR_ENHANCEMENT("ChildHoldsHylianShield"), 0) && + player->currentShield == PLAYER_SHIELD_HYLIAN)) { if ((this->collider.base.atFlags & AT_HIT) && (this->collider.base.atFlags & AT_TYPE_ENEMY) && (this->collider.base.atFlags & AT_BOUNCED)) { this->collider.base.atFlags &= ~AT_TYPE_ENEMY & ~AT_BOUNCED & ~AT_HIT; From 566af912fe260699d1a4cf8d3c976d05b05c92d0 Mon Sep 17 00:00:00 2001 From: nclok1405 <155463060+nclok1405@users.noreply.github.com> Date: Thu, 19 Jun 2025 21:19:23 +0900 Subject: [PATCH 02/10] * Moved CVar to CHEAT section * Maybe "Show Age-Dependent Equipment" is not required? --- soh/soh/SohGui/SohMenuEnhancements.cpp | 5 +---- soh/src/code/z_player_lib.c | 6 +++--- soh/src/overlays/actors/ovl_En_Nutsball/z_en_nutsball.c | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index dce441eb623..7f4a8ba811f 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -1699,10 +1699,7 @@ void SohMenu::AddMenuEnhancements() { .Options(CheckboxOptions().Tooltip("Allows any item to be equipped, regardless of age.\n" "Also allows child to use adult strength upgrades.")); AddWidget(path, "Hold Hylian Shield as Child Link", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("ChildHoldsHylianShield")) - .PreFunc([](WidgetInfo& info) { - info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("EquipmentAlwaysVisible"), 0) == 0; - }) + .CVar(CVAR_CHEAT("ChildHoldsHylianShield")) .Options(CheckboxOptions().Tooltip( "Allows Child Link to hold the Hylian Shield the same way as the rest of the shields.")); AddWidget(path, "Unrestricted Items", WIDGET_CVAR_CHECKBOX) diff --git a/soh/src/code/z_player_lib.c b/soh/src/code/z_player_lib.c index 40e0e59a4a9..f826401c8b2 100644 --- a/soh/src/code/z_player_lib.c +++ b/soh/src/code/z_player_lib.c @@ -526,7 +526,7 @@ s32 Player_CheckHostileLockOn(Player* this) { } s32 Player_IsChildWithHylianShield(Player* this) { - if (CVarGetInteger(CVAR_ENHANCEMENT("ChildHoldsHylianShield"), 0)) { + if (CVarGetInteger(CVAR_CHEAT("ChildHoldsHylianShield"), 0)) { return false; // Skip vanilla check for making child Link have the Hylian Shield on his back, allowing for it to // be used in hand } else { @@ -552,7 +552,7 @@ void Player_SetModelsForHoldingShield(Player* this) { !Player_HoldsTwoHandedWeapon(this)) && !Player_IsChildWithHylianShield(this)) { this->rightHandType = PLAYER_MODELTYPE_RH_SHIELD; - if (LINK_IS_CHILD && CVarGetInteger(CVAR_ENHANCEMENT("ChildHoldsHylianShield"), 0) && + if (LINK_IS_CHILD && CVarGetInteger(CVAR_CHEAT("ChildHoldsHylianShield"), 0) && this->currentShield == PLAYER_SHIELD_HYLIAN) { this->rightHandDLists = &sPlayerDListGroups[PLAYER_MODELTYPE_RH_SHIELD][LINK_AGE_ADULT]; } else if (LINK_IS_CHILD && (CVarGetInteger(CVAR_ENHANCEMENT("EquipmentAlwaysVisible"), 0)) && @@ -607,7 +607,7 @@ void Player_SetModels(Player* this, s32 modelGroup) { this->rightHandType = gPlayerModelTypes[modelGroup][PLAYER_MODELGROUPENTRY_RIGHT_HAND]; this->rightHandDLists = &sPlayerDListGroups[this->rightHandType][gSaveContext.linkAge]; - if (LINK_IS_CHILD && CVarGetInteger(CVAR_ENHANCEMENT("ChildHoldsHylianShield"), 0) && + if (LINK_IS_CHILD && CVarGetInteger(CVAR_CHEAT("ChildHoldsHylianShield"), 0) && this->rightHandType == PLAYER_MODELTYPE_RH_SHIELD && this->currentShield == PLAYER_SHIELD_HYLIAN) { this->rightHandDLists = &sPlayerDListGroups[this->rightHandType][LINK_AGE_ADULT]; } diff --git a/soh/src/overlays/actors/ovl_En_Nutsball/z_en_nutsball.c b/soh/src/overlays/actors/ovl_En_Nutsball/z_en_nutsball.c index b470a5fb6c7..3922421f6e5 100644 --- a/soh/src/overlays/actors/ovl_En_Nutsball/z_en_nutsball.c +++ b/soh/src/overlays/actors/ovl_En_Nutsball/z_en_nutsball.c @@ -124,7 +124,7 @@ void func_80ABBBA8(EnNutsball* this, PlayState* play) { // And if so, reflects the projectile on impact if ((player->currentShield == PLAYER_SHIELD_DEKU) || ((player->currentShield == PLAYER_SHIELD_HYLIAN) && LINK_IS_ADULT) || - (CVarGetInteger(CVAR_ENHANCEMENT("ChildHoldsHylianShield"), 0) && + (CVarGetInteger(CVAR_CHEAT("ChildHoldsHylianShield"), 0) && player->currentShield == PLAYER_SHIELD_HYLIAN)) { if ((this->collider.base.atFlags & AT_HIT) && (this->collider.base.atFlags & AT_TYPE_ENEMY) && (this->collider.base.atFlags & AT_BOUNCED)) { From 2f6f6229456982cf02d4155eafff6e29c8e0db19 Mon Sep 17 00:00:00 2001 From: nclok1405 <155463060+nclok1405@users.noreply.github.com> Date: Thu, 19 Jun 2025 21:53:59 +0900 Subject: [PATCH 03/10] Apply graphics patches; fix merge conflicts TODO: mods.cpp is going to be retired and graphics patches will need to be moved into its own cpp file eventually --- soh/soh/Enhancements/mods.h | 2 ++ soh/src/code/z_player_lib.c | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/soh/soh/Enhancements/mods.h b/soh/soh/Enhancements/mods.h index 9a0c6794bf3..3d88efeab32 100644 --- a/soh/soh/Enhancements/mods.h +++ b/soh/soh/Enhancements/mods.h @@ -9,6 +9,8 @@ extern "C" { void UpdateHyperBossesState(); void InitMods(); +void UpdatePatchChildHylianShield(); +void UpdateChildHylianShieldState(); void SwitchAge(); #ifdef __cplusplus diff --git a/soh/src/code/z_player_lib.c b/soh/src/code/z_player_lib.c index f826401c8b2..90b2a2399a2 100644 --- a/soh/src/code/z_player_lib.c +++ b/soh/src/code/z_player_lib.c @@ -633,6 +633,15 @@ void Player_SetModels(Player* this, s32 modelGroup) { this->sheathType = gPlayerModelTypes[modelGroup][PLAYER_MODELGROUPENTRY_SHEATH]; this->sheathDLists = &sPlayerDListGroups[this->sheathType][gSaveContext.linkAge]; + if (CVarGetInteger(CVAR_ENHANCEMENT("ScaleAdultEquipmentAsChild"), 0)) { + if (LINK_IS_CHILD && this->sheathType == PLAYER_MODELTYPE_SHEATH_18 && + this->currentShield == PLAYER_SHIELD_HYLIAN && + (gSaveContext.equips.buttonItems[0] != ITEM_SWORD_MASTER && + gSaveContext.equips.buttonItems[0] != ITEM_SWORD_BGS)) { + this->sheathDLists = &sPlayerDListGroups[this->sheathType][LINK_AGE_ADULT]; + } + } + if (CVarGetInteger(CVAR_ENHANCEMENT("EquipmentAlwaysVisible"), 0)) { if (LINK_IS_CHILD && (this->currentShield == PLAYER_SHIELD_HYLIAN && ((gSaveContext.equips.buttonItems[0] == ITEM_SWORD_MASTER) || @@ -647,7 +656,9 @@ void Player_SetModels(Player* this, s32 modelGroup) { } else if (LINK_IS_ADULT && (this->currentShield == PLAYER_SHIELD_DEKU && gSaveContext.equips.buttonItems[0] != ITEM_SWORD_MASTER) || (gSaveContext.equips.buttonItems[0] == ITEM_SWORD_MASTER && - this->sheathType == PLAYER_MODELTYPE_SHEATH_18 && this->currentShield == PLAYER_SHIELD_DEKU)) { + this->sheathType == PLAYER_MODELTYPE_SHEATH_18 && this->currentShield == PLAYER_SHIELD_DEKU) || + (this->sheathType == PLAYER_MODELTYPE_SHEATH_17 && + gSaveContext.equips.buttonItems[0] == ITEM_SWORD_KOKIRI)) { this->sheathDLists = &sPlayerDListGroups[this->sheathType][1]; } else if (LINK_IS_CHILD && this->sheathType == PLAYER_MODELTYPE_SHEATH_17 && ((gSaveContext.equips.buttonItems[0] == ITEM_SWORD_MASTER) || @@ -1277,6 +1288,10 @@ s32 Player_OverrideLimbDrawGameplayCommon(PlayState* play, s32 limbIndex, Gfx** (sRightHandType == PLAYER_MODELTYPE_RH_BOW_SLINGSHOT && Player_HoldsBow(this))) { Matrix_Scale(0.8, 0.8, 0.8, MTXMODE_APPLY); } + if (CVarGetInteger(CVAR_CHEAT("ChildHoldsHylianShield"), 0) && + this->currentShield == PLAYER_SHIELD_HYLIAN && sRightHandType == PLAYER_MODELTYPE_RH_SHIELD) { + Matrix_Scale(0.8, 0.8, 0.8, MTXMODE_APPLY); + } } if (limbIndex == PLAYER_LIMB_SHEATH) { if ((this->currentShield == PLAYER_SHIELD_MIRROR || From c5b0f973c6d6db8f593bf4506d9e969681aa1d13 Mon Sep 17 00:00:00 2001 From: nclok1405 <155463060+nclok1405@users.noreply.github.com> Date: Thu, 27 Nov 2025 11:49:21 +0900 Subject: [PATCH 04/10] Build fix (including object_link_boy.h and object_link_child.h again) --- soh/soh/Enhancements/mods.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp index 8605567a675..7cfb4c4bfdd 100644 --- a/soh/soh/Enhancements/mods.cpp +++ b/soh/soh/Enhancements/mods.cpp @@ -24,6 +24,9 @@ #include "src/overlays/actors/ovl_En_Elf/z_en_elf.h" #include "soh_assets.h" +#include "objects/object_link_boy/object_link_boy.h" +#include "objects/object_link_child/object_link_child.h" + extern "C" { #include #include "align_asset_macro.h" From 4ddb9664f4cf011a5933e9ef2fb0470856a72aee Mon Sep 17 00:00:00 2001 From: nclok1405 <155463060+nclok1405@users.noreply.github.com> Date: Thu, 27 Nov 2025 12:57:21 +0900 Subject: [PATCH 05/10] Moved graphics patches to its own cpp file --- .../Graphics/ChildHoldsHylianShield.cpp | 64 +++++++++++++++++++ soh/soh/Enhancements/mods.cpp | 3 - soh/soh/Enhancements/mods.h | 2 - 3 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 soh/soh/Enhancements/Graphics/ChildHoldsHylianShield.cpp diff --git a/soh/soh/Enhancements/Graphics/ChildHoldsHylianShield.cpp b/soh/soh/Enhancements/Graphics/ChildHoldsHylianShield.cpp new file mode 100644 index 00000000000..94fc37de981 --- /dev/null +++ b/soh/soh/Enhancements/Graphics/ChildHoldsHylianShield.cpp @@ -0,0 +1,64 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" +#include + +extern "C" { +#include "macros.h" +#include "variables.h" +#include "functions.h" +#include "soh/ResourceManagerHelpers.h" +#include "objects/object_link_boy/object_link_boy.h" +#include "objects/object_link_child/object_link_child.h" + +extern SaveContext gSaveContext; +extern PlayState* gPlayState; +} + +static constexpr int32_t CVAR_SCALEADULTEQUIPMENTASCHILD_DEFAULT = 0; +#define CVAR_SCALEADULTEQUIPMENTASCHILD_NAME CVAR_ENHANCEMENT("ScaleAdultEquipmentAsChild") +#define CVAR_SCALEADULTEQUIPMENTASCHILD_VALUE \ + CVarGetInteger(CVAR_SCALEADULTEQUIPMENTASCHILD_NAME, CVAR_SCALEADULTEQUIPMENTASCHILD_DEFAULT) + +static void UpdatePatchChildHylianShield() { + SPDLOG_DEBUG("ChildHoldsHylianShield: UpdatePatchChildHylianShield called"); + + ResourceMgr_UnpatchGfxByName(gLinkAdultHylianShieldSwordAndSheathNearDL, "childHylianShield1"); + ResourceMgr_UnpatchGfxByName(gLinkAdultHylianShieldSwordAndSheathNearDL, "childHylianShield2"); + ResourceMgr_UnpatchGfxByName(gLinkAdultHylianShieldSwordAndSheathNearDL, "childHylianShield3"); + + if (CVAR_SCALEADULTEQUIPMENTASCHILD_VALUE && LINK_IS_CHILD) { + if (gSaveContext.equips.buttonItems[0] == ITEM_SWORD_KOKIRI || + gSaveContext.equips.buttonItems[0] == ITEM_FISHING_POLE) { + ResourceMgr_PatchGfxByName(gLinkAdultHylianShieldSwordAndSheathNearDL, "childHylianShield1", 82, + gsSPDisplayListOTRFilePath(gLinkChildSwordAndSheathNearDL)); + ResourceMgr_PatchGfxByName(gLinkAdultHylianShieldSwordAndSheathNearDL, "childHylianShield2", 83, + gsSPEndDisplayList()); + } + if (gSaveContext.equips.buttonItems[0] == ITEM_NONE || gSaveContext.equips.buttonItems[0] == ITEM_STICK) { + ResourceMgr_PatchGfxByName(gLinkAdultHylianShieldSwordAndSheathNearDL, "childHylianShield3", 82, + gsSPEndDisplayList()); + } + } +} + +static void RegisterChildHoldsHylianShield() { + COND_HOOK(OnLoadGame, true, [](int32_t fileNum) { + if (gPlayState == nullptr) { + return; + } + Player* player = GET_PLAYER(gPlayState); + Player_SetModels(player, Player_ActionToModelGroup(player, player->heldItemAction)); + }); + + COND_HOOK(OnPlayerUpdate, true, []() { + static uint16_t lastItemOnB = gSaveContext.equips.buttonItems[0]; + if (lastItemOnB != gSaveContext.equips.buttonItems[0]) { + UpdatePatchChildHylianShield(); + lastItemOnB = gSaveContext.equips.buttonItems[0]; + } + }); + + COND_HOOK(OnSceneInit, true, [](int16_t sceneNum) { UpdatePatchChildHylianShield(); }); +} + +static RegisterShipInitFunc initFunc(RegisterChildHoldsHylianShield); diff --git a/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp index 7cfb4c4bfdd..8605567a675 100644 --- a/soh/soh/Enhancements/mods.cpp +++ b/soh/soh/Enhancements/mods.cpp @@ -24,9 +24,6 @@ #include "src/overlays/actors/ovl_En_Elf/z_en_elf.h" #include "soh_assets.h" -#include "objects/object_link_boy/object_link_boy.h" -#include "objects/object_link_child/object_link_child.h" - extern "C" { #include #include "align_asset_macro.h" diff --git a/soh/soh/Enhancements/mods.h b/soh/soh/Enhancements/mods.h index 3d88efeab32..9a0c6794bf3 100644 --- a/soh/soh/Enhancements/mods.h +++ b/soh/soh/Enhancements/mods.h @@ -9,8 +9,6 @@ extern "C" { void UpdateHyperBossesState(); void InitMods(); -void UpdatePatchChildHylianShield(); -void UpdateChildHylianShieldState(); void SwitchAge(); #ifdef __cplusplus From 17f314e5faaf8176f071b3a8b76ac29702bcc394 Mon Sep 17 00:00:00 2001 From: nclok1405 <155463060+nclok1405@users.noreply.github.com> Date: Thu, 27 Nov 2025 13:16:07 +0900 Subject: [PATCH 06/10] Reflect Octorok's projectile --- soh/src/overlays/actors/ovl_En_Okuta/z_en_okuta.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/soh/src/overlays/actors/ovl_En_Okuta/z_en_okuta.c b/soh/src/overlays/actors/ovl_En_Okuta/z_en_okuta.c index 279bc68343a..42743aa6ca5 100644 --- a/soh/src/overlays/actors/ovl_En_Okuta/z_en_okuta.c +++ b/soh/src/overlays/actors/ovl_En_Okuta/z_en_okuta.c @@ -494,7 +494,9 @@ void EnOkuta_ProjectileFly(EnOkuta* this, PlayState* play) { this->collider.base.acFlags & AC_HIT || this->collider.base.ocFlags1 & OC1_HIT || this->actor.floorHeight == BGCHECK_Y_MIN) { if ((player->currentShield == PLAYER_SHIELD_DEKU || - (player->currentShield == PLAYER_SHIELD_HYLIAN && LINK_IS_ADULT)) && + (player->currentShield == PLAYER_SHIELD_HYLIAN && LINK_IS_ADULT) || + (CVarGetInteger(CVAR_CHEAT("ChildHoldsHylianShield"), 0) && + player->currentShield == PLAYER_SHIELD_HYLIAN)) && this->collider.base.atFlags & AT_HIT && this->collider.base.atFlags & AT_TYPE_ENEMY && this->collider.base.atFlags & AT_BOUNCED) { this->collider.base.atFlags &= ~(AT_HIT | AT_BOUNCED | AT_TYPE_ENEMY); From 830e03950daa4f6bae580e277e818d29a18f2a4b Mon Sep 17 00:00:00 2001 From: nclok1405 <155463060+nclok1405@users.noreply.github.com> Date: Fri, 28 Nov 2025 00:03:15 +0900 Subject: [PATCH 07/10] Made projectile reflection to hooks --- .../Graphics/ChildHoldsHylianShield.cpp | 32 +++++++++++++++++-- .../vanilla-behavior/GIVanillaBehavior.h | 18 +++++++++++ .../actors/ovl_En_Nutsball/z_en_nutsball.c | 9 +++--- .../overlays/actors/ovl_En_Okuta/z_en_okuta.c | 8 ++--- 4 files changed, 57 insertions(+), 10 deletions(-) diff --git a/soh/soh/Enhancements/Graphics/ChildHoldsHylianShield.cpp b/soh/soh/Enhancements/Graphics/ChildHoldsHylianShield.cpp index 94fc37de981..788ce5ea559 100644 --- a/soh/soh/Enhancements/Graphics/ChildHoldsHylianShield.cpp +++ b/soh/soh/Enhancements/Graphics/ChildHoldsHylianShield.cpp @@ -19,6 +19,11 @@ static constexpr int32_t CVAR_SCALEADULTEQUIPMENTASCHILD_DEFAULT = 0; #define CVAR_SCALEADULTEQUIPMENTASCHILD_VALUE \ CVarGetInteger(CVAR_SCALEADULTEQUIPMENTASCHILD_NAME, CVAR_SCALEADULTEQUIPMENTASCHILD_DEFAULT) +static constexpr int32_t CVAR_CHILDHOLDSHYLIANSHIELD_DEFAULT = 0; +#define CVAR_CHILDHOLDSHYLIANSHIELD_NAME CVAR_CHEAT("ChildHoldsHylianShield") +#define CVAR_CHILDHOLDSHYLIANSHIELD_VALUE \ + CVarGetInteger(CVAR_CHILDHOLDSHYLIANSHIELD_NAME, CVAR_CHILDHOLDSHYLIANSHIELD_DEFAULT) + static void UpdatePatchChildHylianShield() { SPDLOG_DEBUG("ChildHoldsHylianShield: UpdatePatchChildHylianShield called"); @@ -41,7 +46,7 @@ static void UpdatePatchChildHylianShield() { } } -static void RegisterChildHoldsHylianShield() { +static void RegisterChildHoldsHylianShieldGraphics() { COND_HOOK(OnLoadGame, true, [](int32_t fileNum) { if (gPlayState == nullptr) { return; @@ -61,4 +66,27 @@ static void RegisterChildHoldsHylianShield() { COND_HOOK(OnSceneInit, true, [](int16_t sceneNum) { UpdatePatchChildHylianShield(); }); } -static RegisterShipInitFunc initFunc(RegisterChildHoldsHylianShield); +static void RegisterChildHoldsHylianShieldReflect() { + COND_VB_SHOULD(VB_REFLECT_NUTSBALL, CVAR_CHILDHOLDSHYLIANSHIELD_VALUE, { + Player* player = GET_PLAYER(gPlayState); + + if (LINK_IS_CHILD && (player->currentShield == PLAYER_SHIELD_HYLIAN)) { + SPDLOG_DEBUG("Reflecting Nutsball"); + *should = true; + } + }); + + COND_VB_SHOULD(VB_REFLECT_OCTOROK_PROJECTILE, CVAR_CHILDHOLDSHYLIANSHIELD_VALUE, { + Player* player = GET_PLAYER(gPlayState); + + if (LINK_IS_CHILD && (player->currentShield == PLAYER_SHIELD_HYLIAN)) { + SPDLOG_DEBUG("Reflecting Octorok Projectile"); + *should = true; + } + }); +} + +static RegisterShipInitFunc initFunc_Graphics(RegisterChildHoldsHylianShieldGraphics); + +static RegisterShipInitFunc initFunc_Reflect(RegisterChildHoldsHylianShieldReflect, + { CVAR_CHILDHOLDSHYLIANSHIELD_NAME }); diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index b561985edd1..39e66f2e785 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -1846,6 +1846,24 @@ typedef enum { // - `*EnRd` VB_REDEAD_GIBDO_FREEZE_LINK, + // #### `result` + // ```c + // (player->currentShield == PLAYER_SHIELD_DEKU) || ((player->currentShield == PLAYER_SHIELD_HYLIAN) && + // LINK_IS_ADULT) + // ``` + // #### `args` + // - `*EnNutsball` + VB_REFLECT_NUTSBALL, + + // #### `result` + // ```c + // (player->currentShield == PLAYER_SHIELD_DEKU) || ((player->currentShield == PLAYER_SHIELD_HYLIAN) && + // LINK_IS_ADULT) + // ``` + // #### `args` + // - `*EnOkuta` + VB_REFLECT_OCTOROK_PROJECTILE, + // #### `result` // #### `result` // ```c diff --git a/soh/src/overlays/actors/ovl_En_Nutsball/z_en_nutsball.c b/soh/src/overlays/actors/ovl_En_Nutsball/z_en_nutsball.c index 3922421f6e5..d7472ab187a 100644 --- a/soh/src/overlays/actors/ovl_En_Nutsball/z_en_nutsball.c +++ b/soh/src/overlays/actors/ovl_En_Nutsball/z_en_nutsball.c @@ -12,6 +12,7 @@ #include "objects/object_shopnuts/object_shopnuts.h" #include "objects/object_dns/object_dns.h" #include "objects/object_dnk/object_dnk.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS ACTOR_FLAG_UPDATE_CULLING_DISABLED @@ -122,10 +123,10 @@ void func_80ABBBA8(EnNutsball* this, PlayState* play) { (this->collider.base.acFlags & AC_HIT) || (this->collider.base.ocFlags1 & OC1_HIT)) { // Checking if the player is using a shield that reflects projectiles // And if so, reflects the projectile on impact - if ((player->currentShield == PLAYER_SHIELD_DEKU) || - ((player->currentShield == PLAYER_SHIELD_HYLIAN) && LINK_IS_ADULT) || - (CVarGetInteger(CVAR_CHEAT("ChildHoldsHylianShield"), 0) && - player->currentShield == PLAYER_SHIELD_HYLIAN)) { + if (GameInteractor_Should(VB_REFLECT_NUTSBALL, + (player->currentShield == PLAYER_SHIELD_DEKU) || + ((player->currentShield == PLAYER_SHIELD_HYLIAN) && LINK_IS_ADULT), + this)) { if ((this->collider.base.atFlags & AT_HIT) && (this->collider.base.atFlags & AT_TYPE_ENEMY) && (this->collider.base.atFlags & AT_BOUNCED)) { this->collider.base.atFlags &= ~AT_TYPE_ENEMY & ~AT_BOUNCED & ~AT_HIT; diff --git a/soh/src/overlays/actors/ovl_En_Okuta/z_en_okuta.c b/soh/src/overlays/actors/ovl_En_Okuta/z_en_okuta.c index 42743aa6ca5..8888c8e9d29 100644 --- a/soh/src/overlays/actors/ovl_En_Okuta/z_en_okuta.c +++ b/soh/src/overlays/actors/ovl_En_Okuta/z_en_okuta.c @@ -493,10 +493,10 @@ void EnOkuta_ProjectileFly(EnOkuta* this, PlayState* play) { if ((this->actor.bgCheckFlags & 8) || (this->actor.bgCheckFlags & 1) || (this->collider.base.atFlags & AT_HIT) || this->collider.base.acFlags & AC_HIT || this->collider.base.ocFlags1 & OC1_HIT || this->actor.floorHeight == BGCHECK_Y_MIN) { - if ((player->currentShield == PLAYER_SHIELD_DEKU || - (player->currentShield == PLAYER_SHIELD_HYLIAN && LINK_IS_ADULT) || - (CVarGetInteger(CVAR_CHEAT("ChildHoldsHylianShield"), 0) && - player->currentShield == PLAYER_SHIELD_HYLIAN)) && + if (GameInteractor_Should(VB_REFLECT_OCTOROK_PROJECTILE, + (player->currentShield == PLAYER_SHIELD_DEKU) || + ((player->currentShield == PLAYER_SHIELD_HYLIAN) && LINK_IS_ADULT), + this) && this->collider.base.atFlags & AT_HIT && this->collider.base.atFlags & AT_TYPE_ENEMY && this->collider.base.atFlags & AT_BOUNCED) { this->collider.base.atFlags &= ~(AT_HIT | AT_BOUNCED | AT_TYPE_ENEMY); From 13e3b8c9c94d0a690a0628c45e131ae155502510 Mon Sep 17 00:00:00 2001 From: nclok1405 <155463060+nclok1405@users.noreply.github.com> Date: Fri, 28 Nov 2025 10:08:23 +0900 Subject: [PATCH 08/10] Only run graphics patch hooks if ScaleAdultEquipmentAsChild is enabled --- .../Graphics/ChildHoldsHylianShield.cpp | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/soh/soh/Enhancements/Graphics/ChildHoldsHylianShield.cpp b/soh/soh/Enhancements/Graphics/ChildHoldsHylianShield.cpp index 788ce5ea559..b99621ea423 100644 --- a/soh/soh/Enhancements/Graphics/ChildHoldsHylianShield.cpp +++ b/soh/soh/Enhancements/Graphics/ChildHoldsHylianShield.cpp @@ -24,12 +24,14 @@ static constexpr int32_t CVAR_CHILDHOLDSHYLIANSHIELD_DEFAULT = 0; #define CVAR_CHILDHOLDSHYLIANSHIELD_VALUE \ CVarGetInteger(CVAR_CHILDHOLDSHYLIANSHIELD_NAME, CVAR_CHILDHOLDSHYLIANSHIELD_DEFAULT) -static void UpdatePatchChildHylianShield() { - SPDLOG_DEBUG("ChildHoldsHylianShield: UpdatePatchChildHylianShield called"); - +static void ResetPatchChildHylianShield() { ResourceMgr_UnpatchGfxByName(gLinkAdultHylianShieldSwordAndSheathNearDL, "childHylianShield1"); ResourceMgr_UnpatchGfxByName(gLinkAdultHylianShieldSwordAndSheathNearDL, "childHylianShield2"); ResourceMgr_UnpatchGfxByName(gLinkAdultHylianShieldSwordAndSheathNearDL, "childHylianShield3"); +} + +static void UpdatePatchChildHylianShield() { + ResetPatchChildHylianShield(); if (CVAR_SCALEADULTEQUIPMENTASCHILD_VALUE && LINK_IS_CHILD) { if (gSaveContext.equips.buttonItems[0] == ITEM_SWORD_KOKIRI || @@ -47,7 +49,9 @@ static void UpdatePatchChildHylianShield() { } static void RegisterChildHoldsHylianShieldGraphics() { - COND_HOOK(OnLoadGame, true, [](int32_t fileNum) { + ResetPatchChildHylianShield(); + + COND_HOOK(OnLoadGame, CVAR_SCALEADULTEQUIPMENTASCHILD_VALUE, [](int32_t fileNum) { if (gPlayState == nullptr) { return; } @@ -55,7 +59,7 @@ static void RegisterChildHoldsHylianShieldGraphics() { Player_SetModels(player, Player_ActionToModelGroup(player, player->heldItemAction)); }); - COND_HOOK(OnPlayerUpdate, true, []() { + COND_HOOK(OnPlayerUpdate, CVAR_SCALEADULTEQUIPMENTASCHILD_VALUE, []() { static uint16_t lastItemOnB = gSaveContext.equips.buttonItems[0]; if (lastItemOnB != gSaveContext.equips.buttonItems[0]) { UpdatePatchChildHylianShield(); @@ -63,7 +67,8 @@ static void RegisterChildHoldsHylianShieldGraphics() { } }); - COND_HOOK(OnSceneInit, true, [](int16_t sceneNum) { UpdatePatchChildHylianShield(); }); + COND_HOOK(OnSceneInit, CVAR_SCALEADULTEQUIPMENTASCHILD_VALUE, + [](int16_t sceneNum) { UpdatePatchChildHylianShield(); }); } static void RegisterChildHoldsHylianShieldReflect() { @@ -71,7 +76,6 @@ static void RegisterChildHoldsHylianShieldReflect() { Player* player = GET_PLAYER(gPlayState); if (LINK_IS_CHILD && (player->currentShield == PLAYER_SHIELD_HYLIAN)) { - SPDLOG_DEBUG("Reflecting Nutsball"); *should = true; } }); @@ -80,13 +84,13 @@ static void RegisterChildHoldsHylianShieldReflect() { Player* player = GET_PLAYER(gPlayState); if (LINK_IS_CHILD && (player->currentShield == PLAYER_SHIELD_HYLIAN)) { - SPDLOG_DEBUG("Reflecting Octorok Projectile"); *should = true; } }); } -static RegisterShipInitFunc initFunc_Graphics(RegisterChildHoldsHylianShieldGraphics); +static RegisterShipInitFunc initFunc_Graphics(RegisterChildHoldsHylianShieldGraphics, + { CVAR_SCALEADULTEQUIPMENTASCHILD_NAME }); static RegisterShipInitFunc initFunc_Reflect(RegisterChildHoldsHylianShieldReflect, { CVAR_CHILDHOLDSHYLIANSHIELD_NAME }); From 82b5367621abdbbf6eaec66152af8b49669558f9 Mon Sep 17 00:00:00 2001 From: nclok1405 <155463060+nclok1405@users.noreply.github.com> Date: Fri, 28 Nov 2025 10:21:00 +0900 Subject: [PATCH 09/10] Fix Hylian Shield on Sheath becoming too big when ChildHoldsHylianShield is enabled --- soh/src/code/z_player_lib.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/soh/src/code/z_player_lib.c b/soh/src/code/z_player_lib.c index 90b2a2399a2..e372b9ae03f 100644 --- a/soh/src/code/z_player_lib.c +++ b/soh/src/code/z_player_lib.c @@ -633,7 +633,8 @@ void Player_SetModels(Player* this, s32 modelGroup) { this->sheathType = gPlayerModelTypes[modelGroup][PLAYER_MODELGROUPENTRY_SHEATH]; this->sheathDLists = &sPlayerDListGroups[this->sheathType][gSaveContext.linkAge]; - if (CVarGetInteger(CVAR_ENHANCEMENT("ScaleAdultEquipmentAsChild"), 0)) { + if (CVarGetInteger(CVAR_ENHANCEMENT("ScaleAdultEquipmentAsChild"), 0) && + !CVarGetInteger(CVAR_CHEAT("ChildHoldsHylianShield"), 0)) { if (LINK_IS_CHILD && this->sheathType == PLAYER_MODELTYPE_SHEATH_18 && this->currentShield == PLAYER_SHIELD_HYLIAN && (gSaveContext.equips.buttonItems[0] != ITEM_SWORD_MASTER && From 9345ff7303f058bfdcc285a7662545745bc9b58f Mon Sep 17 00:00:00 2001 From: nclok1405 <155463060+nclok1405@users.noreply.github.com> Date: Wed, 7 Jan 2026 15:56:15 +0900 Subject: [PATCH 10/10] Added new hook VB_BE_CHILD_WITH_HYLIAN_SHIELD and changed Player_IsChildWithHylianShield to use that Removed unneeded gSaveContext --- .../Enhancements/Graphics/ChildHoldsHylianShield.cpp | 12 ++++++++---- .../vanilla-behavior/GIVanillaBehavior.h | 8 ++++++++ soh/src/code/z_player_lib.c | 8 ++------ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/soh/soh/Enhancements/Graphics/ChildHoldsHylianShield.cpp b/soh/soh/Enhancements/Graphics/ChildHoldsHylianShield.cpp index b99621ea423..14e7978eaf4 100644 --- a/soh/soh/Enhancements/Graphics/ChildHoldsHylianShield.cpp +++ b/soh/soh/Enhancements/Graphics/ChildHoldsHylianShield.cpp @@ -10,7 +10,6 @@ extern "C" { #include "objects/object_link_boy/object_link_boy.h" #include "objects/object_link_child/object_link_child.h" -extern SaveContext gSaveContext; extern PlayState* gPlayState; } @@ -71,7 +70,11 @@ static void RegisterChildHoldsHylianShieldGraphics() { [](int16_t sceneNum) { UpdatePatchChildHylianShield(); }); } -static void RegisterChildHoldsHylianShieldReflect() { +static void RegisterChildHoldsHylianShieldGameplay() { + // Skip vanilla check for making child Link have the Hylian Shield on his back, allowing for it to be used in hand + COND_VB_SHOULD(VB_BE_CHILD_WITH_HYLIAN_SHIELD, CVAR_CHILDHOLDSHYLIANSHIELD_VALUE, { *should = false; }); + + // Deku Scrub Projectile Reflection COND_VB_SHOULD(VB_REFLECT_NUTSBALL, CVAR_CHILDHOLDSHYLIANSHIELD_VALUE, { Player* player = GET_PLAYER(gPlayState); @@ -80,6 +83,7 @@ static void RegisterChildHoldsHylianShieldReflect() { } }); + // Octorok Projectile Reflection COND_VB_SHOULD(VB_REFLECT_OCTOROK_PROJECTILE, CVAR_CHILDHOLDSHYLIANSHIELD_VALUE, { Player* player = GET_PLAYER(gPlayState); @@ -92,5 +96,5 @@ static void RegisterChildHoldsHylianShieldReflect() { static RegisterShipInitFunc initFunc_Graphics(RegisterChildHoldsHylianShieldGraphics, { CVAR_SCALEADULTEQUIPMENTASCHILD_NAME }); -static RegisterShipInitFunc initFunc_Reflect(RegisterChildHoldsHylianShieldReflect, - { CVAR_CHILDHOLDSHYLIANSHIELD_NAME }); +static RegisterShipInitFunc initFunc_Gameplay(RegisterChildHoldsHylianShieldGameplay, + { CVAR_CHILDHOLDSHYLIANSHIELD_NAME }); diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index 39e66f2e785..d7b973c627c 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -60,6 +60,14 @@ typedef enum { // - None VB_BE_ABLE_TO_SAVE, + // #### `result` + // ```c + // ((gSaveContext.linkAge != 0) && (this->currentShield == PLAYER_SHIELD_HYLIAN)) + // ``` + // #### `args` + // - `*Player` + VB_BE_CHILD_WITH_HYLIAN_SHIELD, + // #### `result` // ```c // this->currentReward == 3 diff --git a/soh/src/code/z_player_lib.c b/soh/src/code/z_player_lib.c index e372b9ae03f..ddf759c2695 100644 --- a/soh/src/code/z_player_lib.c +++ b/soh/src/code/z_player_lib.c @@ -526,12 +526,8 @@ s32 Player_CheckHostileLockOn(Player* this) { } s32 Player_IsChildWithHylianShield(Player* this) { - if (CVarGetInteger(CVAR_CHEAT("ChildHoldsHylianShield"), 0)) { - return false; // Skip vanilla check for making child Link have the Hylian Shield on his back, allowing for it to - // be used in hand - } else { - return gSaveContext.linkAge != 0 && (this->currentShield == PLAYER_SHIELD_HYLIAN); - } + return GameInteractor_Should(VB_BE_CHILD_WITH_HYLIAN_SHIELD, + ((gSaveContext.linkAge != 0) && (this->currentShield == PLAYER_SHIELD_HYLIAN)), this); } s32 Player_ActionToModelGroup(Player* this, s32 actionParam) {