Skip to content

Commit

Permalink
Anchor Build 12 (alpha)
Browse files Browse the repository at this point in the history
  • Loading branch information
garrettjoecox committed Sep 19, 2023
1 parent 5a97a25 commit 263a55b
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 30 deletions.
91 changes: 70 additions & 21 deletions soh/soh/Enhancements/game-interactor/GameInteractor_Anchor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <soh/Enhancements/randomizer/randomizerTypes.h>
#include <soh/Enhancements/randomizer/adult_trade_shuffle.h>
#include <soh/Enhancements/nametag.h>
#include <soh/Enhancements/presets.h>
#include <soh/util.h>
#include <nlohmann/json.hpp>

Expand Down Expand Up @@ -82,9 +83,10 @@ void from_json(const json& j, AnchorClient& client) {
j.contains("clientVersion") ? j.at("clientVersion").get_to(client.clientVersion) : client.clientVersion = "???";
j.contains("name") ? j.at("name").get_to(client.name) : client.name = "???";
j.contains("color") ? j.at("color").get_to(client.color) : client.color = {255, 255, 255};
j.contains("seed") ? j.at("seed").get_to(client.seed) : client.seed = "???";
j.contains("seed") ? j.at("seed").get_to(client.seed) : client.seed = 0;
j.contains("fileNum") ? j.at("fileNum").get_to(client.fileNum) : client.fileNum = 0xFF;
j.contains("gameComplete") ? j.at("gameComplete").get_to(client.gameComplete) : client.gameComplete = false;
j.contains("scene") ? j.at("scene").get_to(client.scene) : client.scene = SCENE_ID_MAX;
j.contains("sceneNum") ? j.at("sceneNum").get_to(client.sceneNum) : client.sceneNum = SCENE_ID_MAX;
j.contains("roomIndex") ? j.at("roomIndex").get_to(client.roomIndex) : client.roomIndex = 0;
j.contains("entranceIndex") ? j.at("entranceIndex").get_to(client.entranceIndex) : client.entranceIndex = 0;
j.contains("posRot") ? j.at("posRot").get_to(client.posRot) : client.posRot = { -9999, -9999, -9999, 0, 0, 0 };
Expand Down Expand Up @@ -141,7 +143,7 @@ void to_json(json& j, const SohStats& sohStats) {

void from_json(const json& j, SohStats& sohStats) {
j.at("locationsSkipped").get_to(sohStats.locationsSkipped);
j.contains("fileCreatedAt") ? j.at("fileCreatedAt").get_to(sohStats.fileCreatedAt) : gSaveContext.sohStats.fileCreatedAt;
j.at("fileCreatedAt").get_to(sohStats.fileCreatedAt);
}

void to_json(json& j, const SaveContext& saveContext) {
Expand Down Expand Up @@ -188,29 +190,33 @@ void from_json(const json& j, SaveContext& saveContext) {

std::map<uint32_t, AnchorClient> GameInteractorAnchor::AnchorClients = {};
std::vector<uint32_t> GameInteractorAnchor::FairyIndexToClientId = {};
std::string GameInteractorAnchor::clientVersion = "Anchor Build 11";
std::string GameInteractorAnchor::seed = "00000";
std::string GameInteractorAnchor::clientVersion = "Anchor Build 12 (alpha)";
std::vector<std::pair<uint16_t, int16_t>> receivedItems = {};
std::vector<AnchorMessage> anchorMessages = {};
uint32_t notificationId = 0;

void Anchor_DisplayMessage(AnchorMessage message = {}) {
message.id = notificationId++;
anchorMessages.push_back(message);
Audio_PlaySoundGeneral(NA_SE_SY_METRONOME, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
}

void Anchor_SendClientData() {
GameInteractorAnchor::seed = std::accumulate(std::begin(gSaveContext.seedIcons), std::end(gSaveContext.seedIcons), std::string(), [](std::string a, int b) {
return a + std::to_string(b);
});

nlohmann::json payload;
payload["data"]["name"] = CVarGetString("gRemote.AnchorName", "");
payload["data"]["color"] = CVarGetColor24("gRemote.AnchorColor", { 100, 255, 100 });
payload["data"]["clientVersion"] = GameInteractorAnchor::clientVersion;
payload["data"]["seed"] = GameInteractorAnchor::seed;
payload["data"]["seed"] = gSaveContext.finalSeed;
payload["data"]["fileNum"] = gSaveContext.fileNum;
payload["data"]["gameComplete"] = gSaveContext.sohStats.gameComplete;
payload["type"] = "UPDATE_CLIENT_DATA";

if (gPlayState != NULL) {
payload["data"]["sceneNum"] = gPlayState->sceneNum;
} else {
payload["data"]["sceneNum"] = SCENE_ID_MAX;
}

GameInteractorAnchor::Instance->TransmitJsonToRemote(payload);
}

Expand All @@ -226,7 +232,12 @@ void GameInteractorAnchor::Enable() {
});
GameInteractor::Instance->RegisterRemoteConnectedHandler([&]() {
Anchor_DisplayMessage({ .message = "Connected to Anchor" });
Anchor_SendClientData();
if (GameInteractor::IsSaveLoaded() || gSaveContext.fileNum == 0xFF) {
Anchor_SendClientData();
}
if (GameInteractor::IsSaveLoaded()) {
Anchor_RequestSaveStateFromRemote();
}
});
GameInteractor::Instance->RegisterRemoteDisconnectedHandler([&]() {
Anchor_DisplayMessage({ .message = "Disconnected from Anchor" });
Expand Down Expand Up @@ -336,7 +347,7 @@ void GameInteractorAnchor::HandleRemoteJson(nlohmann::json payload) {
uint32_t clientId = payload["clientId"].get<uint32_t>();

if (GameInteractorAnchor::AnchorClients.contains(clientId)) {
GameInteractorAnchor::AnchorClients[clientId].scene = payload["sceneNum"].get<int16_t>();
GameInteractorAnchor::AnchorClients[clientId].sceneNum = payload["sceneNum"].get<int16_t>();
GameInteractorAnchor::AnchorClients[clientId].roomIndex = payload.contains("roomIndex") ? payload.at("roomIndex").get<int16_t>() : 0;
GameInteractorAnchor::AnchorClients[clientId].entranceIndex = payload.contains("entranceIndex") ? payload.at("entranceIndex").get<int16_t>() : 0;
GameInteractorAnchor::AnchorClients[clientId].posRot = payload["posRot"].get<PosRot>();
Expand All @@ -360,6 +371,7 @@ void GameInteractorAnchor::HandleRemoteJson(nlohmann::json payload) {
client.name,
client.color,
client.seed,
client.fileNum,
client.gameComplete,
SCENE_ID_MAX,
0,
Expand Down Expand Up @@ -402,7 +414,9 @@ void GameInteractorAnchor::HandleRemoteJson(nlohmann::json payload) {
GameInteractorAnchor::AnchorClients[clientId].name = client.name;
GameInteractorAnchor::AnchorClients[clientId].color = client.color;
GameInteractorAnchor::AnchorClients[clientId].seed = client.seed;
GameInteractorAnchor::AnchorClients[clientId].fileNum = client.fileNum;
GameInteractorAnchor::AnchorClients[clientId].gameComplete = client.gameComplete;
GameInteractorAnchor::AnchorClients[clientId].sceneNum = client.sceneNum;
}
}
if (payload["type"] == "SKIP_LOCATION" && GameInteractor::IsSaveLoaded()) {
Expand Down Expand Up @@ -440,6 +454,9 @@ void GameInteractorAnchor::HandleRemoteJson(nlohmann::json payload) {
GameInteractor::Instance->isRemoteInteractorEnabled = false;
GameInteractorAnchor::Instance->isEnabled = false;
}
if (payload["type"] == "RESET") {
std::reinterpret_pointer_cast<LUS::ConsoleWindow>(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->Dispatch("reset");
}
}

void Anchor_PushSaveStateToRemote() {
Expand Down Expand Up @@ -511,6 +528,7 @@ void Anchor_ParseSaveStateFromRemote(nlohmann::json payload) {
gSaveContext.sohStats.locationsSkipped[i] = loadedData.sohStats.locationsSkipped[i];
}
}
gSaveContext.sohStats.fileCreatedAt = loadedData.sohStats.fileCreatedAt;

// Restore master sword state
u8 hasMasterSword = CHECK_OWNED_EQUIP(EQUIP_SWORD, 1);
Expand Down Expand Up @@ -544,7 +562,7 @@ uint8_t Anchor_GetClientScene(uint32_t fairyIndex) {
return SCENE_ID_MAX;
}

return GameInteractorAnchor::AnchorClients[clientId].scene;
return GameInteractorAnchor::AnchorClients[clientId].sceneNum;
}

PosRot Anchor_GetClientPosition(uint32_t fairyIndex) {
Expand Down Expand Up @@ -595,12 +613,29 @@ void Anchor_SpawnClientFairies() {
}
}

static uint32_t lastSceneNum = SCENE_ID_MAX;

void Anchor_RegisterHooks() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnLoadGame>([](int32_t fileNum) {
if (!GameInteractor::Instance->isRemoteInteractorConnected || gSaveContext.fileNum > 2) return;
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPlayerUpdate>([]() {
if (gPlayState == NULL || !GameInteractor::Instance->isRemoteInteractorConnected) return;

// Moved to a new scene
if (lastSceneNum != gPlayState->sceneNum) {
Anchor_SendClientData();
}

// Player loaded into file
if (lastSceneNum == SCENE_ID_MAX && GameInteractor::Instance->IsSaveLoaded()) {
Anchor_RequestSaveStateFromRemote();
}

lastSceneNum = gPlayState->sceneNum;
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPresentFileSelect>([]() {
lastSceneNum = SCENE_ID_MAX;
if (!GameInteractor::Instance->isRemoteInteractorConnected) return;

Anchor_SendClientData();
Anchor_RequestSaveStateFromRemote();
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnItemReceive>([](GetItemEntry itemEntry) {
if (itemEntry.modIndex == MOD_NONE && (itemEntry.itemId == ITEM_KEY_SMALL || itemEntry.itemId == ITEM_KEY_BOSS || itemEntry.itemId == ITEM_SWORD_MASTER)) {
Expand Down Expand Up @@ -700,7 +735,12 @@ void Anchor_RegisterHooks() {
lastPosition = currentPosition;
lastPlayerCount = currentPlayerCount;

GameInteractorAnchor::Instance->TransmitJsonToRemote(payload);
for (auto& [clientId, client] : GameInteractorAnchor::AnchorClients) {
if (client.sceneNum == gPlayState->sceneNum) {
payload["targetClientId"] = clientId;
GameInteractorAnchor::Instance->TransmitJsonToRemote(payload);
}
}
});
}

Expand Down Expand Up @@ -793,17 +833,26 @@ void AnchorPlayerLocationWindow::DrawElement() {
);

ImGui::TextColored(gSaveContext.sohStats.gameComplete ? GREEN : WHITE, "%s", CVarGetString("gRemote.AnchorName", ""));
if (gPlayState != NULL) {
if (gPlayState != NULL && gSaveContext.fileNum != 255) {
ImGui::SameLine();
ImGui::TextColored(ImVec4(0.5, 0.5, 0.5, 1), "%s", SohUtils::GetSceneName(gPlayState->sceneNum).c_str());
}
for (auto& [clientId, client] : GameInteractorAnchor::AnchorClients) {
ImGui::PushID(clientId);
ImGui::TextColored(client.gameComplete ? GREEN : WHITE, "%s", client.name.c_str());
if (client.scene < SCENE_ID_MAX) {
if (client.seed != gSaveContext.finalSeed && client.fileNum != 0xFF && gSaveContext.fileNum != 0xFF) {
ImGui::SameLine();
ImGui::TextColored(ImVec4(1, 0, 0, 1), ICON_FA_EXCLAMATION_TRIANGLE);
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text("Seed mismatch (%u != %u)", client.seed, gSaveContext.finalSeed);
ImGui::EndTooltip();
}
}
if (client.sceneNum < SCENE_ID_MAX && client.fileNum != 0xFF) {
ImGui::SameLine();
ImGui::TextColored(ImVec4(0.5, 0.5, 0.5, 1), "%s", SohUtils::GetSceneName(client.scene).c_str());
if (gPlayState != NULL && client.scene != SCENE_GROTTOS && client.scene != SCENE_ID_MAX) {
ImGui::TextColored(ImVec4(0.5, 0.5, 0.5, 1), "%s", SohUtils::GetSceneName(client.sceneNum).c_str());
if (gPlayState != NULL && client.sceneNum != SCENE_GROTTOS && client.sceneNum != SCENE_ID_MAX) {
ImGui::SameLine();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
if (ImGui::Button(ICON_FA_CHEVRON_RIGHT, ImVec2(ImGui::GetFontSize() * 1.0f, ImGui::GetFontSize() * 1.0f))) {
Expand Down
6 changes: 3 additions & 3 deletions soh/soh/Enhancements/game-interactor/GameInteractor_Anchor.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ typedef struct {
std::string clientVersion;
std::string name;
Color_RGB8 color;
std::string seed;
uint32_t seed;
uint8_t fileNum;
bool gameComplete;
uint8_t scene;
uint8_t sceneNum;
uint8_t roomIndex;
uint32_t entranceIndex;
PosRot posRot;
Expand All @@ -30,7 +31,6 @@ class GameInteractorAnchor {
static std::map<uint32_t, AnchorClient> AnchorClients;
static std::vector<uint32_t> FairyIndexToClientId;
static std::string clientVersion;
static std::string seed;

void Enable();
void Disable();
Expand Down
11 changes: 7 additions & 4 deletions soh/soh/Enhancements/game-interactor/GameInteractor_Remote.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,14 @@ void GameInteractor::DisableRemoteInteractor() {
}

void GameInteractor::TransmitDataToRemote(const char* payload) {
SDLNet_TCP_Send(remoteSocket, payload, strlen(payload) + 1);
SDLNet_TCP_Send(remoteSocket, payload, strlen(payload));
}

// Appends a newline character to the end of the json payload and sends it to the remote
void GameInteractor::TransmitJsonToRemote(nlohmann::json payload) {
TransmitDataToRemote(payload.dump().c_str());
// TODO: Migrate anchor server to use null terminators instead of newlines
std::string payloadString = payload.dump() + '\n';
TransmitDataToRemote(payloadString.c_str());
}

// MARK: - Private
Expand Down Expand Up @@ -134,15 +136,16 @@ void GameInteractor::ReceiveFromServer() {
receivedData.append(remoteDataReceived, len);

// Proess all complete packets
size_t delimiterPos = receivedData.find('\0');
// TODO: Migrate anchor server to use null terminators instead of newlines
size_t delimiterPos = receivedData.find('\n');
while (delimiterPos != std::string::npos) {
// Extract the complete packet until the delimiter
std::string packet = receivedData.substr(0, delimiterPos);
// Remove the packet (including the delimiter) from the received data
receivedData.erase(0, delimiterPos + 1);
HandleRemoteJson(packet);
// Find the next delimiter
delimiterPos = receivedData.find('\0');
delimiterPos = receivedData.find('\n');
}
}

Expand Down
4 changes: 2 additions & 2 deletions soh/soh/SohMenuBar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1590,12 +1590,12 @@ void DrawRemoteControlMenu() {
ImGui::EndTooltip();
}
}
if (client.seed != GameInteractorAnchor::seed) {
if (client.seed != gSaveContext.finalSeed && client.fileNum != 0xFF && gSaveContext.fileNum != 0xFF) {
ImGui::SameLine();
ImGui::TextColored(ImVec4(1, 0, 0, 1), ICON_FA_EXCLAMATION_TRIANGLE);
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text("Seed mismatch (%s)", client.seed.c_str());
ImGui::Text("Seed mismatch (%u != %u)", client.seed, gSaveContext.finalSeed);
ImGui::EndTooltip();
}
}
Expand Down

0 comments on commit 263a55b

Please sign in to comment.