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/data/entrance_shuffle_data.yaml b/mods/randomizer/generator/data/entrance_shuffle_data.yaml index 4634003d31..d967cc0e48 100644 --- a/mods/randomizer/generator/data/entrance_shuffle_data.yaml +++ b/mods/randomizer/generator/data/entrance_shuffle_data.yaml @@ -1222,7 +1222,7 @@ Forward: Connection: Kakariko Renados Sanctuary -> Kakariko Renados Sanctuary Basement Stage: 75 - Room: 5 + Room: 7 Spawn: "00" Spawn Type: "FF" Parameters: "FFFF" 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..78c1df9bf2 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,18 @@ 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); + void SetCoupledDoor(int16_t entrance) { _coupledEntrance = entrance; } + int16_t getCoupledDoor() { return _coupledEntrance; } + private: int _id = -1; area::Area* _parentArea = nullptr; @@ -159,6 +173,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. */ @@ -202,6 +221,9 @@ namespace randomizer::logic::entrance // to this one. So if the entrance is North Faron Woods -> Forest Temple Entrance, // then _assumed is the target entrance Root -> Forest Temple Entrance Entrance* _assumed = nullptr; + + // If the entrance is a coupled door, this is the other door's point to override + int16_t _coupledEntrance = -1; }; using EntrancePool = std::vector; diff --git a/mods/randomizer/generator/logic/entrance_shuffle.cpp b/mods/randomizer/generator/logic/entrance_shuffle.cpp index cc63431318..d042f2d264 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() : ""); @@ -114,7 +114,7 @@ namespace randomizer::logic::entrance_shuffle [&](const auto& door) { return door->IsPrimary() == mainDoor->IsPrimary(); }); auto coupledDoor = *coupledDoorItr; - // TODO: Add the coupled door's info to the main door + mainDoor->SetCoupledDoor(coupledDoor->GetPointNo()); // Completely remove the coupled door from the world graph doors.erase(coupledDoorItr); @@ -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 f40bd26073..3e7dd8bc81 100644 --- a/mods/randomizer/src/hooks.cpp +++ b/mods/randomizer/src/hooks.cpp @@ -33,6 +33,9 @@ #include "d/d_msg_object.h" #include "d/d_save.h" #include "d/d_shop_system.h" +#include "d/d_s_name.h" +#include "f_op/f_op_overlap_mng.h" +#include "m_Do/m_Do_Reset.h" #include "c/c_damagereaction.h" DEFINE_HOOK(&dFile_select_c::selectDataNameMove, dFile_select_c__selectDataNameMove); @@ -56,8 +59,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); @@ -119,6 +126,8 @@ DEFINE_HOOK_SYMBOL("daE_MD_Create", int(fopAc_ac_c*), daE_MD_c__create); DEFINE_HOOK(&daObjMasterSword_c::executeWait, daObjMasterSword_c__executeWait); DEFINE_HOOK_SYMBOL("daObjMasterSword_Execute", int(daObjMasterSword_c*), daObjMasterSword_c__execute); +DEFINE_HOOK(&dScnName_c::changeGameScene, dScnName_c__changeGameScene); + namespace randomizer::ui { dialogSelectModeState g_dialogSelectModeState = SelectReady; } @@ -1933,6 +1942,9 @@ HookAction hookPreNpcFOrderEvent(ModContext*, void* args, void* retval, void*) { u16 i_priority = mods::arg(args, 4); // kinda hacky way to check for the state where Bo is trying to talk after getting Iron Boots + if (i_this->eventInfo.getArchiveName() == nullptr) { + return HOOK_CONTINUE; + } const std::string arcName = i_this->eventInfo.getArchiveName(); if (arcName == "Bou4" && i_priority == 40) { i_forceSpeak = FALSE; @@ -2106,6 +2118,12 @@ HookAction hookPreMasterSwordExecute(ModContext*, void* args, void* retval, void return HOOK_CONTINUE; } +void hookPost_dScnName_c__changeGameScene(ModContext* ctx, void* args, void* retval, void* userdata) { + if (!mDoRst::isReset() && !fopOvlpM_IsPeek()) { + randomizer::session::registerStartingLocation(); + } +} + } ModResult initialize() { @@ -2148,7 +2166,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); @@ -2211,6 +2229,8 @@ ModResult initialize() { ADD_HOOK_REPLACE(daObjMasterSword_c__executeWait, hookReplaceMasterSwordExecuteWait); ADD_HOOK_PRE(daObjMasterSword_c__execute, hookPreMasterSwordExecute); + ADD_HOOK_POST(dScnName_c__changeGameScene, hookPost_dScnName_c__changeGameScene); + return MOD_OK; } @@ -2237,6 +2257,7 @@ ModResult uninstall() { mods::hook::uninstall(svc_hook); mods::hook::uninstall(svc_hook); + mods::hook::uninstall(); mods::hook::uninstall(svc_hook); @@ -2296,6 +2317,8 @@ ModResult uninstall() { mods::hook::uninstall(svc_hook); mods::hook::uninstall(svc_hook); + mods::hook::uninstall(); + return MOD_OK; } } diff --git a/mods/randomizer/src/randomizer_context.cpp b/mods/randomizer/src/randomizer_context.cpp index 4d2599cd4b..55edcd5ab9 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::debug("({},{},{},{}) -> ({},{},{},{})",key.stageId,key.roomNo,key.mapLayer,key.pointNo,val.stageId,val.roomNo,val.mapLayer,val.pointNo); + // } + + // mods::log::debug("Original Stage:{}, {}, {}, {}",stageName,roomNo,pointNo,mapLayer); + if (randomizer_GetContext().mEntranceOverrides.contains(override)) { + auto& newOverride = randomizer_GetContext().mEntranceOverrides[override]; stageName = allStages[newOverride.stageId]; pointNo = newOverride.pointNo; roomNo = newOverride.roomNo; mapLayer = newOverride.mapLayer; + // mods::log::debug("New Stage:{}, {}, {}, {}",stageName,roomNo,pointNo,mapLayer); } } @@ -1723,39 +1729,18 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) { } } - // Entrance Overrides - if (world->Setting("Mirror Chamber Access") == "Closed") { - // 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 - }; - - RandomizerContext::EntranceOverride override = { - StageIDs::Bulblin_Camp, - 3, - 3, - -1 - }; - - randoData.mEntranceOverrides[std::bit_cast(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 +1750,48 @@ 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[forward] = replaces; + + // Set an override for the second door, if the entrance is coupled + if (entrance->getCoupledDoor() != -1) { + RandomizerContext::EntranceOverride coupled = {.stageId = entrance->GetStageId(), .roomNo = entrance->GetRoomNo(), .mapLayer = entrance->GetLayerNo(), .pointNo = entrance->getCoupledDoor()}; + randoData.mEntranceOverrides[coupled] = replaces; + } + } + } + + if (world->Setting("Mirror Chamber Access") == "Closed") { + // 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 = { + .stageId = StageIDs::Mirror_Chamber, + .roomNo = 4, + .mapLayer = -1, + .pointNo = 0, + }; + + RandomizerContext::EntranceOverride override = { + .stageId = StageIDs::Bulblin_Camp, + .roomNo = 3, + .mapLayer = -1, + .pointNo = 3, + }; + + // Check if we are already overriding the bulblin camp entrance, and correctly override the entrance + const auto& it = randoData.mEntranceOverrides.find(override); + randoData.mEntranceOverrides[original] = (it != randoData.mEntranceOverrides.end()) ? it->second : override; + } + return std::move(randoData); } @@ -1807,4 +1834,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..7e789a500c 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,26 @@ 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{}(std::bit_cast(x)); + } + }; + + std::unordered_map + mEntranceOverrides{}; // Overrides for returning to spawn. Keyed by stageId std::unordered_map mReturnToPlaceOverrides{}; @@ -153,7 +164,7 @@ public: static constexpr u8 EVENT_ITEM_QUEUE_SIZE = 10; - RandomizerState() {mInitialized = false;} + RandomizerState() { mInitialized = false; } int _create(); int _delete(); @@ -161,19 +172,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 +228,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 +297,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/session.cpp b/mods/randomizer/src/session.cpp index f4bec82ba6..dc5c74218f 100644 --- a/mods/randomizer/src/session.cpp +++ b/mods/randomizer/src/session.cpp @@ -135,7 +135,7 @@ bool activateSeed(const char* hash) { svc_mng.item->set_check_resolver(mod_ctx, nullptr, resolve_check, nullptr, &s_check_resolver); svc_mng.item->observe_gives(mod_ctx, observe_give, nullptr, &s_check_observer); - registerStageEdits(); + registerStageEdits(); mods::log::info("activated seed {}", ctx.mHash); return true; } @@ -292,6 +292,62 @@ void registerStageEdits() { } } +// Set link to spawn outside of his house if we are playing with entrance rando and aren't already spawning in a dungeon +void registerStartingLocation() { + auto& ctx = randomizer_GetContext(); + + if (ctx.mEntranceOverrides.empty()) { + return; + } + + // Check that we aren't playing only with the setting "Mirror Chamber Access") == "Closed" + bool isExclusivelyMirrorChamber = false; + const auto& mirrorChamberIt = ctx.mEntranceOverrides.find(RandomizerContext::EntranceOverride{ + .stageId = StageIDs::Mirror_Chamber, + .roomNo = 4, + .mapLayer = -1, + .pointNo = 0 + }); + if (ctx.mEntranceOverrides.size() == 1 && mirrorChamberIt != ctx.mEntranceOverrides.end()) { + if (mirrorChamberIt->second == RandomizerContext::EntranceOverride{ + .stageId = StageIDs::Bulblin_Camp, + .roomNo = 3, + .mapLayer = -1, + .pointNo = 3, + }) { + isExclusivelyMirrorChamber = true; + } + } + + if (isExclusivelyMirrorChamber) { + return; + } + + constexpr std::array kDungeonNames = { + "D_MN01","D_MN04","D_MN05","D_MN06","D_MN07","D_MN08","D_MN09","D_MN10","D_MN11" + }; + + dSv_player_return_place_c& returnPlace = dComIfGs_getSaveData()->mPlayer.getPlayerReturnPlace(); + bool isDungeon = false; + for (const auto& name : kDungeonNames) { + if (returnPlace.getName() == name) { + isDungeon = true; + break; + } + } + + if (isDungeon) { + // Force-set the entrance without the override + g_dComIfG_gameInfo.play.mNextStage.getStartStage()->set(returnPlace.getName(), returnPlace.getRoomNo(), returnPlace.mPlayerStatus, -1); + return; + } + + // Set the spawn to outside of link's house, which will get overrided later if we are replacing it + returnPlace.set("F_SP103",1,1); + // g_dComIfG_gameInfo.play.mNextStage.getStartStage()->set("F_SP103", 1, 1, -1); + dComIfGp_setNextStage("F_SP103", 1, 1, -1); +} + void onNewSave(ModContext*, uint32_t, void*) { const std::string hash = g_pending_seed_hash; if (hash.empty()) @@ -351,4 +407,4 @@ void update() { g_randomizerState.execute(); } -} \ No newline at end of file +} diff --git a/mods/randomizer/src/session.hpp b/mods/randomizer/src/session.hpp index ebe9ce0516..002bba97d3 100644 --- a/mods/randomizer/src/session.hpp +++ b/mods/randomizer/src/session.hpp @@ -35,4 +35,5 @@ void update(); void deactivateSeed(); void setupRandomizerFile(); void registerStageEdits(); -} \ No newline at end of file +void registerStartingLocation(); +} 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");