From d6fac7735904e18e3ccaebe29cf4956685d944a3 Mon Sep 17 00:00:00 2001 From: Jesper Arvidsson Date: Mon, 26 Jan 2026 16:59:08 +0100 Subject: [PATCH 1/7] First version --- .../randomizer_entrance_tracker.cpp | 6 +- .../randomizer/randomizer_entrance_tracker.h | 1 + soh/soh/Network/Sail/Sail.cpp | 184 ++++++++++++++++++ 3 files changed, 188 insertions(+), 3 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp index 2891e7755a8..7692379bfc5 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp @@ -452,7 +452,7 @@ int16_t LinkIsInArea(const EntranceData* entrance) { return -1; } -bool IsEntranceDiscovered(s16 index) { +bool EntranceTracker_IsEntranceDiscovered(s16 index) { bool isDiscovered = Entrance_GetIsEntranceDiscovered(index); if (!isDiscovered) { // If the pair included one of the hyrule field <-> zora's river entrances, @@ -907,7 +907,7 @@ void EntranceTrackerWindow::DrawElement() { continue; } - bool isDiscovered = IsEntranceDiscovered(entrance.index); + bool isDiscovered = EntranceTracker_IsEntranceDiscovered(entrance.index); bool showOverride = (!destToggle ? showTo : showFrom) || isDiscovered; bool showOriginal = (!destToggle ? showFrom : showTo) || isDiscovered; @@ -956,7 +956,7 @@ void EntranceTrackerWindow::DrawElement() { const EntranceData* original = GetEntranceData(entrance.index); const EntranceData* override = GetEntranceData(entrance.override); - bool isDiscovered = IsEntranceDiscovered(entrance.index); + bool isDiscovered = EntranceTracker_IsEntranceDiscovered(entrance.index); bool showOverride = (!destToggle ? showTo : showFrom) || isDiscovered; bool showOriginal = (!destToggle ? showFrom : showTo) || isDiscovered; diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.h b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.h index 31592cd0812..00715ba19e8 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.h +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.h @@ -91,6 +91,7 @@ void InitEntranceTrackingData(); s16 GetLastEntranceOverride(); s16 GetCurrentGrottoId(); const EntranceData* GetEntranceData(s16); +bool EntranceTracker_IsEntranceDiscovered(s16 index); void EntranceTracker_LoadFromPreset(nlohmann::json info); class EntranceTrackerSettingsWindow final : public Ship::GuiWindow { diff --git a/soh/soh/Network/Sail/Sail.cpp b/soh/soh/Network/Sail/Sail.cpp index ddaf059c589..46a9d6912ae 100644 --- a/soh/soh/Network/Sail/Sail.cpp +++ b/soh/soh/Network/Sail/Sail.cpp @@ -2,13 +2,127 @@ #include #include #include +#include "global.h" #include "soh/OTRGlobals.h" +#include "soh/Enhancements/randomizer/SeedContext.h" +#include "soh/Enhancements/randomizer/entrance.h" +#include "soh/Enhancements/randomizer/randomizer_entrance_tracker.h" +#include "soh/Enhancements/randomizer/randomizerTypes.h" #include "soh/util.h" template bool IsType(const SrcType* src) { return dynamic_cast(src) != nullptr; } +static void Sail_GetEntranceSceneRoomSpawn(const EntranceData* data, int32_t* scene, int32_t* room, int32_t* spawn) { + if (scene) { + *scene = -1; + } + if (room) { + *room = -1; + } + if (spawn) { + *spawn = -1; + } + + if (data == nullptr || data->scenes.empty()) { + return; + } + + const auto& info = data->scenes.front(); + if (scene) { + *scene = info.scene; + } + if (spawn) { + *spawn = info.spawn; + } + if (room) { + *room = (info.scene == SCENE_THIEVES_HIDEOUT && info.spawn >= 0) ? info.spawn : -1; + } +} + +static bool Sail_ShouldSkipEntrance(const EntranceData* original, const EntranceData* overrideData, s16 index, + bool hideReverse, bool discoveredOnly) { + if (original == nullptr || overrideData == nullptr) { + return true; + } + + if (original->metaTag.ends_with("bw") || overrideData->metaTag.ends_with("bw")) { + return true; + } + + const bool decoupled = + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_DECOUPLED_ENTRANCES) == RO_GENERIC_ON; + + if ((original->type == ENTRANCE_TYPE_DUNGEON || original->type == ENTRANCE_TYPE_GROTTO || + original->type == ENTRANCE_TYPE_INTERIOR) && + (original->oneExit != 1 && !decoupled && hideReverse)) { + return true; + } + + if (discoveredOnly && !EntranceTracker_IsEntranceDiscovered(index)) { + return true; + } + + return false; +} + +static void Sail_SendEntranceMap(Sail* sail) { + if (sail == nullptr || !sail->isConnected || !GameInteractor::IsSaveLoaded()) { + return; + } + + if (!IS_RANDO || + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_ENTRANCES) != RO_GENERIC_ON) { + return; + } + + auto entranceCtx = OTRGlobals::Instance->gRandoContext->GetEntranceShuffler(); + if (entranceCtx == nullptr) { + return; + } + + const bool hideReverse = CVarGetInteger(CVAR_TRACKER_ENTRANCE("HideReverseEntrances"), 1); + + nlohmann::json payload; + payload["type"] = "entrance_map"; + payload["connections"] = nlohmann::json::array(); + + for (size_t i = 0; i < ENTRANCE_OVERRIDES_MAX_COUNT; i++) { + EntranceOverride entrance = entranceCtx->entranceOverrides[i]; + if (Entrance_EntranceIsNull(&entrance)) { + break; + } + + const EntranceData* original = GetEntranceData(entrance.index); + const EntranceData* overrideData = GetEntranceData(entrance.override); + + if (Sail_ShouldSkipEntrance(original, overrideData, entrance.index, hideReverse, true)) { + continue; + } + + int32_t fromScene = -1; + int32_t fromRoom = -1; + int32_t toScene = -1; + int32_t toSpawn = -1; + + Sail_GetEntranceSceneRoomSpawn(original, &fromScene, &fromRoom, nullptr); + Sail_GetEntranceSceneRoomSpawn(overrideData, &toScene, nullptr, &toSpawn); + + nlohmann::json entry; + entry["fromEntrance"] = static_cast(entrance.index); + entry["toEntrance"] = static_cast(entrance.override); + entry["fromScene"] = fromScene; + entry["fromRoom"] = fromRoom; + entry["toScene"] = toScene; + entry["spawn"] = toSpawn; + + payload["connections"].push_back(entry); + } + + sail->SendJsonToRemote(payload); +} + void Sail::Enable() { Network::Enable(CVarGetString(CVAR_REMOTE_SAIL("Host"), "127.0.0.1"), CVarGetInteger(CVAR_REMOTE_SAIL("Port"), 43384)); @@ -16,6 +130,7 @@ void Sail::Enable() { void Sail::OnConnected() { RegisterHooks(); + Sail_SendEntranceMap(this); } void Sail::OnDisconnected() { @@ -347,10 +462,79 @@ void Sail::RegisterHooks() { SendJsonToRemote(payload); }); + COND_HOOK(OnSceneInit, isConnected, [&](int32_t sceneNum) { + if (!isConnected || !GameInteractor::IsSaveLoaded()) + return; + + static_cast(sceneNum); + + if (!IS_RANDO || + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_ENTRANCES) != RO_GENERIC_ON) { + return; + } + + const s16 lastEntranceIndex = GetLastEntranceOverride(); + if (lastEntranceIndex < 0) { + return; + } + + const bool hideReverse = CVarGetInteger(CVAR_TRACKER_ENTRANCE("HideReverseEntrances"), 1); + + const s16 nextEntranceIndex = Entrance_PeekNextIndexOverride(lastEntranceIndex); + const EntranceData* original = GetEntranceData(lastEntranceIndex); + const EntranceData* overrideData = GetEntranceData(nextEntranceIndex); + + if (Sail_ShouldSkipEntrance(original, overrideData, lastEntranceIndex, hideReverse, true)) { + return; + } + + int32_t fromScene = -1; + int32_t fromRoom = -1; + int32_t toScene = -1; + int32_t toSpawn = -1; + + Sail_GetEntranceSceneRoomSpawn(original, &fromScene, &fromRoom, nullptr); + Sail_GetEntranceSceneRoomSpawn(overrideData, &toScene, nullptr, &toSpawn); + + if (toScene < 0 || toSpawn < 0) { + const int32_t entranceIndex = static_cast(gSaveContext.entranceIndex); + const int32_t entranceTableIndex = entranceIndex + static_cast(gSaveContext.sceneSetupIndex); + const EntranceInfo entranceInfo = gEntranceTable[entranceTableIndex]; + toScene = entranceInfo.scene; + toSpawn = entranceInfo.spawn; + } + + nlohmann::json payload; + payload["type"] = "transition"; + payload["fromScene"] = fromScene; + payload["fromRoom"] = fromRoom; + payload["exit"] = static_cast(lastEntranceIndex); + payload["toScene"] = toScene; + payload["spawn"] = toSpawn; + + SendJsonToRemote(payload); + }); + COND_HOOK(OnLoadGame, isConnected, [&](int32_t fileNum) { if (!isConnected || !GameInteractor::IsSaveLoaded()) return; + bool entranceRando = false; + bool decoupledEntrances = false; + if (IS_RANDO) { + entranceRando = + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_ENTRANCES) == RO_GENERIC_ON; + decoupledEntrances = + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_DECOUPLED_ENTRANCES) == RO_GENERIC_ON; + } + + nlohmann::json seedPayload; + seedPayload["type"] = "seed_info"; + seedPayload["entranceRando"] = entranceRando; + seedPayload["decoupledEntrances"] = decoupledEntrances; + SendJsonToRemote(seedPayload); + Sail_SendEntranceMap(this); + nlohmann::json payload; payload["id"] = std::rand(); payload["type"] = "hook"; From 1c665e6b5359615211e5ba41387a2ba75d2a42c6 Mon Sep 17 00:00:00 2001 From: Jesper Arvidsson Date: Mon, 26 Jan 2026 19:27:00 +0100 Subject: [PATCH 2/7] Fix Sending Package --- soh/soh/Network/Sail/Sail.cpp | 46 ++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/soh/soh/Network/Sail/Sail.cpp b/soh/soh/Network/Sail/Sail.cpp index 46a9d6912ae..0f43d8d4c41 100644 --- a/soh/soh/Network/Sail/Sail.cpp +++ b/soh/soh/Network/Sail/Sail.cpp @@ -14,6 +14,8 @@ template bool IsType(const SrcType* src) { return dynamic_cast(src) != nullptr; } +static bool sPendingInitialSync = false; + static void Sail_GetEntranceSceneRoomSpawn(const EntranceData* data, int32_t* scene, int32_t* room, int32_t* spawn) { if (scene) { *scene = -1; @@ -67,6 +69,27 @@ static bool Sail_ShouldSkipEntrance(const EntranceData* original, const Entrance return false; } +static void Sail_SendSeedInfo(Sail* sail) { + if (sail == nullptr || !sail->isConnected || !GameInteractor::IsSaveLoaded()) { + return; + } + + bool entranceRando = false; + bool decoupledEntrances = false; + if (IS_RANDO) { + entranceRando = + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_ENTRANCES) == RO_GENERIC_ON; + decoupledEntrances = + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_DECOUPLED_ENTRANCES) == RO_GENERIC_ON; + } + + nlohmann::json seedPayload; + seedPayload["type"] = "seed_info"; + seedPayload["entranceRando"] = entranceRando; + seedPayload["decoupledEntrances"] = decoupledEntrances; + sail->SendJsonToRemote(seedPayload); +} + static void Sail_SendEntranceMap(Sail* sail) { if (sail == nullptr || !sail->isConnected || !GameInteractor::IsSaveLoaded()) { return; @@ -130,6 +153,7 @@ void Sail::Enable() { void Sail::OnConnected() { RegisterHooks(); + sPendingInitialSync = true; Sail_SendEntranceMap(this); } @@ -468,6 +492,12 @@ void Sail::RegisterHooks() { static_cast(sceneNum); + if (sPendingInitialSync) { + Sail_SendSeedInfo(this); + Sail_SendEntranceMap(this); + sPendingInitialSync = false; + } + if (!IS_RANDO || OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_ENTRANCES) != RO_GENERIC_ON) { return; @@ -519,21 +549,9 @@ void Sail::RegisterHooks() { if (!isConnected || !GameInteractor::IsSaveLoaded()) return; - bool entranceRando = false; - bool decoupledEntrances = false; - if (IS_RANDO) { - entranceRando = - OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_ENTRANCES) == RO_GENERIC_ON; - decoupledEntrances = - OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_DECOUPLED_ENTRANCES) == RO_GENERIC_ON; - } - - nlohmann::json seedPayload; - seedPayload["type"] = "seed_info"; - seedPayload["entranceRando"] = entranceRando; - seedPayload["decoupledEntrances"] = decoupledEntrances; - SendJsonToRemote(seedPayload); + Sail_SendSeedInfo(this); Sail_SendEntranceMap(this); + sPendingInitialSync = false; nlohmann::json payload; payload["id"] = std::rand(); From ba4c13fe1d3e3ec95f4d3eb56cb00f110db4c96f Mon Sep 17 00:00:00 2001 From: Jesper Arvidsson Date: Tue, 27 Jan 2026 08:38:21 +0100 Subject: [PATCH 3/7] add current position --- soh/soh/Network/Sail/Sail.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/soh/soh/Network/Sail/Sail.cpp b/soh/soh/Network/Sail/Sail.cpp index 0f43d8d4c41..7d7ca454653 100644 --- a/soh/soh/Network/Sail/Sail.cpp +++ b/soh/soh/Network/Sail/Sail.cpp @@ -498,6 +498,20 @@ void Sail::RegisterHooks() { sPendingInitialSync = false; } + // Always publish the player's current scene/spawn on scene init so downstream tools + // can establish a reliable "current location" anchor. + const int32_t entranceIndex = static_cast(gSaveContext.entranceIndex); + const int32_t entranceTableIndex = entranceIndex + static_cast(gSaveContext.sceneSetupIndex); + const EntranceInfo entranceInfo = gEntranceTable[entranceTableIndex]; + const int32_t roomNum = gPlayState ? static_cast(gPlayState->roomCtx.curRoom.num) : -1; + + nlohmann::json currentScenePayload; + currentScenePayload["type"] = "current_scene"; + currentScenePayload["sceneNum"] = static_cast(entranceInfo.scene); + currentScenePayload["spawn"] = static_cast(entranceInfo.spawn); + currentScenePayload["room"] = roomNum; + SendJsonToRemote(currentScenePayload); + if (!IS_RANDO || OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_ENTRANCES) != RO_GENERIC_ON) { return; From 16d7b44211544b310559900edf4be791e50375a2 Mon Sep 17 00:00:00 2001 From: Jesper Arvidsson Date: Tue, 27 Jan 2026 09:02:53 +0100 Subject: [PATCH 4/7] fix start of game --- soh/soh/Network/Sail/Sail.cpp | 60 +++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/soh/soh/Network/Sail/Sail.cpp b/soh/soh/Network/Sail/Sail.cpp index 7d7ca454653..6c435c82ff2 100644 --- a/soh/soh/Network/Sail/Sail.cpp +++ b/soh/soh/Network/Sail/Sail.cpp @@ -90,6 +90,38 @@ static void Sail_SendSeedInfo(Sail* sail) { sail->SendJsonToRemote(seedPayload); } +static void Sail_SendCurrentScene(Sail* sail) { + if (sail == nullptr || !sail->isConnected || !GameInteractor::IsSaveLoaded()) { + return; + } + + const int32_t entranceIndex = static_cast(gSaveContext.entranceIndex); + const int32_t entranceTableIndex = entranceIndex + static_cast(gSaveContext.sceneSetupIndex); + const EntranceInfo entranceInfo = gEntranceTable[entranceTableIndex]; + const int32_t roomNum = gPlayState ? static_cast(gPlayState->roomCtx.curRoom.num) : -1; + + std::string sceneName; + const s16 lastEntranceIndex = GetLastEntranceOverride(); + if (lastEntranceIndex >= 0) { + const s16 nextEntranceIndex = Entrance_PeekNextIndexOverride(lastEntranceIndex); + const EntranceData* overrideData = GetEntranceData(nextEntranceIndex); + if (overrideData != nullptr) { + sceneName = overrideData->destination; + } + } + + nlohmann::json currentScenePayload; + currentScenePayload["type"] = "current_scene"; + currentScenePayload["sceneNum"] = static_cast(entranceInfo.scene); + currentScenePayload["spawn"] = static_cast(entranceInfo.spawn); + currentScenePayload["room"] = roomNum; + if (!sceneName.empty()) { + currentScenePayload["sceneName"] = sceneName; + } + + sail->SendJsonToRemote(currentScenePayload); +} + static void Sail_SendEntranceMap(Sail* sail) { if (sail == nullptr || !sail->isConnected || !GameInteractor::IsSaveLoaded()) { return; @@ -139,6 +171,8 @@ static void Sail_SendEntranceMap(Sail* sail) { entry["fromRoom"] = fromRoom; entry["toScene"] = toScene; entry["spawn"] = toSpawn; + entry["fromName"] = original->source; + entry["toName"] = overrideData->destination; payload["connections"].push_back(entry); } @@ -153,8 +187,14 @@ void Sail::Enable() { void Sail::OnConnected() { RegisterHooks(); - sPendingInitialSync = true; - Sail_SendEntranceMap(this); + if (GameInteractor::IsSaveLoaded()) { + Sail_SendSeedInfo(this); + Sail_SendCurrentScene(this); + Sail_SendEntranceMap(this); + sPendingInitialSync = false; + } else { + sPendingInitialSync = true; + } } void Sail::OnDisconnected() { @@ -494,23 +534,14 @@ void Sail::RegisterHooks() { if (sPendingInitialSync) { Sail_SendSeedInfo(this); + Sail_SendCurrentScene(this); Sail_SendEntranceMap(this); sPendingInitialSync = false; } // Always publish the player's current scene/spawn on scene init so downstream tools // can establish a reliable "current location" anchor. - const int32_t entranceIndex = static_cast(gSaveContext.entranceIndex); - const int32_t entranceTableIndex = entranceIndex + static_cast(gSaveContext.sceneSetupIndex); - const EntranceInfo entranceInfo = gEntranceTable[entranceTableIndex]; - const int32_t roomNum = gPlayState ? static_cast(gPlayState->roomCtx.curRoom.num) : -1; - - nlohmann::json currentScenePayload; - currentScenePayload["type"] = "current_scene"; - currentScenePayload["sceneNum"] = static_cast(entranceInfo.scene); - currentScenePayload["spawn"] = static_cast(entranceInfo.spawn); - currentScenePayload["room"] = roomNum; - SendJsonToRemote(currentScenePayload); + Sail_SendCurrentScene(this); if (!IS_RANDO || OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_ENTRANCES) != RO_GENERIC_ON) { @@ -555,6 +586,8 @@ void Sail::RegisterHooks() { payload["exit"] = static_cast(lastEntranceIndex); payload["toScene"] = toScene; payload["spawn"] = toSpawn; + payload["fromName"] = original->source; + payload["toName"] = overrideData->destination; SendJsonToRemote(payload); }); @@ -564,6 +597,7 @@ void Sail::RegisterHooks() { return; Sail_SendSeedInfo(this); + Sail_SendCurrentScene(this); Sail_SendEntranceMap(this); sPendingInitialSync = false; From b6b4faebc845921e32321ee6d31df64eba71112f Mon Sep 17 00:00:00 2001 From: Jesper Arvidsson Date: Tue, 27 Jan 2026 09:40:41 +0100 Subject: [PATCH 5/7] readjsut hooks --- soh/soh/Network/Sail/Sail.cpp | 74 ++--------------------------------- 1 file changed, 4 insertions(+), 70 deletions(-) diff --git a/soh/soh/Network/Sail/Sail.cpp b/soh/soh/Network/Sail/Sail.cpp index 6c435c82ff2..8bd1e7b8a69 100644 --- a/soh/soh/Network/Sail/Sail.cpp +++ b/soh/soh/Network/Sail/Sail.cpp @@ -517,79 +517,13 @@ void Sail::RegisterHooks() { if (!isConnected || !GameInteractor::IsSaveLoaded()) return; - nlohmann::json payload; - payload["id"] = std::rand(); - payload["type"] = "hook"; - payload["hook"]["type"] = "OnTransitionEnd"; - payload["hook"]["sceneNum"] = sceneNum; - - SendJsonToRemote(payload); - }); - - COND_HOOK(OnSceneInit, isConnected, [&](int32_t sceneNum) { - if (!isConnected || !GameInteractor::IsSaveLoaded()) - return; - static_cast(sceneNum); - if (sPendingInitialSync) { - Sail_SendSeedInfo(this); - Sail_SendCurrentScene(this); - Sail_SendEntranceMap(this); - sPendingInitialSync = false; - } - - // Always publish the player's current scene/spawn on scene init so downstream tools - // can establish a reliable "current location" anchor. + // Treat transition end as the sync point: send the tracker state the website needs. + Sail_SendSeedInfo(this); Sail_SendCurrentScene(this); - - if (!IS_RANDO || - OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_ENTRANCES) != RO_GENERIC_ON) { - return; - } - - const s16 lastEntranceIndex = GetLastEntranceOverride(); - if (lastEntranceIndex < 0) { - return; - } - - const bool hideReverse = CVarGetInteger(CVAR_TRACKER_ENTRANCE("HideReverseEntrances"), 1); - - const s16 nextEntranceIndex = Entrance_PeekNextIndexOverride(lastEntranceIndex); - const EntranceData* original = GetEntranceData(lastEntranceIndex); - const EntranceData* overrideData = GetEntranceData(nextEntranceIndex); - - if (Sail_ShouldSkipEntrance(original, overrideData, lastEntranceIndex, hideReverse, true)) { - return; - } - - int32_t fromScene = -1; - int32_t fromRoom = -1; - int32_t toScene = -1; - int32_t toSpawn = -1; - - Sail_GetEntranceSceneRoomSpawn(original, &fromScene, &fromRoom, nullptr); - Sail_GetEntranceSceneRoomSpawn(overrideData, &toScene, nullptr, &toSpawn); - - if (toScene < 0 || toSpawn < 0) { - const int32_t entranceIndex = static_cast(gSaveContext.entranceIndex); - const int32_t entranceTableIndex = entranceIndex + static_cast(gSaveContext.sceneSetupIndex); - const EntranceInfo entranceInfo = gEntranceTable[entranceTableIndex]; - toScene = entranceInfo.scene; - toSpawn = entranceInfo.spawn; - } - - nlohmann::json payload; - payload["type"] = "transition"; - payload["fromScene"] = fromScene; - payload["fromRoom"] = fromRoom; - payload["exit"] = static_cast(lastEntranceIndex); - payload["toScene"] = toScene; - payload["spawn"] = toSpawn; - payload["fromName"] = original->source; - payload["toName"] = overrideData->destination; - - SendJsonToRemote(payload); + Sail_SendEntranceMap(this); + sPendingInitialSync = false; }); COND_HOOK(OnLoadGame, isConnected, [&](int32_t fileNum) { From bc31873cae6a8f98ce14376d93da48b128cd5100 Mon Sep 17 00:00:00 2001 From: Jesper Arvidsson Date: Tue, 27 Jan 2026 12:51:11 +0100 Subject: [PATCH 6/7] Add area source of truth for sail --- .../randomizer/randomizer_entrance_tracker.cpp | 18 ++++++++++++++++++ .../randomizer/randomizer_entrance_tracker.h | 2 ++ soh/soh/Network/Sail/Sail.cpp | 8 ++++++++ 3 files changed, 28 insertions(+) diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp index 7692379bfc5..4fb2293628d 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp @@ -1020,3 +1020,21 @@ void EntranceTrackerWindow::InitElement() { GameInteractor::Instance->RegisterGameHook( [](int32_t fileNum) { ClearEntranceTrackingData(); }); } + +const char* EntranceTracker_GetGroupName(SpoilerEntranceGroup group) { + const size_t idx = static_cast(group); + const size_t count = sizeof(spoilerEntranceGroupNames) / sizeof(spoilerEntranceGroupNames[0]); + if (idx >= count) { + return "Unknown"; + } + return spoilerEntranceGroupNames[idx].c_str(); +} + +const char* EntranceTracker_GetTypeName(TrackerEntranceType type) { + const size_t idx = static_cast(type); + const size_t count = sizeof(groupTypeNames) / sizeof(groupTypeNames[0]); + if (idx >= count) { + return "Unknown"; + } + return groupTypeNames[idx].c_str(); +} diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.h b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.h index 00715ba19e8..1139d00a3b1 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.h +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.h @@ -92,6 +92,8 @@ s16 GetLastEntranceOverride(); s16 GetCurrentGrottoId(); const EntranceData* GetEntranceData(s16); bool EntranceTracker_IsEntranceDiscovered(s16 index); +const char* EntranceTracker_GetGroupName(SpoilerEntranceGroup group); +const char* EntranceTracker_GetTypeName(TrackerEntranceType type); void EntranceTracker_LoadFromPreset(nlohmann::json info); class EntranceTrackerSettingsWindow final : public Ship::GuiWindow { diff --git a/soh/soh/Network/Sail/Sail.cpp b/soh/soh/Network/Sail/Sail.cpp index 8bd1e7b8a69..94d2310c7d5 100644 --- a/soh/soh/Network/Sail/Sail.cpp +++ b/soh/soh/Network/Sail/Sail.cpp @@ -173,6 +173,14 @@ static void Sail_SendEntranceMap(Sail* sail) { entry["spawn"] = toSpawn; entry["fromName"] = original->source; entry["toName"] = overrideData->destination; + entry["fromGroupId"] = static_cast(original->srcGroup); + entry["fromGroupName"] = EntranceTracker_GetGroupName(original->srcGroup); + entry["toGroupId"] = static_cast(overrideData->dstGroup); + entry["toGroupName"] = EntranceTracker_GetGroupName(overrideData->dstGroup); + entry["fromTypeId"] = static_cast(original->type); + entry["fromTypeName"] = EntranceTracker_GetTypeName(original->type); + entry["toTypeId"] = static_cast(overrideData->type); + entry["toTypeName"] = EntranceTracker_GetTypeName(overrideData->type); payload["connections"].push_back(entry); } From 3acbbf31257f98255e9f4df7b737ca26dc4535a4 Mon Sep 17 00:00:00 2001 From: Jesper Arvidsson Date: Fri, 30 Jan 2026 14:11:18 +0100 Subject: [PATCH 7/7] a --- soh/soh/Network/Sail/Sail.cpp | 60 +++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/soh/soh/Network/Sail/Sail.cpp b/soh/soh/Network/Sail/Sail.cpp index 94d2310c7d5..4b9737fee20 100644 --- a/soh/soh/Network/Sail/Sail.cpp +++ b/soh/soh/Network/Sail/Sail.cpp @@ -43,6 +43,24 @@ static void Sail_GetEntranceSceneRoomSpawn(const EntranceData* data, int32_t* sc } } +static nlohmann::json Sail_BuildEntranceScenes(const EntranceData* data) { + nlohmann::json scenes = nlohmann::json::array(); + if (data == nullptr || data->scenes.empty()) { + return scenes; + } + + for (const auto& info : data->scenes) { + nlohmann::json entry; + entry["scene"] = info.scene; + entry["sceneName"] = SohUtils::GetSceneName(info.scene); + entry["spawn"] = info.spawn; + entry["room"] = (info.scene == SCENE_THIEVES_HIDEOUT && info.spawn >= 0) ? info.spawn : -1; + scenes.push_back(entry); + } + + return scenes; +} + static bool Sail_ShouldSkipEntrance(const EntranceData* original, const EntranceData* overrideData, s16 index, bool hideReverse, bool discoveredOnly) { if (original == nullptr || overrideData == nullptr) { @@ -100,24 +118,17 @@ static void Sail_SendCurrentScene(Sail* sail) { const EntranceInfo entranceInfo = gEntranceTable[entranceTableIndex]; const int32_t roomNum = gPlayState ? static_cast(gPlayState->roomCtx.curRoom.num) : -1; - std::string sceneName; const s16 lastEntranceIndex = GetLastEntranceOverride(); - if (lastEntranceIndex >= 0) { - const s16 nextEntranceIndex = Entrance_PeekNextIndexOverride(lastEntranceIndex); - const EntranceData* overrideData = GetEntranceData(nextEntranceIndex); - if (overrideData != nullptr) { - sceneName = overrideData->destination; - } - } + const s16 lastOverrideEntrance = + lastEntranceIndex >= 0 ? Entrance_PeekNextIndexOverride(lastEntranceIndex) : -1; nlohmann::json currentScenePayload; currentScenePayload["type"] = "current_scene"; currentScenePayload["sceneNum"] = static_cast(entranceInfo.scene); currentScenePayload["spawn"] = static_cast(entranceInfo.spawn); currentScenePayload["room"] = roomNum; - if (!sceneName.empty()) { - currentScenePayload["sceneName"] = sceneName; - } + currentScenePayload["lastEntranceIndex"] = static_cast(entranceIndex); + currentScenePayload["lastOverrideEntrance"] = static_cast(lastOverrideEntrance); sail->SendJsonToRemote(currentScenePayload); } @@ -158,19 +169,28 @@ static void Sail_SendEntranceMap(Sail* sail) { int32_t fromScene = -1; int32_t fromRoom = -1; + int32_t fromSpawn = -1; int32_t toScene = -1; + int32_t toRoom = -1; int32_t toSpawn = -1; - Sail_GetEntranceSceneRoomSpawn(original, &fromScene, &fromRoom, nullptr); - Sail_GetEntranceSceneRoomSpawn(overrideData, &toScene, nullptr, &toSpawn); + Sail_GetEntranceSceneRoomSpawn(original, &fromScene, &fromRoom, &fromSpawn); + Sail_GetEntranceSceneRoomSpawn(overrideData, &toScene, &toRoom, &toSpawn); nlohmann::json entry; entry["fromEntrance"] = static_cast(entrance.index); entry["toEntrance"] = static_cast(entrance.override); + entry["fromReverseEntrance"] = static_cast(original->reverseIndex); + entry["toReverseEntrance"] = static_cast(overrideData->reverseIndex); entry["fromScene"] = fromScene; + entry["fromSceneName"] = SohUtils::GetSceneName(fromScene); entry["fromRoom"] = fromRoom; + entry["fromSpawn"] = fromSpawn; entry["toScene"] = toScene; + entry["toSceneName"] = SohUtils::GetSceneName(toScene); + entry["toRoom"] = toRoom; entry["spawn"] = toSpawn; + entry["toSpawn"] = toSpawn; entry["fromName"] = original->source; entry["toName"] = overrideData->destination; entry["fromGroupId"] = static_cast(original->srcGroup); @@ -181,6 +201,20 @@ static void Sail_SendEntranceMap(Sail* sail) { entry["fromTypeName"] = EntranceTracker_GetTypeName(original->type); entry["toTypeId"] = static_cast(overrideData->type); entry["toTypeName"] = EntranceTracker_GetTypeName(overrideData->type); + entry["fromOneExit"] = static_cast(original->oneExit); + entry["toOneExit"] = static_cast(overrideData->oneExit); + entry["fromIsOneWay"] = original->type == ENTRANCE_TYPE_ONE_WAY; + entry["toIsOneWay"] = overrideData->type == ENTRANCE_TYPE_ONE_WAY; + entry["fromReverseIsNull"] = original->reverseIndex < 0; + entry["toReverseIsNull"] = overrideData->reverseIndex < 0; + entry["fromMetaTag"] = original->metaTag; + entry["toMetaTag"] = overrideData->metaTag; + entry["fromSource"] = original->source; + entry["fromDestination"] = original->destination; + entry["toSource"] = overrideData->source; + entry["toDestination"] = overrideData->destination; + entry["fromScenes"] = Sail_BuildEntranceScenes(original); + entry["toScenes"] = Sail_BuildEntranceScenes(overrideData); payload["connections"].push_back(entry); }