Merge branch 'develop' of garrettjoecox.github.com:HarbourMasters/Shipwright into develop-rando-develop

This commit is contained in:
Garrett Cox 2023-12-26 10:33:48 -06:00
commit 1979d78788
45 changed files with 2206 additions and 542 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

@ -5,20 +5,14 @@ set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard to use")
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version")
project(Ship VERSION 8.0.3 LANGUAGES C CXX)
set(PROJECT_BUILD_NAME "MacReady Delta" CACHE STRING "")
project(Ship VERSION 8.0.4 LANGUAGES C CXX)
set(PROJECT_BUILD_NAME "MacReady Echo" CACHE STRING "")
set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "")
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)

@ -1 +1 @@
Subproject commit 4600eedcc18f496319c99e07b8b2b4f11a0f6e64
Subproject commit 15d57d806e39d7f19783e26acc1a062d402169c7

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,8 @@ 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;"
"UNICODE;"
"_UNICODE"
STORMLIB_NO_AUTO_LINK
@ -455,7 +454,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 +688,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 +741,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

@ -4,6 +4,9 @@
#ifdef __cplusplus
#include <libultraship/libultraship.h>
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include <ImGui/imgui.h>
class AudioEditor : public LUS::GuiWindow {

View File

@ -7,6 +7,9 @@
#include <iterator>
#include <variables.h>
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include <ImGui/imgui.h>
#include <ImGui/imgui_internal.h>
#include <libultraship/bridge.h>
@ -225,6 +228,10 @@ namespace GameControlEditor {
window->BeginGroupPanelPublic("Aiming/First-Person Camera", ImGui::GetContentRegionAvail());
UIWidgets::PaddedEnhancementCheckbox("Right Stick Aiming", "gRightStickAiming");
DrawHelpIcon("Allows for aiming with the right stick in:\n-First-Person/C-Up view\n-Weapon Aiming");
if (CVarGetInteger("gRightStickAiming", 0)) {
UIWidgets::PaddedEnhancementCheckbox("Allow moving while in first person mode", "gMoveWhileFirstPerson");
DrawHelpIcon("Changes the left stick to move the player while in first person mode");
}
UIWidgets::PaddedEnhancementCheckbox("Invert Aiming X Axis", "gInvertAimingXAxis");
DrawHelpIcon("Inverts the Camera X Axis in:\n-First-Person/C-Up view\n-Weapon Aiming");
UIWidgets::PaddedEnhancementCheckbox("Invert Aiming Y Axis", "gInvertAimingYAxis", true, true, false, "", UIWidgets::CheckboxGraphics::Cross, true);
@ -237,7 +244,8 @@ namespace GameControlEditor {
DrawHelpIcon("Prevents the C-Up view from auto-centering, allowing for Gyro Aiming");
if (UIWidgets::PaddedEnhancementCheckbox("Enable Custom Aiming/First-Person sensitivity", "gEnableFirstPersonSensitivity", true, false)) {
if (!CVarGetInteger("gEnableFirstPersonSensitivity", 0)) {
CVarClear("gFirstPersonCameraSensitivity");
CVarClear("gFirstPersonCameraSensitivityX");
CVarClear("gFirstPersonCameraSensitivityY");
}
}
if (CVarGetInteger("gEnableFirstPersonSensitivity", 0)) {

View File

@ -636,6 +636,45 @@ void SohInputEditorWindow::DrawStickSection(uint8_t port, uint8_t stick, int32_t
ImGui::EndGroup();
ImGui::SetNextItemOpen(true, ImGuiCond_Once);
if (ImGui::TreeNode(StringHelper::Sprintf("Analog Stick Options##%d", id).c_str())) {
ImGui::Text("Sensitivity:");
int32_t sensitivityPercentage = controllerStick->GetSensitivityPercentage();
if (sensitivityPercentage == 0) {
ImGui::BeginDisabled();
}
ImGui::PushButtonRepeat(true);
if (ImGui::Button(StringHelper::Sprintf("-##Sensitivity%d", id).c_str())) {
controllerStick->SetSensitivity(sensitivityPercentage - 1);
}
ImGui::PopButtonRepeat();
if (sensitivityPercentage == 0) {
ImGui::EndDisabled();
}
ImGui::SameLine(0.0f, 0.0f);
ImGui::SetNextItemWidth(SCALE_IMGUI_SIZE(160.0f));
if (ImGui::SliderInt(StringHelper::Sprintf("##Sensitivity%d", id).c_str(), &sensitivityPercentage, 0, 200, "%d%%",
ImGuiSliderFlags_AlwaysClamp)) {
controllerStick->SetSensitivity(sensitivityPercentage);
}
ImGui::SameLine(0.0f, 0.0f);
if (sensitivityPercentage == 200) {
ImGui::BeginDisabled();
}
ImGui::PushButtonRepeat(true);
if (ImGui::Button(StringHelper::Sprintf("+##Sensitivity%d", id).c_str())) {
controllerStick->SetSensitivity(sensitivityPercentage + 1);
}
ImGui::PopButtonRepeat();
if (sensitivityPercentage == 200) {
ImGui::EndDisabled();
}
if (!controllerStick->SensitivityIsDefault()) {
ImGui::SameLine();
if (ImGui::Button(StringHelper::Sprintf("Reset to Default###resetStickSensitivity%d", id).c_str())) {
controllerStick->ResetSensitivityToDefault();
}
}
ImGui::Text("Deadzone:");
int32_t deadzonePercentage = controllerStick->GetDeadzonePercentage();

View File

@ -2,6 +2,9 @@
#include "stdint.h"
#include <libultraship/libultraship.h>
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include <ImGui/imgui.h>
#include <unordered_map>
#include <string>

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,64 +37,27 @@ 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);
if (tcpsock) {
connected = true;
SPDLOG_TRACE("[CrowdControl] Connection to server established!");
break;
}
}
SDLNet_SocketSet socketSet = SDLNet_AllocSocketSet(1);
if (tcpsock) {
SDLNet_TCP_AddSocket(socketSet, tcpsock);
}
// Listen to socket messages
while (connected && tcpsock && isEnabled) {
// we check first if socket has data, to not block in the TCP_Recv
int socketsReady = SDLNet_CheckSockets(socketSet, 0);
if (socketsReady == -1) {
SPDLOG_ERROR("[CrowdControl] SDLNet_CheckSockets: {}", SDLNet_GetError());
break;
}
if (socketsReady == 0) {
continue;
}
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);
void CrowdControl::HandleRemoteData(nlohmann::json payload) {
Effect* incomingEffect = ParseMessage(payload);
if (!incomingEffect) {
continue;
return;
}
// 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);
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(tcpsock, incomingEffect->id, incomingEffect->timeRemaining, EffectResult::Retry);
EmitMessage(incomingEffect->id, incomingEffect->timeRemaining, EffectResult::Retry);
break;
}
}
@ -111,8 +66,8 @@ void CrowdControl::ListenToServer() {
// 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;
EmitMessage(incomingEffect->id, incomingEffect->timeRemaining, result);
return;
}
activeEffectsMutex.lock();
@ -122,14 +77,6 @@ void CrowdControl::ListenToServer() {
}
}
if (connected) {
SDLNet_TCP_Close(tcpsock);
connected = false;
SPDLOG_TRACE("[CrowdControl] Ending Listen thread...");
}
}
}
void CrowdControl::ProcessActiveEffects() {
while (isEnabled) {
// We only want to send events when status changes, on start we send Success.
@ -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

@ -120,7 +120,8 @@ typedef enum {
TEXT_SCRUB_RANDOM_FREE = 0x9001,
TEXT_SHOP_ITEM_RANDOM = 0x9100,
TEXT_SHOP_ITEM_RANDOM_CONFIRM = 0x9101,
TEXT_WARP_RANDOM_REPLACED_TEXT = 0x9200
TEXT_WARP_RANDOM_REPLACED_TEXT = 0x9200,
TEXT_SHOOTING_GALLERY_MAN_COME_BACK_WITH_BOW = 0x9210,
} TextIDs;
#ifdef __cplusplus

View File

@ -17,6 +17,10 @@
#include <Window.h>
#include <Context.h>
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include <ImGui/imgui.h>
#include <ImGui/imgui_internal.h>
#undef PATH_HACK
#undef Path
@ -99,7 +103,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 +134,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 +251,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 +291,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 +581,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 +608,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 +636,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 +670,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 +701,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 +721,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 +751,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 +786,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 +794,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 +816,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 +846,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 +902,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 +930,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 +958,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 +986,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 +1015,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 +1043,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 +1066,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 +1090,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 +1125,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 +1156,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 +1181,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 +1207,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:
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,9 +97,15 @@ void GameInteractor_SetTriforceHuntCreditsWarpActive(uint8_t state);
#ifdef __cplusplus
#include <thread>
#include <vector>
#include <functional>
#include <string>
#ifdef ENABLE_REMOTE_CONTROL
#include <SDL2/SDL_net.h>
#include <nlohmann/json.hpp>
#endif
#define DEFINE_HOOK(name, type) \
struct name { \
@ -132,10 +143,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; };
@ -194,6 +219,7 @@ public:
DEFINE_HOOK(OnSetGameLanguage, void());
DEFINE_HOOK(OnFileDropped, void(std::string filePath));
DEFINE_HOOK(OnAssetAltChange, void());
// Helpers
@ -238,6 +264,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

@ -618,7 +618,7 @@ void DrawGameplayStatsOptionsTab() {
}
void GameplayStatsWindow::DrawElement() {
ImGui::SetNextWindowSize(ImVec2(480, 550), ImGuiCond_Appearing);
ImGui::SetNextWindowSize(ImVec2(480, 550), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("Gameplay Stats", &mIsVisible, ImGuiWindowFlags_NoFocusOnAppearing)) {
ImGui::End();
return;

View File

@ -1100,8 +1100,8 @@ void RegisterRandomizedEnemySizes() {
uint8_t excludedEnemy = actor->id == ACTOR_EN_BROB || actor->id == ACTOR_EN_DHA || (actor->id == ACTOR_BOSS_SST && actor->params == -1);
// Dodongo, Volvagia and Dead Hand are always smaller because they're impossible when bigger.
uint8_t smallOnlyEnemy =
actor->id == ACTOR_BOSS_DODONGO || actor->id == ACTOR_BOSS_FD || actor->id == ACTOR_BOSS_FD2 || ACTOR_EN_DH;
uint8_t smallOnlyEnemy = actor->id == ACTOR_BOSS_DODONGO || actor->id == ACTOR_BOSS_FD ||
actor->id == ACTOR_BOSS_FD2 || actor->id == ACTOR_EN_DH;
// Only apply to enemies and bosses.
if (!CVarGetInteger("gRandomizedEnemySizes", 0) || (actor->category != ACTORCAT_ENEMY && actor->category != ACTORCAT_BOSS) || excludedEnemy) {

View File

@ -105,7 +105,7 @@ void AreaTable_Init_DeathMountain() {
Entrance(RR_DEATH_MOUNTAIN_TRAIL, {[]{return true;}}),
Entrance(RR_GC_WOODS_WARP, {[]{return GCWoodsWarpOpen;}}),
Entrance(RR_GC_SHOP, {[]{return (IsAdult && StopGCRollingGoronAsAdult) || (IsChild && (CanBlastOrSmash || GoronBracelet || GoronCityChildFire || CanUse(RG_FAIRY_BOW)));}}),
Entrance(RR_GC_DARUNIAS_CHAMBER, {[]{return (IsAdult && StopGCRollingGoronAsAdult) || GCDaruniasDoorOpenChild;}}),
Entrance(RR_GC_DARUNIAS_CHAMBER, {[]{return (IsAdult && StopGCRollingGoronAsAdult) || (IsChild && GCDaruniasDoorOpenChild);}}),
Entrance(RR_GC_GROTTO_PLATFORM, {[]{return IsAdult && ((CanPlay(SongOfTime) && ((EffectiveHealth > 2) || CanUse(RG_GORON_TUNIC) || CanUse(RG_LONGSHOT) || CanUse(RG_NAYRUS_LOVE))) || (EffectiveHealth > 1 && CanUse(RG_GORON_TUNIC) && CanUse(RG_HOOKSHOT)) || (CanUse(RG_NAYRUS_LOVE) && CanUse(RG_HOOKSHOT)) || (EffectiveHealth > 2 && CanUse(RG_HOOKSHOT) && randoCtx->GetTrickOption(RT_GC_GROTTO)));}}),
});

View File

@ -11,6 +11,10 @@
#include "3drando/rando_main.hpp"
#include "3drando/random.hpp"
#include "../../UIWidgets.hpp"
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include <ImGui/imgui.h>
#include <ImGui/imgui_internal.h>
#include "../custom-message/CustomMessageTypes.h"
#include "../item-tables/ItemTableManager.h"
@ -253,31 +257,16 @@ std::string sanitize(std::string stringValue) {
#pragma GCC push_options
#pragma GCC optimize ("O0")
bool Randomizer::SpoilerFileExists(const char* spoilerFileName) {
try {
if (strcmp(spoilerFileName, "") != 0) {
std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName));
if (!spoilerFileStream) {
return false;
}
json spoilerFileJson;
spoilerFileStream >> spoilerFileJson;
if (!spoilerFileJson.contains("version") || !spoilerFileJson.contains("finalSeed")) {
return false;
}
} else {
return true;
}
}
return false;
} catch (std::exception& e) {
SPDLOG_ERROR("Error checking if spoiler file exists: {}", e.what());
return false;
} catch (...) {
SPDLOG_ERROR("Error checking if spoiler file exists");
return false;
}
}
#pragma GCC pop_options
#pragma optimize("", on)
@ -365,6 +354,13 @@ void Randomizer::LoadHintMessages() {
"Zu {{location}}?\x1B&%gOK&No%w\x02",
"Se téléporter vers&{{location}}?\x1B&%gOK!&Non%w\x02"));
// Bow Shooting Gallery reminder
CustomMessageManager::Instance->CreateMessage(Randomizer::hintMessageTableID, TEXT_SHOOTING_GALLERY_MAN_COME_BACK_WITH_BOW,
CustomMessage("Come back when you have your own&bow and you'll get a %rdifferent prize%w!",
"Komm wieder sobald du deinen eigenen&Bogen hast, um einen %rspeziellen Preis%w zu&erhalten!",
"J'aurai %rune autre récompense%w pour toi&lorsque tu auras ton propre arc."));
// Lake Hylia water level system
CustomMessageManager::Instance->CreateMessage(Randomizer::hintMessageTableID, TEXT_LAKE_HYLIA_WATER_SWITCH_SIGN,
CustomMessage("Water level control system.&Keep away!",
"Wasserstand Kontrollsystem&Finger weg!",

View File

@ -82,6 +82,7 @@ bool doAreaScroll;
bool previousShowHidden = false;
bool hideShopRightChecks = true;
bool hideTriforceCompleted = true;
bool alwaysShowGS = false;
std::map<uint32_t, RandomizerCheck> startingShopItem = { { SCENE_KOKIRI_SHOP, RC_KF_SHOP_ITEM_1 },
{ SCENE_BAZAAR, RC_MARKET_BAZAAR_ITEM_1 },
@ -435,7 +436,6 @@ void CheckTrackerLoadGame(int32_t fileNum) {
} else {
entry2 = Rando::StaticData::GetLocation(rc);
}
if (!IsVisibleInCheckTracker(entry2->GetRandomizerCheck())) continue;
checksByArea.find(entry2->GetArea())->second.push_back(entry2->GetRandomizerCheck());
if (rcTrackerData.status == RCSHOW_SAVED || rcTrackerData.skipped) {
@ -514,6 +514,10 @@ void CheckTrackerTransition(uint32_t sceneNum) {
}
void CheckTrackerFrame() {
if (IS_RANDO) {
hideShopRightChecks = CVarGetInteger("gCheckTrackerOptionHideRightShopChecks", 1);
alwaysShowGS = CVarGetInteger("gCheckTrackerOptionAlwaysShowGSLocs", 0);
}
if (!GameInteractor::IsSaveLoaded()) {
return;
}
@ -1085,7 +1089,6 @@ void LoadSettings() {
showLinksPocket = IS_RANDO ? // don't show Link's Pocket if not randomizer, or if rando and pocket is disabled
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_LINKS_POCKET) != RO_LINKS_POCKET_NOTHING
:false;
hideShopRightChecks = IS_RANDO ? CVarGetInteger("gCheckTrackerOptionHideRightShopChecks", 1) : false;
hideTriforceCompleted = IS_RANDO ?
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT) != RO_GENERIC_ON : false;
@ -1155,7 +1158,7 @@ bool IsVisibleInCheckTracker(RandomizerCheck rc) {
) &&
(loc->GetRCType() != RCTYPE_MERCHANT || showMerchants) &&
(loc->GetRCType() != RCTYPE_OCARINA || showOcarinas) &&
(loc->GetRCType() != RCTYPE_SKULL_TOKEN ||
(loc->GetRCType() != RCTYPE_SKULL_TOKEN || alwaysShowGS ||
(showOverworldTokens && RandomizerCheckObjects::AreaIsOverworld(loc->GetArea())) ||
(showDungeonTokens && RandomizerCheckObjects::AreaIsDungeon(loc->GetArea()))
) &&
@ -1541,7 +1544,9 @@ void CheckTrackerSettingsWindow::DrawElement() {
UIWidgets::EnhancementCheckbox("Vanilla/MQ Dungeon Spoilers", "gCheckTrackerOptionMQSpoilers");
UIWidgets::Tooltip("If enabled, Vanilla/MQ dungeons will show on the tracker immediately. Otherwise, Vanilla/MQ dungeon locations must be unlocked.");
UIWidgets::EnhancementCheckbox("Hide right-side shop item checks", "gCheckTrackerOptionHideRightShopChecks", false, "", UIWidgets::CheckboxGraphics::Cross, true);
UIWidgets::Tooltip("If enabled, will prevent the tracker from displaying slots 1-4 in all shops. Requires save reload.");
UIWidgets::Tooltip("If enabled, will prevent the tracker from displaying slots 1-4 in all shops.");
UIWidgets::EnhancementCheckbox("Always show gold skulltulas", "gCheckTrackerOptionAlwaysShowGSLocs", false, "");
UIWidgets::Tooltip("If enabled, will show GS locations in the tracker regardless of tokensanity settings.");
ImGui::TableNextColumn();

View File

@ -303,7 +303,7 @@ const EntranceData entranceData[] = {
// Gerudo Area
{ ENTR_HYRULE_FIELD_5, ENTR_GERUDO_VALLEY_0, SINGLE_SCENE_INFO(SCENE_GERUDO_VALLEY), "GV", "Hyrule Field", ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_GROUP_HYRULE_FIELD, ENTRANCE_TYPE_OVERWORLD, "hf"},
{ ENTR_GERUDOS_FORTRESS_0, ENTR_GERUDO_VALLEY_3, SINGLE_SCENE_INFO(SCENE_GERUDO_VALLEY), "GV", "GF", ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_TYPE_OVERWORLD, "gerudo fortress"},
{ ENTR_GERUDOS_FORTRESS_0_5, -1, SINGLE_SCENE_INFO(SCENE_GERUDO_VALLEY), "GV", "Lake Hylia", ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_TYPE_OVERWORLD, "lh"},
{ ENTR_LAKE_HYLIA_1, -1, SINGLE_SCENE_INFO(SCENE_GERUDO_VALLEY), "GV", "Lake Hylia", ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_GROUP_LAKE_HYLIA, ENTRANCE_TYPE_OVERWORLD, "lh"},
{ ENTR_CARPENTERS_TENT_0, ENTR_GERUDO_VALLEY_4, SINGLE_SCENE_INFO(SCENE_GERUDO_VALLEY), "GV", "Carpenters' Tent", ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_TYPE_INTERIOR, "", 1},
{ ENTRANCE_RANDO_GROTTO_LOAD(GROTTO_GV_OCTOROK_OFFSET), ENTRANCE_RANDO_GROTTO_EXIT(GROTTO_GV_OCTOROK_OFFSET), SINGLE_SCENE_INFO(SCENE_GERUDO_VALLEY), "GV", "GV Octorok Grotto", ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_TYPE_GROTTO, "", 1},
{ ENTRANCE_RANDO_GROTTO_LOAD(GROTTO_GV_STORMS_OFFSET), ENTRANCE_RANDO_GROTTO_EXIT(GROTTO_GV_STORMS_OFFSET), SINGLE_SCENE_INFO(SCENE_GERUDO_VALLEY), "GV", "GV Storms Grotto", ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_TYPE_GROTTO, "scrubs", 1},

View File

@ -0,0 +1,491 @@
#include "ResolutionEditor.h"
#include <ImGui/imgui.h>
#include <libultraship/libultraship.h>
#include <soh/UIWidgets.hpp>
#include <graphic/Fast3D/gfx_pc.h>
/* Console Variables are grouped under gAdvancedResolution. (e.g. "gAdvancedResolution.Enabled")
The following cvars are used in Libultraship and can be edited here:
- Enabled - Turns Advanced Resolution Mode on.
- AspectRatioX, AspectRatioY - Aspect ratio controls. To toggle off, set either to zero.
- VerticalPixelCount, VerticalResolutionToggle - Resolution controls.
- PixelPerfectMode, IntegerScale.Factor - Pixel Perfect Mode a.k.a. integer scaling controls.
- IntegerScale.FitAutomatically - Automatic resizing for Pixel Perfect Mode.
- IntegerScale.NeverExceedBounds - Prevents manual resizing from exceeding screen bounds.
The following cvars are also implemented in LUS for niche use cases:
- IgnoreAspectCorrection - Stretch framebuffer to fill screen.
This is something of a power-user setting for niche setups that most people won't need or care about,
but may be useful if playing the Switch/Wii U ports on a 4:3 television.
- IntegerScale.ExceedBoundsBy - Offset the max screen bounds, usually by +1.
This isn't that useful at the moment, so it's unused here.
*/
namespace AdvancedResolutionSettings {
enum setting { UPDATE_aspectRatioX, UPDATE_aspectRatioY, UPDATE_verticalPixelCount };
const char* aspectRatioPresetLabels[] = {
"Off", "Custom", "Original (4:3)", "Widescreen (16:9)", "Nintendo 3DS (5:3)", "16:10 (8:5)", "Ultrawide (21:9)"
};
const float aspectRatioPresetsX[] = { 0.0f, 16.0f, 4.0f, 16.0f, 5.0f, 16.0f, 21.0f };
const float aspectRatioPresetsY[] = { 0.0f, 9.0f, 3.0f, 9.0f, 3.0f, 10.0f, 9.0f };
const int default_aspectRatio = 1; // Default combo list option
const char* pixelCountPresetLabels[] = { "Custom", "Native N64 (240p)", "2x (480p)", "3x (720p)", "4x (960p)",
"5x (1200p)", "6x (1440p)", "Full HD (1080p)", "4K (2160p)" };
const int pixelCountPresets[] = { 480, 240, 480, 720, 960, 1200, 1440, 1080, 2160 };
const int default_pixelCount = 0; // Default combo list option
// Resolution clamp values as hardcoded in LUS::Gui::ApplyResolutionChanges()
const uint32_t minVerticalPixelCount = SCREEN_HEIGHT;
const uint32_t maxVerticalPixelCount = 4320; // 18x native, or 8K TV resolution
const unsigned short default_maxIntegerScaleFactor = 6; // Default size of Integer scale factor slider.
enum messageType { MESSAGE_ERROR, MESSAGE_WARNING, MESSAGE_QUESTION, MESSAGE_INFO, MESSAGE_GRAY_75 };
const ImVec4 messageColor[]{
{ 0.85f, 0.0f, 0.0f, 1.0f }, // MESSAGE_ERROR
{ 0.85f, 0.85f, 0.0f, 1.0f }, // MESSAGE_WARNING
{ 0.0f, 0.85f, 0.85f, 1.0f }, // MESSAGE_QUESTION
{ 0.0f, 0.85f, 0.55f, 1.0f }, // MESSAGE_INFO
{ 0.75f, 0.75f, 0.75f, 1.0f } // MESSAGE_GRAY_75
};
const float enhancementSpacerHeight = 19.0f;
void AdvancedResolutionSettingsWindow::InitElement() {
}
void AdvancedResolutionSettingsWindow::DrawElement() {
ImGui::SetNextWindowSize(ImVec2(497, 599), ImGuiCond_FirstUseEver);
if (ImGui::Begin("Advanced Resolution Settings", &mIsVisible)) {
// Initialise update flags.
bool update[3];
for (uint8_t i = 0; i < sizeof(update); i++)
update[i] = false;
// Initialise integer scale bounds.
short max_integerScaleFactor = default_maxIntegerScaleFactor; // default value, which may or may not get
// overridden depending on viewport res
short integerScale_maximumBounds = 1; // can change when window is resized
// This is mostly just for UX purposes, as Fit Automatically logic is part of LUS.
if (((float)gfx_current_game_window_viewport.width / gfx_current_game_window_viewport.height) >
((float)gfx_current_dimensions.width / gfx_current_dimensions.height)) {
// Scale to window height
integerScale_maximumBounds = gfx_current_game_window_viewport.height / gfx_current_dimensions.height;
} else {
// Scale to window width
integerScale_maximumBounds = gfx_current_game_window_viewport.width / gfx_current_dimensions.width;
}
// Lower-clamping maximum bounds value to 1 is no-longer necessary as that's accounted for in LUS.
// Letting it go below 1 in this Editor will even allow for checking if screen bounds are being exceeded.
if (default_maxIntegerScaleFactor < integerScale_maximumBounds) {
max_integerScaleFactor =
integerScale_maximumBounds + CVarGetInteger("gAdvancedResolution.IntegerScale.ExceedBoundsBy", 0);
}
// Combo List defaults
static int item_aspectRatio = CVarGetInteger("gAdvancedResolution.UIComboItem.AspectRatio", 3);
static int item_pixelCount = CVarGetInteger("gAdvancedResolution.UIComboItem.PixelCount", default_pixelCount);
// Stored Values for non-UIWidgets elements
static float aspectRatioX =
CVarGetFloat("gAdvancedResolution.AspectRatioX", aspectRatioPresetsX[item_aspectRatio]);
static float aspectRatioY =
CVarGetFloat("gAdvancedResolution.AspectRatioY", aspectRatioPresetsY[item_aspectRatio]);
static int verticalPixelCount =
CVarGetInteger("gAdvancedResolution.VerticalPixelCount", pixelCountPresets[item_pixelCount]);
// Additional settings
static bool showHorizontalResField = false;
static int horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
// Disabling flags
const bool disabled_everything = !CVarGetInteger("gAdvancedResolution.Enabled", 0);
const bool disabled_pixelCount = !CVarGetInteger("gAdvancedResolution.VerticalResolutionToggle", 0);
#ifdef __APPLE__
// Display HiDPI warning. (Remove this once we can definitively say it's fixed.)
ImGui::TextColored(messageColor[MESSAGE_INFO],
ICON_FA_INFO_CIRCLE " These settings may behave incorrectly on Retina displays.");
UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f);
#endif
if (ImGui::CollapsingHeader("Original Settings", ImGuiTreeNodeFlags_DefaultOpen)) {
// The original resolution slider (for convenience)
const bool disabled_resolutionSlider = (CVarGetInteger("gAdvancedResolution.VerticalResolutionToggle", 0) &&
CVarGetInteger("gAdvancedResolution.Enabled", 0)) ||
CVarGetInteger("gLowResMode", 0);
if (UIWidgets::EnhancementSliderFloat("Internal Resolution: %d %%", "##IMul", "gInternalResolution", 0.5f,
2.0f, "", 1.0f, true, true, disabled_resolutionSlider)) {
LUS::Context::GetInstance()->GetWindow()->SetResolutionMultiplier(
CVarGetFloat("gInternalResolution", 1));
}
UIWidgets::Tooltip("Multiplies your output resolution by the value entered.");
// The original MSAA slider (also for convenience)
#ifndef __WIIU__
if (UIWidgets::PaddedEnhancementSliderInt("MSAA: %d", "##IMSAA", "gMSAAValue", 1, 8, "", 1, true, true,
false)) {
LUS::Context::GetInstance()->GetWindow()->SetMsaaLevel(CVarGetInteger("gMSAAValue", 1));
};
UIWidgets::Tooltip(
"Activates multi-sample anti-aliasing when above 1x, up to 8x for 8 samples for every pixel.\n\n"
" " ICON_FA_INFO_CIRCLE
" (Higher MSAA with low resolution can approximate an authentic \"real N64\" look!)");
#endif
// N64 Mode toggle (again for convenience)
// UIWidgets::PaddedEnhancementCheckbox("(Enhancements>Graphics) N64 Mode", "gLowResMode", false, false, false, "", UIWidgets::CheckboxGraphics::Cross, false);
}
UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f);
// Activator
UIWidgets::PaddedEnhancementCheckbox("Enable advanced settings.", "gAdvancedResolution.Enabled", false, false,
false, "", UIWidgets::CheckboxGraphics::Cross, false);
// Error/Warning display
if (!CVarGetInteger("gLowResMode", 0)) {
if (IsDroppingFrames()) { // Significant frame drop warning
ImGui::TextColored(messageColor[MESSAGE_WARNING],
ICON_FA_EXCLAMATION_TRIANGLE " Significant frame rate (FPS) drops may be occuring.");
UIWidgets::Spacer(2);
} else { // No warnings
UIWidgets::Spacer(enhancementSpacerHeight);
}
} else { // N64 Mode warning
ImGui::TextColored(messageColor[MESSAGE_QUESTION],
ICON_FA_QUESTION_CIRCLE " \"N64 Mode\" is overriding these settings.");
ImGui::SameLine();
if (ImGui::Button("Click to disable")) {
CVarSetInteger("gLowResMode", 0);
CVarSave();
}
}
// Resolution visualiser
ImGui::Text("Viewport dimensions: %d x %d", gfx_current_game_window_viewport.width,
gfx_current_game_window_viewport.height);
ImGui::Text("Internal resolution: %d x %d", gfx_current_dimensions.width, gfx_current_dimensions.height);
UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f);
if (disabled_everything) { // Hide aspect ratio controls.
UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f);
}
// Aspect Ratio
ImGui::Text("Force aspect ratio:");
ImGui::SameLine();
ImGui::TextColored(messageColor[MESSAGE_GRAY_75], "(Select \"Off\" to disable.)");
// Presets
if (ImGui::Combo(" ", &item_aspectRatio, aspectRatioPresetLabels,
IM_ARRAYSIZE(aspectRatioPresetLabels)) &&
item_aspectRatio != default_aspectRatio) { // don't change anything if "Custom" is selected.
aspectRatioX = aspectRatioPresetsX[item_aspectRatio];
aspectRatioY = aspectRatioPresetsY[item_aspectRatio];
if (showHorizontalResField) {
horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
}
CVarSetFloat("gAdvancedResolution.AspectRatioX", aspectRatioX);
CVarSetFloat("gAdvancedResolution.AspectRatioY", aspectRatioY);
CVarSetInteger("gAdvancedResolution.UIComboItem.AspectRatio", item_aspectRatio);
CVarSave();
}
// Hide aspect ratio input fields if using one of the presets.
if (item_aspectRatio == default_aspectRatio && !showHorizontalResField) {
// Declare input interaction bools outside of IF statement to prevent Y field from disappearing.
const bool input_X = ImGui::InputFloat("X", &aspectRatioX, 0.1f, 1.0f, "%.3f");
const bool input_Y = ImGui::InputFloat("Y", &aspectRatioY, 0.1f, 1.0f, "%.3f");
if (input_X || input_Y) {
item_aspectRatio = default_aspectRatio;
update[UPDATE_aspectRatioX] = true;
update[UPDATE_aspectRatioY] = true;
}
} else if (showHorizontalResField) { // Show calculated aspect ratio
if (item_aspectRatio) {
UIWidgets::Spacer(2);
const float resolvedAspectRatio = (float)gfx_current_dimensions.width / gfx_current_dimensions.height;
ImGui::Text("Aspect ratio: %.2f:1", resolvedAspectRatio);
} else {
UIWidgets::Spacer(enhancementSpacerHeight);
}
}
if (disabled_everything) { // Hide aspect ratio controls.
UIWidgets::ReEnableComponent("disabledTooltipText");
}
UIWidgets::Spacer(0);
// Vertical Resolution
UIWidgets::PaddedEnhancementCheckbox("Set fixed vertical resolution (disables Resolution slider)",
"gAdvancedResolution.VerticalResolutionToggle", true, false,
disabled_everything, "", UIWidgets::CheckboxGraphics::Cross, false);
UIWidgets::Tooltip(
"Override the resolution scale slider and use the settings below, irrespective of window size.");
if (disabled_pixelCount || disabled_everything) { // Hide pixel count controls.
UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f);
}
if (ImGui::Combo("Pixel Count Presets", &item_pixelCount, pixelCountPresetLabels,
IM_ARRAYSIZE(pixelCountPresetLabels)) &&
item_pixelCount != default_pixelCount) { // don't change anything if "Custom" is selected.
verticalPixelCount = pixelCountPresets[item_pixelCount];
if (showHorizontalResField) {
horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
}
CVarSetInteger("gAdvancedResolution.VerticalPixelCount", verticalPixelCount);
CVarSetInteger("gAdvancedResolution.UIComboItem.PixelCount", item_pixelCount);
CVarSave();
}
// Horizontal Resolution, if visibility is enabled for it.
if (showHorizontalResField) {
// Only show the field if Aspect Ratio is being enforced.
if ((aspectRatioX > 0.0f) && (aspectRatioY > 0.0f)) {
// So basically we're "faking" this one by setting aspectRatioX instead.
if (ImGui::InputInt("Horiz. Pixel Count", &horizontalPixelCount, 8, 320)) {
item_aspectRatio = default_aspectRatio;
if (horizontalPixelCount < SCREEN_WIDTH) {
horizontalPixelCount = SCREEN_WIDTH;
}
aspectRatioX = horizontalPixelCount;
aspectRatioY = verticalPixelCount;
update[UPDATE_aspectRatioX] = true;
update[UPDATE_aspectRatioY] = true;
}
} else { // Display a notice instead.
ImGui::TextColored(messageColor[MESSAGE_QUESTION],
ICON_FA_QUESTION_CIRCLE " \"Force aspect ratio\" required.");
// ImGui::Text(" ");
ImGui::SameLine();
if (ImGui::Button("Click to resolve")) {
item_aspectRatio = default_aspectRatio; // Set it to Custom
aspectRatioX = aspectRatioPresetsX[2]; // but use the 4:3 defaults
aspectRatioY = aspectRatioPresetsY[2];
update[UPDATE_aspectRatioX] = true;
update[UPDATE_aspectRatioY] = true;
horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
}
}
}
// Vertical Resolution part 2
if (ImGui::InputInt("Vertical Pixel Count", &verticalPixelCount, 8, 240)) {
item_pixelCount = default_pixelCount;
update[UPDATE_verticalPixelCount] = true;
// Account for the natural instinct to enter horizontal first.
// Ignore vertical resolutions that are below the lower clamp constant.
if (showHorizontalResField && !(verticalPixelCount < minVerticalPixelCount)) {
item_aspectRatio = default_aspectRatio;
aspectRatioX = horizontalPixelCount;
aspectRatioY = verticalPixelCount;
update[UPDATE_aspectRatioX] = true;
update[UPDATE_aspectRatioY] = true;
}
}
if (disabled_pixelCount || disabled_everything) { // Hide pixel count controls.
UIWidgets::ReEnableComponent("disabledTooltipText");
}
UIWidgets::Spacer(0);
// Integer scaling settings group (Pixel-perfect Mode)
static const ImGuiTreeNodeFlags IntegerScalingResolvedImGuiFlag =
CVarGetInteger("gAdvancedResolution.PixelPerfectMode", 0) ? ImGuiTreeNodeFlags_DefaultOpen
: ImGuiTreeNodeFlags_None;
if (ImGui::CollapsingHeader("Integer Scaling Settings", IntegerScalingResolvedImGuiFlag)) {
const bool disabled_pixelPerfectMode =
!CVarGetInteger("gAdvancedResolution.PixelPerfectMode", 0) || disabled_everything;
// Pixel-perfect Mode
UIWidgets::PaddedEnhancementCheckbox("Pixel-perfect Mode", "gAdvancedResolution.PixelPerfectMode", true,
true, disabled_pixelCount || disabled_everything, "",
UIWidgets::CheckboxGraphics::Cross, false);
UIWidgets::Tooltip("Don't scale image to fill window.");
if (disabled_pixelCount && CVarGetInteger("gAdvancedResolution.PixelPerfectMode", 0)) {
CVarSetInteger("gAdvancedResolution.PixelPerfectMode", 0);
CVarSave();
}
// Integer Scaling
UIWidgets::EnhancementSliderInt(
"Integer scale factor: %d", "##ARSIntScale", "gAdvancedResolution.IntegerScale.Factor", 1,
max_integerScaleFactor, "%d", 1, true,
disabled_pixelPerfectMode || CVarGetInteger("gAdvancedResolution.IntegerScale.FitAutomatically", 0));
UIWidgets::Tooltip("Integer scales the image. Only available in pixel-perfect mode.");
// Display warning if size is being clamped or if framebuffer is larger than viewport.
if (!disabled_pixelPerfectMode &&
(CVarGetInteger("gAdvancedResolution.IntegerScale.NeverExceedBounds", 1) &&
CVarGetInteger("gAdvancedResolution.IntegerScale.Factor", 1) > integerScale_maximumBounds)) {
ImGui::SameLine();
ImGui::TextColored(messageColor[MESSAGE_WARNING], ICON_FA_EXCLAMATION_TRIANGLE " Window exceeded.");
}
UIWidgets::PaddedEnhancementCheckbox(
"Automatically scale image to fit viewport", "gAdvancedResolution.IntegerScale.FitAutomatically", true,
true, disabled_pixelPerfectMode, "", UIWidgets::CheckboxGraphics::Cross, false);
UIWidgets::Tooltip("Automatically sets scale factor to fit window. Only available in pixel-perfect mode.");
if (CVarGetInteger("gAdvancedResolution.IntegerScale.FitAutomatically", 0)) {
// This is just here to update the value shown on the slider.
// The function in LUS to handle this setting will ignore IntegerScaleFactor while active.
CVarSetInteger("gAdvancedResolution.IntegerScale.Factor", integerScale_maximumBounds);
// CVarSave();
}
} // End of integer scaling settings
UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f);
// Collapsible panel for additional settings
if (ImGui::CollapsingHeader("Additional Settings")) {
UIWidgets::Spacer(0);
#if defined(__SWITCH__) || defined(__WIIU__)
// Disable aspect correction, stretching the framebuffer to fill the viewport.
// This option is only really needed on systems limited to 16:9 TV resolutions, such as consoles.
// The associated cvar is still functional on PC platforms if you want to use it though.
UIWidgets::PaddedEnhancementCheckbox("Disable aspect correction and stretch the output image.\n"
"(Might be useful for 4:3 televisions!)\n"
"Not available in Pixel Perfect Mode.",
"gAdvancedResolution.IgnoreAspectCorrection", false, true,
CVarGetInteger("gAdvancedResolution.PixelPerfectMode", 0) ||
disabled_everything,
"", UIWidgets::CheckboxGraphics::Cross, false);
#else
if (CVarGetInteger("gAdvancedResolution.IgnoreAspectCorrection", 0)) {
// This setting is intentionally not exposed on PC platforms,
// but may be accidentally activated for varying reasons.
// Having this button should hopefully prevent support headaches.
ImGui::TextColored(messageColor[MESSAGE_QUESTION], ICON_FA_QUESTION_CIRCLE
" If the image is stretched and you don't know why, click this.");
if (ImGui::Button("Click to reenable aspect correction.")) {
CVarSetInteger("gAdvancedResolution.IgnoreAspectCorrection", 0);
CVarSave();
}
UIWidgets::Spacer(2);
}
#endif
// A requested addition; an alternative way of displaying the resolution field.
if (ImGui::Checkbox("Show a horizontal resolution field, instead of aspect ratio.", &showHorizontalResField)) {
if (!showHorizontalResField && (aspectRatioX > 0.0f)) { // when turning this setting off
// Refresh relevant values
aspectRatioX = aspectRatioY * horizontalPixelCount / verticalPixelCount;
horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
} else { // when turning this setting on
item_aspectRatio = default_aspectRatio;
if (aspectRatioX > 0.0f) {
// Refresh relevant values in the opposite order
horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX;
aspectRatioX = aspectRatioY * horizontalPixelCount / verticalPixelCount;
}
}
update[UPDATE_aspectRatioX] = true;
}
// Beginning of Integer Scaling additional settings.
{
// UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f);
// Integer Scaling - Never Exceed Bounds.
const bool disabled_neverExceedBounds =
!CVarGetInteger("gAdvancedResolution.PixelPerfectMode", 0) ||
CVarGetInteger("gAdvancedResolution.IntegerScale.FitAutomatically", 0) || disabled_everything;
const bool checkbox_neverExceedBounds =
UIWidgets::PaddedEnhancementCheckbox("Prevent integer scaling from exceeding screen bounds.\n"
"(Makes screen bounds take priority over specified factor.)",
"gAdvancedResolution.IntegerScale.NeverExceedBounds",
true, false, disabled_neverExceedBounds, "",
UIWidgets::CheckboxGraphics::Cross, true);
UIWidgets::Tooltip(
"Prevents integer scaling factor from exceeding screen bounds.\n\n"
"Enabled: Will clamp the scaling factor and display a gentle warning in the resolution editor.\n"
"Disabled: Will allow scaling to exceed screen bounds, for users who want to crop overscan.\n\n"
" " ICON_FA_INFO_CIRCLE
" Please note that exceeding screen bounds may show a scroll bar on-screen.");
// Initialise the (currently unused) "Exceed Bounds By" cvar if it's been changed.
if (checkbox_neverExceedBounds &&
CVarGetInteger("gAdvancedResolution.IntegerScale.ExceedBoundsBy", 0)) {
CVarSetInteger("gAdvancedResolution.IntegerScale.ExceedBoundsBy", 0);
CVarSave();
}
// Integer Scaling - Exceed Bounds By 1x/Offset.
// A popular feature in some retro frontends/upscalers, sometimes called "crop overscan" or "1080p 5x".
/*
UIWidgets::PaddedEnhancementCheckbox("Allow integer scale factor to go +1 above maximum screen bounds.", "gAdvancedResolution.IntegerScale.ExceedBoundsBy", false, false, !CVarGetInteger("gAdvancedResolution.PixelPerfectMode", 0) || disabled_everything, "", UIWidgets::CheckboxGraphics::Cross, false);
*/
// It does actually function as expected, but exceeding the bottom of the screen shows a scroll bar.
// I've ended up commenting this one out because of the scroll bar, and for simplicity.
// Display an info message about the scroll bar.
if (!CVarGetInteger("gAdvancedResolution.IntegerScale.NeverExceedBounds", 1) ||
CVarGetInteger("gAdvancedResolution.IntegerScale.ExceedBoundsBy", 0)) {
if (disabled_neverExceedBounds) { // Dim this help text accordingly
UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f);
}
ImGui::TextColored(messageColor[MESSAGE_INFO],
" " ICON_FA_INFO_CIRCLE
" A scroll bar may become visible if screen bounds are exceeded.");
if (disabled_neverExceedBounds) { // Dim this help text accordingly
UIWidgets::ReEnableComponent("disabledTooltipText");
}
// Another support helper button, to disable the unused "Exceed Bounds By" cvar.
// (Remove this button if uncommenting the checkbox.)
if (CVarGetInteger("gAdvancedResolution.IntegerScale.ExceedBoundsBy", 0)) {
if (ImGui::Button("Click to reset a console variable that may be causing this.")) {
CVarSetInteger("gAdvancedResolution.IntegerScale.ExceedBoundsBy", 0);
CVarSave();
}
}
} else {
ImGui::Text(" ");
}
// UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f);
} // End of Integer Scaling additional settings.
} // End of additional settings
// Clamp and update the cvars that don't use UIWidgets
if (update[UPDATE_aspectRatioX] || update[UPDATE_aspectRatioY] || update[UPDATE_verticalPixelCount]) {
if (update[UPDATE_aspectRatioX]) {
if (aspectRatioX < 0.0f) {
aspectRatioX = 0.0f;
}
CVarSetFloat("gAdvancedResolution.AspectRatioX", aspectRatioX);
}
if (update[UPDATE_aspectRatioY]) {
if (aspectRatioY < 0.0f) {
aspectRatioY = 0.0f;
}
CVarSetFloat("gAdvancedResolution.AspectRatioY", aspectRatioY);
}
if (update[UPDATE_verticalPixelCount]) {
// There's a upper and lower clamp on the Libultraship side too,
// so clamping it here is entirely visual, so the vertical resolution field reflects it.
if (verticalPixelCount < minVerticalPixelCount) {
verticalPixelCount = minVerticalPixelCount;
}
if (verticalPixelCount > maxVerticalPixelCount) {
verticalPixelCount = maxVerticalPixelCount;
}
CVarSetInteger("gAdvancedResolution.VerticalPixelCount", verticalPixelCount);
}
CVarSetInteger("gAdvancedResolution.UIComboItem.AspectRatio", item_aspectRatio);
CVarSetInteger("gAdvancedResolution.UIComboItem.PixelCount", item_pixelCount);
CVarSave();
}
}
ImGui::End();
}
void AdvancedResolutionSettingsWindow::UpdateElement() {
}
bool AdvancedResolutionSettingsWindow::IsDroppingFrames() {
// a rather imprecise way of checking for frame drops.
// but it's mostly there to inform the player of large drops.
const short targetFPS = CVarGetInteger("gInterpolationFPS", 20);
const float threshold = targetFPS / 20.0f + 4.1f;
return ImGui::GetIO().Framerate < targetFPS - threshold;
}
} // namespace AdvancedResolutionSettings

View File

@ -0,0 +1,16 @@
#pragma once
#include <libultraship/libultraship.h>
namespace AdvancedResolutionSettings {
class AdvancedResolutionSettingsWindow : public LUS::GuiWindow {
private:
bool IsDroppingFrames();
public:
using LUS::GuiWindow::GuiWindow;
void InitElement() override;
void DrawElement() override;
void UpdateElement() override;
};
} // namespace AdvancedResolutionSettings

View File

@ -83,9 +83,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"
@ -121,6 +123,8 @@ CrowdControl* CrowdControl::Instance;
#include "soh/config/ConfigUpdaters.h"
void SoH_ProcessDroppedFiles(std::string filePath);
OTRGlobals* OTRGlobals::Instance;
SaveManager* SaveManager::Instance;
CustomMessageManager* CustomMessageManager::Instance;
@ -1090,9 +1094,9 @@ extern "C" void InitOTR() {
OTRGlobals::Instance = new OTRGlobals();
CustomMessageManager::Instance = new CustomMessageManager();
ItemTableManager::Instance = new ItemTableManager();
GameInteractor::Instance = new GameInteractor();
SaveManager::Instance = new SaveManager();
SohGui::SetupGuiElements();
GameInteractor::Instance = new GameInteractor();
AudioCollection::Instance = new AudioCollection();
ActorDB::Instance = new ActorDB();
#ifdef __APPLE__
@ -1103,6 +1107,11 @@ extern "C" void InitOTR() {
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();
@ -1112,6 +1121,11 @@ extern "C" void InitOTR() {
InitMods();
ActorDB::AddBuiltInCustomActors();
// #region SOH [Randomizer] TODO: Remove these and refactor spoiler file handling for randomizer
CVarClear("gRandomizerNewFileDropped");
CVarClear("gRandomizerDroppedFile");
// #endregion
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnFileDropped>(SoH_ProcessDroppedFiles);
time_t now = time(NULL);
tm *tm_now = localtime(&now);
@ -1122,13 +1136,17 @@ extern "C" void InitOTR() {
}
srand(now);
#ifdef ENABLE_CROWD_CONTROL
CrowdControl::Instance = new CrowdControl();
CrowdControl::Instance->Init();
if (CVarGetInteger("gCrowdControl", 0)) {
#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();
} else {
CrowdControl::Instance->Disable();
break;
}
}
#endif
@ -1145,9 +1163,18 @@ extern "C" void SaveManager_ThreadPoolWait() {
extern "C" void DeinitOTR() {
SaveManager_ThreadPoolWait();
OTRAudio_Exit();
#ifdef ENABLE_CROWD_CONTROL
#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();
CrowdControl::Instance->Shutdown();
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.
@ -1288,6 +1315,16 @@ extern "C" void Graph_StartFrame() {
}
}
#endif
if (CVarGetInteger("gNewFileDropped", 0)) {
std::string filePath = SohUtils::Sanitize(CVarGetString("gDroppedFile", ""));
if (!filePath.empty()) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnFileDropped>(filePath);
}
CVarClear("gNewFileDropped");
CVarClear("gDroppedFile");
}
OTRGlobals::Instance->context->GetWindow()->StartFrame();
}
@ -2161,10 +2198,10 @@ Color_RGB8 GetColorForControllerLED() {
if (source == LED_SOURCE_CUSTOM) {
color = CVarGetColor24("gLedPort1Color", { 255, 255, 255 });
}
if (criticalOverride || source == LED_SOURCE_HEALTH) {
if (gPlayState && (criticalOverride || source == LED_SOURCE_HEALTH)) {
if (HealthMeter_IsCritical()) {
color = { 0xFF, 0, 0 };
} else if (source == LED_SOURCE_HEALTH) {
} else if (gSaveContext.healthCapacity != 0 && source == LED_SOURCE_HEALTH) {
if (gSaveContext.health / gSaveContext.healthCapacity <= 0.4f) {
color = { 0xFF, 0xFF, 0 };
} else {
@ -2576,6 +2613,8 @@ extern "C" int CustomMessage_RetrieveIfExists(PlayState* play) {
messageEntry = OTRGlobals::Instance->gRandomizer->GetWarpSongMessage(textId, Randomizer_GetSettingValue(RSK_WARP_SONG_HINTS) == RO_GENERIC_OFF);
} else if (textId == TEXT_LAKE_HYLIA_WATER_SWITCH_NAVI || textId == TEXT_LAKE_HYLIA_WATER_SWITCH_SIGN) {
messageEntry = CustomMessageManager::Instance->RetrieveMessage(Randomizer::hintMessageTableID, textId);
} else if (textId == TEXT_SHOOTING_GALLERY_MAN_COME_BACK_WITH_BOW) {
messageEntry = CustomMessageManager::Instance->RetrieveMessage(Randomizer::hintMessageTableID, TEXT_SHOOTING_GALLERY_MAN_COME_BACK_WITH_BOW);
} else if (textId == 0x3052 || (textId >= 0x3069 && textId <= 0x3070)) { //Fire Temple gorons
u16 choice = Random(0, NUM_GORON_MESSAGES);
messageEntry = OTRGlobals::Instance->gRandomizer->GetGoronMessage(choice);
@ -2665,12 +2704,9 @@ extern "C" void Gfx_RegisterBlendedTexture(const char* name, u8* mask, u8* repla
gfx_register_blended_texture(name, mask, replacement);
}
// #region SOH [TODO] Ideally this should move to being event based, it's currently run every frame on the file select screen
extern "C" void SoH_ProcessDroppedFiles() {
const char* droppedFile = CVarGetString("gDroppedFile", "");
if (CVarGetInteger("gNewFileDropped", 0) && strcmp(droppedFile, "") != 0) {
void SoH_ProcessDroppedFiles(std::string filePath) {
try {
std::ifstream configStream(SohUtils::Sanitize(droppedFile));
std::ifstream configStream(filePath);
if (!configStream) {
return;
}
@ -2678,6 +2714,14 @@ extern "C" void SoH_ProcessDroppedFiles() {
nlohmann::json configJson;
configStream >> configJson;
// #region SOH [Randomizer] TODO: Refactor spoiler file handling for randomizer
if (configJson.contains("version") && configJson.contains("finalSeed")) {
CVarSetString("gRandomizerDroppedFile", filePath.c_str());
CVarSetInteger("gRandomizerNewFileDropped", 1);
return;
}
// #endregion
if (!configJson.contains("CVars")) {
return;
}
@ -2730,5 +2774,4 @@ extern "C" void SoH_ProcessDroppedFiles() {
return;
}
}
}
// #endregion

View File

@ -184,7 +184,6 @@ void EntranceTracker_SetLastEntranceOverride(s16 entranceIndex);
void Gfx_RegisterBlendedTexture(const char* name, u8* mask, u8* replacement);
void SaveManager_ThreadPoolWait();
void CheckTracker_OnMessageClose();
void SoH_ProcessDroppedFiles();
GetItemID RetrieveGetItemIDFromItemID(ItemID itemID);
RandomizerGet RetrieveRandomizerGetFromItemID(ItemID itemID);

View File

@ -8,8 +8,10 @@
#include "SohGui.hpp"
#include <spdlog/spdlog.h>
#include <ImGui/imgui.h>
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include <ImGui/imgui.h>
#include <ImGui/imgui_internal.h>
#include <libultraship/libultraship.h>
#include <Fast3D/gfx_pc.h>
@ -31,12 +33,14 @@
#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"
#include "Enhancements/cosmetics/authenticGfxPatches.h"
#include "Enhancements/resolution-editor/ResolutionEditor.h"
bool ToggleAltAssetsAtEndOfFrame = false;
bool isBetaQuestEnabled = false;
@ -127,6 +131,8 @@ namespace SohGui {
std::shared_ptr<ItemTrackerWindow> mItemTrackerWindow;
std::shared_ptr<RandomizerSettingsWindow> mRandomizerSettingsWindow;
std::shared_ptr<AdvancedResolutionSettings::AdvancedResolutionSettingsWindow> mAdvancedResolutionSettingsWindow;
void SetupGuiElements() {
auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui();
@ -186,9 +192,12 @@ namespace SohGui {
gui->AddGuiWindow(mItemTrackerSettingsWindow);
mRandomizerSettingsWindow = std::make_shared<RandomizerSettingsWindow>("gRandomizerSettingsEnabled", "Randomizer Settings");
gui->AddGuiWindow(mRandomizerSettingsWindow);
mAdvancedResolutionSettingsWindow = std::make_shared<AdvancedResolutionSettings::AdvancedResolutionSettingsWindow>("gAdvancedResolutionEditorEnabled", "Advanced Resolution Settings");
gui->AddGuiWindow(mAdvancedResolutionSettingsWindow);
}
void Destroy() {
mAdvancedResolutionSettingsWindow = nullptr;
mRandomizerSettingsWindow = nullptr;
mItemTrackerWindow = nullptr;
mItemTrackerSettingsWindow = nullptr;

View File

@ -1,5 +1,9 @@
#include "SohMenuBar.h"
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include "ImGui/imgui.h"
#include "regex"
#include "public/bridge/consolevariablebridge.h"
#include <libultraship/libultraship.h>
#include "UIWidgets.hpp"
@ -10,8 +14,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
@ -28,6 +33,7 @@
#include "Enhancements/randomizer/randomizer_entrance_tracker.h"
#include "Enhancements/randomizer/randomizer_item_tracker.h"
#include "Enhancements/randomizer/randomizer_settings_window.h"
#include "Enhancements/resolution-editor/ResolutionEditor.h"
extern bool ToggleAltAssetsAtEndOfFrame;
extern bool isBetaQuestEnabled;
@ -175,6 +181,7 @@ void DrawShipMenu() {
extern std::shared_ptr<LUS::GuiWindow> mInputEditorWindow;
extern std::shared_ptr<GameControlEditor::GameControlEditorWindow> mGameControlEditorWindow;
extern std::shared_ptr<AdvancedResolutionSettings::AdvancedResolutionSettingsWindow> mAdvancedResolutionSettingsWindow;
void DrawSettingsMenu() {
if (ImGui::BeginMenu("Settings"))
@ -260,11 +267,28 @@ void DrawSettingsMenu() {
if (ImGui::BeginMenu("Graphics")) {
#ifndef __APPLE__
if (UIWidgets::EnhancementSliderFloat("Internal Resolution: %d %%", "##IMul", "gInternalResolution", 0.5f, 2.0f, "", 1.0f, true)) {
const bool disabled_resolutionSlider = CVarGetInteger("gAdvancedResolution.VerticalResolutionToggle", 0) &&
CVarGetInteger("gAdvancedResolution.Enabled", 0);
if (UIWidgets::EnhancementSliderFloat("Internal Resolution: %d %%", "##IMul", "gInternalResolution", 0.5f,
2.0f, "", 1.0f, true, true, disabled_resolutionSlider)) {
LUS::Context::GetInstance()->GetWindow()->SetResolutionMultiplier(CVarGetFloat("gInternalResolution", 1));
};
}
UIWidgets::Tooltip("Multiplies your output resolution by the value inputted, as a more intensive but effective form of anti-aliasing");
#endif
if (mAdvancedResolutionSettingsWindow) {
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(12.0f, 6.0f));
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.22f, 0.38f, 0.56f, 1.0f));
UIWidgets::Spacer(0);
if (ImGui::Button(GetWindowButtonText("Advanced Resolution", CVarGetInteger("gAdvancedResolutionEditorEnabled", 0)).c_str(), ImVec2(-1.0f, 0.0f))) {
mAdvancedResolutionSettingsWindow->ToggleVisibility();
}
ImGui::PopStyleColor(1);
ImGui::PopStyleVar(3);
}
#ifndef __WIIU__
if (UIWidgets::PaddedEnhancementSliderInt("MSAA: %d", "##IMSAA", "gMSAAValue", 1, 8, "", 1, true, true, false)) {
LUS::Context::GetInstance()->GetWindow()->SetMsaaLevel(CVarGetInteger("gMSAAValue", 1));
@ -1116,17 +1140,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 +1533,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;
@ -1666,6 +1808,12 @@ void SohMenuBar::DrawElement() {
ImGui::SetCursorPosY(0.0f);
#ifdef ENABLE_REMOTE_CONTROL
DrawRemoteControlMenu();
ImGui::SetCursorPosY(0.0f);
#endif
DrawRandomizerMenu();
ImGui::PopStyleVar(1);

View File

@ -7,7 +7,10 @@
#include "UIWidgets.hpp"
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include <ImGui/imgui.h>
#include <ImGui/imgui_internal.h>
#include <libultraship/libultraship.h>

View File

@ -12,6 +12,9 @@
#include <vector>
#include <span>
#include <stdint.h>
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include <ImGui/imgui.h>
namespace UIWidgets {

View File

@ -1521,6 +1521,13 @@ void Inventory_SwapAgeEquipment(void) {
}
}
// In Rando, when switching to adult for the second+ time, if a sword was not previously
// equiped in MS shuffle, then we need to set the swordless flag again
if (IS_RANDO && Randomizer_GetSettingValue(RSK_SHUFFLE_MASTER_SWORD) &&
gSaveContext.equips.buttonItems[0] == ITEM_NONE) {
Flags_SetInfTable(INFTABLE_SWORDLESS);
}
gSaveContext.equips.equipment = gSaveContext.adultEquips.equipment;
}
} else {
@ -1589,6 +1596,13 @@ void Inventory_SwapAgeEquipment(void) {
}
}
// In Rando, when switching to child from a swordless adult, and child Link previously had a
// sword equiped, then we need to unset the swordless flag to match
if (IS_RANDO && Randomizer_GetSettingValue(RSK_SHUFFLE_MASTER_SWORD) &&
gSaveContext.equips.buttonItems[0] != ITEM_NONE) {
Flags_UnsetInfTable(INFTABLE_SWORDLESS);
}
gSaveContext.equips.equipment = gSaveContext.childEquips.equipment;
gSaveContext.equips.equipment &= (u16) ~(0xF << (EQUIP_TYPE_SWORD * 4));
gSaveContext.equips.equipment |= EQUIP_VALUE_SWORD_KOKIRI << (EQUIP_TYPE_SWORD * 4);

View File

@ -69,7 +69,7 @@ static u8 sMaskTex16x32[16 * 32] = { { 0 } };
static u8 sMaskTex32x16[32 * 16] = { { 0 } };
static u8 sMaskTex8x8[8 * 8] = { { 0 } };
static u8 sMaskTex8x32[8 * 32] = { { 0 } };
static u8 sMaskTexLava[32 * 64] = { { 0 } };
static u8 sMaskTexLava[LAVA_TEX_WIDTH * LAVA_TEX_HEIGHT] = { { 0 } };
static u32* sLavaFloorModifiedTexRaw = NULL;
static u32* sLavaWavyTexRaw = NULL;
@ -112,6 +112,20 @@ void BossDodongo_RegisterBlendedLavaTextureUpdate() {
u32* lavaTex = ResourceGetDataByName(sLavaFloorLavaTex);
size_t lavaSize = ResourceGetSizeByName(sLavaFloorLavaTex);
size_t floorSize = ResourceGetSizeByName(gDodongosCavernBossLavaFloorTex);
size_t rockSize = ResourceGetSizeByName(sLavaFloorRockTex);
// If the sizes don't match, then don't bother with the blended effect to avoid crashing
if (floorSize != lavaSize || floorSize != rockSize) {
uint8_t maskVal = !!Flags_GetClear(gPlayState, gPlayState->roomCtx.curRoom.num);
if (sMaskTexLava[0] != maskVal) {
for (int i = 0; i < ARRAY_COUNT(sMaskTexLava); i++) {
sMaskTexLava[i] = maskVal;
}
}
Gfx_RegisterBlendedTexture(gDodongosCavernBossLavaFloorTex, sMaskTexLava, NULL);
return;
}
sLavaFloorModifiedTexRaw = malloc(lavaSize);
sLavaWavyTexRaw = malloc(floorSize);
@ -121,7 +135,6 @@ void BossDodongo_RegisterBlendedLavaTextureUpdate() {
// When KD is dead, just immediately copy the rock texture
if (Flags_GetClear(gPlayState, gPlayState->roomCtx.curRoom.num)) {
u32* rockTex = ResourceGetDataByName(sLavaFloorRockTex);
size_t rockSize = ResourceGetSizeByName(sLavaFloorRockTex);
memcpy(sLavaFloorModifiedTexRaw, rockTex, rockSize);
}
@ -145,6 +158,13 @@ void BossDodongo_RegisterBlendedLavaTextureUpdate() {
Gfx_RegisterBlendedTexture(gDodongosCavernBossLavaFloorTex, sMaskTexLava, sLavaWavyTex);
}
// Set all true for the lava as it will always replace the scene texture
if (sMaskTexLava[0] == 0) {
for (int i = 0; i < ARRAY_COUNT(sMaskTexLava); i++) {
sMaskTexLava[i] = 1;
}
}
gfx_texture_cache_clear();
}
@ -170,6 +190,11 @@ void func_808C12C4(u8* arg1, s16 arg2) {
// Same as func_808C1554 but works with u32 values for RGBA32 raw textures
void func_808C1554_Raw(void* arg0, void* floorTex, s32 arg2, f32 arg3) {
// Raw lava not registered, so abort the wave modification
if (sLavaWavyTexRaw == NULL || sLavaFloorModifiedTexRaw == NULL) {
return;
}
u16 width = ResourceGetTexWidthByName(arg0);
s32 size = ResourceGetTexHeightByName(arg0) * width;
@ -203,9 +228,6 @@ void func_808C1554_Raw(void* arg0, void* floorTex, s32 arg2, f32 arg3) {
}
free(sp54);
// Need to clear the cache after updating sLavaWavyTexRaw
gfx_texture_cache_clear();
}
// Modified to support CPU modified texture with the resource system
@ -233,9 +255,6 @@ void func_808C1554(void* arg0, void* floorTex, s32 arg2, f32 arg3) {
temp_s3[i + temp2] = sp54[i + i2];
}
}
// Need to clear the cache after updating sLavaWavyTex
gfx_texture_cache_clear();
}
void func_808C17C8(PlayState* play, Vec3f* arg1, Vec3f* arg2, Vec3f* arg3, f32 arg4, s16 arg5) {
@ -325,7 +344,7 @@ void BossDodongo_Init(Actor* thisx, PlayState* play) {
this->actor.flags &= ~ACTOR_FLAG_TARGETABLE;
// #region SOH [General]
// Init mask values for all blended textures
// Init mask values for all KD blended textures
for (int i = 0; i < ARRAY_COUNT(sMaskTex8x16); i++) {
sMaskTex8x16[i] = 0;
}
@ -341,10 +360,6 @@ void BossDodongo_Init(Actor* thisx, PlayState* play) {
for (int i = 0; i < ARRAY_COUNT(sMaskTex32x16); i++) {
sMaskTex32x16[i] = 0;
}
// Set all true for the lava as it will always replace the scene texture
for (int i = 0; i < ARRAY_COUNT(sMaskTexLava); i++) {
sMaskTexLava[i] = 1;
}
// Register all blended textures
Gfx_RegisterBlendedTexture(object_kingdodongo_Tex_015890, sMaskTex8x16, NULL);
@ -1174,8 +1189,13 @@ void BossDodongo_Update(Actor* thisx, PlayState* play2) {
for (i2 = 0; i2 < 20; i2++) {
s16 new_var = this->unk_1C2 & (LAVA_TEX_SIZE - 1);
// Raw lava must be registered, otherwise skip the effect for incompatible texture pack
// and instead set the mask to simulate the lava disappearing by turning black
if (sLavaFloorModifiedTexRaw != NULL) {
// Compute the index to a scaled position (scaling pseudo x,y as a 1D value)
s32 indexStart = ((new_var % LAVA_TEX_WIDTH) * widthScale) + ((new_var / LAVA_TEX_WIDTH) * width * heightScale);
s32 indexStart =
((new_var % LAVA_TEX_WIDTH) * widthScale) + ((new_var / LAVA_TEX_WIDTH) * width * heightScale);
// From the starting index, apply extra pixels right/down based on the scale
for (size_t j = 0; j < heightScale; j++) {
@ -1184,6 +1204,9 @@ void BossDodongo_Update(Actor* thisx, PlayState* play2) {
ptr1[scaledIndex] = ptr2[scaledIndex];
}
}
} else {
sMaskTexLava[new_var] = 1;
}
this->unk_1C2 += 37;
}
@ -1322,8 +1345,16 @@ void BossDodongo_Draw(Actor* thisx, PlayState* play) {
gSPInvalidateTexCache(POLY_OPA_DISP++, sMaskTex32x16);
}
if (this->unk_1C6 != 0) {
gSPInvalidateTexCache(POLY_OPA_DISP++, sMaskTexLava);
// Using WORK_DISP to invalidate these textures as they are used in drawing the scene textures which happens
// before actors are drawn. WORK_DISP comes before POLAY_OPA_DISP. It is probably not meant for this, but it
// at least works for now.
// Alternatively, having a way to invalidate just these pointers from the Update func should be sufficient.
if (sLavaFloorModifiedTexRaw != NULL) {
gSPInvalidateTexCache(WORK_DISP++, sLavaWavyTexRaw);
} else {
gSPInvalidateTexCache(WORK_DISP++, sLavaWavyTex);
}
if ((this->unk_1C0 >= 2) && (this->unk_1C0 & 1)) {

View File

@ -75,7 +75,6 @@ static InitChainEntry sInitChain[] = {
};
static UNK_TYPE sUnused;
GetItemEntry sItem;
Gfx gSkullTreasureChestChestSideAndLidDL[116] = {0};
Gfx gGoldTreasureChestChestSideAndLidDL[116] = {0};
@ -472,7 +471,7 @@ void EnBox_WaitOpen(EnBox* this, PlayState* play) {
func_8002DBD0(&this->dyna.actor, &sp4C, &player->actor.world.pos);
if (sp4C.z > -50.0f && sp4C.z < 0.0f && fabsf(sp4C.y) < 10.0f && fabsf(sp4C.x) < 20.0f &&
Player_IsFacingActor(&this->dyna.actor, 0x3000, play)) {
sItem = Randomizer_GetItemFromActor(this->dyna.actor.id, play->sceneNum, this->dyna.actor.params, this->dyna.actor.params >> 5 & 0x7F);
GetItemEntry sItem = Randomizer_GetItemFromActor(this->dyna.actor.id, play->sceneNum, this->dyna.actor.params, this->dyna.actor.params >> 5 & 0x7F);
GetItemEntry blueRupee = ItemTable_RetrieveEntry(MOD_NONE, GI_RUPEE_BLUE);
// RANDOTODO treasure chest game rando
@ -628,7 +627,7 @@ void EnBox_Update(Actor* thisx, PlayState* play) {
}
if (((!IS_RANDO && ((this->dyna.actor.params >> 5 & 0x7F) == 0x7C)) ||
(IS_RANDO && ABS(sItem.getItemId) == RG_ICE_TRAP)) &&
(IS_RANDO && this->getItemEntry.getItemId == RG_ICE_TRAP)) &&
this->actionFunc == EnBox_Open && this->skelanime.curFrame > 45 && this->iceSmokeTimer < 100) {
if (!CVarGetInteger("gAddTraps.enabled", 0)) {
EnBox_SpawnIceSmoke(this, play);

View File

@ -3,6 +3,7 @@
#include "overlays/actors/ovl_En_Syateki_Itm/z_en_syateki_itm.h"
#include "objects/object_ossan/object_ossan.h"
#include "soh/Enhancements/randomizer/randomizer_entrance.h"
#include "soh/Enhancements/custom-message/CustomMessageTypes.h"
#define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_WHILE_CULLED | ACTOR_FLAG_NO_LOCKON)
@ -371,7 +372,8 @@ void EnSyatekiMan_EndGame(EnSyatekiMan* this, PlayState* play) {
this->getItemId = GI_RUPEE_PURPLE;
}
} else {
if(IS_RANDO && !Flags_GetTreasure(play, 0x1F)) {
// Only give the adult rando reward when the player has a quiver
if (IS_RANDO && !Flags_GetTreasure(play, 0x1F) && CUR_UPG_VALUE(UPG_QUIVER) > 0) {
this->getItemEntry = Randomizer_GetItemFromKnownCheck(RC_KAK_SHOOTING_GALLERY_REWARD, GI_QUIVER_50);
this->getItemId = this->getItemEntry.getItemId;
Flags_SetTreasure(play, 0x1F);
@ -448,6 +450,9 @@ void EnSyatekiMan_FinishPrize(EnSyatekiMan* this, PlayState* play) {
Flags_SetItemGetInf(ITEMGETINF_0D);
} else if ((this->getItemId == GI_QUIVER_40) || (this->getItemId == GI_QUIVER_50)) {
Flags_SetItemGetInf(ITEMGETINF_0E);
} else if (IS_RANDO && LINK_IS_ADULT && CUR_UPG_VALUE(UPG_QUIVER) == 0) {
// In Rando without a quiver, display a message reminding the player to come back with a bow
Message_StartTextbox(play, TEXT_SHOOTING_GALLERY_MAN_COME_BACK_WITH_BOW, NULL);
}
this->gameResult = SYATEKI_RESULT_NONE;
this->actor.parent = this->tempGallery;

View File

@ -11003,7 +11003,14 @@ void Player_UseTunicBoots(Player* this, PlayState* play) {
s32 i;
s32 item;
s32 itemAction;
if (!(this->stateFlags1 & PLAYER_STATE1_INPUT_DISABLED || this->stateFlags1 & PLAYER_STATE1_IN_ITEM_CS || this->stateFlags1 & PLAYER_STATE1_IN_CUTSCENE || this->stateFlags1 & PLAYER_STATE1_TEXT_ON_SCREEN || this->stateFlags2 & PLAYER_STATE2_OCARINA_PLAYING)) {
if (!(
this->stateFlags1 & PLAYER_STATE1_INPUT_DISABLED ||
this->stateFlags1 & PLAYER_STATE1_IN_ITEM_CS ||
this->stateFlags1 & PLAYER_STATE1_IN_CUTSCENE ||
this->stateFlags1 & PLAYER_STATE1_TEXT_ON_SCREEN ||
this->stateFlags1 & PLAYER_STATE1_DEAD ||
this->stateFlags2 & PLAYER_STATE2_OCARINA_PLAYING
)) {
for (i = 0; i < ARRAY_COUNT(sItemButtons); i++) {
if (CHECK_BTN_ALL(sControlInput->press.button, sItemButtons[i])) {
break;
@ -11815,58 +11822,112 @@ void Player_Destroy(Actor* thisx, PlayState* play) {
//first person manipulate player actor
s16 func_8084ABD8(PlayState* play, Player* this, s32 arg2, s16 arg3) {
s32 temp1;
s16 temp2;
s16 temp3;
bool gInvertAimingXAxis = (CVarGetInteger("gInvertAimingXAxis", 0) && !CVarGetInteger("gMirroredWorld", 0)) || (!CVarGetInteger("gInvertAimingXAxis", 0) && CVarGetInteger("gMirroredWorld", 0));
s32 temp1 = 0;
s16 temp2 = 0;
s16 temp3 = 0;
s8 invertXAxisMulti = ((CVarGetInteger("gInvertAimingXAxis", 0) && !CVarGetInteger("gMirroredWorld", 0)) || (!CVarGetInteger("gInvertAimingXAxis", 0) && CVarGetInteger("gMirroredWorld", 0))) ? -1 : 1;
s8 invertYAxisMulti = CVarGetInteger("gInvertAimingYAxis", 1) ? 1 : -1;
f32 xAxisMulti = CVarGetFloat("gFirstPersonCameraSensitivityX", 1.0f);
f32 yAxisMulti = CVarGetFloat("gFirstPersonCameraSensitivityY", 1.0f);
if (!func_8002DD78(this) && !func_808334B4(this) && (arg2 == 0) && !CVarGetInteger("gDisableAutoCenterViewFirstPerson", 0)) {
temp2 = sControlInput->rel.stick_y * 240.0f * (CVarGetInteger("gInvertAimingYAxis", 1) ? 1 : -1); // Sensitivity not applied here because higher than default sensitivies will allow the camera to escape the autocentering, and glitch out massively
if (!func_8002DD78(this) && !func_808334B4(this) && (arg2 == 0)) { // First person without weapon
// Y Axis
if (!CVarGetInteger("gMoveWhileFirstPerson", 0)) {
temp2 += sControlInput->rel.stick_y * 240.0f * invertYAxisMulti * yAxisMulti;
}
if (CVarGetInteger("gRightStickAiming", 0) && fabsf(sControlInput->cur.right_stick_y) > 15.0f) {
temp2 += sControlInput->cur.right_stick_y * 240.0f * invertYAxisMulti * yAxisMulti;
}
if (fabsf(sControlInput->cur.gyro_x) > 0.01f) {
temp2 += (-sControlInput->cur.gyro_x) * 750.0f;
}
if (CVarGetInteger("gDisableAutoCenterViewFirstPerson", 0)) {
this->actor.focus.rot.x += temp2 * 0.1f;
this->actor.focus.rot.x = CLAMP(this->actor.focus.rot.x, -14000, 14000);
} else {
Math_SmoothStepToS(&this->actor.focus.rot.x, temp2, 14, 4000, 30);
}
temp2 = sControlInput->rel.stick_x * -16.0f * (gInvertAimingXAxis ? -1 : 1) * (CVarGetFloat("gFirstPersonCameraSensitivityX", 1.0f));
// X Axis
temp2 = 0;
if (!CVarGetInteger("gMoveWhileFirstPerson", 0)) {
temp2 += sControlInput->rel.stick_x * -16.0f * invertXAxisMulti * xAxisMulti;
}
if (CVarGetInteger("gRightStickAiming", 0) && fabsf(sControlInput->cur.right_stick_x) > 15.0f) {
temp2 += sControlInput->cur.right_stick_x * -16.0f * invertXAxisMulti * xAxisMulti;
}
if (fabsf(sControlInput->cur.gyro_y) > 0.01f) {
temp2 += (sControlInput->cur.gyro_y) * 750.0f * invertXAxisMulti;
}
temp2 = CLAMP(temp2, -3000, 3000);
this->actor.focus.rot.y += temp2;
} else {
} else { // First person with weapon
// Y Axis
temp1 = (this->stateFlags1 & PLAYER_STATE1_ON_HORSE) ? 3500 : 14000;
temp3 = ((sControlInput->rel.stick_y >= 0) ? 1 : -1) *
(s32)((1.0f - Math_CosS(sControlInput->rel.stick_y * 200)) * 1500.0f *
(CVarGetInteger("gInvertAimingYAxis", 1) ? 1 : -1)) * (CVarGetFloat("gFirstPersonCameraSensitivityY", 1.0f));
this->actor.focus.rot.x += temp3;
if (!CVarGetInteger("gMoveWhileFirstPerson", 0)) {
temp3 += ((sControlInput->rel.stick_y >= 0) ? 1 : -1) *
(s32)((1.0f - Math_CosS(sControlInput->rel.stick_y * 200)) * 1500.0f) * invertYAxisMulti * yAxisMulti;
}
if (CVarGetInteger("gRightStickAiming", 0) && fabsf(sControlInput->cur.right_stick_y) > 15.0f) {
temp3 += ((sControlInput->cur.right_stick_y >= 0) ? 1 : -1) *
(s32)((1.0f - Math_CosS(sControlInput->cur.right_stick_y * 200)) * 1500.0f) * invertYAxisMulti * yAxisMulti;
}
if (fabsf(sControlInput->cur.gyro_x) > 0.01f) {
this->actor.focus.rot.x -= (sControlInput->cur.gyro_x) * 750.0f;
temp3 += (-sControlInput->cur.gyro_x) * 750.0f;
}
if (fabsf(sControlInput->cur.right_stick_y) > 15.0f && CVarGetInteger("gRightStickAiming", 0) != 0) {
this->actor.focus.rot.x -=
(sControlInput->cur.right_stick_y) * 10.0f * (CVarGetInteger("gInvertAimingYAxis", 1) ? -1 : 1) * (CVarGetFloat("gFirstPersonCameraSensitivityY", 1.0f));
}
this->actor.focus.rot.x += temp3;
this->actor.focus.rot.x = CLAMP(this->actor.focus.rot.x, -temp1, temp1);
// X Axis
temp1 = 19114;
temp2 = this->actor.focus.rot.y - this->actor.shape.rot.y;
temp3 = 0;
if (!CVarGetInteger("gMoveWhileFirstPerson", 0)) {
temp3 = ((sControlInput->rel.stick_x >= 0) ? 1 : -1) *
(s32)((1.0f - Math_CosS(sControlInput->rel.stick_x * 200)) * -1500.0f *
(gInvertAimingXAxis ? -1 : 1)) * (CVarGetFloat("gFirstPersonCameraSensitivityX", 1.0f));
temp2 += temp3;
this->actor.focus.rot.y = CLAMP(temp2, -temp1, temp1) + this->actor.shape.rot.y;
(s32)((1.0f - Math_CosS(sControlInput->rel.stick_x * 200)) * -1500.0f) * invertXAxisMulti * xAxisMulti;
}
if (CVarGetInteger("gRightStickAiming", 0) && fabsf(sControlInput->cur.right_stick_x) > 15.0f) {
temp3 += ((sControlInput->cur.right_stick_x >= 0) ? 1 : -1) *
(s32)((1.0f - Math_CosS(sControlInput->cur.right_stick_x * 200)) * -1500.0f) * invertXAxisMulti * xAxisMulti;
}
if (fabsf(sControlInput->cur.gyro_y) > 0.01f) {
this->actor.focus.rot.y += (sControlInput->cur.gyro_y) * 750.0f * (CVarGetInteger("gMirroredWorld", 0) ? -1 : 1);
temp3 += (sControlInput->cur.gyro_y) * 750.0f * invertXAxisMulti;
}
temp2 += temp3;
this->actor.focus.rot.y = CLAMP(temp2, -temp1, temp1) + this->actor.shape.rot.y;
}
if (fabsf(sControlInput->cur.right_stick_x) > 15.0f && CVarGetInteger("gRightStickAiming", 0) != 0) {
this->actor.focus.rot.y +=
(sControlInput->cur.right_stick_x) * 10.0f * (gInvertAimingXAxis ? 1 : -1) * (CVarGetFloat("gFirstPersonCameraSensitivityX", 1.0f));
if (CVarGetInteger("gMoveWhileFirstPerson", 0)) {
f32 movementSpeed = LINK_IS_ADULT ? 9.0f : 8.25f;
if (CVarGetInteger("gMMBunnyHood", BUNNY_HOOD_VANILLA) != BUNNY_HOOD_VANILLA && this->currentMask == PLAYER_MASK_BUNNY) {
movementSpeed *= 1.5f;
}
f32 relX = (sControlInput->rel.stick_x / 10 * -invertXAxisMulti);
f32 relY = (sControlInput->rel.stick_y / 10);
// Normalize so that diagonal movement isn't faster
f32 relMag = sqrtf((relX * relX) + (relY * relY));
if (relMag > 1.0f) {
relX /= relMag;
relY /= relMag;
}
// Determine what left and right mean based on camera angle
f32 relX2 = relX * Math_CosS(this->actor.focus.rot.y) + relY * Math_SinS(this->actor.focus.rot.y);
f32 relY2 = relY * Math_CosS(this->actor.focus.rot.y) - relX * Math_SinS(this->actor.focus.rot.y);
// Calculate distance for footstep sound
f32 distance = sqrtf((relX2 * relX2) + (relY2 * relY2)) * movementSpeed;
func_8084029C(this, distance / 4.5f);
this->actor.world.pos.x += (relX2 * movementSpeed) + this->actor.colChkInfo.displacement.x;
this->actor.world.pos.z += (relY2 * movementSpeed) + this->actor.colChkInfo.displacement.z;
}
this->unk_6AE |= 2;
return func_80836AB8(this, (play->shootingGalleryStatus != 0) || func_8002DD78(this) || func_808334B4(this)) -
arg3;
return func_80836AB8(this, (play->shootingGalleryStatus != 0) || func_8002DD78(this) || func_808334B4(this)) - arg3;
}
void func_8084AEEC(Player* this, f32* arg1, f32 arg2, s16 arg3) {

View File

@ -1023,14 +1023,14 @@ void FileChoose_UpdateRandomizer() {
CVarSetString("gSpoilerLog", "");
}
if (CVarGetInteger("gNewFileDropped", 0) != 0 || !(Randomizer_IsSeedGenerated() || Randomizer_IsSpoilerLoaded()) &&
if (CVarGetInteger("gRandomizerNewFileDropped", 0) != 0 || !(Randomizer_IsSeedGenerated() || Randomizer_IsSpoilerLoaded()) &&
SpoilerFileExists(CVarGetString("gSpoilerLog", "")) && !fileSelectSpoilerFileLoaded) {
const char* fileLoc = CVarGetString("gSpoilerLog", "");
if (CVarGetInteger("gNewFileDropped", 0) != 0) {
CVarSetString("gSpoilerLog", CVarGetString("gDroppedFile", ""));
if (CVarGetInteger("gRandomizerNewFileDropped", 0) != 0) {
CVarSetString("gSpoilerLog", CVarGetString("gRandomizerDroppedFile", ""));
}
CVarSetInteger("gNewFileDropped", 0);
CVarSetString("gDroppedFile", "");
CVarSetInteger("gRandomizerNewFileDropped", 0);
CVarSetString("gRandomizerDroppedFile", "");
Randomizer_ParseSpoiler(fileLoc);
fileSelectSpoilerFileLoaded = true;
@ -1057,7 +1057,6 @@ void FileChoose_UpdateMainMenu(GameState* thisx) {
Input* input = &this->state.input[0];
bool dpad = CVarGetInteger("gDpadText", 0);
SoH_ProcessDroppedFiles();
FileChoose_UpdateRandomizer();
if (CHECK_BTN_ALL(input->press.button, BTN_START) || CHECK_BTN_ALL(input->press.button, BTN_A)) {
@ -1248,7 +1247,6 @@ void FileChoose_UpdateQuestMenu(GameState* thisx) {
s8 i = 0;
bool dpad = CVarGetInteger("gDpadText", 0);
SoH_ProcessDroppedFiles();
FileChoose_UpdateRandomizer();
if (ABS(this->stickRelX) > 30 || (dpad && CHECK_BTN_ANY(input->press.button, BTN_DLEFT | BTN_DRIGHT))) {

View File

@ -457,7 +457,6 @@ void FileChoose_DrawNameEntry(GameState* thisx) {
this->prevConfigMode = CM_MAIN_MENU;
this->configMode = CM_NAME_ENTRY_TO_MAIN;
CVarSetInteger("gOnFileSelectNameEntry", 0);
CVarSetInteger("gNewFileDropped", 0);
Randomizer_SetSeedGenerated(false);
this->nameBoxAlpha[this->buttonIndex] = this->nameAlpha[this->buttonIndex] = 200;
this->connectorAlpha[this->buttonIndex] = 255;

View File

@ -343,7 +343,7 @@ void KaleidoScope_DrawDungeonMap(PlayState* play, GraphicsContext* gfxCtx) {
// Offset the U value of each vertex to be in the mirror boundary for the map textures
if (mirroredWorld) {
for (size_t i = 0; i < 8; i++) {
pauseCtx->mapPageVtx[60 + i].v.tc[0] += 48 << 5;
pauseCtx->mapPageVtx[60 + i].v.tc[0] += MAP_48x85_TEX_WIDTH << 5;
}
}
@ -353,8 +353,9 @@ void KaleidoScope_DrawDungeonMap(PlayState* play, GraphicsContext* gfxCtx) {
gSPInvalidateTexCache(POLY_KAL_DISP++, interfaceCtx->mapSegment[0]);
gSPInvalidateTexCache(POLY_KAL_DISP++, interfaceCtx->mapSegment[1]);
gDPLoadTextureBlock_4b(POLY_KAL_DISP++, interfaceCtx->mapSegmentName[0], G_IM_FMT_CI, 48, 85, 0, G_TX_WRAP | mirrorMode,
G_TX_WRAP | G_TX_NOMIRROR, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD);
gDPLoadTextureBlock_4b(POLY_KAL_DISP++, interfaceCtx->mapSegmentName[0], G_IM_FMT_CI, MAP_48x85_TEX_WIDTH,
MAP_48x85_TEX_HEIGHT, 0, G_TX_WRAP | mirrorMode, G_TX_WRAP | G_TX_NOMIRROR, G_TX_NOMASK,
G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD);
// Swap vertices to render left half on the right and vice-versa
if (mirroredWorld) {
@ -363,9 +364,9 @@ void KaleidoScope_DrawDungeonMap(PlayState* play, GraphicsContext* gfxCtx) {
gSP1Quadrangle(POLY_KAL_DISP++, 0, 2, 3, 1, 0);
}
gDPLoadTextureBlock_4b(POLY_KAL_DISP++, interfaceCtx->mapSegmentName[1], G_IM_FMT_CI, 48, 85, 0,
G_TX_WRAP | mirrorMode, G_TX_WRAP | G_TX_NOMIRROR, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD,
G_TX_NOLOD);
gDPLoadTextureBlock_4b(POLY_KAL_DISP++, interfaceCtx->mapSegmentName[1], G_IM_FMT_CI, MAP_48x85_TEX_WIDTH,
MAP_48x85_TEX_HEIGHT, 0, G_TX_WRAP | mirrorMode, G_TX_WRAP | G_TX_NOMIRROR, G_TX_NOMASK,
G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD);
if (mirroredWorld) {
gSP1Quadrangle(POLY_KAL_DISP++, 0, 2, 3, 1, 0);

View File

@ -13,6 +13,10 @@ extern u8 gItemAgeReqs[];
extern u8 gAreaGsFlags[];
extern bool gSelectingMask;
#define MAP_48x85_TEX_WIDTH 48
#define MAP_48x85_TEX_HEIGHT 85
#define MAP_48x85_TEX_SIZE ((MAP_48x85_TEX_WIDTH * MAP_48x85_TEX_HEIGHT) / 2) // 48x85 CI4 texture
#define AGE_REQ_ADULT LINK_AGE_ADULT
#define AGE_REQ_CHILD LINK_AGE_CHILD
#define AGE_REQ_NONE 9

View File

@ -1205,6 +1205,8 @@ Gfx* KaleidoScope_DrawPageSections(Gfx* gfx, Vtx* vertices, void** textures) {
return gfx;
}
static uint8_t mapBlendMask[MAP_48x85_TEX_WIDTH * MAP_48x85_TEX_HEIGHT];
void KaleidoScope_DrawPages(PlayState* play, GraphicsContext* gfxCtx) {
static Color_RGB8 D_8082ACF4[12] = {
{ 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 255, 255, 0 }, { 0, 0, 0 },
@ -1373,6 +1375,10 @@ void KaleidoScope_DrawPages(PlayState* play, GraphicsContext* gfxCtx) {
}
}
// Need to invalidate the blend mask every frame. Ideally this would be done in KaleidoScope_DrawDungeonMap
// but the reference is not shared between files
gSPInvalidateTexCache(POLY_KAL_DISP++, mapBlendMask);
if (pauseCtx->pageIndex) { // pageIndex != PAUSE_ITEM
gDPPipeSync(OVERLAY_DISP++);
gDPSetCombineMode(OVERLAY_DISP++, G_CC_MODULATEIA, G_CC_MODULATEIA);
@ -3315,13 +3321,118 @@ void KaleidoScope_UpdateCursorSize(PauseContext* pauseCtx) {
pauseCtx->cursorVtx[14].v.ob[1] = pauseCtx->cursorVtx[15].v.ob[1] = pauseCtx->cursorVtx[12].v.ob[1] - 16;
}
// Modifed map texture buffers for registered blend effects and the room indicator color
static uint8_t mapLeftTexModified[MAP_48x85_TEX_SIZE];
static uint8_t mapRightTexModified[MAP_48x85_TEX_SIZE];
static uint8_t* mapLeftTexModifiedRaw = NULL;
static uint8_t* mapRightTexModifiedRaw = NULL;
// Load dungeon maps into the interface context
// SoH [General] - Modified to account for our resource system and HD textures
void KaleidoScope_LoadDungeonMap(PlayState* play) {
InterfaceContext* interfaceCtx = &play->interfaceCtx;
// Free old textures
if (mapLeftTexModifiedRaw != NULL) {
free(mapLeftTexModifiedRaw);
mapLeftTexModifiedRaw = NULL;
}
if (mapRightTexModifiedRaw != NULL) {
free(mapRightTexModifiedRaw);
mapRightTexModifiedRaw = NULL;
}
// Unload original textures to bypass cache result for lookups
ResourceMgr_UnloadOriginalWhenAltExists(sDungeonMapTexs[R_MAP_TEX_INDEX]);
ResourceMgr_UnloadOriginalWhenAltExists(sDungeonMapTexs[R_MAP_TEX_INDEX + 1]);
interfaceCtx->mapSegmentName[0] = sDungeonMapTexs[R_MAP_TEX_INDEX];
interfaceCtx->mapSegmentName[1] = sDungeonMapTexs[R_MAP_TEX_INDEX + 1];
interfaceCtx->mapSegment[0] = ResourceGetDataByName(sDungeonMapTexs[R_MAP_TEX_INDEX]);
interfaceCtx->mapSegment[1] = ResourceGetDataByName(sDungeonMapTexs[R_MAP_TEX_INDEX + 1]);
// When the texture is HD (raw) we need to copy a dynamic amount of data
// Otherwise the original asset has a static size
if (ResourceMgr_TexIsRaw(interfaceCtx->mapSegmentName[0])) {
u32 width = ResourceGetTexWidthByName(interfaceCtx->mapSegmentName[0]);
u32 height = ResourceGetTexHeightByName(interfaceCtx->mapSegmentName[0]);
size_t size = (width * height) / 2; // account for CI4 size
// Resource size being larger than the calculated CI size means it is most likely not a CI4 texture
// Abort early end undo the blended effect by clearing the mask to avoid crashing
if (size < ResourceGetTexSizeByName(interfaceCtx->mapSegmentName[0])) {
if (mapBlendMask[0] != 0) {
for (size_t i = 0; i < ARRAY_COUNT(mapBlendMask); i++) {
mapBlendMask[i] = 0;
}
}
interfaceCtx->mapSegment[0] = NULL;
interfaceCtx->mapSegment[1] = NULL;
Gfx_RegisterBlendedTexture(interfaceCtx->mapSegmentName[0], mapBlendMask, NULL);
Gfx_RegisterBlendedTexture(interfaceCtx->mapSegmentName[1], mapBlendMask, NULL);
return;
}
u8* map1TexRaw = ResourceGetDataByName(interfaceCtx->mapSegmentName[0]);
u8* map2TexRaw = ResourceGetDataByName(interfaceCtx->mapSegmentName[1]);
mapLeftTexModifiedRaw = malloc(size);
mapRightTexModifiedRaw = malloc(size);
memcpy(mapLeftTexModifiedRaw, map1TexRaw, size);
memcpy(mapRightTexModifiedRaw, map2TexRaw, size);
interfaceCtx->mapSegment[0] = mapLeftTexModifiedRaw;
interfaceCtx->mapSegment[1] = mapRightTexModifiedRaw;
} else {
u8* map1Tex = ResourceGetDataByName(interfaceCtx->mapSegmentName[0]);
u8* map2Tex = ResourceGetDataByName(interfaceCtx->mapSegmentName[1]);
memcpy(mapLeftTexModified, map1Tex, MAP_48x85_TEX_SIZE);
memcpy(mapRightTexModified, map2Tex, MAP_48x85_TEX_SIZE);
interfaceCtx->mapSegment[0] = mapLeftTexModified;
interfaceCtx->mapSegment[1] = mapRightTexModified;
}
// Mark and register the blend mask for the copied textures
if (mapBlendMask[0] != 1) {
for (size_t i = 0; i < ARRAY_COUNT(mapBlendMask); i++) {
mapBlendMask[i] = 1;
}
}
Gfx_RegisterBlendedTexture(interfaceCtx->mapSegmentName[0], mapBlendMask, interfaceCtx->mapSegment[0]);
Gfx_RegisterBlendedTexture(interfaceCtx->mapSegmentName[1], mapBlendMask, interfaceCtx->mapSegment[1]);
}
static uint8_t registeredDungeonMapTextureHook = false;
void KaleidoScope_RegisterUpdatedDungeonMapTexture() {
if (gPlayState == NULL) {
return;
}
PauseContext* pauseCtx = &gPlayState->pauseCtx;
// Kaleido is not open in a dungeon so there is nothing to do
if (R_PAUSE_MENU_MODE < 3 || pauseCtx->state < 4 || pauseCtx->state > 7 || !sInDungeonScene) {
return;
}
KaleidoScope_UpdateDungeonMap(gPlayState);
// KaleidoScope_UpdateDungeonMap will update the palette index for the current floor if the cursor is on the floor
// If the player toggles alt assets while the cursor is not in the floor level, then we handle the palette index here
if (gPlayState->sceneNum >= SCENE_DEKU_TREE && gPlayState->sceneNum <= SCENE_TREASURE_BOX_SHOP &&
(VREG(30) + 3) == pauseCtx->dungeonMapSlot && (VREG(30) + 3) != pauseCtx->cursorPoint[PAUSE_MAP]) {
InterfaceContext* interfaceCtx = &gPlayState->interfaceCtx;
int32_t size = ResourceGetTexSizeByName(interfaceCtx->mapSegmentName[0]);
KaleidoScope_OverridePalIndexCI4(interfaceCtx->mapSegment[0], size, interfaceCtx->mapPaletteIndex, 14);
KaleidoScope_OverridePalIndexCI4(interfaceCtx->mapSegment[1], size, interfaceCtx->mapPaletteIndex, 14);
}
}
void KaleidoScope_UpdateDungeonMap(PlayState* play) {
@ -3333,19 +3444,29 @@ void KaleidoScope_UpdateDungeonMap(PlayState* play) {
KaleidoScope_LoadDungeonMap(play);
Map_SetFloorPalettesData(play, pauseCtx->dungeonMapSlot - 3);
s32 size = MAP_48x85_TEX_SIZE;
if (ResourceMgr_TexIsRaw(interfaceCtx->mapSegmentName[0])) {
size = ResourceGetTexSizeByName(interfaceCtx->mapSegmentName[0]);
}
if ((play->sceneNum >= SCENE_DEKU_TREE) && (play->sceneNum <= SCENE_TREASURE_BOX_SHOP)) {
if ((VREG(30) + 3) == pauseCtx->cursorPoint[PAUSE_MAP]) {
// HDTODO: Handle Runtime Modified Textures (HD)
KaleidoScope_OverridePalIndexCI4(interfaceCtx->mapSegment[0], 2040, interfaceCtx->mapPaletteIndex, 14);
KaleidoScope_OverridePalIndexCI4(interfaceCtx->mapSegment[0], size, interfaceCtx->mapPaletteIndex, 14);
}
}
if ((play->sceneNum >= SCENE_DEKU_TREE) && (play->sceneNum <= SCENE_TREASURE_BOX_SHOP)) {
if ((VREG(30) + 3) == pauseCtx->cursorPoint[PAUSE_MAP]) {
// HDTODO: Handle Runtime Modified Textures (HD)
KaleidoScope_OverridePalIndexCI4(interfaceCtx->mapSegment[1], 2040, interfaceCtx->mapPaletteIndex, 14);
KaleidoScope_OverridePalIndexCI4(interfaceCtx->mapSegment[1], size, interfaceCtx->mapPaletteIndex, 14);
}
}
// Register alt listener to update the blended dungeon map textures on alt toggle
if (!registeredDungeonMapTextureHook) {
registeredDungeonMapTextureHook = true;
GameInteractor_RegisterOnAssetAltChange(KaleidoScope_RegisterUpdatedDungeonMapTexture);
}
}
void KaleidoScope_Update(PlayState* play)

View File

@ -1762,6 +1762,13 @@ static MapMarkData sMapMarkJabuJabuBellyMq[] = {
} },
{ MAP_MARK_NONE, 0, { 0 } },
},
// Jabu-Jabu's Belly minimap 16
// SoH [General] - This entry corresponds to Big Octorok's room and is missing in the MQ game
// N64 hardware does an OoB read and lands on MQ Forest Temple room 0
// To avoid UB with OoB for SoH, the correct entry is now added below
{
{ MAP_MARK_NONE, 0, { 0 } },
},
};
static MapMarkData sMapMarkForestTempleMq[] = {