diff --git a/src/dusk/randomizer/data/macros.yaml b/src/dusk/randomizer/data/macros.yaml index 0e274d0d96..ec8a93070f 100644 --- a/src/dusk/randomizer/data/macros.yaml +++ b/src/dusk/randomizer/data/macros.yaml @@ -236,11 +236,20 @@ Can Complete All Dungeons: "'Can_Complete_Forest_Temple' and 'Can_Complete_Goron 'Can_Complete_Arbiters_Grounds' and 'Can_Complete_Snowpeak_Ruins' and 'Can_Complete_Temple_of_Time' and 'Can_Complete_City_in_the_Sky' and 'Can_Complete_Palace_of_Twilight'" -Can Break Hyrule Castle Barrier: Hyrule_Castle_Requirements == Open or - (Hyrule_Castle_Requirements == Vanilla and 'Can_Complete_Palace_of_Twilight') or - (Hyrule_Castle_Requirements == Fused_Shadows and count(Progressive_Fused_Shadow, 3)) or - (Hyrule_Castle_Requirements == Mirror_Shards and count(Progressive_Mirror_Shard, 4)) or - (Hyrule_Castle_Requirements == All_Dungeons and Can_Complete_All_Dungeons) +Can Break Hyrule Castle Barrier: Hyrule_Barrier_Requirements == Open or + (Hyrule_Barrier_Requirements == Vanilla and 'Can_Complete_Palace_of_Twilight') or + (Hyrule_Barrier_Requirements == Fused_Shadows and count(Progressive_Fused_Shadow, Hyrule_Barrier_Fused_Shadows)) or + (Hyrule_Barrier_Requirements == Mirror_Shards and count(Progressive_Mirror_Shard, Hyrule_Barrier_Mirror_Shards)) or + (Hyrule_Barrier_Requirements == Dungeons and dungeons_completed(Hyrule_Barrier_Dungeons)) or + (Hyrule_Barrier_Requirements == Poe_Souls and count(Poe_Soul, Hyrule_Barrier_Poe_Souls)) or + (Hyrule_Barrier_Requirements == Hearts and hearts(Hyrule_Barrier_Hearts)) + +Can Open Hyrule Castle Big Key Gate: Hyrule_Castle_Big_Key_Requirements == None or + (Hyrule_Castle_Big_Key_Requirements == Fused_Shadows and count(Progressive_Fused_Shadow, Hyrule_Castle_Big_Key_Fused_Shadows)) or + (Hyrule_Castle_Big_Key_Requirements == Mirror_Shards and count(Progressive_Fused_Shadow, Hyrule_Castle_Big_Key_Mirror_Shards)) or + (Hyrule_Castle_Big_Key_Requirements == Dungeons and dungeons_completed(Hyrule_Castle_Big_Key_Dungeons)) or + (Hyrule_Castle_Big_Key_Requirements == Poe_Souls and count(Poe_Soul, Hyrule_Castle_Big_Key_Poe_Souls)) or + (Hyrule_Castle_Big_Key_Requirements == Hearts and hearts(Hyrule_Castle_Big_Key_Hearts)) # WARP PORTALS diff --git a/src/dusk/randomizer/data/settings_list.yaml b/src/dusk/randomizer/data/settings_list.yaml index 995c47d591..6fc5f33088 100644 --- a/src/dusk/randomizer/data/settings_list.yaml +++ b/src/dusk/randomizer/data/settings_list.yaml @@ -16,14 +16,46 @@ ## Access Options ## ###################### -- Name: Hyrule Castle Requirements +- Name: Hyrule Barrier Requirements Default Option: Vanilla Options: - Open: "The barrier around Hyrule Castle is dispelled from the beginning." - - Fused Shadows: "The player must collect all 3 Fused Shadows." - - Mirror Shards: "The player must collect all 4 Mirror Shards." - - All Dungeons: "The player must complete all dungeons." - - Vanilla: "The player must complete Palace of Twilight." + - Vanilla: "The barrier will be dispelled once Palace of Twilight is cleared." + - Fused Shadows: "The barrier will be dispelled once the required number of Fused Shadows have been collected." + - Mirror Shards: "The barrier will be dispelled once the required number of Mirror Shards have been collected." + - Dungeons: "The barrier will be dispelled once the required number of Dungeons have been cleared." + - Poe Souls: "The barrier will be dispelled once the required number of Poe Souls have been collected." + - Hearts: "The barrier will be dispelled once the required number of Hearts have been reached." + +- Name: Hyrule Barrier Fused Shadows + Tracker Important: True + Default Option: 1 + Options: + - 1-3: description + +- Name: Hyrule Barrier Mirror Shards + Tracker Important: True + Default Option: 1 + Options: + - 1-4: description + +- Name: Hyrule Barrier Dungeons + Tracker Important: True + Default Option: 1 + Options: + - 1-8: description + +- Name: Hyrule Barrier Poe Souls + Tracker Important: True + Default Option: 1 + Options: + - 1-60: description + +- Name: Hyrule Barrier Hearts + Tracker Important: True + Default Option: 4 + Options: + - 4-20: description # Hehe 420 - Name: Palace of Twilight Requirements Default Option: Vanilla @@ -150,6 +182,47 @@ - Anywhere: "Maps and Compasses can appear anywhere." - Start With: "The player starts with all Maps and Compasses." +- Name: Hyrule Castle Big Key Requirements + Tracker Important: True + Default Option: None + Options: + - None: "The gate is opened and the key is randomized according to the respective Big Key settings." + - Fused Shadows: "The gate will open once the required number of Fused Shadows have been collected." + - Mirror Shards: "The gate will open once the required number of Mirror Shards have been collected." + - Dungeons: "The gate will open once the required number of Dungeons have been cleared." + - Poe Souls: "The gate will open once the required number of Poe Souls have been collected." + - Hearts: "The gate will open once the required number of Hearts have been reached." + +- Name: Hyrule Castle Big Key Fused Shadows + Tracker Important: True + Default Option: 1 + Options: + - 1-3: description + +- Name: Hyrule Castle Big Key Mirror Shards + Tracker Important: True + Default Option: 1 + Options: + - 1-4: description + +- Name: Hyrule Castle Big Key Dungeons + Tracker Important: True + Default Option: 1 + Options: + - 1-8: description + +- Name: Hyrule Castle Big Key Poe Souls + Tracker Important: True + Default Option: 1 + Options: + - 1-60: description + +- Name: Hyrule Castle Big Key Hearts + Tracker Important: True + Default Option: 4 + Options: + - 4-20: description # Hehe 420 + - Name: Dungeon Rewards Can Be Anywhere Default Option: "Off" Options: diff --git a/src/dusk/randomizer/data/startflags.yaml b/src/dusk/randomizer/data/startflags.yaml index 6f4c1f013d..6f26398797 100644 --- a/src/dusk/randomizer/data/startflags.yaml +++ b/src/dusk/randomizer/data/startflags.yaml @@ -85,7 +85,7 @@ EventFlags: - 0x0003 # Yeto put pumpkin and cheese in soup. - 0x1460 # Snowpeak Ruins North and West doors unlocked. - 0x0120 # Told Yeta about cheese - - Hyrule_Castle_Requirements == Open: + - Hyrule_Barrier_Requirements == Open: - 0x4208 # Remove Castle Barrier - Palace_of_Twilight_Requirements == Open: - 0x2B08 # Mirror of Twilight Repaired. @@ -592,6 +592,9 @@ RegionFlags: - 0x93 # Unlock door outside 3F. - 0xB0 # Unlock treasure room door. - 0xA3 # Unlock door in south garden. + - Big_Keys == Keysy and Hyrule_Castle_Big_Key_Requirements == None: + - 0xA1 # Unlocked Hyrule Castle Boss Door. + - 0xED # Got Hyrule Castle Big Key. - Maps_and_Compasses == Start_With: - 0xEE # Got Hyrule Castle Compass. - 0xEF # Got Hyrule Castle Dungeon Map. @@ -601,4 +604,5 @@ RegionFlags: - 0x85 # watched focus on lowered chandelier cs - 0x9D # lower the main hall chandelier - 0xAF # defeated double Dinalfos (opens gates both sides) - \ No newline at end of file + - Hyrule_Castle_Big_Key_Requirements == None: + - 0x94 # Open HC BK gate diff --git a/src/dusk/randomizer/data/tests/logic/all random/settings.yaml b/src/dusk/randomizer/data/tests/logic/all random/settings.yaml index cd3035b8e2..9ff0fb4e65 100644 --- a/src/dusk/randomizer/data/tests/logic/all random/settings.yaml +++ b/src/dusk/randomizer/data/tests/logic/all random/settings.yaml @@ -17,7 +17,7 @@ Gifts From NPCs: Random Golden Bugs: Random Goron Mines Entrance: Random Hidden Skills: Random -Hyrule Castle Requirements: Random +Hyrule Barrier Requirements: Random Increase Spinner Speed: Random Increase Wallet Capacity: Random Instant Message Text: Random diff --git a/src/dusk/randomizer/data/tests/logic/hyrule barrier all dungeons/settings.yaml b/src/dusk/randomizer/data/tests/logic/hyrule barrier all dungeons/settings.yaml new file mode 100644 index 0000000000..b422f42720 --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/hyrule barrier all dungeons/settings.yaml @@ -0,0 +1,3 @@ +Seed: TESTTESTTEST +Hyrule Barrier Requirements: Dungeons +Hyrule Barrier Dungeons: 8 \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/hyrule barrier fused shadows/settings.yaml b/src/dusk/randomizer/data/tests/logic/hyrule barrier fused shadows/settings.yaml new file mode 100644 index 0000000000..46cc3cee9e --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/hyrule barrier fused shadows/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Hyrule Barrier Requirements: Fused Shadows \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/hyrule barrier hearts/settings.yaml b/src/dusk/randomizer/data/tests/logic/hyrule barrier hearts/settings.yaml new file mode 100644 index 0000000000..fd421ef49e --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/hyrule barrier hearts/settings.yaml @@ -0,0 +1,3 @@ +seed: TESTTESTTEST +Hyrule Barrier Requirements: Hearts +Hyrule Barrier Hearts: 13 \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/hyrule barrier mirror shards/settings.yaml b/src/dusk/randomizer/data/tests/logic/hyrule barrier mirror shards/settings.yaml new file mode 100644 index 0000000000..7b2b9536ea --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/hyrule barrier mirror shards/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Hyrule Barrier Requirements: Mirror Shards \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/hyrule barrier open/settings.yaml b/src/dusk/randomizer/data/tests/logic/hyrule barrier open/settings.yaml new file mode 100644 index 0000000000..390c0a24dd --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/hyrule barrier open/settings.yaml @@ -0,0 +1,2 @@ +Seed: TESTTESTTEST +Hyrule Barrier Requirements: Open \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/hyrule barrier poe souls/settings.yaml b/src/dusk/randomizer/data/tests/logic/hyrule barrier poe souls/settings.yaml new file mode 100644 index 0000000000..fe433aa43d --- /dev/null +++ b/src/dusk/randomizer/data/tests/logic/hyrule barrier poe souls/settings.yaml @@ -0,0 +1,3 @@ +seed: TESTTESTTEST +Hyrule Barrier Requirements: Poe Souls +Hyrule Barrier Poe Souls: 30 \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/hyrule castle all dungeons/settings.yaml b/src/dusk/randomizer/data/tests/logic/hyrule castle all dungeons/settings.yaml deleted file mode 100644 index f49d10949c..0000000000 --- a/src/dusk/randomizer/data/tests/logic/hyrule castle all dungeons/settings.yaml +++ /dev/null @@ -1,2 +0,0 @@ -Seed: TESTTESTTEST -Hyrule Castle Requirements: All Dungeons \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/hyrule castle fused shadows/settings.yaml b/src/dusk/randomizer/data/tests/logic/hyrule castle fused shadows/settings.yaml deleted file mode 100644 index fa53bccd12..0000000000 --- a/src/dusk/randomizer/data/tests/logic/hyrule castle fused shadows/settings.yaml +++ /dev/null @@ -1,2 +0,0 @@ -Seed: TESTTESTTEST -Hyrule Castle Requirements: Fused Shadows \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/hyrule castle mirror shards/settings.yaml b/src/dusk/randomizer/data/tests/logic/hyrule castle mirror shards/settings.yaml deleted file mode 100644 index 983a271e6b..0000000000 --- a/src/dusk/randomizer/data/tests/logic/hyrule castle mirror shards/settings.yaml +++ /dev/null @@ -1,2 +0,0 @@ -Seed: TESTTESTTEST -Hyrule Castle Requirements: Mirror Shards \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/hyrule castle open/settings.yaml b/src/dusk/randomizer/data/tests/logic/hyrule castle open/settings.yaml deleted file mode 100644 index c5c79d9e92..0000000000 --- a/src/dusk/randomizer/data/tests/logic/hyrule castle open/settings.yaml +++ /dev/null @@ -1,2 +0,0 @@ -Seed: TESTTESTTEST -Hyrule Castle Requirements: Open \ No newline at end of file diff --git a/src/dusk/randomizer/data/tests/logic/unrequired dungeons are barren/settings.yaml b/src/dusk/randomizer/data/tests/logic/unrequired dungeons are barren/settings.yaml index a0980349e1..99d39af282 100644 --- a/src/dusk/randomizer/data/tests/logic/unrequired dungeons are barren/settings.yaml +++ b/src/dusk/randomizer/data/tests/logic/unrequired dungeons are barren/settings.yaml @@ -1,3 +1,3 @@ Seed: TESTTESTTEST Unrequired Dungeons Are Barren: On -Hyrule Castle Requirements: Mirror Shards \ No newline at end of file +Hyrule Barrier Requirements: Mirror Shards \ No newline at end of file diff --git a/src/dusk/randomizer/data/world/dungeons/Hyrule Castle.yaml b/src/dusk/randomizer/data/world/dungeons/Hyrule Castle.yaml index c7cef7e262..5d165c2ab3 100644 --- a/src/dusk/randomizer/data/world/dungeons/Hyrule Castle.yaml +++ b/src/dusk/randomizer/data/world/dungeons/Hyrule Castle.yaml @@ -161,7 +161,7 @@ Region: Hyrule Castle Locations: Hyrule Castle Southeast Balcony Tower Chest: Can_Defeat_Aerolfos - Hyrule Castle Big Key Chest: Nothing + Hyrule Castle Big Key Chest: Can_Open_Hyrule_Castle_Big_Key_Gate Exits: Hyrule Castle Final Climb Near Outside Balcony: Can_Open_Doors and (count(Hyrule_Castle_Small_Key, 2) or Small_Keys == Keysy) Hyrule Castle Double Darknut Room: Can_Open_Doors and 'Can_Defeat_Hyrule_Castle_Double_Darknuts' diff --git a/src/dusk/randomizer/logic/entrance_shuffle.cpp b/src/dusk/randomizer/logic/entrance_shuffle.cpp index 86eb0e3245..104f42eb6c 100644 --- a/src/dusk/randomizer/logic/entrance_shuffle.cpp +++ b/src/dusk/randomizer/logic/entrance_shuffle.cpp @@ -747,11 +747,11 @@ namespace randomizer::logic::entrance_shuffle foundLocations.end(), [](const auto& location) { return location->IsProgression(); }); - // If there are no sphere zero locations available and we didn't find a disconnected exit, then this world will not - // be valid. Often times when many entrances are randomized we won't find any locations, but will find disconnected + // If there are no sphere zero locations available and we didn't find an accessible disconnected exit, then this world will not + // be valid. Often times when many entrances are randomized we won't find any locations, but will find accessible disconnected // exits that haven't been shuffled yet. In this case we can usually wait until these exits are connected and more often // than not this will lead us to sphere zero locations. - if (numSphereZeroLocations == 0 && !sphereZeroSearch._foundDisconnectedExit) + if (numSphereZeroLocations == 0 && !sphereZeroSearch.HasAccessibleDisconnectedExit()) { throw EntranceShuffleError("No sphere 0 locations reachable at the start!"); } diff --git a/src/dusk/randomizer/logic/flatten/bits.cpp b/src/dusk/randomizer/logic/flatten/bits.cpp index e753a7278c..a4483a4a3c 100644 --- a/src/dusk/randomizer/logic/flatten/bits.cpp +++ b/src/dusk/randomizer/logic/flatten/bits.cpp @@ -243,18 +243,6 @@ int BitIndex::reqBit(const randomizer::logic::requirement::Requirement& req) reverseIndex.push_back(req); return bump(); } - // case randomizer::logic::requirement::Type::HEALTH: - // key = std::to_string(std::get(req._args[0])); - // if (heartCount.contains(key)) - // { - // return heartCount[key]; - // } - // else - // { - // heartCount[key] = counter; - // reverseIndex.push_back(req); - // return bump(); - // } case randomizer::logic::requirement::Type::GOLDEN_BUGS: key = std::to_string(std::get(req._args[0])); if (goldenBugCount.contains(key)) @@ -267,6 +255,30 @@ int BitIndex::reqBit(const randomizer::logic::requirement::Requirement& req) reverseIndex.push_back(req); return bump(); } + case randomizer::logic::requirement::Type::HEARTS: + key = std::to_string(std::get(req._args[0])); + if (heartCount.contains(key)) + { + return heartCount[key]; + } + else + { + heartCount[key] = counter; + reverseIndex.push_back(req); + return bump(); + } + case randomizer::logic::requirement::Type::DUNGEONS_COMPLETED: + key = std::to_string(std::get(req._args[0])); + if (dungeonCompletedCount.contains(key)) + { + return dungeonCompletedCount[key]; + } + else + { + dungeonCompletedCount[key] = counter; + reverseIndex.push_back(req); + return bump(); + } default: // Not a flattening requirement return -1; diff --git a/src/dusk/randomizer/logic/flatten/bits.hpp b/src/dusk/randomizer/logic/flatten/bits.hpp index 738ee678db..aef5302234 100644 --- a/src/dusk/randomizer/logic/flatten/bits.hpp +++ b/src/dusk/randomizer/logic/flatten/bits.hpp @@ -64,8 +64,9 @@ class BitIndex int reqBit(const randomizer::logic::requirement::Requirement& req); std::unordered_map itemBits = {}; - // std::unordered_map heartCount = {}; + std::unordered_map heartCount = {}; std::unordered_map goldenBugCount = {}; + std::unordered_map dungeonCompletedCount = {}; std::vector reverseIndex = {}; int counter = 0; }; diff --git a/src/dusk/randomizer/logic/flatten/flatten.cpp b/src/dusk/randomizer/logic/flatten/flatten.cpp index 1b944eb0be..db7a12c50a 100644 --- a/src/dusk/randomizer/logic/flatten/flatten.cpp +++ b/src/dusk/randomizer/logic/flatten/flatten.cpp @@ -426,12 +426,14 @@ DNF evaluatePartialRequirement(BitIndex& bitIndex, formTime)); } return d; - + + case randomizer::logic::requirement::Type::ITEM: + [[fallthrough]]; case randomizer::logic::requirement::Type::GOLDEN_BUGS: [[fallthrough]]; - case randomizer::logic::requirement::Type::ITEM: - // [[fallthrough]]; - // case randomizer::logic::requirement::Type::HEALTH: + case randomizer::logic::requirement::Type::HEARTS: + [[fallthrough]]; + case randomizer::logic::requirement::Type::DUNGEONS_COMPLETED: bits[bitIndex.reqBit(req)] = 1; return DNF({bits}); diff --git a/src/dusk/randomizer/logic/item.cpp b/src/dusk/randomizer/logic/item.cpp index 24f7e2c86c..d1d65f0cc1 100644 --- a/src/dusk/randomizer/logic/item.cpp +++ b/src/dusk/randomizer/logic/item.cpp @@ -54,6 +54,13 @@ namespace randomizer::logic::item { this->_stamp = true; } + // Make hearts major items if they're required for anything + else if ((name == "Piece of Heart" || name == "Heart Container") && + ((world->Setting("Hyrule Barrier Requirements") == "Hearts") || + (world->Setting("Hyrule Castle Big Key Requirements") == "Hearts"))) + { + this->_importance = Importance::MAJOR; + } } int Item::GetID() const diff --git a/src/dusk/randomizer/logic/item_pool.cpp b/src/dusk/randomizer/logic/item_pool.cpp index 1498d136a6..79bb86a453 100644 --- a/src/dusk/randomizer/logic/item_pool.cpp +++ b/src/dusk/randomizer/logic/item_pool.cpp @@ -291,8 +291,13 @@ namespace randomizer::logic::item_pool {"Temple of Time Big Key"}, {"City in the Sky Big Key"}, {"Palace of Twilight Big Key"}, - {"Hyrule Castle Big Key"}, }; + + if (world->Setting("Hyrule Castle Big Key Requirements") == "None") + { + bigKeys.emplace_back("Hyrule Castle Big Key"); + } + for (const auto& key : bigKeys) { itemPool.at(key) = 0; diff --git a/src/dusk/randomizer/logic/requirement.cpp b/src/dusk/randomizer/logic/requirement.cpp index 7578ca96bf..4e00229bfe 100644 --- a/src/dusk/randomizer/logic/requirement.cpp +++ b/src/dusk/randomizer/logic/requirement.cpp @@ -3,6 +3,7 @@ #include "search.hpp" #include "world.hpp" #include "../utility/container.hpp" +#include "../utility/general.hpp" #include "../utility/log.hpp" #include "../utility/string.hpp" @@ -135,6 +136,15 @@ namespace randomizer::logic::requirement case Type::GOLDEN_BUGS: count = std::get(this->_args[0]); return "golden_bugs(" + std::to_string(count) + ")"; + + case Type::HEARTS: + count = std::get(this->_args[0]); + return "hearts(" + std::to_string(count) + ")"; + + case Type::DUNGEONS_COMPLETED: + count = std::get(this->_args[0]); + return "dungeons_completed(" + std::to_string(count) + ")"; + default: return reqStr; } @@ -334,9 +344,16 @@ namespace randomizer::logic::requirement } splitLogicStr.push_back(countArgs); + // For the count, if a setting is passed in, use the setting's value instead + auto& countStr = splitLogicStr[1]; + if (seedgen::settings::GetAllSettingsInfo()->contains(countStr)) + { + countStr = world->Setting(countStr).GetCurrentOption(); + } + // Get the arguments auto& itemName = splitLogicStr[0]; - int count = std::stoi(splitLogicStr[1]); + int count = std::stoi(countStr); auto item = world->GetItem(itemName); req._args.emplace_back(count); req._args.emplace_back(item); @@ -357,24 +374,31 @@ namespace randomizer::logic::requirement return req; } - // And finally a health check - // else if (argStr.find("health") != std::string::npos) - // { - // req._type = randomizer::logic::requirement::Type::HEALTH; - // std::string numHeartsStr(argStr.begin() + argStr.find('(') + 1, argStr.end() - 1); - // int numHearts = std::stoi(numHeartsStr); - // req._args.emplace_back(numHearts); - // return req; - // } + // Then health + else if (argStr.find("hearts") != std::string::npos) + { + req._type = randomizer::logic::requirement::Type::HEARTS; + std::string numHeartsStr(argStr.begin() + argStr.find('(') + 1, argStr.end() - 1); + + // If the string for the count is a setting, use the settings current option instead + if (seedgen::settings::GetAllSettingsInfo()->contains(numHeartsStr)) + { + numHeartsStr = world->Setting(numHeartsStr).GetCurrentOption(); + } - // Check Impossible down here since it's very unlikely + int numHearts = std::stoi(numHeartsStr); + req._args.emplace_back(numHearts); + return req; + } + + // Then Impossible... else if (argStr == "Impossible") { req._type = randomizer::logic::requirement::Type::IMPOSSIBLE; return req; } - // Check golden bugs last since it's least likely + // Then golden bugs... else if (argStr.find("golden bugs") != std::string::npos) { req._type = randomizer::logic::requirement::Type::GOLDEN_BUGS; @@ -385,6 +409,24 @@ namespace randomizer::logic::requirement return req; } + // Then dungeons completed + else if (argStr.find("dungeons completed") != std::string::npos) + { + req._type = Type::DUNGEONS_COMPLETED; + // Get rid of parenthesis + std::string countStr(argStr.begin() + argStr.find('(') + 1, argStr.end() - 1); + + // For the count, if a setting is passed in, use the setting's value instead + if (seedgen::settings::GetAllSettingsInfo()->contains(countStr)) + { + countStr = world->Setting(countStr).GetCurrentOption(); + } + + int count = std::stoi(countStr); + req._args.emplace_back(count); + return req; + } + throw std::runtime_error("Unrecognized logic symbol: \"" + reqStr + "\""); } @@ -445,12 +487,73 @@ namespace randomizer::logic::requirement return req; } + bool EvaluateSimpleRequirement(const randomizer::logic::requirement::Requirement& req, randomizer::logic::world::World* world) + { + randomizer::logic::item::Item* item; + randomizer::logic::item::Item* heartPiece; + randomizer::logic::item::Item* heartContainer; + int count; + int macroIndex; + switch (req._type) + { + case Type::NOTHING: + return true; + + case Type::IMPOSSIBLE: + return false; + + case Type::OR: + return std::any_of( + req._args.begin(), + req._args.end(), + [&](const auto& arg) + { return EvaluateSimpleRequirement(std::get(arg), world); }); + + case Type::AND: + return std::all_of( + req._args.begin(), + req._args.end(), + [&](const auto& arg) + { return EvaluateSimpleRequirement(std::get(arg), world); }); + + case Type::ITEM: + item = std::get(req._args[0]); + return randomizer::utility::container::ElementInContainer(world->GetStartingItemPool(), item); + + case Type::COUNT: + count = std::get(req._args[0]); + item = std::get(req._args[1]); + return std::ranges::count(world->GetStartingItemPool(), item) >= count; + + case Type::MACRO: + macroIndex = std::get(req._args[0]); + return EvaluateSimpleRequirement(world->GetMacro(macroIndex), world); + + case Type::GOLDEN_BUGS: + count = std::get(req._args[0]); + return std::ranges::count_if(world->GetStartingItemPool(), + [](const auto& item) { return item->IsGoldenBug(); }) >= count; + + case Type::HEARTS: + count = std::get(req._args[0]); + heartPiece = world->GetItem("Piece of Heart"); + heartContainer = world->GetItem("Heart Container"); + return std::ranges::count(world->GetStartingItemPool(), heartPiece) + + std::ranges::count(world->GetStartingItemPool(), heartContainer) * 5 >= count * 5; + default: + return false; + } + return false; + } + bool EvaluateRequirementAtFormTime(const randomizer::logic::requirement::Requirement& req, randomizer::logic::search::Search* search, const int& formTime, randomizer::logic::world::World* world) { randomizer::logic::item::Item* item; + randomizer::logic::item::Item* heartPiece; + randomizer::logic::item::Item* heartContainer; int count; int eventIndex; int macroIndex; @@ -513,6 +616,37 @@ namespace randomizer::logic::requirement return std::count_if(search->_ownedItems.begin(), search->_ownedItems.end(), [](const auto& item) { return item->IsGoldenBug(); }) >= count; + + case Type::HEARTS: + count = std::get(req._args[0]); + heartPiece = world->GetItem("Piece of Heart"); + heartContainer = world->GetItem("Heart Container"); + return search->_ownedItems.count(heartPiece) + + (search->_ownedItems.count(heartContainer) + 3) * 5 >= count * 5; + + case Type::DUNGEONS_COMPLETED: + count = std::get(req._args[0]); + return std::ranges::count_if(search->_ownedEvents, [&](int eventId){ + std::list dungeonCompletionEvents = { + "Can Complete Forest Temple", + "Can Complete Goron Mines", + "Can Complete Lakebed Temple", + "Can Complete Arbiters Grounds", + "Can Complete Snowpeak Ruins", + "Can Complete Temple of Time", + "Can Complete City in the Sky", + "Can Complete Palace of Twilight" + }; + for (const auto& eventName : dungeonCompletionEvents) + { + if (world->GetEventIndex(eventName) == eventId) + { + return true; + } + } + return false; + }) >= count; + default: return false; } @@ -534,7 +668,7 @@ namespace randomizer::logic::requirement // Some exits in the middle of entrance shuffling will not have a connected area. Ignore these if (exit->GetConnectedArea() == nullptr) { - return EvalSuccess::UNNECESSARY; + return EvalSuccess::DISCONNECTED; } // If the exit is currently disabled, don't try it @@ -621,6 +755,36 @@ namespace randomizer::logic::requirement return evalSuccess; } + EvalSuccess EvaluateDisconnectedExitRequiremrnt(randomizer::logic::search::Search* search, randomizer::logic::entrance::Entrance* exit) + { + // If the exit is currently disabled, don't try it + if (exit->IsDisabled()) + { + return EvalSuccess::NONE; + } + + auto& exitFormTimeCache = exit->GetWorld()->GetExitTimeFormCache(); + auto parentArea = exit->GetParentArea(); + auto parentAreaFormTime = search->_areaFormTime[parentArea]; + + // Check each form time individually and spread the ones which succeed. If any of them pass, set the evaluation success + // to partial. + auto evalSuccess = EvalSuccess::NONE; + const auto& formTimes = FormTime::ALL_FORM_TIMES; + for (const auto& formTime : formTimes) + { + if (formTime & parentAreaFormTime) + { + if (EvaluateRequirementAtFormTime(exit->GetRequirement(), search, formTime, exit->GetWorld())) + { + return EvalSuccess::PARTIAL; + } + } + } + + return EvalSuccess::NONE; + } + EvalSuccess EvaluateLocationRequirement(randomizer::logic::search::Search* search, randomizer::logic::area::LocationAccess* locAccess) { auto& formTime = search->_areaFormTime[locAccess->GetArea()]; diff --git a/src/dusk/randomizer/logic/requirement.hpp b/src/dusk/randomizer/logic/requirement.hpp index 02b80bac16..f37e0d70b3 100644 --- a/src/dusk/randomizer/logic/requirement.hpp +++ b/src/dusk/randomizer/logic/requirement.hpp @@ -51,6 +51,8 @@ namespace randomizer::logic::requirement WOLF_LINK, TWILIGHT, GOLDEN_BUGS, + HEARTS, + DUNGEONS_COMPLETED, }; enum class EvalSuccess @@ -58,7 +60,7 @@ namespace randomizer::logic::requirement NONE, PARTIAL, COMPLETE, - UNNECESSARY, + DISCONNECTED, }; // FormTime is a set of flags that cover all the possible cases of human-wolf/day-night combinations that are needed @@ -101,12 +103,22 @@ namespace randomizer::logic::requirement randomizer::logic::world::World* world, const bool& forceLogic = false); + /** + * @brief Evaluates a requirement assuming it meets a simplistic criteria. This is used + * for checking settings when reading them in from, for example, startflags.yaml + * + * @param req - The simple requirement + * @return true if the requirment holds, false otherwise + */ + bool EvaluateSimpleRequirement(const randomizer::logic::requirement::Requirement& req, randomizer::logic::world::World* world); + bool EvaluateRequirementAtFormTime(const randomizer::logic::requirement::Requirement& req, randomizer::logic::search::Search* search, const int& formTime, randomizer::logic::world::World*); EvalSuccess EvaluateEventRequirement(randomizer::logic::search::Search* search, randomizer::logic::area::EventAccess* event); EvalSuccess EvaluateExitRequirement(randomizer::logic::search::Search* search, randomizer::logic::entrance::Entrance* exit); + EvalSuccess EvaluateDisconnectedExitRequiremrnt(randomizer::logic::search::Search* search, randomizer::logic::entrance::Entrance* exit); EvalSuccess EvaluateLocationRequirement(randomizer::logic::search::Search* search, randomizer::logic::area::LocationAccess* locAccess); diff --git a/src/dusk/randomizer/logic/search.cpp b/src/dusk/randomizer/logic/search.cpp index 960a06b266..b4ebdcf18a 100644 --- a/src/dusk/randomizer/logic/search.cpp +++ b/src/dusk/randomizer/logic/search.cpp @@ -140,13 +140,9 @@ namespace randomizer::logic::search continue; } - // If the exit is unnecessary, we'll just consider it successful and move on + // If the exit is successful auto evalSuccess = randomizer::logic::requirement::EvaluateExitRequirement(this, exit); - if (evalSuccess == randomizer::logic::requirement::EvalSuccess::UNNECESSARY) - { - this->_successfulExits.insert(exit); - } - else if (randomizer::utility::general::IsAnyOf(evalSuccess, + if (randomizer::utility::general::IsAnyOf(evalSuccess, randomizer::logic::requirement::EvalSuccess::COMPLETE, randomizer::logic::requirement::EvalSuccess::PARTIAL)) { @@ -305,9 +301,9 @@ namespace randomizer::logic::search this->Explore(exit->GetConnectedArea()); } case randomizer::logic::requirement::EvalSuccess::NONE: + [[fallthrough]]; + case randomizer::logic::requirement::EvalSuccess::DISCONNECTED: this->_exitsToTry.push_back(exit); - case randomizer::logic::requirement::EvalSuccess::UNNECESSARY: - this->_foundDisconnectedExit = true; } } } @@ -376,6 +372,19 @@ namespace randomizer::logic::search } } + bool Search::HasAccessibleDisconnectedExit() + { + for (const auto& exit : this->_exitsToTry) + { + if (exit->GetConnectedArea() == nullptr && + randomizer::logic::requirement::EvaluateDisconnectedExitRequiremrnt(this, exit) != requirement::EvalSuccess::NONE) + { + return true; + } + } + return false; + } + void Search::RemoveEmptySpheres() { // Get rid of any empty spheres in both the item playthrough and entrance playthrough diff --git a/src/dusk/randomizer/logic/search.hpp b/src/dusk/randomizer/logic/search.hpp index c515dbf0c2..3fb4331f77 100644 --- a/src/dusk/randomizer/logic/search.hpp +++ b/src/dusk/randomizer/logic/search.hpp @@ -115,6 +115,7 @@ namespace randomizer::logic::search void ExpandFormTimes(randomizer::logic::area::Area* area); void AddExitToEntranceSpheres(randomizer::logic::entrance::Entrance*); + bool HasAccessibleDisconnectedExit(); void RemoveEmptySpheres(); /** @@ -143,7 +144,6 @@ namespace randomizer::logic::search std::unordered_set _visitedAreas; std::unordered_set _successfulExits; std::unordered_set _playthroughEntrances; - bool _foundDisconnectedExit = false; std::list> _playthroughSpheres; std::list> _entranceSpheres; diff --git a/src/dusk/randomizer/logic/world.cpp b/src/dusk/randomizer/logic/world.cpp index b2825c8973..ae120a0816 100644 --- a/src/dusk/randomizer/logic/world.cpp +++ b/src/dusk/randomizer/logic/world.cpp @@ -493,11 +493,7 @@ namespace randomizer::logic::world bool World::EvaluateSettingCondition(const std::string& condition) { auto req = randomizer::logic::requirement::ParseRequirementString(condition, this, true); - if (req._type == requirement::Type::NOTHING) - { - return true; - } - return false; + return requirement::EvaluateSimpleRequirement(req, this); } void World::GenerateItemPools() @@ -540,11 +536,14 @@ namespace randomizer::logic::world if ((this->Setting("Small Keys") == "Vanilla" && (originalItem->IsDungeonSmallKey() || randomizer::utility::str::Contains(originalItemName, "Ordon Pumpkin", "Ordon Cheese"))) || - // Vanilla Big Keys - (this->Setting("Big Keys") == "Vanilla" && originalItem->IsBigKey()) || + // Vanilla Big Keys (only include Hyrule Castle Big Key if it has no requirements) + (this->Setting("Big Keys") == "Vanilla" && originalItem->IsBigKey() && + (originalItemName != "Hyrule Castle Big Key" || this->Setting("Hyrule Castle Big Key Requirements") == "None")) || // Vanilla Maps and Compasses (this->Setting("Maps and Compasses") == "Vanilla" && (originalItem->IsDungeonMap() || originalItem->IsCompass())) || + // Hyrule Castle Big Key + (originalItemName == "Hyrule Castle Big Key" && this->Setting("Hyrule Castle Big Key Requirements") != "None") || // Vanilla Poe Souls (originalItemName == "Poe Soul" && (this->Setting("Poe Souls") == "Vanilla" || @@ -1134,15 +1133,22 @@ namespace randomizer::logic::world return this->_macros.at(macroIndex); } - int World::GetEventIndex(const std::string& eventName) + int World::GetEventIndex(const std::string& eventName, bool addIfNone /*= true*/) { - // Add the event if it doesn't exist yet + // If the event doesn't exist if (!this->_eventIndexes.contains(eventName)) { - auto index = this->_randomizer->GetNewEventID(); - this->_eventIndexes.emplace(eventName, index); - this->_eventNames.emplace(index, eventName); - LOG_TO_DEBUG("Event \"" + eventName + "\" was assigned eventIndex " + std::to_string(index)); + if (addIfNone) + { + auto index = this->_randomizer->GetNewEventID(); + this->_eventIndexes.emplace(eventName, index); + this->_eventNames.emplace(index, eventName); + LOG_TO_DEBUG("Event \"" + eventName + "\" was assigned eventIndex " + std::to_string(index)); + } + else + { + throw std::runtime_error("Event \"" + eventName + "\" does not exist"); + } } return this->_eventIndexes.at(eventName); diff --git a/src/dusk/randomizer/logic/world.hpp b/src/dusk/randomizer/logic/world.hpp index d1b266d1a7..76116f01e2 100644 --- a/src/dusk/randomizer/logic/world.hpp +++ b/src/dusk/randomizer/logic/world.hpp @@ -136,7 +136,7 @@ namespace randomizer::logic::world int GetMacroIndex(const std::string& macroName) const; const randomizer::logic::requirement::Requirement& GetMacro(const int& macroIndex); - int GetEventIndex(const std::string& eventName); + int GetEventIndex(const std::string& eventName, bool addIfNone = true); std::string GetEventName(const int& eventIndex); randomizer::seedgen::settings::Setting& Setting(const std::string& settingName); diff --git a/src/dusk/randomizer/test/test.cpp b/src/dusk/randomizer/test/test.cpp index 21476af6b3..99ec4ac3fa 100644 --- a/src/dusk/randomizer/test/test.cpp +++ b/src/dusk/randomizer/test/test.cpp @@ -27,7 +27,7 @@ namespace randomizer::test::test } catch(const std::exception& e) { std::cout << "Test \"" << testName << "\" failed! Failed settings saved to " << SETTINGS_PATH << std::endl; - std::cout << "Error Message: " << std::endl; + std::cout << "Error Message: " << e.what() << std::endl; throw e; }