mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2024-12-23 08:38:52 -05:00
Merge branch 'develop' of garrettjoecox.github.com:HarbourMasters/Shipwright into develop-rando-develop
This commit is contained in:
commit
1979d78788
17
.github/workflows/generate-builds.yml
vendored
17
.github/workflows/generate-builds.yml
vendored
@ -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
|
||||
|
2
.github/workflows/macports-deps.txt
vendored
2
.github/workflows/macports-deps.txt
vendored
@ -1 +1 @@
|
||||
libsdl2 +universal libpng +universal glew +universal
|
||||
libsdl2 +universal libsdl2_net +universal libpng +universal glew +universal
|
||||
|
@ -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
|
@ -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
|
||||
)
|
||||
|
@ -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 {
|
||||
|
@ -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)) {
|
||||
|
@ -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();
|
||||
|
@ -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>
|
||||
|
@ -1,4 +1,4 @@
|
||||
#ifdef ENABLE_CROWD_CONTROL
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
|
||||
#include "CrowdControl.h"
|
||||
#include "CrowdControlTypes.h"
|
||||
@ -17,25 +17,17 @@ extern "C" {
|
||||
extern PlayState* gPlayState;
|
||||
}
|
||||
|
||||
void CrowdControl::Init() {
|
||||
SDLNet_Init();
|
||||
}
|
||||
|
||||
void CrowdControl::Shutdown() {
|
||||
SDLNet_Quit();
|
||||
}
|
||||
|
||||
void CrowdControl::Enable() {
|
||||
if (isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (SDLNet_ResolveHost(&ip, "127.0.0.1", 43384) == -1) {
|
||||
SPDLOG_ERROR("[CrowdControl] SDLNet_ResolveHost: {}", SDLNet_GetError());
|
||||
}
|
||||
|
||||
isEnabled = true;
|
||||
ccThreadReceive = std::thread(&CrowdControl::ListenToServer, this);
|
||||
GameInteractor::Instance->EnableRemoteInteractor();
|
||||
GameInteractor::Instance->RegisterRemoteJsonHandler([&](nlohmann::json payload) {
|
||||
HandleRemoteData(payload);
|
||||
});
|
||||
|
||||
ccThreadProcess = std::thread(&CrowdControl::ProcessActiveEffects, this);
|
||||
}
|
||||
|
||||
@ -45,87 +37,42 @@ void CrowdControl::Disable() {
|
||||
}
|
||||
|
||||
isEnabled = false;
|
||||
ccThreadReceive.join();
|
||||
ccThreadProcess.join();
|
||||
GameInteractor::Instance->DisableRemoteInteractor();
|
||||
}
|
||||
|
||||
void CrowdControl::ListenToServer() {
|
||||
while (isEnabled) {
|
||||
while (!connected && isEnabled) {
|
||||
SPDLOG_TRACE("[CrowdControl] Attempting to make connection to server...");
|
||||
tcpsock = SDLNet_TCP_Open(&ip);
|
||||
void CrowdControl::HandleRemoteData(nlohmann::json payload) {
|
||||
Effect* incomingEffect = ParseMessage(payload);
|
||||
if (!incomingEffect) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tcpsock) {
|
||||
connected = true;
|
||||
SPDLOG_TRACE("[CrowdControl] Connection to server established!");
|
||||
// If effect is not a timed effect, execute and return result.
|
||||
if (!incomingEffect->timeRemaining) {
|
||||
EffectResult result = CrowdControl::ExecuteEffect(incomingEffect);
|
||||
EmitMessage(incomingEffect->id, incomingEffect->timeRemaining, result);
|
||||
} else {
|
||||
// If another timed effect is already active that conflicts with the incoming effect.
|
||||
bool isConflictingEffectActive = false;
|
||||
for (Effect* effect : activeEffects) {
|
||||
if (effect != incomingEffect && effect->category == incomingEffect->category && effect->id < incomingEffect->id) {
|
||||
isConflictingEffectActive = true;
|
||||
EmitMessage(incomingEffect->id, incomingEffect->timeRemaining, EffectResult::Retry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SDLNet_SocketSet socketSet = SDLNet_AllocSocketSet(1);
|
||||
if (tcpsock) {
|
||||
SDLNet_TCP_AddSocket(socketSet, tcpsock);
|
||||
}
|
||||
|
||||
// Listen to socket messages
|
||||
while (connected && tcpsock && isEnabled) {
|
||||
// we check first if socket has data, to not block in the TCP_Recv
|
||||
int socketsReady = SDLNet_CheckSockets(socketSet, 0);
|
||||
|
||||
if (socketsReady == -1) {
|
||||
SPDLOG_ERROR("[CrowdControl] SDLNet_CheckSockets: {}", SDLNet_GetError());
|
||||
break;
|
||||
if (!isConflictingEffectActive) {
|
||||
// Check if effect can be applied, if it can't, let CC know.
|
||||
EffectResult result = CrowdControl::CanApplyEffect(incomingEffect);
|
||||
if (result == EffectResult::Retry || result == EffectResult::Failure) {
|
||||
EmitMessage(incomingEffect->id, incomingEffect->timeRemaining, result);
|
||||
return;
|
||||
}
|
||||
|
||||
if (socketsReady == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int len = SDLNet_TCP_Recv(tcpsock, &received, sizeof(received));
|
||||
if (!len || !tcpsock || len == -1) {
|
||||
SPDLOG_ERROR("[CrowdControl] SDLNet_TCP_Recv: {}", SDLNet_GetError());
|
||||
break;
|
||||
}
|
||||
|
||||
Effect* incomingEffect = ParseMessage(received);
|
||||
if (!incomingEffect) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If effect is not a timed effect, execute and return result.
|
||||
if (!incomingEffect->timeRemaining) {
|
||||
EffectResult result = CrowdControl::ExecuteEffect(incomingEffect);
|
||||
EmitMessage(tcpsock, incomingEffect->id, incomingEffect->timeRemaining, result);
|
||||
} else {
|
||||
// If another timed effect is already active that conflicts with the incoming effect.
|
||||
bool isConflictingEffectActive = false;
|
||||
for (Effect* effect : activeEffects) {
|
||||
if (effect != incomingEffect && effect->category == incomingEffect->category && effect->id < incomingEffect->id) {
|
||||
isConflictingEffectActive = true;
|
||||
EmitMessage(tcpsock, incomingEffect->id, incomingEffect->timeRemaining, EffectResult::Retry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isConflictingEffectActive) {
|
||||
// Check if effect can be applied, if it can't, let CC know.
|
||||
EffectResult result = CrowdControl::CanApplyEffect(incomingEffect);
|
||||
if (result == EffectResult::Retry || result == EffectResult::Failure) {
|
||||
EmitMessage(tcpsock, incomingEffect->id, incomingEffect->timeRemaining, result);
|
||||
continue;
|
||||
}
|
||||
|
||||
activeEffectsMutex.lock();
|
||||
activeEffects.push_back(incomingEffect);
|
||||
activeEffectsMutex.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (connected) {
|
||||
SDLNet_TCP_Close(tcpsock);
|
||||
connected = false;
|
||||
SPDLOG_TRACE("[CrowdControl] Ending Listen thread...");
|
||||
activeEffectsMutex.lock();
|
||||
activeEffects.push_back(incomingEffect);
|
||||
activeEffectsMutex.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -147,13 +94,13 @@ void CrowdControl::ProcessActiveEffects() {
|
||||
if (effect->timeRemaining <= 0) {
|
||||
it = activeEffects.erase(std::remove(activeEffects.begin(), activeEffects.end(), effect),
|
||||
activeEffects.end());
|
||||
GameInteractor::RemoveEffect(effect->giEffect);
|
||||
GameInteractor::RemoveEffect(dynamic_cast<RemovableGameInteractionEffect*>(effect->giEffect));
|
||||
delete effect;
|
||||
} else {
|
||||
// If we have a success after previously being paused, tell CC to resume timer.
|
||||
if (effect->isPaused) {
|
||||
effect->isPaused = false;
|
||||
EmitMessage(tcpsock, effect->id, effect->timeRemaining, EffectResult::Resumed);
|
||||
EmitMessage(effect->id, effect->timeRemaining, EffectResult::Resumed);
|
||||
// If not paused before, subtract time from the timer and send a Success event if
|
||||
// the result is different from the last time this was ran.
|
||||
// Timed events are put on a thread that runs once per second.
|
||||
@ -161,7 +108,7 @@ void CrowdControl::ProcessActiveEffects() {
|
||||
effect->timeRemaining -= 1000;
|
||||
if (result != effect->lastExecutionResult) {
|
||||
effect->lastExecutionResult = result;
|
||||
EmitMessage(tcpsock, effect->id, effect->timeRemaining, EffectResult::Success);
|
||||
EmitMessage(effect->id, effect->timeRemaining, EffectResult::Success);
|
||||
}
|
||||
}
|
||||
it++;
|
||||
@ -169,7 +116,7 @@ void CrowdControl::ProcessActiveEffects() {
|
||||
} else { // Timed effects only do Success or Retry
|
||||
if (!effect->isPaused && effect->timeRemaining > 0) {
|
||||
effect->isPaused = true;
|
||||
EmitMessage(tcpsock, effect->id, effect->timeRemaining, EffectResult::Paused);
|
||||
EmitMessage(effect->id, effect->timeRemaining, EffectResult::Paused);
|
||||
}
|
||||
it++;
|
||||
}
|
||||
@ -182,7 +129,7 @@ void CrowdControl::ProcessActiveEffects() {
|
||||
SPDLOG_TRACE("[CrowdControl] Ending Process thread...");
|
||||
}
|
||||
|
||||
void CrowdControl::EmitMessage(TCPsocket socket, uint32_t eventId, long timeRemaining, EffectResult status) {
|
||||
void CrowdControl::EmitMessage(uint32_t eventId, long timeRemaining, EffectResult status) {
|
||||
nlohmann::json payload;
|
||||
|
||||
payload["id"] = eventId;
|
||||
@ -190,8 +137,9 @@ void CrowdControl::EmitMessage(TCPsocket socket, uint32_t eventId, long timeRema
|
||||
payload["timeRemaining"] = timeRemaining;
|
||||
payload["status"] = status;
|
||||
|
||||
std::string jsonPayload = payload.dump();
|
||||
SDLNet_TCP_Send(socket, jsonPayload.c_str(), jsonPayload.size() + 1);
|
||||
SPDLOG_INFO("[CrowdControl] Sending payload:\n{}", payload.dump());
|
||||
|
||||
GameInteractor::Instance->TransmitJsonToRemote(payload);
|
||||
}
|
||||
|
||||
CrowdControl::EffectResult CrowdControl::ExecuteEffect(Effect* effect) {
|
||||
@ -229,13 +177,14 @@ CrowdControl::EffectResult CrowdControl::TranslateGiEnum(GameInteractionEffectQu
|
||||
return result;
|
||||
}
|
||||
|
||||
CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) {
|
||||
nlohmann::json dataReceived = nlohmann::json::parse(payload, nullptr, false);
|
||||
if (dataReceived.is_discarded()) {
|
||||
SPDLOG_ERROR("Error parsing JSON");
|
||||
CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) {
|
||||
if (!dataReceived.contains("id") || !dataReceived.contains("type")) {
|
||||
SPDLOG_ERROR("[CrowdControl] Invalid payload received:\n{}", dataReceived);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SPDLOG_INFO("[CrowdControl] Received payload:\n{}", dataReceived.dump());
|
||||
|
||||
Effect* effect = new Effect();
|
||||
effect->lastExecutionResult = EffectResult::Initiate;
|
||||
effect->id = dataReceived["id"];
|
||||
@ -333,13 +282,13 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) {
|
||||
effect->category = kEffectCatDamageTaken;
|
||||
effect->timeRemaining = 30000;
|
||||
effect->giEffect = new GameInteractionEffect::ModifyDefenseModifier();
|
||||
effect->giEffect->parameters[0] = 2;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = 2;
|
||||
break;
|
||||
case kEffectTakeDoubleDamage:
|
||||
effect->category = kEffectCatDamageTaken;
|
||||
effect->timeRemaining = 30000;
|
||||
effect->giEffect = new GameInteractionEffect::ModifyDefenseModifier();
|
||||
effect->giEffect->parameters[0] = -2;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = -2;
|
||||
break;
|
||||
case kEffectOneHitKo:
|
||||
effect->category = kEffectCatDamageTaken;
|
||||
@ -356,37 +305,37 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) {
|
||||
effect->category = kEffectCatSpeed;
|
||||
effect->timeRemaining = 30000;
|
||||
effect->giEffect = new GameInteractionEffect::ModifyRunSpeedModifier();
|
||||
effect->giEffect->parameters[0] = 2;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = 2;
|
||||
break;
|
||||
case kEffectDecreaseSpeed:
|
||||
effect->category = kEffectCatSpeed;
|
||||
effect->timeRemaining = 30000;
|
||||
effect->giEffect = new GameInteractionEffect::ModifyRunSpeedModifier();
|
||||
effect->giEffect->parameters[0] = -2;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = -2;
|
||||
break;
|
||||
case kEffectLowGravity:
|
||||
effect->category = kEffectCatGravity;
|
||||
effect->timeRemaining = 30000;
|
||||
effect->giEffect = new GameInteractionEffect::ModifyGravity();
|
||||
effect->giEffect->parameters[0] = GI_GRAVITY_LEVEL_LIGHT;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_GRAVITY_LEVEL_LIGHT;
|
||||
break;
|
||||
case kEffectHighGravity:
|
||||
effect->category = kEffectCatGravity;
|
||||
effect->timeRemaining = 30000;
|
||||
effect->giEffect = new GameInteractionEffect::ModifyGravity();
|
||||
effect->giEffect->parameters[0] = GI_GRAVITY_LEVEL_HEAVY;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_GRAVITY_LEVEL_HEAVY;
|
||||
break;
|
||||
case kEffectForceIronBoots:
|
||||
effect->category = kEffectCatBoots;
|
||||
effect->timeRemaining = 30000;
|
||||
effect->giEffect = new GameInteractionEffect::ForceEquipBoots();
|
||||
effect->giEffect->parameters[0] = EQUIP_VALUE_BOOTS_IRON;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = EQUIP_VALUE_BOOTS_IRON;
|
||||
break;
|
||||
case kEffectForceHoverBoots:
|
||||
effect->category = kEffectCatBoots;
|
||||
effect->timeRemaining = 30000;
|
||||
effect->giEffect = new GameInteractionEffect::ForceEquipBoots();
|
||||
effect->giEffect->parameters[0] = EQUIP_VALUE_BOOTS_HOVER;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = EQUIP_VALUE_BOOTS_HOVER;
|
||||
break;
|
||||
case kEffectSlipperyFloor:
|
||||
effect->category = kEffectCatSlipperyFloor;
|
||||
@ -412,23 +361,23 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) {
|
||||
// Hurt or Heal Link
|
||||
case kEffectEmptyHeart:
|
||||
effect->giEffect = new GameInteractionEffect::ModifyHealth();
|
||||
effect->giEffect->parameters[0] = receivedParameter * -1;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter * -1;
|
||||
break;
|
||||
case kEffectFillHeart:
|
||||
effect->giEffect = new GameInteractionEffect::ModifyHealth();
|
||||
effect->giEffect->parameters[0] = receivedParameter;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter;
|
||||
break;
|
||||
case kEffectKnockbackLinkWeak:
|
||||
effect->giEffect = new GameInteractionEffect::KnockbackPlayer();
|
||||
effect->giEffect->parameters[0] = 1;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = 1;
|
||||
break;
|
||||
case kEffectKnockbackLinkStrong:
|
||||
effect->giEffect = new GameInteractionEffect::KnockbackPlayer();
|
||||
effect->giEffect->parameters[0] = 3;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = 3;
|
||||
break;
|
||||
case kEffectKnockbackLinkMega:
|
||||
effect->giEffect = new GameInteractionEffect::KnockbackPlayer();
|
||||
effect->giEffect->parameters[0] = 6;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = 6;
|
||||
break;
|
||||
case kEffectBurnLink:
|
||||
effect->giEffect = new GameInteractionEffect::BurnPlayer();
|
||||
@ -441,109 +390,109 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) {
|
||||
break;
|
||||
case kEffectKillLink:
|
||||
effect->giEffect = new GameInteractionEffect::SetPlayerHealth();
|
||||
effect->giEffect->parameters[0] = 0;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = 0;
|
||||
break;
|
||||
|
||||
// Give Items and Consumables
|
||||
case kEffectAddHeartContainer:
|
||||
effect->giEffect = new GameInteractionEffect::ModifyHeartContainers();
|
||||
effect->giEffect->parameters[0] = 1;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = 1;
|
||||
break;
|
||||
case kEffectFillMagic:
|
||||
effect->giEffect = new GameInteractionEffect::FillMagic();
|
||||
break;
|
||||
case kEffectAddRupees:
|
||||
effect->giEffect = new GameInteractionEffect::ModifyRupees();
|
||||
effect->giEffect->parameters[0] = receivedParameter;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter;
|
||||
break;
|
||||
case kEffectGiveDekuShield:
|
||||
effect->giEffect = new GameInteractionEffect::GiveOrTakeShield();
|
||||
effect->giEffect->parameters[0] = ITEM_SHIELD_DEKU;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = ITEM_SHIELD_DEKU;
|
||||
break;
|
||||
case kEffectGiveHylianShield:
|
||||
effect->giEffect = new GameInteractionEffect::GiveOrTakeShield();
|
||||
effect->giEffect->parameters[0] = ITEM_SHIELD_HYLIAN;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = ITEM_SHIELD_HYLIAN;
|
||||
break;
|
||||
case kEffectRefillSticks:
|
||||
effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo();
|
||||
effect->giEffect->parameters[0] = receivedParameter;
|
||||
effect->giEffect->parameters[1] = ITEM_STICK;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = ITEM_STICK;
|
||||
break;
|
||||
case kEffectRefillNuts:
|
||||
effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo();
|
||||
effect->giEffect->parameters[0] = receivedParameter;
|
||||
effect->giEffect->parameters[1] = ITEM_NUT;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = ITEM_NUT;
|
||||
break;
|
||||
case kEffectRefillBombs:
|
||||
effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo();
|
||||
effect->giEffect->parameters[0] = receivedParameter;
|
||||
effect->giEffect->parameters[1] = ITEM_BOMB;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = ITEM_BOMB;
|
||||
break;
|
||||
case kEffectRefillSeeds:
|
||||
effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo();
|
||||
effect->giEffect->parameters[0] = receivedParameter;
|
||||
effect->giEffect->parameters[1] = ITEM_SLINGSHOT;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = ITEM_SLINGSHOT;
|
||||
break;
|
||||
case kEffectRefillArrows:
|
||||
effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo();
|
||||
effect->giEffect->parameters[0] = receivedParameter;
|
||||
effect->giEffect->parameters[1] = ITEM_BOW;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = ITEM_BOW;
|
||||
break;
|
||||
case kEffectRefillBombchus:
|
||||
effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo();
|
||||
effect->giEffect->parameters[0] = receivedParameter;
|
||||
effect->giEffect->parameters[1] = ITEM_BOMBCHU;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = ITEM_BOMBCHU;
|
||||
break;
|
||||
|
||||
// Take Items and Consumables
|
||||
case kEffectRemoveHeartContainer:
|
||||
effect->giEffect = new GameInteractionEffect::ModifyHeartContainers();
|
||||
effect->giEffect->parameters[0] = -1;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = -1;
|
||||
break;
|
||||
case kEffectEmptyMagic:
|
||||
effect->giEffect = new GameInteractionEffect::EmptyMagic();
|
||||
break;
|
||||
case kEffectRemoveRupees:
|
||||
effect->giEffect = new GameInteractionEffect::ModifyRupees();
|
||||
effect->giEffect->parameters[0] = receivedParameter * -1;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter * -1;
|
||||
break;
|
||||
case kEffectTakeDekuShield:
|
||||
effect->giEffect = new GameInteractionEffect::GiveOrTakeShield();
|
||||
effect->giEffect->parameters[0] = -ITEM_SHIELD_DEKU;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = -ITEM_SHIELD_DEKU;
|
||||
break;
|
||||
case kEffectTakeHylianShield:
|
||||
effect->giEffect = new GameInteractionEffect::GiveOrTakeShield();
|
||||
effect->giEffect->parameters[0] = -ITEM_SHIELD_HYLIAN;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = -ITEM_SHIELD_HYLIAN;
|
||||
break;
|
||||
case kEffectTakeSticks:
|
||||
effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo();
|
||||
effect->giEffect->parameters[0] = receivedParameter * -1;
|
||||
effect->giEffect->parameters[1] = ITEM_STICK;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter * -1;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = ITEM_STICK;
|
||||
break;
|
||||
case kEffectTakeNuts:
|
||||
effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo();
|
||||
effect->giEffect->parameters[0] = receivedParameter * -1;
|
||||
effect->giEffect->parameters[1] = ITEM_NUT;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter * -1;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = ITEM_NUT;
|
||||
break;
|
||||
case kEffectTakeBombs:
|
||||
effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo();
|
||||
effect->giEffect->parameters[0] = receivedParameter * -1;
|
||||
effect->giEffect->parameters[1] = ITEM_BOMB;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter * -1;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = ITEM_BOMB;
|
||||
break;
|
||||
case kEffectTakeSeeds:
|
||||
effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo();
|
||||
effect->giEffect->parameters[0] = receivedParameter * -1;
|
||||
effect->giEffect->parameters[1] = ITEM_SLINGSHOT;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter * -1;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = ITEM_SLINGSHOT;
|
||||
break;
|
||||
case kEffectTakeArrows:
|
||||
effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo();
|
||||
effect->giEffect->parameters[0] = receivedParameter * -1;
|
||||
effect->giEffect->parameters[1] = ITEM_BOW;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter * -1;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = ITEM_BOW;
|
||||
break;
|
||||
case kEffectTakeBombchus:
|
||||
effect->giEffect = new GameInteractionEffect::AddOrTakeAmmo();
|
||||
effect->giEffect->parameters[0] = receivedParameter * -1;
|
||||
effect->giEffect->parameters[1] = ITEM_BOMBCHU;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = receivedParameter * -1;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = ITEM_BOMBCHU;
|
||||
break;
|
||||
|
||||
// Link Size Modifiers
|
||||
@ -551,25 +500,25 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) {
|
||||
effect->category = kEffectCatLinkSize;
|
||||
effect->timeRemaining = 30000;
|
||||
effect->giEffect = new GameInteractionEffect::ModifyLinkSize();
|
||||
effect->giEffect->parameters[0] = GI_LINK_SIZE_GIANT;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_LINK_SIZE_GIANT;
|
||||
break;
|
||||
case kEffectMinishLink:
|
||||
effect->category = kEffectCatLinkSize;
|
||||
effect->timeRemaining = 30000;
|
||||
effect->giEffect = new GameInteractionEffect::ModifyLinkSize();
|
||||
effect->giEffect->parameters[0] = GI_LINK_SIZE_MINISH;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_LINK_SIZE_MINISH;
|
||||
break;
|
||||
case kEffectPaperLink:
|
||||
effect->category = kEffectCatLinkSize;
|
||||
effect->timeRemaining = 30000;
|
||||
effect->giEffect = new GameInteractionEffect::ModifyLinkSize();
|
||||
effect->giEffect->parameters[0] = GI_LINK_SIZE_PAPER;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_LINK_SIZE_PAPER;
|
||||
break;
|
||||
case kEffectSquishedLink:
|
||||
effect->category = kEffectCatLinkSize;
|
||||
effect->timeRemaining = 30000;
|
||||
effect->giEffect = new GameInteractionEffect::ModifyLinkSize();
|
||||
effect->giEffect->parameters[0] = GI_LINK_SIZE_SQUISHED;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_LINK_SIZE_SQUISHED;
|
||||
break;
|
||||
case kEffectInvisibleLink:
|
||||
effect->category = kEffectCatLinkSize;
|
||||
@ -585,11 +534,11 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) {
|
||||
break;
|
||||
case kEffectSetTimeToDawn:
|
||||
effect->giEffect = new GameInteractionEffect::SetTimeOfDay();
|
||||
effect->giEffect->parameters[0] = GI_TIMEOFDAY_DAWN;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_TIMEOFDAY_DAWN;
|
||||
break;
|
||||
case kEffectSetTimeToDusk:
|
||||
effect->giEffect = new GameInteractionEffect::SetTimeOfDay();
|
||||
effect->giEffect->parameters[0] = GI_TIMEOFDAY_DUSK;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_TIMEOFDAY_DUSK;
|
||||
break;
|
||||
|
||||
// Visual Effects
|
||||
@ -632,186 +581,186 @@ CrowdControl::Effect* CrowdControl::ParseMessage(char payload[512]) {
|
||||
effect->category = kEffectCatRandomButtons;
|
||||
effect->timeRemaining = 30000;
|
||||
effect->giEffect = new GameInteractionEffect::PressRandomButton();
|
||||
effect->giEffect->parameters[0] = 30;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = 30;
|
||||
break;
|
||||
case kEffectClearCbuttons:
|
||||
effect->giEffect = new GameInteractionEffect::ClearAssignedButtons();
|
||||
effect->giEffect->parameters[0] = GI_BUTTONS_CBUTTONS;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_BUTTONS_CBUTTONS;
|
||||
break;
|
||||
case kEffectClearDpad:
|
||||
effect->giEffect = new GameInteractionEffect::ClearAssignedButtons();
|
||||
effect->giEffect->parameters[0] = GI_BUTTONS_DPAD;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_BUTTONS_DPAD;
|
||||
break;
|
||||
|
||||
// Teleport Player
|
||||
case kEffectTpLinksHouse:
|
||||
effect->giEffect = new GameInteractionEffect::TeleportPlayer();
|
||||
effect->giEffect->parameters[0] = GI_TP_DEST_LINKSHOUSE;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_TP_DEST_LINKSHOUSE;
|
||||
break;
|
||||
case kEffectTpMinuet:
|
||||
effect->giEffect = new GameInteractionEffect::TeleportPlayer();
|
||||
effect->giEffect->parameters[0] = GI_TP_DEST_MINUET;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_TP_DEST_MINUET;
|
||||
break;
|
||||
case kEffectTpBolero:
|
||||
effect->giEffect = new GameInteractionEffect::TeleportPlayer();
|
||||
effect->giEffect->parameters[0] = GI_TP_DEST_BOLERO;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_TP_DEST_BOLERO;
|
||||
break;
|
||||
case kEffectTpSerenade:
|
||||
effect->giEffect = new GameInteractionEffect::TeleportPlayer();
|
||||
effect->giEffect->parameters[0] = GI_TP_DEST_SERENADE;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_TP_DEST_SERENADE;
|
||||
break;
|
||||
case kEffectTpRequiem:
|
||||
effect->giEffect = new GameInteractionEffect::TeleportPlayer();
|
||||
effect->giEffect->parameters[0] = GI_TP_DEST_REQUIEM;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_TP_DEST_REQUIEM;
|
||||
break;
|
||||
case kEffectTpNocturne:
|
||||
effect->giEffect = new GameInteractionEffect::TeleportPlayer();
|
||||
effect->giEffect->parameters[0] = GI_TP_DEST_NOCTURNE;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_TP_DEST_NOCTURNE;
|
||||
break;
|
||||
case kEffectTpPrelude:
|
||||
effect->giEffect = new GameInteractionEffect::TeleportPlayer();
|
||||
effect->giEffect->parameters[0] = GI_TP_DEST_PRELUDE;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_TP_DEST_PRELUDE;
|
||||
break;
|
||||
|
||||
// Tunic Color (Bidding War)
|
||||
case kEffectTunicRed:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_RED;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_RED;
|
||||
break;
|
||||
case kEffectTunicGreen:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_GREEN;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_GREEN;
|
||||
break;
|
||||
case kEffectTunicBlue:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_BLUE;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_BLUE;
|
||||
break;
|
||||
case kEffectTunicOrange:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_ORANGE;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_ORANGE;
|
||||
break;
|
||||
case kEffectTunicYellow:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_YELLOW;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_YELLOW;
|
||||
break;
|
||||
case kEffectTunicPurple:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_PURPLE;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_PURPLE;
|
||||
break;
|
||||
case kEffectTunicPink:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_PINK;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_PINK;
|
||||
break;
|
||||
case kEffectTunicBrown:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_BROWN;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_BROWN;
|
||||
break;
|
||||
case kEffectTunicBlack:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_TUNICS;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_BLACK;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_BLACK;
|
||||
break;
|
||||
|
||||
// Navi Color (Bidding War)
|
||||
case kEffectNaviRed:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_NAVI;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_RED;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_RED;
|
||||
break;
|
||||
case kEffectNaviGreen:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_NAVI;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_GREEN;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_GREEN;
|
||||
break;
|
||||
case kEffectNaviBlue:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_NAVI;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_BLUE;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_BLUE;
|
||||
break;
|
||||
case kEffectNaviOrange:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_NAVI;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_ORANGE;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_ORANGE;
|
||||
break;
|
||||
case kEffectNaviYellow:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_NAVI;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_YELLOW;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_YELLOW;
|
||||
break;
|
||||
case kEffectNaviPurple:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_NAVI;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_PURPLE;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_PURPLE;
|
||||
break;
|
||||
case kEffectNaviPink:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_NAVI;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_PINK;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_PINK;
|
||||
break;
|
||||
case kEffectNaviBrown:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_NAVI;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_BROWN;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_BROWN;
|
||||
break;
|
||||
case kEffectNaviBlack:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_NAVI;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_BLACK;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_BLACK;
|
||||
break;
|
||||
|
||||
// Link's Hair Color (Bidding War)
|
||||
case kEffectHairRed:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_HAIR;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_RED;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_RED;
|
||||
break;
|
||||
case kEffectHairGreen:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_HAIR;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_GREEN;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_GREEN;
|
||||
break;
|
||||
case kEffectHairBlue:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_HAIR;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_BLUE;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_BLUE;
|
||||
break;
|
||||
case kEffectHairOrange:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_HAIR;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_ORANGE;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_ORANGE;
|
||||
break;
|
||||
case kEffectHairYellow:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_HAIR;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_YELLOW;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_YELLOW;
|
||||
break;
|
||||
case kEffectHairPurple:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_HAIR;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_PURPLE;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_PURPLE;
|
||||
break;
|
||||
case kEffectHairPink:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_HAIR;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_PINK;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_PINK;
|
||||
break;
|
||||
case kEffectHairBrown:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_HAIR;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_BROWN;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_BROWN;
|
||||
break;
|
||||
case kEffectHairBlack:
|
||||
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
|
||||
effect->giEffect->parameters[0] = GI_COSMETICS_HAIR;
|
||||
effect->giEffect->parameters[1] = GI_COLOR_BLACK;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
|
||||
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_BLACK;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -15,38 +15,46 @@ enum GameInteractionEffectQueryResult {
|
||||
class GameInteractionEffectBase {
|
||||
public:
|
||||
virtual GameInteractionEffectQueryResult CanBeApplied() = 0;
|
||||
virtual GameInteractionEffectQueryResult CanBeRemoved();
|
||||
GameInteractionEffectQueryResult Apply();
|
||||
GameInteractionEffectQueryResult Remove();
|
||||
int32_t parameters[3];
|
||||
|
||||
protected:
|
||||
protected:
|
||||
virtual void _Apply() = 0;
|
||||
};
|
||||
|
||||
class RemovableGameInteractionEffect: public GameInteractionEffectBase {
|
||||
public:
|
||||
virtual GameInteractionEffectQueryResult CanBeRemoved();
|
||||
GameInteractionEffectQueryResult Remove();
|
||||
protected:
|
||||
virtual void _Remove() {};
|
||||
};
|
||||
|
||||
class ParameterizedGameInteractionEffect {
|
||||
public:
|
||||
int32_t parameters[3];
|
||||
};
|
||||
|
||||
namespace GameInteractionEffect {
|
||||
class SetSceneFlag: public GameInteractionEffectBase {
|
||||
class SetSceneFlag: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
};
|
||||
|
||||
class UnsetSceneFlag: public GameInteractionEffectBase {
|
||||
class UnsetSceneFlag: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
};
|
||||
|
||||
class SetFlag: public GameInteractionEffectBase {
|
||||
class SetFlag: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
};
|
||||
|
||||
class UnsetFlag: public GameInteractionEffectBase {
|
||||
class UnsetFlag: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
};
|
||||
|
||||
class ModifyHeartContainers: public GameInteractionEffectBase {
|
||||
class ModifyHeartContainers: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
};
|
||||
@ -61,29 +69,29 @@ namespace GameInteractionEffect {
|
||||
void _Apply() override;
|
||||
};
|
||||
|
||||
class ModifyRupees: public GameInteractionEffectBase {
|
||||
class ModifyRupees: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
};
|
||||
|
||||
class NoUI: public GameInteractionEffectBase {
|
||||
class NoUI: public RemovableGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
void _Remove() override;
|
||||
};
|
||||
|
||||
class ModifyGravity: public GameInteractionEffectBase {
|
||||
class ModifyGravity: public RemovableGameInteractionEffect, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
void _Remove() override;
|
||||
};
|
||||
|
||||
class ModifyHealth: public GameInteractionEffectBase {
|
||||
class ModifyHealth: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
};
|
||||
|
||||
class SetPlayerHealth: public GameInteractionEffectBase {
|
||||
class SetPlayerHealth: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
};
|
||||
@ -103,98 +111,98 @@ namespace GameInteractionEffect {
|
||||
void _Apply() override;
|
||||
};
|
||||
|
||||
class KnockbackPlayer: public GameInteractionEffectBase {
|
||||
class KnockbackPlayer: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
};
|
||||
|
||||
class ModifyLinkSize: public GameInteractionEffectBase {
|
||||
class ModifyLinkSize: public RemovableGameInteractionEffect, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
void _Remove() override;
|
||||
};
|
||||
|
||||
class InvisibleLink : public GameInteractionEffectBase {
|
||||
class InvisibleLink : public RemovableGameInteractionEffect, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
void _Remove() override;
|
||||
};
|
||||
|
||||
class PacifistMode : public GameInteractionEffectBase {
|
||||
class PacifistMode : public RemovableGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
void _Remove() override;
|
||||
};
|
||||
|
||||
class DisableZTargeting: public GameInteractionEffectBase {
|
||||
class DisableZTargeting: public RemovableGameInteractionEffect, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
void _Remove() override;
|
||||
};
|
||||
|
||||
class WeatherRainstorm: public GameInteractionEffectBase {
|
||||
class WeatherRainstorm: public RemovableGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
void _Remove() override;
|
||||
};
|
||||
|
||||
class ReverseControls: public GameInteractionEffectBase {
|
||||
class ReverseControls: public RemovableGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
void _Remove() override;
|
||||
};
|
||||
|
||||
class ForceEquipBoots: public GameInteractionEffectBase {
|
||||
class ForceEquipBoots: public RemovableGameInteractionEffect, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
void _Remove() override;
|
||||
};
|
||||
|
||||
class ModifyRunSpeedModifier: public GameInteractionEffectBase {
|
||||
class ModifyRunSpeedModifier: public RemovableGameInteractionEffect, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
void _Remove() override;
|
||||
};
|
||||
|
||||
class OneHitKO : public GameInteractionEffectBase {
|
||||
class OneHitKO : public RemovableGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
void _Remove() override;
|
||||
};
|
||||
|
||||
class ModifyDefenseModifier: public GameInteractionEffectBase {
|
||||
class ModifyDefenseModifier: public RemovableGameInteractionEffect, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
void _Remove() override;
|
||||
};
|
||||
|
||||
class GiveOrTakeShield: public GameInteractionEffectBase {
|
||||
class GiveOrTakeShield: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
};
|
||||
|
||||
class TeleportPlayer: public GameInteractionEffectBase {
|
||||
class TeleportPlayer: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
};
|
||||
|
||||
class ClearAssignedButtons: public GameInteractionEffectBase {
|
||||
class ClearAssignedButtons: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
};
|
||||
|
||||
class SetTimeOfDay: public GameInteractionEffectBase {
|
||||
class SetTimeOfDay: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
};
|
||||
|
||||
class SetCollisionViewer: public GameInteractionEffectBase {
|
||||
class SetCollisionViewer: public RemovableGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
void _Remove() override;
|
||||
};
|
||||
|
||||
class SetCosmeticsColor: public GameInteractionEffectBase {
|
||||
class SetCosmeticsColor: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
};
|
||||
@ -204,52 +212,52 @@ namespace GameInteractionEffect {
|
||||
void _Apply() override;
|
||||
};
|
||||
|
||||
class PressButton: public GameInteractionEffectBase {
|
||||
class PressButton: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
};
|
||||
|
||||
class PressRandomButton: public GameInteractionEffectBase {
|
||||
class PressRandomButton: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
};
|
||||
|
||||
class AddOrTakeAmmo: public GameInteractionEffectBase {
|
||||
class AddOrTakeAmmo: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
};
|
||||
|
||||
class RandomBombFuseTimer: public GameInteractionEffectBase {
|
||||
class RandomBombFuseTimer: public RemovableGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
void _Remove() override;
|
||||
};
|
||||
|
||||
class DisableLedgeGrabs: public GameInteractionEffectBase {
|
||||
class DisableLedgeGrabs: public RemovableGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
void _Remove() override;
|
||||
};
|
||||
|
||||
class RandomWind: public GameInteractionEffectBase {
|
||||
class RandomWind: public RemovableGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
void _Remove() override;
|
||||
};
|
||||
|
||||
class RandomBonks: public GameInteractionEffectBase {
|
||||
class RandomBonks: public RemovableGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
void _Remove() override;
|
||||
};
|
||||
|
||||
class PlayerInvincibility: public GameInteractionEffectBase {
|
||||
class PlayerInvincibility: public RemovableGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
void _Remove() override;
|
||||
};
|
||||
|
||||
class SlipperyFloor: public GameInteractionEffectBase {
|
||||
class SlipperyFloor: public RemovableGameInteractionEffect {
|
||||
GameInteractionEffectQueryResult CanBeApplied() override;
|
||||
void _Apply() override;
|
||||
void _Remove() override;
|
||||
|
@ -31,7 +31,7 @@ GameInteractionEffectQueryResult GameInteractor::ApplyEffect(GameInteractionEffe
|
||||
return effect->Apply();
|
||||
}
|
||||
|
||||
GameInteractionEffectQueryResult GameInteractor::RemoveEffect(GameInteractionEffectBase* effect) {
|
||||
GameInteractionEffectQueryResult GameInteractor::RemoveEffect(RemovableGameInteractionEffect* effect) {
|
||||
return effect->Remove();
|
||||
}
|
||||
|
||||
|
@ -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 */
|
||||
|
182
soh/soh/Enhancements/game-interactor/GameInteractor_Remote.cpp
Normal file
182
soh/soh/Enhancements/game-interactor/GameInteractor_Remote.cpp
Normal 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
|
471
soh/soh/Enhancements/game-interactor/GameInteractor_Sail.cpp
Normal file
471
soh/soh/Enhancements/game-interactor/GameInteractor_Sail.cpp
Normal 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
|
29
soh/soh/Enhancements/game-interactor/GameInteractor_Sail.h
Normal file
29
soh/soh/Enhancements/game-interactor/GameInteractor_Sail.h
Normal 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
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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)));}}),
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
if (strcmp(spoilerFileName, "") != 0) {
|
||||
std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName));
|
||||
if (!spoilerFileStream) {
|
||||
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;
|
||||
}
|
||||
|
||||
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!",
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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},
|
||||
|
491
soh/soh/Enhancements/resolution-editor/ResolutionEditor.cpp
Normal file
491
soh/soh/Enhancements/resolution-editor/ResolutionEditor.cpp
Normal 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
|
16
soh/soh/Enhancements/resolution-editor/ResolutionEditor.h
Normal file
16
soh/soh/Enhancements/resolution-editor/ResolutionEditor.h
Normal 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
|
@ -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__
|
||||
@ -1102,7 +1106,12 @@ extern "C" void InitOTR() {
|
||||
SpeechSynthesizer::Instance = new SAPISpeechSynthesizer();
|
||||
SpeechSynthesizer::Instance->Init();
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
CrowdControl::Instance = new CrowdControl();
|
||||
GameInteractorSail::Instance = new GameInteractorSail();
|
||||
#endif
|
||||
|
||||
clearMtx = (uintptr_t)&gMtxClear;
|
||||
OTRMessage_Init();
|
||||
OTRAudio_Init();
|
||||
@ -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)) {
|
||||
CrowdControl::Instance->Enable();
|
||||
} else {
|
||||
CrowdControl::Instance->Disable();
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
SDLNet_Init();
|
||||
if (CVarGetInteger("gRemote.Enabled", 0)) {
|
||||
switch (CVarGetInteger("gRemote.Scheme", GI_SCHEME_SAIL)) {
|
||||
case GI_SCHEME_SAIL:
|
||||
GameInteractorSail::Instance->Enable();
|
||||
break;
|
||||
case GI_SCHEME_CROWD_CONTROL:
|
||||
CrowdControl::Instance->Enable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -1145,9 +1163,18 @@ extern "C" void SaveManager_ThreadPoolWait() {
|
||||
extern "C" void DeinitOTR() {
|
||||
SaveManager_ThreadPoolWait();
|
||||
OTRAudio_Exit();
|
||||
#ifdef ENABLE_CROWD_CONTROL
|
||||
CrowdControl::Instance->Disable();
|
||||
CrowdControl::Instance->Shutdown();
|
||||
#ifdef ENABLE_REMOTE_CONTROL
|
||||
if (CVarGetInteger("gRemote.Enabled", 0)) {
|
||||
switch (CVarGetInteger("gRemote.Scheme", GI_SCHEME_SAIL)) {
|
||||
case GI_SCHEME_SAIL:
|
||||
GameInteractorSail::Instance->Disable();
|
||||
break;
|
||||
case GI_SCHEME_CROWD_CONTROL:
|
||||
CrowdControl::Instance->Disable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
SDLNet_Quit();
|
||||
#endif
|
||||
|
||||
// Destroying gui here because we have shared ptrs to LUS objects which output to SPDLOG which is destroyed before these shared ptrs.
|
||||
@ -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,70 +2704,74 @@ 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) {
|
||||
try {
|
||||
std::ifstream configStream(SohUtils::Sanitize(droppedFile));
|
||||
if (!configStream) {
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json configJson;
|
||||
configStream >> configJson;
|
||||
|
||||
if (!configJson.contains("CVars")) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearCvars(enhancementsCvars);
|
||||
clearCvars(cheatCvars);
|
||||
clearCvars(randomizerCvars);
|
||||
|
||||
// Flatten everything under CVars into a single array
|
||||
auto cvars = configJson["CVars"].flatten();
|
||||
|
||||
for (auto& [key, value] : cvars.items()) {
|
||||
// Replace slashes with dots in key, and remove leading dot
|
||||
std::string path = key;
|
||||
std::replace(path.begin(), path.end(), '/', '.');
|
||||
if (path[0] == '.') {
|
||||
path.erase(0, 1);
|
||||
}
|
||||
if (value.is_string()) {
|
||||
CVarSetString(path.c_str(), value.get<std::string>().c_str());
|
||||
} else if (value.is_number_integer()) {
|
||||
CVarSetInteger(path.c_str(), value.get<int>());
|
||||
} else if (value.is_number_float()) {
|
||||
CVarSetFloat(path.c_str(), value.get<float>());
|
||||
}
|
||||
}
|
||||
|
||||
auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui();
|
||||
gui->GetGuiWindow("Console")->Hide();
|
||||
gui->GetGuiWindow("Actor Viewer")->Hide();
|
||||
gui->GetGuiWindow("Collision Viewer")->Hide();
|
||||
gui->GetGuiWindow("Save Editor")->Hide();
|
||||
gui->GetGuiWindow("Display List Viewer")->Hide();
|
||||
gui->GetGuiWindow("Stats")->Hide();
|
||||
std::dynamic_pointer_cast<LUS::ConsoleWindow>(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->ClearBindings();
|
||||
|
||||
gui->SaveConsoleVariablesOnNextTick();
|
||||
|
||||
uint32_t finalHash = boost::hash_32<std::string>{}(configJson.dump());
|
||||
gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Configuration Loaded. Hash: %d", finalHash);
|
||||
} catch (std::exception& e) {
|
||||
SPDLOG_ERROR("Failed to load config file: {}", e.what());
|
||||
auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui();
|
||||
gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Failed to load config file");
|
||||
return;
|
||||
} catch (...) {
|
||||
SPDLOG_ERROR("Failed to load config file");
|
||||
auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui();
|
||||
gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Failed to load config file");
|
||||
void SoH_ProcessDroppedFiles(std::string filePath) {
|
||||
try {
|
||||
std::ifstream configStream(filePath);
|
||||
if (!configStream) {
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
clearCvars(enhancementsCvars);
|
||||
clearCvars(cheatCvars);
|
||||
clearCvars(randomizerCvars);
|
||||
|
||||
// Flatten everything under CVars into a single array
|
||||
auto cvars = configJson["CVars"].flatten();
|
||||
|
||||
for (auto& [key, value] : cvars.items()) {
|
||||
// Replace slashes with dots in key, and remove leading dot
|
||||
std::string path = key;
|
||||
std::replace(path.begin(), path.end(), '/', '.');
|
||||
if (path[0] == '.') {
|
||||
path.erase(0, 1);
|
||||
}
|
||||
if (value.is_string()) {
|
||||
CVarSetString(path.c_str(), value.get<std::string>().c_str());
|
||||
} else if (value.is_number_integer()) {
|
||||
CVarSetInteger(path.c_str(), value.get<int>());
|
||||
} else if (value.is_number_float()) {
|
||||
CVarSetFloat(path.c_str(), value.get<float>());
|
||||
}
|
||||
}
|
||||
|
||||
auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui();
|
||||
gui->GetGuiWindow("Console")->Hide();
|
||||
gui->GetGuiWindow("Actor Viewer")->Hide();
|
||||
gui->GetGuiWindow("Collision Viewer")->Hide();
|
||||
gui->GetGuiWindow("Save Editor")->Hide();
|
||||
gui->GetGuiWindow("Display List Viewer")->Hide();
|
||||
gui->GetGuiWindow("Stats")->Hide();
|
||||
std::dynamic_pointer_cast<LUS::ConsoleWindow>(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->ClearBindings();
|
||||
|
||||
gui->SaveConsoleVariablesOnNextTick();
|
||||
|
||||
uint32_t finalHash = boost::hash_32<std::string>{}(configJson.dump());
|
||||
gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Configuration Loaded. Hash: %d", finalHash);
|
||||
} catch (std::exception& e) {
|
||||
SPDLOG_ERROR("Failed to load config file: {}", e.what());
|
||||
auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui();
|
||||
gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Failed to load config file");
|
||||
return;
|
||||
} catch (...) {
|
||||
SPDLOG_ERROR("Failed to load config file");
|
||||
auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui();
|
||||
gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Failed to load config file");
|
||||
return;
|
||||
}
|
||||
}
|
||||
// #endregion
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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,15 +1189,23 @@ void BossDodongo_Update(Actor* thisx, PlayState* play2) {
|
||||
|
||||
for (i2 = 0; i2 < 20; i2++) {
|
||||
s16 new_var = this->unk_1C2 & (LAVA_TEX_SIZE - 1);
|
||||
// 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);
|
||||
|
||||
// From the starting index, apply extra pixels right/down based on the scale
|
||||
for (size_t j = 0; j < heightScale; j++) {
|
||||
for (size_t i3 = 0; i3 < widthScale; i3++) {
|
||||
s32 scaledIndex = (indexStart + i3 + (j * width)) & (size - 1);
|
||||
ptr1[scaledIndex] = ptr2[scaledIndex];
|
||||
// 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);
|
||||
|
||||
// From the starting index, apply extra pixels right/down based on the scale
|
||||
for (size_t j = 0; j < heightScale; j++) {
|
||||
for (size_t i3 = 0; i3 < widthScale; i3++) {
|
||||
s32 scaledIndex = (indexStart + i3 + (j * width)) & (size - 1);
|
||||
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);
|
||||
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)) {
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
Math_SmoothStepToS(&this->actor.focus.rot.x, temp2, 14, 4000, 30);
|
||||
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 = ((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;
|
||||
|
||||
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) * 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 (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;
|
||||
}
|
||||
|
||||
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));
|
||||
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) {
|
||||
|
@ -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))) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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[] = {
|
||||
|
Loading…
Reference in New Issue
Block a user