diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 2e8fcd74..c18fee26 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -72,3 +72,65 @@ if(DOWNLOAD_DEPENDENCIES) set_property(TARGET libweaver PROPERTY CXX_STANDARD_REQUIRED ON) endif() +if(ISLE_USE_LWS) + if(DOWNLOAD_DEPENDENCIES) + include(FetchContent) + + # Fetch mbedTLS for TLS/WSS support in libwebsockets + FetchContent_Declare( + mbedtls + URL https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-3.6.5/mbedtls-3.6.5.tar.bz2 + URL_MD5 bc79602daf85f1cf35a686b53056de58 + # Patch cmake_minimum_required to avoid deprecation error with -Werror=dev + PATCH_COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/patch_mbedtls_cmake.cmake + ) + block() + set(ENABLE_TESTING OFF CACHE BOOL "" FORCE) + set(ENABLE_PROGRAMS OFF CACHE BOOL "" FORCE) + set(MBEDTLS_FATAL_WARNINGS OFF CACHE BOOL "" FORCE) + set(BUILD_SHARED_LIBS OFF) + FetchContent_MakeAvailable(mbedtls) + + # Point lws at the mbedTLS targets so it skips its own find logic + # Must be inside block() to access mbedtls_SOURCE_DIR/mbedtls_BINARY_DIR + set(LWS_MBEDTLS_INCLUDE_DIRS + "${mbedtls_SOURCE_DIR}/include;${mbedtls_BINARY_DIR}/include" + CACHE STRING "" FORCE + ) + set(LWS_MBEDTLS_LIBRARIES + "mbedtls;mbedx509;mbedcrypto" + CACHE STRING "" FORCE + ) + endblock() + + FetchContent_Declare( + libwebsockets + GIT_REPOSITORY "https://github.com/warmcat/libwebsockets.git" + GIT_TAG "v4.5-stable" + EXCLUDE_FROM_ALL + ) + block() + set(LWS_WITH_SSL ON CACHE BOOL "" FORCE) + set(LWS_WITH_MBEDTLS ON CACHE BOOL "" FORCE) + set(LWS_WITH_EXPORT_LWSTARGETS OFF CACHE BOOL "" FORCE) + # mbedTLS isn't built yet at configure time, so lws's check_function_exists + # calls fail. Pre-set the results for APIs that exist in mbedTLS 3.x. + set(LWS_HAVE_mbedtls_md_setup 1 CACHE INTERNAL "" FORCE) + set(LWS_HAVE_mbedtls_rsa_complete 1 CACHE INTERNAL "" FORCE) + set(LWS_WITH_SHARED OFF CACHE BOOL "" FORCE) + set(LWS_WITH_STATIC ON CACHE BOOL "" FORCE) + set(LWS_WITHOUT_TESTAPPS ON CACHE BOOL "" FORCE) + set(LWS_WITH_MINIMAL_EXAMPLES OFF CACHE BOOL "" FORCE) + # Disable treating warnings as errors in libwebsockets (GCC/Clang) + set(DISABLE_WERROR ON CACHE BOOL "" FORCE) + FetchContent_MakeAvailable(libwebsockets) + endblock() + # Disable MSVC /WX (warnings as errors) that lws sets unconditionally + if(TARGET websockets AND MSVC) + target_compile_options(websockets PRIVATE /WX-) + endif() + else() + find_package(Libwebsockets REQUIRED) + endif() +endif() + diff --git a/3rdparty/patch_mbedtls_cmake.cmake b/3rdparty/patch_mbedtls_cmake.cmake new file mode 100644 index 00000000..060da92e --- /dev/null +++ b/3rdparty/patch_mbedtls_cmake.cmake @@ -0,0 +1,7 @@ +file(READ "CMakeLists.txt" content) +string(REGEX REPLACE + "cmake_minimum_required\\(VERSION [0-9]+\\.[0-9]+[0-9.]*\\)" + "cmake_minimum_required(VERSION 3.10)" + content "${content}" +) +file(WRITE "CMakeLists.txt" "${content}") diff --git a/CMakeLists.txt b/CMakeLists.txt index 05dbacf7..dd2b5a30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,7 @@ option(ISLE_WERROR "Treat warnings as errors" OFF) cmake_dependent_option(ISLE_USE_DX5 "Build with internal DirectX 5 SDK" "${NOT_MINGW}" "WIN32;CMAKE_SIZEOF_VOID_P EQUAL 4" OFF) cmake_dependent_option(ISLE_MINIWIN "Use miniwin" ON "NOT ISLE_USE_DX5" OFF) cmake_dependent_option(ISLE_EXTENSIONS "Use extensions" ON "NOT ISLE_USE_DX5;NOT WINDOWS_STORE" OFF) +cmake_dependent_option(ISLE_USE_LWS "Use libwebsockets for native multiplayer" ON "ISLE_EXTENSIONS;NOT EMSCRIPTEN;NOT NINTENDO_3DS;NOT NINTENDO_SWITCH;NOT VITA" OFF) cmake_dependent_option(ISLE_BUILD_CONFIG "Build CONFIG.EXE application" ON "MSVC OR ISLE_MINIWIN;NOT NINTENDO_3DS;NOT NINTENDO_SWITCH;NOT WINDOWS_STORE;NOT VITA" OFF) cmake_dependent_option(ISLE_COMPILE_SHADERS "Compile shaders" ON "SDL_SHADERCROSS_BIN;TARGET Python3::Interpreter" OFF) cmake_dependent_option(CMAKE_POSITION_INDEPENDENT_CODE "Build with -fPIC" ON "NOT VITA" OFF) @@ -560,6 +561,19 @@ if (ISLE_EXTENSIONS) extensions/src/multiplayer/platforms/emscripten/websockettransport.cpp extensions/src/multiplayer/platforms/emscripten/callbacks.cpp ) + elseif(ISLE_USE_LWS) + target_sources(lego1 PRIVATE + extensions/src/multiplayer/platforms/native/lwstransport.cpp + extensions/src/multiplayer/platforms/native/nativecallbacks.cpp + ) + # Skip precompiled headers for native transport files to avoid miniwin/windows.h conflicts + set_source_files_properties( + extensions/src/multiplayer/platforms/native/lwstransport.cpp + extensions/src/multiplayer/platforms/native/nativecallbacks.cpp + PROPERTIES SKIP_PRECOMPILE_HEADERS ON + ) + target_compile_definitions(lego1 PRIVATE ISLE_USE_LWS) + target_link_libraries(lego1 PRIVATE websockets) endif() endif() diff --git a/extensions/include/extensions/multiplayer/networktransport.h b/extensions/include/extensions/multiplayer/networktransport.h index 7fd8876e..1a33f119 100644 --- a/extensions/include/extensions/multiplayer/networktransport.h +++ b/extensions/include/extensions/multiplayer/networktransport.h @@ -18,6 +18,7 @@ class NetworkTransport { virtual void Disconnect() = 0; virtual bool IsConnected() const = 0; virtual bool WasDisconnected() const = 0; + virtual bool WasRejected() const = 0; // Send binary data to all peers via relay virtual void Send(const uint8_t* p_data, size_t p_length) = 0; diff --git a/extensions/include/extensions/multiplayer/platforms/emscripten/websockettransport.h b/extensions/include/extensions/multiplayer/platforms/emscripten/websockettransport.h index 533447d2..88386ba3 100644 --- a/extensions/include/extensions/multiplayer/platforms/emscripten/websockettransport.h +++ b/extensions/include/extensions/multiplayer/platforms/emscripten/websockettransport.h @@ -18,6 +18,7 @@ class WebSocketTransport : public NetworkTransport { void Disconnect() override; bool IsConnected() const override; bool WasDisconnected() const override; + bool WasRejected() const override; void Send(const uint8_t* p_data, size_t p_length) override; size_t Receive(std::function p_callback) override; diff --git a/extensions/include/extensions/multiplayer/platforms/native/lwstransport.h b/extensions/include/extensions/multiplayer/platforms/native/lwstransport.h new file mode 100644 index 00000000..fc70682b --- /dev/null +++ b/extensions/include/extensions/multiplayer/platforms/native/lwstransport.h @@ -0,0 +1,72 @@ +#pragma once + +#ifndef __EMSCRIPTEN__ + +#include "extensions/multiplayer/networktransport.h" +#include "mxcriticalsection.h" +#include "mxthread.h" + +#include +#include +#include +#include + +struct lws_context; +struct lws; + +namespace Multiplayer +{ + +class LwsTransport; + +class LwsServiceThread : public MxThread { +public: + LwsServiceThread() : m_transport(nullptr) {} + MxResult Run() override; + void SetTransport(LwsTransport* p_transport) { m_transport = p_transport; } + +private: + LwsTransport* m_transport; +}; + +class LwsTransport : public NetworkTransport { + friend class LwsServiceThread; + +public: + LwsTransport(const std::string& p_relayBaseUrl); + ~LwsTransport() override; + + void Connect(const char* p_roomId) override; + void Disconnect() override; + bool IsConnected() const override; + bool WasDisconnected() const override; + bool WasRejected() const override; + void Send(const uint8_t* p_data, size_t p_length) override; + size_t Receive(std::function p_callback) override; + + // Called from static lws callback trampoline + int HandleLwsEvent(struct lws* p_wsi, int p_reason, void* p_in, size_t p_len); + +private: + void ServiceLoop(); + + std::string m_relayBaseUrl; + struct lws_context* m_context; + std::atomic m_wsi; + std::atomic m_connected; + std::atomic m_disconnected; + std::atomic m_wasEverConnected; + + MxCriticalSection m_sendCS; + MxCriticalSection m_recvCS; + std::deque> m_sendQueue; + std::deque> m_recvQueue; + std::vector m_fragment; + + LwsServiceThread* m_serviceThread; + std::atomic m_wantWritable; +}; + +} // namespace Multiplayer + +#endif // !__EMSCRIPTEN__ diff --git a/extensions/include/extensions/multiplayer/platforms/native/nativecallbacks.h b/extensions/include/extensions/multiplayer/platforms/native/nativecallbacks.h new file mode 100644 index 00000000..50d305ba --- /dev/null +++ b/extensions/include/extensions/multiplayer/platforms/native/nativecallbacks.h @@ -0,0 +1,20 @@ +#pragma once + +#ifndef __EMSCRIPTEN__ + +#include "extensions/multiplayer/platformcallbacks.h" + +namespace Multiplayer +{ + +class NativeCallbacks : public PlatformCallbacks { +public: + void OnPlayerCountChanged(int p_count) override; + void OnThirdPersonChanged(bool p_enabled) override; + void OnNameBubblesChanged(bool p_enabled) override; + void OnAllowCustomizeChanged(bool p_enabled) override; +}; + +} // namespace Multiplayer + +#endif // !__EMSCRIPTEN__ diff --git a/extensions/include/extensions/thirdpersoncamera/inputhandler.h b/extensions/include/extensions/thirdpersoncamera/inputhandler.h index 120aa06f..8ecd1746 100644 --- a/extensions/include/extensions/thirdpersoncamera/inputhandler.h +++ b/extensions/include/extensions/thirdpersoncamera/inputhandler.h @@ -42,6 +42,9 @@ class InputHandler { bool m_wantsAutoDisable; bool m_wantsAutoEnable; + bool m_rightButtonHeld; + float m_savedMouseX; + float m_savedMouseY; }; } // namespace ThirdPersonCamera diff --git a/extensions/src/multiplayer.cpp b/extensions/src/multiplayer.cpp index ae537381..7b29ded3 100644 --- a/extensions/src/multiplayer.cpp +++ b/extensions/src/multiplayer.cpp @@ -25,6 +25,9 @@ #include "extensions/multiplayer/platforms/emscripten/websockettransport.h" #include +#elif defined(ISLE_USE_LWS) +#include "extensions/multiplayer/platforms/native/lwstransport.h" +#include "extensions/multiplayer/platforms/native/nativecallbacks.h" #endif using namespace Extensions; @@ -51,7 +54,12 @@ void MultiplayerExt::Initialize() #ifdef __EMSCRIPTEN__ s_transport = new Multiplayer::WebSocketTransport(s_relayUrl); s_callbacks = new Multiplayer::EmscriptenCallbacks(); +#elif defined(ISLE_USE_LWS) + s_transport = new Multiplayer::LwsTransport(s_relayUrl); + s_callbacks = new Multiplayer::NativeCallbacks(); +#endif +#if defined(__EMSCRIPTEN__) || defined(ISLE_USE_LWS) s_networkManager = new Multiplayer::NetworkManager(); s_networkManager->Initialize(s_transport, s_callbacks); diff --git a/extensions/src/multiplayer/platforms/emscripten/websockettransport.cpp b/extensions/src/multiplayer/platforms/emscripten/websockettransport.cpp index 5e7a70ad..0b55dea8 100644 --- a/extensions/src/multiplayer/platforms/emscripten/websockettransport.cpp +++ b/extensions/src/multiplayer/platforms/emscripten/websockettransport.cpp @@ -121,6 +121,11 @@ bool WebSocketTransport::WasDisconnected() const return m_disconnectedFlag != 0; } +bool WebSocketTransport::WasRejected() const +{ + return m_disconnectedFlag != 0 && m_wasEverConnected == 0; +} + void WebSocketTransport::Send(const uint8_t* p_data, size_t p_length) { if (m_socketId <= 0 || !m_connectedFlag) { diff --git a/extensions/src/multiplayer/platforms/native/lwstransport.cpp b/extensions/src/multiplayer/platforms/native/lwstransport.cpp new file mode 100644 index 00000000..7c35a6c0 --- /dev/null +++ b/extensions/src/multiplayer/platforms/native/lwstransport.cpp @@ -0,0 +1,289 @@ +#ifndef __EMSCRIPTEN__ + +#include "extensions/multiplayer/platforms/native/lwstransport.h" + +#include +#include +#include + +namespace Multiplayer +{ + +static int LwsCallback(struct lws* p_wsi, enum lws_callback_reasons p_reason, void* p_user, void* p_in, size_t p_len) +{ + LwsTransport* transport = static_cast(lws_get_opaque_user_data(p_wsi)); + if (transport) { + return transport->HandleLwsEvent(p_wsi, static_cast(p_reason), p_in, p_len); + } + return 0; +} + +static constexpr size_t LWS_RX_BUFFER_SIZE = 8192; +static constexpr int LWS_SERVICE_TIMEOUT_MS = 50; + +// clang-format off +static const struct lws_protocols s_protocols[] = { + {"lws-multiplayer", LwsCallback, 0, LWS_RX_BUFFER_SIZE}, + LWS_PROTOCOL_LIST_TERM +}; +// clang-format on + +MxResult LwsServiceThread::Run() +{ + while (IsRunning()) { + m_transport->ServiceLoop(); + } + return MxThread::Run(); +} + +LwsTransport::LwsTransport(const std::string& p_relayBaseUrl) + : m_relayBaseUrl(p_relayBaseUrl), m_context(nullptr), m_wsi(nullptr), m_connected(false), m_disconnected(false), + m_wasEverConnected(false), m_serviceThread(nullptr), m_wantWritable(false) +{ +} + +LwsTransport::~LwsTransport() +{ + Disconnect(); +} + +void LwsTransport::Connect(const char* p_roomId) +{ + if (m_context) { + Disconnect(); + } + + m_disconnected.store(false); + m_wasEverConnected.store(false); + + // lws_parse_uri modifies the string in place, so we need a mutable copy + std::string fullUrl = m_relayBaseUrl + "/room/" + p_roomId; + std::vector urlBuf(fullUrl.begin(), fullUrl.end()); + urlBuf.push_back('\0'); + + const char* protocol = nullptr; + const char* address = nullptr; + const char* path = nullptr; + int port = 0; + + if (lws_parse_uri(&urlBuf[0], &protocol, &address, &port, &path)) { + SDL_Log("[Multiplayer] Failed to parse relay URL: %s", fullUrl.c_str()); + m_disconnected.store(true); + return; + } + + bool useSSL = (SDL_strcmp(protocol, "wss") == 0 || SDL_strcmp(protocol, "https") == 0); + SDL_Log("[Multiplayer] Connecting to %s://%s:%d/%s (SSL=%d)", protocol, address, port, path, useSSL); + + lws_set_log_level(LLL_ERR | LLL_WARN, nullptr); + + struct lws_context_creation_info ctxInfo; + SDL_memset(&ctxInfo, 0, sizeof(ctxInfo)); + ctxInfo.port = CONTEXT_PORT_NO_LISTEN; + ctxInfo.protocols = s_protocols; + if (useSSL) { + ctxInfo.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + } + + m_context = lws_create_context(&ctxInfo); + if (!m_context) { + SDL_Log("[Multiplayer] Failed to create lws context"); + m_disconnected.store(true); + return; + } + + // path from lws_parse_uri does not include the leading '/', so prepend it + std::string fullPath = std::string("/") + path; + + struct lws_client_connect_info connInfo; + SDL_memset(&connInfo, 0, sizeof(connInfo)); + connInfo.context = m_context; + connInfo.address = address; + connInfo.port = port; + connInfo.path = fullPath.c_str(); + connInfo.host = address; + connInfo.origin = address; + connInfo.ssl_connection = useSSL ? (LCCSCF_USE_SSL | LCCSCF_ALLOW_INSECURE) : 0; + connInfo.local_protocol_name = s_protocols[0].name; + connInfo.opaque_user_data = this; + + struct lws* wsi = lws_client_connect_via_info(&connInfo); + if (!wsi) { + SDL_Log("[Multiplayer] Failed to initiate WebSocket connection to %s:%d%s", address, port, fullPath.c_str()); + lws_context_destroy(m_context); + m_context = nullptr; + m_disconnected.store(true); + return; + } + + m_wsi.store(wsi); + m_serviceThread = new LwsServiceThread(); + m_serviceThread->SetTransport(this); + if (m_serviceThread->Start(0, 0) != SUCCESS) { + SDL_Log("[Multiplayer] Failed to start WebSocket service thread"); + delete m_serviceThread; + m_serviceThread = nullptr; + m_wsi.store(nullptr); + lws_context_destroy(m_context); + m_context = nullptr; + m_disconnected.store(true); + return; + } +} + +void LwsTransport::Disconnect() +{ + if (m_context) { + lws_cancel_service(m_context); + m_serviceThread->Terminate(); + delete m_serviceThread; + m_serviceThread = nullptr; + + lws_context_destroy(m_context); + m_context = nullptr; + } + + m_wsi.store(nullptr); + m_connected.store(false); + m_wantWritable.store(false); + + m_sendCS.Enter(); + m_sendQueue.clear(); + m_sendCS.Leave(); + + m_recvCS.Enter(); + m_recvQueue.clear(); + m_recvCS.Leave(); + + m_fragment.clear(); +} + +bool LwsTransport::IsConnected() const +{ + return m_connected.load(); +} + +bool LwsTransport::WasDisconnected() const +{ + return m_disconnected.load(); +} + +bool LwsTransport::WasRejected() const +{ + return m_disconnected.load() && !m_wasEverConnected.load(); +} + +void LwsTransport::Send(const uint8_t* p_data, size_t p_length) +{ + if (!m_connected.load() || !m_wsi.load()) { + return; + } + + std::vector buf(LWS_PRE + p_length); + SDL_memcpy(&buf[LWS_PRE], p_data, p_length); + + m_sendCS.Enter(); + m_sendQueue.push_back(std::move(buf)); + m_sendCS.Leave(); + + m_wantWritable.store(true); + if (m_context) { + lws_cancel_service(m_context); + } +} + +size_t LwsTransport::Receive(std::function p_callback) +{ + if (!m_context) { + return 0; + } + + std::deque> local; + + m_recvCS.Enter(); + local.swap(m_recvQueue); + m_recvCS.Leave(); + + for (const auto& msg : local) { + p_callback(msg.data(), msg.size()); + } + + return local.size(); +} + +void LwsTransport::ServiceLoop() +{ + if (m_wantWritable.exchange(false)) { + struct lws* wsi = m_wsi.load(); + if (wsi) { + lws_callback_on_writable(wsi); + } + } + + lws_service(m_context, LWS_SERVICE_TIMEOUT_MS); +} + +int LwsTransport::HandleLwsEvent(struct lws* p_wsi, int p_reason, void* p_in, size_t p_len) +{ + switch (p_reason) { + case LWS_CALLBACK_CLIENT_ESTABLISHED: + SDL_Log("[Multiplayer] WebSocket connection established"); + m_connected.store(true); + m_wasEverConnected.store(true); + break; + + case LWS_CALLBACK_CLIENT_RECEIVE: + m_fragment.insert(m_fragment.end(), static_cast(p_in), static_cast(p_in) + p_len); + if (lws_is_final_fragment(p_wsi)) { + m_recvCS.Enter(); + m_recvQueue.push_back(std::move(m_fragment)); + m_recvCS.Leave(); + m_fragment.clear(); + } + break; + + case LWS_CALLBACK_CLIENT_WRITEABLE: { + m_sendCS.Enter(); + if (!m_sendQueue.empty()) { + std::vector front = std::move(m_sendQueue.front()); + m_sendQueue.pop_front(); + bool hasMore = !m_sendQueue.empty(); + m_sendCS.Leave(); + + size_t payloadLen = front.size() - LWS_PRE; + lws_write(p_wsi, &front[LWS_PRE], payloadLen, LWS_WRITE_BINARY); + + if (hasMore) { + lws_callback_on_writable(p_wsi); + } + } + else { + m_sendCS.Leave(); + } + break; + } + + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + SDL_Log("[Multiplayer] WebSocket connection error: %s", p_in ? static_cast(p_in) : "unknown"); + m_disconnected.store(true); + m_connected.store(false); + m_wsi.store(nullptr); + break; + + case LWS_CALLBACK_CLIENT_CLOSED: + SDL_Log("[Multiplayer] WebSocket connection closed"); + m_disconnected.store(true); + m_connected.store(false); + m_wsi.store(nullptr); + break; + + default: + break; + } + + return 0; +} + +} // namespace Multiplayer + +#endif // !__EMSCRIPTEN__ diff --git a/extensions/src/multiplayer/platforms/native/nativecallbacks.cpp b/extensions/src/multiplayer/platforms/native/nativecallbacks.cpp new file mode 100644 index 00000000..2007345d --- /dev/null +++ b/extensions/src/multiplayer/platforms/native/nativecallbacks.cpp @@ -0,0 +1,37 @@ +#ifndef __EMSCRIPTEN__ + +#include "extensions/multiplayer/platforms/native/nativecallbacks.h" + +#include + +namespace Multiplayer +{ + +void NativeCallbacks::OnPlayerCountChanged(int p_count) +{ + if (p_count < 0) { + SDL_Log("[Multiplayer] Left multiplayer world"); + } + else { + SDL_Log("[Multiplayer] Player count changed: %d", p_count); + } +} + +void NativeCallbacks::OnThirdPersonChanged(bool p_enabled) +{ + SDL_Log("[Multiplayer] Third person camera: %s", p_enabled ? "enabled" : "disabled"); +} + +void NativeCallbacks::OnNameBubblesChanged(bool p_enabled) +{ + SDL_Log("[Multiplayer] Name bubbles: %s", p_enabled ? "enabled" : "disabled"); +} + +void NativeCallbacks::OnAllowCustomizeChanged(bool p_enabled) +{ + SDL_Log("[Multiplayer] Allow customization: %s", p_enabled ? "enabled" : "disabled"); +} + +} // namespace Multiplayer + +#endif // !__EMSCRIPTEN__ diff --git a/extensions/src/multiplayer/server/gameroom.ts b/extensions/src/multiplayer/server/gameroom.ts index bc97ec22..1ef2e50d 100644 --- a/extensions/src/multiplayer/server/gameroom.ts +++ b/extensions/src/multiplayer/server/gameroom.ts @@ -23,7 +23,7 @@ export class GameRoom implements DurableObject { private nextPeerId = 1; private hostPeerId = 0; private maxPlayers = 5; - private maxActors = 5; + private maxActors = 0; constructor( private state: DurableObjectState, diff --git a/extensions/src/multiplayer/worldstatesync.cpp b/extensions/src/multiplayer/worldstatesync.cpp index 9eae76c8..d16c08b1 100644 --- a/extensions/src/multiplayer/worldstatesync.cpp +++ b/extensions/src/multiplayer/worldstatesync.cpp @@ -182,12 +182,14 @@ MxBool WorldStateSync::HandleEntityMutation(LegoEntity* p_entity, MxU8 p_changeT if (p_entity->GetType() == LegoEntity::e_plant) { entityType = ENTITY_PLANT; MxS32 count; - idx = FindEntityIndex(PlantManager()->GetInfoArray(count), count, p_entity); + LegoPlantInfo* info = PlantManager()->GetInfoArray(count); + idx = FindEntityIndex(info, count, p_entity); } else if (p_entity->GetType() == LegoEntity::e_building) { entityType = ENTITY_BUILDING; MxS32 count; - idx = FindEntityIndex(BuildingManager()->GetInfoArray(count), count, p_entity); + LegoBuildingInfo* info = BuildingManager()->GetInfoArray(count); + idx = FindEntityIndex(info, count, p_entity); } else { return FALSE; diff --git a/extensions/src/thirdpersoncamera/inputhandler.cpp b/extensions/src/thirdpersoncamera/inputhandler.cpp index 2c87c000..e2288715 100644 --- a/extensions/src/thirdpersoncamera/inputhandler.cpp +++ b/extensions/src/thirdpersoncamera/inputhandler.cpp @@ -7,7 +7,9 @@ using namespace Extensions::ThirdPersonCamera; -InputHandler::InputHandler() : m_touch{}, m_wantsAutoDisable(false), m_wantsAutoEnable(false) +InputHandler::InputHandler() + : m_touch{}, m_wantsAutoDisable(false), m_wantsAutoEnable(false), m_rightButtonHeld(false), m_savedMouseX(0.0f), + m_savedMouseY(0.0f) { } @@ -103,7 +105,7 @@ void InputHandler::HandleSDLEvent(SDL_Event* p_event, OrbitCamera& p_orbit, bool if (!p_active) { break; } - if (p_event->motion.state & SDL_BUTTON_RMASK) { + if (m_rightButtonHeld) { p_orbit.AdjustYaw(-p_event->motion.xrel * 0.005f); p_orbit.AdjustPitch(p_event->motion.yrel * 0.005f); p_orbit.ClampPitch(); @@ -112,19 +114,28 @@ void InputHandler::HandleSDLEvent(SDL_Event* p_event, OrbitCamera& p_orbit, bool case SDL_EVENT_MOUSE_BUTTON_DOWN: case SDL_EVENT_MOUSE_BUTTON_UP: { - if (!p_active) { - break; - } - SDL_Window* window = SDL_GetWindowFromID(p_event->button.windowID); - if (window) { - SDL_SetWindowRelativeMouseMode(window, SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_RMASK); + if (p_event->button.button == SDL_BUTTON_RIGHT) { + m_rightButtonHeld = p_event->button.down; + if (!p_active) { + break; + } + SDL_Window* window = SDL_GetWindowFromID(p_event->button.windowID); + if (window) { + if (m_rightButtonHeld) { + SDL_GetMouseState(&m_savedMouseX, &m_savedMouseY); + SDL_SetWindowRelativeMouseMode(window, true); + } + else { + SDL_SetWindowRelativeMouseMode(window, false); + SDL_WarpMouseInWindow(window, m_savedMouseX, m_savedMouseY); + } + } } break; } case SDL_EVENT_FINGER_DOWN: { - if (!IsFingerTracked(p_event->tfinger.fingerID) && m_touch.count < 2 && - p_event->tfinger.x >= CAMERA_ZONE_X) { + if (!IsFingerTracked(p_event->tfinger.fingerID) && m_touch.count < 2 && p_event->tfinger.x >= CAMERA_ZONE_X) { int idx = m_touch.count; m_touch.id[idx] = p_event->tfinger.fingerID; m_touch.x[idx] = p_event->tfinger.x;