From 12dddc5e8e6c25149639b3e7eaec36a69eed2801 Mon Sep 17 00:00:00 2001 From: Sophia Caspe Date: Wed, 15 Apr 2026 06:44:52 -0700 Subject: [PATCH] Dungeon Rewards Own Dungeon + Light Medallion Handling Refactor (#6500) Co-authored-by: Pepper0ni <93387759+Pepper0ni@users.noreply.github.com> --- .../Enhancements/randomizer/3drando/fill.cpp | 174 +++++++++--------- .../randomizer/3drando/item_pool.cpp | 2 +- soh/soh/Enhancements/randomizer/dungeon.cpp | 62 ++++--- soh/soh/Enhancements/randomizer/dungeon.h | 8 +- .../randomizer/option_descriptions.cpp | 10 + .../randomizerEnums/RandomizerOptions.h | 8 +- soh/soh/Enhancements/randomizer/settings.cpp | 44 +++-- 7 files changed, 173 insertions(+), 135 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/3drando/fill.cpp b/soh/soh/Enhancements/randomizer/3drando/fill.cpp index 5f92344c6..799b9a344 100644 --- a/soh/soh/Enhancements/randomizer/3drando/fill.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/fill.cpp @@ -933,102 +933,96 @@ static void AssumedFill(const std::vector& items, const std::vect } while (unsuccessfulPlacement); } +static std::vector GetStonesInPool(std::vector pool) { + return FilterFromPool(pool, [](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; + }); +} + +static std::vector GetMedallionsInPool(std::vector pool) { + return FilterFromPool(pool, [](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; + }); +} + // This function will specifically randomize dungeon rewards for the End of Dungeons // setting, or randomize one dungeon reward to Link's Pocket if that setting is on +// RANDOTODO this function assumes only 1 of each reward can exist, fix it when starting items are refactored static void RandomizeDungeonRewards() { auto ctx = Rando::Context::GetInstance(); - // 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)) { - // 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; - }); + std::vector rewards = FilterFromPool(itemPool, [](const auto i) { + return Rando::StaticData::RetrieveItem(i).GetItemType() == ITEMTYPE_DUNGEONREWARD; + }); - if (ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS) - .Is(RO_DUNGEON_REWARDS_VANILLA)) { // Place dungeon rewards in vanilla locations - for (RandomizerCheck loc : Rando::StaticData::dungeonRewardLocations) { - ctx->GetItemLocation(loc)->PlaceVanillaItem(); + if (ctx->GetOption(RSK_LINKS_POCKET).Is(RO_LINKS_POCKET_DUNGEON_REWARD) && rewards.size() >= 9) { + RandomizerGet pocketItem = RG_GREEN_RUPEE; + std::vector pocketPossibilities = {}; + + if (ctx->GetOption(RSK_LINKS_POCKET_REWARD).Is(RO_LINKS_POCKET_ANY_STONE)) { + // get existing stones + pocketPossibilities = GetStonesInPool(rewards); + } else if (ctx->GetOption(RSK_LINKS_POCKET_REWARD).Is(RO_LINKS_POCKET_LIGHT_MEDALLION)) { + // check if Light medallion exists + std::vector lightMedallion = FilterFromPool(rewards, [](const auto i) { + return Rando::StaticData::RetrieveItem(i).GetRandomizerGet() == RG_LIGHT_MEDALLION; + }); + // If there are no light med, then Link's pocket can't get one + if (!lightMedallion.empty()) { + pocketPossibilities = { RG_LIGHT_MEDALLION }; } - ctx->GetItemLocation(RC_GIFT_FROM_RAURU)->PlaceVanillaItem(); - } else { // Randomize dungeon rewards with assumed fill - std::vector rewardLocations(Rando::StaticData::dungeonRewardLocations); - // If there are less than 9 dungeon rewards, prioritize actual dungeons for placement - if (rewards.size() < 9) { - ctx->PlaceItemInLocation(RC_LINKS_POCKET, RG_GREEN_RUPEE); - } else { - 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_REWARD).Is(RO_LINKS_POCKET_ANY_MEDALLION)) { + // get existing medallions + pocketPossibilities = GetMedallionsInPool(rewards); + } else if (ctx->GetOption(RSK_LINKS_POCKET_REWARD).Is(RO_LINKS_POCKET_ANY_REWARD)) { + // get all existing rewards + pocketPossibilities = rewards; } - } else if (ctx->GetOption(RSK_LINKS_POCKET).Is(RO_LINKS_POCKET_DUNGEON_REWARD)) { - // 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; - }); - // If there are no remaining stones/medallions, then Link's pocket won't get one - if (rewards.empty()) { - ctx->PlaceItemInLocation(RC_LINKS_POCKET, RG_GREEN_RUPEE); - return; - } - if (ctx->GetOption(RSK_LINKS_POCKET_REWARD).Is(RO_LINKS_POCKET_STONE)) { + + if (!pocketPossibilities.empty()) { // 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); - - ctx->PlaceItemInLocation(RC_LINKS_POCKET, startingReward); - // erase the stone/medallion from the Item Pool - FilterAndEraseFromPool(itemPool, [startingReward](const RandomizerGet i) { return i == startingReward; }); + pocketItem = RandomElement(pocketPossibilities); } + // erase from rewards so remaining are placed + erase_if(rewards, [&](RandomizerGet r) { return r == pocketItem; }); + // and from the item pool so it's not placed twice + FilterAndEraseFromPool(itemPool, [pocketItem](const RandomizerGet i) { return i == pocketItem; }); + // and add to the pocket + ctx->PlaceItemInLocation(RC_LINKS_POCKET, pocketItem); + } + + // If we didn't place the Light Medallion on pocket, and we have rewards in their own dungeons or at the end of + // dungeons... + if ((ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Is(RO_DUNGEON_REWARDS_VANILLA) || + ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Is(RO_DUNGEON_REWARDS_OWN_DUNGEON)) && + ctx->GetOption(RSK_LINKS_POCKET).IsNot(RO_LINKS_POCKET_DUNGEON_REWARD)) { + // place it on Gift From Rauru + ctx->GetItemLocation(RC_GIFT_FROM_RAURU)->PlaceVanillaItem(); + // then erase from rewards so remaining are placed + erase_if(rewards, [&](RandomizerGet r) { return r == RG_LIGHT_MEDALLION; }); + // and from the item pool so it's not placed twice + FilterAndEraseFromPool(itemPool, [](const RandomizerGet i) { return i == RG_LIGHT_MEDALLION; }); + } + + if (ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Is(RO_DUNGEON_REWARDS_END_OF_DUNGEON)) { + // Randomize dungeon rewards with assumed fill + AssumedFill(rewards, Rando::StaticData::dungeonRewardLocations); + // Then remove them from the item pool + FilterAndEraseFromPool(itemPool, [](const auto i) { + return Rando::StaticData::RetrieveItem(i).GetItemType() == ITEMTYPE_DUNGEONREWARD; + }); + } else if (ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Is(RO_DUNGEON_REWARDS_VANILLA)) { + for (RandomizerCheck loc : Rando::StaticData::dungeonRewardLocations) { + ctx->GetItemLocation(loc)->PlaceVanillaItem(); + } + // Then remove rewards from the item pool + FilterAndEraseFromPool(itemPool, [](const auto i) { + return Rando::StaticData::RetrieveItem(i).GetItemType() == ITEMTYPE_DUNGEONREWARD; + }); } } @@ -1078,6 +1072,12 @@ static void RandomizeOwnDungeon(const Rando::DungeonInfo* dungeon) { }); AddElementsToPool(dungeonItems, dungeonSmallKeys); } + if (ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Is(RO_DUNGEON_REWARDS_OWN_DUNGEON) && + dungeon->GetReward() != RG_NONE) { + std::vector dungeonReward = + FilterAndEraseFromPool(itemPool, [dungeon](const RandomizerGet i) { return (i == dungeon->GetReward()); }); + AddElementsToPool(dungeonItems, dungeonReward); + } if ((ctx->GetOption(RSK_BOSS_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_OWN_DUNGEON) && dungeon->GetBossKey() != RG_GANONS_CASTLE_BOSS_KEY) || @@ -1088,7 +1088,7 @@ static void RandomizeOwnDungeon(const Rando::DungeonInfo* dungeon) { AddElementsToPool(dungeonItems, dungeonBossKey); } - // randomize boss key and small keys together for even distribution + // randomize boss key, small keys, and rewards together for even distribution AssumedFill(dungeonItems, dungeonLocations); // randomize map and compass separately since they're not progressive diff --git a/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp b/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp index 7f1e886a9..db73b966e 100644 --- a/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp @@ -303,7 +303,7 @@ void GenerateItemPool() { ctx->PlaceItemInLocation(RC_SONG_FROM_WINDMILL, RG_SONG_OF_STORMS, false, true); } - bool rewardIceTraps = ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Get() >= RO_DUNGEON_REWARDS_ANY_DUNGEON; + bool rewardIceTraps = ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Get() >= RO_DUNGEON_REWARDS_OWN_DUNGEON; AddFixedItemToPool(RG_KOKIRI_EMERALD, 1, rewardIceTraps); AddFixedItemToPool(RG_GORON_RUBY, 1, rewardIceTraps); AddFixedItemToPool(RG_ZORA_SAPPHIRE, 1, rewardIceTraps); diff --git a/soh/soh/Enhancements/randomizer/dungeon.cpp b/soh/soh/Enhancements/randomizer/dungeon.cpp index cf37b5f39..6cb077c63 100644 --- a/soh/soh/Enhancements/randomizer/dungeon.cpp +++ b/soh/soh/Enhancements/randomizer/dungeon.cpp @@ -7,14 +7,16 @@ namespace Rando { DungeonInfo::DungeonInfo(std::string name_, const RandomizerHintTextKey hintKey_, const RandomizerGet map_, const RandomizerGet compass_, const RandomizerGet smallKey_, const RandomizerGet keyRing_, - const RandomizerGet bossKey_, RandomizerArea area_, const uint8_t vanillaKeyCount_, - const uint8_t mqKeyCount_, const RandomizerSettingKey mqSetting_) + const RandomizerGet bossKey_, RandomizerGet reward_, RandomizerArea area_, + const uint8_t vanillaKeyCount_, const uint8_t mqKeyCount_, + const RandomizerSettingKey mqSetting_) : name(std::move(name_)), hintKey(hintKey_), map(map_), compass(compass_), smallKey(smallKey_), keyRing(keyRing_), - bossKey(bossKey_), area(area_), vanillaKeyCount(vanillaKeyCount_), mqKeyCount(mqKeyCount_), + bossKey(bossKey_), reward(reward_), area(area_), vanillaKeyCount(vanillaKeyCount_), mqKeyCount(mqKeyCount_), mqSetting(mqSetting_) { } DungeonInfo::DungeonInfo() - : hintKey(RHT_NONE), map(RG_NONE), compass(RG_NONE), smallKey(RG_NONE), keyRing(RG_NONE), bossKey(RG_NONE) { + : hintKey(RHT_NONE), map(RG_NONE), compass(RG_NONE), smallKey(RG_NONE), keyRing(RG_NONE), bossKey(RG_NONE), + reward(RG_NONE) { } DungeonInfo::~DungeonInfo() = default; @@ -82,6 +84,10 @@ RandomizerGet DungeonInfo::GetBossKey() const { return bossKey; } +RandomizerGet DungeonInfo::GetReward() const { + return reward; +} + RandomizerSettingKey DungeonInfo::GetMQSetting() const { return mqSetting; } @@ -147,40 +153,44 @@ std::vector DungeonInfo::GetDungeonLocations() const { Dungeons::Dungeons() { dungeonList[DEKU_TREE] = DungeonInfo("Deku Tree", RHT_DEKU_TREE, RG_DEKU_TREE_MAP, RG_DEKU_TREE_COMPASS, RG_NONE, - RG_NONE, RG_NONE, RA_DEKU_TREE, 0, 0, RSK_MQ_DEKU_TREE); + RG_NONE, RG_NONE, RG_KOKIRI_EMERALD, RA_DEKU_TREE, 0, 0, RSK_MQ_DEKU_TREE); dungeonList[DODONGOS_CAVERN] = DungeonInfo("Dodongo's Cavern", RHT_DODONGOS_CAVERN, RG_DODONGOS_CAVERN_MAP, RG_DODONGOS_CAVERN_COMPASS, - RG_NONE, RG_NONE, RG_NONE, RA_DODONGOS_CAVERN, 0, 0, RSK_MQ_DODONGOS_CAVERN); + RG_NONE, RG_NONE, RG_NONE, RG_GORON_RUBY, RA_DODONGOS_CAVERN, 0, 0, RSK_MQ_DODONGOS_CAVERN); dungeonList[JABU_JABUS_BELLY] = DungeonInfo("Jabu Jabu's Belly", RHT_JABU_JABUS_BELLY, RG_JABU_JABUS_BELLY_MAP, RG_JABU_JABUS_BELLY_COMPASS, - RG_NONE, RG_NONE, RG_NONE, RA_JABU_JABUS_BELLY, 0, 0, RSK_MQ_JABU_JABU); - dungeonList[FOREST_TEMPLE] = DungeonInfo( - "Forest Temple", RHT_FOREST_TEMPLE, RG_FOREST_TEMPLE_MAP, RG_FOREST_TEMPLE_COMPASS, RG_FOREST_TEMPLE_SMALL_KEY, - RG_FOREST_TEMPLE_KEY_RING, RG_FOREST_TEMPLE_BOSS_KEY, RA_FOREST_TEMPLE, 5, 6, RSK_MQ_FOREST_TEMPLE); + RG_NONE, RG_NONE, RG_NONE, RG_ZORA_SAPPHIRE, RA_JABU_JABUS_BELLY, 0, 0, RSK_MQ_JABU_JABU); + dungeonList[FOREST_TEMPLE] = + DungeonInfo("Forest Temple", RHT_FOREST_TEMPLE, RG_FOREST_TEMPLE_MAP, RG_FOREST_TEMPLE_COMPASS, + RG_FOREST_TEMPLE_SMALL_KEY, RG_FOREST_TEMPLE_KEY_RING, RG_FOREST_TEMPLE_BOSS_KEY, + RG_FOREST_MEDALLION, RA_FOREST_TEMPLE, 5, 6, RSK_MQ_FOREST_TEMPLE); dungeonList[FIRE_TEMPLE] = DungeonInfo("Fire Temple", RHT_FIRE_TEMPLE, RG_FIRE_TEMPLE_MAP, RG_FIRE_TEMPLE_COMPASS, RG_FIRE_TEMPLE_SMALL_KEY, RG_FIRE_TEMPLE_KEY_RING, RG_FIRE_TEMPLE_BOSS_KEY, - RA_FIRE_TEMPLE, 8, 5, RSK_MQ_FIRE_TEMPLE); - dungeonList[WATER_TEMPLE] = DungeonInfo( - "Water Temple", RHT_WATER_TEMPLE, RG_WATER_TEMPLE_MAP, RG_WATER_TEMPLE_COMPASS, RG_WATER_TEMPLE_SMALL_KEY, - RG_WATER_TEMPLE_KEY_RING, RG_WATER_TEMPLE_BOSS_KEY, RA_WATER_TEMPLE, 6, 2, RSK_MQ_WATER_TEMPLE); - dungeonList[SPIRIT_TEMPLE] = DungeonInfo( - "Spirit Temple", RHT_SPIRIT_TEMPLE, RG_SPIRIT_TEMPLE_MAP, RG_SPIRIT_TEMPLE_COMPASS, RG_SPIRIT_TEMPLE_SMALL_KEY, - RG_SPIRIT_TEMPLE_KEY_RING, RG_SPIRIT_TEMPLE_BOSS_KEY, RA_SPIRIT_TEMPLE, 5, 7, RSK_MQ_SPIRIT_TEMPLE); - dungeonList[SHADOW_TEMPLE] = DungeonInfo( - "Shadow Temple", RHT_SHADOW_TEMPLE, RG_SHADOW_TEMPLE_MAP, RG_SHADOW_TEMPLE_COMPASS, RG_SHADOW_TEMPLE_SMALL_KEY, - RG_SHADOW_TEMPLE_KEY_RING, RG_SHADOW_TEMPLE_BOSS_KEY, RA_SHADOW_TEMPLE, 5, 6, RSK_MQ_SHADOW_TEMPLE); + RG_FIRE_MEDALLION, RA_FIRE_TEMPLE, 8, 5, RSK_MQ_FIRE_TEMPLE); + dungeonList[WATER_TEMPLE] = + DungeonInfo("Water Temple", RHT_WATER_TEMPLE, RG_WATER_TEMPLE_MAP, RG_WATER_TEMPLE_COMPASS, + RG_WATER_TEMPLE_SMALL_KEY, RG_WATER_TEMPLE_KEY_RING, RG_WATER_TEMPLE_BOSS_KEY, RG_WATER_MEDALLION, + RA_WATER_TEMPLE, 6, 2, RSK_MQ_WATER_TEMPLE); + dungeonList[SPIRIT_TEMPLE] = + DungeonInfo("Spirit Temple", RHT_SPIRIT_TEMPLE, RG_SPIRIT_TEMPLE_MAP, RG_SPIRIT_TEMPLE_COMPASS, + RG_SPIRIT_TEMPLE_SMALL_KEY, RG_SPIRIT_TEMPLE_KEY_RING, RG_SPIRIT_TEMPLE_BOSS_KEY, + RG_SPIRIT_MEDALLION, RA_SPIRIT_TEMPLE, 5, 7, RSK_MQ_SPIRIT_TEMPLE); + dungeonList[SHADOW_TEMPLE] = + DungeonInfo("Shadow Temple", RHT_SHADOW_TEMPLE, RG_SHADOW_TEMPLE_MAP, RG_SHADOW_TEMPLE_COMPASS, + RG_SHADOW_TEMPLE_SMALL_KEY, RG_SHADOW_TEMPLE_KEY_RING, RG_SHADOW_TEMPLE_BOSS_KEY, + RG_SHADOW_MEDALLION, RA_SHADOW_TEMPLE, 5, 6, RSK_MQ_SHADOW_TEMPLE); dungeonList[BOTTOM_OF_THE_WELL] = DungeonInfo("Bottom of the Well", RHT_BOTTOM_OF_THE_WELL, RG_BOTTOM_OF_THE_WELL_MAP, RG_BOTTOM_OF_THE_WELL_COMPASS, RG_BOTTOM_OF_THE_WELL_SMALL_KEY, RG_BOTTOM_OF_THE_WELL_KEY_RING, - RG_NONE, RA_BOTTOM_OF_THE_WELL, 3, 2, RSK_MQ_BOTTOM_OF_THE_WELL); + RG_NONE, RG_NONE, RA_BOTTOM_OF_THE_WELL, 3, 2, RSK_MQ_BOTTOM_OF_THE_WELL); dungeonList[ICE_CAVERN] = DungeonInfo("Ice Cavern", RHT_ICE_CAVERN, RG_ICE_CAVERN_MAP, RG_ICE_CAVERN_COMPASS, - RG_NONE, RG_NONE, RG_NONE, RA_ICE_CAVERN, 0, 0, RSK_MQ_ICE_CAVERN); + RG_NONE, RG_NONE, RG_NONE, RG_NONE, RA_ICE_CAVERN, 0, 0, RSK_MQ_ICE_CAVERN); dungeonList[GERUDO_TRAINING_GROUND] = DungeonInfo( "Gerudo Training Ground", RHT_GERUDO_TRAINING_GROUND, RG_NONE, RG_NONE, RG_GERUDO_TRAINING_GROUND_SMALL_KEY, - RG_GERUDO_TRAINING_GROUND_KEY_RING, RG_NONE, RA_GERUDO_TRAINING_GROUND, 9, 3, RSK_MQ_GTG); - dungeonList[GANONS_CASTLE] = - DungeonInfo("Ganon's Castle", RHT_GANONS_CASTLE, RG_NONE, RG_NONE, RG_GANONS_CASTLE_SMALL_KEY, - RG_GANONS_CASTLE_KEY_RING, RG_GANONS_CASTLE_BOSS_KEY, RA_GANONS_CASTLE, 2, 3, RSK_MQ_GANONS_CASTLE); + RG_GERUDO_TRAINING_GROUND_KEY_RING, RG_NONE, RG_NONE, RA_GERUDO_TRAINING_GROUND, 9, 3, RSK_MQ_GTG); + dungeonList[GANONS_CASTLE] = DungeonInfo( + "Ganon's Castle", RHT_GANONS_CASTLE, RG_NONE, RG_NONE, RG_GANONS_CASTLE_SMALL_KEY, RG_GANONS_CASTLE_KEY_RING, + RG_GANONS_CASTLE_BOSS_KEY, RG_NONE, RA_GANONS_CASTLE, 2, 3, RSK_MQ_GANONS_CASTLE); } Dungeons::~Dungeons() = default; diff --git a/soh/soh/Enhancements/randomizer/dungeon.h b/soh/soh/Enhancements/randomizer/dungeon.h index ba81d289d..8685f913e 100644 --- a/soh/soh/Enhancements/randomizer/dungeon.h +++ b/soh/soh/Enhancements/randomizer/dungeon.h @@ -11,8 +11,8 @@ namespace Rando { class DungeonInfo { public: DungeonInfo(std::string name_, RandomizerHintTextKey hintKey_, RandomizerGet map_, RandomizerGet compass_, - RandomizerGet smallKey_, RandomizerGet keyRing_, RandomizerGet bossKey_, RandomizerArea area_, - uint8_t vanillaKeyCount_, uint8_t mqKeyCount_, RandomizerSettingKey mqSetting_); + RandomizerGet smallKey_, RandomizerGet keyRing_, RandomizerGet bossKey_, RandomizerGet reward_, + RandomizerArea area_, uint8_t vanillaKeyCount_, uint8_t mqKeyCount_, RandomizerSettingKey mqSetting_); DungeonInfo(); ~DungeonInfo(); @@ -32,6 +32,7 @@ class DungeonInfo { RandomizerGet GetMap() const; RandomizerGet GetCompass() const; RandomizerGet GetBossKey() const; + RandomizerGet GetReward() const; RandomizerSettingKey GetMQSetting() const; void SetDungeonKnown(bool known); void PlaceVanillaMap() const; @@ -50,6 +51,7 @@ class DungeonInfo { RandomizerGet smallKey; RandomizerGet keyRing; RandomizerGet bossKey; + RandomizerGet reward; RandomizerSettingKey mqSetting; bool isDungeonModeKnown = true; uint8_t vanillaKeyCount{}; @@ -94,4 +96,4 @@ class Dungeons { private: std::array dungeonList; }; -} // namespace Rando \ No newline at end of file +} // namespace Rando diff --git a/soh/soh/Enhancements/randomizer/option_descriptions.cpp b/soh/soh/Enhancements/randomizer/option_descriptions.cpp index d2c18d660..95481e9f6 100644 --- a/soh/soh/Enhancements/randomizer/option_descriptions.cpp +++ b/soh/soh/Enhancements/randomizer/option_descriptions.cpp @@ -804,6 +804,16 @@ void Settings::CreateOptionDescriptions() { "of 20. The second one will upgrade this capacity to 30, and the final one will upgrade the capacity to the " "usual 50.\n\n" "Bombchu Bowling is opened by obtaining the first Bombchu bag."; + mOptionDescriptions[RSK_LINKS_POCKET] = + "Dungeon Reward - Link will start with a Spiritual Stone or Medallion, and specific options will open up\n\n" + "Advancement - Link will start with a useful item.\n\n" + "Anything - Link will start with a random item.\n\n" + "Nothing - Link will not start with a bonus item."; + mOptionDescriptions[RSK_LINKS_POCKET_REWARD] = + "Any Reward - Link starts with a random Spiritual Stone or Medallion\n\n" + "Stone - Link starts with a random Spiritual Stone.\n\n" + "Any Medallion - Link starts with a random Medallion.\n\n" + "Light Medallion - Link starts with the Light Medallion."; mOptionDescriptions[RSK_ENABLE_BOMBCHU_DROPS] = "Once you obtain a Bombchu Bag, refills will sometimes replace " "Bomb drops that would spawn." "\n" diff --git a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerOptions.h b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerOptions.h index b81225cb2..e38d32293 100644 --- a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerOptions.h +++ b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerOptions.h @@ -189,6 +189,7 @@ RANDO_ENUM_END(RandoOptionDungeonItemLocation) RANDO_ENUM_BEGIN(RandoOptionDungeonRewards) RANDO_ENUM_ITEM(RO_DUNGEON_REWARDS_VANILLA) RANDO_ENUM_ITEM(RO_DUNGEON_REWARDS_END_OF_DUNGEON) +RANDO_ENUM_ITEM(RO_DUNGEON_REWARDS_OWN_DUNGEON) RANDO_ENUM_ITEM(RO_DUNGEON_REWARDS_ANY_DUNGEON) RANDO_ENUM_ITEM(RO_DUNGEON_REWARDS_OVERWORLD) RANDO_ENUM_ITEM(RO_DUNGEON_REWARDS_ANYWHERE) @@ -415,9 +416,10 @@ 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_ITEM(RO_LINKS_POCKET_ANY_REWARD) +RANDO_ENUM_ITEM(RO_LINKS_POCKET_ANY_STONE) +RANDO_ENUM_ITEM(RO_LINKS_POCKET_ANY_MEDALLION) +RANDO_ENUM_ITEM(RO_LINKS_POCKET_LIGHT_MEDALLION) RANDO_ENUM_END(RandoOptionLinksPocketReward) // Logic (glitchless/no logic) diff --git a/soh/soh/Enhancements/randomizer/settings.cpp b/soh/soh/Enhancements/randomizer/settings.cpp index 3b69fd14d..ad92fede4 100644 --- a/soh/soh/Enhancements/randomizer/settings.cpp +++ b/soh/soh/Enhancements/randomizer/settings.cpp @@ -564,7 +564,7 @@ void Settings::CreateOptions() { OPT_U8(RSK_MQ_ICE_CAVERN, "Ice Cavern Quest", {"Vanilla", "Master Quest", "Random"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("MQDungeonsIceCavern"), "", WIDGET_CVAR_COMBOBOX, RO_MQ_SET_VANILLA, false, nullptr, IMFLAG_NONE); OPT_U8(RSK_MQ_GTG, "Gerudo Training Ground Quest", {"Vanilla", "Master Quest", "Random"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("MQDungeonsGTG"), "", WIDGET_CVAR_COMBOBOX, RO_MQ_SET_VANILLA, false, nullptr, IMFLAG_NONE); OPT_U8(RSK_MQ_GANONS_CASTLE, "Ganon's Castle Quest", {"Vanilla", "Master Quest", "Random"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("MQDungeonsGanonsCastle"), "", WIDGET_CVAR_COMBOBOX, RO_MQ_SET_VANILLA); - OPT_U8(RSK_SHUFFLE_DUNGEON_REWARDS, "Shuffle Dungeon Rewards", {"Vanilla", "End of Dungeons", "Any Dungeon", "Overworld", "Anywhere"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleDungeonReward"), mOptionDescriptions[RSK_SHUFFLE_DUNGEON_REWARDS], WIDGET_CVAR_COMBOBOX, RO_DUNGEON_REWARDS_END_OF_DUNGEON); + OPT_U8(RSK_SHUFFLE_DUNGEON_REWARDS, "Shuffle Dungeon Rewards", {"Vanilla", "End of Dungeons", "Own Dungeon", "Any Dungeon", "Overworld", "Anywhere"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleDungeonReward"), mOptionDescriptions[RSK_SHUFFLE_DUNGEON_REWARDS], WIDGET_CVAR_COMBOBOX, RO_DUNGEON_REWARDS_END_OF_DUNGEON); OPT_CALLBACK(RSK_SHUFFLE_DUNGEON_REWARDS, { // Link's Pocket - Disabled when Dungeon Rewards are shuffled to End of Dungeon if (CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleDungeonReward"), RO_DUNGEON_REWARDS_END_OF_DUNGEON) == @@ -573,32 +573,39 @@ void Settings::CreateOptions() { "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("ShuffleDungeonReward"), RO_DUNGEON_REWARDS_END_OF_DUNGEON) == + RO_DUNGEON_REWARDS_OWN_DUNGEON) { + mOptions[RSK_LINKS_POCKET].Enable(); + mOptions[RSK_LINKS_POCKET_REWARD].Disable( + "As \"Link's Pocket\" is set to \"Dungeon Reward\" while \"Dungeon Rewards\" is set to \"Own Dungeon\", Link's Pocket will always have the Light Medallion"); + }else if (CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleDungeonReward"), RO_DUNGEON_REWARDS_END_OF_DUNGEON) == + RO_DUNGEON_REWARDS_VANILLA) { + mOptions[RSK_LINKS_POCKET].Enable(); + mOptions[RSK_LINKS_POCKET_REWARD].Disable( + "As \"Link's Pocket\" is set to \"Dungeon Reward\" while \"Dungeon Rewards\" is set to \"Vanilla\", Link's Pocket will always have the Light Medallion"); + } 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(); - } + } else { + mOptions[RSK_LINKS_POCKET_REWARD].Hide(); + } } }); - 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_U8(RSK_LINKS_POCKET, "Link's Pocket", {"Dungeon Reward", "Advancement", "Anything", "Nothing"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("LinksPocket"), mOptionDescriptions[RSK_LINKS_POCKET], 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) { + 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_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_LINKS_POCKET_REWARD, "Link's Pocket Reward Type", {"Any Reward", "Any Stone", "Any Medallion", "Light Medallion"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("LinksPocketReward"), mOptionDescriptions[RSK_LINKS_POCKET_REWARD], WIDGET_CVAR_COMBOBOX, RO_LINKS_POCKET_ANY_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, { @@ -2519,6 +2526,13 @@ void Context::FinalizeSettings(const std::set& excludedLocation if (mOptions[RSK_SHUFFLE_DUNGEON_REWARDS].Is(RO_DUNGEON_REWARDS_END_OF_DUNGEON)) { mOptions[RSK_LINKS_POCKET].Set(RO_LINKS_POCKET_DUNGEON_REWARD); + } else if (mOptions[RSK_SHUFFLE_DUNGEON_REWARDS].Is(RO_DUNGEON_REWARDS_OWN_DUNGEON) || + mOptions[RSK_SHUFFLE_DUNGEON_REWARDS].Is(RO_DUNGEON_REWARDS_VANILLA)) { + mOptions[RSK_LINKS_POCKET_REWARD].Set(RO_LINKS_POCKET_LIGHT_MEDALLION); + } + + if (mOptions[RSK_LINKS_POCKET].IsNot(RO_LINKS_POCKET_DUNGEON_REWARD)) { + mOptions[RSK_LINKS_POCKET_REWARD].Set(RO_LINKS_POCKET_ANY_REWARD); } for (const auto locationKey : this->everyPossibleLocation) {