From bf70de83f10d06313489610274ac3020bd84d83e Mon Sep 17 00:00:00 2001 From: gymnast86 Date: Mon, 13 Apr 2026 23:51:10 -0700 Subject: [PATCH] add basic chest overrides --- include/d/d_com_inf_game.h | 8 ++++ include/d/d_save.h | 18 +++++++++ src/d/actor/d_a_tbox.cpp | 7 ++++ src/d/d_com_inf_game.cpp | 13 ++++++ src/d/d_save.cpp | 6 +++ src/dusk/randomizer/logic/location.cpp | 11 ++++- src/dusk/randomizer/logic/location.hpp | 6 ++- src/dusk/randomizer/logic/world.cpp | 56 +++++++++++++------------- 8 files changed, 95 insertions(+), 30 deletions(-) diff --git a/include/d/d_com_inf_game.h b/include/d/d_com_inf_game.h index 0ca4da42ca..5ce43efd6d 100644 --- a/include/d/d_com_inf_game.h +++ b/include/d/d_com_inf_game.h @@ -1269,6 +1269,14 @@ int dComIfGd_setShadow(u32 param_0, s8 param_1, J3DModel* param_2, cXyz* param_3 dKy_tevstr_c* param_9, s16 param_10, f32 param_11, TGXTexObj* param_12); #if TARGET_PC +inline dSv_randomizer_c& dComIfG_getRandomizer() { + return g_dComIfG_gameInfo.info.mRandomizer; +} + +inline BOOL dComIfG_isRandomizer() { + return g_dComIfG_gameInfo.info.mRandomizer.mActive; +} + void dComIfGs_setupRandomizerSave(); #endif diff --git a/include/d/d_save.h b/include/d/d_save.h index b0682e7ed6..e43e7f1ad5 100644 --- a/include/d/d_save.h +++ b/include/d/d_save.h @@ -10,6 +10,10 @@ #include "JSystem/JHostIO/JORReflexible.h" #include "dusk/endian.h" +#if TARGET_PC +#include +#endif + static const int DEFAULT_SELECT_ITEM_INDEX = 0; static const int MAX_SELECT_ITEM = 4; static const int SELECT_ITEM_NUM = 2; @@ -929,6 +933,17 @@ public: /* 0x8 */ s8 m_no; }; +#if TARGET_PC +class dSv_randomizer_c { +public: + BOOL mActive{false}; + std::unordered_map> mTreasureChestOverrides{}; + std::unordered_map> mPoeOverrides{}; + + void clear(); +}; +#endif + class dSv_info_c { public: void init(); @@ -1011,6 +1026,9 @@ public: #if DEBUG /* 0xF80 */ flagFile_c mFlagFile; #endif +#if TARGET_PC + /* 0xF38 */ dSv_randomizer_c mRandomizer; +#endif }; // Size: 0xF38 class dSv_event_flag_c { diff --git a/src/d/actor/d_a_tbox.cpp b/src/d/actor/d_a_tbox.cpp index 6cad41b4e3..fdaf9978ba 100644 --- a/src/d/actor/d_a_tbox.cpp +++ b/src/d/actor/d_a_tbox.cpp @@ -1785,6 +1785,13 @@ void daTbox_c::mode_exec() { cPhs_Step daTbox_c::create1st() { if (!mParamsInit) { field_0x980 = home.angle.x; +#if TARGET_PC + // The upper 8 bits of home.angle.z hold the itemId + if (dComIfG_isRandomizer()) { + home.angle.z &= 0x00FF; + home.angle.z |= dComIfG_getRandomizer().mTreasureChestOverrides[dComIfGp_getStartStageName()][getTboxNo()] << 8; + } +#endif field_0x982 = home.angle.z; home.angle.z = 0; home.angle.x = 0; diff --git a/src/d/d_com_inf_game.cpp b/src/d/d_com_inf_game.cpp index 7397653989..af0591783d 100644 --- a/src/d/d_com_inf_game.cpp +++ b/src/d/d_com_inf_game.cpp @@ -2329,6 +2329,19 @@ void dComIfGs_setupRandomizerSave() { } } + // Setup randomizer data + auto& randoData = g_dComIfG_gameInfo.info.mRandomizer; + randoData.mActive = TRUE; + for (const auto& location : world->GetAllLocations()) { + const auto& metaData = location->GetMetadata(); + if (location->HasCategories("Chest")) { + const auto& stage = metaData[0]["Stage"].as(); + const auto& tboxId = metaData[0]["Tbox ID"].as(); + const auto& itemId = location->GetCurrentItem()->GetID(); + randoData.mTreasureChestOverrides[stage][tboxId] = itemId; + } + } + DuskLog.debug("Created Rando Save"); } #endif diff --git a/src/d/d_save.cpp b/src/d/d_save.cpp index 3289a49c64..7caea3c3e8 100644 --- a/src/d/d_save.cpp +++ b/src/d/d_save.cpp @@ -1528,6 +1528,12 @@ dSv_memory2_c* dSv_save_c::getSave2(int i_stage2No) { return &mSave2[i_stage2No]; } +void dSv_randomizer_c::clear() { + mTreasureChestOverrides.clear(); + mPoeOverrides.clear(); + mActive = FALSE; +} + void dSv_info_c::getSave(int i_stageNo) { JUT_ASSERT(4133, 0 <= i_stageNo && i_stageNo < dSv_save_c::STAGE_MAX); mMemory = mSavedata.getSave(i_stageNo); diff --git a/src/dusk/randomizer/logic/location.cpp b/src/dusk/randomizer/logic/location.cpp index 7dff54b10c..d0edc704b3 100644 --- a/src/dusk/randomizer/logic/location.cpp +++ b/src/dusk/randomizer/logic/location.cpp @@ -11,14 +11,16 @@ namespace randomizer::logic::location world::World* world, item::Item* originalItem, const bool& goalLocation, - const std::string& hintPriority): + const std::string& hintPriority, + const YAML::Node& metadata): _id(id), _name(name), _categories(categories), _world(world), _originalItem(originalItem), _goalLocation(goalLocation), - _hintPriority(hintPriority) + _hintPriority(hintPriority), + _metadata(metadata) { this->_computedRequirement._type = requirement::Type::IMPOSSIBLE; } @@ -106,6 +108,11 @@ namespace randomizer::logic::location return this->_hinted; } + const YAML::Node& Location::GetMetadata() const + { + return this->_metadata; + } + void Location::AddLocationAccess(area::LocationAccess* locAcc) { this->_locationAccessList.push_back(locAcc); diff --git a/src/dusk/randomizer/logic/location.hpp b/src/dusk/randomizer/logic/location.hpp index 583148a265..9244ab3d68 100644 --- a/src/dusk/randomizer/logic/location.hpp +++ b/src/dusk/randomizer/logic/location.hpp @@ -2,6 +2,7 @@ #include "item.hpp" #include "requirement.hpp" +#include "../utility/yaml.hpp" #include #include @@ -28,7 +29,8 @@ namespace randomizer::logic::location world::World* world, item::Item* originalItem, const bool& goalLocation, - const std::string& hintPriority); + const std::string& hintPriority, + const YAML::Node& metadata); int GetID() const; std::string GetName() const; @@ -46,6 +48,7 @@ namespace randomizer::logic::location bool IsProgression() const; void SetHinted(const bool& hinted); bool IsHinted() const; + const YAML::Node& GetMetadata() const; void AddLocationAccess(area::LocationAccess* locAcc); std::list GetAccessList() const; void AddForbiddenItem(item::Item* forbiddenItem); @@ -94,6 +97,7 @@ namespace randomizer::logic::location std::string _hintPriority = "Never"; std::unordered_set _forbiddenItems = {}; requirement::Requirement _computedRequirement; + YAML::Node _metadata{}; /** * @brief _registeredLocationCategories is the set of all categories that are processed after reading locations.yaml. * This structure is held in the World class and every location in that world has a pointer to it. diff --git a/src/dusk/randomizer/logic/world.cpp b/src/dusk/randomizer/logic/world.cpp index f236cb8853..cbb901014d 100644 --- a/src/dusk/randomizer/logic/world.cpp +++ b/src/dusk/randomizer/logic/world.cpp @@ -76,7 +76,7 @@ namespace randomizer::logic::world void World::Build() { - randomizer::utility::platform::Log(std::string("Building World ") + std::to_string(this->GetID())); + utility::platform::Log(std::string("Building World ") + std::to_string(this->GetID())); this->BuildItemTable(); this->BuildLocationTable(); this->LoadLogicMacros(); @@ -90,7 +90,7 @@ namespace randomizer::logic::world LOG_TO_DEBUG("Building Item Table for World " + std::to_string(this->GetID())); // Check if we can open the file before parsing auto filepath = RANDO_DATA_PATH "items.yaml"; - randomizer::utility::file::Verify(filepath); + utility::file::Verify(filepath); auto itemDataTree = LoadYAML(filepath); // Process all nodes of the yaml file. Each node contains one item @@ -157,8 +157,8 @@ namespace randomizer::logic::world { LOG_TO_DEBUG("Building Location Table for World " + std::to_string(this->GetID())); // check if we can open the file before parsing because exceptions won't work on console - auto filepath = RANDO_DATA_PATH "locations.yaml"; - randomizer::utility::file::Verify(filepath); + const auto filepath = RANDO_DATA_PATH "locations.yaml"; + utility::file::Verify(filepath); auto locationDataTree = LoadYAML(filepath); // Process all nodes of the yaml file. Each node contains one location @@ -197,14 +197,16 @@ namespace randomizer::logic::world auto originalItem = this->GetItem(originalItemName); auto goalLocation = locationNode["Goal Location"].as(false); auto hintPriority = locationNode["Hint Priority"].as("Never"); + auto metadata = locationNode["Metadata"]; auto location = std::make_unique(locationIdCounter++, - name, - categories, - this, - originalItem, - goalLocation, - hintPriority); + name, + categories, + this, + originalItem, + goalLocation, + hintPriority, + metadata); LOG_TO_DEBUG("Processing new location " + name + "\tid: " + std::to_string(locationIdCounter - 1) + "\toriginal item: " + originalItemName); @@ -220,7 +222,7 @@ namespace randomizer::logic::world LOG_TO_DEBUG("Loading Macros for World " + std::to_string(this->GetID())); // check if we can open the file before parsing auto filepath = RANDO_DATA_PATH "macros.yaml"; - randomizer::utility::file::Verify(filepath); + utility::file::Verify(filepath); auto macrosDataTree = LoadYAML(filepath); @@ -275,7 +277,7 @@ namespace randomizer::logic::world for (const auto& file : files) { auto filepath = folder + file; - randomizer::utility::file::Verify(filepath); + utility::file::Verify(filepath); auto worldDataTree = LoadYAML(filepath); for (const auto& areaNode : worldDataTree) @@ -535,7 +537,7 @@ namespace randomizer::logic::world // Vanilla Small Keys if ((this->Setting("Small Keys") == "Vanilla" && (originalItem->IsDungeonSmallKey() || - randomizer::utility::str::Contains(originalItemName, "Ordon Pumpkin", "Ordon Cheese"))) || + utility::str::Contains(originalItemName, "Ordon Pumpkin", "Ordon Cheese"))) || // 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")) || @@ -566,7 +568,7 @@ namespace randomizer::logic::world // North Faron Woods Gate Key (this->Setting("Skip Prologue") == "Off" && locationName == "Faron Mist Cave Open Chest") || // Some locations which will always be vanilla for the time being - (randomizer::utility::str::Contains(locationName, + (utility::str::Contains(locationName, "Renados Letter", "Telma Invoice", "Wooden Statue", @@ -592,7 +594,7 @@ namespace randomizer::logic::world location->SetCurrentItem(originalItem); location->SetKnownVanillaItem(true); - randomizer::utility::container::Erase(this->_itemPool, originalItem); + utility::container::Erase(this->_itemPool, originalItem); } } } @@ -608,7 +610,7 @@ namespace randomizer::logic::world "\" already exists there."); } location->SetCurrentItem(item); - randomizer::utility::container::Erase(this->_itemPool, item); + utility::container::Erase(this->_itemPool, item); } } @@ -643,7 +645,7 @@ namespace randomizer::logic::world location->HasCategories("Sky Book")) || // We're starting with a shop item, but shop items aren't randomized (this->Setting("Shop Items") == "Off" && location->HasCategories("Shop") && - randomizer::utility::container::ElementInContainer(this->_startingItemPool, originalItem))) + utility::container::ElementInContainer(this->_startingItemPool, originalItem))) { location->RemoveCurrentItem(); location->SetKnownVanillaItem(false); @@ -721,7 +723,7 @@ namespace randomizer::logic::world // assigned a goal location. Dungeons without a goal location cannot be chosen as required dungeons. if (!possibleGoalLocations.empty()) { - dungeon->SetGoalLocation(randomizer::utility::random::RandomElement(possibleGoalLocations)); + dungeon->SetGoalLocation(utility::random::RandomElement(possibleGoalLocations)); } else { @@ -737,16 +739,16 @@ namespace randomizer::logic::world { // Gather all boss locations (heart container and dungeon reward checks) auto bossLocations = this->GetAllLocations(); - randomizer::utility::container::FilterAndEraseFromVector( + utility::container::FilterAndEraseFromVector( bossLocations, [](const auto& location) - { return !randomizer::utility::str::Contains(location->GetName(), "Heart Container", "Dungeon Reward"); }); + { return !utility::str::Contains(location->GetName(), "Heart Container", "Dungeon Reward"); }); // Gather all small key items item_pool::ItemPool smallKeys = {}; for (const auto& [itemName, item] : this->_itemTable) { - if (item->IsDungeonSmallKey() || randomizer::utility::general::IsAnyOf(itemName, + if (item->IsDungeonSmallKey() || utility::general::IsAnyOf(itemName, "Ordon Pumpkin", "Ordon Cheese", "North Faron Woods Gate Key", @@ -858,11 +860,11 @@ namespace randomizer::logic::world item::Item* randomJunkItem; if (!mainJunkPool.empty()) { - randomJunkItem = randomizer::utility::random::PopRandomElement(mainJunkPool); + randomJunkItem = utility::random::PopRandomElement(mainJunkPool); } else { - randomJunkItem = randomizer::utility::random::RandomElement(mainJunkPoolCopy); + randomJunkItem = utility::random::RandomElement(mainJunkPoolCopy); } this->_itemPool.emplace_back(randomJunkItem); LOG_TO_DEBUG("Added junk item \"" + randomJunkItem->GetName() + "\" to item pool for world " + @@ -921,10 +923,10 @@ namespace randomizer::logic::world } // Place the new bottle items - randomizer::utility::random::ShufflePool(bottleLocations); + utility::random::ShufflePool(bottleLocations); for (auto& bottleLocation : bottleLocations) { - bottleLocation->SetCurrentItem(randomizer::utility::random::PopRandomElement(bottlePool)); + bottleLocation->SetCurrentItem(utility::random::PopRandomElement(bottlePool)); } } } @@ -1109,7 +1111,7 @@ namespace randomizer::logic::world auto entrances = this->GetShuffleableEntrances(type, onlyPrimary); // Remove any entrances which aren't shuffled - randomizer::utility::container::FilterAndEraseFromVector(entrances, [](const auto& e) { return !e->IsShuffled(); }); + utility::container::FilterAndEraseFromVector(entrances, [](const auto& e) { return !e->IsShuffled(); }); return entrances; } @@ -1163,7 +1165,7 @@ namespace randomizer::logic::world return this->_eventNames.at(eventIndex); } - randomizer::seedgen::settings::Setting& World::Setting(const std::string& settingName) + seedgen::settings::Setting& World::Setting(const std::string& settingName) { auto& settings = this->_settings; // Check to make sure the setting exists