diff --git a/soh/soh/Enhancements/Graphics/ChildHoldsHylianShield.cpp b/soh/soh/Enhancements/Graphics/ChildHoldsHylianShield.cpp new file mode 100644 index 00000000000..14e7978eaf4 --- /dev/null +++ b/soh/soh/Enhancements/Graphics/ChildHoldsHylianShield.cpp @@ -0,0 +1,100 @@ +#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 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 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 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 || + 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 RegisterChildHoldsHylianShieldGraphics() { + ResetPatchChildHylianShield(); + + COND_HOOK(OnLoadGame, CVAR_SCALEADULTEQUIPMENTASCHILD_VALUE, [](int32_t fileNum) { + if (gPlayState == nullptr) { + return; + } + Player* player = GET_PLAYER(gPlayState); + Player_SetModels(player, Player_ActionToModelGroup(player, player->heldItemAction)); + }); + + COND_HOOK(OnPlayerUpdate, CVAR_SCALEADULTEQUIPMENTASCHILD_VALUE, []() { + static uint16_t lastItemOnB = gSaveContext.equips.buttonItems[0]; + if (lastItemOnB != gSaveContext.equips.buttonItems[0]) { + UpdatePatchChildHylianShield(); + lastItemOnB = gSaveContext.equips.buttonItems[0]; + } + }); + + COND_HOOK(OnSceneInit, CVAR_SCALEADULTEQUIPMENTASCHILD_VALUE, + [](int16_t sceneNum) { UpdatePatchChildHylianShield(); }); +} + +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); + + if (LINK_IS_CHILD && (player->currentShield == PLAYER_SHIELD_HYLIAN)) { + *should = true; + } + }); + + // Octorok Projectile Reflection + COND_VB_SHOULD(VB_REFLECT_OCTOROK_PROJECTILE, CVAR_CHILDHOLDSHYLIANSHIELD_VALUE, { + Player* player = GET_PLAYER(gPlayState); + + if (LINK_IS_CHILD && (player->currentShield == PLAYER_SHIELD_HYLIAN)) { + *should = true; + } + }); +} + +static RegisterShipInitFunc initFunc_Graphics(RegisterChildHoldsHylianShieldGraphics, + { CVAR_SCALEADULTEQUIPMENTASCHILD_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 b561985edd1..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 @@ -1846,6 +1854,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/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index 30cf7d5494f..7f4a8ba811f 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -1698,6 +1698,10 @@ 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_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) .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..ddf759c2695 100644 --- a/soh/src/code/z_player_lib.c +++ b/soh/src/code/z_player_lib.c @@ -526,7 +526,8 @@ s32 Player_CheckHostileLockOn(Player* this) { } s32 Player_IsChildWithHylianShield(Player* this) { - 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) { @@ -547,8 +548,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_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)) && + (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 +603,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_CHEAT("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 && @@ -623,6 +629,16 @@ 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) && + !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 && + 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) || @@ -637,7 +653,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) || @@ -1267,6 +1285,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 || 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..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,8 +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)) { + 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 279bc68343a..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,8 +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)) && + 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);