From f60f9a554e214e2c0b303bced1f9a8bbd3d8dcfa Mon Sep 17 00:00:00 2001 From: Garrett Cox Date: Wed, 5 Jul 2023 12:04:36 -0500 Subject: [PATCH] Remote GI Work Co-authored-by: David Chavez --- .github/workflows/generate-builds.yml | 8 +- .github/workflows/macports-deps.txt | 2 +- CMakeLists.txt | 6 - soh/CMakeLists.txt | 14 +- .../crowd-control/CrowdControl.cpp | 367 ++++++++---------- .../Enhancements/crowd-control/CrowdControl.h | 15 +- soh/soh/Enhancements/debugconsole.cpp | 64 +-- .../game-interactor/GameInteractionEffect.cpp | 4 +- .../game-interactor/GameInteractionEffect.h | 82 ++-- .../game-interactor/GameInteractor.cpp | 2 +- .../game-interactor/GameInteractor.h | 43 +- .../GameInteractor_BuiltIn.cpp | 290 ++++++++++++++ .../game-interactor/GameInteractor_BuiltIn.h | 69 ++++ .../game-interactor/GameInteractor_Remote.cpp | 184 +++++++++ soh/soh/OTRGlobals.cpp | 44 ++- soh/soh/SohGui.cpp | 3 +- soh/soh/SohMenuBar.cpp | 120 +++++- 17 files changed, 980 insertions(+), 337 deletions(-) create mode 100644 soh/soh/Enhancements/game-interactor/GameInteractor_BuiltIn.cpp create mode 100644 soh/soh/Enhancements/game-interactor/GameInteractor_BuiltIn.h create mode 100644 soh/soh/Enhancements/game-interactor/GameInteractor_Remote.cpp diff --git a/.github/workflows/generate-builds.yml b/.github/workflows/generate-builds.yml index ff473058217..209dc9cb99b 100644 --- a/.github/workflows/generate-builds.yml +++ b/.github/workflows/generate-builds.yml @@ -46,7 +46,7 @@ jobs: - name: Generate soh.otr run: | export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" - cmake --no-warn-unused-cli -H. -Bbuild-cmake -GNinja -DCMAKE_BUILD_TYPE:STRING=Release + cmake --no-warn-unused-cli -H. -Bbuild-cmake -GNinja -DCMAKE_BUILD_TYPE:STRING=Release -DBUILD_REMOTE_CONTROL=1 cmake --build build-cmake --config Release --target GenerateSohOtr - uses: actions/upload-artifact@v3 with: @@ -102,7 +102,7 @@ jobs: - name: Build SoH run: | export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" - cmake --no-warn-unused-cli -H. -Bbuild-cmake -GNinja -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" + cmake --no-warn-unused-cli -H. -Bbuild-cmake -GNinja -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DBUILD_REMOTE_CONTROL=1 cmake --build build-cmake --config Release --parallel 10 mv soh.otr build-cmake/soh (cd build-cmake && cpack) @@ -171,7 +171,7 @@ jobs: - name: Build SoH run: | export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" - cmake --no-warn-unused-cli -H. -Bbuild-cmake -GNinja -DCMAKE_BUILD_TYPE:STRING=Release + cmake --no-warn-unused-cli -H. -Bbuild-cmake -GNinja -DCMAKE_BUILD_TYPE:STRING=Release -DBUILD_REMOTE_CONTROL=1 cmake --build build-cmake --config Release -j3 (cd build-cmake && cpack -G External) @@ -290,7 +290,7 @@ jobs: VCPKG_ROOT: D:/a/vcpkg run: | set $env:PATH="$env:USERPROFILE/.cargo/bin;$env:PATH" - cmake -S . -B build-windows -G Ninja -DCMAKE_MAKE_PROGRAM=ninja -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + cmake -S . -B build-windows -G Ninja -DCMAKE_MAKE_PROGRAM=ninja -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DBUILD_REMOTE_CONTROL=1 cmake --build build-windows --config Release --parallel 10 mkdir soh-windows diff --git a/.github/workflows/macports-deps.txt b/.github/workflows/macports-deps.txt index 02c11cc68a7..6f5948557a3 100644 --- a/.github/workflows/macports-deps.txt +++ b/.github/workflows/macports-deps.txt @@ -1 +1 @@ -libsdl2 +universal libpng +universal glew +universal +libsdl2 +universal libsdl2_net +universal libpng +universal glew +universal diff --git a/CMakeLists.txt b/CMakeLists.txt index 855b9da3d61..4ec9d084112 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,12 +13,6 @@ set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT soh) add_compile_options($<$:/MP>) add_compile_options($<$:/utf-8>) -if (CMAKE_SYSTEM_NAME MATCHES "Windows|Linux") - if(NOT DEFINED BUILD_CROWD_CONTROL) - set(BUILD_CROWD_CONTROL ON) - endif() -endif() - if (CMAKE_SYSTEM_NAME STREQUAL "Windows") include(CMake/automate-vcpkg.cmake) diff --git a/soh/CMakeLists.txt b/soh/CMakeLists.txt index 3c12c41a976..b6ef2a71901 100644 --- a/soh/CMakeLists.txt +++ b/soh/CMakeLists.txt @@ -154,7 +154,7 @@ list(FILTER soh__Enhancements EXCLUDE REGEX "soh/Enhancements/gfx.*") # handle crowd control removals list(REMOVE_ITEM soh__Enhancements "soh/Enhancements/crowd-control/soh.cs") list(REMOVE_ITEM soh__Enhancements "soh/Enhancements/crowd-control/soh.ccpak") -if (!BUILD_CROWD_CONTROL) +if (!BUILD_REMOTE_CONTROL) list(FILTER soh__Enhancements EXCLUDE REGEX "soh/Enhancements/crowd-control/*") endif() @@ -354,7 +354,7 @@ endif() find_package(SDL2) set(SDL2-INCLUDE ${SDL2_INCLUDE_DIRS}) -if (BUILD_CROWD_CONTROL) +if (BUILD_REMOTE_CONTROL) find_package(SDL2_net) set(SDL2-NET-INCLUDE ${SDL_NET_INCLUDE_DIRS}) endif() @@ -408,9 +408,9 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows") "$<$:" "NDEBUG" ">" - "$<$:ENABLE_CROWD_CONTROL>" + "$<$:ENABLE_REMOTE_CONTROL>" "INCLUDE_GAME_PRINTF;" - "ENABLE_CROWD_CONTROL;" + "ENABLE_REMOTE_CONTROL;" "UNICODE;" "_UNICODE" STORMLIB_NO_AUTO_LINK @@ -455,7 +455,7 @@ elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU|Clang|AppleClang") "$<$:" "NDEBUG" ">" - "$<$:ENABLE_CROWD_CONTROL>" + "$<$:ENABLE_REMOTE_CONTROL>" "SPDLOG_ACTIVE_LEVEL=0;" "_CONSOLE;" "_CRT_SECURE_NO_WARNINGS;" @@ -689,7 +689,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows") "glu32;" "SDL2::SDL2;" "SDL2::SDL2main;" - "$<$:SDL2_net::SDL2_net-static>" + "$<$:SDL2_net::SDL2_net-static>" "glfw;" "winmm;" "imm32;" @@ -742,7 +742,7 @@ else() "ZAPDUtils;" "ZAPDLib;" SDL2::SDL2 - "$<$:SDL2_net::SDL2_net>" + "$<$:SDL2_net::SDL2_net>" ${CMAKE_DL_LIBS} Threads::Threads ) diff --git a/soh/soh/Enhancements/crowd-control/CrowdControl.cpp b/soh/soh/Enhancements/crowd-control/CrowdControl.cpp index a9f3b1f198e..dd6651a587f 100644 --- a/soh/soh/Enhancements/crowd-control/CrowdControl.cpp +++ b/soh/soh/Enhancements/crowd-control/CrowdControl.cpp @@ -1,4 +1,4 @@ -#ifdef ENABLE_CROWD_CONTROL +#ifdef ENABLE_REMOTE_CONTROL #include "CrowdControl.h" #include "CrowdControlTypes.h" @@ -17,25 +17,17 @@ extern "C" { extern PlayState* gPlayState; } -void CrowdControl::Init() { - SDLNet_Init(); -} - -void CrowdControl::Shutdown() { - SDLNet_Quit(); -} - void CrowdControl::Enable() { if (isEnabled) { return; } - if (SDLNet_ResolveHost(&ip, "127.0.0.1", 43384) == -1) { - SPDLOG_ERROR("[CrowdControl] SDLNet_ResolveHost: {}", SDLNet_GetError()); - } - isEnabled = true; - ccThreadReceive = std::thread(&CrowdControl::ListenToServer, this); + GameInteractor::Instance->EnableRemoteInteractor(); + GameInteractor::Instance->RegisterRemoteDataHandler([&](char payload[512]) { + HandleRemoteData(payload); + }); + ccThreadProcess = std::thread(&CrowdControl::ProcessActiveEffects, this); } @@ -45,87 +37,42 @@ void CrowdControl::Disable() { } isEnabled = false; - ccThreadReceive.join(); ccThreadProcess.join(); + GameInteractor::Instance->DisableRemoteInteractor(); } -void CrowdControl::ListenToServer() { - while (isEnabled) { - while (!connected && isEnabled) { - SPDLOG_TRACE("[CrowdControl] Attempting to make connection to server..."); - tcpsock = SDLNet_TCP_Open(&ip); +void CrowdControl::HandleRemoteData(char payload[512]) { + Effect* incomingEffect = ParseMessage(payload); + if (!incomingEffect) { + return; + } - if (tcpsock) { - connected = true; - SPDLOG_TRACE("[CrowdControl] Connection to server established!"); + // If effect is not a timed effect, execute and return result. + if (!incomingEffect->timeRemaining) { + EffectResult result = CrowdControl::ExecuteEffect(incomingEffect); + EmitMessage(incomingEffect->id, incomingEffect->timeRemaining, result); + } else { + // If another timed effect is already active that conflicts with the incoming effect. + bool isConflictingEffectActive = false; + for (Effect* effect : activeEffects) { + if (effect != incomingEffect && effect->category == incomingEffect->category && effect->id < incomingEffect->id) { + isConflictingEffectActive = true; + EmitMessage(incomingEffect->id, incomingEffect->timeRemaining, EffectResult::Retry); break; } } - SDLNet_SocketSet socketSet = SDLNet_AllocSocketSet(1); - if (tcpsock) { - SDLNet_TCP_AddSocket(socketSet, tcpsock); - } - - // Listen to socket messages - while (connected && tcpsock && 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("[CrowdControl] SDLNet_CheckSockets: {}", SDLNet_GetError()); - break; - } - - if (socketsReady == 0) { - continue; + if (!isConflictingEffectActive) { + // Check if effect can be applied, if it can't, let CC know. + EffectResult result = CrowdControl::CanApplyEffect(incomingEffect); + if (result == EffectResult::Retry || result == EffectResult::Failure) { + EmitMessage(incomingEffect->id, incomingEffect->timeRemaining, result); + return; } - int len = SDLNet_TCP_Recv(tcpsock, &received, sizeof(received)); - if (!len || !tcpsock || len == -1) { - SPDLOG_ERROR("[CrowdControl] SDLNet_TCP_Recv: {}", SDLNet_GetError()); - break; - } - - Effect* incomingEffect = ParseMessage(received); - if (!incomingEffect) { - continue; - } - - // If effect is not a timed effect, execute and return result. - if (!incomingEffect->timeRemaining) { - EffectResult result = CrowdControl::ExecuteEffect(incomingEffect); - EmitMessage(tcpsock, incomingEffect->id, incomingEffect->timeRemaining, result); - } else { - // If another timed effect is already active that conflicts with the incoming effect. - bool isConflictingEffectActive = false; - for (Effect* effect : activeEffects) { - if (effect != incomingEffect && effect->category == incomingEffect->category && effect->id < incomingEffect->id) { - isConflictingEffectActive = true; - EmitMessage(tcpsock, incomingEffect->id, incomingEffect->timeRemaining, EffectResult::Retry); - break; - } - } - - if (!isConflictingEffectActive) { - // Check if effect can be applied, if it can't, let CC know. - EffectResult result = CrowdControl::CanApplyEffect(incomingEffect); - if (result == EffectResult::Retry || result == EffectResult::Failure) { - EmitMessage(tcpsock, incomingEffect->id, incomingEffect->timeRemaining, result); - continue; - } - - activeEffectsMutex.lock(); - activeEffects.push_back(incomingEffect); - activeEffectsMutex.unlock(); - } - } - } - - if (connected) { - SDLNet_TCP_Close(tcpsock); - connected = false; - SPDLOG_TRACE("[CrowdControl] Ending Listen thread..."); + activeEffectsMutex.lock(); + activeEffects.push_back(incomingEffect); + activeEffectsMutex.unlock(); } } } @@ -147,13 +94,13 @@ void CrowdControl::ProcessActiveEffects() { if (effect->timeRemaining <= 0) { it = activeEffects.erase(std::remove(activeEffects.begin(), activeEffects.end(), effect), activeEffects.end()); - GameInteractor::RemoveEffect(effect->giEffect); + GameInteractor::RemoveEffect(dynamic_cast(effect->giEffect)); delete effect; } else { // If we have a success after previously being paused, tell CC to resume timer. if (effect->isPaused) { effect->isPaused = false; - EmitMessage(tcpsock, effect->id, effect->timeRemaining, EffectResult::Resumed); + EmitMessage(effect->id, effect->timeRemaining, EffectResult::Resumed); // If not paused before, subtract time from the timer and send a Success event if // the result is different from the last time this was ran. // Timed events are put on a thread that runs once per second. @@ -161,7 +108,7 @@ void CrowdControl::ProcessActiveEffects() { effect->timeRemaining -= 1000; if (result != effect->lastExecutionResult) { effect->lastExecutionResult = result; - EmitMessage(tcpsock, effect->id, effect->timeRemaining, EffectResult::Success); + EmitMessage(effect->id, effect->timeRemaining, EffectResult::Success); } } it++; @@ -169,7 +116,7 @@ void CrowdControl::ProcessActiveEffects() { } else { // Timed effects only do Success or Retry if (!effect->isPaused && effect->timeRemaining > 0) { effect->isPaused = true; - EmitMessage(tcpsock, effect->id, effect->timeRemaining, EffectResult::Paused); + EmitMessage(effect->id, effect->timeRemaining, EffectResult::Paused); } it++; } @@ -182,7 +129,7 @@ void CrowdControl::ProcessActiveEffects() { SPDLOG_TRACE("[CrowdControl] Ending Process thread..."); } -void CrowdControl::EmitMessage(TCPsocket socket, uint32_t eventId, long timeRemaining, EffectResult status) { +void CrowdControl::EmitMessage(uint32_t eventId, long timeRemaining, EffectResult status) { nlohmann::json payload; payload["id"] = eventId; @@ -190,8 +137,7 @@ void CrowdControl::EmitMessage(TCPsocket socket, uint32_t eventId, long timeRema payload["timeRemaining"] = timeRemaining; payload["status"] = status; - std::string jsonPayload = payload.dump(); - SDLNet_TCP_Send(socket, jsonPayload.c_str(), jsonPayload.size() + 1); + GameInteractor::Instance->TransmitDataToRemote(payload.dump().c_str()); } CrowdControl::EffectResult CrowdControl::ExecuteEffect(Effect* effect) { @@ -236,6 +182,11 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) { return nullptr; } + if (!dataReceived.contains("id") || !dataReceived.contains("code")) { + SPDLOG_ERROR("Invalid CrowdControl message received {}", payload); + return nullptr; + } + Effect* effect = new Effect(); effect->lastExecutionResult = EffectResult::Initiate; effect->id = dataReceived["id"]; @@ -333,13 +284,13 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) { effect->category = kEffectCatDamageTaken; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ModifyDefenseModifier(); - effect->giEffect->parameters[0] = 2; + dynamic_cast(effect->giEffect)->parameters[0] = 2; break; case kEffectTakeDoubleDamage: effect->category = kEffectCatDamageTaken; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ModifyDefenseModifier(); - effect->giEffect->parameters[0] = -2; + dynamic_cast(effect->giEffect)->parameters[0] = -2; break; case kEffectOneHitKo: effect->category = kEffectCatDamageTaken; @@ -356,37 +307,37 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) { effect->category = kEffectCatSpeed; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ModifyRunSpeedModifier(); - effect->giEffect->parameters[0] = 2; + dynamic_cast(effect->giEffect)->parameters[0] = 2; break; case kEffectDecreaseSpeed: effect->category = kEffectCatSpeed; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ModifyRunSpeedModifier(); - effect->giEffect->parameters[0] = -2; + dynamic_cast(effect->giEffect)->parameters[0] = -2; break; case kEffectLowGravity: effect->category = kEffectCatGravity; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ModifyGravity(); - effect->giEffect->parameters[0] = GI_GRAVITY_LEVEL_LIGHT; + dynamic_cast(effect->giEffect)->parameters[0] = GI_GRAVITY_LEVEL_LIGHT; break; case kEffectHighGravity: effect->category = kEffectCatGravity; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ModifyGravity(); - effect->giEffect->parameters[0] = GI_GRAVITY_LEVEL_HEAVY; + dynamic_cast(effect->giEffect)->parameters[0] = GI_GRAVITY_LEVEL_HEAVY; break; case kEffectForceIronBoots: effect->category = kEffectCatBoots; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ForceEquipBoots(); - effect->giEffect->parameters[0] = PLAYER_BOOTS_IRON; + dynamic_cast(effect->giEffect)->parameters[0] = PLAYER_BOOTS_IRON; break; case kEffectForceHoverBoots: effect->category = kEffectCatBoots; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ForceEquipBoots(); - effect->giEffect->parameters[0] = PLAYER_BOOTS_HOVER; + dynamic_cast(effect->giEffect)->parameters[0] = PLAYER_BOOTS_HOVER; break; case kEffectSlipperyFloor: effect->category = kEffectCatSlipperyFloor; @@ -412,23 +363,23 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) { // Hurt or Heal Link case kEffectEmptyHeart: effect->giEffect = new GameInteractionEffect::ModifyHealth(); - effect->giEffect->parameters[0] = receivedParameter * -1; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter * -1; break; case kEffectFillHeart: effect->giEffect = new GameInteractionEffect::ModifyHealth(); - effect->giEffect->parameters[0] = receivedParameter; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter; break; case kEffectKnockbackLinkWeak: effect->giEffect = new GameInteractionEffect::KnockbackPlayer(); - effect->giEffect->parameters[0] = 1; + dynamic_cast(effect->giEffect)->parameters[0] = 1; break; case kEffectKnockbackLinkStrong: effect->giEffect = new GameInteractionEffect::KnockbackPlayer(); - effect->giEffect->parameters[0] = 3; + dynamic_cast(effect->giEffect)->parameters[0] = 3; break; case kEffectKnockbackLinkMega: effect->giEffect = new GameInteractionEffect::KnockbackPlayer(); - effect->giEffect->parameters[0] = 6; + dynamic_cast(effect->giEffect)->parameters[0] = 6; break; case kEffectBurnLink: effect->giEffect = new GameInteractionEffect::BurnPlayer(); @@ -441,109 +392,109 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) { break; case kEffectKillLink: effect->giEffect = new GameInteractionEffect::SetPlayerHealth(); - effect->giEffect->parameters[0] = 0; + dynamic_cast(effect->giEffect)->parameters[0] = 0; break; // Give Items and Consumables case kEffectAddHeartContainer: effect->giEffect = new GameInteractionEffect::ModifyHeartContainers(); - effect->giEffect->parameters[0] = 1; + dynamic_cast(effect->giEffect)->parameters[0] = 1; break; case kEffectFillMagic: effect->giEffect = new GameInteractionEffect::FillMagic(); break; case kEffectAddRupees: effect->giEffect = new GameInteractionEffect::ModifyRupees(); - effect->giEffect->parameters[0] = receivedParameter; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter; break; case kEffectGiveDekuShield: effect->giEffect = new GameInteractionEffect::GiveOrTakeShield(); - effect->giEffect->parameters[0] = ITEM_SHIELD_DEKU; + dynamic_cast(effect->giEffect)->parameters[0] = ITEM_SHIELD_DEKU; break; case kEffectGiveHylianShield: effect->giEffect = new GameInteractionEffect::GiveOrTakeShield(); - effect->giEffect->parameters[0] = ITEM_SHIELD_HYLIAN; + dynamic_cast(effect->giEffect)->parameters[0] = ITEM_SHIELD_HYLIAN; break; case kEffectRefillSticks: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter; - effect->giEffect->parameters[1] = ITEM_STICK; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_STICK; break; case kEffectRefillNuts: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter; - effect->giEffect->parameters[1] = ITEM_NUT; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_NUT; break; case kEffectRefillBombs: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter; - effect->giEffect->parameters[1] = ITEM_BOMB; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_BOMB; break; case kEffectRefillSeeds: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter; - effect->giEffect->parameters[1] = ITEM_SLINGSHOT; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_SLINGSHOT; break; case kEffectRefillArrows: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter; - effect->giEffect->parameters[1] = ITEM_BOW; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_BOW; break; case kEffectRefillBombchus: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter; - effect->giEffect->parameters[1] = ITEM_BOMBCHU; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_BOMBCHU; break; // Take Items and Consumables case kEffectRemoveHeartContainer: effect->giEffect = new GameInteractionEffect::ModifyHeartContainers(); - effect->giEffect->parameters[0] = -1; + dynamic_cast(effect->giEffect)->parameters[0] = -1; break; case kEffectEmptyMagic: effect->giEffect = new GameInteractionEffect::EmptyMagic(); break; case kEffectRemoveRupees: effect->giEffect = new GameInteractionEffect::ModifyRupees(); - effect->giEffect->parameters[0] = receivedParameter * -1; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter * -1; break; case kEffectTakeDekuShield: effect->giEffect = new GameInteractionEffect::GiveOrTakeShield(); - effect->giEffect->parameters[0] = -ITEM_SHIELD_DEKU; + dynamic_cast(effect->giEffect)->parameters[0] = -ITEM_SHIELD_DEKU; break; case kEffectTakeHylianShield: effect->giEffect = new GameInteractionEffect::GiveOrTakeShield(); - effect->giEffect->parameters[0] = -ITEM_SHIELD_HYLIAN; + dynamic_cast(effect->giEffect)->parameters[0] = -ITEM_SHIELD_HYLIAN; break; case kEffectTakeSticks: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter * -1; - effect->giEffect->parameters[1] = ITEM_STICK; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter * -1; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_STICK; break; case kEffectTakeNuts: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter * -1; - effect->giEffect->parameters[1] = ITEM_NUT; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter * -1; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_NUT; break; case kEffectTakeBombs: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter * -1; - effect->giEffect->parameters[1] = ITEM_BOMB; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter * -1; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_BOMB; break; case kEffectTakeSeeds: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter * -1; - effect->giEffect->parameters[1] = ITEM_SLINGSHOT; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter * -1; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_SLINGSHOT; break; case kEffectTakeArrows: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter * -1; - effect->giEffect->parameters[1] = ITEM_BOW; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter * -1; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_BOW; break; case kEffectTakeBombchus: effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->giEffect->parameters[0] = receivedParameter * -1; - effect->giEffect->parameters[1] = ITEM_BOMBCHU; + dynamic_cast(effect->giEffect)->parameters[0] = receivedParameter * -1; + dynamic_cast(effect->giEffect)->parameters[1] = ITEM_BOMBCHU; break; // Link Size Modifiers @@ -551,25 +502,25 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) { effect->category = kEffectCatLinkSize; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ModifyLinkSize(); - effect->giEffect->parameters[0] = GI_LINK_SIZE_GIANT; + dynamic_cast(effect->giEffect)->parameters[0] = GI_LINK_SIZE_GIANT; break; case kEffectMinishLink: effect->category = kEffectCatLinkSize; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ModifyLinkSize(); - effect->giEffect->parameters[0] = GI_LINK_SIZE_MINISH; + dynamic_cast(effect->giEffect)->parameters[0] = GI_LINK_SIZE_MINISH; break; case kEffectPaperLink: effect->category = kEffectCatLinkSize; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ModifyLinkSize(); - effect->giEffect->parameters[0] = GI_LINK_SIZE_PAPER; + dynamic_cast(effect->giEffect)->parameters[0] = GI_LINK_SIZE_PAPER; break; case kEffectSquishedLink: effect->category = kEffectCatLinkSize; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::ModifyLinkSize(); - effect->giEffect->parameters[0] = GI_LINK_SIZE_SQUISHED; + dynamic_cast(effect->giEffect)->parameters[0] = GI_LINK_SIZE_SQUISHED; break; case kEffectInvisibleLink: effect->category = kEffectCatLinkSize; @@ -585,11 +536,11 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) { break; case kEffectSetTimeToDawn: effect->giEffect = new GameInteractionEffect::SetTimeOfDay(); - effect->giEffect->parameters[0] = GI_TIMEOFDAY_DAWN; + dynamic_cast(effect->giEffect)->parameters[0] = GI_TIMEOFDAY_DAWN; break; case kEffectSetTimeToDusk: effect->giEffect = new GameInteractionEffect::SetTimeOfDay(); - effect->giEffect->parameters[0] = GI_TIMEOFDAY_DUSK; + dynamic_cast(effect->giEffect)->parameters[0] = GI_TIMEOFDAY_DUSK; break; // Visual Effects @@ -632,186 +583,186 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) { effect->category = kEffectCatRandomButtons; effect->timeRemaining = 30000; effect->giEffect = new GameInteractionEffect::PressRandomButton(); - effect->giEffect->parameters[0] = 30; + dynamic_cast(effect->giEffect)->parameters[0] = 30; break; case kEffectClearCbuttons: effect->giEffect = new GameInteractionEffect::ClearAssignedButtons(); - effect->giEffect->parameters[0] = GI_BUTTONS_CBUTTONS; + dynamic_cast(effect->giEffect)->parameters[0] = GI_BUTTONS_CBUTTONS; break; case kEffectClearDpad: effect->giEffect = new GameInteractionEffect::ClearAssignedButtons(); - effect->giEffect->parameters[0] = GI_BUTTONS_DPAD; + dynamic_cast(effect->giEffect)->parameters[0] = GI_BUTTONS_DPAD; break; // Teleport Player case kEffectTpLinksHouse: effect->giEffect = new GameInteractionEffect::TeleportPlayer(); - effect->giEffect->parameters[0] = GI_TP_DEST_LINKSHOUSE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_TP_DEST_LINKSHOUSE; break; case kEffectTpMinuet: effect->giEffect = new GameInteractionEffect::TeleportPlayer(); - effect->giEffect->parameters[0] = GI_TP_DEST_MINUET; + dynamic_cast(effect->giEffect)->parameters[0] = GI_TP_DEST_MINUET; break; case kEffectTpBolero: effect->giEffect = new GameInteractionEffect::TeleportPlayer(); - effect->giEffect->parameters[0] = GI_TP_DEST_BOLERO; + dynamic_cast(effect->giEffect)->parameters[0] = GI_TP_DEST_BOLERO; break; case kEffectTpSerenade: effect->giEffect = new GameInteractionEffect::TeleportPlayer(); - effect->giEffect->parameters[0] = GI_TP_DEST_SERENADE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_TP_DEST_SERENADE; break; case kEffectTpRequiem: effect->giEffect = new GameInteractionEffect::TeleportPlayer(); - effect->giEffect->parameters[0] = GI_TP_DEST_REQUIEM; + dynamic_cast(effect->giEffect)->parameters[0] = GI_TP_DEST_REQUIEM; break; case kEffectTpNocturne: effect->giEffect = new GameInteractionEffect::TeleportPlayer(); - effect->giEffect->parameters[0] = GI_TP_DEST_NOCTURNE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_TP_DEST_NOCTURNE; break; case kEffectTpPrelude: effect->giEffect = new GameInteractionEffect::TeleportPlayer(); - effect->giEffect->parameters[0] = GI_TP_DEST_PRELUDE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_TP_DEST_PRELUDE; break; // Tunic Color (Bidding War) case kEffectTunicRed: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS; - effect->giEffect->parameters[1] = GI_COLOR_RED; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_RED; break; case kEffectTunicGreen: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS; - effect->giEffect->parameters[1] = GI_COLOR_GREEN; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_GREEN; break; case kEffectTunicBlue: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS; - effect->giEffect->parameters[1] = GI_COLOR_BLUE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_BLUE; break; case kEffectTunicOrange: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS; - effect->giEffect->parameters[1] = GI_COLOR_ORANGE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_ORANGE; break; case kEffectTunicYellow: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS; - effect->giEffect->parameters[1] = GI_COLOR_YELLOW; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_YELLOW; break; case kEffectTunicPurple: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS; - effect->giEffect->parameters[1] = GI_COLOR_PURPLE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_PURPLE; break; case kEffectTunicPink: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS; - effect->giEffect->parameters[1] = GI_COLOR_PINK; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_PINK; break; case kEffectTunicBrown: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS; - effect->giEffect->parameters[1] = GI_COLOR_BROWN; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_BROWN; break; case kEffectTunicBlack: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS; - effect->giEffect->parameters[1] = GI_COLOR_BLACK; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_BLACK; break; // Navi Color (Bidding War) case kEffectNaviRed: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_NAVI; - effect->giEffect->parameters[1] = GI_COLOR_RED; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_RED; break; case kEffectNaviGreen: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_NAVI; - effect->giEffect->parameters[1] = GI_COLOR_GREEN; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_GREEN; break; case kEffectNaviBlue: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_NAVI; - effect->giEffect->parameters[1] = GI_COLOR_BLUE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_BLUE; break; case kEffectNaviOrange: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_NAVI; - effect->giEffect->parameters[1] = GI_COLOR_ORANGE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_ORANGE; break; case kEffectNaviYellow: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_NAVI; - effect->giEffect->parameters[1] = GI_COLOR_YELLOW; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_YELLOW; break; case kEffectNaviPurple: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_NAVI; - effect->giEffect->parameters[1] = GI_COLOR_PURPLE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_PURPLE; break; case kEffectNaviPink: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_NAVI; - effect->giEffect->parameters[1] = GI_COLOR_PINK; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_PINK; break; case kEffectNaviBrown: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_NAVI; - effect->giEffect->parameters[1] = GI_COLOR_BROWN; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_BROWN; break; case kEffectNaviBlack: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_NAVI; - effect->giEffect->parameters[1] = GI_COLOR_BLACK; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_BLACK; break; // Link's Hair Color (Bidding War) case kEffectHairRed: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_HAIR; - effect->giEffect->parameters[1] = GI_COLOR_RED; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_RED; break; case kEffectHairGreen: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_HAIR; - effect->giEffect->parameters[1] = GI_COLOR_GREEN; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_GREEN; break; case kEffectHairBlue: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_HAIR; - effect->giEffect->parameters[1] = GI_COLOR_BLUE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_BLUE; break; case kEffectHairOrange: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_HAIR; - effect->giEffect->parameters[1] = GI_COLOR_ORANGE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_ORANGE; break; case kEffectHairYellow: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_HAIR; - effect->giEffect->parameters[1] = GI_COLOR_YELLOW; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_YELLOW; break; case kEffectHairPurple: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_HAIR; - effect->giEffect->parameters[1] = GI_COLOR_PURPLE; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_PURPLE; break; case kEffectHairPink: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_HAIR; - effect->giEffect->parameters[1] = GI_COLOR_PINK; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_PINK; break; case kEffectHairBrown: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_HAIR; - effect->giEffect->parameters[1] = GI_COLOR_BROWN; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_BROWN; break; case kEffectHairBlack: effect->giEffect = new GameInteractionEffect::SetCosmeticsColor(); - effect->giEffect->parameters[0] = GI_COSMETICS_HAIR; - effect->giEffect->parameters[1] = GI_COLOR_BLACK; + dynamic_cast(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR; + dynamic_cast(effect->giEffect)->parameters[1] = GI_COLOR_BLACK; break; default: diff --git a/soh/soh/Enhancements/crowd-control/CrowdControl.h b/soh/soh/Enhancements/crowd-control/CrowdControl.h index 672e6d36195..565a2a393be 100644 --- a/soh/soh/Enhancements/crowd-control/CrowdControl.h +++ b/soh/soh/Enhancements/crowd-control/CrowdControl.h @@ -1,4 +1,4 @@ -#ifdef ENABLE_CROWD_CONTROL +#ifdef ENABLE_REMOTE_CONTROL #ifndef _CROWDCONTROL_C #define _CROWDCONTROL_C @@ -73,24 +73,17 @@ class CrowdControl { EffectResult lastExecutionResult; } Effect; - std::thread ccThreadReceive; std::thread ccThreadProcess; - TCPsocket tcpsock; - IPaddress ip; - bool isEnabled; - bool connected; - - char received[512]; std::vector activeEffects; std::mutex activeEffectsMutex; - void ListenToServer(); + void HandleRemoteData(char payload[512]); void ProcessActiveEffects(); - void EmitMessage(TCPsocket socket, uint32_t eventId, long timeRemaining, EffectResult status); + void EmitMessage(uint32_t eventId, long timeRemaining, EffectResult status); Effect* ParseMessage(char payload[512]); EffectResult ExecuteEffect(Effect* effect); EffectResult CanApplyEffect(Effect *effect); @@ -98,8 +91,6 @@ class CrowdControl { public: static CrowdControl* Instance; - void Init(); - void Shutdown(); void Enable(); void Disable(); }; diff --git a/soh/soh/Enhancements/debugconsole.cpp b/soh/soh/Enhancements/debugconsole.cpp index c94d5f8063b..cbdb4cfa54f 100644 --- a/soh/soh/Enhancements/debugconsole.cpp +++ b/soh/soh/Enhancements/debugconsole.cpp @@ -99,7 +99,7 @@ static bool ActorSpawnHandler(std::shared_ptr Console, const std:: static bool KillPlayerHandler(std::shared_ptr Console, const std::vector&, std::string* output) { GameInteractionEffectBase* effect = new GameInteractionEffect::SetPlayerHealth(); - effect->parameters[0] = 0; + dynamic_cast(effect)->parameters[0] = 0; GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { INFO_MESSAGE("[SOH] You've met with a terrible fate, haven't you?"); @@ -130,7 +130,7 @@ static bool SetPlayerHealthHandler(std::shared_ptr Console, const } GameInteractionEffectBase* effect = new GameInteractionEffect::SetPlayerHealth(); - effect->parameters[0] = health; + dynamic_cast(effect)->parameters[0] = health; GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { INFO_MESSAGE("[SOH] Player health updated to %d", health); @@ -246,8 +246,8 @@ static bool AddAmmoHandler(std::shared_ptr Console, const std::vec } GameInteractionEffectBase* effect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->parameters[0] = amount; - effect->parameters[1] = it->second; + dynamic_cast(effect)->parameters[0] = amount; + dynamic_cast(effect)->parameters[1] = it->second; GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { @@ -286,8 +286,8 @@ static bool TakeAmmoHandler(std::shared_ptr Console, const std::ve } GameInteractionEffectBase* effect = new GameInteractionEffect::AddOrTakeAmmo(); - effect->parameters[0] = -amount; - effect->parameters[1] = it->second; + dynamic_cast(effect)->parameters[0] = -amount; + dynamic_cast(effect)->parameters[1] = it->second; GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { @@ -576,7 +576,7 @@ static bool InvisibleHandler(std::shared_ptr Console, const std::v return 1; } - GameInteractionEffectBase* effect = new GameInteractionEffect::InvisibleLink(); + RemovableGameInteractionEffect* effect = new GameInteractionEffect::InvisibleLink(); GameInteractionEffectQueryResult result = state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { @@ -603,8 +603,8 @@ static bool GiantLinkHandler(std::shared_ptr Console, const std::v return 1; } - GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyLinkSize(); - effect->parameters[0] = GI_LINK_SIZE_GIANT; + RemovableGameInteractionEffect* effect = new GameInteractionEffect::ModifyLinkSize(); + dynamic_cast(effect)->parameters[0] = GI_LINK_SIZE_GIANT; GameInteractionEffectQueryResult result = state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { @@ -631,8 +631,8 @@ static bool MinishLinkHandler(std::shared_ptr Console, const std:: return 1; } - GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyLinkSize(); - effect->parameters[0] = GI_LINK_SIZE_MINISH; + RemovableGameInteractionEffect* effect = new GameInteractionEffect::ModifyLinkSize(); + dynamic_cast(effect)->parameters[0] = GI_LINK_SIZE_MINISH; GameInteractionEffectQueryResult result = state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { @@ -665,7 +665,7 @@ static bool AddHeartContainerHandler(std::shared_ptr Console, cons } GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyHeartContainers(); - effect->parameters[0] = hearts; + dynamic_cast(effect)->parameters[0] = hearts; GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { INFO_MESSAGE("[SOH] Added %d heart containers", hearts); @@ -696,7 +696,7 @@ static bool RemoveHeartContainerHandler(std::shared_ptr Console, c } GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyHeartContainers(); - effect->parameters[0] = -hearts; + dynamic_cast(effect)->parameters[0] = -hearts; GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { INFO_MESSAGE("[SOH] Removed %d heart containers", hearts); @@ -716,7 +716,7 @@ static bool GravityHandler(std::shared_ptr Console, const std::vec GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyGravity(); try { - effect->parameters[0] = LUS::Math::clamp(std::stoi(args[1], nullptr, 10), GI_GRAVITY_LEVEL_LIGHT, GI_GRAVITY_LEVEL_HEAVY); + dynamic_cast(effect)->parameters[0] = LUS::Math::clamp(std::stoi(args[1], nullptr, 10), GI_GRAVITY_LEVEL_LIGHT, GI_GRAVITY_LEVEL_HEAVY); } catch (std::invalid_argument const& ex) { ERROR_MESSAGE("[SOH] Gravity value must be a number."); return 1; @@ -746,7 +746,7 @@ static bool NoUIHandler(std::shared_ptr Console, const std::vector return 1; } - GameInteractionEffectBase* effect = new GameInteractionEffect::NoUI(); + RemovableGameInteractionEffect* effect = new GameInteractionEffect::NoUI(); GameInteractionEffectQueryResult result = state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect); @@ -781,7 +781,7 @@ static bool DefenseModifierHandler(std::shared_ptr Console, const GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyDefenseModifier(); try { - effect->parameters[0] = std::stoi(args[1], nullptr, 10); + dynamic_cast(effect)->parameters[0] = std::stoi(args[1], nullptr, 10); } catch (std::invalid_argument const& ex) { ERROR_MESSAGE("[SOH] Defense modifier value must be a number."); return 1; @@ -789,7 +789,7 @@ static bool DefenseModifierHandler(std::shared_ptr Console, const GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { - INFO_MESSAGE("[SOH] Defense modifier set to %d", effect->parameters[0]); + INFO_MESSAGE("[SOH] Defense modifier set to %d", dynamic_cast(effect)->parameters[0]); return 0; } else { INFO_MESSAGE("[SOH] Command failed: Could not set defense modifier."); @@ -811,7 +811,7 @@ static bool DamageHandler(std::shared_ptr Console, const std::vect return 1; } - effect->parameters[0] = -value; + dynamic_cast(effect)->parameters[0] = -value; } catch (std::invalid_argument const& ex) { ERROR_MESSAGE("[SOH] Damage value must be a number."); return 1; @@ -841,7 +841,7 @@ static bool HealHandler(std::shared_ptr Console, const std::vector return 1; } - effect->parameters[0] = value; + dynamic_cast(effect)->parameters[0] = value; } catch (std::invalid_argument const& ex) { ERROR_MESSAGE("[SOH] Damage value must be a number."); return 1; @@ -897,7 +897,7 @@ static bool NoZHandler(std::shared_ptr Console, const std::vector< return 1; } - GameInteractionEffectBase* effect = new GameInteractionEffect::DisableZTargeting(); + RemovableGameInteractionEffect* effect = new GameInteractionEffect::DisableZTargeting(); GameInteractionEffectQueryResult result = state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect); @@ -925,7 +925,7 @@ static bool OneHitKOHandler(std::shared_ptr Console, const std::ve return 1; } - GameInteractionEffectBase* effect = new GameInteractionEffect::OneHitKO(); + RemovableGameInteractionEffect* effect = new GameInteractionEffect::OneHitKO(); GameInteractionEffectQueryResult result = state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect); @@ -953,7 +953,7 @@ static bool PacifistHandler(std::shared_ptr Console, const std::ve return 1; } - GameInteractionEffectBase* effect = new GameInteractionEffect::PacifistMode(); + RemovableGameInteractionEffect* effect = new GameInteractionEffect::PacifistMode(); GameInteractionEffectQueryResult result = state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect); @@ -981,8 +981,8 @@ static bool PaperLinkHandler(std::shared_ptr Console, const std::v return 1; } - GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyLinkSize(); - effect->parameters[0] = GI_LINK_SIZE_PAPER; + RemovableGameInteractionEffect* effect = new GameInteractionEffect::ModifyLinkSize(); + dynamic_cast(effect)->parameters[0] = GI_LINK_SIZE_PAPER; GameInteractionEffectQueryResult result = state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect); @@ -1010,7 +1010,7 @@ static bool RainstormHandler(std::shared_ptr Console, const std::v return 1; } - GameInteractionEffectBase* effect = new GameInteractionEffect::WeatherRainstorm(); + RemovableGameInteractionEffect* effect = new GameInteractionEffect::WeatherRainstorm(); GameInteractionEffectQueryResult result = state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect); @@ -1038,7 +1038,7 @@ static bool ReverseControlsHandler(std::shared_ptr Console, const return 1; } - GameInteractionEffectBase* effect = new GameInteractionEffect::ReverseControls(); + RemovableGameInteractionEffect* effect = new GameInteractionEffect::ReverseControls(); GameInteractionEffectQueryResult result = state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect); @@ -1061,7 +1061,7 @@ static bool UpdateRupeesHandler(std::shared_ptr Console, const std GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyRupees(); try { - effect->parameters[0] = std::stoi(args[1], nullptr, 10); + dynamic_cast(effect)->parameters[0] = std::stoi(args[1], nullptr, 10); } catch (std::invalid_argument const& ex) { ERROR_MESSAGE("[SOH] Rupee value must be a number."); return 1; @@ -1085,7 +1085,7 @@ static bool SpeedModifierHandler(std::shared_ptr Console, const st GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyRunSpeedModifier(); try { - effect->parameters[0] = std::stoi(args[1], nullptr, 10); + dynamic_cast(effect)->parameters[0] = std::stoi(args[1], nullptr, 10); } catch (std::invalid_argument const& ex) { ERROR_MESSAGE("[SOH] Speed modifier value must be a number."); return 1; @@ -1120,7 +1120,7 @@ static bool BootsHandler(std::shared_ptr Console, const std::vecto } GameInteractionEffectBase* effect = new GameInteractionEffect::ForceEquipBoots(); - effect->parameters[0] = it->second; + dynamic_cast(effect)->parameters[0] = it->second; GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { @@ -1151,7 +1151,7 @@ static bool GiveShieldHandler(std::shared_ptr Console, const std:: } GameInteractionEffectBase* effect = new GameInteractionEffect::GiveOrTakeShield(); - effect->parameters[0] = it->second; + dynamic_cast(effect)->parameters[0] = it->second; GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { @@ -1176,7 +1176,7 @@ static bool TakeShieldHandler(std::shared_ptr Console, const std:: } GameInteractionEffectBase* effect = new GameInteractionEffect::GiveOrTakeShield(); - effect->parameters[0] = it->second * -1; + dynamic_cast(effect)->parameters[0] = it->second * -1; GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect); if (result == GameInteractionEffectQueryResult::Possible) { @@ -1202,7 +1202,7 @@ static bool KnockbackHandler(std::shared_ptr Console, const std::v return 1; } - effect->parameters[0] = value; + dynamic_cast(effect)->parameters[0] = value; } catch (std::invalid_argument const& ex) { ERROR_MESSAGE("[SOH] Knockback value must be a number."); return 1; diff --git a/soh/soh/Enhancements/game-interactor/GameInteractionEffect.cpp b/soh/soh/Enhancements/game-interactor/GameInteractionEffect.cpp index d0fda0db88f..e62c836a4e1 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractionEffect.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractionEffect.cpp @@ -31,11 +31,11 @@ GameInteractionEffectQueryResult GameInteractionEffectBase::Apply() { } /// For most effects, CanBeRemoved is the same as CanBeApplied. When its not: please override `CanBeRemoved`. -GameInteractionEffectQueryResult GameInteractionEffectBase::CanBeRemoved() { +GameInteractionEffectQueryResult RemovableGameInteractionEffect::CanBeRemoved() { return CanBeApplied(); } -GameInteractionEffectQueryResult GameInteractionEffectBase::Remove() { +GameInteractionEffectQueryResult RemovableGameInteractionEffect::Remove() { GameInteractionEffectQueryResult result = CanBeRemoved(); if (result != GameInteractionEffectQueryResult::Possible) { return result; diff --git a/soh/soh/Enhancements/game-interactor/GameInteractionEffect.h b/soh/soh/Enhancements/game-interactor/GameInteractionEffect.h index ab8cfb61044..856ac0945ba 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractionEffect.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractionEffect.h @@ -15,18 +15,26 @@ enum GameInteractionEffectQueryResult { class GameInteractionEffectBase { public: virtual GameInteractionEffectQueryResult CanBeApplied() = 0; - virtual GameInteractionEffectQueryResult CanBeRemoved(); GameInteractionEffectQueryResult Apply(); - GameInteractionEffectQueryResult Remove(); - int32_t parameters[2]; - - protected: +protected: virtual void _Apply() = 0; +}; + +class RemovableGameInteractionEffect: public GameInteractionEffectBase { +public: + virtual GameInteractionEffectQueryResult CanBeRemoved(); + GameInteractionEffectQueryResult Remove(); +protected: virtual void _Remove() {}; }; +class ParameterizedGameInteractionEffect { +public: + int32_t parameters[3]; +}; + namespace GameInteractionEffect { - class ModifyHeartContainers: public GameInteractionEffectBase { + class ModifyHeartContainers: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; @@ -41,29 +49,29 @@ namespace GameInteractionEffect { void _Apply() override; }; - class ModifyRupees: public GameInteractionEffectBase { + class ModifyRupees: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class NoUI: public GameInteractionEffectBase { + class NoUI: public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class ModifyGravity: public GameInteractionEffectBase { + class ModifyGravity: public RemovableGameInteractionEffect, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class ModifyHealth: public GameInteractionEffectBase { + class ModifyHealth: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class SetPlayerHealth: public GameInteractionEffectBase { + class SetPlayerHealth: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; @@ -83,98 +91,98 @@ namespace GameInteractionEffect { void _Apply() override; }; - class KnockbackPlayer: public GameInteractionEffectBase { + class KnockbackPlayer: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class ModifyLinkSize: public GameInteractionEffectBase { + class ModifyLinkSize: public RemovableGameInteractionEffect, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class InvisibleLink : public GameInteractionEffectBase { + class InvisibleLink : public RemovableGameInteractionEffect, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class PacifistMode : public GameInteractionEffectBase { + class PacifistMode : public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class DisableZTargeting: public GameInteractionEffectBase { + class DisableZTargeting: public RemovableGameInteractionEffect, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class WeatherRainstorm: public GameInteractionEffectBase { + class WeatherRainstorm: public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class ReverseControls: public GameInteractionEffectBase { + class ReverseControls: public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class ForceEquipBoots: public GameInteractionEffectBase { + class ForceEquipBoots: public RemovableGameInteractionEffect, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class ModifyRunSpeedModifier: public GameInteractionEffectBase { + class ModifyRunSpeedModifier: public RemovableGameInteractionEffect, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class OneHitKO : public GameInteractionEffectBase { + class OneHitKO : public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class ModifyDefenseModifier: public GameInteractionEffectBase { + class ModifyDefenseModifier: public RemovableGameInteractionEffect, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class GiveOrTakeShield: public GameInteractionEffectBase { + class GiveOrTakeShield: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class TeleportPlayer: public GameInteractionEffectBase { + class TeleportPlayer: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class ClearAssignedButtons: public GameInteractionEffectBase { + class ClearAssignedButtons: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class SetTimeOfDay: public GameInteractionEffectBase { + class SetTimeOfDay: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class SetCollisionViewer: public GameInteractionEffectBase { + class SetCollisionViewer: public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class SetCosmeticsColor: public GameInteractionEffectBase { + class SetCosmeticsColor: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; @@ -184,52 +192,52 @@ namespace GameInteractionEffect { void _Apply() override; }; - class PressButton: public GameInteractionEffectBase { + class PressButton: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class PressRandomButton: public GameInteractionEffectBase { + class PressRandomButton: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class AddOrTakeAmmo: public GameInteractionEffectBase { + class AddOrTakeAmmo: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; }; - class RandomBombFuseTimer: public GameInteractionEffectBase { + class RandomBombFuseTimer: public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class DisableLedgeGrabs: public GameInteractionEffectBase { + class DisableLedgeGrabs: public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class RandomWind: public GameInteractionEffectBase { + class RandomWind: public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class RandomBonks: public GameInteractionEffectBase { + class RandomBonks: public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class PlayerInvincibility: public GameInteractionEffectBase { + class PlayerInvincibility: public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; }; - class SlipperyFloor: public GameInteractionEffectBase { + class SlipperyFloor: public RemovableGameInteractionEffect { GameInteractionEffectQueryResult CanBeApplied() override; void _Apply() override; void _Remove() override; diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor.cpp index eb330947bbb..d6641133c97 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.cpp @@ -31,7 +31,7 @@ GameInteractionEffectQueryResult GameInteractor::ApplyEffect(GameInteractionEffe return effect->Apply(); } -GameInteractionEffectQueryResult GameInteractor::RemoveEffect(GameInteractionEffectBase* effect) { +GameInteractionEffectQueryResult GameInteractor::RemoveEffect(RemovableGameInteractionEffect* effect) { return effect->Remove(); } diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor.h b/soh/soh/Enhancements/game-interactor/GameInteractor.h index 193adb6c4bd..a444b906a68 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.h @@ -6,6 +6,11 @@ #include "GameInteractionEffect.h" #include "soh/Enhancements/item-tables/ItemTableTypes.h" +typedef enum { + GI_SCHEME_BUILT_IN, + GI_SCHEME_CROWD_CONTROL, +} GIScheme; + typedef enum { /* 0x00 */ GI_LINK_SIZE_NORMAL, /* 0x01 */ GI_LINK_SIZE_GIANT, @@ -89,10 +94,15 @@ uint8_t GameInteractor_SecondCollisionUpdate(); #ifdef __cplusplus - +#include #include #include +#ifdef ENABLE_REMOTE_CONTROL +#include +#include +#endif + #define DEFINE_HOOK(name, type) \ struct name { \ typedef std::function fn; \ @@ -127,10 +137,24 @@ class GameInteractor { static void SetPacifistMode(bool active); }; + #ifdef ENABLE_REMOTE_CONTROL + bool isRemoteInteractorEnabled; + bool isRemoteInteractorConnected; + + void EnableRemoteInteractor(); + void DisableRemoteInteractor(); + void RegisterRemoteDataHandler(std::function method); + void RegisterRemoteJsonHandler(std::function method); + void RegisterRemoteConnectedHandler(std::function method); + void RegisterRemoteDisconnectedHandler(std::function method); + void TransmitDataToRemote(const char* payload); + void TransmitJsonToRemote(nlohmann::json packet); + #endif + // Effects static GameInteractionEffectQueryResult CanApplyEffect(GameInteractionEffectBase* effect); static GameInteractionEffectQueryResult ApplyEffect(GameInteractionEffectBase* effect); - static GameInteractionEffectQueryResult RemoveEffect(GameInteractionEffectBase* effect); + static GameInteractionEffectQueryResult RemoveEffect(RemovableGameInteractionEffect* effect); // Game Hooks template struct RegisteredGameHooks { inline static std::vector functions; }; @@ -216,6 +240,21 @@ class GameInteractor { 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 remoteDataHandler; + std::function remoteJsonHandler; + std::function remoteConnectedHandler; + std::function remoteDisconnectedHandler; + + void ReceiveFromServer(); + void HandleRemoteData(char payload[512]); + void HandleRemoteJson(std::string payload); + #endif }; #endif /* __cplusplus */ diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_BuiltIn.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_BuiltIn.cpp new file mode 100644 index 00000000000..0128957df58 --- /dev/null +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_BuiltIn.cpp @@ -0,0 +1,290 @@ +#ifdef ENABLE_REMOTE_CONTROL + +#include "GameInteractor_BuiltIn.h" +#include +#include +#include + +// MARK: - Declarations + +template +bool IsType(const SrcType* src) { + return dynamic_cast(src) != nullptr; +} + +template +bool TakesParameter() { + return std::is_base_of_v; +} + +template +bool IsRemovable() { + return std::is_base_of_v; +} + +/// Map of string name to enum value and flag whether it takes in a param or not +std::unordered_map> nameToEnum = { + {"modify_heart_container", { + GameInteractorBuiltInEffects::modifyHeartContainers, + IsRemovable(), + TakesParameter() + }}, + { "fill_magic", { + GameInteractorBuiltInEffects::fillMagic, + IsRemovable(), + TakesParameter() + }}, + { "empty_magic", { + GameInteractorBuiltInEffects::emptyMagic, + IsRemovable(), + TakesParameter() + }}, + { "modify_rupees", { + GameInteractorBuiltInEffects::modifyRupees, + IsRemovable(), + TakesParameter() + }}, + { "no_ui", { + GameInteractorBuiltInEffects::noUI, + IsRemovable(), + TakesParameter() + }}, + { "modify_gravity", { + GameInteractorBuiltInEffects::modifyGravity, + IsRemovable(), + TakesParameter() + }}, + { "modify_health", { + GameInteractorBuiltInEffects::modifyHealth, + IsRemovable(), + TakesParameter() + }}, + { "set_player_health", { + GameInteractorBuiltInEffects::setPlayerHealth, + IsRemovable(), + TakesParameter() + }}, + { "freeze_player", { + GameInteractorBuiltInEffects::freezePlayer, + IsRemovable(), + TakesParameter() + }}, + { "burn_player", { + GameInteractorBuiltInEffects::burnPlayer, + IsRemovable(), + TakesParameter() + }}, + { "electrocute_player", { + GameInteractorBuiltInEffects::electrocutePlayer, + IsRemovable(), + TakesParameter() + }}, + { "knockback_player", { + GameInteractorBuiltInEffects::knockbackPlayer, + IsRemovable(), + TakesParameter() + }}, + { "modify_link_size", { + GameInteractorBuiltInEffects::modifyLinkSize, + IsRemovable(), + TakesParameter() + }}, + { "invisible_link", { + GameInteractorBuiltInEffects::invisibleLink, + IsRemovable(), + TakesParameter() + }}, + { "pacifist_mode", { + GameInteractorBuiltInEffects::pacifistMode, + IsRemovable(), + TakesParameter() + }}, + { "disable_z_targeting", { + GameInteractorBuiltInEffects::disableZTargeting, + IsRemovable(), + TakesParameter() + }}, + { "weather_rainstorm", { + GameInteractorBuiltInEffects::weatherRainstorm, + IsRemovable(), + TakesParameter() + }}, + { "reverse_controls", { + GameInteractorBuiltInEffects::reverseControls, + IsRemovable(), + TakesParameter() + }}, + { "force_equip_boots", { + GameInteractorBuiltInEffects::forceEquipBoots, + IsRemovable(), + TakesParameter() + }}, + { "modify_run_speed_modifier", { + GameInteractorBuiltInEffects::modifyRunSpeedModifier, + IsRemovable(), + TakesParameter() + }}, + { "one_hit_ko", { + GameInteractorBuiltInEffects::oneHitKO, + IsRemovable(), + TakesParameter() + }}, + { "modify_defense_modifier", { + GameInteractorBuiltInEffects::modifyDefenseModifier, + IsRemovable(), + TakesParameter() + }}, + { "give_or_take_shield", { + GameInteractorBuiltInEffects::giveOrTakeShield, + IsRemovable(), + TakesParameter() + }}, +}; + +void GameInteractorBuiltIn::Enable() { + if (isEnabled) { + return; + } + + isEnabled = true; + GameInteractor::Instance->EnableRemoteInteractor(); + GameInteractor::Instance->RegisterRemoteJsonHandler([&](nlohmann::json payload) { + HandleRemoteJson(payload); + }); + GameInteractor::Instance->RegisterRemoteConnectedHandler([&]() { + nlohmann::json payload; + payload["action"] = "identify"; + payload["supported_events"] = nlohmann::json::array(); + for (auto& [key, value] : nameToEnum) { + nlohmann::json entry; + entry["event"] = key; + entry["is_removable"] = std::get<1>(value); + entry["takes_param"] = std::get<2>(value); + payload["supported_events"].push_back(entry); + } + GameInteractor::Instance->TransmitJsonToRemote(payload); + }); +} + +void GameInteractorBuiltIn::Disable() { + if (!isEnabled) { + return; + } + + isEnabled = false; + GameInteractor::Instance->DisableRemoteInteractor(); +} + +void GameInteractorBuiltIn::HandleRemoteJson(nlohmann::json payload) { + SPDLOG_INFO("[GameInteractorBuiltIn] Received payload: \n{}\n", payload.dump()); + + if (!payload.contains("action")) { + return; + } + // { action: "apply_effect, effect: { "name: "value", "payload": { "parameter": "value" } } + // { action: "remove_effect, effect: { "name: "value" } + // if action contains effect then it's an effect + if (payload["action"] == "apply_effect" || payload["action"] == "remove_effect") { + nlohmann::json effect = payload["effect"]; + GameInteractionEffectBase* giEffect = EffectFromJson(effect["name"].get(), effect["payload"]); + if (giEffect) { + if (payload["action"] == "apply_effect") { + giEffect->Apply(); + } else if (IsType(giEffect)) { + dynamic_cast(giEffect)->Remove(); + } + } + } +} + +GameInteractionEffectBase* GameInteractorBuiltIn::EffectFromJson(std::string name, nlohmann::json payload) { + if (nameToEnum.find(name) == nameToEnum.end()) { + return nullptr; + } + + switch (std::get<0>(nameToEnum[name])) { + case GameInteractorBuiltInEffects::modifyHeartContainers: { + auto effect = new GameInteractionEffect::ModifyHeartContainers(); + effect->parameters[0] = payload["parameter"].get(); + return effect; + } + case GameInteractorBuiltInEffects::fillMagic: + return new GameInteractionEffect::FillMagic(); + case GameInteractorBuiltInEffects::emptyMagic: + return new GameInteractionEffect::EmptyMagic(); + case GameInteractorBuiltInEffects::modifyRupees: { + auto effect = new GameInteractionEffect::ModifyRupees(); + effect->parameters[0] = payload["parameter"].get(); + return effect; + } + case GameInteractorBuiltInEffects::noUI: + return new GameInteractionEffect::NoUI(); + case GameInteractorBuiltInEffects::modifyGravity: { + auto effect = new GameInteractionEffect::ModifyGravity(); + effect->parameters[0] = payload["parameter"].get(); + return effect; + } + case GameInteractorBuiltInEffects::modifyHealth: { + auto effect = new GameInteractionEffect::ModifyHealth(); + effect->parameters[0] = payload["parameter"].get(); + return effect; + } + case GameInteractorBuiltInEffects::setPlayerHealth: { + auto effect = new GameInteractionEffect::SetPlayerHealth(); + effect->parameters[0] = payload["parameter"].get(); + return effect; + } + case GameInteractorBuiltInEffects::freezePlayer: + return new GameInteractionEffect::FreezePlayer(); + case GameInteractorBuiltInEffects::burnPlayer: + return new GameInteractionEffect::BurnPlayer(); + case GameInteractorBuiltInEffects::electrocutePlayer: + return new GameInteractionEffect::ElectrocutePlayer(); + case GameInteractorBuiltInEffects::knockbackPlayer: { + auto effect = new GameInteractionEffect::KnockbackPlayer(); + effect->parameters[0] = payload["parameter"].get(); + return effect; + } + case GameInteractorBuiltInEffects::modifyLinkSize: { + auto effect = new GameInteractionEffect::ModifyLinkSize(); + effect->parameters[0] = payload["parameter"].get(); + return effect; + } + case GameInteractorBuiltInEffects::invisibleLink: + return new GameInteractionEffect::InvisibleLink(); + case GameInteractorBuiltInEffects::pacifistMode: + return new GameInteractionEffect::PacifistMode(); + case GameInteractorBuiltInEffects::disableZTargeting: + return new GameInteractionEffect::DisableZTargeting(); + case GameInteractorBuiltInEffects::weatherRainstorm: + return new GameInteractionEffect::WeatherRainstorm(); + case GameInteractorBuiltInEffects::reverseControls: + return new GameInteractionEffect::ReverseControls(); + case GameInteractorBuiltInEffects::forceEquipBoots: { + auto effect = new GameInteractionEffect::ForceEquipBoots(); + effect->parameters[0] = payload["parameter"].get(); + return effect; + } + case GameInteractorBuiltInEffects::modifyRunSpeedModifier: { + auto effect = new GameInteractionEffect::ModifyRunSpeedModifier(); + effect->parameters[0] = payload["parameter"].get(); + return effect; + } + case GameInteractorBuiltInEffects::oneHitKO: + return new GameInteractionEffect::OneHitKO(); + case GameInteractorBuiltInEffects::modifyDefenseModifier: { + auto effect = new GameInteractionEffect::ModifyDefenseModifier(); + effect->parameters[0] = payload["parameter"].get(); + return effect; + } + case GameInteractorBuiltInEffects::giveOrTakeShield: { + auto effect = new GameInteractionEffect::GiveOrTakeShield(); + effect->parameters[0] = payload["parameter"].get(); + return effect; + } + default: + return nullptr; + } +} + +#endif diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_BuiltIn.h b/soh/soh/Enhancements/game-interactor/GameInteractor_BuiltIn.h new file mode 100644 index 00000000000..15aaf394325 --- /dev/null +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_BuiltIn.h @@ -0,0 +1,69 @@ +#ifdef ENABLE_REMOTE_CONTROL + +#ifdef __cplusplus +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "./GameInteractor.h" + +typedef enum { + modifyHeartContainers, + fillMagic, + emptyMagic, + modifyRupees, + noUI, + modifyGravity, + modifyHealth, + setPlayerHealth, + freezePlayer, + burnPlayer, + electrocutePlayer, + knockbackPlayer, + modifyLinkSize, + invisibleLink, + pacifistMode, + disableZTargeting, + weatherRainstorm, + reverseControls, + forceEquipBoots, + modifyRunSpeedModifier, + oneHitKO, + modifyDefenseModifier, + giveOrTakeShield, + teleportPlayer, + clearAssignedButtons, + setTimeOfDay, + setCollisionViewer, + setCosmeticsColor, + randomizeCosmetics, + pressButton, + pressRandomButton, + addOrTakeAmmo, + randomBombFuseTimer, + disableLedgeGrabs, + randomWind, + randomBonks, + playerInvincibility, + slipperyFloor, +} GameInteractorBuiltInEffects; + +class GameInteractorBuiltIn { + private: + bool isEnabled; + + void HandleRemoteJson(nlohmann::json payload); + GameInteractionEffectBase* EffectFromJson(std::string name, nlohmann::json payload); + public: + static GameInteractorBuiltIn* Instance; + void Enable(); + void Disable(); +}; +#endif +#endif diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Remote.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Remote.cpp new file mode 100644 index 00000000000..3737d3779b0 --- /dev/null +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Remote.cpp @@ -0,0 +1,184 @@ +#ifdef ENABLE_REMOTE_CONTROL + +#include "GameInteractor.h" +#include +#include +#include +#include +#include +#include +#include + +// MARK: - Remote + +void GameInteractor::EnableRemoteInteractor() { + if (isRemoteInteractorEnabled) { + return; + } + + if (SDLNet_ResolveHost(&remoteIP, CVarGetString("gRemote.IP", "127.0.0.1"), CVarGetInteger("gRemote.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, currently used by CrowdControl + * + * 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 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 newline character (\n). + */ +void GameInteractor::RegisterRemoteJsonHandler(std::function method) { + remoteJsonHandler = method; +} + +void GameInteractor::RegisterRemoteConnectedHandler(std::function method) { + remoteConnectedHandler = method; +} + +void GameInteractor::RegisterRemoteDisconnectedHandler(std::function 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)); +} + +// Appends a newline character to the end of the json payload and sends it to the remote +void GameInteractor::TransmitJsonToRemote(nlohmann::json payload) { + std::string payloadStr = payload.dump(); + payloadStr += "\n"; + TransmitDataToRemote(payloadStr.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("\n"); + while (delimiterPos != std::string::npos) { + // Extract the complete packet until the delimiter + std::string packet = receivedData.substr(0, delimiterPos); + // Remove the packet (including the delimiter) from the received data + receivedData.erase(0, delimiterPos + 1); + HandleRemoteJson(packet); + // Find the next delimiter + delimiterPos = receivedData.find("\n"); + } + } + + 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 diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index e054686848d..78158b0bda0 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -75,9 +75,11 @@ #include "SohGui.hpp" #include "ActorDB.h" -#ifdef ENABLE_CROWD_CONTROL +#ifdef ENABLE_REMOTE_CONTROL #include "Enhancements/crowd-control/CrowdControl.h" +#include "Enhancements/game-interactor/GameInteractor_BuiltIn.h" CrowdControl* CrowdControl::Instance; +GameInteractorBuiltIn* GameInteractorBuiltIn::Instance; #endif #include "Enhancements/mods.h" @@ -771,7 +773,12 @@ extern "C" void InitOTR() { SpeechSynthesizer::Instance = new SAPISpeechSynthesizer(); SpeechSynthesizer::Instance->Init(); #endif - + +#ifdef ENABLE_REMOTE_CONTROL + CrowdControl::Instance = new CrowdControl(); + GameInteractorBuiltIn::Instance = new GameInteractorBuiltIn(); +#endif + clearMtx = (uintptr_t)&gMtxClear; OTRMessage_Init(); OTRAudio_Init(); @@ -791,13 +798,17 @@ extern "C" void InitOTR() { } srand(now); -#ifdef ENABLE_CROWD_CONTROL - CrowdControl::Instance = new CrowdControl(); - CrowdControl::Instance->Init(); - if (CVarGetInteger("gCrowdControl", 0)) { - CrowdControl::Instance->Enable(); - } else { - CrowdControl::Instance->Disable(); +#ifdef ENABLE_REMOTE_CONTROL + SDLNet_Init(); + if (CVarGetInteger("gRemote.Enabled", 0)) { + switch (CVarGetInteger("gRemote.Scheme", GI_SCHEME_BUILT_IN)) { + case GI_SCHEME_BUILT_IN: + GameInteractorBuiltIn::Instance->Enable(); + break; + case GI_SCHEME_CROWD_CONTROL: + CrowdControl::Instance->Enable(); + break; + } } #endif @@ -813,9 +824,18 @@ extern "C" void SaveManager_ThreadPoolWait() { extern "C" void DeinitOTR() { SaveManager_ThreadPoolWait(); OTRAudio_Exit(); -#ifdef ENABLE_CROWD_CONTROL - CrowdControl::Instance->Disable(); - CrowdControl::Instance->Shutdown(); +#ifdef ENABLE_REMOTE_CONTROL + if (CVarGetInteger("gRemote.Enabled", 0)) { + switch (CVarGetInteger("gRemote.Scheme", GI_SCHEME_BUILT_IN)) { + case GI_SCHEME_BUILT_IN: + GameInteractorBuiltIn::Instance->Disable(); + break; + case GI_SCHEME_CROWD_CONTROL: + CrowdControl::Instance->Disable(); + break; + } + } + SDLNet_Quit(); #endif // Destroying gui here because we have shared ptrs to LUS objects which output to SPDLOG which is destroyed before these shared ptrs. diff --git a/soh/soh/SohGui.cpp b/soh/soh/SohGui.cpp index 73b006df997..77fe2f0a6dc 100644 --- a/soh/soh/SohGui.cpp +++ b/soh/soh/SohGui.cpp @@ -31,8 +31,9 @@ #include "soh/resource/type/Skeleton.h" #include "libultraship/libultraship.h" -#ifdef ENABLE_CROWD_CONTROL +#ifdef ENABLE_REMOTE_CONTROL #include "Enhancements/crowd-control/CrowdControl.h" +#include "Enhancements/game-interactor/GameInteractor_BuiltIn.h" #endif #include "Enhancements/game-interactor/GameInteractor.h" diff --git a/soh/soh/SohMenuBar.cpp b/soh/soh/SohMenuBar.cpp index 313bf403636..4ecbb5c0c18 100644 --- a/soh/soh/SohMenuBar.cpp +++ b/soh/soh/SohMenuBar.cpp @@ -1,5 +1,6 @@ #include "SohMenuBar.h" #include "ImGui/imgui.h" +#include "regex" #include "public/bridge/consolevariablebridge.h" #include #include "UIWidgets.hpp" @@ -10,8 +11,9 @@ #include "soh/Enhancements/presets.h" #include "soh/Enhancements/mods.h" #include "Enhancements/cosmetics/authenticGfxPatches.h" -#ifdef ENABLE_CROWD_CONTROL +#ifdef ENABLE_REMOTE_CONTROL #include "Enhancements/crowd-control/CrowdControl.h" +#include "Enhancements/game-interactor/GameInteractor_BuiltIn.h" #endif @@ -1044,17 +1046,6 @@ void DrawEnhancementsMenu() { UIWidgets::Spacer(0); if (ImGui::BeginMenu("Extra Modes")) { - #ifdef ENABLE_CROWD_CONTROL - if (UIWidgets::PaddedEnhancementCheckbox("Crowd Control", "gCrowdControl", false, false)) { - if (CVarGetInteger("gCrowdControl", 0)) { - CrowdControl::Instance->Enable(); - } else { - CrowdControl::Instance->Disable(); - } - } - UIWidgets::Tooltip("Will attempt to connect to the Crowd Control server. Check out crowdcontrol.live for more information."); - #endif - UIWidgets::PaddedText("Mirrored World", true, false); if (UIWidgets::EnhancementCombobox("gMirroredWorldMode", mirroredWorldModes, MIRRORED_WORLD_OFF) && gPlayState != NULL) { UpdateMirrorModeState(gPlayState->sceneNum); @@ -1344,6 +1335,105 @@ 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("gRemote.IP", "127.0.0.1"); + static uint16_t port = CVarGetInteger("gRemote.Port", 43384); + bool isFormValid = !isStringEmpty(CVarGetString("gRemote.IP", "127.0.0.1")) && port > 1024 && port < 65535; + + const char* remoteOptions[2] = { "Built-in", "Crowd Control"}; + + ImGui::BeginDisabled(GameInteractor::Instance->isRemoteInteractorEnabled); + ImGui::Text("Remote Interaction Scheme"); + if (UIWidgets::EnhancementCombobox("gRemote.Scheme", remoteOptions, GI_SCHEME_BUILT_IN)) { + switch (CVarGetInteger("gRemote.Scheme", GI_SCHEME_BUILT_IN)) { + case GI_SCHEME_BUILT_IN: + case GI_SCHEME_CROWD_CONTROL: + CVarSetString("gRemote.IP", "127.0.0.1"); + CVarSetInteger("gRemote.Port", 43384); + ip = "127.0.0.1"; + port = 43384; + break; + } + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + } + + ImGui::Text("Remote IP & Port"); + if (ImGui::InputText("##gRemote.IP", (char*)ip.c_str(), ip.capacity() + 1)) { + CVarSetString("gRemote.IP", ip.c_str()); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + } + + ImGui::SameLine(); + ImGui::PushItemWidth(ImGui::GetFontSize() * 5); + if (ImGui::InputScalar("##gRemote.Port", ImGuiDataType_U16, &port)) { + CVarSetInteger("gRemote.Port", port); + LUS::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) { + CVarSetInteger("gRemote.Enabled", 0); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + switch (CVarGetInteger("gRemote.Scheme", GI_SCHEME_BUILT_IN)) { + case GI_SCHEME_BUILT_IN: + GameInteractorBuiltIn::Instance->Disable(); + break; + case GI_SCHEME_CROWD_CONTROL: + CrowdControl::Instance->Disable(); + break; + } + } else { + CVarSetInteger("gRemote.Enabled", 1); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + switch (CVarGetInteger("gRemote.Scheme", GI_SCHEME_BUILT_IN)) { + case GI_SCHEME_BUILT_IN: + GameInteractorBuiltIn::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)); + ImGui::EndMenu(); + } +} +#endif + extern std::shared_ptr mRandomizerSettingsWindow; extern std::shared_ptr mItemTrackerWindow; extern std::shared_ptr mItemTrackerSettingsWindow; @@ -1482,6 +1572,12 @@ void SohMenuBar::DrawElement() { ImGui::SetCursorPosY(0.0f); + #ifdef ENABLE_REMOTE_CONTROL + DrawRemoteControlMenu(); + + ImGui::SetCursorPosY(0.0f); + #endif + DrawRandomizerMenu(); ImGui::PopStyleVar(1);