From 180edfe718436a7fdad01a77bb1395a953131f20 Mon Sep 17 00:00:00 2001 From: Jesper Arvidsson Date: Sun, 21 Dec 2025 23:59:02 +0100 Subject: [PATCH 1/7] Firstshot --- soh/soh/Enhancements/ChildLink2hMS.cpp | 37 +++++++++++++++++++ soh/soh/Enhancements/ChildLink2hMS.h | 23 ++++++++++++ soh/soh/Enhancements/randomizer/logic.cpp | 2 +- .../randomizer/option_descriptions.cpp | 3 ++ .../Enhancements/randomizer/randomizerTypes.h | 1 + soh/soh/Enhancements/randomizer/settings.cpp | 2 + soh/soh/SohGui/SohMenuEnhancements.cpp | 11 ++++++ soh/src/code/z_player_lib.c | 5 +++ 8 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 soh/soh/Enhancements/ChildLink2hMS.cpp create mode 100644 soh/soh/Enhancements/ChildLink2hMS.h diff --git a/soh/soh/Enhancements/ChildLink2hMS.cpp b/soh/soh/Enhancements/ChildLink2hMS.cpp new file mode 100644 index 00000000000..dcfbb33b8d5 --- /dev/null +++ b/soh/soh/Enhancements/ChildLink2hMS.cpp @@ -0,0 +1,37 @@ +#include "soh/Enhancements/ChildLink2hMS.h" +#include "soh/OTRGlobals.h" +#include "soh/Enhancements/randomizer/randomizerTypes.h" + +extern "C" u8 Randomizer_GetSettingValue(RandomizerSettingKey randoSettingKey); + +extern "C" { +#include "global.h" +#include "z64player.h" +} + +static bool ChildLink2hMS_IsEnabled() { + // Allow either the generated randomizer option or the backing cvars to enable the feature. + // Enhancement cvar lets non-rando saves opt in; randomizer setting forces it on when enabled. + return (IS_RANDO && Randomizer_GetSettingValue(RSK_CHILD_LINK_2H_MS) != RO_GENERIC_OFF) || + CVarGetInteger(CVAR_RANDOMIZER_SETTING("ChildMasterSword"), 0) || + CVarGetInteger(CVAR_ENHANCEMENT("ChildMasterSword"), 0); +} + +// Centralize the conditions for allowing child Link to wield the Master Sword. +extern "C" s32 ChildLink2hMS_CanChildUseMasterSword(void) { + return ChildLink2hMS_IsEnabled() && (gSaveContext.linkAge == LINK_AGE_CHILD) && + CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_MASTER); +} + +extern "C" s32 ChildLink2hMS_ShouldBypassAgeReq(s32 equipType, s32 equipValue) { + return (equipType == EQUIP_TYPE_SWORD) && (equipValue == EQUIP_VALUE_SWORD_MASTER) && + ChildLink2hMS_CanChildUseMasterSword(); +} + +extern "C" s32 ChildLink2hMS_IsTwoHanded(Player* player) { + if (player == NULL) { + return 0; + } + + return ChildLink2hMS_CanChildUseMasterSword() && (player->heldItemAction == PLAYER_IA_SWORD_MASTER); +} diff --git a/soh/soh/Enhancements/ChildLink2hMS.h b/soh/soh/Enhancements/ChildLink2hMS.h new file mode 100644 index 00000000000..783b538e567 --- /dev/null +++ b/soh/soh/Enhancements/ChildLink2hMS.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include "global.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct Player; + +// Returns true when child Link is allowed to use the Master Sword (randomizer shuffle). +s32 ChildLink2hMS_CanChildUseMasterSword(void); + +// Returns true when the age requirement should be bypassed for Master Sword while child. +s32 ChildLink2hMS_ShouldBypassAgeReq(s32 equipType, s32 equipValue); + +// Returns true when the currently held Master Sword should be treated as a two-handed weapon for child Link. +s32 ChildLink2hMS_IsTwoHanded(struct Player* player); + +#ifdef __cplusplus +} +#endif diff --git a/soh/soh/Enhancements/randomizer/logic.cpp b/soh/soh/Enhancements/randomizer/logic.cpp index 1d7b52b3ec9..4a883baa6ad 100644 --- a/soh/soh/Enhancements/randomizer/logic.cpp +++ b/soh/soh/Enhancements/randomizer/logic.cpp @@ -295,7 +295,7 @@ bool Logic::CanUse(RandomizerGet itemName) { case RG_MIRROR_SHIELD: return IsAdult; // || MirrorShieldAsChild; case RG_MASTER_SWORD: - return IsAdult; // || MasterSwordAsChild; + return IsAdult || (ctx->GetOption(RSK_CHILD_LINK_2H_MS) && IsChild); case RG_BIGGORON_SWORD: return IsAdult; // || BiggoronSwordAsChild; case RG_SILVER_GAUNTLETS: diff --git a/soh/soh/Enhancements/randomizer/option_descriptions.cpp b/soh/soh/Enhancements/randomizer/option_descriptions.cpp index 1f6b71a881a..59f59ca98dd 100644 --- a/soh/soh/Enhancements/randomizer/option_descriptions.cpp +++ b/soh/soh/Enhancements/randomizer/option_descriptions.cpp @@ -757,6 +757,9 @@ void Settings::CreateOptionDescriptions() { mOptionDescriptions[RSK_BLUE_FIRE_ARROWS] = "Ice Arrows act like Blue Fire, making them able to melt red ice. " "Item placement logic will respect this option, so it might be required to use this to progress."; + mOptionDescriptions[RSK_CHILD_LINK_2H_MS] = + "Allows child Link to wield the Master Sword as a two-handed weapon. " + "Item placement logic will consider this when placing items."; mOptionDescriptions[RSK_SKELETON_KEY] = "Adds a new item called the \"Skeleton Key\", it unlocks every dungeon door locked by a small key."; mOptionDescriptions[RSK_SUNLIGHT_ARROWS] = diff --git a/soh/soh/Enhancements/randomizer/randomizerTypes.h b/soh/soh/Enhancements/randomizer/randomizerTypes.h index cea8437d79c..dbca1e3a64b 100644 --- a/soh/soh/Enhancements/randomizer/randomizerTypes.h +++ b/soh/soh/Enhancements/randomizer/randomizerTypes.h @@ -6183,6 +6183,7 @@ typedef enum { RSK_MERCHANT_PRICES_TYCOON_WALLET_WEIGHT, RSK_MERCHANT_PRICES_AFFORDABLE, RSK_BLUE_FIRE_ARROWS, + RSK_CHILD_LINK_2H_MS, RSK_SUNLIGHT_ARROWS, RSK_SLINGBOW_BREAK_BEEHIVES, RSK_ENABLE_BOMBCHU_DROPS, diff --git a/soh/soh/Enhancements/randomizer/settings.cpp b/soh/soh/Enhancements/randomizer/settings.cpp index 066d2fe49d4..5a1847dfa02 100644 --- a/soh/soh/Enhancements/randomizer/settings.cpp +++ b/soh/soh/Enhancements/randomizer/settings.cpp @@ -308,6 +308,7 @@ void Settings::CreateOptions() { OPT_BOOL(RSK_MASK_SHOP_HINT, "Mask Shop Hint", CVAR_RANDOMIZER_SETTING("MaskShopHint"), mOptionDescriptions[RSK_MASK_SHOP_HINT]); // TODO: Compasses show rewards/woth, maps show dungeon mode OPT_BOOL(RSK_BLUE_FIRE_ARROWS, "Blue Fire Arrows", CVAR_RANDOMIZER_SETTING("BlueFireArrows"), mOptionDescriptions[RSK_BLUE_FIRE_ARROWS]); + OPT_BOOL(RSK_CHILD_LINK_2H_MS, "Child Master Sword", CVAR_RANDOMIZER_SETTING("ChildMasterSword"), mOptionDescriptions[RSK_CHILD_LINK_2H_MS]); OPT_BOOL(RSK_SUNLIGHT_ARROWS, "Sunlight Arrows", CVAR_RANDOMIZER_SETTING("SunlightArrows"), mOptionDescriptions[RSK_SUNLIGHT_ARROWS]); OPT_U8(RSK_INFINITE_UPGRADES, "Infinite Upgrades", {"Off", "Progressive", "Condensed Progressive"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("InfiniteUpgrades"), mOptionDescriptions[RSK_INFINITE_UPGRADES]); OPT_BOOL(RSK_SKELETON_KEY, "Skeleton Key", CVAR_RANDOMIZER_SETTING("SkeletonKey"), mOptionDescriptions[RSK_SKELETON_KEY]); @@ -1423,6 +1424,7 @@ void Settings::CreateOptions() { &mOptions[RSK_BOMBCHU_BAG], &mOptions[RSK_ENABLE_BOMBCHU_DROPS], &mOptions[RSK_BLUE_FIRE_ARROWS], + &mOptions[RSK_CHILD_LINK_2H_MS], &mOptions[RSK_SUNLIGHT_ARROWS], &mOptions[RSK_INFINITE_UPGRADES], &mOptions[RSK_SKELETON_KEY], diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index c32b1175587..a458fdd9ed5 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -763,6 +763,17 @@ void SohMenu::AddMenuEnhancements() { "Child Toggle: This will allow for completely unequipping any sword as Child link.\n\n" "Both Ages: Any sword can be unequipped as either age. This may lead to swordless " "glitches as adult.")); + AddWidget(path, "Child Master Sword (Two-Handed)", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ChildMasterSword")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = + OTRGlobals::Instance->gRandoContext->GetOption(RSK_CHILD_LINK_2H_MS).Is(RO_GENERIC_ON); + info.options->disabledTooltip = "This setting is forcefully enabled because a randomized savefile with " + "\"Child Master Sword\" is currently loaded."; + }) + .Options(CheckboxOptions().Tooltip( + "Allows child Link to equip and swing the Master Sword like a two-handed weapon.\n" + "This mirrors the randomizer option and is locked on when that option is active.")); AddWidget(path, "Ask to Equip New Items", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("AskToEquip")) .Options(CheckboxOptions().Tooltip("Adds a prompt to equip newly-obtained Swords, Shields, and Tunics.")); diff --git a/soh/src/code/z_player_lib.c b/soh/src/code/z_player_lib.c index 49d9380bf66..12bc373ef72 100644 --- a/soh/src/code/z_player_lib.c +++ b/soh/src/code/z_player_lib.c @@ -532,6 +532,11 @@ s32 Player_IsChildWithHylianShield(Player* this) { s32 Player_ActionToModelGroup(Player* this, s32 actionParam) { s32 modelGroup = sActionModelGroups[actionParam]; + if ((actionParam == PLAYER_IA_SWORD_MASTER) && ChildLink2hMS_CanChildUseMasterSword()) { + // Use two-handed sword animations when child can wield the Master Sword + modelGroup = PLAYER_MODELGROUP_BGS; + } + if ((modelGroup == PLAYER_MODELGROUP_SWORD_AND_SHIELD) && Player_IsChildWithHylianShield(this)) { // child, using kokiri sword with hylian shield equipped return PLAYER_MODELGROUP_CHILD_HYLIAN_SHIELD; From 797026d4286e1b7f2db5f021adcc4287fb5d1985 Mon Sep 17 00:00:00 2001 From: Jesper Arvidsson Date: Mon, 22 Dec 2025 00:11:24 +0100 Subject: [PATCH 2/7] missingheader --- soh/src/code/z_player_lib.c | 1 + 1 file changed, 1 insertion(+) diff --git a/soh/src/code/z_player_lib.c b/soh/src/code/z_player_lib.c index 12bc373ef72..ac53f89eb0f 100644 --- a/soh/src/code/z_player_lib.c +++ b/soh/src/code/z_player_lib.c @@ -8,6 +8,7 @@ #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/ChildLink2hMS.h" #include "soh/Enhancements/randomizer/draw.h" #include "soh/ResourceManagerHelpers.h" From 0257203237b94fcb429d65759811c268f23d5948 Mon Sep 17 00:00:00 2001 From: Jesper Arvidsson Date: Mon, 22 Dec 2025 01:43:48 +0100 Subject: [PATCH 3/7] PoC --- soh/soh/Enhancements/ChildLink2hMS.cpp | 9 ++++++++ soh/soh/Enhancements/ChildLink2hMS.h | 6 +++++ soh/src/code/z_player_lib.c | 20 +++++++++++++---- .../actors/ovl_player_actor/z_player.c | 1 + .../ovl_kaleido_scope/z_kaleido_equipment.c | 22 ++++++++++++++++--- 5 files changed, 51 insertions(+), 7 deletions(-) diff --git a/soh/soh/Enhancements/ChildLink2hMS.cpp b/soh/soh/Enhancements/ChildLink2hMS.cpp index dcfbb33b8d5..9d0eef54bf2 100644 --- a/soh/soh/Enhancements/ChildLink2hMS.cpp +++ b/soh/soh/Enhancements/ChildLink2hMS.cpp @@ -35,3 +35,12 @@ extern "C" s32 ChildLink2hMS_IsTwoHanded(Player* player) { return ChildLink2hMS_CanChildUseMasterSword() && (player->heldItemAction == PLAYER_IA_SWORD_MASTER); } + +extern "C" s32 ChildLink2hMS_ShouldAllowEquip(s32 equipType, s32 equipValue) { + return ChildLink2hMS_ShouldBypassAgeReq(equipType, equipValue); +} + +extern "C" s32 ChildLink2hMS_OverrideMeleeWeapon(s32 actionParam, s32 baseMeleeWeapon) { + // Keep Master Sword hitbox/damage while still using two-handed animations/models. + return baseMeleeWeapon; +} diff --git a/soh/soh/Enhancements/ChildLink2hMS.h b/soh/soh/Enhancements/ChildLink2hMS.h index 783b538e567..3c00a3af248 100644 --- a/soh/soh/Enhancements/ChildLink2hMS.h +++ b/soh/soh/Enhancements/ChildLink2hMS.h @@ -18,6 +18,12 @@ s32 ChildLink2hMS_ShouldBypassAgeReq(s32 equipType, s32 equipValue); // Returns true when the currently held Master Sword should be treated as a two-handed weapon for child Link. s32 ChildLink2hMS_IsTwoHanded(struct Player* player); +// Returns true when age restrictions should be treated as met for the given equip slot (used by pause menu). +s32 ChildLink2hMS_ShouldAllowEquip(s32 equipType, s32 equipValue); + +// Optional override to map Master Sword to two-handed melee behavior for child Link. +s32 ChildLink2hMS_OverrideMeleeWeapon(s32 actionParam, s32 baseMeleeWeapon); + #ifdef __cplusplus } #endif diff --git a/soh/src/code/z_player_lib.c b/soh/src/code/z_player_lib.c index ac53f89eb0f..8a9330a4001 100644 --- a/soh/src/code/z_player_lib.c +++ b/soh/src/code/z_player_lib.c @@ -868,9 +868,9 @@ s32 Player_ActionToMeleeWeapon(s32 actionParam) { s32 sword = actionParam - PLAYER_IA_FISHING_POLE; if ((sword > 0) && (sword < 6)) { - return sword; + return ChildLink2hMS_OverrideMeleeWeapon(actionParam, sword); } else { - return 0; + return ChildLink2hMS_OverrideMeleeWeapon(actionParam, 0); } } @@ -879,11 +879,15 @@ s32 Player_GetMeleeWeaponHeld(Player* this) { } s32 Player_HoldsTwoHandedWeapon(Player* this) { + if (ChildLink2hMS_IsTwoHanded(this)) { + return 1; + } + if ((this->heldItemAction >= PLAYER_IA_SWORD_BIGGORON) && (this->heldItemAction <= PLAYER_IA_HAMMER)) { return 1; - } else { - return 0; } + + return 0; } s32 Player_HoldsBrokenKnife(Player* this) { @@ -1379,6 +1383,14 @@ s32 Player_OverrideLimbDrawGameplayDefault(PlayState* play, s32 limbIndex, Gfx** if (limbIndex == PLAYER_LIMB_L_HAND) { Gfx** dLists = this->leftHandDLists; + if (ChildLink2hMS_IsTwoHanded(this) && (sLeftHandType == PLAYER_MODELTYPE_LH_BGS)) { + // Force adult Master Sword grip DL for child two-handed Master Sword + Gfx* overrideDl = (sDListsLodOffset >= 2) ? gLinkAdultLeftHandHoldingMasterSwordFarDL + : gLinkAdultLeftHandHoldingMasterSwordNearDL; + *dList = ResourceMgr_LoadGfxByName(overrideDl); + return false; + } + if ((sLeftHandType == PLAYER_MODELTYPE_LH_BGS) && (gSaveContext.swordHealth <= 0.0f)) { dLists += 4; } else if ((sLeftHandType == PLAYER_MODELTYPE_LH_BOOMERANG) && diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index 2de7113d218..652af901a89 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -30,6 +30,7 @@ #include "soh/Enhancements/enhancementTypes.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/Enhancements/randomizer/randomizer_grotto.h" +#include "soh/Enhancements/ChildLink2hMS.h" #include "soh/frame_interpolation.h" #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" diff --git a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_equipment.c b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_equipment.c index 59dfffb1fa5..1806f21b6c0 100644 --- a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_equipment.c +++ b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_equipment.c @@ -3,6 +3,7 @@ #include "textures/parameter_static/parameter_static.h" #include "soh/Enhancements/cosmetics/cosmeticsTypes.h" #include "soh/Enhancements/enhancementTypes.h" +#include "soh/Enhancements/ChildLink2hMS.h" static u8 sChildUpgrades[] = { UPG_BULLET_BAG, UPG_BOMB_BAG, UPG_STRENGTH, UPG_SCALE }; static u8 sAdultUpgrades[] = { UPG_QUIVER, UPG_BOMB_BAG, UPG_STRENGTH, UPG_SCALE }; @@ -493,7 +494,16 @@ void KaleidoScope_DrawEquipment(PlayState* play) { osSyncPrintf("kscope->select_name[Display_Equipment] = %d\n", pauseCtx->cursorItem[PAUSE_EQUIP]); - if (!(CHECK_AGE_REQ_EQUIP(pauseCtx->cursorY[PAUSE_EQUIP], pauseCtx->cursorX[PAUSE_EQUIP]))) { + s32 bypassChildMasterSword = + ChildLink2hMS_ShouldAllowEquip(pauseCtx->cursorY[PAUSE_EQUIP], pauseCtx->cursorX[PAUSE_EQUIP]); + + if (bypassChildMasterSword) { + // Treat the Master Sword as age-allowed so the icon is not greyed out when the setting is active. + pauseCtx->cursorColorSet = 0; + } + + if (!(CHECK_AGE_REQ_EQUIP(pauseCtx->cursorY[PAUSE_EQUIP], pauseCtx->cursorX[PAUSE_EQUIP]) || + bypassChildMasterSword)) { pauseCtx->nameColorSet = 1; } @@ -543,7 +553,8 @@ void KaleidoScope_DrawEquipment(PlayState* play) { (pauseCtx->unk_1E4 == 0) && CHECK_BTN_ANY(input->press.button, buttonsToCheck) && (pauseCtx->cursorX[PAUSE_EQUIP] != 0)) { - if (CHECK_AGE_REQ_EQUIP(pauseCtx->cursorY[PAUSE_EQUIP], pauseCtx->cursorX[PAUSE_EQUIP])) { + if (CHECK_AGE_REQ_EQUIP(pauseCtx->cursorY[PAUSE_EQUIP], pauseCtx->cursorX[PAUSE_EQUIP]) || + bypassChildMasterSword) { if (CHECK_BTN_ALL(input->press.button, BTN_A)) { // #Region SoH [Enhancements] @@ -731,7 +742,8 @@ void KaleidoScope_DrawEquipment(PlayState* play) { for (k = 0, temp = rowStart + 1, bit = rowStart, j = point; k < 3; k++, bit++, j += 4, temp++) { if ((gBitFlags[bit] & gSaveContext.inventory.equipment) && (pauseCtx->cursorSpecialPos == 0)) { - if (CHECK_AGE_REQ_EQUIP(i, k + 1)) { + if (CHECK_AGE_REQ_EQUIP(i, k + 1) || + ChildLink2hMS_ShouldAllowEquip(i, k + 1)) { if (temp == cursorSlot) { pauseCtx->equipVtx[j].v.ob[0] = pauseCtx->equipVtx[j + 2].v.ob[0] = pauseCtx->equipVtx[j].v.ob[0] - 2; @@ -822,6 +834,10 @@ void KaleidoScope_DrawEquipment(PlayState* play) { int itemId = ITEM_SWORD_KOKIRI + temp; bool age_restricted = !CHECK_AGE_REQ_ITEM(itemId); + if ((itemId == ITEM_SWORD_MASTER) && + ChildLink2hMS_ShouldAllowEquip(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_MASTER)) { + age_restricted = false; + } if (age_restricted) { gDPSetGrayscaleColor(POLY_OPA_DISP++, 109, 109, 109, 255); gSPGrayscale(POLY_OPA_DISP++, true); From a899fc1a7df021da3eb50f05f1e9de4b007f394a Mon Sep 17 00:00:00 2001 From: Jesper Arvidsson Date: Thu, 25 Dec 2025 18:35:25 +0100 Subject: [PATCH 4/7] Clang --- soh/soh/Enhancements/randomizer/option_descriptions.cpp | 5 ++--- soh/src/code/z_player_lib.c | 2 +- .../overlays/misc/ovl_kaleido_scope/z_kaleido_equipment.c | 3 +-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/option_descriptions.cpp b/soh/soh/Enhancements/randomizer/option_descriptions.cpp index 59f59ca98dd..78b616ff53a 100644 --- a/soh/soh/Enhancements/randomizer/option_descriptions.cpp +++ b/soh/soh/Enhancements/randomizer/option_descriptions.cpp @@ -757,9 +757,8 @@ void Settings::CreateOptionDescriptions() { mOptionDescriptions[RSK_BLUE_FIRE_ARROWS] = "Ice Arrows act like Blue Fire, making them able to melt red ice. " "Item placement logic will respect this option, so it might be required to use this to progress."; - mOptionDescriptions[RSK_CHILD_LINK_2H_MS] = - "Allows child Link to wield the Master Sword as a two-handed weapon. " - "Item placement logic will consider this when placing items."; + mOptionDescriptions[RSK_CHILD_LINK_2H_MS] = "Allows child Link to wield the Master Sword as a two-handed weapon. " + "Item placement logic will consider this when placing items."; mOptionDescriptions[RSK_SKELETON_KEY] = "Adds a new item called the \"Skeleton Key\", it unlocks every dungeon door locked by a small key."; mOptionDescriptions[RSK_SUNLIGHT_ARROWS] = diff --git a/soh/src/code/z_player_lib.c b/soh/src/code/z_player_lib.c index 8a9330a4001..75972f2bf69 100644 --- a/soh/src/code/z_player_lib.c +++ b/soh/src/code/z_player_lib.c @@ -1386,7 +1386,7 @@ s32 Player_OverrideLimbDrawGameplayDefault(PlayState* play, s32 limbIndex, Gfx** if (ChildLink2hMS_IsTwoHanded(this) && (sLeftHandType == PLAYER_MODELTYPE_LH_BGS)) { // Force adult Master Sword grip DL for child two-handed Master Sword Gfx* overrideDl = (sDListsLodOffset >= 2) ? gLinkAdultLeftHandHoldingMasterSwordFarDL - : gLinkAdultLeftHandHoldingMasterSwordNearDL; + : gLinkAdultLeftHandHoldingMasterSwordNearDL; *dList = ResourceMgr_LoadGfxByName(overrideDl); return false; } diff --git a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_equipment.c b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_equipment.c index 1806f21b6c0..658cd2fe23d 100644 --- a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_equipment.c +++ b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_equipment.c @@ -742,8 +742,7 @@ void KaleidoScope_DrawEquipment(PlayState* play) { for (k = 0, temp = rowStart + 1, bit = rowStart, j = point; k < 3; k++, bit++, j += 4, temp++) { if ((gBitFlags[bit] & gSaveContext.inventory.equipment) && (pauseCtx->cursorSpecialPos == 0)) { - if (CHECK_AGE_REQ_EQUIP(i, k + 1) || - ChildLink2hMS_ShouldAllowEquip(i, k + 1)) { + if (CHECK_AGE_REQ_EQUIP(i, k + 1) || ChildLink2hMS_ShouldAllowEquip(i, k + 1)) { if (temp == cursorSlot) { pauseCtx->equipVtx[j].v.ob[0] = pauseCtx->equipVtx[j + 2].v.ob[0] = pauseCtx->equipVtx[j].v.ob[0] - 2; From 7f317c3d4865e30a1dd31aa7e6b8bc993f9f83c8 Mon Sep 17 00:00:00 2001 From: Jesper Arvidsson Date: Fri, 26 Dec 2025 01:01:52 +0100 Subject: [PATCH 5/7] Fick ToT Stick Issue --- .../overlays/actors/ovl_Bg_Toki_Swd/z_bg_toki_swd.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/soh/src/overlays/actors/ovl_Bg_Toki_Swd/z_bg_toki_swd.c b/soh/src/overlays/actors/ovl_Bg_Toki_Swd/z_bg_toki_swd.c index a11180a7b5d..654981dc296 100644 --- a/soh/src/overlays/actors/ovl_Bg_Toki_Swd/z_bg_toki_swd.c +++ b/soh/src/overlays/actors/ovl_Bg_Toki_Swd/z_bg_toki_swd.c @@ -8,6 +8,7 @@ #include "objects/object_toki_objects/object_toki_objects.h" #include "soh/OTRGlobals.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/ChildLink2hMS.h" #define FLAGS ACTOR_FLAG_UPDATE_CULLING_DISABLED @@ -87,10 +88,11 @@ void BgTokiSwd_Init(Actor* thisx, PlayState* play) { } this->actor.draw = NULL; } else if (IS_RANDO) { - // don't give child link a kokiri sword if we don't have one - uint32_t kokiriSwordBitMask = 1 << 0; - if (!(gSaveContext.inventory.equipment & kokiriSwordBitMask)) { - Player* player = GET_PLAYER(gPlayState); + // Child Link in ToT: only clear sword if NO valid sword is allowed + if (!CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_KOKIRI) && + !(ChildLink2hMS_CanChildUseMasterSword() && CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_MASTER))) { + + Player* player = GET_PLAYER(play); player->currentSwordItemId = ITEM_NONE; gSaveContext.equips.buttonItems[0] = ITEM_NONE; Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_NONE); From 5b08967584550f471478c0f241a20795d313167b Mon Sep 17 00:00:00 2001 From: Jesper Arvidsson Date: Sat, 27 Dec 2025 14:17:13 +0100 Subject: [PATCH 6/7] Add Mastersword to Rando age change equipment logic --- soh/soh/Enhancements/ChildLink2hMS.cpp | 61 +++++++++++++++++++++++++- soh/soh/Enhancements/ChildLink2hMS.h | 7 +++ soh/src/code/z_parameter.c | 19 +++++--- 3 files changed, 78 insertions(+), 9 deletions(-) diff --git a/soh/soh/Enhancements/ChildLink2hMS.cpp b/soh/soh/Enhancements/ChildLink2hMS.cpp index 9d0eef54bf2..4d3d3018986 100644 --- a/soh/soh/Enhancements/ChildLink2hMS.cpp +++ b/soh/soh/Enhancements/ChildLink2hMS.cpp @@ -9,7 +9,7 @@ extern "C" { #include "z64player.h" } -static bool ChildLink2hMS_IsEnabled() { +static bool ChildLink2hMS_IsEnabledInternal() { // Allow either the generated randomizer option or the backing cvars to enable the feature. // Enhancement cvar lets non-rando saves opt in; randomizer setting forces it on when enabled. return (IS_RANDO && Randomizer_GetSettingValue(RSK_CHILD_LINK_2H_MS) != RO_GENERIC_OFF) || @@ -17,9 +17,37 @@ static bool ChildLink2hMS_IsEnabled() { CVarGetInteger(CVAR_ENHANCEMENT("ChildMasterSword"), 0); } +extern "C" s32 ChildLink2hMS_IsEnabled(void) { + return ChildLink2hMS_IsEnabledInternal(); +} + +static s16 ChildLink2hMS_SelectChildSwordInternal(s16 preferredSword) { + s32 hasKokiriSword = CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_KOKIRI); + s32 hasMasterSword = CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_MASTER); + + // Keep the last equipped sword if still owned; otherwise fall back to the other owned sword, else none. + if ((preferredSword == ITEM_SWORD_MASTER) && hasMasterSword) { + return ITEM_SWORD_MASTER; + } + + if ((preferredSword == ITEM_SWORD_KOKIRI) && hasKokiriSword) { + return ITEM_SWORD_KOKIRI; + } + + if (hasKokiriSword) { + return ITEM_SWORD_KOKIRI; + } + + if (hasMasterSword) { + return ITEM_SWORD_MASTER; + } + + return ITEM_NONE; +} + // Centralize the conditions for allowing child Link to wield the Master Sword. extern "C" s32 ChildLink2hMS_CanChildUseMasterSword(void) { - return ChildLink2hMS_IsEnabled() && (gSaveContext.linkAge == LINK_AGE_CHILD) && + return ChildLink2hMS_IsEnabledInternal() && (gSaveContext.linkAge == LINK_AGE_CHILD) && CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_MASTER); } @@ -44,3 +72,32 @@ extern "C" s32 ChildLink2hMS_OverrideMeleeWeapon(s32 actionParam, s32 baseMeleeW // Keep Master Sword hitbox/damage while still using two-handed animations/models. return baseMeleeWeapon; } + +extern "C" s16 ChildLink2hMS_SelectChildSword(s16 preferredSword) { + if (!ChildLink2hMS_IsEnabledInternal()) { + return preferredSword; + } + + return ChildLink2hMS_SelectChildSwordInternal(preferredSword); +} + +extern "C" void ChildLink2hMS_ApplyChildSwordEquip(s16 preferredSword) { + if (!ChildLink2hMS_IsEnabledInternal()) { + return; + } + + s16 sword = ChildLink2hMS_SelectChildSwordInternal(preferredSword); + + gSaveContext.equips.buttonItems[0] = sword; + gSaveContext.equips.equipment &= (u16) ~(0xF << (EQUIP_TYPE_SWORD * 4)); + + if (sword == ITEM_SWORD_MASTER) { + gSaveContext.equips.equipment |= (EQUIP_VALUE_SWORD_MASTER << (EQUIP_TYPE_SWORD * 4)); + Flags_UnsetInfTable(INFTABLE_SWORDLESS); + } else if (sword == ITEM_SWORD_KOKIRI) { + gSaveContext.equips.equipment |= (EQUIP_VALUE_SWORD_KOKIRI << (EQUIP_TYPE_SWORD * 4)); + Flags_UnsetInfTable(INFTABLE_SWORDLESS); + } else { + Flags_SetInfTable(INFTABLE_SWORDLESS); + } +} diff --git a/soh/soh/Enhancements/ChildLink2hMS.h b/soh/soh/Enhancements/ChildLink2hMS.h index 3c00a3af248..87298b2130a 100644 --- a/soh/soh/Enhancements/ChildLink2hMS.h +++ b/soh/soh/Enhancements/ChildLink2hMS.h @@ -10,6 +10,7 @@ extern "C" { struct Player; // Returns true when child Link is allowed to use the Master Sword (randomizer shuffle). +s32 ChildLink2hMS_IsEnabled(void); s32 ChildLink2hMS_CanChildUseMasterSword(void); // Returns true when the age requirement should be bypassed for Master Sword while child. @@ -24,6 +25,12 @@ s32 ChildLink2hMS_ShouldAllowEquip(s32 equipType, s32 equipValue); // Optional override to map Master Sword to two-handed melee behavior for child Link. s32 ChildLink2hMS_OverrideMeleeWeapon(s32 actionParam, s32 baseMeleeWeapon); +// Returns the last sword to equip on child B based on owned swords and feature state. +s16 ChildLink2hMS_SelectChildSword(s16 preferredSword); + +// Applies B-button sword/equipment/flags for child based on owned swords and feature state. +void ChildLink2hMS_ApplyChildSwordEquip(s16 preferredSword); + #ifdef __cplusplus } #endif diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index 3b1d91f056a..87c40180899 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -12,6 +12,7 @@ #include "soh/Enhancements/cosmetics/cosmeticsTypes.h" #include "soh/Enhancements/enhancementTypes.h" #include "soh/ShipUtils.h" +#include "soh/Enhancements/randomizer/randomizerTypes.h" #include #include @@ -24,6 +25,7 @@ #include "soh/ResourceManagerHelpers.h" #include "soh/Enhancements/gameplaystats.h" #include "soh/ObjectExtension/ActorMaximumHealth.h" +#include "soh/Enhancements/ChildLink2hMS.h" #include "message_data_static.h" extern MessageTableEntry* sNesMessageEntryTablePtr; @@ -1435,7 +1437,9 @@ void Rando_Inventory_SwapAgeEquipment(void) { if (i != 0) { gSaveContext.childEquips.buttonItems[i] = gSaveContext.equips.buttonItems[i]; } else { - gSaveContext.childEquips.buttonItems[i] = ITEM_SWORD_KOKIRI; + gSaveContext.childEquips.buttonItems[i] = + ChildLink2hMS_IsEnabled() ? ChildLink2hMS_SelectChildSword(gSaveContext.equips.buttonItems[i]) + : ITEM_SWORD_KOKIRI; } if (i != 0) { @@ -1529,7 +1533,10 @@ void Rando_Inventory_SwapAgeEquipment(void) { gSaveContext.adultEquips.equipment = gSaveContext.equips.equipment; - if (gSaveContext.childEquips.buttonItems[0] != ITEM_NONE) { + s32 childEquipsInitialized = + (gSaveContext.childEquips.equipment != 0) || (gSaveContext.childEquips.buttonItems[0] != ITEM_NONE); + + if (childEquipsInitialized) { for (i = 0; i < ARRAY_COUNT(gSaveContext.equips.buttonItems); i++) { gSaveContext.equips.buttonItems[i] = gSaveContext.childEquips.buttonItems[i]; @@ -1547,8 +1554,6 @@ void Rando_Inventory_SwapAgeEquipment(void) { } gSaveContext.equips.equipment = gSaveContext.childEquips.equipment; - gSaveContext.equips.equipment &= (u16) ~(0xF << (EQUIP_TYPE_SWORD * 4)); - gSaveContext.equips.equipment |= EQUIP_VALUE_SWORD_KOKIRI << (EQUIP_TYPE_SWORD * 4); } // In Rando we need an extra case to handle starting as adult. We can use the fact that the childEquips will be // uninitialised (i.e. 0) at this point @@ -1567,9 +1572,9 @@ void Rando_Inventory_SwapAgeEquipment(void) { (EQUIP_VALUE_BOOTS_KOKIRI << (EQUIP_TYPE_BOOTS * 4)); } - // When becoming child in rando, set swordless flag and clear B button if player doesn't have kokiri sword - // Otherwise, equip sword and unset flag - if (!CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_KOKIRI)) { + if (ChildLink2hMS_IsEnabled()) { + ChildLink2hMS_ApplyChildSwordEquip(gSaveContext.equips.buttonItems[0]); + } else if (!CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_KOKIRI)) { gSaveContext.equips.equipment &= (u16) ~(0xF << (EQUIP_TYPE_SWORD * 4)); gSaveContext.equips.buttonItems[0] = ITEM_NONE; Flags_SetInfTable(INFTABLE_SWORDLESS); From 4012cad26c8ec286ac29d332fa3d551bc9de282c Mon Sep 17 00:00:00 2001 From: Jesper Arvidsson Date: Sun, 4 Jan 2026 16:02:06 +0100 Subject: [PATCH 7/7] Messed up my conlicts --- soh/soh/Enhancements/randomizer/settings.cpp | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/settings.cpp b/soh/soh/Enhancements/randomizer/settings.cpp index 54d36cb8baf..4c54b04bf5a 100644 --- a/soh/soh/Enhancements/randomizer/settings.cpp +++ b/soh/soh/Enhancements/randomizer/settings.cpp @@ -2199,6 +2199,7 @@ void Settings::CreateOptions() { &mOptions[RSK_SKULLS_SUNS_SONG], &mOptions[RSK_BLUE_FIRE_ARROWS], &mOptions[RSK_SUNLIGHT_ARROWS], + &mOptions[RSK_CHILD_LINK_2H_MS], &mOptions[RSK_FULL_WALLETS], &mOptions[RSK_SLINGBOW_BREAK_BEEHIVES], &mOptions[RSK_SKIP_CHILD_ZELDA], @@ -2459,24 +2460,6 @@ void Settings::CreateOptions() { &mOptionGroups[RSG_MENU_COLUMN_HINTS_TRAPS], &mOptionGroups[RSG_MENU_COLUMN_STATIC_HINTS], }, - WidgetContainerType::COLUMN); - mOptionGroups[RSG_ADDITIONAL_FEATURES_IMGUI] = OptionGroup::SubGroup("Additional Features", - { - &mOptions[RSK_FULL_WALLETS], - &mOptions[RSK_BOMBCHU_BAG], - &mOptions[RSK_ENABLE_BOMBCHU_DROPS], - &mOptions[RSK_BLUE_FIRE_ARROWS], - &mOptions[RSK_CHILD_LINK_2H_MS], - &mOptions[RSK_SUNLIGHT_ARROWS], - &mOptions[RSK_INFINITE_UPGRADES], - &mOptions[RSK_SKELETON_KEY], - &mOptions[RSK_SLINGBOW_BREAK_BEEHIVES], - }, - WidgetContainerType::COLUMN); - mOptionGroups[RSG_GAMEPLAY_IMGUI_TABLE] = - OptionGroup::SubGroup("Gameplay", - { &mOptionGroups[RSG_TIMESAVERS_IMGUI], &mOptionGroups[RSG_ITEM_POOL_HINTS_IMGUI_COLUMN], - &mOptionGroups[RSG_ADDITIONAL_FEATURES_IMGUI] }, WidgetContainerType::TABLE); mOptionGroups[RSG_MENU_SECTION_STARTING_EQUIPS] = OptionGroup::SubGroup("Equips",