diff --git a/mods/randomizer/CMakeLists.txt b/mods/randomizer/CMakeLists.txt index 300539ce2e..7c25b203bb 100644 --- a/mods/randomizer/CMakeLists.txt +++ b/mods/randomizer/CMakeLists.txt @@ -1,5 +1,6 @@ cmake_minimum_required(VERSION 3.25) project(randomizer CXX) +set(CMAKE_CXX_STANDARD 23) if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) set(DUSK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../.." CACHE PATH "Path to dusk source root") diff --git a/mods/randomizer/generator/logic/entrance.cpp b/mods/randomizer/generator/logic/entrance.cpp index b27d8d2c47..a9095d914b 100644 --- a/mods/randomizer/generator/logic/entrance.cpp +++ b/mods/randomizer/generator/logic/entrance.cpp @@ -356,4 +356,31 @@ namespace randomizer::logic::entrance return {parentAreaName, connectedAreaName}; } + + bool Entrance::SetGameInfo(const YAML::Node& node) { + bool ret = true; + std::string stageIdString = node["Stage"].as(); + if (stageIdString == "null") { + stageIdString = "-1"; + ret = false; + } + int stageId = std::stoi(stageIdString); + if (stageId == -1) { + stageId = 0xFF; + } + SetStageId(static_cast(stageId)); + std::string roomNoString = node["Room"].as(); + if (roomNoString == "null") { + roomNoString = "-1"; + ret = false; + } + SetRoomNo(static_cast(std::stoi(roomNoString))); + SetPointNo(static_cast(std::stoi("0x0"+node["Spawn"].as(),nullptr,16))); + int layer = std::stoi("0x0"+node["State"].as(),nullptr,16); + if (layer == 0xFF) { + layer = -1; + } + SetLayerNo(static_cast(layer)); + return ret; + } } // namespace randomizer::logic::entrance diff --git a/mods/randomizer/generator/logic/entrance.hpp b/mods/randomizer/generator/logic/entrance.hpp index 8159d4353c..d9d27695f1 100644 --- a/mods/randomizer/generator/logic/entrance.hpp +++ b/mods/randomizer/generator/logic/entrance.hpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include "../utility/yaml.hpp" // Forward Declarations namespace randomizer::logic::area @@ -148,6 +150,16 @@ namespace randomizer::logic::entrance */ Entrance* AssumeReachable(); + void SetStageId(uint8_t stageId) { _stageId = stageId; } + void SetRoomNo(int8_t roomNo) { _roomNo = roomNo; } + void SetLayerNo(int8_t layerNo) { _layerNo = layerNo; } + void SetPointNo(int16_t pointNo) { _pointNo = pointNo; } + uint8_t GetStageId() const { return _stageId; } + int8_t GetRoomNo() const { return _roomNo; } + int8_t GetLayerNo() const { return _layerNo; } + int16_t GetPointNo() const { return _pointNo; } + bool SetGameInfo(const YAML::Node& node); + private: int _id = -1; area::Area* _parentArea = nullptr; @@ -159,6 +171,11 @@ namespace randomizer::logic::entrance std::string _alias = ""; world::World* _world = nullptr; + uint8_t _stageId = 0xFF; + int8_t _roomNo = -1; + int8_t _layerNo = -1; + int16_t _pointNo = -1; + /** * @brief The local requirement for this entrance assuming we have access to its parent area. */ diff --git a/mods/randomizer/generator/logic/entrance_shuffle.cpp b/mods/randomizer/generator/logic/entrance_shuffle.cpp index cc63431318..7dfc5dee54 100644 --- a/mods/randomizer/generator/logic/entrance_shuffle.cpp +++ b/mods/randomizer/generator/logic/entrance_shuffle.cpp @@ -65,7 +65,7 @@ namespace randomizer::logic::entrance_shuffle auto forwardEntrance = world->GetEntrance(forwardEntry["Connection"].as()); forwardEntrance->SetType(type); - // TODO: Set actual entrance data + forwardEntrance->SetGameInfo(forwardEntry); forwardEntrance->SetID(world->GetNewEntranceID()); forwardEntrance->SetPrimary(true); forwardEntrance->SetAlias( @@ -78,7 +78,7 @@ namespace randomizer::logic::entrance_shuffle auto returnEntrance = world->GetEntrance(returnEntry["Connection"].as()); returnEntrance->SetType(type); - // TODO: Set actual entrance data + returnEntrance->SetGameInfo(returnEntry); returnEntrance->SetID(world->GetNewEntranceID()); returnEntrance->SetAlias( returnEntry["Alias"] ? returnEntry["Alias"].as() : ""); @@ -471,7 +471,7 @@ namespace randomizer::logic::entrance_shuffle // plandomizer stuff going on. Currently, the only non-assumed entrance we're shuffling // is the randomized spawn, but if we ever shuffle warp portals, they'll go here too. - int retries = 20; + int retries = 10000; while (retries > 0) { std::unordered_map rollbacks = {}; diff --git a/mods/randomizer/generator/logic/flatten/bits.cpp b/mods/randomizer/generator/logic/flatten/bits.cpp index b95a699e75..6fef22a9a1 100644 --- a/mods/randomizer/generator/logic/flatten/bits.cpp +++ b/mods/randomizer/generator/logic/flatten/bits.cpp @@ -4,6 +4,7 @@ #include "../item.hpp" #include +#include BitVector::BitVector(const std::list& bits) { diff --git a/mods/randomizer/generator/seedgen/settings.hpp b/mods/randomizer/generator/seedgen/settings.hpp index 66c3b68fdb..3ae2a204bb 100644 --- a/mods/randomizer/generator/seedgen/settings.hpp +++ b/mods/randomizer/generator/seedgen/settings.hpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace randomizer::seedgen::settings { diff --git a/mods/randomizer/src/hooks.cpp b/mods/randomizer/src/hooks.cpp index 314576e114..17fd01ab75 100644 --- a/mods/randomizer/src/hooks.cpp +++ b/mods/randomizer/src/hooks.cpp @@ -45,8 +45,12 @@ DEFINE_HOOK(&dSv_player_item_c::setLineUpItem, dSv_player_item_c__setLineUpItem) DEFINE_HOOK(&dSv_info_c::onSwitch, dSv_info_c__onSwitch); -/*DEFINE_HOOK_SYMBOL("__Z21dComIfGp_setNextStagePKcsaafjiasii", - void(char const*, s16, s8, s8, f32, u32, int, s8, s16, int, int), setNextStage);*/ +#ifdef _MSVC_LANG +#define setNextStage_sig "?dComIfGp_setNextStage@@YAXPEBDFCCMIHCFHH@Z" +#else +#define setNextStage_sig "_Z21dComIfGp_setNextStagePKcsaafjiasii" +#endif +DEFINE_HOOK_SYMBOL(setNextStage_sig, void(char const *, s16, s8, s8, f32, u32, int, s8, s16, int, int), setNextStage); DEFINE_HOOK_SYMBOL("daObj_Gb_Create", int(fopAc_ac_c*), ObjGb_Create); @@ -1433,7 +1437,7 @@ ModResult initialize() { ADD_HOOK_PRE(dSv_info_c__onSwitch, hookPreSaveInfoOnSwitch); - //ADD_HOOK_PRE(setNextStage, hookPreSetNextStage); + ADD_HOOK_PRE(setNextStage, hookPreSetNextStage); ADD_HOOK_PRE(ObjGb_Create, hookPreObjGbCreate); @@ -1491,6 +1495,7 @@ ModResult uninstall() { mods::hook::uninstall(svc_hook); mods::hook::uninstall(svc_hook); + mods::hook::uninstall(); mods::hook::uninstall(svc_hook); @@ -1524,4 +1529,4 @@ ModResult uninstall() { return MOD_OK; } -} \ No newline at end of file +} diff --git a/mods/randomizer/src/randomizer_context.cpp b/mods/randomizer/src/randomizer_context.cpp index 4d2599cd4b..84c775e623 100644 --- a/mods/randomizer/src/randomizer_context.cpp +++ b/mods/randomizer/src/randomizer_context.cpp @@ -13,6 +13,7 @@ #include "../generator/randomizer.hpp" #include "../generator/utility/text.hpp" #include "../generator/utility/string.hpp" +#include "../generator/logic/entrance_shuffle.hpp" #include #include @@ -136,11 +137,11 @@ std::optional RandomizerContext::WriteToFile() { } for (const auto& [key, override] : mEntranceOverrides) { - out["mEntranceOverrides"][key] = std::bit_cast(override); + out["mEntranceOverrides"][std::bit_cast(key)] = std::bit_cast(override); } for (const auto& [key, override] : mReturnToPlaceOverrides) { - out["mReturnToPlaceOverrides"][key] = std::bit_cast(override); + out["mReturnToPlaceOverrides"][key] = std::bit_cast(override); } seedData << YAML::Dump(out); @@ -320,15 +321,15 @@ std::optional RandomizerContext::LoadFromHash(const std::string& ha // Entrance Overrides for (const auto& entranceNode : in["mEntranceOverrides"]) { - auto key = entranceNode.first.as(); - auto override = std::bit_cast(entranceNode.second.as()); + const auto key = std::bit_cast(entranceNode.first.as());; + const auto override = std::bit_cast(entranceNode.second.as()); this->mEntranceOverrides[key] = override; } // Return to Place Overrides for (const auto& entranceNode : in["mReturnToPlaceOverrides"]) { auto key = entranceNode.first.as(); - auto override = std::bit_cast(entranceNode.second.as()); + const auto override = std::bit_cast(entranceNode.second.as()); this->mReturnToPlaceOverrides[key] = override; } @@ -862,16 +863,21 @@ int randomizer_getItemAtLocation(const std::string& locationName) { void randomizer_checkAndOverrideEntranceData(const char*& stageName, s8& roomNo, s16& pointNo, s8& mapLayer) { RandomizerContext::EntranceOverride override = { - static_cast(getStageID(stageName)), roomNo, static_cast(pointNo), mapLayer - }; + .stageId = static_cast(getStageID(stageName)), .roomNo = roomNo, .mapLayer = mapLayer, .pointNo = static_cast(pointNo)}; - int key = std::bit_cast(override); - if (randomizer_GetContext().mEntranceOverrides.contains(key)) { - auto& newOverride = randomizer_GetContext().mEntranceOverrides[key]; + // Debugging: Dump mEntranceOverrides + // for (const auto& [key,val] : randomizer_GetContext().mEntranceOverrides) { + // mods::log::info("({},{},{},{}) -> ({},{},{},{})",key.stageId,key.roomNo,key.mapLayer,key.pointNo,val.stageId,val.roomNo,val.mapLayer,val.pointNo); + // } + + if (randomizer_GetContext().mEntranceOverrides.contains(override)) { + auto& newOverride = randomizer_GetContext().mEntranceOverrides[override]; + mods::log::debug("Original Stage:{}, {}, {}, {}",stageName,roomNo,pointNo,mapLayer); stageName = allStages[newOverride.stageId]; pointNo = newOverride.pointNo; roomNo = newOverride.roomNo; mapLayer = newOverride.mapLayer; + mods::log::debug("New Stage:{}, {}, {}, {}",stageName,roomNo,pointNo,mapLayer); } } @@ -1728,34 +1734,34 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) { // Set exiting the Arbiter's Grounds Boss Room to spawn at the Arbiter's Grounds entrance // if mirror chamber access is closed RandomizerContext::EntranceOverride original = { - StageIDs::Mirror_Chamber, - 4, - 0, - -1 + .stageId = StageIDs::Mirror_Chamber, + .roomNo = 4, + .mapLayer = -1, + .pointNo = 0, }; RandomizerContext::EntranceOverride override = { - StageIDs::Bulblin_Camp, - 3, - 3, - -1 + .stageId = StageIDs::Bulblin_Camp, + .roomNo = 3, + .mapLayer = -1, + .pointNo = 3, }; - randoData.mEntranceOverrides[std::bit_cast(original)] = override; + randoData.mEntranceOverrides[original] = override; } // Vanilla Return to Place Overrides. Will need to change when boss/miniboss ER is implemented - static const std::list, RandomizerContext::EntranceOverride>> defaultPlaceOverrides{ - {{Forest_Temple, Ook, Diababa}, {Forest_Temple, 22, 0, -1}}, - {{Goron_Mines, Dangoro, Fyrus}, {Goron_Mines, 1, 0, -1}}, - {{Lakebed_Temple, Deku_Toad, Morpheel}, {Lakebed_Temple, 0, 0, -1}}, - {{Arbiters_Grounds, Death_Sword, Stallord}, {Arbiters_Grounds, 0, 0, -1}}, - {{Snowpeak_Ruins, Darkhammer, Blizzeta}, {Snowpeak_Ruins, 0, 0, -1}}, - {{Temple_of_Time, Darknut, Armogohma}, {Temple_of_Time, 0, 0, -1}}, - {{City_in_the_Sky, Aeralfos, Argorok}, {City_in_the_Sky, 0, 3, -1}}, + static const std::vector, RandomizerContext::EntranceOverride>> defaultPlaceOverrides{ + {{Forest_Temple, Ook, Diababa}, {.stageId = Forest_Temple, .roomNo = 22, .mapLayer = -1, .pointNo = 0}}, + {{Goron_Mines, Dangoro, Fyrus}, {.stageId = Goron_Mines, .roomNo = 1, .mapLayer = -1, .pointNo = 0}}, + {{Lakebed_Temple, Deku_Toad, Morpheel}, {.stageId = Lakebed_Temple, .roomNo = 0, .mapLayer = -1, .pointNo = 0}}, + {{Arbiters_Grounds, Death_Sword, Stallord}, {.stageId = Arbiters_Grounds, .roomNo = 0, .mapLayer = -1, .pointNo = 0}}, + {{Snowpeak_Ruins, Darkhammer, Blizzeta}, {.stageId = Snowpeak_Ruins, .roomNo = 0, .mapLayer = -1, .pointNo = 0}}, + {{Temple_of_Time, Darknut, Armogohma}, {.stageId = Temple_of_Time, .roomNo = 0, .mapLayer = -1, .pointNo = 0}}, + {{City_in_the_Sky, Aeralfos, Argorok}, {.stageId = City_in_the_Sky, .roomNo = 0, .mapLayer = -1, .pointNo = 3}}, {{Palace_of_Twilight, Phantom_Zant_1, - Phantom_Zant_2, Zant_Main_Room, Zant_Fight}, {Palace_of_Twilight, 0, 0, -1}}, - {{Hyrule_Castle, Ganondorf_Castle, Ganondorf_Field}, {Hyrule_Castle, 11, 0, -1}}, + Phantom_Zant_2, Zant_Main_Room, Zant_Fight}, {.stageId = Palace_of_Twilight, .roomNo = 0, .mapLayer = -1, .pointNo = 0}}, + {{Hyrule_Castle, Ganondorf_Castle, Ganondorf_Field}, {.stageId = Hyrule_Castle, .roomNo = 11, .mapLayer = -1, .pointNo = 0}}, }; // Return to Place Overrides @@ -1765,6 +1771,20 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) { } } + // Apply entrance randomization + auto shuffleData = LOAD_EMBED_YAML(RANDO_DATA_PATH "entrance_shuffle_data.yaml"); + if (world->AnyEntranceRandomizerEnabled()) { + for (const auto& entrance : world->GetShuffledEntrances()) { + randomizer::logic::entrance::Entrance* replacesEntrance = entrance->GetReplaces(); + RandomizerContext::EntranceOverride forward = {.stageId = entrance->GetStageId(), .roomNo = entrance->GetRoomNo(), .mapLayer = entrance->GetLayerNo(), .pointNo = entrance->GetPointNo()}; + RandomizerContext::EntranceOverride replaces = {.stageId = replacesEntrance->GetStageId(), .roomNo = replacesEntrance->GetRoomNo(), .mapLayer = replacesEntrance->GetLayerNo(), .pointNo = replacesEntrance->GetPointNo()}; + if (forward.stageId == 0xFF || replaces.stageId == 0xFF || forward.roomNo == -1 || replaces.roomNo == -1) { + continue; + } + randoData.mEntranceOverrides[replaces] = forward; + } + } + return std::move(randoData); } @@ -1807,4 +1827,4 @@ bool GenerateAndWriteSeed(std::string& generationStatusMsg) { generationStatusMsg = fmt::format("Seed generated! Hash: {}", randoData.mHash); return true; -} \ No newline at end of file +} diff --git a/mods/randomizer/src/randomizer_context.hpp b/mods/randomizer/src/randomizer_context.hpp index 23f41b3378..75bc918ec3 100644 --- a/mods/randomizer/src/randomizer_context.hpp +++ b/mods/randomizer/src/randomizer_context.hpp @@ -20,7 +20,7 @@ class RandomizerContext { public: static constexpr size_t ACTR_CRC_SIZE = 32; - static constexpr size_t TGSC_CRC_SIZE = 35; // 3 extra bytes for scale x, y, z + static constexpr size_t TGSC_CRC_SIZE = 35; // 3 extra bytes for scale x, y, z static constexpr size_t OBJ_DELETE_SIZE = 1; static constexpr u8 ROOM_STAGE = 0xFF; @@ -37,7 +37,7 @@ public: std::unordered_map> mStartRegionFlags{}; std::list mStartingInventory{}; - struct itemLocationData{ + struct itemLocationData { int itemId{0xFF}; int stage{0xFF}; u16 flag{0xFFFF}; @@ -50,7 +50,7 @@ public: std::unordered_map mSkyCharacterOverrides{}; std::unordered_map mGoldenWolfOverrides{}; std::unordered_map mShopOverrides{}; - std::unordered_map mTwilitInsectOverrides{}; // Just used in tracker for now + std::unordered_map mTwilitInsectOverrides{}; // Just used in tracker for now std::unordered_map mFlowItemMessageOverrides{}; std::unordered_map mItemLocations{}; @@ -71,15 +71,27 @@ public: // Map of language -> map of key -> string std::unordered_map> mTextOverrides{}; + // Padded to 8 bytes so bit_cast can convert it to a uint64_t struct EntranceOverride { u8 stageId = 0xFF; s8 roomNo = -1; - s8 pointNo = -1; s8 mapLayer = -1; + u8 _pad0; + s16 pointNo = -1; + u16 _pad1; + bool operator==(const EntranceOverride& rhs) const = default; }; - // keyed by stageId << 24 | pointNo << 16 | roomNo << 8 | mapLayer - std::unordered_map mEntranceOverrides{}; + // Packs an EntranceOverride into a uint64_t + struct EntranceOverrideHash { + size_t operator()(const EntranceOverride& x) const noexcept { + return std::hash{}( + ((uint64_t)x.stageId << 32) | (x.roomNo << 24) | (x.mapLayer << 16) | x.pointNo); + } + }; + + std::unordered_map + mEntranceOverrides{}; // Overrides for returning to spawn. Keyed by stageId std::unordered_map mReturnToPlaceOverrides{}; @@ -153,7 +165,7 @@ public: static constexpr u8 EVENT_ITEM_QUEUE_SIZE = 10; - RandomizerState() {mInitialized = false;} + RandomizerState() { mInitialized = false; } int _create(); int _delete(); @@ -161,19 +173,19 @@ public: int draw(); void addItemToEventQueue(u8 item); void initGiveItemToPlayer(); - //void handleBonkDamage(); + // void handleBonkDamage(); void handleTimeOfDayChange(); void handleTimeSpeed(); void offLoad(); void handlePoeItem(u8 bitSw); - u8 getGiveItemToPlayerStatus() const { return mEventItemStatus;} + u8 getGiveItemToPlayerStatus() const { return mEventItemStatus; } u8 getTimeChange() const { return mTimeChange; } bool getRoomReloadingState() const { return mRoomReloadingState; } bool getHasPendingToDChange() const { return mHasPendingToDChange; } - void setGiveItemToPlayerStatus(u8 status) { mEventItemStatus = status;} + void setGiveItemToPlayerStatus(u8 status) { mEventItemStatus = status; } void setHasPendingToDChange(bool hasPending) { mHasPendingToDChange = hasPending; } void setTimeChange(u8 newTimeChange) { mTimeChange = newTimeChange; } void setRoomReloadingState(bool newState) { mRoomReloadingState = newState; } @@ -217,7 +229,8 @@ int randomizer_getItemAtLocation(const std::string& locationName); /* * @brief Overrides the given entrance paramaters if an override exists for them */ -void randomizer_checkAndOverrideEntranceData(const char*& i_Name, s8& i_RoomNo, s16& i_Point, s8& i_Layer); +void randomizer_checkAndOverrideEntranceData( + const char*& i_Name, s8& i_RoomNo, s16& i_Point, s8& i_Layer); /* * @brief Puts the associated flag into the randomizer state's temporary flag * variable. This allows the tracker/Archipelago to know a location has been checked @@ -285,4 +298,4 @@ u32 getStageObjCRC32(u8* data, size_t size); */ bool GenerateAndWriteSeed(std::string& generationStatusMsg); -#endif //DUSK_RANDOMIZER_CONTEXT_HPP +#endif // DUSK_RANDOMIZER_CONTEXT_HPP diff --git a/mods/randomizer/src/ui/rando_config.cpp b/mods/randomizer/src/ui/rando_config.cpp index 6bcc42bbe4..d4b7d85a95 100644 --- a/mods/randomizer/src/ui/rando_config.cpp +++ b/mods/randomizer/src/ui/rando_config.cpp @@ -420,6 +420,17 @@ ModResult buildSeedOptionsTab(ModContext* ctx, UiWindowHandle, UiElementHandle l add_select_setting(leftPane, "Lower Hyrule Castle Chandelier"); add_select_setting(leftPane, "Skip Bridge Donation"); + add_section(leftPane, "Entrance Randomizer Settings"); + add_select_setting(leftPane, "Randomize Starting Spawn"); + add_select_setting(leftPane, "Randomize Dungeon Entrances"); + add_select_setting(leftPane, "Randomize Boss Entrances"); + add_select_setting(leftPane, "Randomize Grotto Entrances"); + add_select_setting(leftPane, "Randomize Cave Entrances"); + add_select_setting(leftPane, "Randomize Interior Entrances"); + add_select_setting(leftPane, "Randomize Overworld Entrances"); + add_select_setting(leftPane, "Decouple Double Door Entrances"); + add_select_setting(leftPane, "Decouple Entrances"); + add_section(leftPane, "Additional Settings"); add_select_setting(leftPane, "Starting Time of Day"); add_select_setting(leftPane, "Logic Transform Anywhere");