From 92a1d260da3739abea9f2c2d3c0feebeb582a1bf Mon Sep 17 00:00:00 2001 From: A Green Spoon <121978037+A-Green-Spoon@users.noreply.github.com> Date: Fri, 6 Mar 2026 07:05:55 +0900 Subject: [PATCH] Choose Link's Pocket Dungeon Reward Type (#6213) --- .../Enhancements/randomizer/3drando/fill.cpp | 81 ++++++++++++++----- .../randomizerEnums/RandomizerOptions.h | 7 ++ .../randomizerEnums/RandomizerSettingKey.h | 1 + soh/soh/Enhancements/randomizer/settings.cpp | 33 ++++++-- 4 files changed, 95 insertions(+), 27 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/3drando/fill.cpp b/soh/soh/Enhancements/randomizer/3drando/fill.cpp index 522b37f34..6e38ca33d 100644 --- a/soh/soh/Enhancements/randomizer/3drando/fill.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/fill.cpp @@ -944,24 +944,21 @@ static void AssumedFill(const std::vector& items, const std::vect // setting, or randomize one dungeon reward to Link's Pocket if that setting is on static void RandomizeDungeonRewards() { auto ctx = Rando::Context::GetInstance(); - // quest item bit mask of each stone/medallion for the savefile - // static constexpr std::array bitMaskTable = { - // 0x00040000, //Kokiri Emerald - // 0x00080000, //Goron Ruby - // 0x00100000, //Zora Sapphire - // 0x00000001, //Forest Medallion - // 0x00000002, //Fire Medallion - // 0x00000004, //Water Medallion - // 0x00000008, //Spirit Medallion - // 0x00000010, //Shadow Medallion - // 0x00000020, //Light Medallion - // }; - int baseOffset = Rando::StaticData::RetrieveItem(RG_KOKIRI_EMERALD).GetItemID(); // End of Dungeons includes Link's Pocket if (ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Is(RO_DUNGEON_REWARDS_END_OF_DUNGEON) || ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Is(RO_DUNGEON_REWARDS_VANILLA)) { - // get stones and medallions + // make temporary pools of stones and medallions, get rewards + std::vector stones = FilterFromPool(itemPool, [](const auto i) { + return Rando::StaticData::RetrieveItem(i).GetItemType() == ITEMTYPE_DUNGEONREWARD && + Rando::StaticData::RetrieveItem(i).GetRandomizerGet() >= RG_KOKIRI_EMERALD && + Rando::StaticData::RetrieveItem(i).GetRandomizerGet() <= RG_ZORA_SAPPHIRE; + }); + std::vector medallions = FilterFromPool(itemPool, [](const auto i) { + return Rando::StaticData::RetrieveItem(i).GetItemType() == ITEMTYPE_DUNGEONREWARD && + Rando::StaticData::RetrieveItem(i).GetRandomizerGet() >= RG_FOREST_MEDALLION && + Rando::StaticData::RetrieveItem(i).GetRandomizerGet() <= RG_LIGHT_MEDALLION; + }); std::vector rewards = FilterAndEraseFromPool(itemPool, [](const auto i) { return Rando::StaticData::RetrieveItem(i).GetItemType() == ITEMTYPE_DUNGEONREWARD; }); @@ -978,12 +975,38 @@ static void RandomizeDungeonRewards() { if (rewards.size() < 9) { ctx->PlaceItemInLocation(RC_LINKS_POCKET, RG_GREEN_RUPEE); } else { - rewardLocations.push_back(RC_LINKS_POCKET); + if (ctx->GetOption(RSK_LINKS_POCKET_REWARD).IsNot(RO_LINKS_POCKET_REWARD)) { + if (ctx->GetOption(RSK_LINKS_POCKET_REWARD).Is(RO_LINKS_POCKET_STONE)) { + // get one stone + RandomizerGet startingStone = RandomElement(stones, true); + // erase from rewards so remaining are placed + erase_if(rewards, [&](RandomizerGet r) { return r == startingStone; }); + ctx->PlaceItemInLocation(RC_LINKS_POCKET, startingStone); + } else { + // get one medallion + RandomizerGet startingMedallion = RandomElement(medallions, true); + // erase from rewards so remaining are placed + erase_if(rewards, [&](RandomizerGet r) { return r == startingMedallion; }); + ctx->PlaceItemInLocation(RC_LINKS_POCKET, startingMedallion); + } + } else { + rewardLocations.push_back(RC_LINKS_POCKET); + } } AssumedFill(rewards, rewardLocations); } } else if (ctx->GetOption(RSK_LINKS_POCKET).Is(RO_LINKS_POCKET_DUNGEON_REWARD)) { - // get 1 stone/medallion + // make temporary pools of stones, medallions, and rewards + std::vector stones = FilterFromPool(itemPool, [](const auto i) { + return Rando::StaticData::RetrieveItem(i).GetItemType() == ITEMTYPE_DUNGEONREWARD && + Rando::StaticData::RetrieveItem(i).GetRandomizerGet() >= RG_KOKIRI_EMERALD && + Rando::StaticData::RetrieveItem(i).GetRandomizerGet() <= RG_ZORA_SAPPHIRE; + }); + std::vector medallions = FilterFromPool(itemPool, [](const auto i) { + return Rando::StaticData::RetrieveItem(i).GetItemType() == ITEMTYPE_DUNGEONREWARD && + Rando::StaticData::RetrieveItem(i).GetRandomizerGet() >= RG_FOREST_MEDALLION && + Rando::StaticData::RetrieveItem(i).GetRandomizerGet() <= RG_LIGHT_MEDALLION; + }); std::vector rewards = FilterFromPool(itemPool, [](const auto i) { return Rando::StaticData::RetrieveItem(i).GetItemType() == ITEMTYPE_DUNGEONREWARD; }); @@ -992,13 +1015,27 @@ static void RandomizeDungeonRewards() { ctx->PlaceItemInLocation(RC_LINKS_POCKET, RG_GREEN_RUPEE); return; } - RandomizerGet startingReward = RandomElement(rewards, true); + if (ctx->GetOption(RSK_LINKS_POCKET_REWARD).Is(RO_LINKS_POCKET_STONE)) { + // get one stone + RandomizerGet startingStone = RandomElement(stones, true); + ctx->PlaceItemInLocation(RC_LINKS_POCKET, startingStone); + // erase stone from item pool + FilterAndEraseFromPool(itemPool, [startingStone](const RandomizerGet i) { return i == startingStone; }); + } else if (ctx->GetOption(RSK_LINKS_POCKET_REWARD).Is(RO_LINKS_POCKET_MEDALLION)) { + // get one medallion + RandomizerGet startingMedallion = RandomElement(medallions, true); + ctx->PlaceItemInLocation(RC_LINKS_POCKET, startingMedallion); + // erase medallion from item pool + FilterAndEraseFromPool(itemPool, + [startingMedallion](const RandomizerGet i) { return i == startingMedallion; }); + } else { + // get one reward + RandomizerGet startingReward = RandomElement(rewards, true); - // LinksPocketRewardBitMask = bitMaskTable[Rando::StaticData::RetrieveItem(startingReward).GetItemID() - - // baseOffset]; - ctx->PlaceItemInLocation(RC_LINKS_POCKET, startingReward); - // erase the stone/medallion from the Item Pool - FilterAndEraseFromPool(itemPool, [startingReward](const RandomizerGet i) { return i == startingReward; }); + ctx->PlaceItemInLocation(RC_LINKS_POCKET, startingReward); + // erase the stone/medallion from the Item Pool + FilterAndEraseFromPool(itemPool, [startingReward](const RandomizerGet i) { return i == startingReward; }); + } } } diff --git a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerOptions.h b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerOptions.h index 9cfd3971b..ac0c4dded 100644 --- a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerOptions.h +++ b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerOptions.h @@ -397,6 +397,13 @@ RANDO_ENUM_ITEM(RO_LINKS_POCKET_ANYTHING) RANDO_ENUM_ITEM(RO_LINKS_POCKET_NOTHING) RANDO_ENUM_END(RandoOptionLinksPocket) +// Link's Pocket Dungeon Reward Settings (dungeon reward, stone, medallion) +RANDO_ENUM_BEGIN(RandoOptionLinksPocketReward) +RANDO_ENUM_ITEM(RO_LINKS_POCKET_REWARD) +RANDO_ENUM_ITEM(RO_LINKS_POCKET_STONE) +RANDO_ENUM_ITEM(RO_LINKS_POCKET_MEDALLION) +RANDO_ENUM_END(RandoOptionLinksPocketReward) + // Logic (glitchless/no logic) RANDO_ENUM_BEGIN(RandoOptionLogic) RANDO_ENUM_ITEM(RO_LOGIC_GLITCHLESS) diff --git a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerSettingKey.h b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerSettingKey.h index 994d6d8f1..40ebb218d 100644 --- a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerSettingKey.h +++ b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerSettingKey.h @@ -166,6 +166,7 @@ RANDO_ENUM_ITEM(RSK_SLINGBOW_BREAK_BEEHIVES) RANDO_ENUM_ITEM(RSK_ENABLE_BOMBCHU_DROPS) RANDO_ENUM_ITEM(RSK_BOMBCHU_BAG) RANDO_ENUM_ITEM(RSK_LINKS_POCKET) +RANDO_ENUM_ITEM(RSK_LINKS_POCKET_REWARD) RANDO_ENUM_ITEM(RSK_MQ_DUNGEON_RANDOM) RANDO_ENUM_ITEM(RSK_MQ_DUNGEON_COUNT) RANDO_ENUM_ITEM(RSK_MQ_DUNGEON_SET) diff --git a/soh/soh/Enhancements/randomizer/settings.cpp b/soh/soh/Enhancements/randomizer/settings.cpp index cebca12a5..873f17b9d 100644 --- a/soh/soh/Enhancements/randomizer/settings.cpp +++ b/soh/soh/Enhancements/randomizer/settings.cpp @@ -574,11 +574,34 @@ void Settings::CreateOptions() { RO_DUNGEON_REWARDS_END_OF_DUNGEON) { mOptions[RSK_LINKS_POCKET].Disable( "This option is disabled because \"Dungeon Rewards\" are shuffled to \"End of Dungeons\"."); + mOptions[RSK_LINKS_POCKET_REWARD].Enable(); + mOptions[RSK_LINKS_POCKET_REWARD].Unhide(); + } else if (CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleDungeonReward"), RO_DUNGEON_REWARDS_END_OF_DUNGEON) == + RO_DUNGEON_REWARDS_VANILLA) { + mOptions[RSK_LINKS_POCKET_REWARD].Disable("This option is disabled because \"Dungeon Rewards\" are shuffled to \"Vanilla\"."); + mOptions[RSK_LINKS_POCKET_REWARD].Hide(); + mOptions[RSK_LINKS_POCKET].Enable(); } else { mOptions[RSK_LINKS_POCKET].Enable(); + mOptions[RSK_LINKS_POCKET_REWARD].Enable(); + if (CVarGetInteger(CVAR_RANDOMIZER_SETTING("LinksPocket"), RO_LINKS_POCKET_DUNGEON_REWARD) == RO_LINKS_POCKET_DUNGEON_REWARD) { + mOptions[RSK_LINKS_POCKET_REWARD].Unhide(); + } } }); OPT_U8(RSK_LINKS_POCKET, "Link's Pocket", {"Dungeon Reward", "Advancement", "Anything", "Nothing"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("LinksPocket"), "", WIDGET_CVAR_COMBOBOX, RO_LINKS_POCKET_DUNGEON_REWARD); + OPT_CALLBACK(RSK_LINKS_POCKET, { + // Only show the dungeon reward type if Link's Pocket is set to Dungeon Reward and Dungeon Rewards are not Vanilla, OR Dungeon Rewards are end of dungeon + if ((CVarGetInteger(CVAR_RANDOMIZER_SETTING("LinksPocket"), RO_LINKS_POCKET_DUNGEON_REWARD) == + RO_LINKS_POCKET_DUNGEON_REWARD && CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleDungeonReward"), RO_DUNGEON_REWARDS_END_OF_DUNGEON) != + RO_DUNGEON_REWARDS_VANILLA) || CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleDungeonReward"), RO_DUNGEON_REWARDS_END_OF_DUNGEON) == + RO_DUNGEON_REWARDS_END_OF_DUNGEON) { + mOptions[RSK_LINKS_POCKET_REWARD].Unhide(); + } else { + mOptions[RSK_LINKS_POCKET_REWARD].Hide(); + } + }); + OPT_U8(RSK_LINKS_POCKET_REWARD, "Link's Pocket Reward Type", {"Dungeon Reward", "Stone", "Medallion"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("LinksPocketReward"), "", WIDGET_CVAR_COMBOBOX, RO_LINKS_POCKET_REWARD); OPT_U8(RSK_SHUFFLE_SONGS, "Shuffle Songs", {"Off", "Song Locations", "Dungeon Rewards", "Anywhere"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleSongs"), mOptionDescriptions[RSK_SHUFFLE_SONGS], WIDGET_CVAR_COMBOBOX, RO_SONG_SHUFFLE_SONG_LOCATIONS); OPT_U8(RSK_SHOPSANITY, "Shop Shuffle", {"Off", "Specific Count", "Random"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("Shopsanity"), mOptionDescriptions[RSK_SHOPSANITY], WIDGET_CVAR_COMBOBOX, RO_SHOPSANITY_OFF); OPT_CALLBACK(RSK_SHOPSANITY, { @@ -2547,11 +2570,11 @@ void Settings::CreateOptions() { &mOptionGroups[RSG_MENU_COLUMN_STATIC_HINTS], }, WidgetContainerType::TABLE); - mOptionGroups[RSG_MENU_SECTION_STARTING_EQUIPS] = - OptionGroup::SubGroup("Equips", - { &mOptions[RSK_LINKS_POCKET], &mOptions[RSK_STARTING_KOKIRI_SWORD], - &mOptions[RSK_STARTING_MASTER_SWORD], &mOptions[RSK_STARTING_DEKU_SHIELD] }, - WidgetContainerType::SECTION); + mOptionGroups[RSG_MENU_SECTION_STARTING_EQUIPS] = OptionGroup::SubGroup( + "Equips", + { &mOptions[RSK_LINKS_POCKET], &mOptions[RSK_LINKS_POCKET_REWARD], &mOptions[RSK_STARTING_KOKIRI_SWORD], + &mOptions[RSK_STARTING_MASTER_SWORD], &mOptions[RSK_STARTING_DEKU_SHIELD] }, + WidgetContainerType::SECTION); mOptionGroups[RSG_MENU_SECTION_STARTING_ITEMS] = OptionGroup::SubGroup("Items", { &mOptions[RSK_STARTING_OCARINA],