mirror of
https://github.com/HarbourMasters/Shipwright
synced 2026-08-15 21:20:06 -04:00
523 lines
21 KiB
C++
523 lines
21 KiB
C++
#ifdef ENABLE_REMOTE_CONTROL
|
|
|
|
#include "Sail.h"
|
|
#include <libultraship/bridge.h>
|
|
#include <libultraship/libultraship.h>
|
|
#include <nlohmann/json.hpp>
|
|
#include "soh/OTRGlobals.h"
|
|
#include "soh/util.h"
|
|
|
|
template <class DstType, class SrcType> bool IsType(const SrcType* src) {
|
|
return dynamic_cast<const DstType*>(src) != nullptr;
|
|
}
|
|
|
|
void Sail::Enable() {
|
|
Network::Enable(CVarGetString(CVAR_REMOTE_SAIL("Host"), "127.0.0.1"),
|
|
CVarGetInteger(CVAR_REMOTE_SAIL("Port"), 43384));
|
|
}
|
|
|
|
void Sail::OnConnected() {
|
|
RegisterHooks();
|
|
}
|
|
|
|
void Sail::OnDisconnected() {
|
|
RegisterHooks();
|
|
}
|
|
|
|
void Sail::OnIncomingJson(nlohmann::json payload) {
|
|
SPDLOG_INFO("[Sail] Received payload: \n{}", payload.dump());
|
|
|
|
nlohmann::json responsePayload;
|
|
responsePayload["type"] = "result";
|
|
responsePayload["status"] = "failure";
|
|
|
|
try {
|
|
if (!payload.contains("id")) {
|
|
SPDLOG_ERROR("[Sail] Received payload without ID");
|
|
SendJsonToRemote(responsePayload);
|
|
return;
|
|
}
|
|
|
|
responsePayload["id"] = payload["id"];
|
|
|
|
if (!payload.contains("type")) {
|
|
SPDLOG_ERROR("[Sail] Received payload without type");
|
|
SendJsonToRemote(responsePayload);
|
|
return;
|
|
}
|
|
|
|
std::string payloadType = payload["type"].get<std::string>();
|
|
|
|
if (payloadType == "command") {
|
|
if (!payload.contains("command")) {
|
|
SPDLOG_ERROR("[Sail] Received command payload without command");
|
|
SendJsonToRemote(responsePayload);
|
|
return;
|
|
}
|
|
|
|
std::string command = payload["command"].get<std::string>();
|
|
std::reinterpret_pointer_cast<Ship::ConsoleWindow>(
|
|
Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))
|
|
->Dispatch(command);
|
|
responsePayload["status"] = "success";
|
|
SendJsonToRemote(responsePayload);
|
|
return;
|
|
} else if (payloadType == "effect") {
|
|
if (!payload.contains("effect") || !payload["effect"].contains("type")) {
|
|
SPDLOG_ERROR("[Sail] Received effect payload without effect type");
|
|
SendJsonToRemote(responsePayload);
|
|
return;
|
|
}
|
|
|
|
std::string effectType = payload["effect"]["type"].get<std::string>();
|
|
|
|
// Special case for "command" effect, so we can also run commands from the `simple_twitch_sail` script
|
|
if (effectType == "command") {
|
|
if (!payload["effect"].contains("command")) {
|
|
SPDLOG_ERROR("[Sail] Received command effect payload without command");
|
|
SendJsonToRemote(responsePayload);
|
|
return;
|
|
}
|
|
|
|
std::string command = payload["effect"]["command"].get<std::string>();
|
|
std::reinterpret_pointer_cast<Ship::ConsoleWindow>(
|
|
Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))
|
|
->Dispatch(command);
|
|
responsePayload["status"] = "success";
|
|
SendJsonToRemote(responsePayload);
|
|
return;
|
|
}
|
|
|
|
if (effectType != "apply" && effectType != "remove") {
|
|
SPDLOG_ERROR("[Sail] Received effect payload with unknown effect type: {}", effectType);
|
|
SendJsonToRemote(responsePayload);
|
|
return;
|
|
}
|
|
|
|
if (!GameInteractor::IsSaveLoaded()) {
|
|
responsePayload["status"] = "try_again";
|
|
SendJsonToRemote(responsePayload);
|
|
return;
|
|
}
|
|
|
|
GameInteractionEffectBase* giEffect = EffectFromJson(payload["effect"]);
|
|
if (giEffect) {
|
|
GameInteractionEffectQueryResult result;
|
|
if (effectType == "remove") {
|
|
if (IsType<RemovableGameInteractionEffect>(giEffect)) {
|
|
result = dynamic_cast<RemovableGameInteractionEffect*>(giEffect)->Remove();
|
|
} else {
|
|
result = GameInteractionEffectQueryResult::NotPossible;
|
|
}
|
|
} else {
|
|
result = giEffect->Apply();
|
|
}
|
|
|
|
if (result == GameInteractionEffectQueryResult::Possible) {
|
|
responsePayload["status"] = "success";
|
|
} else if (result == GameInteractionEffectQueryResult::TemporarilyNotPossible) {
|
|
responsePayload["status"] = "try_again";
|
|
}
|
|
SendJsonToRemote(responsePayload);
|
|
return;
|
|
}
|
|
} else {
|
|
SPDLOG_ERROR("[Sail] Unknown payload type: {}", payloadType);
|
|
SendJsonToRemote(responsePayload);
|
|
return;
|
|
}
|
|
|
|
// If we get here, something went wrong, send the failure response
|
|
SPDLOG_ERROR("[Sail] Failed to handle remote JSON, sending failure response");
|
|
SendJsonToRemote(responsePayload);
|
|
} catch (const std::exception& e) {
|
|
SPDLOG_ERROR("[Sail] Exception handling remote JSON: {}", e.what());
|
|
} catch (...) { SPDLOG_ERROR("[Sail] Unknown exception handling remote JSON"); }
|
|
}
|
|
|
|
GameInteractionEffectBase* Sail::EffectFromJson(nlohmann::json payload) {
|
|
if (!payload.contains("name")) {
|
|
return nullptr;
|
|
}
|
|
|
|
std::string name = payload["name"].get<std::string>();
|
|
|
|
if (name == "SetSceneFlag") {
|
|
auto effect = new GameInteractionEffect::SetSceneFlag();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
|
|
effect->parameters[2] = payload["parameters"][2].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else if (name == "UnsetSceneFlag") {
|
|
auto effect = new GameInteractionEffect::UnsetSceneFlag();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
|
|
effect->parameters[2] = payload["parameters"][2].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else if (name == "SetFlag") {
|
|
auto effect = new GameInteractionEffect::SetFlag();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else if (name == "UnsetFlag") {
|
|
auto effect = new GameInteractionEffect::UnsetFlag();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else if (name == "ModifyHeartContainers") {
|
|
auto effect = new GameInteractionEffect::ModifyHeartContainers();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else if (name == "FillMagic") {
|
|
return new GameInteractionEffect::FillMagic();
|
|
} else if (name == "EmptyMagic") {
|
|
return new GameInteractionEffect::EmptyMagic();
|
|
} else if (name == "ModifyRupees") {
|
|
auto effect = new GameInteractionEffect::ModifyRupees();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else if (name == "NoUI") {
|
|
return new GameInteractionEffect::NoUI();
|
|
} else if (name == "ModifyGravity") {
|
|
auto effect = new GameInteractionEffect::ModifyGravity();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else if (name == "ModifyHealth") {
|
|
auto effect = new GameInteractionEffect::ModifyHealth();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else if (name == "SetPlayerHealth") {
|
|
auto effect = new GameInteractionEffect::SetPlayerHealth();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else if (name == "FreezePlayer") {
|
|
return new GameInteractionEffect::FreezePlayer();
|
|
} else if (name == "BurnPlayer") {
|
|
return new GameInteractionEffect::BurnPlayer();
|
|
} else if (name == "ElectrocutePlayer") {
|
|
return new GameInteractionEffect::ElectrocutePlayer();
|
|
} else if (name == "KnockbackPlayer") {
|
|
auto effect = new GameInteractionEffect::KnockbackPlayer();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else if (name == "ModifyLinkSize") {
|
|
auto effect = new GameInteractionEffect::ModifyLinkSize();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else if (name == "InvisibleLink") {
|
|
return new GameInteractionEffect::InvisibleLink();
|
|
} else if (name == "PacifistMode") {
|
|
return new GameInteractionEffect::PacifistMode();
|
|
} else if (name == "DisableZTargeting") {
|
|
return new GameInteractionEffect::DisableZTargeting();
|
|
} else if (name == "WeatherRainstorm") {
|
|
return new GameInteractionEffect::WeatherRainstorm();
|
|
} else if (name == "ReverseControls") {
|
|
return new GameInteractionEffect::ReverseControls();
|
|
} else if (name == "ForceEquipBoots") {
|
|
auto effect = new GameInteractionEffect::ForceEquipBoots();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else if (name == "ModifyRunSpeedModifier") {
|
|
auto effect = new GameInteractionEffect::ModifyRunSpeedModifier();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else if (name == "OneHitKO") {
|
|
return new GameInteractionEffect::OneHitKO();
|
|
} else if (name == "ModifyDefenseModifier") {
|
|
auto effect = new GameInteractionEffect::ModifyDefenseModifier();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else if (name == "GiveOrTakeShield") {
|
|
auto effect = new GameInteractionEffect::GiveOrTakeShield();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else if (name == "TeleportPlayer") {
|
|
auto effect = new GameInteractionEffect::TeleportPlayer();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else if (name == "ClearAssignedButtons") {
|
|
auto effect = new GameInteractionEffect::ClearAssignedButtons();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else if (name == "SetTimeOfDay") {
|
|
auto effect = new GameInteractionEffect::SetTimeOfDay();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else if (name == "SetCollisionViewer") {
|
|
return new GameInteractionEffect::SetCollisionViewer();
|
|
} else if (name == "SetCosmeticsColor") {
|
|
auto effect = new GameInteractionEffect::SetCosmeticsColor();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else if (name == "RandomizeCosmetics") {
|
|
return new GameInteractionEffect::RandomizeCosmetics();
|
|
} else if (name == "PressButton") {
|
|
auto effect = new GameInteractionEffect::PressButton();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else if (name == "PressRandomButton") {
|
|
auto effect = new GameInteractionEffect::PressRandomButton();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else if (name == "AddOrTakeAmmo") {
|
|
auto effect = new GameInteractionEffect::AddOrTakeAmmo();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else if (name == "RandomBombFuseTimer") {
|
|
return new GameInteractionEffect::RandomBombFuseTimer();
|
|
} else if (name == "DisableLedgeGrabs") {
|
|
return new GameInteractionEffect::DisableLedgeGrabs();
|
|
} else if (name == "RandomWind") {
|
|
return new GameInteractionEffect::RandomWind();
|
|
} else if (name == "RandomBonks") {
|
|
return new GameInteractionEffect::RandomBonks();
|
|
} else if (name == "PlayerInvincibility") {
|
|
return new GameInteractionEffect::PlayerInvincibility();
|
|
} else if (name == "SlipperyFloor") {
|
|
return new GameInteractionEffect::SlipperyFloor();
|
|
} else if (name == "SpawnEnemyWithOffset") {
|
|
auto effect = new GameInteractionEffect::SpawnEnemyWithOffset();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else if (name == "SpawnActor") {
|
|
auto effect = new GameInteractionEffect::SpawnActor();
|
|
if (payload.contains("parameters")) {
|
|
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
|
|
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
|
|
}
|
|
return effect;
|
|
} else {
|
|
SPDLOG_INFO("[Sail] Unknown effect name: {}", name);
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
void Sail::RegisterHooks() {
|
|
static HOOK_ID onTransitionEndHook = 0;
|
|
static HOOK_ID onLoadGameHook = 0;
|
|
static HOOK_ID onExitGameHook = 0;
|
|
static HOOK_ID onItemReceiveHook = 0;
|
|
static HOOK_ID onEnemyDefeatHook = 0;
|
|
static HOOK_ID onActorInitHook = 0;
|
|
static HOOK_ID onFlagSetHook = 0;
|
|
static HOOK_ID onFlagUnsetHook = 0;
|
|
static HOOK_ID onSceneFlagSetHook = 0;
|
|
static HOOK_ID onSceneFlagUnsetHook = 0;
|
|
|
|
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnTransitionEnd>(onTransitionEndHook);
|
|
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnLoadGame>(onLoadGameHook);
|
|
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnExitGame>(onExitGameHook);
|
|
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnItemReceive>(onItemReceiveHook);
|
|
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnEnemyDefeat>(onEnemyDefeatHook);
|
|
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorInit>(onActorInitHook);
|
|
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnFlagSet>(onFlagSetHook);
|
|
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnFlagUnset>(onFlagUnsetHook);
|
|
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneFlagSet>(onSceneFlagSetHook);
|
|
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneFlagUnset>(onSceneFlagUnsetHook);
|
|
|
|
onTransitionEndHook = 0;
|
|
onLoadGameHook = 0;
|
|
onExitGameHook = 0;
|
|
onItemReceiveHook = 0;
|
|
onEnemyDefeatHook = 0;
|
|
onActorInitHook = 0;
|
|
onFlagSetHook = 0;
|
|
onFlagUnsetHook = 0;
|
|
onSceneFlagSetHook = 0;
|
|
onSceneFlagUnsetHook = 0;
|
|
|
|
if (!isConnected) {
|
|
return;
|
|
}
|
|
|
|
onTransitionEndHook =
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnTransitionEnd>([&](int32_t sceneNum) {
|
|
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);
|
|
});
|
|
onLoadGameHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnLoadGame>([&](int32_t fileNum) {
|
|
if (!isConnected || !GameInteractor::IsSaveLoaded())
|
|
return;
|
|
|
|
nlohmann::json payload;
|
|
payload["id"] = std::rand();
|
|
payload["type"] = "hook";
|
|
payload["hook"]["type"] = "OnLoadGame";
|
|
payload["hook"]["fileNum"] = fileNum;
|
|
|
|
SendJsonToRemote(payload);
|
|
});
|
|
onExitGameHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnExitGame>([&](int32_t fileNum) {
|
|
if (!isConnected || !GameInteractor::IsSaveLoaded())
|
|
return;
|
|
|
|
nlohmann::json payload;
|
|
payload["id"] = std::rand();
|
|
payload["type"] = "hook";
|
|
payload["hook"]["type"] = "OnExitGame";
|
|
payload["hook"]["fileNum"] = fileNum;
|
|
|
|
SendJsonToRemote(payload);
|
|
});
|
|
onItemReceiveHook =
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnItemReceive>([&](GetItemEntry itemEntry) {
|
|
if (!isConnected || !GameInteractor::IsSaveLoaded())
|
|
return;
|
|
|
|
nlohmann::json payload;
|
|
payload["id"] = std::rand();
|
|
payload["type"] = "hook";
|
|
payload["hook"]["type"] = "OnItemReceive";
|
|
payload["hook"]["tableId"] = itemEntry.tableId;
|
|
payload["hook"]["getItemId"] = itemEntry.getItemId;
|
|
|
|
SendJsonToRemote(payload);
|
|
});
|
|
onEnemyDefeatHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnEnemyDefeat>([&](void* refActor) {
|
|
if (!isConnected || !GameInteractor::IsSaveLoaded())
|
|
return;
|
|
|
|
Actor* actor = (Actor*)refActor;
|
|
nlohmann::json payload;
|
|
payload["id"] = std::rand();
|
|
payload["type"] = "hook";
|
|
payload["hook"]["type"] = "OnEnemyDefeat";
|
|
payload["hook"]["actorId"] = actor->id;
|
|
payload["hook"]["params"] = actor->params;
|
|
|
|
SendJsonToRemote(payload);
|
|
});
|
|
onActorInitHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorInit>([&](void* refActor) {
|
|
if (!isConnected || !GameInteractor::IsSaveLoaded())
|
|
return;
|
|
|
|
Actor* actor = (Actor*)refActor;
|
|
nlohmann::json payload;
|
|
payload["id"] = std::rand();
|
|
payload["type"] = "hook";
|
|
payload["hook"]["type"] = "OnActorInit";
|
|
payload["hook"]["actorId"] = actor->id;
|
|
payload["hook"]["params"] = actor->params;
|
|
|
|
SendJsonToRemote(payload);
|
|
});
|
|
onFlagSetHook =
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnFlagSet>([&](int16_t flagType, int16_t flag) {
|
|
if (!isConnected || !GameInteractor::IsSaveLoaded())
|
|
return;
|
|
|
|
nlohmann::json payload;
|
|
payload["id"] = std::rand();
|
|
payload["type"] = "hook";
|
|
payload["hook"]["type"] = "OnFlagSet";
|
|
payload["hook"]["flagType"] = flagType;
|
|
payload["hook"]["flag"] = flag;
|
|
|
|
SendJsonToRemote(payload);
|
|
});
|
|
onFlagUnsetHook =
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnFlagUnset>([&](int16_t flagType, int16_t flag) {
|
|
if (!isConnected || !GameInteractor::IsSaveLoaded())
|
|
return;
|
|
|
|
nlohmann::json payload;
|
|
payload["id"] = std::rand();
|
|
payload["type"] = "hook";
|
|
payload["hook"]["type"] = "OnFlagUnset";
|
|
payload["hook"]["flagType"] = flagType;
|
|
payload["hook"]["flag"] = flag;
|
|
|
|
SendJsonToRemote(payload);
|
|
});
|
|
onSceneFlagSetHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneFlagSet>(
|
|
[&](int16_t sceneNum, int16_t flagType, int16_t flag) {
|
|
if (!isConnected || !GameInteractor::IsSaveLoaded())
|
|
return;
|
|
|
|
nlohmann::json payload;
|
|
payload["id"] = std::rand();
|
|
payload["type"] = "hook";
|
|
payload["hook"]["type"] = "OnSceneFlagSet";
|
|
payload["hook"]["flagType"] = flagType;
|
|
payload["hook"]["flag"] = flag;
|
|
payload["hook"]["sceneNum"] = sceneNum;
|
|
|
|
SendJsonToRemote(payload);
|
|
});
|
|
onSceneFlagUnsetHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneFlagUnset>(
|
|
[&](int16_t sceneNum, int16_t flagType, int16_t flag) {
|
|
if (!isConnected || !GameInteractor::IsSaveLoaded())
|
|
return;
|
|
|
|
nlohmann::json payload;
|
|
payload["id"] = std::rand();
|
|
payload["type"] = "hook";
|
|
payload["hook"]["type"] = "OnSceneFlagUnset";
|
|
payload["hook"]["flagType"] = flagType;
|
|
payload["hook"]["flag"] = flag;
|
|
payload["hook"]["sceneNum"] = sceneNum;
|
|
|
|
SendJsonToRemote(payload);
|
|
});
|
|
}
|
|
|
|
#endif // ENABLE_REMOTE_CONTROL
|