mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2024-10-31 23:55:06 -04:00
Refactor network usage and adapt Sail/CC to changes
This commit is contained in:
parent
29d7c54250
commit
df6763257b
@ -8,11 +8,6 @@
|
||||
#include "soh/Enhancements/item-tables/ItemTableTypes.h"
|
||||
#include <z64.h>
|
||||
|
||||
typedef enum {
|
||||
GI_SCHEME_SAIL,
|
||||
GI_SCHEME_CROWD_CONTROL,
|
||||
} GIScheme;
|
||||
|
||||
typedef enum {
|
||||
/* 0x00 */ GI_LINK_SIZE_NORMAL,
|
||||
/* 0x01 */ GI_LINK_SIZE_GIANT,
|
||||
@ -524,11 +519,6 @@ void GameInteractor_SetTriforceHuntCreditsWarpActive(uint8_t state);
|
||||
#pragma message("Compiling without <source_location> support, the Hook Debugger will not be avaliable")
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
#include <SDL2/SDL_net.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#endif
|
||||
|
||||
typedef uint32_t HOOK_ID;
|
||||
|
||||
enum HookType {
|
||||
@ -606,20 +596,6 @@ public:
|
||||
static void SetPacifistMode(bool active);
|
||||
};
|
||||
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
bool isRemoteInteractorEnabled;
|
||||
bool isRemoteInteractorConnected;
|
||||
|
||||
void EnableRemoteInteractor();
|
||||
void DisableRemoteInteractor();
|
||||
void RegisterRemoteDataHandler(std::function<void(char payload[512])> method);
|
||||
void RegisterRemoteJsonHandler(std::function<void(nlohmann::json)> method);
|
||||
void RegisterRemoteConnectedHandler(std::function<void()> method);
|
||||
void RegisterRemoteDisconnectedHandler(std::function<void()> method);
|
||||
void TransmitDataToRemote(const char* payload);
|
||||
void TransmitJsonToRemote(nlohmann::json packet);
|
||||
#endif
|
||||
|
||||
// Effects
|
||||
static GameInteractionEffectQueryResult CanApplyEffect(GameInteractionEffectBase* effect);
|
||||
static GameInteractionEffectQueryResult ApplyEffect(GameInteractionEffectBase* effect);
|
||||
@ -874,21 +850,6 @@ public:
|
||||
static GameInteractionEffectQueryResult SpawnEnemyWithOffset(uint32_t enemyId, int32_t enemyParams);
|
||||
static GameInteractionEffectQueryResult SpawnActor(uint32_t actorId, int32_t actorParams);
|
||||
};
|
||||
|
||||
private:
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
IPaddress remoteIP;
|
||||
TCPsocket remoteSocket;
|
||||
std::thread remoteThreadReceive;
|
||||
std::function<void(char payload[512])> remoteDataHandler;
|
||||
std::function<void(nlohmann::json)> remoteJsonHandler;
|
||||
std::function<void()> remoteConnectedHandler;
|
||||
std::function<void()> remoteDisconnectedHandler;
|
||||
|
||||
void ReceiveFromServer();
|
||||
void HandleRemoteData(char payload[512]);
|
||||
void HandleRemoteJson(std::string payload);
|
||||
#endif
|
||||
};
|
||||
|
||||
#undef GET_CURRENT_REGISTERING_INFO
|
||||
|
@ -1,183 +0,0 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "GameInteractor.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <unordered_map>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/OTRGlobals.h"
|
||||
|
||||
// MARK: - Remote
|
||||
|
||||
void GameInteractor::EnableRemoteInteractor() {
|
||||
if (isRemoteInteractorEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (SDLNet_ResolveHost(&remoteIP, CVarGetString(CVAR_REMOTE("IP"), "127.0.0.1"), CVarGetInteger(CVAR_REMOTE("Port"), 43384)) == -1) {
|
||||
SPDLOG_ERROR("[GameInteractor] SDLNet_ResolveHost: {}", SDLNet_GetError());
|
||||
}
|
||||
|
||||
isRemoteInteractorEnabled = true;
|
||||
|
||||
// First check if there is a thread running, if so, join it
|
||||
if (remoteThreadReceive.joinable()) {
|
||||
remoteThreadReceive.join();
|
||||
}
|
||||
|
||||
remoteThreadReceive = std::thread(&GameInteractor::ReceiveFromServer, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Raw data handler
|
||||
*
|
||||
* If you are developing a new remote, you should probably use the json methods instead. This
|
||||
* method requires you to parse the data and ensure packets are complete manually, we cannot
|
||||
* gaurentee that the data will be complete, or that it will only contain one packet with this
|
||||
*/
|
||||
void GameInteractor::RegisterRemoteDataHandler(std::function<void(char payload[512])> method) {
|
||||
remoteDataHandler = method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Json handler
|
||||
*
|
||||
* This method will be called when a complete json packet is received. All json packets must
|
||||
* be delimited by a null terminator (\0).
|
||||
*/
|
||||
void GameInteractor::RegisterRemoteJsonHandler(std::function<void(nlohmann::json)> method) {
|
||||
remoteJsonHandler = method;
|
||||
}
|
||||
|
||||
void GameInteractor::RegisterRemoteConnectedHandler(std::function<void()> method) {
|
||||
remoteConnectedHandler = method;
|
||||
}
|
||||
|
||||
void GameInteractor::RegisterRemoteDisconnectedHandler(std::function<void()> method) {
|
||||
remoteDisconnectedHandler = method;
|
||||
}
|
||||
|
||||
void GameInteractor::DisableRemoteInteractor() {
|
||||
if (!isRemoteInteractorEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
isRemoteInteractorEnabled = false;
|
||||
remoteThreadReceive.join();
|
||||
remoteDataHandler = nullptr;
|
||||
remoteJsonHandler = nullptr;
|
||||
remoteConnectedHandler = nullptr;
|
||||
remoteDisconnectedHandler = nullptr;
|
||||
}
|
||||
|
||||
void GameInteractor::TransmitDataToRemote(const char* payload) {
|
||||
SDLNet_TCP_Send(remoteSocket, payload, strlen(payload) + 1);
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
std::string receivedData;
|
||||
|
||||
void GameInteractor::ReceiveFromServer() {
|
||||
while (isRemoteInteractorEnabled) {
|
||||
while (!isRemoteInteractorConnected && isRemoteInteractorEnabled) {
|
||||
SPDLOG_TRACE("[GameInteractor] Attempting to make connection to server...");
|
||||
remoteSocket = SDLNet_TCP_Open(&remoteIP);
|
||||
|
||||
if (remoteSocket) {
|
||||
isRemoteInteractorConnected = true;
|
||||
SPDLOG_INFO("[GameInteractor] Connection to server established!");
|
||||
|
||||
if (remoteConnectedHandler) {
|
||||
remoteConnectedHandler();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SDLNet_SocketSet socketSet = SDLNet_AllocSocketSet(1);
|
||||
if (remoteSocket) {
|
||||
SDLNet_TCP_AddSocket(socketSet, remoteSocket);
|
||||
}
|
||||
|
||||
// Listen to socket messages
|
||||
while (isRemoteInteractorConnected && remoteSocket && isRemoteInteractorEnabled) {
|
||||
// we check first if socket has data, to not block in the TCP_Recv
|
||||
int socketsReady = SDLNet_CheckSockets(socketSet, 0);
|
||||
|
||||
if (socketsReady == -1) {
|
||||
SPDLOG_ERROR("[GameInteractor] SDLNet_CheckSockets: {}", SDLNet_GetError());
|
||||
break;
|
||||
}
|
||||
|
||||
if (socketsReady == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char remoteDataReceived[512];
|
||||
memset(remoteDataReceived, 0, sizeof(remoteDataReceived));
|
||||
int len = SDLNet_TCP_Recv(remoteSocket, &remoteDataReceived, sizeof(remoteDataReceived));
|
||||
if (!len || !remoteSocket || len == -1) {
|
||||
SPDLOG_ERROR("[GameInteractor] SDLNet_TCP_Recv: {}", SDLNet_GetError());
|
||||
break;
|
||||
}
|
||||
|
||||
HandleRemoteData(remoteDataReceived);
|
||||
|
||||
receivedData.append(remoteDataReceived, len);
|
||||
|
||||
// Proess all complete packets
|
||||
size_t delimiterPos = receivedData.find('\0');
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
if (isRemoteInteractorConnected) {
|
||||
SDLNet_TCP_Close(remoteSocket);
|
||||
isRemoteInteractorConnected = false;
|
||||
if (remoteDisconnectedHandler) {
|
||||
remoteDisconnectedHandler();
|
||||
}
|
||||
SPDLOG_INFO("[GameInteractor] Ending receiving thread...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameInteractor::HandleRemoteData(char payload[512]) {
|
||||
if (remoteDataHandler) {
|
||||
remoteDataHandler(payload);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void GameInteractor::HandleRemoteJson(std::string payload) {
|
||||
nlohmann::json jsonPayload;
|
||||
try {
|
||||
jsonPayload = nlohmann::json::parse(payload);
|
||||
} catch (const std::exception& e) {
|
||||
SPDLOG_ERROR("[GameInteractor] Failed to parse json: \n{}\n{}\n", payload, e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
if (remoteJsonHandler) {
|
||||
remoteJsonHandler(jsonPayload);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -8,6 +8,7 @@
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <spdlog/fmt/fmt.h>
|
||||
#include <regex>
|
||||
#include "soh/OTRGlobals.h"
|
||||
|
||||
extern "C" {
|
||||
#include <z64.h>
|
||||
@ -18,30 +19,18 @@ extern PlayState* gPlayState;
|
||||
}
|
||||
|
||||
void CrowdControl::Enable() {
|
||||
if (isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
isEnabled = true;
|
||||
GameInteractor::Instance->EnableRemoteInteractor();
|
||||
GameInteractor::Instance->RegisterRemoteJsonHandler([&](nlohmann::json payload) {
|
||||
HandleRemoteData(payload);
|
||||
});
|
||||
Network::Enable(CVarGetString(CVAR_REMOTE_CROWD_CONTROL("Host"), "127.0.0.1"), CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Port"), 43384));
|
||||
}
|
||||
|
||||
void CrowdControl::OnConnected() {
|
||||
ccThreadProcess = std::thread(&CrowdControl::ProcessActiveEffects, this);
|
||||
}
|
||||
|
||||
void CrowdControl::Disable() {
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
isEnabled = false;
|
||||
void CrowdControl::OnDisconnected() {
|
||||
ccThreadProcess.join();
|
||||
GameInteractor::Instance->DisableRemoteInteractor();
|
||||
}
|
||||
|
||||
void CrowdControl::HandleRemoteData(nlohmann::json payload) {
|
||||
void CrowdControl::OnIncomingJson(nlohmann::json payload) {
|
||||
Effect* incomingEffect = ParseMessage(payload);
|
||||
if (!incomingEffect) {
|
||||
return;
|
||||
@ -139,7 +128,7 @@ void CrowdControl::EmitMessage(uint32_t eventId, long timeRemaining, EffectResul
|
||||
|
||||
SPDLOG_INFO("[CrowdControl] Sending payload:\n{}", payload.dump());
|
||||
|
||||
GameInteractor::Instance->TransmitJsonToRemote(payload);
|
||||
SendJsonToRemote(payload);
|
||||
}
|
||||
|
||||
CrowdControl::EffectResult CrowdControl::ExecuteEffect(Effect* effect) {
|
||||
@ -185,6 +174,12 @@ CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) {
|
||||
|
||||
SPDLOG_INFO("[CrowdControl] Received payload:\n{}", dataReceived.dump());
|
||||
|
||||
if (!dataReceived.contains("code")) {
|
||||
// This seems to happen when the CC session ends
|
||||
SPDLOG_ERROR("[CrowdControl] Payload does not contain code, ignoring.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Effect* effect = new Effect();
|
||||
effect->lastExecutionResult = EffectResult::Initiate;
|
||||
effect->id = dataReceived["id"];
|
||||
@ -770,4 +765,68 @@ CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) {
|
||||
return effect;
|
||||
}
|
||||
|
||||
void CrowdControl::DrawMenu() {
|
||||
ImGui::PushID("CrowdControl");
|
||||
|
||||
static std::string host = CVarGetString(CVAR_REMOTE_CROWD_CONTROL("Host"), "127.0.0.1");
|
||||
static uint16_t port = CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Port"), 43384);
|
||||
bool isFormValid = !SohUtils::IsStringEmpty(host) && port > 1024 && port < 65535;
|
||||
|
||||
ImGui::SeparatorText("Crowd Control");
|
||||
UIWidgets::Tooltip(
|
||||
"Crowd Control is a platform that allows viewers to interact "
|
||||
"with a streamer's game in real time.\n"
|
||||
"\n"
|
||||
"Click the question mark to copy the link to the Crowd Control "
|
||||
"website to your clipboard."
|
||||
);
|
||||
if (ImGui::IsItemClicked()) {
|
||||
ImGui::SetClipboardText("https://crowdcontrol.live");
|
||||
}
|
||||
|
||||
ImGui::BeginDisabled(isEnabled);
|
||||
ImGui::Text("Host & Port");
|
||||
if (UIWidgets::InputString("##Host", &host)) {
|
||||
CVarSetString(CVAR_REMOTE_CROWD_CONTROL("Host"), host.c_str());
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::PushItemWidth(ImGui::GetFontSize() * 5);
|
||||
if (ImGui::InputScalar("##Port", ImGuiDataType_U16, &port)) {
|
||||
CVarSetInteger(CVAR_REMOTE_CROWD_CONTROL("Port"), port);
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::BeginDisabled(!isFormValid);
|
||||
const char* buttonLabel = isEnabled ? "Disable" : "Enable";
|
||||
if (ImGui::Button(buttonLabel, ImVec2(-1.0f, 0.0f))) {
|
||||
if (isEnabled) {
|
||||
CVarClear(CVAR_REMOTE_CROWD_CONTROL("Enabled"));
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
|
||||
Disable();
|
||||
} else {
|
||||
CVarSetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 1);
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
|
||||
Enable();
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (isEnabled) {
|
||||
ImGui::Spacing();
|
||||
if (isConnected) {
|
||||
ImGui::Text("Connected");
|
||||
} else {
|
||||
ImGui::Text("Connecting...");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -1,25 +1,16 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#ifndef _CROWDCONTROL_C
|
||||
#define _CROWDCONTROL_C
|
||||
#endif
|
||||
|
||||
#include "stdint.h"
|
||||
|
||||
#ifndef NETWORK_CROWD_CONTROL_H
|
||||
#define NETWORK_CROWD_CONTROL_H
|
||||
#ifdef __cplusplus
|
||||
#include <SDL2/SDL_net.h>
|
||||
#include <cstdint>
|
||||
|
||||
#include <thread>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
|
||||
#include "../game-interactor/GameInteractor.h"
|
||||
#include "soh/Network/Network.h"
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
|
||||
class CrowdControl {
|
||||
class CrowdControl : public Network {
|
||||
private:
|
||||
enum EffectResult {
|
||||
/// <summary>The effect executed successfully.</summary>
|
||||
@ -75,8 +66,6 @@ class CrowdControl {
|
||||
|
||||
std::thread ccThreadProcess;
|
||||
|
||||
bool isEnabled;
|
||||
|
||||
std::vector<Effect*> activeEffects;
|
||||
std::mutex activeEffectsMutex;
|
||||
|
||||
@ -92,7 +81,12 @@ class CrowdControl {
|
||||
public:
|
||||
static CrowdControl* Instance;
|
||||
void Enable();
|
||||
void Disable();
|
||||
void OnIncomingJson(nlohmann::json payload);
|
||||
void OnConnected();
|
||||
void OnDisconnected();
|
||||
void DrawMenu();
|
||||
};
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif // __cplusplus
|
||||
#endif // NETWORK_CROWD_CONTROL_H
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
||||
|
145
soh/soh/Network/Network.cpp
Normal file
145
soh/soh/Network/Network.cpp
Normal file
@ -0,0 +1,145 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "Network.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <libultraship/libultraship.h>
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
void Network::Enable(const char* host, uint16_t port) {
|
||||
if (isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (SDLNet_ResolveHost(&networkAddress, host, port) == -1) {
|
||||
SPDLOG_ERROR("[Network] SDLNet_ResolveHost: {}", SDLNet_GetError());
|
||||
}
|
||||
|
||||
isEnabled = true;
|
||||
|
||||
// First check if there is a thread running, if so, join it
|
||||
if (receiveThread.joinable()) {
|
||||
receiveThread.join();
|
||||
}
|
||||
|
||||
receiveThread = std::thread(&Network::ReceiveFromServer, this);
|
||||
}
|
||||
|
||||
void Network::Disable() {
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
isEnabled = false;
|
||||
receiveThread.join();
|
||||
}
|
||||
|
||||
void Network::OnIncomingData(char payload[512]) {
|
||||
}
|
||||
|
||||
void Network::OnIncomingJson(nlohmann::json payload) {
|
||||
}
|
||||
|
||||
void Network::OnConnected() {
|
||||
}
|
||||
|
||||
void Network::OnDisconnected() {
|
||||
}
|
||||
|
||||
void Network::SendDataToRemote(const char* payload) {
|
||||
SPDLOG_DEBUG("[Network] Sending data: {}", payload);
|
||||
SDLNet_TCP_Send(networkSocket, payload, strlen(payload) + 1);
|
||||
}
|
||||
|
||||
void Network::SendJsonToRemote(nlohmann::json payload) {
|
||||
SendDataToRemote(payload.dump().c_str());
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
void Network::ReceiveFromServer() {
|
||||
while (isEnabled) {
|
||||
while (!isConnected && isEnabled) {
|
||||
SPDLOG_TRACE("[Network] Attempting to make connection to server...");
|
||||
networkSocket = SDLNet_TCP_Open(&networkAddress);
|
||||
|
||||
if (networkSocket) {
|
||||
isConnected = true;
|
||||
SPDLOG_INFO("[Network] Connection to server established!");
|
||||
|
||||
OnConnected();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SDLNet_SocketSet socketSet = SDLNet_AllocSocketSet(1);
|
||||
if (networkSocket) {
|
||||
SDLNet_TCP_AddSocket(socketSet, networkSocket);
|
||||
}
|
||||
|
||||
// Listen to socket messages
|
||||
while (isConnected && networkSocket && isEnabled) {
|
||||
// we check first if socket has data, to not block in the TCP_Recv
|
||||
int socketsReady = SDLNet_CheckSockets(socketSet, 0);
|
||||
|
||||
if (socketsReady == -1) {
|
||||
SPDLOG_ERROR("[Network] SDLNet_CheckSockets: {}", SDLNet_GetError());
|
||||
break;
|
||||
}
|
||||
|
||||
if (socketsReady == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char remoteDataReceived[512];
|
||||
memset(remoteDataReceived, 0, sizeof(remoteDataReceived));
|
||||
int len = SDLNet_TCP_Recv(networkSocket, &remoteDataReceived, sizeof(remoteDataReceived));
|
||||
if (!len || !networkSocket || len == -1) {
|
||||
SPDLOG_ERROR("[Network] SDLNet_TCP_Recv: {}", SDLNet_GetError());
|
||||
break;
|
||||
}
|
||||
|
||||
HandleRemoteData(remoteDataReceived);
|
||||
|
||||
receivedData.append(remoteDataReceived, len);
|
||||
|
||||
// Proess all complete packets
|
||||
size_t delimiterPos = receivedData.find('\0');
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
if (isConnected) {
|
||||
SDLNet_TCP_Close(networkSocket);
|
||||
isConnected = false;
|
||||
OnDisconnected();
|
||||
SPDLOG_INFO("[Network] Ending receiving thread...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Network::HandleRemoteData(char payload[512]) {
|
||||
OnIncomingData(payload);
|
||||
}
|
||||
|
||||
void Network::HandleRemoteJson(std::string payload) {
|
||||
SPDLOG_DEBUG("[Network] Received json: {}", payload);
|
||||
nlohmann::json jsonPayload;
|
||||
try {
|
||||
jsonPayload = nlohmann::json::parse(payload);
|
||||
} catch (const std::exception& e) {
|
||||
SPDLOG_ERROR("[Network] Failed to parse json: \n{}\n{}\n", payload, e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
OnIncomingJson(jsonPayload);
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
50
soh/soh/Network/Network.h
Normal file
50
soh/soh/Network/Network.h
Normal file
@ -0,0 +1,50 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
#ifndef NETWORK_H
|
||||
#define NETWORK_H
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include <thread>
|
||||
#include <SDL2/SDL_net.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
class Network {
|
||||
private:
|
||||
IPaddress networkAddress;
|
||||
TCPsocket networkSocket;
|
||||
std::thread receiveThread;
|
||||
std::string receivedData;
|
||||
|
||||
void ReceiveFromServer();
|
||||
void HandleRemoteData(char payload[512]);
|
||||
void HandleRemoteJson(std::string payload);
|
||||
|
||||
public:
|
||||
bool isEnabled;
|
||||
bool isConnected;
|
||||
|
||||
void Enable(const char* host, uint16_t port);
|
||||
void Disable();
|
||||
/**
|
||||
* Raw data handler
|
||||
*
|
||||
* If you are developing a new remote, you should probably use the json methods instead. This
|
||||
* method requires you to parse the data and ensure packets are complete manually, we cannot
|
||||
* gaurentee that the data will be complete, or that it will only contain one packet with this
|
||||
*/
|
||||
virtual void OnIncomingData(char payload[512]);
|
||||
/**
|
||||
* Json handler
|
||||
*
|
||||
* This method will be called when a complete json packet is received. All json packets must
|
||||
* be delimited by a null terminator (\0).
|
||||
*/
|
||||
virtual void OnIncomingJson(nlohmann::json payload);
|
||||
virtual void OnConnected();
|
||||
virtual void OnDisconnected();
|
||||
void SendDataToRemote(const char* payload);
|
||||
virtual void SendJsonToRemote(nlohmann::json packet);
|
||||
};
|
||||
|
||||
#endif // __cplusplus
|
||||
#endif // NETWORK_H
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
@ -1,41 +1,31 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "GameInteractor_Sail.h"
|
||||
#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 GameInteractorSail::Enable() {
|
||||
if (isEnabled) {
|
||||
return;
|
||||
}
|
||||
void Sail::Enable() {
|
||||
Network::Enable(CVarGetString(CVAR_REMOTE_SAIL("Host"), "127.0.0.1"), CVarGetInteger(CVAR_REMOTE_SAIL("Port"), 43384));
|
||||
}
|
||||
|
||||
isEnabled = true;
|
||||
GameInteractor::Instance->EnableRemoteInteractor();
|
||||
GameInteractor::Instance->RegisterRemoteJsonHandler([&](nlohmann::json payload) {
|
||||
HandleRemoteJson(payload);
|
||||
});
|
||||
GameInteractor::Instance->RegisterRemoteConnectedHandler([&]() {
|
||||
void Sail::OnConnected() {
|
||||
RegisterHooks();
|
||||
});
|
||||
}
|
||||
|
||||
void GameInteractorSail::Disable() {
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
isEnabled = false;
|
||||
GameInteractor::Instance->DisableRemoteInteractor();
|
||||
void Sail::OnDisconnected() {
|
||||
RegisterHooks();
|
||||
}
|
||||
|
||||
void GameInteractorSail::HandleRemoteJson(nlohmann::json payload) {
|
||||
SPDLOG_INFO("[GameInteractorSail] Received payload: \n{}", payload.dump());
|
||||
void Sail::OnIncomingJson(nlohmann::json payload) {
|
||||
SPDLOG_INFO("[Sail] Received payload: \n{}", payload.dump());
|
||||
|
||||
nlohmann::json responsePayload;
|
||||
responsePayload["type"] = "result";
|
||||
@ -43,16 +33,16 @@ void GameInteractorSail::HandleRemoteJson(nlohmann::json payload) {
|
||||
|
||||
try {
|
||||
if (!payload.contains("id")) {
|
||||
SPDLOG_ERROR("[GameInteractorSail] Received payload without ID");
|
||||
GameInteractor::Instance->TransmitJsonToRemote(responsePayload);
|
||||
SPDLOG_ERROR("[Sail] Received payload without ID");
|
||||
SendJsonToRemote(responsePayload);
|
||||
return;
|
||||
}
|
||||
|
||||
responsePayload["id"] = payload["id"];
|
||||
|
||||
if (!payload.contains("type")) {
|
||||
SPDLOG_ERROR("[GameInteractorSail] Received payload without type");
|
||||
GameInteractor::Instance->TransmitJsonToRemote(responsePayload);
|
||||
SPDLOG_ERROR("[Sail] Received payload without type");
|
||||
SendJsonToRemote(responsePayload);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -60,20 +50,20 @@ void GameInteractorSail::HandleRemoteJson(nlohmann::json payload) {
|
||||
|
||||
if (payloadType == "command") {
|
||||
if (!payload.contains("command")) {
|
||||
SPDLOG_ERROR("[GameInteractorSail] Received command payload without command");
|
||||
GameInteractor::Instance->TransmitJsonToRemote(responsePayload);
|
||||
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";
|
||||
GameInteractor::Instance->TransmitJsonToRemote(responsePayload);
|
||||
SendJsonToRemote(responsePayload);
|
||||
return;
|
||||
} else if (payloadType == "effect") {
|
||||
if (!payload.contains("effect") || !payload["effect"].contains("type")) {
|
||||
SPDLOG_ERROR("[GameInteractorSail] Received effect payload without effect type");
|
||||
GameInteractor::Instance->TransmitJsonToRemote(responsePayload);
|
||||
SPDLOG_ERROR("[Sail] Received effect payload without effect type");
|
||||
SendJsonToRemote(responsePayload);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -82,27 +72,27 @@ void GameInteractorSail::HandleRemoteJson(nlohmann::json payload) {
|
||||
// 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("[GameInteractorSail] Received command effect payload without command");
|
||||
GameInteractor::Instance->TransmitJsonToRemote(responsePayload);
|
||||
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";
|
||||
GameInteractor::Instance->TransmitJsonToRemote(responsePayload);
|
||||
SendJsonToRemote(responsePayload);
|
||||
return;
|
||||
}
|
||||
|
||||
if (effectType != "apply" && effectType != "remove") {
|
||||
SPDLOG_ERROR("[GameInteractorSail] Received effect payload with unknown effect type: {}", effectType);
|
||||
GameInteractor::Instance->TransmitJsonToRemote(responsePayload);
|
||||
SPDLOG_ERROR("[Sail] Received effect payload with unknown effect type: {}", effectType);
|
||||
SendJsonToRemote(responsePayload);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GameInteractor::IsSaveLoaded()) {
|
||||
responsePayload["status"] = "try_again";
|
||||
GameInteractor::Instance->TransmitJsonToRemote(responsePayload);
|
||||
SendJsonToRemote(responsePayload);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -124,26 +114,26 @@ void GameInteractorSail::HandleRemoteJson(nlohmann::json payload) {
|
||||
} else if (result == GameInteractionEffectQueryResult::TemporarilyNotPossible) {
|
||||
responsePayload["status"] = "try_again";
|
||||
}
|
||||
GameInteractor::Instance->TransmitJsonToRemote(responsePayload);
|
||||
SendJsonToRemote(responsePayload);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
SPDLOG_ERROR("[GameInteractorSail] Unknown payload type: {}", payloadType);
|
||||
GameInteractor::Instance->TransmitJsonToRemote(responsePayload);
|
||||
SPDLOG_ERROR("[Sail] Unknown payload type: {}", payloadType);
|
||||
SendJsonToRemote(responsePayload);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we get here, something went wrong, send the failure response
|
||||
SPDLOG_ERROR("[GameInteractorSail] Failed to handle remote JSON, sending failure response");
|
||||
GameInteractor::Instance->TransmitJsonToRemote(responsePayload);
|
||||
SPDLOG_ERROR("[Sail] Failed to handle remote JSON, sending failure response");
|
||||
SendJsonToRemote(responsePayload);
|
||||
} catch (const std::exception& e) {
|
||||
SPDLOG_ERROR("[GameInteractorSail] Exception handling remote JSON: {}", e.what());
|
||||
SPDLOG_ERROR("[Sail] Exception handling remote JSON: {}", e.what());
|
||||
} catch (...) {
|
||||
SPDLOG_ERROR("[GameInteractorSail] Unknown exception handling remote JSON");
|
||||
SPDLOG_ERROR("[Sail] Unknown exception handling remote JSON");
|
||||
}
|
||||
}
|
||||
|
||||
GameInteractionEffectBase* GameInteractorSail::EffectFromJson(nlohmann::json payload) {
|
||||
GameInteractionEffectBase* Sail::EffectFromJson(nlohmann::json payload) {
|
||||
if (!payload.contains("name")) {
|
||||
return nullptr;
|
||||
}
|
||||
@ -331,22 +321,51 @@ GameInteractionEffectBase* GameInteractorSail::EffectFromJson(nlohmann::json pay
|
||||
} else if (name == "SlipperyFloor") {
|
||||
return new GameInteractionEffect::SlipperyFloor();
|
||||
} else {
|
||||
SPDLOG_INFO("[GameInteractorSail] Unknown effect name: {}", name);
|
||||
SPDLOG_INFO("[Sail] Unknown effect name: {}", name);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround until we have a way to unregister hooks
|
||||
static bool hasRegisteredHooks = false;
|
||||
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;
|
||||
|
||||
void GameInteractorSail::RegisterHooks() {
|
||||
if (hasRegisteredHooks) {
|
||||
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;
|
||||
}
|
||||
hasRegisteredHooks = true;
|
||||
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnTransitionEnd>([](int32_t sceneNum) {
|
||||
if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return;
|
||||
onTransitionEndHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnTransitionEnd>([&](int32_t sceneNum) {
|
||||
if (!isConnected || !GameInteractor::IsSaveLoaded()) return;
|
||||
|
||||
nlohmann::json payload;
|
||||
payload["id"] = std::rand();
|
||||
@ -354,10 +373,10 @@ void GameInteractorSail::RegisterHooks() {
|
||||
payload["hook"]["type"] = "OnTransitionEnd";
|
||||
payload["hook"]["sceneNum"] = sceneNum;
|
||||
|
||||
GameInteractor::Instance->TransmitJsonToRemote(payload);
|
||||
SendJsonToRemote(payload);
|
||||
});
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnLoadGame>([](int32_t fileNum) {
|
||||
if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return;
|
||||
onLoadGameHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnLoadGame>([&](int32_t fileNum) {
|
||||
if (!isConnected || !GameInteractor::IsSaveLoaded()) return;
|
||||
|
||||
nlohmann::json payload;
|
||||
payload["id"] = std::rand();
|
||||
@ -365,10 +384,10 @@ void GameInteractorSail::RegisterHooks() {
|
||||
payload["hook"]["type"] = "OnLoadGame";
|
||||
payload["hook"]["fileNum"] = fileNum;
|
||||
|
||||
GameInteractor::Instance->TransmitJsonToRemote(payload);
|
||||
SendJsonToRemote(payload);
|
||||
});
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnExitGame>([](int32_t fileNum) {
|
||||
if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return;
|
||||
onExitGameHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnExitGame>([&](int32_t fileNum) {
|
||||
if (!isConnected || !GameInteractor::IsSaveLoaded()) return;
|
||||
|
||||
nlohmann::json payload;
|
||||
payload["id"] = std::rand();
|
||||
@ -376,10 +395,10 @@ void GameInteractorSail::RegisterHooks() {
|
||||
payload["hook"]["type"] = "OnExitGame";
|
||||
payload["hook"]["fileNum"] = fileNum;
|
||||
|
||||
GameInteractor::Instance->TransmitJsonToRemote(payload);
|
||||
SendJsonToRemote(payload);
|
||||
});
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnItemReceive>([](GetItemEntry itemEntry) {
|
||||
if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return;
|
||||
onItemReceiveHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnItemReceive>([&](GetItemEntry itemEntry) {
|
||||
if (!isConnected || !GameInteractor::IsSaveLoaded()) return;
|
||||
|
||||
nlohmann::json payload;
|
||||
payload["id"] = std::rand();
|
||||
@ -388,10 +407,10 @@ void GameInteractorSail::RegisterHooks() {
|
||||
payload["hook"]["tableId"] = itemEntry.tableId;
|
||||
payload["hook"]["getItemId"] = itemEntry.getItemId;
|
||||
|
||||
GameInteractor::Instance->TransmitJsonToRemote(payload);
|
||||
SendJsonToRemote(payload);
|
||||
});
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnEnemyDefeat>([](void* refActor) {
|
||||
if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return;
|
||||
onEnemyDefeatHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnEnemyDefeat>([&](void* refActor) {
|
||||
if (!isConnected || !GameInteractor::IsSaveLoaded()) return;
|
||||
|
||||
Actor* actor = (Actor*)refActor;
|
||||
nlohmann::json payload;
|
||||
@ -401,10 +420,10 @@ void GameInteractorSail::RegisterHooks() {
|
||||
payload["hook"]["actorId"] = actor->id;
|
||||
payload["hook"]["params"] = actor->params;
|
||||
|
||||
GameInteractor::Instance->TransmitJsonToRemote(payload);
|
||||
SendJsonToRemote(payload);
|
||||
});
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorInit>([](void* refActor) {
|
||||
if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return;
|
||||
onActorInitHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorInit>([&](void* refActor) {
|
||||
if (!isConnected || !GameInteractor::IsSaveLoaded()) return;
|
||||
|
||||
Actor* actor = (Actor*)refActor;
|
||||
nlohmann::json payload;
|
||||
@ -414,10 +433,10 @@ void GameInteractorSail::RegisterHooks() {
|
||||
payload["hook"]["actorId"] = actor->id;
|
||||
payload["hook"]["params"] = actor->params;
|
||||
|
||||
GameInteractor::Instance->TransmitJsonToRemote(payload);
|
||||
SendJsonToRemote(payload);
|
||||
});
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnFlagSet>([](int16_t flagType, int16_t flag) {
|
||||
if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return;
|
||||
onFlagSetHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnFlagSet>([&](int16_t flagType, int16_t flag) {
|
||||
if (!isConnected || !GameInteractor::IsSaveLoaded()) return;
|
||||
|
||||
nlohmann::json payload;
|
||||
payload["id"] = std::rand();
|
||||
@ -426,10 +445,10 @@ void GameInteractorSail::RegisterHooks() {
|
||||
payload["hook"]["flagType"] = flagType;
|
||||
payload["hook"]["flag"] = flag;
|
||||
|
||||
GameInteractor::Instance->TransmitJsonToRemote(payload);
|
||||
SendJsonToRemote(payload);
|
||||
});
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnFlagUnset>([](int16_t flagType, int16_t flag) {
|
||||
if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return;
|
||||
onFlagUnsetHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnFlagUnset>([&](int16_t flagType, int16_t flag) {
|
||||
if (!isConnected || !GameInteractor::IsSaveLoaded()) return;
|
||||
|
||||
nlohmann::json payload;
|
||||
payload["id"] = std::rand();
|
||||
@ -438,10 +457,10 @@ void GameInteractorSail::RegisterHooks() {
|
||||
payload["hook"]["flagType"] = flagType;
|
||||
payload["hook"]["flag"] = flag;
|
||||
|
||||
GameInteractor::Instance->TransmitJsonToRemote(payload);
|
||||
SendJsonToRemote(payload);
|
||||
});
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneFlagSet>([](int16_t sceneNum, int16_t flagType, int16_t flag) {
|
||||
if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return;
|
||||
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();
|
||||
@ -451,10 +470,10 @@ void GameInteractorSail::RegisterHooks() {
|
||||
payload["hook"]["flag"] = flag;
|
||||
payload["hook"]["sceneNum"] = sceneNum;
|
||||
|
||||
GameInteractor::Instance->TransmitJsonToRemote(payload);
|
||||
SendJsonToRemote(payload);
|
||||
});
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneFlagUnset>([](int16_t sceneNum, int16_t flagType, int16_t flag) {
|
||||
if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return;
|
||||
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();
|
||||
@ -464,8 +483,76 @@ void GameInteractorSail::RegisterHooks() {
|
||||
payload["hook"]["flag"] = flag;
|
||||
payload["hook"]["sceneNum"] = sceneNum;
|
||||
|
||||
GameInteractor::Instance->TransmitJsonToRemote(payload);
|
||||
SendJsonToRemote(payload);
|
||||
});
|
||||
}
|
||||
|
||||
#endif
|
||||
void Sail::DrawMenu() {
|
||||
ImGui::PushID("Sail");
|
||||
|
||||
static std::string host = CVarGetString(CVAR_REMOTE_SAIL("Host"), "127.0.0.1");
|
||||
static uint16_t port = CVarGetInteger(CVAR_REMOTE_SAIL("Port"), 43384);
|
||||
bool isFormValid = !SohUtils::IsStringEmpty(host) && port > 1024 && port < 65535;
|
||||
|
||||
ImGui::SeparatorText("Sail");
|
||||
UIWidgets::Tooltip(
|
||||
"Sail is a networking protocol designed to facilitate remote "
|
||||
"control of the Ship of Harkinian client. It is intended to "
|
||||
"be utilized alongside a Sail server, for which we provide a "
|
||||
"few straightforward implementations on our GitHub. The current "
|
||||
"implementations available allow integration with Twitch chat "
|
||||
"and SAMMI Bot, feel free to contribute your own!\n"
|
||||
"\n"
|
||||
"Click the question mark to copy the link to the Sail Github "
|
||||
"page to your clipboard."
|
||||
);
|
||||
if (ImGui::IsItemClicked()) {
|
||||
ImGui::SetClipboardText("https://github.com/HarbourMasters/sail");
|
||||
}
|
||||
|
||||
ImGui::BeginDisabled(isEnabled);
|
||||
ImGui::Text("Host & Port");
|
||||
if (UIWidgets::InputString("##Host", &host)) {
|
||||
CVarSetString(CVAR_REMOTE_SAIL("Host"), host.c_str());
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::PushItemWidth(ImGui::GetFontSize() * 5);
|
||||
if (ImGui::InputScalar("##Port", ImGuiDataType_U16, &port)) {
|
||||
CVarSetInteger(CVAR_REMOTE_SAIL("Port"), port);
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::BeginDisabled(!isFormValid);
|
||||
const char* buttonLabel = isEnabled ? "Disable" : "Enable";
|
||||
if (ImGui::Button(buttonLabel, ImVec2(-1.0f, 0.0f))) {
|
||||
if (isEnabled) {
|
||||
CVarClear(CVAR_REMOTE_SAIL("Enabled"));
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
|
||||
Disable();
|
||||
} else {
|
||||
CVarSetInteger(CVAR_REMOTE_SAIL("Enabled"), 1);
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
|
||||
Enable();
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (isEnabled) {
|
||||
ImGui::Spacing();
|
||||
if (isConnected) {
|
||||
ImGui::Text("Connected");
|
||||
} else {
|
||||
ImGui::Text("Connecting...");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
||||
|
@ -1,29 +1,26 @@
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#ifndef NETWORK_SAIL_H
|
||||
#define NETWORK_SAIL_H
|
||||
#ifdef __cplusplus
|
||||
#include <SDL2/SDL_net.h>
|
||||
#include <cstdint>
|
||||
#include <thread>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
|
||||
#include "./GameInteractor.h"
|
||||
#include "soh/Network/Network.h"
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
|
||||
class GameInteractorSail {
|
||||
class Sail : public Network {
|
||||
private:
|
||||
bool isEnabled;
|
||||
|
||||
void HandleRemoteJson(nlohmann::json payload);
|
||||
GameInteractionEffectBase* EffectFromJson(nlohmann::json payload);
|
||||
void RegisterHooks();
|
||||
|
||||
public:
|
||||
static GameInteractorSail* Instance;
|
||||
static Sail* Instance;
|
||||
|
||||
void Enable();
|
||||
void Disable();
|
||||
void OnIncomingJson(nlohmann::json payload);
|
||||
void OnConnected();
|
||||
void OnDisconnected();
|
||||
void DrawMenu();
|
||||
};
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif // __cplusplus
|
||||
#endif // NETWORK_SAIL_H
|
||||
#endif // ENABLE_REMOTE_CONTROL
|
||||
|
@ -79,10 +79,10 @@
|
||||
#include "ActorDB.h"
|
||||
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
#include "Enhancements/crowd-control/CrowdControl.h"
|
||||
#include "Enhancements/game-interactor/GameInteractor_Sail.h"
|
||||
#include "soh/Network/CrowdControl/CrowdControl.h"
|
||||
#include "soh/Network/Sail/Sail.h"
|
||||
CrowdControl* CrowdControl::Instance;
|
||||
GameInteractorSail* GameInteractorSail::Instance;
|
||||
Sail* Sail::Instance;
|
||||
#endif
|
||||
|
||||
#include "Enhancements/mods.h"
|
||||
@ -1173,7 +1173,7 @@ extern "C" void InitOTR() {
|
||||
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
CrowdControl::Instance = new CrowdControl();
|
||||
GameInteractorSail::Instance = new GameInteractorSail();
|
||||
Sail::Instance = new Sail();
|
||||
#endif
|
||||
|
||||
OTRMessage_Init();
|
||||
@ -1203,15 +1203,11 @@ extern "C" void InitOTR() {
|
||||
srand(now);
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
SDLNet_Init();
|
||||
if (CVarGetInteger(CVAR_REMOTE("Enabled"), 0)) {
|
||||
switch (CVarGetInteger(CVAR_REMOTE("Scheme"), GI_SCHEME_SAIL)) {
|
||||
case GI_SCHEME_SAIL:
|
||||
GameInteractorSail::Instance->Enable();
|
||||
break;
|
||||
case GI_SCHEME_CROWD_CONTROL:
|
||||
if (CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 0)) {
|
||||
CrowdControl::Instance->Enable();
|
||||
break;
|
||||
}
|
||||
if (CVarGetInteger(CVAR_REMOTE_SAIL("Enabled"), 0)) {
|
||||
Sail::Instance->Enable();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@ -1224,15 +1220,11 @@ extern "C" void DeinitOTR() {
|
||||
SaveManager_ThreadPoolWait();
|
||||
OTRAudio_Exit();
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
if (CVarGetInteger(CVAR_REMOTE("Enabled"), 0)) {
|
||||
switch (CVarGetInteger(CVAR_REMOTE("Scheme"), GI_SCHEME_SAIL)) {
|
||||
case GI_SCHEME_SAIL:
|
||||
GameInteractorSail::Instance->Disable();
|
||||
break;
|
||||
case GI_SCHEME_CROWD_CONTROL:
|
||||
if (CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 0)) {
|
||||
CrowdControl::Instance->Disable();
|
||||
break;
|
||||
}
|
||||
if (CVarGetInteger(CVAR_REMOTE_SAIL("Enabled"), 0)) {
|
||||
Sail::Instance->Disable();
|
||||
}
|
||||
SDLNet_Quit();
|
||||
#endif
|
||||
|
@ -91,6 +91,8 @@ uint32_t IsGameMasterQuest();
|
||||
#define CVAR_DEVELOPER_TOOLS(var) CVAR_PREFIX_DEVELOPER_TOOLS "." var
|
||||
#define CVAR_GENERAL(var) CVAR_PREFIX_GENERAL "." var
|
||||
#define CVAR_REMOTE(var) CVAR_PREFIX_REMOTE "." var
|
||||
#define CVAR_REMOTE_CROWD_CONTROL(var) CVAR_REMOTE(".CrowdControl." var)
|
||||
#define CVAR_REMOTE_SAIL(var) CVAR_REMOTE(".Sail." var)
|
||||
|
||||
#ifndef __cplusplus
|
||||
void InitOTR(void);
|
||||
|
@ -33,11 +33,6 @@
|
||||
#include "soh/resource/type/Skeleton.h"
|
||||
#include "libultraship/libultraship.h"
|
||||
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
#include "Enhancements/crowd-control/CrowdControl.h"
|
||||
#include "Enhancements/game-interactor/GameInteractor_Sail.h"
|
||||
#endif
|
||||
|
||||
#include "Enhancements/game-interactor/GameInteractor.h"
|
||||
#include "Enhancements/cosmetics/authenticGfxPatches.h"
|
||||
#include "Enhancements/resolution-editor/ResolutionEditor.h"
|
||||
|
@ -17,8 +17,8 @@
|
||||
#include "soh/Enhancements/mods.h"
|
||||
#include "Enhancements/cosmetics/authenticGfxPatches.h"
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
#include "Enhancements/crowd-control/CrowdControl.h"
|
||||
#include "Enhancements/game-interactor/GameInteractor_Sail.h"
|
||||
#include "soh/Network/CrowdControl/CrowdControl.h"
|
||||
#include "soh/Network/Sail/Sail.h"
|
||||
#endif
|
||||
|
||||
|
||||
@ -1990,132 +1990,11 @@ void DrawDeveloperToolsMenu() {
|
||||
}
|
||||
}
|
||||
|
||||
bool isStringEmpty(std::string str) {
|
||||
// Remove spaces at the beginning of the string
|
||||
std::string::size_type start = str.find_first_not_of(' ');
|
||||
// Remove spaces at the end of the string
|
||||
std::string::size_type end = str.find_last_not_of(' ');
|
||||
|
||||
// Check if the string is empty after stripping spaces
|
||||
if (start == std::string::npos || end == std::string::npos)
|
||||
return true; // The string is empty
|
||||
else
|
||||
return false; // The string is not empty
|
||||
}
|
||||
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
void DrawRemoteControlMenu() {
|
||||
if (ImGui::BeginMenu("Network")) {
|
||||
static std::string ip = CVarGetString(CVAR_REMOTE("IP"), "127.0.0.1");
|
||||
static uint16_t port = CVarGetInteger(CVAR_REMOTE("Port"), 43384);
|
||||
bool isFormValid = !isStringEmpty(CVarGetString(CVAR_REMOTE("IP"), "127.0.0.1")) && port > 1024 && port < 65535;
|
||||
|
||||
const char* remoteOptions[2] = { "Sail", "Crowd Control"};
|
||||
|
||||
ImGui::BeginDisabled(GameInteractor::Instance->isRemoteInteractorEnabled);
|
||||
ImGui::Text("Remote Interaction Scheme");
|
||||
if (UIWidgets::EnhancementCombobox(CVAR_REMOTE("Scheme"), remoteOptions, GI_SCHEME_SAIL)) {
|
||||
auto scheme = CVarGetInteger(CVAR_REMOTE("Scheme"), GI_SCHEME_SAIL);
|
||||
switch (scheme) {
|
||||
case GI_SCHEME_SAIL:
|
||||
case GI_SCHEME_CROWD_CONTROL:
|
||||
CVarSetString(CVAR_REMOTE("IP"), "127.0.0.1");
|
||||
CVarSetInteger(CVAR_REMOTE("Port"), 43384);
|
||||
ip = "127.0.0.1";
|
||||
port = 43384;
|
||||
break;
|
||||
}
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
|
||||
}
|
||||
switch (CVarGetInteger(CVAR_REMOTE("Scheme"), GI_SCHEME_SAIL)) {
|
||||
case GI_SCHEME_SAIL:
|
||||
UIWidgets::InsertHelpHoverText(
|
||||
"Sail is a networking protocol designed to facilitate remote "
|
||||
"control of the Ship of Harkinian client. It is intended to "
|
||||
"be utilized alongside a Sail server, for which we provide a "
|
||||
"few straightforward implementations on our GitHub. The current "
|
||||
"implementations available allow integration with Twitch chat "
|
||||
"and SAMMI Bot, feel free to contribute your own!\n"
|
||||
"\n"
|
||||
"Click the question mark to copy the link to the Sail Github "
|
||||
"page to your clipboard."
|
||||
);
|
||||
if (ImGui::IsItemClicked()) {
|
||||
ImGui::SetClipboardText("https://github.com/HarbourMasters/sail");
|
||||
}
|
||||
break;
|
||||
case GI_SCHEME_CROWD_CONTROL:
|
||||
UIWidgets::InsertHelpHoverText(
|
||||
"Crowd Control is a platform that allows viewers to interact "
|
||||
"with a streamer's game in real time.\n"
|
||||
"\n"
|
||||
"Click the question mark to copy the link to the Crowd Control "
|
||||
"website to your clipboard."
|
||||
);
|
||||
if (ImGui::IsItemClicked()) {
|
||||
ImGui::SetClipboardText("https://crowdcontrol.live");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ImGui::Text("Remote IP & Port");
|
||||
if (ImGui::InputText("##gRemote.IP", (char*)ip.c_str(), ip.capacity() + 1)) {
|
||||
CVarSetString(CVAR_REMOTE("IP"), ip.c_str());
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::PushItemWidth(ImGui::GetFontSize() * 5);
|
||||
if (ImGui::InputScalar("##gRemote.Port", ImGuiDataType_U16, &port)) {
|
||||
CVarSetInteger(CVAR_REMOTE("Port"), port);
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
|
||||
}
|
||||
|
||||
ImGui::PopItemWidth();
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::BeginDisabled(!isFormValid);
|
||||
const char* buttonLabel = GameInteractor::Instance->isRemoteInteractorEnabled ? "Disable" : "Enable";
|
||||
if (ImGui::Button(buttonLabel, ImVec2(-1.0f, 0.0f))) {
|
||||
if (GameInteractor::Instance->isRemoteInteractorEnabled) {
|
||||
CVarClear(CVAR_REMOTE("Enabled"));
|
||||
CVarClear(CVAR_REMOTE("CrowdControl"));
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
|
||||
switch (CVarGetInteger(CVAR_REMOTE("Scheme"), GI_SCHEME_SAIL)) {
|
||||
case GI_SCHEME_SAIL:
|
||||
GameInteractorSail::Instance->Disable();
|
||||
break;
|
||||
case GI_SCHEME_CROWD_CONTROL:
|
||||
CrowdControl::Instance->Disable();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
CVarSetInteger(CVAR_REMOTE("Enabled"), 1);
|
||||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
|
||||
switch (CVarGetInteger(CVAR_REMOTE("Scheme"), GI_SCHEME_SAIL)) {
|
||||
case GI_SCHEME_SAIL:
|
||||
GameInteractorSail::Instance->Enable();
|
||||
break;
|
||||
case GI_SCHEME_CROWD_CONTROL:
|
||||
CrowdControl::Instance->Enable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (GameInteractor::Instance->isRemoteInteractorEnabled) {
|
||||
ImGui::Spacing();
|
||||
if (GameInteractor::Instance->isRemoteInteractorConnected) {
|
||||
ImGui::Text("Connected");
|
||||
} else {
|
||||
ImGui::Text("Connecting...");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0.0f, 0.0f));
|
||||
Sail::Instance->DrawMenu();
|
||||
CrowdControl::Instance->DrawMenu();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
@ -814,4 +814,18 @@ namespace UIWidgets {
|
||||
float sz = ImGui::GetFrameHeight();
|
||||
return StateButtonEx(str_id, label, ImVec2(sz, sz), ImGuiButtonFlags_None);
|
||||
}
|
||||
|
||||
// Reference: imgui-src/misc/cpp/imgui_stdlib.cpp
|
||||
int InputTextResizeCallback(ImGuiInputTextCallbackData* data) {
|
||||
std::string* value = (std::string*)data->UserData;
|
||||
if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) {
|
||||
value->resize(data->BufTextLen);
|
||||
data->Buf = (char*)value->c_str();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool InputString(const char* label, std::string* value) {
|
||||
return ImGui::InputText(label, (char*)value->c_str(), value->capacity() + 1, ImGuiInputTextFlags_CallbackResize, InputTextResizeCallback, value);
|
||||
}
|
||||
}
|
||||
|
@ -102,6 +102,7 @@ namespace UIWidgets {
|
||||
void DrawFlagArray16(const std::string& name, uint16_t& flags);
|
||||
void DrawFlagArray8(const std::string& name, uint8_t& flags);
|
||||
bool StateButton(const char* str_id, const char* label);
|
||||
bool InputString(const char* label, std::string* value);
|
||||
}
|
||||
|
||||
#endif /* UIWidgets_hpp */
|
||||
|
@ -390,3 +390,16 @@ size_t SohUtils::CopyStringToCharBuffer(char* buffer, const std::string& source,
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool SohUtils::IsStringEmpty(std::string str) {
|
||||
// Remove spaces at the beginning of the string
|
||||
std::string::size_type start = str.find_first_not_of(' ');
|
||||
// Remove spaces at the end of the string
|
||||
std::string::size_type end = str.find_last_not_of(' ');
|
||||
|
||||
// Check if the string is empty after stripping spaces
|
||||
if (start == std::string::npos || end == std::string::npos)
|
||||
return true; // The string is empty
|
||||
else
|
||||
return false; // The string is not empty
|
||||
}
|
||||
|
@ -20,4 +20,6 @@ namespace SohUtils {
|
||||
// Copies a string into a char buffer up to maxBufferSize characters. This does NOT insert a null terminator
|
||||
// on the end, as this is used for in-game messages which are not null-terminated.
|
||||
size_t CopyStringToCharBuffer(char* buffer, const std::string& source, size_t maxBufferSize);
|
||||
|
||||
bool IsStringEmpty(std::string str);
|
||||
} // namespace SohUtils
|
||||
|
@ -335,7 +335,7 @@ void EnItem00_SetupAction(EnItem00* this, EnItem00ActionFunc actionFunc) {
|
||||
void EnItem00_SetObjectDependency(EnItem00* this, PlayState* play, s16 objectIndex) {
|
||||
// Remove object dependency for Enemy Randomizer and Crowd Control to allow Like-likes to
|
||||
// drop equipment correctly in rooms where Like-likes normally don't spawn.
|
||||
if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) || (CVarGetInteger(CVAR_REMOTE("Scheme"), GI_SCHEME_SAIL) == GI_SCHEME_CROWD_CONTROL && CVarGetInteger(CVAR_REMOTE("Enabled"), 0))) {
|
||||
if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) || (CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 0))) {
|
||||
this->actor.objBankIndex = 0;
|
||||
} else {
|
||||
this->actor.objBankIndex = Object_GetIndex(&play->objectCtx, objectIndex);
|
||||
|
@ -413,7 +413,7 @@ BgImage* func_80096A74(PolygonType1* polygon1, PlayState* play) {
|
||||
|
||||
camera = GET_ACTIVE_CAM(play);
|
||||
camId = camera->camDataIdx;
|
||||
if (camId == -1 && (CVarGetInteger(CVAR_CHEAT("NoRestrictItems"), 0) || (CVarGetInteger(CVAR_REMOTE("Scheme"), GI_SCHEME_SAIL) == GI_SCHEME_CROWD_CONTROL && CVarGetInteger(CVAR_REMOTE("Enabled"), 0)))) {
|
||||
if (camId == -1 && (CVarGetInteger(CVAR_CHEAT("NoRestrictItems"), 0) || (CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 0)))) {
|
||||
// This prevents a crash when using items that change the
|
||||
// camera (such as din's fire), voiding out or dying on
|
||||
// scenes with prerendered backgrounds.
|
||||
|
@ -223,7 +223,7 @@ void BgMoriBigst_StalfosPairFight(BgMoriBigst* this, PlayState* play) {
|
||||
if ((this->dyna.actor.home.rot.z == 0 ||
|
||||
// Check if all enemies are defeated instead of the regular stalfos when enemy randomizer or crowd control is on.
|
||||
(Flags_GetTempClear(play, this->dyna.actor.room) && (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) ||
|
||||
((CVarGetInteger(CVAR_REMOTE("Scheme"), GI_SCHEME_SAIL) == GI_SCHEME_CROWD_CONTROL && CVarGetInteger(CVAR_REMOTE("Enabled"), 0)))))) && !Player_InCsMode(play)) {
|
||||
((CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 0)))))) && !Player_InCsMode(play)) {
|
||||
Flags_SetSwitch(play, (this->dyna.actor.params >> 8) & 0x3F);
|
||||
BgMoriBigst_SetupDone(this, play);
|
||||
}
|
||||
|
@ -106,9 +106,9 @@ void EnBlkobj_DarkLinkFight(EnBlkobj* this, PlayState* play) {
|
||||
// Check for if all enemies are defeated with enemy randomizer or crowd control on.
|
||||
uint8_t roomCleared =
|
||||
(!CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) &&
|
||||
!(CVarGetInteger(CVAR_REMOTE("Scheme"), GI_SCHEME_SAIL) == GI_SCHEME_CROWD_CONTROL && CVarGetInteger(CVAR_REMOTE("Enabled"), 0)) &&
|
||||
!(CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 0)) &&
|
||||
Actor_Find(&play->actorCtx, ACTOR_EN_TORCH2, ACTORCAT_BOSS) == NULL) ||
|
||||
((CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) || (CVarGetInteger(CVAR_REMOTE("Scheme"), GI_SCHEME_SAIL) == GI_SCHEME_CROWD_CONTROL && CVarGetInteger(CVAR_REMOTE("Enabled"), 0))) &&
|
||||
((CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) || (CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 0))) &&
|
||||
Flags_GetTempClear(play, this->dyna.actor.room));
|
||||
if (roomCleared) {
|
||||
Flags_SetClear(play, this->dyna.actor.room);
|
||||
|
@ -262,7 +262,7 @@ void EnClearTag_Init(Actor* thisx, PlayState* play) {
|
||||
|
||||
// Change Arwing to regular enemy instead of boss with enemy randomizer and crowd control.
|
||||
// This way Arwings will be considered for "clear enemy" rooms properly.
|
||||
if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) || (CVarGetInteger(CVAR_REMOTE("Scheme"), GI_SCHEME_SAIL) == GI_SCHEME_CROWD_CONTROL && CVarGetInteger(CVAR_REMOTE("Enabled"), 0))) {
|
||||
if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) || (CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 0))) {
|
||||
Actor_ChangeCategory(play, &play->actorCtx, thisx, ACTORCAT_ENEMY);
|
||||
}
|
||||
|
||||
|
@ -234,7 +234,7 @@ void func_80A74398(Actor* thisx, PlayState* play) {
|
||||
func_80A74714(this);
|
||||
|
||||
uint8_t enemyRandoCCActive = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) ||
|
||||
(CVarGetInteger(CVAR_REMOTE("Scheme"), GI_SCHEME_SAIL) == GI_SCHEME_CROWD_CONTROL && CVarGetInteger(CVAR_REMOTE("Enabled"), 0));
|
||||
(CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 0));
|
||||
|
||||
if (this->switchFlags != 0xFF) {
|
||||
// In vanilla gameplay, Iron Knuckles are despawned based on specific flags in specific scenarios.
|
||||
@ -665,7 +665,7 @@ void func_80A75A38(EnIk* this, PlayState* play) {
|
||||
// Don't set flag when Enemy Rando or CrowdControl are on.
|
||||
// Instead Iron Knuckles rely on the "clear room" flag.
|
||||
if (this->switchFlags != 0xFF && !CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) &&
|
||||
!(CVarGetInteger(CVAR_REMOTE("Scheme"), GI_SCHEME_SAIL) == GI_SCHEME_CROWD_CONTROL && CVarGetInteger(CVAR_REMOTE("Enabled"), 0))) {
|
||||
!(CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 0))) {
|
||||
Flags_SetSwitch(play, this->switchFlags);
|
||||
}
|
||||
Actor_Kill(&this->actor);
|
||||
@ -1468,7 +1468,7 @@ void EnIk_Init(Actor* thisx, PlayState* play) {
|
||||
}
|
||||
|
||||
// Immediately trigger Iron Knuckle for Enemy Rando and Crowd Control
|
||||
if ((CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) || (CVarGetInteger(CVAR_REMOTE("Scheme"), GI_SCHEME_SAIL) == GI_SCHEME_CROWD_CONTROL && CVarGetInteger(CVAR_REMOTE("Enabled"), 0)))
|
||||
if ((CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) || (CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 0)))
|
||||
&& (thisx->params == 2 || thisx->params == 3)) {
|
||||
this->skelAnime.playSpeed = 1.0f;
|
||||
}
|
||||
|
@ -254,7 +254,7 @@ void func_80AE2744(EnRd* this, PlayState* play) {
|
||||
// Add a height check to redeads/gibdos freeze when Enemy Randomizer is on.
|
||||
// Without the height check, redeads/gibdos can freeze the player from insane distances in
|
||||
// vertical rooms (like the first room in Deku Tree), making these rooms nearly unplayable.
|
||||
s8 enemyRandoCCActive = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) || (CVarGetInteger(CVAR_REMOTE("Scheme"), GI_SCHEME_SAIL) == GI_SCHEME_CROWD_CONTROL && CVarGetInteger(CVAR_REMOTE("Enabled"), 0));
|
||||
s8 enemyRandoCCActive = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) || (CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 0));
|
||||
if (!enemyRandoCCActive || (enemyRandoCCActive && this->actor.yDistToPlayer <= 100.0f && this->actor.yDistToPlayer >= -100.0f)) {
|
||||
if ((this->actor.params != 2) && (this->unk_305 == 0)) {
|
||||
func_80AE37BC(this);
|
||||
@ -668,7 +668,7 @@ void func_80AE3C98(EnRd* this, PlayState* play) {
|
||||
|
||||
if (SkelAnime_Update(&this->skelAnime)) {
|
||||
if (this->unk_30C == 0) {
|
||||
s8 enemyRandoCCActive = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) || (CVarGetInteger(CVAR_REMOTE("Scheme"), GI_SCHEME_SAIL) == GI_SCHEME_CROWD_CONTROL && CVarGetInteger(CVAR_REMOTE("Enabled"), 0));
|
||||
s8 enemyRandoCCActive = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) || (CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 0));
|
||||
// Don't set this flag in Enemy Rando as it can overlap with other objects using the same flag.
|
||||
if (!Flags_GetSwitch(play, this->unk_312 & 0x7F) && !enemyRandoCCActive) {
|
||||
Flags_SetSwitch(play, this->unk_312 & 0x7F);
|
||||
|
@ -128,7 +128,7 @@ void EnTorch2_Init(Actor* thisx, PlayState* play2) {
|
||||
|
||||
// Change Dark Link to regular enemy instead of boss with enemy randomizer and crowd control.
|
||||
// This way Dark Link will be considered for "clear enemy" rooms properly.
|
||||
if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) || (CVarGetInteger(CVAR_REMOTE("Scheme"), GI_SCHEME_SAIL) == GI_SCHEME_CROWD_CONTROL && CVarGetInteger(CVAR_REMOTE("Enabled"), 0))) {
|
||||
if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) || (CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 0))) {
|
||||
Actor_ChangeCategory(play, &play->actorCtx, thisx, ACTORCAT_ENEMY);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user