Improve Entrance Coupling Logic, Improve Entrance Shuffle Data, fix seed name

This commit is contained in:
jdflyer
2026-08-15 16:42:46 -07:00
parent 33a9db2e49
commit 85a1a2901c
8 changed files with 1051 additions and 805 deletions
File diff suppressed because it is too large Load Diff
@@ -665,9 +665,35 @@
Exits:
Upper Zoras River Izas House: Can_Open_Doors and Upper_Zoras_River_Portal
Fishing Hole: Can_Open_Doors
Zoras Domain: Nothing
Upper Zoras River To Zoras Domain (On Land): Nothing
Upper Zoras River To Zoras Domain (Swimming): Nothing
Upper Zoras River To Zoras Domain (Underwater): Iron_Boots or Zora_Armor
Lanayru Field: Nothing
- Name: Upper Zoras River To Zoras Domain (On Land)
Map Sector: Lanayru Province
Region: Upper Zoras River
Twilight: Lanayru
Exits:
Zoras Domain To Upper Zoras River (On Land): Nothing
Upper Zoras River: Nothing
- Name: Upper Zoras River To Zoras Domain (Swimming)
Map Sector: Lanayru Province
Region: Upper Zoras River
Twilight: Lanayru
Exits:
Zoras Domain To Upper Zoras River (Swimming): Nothing
Upper Zoras River: Nothing
- Name: Upper Zoras River To Zoras Domain (Underwater)
Map Sector: Lanayru Province
Region: Upper Zoras River
Twilight: Lanayru
Exits:
Zoras Domain To Upper Zoras River (Underwater): Iron_Boots or Zora_Armor
Upper Zoras River: Nothing
- Name: Upper Zoras River Izas House
Can Warp: True
Locations:
@@ -718,7 +744,33 @@
Zoras Domain West Ledge: Clawshot or Can_Midna_Jump or Twilight
Zoras Domain Top of Waterfall: Clawshot or Can_Midna_Jump or Twilight
Snowpeak Climb Lower: Not_Twilight
Upper Zoras River: Nothing
Zoras Domain To Upper Zoras River (On Land): Nothing
Zoras Domain To Upper Zoras River (Swimming): Nothing
Zoras Domain To Upper Zoras River (Underwater): Iron_Boots or Zora_Armor
- Name: Zoras Domain To Upper Zoras River (On Land)
Map Sector: Lanayru Province
Region: Zoras Domain
Twilight: Lanayru
Exits:
Upper Zoras River To Zoras Domain (On Land): Nothing
Zoras Domain: Nothing
- Name: Zoras Domain To Upper Zoras River (Swimming)
Map Sector: Lanayru Province
Region: Zoras Domain
Twilight: Lanayru
Exits:
Upper Zoras River To Zoras Domain (Swimming): Nothing
Zoras Domain: Nothing
- Name: Zoras Domain To Upper Zoras River (Underwater)
Map Sector: Lanayru Province
Region: Zoras Domain
Twilight: Lanayru
Exits:
Upper Zoras River To Zoras Domain (Underwater): Nothing
Zoras Domain: Iron_Boots or Zora_Armor
- Name: Zoras Domain West Ledge
Map Sector: Lanayru Province
@@ -747,10 +799,45 @@
Zoras Domain Shortcut Lower Boulder Rupee: Can_Smash
Zoras Domain Shortcut Upper Boulder Rupee: Can_Smash
Exits:
Zoras Throne Room: Nothing
Zoras Domain Top of Waterfall Center: Nothing
Zoras Domain Top of Waterfall West: Nothing
Zoras Domain Top of Waterfall East: Nothing
Zoras Domain Top of Waterfall Underwater: Iron_Boots or Zora_Armor
Zoras Domain West Ledge: Can_Smash
Zoras Domain: Nothing
- Name: Zoras Domain Top of Waterfall Center
Map Sector: Lanayru Province
Region: Zoras Domain
Twilight: Lanayru
Exits:
Zoras Domain Top of Waterfall: Nothing
Zoras Throne Room Center: Nothing
- Name: Zoras Domain Top of Waterfall West
Map Sector: Lanayru Province
Region: Zoras Domain
Twilight: Lanayru
Exits:
Zoras Domain Top of Waterfall: Nothing
Zoras Throne Room West: Nothing
- Name: Zoras Domain Top of Waterfall East
Map Sector: Lanayru Province
Region: Zoras Domain
Twilight: Lanayru
Exits:
Zoras Domain Top of Waterfall: Nothing
Zoras Throne Room East: Iron_Boots or Zora_Armor
- Name: Zoras Domain Top of Waterfall Underwater
Map Sector: Lanayru Province
Region: Zoras Domain
Twilight: Lanayru
Exits:
Zoras Domain Top of Waterfall: Nothing
Zoras Throne Room Underwater: Iron_Boots or Zora_Armor
- Name: Zoras Throne Room
Map Sector: Lanayru Province
Region: Zoras Domain
@@ -769,4 +856,39 @@
Zoras Domain Underwater Goron: Water_Bombs and Iron_Boots and Zora_Armor
Zoras Domain Throne Room Twilit Insect: Can_Defeat_Lanayru_Twilit_Insect
Exits:
Zoras Domain Top of Waterfall: Nothing
Zoras Throne Room Center: Nothing
Zoras Throne Room West: Nothing
Zoras Throne Room East: Nothing
Zoras Throne Room Underwater: Iron_Boots or Zora_Armor
- Name: Zoras Throne Room Center
Map Sector: Lanayru Province
Region: Zoras Domain
Twilight: Lanayru
Exits:
Zoras Domain Top of Waterfall Center: Nothing
Zoras Throne Room: Nothing
- Name: Zoras Throne Room West
Map Sector: Lanayru Province
Region: Zoras Domain
Twilight: Lanayru
Exits:
Zoras Domain Top of Waterfall West: Nothing
Zoras Throne Room: Nothing
- Name: Zoras Throne Room East
Map Sector: Lanayru Province
Region: Zoras Domain
Twilight: Lanayru
Exits:
Zoras Domain Top of Waterfall East: Nothing
Zoras Throne Room: Nothing
- Name: Zoras Throne Room Underwater
Map Sector: Lanayru Province
Region: Zoras Domain
Twilight: Lanayru
Exits:
Zoras Domain Top of Waterfall Underwater: Nothing
Zoras Throne Room: Iron_Boots or Zora_Armor
+5 -25
View File
@@ -357,30 +357,10 @@ namespace randomizer::logic::entrance
return {parentAreaName, connectedAreaName};
}
bool Entrance::SetGameInfo(const YAML::Node& node) {
bool ret = true;
std::string stageIdString = node["Stage"].as<std::string>();
if (stageIdString == "null") {
stageIdString = "-1";
ret = false;
}
int stageId = std::stoi(stageIdString);
if (stageId == -1) {
stageId = 0xFF;
}
SetStageId(static_cast<uint8_t>(stageId));
std::string roomNoString = node["Room"].as<std::string>();
if (roomNoString == "null") {
roomNoString = "-1";
ret = false;
}
SetRoomNo(static_cast<int8_t>(std::stoi(roomNoString)));
SetPointNo(static_cast<int16_t>(std::stoi("0x0"+node["Spawn"].as<std::string>(),nullptr,16)));
int layer = std::stoi("0x0"+node["State"].as<std::string>(),nullptr,16);
if (layer == 0xFF) {
layer = -1;
}
SetLayerNo(static_cast<int8_t>(layer));
return ret;
void Entrance::SetGameInfo(const YAML::Node& node) {
SetStageId(node["Stage"].as<uint8_t>());
SetRoomNo(node["Room"].as<int8_t>());
SetPointNo(node["Spawn"].as<int16_t>());
SetLayerNo(node["State"].as<int8_t>());
}
} // namespace randomizer::logic::entrance
+4 -4
View File
@@ -158,9 +158,9 @@ namespace randomizer::logic::entrance
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; }
void SetGameInfo(const YAML::Node& node);
void SetCoupledEntrances(const std::vector<int16_t>& entrances) { _coupledEntrances = entrances; }
const std::vector<int16_t>& getCoupledEntrances() const { return _coupledEntrances; }
private:
int _id = -1;
@@ -223,7 +223,7 @@ namespace randomizer::logic::entrance
Entrance* _assumed = nullptr;
// If the entrance is a coupled door, this is the other door's point to override
int16_t _coupledEntrance = -1;
std::vector<int16_t> _coupledEntrances = {};
};
using EntrancePool = std::vector<Entrance*>;
@@ -42,8 +42,8 @@ namespace randomizer::logic::entrance_shuffle
void SetAllEntrancesData(world::World* world)
{
// Keep track of which double door entrances are together
std::unordered_map<std::string, std::list<entrance::Entrance*>> coupledDoors = {};
// Keep track of which entrances are together
std::unordered_map<std::string, std::list<entrance::Entrance*>> coupledEntrances = {};
auto entranceDataTree = LOAD_EMBED_YAML(RANDO_DATA_PATH "entrance_shuffle_data.yaml");
for (const auto& entranceDataNode : entranceDataTree)
@@ -60,6 +60,21 @@ namespace randomizer::logic::entrance_shuffle
}
auto& forwardEntry = entranceDataNode["Forward"];
// If the entrance can't actually be mapped in-game, skip it
auto stageString = forwardEntry["Stage"].as<std::string>();
auto roomString = forwardEntry["Stage"].as<std::string>();
if (stageString == "null" || stageString == "-1" || roomString == "null" || roomString == "-1") {
continue;
}
if (entranceDataNode["Return"]) {
stageString = entranceDataNode["Return"]["Stage"].as<std::string>();
roomString = entranceDataNode["Return"]["Stage"].as<std::string>();
if (stageString == "null" || stageString == "-1" || roomString == "null" || roomString == "-1") {
continue;
}
}
// Check to make sure all required fields are present for the forward entry
YAMLVerifyFields(forwardEntry, "Connection" /*, "Info" */);
@@ -84,45 +99,50 @@ namespace randomizer::logic::entrance_shuffle
returnEntry["Alias"] ? returnEntry["Alias"].as<std::string>() : "");
forwardEntrance->BindTwoWay(returnEntrance);
// Add double door entrances to their respective tag group
if (entranceDataNode["Door Couple Tag"])
// Add coupled entrances to their respective tag group
if (entranceDataNode["Entrance Couple Tag"])
{
auto tag = entranceDataNode["Door Couple Tag"].as<std::string>();
if (!coupledDoors.contains(tag))
auto tag = entranceDataNode["Entrance Couple Tag"].as<std::string>();
if (!coupledEntrances.contains(tag))
{
coupledDoors[tag] = {};
coupledEntrances[tag] = {};
}
coupledDoors.at(tag).push_back(forwardEntrance);
coupledDoors.at(tag).push_back(returnEntrance);
coupledEntrances.at(tag).push_back(forwardEntrance);
coupledEntrances.at(tag).push_back(returnEntrance);
}
}
}
// If double doors are coupled, add the coupled door's info to the main door, remove the coupled door entrance, and
// If entrances are coupled, add the coupled entrance's spawn point to the main door, remove the coupled door entrance, and
// rename the main door to be more general
if (world->Setting("Decouple Double Door Entrances") == "Off")
{
for (auto& [tag, doors] : coupledDoors)
for (auto& [tag, entrances] : coupledEntrances)
{
while (!doors.empty())
while (!entrances.empty())
{
auto mainDoor = doors.back();
doors.pop_back();
auto mainEntrance = entrances.back();
entrances.pop_back();
auto coupledDoorItr =
std::ranges::find_if(doors,
[&](const auto& door) { return door->IsPrimary() == mainDoor->IsPrimary(); });
auto coupledDoor = *coupledDoorItr;
std::vector<int16_t> coupledPoints;
for (auto it = entrances.begin(); it != entrances.end();) {
auto coupledEntrance = *it;
if (coupledEntrance->IsPrimary() == mainEntrance->IsPrimary()) {
coupledPoints.push_back(coupledEntrance->GetPointNo());
mainDoor->SetCoupledDoor(coupledDoor->GetPointNo());
// Completely remove the coupled door from the world graph
coupledEntrance->GetConnectedArea()->RemoveEntrance(coupledEntrance);
coupledEntrance->GetParentArea()->RemoveExit(coupledEntrance);
it = entrances.erase(it);
}else{
++it;
}
}
// Completely remove the coupled door from the world graph
doors.erase(coupledDoorItr);
coupledDoor->GetConnectedArea()->RemoveEntrance(coupledDoor);
coupledDoor->GetParentArea()->RemoveExit(coupledDoor);
mainEntrance->SetCoupledEntrances(std::move(coupledPoints));
// Change the main door's name to be more general
mainDoor->GeneralizeName();
mainEntrance->GeneralizeName();
}
}
}
+6 -7
View File
@@ -867,17 +867,17 @@ void randomizer_checkAndOverrideEntranceData(const char*& stageName, s8& roomNo,
// 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::info("({},{},{},{}) -> ({},{},{},{})",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);
// mods::log::info("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);
// mods::log::info("New Stage:{}, {}, {}, {}",stageName,roomNo,pointNo,mapLayer);
}
}
@@ -1751,7 +1751,6 @@ 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();
@@ -1762,9 +1761,9 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) {
}
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()};
// Set overrides for all coupled entrances
for (const auto& point : entrance->getCoupledEntrances()) {
RandomizerContext::EntranceOverride coupled = {.stageId = entrance->GetStageId(), .roomNo = entrance->GetRoomNo(), .mapLayer = entrance->GetLayerNo(), .pointNo = point};
randoData.mEntranceOverrides[coupled] = replaces;
}
}
+4 -15
View File
@@ -300,7 +300,7 @@ void registerStartingLocation() {
return;
}
// Check that we aren't playing only with the setting "Mirror Chamber Access") == "Closed"
// Check that we aren't playing only with the setting Mirror Chamber Access set to Closed
bool isExclusivelyMirrorChamber = false;
const auto& mirrorChamberIt = ctx.mEntranceOverrides.find(RandomizerContext::EntranceOverride{
.stageId = StageIDs::Mirror_Chamber,
@@ -323,20 +323,10 @@ void registerStartingLocation() {
return;
}
constexpr std::array<std::string_view,9> 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) {
// If we are loading into a dungeon, preserve our original return place
if (std::string(returnPlace.getName()).find("D_MN") == 0) {
// Force-set the entrance without the override
g_dComIfG_gameInfo.play.mNextStage.getStartStage()->set(returnPlace.getName(), returnPlace.getRoomNo(), returnPlace.mPlayerStatus, -1);
return;
@@ -344,7 +334,6 @@ void registerStartingLocation() {
// 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);
}
+4 -2
View File
@@ -264,9 +264,11 @@ ModResult buildSeedManagementTab(ModContext* ctx, UiWindowHandle, UiElementHandl
add_string_input(leftPane,
"Seed String",
"Current value of the seed used by the randomizer for generation. Leave blank for a random value.",
32,
31,
[](ModContext*, void*, UiControlValue* out_value) {
out_value->string_value = GetRandomizerConfig().GetSeed().c_str();
static char buffer[32];
strncpy(buffer,GetRandomizerConfig().GetSeed().c_str(),31);
out_value->string_value = buffer;
},
[](ModContext*, void*, const UiControlValue* value) {
GetRandomizerConfig().SetSeed(value->string_value);