From 3aa7abe73f29bdb7eab69296c94a38bd1c28c78d Mon Sep 17 00:00:00 2001 From: gymnast86 Date: Wed, 27 May 2026 10:54:35 -0700 Subject: [PATCH] add required dungeons to links house sign --- files.cmake | 2 + .../randomizer/game/randomizer_context.cpp | 7 ++- .../generator/data/text/text_overrides.yaml | 4 ++ .../randomizer/generator/logic/dungeon.cpp | 8 +++ .../randomizer/generator/logic/dungeon.hpp | 4 ++ src/dusk/randomizer/generator/logic/hints.cpp | 35 +++++++++++ src/dusk/randomizer/generator/logic/hints.hpp | 7 +++ src/dusk/randomizer/generator/logic/world.cpp | 58 ++++++++++++++++--- src/dusk/randomizer/generator/logic/world.hpp | 14 ++++- src/dusk/randomizer/generator/randomizer.cpp | 4 +- .../randomizer/generator/utility/text.cpp | 4 +- .../randomizer/generator/utility/text.hpp | 4 +- 12 files changed, 137 insertions(+), 14 deletions(-) create mode 100644 src/dusk/randomizer/generator/logic/hints.cpp create mode 100644 src/dusk/randomizer/generator/logic/hints.hpp diff --git a/files.cmake b/files.cmake index f231719c57..e8201c195e 100644 --- a/files.cmake +++ b/files.cmake @@ -1560,6 +1560,8 @@ set(DUSK_FILES src/dusk/randomizer/generator/logic/flatten/flatten.hpp src/dusk/randomizer/generator/logic/flatten/simplify_algebraic.cpp src/dusk/randomizer/generator/logic/flatten/simplify_algebraic.hpp + src/dusk/randomizer/generator/logic/hints.cpp + src/dusk/randomizer/generator/logic/hints.hpp src/dusk/randomizer/generator/logic/item.cpp src/dusk/randomizer/generator/logic/item.hpp src/dusk/randomizer/generator/logic/item_pool.cpp diff --git a/src/dusk/randomizer/game/randomizer_context.cpp b/src/dusk/randomizer/game/randomizer_context.cpp index e059e9246f..b488a2f5c5 100644 --- a/src/dusk/randomizer/game/randomizer_context.cpp +++ b/src/dusk/randomizer/game/randomizer_context.cpp @@ -1270,7 +1270,12 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) { const auto& name = overrideNode["Name"].as(); // TODO: Handle multiple languages auto language = randomizer::Text::ENGLISH; - auto text = randomizer::getTextStr(name); + std::string text; + if (world->GetTextDatabase().contains(name)) { + text = world->GetDynamicTextStr(name); + } else { + text = randomizer::getTextStr(name); + } u8 group = overrideNode["Group"].as(); u16 messageId = overrideNode["Message Id"].as(); u32 key = (group << 16) | messageId; diff --git a/src/dusk/randomizer/generator/data/text/text_overrides.yaml b/src/dusk/randomizer/generator/data/text/text_overrides.yaml index b05d036449..aa00d57043 100644 --- a/src/dusk/randomizer/generator/data/text/text_overrides.yaml +++ b/src/dusk/randomizer/generator/data/text/text_overrides.yaml @@ -243,6 +243,10 @@ Group: 0 Message Id: 2041 +- Name: Links House Sign + Group: 1 + Message Id: 9005 + - Name: Charlo Donation Choice Text Group: 4 Message Id: 6403 diff --git a/src/dusk/randomizer/generator/logic/dungeon.cpp b/src/dusk/randomizer/generator/logic/dungeon.cpp index b834454f14..8efd5d488b 100644 --- a/src/dusk/randomizer/generator/logic/dungeon.cpp +++ b/src/dusk/randomizer/generator/logic/dungeon.cpp @@ -119,6 +119,14 @@ namespace randomizer::logic::dungeon return this->_required; } + void Dungeon::AddOutsideDependentLocation(location::Location* location) { + this->_outsideDependentLocations.push_back(location); + } + + std::list Dungeon::GetOutsideDependentLocations() { + return this->_outsideDependentLocations; + } + bool Dungeon::ShouldBeBarren() const { return !this->_required && this->_world->Setting("Unrequired Dungeons Are Barren") == "On"; diff --git a/src/dusk/randomizer/generator/logic/dungeon.hpp b/src/dusk/randomizer/generator/logic/dungeon.hpp index aaeaf9e661..d1fdafa824 100644 --- a/src/dusk/randomizer/generator/logic/dungeon.hpp +++ b/src/dusk/randomizer/generator/logic/dungeon.hpp @@ -53,6 +53,8 @@ namespace randomizer::logic::dungeon location::Location* GetGoalLocation(); void SetRequired(const bool& required); bool IsRequired() const; + void AddOutsideDependentLocation(location::Location* location); + std::list GetOutsideDependentLocations(); /** * @brief Returns whether or not the dungeon should be barren given the current settings and placement of dungeon @@ -71,6 +73,8 @@ namespace randomizer::logic::dungeon std::unordered_set _startingEntrances; location::Location* _goalLocation; location::LocationPool _locations = {}; + // Locations which depend on beating this dungeon + std::list _outsideDependentLocations = {}; bool _required = false; }; } // namespace randomizer::logic::dungeon diff --git a/src/dusk/randomizer/generator/logic/hints.cpp b/src/dusk/randomizer/generator/logic/hints.cpp new file mode 100644 index 0000000000..726c29d04b --- /dev/null +++ b/src/dusk/randomizer/generator/logic/hints.cpp @@ -0,0 +1,35 @@ +#include "hints.hpp" + +#include "dusk/randomizer/generator/utility/text.hpp" +#include "world.hpp" + +namespace randomizer::logic::hints { + + // Tell the player which dungeons are required on the sign in front of Link's House + static void GenerateRequiredDungeonsHint(world::WorldPool& worlds) { + static const std::unordered_map dungeonColors = { + {"Forest Temple", ""}, + {"Goron Mines", ""}, + {"Lakebed Temple", ""}, + {"Arbiters Grounds", ""}, + {"Snowpeak Ruins", ""}, + {"Temple of Time", ""}, + {"City in the Sky", ""}, + {"Palace of Twilight", ""}, + // {"Hyrule Castle", ""} + }; + + for (const auto& world : worlds) { + auto& requiredDungeonText = world->AddDynamicTextStr("Links House Sign"); + for (const auto& [name, dungeon] : world->GetDungeonTable()) { + if (dungeon->IsRequired()) { + requiredDungeonText += dungeonColors.at(name) + name + "\n"; + } + } + } + } + + void GenerateAllHints(world::WorldPool& worlds) { + GenerateRequiredDungeonsHint(worlds); + } +} \ No newline at end of file diff --git a/src/dusk/randomizer/generator/logic/hints.hpp b/src/dusk/randomizer/generator/logic/hints.hpp new file mode 100644 index 0000000000..28e5484773 --- /dev/null +++ b/src/dusk/randomizer/generator/logic/hints.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace randomizer::logic::hints { + + void GenerateAllHints(world::WorldPool& worldPool); + +} \ No newline at end of file diff --git a/src/dusk/randomizer/generator/logic/world.cpp b/src/dusk/randomizer/generator/logic/world.cpp index cc1899871b..7fc35239a2 100644 --- a/src/dusk/randomizer/generator/logic/world.cpp +++ b/src/dusk/randomizer/generator/logic/world.cpp @@ -12,9 +12,10 @@ #include "../utility/string.hpp" #include "../utility/yaml.hpp" -#include -#include #include +#include +#include +#include namespace randomizer::logic::world { @@ -705,7 +706,7 @@ namespace randomizer::logic::world { this->AssignAreaProperties(); this->AssignGoalLocations(); - this->ChooseRequiredDungeons(); + this->DetermineDungeonDependentLocations(); this->SetForbiddenItems(); } @@ -816,9 +817,43 @@ namespace randomizer::logic::world } } - void World::ChooseRequiredDungeons() + void World::DetermineDungeonDependentLocations() { - // STUB + for (const auto& [dungeonName, dungeon] : this->_dungeons) + { + // Hyrule Castle is implicitly required + if (dungeonName == "Hyrule Castle") { + continue; + } + + // Disable the dungeon's starting entrances + for (auto& entrance : dungeon->GetStartingEntrances()) + { + entrance->SetDisbled(true); + } + + // Run an accessibility search to see which locations inherently require accessing this dungeon + auto completeItemPool = item_pool::GetCompleteItemPool(this->_randomizer->GetWorlds()); + auto search = search::Search::Accessible(&this->_randomizer->GetWorlds(), completeItemPool); + search.SearchWorlds(); + for (auto& location : this->_locationTable | std::ranges::views::values) { + // Don't check locations which are part of this dungeon + if (utility::container::ElementInContainer(dungeon->GetLocations(), location.get())) { + continue; + } + + // If the search does not contain this location, then the location is dependent on accessing this dungeon + if (!search._visitedLocations.contains(location.get())) { + dungeon->AddOutsideDependentLocation(location.get()); + } + } + + // Re-enable the dungeon's entrances + for (auto& entrance : dungeon->GetStartingEntrances()) + { + entrance->SetDisbled(false); + } + } } void World::DetermineRequiredDungeons() @@ -826,9 +861,14 @@ namespace randomizer::logic::world for (const auto& [dungeonName, dungeon] : this->_dungeons) { // To determine if a dungeon is required, we're going to disable all of its entrances and then check to see - // that the game is still beatble. If the game is not beatable with the dungeon entrances disabled, then the + // that the game is still beatable. If the game is not beatable with the dungeon entrances disabled, then the // dungeon is required. + // Hyrule Castle is implicitly required + if (dungeonName == "Hyrule Castle") { + continue; + } + // Disable the dungeon's starting entrances for (auto& entrance : dungeon->GetStartingEntrances()) { @@ -838,7 +878,7 @@ namespace randomizer::logic::world // Check if the game is beatable, set dungeon as required if so. If the dungeon is not required and barren // unrequired dungeons is on, then set all the locations in the unrequired dungeon as nonprogress. auto completeItemPool = item_pool::GetCompleteItemPool(this->_randomizer->GetWorlds()); - if (!search::GameBeatable(&(this->_randomizer->GetWorlds()), completeItemPool)) + if (!search::GameBeatable(&this->_randomizer->GetWorlds(), completeItemPool)) { dungeon->SetRequired(true); } @@ -848,6 +888,10 @@ namespace randomizer::logic::world { location->SetProgression(false); } + for (auto& location : dungeon->GetOutsideDependentLocations()) + { + location->SetProgression(false); + } } // Re-enable the dungeon's entrances diff --git a/src/dusk/randomizer/generator/logic/world.hpp b/src/dusk/randomizer/generator/logic/world.hpp index e3a61d4e3f..d0c9fa81dc 100644 --- a/src/dusk/randomizer/generator/logic/world.hpp +++ b/src/dusk/randomizer/generator/logic/world.hpp @@ -9,6 +9,7 @@ #include "../seedgen/settings.hpp" #include "../utility/log.hpp" +#include "../utility/text.hpp" #include #include @@ -99,7 +100,7 @@ namespace randomizer::logic::world * @brief STUB: Would choose required dungeons ahead of placing any non-vanilla and non-plandomized items. Not really * required unless we let users choose a specific amount of directly required dungeons */ - void ChooseRequiredDungeons(); + void DetermineDungeonDependentLocations(); /** * @brief Determines which dungeons are required based on placed items. Sets required dungeons as such in their @@ -149,6 +150,15 @@ namespace randomizer::logic::world seedgen::settings::Setting& Setting(const std::string& settingName); + TextDatabase& GetTextDatabase() { return this->_textDatabase; } + const std::string& GetDynamicTextStr(const std::string& name) { + return this->_textDatabase.at(name).at(Text::Type::STANDARD).mText.at(Text::Language::ENGLISH); + } + // Make a new custom text entry for this world specifically + std::string& AddDynamicTextStr(const std::string& name, Text::Type type = Text::STANDARD, Text::Language language = Text::ENGLISH) { + return this->_textDatabase[name][type].mText[language]; + } + private: int _id = -1; int _entranceIdCounter = 0; @@ -168,6 +178,8 @@ namespace randomizer::logic::world item_pool::ItemPool _itemPool = {}; item_pool::ItemPool _startingItemPool = {}; std::unordered_map _exitTimeFormCache = {}; + // Custom text for this world specifically + TextDatabase _textDatabase = {}; // Plandomizer Data std::unordered_map _plandomizerLocations = {}; diff --git a/src/dusk/randomizer/generator/randomizer.cpp b/src/dusk/randomizer/generator/randomizer.cpp index 632ad3e95d..449ed7945c 100644 --- a/src/dusk/randomizer/generator/randomizer.cpp +++ b/src/dusk/randomizer/generator/randomizer.cpp @@ -3,6 +3,7 @@ #include "logic/entrance_shuffle.hpp" #include "logic/fill.hpp" #include "logic/flatten/flatten.hpp" +#include "logic/hints.hpp" #include "logic/plandomizer.hpp" #include "logic/search.hpp" #include "logic/spoiler_log.hpp" @@ -147,7 +148,8 @@ namespace randomizer // Generate Playthrough logic::search::GeneratePlaythrough(this); - // TODO: Generate Hints + // Generate Hints + logic::hints::GenerateAllHints(this->_worlds); // Write Logs if (this->_config.IsGeneratingSpoilerLog()) diff --git a/src/dusk/randomizer/generator/utility/text.cpp b/src/dusk/randomizer/generator/utility/text.cpp index e2923a34f6..40c871c814 100644 --- a/src/dusk/randomizer/generator/utility/text.cpp +++ b/src/dusk/randomizer/generator/utility/text.cpp @@ -224,7 +224,7 @@ namespace randomizer { auto type = string_to_type(typeNode.first.as()); auto typeData = typeNode.second; const auto& text = typeData["Text"].as(); - tb[name][type].mtext[language] = text; + tb[name][type].mText[language] = text; if (typeData["Gender"]) { tb[name][type].mGender[language] = string_to_gender(typeData["Gender"].as()); } @@ -264,7 +264,7 @@ namespace randomizer { if (!tb.contains(name)) { throw std::runtime_error("Text name \"" + name + "\" is not recognized."); } - return tb.at(name).at(type).mtext.at(language); + return tb.at(name).at(type).mText.at(language); } void applyMessageCodes(std::string& str) { diff --git a/src/dusk/randomizer/generator/utility/text.hpp b/src/dusk/randomizer/generator/utility/text.hpp index b987963ca7..060047226d 100644 --- a/src/dusk/randomizer/generator/utility/text.hpp +++ b/src/dusk/randomizer/generator/utility/text.hpp @@ -49,7 +49,7 @@ namespace randomizer { PLURALITY_MAX, }; - std::array mtext{}; + std::array mText{}; std::array mGender{}; std::array mPlurality{}; }; @@ -66,7 +66,7 @@ namespace randomizer { Text::Gender string_to_gender(const std::string& str); Text::Plurality string_to_plurality(const std::string& str); - // Retrieval of Text objects keyed by name and type (standard, pretty, criptic) + // Retrieval of Text objects keyed by name and type (standard, pretty, cryptic) using TextDatabase = std::unordered_map>; const TextDatabase& getTextDatabase();