Remote GI Work (#3073)

Co-authored-by: David Chavez <david@dcvz.io>
This commit is contained in:
Garrett Cox 2023-12-17 19:41:33 +00:00 committed by GitHub
parent a6b4e0b7fd
commit 86044a1c50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1155 additions and 356 deletions

View File

@ -32,17 +32,6 @@ jobs:
make -j 10
sudo make install
sudo cp -av /usr/local/lib/libSDL* /lib/x86_64-linux-gnu/
- name: Install latest SDL_net
if: ${{ !vars.LINUX_RUNNER }}
run: |
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
wget https://www.libsdl.org/projects/SDL_net/release/SDL2_net-2.2.0.tar.gz
tar -xzf SDL2_net-2.2.0.tar.gz
cd SDL2_net-2.2.0
./configure
make -j 10
sudo make install
sudo cp -av /usr/local/lib/libSDL* /lib/x86_64-linux-gnu/
- name: Generate soh.otr
run: |
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
@ -102,7 +91,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 +160,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)
@ -297,7 +286,7 @@ jobs:
VCPKG_ROOT: ${{github.workspace}}/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

View File

@ -1 +1 @@
libsdl2 +universal libpng +universal glew +universal
libsdl2 +universal libsdl2_net +universal libpng +universal glew +universal

View File

@ -13,12 +13,6 @@ set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT soh)
add_compile_options($<$<CXX_COMPILER_ID:MSVC>:/MP>)
add_compile_options($<$<CXX_COMPILER_ID:MSVC>:/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)

View File

@ -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")
"$<$<CONFIG:Release>:"
"NDEBUG"
">"
"$<$<BOOL:${BUILD_CROWD_CONTROL}>:ENABLE_CROWD_CONTROL>"
"$<$<BOOL:${BUILD_REMOTE_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")
"$<$<CONFIG:Release>:"
"NDEBUG"
">"
"$<$<BOOL:${BUILD_CROWD_CONTROL}>:ENABLE_CROWD_CONTROL>"
"$<$<BOOL:${BUILD_REMOTE_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;"
"$<$<BOOL:${BUILD_CROWD_CONTROL}>:SDL2_net::SDL2_net-static>"
"$<$<BOOL:${BUILD_REMOTE_CONTROL}>:SDL2_net::SDL2_net-static>"
"glfw;"
"winmm;"
"imm32;"
@ -742,7 +742,7 @@ else()
"ZAPDUtils;"
"ZAPDLib;"
SDL2::SDL2
"$<$<BOOL:${BUILD_CROWD_CONTROL}>:SDL2_net::SDL2_net>"
"$<$<BOOL:${BUILD_REMOTE_CONTROL}>:SDL2_net::SDL2_net>"
${CMAKE_DL_LIBS}
Threads::Threads
)

View File

@ -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->RegisterRemoteJsonHandler([&](nlohmann::json payload) {
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(nlohmann::json payload) {
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 (!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;
}
if (socketsReady == 0) {
continue;
}
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<RemovableGameInteractionEffect*>(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,9 @@ 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);
SPDLOG_INFO("[CrowdControl] Sending payload:\n{}", payload.dump());
GameInteractor::Instance->TransmitJsonToRemote(payload);
}
CrowdControl::EffectResult CrowdControl::ExecuteEffect(Effect* effect) {
@ -229,13 +177,14 @@ CrowdControl::EffectResult CrowdControl::TranslateGiEnum(GameInteractionEffectQu
return result;
}
CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) {
nlohmann::json dataReceived = nlohmann::json::parse(payload, nullptr, false);
if (dataReceived.is_discarded()) {
SPDLOG_ERROR("Error parsing JSON");
CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) {
if (!dataReceived.contains("id") || !dataReceived.contains("type")) {
SPDLOG_ERROR("[CrowdControl] Invalid payload received:\n{}", dataReceived);
return nullptr;
}
SPDLOG_INFO("[CrowdControl] Received payload:\n{}", dataReceived.dump());
Effect* effect = new Effect();
effect->lastExecutionResult = EffectResult::Initiate;
effect->id = dataReceived["id"];
@ -333,13 +282,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<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = -2;
break;
case kEffectOneHitKo:
effect->category = kEffectCatDamageTaken;
@ -356,37 +305,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<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(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] = EQUIP_VALUE_BOOTS_IRON;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = EQUIP_VALUE_BOOTS_IRON;
break;
case kEffectForceHoverBoots:
effect->category = kEffectCatBoots;
effect->timeRemaining = 30000;
effect->giEffect = new GameInteractionEffect::ForceEquipBoots();
effect->giEffect->parameters[0] = EQUIP_VALUE_BOOTS_HOVER;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = EQUIP_VALUE_BOOTS_HOVER;
break;
case kEffectSlipperyFloor:
effect->category = kEffectCatSlipperyFloor;
@ -412,23 +361,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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter * -1;
break;
case kEffectFillHeart:
effect->giEffect = new GameInteractionEffect::ModifyHealth();
effect->giEffect->parameters[0] = receivedParameter;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter;
break;
case kEffectKnockbackLinkWeak:
effect->giEffect = new GameInteractionEffect::KnockbackPlayer();
effect->giEffect->parameters[0] = 1;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = 1;
break;
case kEffectKnockbackLinkStrong:
effect->giEffect = new GameInteractionEffect::KnockbackPlayer();
effect->giEffect->parameters[0] = 3;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = 3;
break;
case kEffectKnockbackLinkMega:
effect->giEffect = new GameInteractionEffect::KnockbackPlayer();
effect->giEffect->parameters[0] = 6;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = 6;
break;
case kEffectBurnLink:
effect->giEffect = new GameInteractionEffect::BurnPlayer();
@ -441,109 +390,109 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) {
break;
case kEffectKillLink:
effect->giEffect = new GameInteractionEffect::SetPlayerHealth();
effect->giEffect->parameters[0] = 0;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = 0;
break;
// Give Items and Consumables
case kEffectAddHeartContainer:
effect->giEffect = new GameInteractionEffect::ModifyHeartContainers();
effect->giEffect->parameters[0] = 1;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter;
break;
case kEffectGiveDekuShield:
effect->giEffect = new GameInteractionEffect::GiveOrTakeShield();
effect->giEffect->parameters[0] = ITEM_SHIELD_DEKU;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = ITEM_SHIELD_DEKU;
break;
case kEffectGiveHylianShield:
effect->giEffect = new GameInteractionEffect::GiveOrTakeShield();
effect->giEffect->parameters[0] = ITEM_SHIELD_HYLIAN;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter * -1;
break;
case kEffectTakeDekuShield:
effect->giEffect = new GameInteractionEffect::GiveOrTakeShield();
effect->giEffect->parameters[0] = -ITEM_SHIELD_DEKU;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = -ITEM_SHIELD_DEKU;
break;
case kEffectTakeHylianShield:
effect->giEffect = new GameInteractionEffect::GiveOrTakeShield();
effect->giEffect->parameters[0] = -ITEM_SHIELD_HYLIAN;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter * -1;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter * -1;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter * -1;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter * -1;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter * -1;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter * -1;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = ITEM_BOMBCHU;
break;
// Link Size Modifiers
@ -551,25 +500,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<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_LINK_SIZE_SQUISHED;
break;
case kEffectInvisibleLink:
effect->category = kEffectCatLinkSize;
@ -585,11 +534,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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_TIMEOFDAY_DAWN;
break;
case kEffectSetTimeToDusk:
effect->giEffect = new GameInteractionEffect::SetTimeOfDay();
effect->giEffect->parameters[0] = GI_TIMEOFDAY_DUSK;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_TIMEOFDAY_DUSK;
break;
// Visual Effects
@ -632,186 +581,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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = 30;
break;
case kEffectClearCbuttons:
effect->giEffect = new GameInteractionEffect::ClearAssignedButtons();
effect->giEffect->parameters[0] = GI_BUTTONS_CBUTTONS;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_BUTTONS_CBUTTONS;
break;
case kEffectClearDpad:
effect->giEffect = new GameInteractionEffect::ClearAssignedButtons();
effect->giEffect->parameters[0] = GI_BUTTONS_DPAD;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_BLACK;
break;
default:

View File

@ -1,4 +1,4 @@
#ifdef ENABLE_CROWD_CONTROL
#ifdef ENABLE_REMOTE_CONTROL
#ifndef _CROWDCONTROL_C
#define _CROWDCONTROL_C
@ -73,33 +73,24 @@ class CrowdControl {
EffectResult lastExecutionResult;
} Effect;
std::thread ccThreadReceive;
std::thread ccThreadProcess;
TCPsocket tcpsock;
IPaddress ip;
bool isEnabled;
bool connected;
char received[512];
std::vector<Effect*> activeEffects;
std::mutex activeEffectsMutex;
void ListenToServer();
void HandleRemoteData(nlohmann::json payload);
void ProcessActiveEffects();
void EmitMessage(TCPsocket socket, uint32_t eventId, long timeRemaining, EffectResult status);
Effect* ParseMessage(char payload[512]);
void EmitMessage(uint32_t eventId, long timeRemaining, EffectResult status);
Effect* ParseMessage(nlohmann::json payload);
EffectResult ExecuteEffect(Effect* effect);
EffectResult CanApplyEffect(Effect *effect);
EffectResult TranslateGiEnum(GameInteractionEffectQueryResult giResult);
public:
static CrowdControl* Instance;
void Init();
void Shutdown();
void Enable();
void Disable();
};

View File

@ -99,7 +99,7 @@ static bool ActorSpawnHandler(std::shared_ptr<LUS::Console> Console, const std::
static bool KillPlayerHandler(std::shared_ptr<LUS::Console> Console, const std::vector<std::string>&, std::string* output) {
GameInteractionEffectBase* effect = new GameInteractionEffect::SetPlayerHealth();
effect->parameters[0] = 0;
dynamic_cast<ParameterizedGameInteractionEffect*>(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<LUS::Console> Console, const
}
GameInteractionEffectBase* effect = new GameInteractionEffect::SetPlayerHealth();
effect->parameters[0] = health;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect)->parameters[0] = health;
GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect);
if (result == GameInteractionEffectQueryResult::Possible) {
INFO_MESSAGE("[SOH] Player health updated to %d", health);
@ -247,8 +247,8 @@ static bool AddAmmoHandler(std::shared_ptr<LUS::Console> Console, const std::vec
}
GameInteractionEffectBase* effect = new GameInteractionEffect::AddOrTakeAmmo();
effect->parameters[0] = amount;
effect->parameters[1] = it->second;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect)->parameters[0] = amount;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect)->parameters[1] = it->second;
GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect);
if (result == GameInteractionEffectQueryResult::Possible) {
@ -287,8 +287,8 @@ static bool TakeAmmoHandler(std::shared_ptr<LUS::Console> Console, const std::ve
}
GameInteractionEffectBase* effect = new GameInteractionEffect::AddOrTakeAmmo();
effect->parameters[0] = -amount;
effect->parameters[1] = it->second;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect)->parameters[0] = -amount;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect)->parameters[1] = it->second;
GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect);
if (result == GameInteractionEffectQueryResult::Possible) {
@ -577,7 +577,7 @@ static bool InvisibleHandler(std::shared_ptr<LUS::Console> 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) {
@ -604,8 +604,8 @@ static bool GiantLinkHandler(std::shared_ptr<LUS::Console> 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<ParameterizedGameInteractionEffect*>(effect)->parameters[0] = GI_LINK_SIZE_GIANT;
GameInteractionEffectQueryResult result =
state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect);
if (result == GameInteractionEffectQueryResult::Possible) {
@ -632,8 +632,8 @@ static bool MinishLinkHandler(std::shared_ptr<LUS::Console> Console, const std::
return 1;
}
GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyLinkSize();
effect->parameters[0] = GI_LINK_SIZE_MINISH;
RemovableGameInteractionEffect* effect = new GameInteractionEffect::ModifyLinkSize();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect)->parameters[0] = GI_LINK_SIZE_MINISH;
GameInteractionEffectQueryResult result =
state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect);
if (result == GameInteractionEffectQueryResult::Possible) {
@ -666,7 +666,7 @@ static bool AddHeartContainerHandler(std::shared_ptr<LUS::Console> Console, cons
}
GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyHeartContainers();
effect->parameters[0] = hearts;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect)->parameters[0] = hearts;
GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect);
if (result == GameInteractionEffectQueryResult::Possible) {
INFO_MESSAGE("[SOH] Added %d heart containers", hearts);
@ -697,7 +697,7 @@ static bool RemoveHeartContainerHandler(std::shared_ptr<LUS::Console> Console, c
}
GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyHeartContainers();
effect->parameters[0] = -hearts;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect)->parameters[0] = -hearts;
GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect);
if (result == GameInteractionEffectQueryResult::Possible) {
INFO_MESSAGE("[SOH] Removed %d heart containers", hearts);
@ -717,7 +717,7 @@ static bool GravityHandler(std::shared_ptr<LUS::Console> 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<ParameterizedGameInteractionEffect*>(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;
@ -747,7 +747,7 @@ static bool NoUIHandler(std::shared_ptr<LUS::Console> 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);
@ -782,7 +782,7 @@ static bool DefenseModifierHandler(std::shared_ptr<LUS::Console> Console, const
GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyDefenseModifier();
try {
effect->parameters[0] = std::stoi(args[1], nullptr, 10);
dynamic_cast<ParameterizedGameInteractionEffect*>(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;
@ -790,7 +790,7 @@ static bool DefenseModifierHandler(std::shared_ptr<LUS::Console> 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<ParameterizedGameInteractionEffect*>(effect)->parameters[0]);
return 0;
} else {
INFO_MESSAGE("[SOH] Command failed: Could not set defense modifier.");
@ -812,7 +812,7 @@ static bool DamageHandler(std::shared_ptr<LUS::Console> Console, const std::vect
return 1;
}
effect->parameters[0] = -value;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect)->parameters[0] = -value;
} catch (std::invalid_argument const& ex) {
ERROR_MESSAGE("[SOH] Damage value must be a number.");
return 1;
@ -842,7 +842,7 @@ static bool HealHandler(std::shared_ptr<LUS::Console> Console, const std::vector
return 1;
}
effect->parameters[0] = value;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect)->parameters[0] = value;
} catch (std::invalid_argument const& ex) {
ERROR_MESSAGE("[SOH] Damage value must be a number.");
return 1;
@ -898,7 +898,7 @@ static bool NoZHandler(std::shared_ptr<LUS::Console> 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);
@ -926,7 +926,7 @@ static bool OneHitKOHandler(std::shared_ptr<LUS::Console> 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);
@ -954,7 +954,7 @@ static bool PacifistHandler(std::shared_ptr<LUS::Console> 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);
@ -982,8 +982,8 @@ static bool PaperLinkHandler(std::shared_ptr<LUS::Console> 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<ParameterizedGameInteractionEffect*>(effect)->parameters[0] = GI_LINK_SIZE_PAPER;
GameInteractionEffectQueryResult result =
state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect);
@ -1011,7 +1011,7 @@ static bool RainstormHandler(std::shared_ptr<LUS::Console> 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);
@ -1039,7 +1039,7 @@ static bool ReverseControlsHandler(std::shared_ptr<LUS::Console> Console, const
return 1;
}
GameInteractionEffectBase* effect = new GameInteractionEffect::ReverseControls();
RemovableGameInteractionEffect* effect = new GameInteractionEffect::ReverseControls();
GameInteractionEffectQueryResult result =
state ? GameInteractor::ApplyEffect(effect) : GameInteractor::RemoveEffect(effect);
@ -1062,7 +1062,7 @@ static bool UpdateRupeesHandler(std::shared_ptr<LUS::Console> Console, const std
GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyRupees();
try {
effect->parameters[0] = std::stoi(args[1], nullptr, 10);
dynamic_cast<ParameterizedGameInteractionEffect*>(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;
@ -1086,7 +1086,7 @@ static bool SpeedModifierHandler(std::shared_ptr<LUS::Console> Console, const st
GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyRunSpeedModifier();
try {
effect->parameters[0] = std::stoi(args[1], nullptr, 10);
dynamic_cast<ParameterizedGameInteractionEffect*>(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;
@ -1121,7 +1121,7 @@ static bool BootsHandler(std::shared_ptr<LUS::Console> Console, const std::vecto
}
GameInteractionEffectBase* effect = new GameInteractionEffect::ForceEquipBoots();
effect->parameters[0] = it->second;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect)->parameters[0] = it->second;
GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect);
if (result == GameInteractionEffectQueryResult::Possible) {
@ -1152,7 +1152,7 @@ static bool GiveShieldHandler(std::shared_ptr<LUS::Console> Console, const std::
}
GameInteractionEffectBase* effect = new GameInteractionEffect::GiveOrTakeShield();
effect->parameters[0] = it->second;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect)->parameters[0] = it->second;
GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect);
if (result == GameInteractionEffectQueryResult::Possible) {
@ -1177,7 +1177,7 @@ static bool TakeShieldHandler(std::shared_ptr<LUS::Console> Console, const std::
}
GameInteractionEffectBase* effect = new GameInteractionEffect::GiveOrTakeShield();
effect->parameters[0] = it->second * -1;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect)->parameters[0] = it->second * -1;
GameInteractionEffectQueryResult result = GameInteractor::ApplyEffect(effect);
if (result == GameInteractionEffectQueryResult::Possible) {
@ -1203,7 +1203,7 @@ static bool KnockbackHandler(std::shared_ptr<LUS::Console> Console, const std::v
return 1;
}
effect->parameters[0] = value;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect)->parameters[0] = value;
} catch (std::invalid_argument const& ex) {
ERROR_MESSAGE("[SOH] Knockback value must be a number.");
return 1;

View File

@ -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;

View File

@ -15,38 +15,46 @@ enum GameInteractionEffectQueryResult {
class GameInteractionEffectBase {
public:
virtual GameInteractionEffectQueryResult CanBeApplied() = 0;
virtual GameInteractionEffectQueryResult CanBeRemoved();
GameInteractionEffectQueryResult Apply();
GameInteractionEffectQueryResult Remove();
int32_t parameters[3];
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 SetSceneFlag: public GameInteractionEffectBase {
class SetSceneFlag: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect {
GameInteractionEffectQueryResult CanBeApplied() override;
void _Apply() override;
};
class UnsetSceneFlag: public GameInteractionEffectBase {
class UnsetSceneFlag: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect {
GameInteractionEffectQueryResult CanBeApplied() override;
void _Apply() override;
};
class SetFlag: public GameInteractionEffectBase {
class SetFlag: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect {
GameInteractionEffectQueryResult CanBeApplied() override;
void _Apply() override;
};
class UnsetFlag: public GameInteractionEffectBase {
class UnsetFlag: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect {
GameInteractionEffectQueryResult CanBeApplied() override;
void _Apply() override;
};
class ModifyHeartContainers: public GameInteractionEffectBase {
class ModifyHeartContainers: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect {
GameInteractionEffectQueryResult CanBeApplied() override;
void _Apply() override;
};
@ -61,29 +69,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;
};
@ -103,98 +111,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;
};
@ -204,52 +212,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;

View File

@ -31,7 +31,7 @@ GameInteractionEffectQueryResult GameInteractor::ApplyEffect(GameInteractionEffe
return effect->Apply();
}
GameInteractionEffectQueryResult GameInteractor::RemoveEffect(GameInteractionEffectBase* effect) {
GameInteractionEffectQueryResult GameInteractor::RemoveEffect(RemovableGameInteractionEffect* effect) {
return effect->Remove();
}

View File

@ -7,6 +7,11 @@
#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,
@ -92,10 +97,15 @@ void GameInteractor_SetTriforceHuntCreditsWarpActive(uint8_t state);
#ifdef __cplusplus
#include <thread>
#include <vector>
#include <functional>
#ifdef ENABLE_REMOTE_CONTROL
#include <SDL2/SDL_net.h>
#include <nlohmann/json.hpp>
#endif
#define DEFINE_HOOK(name, type) \
struct name { \
typedef std::function<type> fn; \
@ -132,10 +142,24 @@ 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);
static GameInteractionEffectQueryResult RemoveEffect(GameInteractionEffectBase* effect);
static GameInteractionEffectQueryResult RemoveEffect(RemovableGameInteractionEffect* effect);
// Game Hooks
template <typename H> struct RegisteredGameHooks { inline static std::vector<typename H::fn> functions; };
@ -238,6 +262,21 @@ 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
};
#endif /* __cplusplus */

View File

@ -0,0 +1,182 @@
#ifdef ENABLE_REMOTE_CONTROL
#include "GameInteractor.h"
#include <spdlog/spdlog.h>
#include <ImGui/imgui.h>
#include <ImGui/imgui_internal.h>
#include <unordered_map>
#include <tuple>
#include <type_traits>
#include <libultraship/libultraship.h>
// 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
*
* 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

View File

@ -0,0 +1,471 @@
#ifdef ENABLE_REMOTE_CONTROL
#include "GameInteractor_Sail.h"
#include <libultraship/bridge.h>
#include <libultraship/libultraship.h>
#include <nlohmann/json.hpp>
template <class DstType, class SrcType>
bool IsType(const SrcType* src) {
return dynamic_cast<const DstType*>(src) != nullptr;
}
void GameInteractorSail::Enable() {
if (isEnabled) {
return;
}
isEnabled = true;
GameInteractor::Instance->EnableRemoteInteractor();
GameInteractor::Instance->RegisterRemoteJsonHandler([&](nlohmann::json payload) {
HandleRemoteJson(payload);
});
GameInteractor::Instance->RegisterRemoteConnectedHandler([&]() {
RegisterHooks();
});
}
void GameInteractorSail::Disable() {
if (!isEnabled) {
return;
}
isEnabled = false;
GameInteractor::Instance->DisableRemoteInteractor();
}
void GameInteractorSail::HandleRemoteJson(nlohmann::json payload) {
SPDLOG_INFO("[GameInteractorSail] Received payload: \n{}", payload.dump());
nlohmann::json responsePayload;
responsePayload["type"] = "result";
responsePayload["status"] = "failure";
try {
if (!payload.contains("id")) {
SPDLOG_ERROR("[GameInteractorSail] Received payload without ID");
GameInteractor::Instance->TransmitJsonToRemote(responsePayload);
return;
}
responsePayload["id"] = payload["id"];
if (!payload.contains("type")) {
SPDLOG_ERROR("[GameInteractorSail] Received payload without type");
GameInteractor::Instance->TransmitJsonToRemote(responsePayload);
return;
}
std::string payloadType = payload["type"].get<std::string>();
if (payloadType == "command") {
if (!payload.contains("command")) {
SPDLOG_ERROR("[GameInteractorSail] Received command payload without command");
GameInteractor::Instance->TransmitJsonToRemote(responsePayload);
return;
}
std::string command = payload["command"].get<std::string>();
std::reinterpret_pointer_cast<LUS::ConsoleWindow>(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->Dispatch(command);
responsePayload["status"] = "success";
GameInteractor::Instance->TransmitJsonToRemote(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);
return;
}
std::string effectType = payload["effect"]["type"].get<std::string>();
// Special case for "command" effect, so we can also run commands from the `simple_twitch_sail` script
if (effectType == "command") {
if (!payload["effect"].contains("command")) {
SPDLOG_ERROR("[GameInteractorSail] Received command effect payload without command");
GameInteractor::Instance->TransmitJsonToRemote(responsePayload);
return;
}
std::string command = payload["effect"]["command"].get<std::string>();
std::reinterpret_pointer_cast<LUS::ConsoleWindow>(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->Dispatch(command);
responsePayload["status"] = "success";
GameInteractor::Instance->TransmitJsonToRemote(responsePayload);
return;
}
if (effectType != "apply" && effectType != "remove") {
SPDLOG_ERROR("[GameInteractorSail] Received effect payload with unknown effect type: {}", effectType);
GameInteractor::Instance->TransmitJsonToRemote(responsePayload);
return;
}
if (!GameInteractor::IsSaveLoaded()) {
responsePayload["status"] = "try_again";
GameInteractor::Instance->TransmitJsonToRemote(responsePayload);
return;
}
GameInteractionEffectBase* giEffect = EffectFromJson(payload["effect"]);
if (giEffect) {
GameInteractionEffectQueryResult result;
if (effectType == "remove") {
if (IsType<RemovableGameInteractionEffect>(giEffect)) {
result = dynamic_cast<RemovableGameInteractionEffect*>(giEffect)->Remove();
} else {
result = GameInteractionEffectQueryResult::NotPossible;
}
} else {
result = giEffect->Apply();
}
if (result == GameInteractionEffectQueryResult::Possible) {
responsePayload["status"] = "success";
} else if (result == GameInteractionEffectQueryResult::TemporarilyNotPossible) {
responsePayload["status"] = "try_again";
}
GameInteractor::Instance->TransmitJsonToRemote(responsePayload);
return;
}
} else {
SPDLOG_ERROR("[GameInteractorSail] Unknown payload type: {}", payloadType);
GameInteractor::Instance->TransmitJsonToRemote(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);
} catch (const std::exception& e) {
SPDLOG_ERROR("[GameInteractorSail] Exception handling remote JSON: {}", e.what());
} catch (...) {
SPDLOG_ERROR("[GameInteractorSail] Unknown exception handling remote JSON");
}
}
GameInteractionEffectBase* GameInteractorSail::EffectFromJson(nlohmann::json payload) {
if (!payload.contains("name")) {
return nullptr;
}
std::string name = payload["name"].get<std::string>();
if (name == "SetSceneFlag") {
auto effect = new GameInteractionEffect::SetSceneFlag();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
effect->parameters[2] = payload["parameters"][2].get<int32_t>();
}
return effect;
} else if (name == "UnsetSceneFlag") {
auto effect = new GameInteractionEffect::UnsetSceneFlag();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
effect->parameters[2] = payload["parameters"][2].get<int32_t>();
}
return effect;
} else if (name == "SetFlag") {
auto effect = new GameInteractionEffect::SetFlag();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
}
return effect;
} else if (name == "UnsetFlag") {
auto effect = new GameInteractionEffect::UnsetFlag();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
}
return effect;
} else if (name == "ModifyHeartContainers") {
auto effect = new GameInteractionEffect::ModifyHeartContainers();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "FillMagic") {
return new GameInteractionEffect::FillMagic();
} else if (name == "EmptyMagic") {
return new GameInteractionEffect::EmptyMagic();
} else if (name == "ModifyRupees") {
auto effect = new GameInteractionEffect::ModifyRupees();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "NoUI") {
return new GameInteractionEffect::NoUI();
} else if (name == "ModifyGravity") {
auto effect = new GameInteractionEffect::ModifyGravity();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "ModifyHealth") {
auto effect = new GameInteractionEffect::ModifyHealth();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "SetPlayerHealth") {
auto effect = new GameInteractionEffect::SetPlayerHealth();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "FreezePlayer") {
return new GameInteractionEffect::FreezePlayer();
} else if (name == "BurnPlayer") {
return new GameInteractionEffect::BurnPlayer();
} else if (name == "ElectrocutePlayer") {
return new GameInteractionEffect::ElectrocutePlayer();
} else if (name == "KnockbackPlayer") {
auto effect = new GameInteractionEffect::KnockbackPlayer();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "ModifyLinkSize") {
auto effect = new GameInteractionEffect::ModifyLinkSize();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "InvisibleLink") {
return new GameInteractionEffect::InvisibleLink();
} else if (name == "PacifistMode") {
return new GameInteractionEffect::PacifistMode();
} else if (name == "DisableZTargeting") {
return new GameInteractionEffect::DisableZTargeting();
} else if (name == "WeatherRainstorm") {
return new GameInteractionEffect::WeatherRainstorm();
} else if (name == "ReverseControls") {
return new GameInteractionEffect::ReverseControls();
} else if (name == "ForceEquipBoots") {
auto effect = new GameInteractionEffect::ForceEquipBoots();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "ModifyRunSpeedModifier") {
auto effect = new GameInteractionEffect::ModifyRunSpeedModifier();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "OneHitKO") {
return new GameInteractionEffect::OneHitKO();
} else if (name == "ModifyDefenseModifier") {
auto effect = new GameInteractionEffect::ModifyDefenseModifier();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "GiveOrTakeShield") {
auto effect = new GameInteractionEffect::GiveOrTakeShield();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "TeleportPlayer") {
auto effect = new GameInteractionEffect::TeleportPlayer();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "ClearAssignedButtons") {
auto effect = new GameInteractionEffect::ClearAssignedButtons();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "SetTimeOfDay") {
auto effect = new GameInteractionEffect::SetTimeOfDay();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "SetCollisionViewer") {
return new GameInteractionEffect::SetCollisionViewer();
} else if (name == "SetCosmeticsColor") {
auto effect = new GameInteractionEffect::SetCosmeticsColor();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
}
return effect;
} else if (name == "RandomizeCosmetics") {
return new GameInteractionEffect::RandomizeCosmetics();
} else if (name == "PressButton") {
auto effect = new GameInteractionEffect::PressButton();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "PressRandomButton") {
auto effect = new GameInteractionEffect::PressRandomButton();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "AddOrTakeAmmo") {
auto effect = new GameInteractionEffect::AddOrTakeAmmo();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
}
return effect;
} else if (name == "RandomBombFuseTimer") {
return new GameInteractionEffect::RandomBombFuseTimer();
} else if (name == "DisableLedgeGrabs") {
return new GameInteractionEffect::DisableLedgeGrabs();
} else if (name == "RandomWind") {
return new GameInteractionEffect::RandomWind();
} else if (name == "RandomBonks") {
return new GameInteractionEffect::RandomBonks();
} else if (name == "PlayerInvincibility") {
return new GameInteractionEffect::PlayerInvincibility();
} else if (name == "SlipperyFloor") {
return new GameInteractionEffect::SlipperyFloor();
} else {
SPDLOG_INFO("[GameInteractorSail] Unknown effect name: {}", name);
return nullptr;
}
}
// Workaround until we have a way to unregister hooks
static bool hasRegisteredHooks = false;
void GameInteractorSail::RegisterHooks() {
if (hasRegisteredHooks) {
return;
}
hasRegisteredHooks = true;
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnTransitionEnd>([](int32_t sceneNum) {
if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return;
nlohmann::json payload;
payload["id"] = std::rand();
payload["type"] = "hook";
payload["hook"]["type"] = "OnTransitionEnd";
payload["hook"]["sceneNum"] = sceneNum;
GameInteractor::Instance->TransmitJsonToRemote(payload);
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnLoadGame>([](int32_t fileNum) {
if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return;
nlohmann::json payload;
payload["id"] = std::rand();
payload["type"] = "hook";
payload["hook"]["type"] = "OnLoadGame";
payload["hook"]["fileNum"] = fileNum;
GameInteractor::Instance->TransmitJsonToRemote(payload);
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnExitGame>([](int32_t fileNum) {
if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return;
nlohmann::json payload;
payload["id"] = std::rand();
payload["type"] = "hook";
payload["hook"]["type"] = "OnExitGame";
payload["hook"]["fileNum"] = fileNum;
GameInteractor::Instance->TransmitJsonToRemote(payload);
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnItemReceive>([](GetItemEntry itemEntry) {
if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return;
nlohmann::json payload;
payload["id"] = std::rand();
payload["type"] = "hook";
payload["hook"]["type"] = "OnItemReceive";
payload["hook"]["tableId"] = itemEntry.tableId;
payload["hook"]["getItemId"] = itemEntry.getItemId;
GameInteractor::Instance->TransmitJsonToRemote(payload);
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnEnemyDefeat>([](void* refActor) {
if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return;
Actor* actor = (Actor*)refActor;
nlohmann::json payload;
payload["id"] = std::rand();
payload["type"] = "hook";
payload["hook"]["type"] = "OnEnemyDefeat";
payload["hook"]["actorId"] = actor->id;
payload["hook"]["params"] = actor->params;
GameInteractor::Instance->TransmitJsonToRemote(payload);
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorInit>([](void* refActor) {
if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return;
Actor* actor = (Actor*)refActor;
nlohmann::json payload;
payload["id"] = std::rand();
payload["type"] = "hook";
payload["hook"]["type"] = "OnActorInit";
payload["hook"]["actorId"] = actor->id;
payload["hook"]["params"] = actor->params;
GameInteractor::Instance->TransmitJsonToRemote(payload);
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnFlagSet>([](int16_t flagType, int16_t flag) {
if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return;
nlohmann::json payload;
payload["id"] = std::rand();
payload["type"] = "hook";
payload["hook"]["type"] = "OnFlagSet";
payload["hook"]["flagType"] = flagType;
payload["hook"]["flag"] = flag;
GameInteractor::Instance->TransmitJsonToRemote(payload);
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnFlagUnset>([](int16_t flagType, int16_t flag) {
if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return;
nlohmann::json payload;
payload["id"] = std::rand();
payload["type"] = "hook";
payload["hook"]["type"] = "OnFlagUnset";
payload["hook"]["flagType"] = flagType;
payload["hook"]["flag"] = flag;
GameInteractor::Instance->TransmitJsonToRemote(payload);
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneFlagSet>([](int16_t sceneNum, int16_t flagType, int16_t flag) {
if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return;
nlohmann::json payload;
payload["id"] = std::rand();
payload["type"] = "hook";
payload["hook"]["type"] = "OnSceneFlagSet";
payload["hook"]["flagType"] = flagType;
payload["hook"]["flag"] = flag;
payload["hook"]["sceneNum"] = sceneNum;
GameInteractor::Instance->TransmitJsonToRemote(payload);
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneFlagUnset>([](int16_t sceneNum, int16_t flagType, int16_t flag) {
if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::IsSaveLoaded()) return;
nlohmann::json payload;
payload["id"] = std::rand();
payload["type"] = "hook";
payload["hook"]["type"] = "OnSceneFlagUnset";
payload["hook"]["flagType"] = flagType;
payload["hook"]["flag"] = flag;
payload["hook"]["sceneNum"] = sceneNum;
GameInteractor::Instance->TransmitJsonToRemote(payload);
});
}
#endif

View File

@ -0,0 +1,29 @@
#ifdef ENABLE_REMOTE_CONTROL
#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"
class GameInteractorSail {
private:
bool isEnabled;
void HandleRemoteJson(nlohmann::json payload);
GameInteractionEffectBase* EffectFromJson(nlohmann::json payload);
void RegisterHooks();
public:
static GameInteractorSail* Instance;
void Enable();
void Disable();
};
#endif
#endif

View File

@ -81,9 +81,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_Sail.h"
CrowdControl* CrowdControl::Instance;
GameInteractorSail* GameInteractorSail::Instance;
#endif
#include "Enhancements/mods.h"
@ -1097,7 +1099,12 @@ extern "C" void InitOTR() {
SpeechSynthesizer::Instance = new SAPISpeechSynthesizer();
SpeechSynthesizer::Instance->Init();
#endif
#ifdef ENABLE_REMOTE_CONTROL
CrowdControl::Instance = new CrowdControl();
GameInteractorSail::Instance = new GameInteractorSail();
#endif
clearMtx = (uintptr_t)&gMtxClear;
OTRMessage_Init();
OTRAudio_Init();
@ -1117,13 +1124,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_SAIL)) {
case GI_SCHEME_SAIL:
GameInteractorSail::Instance->Enable();
break;
case GI_SCHEME_CROWD_CONTROL:
CrowdControl::Instance->Enable();
break;
}
}
#endif
@ -1140,9 +1151,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_SAIL)) {
case GI_SCHEME_SAIL:
GameInteractorSail::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.

View File

@ -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_Sail.h"
#endif
#include "Enhancements/game-interactor/GameInteractor.h"

View File

@ -1,5 +1,6 @@
#include "SohMenuBar.h"
#include "ImGui/imgui.h"
#include "regex"
#include "public/bridge/consolevariablebridge.h"
#include <libultraship/libultraship.h>
#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_Sail.h"
#endif
@ -1116,17 +1118,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);
@ -1520,6 +1511,135 @@ 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] = { "Sail", "Crowd Control"};
ImGui::BeginDisabled(GameInteractor::Instance->isRemoteInteractorEnabled);
ImGui::Text("Remote Interaction Scheme");
if (UIWidgets::EnhancementCombobox("gRemote.Scheme", remoteOptions, GI_SCHEME_SAIL)) {
switch (CVarGetInteger("gRemote.Scheme", GI_SCHEME_SAIL)) {
case GI_SCHEME_SAIL:
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();
}
switch (CVarGetInteger("gRemote.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("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_SAIL)) {
case GI_SCHEME_SAIL:
GameInteractorSail::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_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));
ImGui::EndMenu();
}
}
#endif
extern std::shared_ptr<RandomizerSettingsWindow> mRandomizerSettingsWindow;
extern std::shared_ptr<ItemTrackerWindow> mItemTrackerWindow;
extern std::shared_ptr<ItemTrackerSettingsWindow> mItemTrackerSettingsWindow;
@ -1658,6 +1778,12 @@ void SohMenuBar::DrawElement() {
ImGui::SetCursorPosY(0.0f);
#ifdef ENABLE_REMOTE_CONTROL
DrawRemoteControlMenu();
ImGui::SetCursorPosY(0.0f);
#endif
DrawRandomizerMenu();
ImGui::PopStyleVar(1);