Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions soh/soh/Enhancements/ChildLink2hMS.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#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_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) ||
CVarGetInteger(CVAR_RANDOMIZER_SETTING("ChildMasterSword"), 0) ||
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_IsEnabledInternal() && (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);
}

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;
}

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);
}
}
36 changes: 36 additions & 0 deletions soh/soh/Enhancements/ChildLink2hMS.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#pragma once

#include <libultraship/libultra.h>
#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_IsEnabled(void);
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);

// 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);

// 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
2 changes: 1 addition & 1 deletion soh/soh/Enhancements/randomizer/logic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,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_GIANTS_KNIFE:
case RG_BIGGORON_SWORD:
return IsAdult; // || BiggoronSwordAsChild;
Expand Down
2 changes: 2 additions & 0 deletions soh/soh/Enhancements/randomizer/option_descriptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,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_SKELETON_KEY] =
"Adds a new item called the \"Skeleton Key\", it unlocks every dungeon door locked by a small key.";
mOptionDescriptions[RSK_SUNLIGHT_ARROWS] =
Expand Down
1 change: 1 addition & 0 deletions soh/soh/Enhancements/randomizer/randomizerTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -6517,6 +6517,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,
Expand Down
2 changes: 2 additions & 0 deletions soh/soh/Enhancements/randomizer/settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1231,6 +1231,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]);
Expand Down Expand Up @@ -2198,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],
Expand Down
11 changes: 11 additions & 0 deletions soh/soh/SohGui/SohMenuEnhancements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,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."));
Expand Down
19 changes: 12 additions & 7 deletions soh/src/code/z_parameter.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 <string.h>
#include <stdlib.h>
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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];

Expand All @@ -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
Expand All @@ -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);
Expand Down
26 changes: 22 additions & 4 deletions soh/src/code/z_player_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -532,6 +533,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;
Expand Down Expand Up @@ -863,9 +869,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);
}
}

Expand All @@ -874,11 +880,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) {
Expand Down Expand Up @@ -1374,6 +1384,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) &&
Expand Down
10 changes: 6 additions & 4 deletions soh/src/overlays/actors/ovl_Bg_Toki_Swd/z_bg_toki_swd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions soh/src/overlays/actors/ovl_player_actor/z_player.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading
Loading