From 6592871fa2df2c2f4f91c568c612b0c00d24df86 Mon Sep 17 00:00:00 2001 From: gymnast86 Date: Tue, 23 Jun 2026 09:16:39 -0700 Subject: [PATCH] add mirror chamber access setting --- src/d/actor/d_a_obj_gb.cpp | 12 ++++ src/d/d_com_inf_game.cpp | 7 ++ .../randomizer/game/randomizer_context.cpp | 64 +++++++++++++++++-- .../randomizer/game/randomizer_context.hpp | 26 +++++--- .../generator/data/entrance_shuffle_data.yaml | 16 ++--- .../generator/data/object_patches.yaml | 22 +++++++ .../generator/data/settings_list.yaml | 9 +++ .../data/world/overworld/Gerudo Desert.yaml | 2 +- .../randomizer/generator/logic/item_pool.cpp | 9 +++ src/dusk/ui/rando_config.cpp | 4 +- 10 files changed, 148 insertions(+), 23 deletions(-) diff --git a/src/d/actor/d_a_obj_gb.cpp b/src/d/actor/d_a_obj_gb.cpp index a610dfd5fb..795631c066 100644 --- a/src/d/actor/d_a_obj_gb.cpp +++ b/src/d/actor/d_a_obj_gb.cpp @@ -12,6 +12,11 @@ #include "d/d_com_inf_game.h" #include +#if TARGET_PC +#include "dusk/randomizer/game/stages.h" +#include "dusk/randomizer/game/tools.h" +#endif + static int daObj_Gb_Draw(obj_gb_class* i_this) { g_env_light.settingTevStruct(0x10, &i_this->current.pos, &i_this->tevStr); g_env_light.setLightTevColorType_MAJI(i_this->mModel, &i_this->tevStr); @@ -169,6 +174,13 @@ static int useHeapInit(fopAc_ac_c* actor) { static int daObj_Gb_Create(fopAc_ac_c* actor) { fopAcM_ct(actor, obj_gb_class); obj_gb_class* i_this = (obj_gb_class*)actor; +#if TARGET_PC + // Only spawn the added wall in randomizer if it should exist + if (randomizer_IsActive() && getStageID() == StageIDs::Mirror_Chamber && + !randomizer_mirrorChamberWallShouldExist()) { + return cPhs_ERROR_e; + } +#endif int rv = dComIfG_resLoad(&i_this->mPhase, "Obj_gb"); if (rv == cPhs_COMPLEATE_e) { diff --git a/src/d/d_com_inf_game.cpp b/src/d/d_com_inf_game.cpp index db6d243f9a..e0fe128c15 100644 --- a/src/d/d_com_inf_game.cpp +++ b/src/d/d_com_inf_game.cpp @@ -2013,6 +2013,13 @@ stage_arrow_class* dComIfGp_getRoomArrow(int i_roomNo) { void dComIfGp_setNextStage(char const* i_stage, s16 i_point, s8 i_roomNo, s8 i_layer, f32 i_lastSpeed, u32 i_lastMode, int i_setPoint, s8 i_wipe, s16 i_lastAngle, int param_9, int i_wipeSpeedT) { +#if TARGET_PC + // In rando, override this entrance if applicable + if (randomizer_IsActive()) { + randomizer_checkAndOverrideEntranceData(i_stage, i_roomNo, i_point, i_layer); + } +#endif + if (i_layer >= 15) { i_layer = -1; } diff --git a/src/dusk/randomizer/game/randomizer_context.cpp b/src/dusk/randomizer/game/randomizer_context.cpp index 64e159a50b..e03ae7072e 100644 --- a/src/dusk/randomizer/game/randomizer_context.cpp +++ b/src/dusk/randomizer/game/randomizer_context.cpp @@ -121,6 +121,10 @@ std::optional RandomizerContext::WriteToFile() { textData << YAML::EndMap; textData << YAML::EndMap; + for (const auto& [key, override] : mEntranceOverrides) { + out["mEntranceOverrides"][key] = std::bit_cast(override); + } + seedData << YAML::Dump(out); seedData << '\n' << textData.c_str(); seedData.close(); @@ -278,6 +282,13 @@ 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()); + this->mEntranceOverrides[key] = override; + } + dusk::ui::push_toast(dusk::ui::Toast{ .title = "Randomizer", .content = fmt::format("Loaded Randomizer Seed {}", this->mHash), @@ -291,7 +302,7 @@ std::filesystem::path RandomizerContext::GetSeedDataPath() const { } int RandomizerContext::SettingToEnum(const std::string& settingName) { - static const std::unordered_map nameToEnum = { + static const std::map nameToEnum = { {"Hyrule Barrier Dungeons", HYRULE_BARRIER_DUNGEONS}, {"Hyrule Barrier Requirements", HYRULE_BARRIER_REQUIREMENTS}, {"Hyrule Barrier Fused Shadows", HYRULE_BARRIER_FUSED_SHADOWS}, @@ -309,6 +320,7 @@ int RandomizerContext::SettingToEnum(const std::string& settingName) { {"Skip Minor Cutscenes", SKIP_MINOR_CUTSCENES}, {"Skip Major Cutscenes", SKIP_MAJOR_CUTSCENES}, {"Skip Bridge Donation", SKIP_BRIDGE_DONATION}, + {"Mirror Chamber Access", MIRROR_CHAMBER_ACCESS}, }; if (nameToEnum.contains(settingName)) { @@ -319,7 +331,7 @@ int RandomizerContext::SettingToEnum(const std::string& settingName) { } int RandomizerContext::OptionToEnum(const std::string& optionName) { - static const std::unordered_map nameToEnum = { + static const std::map nameToEnum = { {"On", ON}, {"Off", OFF}, {"None", NONE}, @@ -334,6 +346,8 @@ int RandomizerContext::OptionToEnum(const std::string& optionName) { {"Ordon Sword", ORDON_SWORD}, {"Master Sword", MASTER_SWORD}, {"Light Sword", LIGHT_SWORD}, + {"Closed", CLOSED}, + {"Barrier", BARRIER}, }; if (nameToEnum.contains(optionName)) { @@ -531,7 +545,7 @@ static void updateGoalFlags() { } } - // Palace of Twlight Access + // Palace of Twilight Access if (!dComIfGs_isEventBit(FIXED_THE_MIRROR_OF_TWILIGHT)) { bool openPalace = false; switch (settings[RandomizerContext::PALACE_OF_TWILIGHT_REQUIREMENTS]) { @@ -798,6 +812,21 @@ int randomizer_getItemAtLocation(const std::string& locationName) { return randomizer_GetContext().mItemLocations[locationName].itemId; } +void randomizer_checkAndOverrideEntranceData(const char*& stageName, s8& roomNo, s16& pointNo, s8& mapLayer) { + RandomizerContext::EntranceOverride override = { + static_cast(getStageID(stageName)), roomNo, static_cast(pointNo), mapLayer + }; + + int key = std::bit_cast(override); + if (randomizer_GetContext().mEntranceOverrides.contains(key)) { + auto& newOverride = randomizer_GetContext().mEntranceOverrides[key]; + stageName = allStages[newOverride.stageId]; + pointNo = newOverride.pointNo; + roomNo = newOverride.roomNo; + mapLayer = newOverride.mapLayer; + } +} + static void randomizer_setTempFlag(RandomizerContext::itemLocationData data) { // If stage is 0xFF, then this is an event flag if (data.stage == 0xFF) { @@ -861,6 +890,12 @@ bool randomizer_checkTempleOfTimeRequirement() { return false; } +bool randomizer_mirrorChamberWallShouldExist() { + auto mirrorChamberAccess = randomizer_GetContext().mSettings[RandomizerContext::MIRROR_CHAMBER_ACCESS]; + return mirrorChamberAccess == RandomizerContext::CLOSED || + (mirrorChamberAccess == RandomizerContext::BARRIER && !dComIfGs_isStageBossEnemy(0x13)); +} + u8 randomizer_getRandomFoolishItemModelID() { static constexpr auto foolishItemModels = std::to_array({ dItemNo_Randomizer_ARMOR_e, @@ -1204,7 +1239,7 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) { const auto& stageName = stageNode.first.as(); for (const auto& roomNode : stageNode.second) { u8 roomNo{}; - // Special value for + // Special value for actors always on the stage and not just one specific room if (roomNode.first.as() == "Stage") { roomNo = RandomizerContext::ROOM_STAGE; } else { @@ -1316,6 +1351,27 @@ 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; + } + return std::move(randoData); } diff --git a/src/dusk/randomizer/game/randomizer_context.hpp b/src/dusk/randomizer/game/randomizer_context.hpp index 7f5fcf8bf0..28c142149f 100644 --- a/src/dusk/randomizer/game/randomizer_context.hpp +++ b/src/dusk/randomizer/game/randomizer_context.hpp @@ -70,14 +70,15 @@ public: // Map of language -> map of key -> string std::unordered_map> mTextOverrides{}; - // TODO: hook this up to generator data - struct { - // for now use hardcoded values for this - std::string mapName = "F_SP103"; // (Ordon) Outside Link's House - int pointNo = 1; - int roomNo = 1; - int mapLayer = -1; - } mStartLocation; + struct EntranceOverride { + u8 stageId = 0xFF; + s8 roomNo = -1; + s8 pointNo = -1; + s8 mapLayer = -1; + }; + + // keyed by stageId << 24 | pointNo << 16 | roomNo << 8 | mapLayer + std::unordered_map mEntranceOverrides{}; std::optional WriteToFile(); std::optional LoadFromHash(const std::string& hash); @@ -101,6 +102,7 @@ public: SKIP_MINOR_CUTSCENES, SKIP_MAJOR_CUTSCENES, SKIP_BRIDGE_DONATION, + MIRROR_CHAMBER_ACCESS, }; enum Options { @@ -118,6 +120,8 @@ public: ORDON_SWORD, MASTER_SWORD, LIGHT_SWORD, + BARRIER, + CLOSED, }; static int SettingToEnum(const std::string& settingName); @@ -206,6 +210,10 @@ bool randomizer_IsActive(); 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); /* * @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 @@ -217,6 +225,8 @@ void randomizer_setTempFlagForFLWOverride(u32 key); bool randomizer_checkTempleOfTimeRequirement(); +bool randomizer_mirrorChamberWallShouldExist(); + u8 randomizer_getRandomFoolishItemModelID(); /** diff --git a/src/dusk/randomizer/generator/data/entrance_shuffle_data.yaml b/src/dusk/randomizer/generator/data/entrance_shuffle_data.yaml index eee0d702cb..4634003d31 100644 --- a/src/dusk/randomizer/generator/data/entrance_shuffle_data.yaml +++ b/src/dusk/randomizer/generator/data/entrance_shuffle_data.yaml @@ -5,9 +5,9 @@ - Type: Spawn Forward: Connection: Links Spawn -> Outside Links House - Stage: -1 - Room: -1 - Spawn: "" + Stage: 43 + Room: 1 + Spawn: "01" Spawn Type: "" Parameters: "" State: "FF" @@ -449,11 +449,11 @@ Return: Connection: Arbiters Grounds Boss Room -> Mirror Chamber Lower Alias: Arbiters Grounds Boss Room -> Mirror Chamber - Stage: -1 - Room: -1 - Spawn: "" - Spawn Type: "" - Parameters: "" + Stage: 60 + Room: 4 + Spawn: "00" + Spawn Type: "10" + Parameters: "501F" State: "FF" - Type: Boss diff --git a/src/dusk/randomizer/generator/data/object_patches.yaml b/src/dusk/randomizer/generator/data/object_patches.yaml index 27ba39f482..1f8fa231e3 100644 --- a/src/dusk/randomizer/generator/data/object_patches.yaml +++ b/src/dusk/randomizer/generator/data/object_patches.yaml @@ -3470,6 +3470,28 @@ F_SP124: layers: - 0 +# Mirror Chamber +F_SP125: + # Room 4 - Main Chamber + 4: + # Add barrier to prevent players from going back to the Arbiters Boss Room + # depending on settings + - action: add + name: Obj_gb + parameters: 0x800F0601 + position: + x: 1794.0 + y: 2523.0 + z: -17400.0 + angle: + x: 0xFF7F + y: 0x0000 + z: 0x0000 + set id: 0xFFFF + layers: + - 0 + - 1 + # Upper Zora's River F_SP126: # Room 0 - Main area diff --git a/src/dusk/randomizer/generator/data/settings_list.yaml b/src/dusk/randomizer/generator/data/settings_list.yaml index 48111657b5..fae5064bfa 100644 --- a/src/dusk/randomizer/generator/data/settings_list.yaml +++ b/src/dusk/randomizer/generator/data/settings_list.yaml @@ -79,6 +79,15 @@ - Closed: "Midna will block the player from leaving Faron Woods until Forest Temple is completed." - Open: "Midna will not prevent the player from leaving Faron Woods." +- Name: Mirror Chamber Access + Need In Game: True + Tracker Important: True + Default Option: Open + Options: + - Open: "The entrance is open and operates like normal. If you start with the Mirror Chamber Portal, you can access the Stallord boss fight without going through Arbiter's Grounds." + - Barrier: "A barrier is placed in front of the Mirror Chamber entrance and goes away once Stallord is defeated." + - Closed: "The Mirror Chamber is isolated from the world and cannot be reached from the Stallord boss room. To access it, players will either need the portal or access from Palace of Twilight." + ###################### ## Item Pool ## ###################### diff --git a/src/dusk/randomizer/generator/data/world/overworld/Gerudo Desert.yaml b/src/dusk/randomizer/generator/data/world/overworld/Gerudo Desert.yaml index 6b02b6e684..a860328b78 100644 --- a/src/dusk/randomizer/generator/data/world/overworld/Gerudo Desert.yaml +++ b/src/dusk/randomizer/generator/data/world/overworld/Gerudo Desert.yaml @@ -126,7 +126,7 @@ Can Warp: True Exits: Mirror Chamber Upper: Nothing - Arbiters Grounds Boss Room: Nothing + Arbiters Grounds Boss Room: Mirror_Chamber_Access == Open or (Mirror_Chamber_Access == Barrier and 'Can_Complete_Arbiters_Grounds') - Name: Mirror Chamber Upper Map Sector: Desert Province diff --git a/src/dusk/randomizer/generator/logic/item_pool.cpp b/src/dusk/randomizer/generator/logic/item_pool.cpp index 9058ab2132..498aaf5798 100644 --- a/src/dusk/randomizer/generator/logic/item_pool.cpp +++ b/src/dusk/randomizer/generator/logic/item_pool.cpp @@ -423,6 +423,15 @@ namespace randomizer::logic::item_pool startingItems["Castle Town Portal"] = 1; } + // Automatically give players the Mirror Chamber Portal if Mirror Chamber Access is closed + // and they aren't both randomizing and decoupling dungeon entrances. Otherwise, there's no + // way to access the chamber + if (world->Setting("Mirror Chamber Access") == "Closed" && + !(world->Setting("Randomize Dungeon Entrances") == "On" && world->Setting("Decouple Entrances") == "On")) + { + startingItems["Mirror Chamber Portal"] = 1; + } + // Add each item to the world's _startingItemPool and erase it from the regular _itemPool for (const auto& [itemName, count] : startingItems) { diff --git a/src/dusk/ui/rando_config.cpp b/src/dusk/ui/rando_config.cpp index b512e627dc..b8f830e6b7 100644 --- a/src/dusk/ui/rando_config.cpp +++ b/src/dusk/ui/rando_config.cpp @@ -926,6 +926,7 @@ RandomizerWindow::RandomizerWindow(dFile_select_c* fileSelect /*= nullptr*/) : m }); rando_config_group(leftPane, rightPane, "Palace of Twilight Requirements"); rando_config_group(leftPane, rightPane, "Faron Woods Logic"); + rando_config_group(leftPane, rightPane, "Mirror Chamber Access"); // leftPane.add_section("World (TODO)"); @@ -1220,8 +1221,7 @@ RandomizerWindow::RandomizerWindow(dFile_select_c* fileSelect /*= nullptr*/) : m leftPane.register_control(leftPane.add_button("Warp to Start").on_pressed([] { mDoAud_seStartMenu(kSoundClick); - auto& locData = randomizer_GetContext().mStartLocation; - dComIfGp_setNextStage(locData.mapName.c_str(), locData.pointNo, locData.roomNo, locData.mapLayer); + dComIfGp_setNextStage("F_SP103", 1, 1, -1); }), rightPane, [](Pane& pane) { pane.clear(); pane.add_rml("Respawns the player at their appropriate starting location.");