Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,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,
Expand Down Expand Up @@ -993,6 +993,33 @@ void EntranceTrackerWindow::DrawElement() {
doAreaScroll = true;
}

bool isDiscovered = EntranceTracker_IsEntranceDiscovered(entrance.index);

bool showOverride = (!destToggle ? showTo : showFrom) || isDiscovered;
bool showOriginal = (!destToggle ? showFrom : showTo) || isDiscovered;

const char* origSrcAreaName = spoilerEntranceGroupNames[original->srcGroup].c_str();
const char* origTypeName = groupTypeNames[original->type].c_str();
const char* rplcSrcAreaName = spoilerEntranceGroupNames[override->srcGroup].c_str();
const char* rplcTypeName = groupTypeNames[override->type].c_str();

const char* origSrcName = showOriginal ? original->source.c_str() : "";
const char* rplcDstName = showOverride ? override->destination.c_str() : "";

// Filter for entrances by group name, type, source/destination names, and meta tags
if ((!locationSearch.IsActive() && (showOriginal || showOverride || !collapseUndiscovered)) ||
((showOriginal &&
(locationSearch.PassFilter(origSrcName) || locationSearch.PassFilter(origSrcAreaName) ||
locationSearch.PassFilter(origTypeName) || locationSearch.PassFilter(original->metaTag.c_str()))) ||
(showOverride &&
(locationSearch.PassFilter(rplcDstName) || locationSearch.PassFilter(rplcSrcAreaName) ||
locationSearch.PassFilter(rplcTypeName) || locationSearch.PassFilter(override->metaTag.c_str()))))) {

// Detect if a scroll should happen and remember the scene for that scroll
if (!doAreaScroll &&
(lastSceneOrEntranceDetected != LinkIsInArea(original) && LinkIsInArea(original) != -1)) {
lastSceneOrEntranceDetected = LinkIsInArea(original);
doAreaScroll = true;
displayEntrances.push_back(entrance);
} else if (!isDiscovered) {
undiscovered++;
Expand All @@ -1013,6 +1040,7 @@ void EntranceTrackerWindow::DrawElement() {
const EntranceData* original = GetEntranceData(entrance.index);
const EntranceData* override = GetEntranceData(entrance.override);

bool isDiscovered = EntranceTracker_IsEntranceDiscovered(entrance.index);
bool isDiscovered = IsEntranceDiscovered(entrance.index);

bool showOverride = (!destToggle ? showTo : showFrom) || isDiscovered;
Expand Down Expand Up @@ -1079,6 +1107,23 @@ void EntranceTrackerWindow::InitElement() {
[](int32_t fileNum) { ClearEntranceTrackingData(); });
}

const char* EntranceTracker_GetGroupName(SpoilerEntranceGroup group) {
const size_t idx = static_cast<size_t>(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<size_t>(type);
const size_t count = sizeof(groupTypeNames) / sizeof(groupTypeNames[0]);
if (idx >= count) {
return "Unknown";
}
return groupTypeNames[idx].c_str();
}
void RegisterCheckTrackerWidgets() {
backgroundColorWidget = { .name = "Background Color##EntranceTracker",
.type = WidgetType::WIDGET_CVAR_COLOR_PICKER };
Expand Down
4 changes: 4 additions & 0 deletions soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ void InitEntranceTrackingData();
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);
void LoadFromPreset(nlohmann::json info);

class EntranceTrackerSettingsWindow final : public Ship::GuiWindow {
Expand Down
238 changes: 232 additions & 6 deletions soh/soh/Network/Sail/Sail.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,241 @@
#include <libultraship/bridge.h>
#include <libultraship/libultraship.h>
#include <nlohmann/json.hpp>
#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 <class DstType, class SrcType> bool IsType(const SrcType* src) {
return dynamic_cast<const DstType*>(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;
}
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 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) {
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_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_SendCurrentScene(Sail* sail) {
if (sail == nullptr || !sail->isConnected || !GameInteractor::IsSaveLoaded()) {
return;
}

const int32_t entranceIndex = static_cast<int32_t>(gSaveContext.entranceIndex);
const int32_t entranceTableIndex = entranceIndex + static_cast<int32_t>(gSaveContext.sceneSetupIndex);
const EntranceInfo entranceInfo = gEntranceTable[entranceTableIndex];
const int32_t roomNum = gPlayState ? static_cast<int32_t>(gPlayState->roomCtx.curRoom.num) : -1;

const s16 lastEntranceIndex = GetLastEntranceOverride();
const s16 lastOverrideEntrance =
lastEntranceIndex >= 0 ? Entrance_PeekNextIndexOverride(lastEntranceIndex) : -1;

nlohmann::json currentScenePayload;
currentScenePayload["type"] = "current_scene";
currentScenePayload["sceneNum"] = static_cast<int32_t>(entranceInfo.scene);
currentScenePayload["spawn"] = static_cast<int32_t>(entranceInfo.spawn);
currentScenePayload["room"] = roomNum;
currentScenePayload["lastEntranceIndex"] = static_cast<int32_t>(entranceIndex);
currentScenePayload["lastOverrideEntrance"] = static_cast<int32_t>(lastOverrideEntrance);

sail->SendJsonToRemote(currentScenePayload);
}

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 fromSpawn = -1;
int32_t toScene = -1;
int32_t toRoom = -1;
int32_t toSpawn = -1;

Sail_GetEntranceSceneRoomSpawn(original, &fromScene, &fromRoom, &fromSpawn);
Sail_GetEntranceSceneRoomSpawn(overrideData, &toScene, &toRoom, &toSpawn);

nlohmann::json entry;
entry["fromEntrance"] = static_cast<int32_t>(entrance.index);
entry["toEntrance"] = static_cast<int32_t>(entrance.override);
entry["fromReverseEntrance"] = static_cast<int32_t>(original->reverseIndex);
entry["toReverseEntrance"] = static_cast<int32_t>(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<int32_t>(original->srcGroup);
entry["fromGroupName"] = EntranceTracker_GetGroupName(original->srcGroup);
entry["toGroupId"] = static_cast<int32_t>(overrideData->dstGroup);
entry["toGroupName"] = EntranceTracker_GetGroupName(overrideData->dstGroup);
entry["fromTypeId"] = static_cast<int32_t>(original->type);
entry["fromTypeName"] = EntranceTracker_GetTypeName(original->type);
entry["toTypeId"] = static_cast<int32_t>(overrideData->type);
entry["toTypeName"] = EntranceTracker_GetTypeName(overrideData->type);
entry["fromOneExit"] = static_cast<int32_t>(original->oneExit);
entry["toOneExit"] = static_cast<int32_t>(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);
}

sail->SendJsonToRemote(payload);
}

void Sail::Enable() {
Network::Enable(CVarGetString(CVAR_REMOTE_SAIL("Host"), "127.0.0.1"),
CVarGetInteger(CVAR_REMOTE_SAIL("Port"), 43384));
}

void Sail::OnConnected() {
RegisterHooks();
if (GameInteractor::IsSaveLoaded()) {
Sail_SendSeedInfo(this);
Sail_SendCurrentScene(this);
Sail_SendEntranceMap(this);
sPendingInitialSync = false;
} else {
sPendingInitialSync = true;
}
}

void Sail::OnDisconnected() {
Expand Down Expand Up @@ -338,19 +559,24 @@ 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;
static_cast<void>(sceneNum);

SendJsonToRemote(payload);
// Treat transition end as the sync point: send the tracker state the website needs.
Sail_SendSeedInfo(this);
Sail_SendCurrentScene(this);
Sail_SendEntranceMap(this);
sPendingInitialSync = false;
});

COND_HOOK(OnLoadGame, isConnected, [&](int32_t fileNum) {
if (!isConnected || !GameInteractor::IsSaveLoaded())
return;

Sail_SendSeedInfo(this);
Sail_SendCurrentScene(this);
Sail_SendEntranceMap(this);
sPendingInitialSync = false;

nlohmann::json payload;
payload["id"] = std::rand();
payload["type"] = "hook";
Expand Down
Loading