mirror of
https://github.com/HarbourMasters/Shipwright
synced 2026-08-02 08:42:51 -04:00
1785a70f22
The main effect of this PR is completely decoupling OTRGlobals.h from any non-std header making it so that the "everything is being rebuilt because I changed a header that at some point gets included in OTRGlobals" problem doesn't happen anymore.
303 lines
14 KiB
C++
303 lines
14 KiB
C++
#include "soh/Network/Anchor/Anchor.h"
|
|
#include "soh/Network/Anchor/JsonConversions.hpp"
|
|
#include <nlohmann/json.hpp>
|
|
#include <libultraship/libultraship.h>
|
|
#include "soh/OTRGlobals.h"
|
|
#include "soh/Notification/Notification.h"
|
|
#include "soh/Enhancements/randomizer/randomizer.h"
|
|
|
|
extern "C" {
|
|
#include "variables.h"
|
|
extern PlayState* gPlayState;
|
|
}
|
|
|
|
/**
|
|
* UPDATE_TEAM_STATE
|
|
*
|
|
* Pushes the current save state to the server for other teammates to use.
|
|
*
|
|
* Fires when the server passes on a REQUEST_TEAM_STATE packet, or when this client saves the game
|
|
*
|
|
* When sending this packet we will assume that the team queue has been emptied for this client, so the queue
|
|
* stored in the server will be cleared.
|
|
*
|
|
* When receiving this packet, if there is items in the team queue, we will play them back in order.
|
|
*/
|
|
|
|
void Anchor::SendPacket_UpdateTeamState() {
|
|
if (!IsSaveLoaded() || !roomState.syncItemsAndFlags) {
|
|
return;
|
|
}
|
|
|
|
json payload;
|
|
payload["type"] = UPDATE_TEAM_STATE;
|
|
payload["targetTeamId"] = CVarGetString(CVAR_REMOTE_ANCHOR("TeamId"), "default");
|
|
|
|
// Assume the team queue has been emptied, so clear it
|
|
payload["queue"] = json::array();
|
|
|
|
payload["state"] = gSaveContext;
|
|
// manually update current scene flags
|
|
payload["state"]["sceneFlags"][gPlayState->sceneNum * 4] = gPlayState->actorCtx.flags.chest;
|
|
payload["state"]["sceneFlags"][gPlayState->sceneNum * 4 + 1] = gPlayState->actorCtx.flags.swch;
|
|
payload["state"]["sceneFlags"][gPlayState->sceneNum * 4 + 2] = gPlayState->actorCtx.flags.clear;
|
|
payload["state"]["sceneFlags"][gPlayState->sceneNum * 4 + 3] = gPlayState->actorCtx.flags.collect;
|
|
|
|
// The commented out code below is an attempt at sending the entire randomizer seed over, in hopes that a player
|
|
// doesn't have to generate the seed themselves Currently it doesn't work :)
|
|
if (IS_RANDO) {
|
|
auto randoContext = Rando::Context::GetInstance();
|
|
|
|
payload["state"]["rando"] = json::object();
|
|
payload["state"]["rando"]["itemLocations"] = json::array();
|
|
for (int i = 0; i < RC_MAX; i++) {
|
|
payload["state"]["rando"]["itemLocations"][i] = json::array();
|
|
// payload["state"]["rando"]["itemLocations"][i]["rgID"] =
|
|
// randoContext->GetItemLocation(i)->GetPlacedRandomizerGet();
|
|
payload["state"]["rando"]["itemLocations"][i][0] = randoContext->GetItemLocation(i)->GetCheckStatus();
|
|
payload["state"]["rando"]["itemLocations"][i][1] = (u8)randoContext->GetItemLocation(i)->GetIsSkipped();
|
|
|
|
// if (randoContext->GetItemLocation(i)->GetPlacedRandomizerGet() == RG_ICE_TRAP) {
|
|
// payload["state"]["rando"]["itemLocations"][i]["fakeRgID"] =
|
|
// randoContext->GetItemOverride(i).LooksLike();
|
|
// payload["state"]["rando"]["itemLocations"][i]["trickName"] = json::object();
|
|
// payload["state"]["rando"]["itemLocations"][i]["trickName"]["english"] =
|
|
// randoContext->GetItemOverride(i).GetTrickName().GetEnglish();
|
|
// payload["state"]["rando"]["itemLocations"][i]["trickName"]["french"] =
|
|
// randoContext->GetItemOverride(i).GetTrickName().GetFrench();
|
|
// }
|
|
// if (randoContext->GetItemLocation(i)->HasCustomPrice()) {
|
|
// payload["state"]["rando"]["itemLocations"][i]["price"] =
|
|
// randoContext->GetItemLocation(i)->GetPrice();
|
|
// }
|
|
}
|
|
|
|
// auto entranceCtx = randoContext->GetEntranceShuffler();
|
|
// for (int i = 0; i < ENTRANCE_OVERRIDES_MAX_COUNT; i++) {
|
|
// payload["state"]["rando"]["entrances"][i] = json::object();
|
|
// payload["state"]["rando"]["entrances"][i]["type"] = entranceCtx->entranceOverrides[i].type;
|
|
// payload["state"]["rando"]["entrances"][i]["index"] = entranceCtx->entranceOverrides[i].index;
|
|
// payload["state"]["rando"]["entrances"][i]["destination"] = entranceCtx->entranceOverrides[i].destination;
|
|
// payload["state"]["rando"]["entrances"][i]["override"] = entranceCtx->entranceOverrides[i].override;
|
|
// payload["state"]["rando"]["entrances"][i]["overrideDestination"] =
|
|
// entranceCtx->entranceOverrides[i].overrideDestination;
|
|
// }
|
|
|
|
// payload["state"]["rando"]["seed"] = json::array();
|
|
// for (int i = 0; i < randoContext->hashIconIndexes.size(); i++) {
|
|
// payload["state"]["rando"]["seed"][i] = randoContext->hashIconIndexes[i];
|
|
// }
|
|
// payload["state"]["rando"]["inputSeed"] = randoContext->GetSeedString();
|
|
// payload["state"]["rando"]["finalSeed"] = randoContext->GetSeed();
|
|
|
|
// payload["state"]["rando"]["randoSettings"] = json::array();
|
|
// for (int i = 0; i < RSK_MAX; i++) {
|
|
// payload["state"]["rando"]["randoSettings"][i] =
|
|
// randoContext->GetOption((RandomizerSettingKey(i))).GetSelectedOptionIndex();
|
|
// }
|
|
|
|
// payload["state"]["rando"]["masterQuestDungeonCount"] = randoContext->GetDungeons()->CountMQ();
|
|
// payload["state"]["rando"]["masterQuestDungeons"] = json::array();
|
|
// for (int i = 0; i < randoContext->GetDungeons()->GetDungeonListSize(); i++) {
|
|
// payload["state"]["rando"]["masterQuestDungeons"][i] = randoContext->GetDungeon(i)->IsMQ();
|
|
// }
|
|
// for (int i = 0; i < randoContext->GetTrials()->GetTrialListSize(); i++) {
|
|
// payload["state"]["rando"]["requiredTrials"][i] = randoContext->GetTrial(i)->IsRequired();
|
|
// }
|
|
}
|
|
|
|
SendJsonToRemote(payload);
|
|
}
|
|
|
|
void Anchor::SendPacket_ClearTeamState(std::string teamId) {
|
|
json payload;
|
|
payload["type"] = UPDATE_TEAM_STATE;
|
|
payload["targetTeamId"] = teamId;
|
|
payload["queue"] = json::array();
|
|
payload["state"] = json::object();
|
|
SendJsonToRemote(payload);
|
|
}
|
|
|
|
void Anchor::HandlePacket_UpdateTeamState(nlohmann::json payload) {
|
|
if (!roomState.syncItemsAndFlags) {
|
|
return;
|
|
}
|
|
|
|
isHandlingUpdateTeamState = true;
|
|
// This can happen in between file select and the game starting, so we can't use this check, but we need to ensure
|
|
// we be careful to wrap PlayState usage in this check
|
|
//
|
|
// if (!IsSaveLoaded()) {
|
|
// return;
|
|
// }
|
|
|
|
if (payload.contains("state")) {
|
|
SaveContext loadedData = payload["state"].get<SaveContext>();
|
|
|
|
gSaveContext.healthCapacity = loadedData.healthCapacity;
|
|
gSaveContext.magicLevel = loadedData.magicLevel;
|
|
gSaveContext.magicCapacity = gSaveContext.magic = loadedData.magicCapacity;
|
|
gSaveContext.isMagicAcquired = loadedData.isMagicAcquired;
|
|
gSaveContext.isDoubleMagicAcquired = loadedData.isDoubleMagicAcquired;
|
|
gSaveContext.isDoubleDefenseAcquired = loadedData.isDoubleDefenseAcquired;
|
|
gSaveContext.bgsFlag = loadedData.bgsFlag;
|
|
gSaveContext.swordHealth = loadedData.swordHealth;
|
|
gSaveContext.ship.quest = loadedData.ship.quest;
|
|
|
|
for (int i = 0; i < 124; i++) {
|
|
if (i == SCENE_WATER_TEMPLE) {
|
|
// Keep water temple water level flags
|
|
u32 mask = (1 << 0x1C) | (1 << 0x1D) | (1 << 0x1E);
|
|
loadedData.sceneFlags[i].swch =
|
|
(loadedData.sceneFlags[i].swch & ~mask) | (gSaveContext.sceneFlags[i].swch & mask);
|
|
}
|
|
|
|
if (i == SCENE_FOREST_TEMPLE) {
|
|
// Keep forest temple elevator flag
|
|
u32 mask = (1 << 0x1B);
|
|
loadedData.sceneFlags[i].swch =
|
|
(loadedData.sceneFlags[i].swch & ~mask) | (gSaveContext.sceneFlags[i].swch & mask);
|
|
}
|
|
|
|
gSaveContext.sceneFlags[i] = loadedData.sceneFlags[i];
|
|
if (IsSaveLoaded() && gPlayState->sceneNum == i) {
|
|
gPlayState->actorCtx.flags.chest = loadedData.sceneFlags[i].chest;
|
|
gPlayState->actorCtx.flags.swch = loadedData.sceneFlags[i].swch;
|
|
gPlayState->actorCtx.flags.clear = loadedData.sceneFlags[i].clear;
|
|
gPlayState->actorCtx.flags.collect = loadedData.sceneFlags[i].collect;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < 14; i++) {
|
|
gSaveContext.eventChkInf[i] = loadedData.eventChkInf[i];
|
|
}
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
gSaveContext.itemGetInf[i] = loadedData.itemGetInf[i];
|
|
}
|
|
|
|
// Skip last row of infTable, don't want to sync swordless flag
|
|
for (int i = 0; i < 29; i++) {
|
|
gSaveContext.infTable[i] = loadedData.infTable[i];
|
|
}
|
|
|
|
for (int i = 0; i < ceil((RAND_INF_MAX + 15) / 16); i++) {
|
|
gSaveContext.ship.randomizerInf[i] = loadedData.ship.randomizerInf[i];
|
|
}
|
|
|
|
for (int i = 0; i < 6; i++) {
|
|
gSaveContext.gsFlags[i] = loadedData.gsFlags[i];
|
|
}
|
|
|
|
gSaveContext.ship.stats.firstInput = loadedData.ship.stats.firstInput;
|
|
gSaveContext.ship.stats.fileCreatedAt = loadedData.ship.stats.fileCreatedAt;
|
|
|
|
// Restore master sword state
|
|
// Disabling this for now, not really sure I understand why I did this in the past
|
|
// u8 hasMasterSword = CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, 1);
|
|
// if (hasMasterSword) {
|
|
// loadedData.inventory.equipment |= 0x2;
|
|
// } else {
|
|
// loadedData.inventory.equipment &= ~0x2;
|
|
// }
|
|
|
|
// Restore bottle contents (unless it's ruto's letter)
|
|
for (int i = 0; i < 4; i++) {
|
|
if (gSaveContext.inventory.items[SLOT_BOTTLE_1 + i] != ITEM_NONE &&
|
|
gSaveContext.inventory.items[SLOT_BOTTLE_1 + i] != ITEM_LETTER_RUTO) {
|
|
loadedData.inventory.items[SLOT_BOTTLE_1 + i] = gSaveContext.inventory.items[SLOT_BOTTLE_1 + i];
|
|
}
|
|
}
|
|
|
|
// Restore ammo if it's non-zero, unless it's beans
|
|
for (int i = 0; i < ARRAY_COUNT(gSaveContext.inventory.ammo); i++) {
|
|
if (gSaveContext.inventory.ammo[i] != 0 && i != SLOT(ITEM_BEAN) && i != SLOT(ITEM_BEAN + 1)) {
|
|
loadedData.inventory.ammo[i] = gSaveContext.inventory.ammo[i];
|
|
}
|
|
}
|
|
|
|
gSaveContext.inventory = loadedData.inventory;
|
|
|
|
// The commented out code below is an attempt at sending the entire randomizer seed over, in hopes that a player
|
|
// doesn't have to generate the seed themselves Currently it doesn't work :)
|
|
if (IS_RANDO && payload["state"].contains("rando")) {
|
|
auto randoContext = Rando::Context::GetInstance();
|
|
|
|
for (int i = 0; i < RC_MAX; i++) {
|
|
auto itemLocation = payload["state"]["rando"].at("itemLocations").at(i);
|
|
// randoContext->GetItemLocation(i)->RefPlacedItem() =
|
|
// itemLocation.at("rgID").get<RandomizerGet>();
|
|
OTRGlobals::Instance->gRandoContext->GetItemLocation(i)->SetCheckStatus(
|
|
itemLocation.at(0).get<RandomizerCheckStatus>());
|
|
OTRGlobals::Instance->gRandoContext->GetItemLocation(i)->SetIsSkipped(itemLocation.at(1).get<u8>());
|
|
|
|
// if (itemLocation.contains("fakeRgID")) {
|
|
// randoContext->overrides.emplace(static_cast<RandomizerCheck>(i),
|
|
// Rando::ItemOverride(static_cast<RandomizerCheck>(i),
|
|
// itemLocation.at("fakeRgID").get<RandomizerGet>()));
|
|
// randoContext->GetItemOverride(i).GetTrickName().english =
|
|
// itemLocation.at("trickName").at("english").get<std::string>();
|
|
// randoContext->GetItemOverride(i).GetTrickName().french =
|
|
// itemLocation.at("trickName").at("french").get<std::string>();
|
|
// }
|
|
// if (itemLocation.contains("price")) {
|
|
// u16 price = itemLocation.at("price"].get<u16>();
|
|
// if (price > 0) {
|
|
// randoContext->GetItemLocation(i)->SetCustomPrice(price);
|
|
// }
|
|
// }
|
|
}
|
|
|
|
// auto entranceCtx = randoContext->GetEntranceShuffler();
|
|
// for (int i = 0; i < ENTRANCE_OVERRIDES_MAX_COUNT; i++) {
|
|
// entranceCtx->entranceOverrides[i].type =
|
|
// payload["state"]["rando"]["entrances"][i]["type"].get<u16>(); entranceCtx->entranceOverrides[i].index
|
|
// = payload["state"]["rando"]["entrances"][i]["index"].get<s16>();
|
|
// entranceCtx->entranceOverrides[i].destination =
|
|
// payload["state"]["rando"]["entrances"][i]["destination"].get<s16>();
|
|
// entranceCtx->entranceOverrides[i].override =
|
|
// payload["state"]["rando"]["entrances"][i]["override"].get<s16>();
|
|
// entranceCtx->entranceOverrides[i].overrideDestination =
|
|
// payload["state"]["rando"]["entrances"][i]["overrideDestination"].get<s16>();
|
|
// }
|
|
|
|
// for (int i = 0; i < randoContext->hashIconIndexes.size(); i++) {
|
|
// randoContext->hashIconIndexes[i] = payload["state"]["rando"]["seed"][i].get<u8>();
|
|
// }
|
|
// randoContext->GetSettings()->SetSeedString(payload["state"]["rando"]["inputSeed"].get<std::string>());
|
|
// randoContext->GetSettings()->SetSeed(payload["state"]["rando"]["finalSeed"].get<u32>());
|
|
|
|
// for (int i = 0; i < RSK_MAX; i++) {
|
|
// randoContext->GetOption(RandomizerSettingKey(i)).SetSelectedIndex(payload["state"]["rando"]["randoSettings"][i].get<u8>());
|
|
// }
|
|
|
|
// randoContext->GetDungeons()->ClearAllMQ();
|
|
// for (int i = 0; i < randoContext->GetDungeons()->GetDungeonListSize(); i++) {
|
|
// if (payload["state"]["rando"]["masterQuestDungeons"][i].get<bool>()) {
|
|
// randoContext->GetDungeon(i)->SetMQ();
|
|
// }
|
|
// }
|
|
|
|
// randoContext->GetTrials()->SkipAll();
|
|
// for (int i = 0; i < randoContext->GetTrials()->GetTrialListSize(); i++) {
|
|
// if (payload["state"]["rando"]["requiredTrials"][i].get<bool>()) {
|
|
// randoContext->GetTrial(i)->SetAsRequired();
|
|
// }
|
|
// }
|
|
}
|
|
|
|
Notification::Emit({
|
|
.message = "Save updated from team",
|
|
});
|
|
}
|
|
|
|
if (payload.contains("queue")) {
|
|
std::lock_guard<std::mutex> lock(incomingPacketQueueMutex);
|
|
for (auto& item : payload["queue"]) {
|
|
nlohmann::json itemPayload = nlohmann::json::parse(item.get<std::string>());
|
|
incomingPacketQueue.push(itemPayload);
|
|
}
|
|
}
|
|
isHandlingUpdateTeamState = false;
|
|
}
|