From db91fd37e18979bfd5d3f56f161dc4ae403834c4 Mon Sep 17 00:00:00 2001 From: Pepper0ni <93387759+Pepper0ni@users.noreply.github.com> Date: Sat, 3 Jan 2026 18:42:26 +0000 Subject: [PATCH] Refactor GenerateItemPool and Ice Trap settings (#5773) Refactors the item pool to fix numerous bugs, especially with Plentiful item pools, makes ship exclusive items affected by item pool, and changes ice trap settings to be more clear and consistent to the user. --- .../Enhancements/randomizer/3drando/fill.cpp | 82 +- .../randomizer/3drando/item_pool.cpp | 1505 ++++++----------- .../randomizer/3drando/item_pool.hpp | 2 +- .../Enhancements/randomizer/3drando/shops.cpp | 388 ++++- .../Enhancements/randomizer/3drando/shops.hpp | 2 +- .../randomizer/3drando/starting_inventory.cpp | 27 - .../Enhancements/randomizer/SeedContext.cpp | 20 +- soh/soh/Enhancements/randomizer/SeedContext.h | 4 +- soh/soh/Enhancements/randomizer/entrance.cpp | 2 +- .../randomizer/location_access/root.cpp | 2 + .../randomizer/option_descriptions.cpp | 24 +- .../Enhancements/randomizer/randomizerTypes.h | 12 +- soh/soh/Enhancements/randomizer/savefile.cpp | 4 + soh/soh/Enhancements/randomizer/settings.cpp | 12 +- .../Enhancements/randomizer/static_data.cpp | 54 + soh/soh/Enhancements/randomizer/static_data.h | 3 + 16 files changed, 1064 insertions(+), 1079 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/3drando/fill.cpp b/soh/soh/Enhancements/randomizer/3drando/fill.cpp index b6a564c73..0bbe37bae 100644 --- a/soh/soh/Enhancements/randomizer/3drando/fill.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/fill.cpp @@ -60,29 +60,6 @@ PriceSettingsStruct merchantPrices = { RSK_MERCHANT_PRICES_AFFORDABLE, }; -static void RemoveStartingItemsFromPool() { - for (RandomizerGet startingItem : StartingInventory) { - for (size_t i = 0; i < ItemPool.size(); i++) { - if (startingItem == RG_BIGGORON_SWORD) { - if (ItemPool[i] == RG_GIANTS_KNIFE || ItemPool[i] == RG_BIGGORON_SWORD) { - ItemPool[i] = GetJunkItem(); - } - continue; - } else if (startingItem == ItemPool[i] || (Rando::StaticData::RetrieveItem(startingItem).IsBottleItem() && - Rando::StaticData::RetrieveItem(ItemPool[i]).IsBottleItem())) { - if (AdditionalHeartContainers > 0 && - (startingItem == RG_PIECE_OF_HEART || startingItem == RG_TREASURE_GAME_HEART)) { - ItemPool[i] = RG_HEART_CONTAINER; - AdditionalHeartContainers--; - } else { - ItemPool[i] = GetJunkItem(); - } - break; - } - } - } -} - static void PropagateTimeTravel(GetAccessibleLocationsStruct& gals, RandomizerGet ignore = RG_NONE, bool stopOnBeatable = false, bool addToPlaythrough = false) { // special check for temple of time @@ -163,7 +140,7 @@ static void ValidateOtherEntrance(GetAccessibleLocationsStruct& gals) { // Apply all items that are necessary for checking all location access static void ApplyAllAdvancmentItems() { std::vector itemsToPlace = - FilterFromPool(ItemPool, [](const auto i) { return Rando::StaticData::RetrieveItem(i).IsAdvancement(); }); + FilterFromPool(itemPool, [](const auto i) { return Rando::StaticData::RetrieveItem(i).IsAdvancement(); }); for (RandomizerGet unplacedItem : itemsToPlace) { Rando::StaticData::RetrieveItem(unplacedItem).ApplyEffect(); } @@ -321,7 +298,7 @@ bool IsBombchus(RandomizerGet item, bool includeShops = false) { } bool IsBeatableWithout(RandomizerCheck excludedCheck, bool replaceItem, - RandomizerGet ignore = RG_NONE) { // RANDOTODO make excludCheck an ItemLocation + RandomizerGet ignore = RG_NONE) { // RANDOTODO make excludedCheck an ItemLocation auto ctx = Rando::Context::GetInstance(); RandomizerGet copy = ctx->GetItemLocation(excludedCheck)->GetPlacedRandomizerGet(); // Copy out item ctx->GetItemLocation(excludedCheck)->SetPlacedItem(RG_NONE); // Write in empty item @@ -890,7 +867,7 @@ static void AssumedFill(const std::vector& items, const std::vect // copy all not yet placed advancement items so that we can apply their effects for the fill algorithm std::vector itemsToNotPlace = - FilterFromPool(ItemPool, [](const auto i) { return Rando::StaticData::RetrieveItem(i).IsAdvancement(); }); + FilterFromPool(itemPool, [](const auto i) { return Rando::StaticData::RetrieveItem(i).IsAdvancement(); }); // shuffle the order of items to place Shuffle(itemsToPlace); @@ -978,7 +955,7 @@ static void RandomizeDungeonRewards() { 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 - std::vector rewards = FilterAndEraseFromPool(ItemPool, [](const auto i) { + std::vector rewards = FilterAndEraseFromPool(itemPool, [](const auto i) { return Rando::StaticData::RetrieveItem(i).GetItemType() == ITEMTYPE_DUNGEONREWARD; }); @@ -1000,7 +977,7 @@ static void RandomizeDungeonRewards() { } } else if (ctx->GetOption(RSK_LINKS_POCKET).Is(RO_LINKS_POCKET_DUNGEON_REWARD)) { // get 1 stone/medallion - std::vector rewards = FilterFromPool(ItemPool, [](const auto i) { + 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 @@ -1014,7 +991,7 @@ static void RandomizeDungeonRewards() { // baseOffset]; ctx->PlaceItemInLocation(RC_LINKS_POCKET, startingReward); // erase the stone/medallion from the Item Pool - FilterAndEraseFromPool(ItemPool, [startingReward](const RandomizerGet i) { return i == startingReward; }); + FilterAndEraseFromPool(itemPool, [startingReward](const RandomizerGet i) { return i == startingReward; }); } } @@ -1059,7 +1036,7 @@ static void RandomizeOwnDungeon(const Rando::DungeonInfo* dungeon) { // Add specific items that need be randomized within this dungeon if (ctx->GetOption(RSK_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_OWN_DUNGEON) && dungeon->GetSmallKey() != RG_NONE) { std::vector dungeonSmallKeys = - FilterAndEraseFromPool(ItemPool, [dungeon](const RandomizerGet i) { + FilterAndEraseFromPool(itemPool, [dungeon](const RandomizerGet i) { return (i == dungeon->GetSmallKey()) || (i == dungeon->GetKeyRing()); }); AddElementsToPool(dungeonItems, dungeonSmallKeys); @@ -1070,7 +1047,7 @@ static void RandomizeOwnDungeon(const Rando::DungeonInfo* dungeon) { (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_OWN_DUNGEON) && dungeon->GetBossKey() == RG_GANONS_CASTLE_BOSS_KEY)) { auto dungeonBossKey = - FilterAndEraseFromPool(ItemPool, [dungeon](const RandomizerGet i) { return i == dungeon->GetBossKey(); }); + FilterAndEraseFromPool(itemPool, [dungeon](const RandomizerGet i) { return i == dungeon->GetBossKey(); }); AddElementsToPool(dungeonItems, dungeonBossKey); } @@ -1080,7 +1057,7 @@ static void RandomizeOwnDungeon(const Rando::DungeonInfo* dungeon) { // randomize map and compass separately since they're not progressive if (ctx->GetOption(RSK_SHUFFLE_MAPANDCOMPASS).Is(RO_DUNGEON_ITEM_LOC_OWN_DUNGEON) && dungeon->GetMap() != RG_NONE && dungeon->GetCompass() != RG_NONE) { - auto dungeonMapAndCompass = FilterAndEraseFromPool(ItemPool, [dungeon](const RandomizerGet i) { + auto dungeonMapAndCompass = FilterAndEraseFromPool(itemPool, [dungeon](const RandomizerGet i) { return i == dungeon->GetMap() || i == dungeon->GetCompass(); }); AssumedFill(dungeonMapAndCompass, dungeonLocations); @@ -1107,12 +1084,12 @@ static void RandomizeDungeonItems() { for (auto dungeon : ctx->GetDungeons()->GetDungeonList()) { if (ctx->GetOption(RSK_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_ANY_DUNGEON)) { - auto dungeonKeys = FilterAndEraseFromPool(ItemPool, [dungeon](const RandomizerGet i) { + auto dungeonKeys = FilterAndEraseFromPool(itemPool, [dungeon](const RandomizerGet i) { return (i == dungeon->GetSmallKey()) || (i == dungeon->GetKeyRing()); }); AddElementsToPool(anyDungeonItems, dungeonKeys); } else if (ctx->GetOption(RSK_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_OVERWORLD)) { - auto dungeonKeys = FilterAndEraseFromPool(ItemPool, [dungeon](const RandomizerGet i) { + auto dungeonKeys = FilterAndEraseFromPool(itemPool, [dungeon](const RandomizerGet i) { return (i == dungeon->GetSmallKey()) || (i == dungeon->GetKeyRing()); }); AddElementsToPool(overworldItems, dungeonKeys); @@ -1121,45 +1098,45 @@ static void RandomizeDungeonItems() { if (ctx->GetOption(RSK_BOSS_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_ANY_DUNGEON) && dungeon->GetBossKey() != RG_GANONS_CASTLE_BOSS_KEY) { auto bossKey = FilterAndEraseFromPool( - ItemPool, [dungeon](const RandomizerGet i) { return i == dungeon->GetBossKey(); }); + itemPool, [dungeon](const RandomizerGet i) { return i == dungeon->GetBossKey(); }); AddElementsToPool(anyDungeonItems, bossKey); } else if (ctx->GetOption(RSK_BOSS_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_OVERWORLD) && dungeon->GetBossKey() != RG_GANONS_CASTLE_BOSS_KEY) { auto bossKey = FilterAndEraseFromPool( - ItemPool, [dungeon](const RandomizerGet i) { return i == dungeon->GetBossKey(); }); + itemPool, [dungeon](const RandomizerGet i) { return i == dungeon->GetBossKey(); }); AddElementsToPool(overworldItems, bossKey); } if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_ANY_DUNGEON)) { auto ganonBossKey = - FilterAndEraseFromPool(ItemPool, [](const auto i) { return i == RG_GANONS_CASTLE_BOSS_KEY; }); + FilterAndEraseFromPool(itemPool, [](const auto i) { return i == RG_GANONS_CASTLE_BOSS_KEY; }); AddElementsToPool(anyDungeonItems, ganonBossKey); } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_OVERWORLD)) { auto ganonBossKey = - FilterAndEraseFromPool(ItemPool, [](const auto i) { return i == RG_GANONS_CASTLE_BOSS_KEY; }); + FilterAndEraseFromPool(itemPool, [](const auto i) { return i == RG_GANONS_CASTLE_BOSS_KEY; }); AddElementsToPool(overworldItems, ganonBossKey); } } if (ctx->GetOption(RSK_GERUDO_KEYS).Is(RO_GERUDO_KEYS_ANY_DUNGEON)) { - auto gerudoKeys = FilterAndEraseFromPool(ItemPool, [](const auto i) { + auto gerudoKeys = FilterAndEraseFromPool(itemPool, [](const auto i) { return i == RG_GERUDO_FORTRESS_SMALL_KEY || i == RG_GERUDO_FORTRESS_KEY_RING; }); AddElementsToPool(anyDungeonItems, gerudoKeys); } else if (ctx->GetOption(RSK_GERUDO_KEYS).Is(RO_GERUDO_KEYS_OVERWORLD)) { - auto gerudoKeys = FilterAndEraseFromPool(ItemPool, [](const auto i) { + auto gerudoKeys = FilterAndEraseFromPool(itemPool, [](const auto i) { return i == RG_GERUDO_FORTRESS_SMALL_KEY || i == RG_GERUDO_FORTRESS_KEY_RING; }); AddElementsToPool(overworldItems, gerudoKeys); } if (ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Is(RO_DUNGEON_REWARDS_ANY_DUNGEON)) { - auto rewards = FilterAndEraseFromPool(ItemPool, [](const auto i) { + auto rewards = FilterAndEraseFromPool(itemPool, [](const auto i) { return Rando::StaticData::RetrieveItem(i).GetItemType() == ITEMTYPE_DUNGEONREWARD; }); AddElementsToPool(anyDungeonItems, rewards); } else if (ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Is(RO_DUNGEON_REWARDS_OVERWORLD)) { - auto rewards = FilterAndEraseFromPool(ItemPool, [](const auto i) { + auto rewards = FilterAndEraseFromPool(itemPool, [](const auto i) { return Rando::StaticData::RetrieveItem(i).GetItemType() == ITEMTYPE_DUNGEONREWARD; }); AddElementsToPool(overworldItems, rewards); @@ -1172,12 +1149,12 @@ static void RandomizeDungeonItems() { // Randomize maps and compasses after since they're not advancement items for (auto dungeon : ctx->GetDungeons()->GetDungeonList()) { if (ctx->GetOption(RSK_SHUFFLE_MAPANDCOMPASS).Is(RO_DUNGEON_ITEM_LOC_ANY_DUNGEON)) { - auto mapAndCompassItems = FilterAndEraseFromPool(ItemPool, [dungeon](const RandomizerGet i) { + auto mapAndCompassItems = FilterAndEraseFromPool(itemPool, [dungeon](const RandomizerGet i) { return i == dungeon->GetMap() || i == dungeon->GetCompass(); }); AssumedFill(mapAndCompassItems, anyDungeonLocations, true); } else if (ctx->GetOption(RSK_SHUFFLE_MAPANDCOMPASS).Is(RO_DUNGEON_ITEM_LOC_OVERWORLD)) { - auto mapAndCompassItems = FilterAndEraseFromPool(ItemPool, [dungeon](const RandomizerGet i) { + auto mapAndCompassItems = FilterAndEraseFromPool(itemPool, [dungeon](const RandomizerGet i) { return i == dungeon->GetMap() || i == dungeon->GetCompass(); }); AssumedFill(mapAndCompassItems, ctx->overworldLocations, true); @@ -1189,14 +1166,14 @@ static void RandomizeLinksPocket() { auto ctx = Rando::Context::GetInstance(); if (ctx->GetOption(RSK_LINKS_POCKET).Is(RO_LINKS_POCKET_ADVANCEMENT)) { // Get all the advancement items don't include tokens - std::vector advancementItems = FilterAndEraseFromPool(ItemPool, [](const auto i) { + std::vector advancementItems = FilterAndEraseFromPool(itemPool, [](const auto i) { return Rando::StaticData::RetrieveItem(i).IsAdvancement() && Rando::StaticData::RetrieveItem(i).GetItemType() != ITEMTYPE_TOKEN; }); // select a random one RandomizerGet startingItem = RandomElement(advancementItems, true); // add the others back - AddElementsToPool(ItemPool, advancementItems); + AddElementsToPool(itemPool, advancementItems); ctx->PlaceItemInLocation(RC_LINKS_POCKET, startingItem); } else if (ctx->GetOption(RSK_LINKS_POCKET).Is(RO_LINKS_POCKET_NOTHING)) { @@ -1248,13 +1225,12 @@ int Fill() { ctx->GenerateLocationPool(); GenerateItemPool(); GenerateStartingInventory(); - RemoveStartingItemsFromPool(); FillExcludedLocations(); - // Temporarily add shop items to the ItemPool so that entrance randomization + // Temporarily add shop items to the itemPool so that entrance randomization // can validate the world using deku/hylian shields StartPerformanceTimer(PT_ENTRANCE_SHUFFLE); - AddElementsToPool(ItemPool, GetMinVanillaShopItems(8)); // assume worst case shopsanity 7 + AddElementsToPool(itemPool, GetMinVanillaShopItems(8)); // assume worst case shopsanity 7 if (ctx->GetOption(RSK_SHUFFLE_ENTRANCES)) { SPDLOG_INFO("Shuffling Entrances..."); if (ctx->GetEntranceShuffler()->ShuffleAllEntrances() == ENTRANCE_SHUFFLE_FAILURE) { @@ -1266,7 +1242,7 @@ int Fill() { } SetAreas(); // erase temporary shop items - FilterAndEraseFromPool(ItemPool, [](const auto item) { + FilterAndEraseFromPool(itemPool, [](const auto item) { return Rando::StaticData::RetrieveItem(item).GetItemType() == ITEMTYPE_SHOP; }); StopPerformanceTimer(PT_ENTRANCE_SHUFFLE); @@ -1378,7 +1354,7 @@ int Fill() { if (ctx->GetOption(RSK_SHUFFLE_SONGS).IsNot(RO_SONG_SHUFFLE_ANYWHERE) && ctx->GetOption(RSK_SHUFFLE_SONGS).IsNot(RO_SONG_SHUFFLE_OFF)) { // Get each song - std::vector songs = FilterAndEraseFromPool(ItemPool, [](const auto i) { + std::vector songs = FilterAndEraseFromPool(itemPool, [](const auto i) { return Rando::StaticData::RetrieveItem(i).GetItemType() == ITEMTYPE_SONG; }); @@ -1411,14 +1387,14 @@ int Fill() { SPDLOG_INFO("Shuffling Advancement Items"); // Then place the rest of the advancement items std::vector remainingAdvancementItems = FilterAndEraseFromPool( - ItemPool, [](const auto i) { return Rando::StaticData::RetrieveItem(i).IsAdvancement(); }); + itemPool, [](const auto i) { return Rando::StaticData::RetrieveItem(i).IsAdvancement(); }); AssumedFill(remainingAdvancementItems, ctx->allLocations, true); StopPerformanceTimer(PT_ADVANCEMENT_ITEMS); StartPerformanceTimer(PT_REMAINING_ITEMS); // Fast fill for the rest of the pool SPDLOG_INFO("Shuffling Remaining Items"); - std::vector remainingPool = FilterAndEraseFromPool(ItemPool, [](const auto i) { return true; }); + std::vector remainingPool = FilterAndEraseFromPool(itemPool, [](const auto i) { return true; }); FastFill(remainingPool, GetAllEmptyLocations(), false); StopPerformanceTimer(PT_REMAINING_ITEMS); diff --git a/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp b/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp index 5e02c004f..710869d81 100644 --- a/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp @@ -10,340 +10,79 @@ #include "z64item.h" #include -std::vector ItemPool = {}; -std::vector PendingJunkPool = {}; -const std::array dungeonRewards = { - RG_KOKIRI_EMERALD, RG_GORON_RUBY, RG_ZORA_SAPPHIRE, RG_FOREST_MEDALLION, RG_FIRE_MEDALLION, - RG_WATER_MEDALLION, RG_SPIRIT_MEDALLION, RG_SHADOW_MEDALLION, RG_LIGHT_MEDALLION, +std::vector itemPool = {}; +std::vector lesserPool = {}; +std::vector plentifulPool = {}; +std::vector junkPool = {}; +const std::array JunkPoolItems = { + RG_BOMBS_5, RG_BOMBS_10, RG_BOMBS_20, RG_DEKU_NUTS_5, RG_DEKU_STICK_1, RG_DEKU_SEEDS_30, RG_RECOVERY_HEART, + RG_ARROWS_5, RG_ARROWS_10, RG_ARROWS_30, RG_BLUE_RUPEE, RG_RED_RUPEE, RG_DEKU_NUTS_10, }; -const std::array JunkPoolItems = { - RG_BOMBS_5, RG_BOMBS_10, RG_BOMBS_20, RG_DEKU_NUTS_5, RG_DEKU_STICK_1, RG_DEKU_SEEDS_30, - RG_RECOVERY_HEART, RG_ARROWS_5, RG_ARROWS_10, RG_ARROWS_30, RG_BLUE_RUPEE, RG_RED_RUPEE, - RG_PURPLE_RUPEE, RG_HUGE_RUPEE, RG_DEKU_NUTS_10, RG_ICE_TRAP, -}; -const std::array alwaysItems = { - RG_BIGGORON_SWORD, - RG_BOOMERANG, - RG_LENS_OF_TRUTH, - RG_MEGATON_HAMMER, - RG_IRON_BOOTS, - RG_GORON_TUNIC, - RG_ZORA_TUNIC, - RG_HOVER_BOOTS, - RG_MIRROR_SHIELD, - RG_STONE_OF_AGONY, - RG_FIRE_ARROWS, - RG_ICE_ARROWS, - RG_LIGHT_ARROWS, - RG_DINS_FIRE, - RG_FARORES_WIND, - RG_NAYRUS_LOVE, - RG_GREG_RUPEE, - RG_PROGRESSIVE_HOOKSHOT, // 2 progressive hookshots - RG_PROGRESSIVE_HOOKSHOT, - RG_DEKU_SHIELD, - RG_HYLIAN_SHIELD, - RG_PROGRESSIVE_STRENGTH, // 3 progressive strength upgrades - RG_PROGRESSIVE_STRENGTH, - RG_PROGRESSIVE_STRENGTH, - RG_PROGRESSIVE_SCALE, // 2 progressive scales - RG_PROGRESSIVE_SCALE, - RG_PROGRESSIVE_BOW, // 3 progressive Bows - RG_PROGRESSIVE_BOW, - RG_PROGRESSIVE_BOW, - RG_PROGRESSIVE_SLINGSHOT, // 3 progressive bullet bags - RG_PROGRESSIVE_SLINGSHOT, - RG_PROGRESSIVE_SLINGSHOT, - RG_PROGRESSIVE_BOMB_BAG, // 3 progressive bomb bags - RG_PROGRESSIVE_BOMB_BAG, - RG_PROGRESSIVE_BOMB_BAG, - RG_PROGRESSIVE_WALLET, // 2 progressive wallets - RG_PROGRESSIVE_WALLET, - RG_PROGRESSIVE_MAGIC_METER, // 2 progressive magic meters - RG_PROGRESSIVE_MAGIC_METER, - RG_DOUBLE_DEFENSE, - RG_PROGRESSIVE_STICK_UPGRADE, // 2 stick upgrades - RG_PROGRESSIVE_STICK_UPGRADE, - RG_PROGRESSIVE_NUT_UPGRADE, // 2 nut upgrades - RG_PROGRESSIVE_NUT_UPGRADE, - RG_RECOVERY_HEART, // 6 recovery hearts - RG_RECOVERY_HEART, - RG_RECOVERY_HEART, - RG_RECOVERY_HEART, - RG_RECOVERY_HEART, - RG_RECOVERY_HEART, - RG_BOMBS_5, // 2 - RG_BOMBS_5, - RG_BOMBS_10, - RG_BOMBS_20, - RG_ARROWS_5, - RG_ARROWS_10, // 5 - RG_ARROWS_10, - RG_ARROWS_10, - RG_TREASURE_GAME_HEART, -}; -const std::array easyItems = { - RG_BIGGORON_SWORD, - RG_KOKIRI_SWORD, - RG_MASTER_SWORD, - RG_BOOMERANG, - RG_LENS_OF_TRUTH, - RG_MEGATON_HAMMER, - RG_IRON_BOOTS, - RG_GORON_TUNIC, - RG_ZORA_TUNIC, - RG_HOVER_BOOTS, - RG_MIRROR_SHIELD, - RG_FIRE_ARROWS, - RG_LIGHT_ARROWS, - RG_DINS_FIRE, - RG_PROGRESSIVE_HOOKSHOT, - RG_PROGRESSIVE_STRENGTH, - RG_PROGRESSIVE_SCALE, - RG_PROGRESSIVE_WALLET, - RG_PROGRESSIVE_MAGIC_METER, - RG_PROGRESSIVE_STICK_UPGRADE, - RG_PROGRESSIVE_NUT_UPGRADE, - RG_PROGRESSIVE_BOW, - RG_PROGRESSIVE_SLINGSHOT, - RG_PROGRESSIVE_BOMB_BAG, - RG_DOUBLE_DEFENSE, - RG_HEART_CONTAINER, // 16 Heart Containers - RG_HEART_CONTAINER, - RG_HEART_CONTAINER, - RG_HEART_CONTAINER, - RG_HEART_CONTAINER, - RG_HEART_CONTAINER, - RG_HEART_CONTAINER, - RG_HEART_CONTAINER, - RG_HEART_CONTAINER, - RG_HEART_CONTAINER, - RG_HEART_CONTAINER, - RG_HEART_CONTAINER, - RG_HEART_CONTAINER, - RG_HEART_CONTAINER, - RG_HEART_CONTAINER, - RG_HEART_CONTAINER, - RG_PIECE_OF_HEART, // 3 heart pieces - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, -}; -const std::array normalItems = { - // 35 pieces of heart - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - RG_PIECE_OF_HEART, - // 8 heart containers - RG_HEART_CONTAINER, - RG_HEART_CONTAINER, - RG_HEART_CONTAINER, - RG_HEART_CONTAINER, - RG_HEART_CONTAINER, - RG_HEART_CONTAINER, - RG_HEART_CONTAINER, - RG_HEART_CONTAINER, -}; -const std::array DT_Vanilla = { - RG_RECOVERY_HEART, - RG_RECOVERY_HEART, -}; -const std::array DT_MQ = { - RG_DEKU_SHIELD, - RG_DEKU_SHIELD, - RG_PURPLE_RUPEE, -}; -const std::array DC_Vanilla = { - RG_RED_RUPEE, -}; -const std::array DC_MQ = { - RG_HYLIAN_SHIELD, - RG_BLUE_RUPEE, -}; -const std::array JB_MQ = { - RG_DEKU_NUTS_5, RG_DEKU_NUTS_5, RG_DEKU_NUTS_5, RG_DEKU_NUTS_5, RG_RECOVERY_HEART, RG_DEKU_SHIELD, RG_DEKU_STICK_1, -}; -const std::array FoT_Vanilla = { - RG_RECOVERY_HEART, - RG_ARROWS_10, - RG_ARROWS_30, -}; -const std::array FoT_MQ = { - RG_ARROWS_5, -}; -const std::array FiT_Vanilla = { - RG_HUGE_RUPEE, -}; -const std::array FiT_MQ = { - RG_BOMBS_20, - RG_HYLIAN_SHIELD, -}; -const std::array SpT_Vanilla = { - RG_DEKU_SHIELD, - RG_DEKU_SHIELD, - RG_RECOVERY_HEART, - RG_BOMBS_20, -}; -const std::array SpT_MQ = { - RG_PURPLE_RUPEE, - RG_PURPLE_RUPEE, - RG_ARROWS_30, -}; -const std::array ShT_Vanilla = { - RG_ARROWS_30, -}; -const std::array ShT_MQ = { - RG_ARROWS_5, - RG_ARROWS_5, - RG_RED_RUPEE, -}; -const std::array BW_Vanilla = { - RG_RECOVERY_HEART, RG_BOMBS_10, RG_HUGE_RUPEE, RG_DEKU_NUTS_5, RG_DEKU_NUTS_10, RG_DEKU_SHIELD, RG_HYLIAN_SHIELD, -}; -const std::array GTG_Vanilla = { - RG_ARROWS_30, - RG_ARROWS_30, - RG_ARROWS_30, - RG_HUGE_RUPEE, -}; -const std::array GTG_MQ = { - RG_TREASURE_GAME_GREEN_RUPEE, RG_TREASURE_GAME_GREEN_RUPEE, RG_ARROWS_10, RG_GREEN_RUPEE, RG_PURPLE_RUPEE, -}; -const std::array GC_Vanilla = { - RG_BLUE_RUPEE, - RG_BLUE_RUPEE, - RG_BLUE_RUPEE, - RG_ARROWS_30, -}; -const std::array GC_MQ = { - RG_ARROWS_10, RG_ARROWS_10, RG_BOMBS_5, RG_RED_RUPEE, RG_RECOVERY_HEART, -}; -const std::array normalBottles = { - RG_EMPTY_BOTTLE, - RG_BOTTLE_WITH_MILK, - RG_BOTTLE_WITH_RED_POTION, - RG_BOTTLE_WITH_GREEN_POTION, - RG_BOTTLE_WITH_BLUE_POTION, - RG_BOTTLE_WITH_FAIRY, - RG_BOTTLE_WITH_FISH, - RG_BOTTLE_WITH_BUGS, - RG_BOTTLE_WITH_POE, - RG_BOTTLE_WITH_BIG_POE, - RG_BOTTLE_WITH_BLUE_FIRE, -}; -const std::array normalRupees = { - RG_BLUE_RUPEE, RG_BLUE_RUPEE, RG_BLUE_RUPEE, RG_BLUE_RUPEE, RG_BLUE_RUPEE, RG_BLUE_RUPEE, - RG_BLUE_RUPEE, RG_BLUE_RUPEE, RG_BLUE_RUPEE, RG_BLUE_RUPEE, RG_BLUE_RUPEE, RG_BLUE_RUPEE, - RG_BLUE_RUPEE, RG_RED_RUPEE, RG_RED_RUPEE, RG_RED_RUPEE, RG_RED_RUPEE, RG_RED_RUPEE, - RG_PURPLE_RUPEE, RG_PURPLE_RUPEE, RG_PURPLE_RUPEE, RG_PURPLE_RUPEE, RG_PURPLE_RUPEE, RG_PURPLE_RUPEE, - RG_PURPLE_RUPEE, RG_HUGE_RUPEE, RG_HUGE_RUPEE, RG_HUGE_RUPEE, -}; -const std::array shopsanityRupees = { - RG_BLUE_RUPEE, RG_BLUE_RUPEE, RG_RED_RUPEE, RG_RED_RUPEE, RG_RED_RUPEE, RG_RED_RUPEE, - RG_RED_RUPEE, RG_RED_RUPEE, RG_RED_RUPEE, RG_RED_RUPEE, RG_RED_RUPEE, RG_RED_RUPEE, - RG_PURPLE_RUPEE, RG_PURPLE_RUPEE, RG_PURPLE_RUPEE, RG_PURPLE_RUPEE, RG_PURPLE_RUPEE, RG_PURPLE_RUPEE, - RG_PURPLE_RUPEE, RG_PURPLE_RUPEE, RG_PURPLE_RUPEE, RG_PURPLE_RUPEE, RG_HUGE_RUPEE, RG_HUGE_RUPEE, - RG_HUGE_RUPEE, RG_HUGE_RUPEE, RG_HUGE_RUPEE, RG_HUGE_RUPEE, -}; -const std::array dekuScrubItems = { - RG_DEKU_NUTS_5, RG_DEKU_NUTS_5, RG_DEKU_NUTS_5, RG_DEKU_NUTS_5, RG_DEKU_NUTS_5, - RG_DEKU_STICK_1, RG_BOMBS_5, RG_BOMBS_5, RG_BOMBS_5, RG_BOMBS_5, - RG_BOMBS_5, RG_RECOVERY_HEART, RG_RECOVERY_HEART, RG_RECOVERY_HEART, RG_RECOVERY_HEART, - RG_BLUE_RUPEE, RG_BLUE_RUPEE, RG_BLUE_RUPEE, RG_BLUE_RUPEE, -}; -const std::array songList = { - RG_ZELDAS_LULLABY, RG_EPONAS_SONG, RG_SUNS_SONG, RG_SARIAS_SONG, - RG_SONG_OF_TIME, RG_SONG_OF_STORMS, RG_MINUET_OF_FOREST, RG_PRELUDE_OF_LIGHT, - RG_BOLERO_OF_FIRE, RG_SERENADE_OF_WATER, RG_NOCTURNE_OF_SHADOW, RG_REQUIEM_OF_SPIRIT, -}; -const std::array tradeItems = { - RG_POCKET_EGG, - // RG_POCKET_CUCCO, - RG_COJIRO, - RG_ODD_MUSHROOM, - RG_POACHERS_SAW, - RG_BROKEN_SWORD, - RG_PRESCRIPTION, - RG_EYEBALL_FROG, - RG_EYEDROPS, - RG_CLAIM_CHECK, +// RANDOTODO should probably check the same thing as check matches contents at some point +const std::map*> poolForItem = { + { RG_BOMBS_5, &junkPool }, { RG_BOMBS_10, &junkPool }, { RG_BOMBS_20, &junkPool }, + { RG_DEKU_NUTS_5, &junkPool }, { RG_DEKU_STICK_1, &junkPool }, { RG_DEKU_SEEDS_30, &junkPool }, + { RG_RECOVERY_HEART, &junkPool }, { RG_ARROWS_5, &junkPool }, { RG_ARROWS_10, &junkPool }, + { RG_ARROWS_30, &junkPool }, { RG_GREEN_RUPEE, &junkPool }, { RG_BLUE_RUPEE, &junkPool }, + { RG_RED_RUPEE, &junkPool }, { RG_DEKU_NUTS_10, &junkPool }, { RG_TREASURE_GAME_GREEN_RUPEE, &junkPool }, + { RG_PURPLE_RUPEE, &lesserPool }, { RG_HUGE_RUPEE, &lesserPool }, { RG_DEKU_SHIELD, &lesserPool }, + { RG_HYLIAN_SHIELD, &lesserPool }, { RG_BOMBCHU_5, &lesserPool }, { RG_BOMBCHU_10, &lesserPool }, + { RG_BOMBCHU_20, &lesserPool } }; -void AddItemToPool(std::vector& pool, RandomizerGet item, size_t count /*= 1*/) { - pool.insert(pool.end(), count, item); +void AddItemToPool(RandomizerGet item, int plentifulCount, size_t balancedCount, size_t scarceCount = 1, + size_t minimalCount = 1, bool iceTrapModel = true) { + int count = balancedCount; + switch (ctx->GetOption(RSK_ITEM_POOL).Get()) { + case RO_ITEM_POOL_SCARCE: + count = scarceCount; + break; + case RO_ITEM_POOL_MINIMAL: + count = minimalCount; + break; + default: + break; + } + if (!poolForItem.contains(item)) { + itemPool.insert(itemPool.end(), count, item); + if (ctx->GetOption(RSK_ITEM_POOL).Is(RO_ITEM_POOL_PLENTIFUL)) { + plentifulPool.insert(plentifulPool.end(), plentifulCount - count, item); + } + if (iceTrapModel && count > 0 && item != RG_ICE_TRAP) { + ctx->possibleIceTrapModels.insert(item); + } + } else { + poolForItem.at(item)->insert(poolForItem.at(item)->end(), count, item); + } } -template static void AddItemsToPool(std::vector& toPool, const FromPool& fromPool) { - AddElementsToPool(toPool, fromPool); -} - -static void AddItemToMainPool(const RandomizerGet item, size_t count = 1) { - ItemPool.insert(ItemPool.end(), count, item); -} - -static void AddRandomBottle(std::vector& bottlePool) { - AddItemToMainPool(RandomElement(bottlePool, true)); +void AddFixedItemToPool(RandomizerGet item, int count = 1, bool iceTrapModel = true) { + if (!poolForItem.contains(item)) { + itemPool.insert(itemPool.end(), count, item); + if (iceTrapModel && count > 0 && item != RG_ICE_TRAP) { + ctx->possibleIceTrapModels.insert(item); + } + } else { + poolForItem.at(item)->insert(poolForItem.at(item)->end(), count, item); + } } RandomizerGet GetJunkItem() { auto ctx = Rando::Context::GetInstance(); - if (ctx->GetOption(RSK_ICE_TRAPS).Is(RO_ICE_TRAPS_MAYHEM) || - ctx->GetOption(RSK_ICE_TRAPS).Is(RO_ICE_TRAPS_ONSLAUGHT)) { + if (ctx->GetOption(RSK_ICE_TRAP_PERCENT).IsNot(0) && + (ctx->GetOption(RSK_ICE_TRAP_PERCENT).Is(100) || Random(0, 100) < ctx->GetOption(RSK_ICE_TRAP_PERCENT).Get())) { return RG_ICE_TRAP; - } else if (ctx->GetOption(RSK_ICE_TRAPS).Is(RO_ICE_TRAPS_EXTRA)) { - return RandomElement(JunkPoolItems); } - // Ice Trap is the last item in JunkPoolItems, so subtract 1 to never hit that index - uint8_t idx = Random(0, static_cast(JunkPoolItems.size()) - 1); - return JunkPoolItems[idx]; -} - -static RandomizerGet GetPendingJunkItem() { - if (PendingJunkPool.empty()) { - return GetJunkItem(); - } - - return RandomElement(PendingJunkPool, true); + return RandomElement(JunkPoolItems); } // Replace junk items in the pool with pending junk static void ReplaceMaxItem(const RandomizerGet itemToReplace, int max) { int itemCount = 0; - for (size_t i = 0; i < ItemPool.size(); i++) { - if (ItemPool[i] == itemToReplace) { + for (size_t i = 0; i < itemPool.size(); i++) { + if (itemPool[i] == itemToReplace) { if (itemCount >= max) { - ItemPool[i] = RG_NONE; + itemPool[i] = RG_NONE; } itemCount++; } @@ -372,7 +111,7 @@ static void PlaceVanillaBossKeys() { } } -static void PlaceItemsForType(RandomizerCheckType rctype, bool overworldActive, bool dungeonActive) { +static void PlaceItemsForType(RandomizerCheckType rctype, bool overworldActive = true, bool dungeonActive = true) { if (!(overworldActive || dungeonActive)) { return; } @@ -382,14 +121,14 @@ static void PlaceItemsForType(RandomizerCheckType rctype, bool overworldActive, // If item is in the overworld and shuffled, add its item to the pool if (loc->IsOverworld()) { if (overworldActive) { - AddItemToMainPool(loc->GetVanillaItem()); + AddFixedItemToPool(loc->GetVanillaItem(), 1, false); } } else { if (dungeonActive) { // If the same in MQ and vanilla, add. RandomizerCheckQuest currentQuest = loc->GetQuest(); if (currentQuest == RCQUEST_BOTH) { - AddItemToMainPool(loc->GetVanillaItem()); + AddFixedItemToPool(loc->GetVanillaItem(), 1, false); } else { // Check if current item's dungeon is vanilla or MQ, and only add if quest corresponds to it. SceneID itemScene = loc->GetScene(); @@ -398,7 +137,7 @@ static void PlaceItemsForType(RandomizerCheckType rctype, bool overworldActive, bool isMQ = ctx->GetDungeon(itemScene)->IsMQ(); if ((isMQ && currentQuest == RCQUEST_MQ) || (!isMQ && currentQuest == RCQUEST_VANILLA)) { - AddItemToMainPool(loc->GetVanillaItem()); + AddFixedItemToPool(loc->GetVanillaItem(), 1, false); } } } @@ -407,111 +146,181 @@ static void PlaceItemsForType(RandomizerCheckType rctype, bool overworldActive, } } -static void SetScarceItemPool() { - ReplaceMaxItem(RG_PROGRESSIVE_BOMBCHU_BAG, ctx->GetOption(RSK_BOMBCHU_BAG).Is(RO_BOMBCHU_BAG_SINGLE) ? 3 : 2); - ReplaceMaxItem(RG_BOMBCHU_5, 1); - ReplaceMaxItem(RG_BOMBCHU_10, 2); - ReplaceMaxItem(RG_BOMBCHU_20, 0); - ReplaceMaxItem(RG_PROGRESSIVE_MAGIC_METER, 1); - ReplaceMaxItem(RG_DOUBLE_DEFENSE, 0); - ReplaceMaxItem(RG_PROGRESSIVE_STICK_UPGRADE, ctx->GetOption(RSK_SHUFFLE_DEKU_STICK_BAG) ? 2 : 1); - ReplaceMaxItem(RG_PROGRESSIVE_NUT_UPGRADE, ctx->GetOption(RSK_SHUFFLE_DEKU_NUT_BAG) ? 2 : 1); - ReplaceMaxItem(RG_PROGRESSIVE_BOW, 2); - ReplaceMaxItem(RG_PROGRESSIVE_SLINGSHOT, 2); - ReplaceMaxItem(RG_PROGRESSIVE_BOMB_BAG, 2); - ReplaceMaxItem(RG_HEART_CONTAINER, 0); -} - -static void SetMinimalItemPool() { - auto ctx = Rando::Context::GetInstance(); - ReplaceMaxItem(RG_PROGRESSIVE_BOMBCHU_BAG, 1); - ReplaceMaxItem(RG_BOMBCHU_5, 1); - ReplaceMaxItem(RG_BOMBCHU_10, 0); - ReplaceMaxItem(RG_BOMBCHU_20, 0); - ReplaceMaxItem(RG_NAYRUS_LOVE, 0); - ReplaceMaxItem(RG_PROGRESSIVE_MAGIC_METER, 1); - ReplaceMaxItem(RG_DOUBLE_DEFENSE, 0); - ReplaceMaxItem(RG_PROGRESSIVE_STICK_UPGRADE, ctx->GetOption(RSK_SHUFFLE_DEKU_STICK_BAG) ? 1 : 0); - ReplaceMaxItem(RG_PROGRESSIVE_NUT_UPGRADE, ctx->GetOption(RSK_SHUFFLE_DEKU_NUT_BAG) ? 1 : 0); - ReplaceMaxItem(RG_PROGRESSIVE_BOW, 1); - ReplaceMaxItem(RG_PROGRESSIVE_SLINGSHOT, 1); - ReplaceMaxItem(RG_PROGRESSIVE_BOMB_BAG, 1); - ReplaceMaxItem(RG_PIECE_OF_HEART, 0); - // Need an extra heart container when starting with 1 heart to be able to reach 3 hearts - ReplaceMaxItem(RG_HEART_CONTAINER, (ctx->GetOption(RSK_STARTING_HEARTS).Get() == 18) ? 1 : 0); -} - void GenerateItemPool() { // RANDOTODO proper removal of items not in pool or logically relevant instead of dummy checks. auto ctx = Rando::Context::GetInstance(); - ItemPool.clear(); - PendingJunkPool.clear(); + itemPool.clear(); + junkPool.clear(); + plentifulPool.clear(); + lesserPool.clear(); + int reservedSlots = 0; - // Initialize ice trap models to always major items - ctx->possibleIceTrapModels = { - RG_MIRROR_SHIELD, - RG_BOOMERANG, - RG_LENS_OF_TRUTH, - RG_MEGATON_HAMMER, - RG_IRON_BOOTS, - RG_HOVER_BOOTS, - RG_STONE_OF_AGONY, - RG_DINS_FIRE, - RG_FARORES_WIND, - RG_NAYRUS_LOVE, - RG_FIRE_ARROWS, - RG_ICE_ARROWS, - RG_LIGHT_ARROWS, - RG_DOUBLE_DEFENSE, - RG_CLAIM_CHECK, - RG_PROGRESSIVE_HOOKSHOT, - RG_PROGRESSIVE_STRENGTH, - RG_PROGRESSIVE_BOMB_BAG, - RG_PROGRESSIVE_BOW, - RG_PROGRESSIVE_SLINGSHOT, - RG_PROGRESSIVE_WALLET, - RG_PROGRESSIVE_SCALE, - RG_PROGRESSIVE_MAGIC_METER, - }; - // Check song shuffle and dungeon reward shuffle just for ice traps - if (ctx->GetOption(RSK_SHUFFLE_SONGS).Is(RO_SONG_SHUFFLE_ANYWHERE)) { - // Push item ids for songs - ctx->possibleIceTrapModels.push_back(RG_ZELDAS_LULLABY); - ctx->possibleIceTrapModels.push_back(RG_EPONAS_SONG); - ctx->possibleIceTrapModels.push_back(RG_SARIAS_SONG); - ctx->possibleIceTrapModels.push_back(RG_SUNS_SONG); - ctx->possibleIceTrapModels.push_back(RG_SONG_OF_TIME); - ctx->possibleIceTrapModels.push_back(RG_SONG_OF_STORMS); - ctx->possibleIceTrapModels.push_back(RG_MINUET_OF_FOREST); - ctx->possibleIceTrapModels.push_back(RG_BOLERO_OF_FIRE); - ctx->possibleIceTrapModels.push_back(RG_SERENADE_OF_WATER); - ctx->possibleIceTrapModels.push_back(RG_REQUIEM_OF_SPIRIT); - ctx->possibleIceTrapModels.push_back(RG_NOCTURNE_OF_SHADOW); - ctx->possibleIceTrapModels.push_back(RG_PRELUDE_OF_LIGHT); + // clang-format off + AddItemToPool(RG_BOOMERANG, 2, 1, 1, 1); + AddItemToPool(RG_LENS_OF_TRUTH, 2, 1, 1, 1); + AddItemToPool(RG_MEGATON_HAMMER, 2, 1, 1, 1); + AddItemToPool(RG_IRON_BOOTS, 2, 1, 1, 1); + AddItemToPool(RG_GORON_TUNIC, 2, 1, 1, 1); + AddItemToPool(RG_ZORA_TUNIC, 2, 1, 1, 1); + AddItemToPool(RG_HOVER_BOOTS, 2, 1, 1, 1); + AddItemToPool(RG_MIRROR_SHIELD, 2, 1, 1, 1); + AddItemToPool(RG_STONE_OF_AGONY, 2, 1, 1, 1); + AddItemToPool(RG_FIRE_ARROWS, 2, 1, 1, 1); + AddItemToPool(RG_ICE_ARROWS, 2, 1, 1, 1); + AddItemToPool(RG_LIGHT_ARROWS, 2, 1, 1, 1); + AddItemToPool(RG_DINS_FIRE, 2, 1, 1, 1); + AddItemToPool(RG_NAYRUS_LOVE, 2, 1, 0, 0); + AddItemToPool(RG_GREG_RUPEE, 1, 1, 1, 1); + AddItemToPool(RG_PROGRESSIVE_HOOKSHOT, 2, 2, 2, 2); + AddItemToPool(RG_HYLIAN_SHIELD, 1, 1, 1, 1); + AddItemToPool(RG_PROGRESSIVE_STRENGTH, 4, 3, 3, 3); + AddItemToPool(RG_DOUBLE_DEFENSE, 2, 1, 0, 0); + bool isScrubs = ctx->GetOption(RSK_SHUFFLE_SCRUBS).Is(RO_SCRUBS_ALL); + AddFixedItemToPool(RG_DEKU_SHIELD, isScrubs ? 1 : 2); + AddFixedItemToPool(RG_RECOVERY_HEART, isScrubs ? 6 : 11); + AddFixedItemToPool(RG_BOMBS_5, isScrubs ? 2 : 8); + AddFixedItemToPool(RG_DEKU_STICK_1, isScrubs ? 0 : 2); + AddFixedItemToPool(RG_BOMBS_10, 1); + AddFixedItemToPool(RG_BOMBS_20, 1); + AddFixedItemToPool(RG_ARROWS_5, 1); + AddFixedItemToPool(RG_ARROWS_10, 3); + + if (isScrubs) { + AddFixedItemToPool(RG_DEKU_NUTS_5, ctx->GetDungeon(Rando::JABU_JABUS_BELLY)->IsVanilla() ? 5 : 6); + // Scrubs which sell seeds or arrows sell it based on age, this randomly assigns them + for (uint8_t i = 0; i < 7; i++) { + if (Random(0, 2)) { + AddFixedItemToPool(RG_ARROWS_30); + } else { + AddFixedItemToPool(RG_DEKU_SEEDS_30); + } + } } - if (ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Is(RO_DUNGEON_REWARDS_ANYWHERE)) { - // Push item ids for dungeon rewards - ctx->possibleIceTrapModels.push_back(RG_KOKIRI_EMERALD); - ctx->possibleIceTrapModels.push_back(RG_GORON_RUBY); - ctx->possibleIceTrapModels.push_back(RG_ZORA_SAPPHIRE); - ctx->possibleIceTrapModels.push_back(RG_FOREST_MEDALLION); - ctx->possibleIceTrapModels.push_back(RG_FIRE_MEDALLION); - ctx->possibleIceTrapModels.push_back(RG_WATER_MEDALLION); - ctx->possibleIceTrapModels.push_back(RG_SPIRIT_MEDALLION); - ctx->possibleIceTrapModels.push_back(RG_SHADOW_MEDALLION); - ctx->possibleIceTrapModels.push_back(RG_LIGHT_MEDALLION); + + int infiniteProgressive = ctx->GetOption(RSK_INFINITE_UPGRADES).Is(RO_INF_UPGRADES_PROGRESSIVE) ? 1 : 0; + AddItemToPool(RG_PROGRESSIVE_BOW, 4 + infiniteProgressive, + 3 + infiniteProgressive, + 2 + infiniteProgressive, + 1 + infiniteProgressive); + AddItemToPool(RG_PROGRESSIVE_SLINGSHOT, 4 + infiniteProgressive, + 3 + infiniteProgressive, + 2 + infiniteProgressive, + 1 + infiniteProgressive); + AddItemToPool(RG_PROGRESSIVE_BOMB_BAG, 4 + infiniteProgressive, + 3 + infiniteProgressive, + 2 + infiniteProgressive, + 1 + infiniteProgressive); + AddItemToPool(RG_PROGRESSIVE_MAGIC_METER, 3 + infiniteProgressive, + 2 + infiniteProgressive, + 1 + infiniteProgressive, + 1 + infiniteProgressive); + //clang-format on + + int extraWallets =(ctx->GetOption(RSK_SHUFFLE_CHILD_WALLET) ? 1 : 0) + (ctx->GetOption(RSK_INCLUDE_TYCOON_WALLET) ? 1 : 0); + AddItemToPool(RG_PROGRESSIVE_WALLET, 3 + infiniteProgressive + extraWallets, + 2 + infiniteProgressive + extraWallets, + 2 + infiniteProgressive + extraWallets, + 2 + infiniteProgressive + extraWallets); + + int stickShuffle = ctx->GetOption(RSK_SHUFFLE_DEKU_STICK_BAG) ? 1 : 0; + AddItemToPool(RG_PROGRESSIVE_STICK_UPGRADE, 3 + infiniteProgressive + stickShuffle, + 2 + infiniteProgressive + stickShuffle, + 1 + infiniteProgressive + stickShuffle, + 0 + infiniteProgressive + stickShuffle); + + int nutShuffle = ctx->GetOption(RSK_SHUFFLE_DEKU_NUT_BAG) ? 1 : 0; + AddItemToPool(RG_PROGRESSIVE_NUT_UPGRADE, 3 + infiniteProgressive + nutShuffle, + 2 + infiniteProgressive + nutShuffle, + 1 + infiniteProgressive + nutShuffle, + 0 + infiniteProgressive + nutShuffle); + + if (ctx->GetOption(RSK_BOMBCHU_BAG).Is(RO_BOMBCHU_BAG_SINGLE)) { + AddItemToPool(RG_PROGRESSIVE_BOMBCHU_BAG, 6, 5, 3, 1); + } else if (ctx->GetOption(RSK_BOMBCHU_BAG).Is(RO_BOMBCHU_BAG_PROGRESSIVE)) { + AddItemToPool(RG_PROGRESSIVE_BOMBCHU_BAG, 4 + infiniteProgressive, + 3 + infiniteProgressive, + 2 + infiniteProgressive, + 1 + infiniteProgressive); + } else { + AddItemToPool(RG_BOMBCHU_20, 2, 1, 0, 0); + AddItemToPool(RG_BOMBCHU_10, 3, 3, 2, 0); + AddItemToPool(RG_BOMBCHU_5, 1, 1, 1, 1); } + // add extra songs only if song shuffle is anywhere + if (ctx->GetOption(RSK_SHUFFLE_SONGS).IsNot(RO_SONG_SHUFFLE_OFF)) { + bool songAnywhere = ctx->GetOption(RSK_SHUFFLE_SONGS).Is(RO_SONG_SHUFFLE_ANYWHERE); + if (!ctx->GetOption(RSK_STARTING_ZELDAS_LULLABY).Get()) { + AddItemToPool(RG_ZELDAS_LULLABY, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere); + } + if (!ctx->GetOption(RSK_STARTING_EPONAS_SONG).Get()) { + AddItemToPool(RG_EPONAS_SONG, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere); + } + if (!ctx->GetOption(RSK_STARTING_SARIAS_SONG).Get()) { + AddItemToPool(RG_SARIAS_SONG, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere); + } + if (!ctx->GetOption(RSK_STARTING_SUNS_SONG).Get()) { + AddItemToPool(RG_SUNS_SONG, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere); + } + if (!ctx->GetOption(RSK_STARTING_SONG_OF_TIME).Get()) { + AddItemToPool(RG_SONG_OF_TIME, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere); + } + if (!ctx->GetOption(RSK_STARTING_SONG_OF_STORMS).Get()) { + AddItemToPool(RG_SONG_OF_STORMS, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere); + } + if (!ctx->GetOption(RSK_STARTING_MINUET_OF_FOREST).Get()) { + AddItemToPool(RG_MINUET_OF_FOREST, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere); + } + if (!ctx->GetOption(RSK_STARTING_BOLERO_OF_FIRE).Get()) { + AddItemToPool(RG_BOLERO_OF_FIRE, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere); + } + if (!ctx->GetOption(RSK_STARTING_SERENADE_OF_WATER).Get()) { + AddItemToPool(RG_SERENADE_OF_WATER, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere); + } + if (!ctx->GetOption(RSK_STARTING_REQUIEM_OF_SPIRIT).Get()) { + AddItemToPool(RG_REQUIEM_OF_SPIRIT, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere); + } + if (!ctx->GetOption(RSK_STARTING_NOCTURNE_OF_SHADOW).Get()) { + AddItemToPool(RG_NOCTURNE_OF_SHADOW, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere); + } + if (!ctx->GetOption(RSK_STARTING_PRELUDE_OF_LIGHT).Get()) { + AddItemToPool(RG_PRELUDE_OF_LIGHT, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere); + } + } else { + ctx->PlaceItemInLocation(RC_SHEIK_IN_FOREST, RG_MINUET_OF_FOREST, false, true); + ctx->PlaceItemInLocation(RC_SHEIK_IN_CRATER, RG_BOLERO_OF_FIRE, false, true); + ctx->PlaceItemInLocation(RC_SHEIK_IN_ICE_CAVERN, RG_SERENADE_OF_WATER, false, true); + ctx->PlaceItemInLocation(RC_SHEIK_AT_COLOSSUS, RG_REQUIEM_OF_SPIRIT, false, true); + ctx->PlaceItemInLocation(RC_SHEIK_IN_KAKARIKO, RG_NOCTURNE_OF_SHADOW, false, true); + ctx->PlaceItemInLocation(RC_SHEIK_AT_TEMPLE, RG_PRELUDE_OF_LIGHT, false, true); + ctx->PlaceItemInLocation(RC_SONG_FROM_IMPA, RG_ZELDAS_LULLABY, false, true); + ctx->PlaceItemInLocation(RC_SONG_FROM_MALON, RG_EPONAS_SONG, false, true); + ctx->PlaceItemInLocation(RC_SONG_FROM_SARIA, RG_SARIAS_SONG, false, true); + ctx->PlaceItemInLocation(RC_SONG_FROM_ROYAL_FAMILYS_TOMB, RG_SUNS_SONG, false, true); + ctx->PlaceItemInLocation(RC_SONG_FROM_OCARINA_OF_TIME, RG_SONG_OF_TIME, false, true); + 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; + AddFixedItemToPool(RG_KOKIRI_EMERALD, 1, rewardIceTraps); + AddFixedItemToPool(RG_GORON_RUBY, 1, rewardIceTraps); + AddFixedItemToPool(RG_ZORA_SAPPHIRE, 1, rewardIceTraps); + AddFixedItemToPool(RG_FOREST_MEDALLION, 1, rewardIceTraps); + AddFixedItemToPool(RG_FIRE_MEDALLION, 1, rewardIceTraps); + AddFixedItemToPool(RG_WATER_MEDALLION, 1, rewardIceTraps); + AddFixedItemToPool(RG_SPIRIT_MEDALLION, 1, rewardIceTraps); + AddFixedItemToPool(RG_SHADOW_MEDALLION, 1, rewardIceTraps); + AddFixedItemToPool(RG_LIGHT_MEDALLION, 1, rewardIceTraps); + if (ctx->GetOption(RSK_TRIFORCE_HUNT).IsNot(RO_TRIFORCE_HUNT_OFF)) { - ctx->possibleIceTrapModels.push_back(RG_TRIFORCE_PIECE); - AddItemToMainPool(RG_TRIFORCE_PIECE, (ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_TOTAL).Get() + 1)); + AddFixedItemToPool(RG_TRIFORCE_PIECE, ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_TOTAL).Get() + 1, false); switch (ctx->GetOption(RSK_TRIFORCE_HUNT).Get()) { case RO_TRIFORCE_HUNT_OFF: break; case RO_TRIFORCE_HUNT_WIN: ctx->PlaceItemInLocation(RC_TRIFORCE_COMPLETED, RG_TRIFORCE); // Win condition - ctx->PlaceItemInLocation(RC_GANON, GetJunkItem(), false, true); + ctx->PlaceItemInLocation(RC_GANON, RG_BLUE_RUPEE, false, true); break; case RO_TRIFORCE_HUNT_GBK: ctx->PlaceItemInLocation(RC_TRIFORCE_COMPLETED, RG_GANONS_CASTLE_BOSS_KEY); @@ -525,37 +334,33 @@ void GenerateItemPool() { // Fixed item locations ctx->PlaceItemInLocation(RC_HC_ZELDAS_LETTER, RG_ZELDAS_LETTER); - if (ctx->GetOption(RSK_SHUFFLE_KOKIRI_SWORD)) { - AddItemToMainPool(RG_KOKIRI_SWORD); - ctx->possibleIceTrapModels.push_back(RG_KOKIRI_SWORD); - } else { - if (!ctx->GetOption(RSK_STARTING_KOKIRI_SWORD)) { + if (!ctx->GetOption(RSK_STARTING_KOKIRI_SWORD)) { + if (ctx->GetOption(RSK_SHUFFLE_KOKIRI_SWORD)) { + AddItemToPool(RG_KOKIRI_SWORD, 2, 1, 1, 1); + } else { ctx->PlaceItemInLocation(RC_KF_KOKIRI_SWORD_CHEST, RG_KOKIRI_SWORD, false, true); } } - if (ctx->GetOption(RSK_SHUFFLE_MASTER_SWORD)) { - AddItemToMainPool(RG_MASTER_SWORD); - ctx->possibleIceTrapModels.push_back(RG_MASTER_SWORD); - } else { - if (!ctx->GetOption(RSK_STARTING_MASTER_SWORD)) { + if (!ctx->GetOption(RSK_STARTING_MASTER_SWORD)) { + if (ctx->GetOption(RSK_SHUFFLE_MASTER_SWORD)) { + AddItemToPool(RG_MASTER_SWORD, 2, 1, 1, 1); + } else { ctx->PlaceItemInLocation(RC_TOT_MASTER_SWORD, RG_MASTER_SWORD, false, true); } } if (ctx->GetOption(RSK_SHUFFLE_WEIRD_EGG)) { - AddItemToMainPool(RG_WEIRD_EGG); - ctx->possibleIceTrapModels.push_back(RG_WEIRD_EGG); + AddItemToPool(RG_WEIRD_EGG, 2, 1, 1, 1); } else { ctx->PlaceItemInLocation(RC_HC_MALON_EGG, RG_WEIRD_EGG, false, true); } if (ctx->GetOption(RSK_SHUFFLE_OCARINA)) { - AddItemToMainPool(RG_PROGRESSIVE_OCARINA, 2); - if (ctx->GetOption(RSK_ITEM_POOL).Is(RO_ITEM_POOL_PLENTIFUL)) { - AddItemToPool(PendingJunkPool, RG_PROGRESSIVE_OCARINA); + if (ctx->GetOption(RSK_STARTING_OCARINA).IsNot(RO_STARTING_OCARINA_TIME)) { + int baseOcarinas = ctx->GetOption(RSK_STARTING_OCARINA).Is(RO_STARTING_OCARINA_OFF) ? 2 : 1; + AddItemToPool(RG_PROGRESSIVE_OCARINA, baseOcarinas + 1, baseOcarinas, baseOcarinas, baseOcarinas); } - ctx->possibleIceTrapModels.push_back(RG_PROGRESSIVE_OCARINA); } else { if (ctx->GetOption(RSK_STARTING_OCARINA).Is(RO_STARTING_OCARINA_OFF)) { ctx->PlaceItemInLocation(RC_LW_GIFT_FROM_SARIA, RG_PROGRESSIVE_OCARINA, false, true); @@ -568,31 +373,22 @@ void GenerateItemPool() { } if (ctx->GetOption(RSK_SHUFFLE_OCARINA_BUTTONS)) { - AddItemToMainPool(RG_OCARINA_A_BUTTON); - AddItemToMainPool(RG_OCARINA_C_UP_BUTTON); - AddItemToMainPool(RG_OCARINA_C_DOWN_BUTTON); - AddItemToMainPool(RG_OCARINA_C_LEFT_BUTTON); - AddItemToMainPool(RG_OCARINA_C_RIGHT_BUTTON); - - ctx->possibleIceTrapModels.push_back(RG_OCARINA_A_BUTTON); - ctx->possibleIceTrapModels.push_back(RG_OCARINA_C_UP_BUTTON); - ctx->possibleIceTrapModels.push_back(RG_OCARINA_C_DOWN_BUTTON); - ctx->possibleIceTrapModels.push_back(RG_OCARINA_C_LEFT_BUTTON); - ctx->possibleIceTrapModels.push_back(RG_OCARINA_C_RIGHT_BUTTON); + AddItemToPool(RG_OCARINA_A_BUTTON, 2, 1, 1, 1); + AddItemToPool(RG_OCARINA_C_UP_BUTTON, 2, 1, 1, 1); + AddItemToPool(RG_OCARINA_C_DOWN_BUTTON, 2, 1, 1, 1); + AddItemToPool(RG_OCARINA_C_LEFT_BUTTON, 2, 1, 1, 1); + AddItemToPool(RG_OCARINA_C_RIGHT_BUTTON, 2, 1, 1, 1); } if (ctx->GetOption(RSK_SKELETON_KEY)) { - AddItemToMainPool(RG_SKELETON_KEY); + AddFixedItemToPool(RG_SKELETON_KEY, 1); } - if (ctx->GetOption(RSK_SHUFFLE_SWIM)) { - AddItemToMainPool(RG_PROGRESSIVE_SCALE); - } + int bronzeScale = ctx->GetOption(RSK_SHUFFLE_SWIM) ? 1 : 0; + AddItemToPool(RG_PROGRESSIVE_SCALE, 3 + bronzeScale, 2 + bronzeScale, 2 + bronzeScale, 2 + bronzeScale); if (ctx->GetOption(RSK_SHUFFLE_BEEHIVES)) { - // 32 total beehive locations - AddItemToPool(PendingJunkPool, RG_RED_RUPEE, 23); - AddItemToPool(PendingJunkPool, RG_BLUE_RUPEE, 9); + PlaceItemsForType(RCTYPE_BEEHIVE, true, true); } // Shuffle Pots @@ -619,81 +415,36 @@ void GenerateItemPool() { bool dungeonCratesActive = ctx->GetOption(RSK_SHUFFLE_CRATES).Is(RO_SHUFFLE_CRATES_DUNGEONS) || ctx->GetOption(RSK_SHUFFLE_CRATES).Is(RO_SHUFFLE_CRATES_ALL); PlaceItemsForType(RCTYPE_CRATE, overworldCratesActive, dungeonCratesActive); + PlaceItemsForType(RCTYPE_NLCRATE, ctx->GetOption(RSK_LOGIC_RULES).Is(RO_LOGIC_NO_LOGIC) && overworldCratesActive, + ctx->GetOption(RSK_LOGIC_RULES).Is(RO_LOGIC_NO_LOGIC) && dungeonCratesActive); PlaceItemsForType(RCTYPE_SMALL_CRATE, overworldCratesActive, dungeonCratesActive); - if (ctx->GetOption(RSK_LOGIC_RULES).Is(RO_LOGIC_NO_LOGIC)) { - PlaceItemsForType(RCTYPE_NLCRATE, overworldCratesActive, dungeonCratesActive); - } - auto fsMode = ctx->GetOption(RSK_FISHSANITY); - if (fsMode.IsNot(RO_FISHSANITY_OFF)) { - if (fsMode.Is(RO_FISHSANITY_POND) || fsMode.Is(RO_FISHSANITY_BOTH)) { - // 17 max child pond fish - uint8_t pondCt = ctx->GetOption(RSK_FISHSANITY_POND_COUNT).Get(); - for (uint8_t i = 0; i < pondCt; i++) { - AddItemToMainPool(GetJunkItem()); - } - - if (ctx->GetOption(RSK_FISHSANITY_AGE_SPLIT)) { - // 16 max adult pond fish, have to reduce to 16 if every fish is enabled - if (pondCt > 16) - pondCt = 16; - for (uint8_t i = 0; i < pondCt; i++) { - AddItemToMainPool(GetJunkItem()); - } - } - } - // 9 grotto fish, 5 zora's domain fish - if (fsMode.Is(RO_FISHSANITY_OVERWORLD) || fsMode.Is(RO_FISHSANITY_BOTH)) { - for (uint8_t i = 0; i < Rando::StaticData::GetOverworldFishLocations().size(); i++) - AddItemToMainPool(GetJunkItem()); - } - - if (fsMode.Is(RO_FISHSANITY_HYRULE_LOACH)) { - AddItemToMainPool(RG_PURPLE_RUPEE); - } else { - ctx->PlaceItemInLocation(RC_LH_HYRULE_LOACH, RG_PURPLE_RUPEE, false, true); - } + if (ctx->GetOption(RSK_FISHSANITY).Is(RO_FISHSANITY_HYRULE_LOACH)) { + AddFixedItemToPool(RG_PURPLE_RUPEE, 1); + } else { + ctx->PlaceItemInLocation(RC_LH_HYRULE_LOACH, RG_PURPLE_RUPEE, false, true); } if (ctx->GetOption(RSK_SHUFFLE_FISHING_POLE)) { - AddItemToMainPool(RG_FISHING_POLE); - ctx->possibleIceTrapModels.push_back(RG_FISHING_POLE); + AddItemToPool(RG_FISHING_POLE, 2, 1, 1, 1); } - - if (ctx->GetOption(RSK_INFINITE_UPGRADES).Is(RO_INF_UPGRADES_PROGRESSIVE)) { - AddItemToMainPool(RG_PROGRESSIVE_BOMB_BAG); - AddItemToMainPool(RG_PROGRESSIVE_BOW); - AddItemToMainPool(RG_PROGRESSIVE_NUT_UPGRADE); - AddItemToMainPool(RG_PROGRESSIVE_SLINGSHOT); - AddItemToMainPool(RG_PROGRESSIVE_STICK_UPGRADE); - AddItemToMainPool(RG_PROGRESSIVE_MAGIC_METER); - AddItemToMainPool(RG_PROGRESSIVE_WALLET); - if (ctx->GetOption(RSK_BOMBCHU_BAG).Is(RO_BOMBCHU_BAG_PROGRESSIVE)) { - AddItemToMainPool(RG_PROGRESSIVE_BOMBCHU_BAG); - } - } - // if beans unshuffled, put on bean guy, otherwise if not starting with beans, add to pool if (ctx->GetOption(RSK_SHUFFLE_MERCHANTS).IsNot(RO_SHUFFLE_MERCHANTS_BEANS_ONLY) && ctx->GetOption(RSK_SHUFFLE_MERCHANTS).IsNot(RO_SHUFFLE_MERCHANTS_ALL)) { ctx->PlaceItemInLocation(RC_ZR_MAGIC_BEAN_SALESMAN, RG_MAGIC_BEAN, false, true); } else if (!ctx->GetOption(RSK_STARTING_BEANS)) { - AddItemToMainPool(RG_MAGIC_BEAN_PACK); - if (ctx->GetOption(RSK_ITEM_POOL).Is(RO_ITEM_POOL_PLENTIFUL)) { - AddItemToPool(PendingJunkPool, RG_MAGIC_BEAN_PACK); - } - ctx->possibleIceTrapModels.push_back(RG_MAGIC_BEAN_PACK); + AddItemToPool(RG_MAGIC_BEAN_PACK, 2, 1, 1, 1); } if (ctx->GetOption(RSK_SHUFFLE_MERCHANTS).Is(RO_SHUFFLE_MERCHANTS_ALL_BUT_BEANS) || ctx->GetOption(RSK_SHUFFLE_MERCHANTS).Is(RO_SHUFFLE_MERCHANTS_ALL)) { if (/*!ProgressiveGoronSword TODO: Implement Progressive Goron Sword*/ true) { - AddItemToMainPool(RG_GIANTS_KNIFE); + AddFixedItemToPool(RG_GIANTS_KNIFE, 1); } if (ctx->GetOption(RSK_BOMBCHU_BAG).Is(RO_BOMBCHU_BAG_SINGLE)) { - AddItemToMainPool(RG_PROGRESSIVE_BOMBCHU_BAG); + AddFixedItemToPool(RG_PROGRESSIVE_BOMBCHU_BAG, 1); } else if (ctx->GetOption(RSK_BOMBCHU_BAG).Is(RO_BOMBCHU_BAG_NONE)) { - AddItemToMainPool(RG_BOMBCHU_10); + AddFixedItemToPool(RG_BOMBCHU_10, 1); } } else { ctx->PlaceItemInLocation(RC_KAK_GRANNYS_SHOP, RG_BLUE_POTION_REFILL, false, true); @@ -702,7 +453,7 @@ void GenerateItemPool() { } if (ctx->GetOption(RSK_SHUFFLE_FROG_SONG_RUPEES)) { - AddItemToMainPool(RG_PURPLE_RUPEE, 5); + AddFixedItemToPool(RG_PURPLE_RUPEE, 5); } else { ctx->PlaceItemInLocation(RC_ZR_FROGS_ZELDAS_LULLABY, RG_PURPLE_RUPEE, false, true); ctx->PlaceItemInLocation(RC_ZR_FROGS_EPONAS_SONG, RG_PURPLE_RUPEE, false, true); @@ -712,24 +463,25 @@ void GenerateItemPool() { } if (ctx->GetOption(RSK_SHUFFLE_ADULT_TRADE)) { - AddItemToMainPool(RG_POCKET_EGG); - AddItemToMainPool(RG_COJIRO); - AddItemToMainPool(RG_ODD_MUSHROOM); - AddItemToMainPool(RG_ODD_POTION); - AddItemToMainPool(RG_POACHERS_SAW); - AddItemToMainPool(RG_BROKEN_SWORD); - AddItemToMainPool(RG_PRESCRIPTION); - AddItemToMainPool(RG_EYEBALL_FROG); - AddItemToMainPool(RG_EYEDROPS); + AddItemToPool(RG_POCKET_EGG, 2, 1, 1, 1); + AddItemToPool(RG_COJIRO, 2, 1, 1, 1); + AddItemToPool(RG_ODD_MUSHROOM, 2, 1, 1, 1); + AddItemToPool(RG_ODD_POTION, 2, 1, 1, 1); + AddItemToPool(RG_POACHERS_SAW, 2, 1, 1, 1); + AddItemToPool(RG_BROKEN_SWORD, 2, 1, 1, 1); + AddItemToPool(RG_PRESCRIPTION, 2, 1, 1, 1); + AddItemToPool(RG_EYEBALL_FROG, 2, 1, 1, 1); + AddItemToPool(RG_EYEDROPS, 2, 1, 1, 1); } - AddItemToMainPool(RG_CLAIM_CHECK); + AddItemToPool(RG_CLAIM_CHECK, 2, 1, 1, 1); if (ctx->GetOption(RSK_SHUFFLE_CHEST_MINIGAME).Is(RO_CHEST_GAME_SINGLE_KEYS)) { - AddItemToMainPool(RG_TREASURE_GAME_SMALL_KEY, 6); // 6 individual keys + AddItemToPool(RG_TREASURE_GAME_SMALL_KEY, 7, 6, 6, 6); } else if (ctx->GetOption(RSK_SHUFFLE_CHEST_MINIGAME).Is(RO_CHEST_GAME_PACK)) { - AddItemToMainPool(RG_TREASURE_GAME_SMALL_KEY); // 1 key which will behave as a pack of 6 + AddItemToPool(RG_TREASURE_GAME_KEY_RING, 2, 1, 1, 1); } + int tokensToAdd = 0; if (ctx->GetOption(RSK_SHUFFLE_TOKENS).Is(RO_TOKENSANITY_OFF)) { for (RandomizerCheck loc : ctx->GetLocations(ctx->allLocations, RCTYPE_SKULL_TOKEN)) { ctx->PlaceItemInLocation(loc, RG_GOLD_SKULLTULA_TOKEN, false, true); @@ -739,7 +491,7 @@ void GenerateItemPool() { if (Rando::StaticData::GetLocation(loc)->IsOverworld()) { ctx->PlaceItemInLocation((RandomizerCheck)loc, RG_GOLD_SKULLTULA_TOKEN, false, true); } else { - AddItemToMainPool(RG_GOLD_SKULLTULA_TOKEN); + tokensToAdd++; } } } else if (ctx->GetOption(RSK_SHUFFLE_TOKENS).Is(RO_TOKENSANITY_OVERWORLD)) { @@ -747,130 +499,78 @@ void GenerateItemPool() { if (Rando::StaticData::GetLocation(loc)->IsDungeon()) { ctx->PlaceItemInLocation((RandomizerCheck)loc, RG_GOLD_SKULLTULA_TOKEN, false, true); } else { - AddItemToMainPool(RG_GOLD_SKULLTULA_TOKEN); + tokensToAdd++; } } } else { - AddItemToMainPool(RG_GOLD_SKULLTULA_TOKEN, 100); + tokensToAdd = 100; } if (ctx->GetOption(RSK_SHUFFLE_100_GS_REWARD)) { - if (ctx->GetOption(RSK_SHUFFLE_TOKENS).IsNot(RO_TOKENSANITY_OFF) && - ctx->GetOption(RSK_ITEM_POOL).Is(RO_ITEM_POOL_PLENTIFUL)) { - AddItemToPool(PendingJunkPool, RG_GOLD_SKULLTULA_TOKEN, 10); - } - AddItemToMainPool(RG_HUGE_RUPEE); + AddFixedItemToPool(RG_HUGE_RUPEE, 1); } else { ctx->PlaceItemInLocation(RC_KAK_100_GOLD_SKULLTULA_REWARD, RG_HUGE_RUPEE, false, true); } if (ctx->GetOption(RSK_SHUFFLE_BEAN_SOULS)) { - AddItemToMainPool(RG_DEATH_MOUNTAIN_CRATER_BEAN_SOUL); - AddItemToMainPool(RG_DEATH_MOUNTAIN_TRAIL_BEAN_SOUL); - AddItemToMainPool(RG_DESERT_COLOSSUS_BEAN_SOUL); - AddItemToMainPool(RG_GERUDO_VALLEY_BEAN_SOUL); - AddItemToMainPool(RG_GRAVEYARD_BEAN_SOUL); - AddItemToMainPool(RG_KOKIRI_FOREST_BEAN_SOUL); - AddItemToMainPool(RG_LAKE_HYLIA_BEAN_SOUL); - AddItemToMainPool(RG_LOST_WOODS_BRIDGE_BEAN_SOUL); - AddItemToMainPool(RG_LOST_WOODS_BEAN_SOUL); - AddItemToMainPool(RG_ZORAS_RIVER_BEAN_SOUL); + ctx->possibleIceTrapModels.insert(RG_DEATH_MOUNTAIN_CRATER_BEAN_SOUL); // ice traps reroll this into a random bean soul + AddItemToPool(RG_DEATH_MOUNTAIN_CRATER_BEAN_SOUL, 2, 1, 1, 1, false); + AddItemToPool(RG_DEATH_MOUNTAIN_TRAIL_BEAN_SOUL, 2, 1, 1, 1, false); + AddItemToPool(RG_DESERT_COLOSSUS_BEAN_SOUL, 2, 1, 1, 1, false); + AddItemToPool(RG_GERUDO_VALLEY_BEAN_SOUL, 2, 1, 1, 1, false); + AddItemToPool(RG_GRAVEYARD_BEAN_SOUL, 2, 1, 1, 1, false); + AddItemToPool(RG_KOKIRI_FOREST_BEAN_SOUL, 2, 1, 1, 1, false); + AddItemToPool(RG_LAKE_HYLIA_BEAN_SOUL, 2, 1, 1, 1, false); + AddItemToPool(RG_LOST_WOODS_BRIDGE_BEAN_SOUL, 2, 1, 1, 1, false); + AddItemToPool(RG_LOST_WOODS_BEAN_SOUL, 2, 1, 1, 1, false); + AddItemToPool(RG_ZORAS_RIVER_BEAN_SOUL, 2, 1, 1, 1, false); + } + + if (ctx->GetOption(RSK_SHUFFLE_TOKENS).IsNot(RO_TOKENSANITY_OFF) && + ctx->GetOption(RSK_ITEM_POOL).Is(RO_ITEM_POOL_PLENTIFUL)) { + tokensToAdd += 10; + } + + if (ctx->GetOption(RSK_STARTING_SKULLTULA_TOKEN).Get() < tokensToAdd) { + AddFixedItemToPool(RG_GOLD_SKULLTULA_TOKEN, tokensToAdd - ctx->GetOption(RSK_STARTING_SKULLTULA_TOKEN).Get()); } if (ctx->GetOption(RSK_SHUFFLE_BOSS_SOULS)) { - AddItemToMainPool(RG_GOHMA_SOUL); - AddItemToMainPool(RG_KING_DODONGO_SOUL); - AddItemToMainPool(RG_BARINADE_SOUL); - AddItemToMainPool(RG_PHANTOM_GANON_SOUL); - AddItemToMainPool(RG_VOLVAGIA_SOUL); - AddItemToMainPool(RG_MORPHA_SOUL); - AddItemToMainPool(RG_BONGO_BONGO_SOUL); - AddItemToMainPool(RG_TWINROVA_SOUL); - - ctx->possibleIceTrapModels.push_back(RG_GOHMA_SOUL); - ctx->possibleIceTrapModels.push_back(RG_KING_DODONGO_SOUL); - ctx->possibleIceTrapModels.push_back(RG_BARINADE_SOUL); - ctx->possibleIceTrapModels.push_back(RG_PHANTOM_GANON_SOUL); - ctx->possibleIceTrapModels.push_back(RG_VOLVAGIA_SOUL); - ctx->possibleIceTrapModels.push_back(RG_MORPHA_SOUL); - ctx->possibleIceTrapModels.push_back(RG_BONGO_BONGO_SOUL); - ctx->possibleIceTrapModels.push_back(RG_TWINROVA_SOUL); + AddItemToPool(RG_GOHMA_SOUL, 2, 1, 1, 1); + AddItemToPool(RG_KING_DODONGO_SOUL, 2, 1, 1, 1); + AddItemToPool(RG_BARINADE_SOUL, 2, 1, 1, 1); + AddItemToPool(RG_PHANTOM_GANON_SOUL, 2, 1, 1, 1); + AddItemToPool(RG_VOLVAGIA_SOUL, 2, 1, 1, 1); + AddItemToPool(RG_MORPHA_SOUL, 2, 1, 1, 1); + AddItemToPool(RG_BONGO_BONGO_SOUL, 2, 1, 1, 1); + AddItemToPool(RG_TWINROVA_SOUL, 2, 1, 1, 1); if (ctx->GetOption(RSK_SHUFFLE_BOSS_SOULS).Is(RO_BOSS_SOULS_ON_PLUS_GANON)) { - AddItemToMainPool(RG_GANON_SOUL); - ctx->possibleIceTrapModels.push_back(RG_GANON_SOUL); + AddItemToPool(RG_GANON_SOUL, 2, 1, 1, 1); } } - if (ctx->GetOption(RSK_SHUFFLE_CHILD_WALLET)) { - AddItemToMainPool(RG_PROGRESSIVE_WALLET); - } - - if (ctx->GetOption(RSK_INCLUDE_TYCOON_WALLET)) { - AddItemToMainPool(RG_PROGRESSIVE_WALLET); - } - - if (ctx->GetOption(RSK_SHUFFLE_DEKU_STICK_BAG)) { - AddItemToMainPool(RG_PROGRESSIVE_STICK_UPGRADE); - } - - if (ctx->GetOption(RSK_SHUFFLE_DEKU_NUT_BAG)) { - AddItemToMainPool(RG_PROGRESSIVE_NUT_UPGRADE); - } - - if (ctx->GetOption(RSK_BOMBCHU_BAG).Is(RO_BOMBCHU_BAG_SINGLE)) { - AddItemToMainPool(RG_PROGRESSIVE_BOMBCHU_BAG, 5); - } else if (ctx->GetOption(RSK_BOMBCHU_BAG).Is(RO_BOMBCHU_BAG_PROGRESSIVE)) { - AddItemToMainPool(RG_PROGRESSIVE_BOMBCHU_BAG, 3); - if (ctx->GetOption(RSK_ITEM_POOL).Is(RO_ITEM_POOL_PLENTIFUL)) { - AddItemToPool(PendingJunkPool, RG_PROGRESSIVE_BOMBCHU_BAG); - } - } else { - AddItemToMainPool(RG_BOMBCHU_5); - AddItemToMainPool(RG_BOMBCHU_10, 3); - AddItemToMainPool(RG_BOMBCHU_20); - } - - // Ice Traps - AddItemToMainPool(RG_ICE_TRAP); - if (ctx->GetDungeon(Rando::GERUDO_TRAINING_GROUND)->IsVanilla()) { - AddItemToMainPool(RG_ICE_TRAP); - } - if (ctx->GetDungeon(Rando::GANONS_CASTLE)->IsVanilla()) { - AddItemToMainPool(RG_ICE_TRAP, 4); - } - // Gerudo Fortress if (ctx->GetOption(RSK_GERUDO_FORTRESS).Is(RO_GF_CARPENTERS_FREE)) { ctx->PlaceItemInLocation(RC_TH_1_TORCH_CARPENTER, RG_RECOVERY_HEART, false, true); ctx->PlaceItemInLocation(RC_TH_DEAD_END_CARPENTER, RG_RECOVERY_HEART, false, true); ctx->PlaceItemInLocation(RC_TH_DOUBLE_CELL_CARPENTER, RG_RECOVERY_HEART, false, true); ctx->PlaceItemInLocation(RC_TH_STEEP_SLOPE_CARPENTER, RG_RECOVERY_HEART, false, true); + } else if (ctx->GetOption(RSK_GERUDO_KEYS).IsNot(RO_GERUDO_KEYS_VANILLA)) { if (ctx->GetOption(RSK_GERUDO_FORTRESS).Is(RO_GF_CARPENTERS_FAST)) { - AddItemToMainPool(RG_GERUDO_FORTRESS_SMALL_KEY); + AddItemToPool(RG_GERUDO_FORTRESS_SMALL_KEY, 2, 1, 1, 1); ctx->PlaceItemInLocation(RC_TH_DEAD_END_CARPENTER, RG_RECOVERY_HEART, false, true); ctx->PlaceItemInLocation(RC_TH_DOUBLE_CELL_CARPENTER, RG_RECOVERY_HEART, false, true); ctx->PlaceItemInLocation(RC_TH_STEEP_SLOPE_CARPENTER, RG_RECOVERY_HEART, false, true); } else { // Only add key ring if 4 Fortress keys necessary if (ctx->GetOption(RSK_KEYRINGS_GERUDO_FORTRESS) && ctx->GetOption(RSK_KEYRINGS)) { - AddItemToMainPool(RG_GERUDO_FORTRESS_KEY_RING); - // Add junk to make up for missing keys - for (uint8_t i = 0; i < 3; i++) { - AddItemToMainPool(GetJunkItem()); - } + AddItemToPool(RG_GERUDO_FORTRESS_KEY_RING, 2, 1, 1, 1); } else { - AddItemToMainPool(RG_GERUDO_FORTRESS_SMALL_KEY, 4); - } - } - if (ctx->GetOption(RSK_ITEM_POOL).Is(RO_ITEM_POOL_PLENTIFUL)) { - if (ctx->GetOption(RSK_KEYRINGS_GERUDO_FORTRESS) && - ctx->GetOption(RSK_GERUDO_FORTRESS).Is(RO_GF_CARPENTERS_NORMAL) && ctx->GetOption(RSK_KEYRINGS)) { - AddItemToPool(PendingJunkPool, RG_GERUDO_FORTRESS_KEY_RING); - } else { - AddItemToPool(PendingJunkPool, RG_GERUDO_FORTRESS_SMALL_KEY); + AddItemToPool(RG_GERUDO_FORTRESS_SMALL_KEY, 5, 4, 4, 4); } } + } else { if (ctx->GetOption(RSK_GERUDO_FORTRESS).Is(RO_GF_CARPENTERS_FAST)) { ctx->PlaceItemInLocation(RC_TH_1_TORCH_CARPENTER, RG_GERUDO_FORTRESS_SMALL_KEY, false, true); @@ -886,230 +586,95 @@ void GenerateItemPool() { } // Gerudo Membership Card - if (ctx->GetOption(RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD) && - ctx->GetOption(RSK_GERUDO_FORTRESS).IsNot(RO_GF_CARPENTERS_FREE)) { - AddItemToMainPool(RG_GERUDO_MEMBERSHIP_CARD); - ctx->possibleIceTrapModels.push_back(RG_GERUDO_MEMBERSHIP_CARD); - } else if (ctx->GetOption(RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD)) { - AddItemToPool(ItemPool, RG_GERUDO_MEMBERSHIP_CARD); - ctx->PlaceItemInLocation(RC_TH_FREED_CARPENTERS, RG_ICE_TRAP, false, true); + if (ctx->GetOption(RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD)) { + AddItemToPool(RG_GERUDO_MEMBERSHIP_CARD, 2, 1, 1, 1); + if (ctx->GetOption(RSK_GERUDO_FORTRESS).IsNot(RO_GF_CARPENTERS_FREE)) { + ctx->PlaceItemInLocation(RC_TH_FREED_CARPENTERS, RG_BLUE_RUPEE, false, true); + } } else { ctx->PlaceItemInLocation(RC_TH_FREED_CARPENTERS, RG_GERUDO_MEMBERSHIP_CARD, false, true); } // Keys + if (ctx->GetOption(RSK_LOCK_OVERWORLD_DOORS)) { + // only 1 is added to the ice trap pool, to avoid the pool being filled with them. + // a random one is chosen in CreateItemOverrides + AddItemToPool(RG_GUARD_HOUSE_KEY, 2, 1, 1, 1); + AddItemToPool(RG_MARKET_BAZAAR_KEY, 2, 1, 1, 1, false); + AddItemToPool(RG_MARKET_POTION_SHOP_KEY, 2, 1, 1, 1, false); + AddItemToPool(RG_MASK_SHOP_KEY, 2, 1, 1, 1, false); + AddItemToPool(RG_MARKET_SHOOTING_GALLERY_KEY, 2, 1, 1, 1, false); + AddItemToPool(RG_BOMBCHU_BOWLING_KEY, 2, 1, 1, 1, false); + AddItemToPool(RG_TREASURE_CHEST_GAME_BUILDING_KEY, 2, 1, 1, 1, false); + AddItemToPool(RG_BOMBCHU_SHOP_KEY, 2, 1, 1, 1, false); + AddItemToPool(RG_RICHARDS_HOUSE_KEY, 2, 1, 1, 1, false); + AddItemToPool(RG_ALLEY_HOUSE_KEY, 2, 1, 1, 1, false); + AddItemToPool(RG_KAK_BAZAAR_KEY, 2, 1, 1, 1, false); + AddItemToPool(RG_KAK_POTION_SHOP_KEY, 2, 1, 1, 1, false); + AddItemToPool(RG_BOSS_HOUSE_KEY, 2, 1, 1, 1, false); + AddItemToPool(RG_GRANNYS_POTION_SHOP_KEY, 2, 1, 1, 1, false); + AddItemToPool(RG_SKULLTULA_HOUSE_KEY, 2, 1, 1, 1, false); + AddItemToPool(RG_IMPAS_HOUSE_KEY, 2, 1, 1, 1, false); + AddItemToPool(RG_WINDMILL_KEY, 2, 1, 1, 1, false); + AddItemToPool(RG_KAK_SHOOTING_GALLERY_KEY, 2, 1, 1, 1, false); + AddItemToPool(RG_DAMPES_HUT_KEY, 2, 1, 1, 1, false); + AddItemToPool(RG_TALONS_HOUSE_KEY, 2, 1, 1, 1, false); + AddItemToPool(RG_STABLES_KEY, 2, 1, 1, 1, false); + AddItemToPool(RG_BACK_TOWER_KEY, 2, 1, 1, 1, false); + AddItemToPool(RG_HYLIA_LAB_KEY, 2, 1, 1, 1, false); + AddItemToPool(RG_FISHING_HOLE_KEY, 2, 1, 1, 1, false); + } - // For key rings, need to add as many junk items as "missing" keys - if (ctx->GetOption(RSK_KEYRINGS).IsNot(RO_KEYRINGS_OFF)) { - uint8_t ringJunkAmt = 0; + if (ctx->GetOption(RSK_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_VANILLA)) { + PlaceVanillaSmallKeys(); + } else if (ctx->GetOption(RSK_KEYSANITY).IsNot(RO_DUNGEON_ITEM_LOC_STARTWITH)) { for (auto dungeon : ctx->GetDungeons()->GetDungeonList()) { if (dungeon->HasKeyRing()) { - ringJunkAmt += dungeon->GetSmallKeyCount() - 1; + AddItemToPool(dungeon->GetKeyRing(), 2, 1, 1, 1); + } else if (dungeon->GetSmallKeyCount() > 0) { + int smallKeys = dungeon->GetSmallKeyCount(); + AddItemToPool(dungeon->GetSmallKey(), smallKeys + 1, smallKeys, smallKeys, smallKeys); } } - for (uint8_t i = 0; i < ringJunkAmt; i++) { - AddItemToMainPool(GetJunkItem()); - } } - if (ctx->GetOption(RSK_ITEM_POOL).Is(RO_ITEM_POOL_PLENTIFUL)) { - if (ctx->GetOption(RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD)) { - AddItemToPool(PendingJunkPool, RG_GERUDO_MEMBERSHIP_CARD); - } - - if (ctx->GetOption(RSK_SHUFFLE_FISHING_POLE)) { - AddItemToPool(PendingJunkPool, RG_FISHING_POLE); - } - - // Plentiful small keys - if (ctx->GetOption(RSK_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_ANYWHERE) || - ctx->GetOption(RSK_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_ANY_DUNGEON) || - ctx->GetOption(RSK_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_OVERWORLD)) { - if (ctx->GetDungeon(Rando::BOTTOM_OF_THE_WELL)->HasKeyRing()) { - AddItemToPool(PendingJunkPool, RG_BOTTOM_OF_THE_WELL_KEY_RING); - } else { - AddItemToPool(PendingJunkPool, RG_BOTTOM_OF_THE_WELL_SMALL_KEY); - } - if (ctx->GetDungeon(Rando::FOREST_TEMPLE)->HasKeyRing()) { - AddItemToPool(PendingJunkPool, RG_FOREST_TEMPLE_KEY_RING); - } else { - AddItemToPool(PendingJunkPool, RG_FOREST_TEMPLE_SMALL_KEY); - } - if (ctx->GetDungeon(Rando::FIRE_TEMPLE)->HasKeyRing()) { - AddItemToPool(PendingJunkPool, RG_FIRE_TEMPLE_KEY_RING); - } else { - AddItemToPool(PendingJunkPool, RG_FIRE_TEMPLE_SMALL_KEY); - } - if (ctx->GetDungeon(Rando::WATER_TEMPLE)->HasKeyRing()) { - AddItemToPool(PendingJunkPool, RG_WATER_TEMPLE_KEY_RING); - } else { - AddItemToPool(PendingJunkPool, RG_WATER_TEMPLE_SMALL_KEY); - } - if (ctx->GetDungeon(Rando::SPIRIT_TEMPLE)->HasKeyRing()) { - AddItemToPool(PendingJunkPool, RG_SPIRIT_TEMPLE_KEY_RING); - } else { - AddItemToPool(PendingJunkPool, RG_SPIRIT_TEMPLE_SMALL_KEY); - } - if (ctx->GetDungeon(Rando::SHADOW_TEMPLE)->HasKeyRing()) { - AddItemToPool(PendingJunkPool, RG_SHADOW_TEMPLE_KEY_RING); - } else { - AddItemToPool(PendingJunkPool, RG_SHADOW_TEMPLE_SMALL_KEY); - } - if (ctx->GetDungeon(Rando::GERUDO_TRAINING_GROUND)->HasKeyRing()) { - AddItemToPool(PendingJunkPool, RG_GERUDO_TRAINING_GROUND_KEY_RING); - } else { - AddItemToPool(PendingJunkPool, RG_GERUDO_TRAINING_GROUND_SMALL_KEY); - } - if (ctx->GetDungeon(Rando::GANONS_CASTLE)->HasKeyRing()) { - AddItemToPool(PendingJunkPool, RG_GANONS_CASTLE_KEY_RING); - } else { - AddItemToPool(PendingJunkPool, RG_GANONS_CASTLE_SMALL_KEY); - } - } - - if (ctx->GetOption(RSK_BOSS_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_ANYWHERE) || - ctx->GetOption(RSK_BOSS_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_ANY_DUNGEON) || - ctx->GetOption(RSK_BOSS_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_OVERWORLD)) { - AddItemToPool(PendingJunkPool, RG_FOREST_TEMPLE_BOSS_KEY); - AddItemToPool(PendingJunkPool, RG_FIRE_TEMPLE_BOSS_KEY); - AddItemToPool(PendingJunkPool, RG_WATER_TEMPLE_BOSS_KEY); - AddItemToPool(PendingJunkPool, RG_SPIRIT_TEMPLE_BOSS_KEY); - AddItemToPool(PendingJunkPool, RG_SHADOW_TEMPLE_BOSS_KEY); - } - - if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_ANYWHERE) || - ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_ANY_DUNGEON) || - ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_OVERWORLD)) { - AddItemToPool(PendingJunkPool, RG_GANONS_CASTLE_BOSS_KEY); - } + if (ctx->GetOption(RSK_BOSS_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_VANILLA)) { + PlaceVanillaBossKeys(); + } else if (ctx->GetOption(RSK_BOSS_KEYSANITY).IsNot(RO_DUNGEON_ITEM_LOC_STARTWITH)) { + AddItemToPool(RG_FOREST_TEMPLE_BOSS_KEY, 2, 1, 1, 1); + AddItemToPool(RG_FIRE_TEMPLE_BOSS_KEY, 2, 1, 1, 1); + AddItemToPool(RG_WATER_TEMPLE_BOSS_KEY, 2, 1, 1, 1); + AddItemToPool(RG_SPIRIT_TEMPLE_BOSS_KEY, 2, 1, 1, 1); + AddItemToPool(RG_SHADOW_TEMPLE_BOSS_KEY, 2, 1, 1, 1); } - if (ctx->GetOption(RSK_LOCK_OVERWORLD_DOORS)) { - AddItemToPool(ItemPool, RG_GUARD_HOUSE_KEY); - AddItemToPool(ItemPool, RG_MARKET_BAZAAR_KEY); - AddItemToPool(ItemPool, RG_MARKET_POTION_SHOP_KEY); - AddItemToPool(ItemPool, RG_MASK_SHOP_KEY); - AddItemToPool(ItemPool, RG_MARKET_SHOOTING_GALLERY_KEY); - AddItemToPool(ItemPool, RG_BOMBCHU_BOWLING_KEY); - AddItemToPool(ItemPool, RG_TREASURE_CHEST_GAME_BUILDING_KEY); - AddItemToPool(ItemPool, RG_BOMBCHU_SHOP_KEY); - AddItemToPool(ItemPool, RG_RICHARDS_HOUSE_KEY); - AddItemToPool(ItemPool, RG_ALLEY_HOUSE_KEY); - AddItemToPool(ItemPool, RG_KAK_BAZAAR_KEY); - AddItemToPool(ItemPool, RG_KAK_POTION_SHOP_KEY); - AddItemToPool(ItemPool, RG_BOSS_HOUSE_KEY); - AddItemToPool(ItemPool, RG_GRANNYS_POTION_SHOP_KEY); - AddItemToPool(ItemPool, RG_SKULLTULA_HOUSE_KEY); - AddItemToPool(ItemPool, RG_IMPAS_HOUSE_KEY); - AddItemToPool(ItemPool, RG_WINDMILL_KEY); - AddItemToPool(ItemPool, RG_KAK_SHOOTING_GALLERY_KEY); - AddItemToPool(ItemPool, RG_DAMPES_HUT_KEY); - AddItemToPool(ItemPool, RG_TALONS_HOUSE_KEY); - AddItemToPool(ItemPool, RG_STABLES_KEY); - AddItemToPool(ItemPool, RG_BACK_TOWER_KEY); - AddItemToPool(ItemPool, RG_HYLIA_LAB_KEY); - AddItemToPool(ItemPool, RG_FISHING_HOLE_KEY); + // Don't add GBK to the pool at all for Triforce Hunt or if we start with it. + if (!(ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_STARTWITH) || ctx->GetOption(RSK_TRIFORCE_HUNT))) { + if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_KAK_TOKENS)) { + ctx->PlaceItemInLocation(RC_KAK_100_GOLD_SKULLTULA_REWARD, RG_GANONS_CASTLE_BOSS_KEY); + } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Get() >= RO_GANON_BOSS_KEY_LACS_VANILLA) { + ctx->PlaceItemInLocation(RC_TOT_LIGHT_ARROWS_CUTSCENE, RG_GANONS_CASTLE_BOSS_KEY); + } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_VANILLA)) { + ctx->PlaceItemInLocation(RC_GANONS_TOWER_BOSS_KEY_CHEST, RG_GANONS_CASTLE_BOSS_KEY); + } else { + AddItemToPool(RG_GANONS_CASTLE_BOSS_KEY, 2, 1, 1, 1); + } } // Shopsanity if (ctx->GetOption(RSK_SHOPSANITY).Is(RO_SHOPSANITY_OFF) || (ctx->GetOption(RSK_SHOPSANITY).Is(RO_SHOPSANITY_SPECIFIC_COUNT) && ctx->GetOption(RSK_SHOPSANITY_COUNT).Is(RO_SHOPSANITY_COUNT_ZERO_ITEMS))) { - AddItemsToPool(ItemPool, normalRupees); + AddFixedItemToPool(RG_BLUE_RUPEE, 13); + AddFixedItemToPool(RG_RED_RUPEE, 5); + AddFixedItemToPool(RG_PURPLE_RUPEE, 7); + AddFixedItemToPool(RG_HUGE_RUPEE, 3); } else { - AddItemsToPool(ItemPool, shopsanityRupees); // Shopsanity gets extra large rupees - } - - // Shuffle Fountain Fairies - if (ctx->GetOption(RSK_SHUFFLE_FOUNTAIN_FAIRIES)) { - for (auto rc : Rando::StaticData::GetFountainFairyLocations()) { - AddItemToMainPool(GetJunkItem()); - } - // 8 extra for Ganon's Castle - int extra = 8; - for (int i = 0; i < extra; i++) { - AddItemToMainPool(GetJunkItem()); - } - } - - // Shuffle Gossip Stone Fairies - if (ctx->GetOption(RSK_SHUFFLE_STONE_FAIRIES)) { - for (auto rc : Rando::StaticData::GetStoneFairyLocations()) { - AddItemToMainPool(GetJunkItem()); - } - // 2 Dodongo's Cavern Gossip Stone - int extra = 2; - for (int i = 0; i < extra; i++) { - AddItemToMainPool(GetJunkItem()); - } - } - - // Shuffle Bean Fairies - if (ctx->GetOption(RSK_SHUFFLE_BEAN_FAIRIES)) { - for (auto rc : Rando::StaticData::GetBeanFairyLocations()) { - AddItemToMainPool(GetJunkItem()); - } - } - - // Shuffle Song Fairies - if (ctx->GetOption(RSK_SHUFFLE_SONG_FAIRIES)) { - for (auto rc : Rando::StaticData::GetSongFairyLocations()) { - AddItemToMainPool(GetJunkItem()); - } - // 3 Shadow Temple - int extra = 3; - extra += ctx->GetDungeon(Rando::FIRE_TEMPLE)->IsVanilla() ? 0 : 2; - extra += ctx->GetDungeon(Rando::WATER_TEMPLE)->IsVanilla() ? 0 : 3; - extra += ctx->GetDungeon(Rando::SPIRIT_TEMPLE)->IsVanilla() ? 2 : 1; - extra += ctx->GetDungeon(Rando::BOTTOM_OF_THE_WELL)->IsVanilla() ? 1 : 2; - extra += ctx->GetDungeon(Rando::ICE_CAVERN)->IsVanilla() ? 1 : 0; - extra += ctx->GetDungeon(Rando::GERUDO_TRAINING_GROUND)->IsVanilla() ? 1 : 0; - extra += ctx->GetDungeon(Rando::GANONS_CASTLE)->IsVanilla() ? 1 : 0; - for (int i = 0; i < extra; i++) { - AddItemToMainPool(GetJunkItem()); - } - } - - // Scrubsanity - if (ctx->GetOption(RSK_SHUFFLE_SCRUBS).Is(RO_SCRUBS_ALL)) { - // Deku Tree - if (ctx->GetDungeon(Rando::DEKU_TREE)->IsMQ()) { - AddItemToMainPool(RG_DEKU_SHIELD); - } - - // Dodongos Cavern - AddItemToMainPool(RG_DEKU_STICK_1); - AddItemToMainPool(RG_DEKU_SHIELD); - if (ctx->GetDungeon(Rando::DODONGOS_CAVERN)->IsMQ()) { - AddItemToMainPool(RG_RECOVERY_HEART); - } else { - AddItemToMainPool(RG_DEKU_NUTS_5); - } - - // Jabu Jabus Belly - if (ctx->GetDungeon(Rando::JABU_JABUS_BELLY)->IsVanilla()) { - AddItemToMainPool(RG_DEKU_NUTS_5); - } - - // Ganons Castle - AddItemToMainPool(RG_BOMBS_5); - AddItemToMainPool(RG_RECOVERY_HEART); - AddItemToMainPool(RG_BLUE_RUPEE); - if (ctx->GetDungeon(Rando::GANONS_CASTLE)->IsMQ()) { - AddItemToMainPool(RG_DEKU_NUTS_5); - } - - // Overworld Scrubs - AddItemsToPool(ItemPool, dekuScrubItems); - - // Scrubs which sell seeds or arrows sell it based on age, this randomly assigns them - for (uint8_t i = 0; i < 7; i++) { - if (Random(0, 3)) { - AddItemToMainPool(RG_ARROWS_30); - } else { - AddItemToMainPool(RG_DEKU_SEEDS_30); - } - } + // Shopsanity gets extra large rupees + AddFixedItemToPool(RG_BLUE_RUPEE, 2); + AddFixedItemToPool(RG_RED_RUPEE, 10); + AddFixedItemToPool(RG_PURPLE_RUPEE, 10); + AddFixedItemToPool(RG_HUGE_RUPEE, 6); } bool overworldFreeStandingActive = ctx->GetOption(RSK_SHUFFLE_FREESTANDING).Is(RO_SHUFFLE_FREESTANDING_OVERWORLD) || @@ -1118,102 +683,108 @@ void GenerateItemPool() { ctx->GetOption(RSK_SHUFFLE_FREESTANDING).Is(RO_SHUFFLE_FREESTANDING_ALL); PlaceItemsForType(RCTYPE_FREESTANDING, overworldFreeStandingActive, dungeonFreeStandingActive); - AddItemsToPool(ItemPool, alwaysItems); - AddItemsToPool(ItemPool, dungeonRewards); - // Dungeon pools if (ctx->GetDungeon(Rando::DEKU_TREE)->IsMQ()) { - AddItemsToPool(ItemPool, DT_MQ); + AddFixedItemToPool(RG_PURPLE_RUPEE); + if (ctx->GetOption(RSK_SHUFFLE_SCRUBS).Is(RO_SCRUBS_ALL)) { + AddFixedItemToPool(RG_DEKU_SHIELD, 3); + } else { + AddFixedItemToPool(RG_DEKU_SHIELD, 2); + } } else { - AddItemsToPool(ItemPool, DT_Vanilla); + AddFixedItemToPool(RG_RECOVERY_HEART, 2); } if (ctx->GetDungeon(Rando::DODONGOS_CAVERN)->IsMQ()) { - AddItemsToPool(ItemPool, DC_MQ); + AddFixedItemToPool(RG_HYLIAN_SHIELD); + AddFixedItemToPool(RG_BLUE_RUPEE); + if (ctx->GetOption(RSK_SHUFFLE_SCRUBS).Is(RO_SCRUBS_ALL)) { + AddFixedItemToPool(RG_RECOVERY_HEART); + } } else { - AddItemsToPool(ItemPool, DC_Vanilla); + AddFixedItemToPool(RG_RED_RUPEE); + if (ctx->GetOption(RSK_SHUFFLE_SCRUBS).Is(RO_SCRUBS_ALL)) { + AddFixedItemToPool(RG_DEKU_NUTS_5); + } } if (ctx->GetDungeon(Rando::JABU_JABUS_BELLY)->IsMQ()) { - AddItemsToPool(ItemPool, JB_MQ); + AddFixedItemToPool(RG_DEKU_NUTS_5, 4); + AddFixedItemToPool(RG_RECOVERY_HEART); + AddFixedItemToPool(RG_DEKU_STICK_1); + AddFixedItemToPool(RG_DEKU_SHIELD); } if (ctx->GetDungeon(Rando::FOREST_TEMPLE)->IsMQ()) { - AddItemsToPool(ItemPool, FoT_MQ); + AddFixedItemToPool(RG_ARROWS_5); } else { - AddItemsToPool(ItemPool, FoT_Vanilla); + AddFixedItemToPool(RG_RECOVERY_HEART); + AddFixedItemToPool(RG_ARROWS_10); + AddFixedItemToPool(RG_ARROWS_30); } if (ctx->GetDungeon(Rando::FIRE_TEMPLE)->IsMQ()) { - AddItemsToPool(ItemPool, FiT_MQ); + AddFixedItemToPool(RG_HYLIAN_SHIELD); + AddFixedItemToPool(RG_BOMBS_20); } else { - AddItemsToPool(ItemPool, FiT_Vanilla); + AddFixedItemToPool(RG_HUGE_RUPEE); } if (ctx->GetDungeon(Rando::SPIRIT_TEMPLE)->IsMQ()) { - AddItemsToPool(ItemPool, SpT_MQ); + AddFixedItemToPool(RG_PURPLE_RUPEE, 2); + AddFixedItemToPool(RG_ARROWS_30); } else { - AddItemsToPool(ItemPool, SpT_Vanilla); + AddFixedItemToPool(RG_DEKU_SHIELD, 2); + AddFixedItemToPool(RG_BOMBS_20); + AddFixedItemToPool(RG_RECOVERY_HEART, 2); } if (ctx->GetDungeon(Rando::SHADOW_TEMPLE)->IsMQ()) { - AddItemsToPool(ItemPool, ShT_MQ); + AddFixedItemToPool(RG_ARROWS_5, 2); + AddFixedItemToPool(RG_RED_RUPEE); } else { - AddItemsToPool(ItemPool, ShT_Vanilla); + AddFixedItemToPool(RG_ARROWS_30); } if (ctx->GetDungeon(Rando::BOTTOM_OF_THE_WELL)->IsVanilla()) { - AddItemsToPool(ItemPool, BW_Vanilla); + AddFixedItemToPool(RG_DEKU_NUTS_5); + AddFixedItemToPool(RG_DEKU_NUTS_10); + AddFixedItemToPool(RG_RECOVERY_HEART); + AddFixedItemToPool(RG_BOMBS_10); + AddFixedItemToPool(RG_DEKU_SHIELD); + AddFixedItemToPool(RG_HYLIAN_SHIELD); + AddFixedItemToPool(RG_HUGE_RUPEE); } if (ctx->GetDungeon(Rando::GERUDO_TRAINING_GROUND)->IsMQ()) { - AddItemsToPool(ItemPool, GTG_MQ); + AddFixedItemToPool(RG_TREASURE_GAME_GREEN_RUPEE, 2); + AddFixedItemToPool(RG_ARROWS_10); + AddFixedItemToPool(RG_GREEN_RUPEE); + AddFixedItemToPool(RG_PURPLE_RUPEE); } else { - AddItemsToPool(ItemPool, GTG_Vanilla); + AddFixedItemToPool(RG_HUGE_RUPEE); + AddFixedItemToPool(RG_ARROWS_30, 3); } if (ctx->GetDungeon(Rando::GANONS_CASTLE)->IsMQ()) { - AddItemsToPool(ItemPool, GC_MQ); + AddFixedItemToPool(RG_ARROWS_10, 2); + AddFixedItemToPool(RG_BOMBS_5); + AddFixedItemToPool(RG_RED_RUPEE); + AddFixedItemToPool(RG_RECOVERY_HEART); + if (ctx->GetOption(RSK_SHUFFLE_SCRUBS).Is(RO_SCRUBS_ALL)) { + AddFixedItemToPool(RG_DEKU_NUTS_5); + } } else { - AddItemsToPool(ItemPool, GC_Vanilla); - } - - uint8_t rutoBottles = 1; - if (ctx->GetOption(RSK_ZORAS_FOUNTAIN).Is(RO_ZF_OPEN)) { - rutoBottles = 0; + AddFixedItemToPool(RG_BLUE_RUPEE, 3); + AddFixedItemToPool(RG_ARROWS_30); } // Add 4 total bottles uint8_t bottleCount = 4; - std::vector bottles; - bottles.assign(normalBottles.begin(), normalBottles.end()); - ctx->possibleIceTrapModels.push_back(Rando::StaticData::RetrieveItem(RandomElement(bottles)) - .GetRandomizerGet()); // Get one random bottle type for ice traps - for (uint8_t i = 0; i < bottleCount; i++) { - if (i >= rutoBottles) { - if ((i == bottleCount - 1) && - (ctx->GetOption(RSK_SHUFFLE_MERCHANTS).Is(RO_SHUFFLE_MERCHANTS_ALL_BUT_BEANS) || - ctx->GetOption(RSK_SHUFFLE_MERCHANTS).Is(RO_SHUFFLE_MERCHANTS_ALL))) { - AddItemToMainPool(RG_BOTTLE_WITH_BLUE_POTION); - } else { - AddRandomBottle(bottles); - } - } else { - AddItemToMainPool(RG_RUTOS_LETTER); - } + if (ctx->GetOption(RSK_ZORAS_FOUNTAIN).IsNot(RO_ZF_OPEN)) { + AddFixedItemToPool(RG_RUTOS_LETTER); + bottleCount--; + } + if ((ctx->GetOption(RSK_SHUFFLE_MERCHANTS).Is(RO_SHUFFLE_MERCHANTS_ALL_BUT_BEANS) || + ctx->GetOption(RSK_SHUFFLE_MERCHANTS).Is(RO_SHUFFLE_MERCHANTS_ALL))) { + AddFixedItemToPool(RG_BOTTLE_WITH_BLUE_POTION); + bottleCount--; } - if (ctx->GetOption(RSK_SHUFFLE_SONGS).IsNot(RO_SONG_SHUFFLE_OFF)) { - AddItemsToPool(ItemPool, songList); - // add extra songs only if song shuffle is anywhere - if (ctx->GetOption(RSK_SHUFFLE_SONGS).Is(RO_SONG_SHUFFLE_ANYWHERE) && - ctx->GetOption(RSK_ITEM_POOL).Is(RO_ITEM_POOL_PLENTIFUL)) { - AddItemsToPool(PendingJunkPool, songList); - } - } else { - ctx->PlaceItemInLocation(RC_SHEIK_IN_FOREST, RG_MINUET_OF_FOREST, false, true); - ctx->PlaceItemInLocation(RC_SHEIK_IN_CRATER, RG_BOLERO_OF_FIRE, false, true); - ctx->PlaceItemInLocation(RC_SHEIK_IN_ICE_CAVERN, RG_SERENADE_OF_WATER, false, true); - ctx->PlaceItemInLocation(RC_SHEIK_AT_COLOSSUS, RG_REQUIEM_OF_SPIRIT, false, true); - ctx->PlaceItemInLocation(RC_SHEIK_IN_KAKARIKO, RG_NOCTURNE_OF_SHADOW, false, true); - ctx->PlaceItemInLocation(RC_SHEIK_AT_TEMPLE, RG_PRELUDE_OF_LIGHT, false, true); - ctx->PlaceItemInLocation(RC_SONG_FROM_IMPA, RG_ZELDAS_LULLABY, false, true); - ctx->PlaceItemInLocation(RC_SONG_FROM_MALON, RG_EPONAS_SONG, false, true); - ctx->PlaceItemInLocation(RC_SONG_FROM_SARIA, RG_SARIAS_SONG, false, true); - ctx->PlaceItemInLocation(RC_SONG_FROM_ROYAL_FAMILYS_TOMB, RG_SUNS_SONG, false, true); - ctx->PlaceItemInLocation(RC_SONG_FROM_OCARINA_OF_TIME, RG_SONG_OF_TIME, false, true); - ctx->PlaceItemInLocation(RC_SONG_FROM_WINDMILL, RG_SONG_OF_STORMS, false, true); + ctx->possibleIceTrapModels.insert(RG_EMPTY_BOTTLE); // ice traps reroll this into a random normal bottle + for (uint8_t i = 0; i < bottleCount; i++) { + AddFixedItemToPool(RandomElement(Rando::StaticData::normalBottles), 1, false); } /*For item pool generation, dungeon items are either placed in their vanilla @@ -1225,119 +796,117 @@ void GenerateItemPool() { if (ctx->GetOption(RSK_SHUFFLE_MAPANDCOMPASS).Is(RO_DUNGEON_ITEM_LOC_VANILLA)) { PlaceVanillaMapsAndCompasses(); - } else { + } else if (ctx->GetOption(RSK_SHUFFLE_MAPANDCOMPASS).IsNot(RO_DUNGEON_ITEM_LOC_STARTWITH)) { for (auto dungeon : ctx->GetDungeons()->GetDungeonList()) { if (dungeon->GetMap() != RG_NONE) { - AddItemToMainPool(dungeon->GetMap()); + AddFixedItemToPool(dungeon->GetMap(), false); } if (dungeon->GetCompass() != RG_NONE) { - AddItemToMainPool(dungeon->GetCompass()); + AddFixedItemToPool(dungeon->GetCompass(), false); } } } - if (ctx->GetOption(RSK_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_VANILLA)) { - PlaceVanillaSmallKeys(); - } else { - for (auto dungeon : ctx->GetDungeons()->GetDungeonList()) { - if (dungeon->HasKeyRing() && ctx->GetOption(RSK_KEYSANITY).IsNot(RO_DUNGEON_ITEM_LOC_STARTWITH)) { - AddItemToMainPool(dungeon->GetKeyRing()); - } else if (dungeon->GetSmallKeyCount() > 0) { - AddItemToMainPool(dungeon->GetSmallKey(), dungeon->GetSmallKeyCount()); - } - } + int maxHearts = 20; + switch (ctx->GetOption(RSK_ITEM_POOL).Get()) { + case RO_ITEM_POOL_PLENTIFUL: + case RO_ITEM_POOL_BALANCED: + break; + case RO_ITEM_POOL_SCARCE: + maxHearts = 12; + break; + case RO_ITEM_POOL_MINIMAL: + maxHearts = 3; + break; } - if (ctx->GetOption(RSK_BOSS_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_VANILLA)) { - PlaceVanillaBossKeys(); - } else { - AddItemToMainPool(RG_FOREST_TEMPLE_BOSS_KEY); - AddItemToMainPool(RG_FIRE_TEMPLE_BOSS_KEY); - AddItemToMainPool(RG_WATER_TEMPLE_BOSS_KEY); - AddItemToMainPool(RG_SPIRIT_TEMPLE_BOSS_KEY); - AddItemToMainPool(RG_SHADOW_TEMPLE_BOSS_KEY); - } - - if (!ctx->GetOption(RSK_TRIFORCE_HUNT) - .IsNot(RO_TRIFORCE_HUNT_OFF)) { // Don't add GBK to the pool at all for Triforce Hunt. - if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_KAK_TOKENS)) { - ctx->PlaceItemInLocation(RC_KAK_100_GOLD_SKULLTULA_REWARD, RG_GANONS_CASTLE_BOSS_KEY); - } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Get() >= RO_GANON_BOSS_KEY_LACS_VANILLA) { - ctx->PlaceItemInLocation(RC_TOT_LIGHT_ARROWS_CUTSCENE, RG_GANONS_CASTLE_BOSS_KEY); - } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_VANILLA)) { - ctx->PlaceItemInLocation(RC_GANONS_TOWER_BOSS_KEY_CHEST, RG_GANONS_CASTLE_BOSS_KEY); - } else { - AddItemToMainPool(RG_GANONS_CASTLE_BOSS_KEY); - } - } - - if (ctx->GetOption(RSK_ITEM_POOL).Is(RO_ITEM_POOL_PLENTIFUL)) { - AddItemsToPool(ItemPool, easyItems); - } else { - AddItemsToPool(ItemPool, normalItems); - } - - if (!ctx->GetOption(RSK_SHUFFLE_KOKIRI_SWORD)) { - ReplaceMaxItem(RG_KOKIRI_SWORD, 0); - } - - if (!ctx->GetOption(RSK_SHUFFLE_MASTER_SWORD)) { - ReplaceMaxItem(RG_MASTER_SWORD, 0); - } - - if (/*ProgressiveGoronSword TODO: Implement Setting*/ false) { - ReplaceMaxItem(RG_BIGGORON_SWORD, 0); - AddItemToMainPool(RG_PROGRESSIVE_GORONSWORD, 2); - ctx->possibleIceTrapModels.push_back(RG_PROGRESSIVE_GORONSWORD); - } else { - ctx->possibleIceTrapModels.push_back(RG_BIGGORON_SWORD); - } - - // Replace ice traps with junk from the pending junk pool if necessary - if (ctx->GetOption(RSK_ICE_TRAPS).Is(RO_ICE_TRAPS_OFF)) { - ReplaceMaxItem(RG_ICE_TRAP, 0); - } - // Replace all junk items with ice traps for onslaught mode - else if (ctx->GetOption(RSK_ICE_TRAPS).Is(RO_ICE_TRAPS_ONSLAUGHT)) { - PendingJunkPool.clear(); - for (uint8_t i = 0; i < JunkPoolItems.size() - 3; i++) { // -3 Omits Huge Rupees and Deku Nuts 10 - ReplaceMaxItem(JunkPoolItems[i], 0); - } - } - - if (ctx->GetOption(RSK_ITEM_POOL).Is(RO_ITEM_POOL_SCARCE)) { - SetScarceItemPool(); - } else if (ctx->GetOption(RSK_ITEM_POOL).Is(RO_ITEM_POOL_MINIMAL)) { - SetMinimalItemPool(); - } else if (/*RemoveDoubleDefense TODO: Implement setting*/ false) { - ReplaceMaxItem(RG_DOUBLE_DEFENSE, 0); - } - - std::erase(ItemPool, RG_NONE); - - if (ItemPool.size() < ctx->allLocations.size()) { - Shuffle(PendingJunkPool); - size_t junkNeeded = std::min(PendingJunkPool.size(), ctx->allLocations.size() - ItemPool.size()); - ItemPool.insert(ItemPool.end(), PendingJunkPool.begin(), PendingJunkPool.begin() + junkNeeded); - PendingJunkPool.erase(PendingJunkPool.begin(), PendingJunkPool.begin() + junkNeeded); - } else if (ItemPool.size() > ctx->allLocations.size()) { - // RANDOTODO: all junk should be put in PendingJunkPool so this is never needed - size_t remove = ItemPool.size() - ctx->allLocations.size(); - for (size_t i = 0; remove > 0 && i < ItemPool.size(); i++) { - for (size_t j = 0; j < JunkPoolItems.size(); j++) { - if (ItemPool[i] == JunkPoolItems[j]) { - ItemPool[i] = RG_NONE; - remove--; + int startingHearts = ctx->GetOption(RSK_STARTING_HEARTS).Get() + 1; + if (startingHearts < maxHearts) { + AddFixedItemToPool(RG_TREASURE_GAME_HEART, 1, false); + AddFixedItemToPool(RG_PIECE_OF_HEART, 3, false); + startingHearts++; + if (startingHearts < maxHearts) { + switch (ctx->GetOption(RSK_ITEM_POOL).Get()) { + case RO_ITEM_POOL_PLENTIFUL: + case RO_ITEM_POOL_MINIMAL: + AddFixedItemToPool(RG_HEART_CONTAINER, maxHearts - startingHearts, false); break; + case RO_ITEM_POOL_BALANCED: { + int heartsToPlace = maxHearts - startingHearts; + int halfHearts = maxHearts >> 2; + AddFixedItemToPool(RG_HEART_CONTAINER, heartsToPlace - halfHearts, false); + AddFixedItemToPool(RG_PIECE_OF_HEART, halfHearts * 4, false); + break; + } + case RO_ITEM_POOL_SCARCE: + AddFixedItemToPool(RG_PIECE_OF_HEART, (maxHearts - startingHearts) * 4, false); + break; + } + } + } + + std::erase(junkPool, RG_NONE); + std::erase(itemPool, RG_NONE); + std::erase(lesserPool, RG_NONE); + std::erase(plentifulPool, RG_NONE); + + size_t locCount = ctx->CountEmptyLocations(false); + assert(itemPool.size() <= locCount); + int iceTrapstoAdd = 0; + if (itemPool.size() + plentifulPool.size() < locCount) { + itemPool.insert(itemPool.end(), plentifulPool.begin(), plentifulPool.end()); + // Fixed Ice Traps + if (ctx->GetOption(RSK_BASE_ICE_TRAPS)) { + iceTrapstoAdd++; + if (ctx->GetDungeon(Rando::GERUDO_TRAINING_GROUND)->IsVanilla()) { + iceTrapstoAdd++; + } + if (ctx->GetDungeon(Rando::GANONS_CASTLE)->IsVanilla()) { + iceTrapstoAdd += 4; + } + } + iceTrapstoAdd += ctx->GetOption(RSK_ADDITIONAL_ICE_TRAPS).Get(); + AddFixedItemToPool(RG_ICE_TRAP, + itemPool.size() + iceTrapstoAdd < locCount ? iceTrapstoAdd : locCount - itemPool.size(), false); + if (itemPool.size() + lesserPool.size() < locCount) { + itemPool.insert(itemPool.end(), lesserPool.begin(), lesserPool.end()); + } else { + while (itemPool.size() < locCount) { + itemPool.insert(itemPool.end(), RandomElement(lesserPool, true)); + } + } + } else { + while (itemPool.size() < locCount) { + itemPool.insert(itemPool.end(), RandomElement(plentifulPool, true)); + } + } + + size_t junkToAdd = locCount - itemPool.size(); + iceTrapstoAdd = 0; + if (junkToAdd > 0) { + if (ctx->GetOption(RSK_ICE_TRAP_PERCENT).Is(100)) { + iceTrapstoAdd = junkToAdd; + } else if (ctx->GetOption(RSK_ICE_TRAP_PERCENT).Get() >= 0) { + for (int count = 0; count < junkToAdd; count++) { + if (Random(0, 101) < ctx->GetOption(RSK_ICE_TRAP_PERCENT).Get()) { + iceTrapstoAdd++; } } } - std::erase(ItemPool, RG_NONE); + AddFixedItemToPool(RG_ICE_TRAP, iceTrapstoAdd, false); + junkToAdd -= iceTrapstoAdd; + if (junkToAdd > junkPool.size()) { + itemPool.insert(itemPool.end(), junkPool.begin(), junkPool.end()); + while (itemPool.size() < locCount) { + itemPool.insert(itemPool.end(), RandomElement(JunkPoolItems)); + } + } else { + while (itemPool.size() < locCount) { + itemPool.insert(itemPool.end(), RandomElement(junkPool, true)); + } + } } - // RANDOTODO: Ideally this should be checking for equality, but that is not currently the case and has never been - // the case, and isn't even currently the case in the 3drando repo we inherited this from years ago, so it may - // be a large undertaking to fix. - assert(ItemPool.size() <= ctx->allLocations.size() || !"Item Pool larger than Location Pool"); + assert(itemPool.size() == locCount); } diff --git a/soh/soh/Enhancements/randomizer/3drando/item_pool.hpp b/soh/soh/Enhancements/randomizer/3drando/item_pool.hpp index a437099c8..01b9a2459 100644 --- a/soh/soh/Enhancements/randomizer/3drando/item_pool.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/item_pool.hpp @@ -11,4 +11,4 @@ void AddItemToPool(std::vector& pool, const RandomizerGet item, s RandomizerGet GetJunkItem(); void GenerateItemPool(); -extern std::vector ItemPool; +extern std::vector itemPool; diff --git a/soh/soh/Enhancements/randomizer/3drando/shops.cpp b/soh/soh/Enhancements/randomizer/3drando/shops.cpp index 06fe5790f..970b05389 100644 --- a/soh/soh/Enhancements/randomizer/3drando/shops.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/shops.cpp @@ -27,7 +27,7 @@ PriceSettingsStruct::PriceSettingsStruct(RandomizerSettingKey _main, RandomizerS affordable = _affordable; } -static std::array, 0xF1> trickNameTable; // Table of trick names for ice traps +static std::array, RG_MAX> trickNameTable; // Table of trick names for ice traps bool initTrickNames = false; // Indicates if trick ice trap names have been initialized yet // Set vanilla shop item locations before potentially shuffling @@ -887,6 +887,56 @@ void InitTrickNames() { Text{ "Triforce Shard", "Éclat de Triforce", "Triforce-Fragment" }, // "Triforce Shard" Text{ "Shiny Rock", "Caillou Brillant", "glänzender Stein" }, // "Shiny Rock" }; + trickNameTable[RG_DEATH_MOUNTAIN_CRATER_BEAN_SOUL] = { + // TODO_TRANSLATE + Text{ "Volcano Seed Spirit" }, + Text{ "Bolero Sprout Platform" }, + }; + trickNameTable[RG_DEATH_MOUNTAIN_TRAIL_BEAN_SOUL] = { + // TODO_TRANSLATE + Text{ "Dodongo's Seed Spirit" }, + Text{ "Boulder Sprout Platform" }, + }; + trickNameTable[RG_DESERT_COLOSSUS_BEAN_SOUL] = { + // TODO_TRANSLATE + Text{ "Spirit Temple Seed Spirit" }, + Text{ "Colossus Arch Sprout Platform" }, + }; + trickNameTable[RG_GERUDO_VALLEY_BEAN_SOUL] = { + // TODO_TRANSLATE + Text{ "Waterfall Seed Spirit" }, + Text{ "Gerudo Cow Sprout Platform" }, + }; + trickNameTable[RG_GRAVEYARD_BEAN_SOUL] = { + // TODO_TRANSLATE + Text{ "GY Crate Seed Spirit" }, + Text{ "Dampe's Sprout Platform" }, + }; + trickNameTable[RG_KOKIRI_FOREST_BEAN_SOUL] = { + // TODO_TRANSLATE + Text{ "Rupee Ledge Seed Spirit" }, + Text{ "KF Shop Sprout Platform" }, + }; + trickNameTable[RG_LAKE_HYLIA_BEAN_SOUL] = { + // TODO_TRANSLATE + Text{ "Hylia Lab Seed Spirit" }, + Text{ "Fishing Sprout Platform" }, + }; + trickNameTable[RG_LOST_WOODS_BRIDGE_BEAN_SOUL] = { + // TODO_TRANSLATE + Text{ "LW Bridge Seed Spirit" }, + Text{ "Skull Kid Sprout Platform" }, + }; + trickNameTable[RG_LOST_WOODS_BEAN_SOUL] = { + // TODO_TRANSLATE + Text{ "Deku Theatre Seed Spirit" }, + Text{ "Deku Scrubs Sprout Platform" }, + }; + trickNameTable[RG_ZORAS_RIVER_BEAN_SOUL] = { + // TODO_TRANSLATE + Text{ "River Ride Seed Spirit" }, + Text{ "Bean Salesman Sprout Platform" }, + }; trickNameTable[RG_GOHMA_SOUL] = { Text{ "Spider Sense", "Sens de l'Araignée", "Spinnensinn" }, Text{ "Deku Spirit", "Parasite Mojo", "Deku Geist" }, @@ -937,6 +987,11 @@ void InitTrickNames() { Text{ "Floating Lure", "Floating Lure", "Schwimmer" }, Text{ "Fishing Reel", "Fishing Reel", "Angelschnur" }, }; + trickNameTable[RG_SKELETON_KEY] = { + // TODO_TRANSLATE + Text{ "Stalfos Key" }, Text{ "Nightmare Key" }, Text{ "Graveyard Key" }, + Text{ "King's Key" }, Text{ "Hero's Key" }, + }; trickNameTable[RG_OCARINA_A_BUTTON] = { Text{ "Ocarina J Button", "Touche Ha de l'Ocarina", "J-Taste der Okarina" }, Text{ "Ocarina Ayy Button", "Touche Ah de l'Ocarina", "A-Taste der Flöte" }, @@ -965,6 +1020,331 @@ void InitTrickNames() { Text{ "Overworld C Right Button", "Trou Droit de l'Ocarina", "C-Rechts-Taste der E-Gitarre" }, }; + trickNameTable[RG_GREG_RUPEE] = { + // TODO_TRANSALTE + Text{ "Morshu the Green Ruby", "Morshu the Green Ruby", "Morshu the Green Ruby" }, + Text{ "Geoffrey the Gray Rupoor", "Geoffrey the Gray Rupoor", "Geoffrey the Gray Rupoor" }, + Text{ "Validation Rupee", "Validation Rupee", "Validation Rupee" }, + Text{ "Gary, just Gary", "Gary, just Gary", "Gary, just Gary" }, + Text{ "Ike the Indigo Ice Trap", "Ike the Indigo Ice Trap", "Ike the Indigo Ice Trap" }, + }; + + trickNameTable[RG_FOREST_TEMPLE_SMALL_KEY] = { + // TODO_TRANSALTE + Text{ "Wind Temple Smol Key", "Wind Temple Smol Key", "Wind Temple Smol Key" }, + Text{ "Woodfall Temple Small Key", "Woodfall Temple Small Key", "Woodfall Temple Small Key" }, + Text{ "Skull Woods Small Key", "Skull Woods Small Key", "Skull Woods Small Key" }, + }; + trickNameTable[RG_FIRE_TEMPLE_SMALL_KEY] = { + // TODO_TRANSALTE + Text{ "Ice Cavern Small Keese", "Ice Cavern Small Keese", "Ice Cavern Small Keese" }, + Text{ "Goron Temple Small Key", "Goron Temple Small Key", "Goron Temple Small Key" }, + Text{ "Eldin Temple Salmon Koi", "Eldin Temple Salmon Koi", "Eldin Temple Salmon Koi" }, + }; + trickNameTable[RG_WATER_TEMPLE_SMALL_KEY] = { + // TODO_TRANSALTE + Text{ "Swamp Palace Small Keese", "Swamp Palace Small Keese", "Swamp Palace Small Keese" }, + Text{ "Great Bay Temple Small Key", "Great Bay Temple Small Key", "Great Bay Temple Small Key" }, + Text{ "Lakebed Temple Small Key", "Lakebed Temple Small Key", "Lakebed Temple Small Key" }, + }; + trickNameTable[RG_SPIRIT_TEMPLE_SMALL_KEY] = { + // TODO_TRANSALTE + Text{ "Light Temple Small Key", "Light Temple Small Key", "Light Temple Small Key" }, + Text{ "Lightning Temple Smol Key", "Lightning Temple Smol Key", "Lightning Temple Smol Key" }, + Text{ "Desert Palace Small Key", "Desert Palace Small Key", "Desert Palace Small Key" }, + Text{ "Stone Tower Small Keese", "Stone Tower Small Keese", "Stone Tower Small Keese" }, + }; + trickNameTable[RG_SHADOW_TEMPLE_SMALL_KEY] = { + // TODO_TRANSALTE + Text{ "Palace of Darkness Small Key", "Palace of Darkness Small Key", "Palace of Darkness Small Key" }, + Text{ "Shrine of Illusion Salmon Koi", "Shrine of Illusion Salmon Koi", "Shrine of Illusion Salmon Koi" }, + Text{ "Palace of Twilight Small Key", "Palace of Twilight Small Key", "Palace of Twilight Small Key" }, + }; + trickNameTable[RG_BOTTOM_OF_THE_WELL_SMALL_KEY] = { + // TODO_TRANSALTE + Text{ "Top of the Wall Small Key", "Top of the Wall Small Key", "Top of the Wall Small Key" }, + Text{ "Breath of the Wild Small Key", "Breath of the Wild Small Key", "Breath of the Wild Small Key" }, + Text{ "Beneath the Well Small Key", "Beneath the Well Small Key", "Beneath the Well Small Key" }, + }; + trickNameTable[RG_GERUDO_TRAINING_GROUND_SMALL_KEY] = { + // TODO_TRANSALTE + Text{ "Gerudo Sanctum Small Key", "Gerudo Sanctum Small Key", "Gerudo Sanctum Small Key" }, + Text{ "Lady's Lair Small Keese", "Lady's Lair Small Keese", "Lady's Lair Small Keese" }, + Text{ "Knight Acadamy Small Key", "Knight Acadamy Small Key", "Knight Acadamy Small Key" }, + }; + trickNameTable[RG_GERUDO_FORTRESS_SMALL_KEY] = { + // TODO_TRANSALTE + Text{ "Fortress of Winds Small Key", "Fortress of Winds Small Key", "Fortress of Winds Small Key" }, + Text{ "Thieve's Town Small Key", "Thieve's Town Small Key", "Thieve's Town Small Key" }, + Text{ "Fortress Centrum Small Key", "Fortress Centrum Small Key", "Fortress Centrum Small Key" }, + Text{ "Forsaken Fortress Smol Key", "Forsaken Fortress Smol Key", "Forsaken Fortress Smol Key" }, + Text{ "Pirate's Fortress Small Key", "Pirate's Fortress Small Key", "Pirate's Fortress Small Key" }, + }; + trickNameTable[RG_GANONS_CASTLE_SMALL_KEY] = { + // TODO_TRANSALTE + Text{ "Hyrule Castle Salmon Koi", "Hyrule Castle Salmon Koi", "Hyrule Castle Salmon Koi" }, + Text{ "Onox's Castle Small Key", "Onox's Castle Small Key", "Onox's Castle Small Key" }, + Text{ "Vaati's Palace Small Key", "Vaati's Palace Small Key", "Vaati's Palace Small Key" }, + }; + + trickNameTable[RG_FOREST_TEMPLE_KEY_RING] = { + // TODO_TRANSALTE + Text{ "Wind Temple Key Ring", "Wind Temple Key Ring", "Wind Temple Key Ring" }, + Text{ "Woodfall Temple Key Ring", "Woodfall Temple Key Ring", "Woodfall Temple Key Ring" }, + Text{ "Skull Woods Key Ring", "Skull Woods Key Ring", "Skull Woods Key Ring" }, + }; + trickNameTable[RG_FIRE_TEMPLE_KEY_RING] = { + // TODO_TRANSALTE + Text{ "Ice Cavern Keese Ring", "Ice Cavern Keese Ring", "Ice Cavern Keese Ring" }, + Text{ "Goron Temple Key Ring", "Goron Temple Key Ring", "Goron Temple Key Ring" }, + Text{ "Eldin Temple Koi Ray", "Eldin Temple Koi Ray", "Eldin Temple Koi Ray" }, + }; + trickNameTable[RG_WATER_TEMPLE_KEY_RING] = { + // TODO_TRANSALTE + Text{ "Swamp Palace Keese Ring", "Swamp Palace Keese Ring", "Swamp Palace Keese Ring" }, + Text{ "Great Bay Temple Key Ring", "Great Bay Temple Key Ring", "Great Bay Temple Key Ring" }, + Text{ "Lakebed Temple Key Ring", "Lakebed Temple Key Ring", "Lakebed Temple Key Ring" }, + }; + trickNameTable[RG_SPIRIT_TEMPLE_KEY_RING] = { + // TODO_TRANSALTE + Text{ "Light Temple Key Ring", "Light Temple Key Ring", "Light Temple Key Ring" }, + Text{ "Lightning Temple Key Ring", "Lightning Temple Key Ring", "Lightning Temple Key Ring" }, + Text{ "Desert Palace Key Ring", "Desert Palace Key Ring", "Desert Palace Key Ring" }, + Text{ "Stone Tower Keese Ring", "Stone Tower Keese Ring", "Stone Tower Keese Ring" }, + }; + trickNameTable[RG_SHADOW_TEMPLE_KEY_RING] = { + // TODO_TRANSALTE + Text{ "Palace of Darkness Key Ring", "Palace of Darkness Key Ring", "Palace of Darkness Key Ring" }, + Text{ "Shrine of Illusion Koi Ray", "Shrine of Illusion Koi Ray", "Shrine of Illusion Koi Ray" }, + Text{ "Palace of Twilight Key Ring", "Palace of Twilight Key Ring", "Palace of Twilight Key Ring" }, + }; + trickNameTable[RG_BOTTOM_OF_THE_WELL_KEY_RING] = { + // TODO_TRANSALTE + Text{ "Top of the Wall Key Ring", "Top of the Wall Key Ring", "Top of the Wall Key Ring" }, + Text{ "Breath of the Wild Key Ring", "Breath of the Wild Key Ring", "Breath of the Wild Key Ring" }, + Text{ "Beneath the Well Key Ring", "Beneath the Well Key Ring", "Beneath the Well Key Ring" }, + }; + trickNameTable[RG_GERUDO_TRAINING_GROUND_KEY_RING] = { + // TODO_TRANSALTE + Text{ "Gerudo Sanctum Key Ring", "Gerudo Sanctum Key Ring", "Gerudo Sanctum Key Ring" }, + Text{ "Lady's Lair Keese Ring", "Lady's Lair Keese Ring", "Lady's Lair Keese Ring" }, + Text{ "Knight Acadamy Key Ring", "Knight Acadamy Key Ring", "Knight Acadamy Key Ring" }, + }; + trickNameTable[RG_GERUDO_FORTRESS_KEY_RING] = { + // TODO_TRANSALTE + Text{ "Fortress of Winds Key Ring", "Fortress of Winds Key Ring", "Fortress of Winds Key Ring" }, + Text{ "Thieve's Town Key Ring", "Thieve's Town Key Ring", "Thieve's Town Key Ring" }, + Text{ "Fortress Centrum Key Ring", "Fortress Centrum Key Ring", "Fortress Centrum Key Ring" }, + Text{ "Forsaken Fortress Key Ring", "Forsaken Fortress Key Ring", "Forsaken Fortresse Key Ring" }, + Text{ "Pirate's Fortress Key Ring", "Pirate's Fortress Key Ring", "Pirate's Fortress Key Ring" }, + }; + trickNameTable[RG_GANONS_CASTLE_KEY_RING] = { + // TODO_TRANSALTE + Text{ "Hyrule Castle Koi Ray", "Hyrule Castle Koi Ray", "Hyrule Castle Koi Ray" }, + Text{ "Onox's Castle Key Ring", "Onox's Castle Key Ring", "Onox's Castle Key Ring" }, + Text{ "Vaati's Palace Key Ring", "Vaati's Palace Key Ring", "Vaati's Palace Key Ring" }, + }; + + trickNameTable[RG_FOREST_TEMPLE_BOSS_KEY] = { + // TODO_TRANSALTE + Text{ "Wind Temple Boss Key", "Wind Temple Boss Key", "Wind Temple Boss Key" }, + Text{ "Woodfall Temple Boss Key", "Woodfall Temple Boss Key", "Woodfall Temple Boss Key" }, + Text{ "Skull Woods Boss Key", "Skull Woods Boss Key", "Skull Woods Boss Key" }, + Text{ "Phantom Ganon's Key", "Phantom Ganon's Key", "Phantom Ganon's Key" }, + Text{ "Deku Tree's Boss Key", "Deku Tree's Boss Key", "Deku Tree's Boss Key" }, + }; + trickNameTable[RG_FIRE_TEMPLE_BOSS_KEY] = { + // TODO_TRANSALTE + Text{ "Ice Cavern Boss Keese", "Ice Cavern Boss Keese", "Ice Cavern Boss Keese" }, + Text{ "Goron Temple Boss Key", "Goron Temple Boss Key", "Goron Temple Boss Key" }, + Text{ "Eldin Temple Boss Koi", "Eldin Temple Boss Koi", "Eldin Temple Boss Koi" }, + Text{ "Volvagia's Key", "Volvagia's Key", "Volvagia's Key" }, + Text{ "Dodongo's Cavern Boss Key", "Dodongo's Cavern Boss Key", "Dodongo's Cavern Boss Key" }, + }; + trickNameTable[RG_WATER_TEMPLE_BOSS_KEY] = { + // TODO_TRANSALTE + Text{ "Swamp Palace Boss Keese", "Swamp Palace Boss Keese", "Swamp Palace Boss Keese" }, + Text{ "Great Bay Temple Boss Key", "Great Bay Temple Boss Key", "Great Bay Temple Boss Key" }, + Text{ "Lakebed Temple Boss Key", "Lakebed Temple Boss Key", "Lakebed Temple Boss Key" }, + Text{ "Morpha's Key", "Morpha's Key", "Morpha's Key" }, + Text{ "Jabu Jabu's Belly Boss Key", "Jabu Jabu's Belly Boss Key", "Jabu Jabu's Belly Boss Key" }, + }; + trickNameTable[RG_SPIRIT_TEMPLE_BOSS_KEY] = { + // TODO_TRANSALTE + Text{ "Light Temple Boss Key", "Light Temple Boss Key", "Light Temple Boss Key" }, + Text{ "Lightning Temple Boss Key", "Lightning Temple Boss Key", "Lightning Temple Boss Key" }, + Text{ "Desert Palace Boss Key", "Desert Palace Boss Key", "Desert Palace Boss Key" }, + Text{ "Stone Tower Boss Keese", "Stone Tower Boss Keese", "Stone Tower Boss Keese" }, + Text{ "Twinrova's Key", "Twinrova's Key", "Twinrova's Key" }, + }; + trickNameTable[RG_SHADOW_TEMPLE_BOSS_KEY] = { + // TODO_TRANSALTE + Text{ "Palace of Darkness Boss Key", "Palace of Darkness Boss Key", "Palace of Darkness Boss Key" }, + Text{ "Shrine of Illusion Bass Koi", "Shrine of Illusion Bass Koi", "Shrine of Illusion Bass Koi" }, + Text{ "Palace of Twilight Boss Key", "Palace of Twilight Boss Key", "Palace of Twilight Boss Key" }, + Text{ "Bongo Bongo's Key", "Bongo Bongo's Key", "Bongo Bongo's Key" }, + }; + trickNameTable[RG_GANONS_CASTLE_BOSS_KEY] = { + // TODO_TRANSALTE + Text{ "Hyrule Castle Bass Koi", "Hyrule Castle Bass Koi", "Hyrule Castle Bass Koi" }, + Text{ "Onox's Castle Boss Key", "Onox's Castle Boss Key", "Onox's Castle Boss Key" }, + Text{ "Vaati's Palace Boss Key", "Vaati's Palace Boss Key", "Vaati's Palace Boss Key" }, + Text{ "Ganondorf's Key", "Ganondorf's Key", "Ganondorf's Key" }, + }; + + trickNameTable[RG_GUARD_HOUSE_KEY] = { + // TODO_TRANSLATE + Text{ "Pot Room Key", "Pot Room Key", "Pot Room Key" }, + Text{ "Poe Shop Keese", "Poe Shop Keese", "Poe Shop Keese" }, + Text{ "Pot Collectors Club Key", "Pot Collectors Club Key", "Pot Collectors Club Key" }, + }; + trickNameTable[RG_MARKET_BAZAAR_KEY] = { + // TODO_TRANSLATE + Text{ "Malo Mart Key", "Malo Mart Key", "Malo Mart Key" }, + Text{ "Zora Shop Key", "Zora Shop Key", "Zora Shop Key" }, + Text{ "Goronu General Store Key", "Goronu General Store Key", "Goronu General Store Key" }, + Text{ "Chudly's Fine Goods Key", "Chudly's Fine Goods Key", "Chudly's Fine Goods Key" }, + }; + trickNameTable[RG_MARKET_POTION_SHOP_KEY] = { + // TODO_TRANSLATE + Text{ "Market Medicine Shop Koi", "Market Medicine Shop Koi", "Market Medicine Shop Koi" }, + Text{ "Market Pharmacy Key", "Market Pharmacy Key", "Market Pharmacy Key" }, + Text{ "Market Drug Store Keese", "Market Drug Store Keese", "Market Drug Store Keese" }, + }; + trickNameTable[RG_MASK_SHOP_KEY] = { + // TODO_TRANSLATE + Text{ "Masked Ship Koi", "Masked Ship Koi", "Masked Ship Koi" }, + Text{ "Madame Couture's Key", "Madame Couture's Key", "Madame Couture's Key" }, + Text{ "South Clock Town Key", "South Clock Town Key", "South Clock Town Key" }, + }; + trickNameTable[RG_MARKET_SHOOTING_GALLERY_KEY] = { + // TODO_TRANSLATE + Text{ "Swamp Shooting Gallery Key", "Swamp Shooting Gallery Key", "Swamp Shooting Gallery Key" }, + Text{ "Koume's Target Shooting Key", "Koume's Target Shooting Key", "Koume's Target Shooting Key" }, + Text{ "Pumpkin Pull Key", "Pumpkin Pull Key", "Pumpkin Pull Key" }, + }; + trickNameTable[RG_BOMBCHU_BOWLING_KEY] = { + // TODO_TRANSLATE + Text{ "Bombchu Gallery Key", "Bombchu Gallery Key", "Bombchu Gallery Key" }, + Text{ "Cucco Bowling Ally Key", "Cucco Bowling Ally Key", "Cucco Bowling Ally Key" }, + Text{ "Snowball Bowling Key", "Snowball Bowling Key", "Snowball Bowling Key" }, + Text{ "Bombsketball Key", "Bombsketball Key", "Bombsketball Key" }, + }; + trickNameTable[RG_TREASURE_CHEST_GAME_BUILDING_KEY] = { + // TODO_TRANSLATE + Text{ "Lucky Treasure Game Koi", "Lucky Treasure Game Koi", "Lucky Treasure Game Koi" }, + Text{ "1 in 32 Key", "1 in 32 Key", "1 in 32 Key" }, + Text{ "Fortune's Coice Key", "Fortune's Coice Key", "Fortune's Coice Key" }, + Text{ "Money Making Game Key", "Money Making Game Key", "Money Making Game Key" }, + Text{ "Trading Card Game Key", "Trading Card Game Key", "Trading Card Game Key" }, + }; + trickNameTable[RG_BOMBCHU_SHOP_KEY] = { + // TODO_TRANSLATE + Text{ "Curiosity Shop Key", "Curiosity Shop Key", "Curiosity Shop Key" }, + Text{ "Barnes Bomb Shop Key", "Barnes Bomb Shop Key", "Barnes Bomb Shop Key" }, + Text{ "Bomb Flower Shop Key", "Bomb Flower Shop Key", "Bomb Flower Shop Key" }, + }; + trickNameTable[RG_RICHARDS_HOUSE_KEY] = { + // TODO_TRANSLATE + Text{ "Stockwell's House Key", "Stockwell's House Key", "Stockwell's House Key" }, + Text{ "Blue Dog's House Key", "Blue Dog's House Key", "Blue Dog's House Key" }, + Text{ "Barkle's House Key", "Barkle's House Key", "Barkle's House Key" }, + Text{ "Chimimi's House Key", "Chimimi's House Key", "Chimimi's House Key" }, + }; + trickNameTable[RG_ALLEY_HOUSE_KEY] = { + // TODO_TRANSLATE + Text{ "Mido's House Key", "Mido's House Key", "Mido's House Key" }, + Text{ "Saria's House Key", "Saria's House Key", "Saria's House Key" }, + Text{ "@'s House Key", "@'s House Key", "@'s House Key" }, + }; + trickNameTable[RG_KAK_BAZAAR_KEY] = { + // TODO_TRANSLATE + Text{ "Skyloft Bazaar Key", "Skyloft Bazaar Key", "Skyloft Bazaar Key" }, + Text{ "Harlequin Bazaar Key", "Harlequin Bazaar Key", "Harlequin Bazaar Key" }, + Text{ "Kokiri Shop Key", "Kokiri Shop Key", "Kokiri Shop Key" }, + Text{ "Goron Shop Key", "Goron Shop Key", "Goron Shop Key" }, + }; + trickNameTable[RG_KAK_POTION_SHOP_KEY] = { + // TODO_TRANSLATE + Text{ "Kak Medicine Shop Keep", "Kak Medicine Shop Keep", "Kak Medicine Shop Keep" }, + Text{ "Kak Pharmacy Key", "Kak Pharmacy Key", "Kak Pharmacy Key" }, + Text{ "Kak Drug Store Keese", "Kak Drug Store Keese", "Kak Drug Store Keese" }, + }; + trickNameTable[RG_BOSS_HOUSE_KEY] = { + // TODO_TRANSLATE + Text{ "Kakariko Village Boss Key", "Kakariko Village Boss Key", "Kakariko Village Boss Key" }, + Text{ "Lord Kohga's House Key", "Lord Kohga's House Key", "Lord Kohga's House Key" }, + Text{ "Twinrova's House Key", "Twinrova's House Key", "Twinrova's House Key" }, + }; + trickNameTable[RG_GRANNYS_POTION_SHOP_KEY] = { + // TODO_TRANSLATE + Text{ "Grandpa's Potion Shop Key", "Grandpa's Potion Shop Key", "Grandpa's Potion Shop Key" }, + Text{ "Witch's Hut Key", "Witch's Hut Key", "Witch's Hut Key" }, + Text{ "Hags's Potion Shop Key", "Hags's Potion Shop Key", "Hags's Potion Shop Key" }, + Text{ "Syrup's Potion Shop Key", "Syrup's Potion Shop Key", "Syrup's Potion Shop Key" }, + }; + trickNameTable[RG_SKULLTULA_HOUSE_KEY] = { + // TODO_TRANSLATE + Text{ "Town Spider House Key", "Town Spider House Key", "Town Spider House Key" }, + Text{ "Jovani's House Key", "Jovani's House Key", "Jovani's House Key" }, + Text{ "Maiamai House Key", "Maiamai House Key", "Maiamai House Key" }, + }; + trickNameTable[RG_IMPAS_HOUSE_KEY] = { + // TODO_TRANSLATE + Text{ "Zelda's House Key", "Zelda's House Key", "Zelda's House Key" }, + Text{ "Sheik's House Key", "Sheik's House Key", "Sheik's House Key" }, + Text{ "Purah's House Key", "Purah's House Key", "Purah's House Key" }, + }; + trickNameTable[RG_WINDMILL_KEY] = { + // TODO_TRANSLATE + Text{ "Wind Switch Key", "Wind Switch Key", "Wind Switch Key" }, + Text{ "Weather Vane Key", "Weather Vane Key", "Weather Vane Key" }, + }; + trickNameTable[RG_KAK_SHOOTING_GALLERY_KEY] = { + // TODO_TRANSLATE + Text{ "Firing Range Key", "Firing Range Key", "Firing Range Key" }, + Text{ "Crossbow Training Key", "Crossbow Training Key", "Crossbow Training Key" }, + Text{ "Goron Target Range Key", "Goron Target Range Key", "Goron Target Range Key" }, + }; + trickNameTable[RG_DAMPES_HUT_KEY] = { + // TODO_TRANSLATE + Text{ "Dampe's Grave Key", "Dampe's Grave Key", "Dampe's Grave Key" }, + Text{ "Dampe Studio Key", "Dampe Studio Key", "Dampe Studio Key" }, + Text{ "Old Man's Cabin Key", "Old Man's Cabin Key", "Old Man's Cabin Key" }, + }; + trickNameTable[RG_TALONS_HOUSE_KEY] = { + // TODO_TRANSLATE + Text{ "Malon's House Koi", "Malon's House Koi", "Malon's House Koi" }, + Text{ "Ingo's House Keese", "Ingo's House Keese", "Ingo's House Keese" }, + Text{ "Mario's House Key", "Mario's House Key", "Mario's House Key" }, + }; + trickNameTable[RG_STABLES_KEY] = { + // TODO_TRANSLATE + Text{ "Corral Key", "Corral Key", "Corral Key" }, + Text{ "Foothill Stable Key", "Foothill Stable Key", "Foothill Stable Key" }, + Text{ "Goat Barn Key", "Goat Barn Key", "Goat Barn Key" }, + }; + trickNameTable[RG_BACK_TOWER_KEY] = { + // TODO_TRANSLATE + Text{ "Tower of Hera Key", "Tower of Hera Key", "Tower of Hera Key" }, + Text{ "Clock Tower Key", "Clock Tower Key", "Clock Tower Key" }, + Text{ "Tingle Tower Key", "Tingle Tower Key", "Tingle Tower Key" }, + Text{ "Skyview Tower Key", "Skyview Tower Key", "Skyview Tower Key" }, + Text{ "Sheikah Tower Key", "Sheikah Tower Key", "Sheikah Tower Key" }, + }; + trickNameTable[RG_HYLIA_LAB_KEY] = { + // TODO_TRANSLATE + Text{ "Marine Research Lab Key", "Marine Research Lab Key", "Marine Research Lab Key" }, + Text{ "Hateno Tech Lab Key", "Hateno Tech Lab Key", "Hateno Tech Lab Key" }, + Text{ "Tough Mango Lab Key", "Tough Mango Lab Key", "Tough Mango Lab Key" }, + }; + trickNameTable[RG_FISHING_HOLE_KEY] = { + // TODO_TRANSLATE + Text{ "Swamp Fishing Hole Key", "Swamp Fishing Hole Key", "Swamp Fishing Hole Key" }, + Text{ "Beaver Race Key", "Beaver Race Key", "Beaver Race Key" }, + Text{ "Squid-Hunt Key", "Squid-Hunt Key", "Squid-Hunt Key" }, + }; + /* //Names for individual upgrades, in case progressive names are replaced trickNameTable[GI_HOOKSHOT] = { @@ -1159,12 +1539,16 @@ void InitTrickNames() { } // Generate a fake name for the ice trap based on the item it's displayed as -Text GetIceTrapName(uint8_t id) { +Text GetIceTrapName(int id) { // If the trick names table has not been initialized, do so if (!initTrickNames) { InitTrickNames(); initTrickNames = true; } + if (trickNameTable[id].empty()) { + assert(false); + return Text{ "not an Ice Trap" }; + } // Randomly get the easy, medium, or hard name for the given item id return RandomElement(trickNameTable[id]); } diff --git a/soh/soh/Enhancements/randomizer/3drando/shops.hpp b/soh/soh/Enhancements/randomizer/3drando/shops.hpp index 4cf780e83..109ea6143 100644 --- a/soh/soh/Enhancements/randomizer/3drando/shops.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/shops.hpp @@ -27,4 +27,4 @@ extern std::vector GetMinVanillaShopItems(int total_replaced); extern uint16_t GetRandomPrice(Rando::Location* loc, PriceSettingsStruct priceSettings); extern uint16_t GetCheapBalancedPrice(); extern int GetShopsanityReplaceAmount(); -extern Text GetIceTrapName(uint8_t id); +extern Text GetIceTrapName(int id); diff --git a/soh/soh/Enhancements/randomizer/3drando/starting_inventory.cpp b/soh/soh/Enhancements/randomizer/3drando/starting_inventory.cpp index a971ba6e5..e7f875bf1 100644 --- a/soh/soh/Enhancements/randomizer/3drando/starting_inventory.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/starting_inventory.cpp @@ -22,7 +22,6 @@ void GenerateStartingInventory() { if (dungeon->GetMap() != RG_NONE) { AddItemToInventory(dungeon->GetMap()); } - if (dungeon->GetCompass() != RG_NONE) { AddItemToInventory(dungeon->GetCompass()); } @@ -155,32 +154,6 @@ void GenerateStartingInventory() { // AddItemToInventory(RG_SHADOW_MEDALLION, StartingShadowMedallion.Value()); // AddItemToInventory(RG_LIGHT_MEDALLION, StartingLightMedallion.Value()); AddItemToInventory(RG_GOLD_SKULLTULA_TOKEN, ctx->GetOption(RSK_STARTING_SKULLTULA_TOKEN).Get()); - - int8_t hearts = ctx->GetOption(RSK_STARTING_HEARTS).Get() - 2; - AdditionalHeartContainers = 0; - if (hearts < 0) { - AddItemToInventory(RG_PIECE_OF_HEART, 4); - // Plentiful and minimal have less than 4 standard pieces of heart so also replace the winner heart - if (ctx->GetOption(RSK_ITEM_POOL).Get() == 0 || ctx->GetOption(RSK_ITEM_POOL).Get() == 3) { - AddItemToInventory(RG_TREASURE_GAME_HEART); - } - - AdditionalHeartContainers = 1 - hearts; - } else if (hearts > 0) { - // 16 containers in plentiful, 8 in balanced and 0 in the others - uint8_t maxContainers = 8 * std::max(0, 2 - ctx->GetOption(RSK_ITEM_POOL).Get()); - - if (hearts <= maxContainers) { - AddItemToInventory(RG_HEART_CONTAINER, hearts); - } else { - AddItemToInventory(RG_HEART_CONTAINER, maxContainers); - AddItemToInventory(RG_PIECE_OF_HEART, (hearts - maxContainers) * 4); - } - - if (hearts == 17) { - AddItemToInventory(RG_TREASURE_GAME_HEART); - } - } } bool StartingInventoryHasBottle() { diff --git a/soh/soh/Enhancements/randomizer/SeedContext.cpp b/soh/soh/Enhancements/randomizer/SeedContext.cpp index 054f92b2a..41a6cb02b 100644 --- a/soh/soh/Enhancements/randomizer/SeedContext.cpp +++ b/soh/soh/Enhancements/randomizer/SeedContext.cpp @@ -67,6 +67,14 @@ RandomizerArea Context::GetAreaFromString(std::string str) { return (RandomizerArea)StaticData::areaNameToEnum[str]; } +int Context::CountEmptyLocations(const bool countShops) { + auto ctx = Rando::Context::GetInstance(); + return count_if(allLocations.begin(), allLocations.end(), [ctx, countShops](const auto loc) { + return ctx->GetItemLocation(loc)->GetPlacedRandomizerGet() == RG_NONE && + (countShops || Rando::StaticData::GetLocation(loc)->GetRCType() != RCTYPE_SHOP); + }); +} + void Context::InitStaticData() { StaticData::HintTable_Init(); StaticData::trialNameToEnum = StaticData::PopulateTranslationMap(StaticData::trialData); @@ -329,7 +337,17 @@ void Context::CreateItemOverrides() { // If this is an ice trap, store the disguise model in iceTrapModels const auto itemLoc = GetItemLocation(locKey); if (itemLoc->GetPlacedRandomizerGet() == RG_ICE_TRAP) { - ItemOverride val(locKey, RandomElement(possibleIceTrapModels)); + RandomizerGet trickModel = RandomElementFromSet(possibleIceTrapModels); + if (trickModel == RG_EMPTY_BOTTLE) { + trickModel = RandomElement(StaticData::normalBottles); + } + if (trickModel == RG_GUARD_HOUSE_KEY) { + trickModel = RandomElement(StaticData::overworldKeys); + } + if (trickModel == RG_DEATH_MOUNTAIN_CRATER_BEAN_SOUL) { + trickModel = RandomElement(StaticData::beanSouls); + } + ItemOverride val(locKey, trickModel); iceTrapModels[locKey] = val.LooksLike(); val.SetTrickName(GetIceTrapName(val.LooksLike())); // If this is ice trap is in a shop, change the name based on what the model will look like diff --git a/soh/soh/Enhancements/randomizer/SeedContext.h b/soh/soh/Enhancements/randomizer/SeedContext.h index bd20817f5..659531da1 100644 --- a/soh/soh/Enhancements/randomizer/SeedContext.h +++ b/soh/soh/Enhancements/randomizer/SeedContext.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).Get() @@ -125,13 +126,14 @@ class Context { std::map overrides = {}; std::vector> playthroughLocations = {}; std::vector everyPossibleLocation = {}; - std::vector possibleIceTrapModels = {}; + std::set possibleIceTrapModels = {}; std::unordered_map iceTrapModels = {}; std::vector VanillaLogicDefaults = {}; std::array hashIconIndexes = {}; bool playthroughBeatable = false; bool allLocationsReachable = false; RandomizerArea GetAreaFromString(std::string str); + int CountEmptyLocations(bool countShops); /** * @brief Get the hash for the current seed. diff --git a/soh/soh/Enhancements/randomizer/entrance.cpp b/soh/soh/Enhancements/randomizer/entrance.cpp index b2d301988..f008c33a1 100644 --- a/soh/soh/Enhancements/randomizer/entrance.cpp +++ b/soh/soh/Enhancements/randomizer/entrance.cpp @@ -1074,7 +1074,7 @@ static std::array, 2> SplitEntrancesByRequirements(std::v logic->Reset(); // Apply the effects of all advancement items to search for entrance accessibility std::vector items = FilterFromPool( - ItemPool, [](const RandomizerGet i) { return Rando::StaticData::RetrieveItem(i).IsAdvancement(); }); + itemPool, [](const RandomizerGet i) { return Rando::StaticData::RetrieveItem(i).IsAdvancement(); }); for (RandomizerGet unplacedItem : items) { Rando::StaticData::RetrieveItem(unplacedItem).ApplyEffect(); } diff --git a/soh/soh/Enhancements/randomizer/location_access/root.cpp b/soh/soh/Enhancements/randomizer/location_access/root.cpp index 7c7e63540..b96c49ef5 100644 --- a/soh/soh/Enhancements/randomizer/location_access/root.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/root.cpp @@ -20,6 +20,8 @@ void RegionTable_Init_Root() { LOCATION(RC_TRIFORCE_COMPLETED, logic->GetSaveContext()->ship.quest.data.randomizer.triforcePiecesCollected >= ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_REQUIRED).Get() + 1;), LOCATION(RC_SARIA_SONG_HINT, logic->CanUse(RG_SARIAS_SONG)), LOCATION(RC_SONG_FROM_IMPA, (bool)ctx->GetOption(RSK_SKIP_CHILD_ZELDA)), + LOCATION(RC_HC_MALON_EGG, (bool)ctx->GetOption(RSK_SKIP_CHILD_ZELDA)), + LOCATION(RC_HC_ZELDAS_LETTER, (bool)ctx->GetOption(RSK_SKIP_CHILD_ZELDA)), LOCATION(RC_TOT_MASTER_SWORD, (bool)ctx->GetOption(RSK_SELECTED_STARTING_AGE).Is(RO_AGE_ADULT)), }, { //Exits diff --git a/soh/soh/Enhancements/randomizer/option_descriptions.cpp b/soh/soh/Enhancements/randomizer/option_descriptions.cpp index 4ea791619..ea559f7a8 100644 --- a/soh/soh/Enhancements/randomizer/option_descriptions.cpp +++ b/soh/soh/Enhancements/randomizer/option_descriptions.cpp @@ -641,18 +641,18 @@ void Settings::CreateOptionDescriptions() { "Scarce - Some excess items are removed, including health upgrades.\n" "\n" "Minimal - Most excess items are removed."; - mOptionDescriptions[RSK_ICE_TRAPS] = "Sets how many items are replaced by ice traps.\n" - "\n" - "Off - No ice traps.\n" - "\n" - "Normal - Only Ice Traps from the base item pool are shuffled in.\n" - "\n" - "Extra - Chance to replace added junk items with additional ice traps.\n" - "\n" - "Mayhem - All added junk items will be Ice Traps.\n" - "\n" - "Onslaught - All junk items will be replaced by Ice Traps, even those " - "in the base pool."; + mOptionDescriptions[RSK_BASE_ICE_TRAPS] = + "Sets if ice traps that exist in vanilla are shuffled into the item pool.\n" + "If this is on, 1 Trap will always be added to the pool,\n" + "an additional trap will be added if Gerudo Training Grounds\n" + "is NOT master quest,\n" + "and 4 more will be added if Ganon's Castle is NOT Master Quest."; + mOptionDescriptions[RSK_ADDITIONAL_ICE_TRAPS] = + "Sets how many more Ice Traps will be added to item pool,\n" + "assuming there is enough space after placing Progression Items.\n\n" + "You do not need to have base ice traps on for this setting to work."; + mOptionDescriptions[RSK_ICE_TRAP_PERCENT] = + "If set above 0, each Junk item has that chance of being replaced with an extra Ice Trap."; mOptionDescriptions[RSK_GOSSIP_STONE_HINTS] = "Allows Gossip Stones to provide hints on item locations. Hints mentioning " "\"Way of the Hero\" indicate a location that holds an item required to beat " diff --git a/soh/soh/Enhancements/randomizer/randomizerTypes.h b/soh/soh/Enhancements/randomizer/randomizerTypes.h index bac5fa2ee..bf75c9321 100644 --- a/soh/soh/Enhancements/randomizer/randomizerTypes.h +++ b/soh/soh/Enhancements/randomizer/randomizerTypes.h @@ -6453,7 +6453,9 @@ typedef enum { RSK_SHUFFLE_BUSHES, RSK_SHUFFLE_FROG_SONG_RUPEES, RSK_ITEM_POOL, - RSK_ICE_TRAPS, + RSK_BASE_ICE_TRAPS, + RSK_ADDITIONAL_ICE_TRAPS, + RSK_ICE_TRAP_PERCENT, RSK_GOSSIP_STONE_HINTS, RSK_TOT_ALTAR_HINT, RSK_GANONDORF_HINT, @@ -6885,13 +6887,7 @@ typedef enum { } RandoOptionItemPool; // Ice Trap Settings -typedef enum { - RO_ICE_TRAPS_OFF, - RO_ICE_TRAPS_NORMAL, - RO_ICE_TRAPS_EXTRA, - RO_ICE_TRAPS_MAYHEM, - RO_ICE_TRAPS_ONSLAUGHT, -} RandoOptionIceTraps; +typedef enum { RO_ICE_TRAPS_OFF, RO_ICE_TRAPS_NORMAL, RO_ICE_TRAPS_COUNT, RO_ICE_TRAPS_PERCENT } RandoOptionIceTraps; // Gossip Stone Hint Settings (no hints, needs nothing, // needs mask of truth, needs stone of agony) diff --git a/soh/soh/Enhancements/randomizer/savefile.cpp b/soh/soh/Enhancements/randomizer/savefile.cpp index 0bcfa1038..f130f10a3 100644 --- a/soh/soh/Enhancements/randomizer/savefile.cpp +++ b/soh/soh/Enhancements/randomizer/savefile.cpp @@ -363,6 +363,10 @@ extern "C" void Randomizer_InitSaveFile() { if (Randomizer_GetSettingValue(RSK_SKIP_CHILD_ZELDA)) { GetItemEntry getItemEntry = Randomizer_GetItemFromKnownCheck(RC_SONG_FROM_IMPA, (GetItemID)RG_ZELDAS_LULLABY); StartingItemGive(getItemEntry, RC_SONG_FROM_IMPA); + getItemEntry = Randomizer_GetItemFromKnownCheck(RC_HC_MALON_EGG, (GetItemID)RG_WEIRD_EGG); + StartingItemGive(getItemEntry, RC_HC_ZELDAS_LETTER); + getItemEntry = Randomizer_GetItemFromKnownCheck(RC_HC_ZELDAS_LETTER, (GetItemID)RG_ZELDAS_LETTER); + StartingItemGive(getItemEntry, RC_HC_MALON_EGG); // Malon/Talon back at ranch. Flags_SetEventChkInf(EVENTCHKINF_OBTAINED_POCKET_EGG); diff --git a/soh/soh/Enhancements/randomizer/settings.cpp b/soh/soh/Enhancements/randomizer/settings.cpp index ba2fdbda2..52334e851 100644 --- a/soh/soh/Enhancements/randomizer/settings.cpp +++ b/soh/soh/Enhancements/randomizer/settings.cpp @@ -1236,7 +1236,9 @@ void Settings::CreateOptions() { OPT_BOOL(RSK_SKELETON_KEY, "Skeleton Key", CVAR_RANDOMIZER_SETTING("SkeletonKey"), mOptionDescriptions[RSK_SKELETON_KEY]); OPT_BOOL(RSK_SLINGBOW_BREAK_BEEHIVES, "Slingshot/Bow Can Break Beehives", CVAR_RANDOMIZER_SETTING("SlingBowBeehives"), mOptionDescriptions[RSK_SLINGBOW_BREAK_BEEHIVES]); OPT_U8(RSK_ITEM_POOL, "Item Pool", {"Plentiful", "Balanced", "Scarce", "Minimal"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ItemPool"), mOptionDescriptions[RSK_ITEM_POOL], WIDGET_CVAR_COMBOBOX, RO_ITEM_POOL_BALANCED); - OPT_U8(RSK_ICE_TRAPS, "Ice Traps", {"Off", "Normal", "Extra", "Mayhem", "Onslaught"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("IceTraps"), mOptionDescriptions[RSK_ICE_TRAPS], WIDGET_CVAR_COMBOBOX, RO_ICE_TRAPS_NORMAL); + OPT_BOOL(RSK_BASE_ICE_TRAPS, "Base Ice Traps", CVAR_RANDOMIZER_SETTING("BaseIceTraps"), mOptionDescriptions[RSK_BASE_ICE_TRAPS], IMFLAG_NONE, WIDGET_CVAR_COMBOBOX, RO_GENERIC_ON); + OPT_U8(RSK_ADDITIONAL_ICE_TRAPS, "Additional Ice Traps", {NumOpts(0, 100)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("AdditionalIceTraps"), mOptionDescriptions[RSK_ADDITIONAL_ICE_TRAPS], WIDGET_CVAR_SLIDER_INT, 0); + OPT_U8(RSK_ICE_TRAP_PERCENT, "Ice Trap Percent", {NumOpts(0, 100)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("IceTrapPercent"), mOptionDescriptions[RSK_ICE_TRAP_PERCENT], WIDGET_CVAR_SLIDER_INT, 0); // TODO: Remove Double Defense, Progressive Goron Sword OPT_U8(RSK_STARTING_OCARINA, "Start with Ocarina", {"Off", "Fairy Ocarina", "Ocarina of Time"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("StartingOcarina"), "", WIDGET_CVAR_COMBOBOX, RO_STARTING_OCARINA_OFF); OPT_BOOL(RSK_STARTING_DEKU_SHIELD, "Start with Deku Shield", CVAR_RANDOMIZER_SETTING("StartingDekuShield")); @@ -2422,7 +2424,9 @@ void Settings::CreateOptions() { WidgetContainerType::SECTION); mOptionGroups[RSG_MENU_SECTION_TRAPS] = OptionGroup::SubGroup("Traps", { - &mOptions[RSK_ICE_TRAPS], + &mOptions[RSK_BASE_ICE_TRAPS], + &mOptions[RSK_ADDITIONAL_ICE_TRAPS], + &mOptions[RSK_ICE_TRAP_PERCENT], }, WidgetContainerType::SECTION); mOptionGroups[RSG_MENU_COLUMN_HINTS_TRAPS] = @@ -2744,8 +2748,8 @@ void Settings::CreateOptions() { &mOptions[RSK_SKELETON_KEY], &mOptions[RSK_SLINGBOW_BREAK_BEEHIVES], }); - mOptionGroups[RSG_ITEM_POOL] = OptionGroup( - "Item Pool Settings", std::initializer_list({ &mOptions[RSK_ITEM_POOL], &mOptions[RSK_ICE_TRAPS] })); + mOptionGroups[RSG_ITEM_POOL] = + OptionGroup("Item Pool Settings", std::initializer_list({ &mOptions[RSK_ITEM_POOL] })); // TODO: Progressive Goron Sword, Remove Double Defense mOptionGroups[RSG_EXCLUDES_KOKIRI_FOREST] = OptionGroup::SubGroup("Kokiri Forest", mExcludeLocationsOptionsAreas[RCAREA_KOKIRI_FOREST]); diff --git a/soh/soh/Enhancements/randomizer/static_data.cpp b/soh/soh/Enhancements/randomizer/static_data.cpp index 7c5c53cdf..028055ffd 100644 --- a/soh/soh/Enhancements/randomizer/static_data.cpp +++ b/soh/soh/Enhancements/randomizer/static_data.cpp @@ -317,4 +317,58 @@ std::unordered_map StaticData::grottoChestParamsToHint{ }; std::array StaticData::hintTextTable = {}; + +std::vector StaticData::normalBottles = { + RG_EMPTY_BOTTLE, + RG_BOTTLE_WITH_MILK, + RG_BOTTLE_WITH_RED_POTION, + RG_BOTTLE_WITH_GREEN_POTION, + RG_BOTTLE_WITH_BLUE_POTION, + RG_BOTTLE_WITH_FAIRY, + RG_BOTTLE_WITH_FISH, + RG_BOTTLE_WITH_BUGS, + RG_BOTTLE_WITH_POE, + RG_BOTTLE_WITH_BIG_POE, + RG_BOTTLE_WITH_BLUE_FIRE, +}; + +std::vector StaticData::beanSouls = { + RG_DEATH_MOUNTAIN_CRATER_BEAN_SOUL, + RG_DEATH_MOUNTAIN_TRAIL_BEAN_SOUL, + RG_DESERT_COLOSSUS_BEAN_SOUL, + RG_GERUDO_VALLEY_BEAN_SOUL, + RG_GRAVEYARD_BEAN_SOUL, + RG_KOKIRI_FOREST_BEAN_SOUL, + RG_LAKE_HYLIA_BEAN_SOUL, + RG_LOST_WOODS_BRIDGE_BEAN_SOUL, + RG_LOST_WOODS_BEAN_SOUL, + RG_ZORAS_RIVER_BEAN_SOUL, +}; + +std::vector StaticData::overworldKeys = { + RG_GUARD_HOUSE_KEY, + RG_MARKET_BAZAAR_KEY, + RG_MARKET_POTION_SHOP_KEY, + RG_MASK_SHOP_KEY, + RG_MARKET_SHOOTING_GALLERY_KEY, + RG_BOMBCHU_BOWLING_KEY, + RG_TREASURE_CHEST_GAME_BUILDING_KEY, + RG_BOMBCHU_SHOP_KEY, + RG_RICHARDS_HOUSE_KEY, + RG_ALLEY_HOUSE_KEY, + RG_KAK_BAZAAR_KEY, + RG_KAK_POTION_SHOP_KEY, + RG_BOSS_HOUSE_KEY, + RG_GRANNYS_POTION_SHOP_KEY, + RG_SKULLTULA_HOUSE_KEY, + RG_IMPAS_HOUSE_KEY, + RG_WINDMILL_KEY, + RG_KAK_SHOOTING_GALLERY_KEY, + RG_DAMPES_HUT_KEY, + RG_TALONS_HOUSE_KEY, + RG_STABLES_KEY, + RG_BACK_TOWER_KEY, + RG_HYLIA_LAB_KEY, + RG_FISHING_HOLE_KEY, +}; } // namespace Rando diff --git a/soh/soh/Enhancements/randomizer/static_data.h b/soh/soh/Enhancements/randomizer/static_data.h index b01d150ff..0b837179b 100644 --- a/soh/soh/Enhancements/randomizer/static_data.h +++ b/soh/soh/Enhancements/randomizer/static_data.h @@ -83,6 +83,9 @@ class StaticData { static std::unordered_map stoneParamsToHint; static std::unordered_map grottoChestParamsToHint; static std::array hintTextTable; + static std::vector normalBottles; + static std::vector beanSouls; + static std::vector overworldKeys; StaticData(); ~StaticData();