From 1a69378971f5d5dbf5099a6a3fc851fc684bb2d5 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Mon, 20 Apr 2026 16:11:02 +0200 Subject: [PATCH 01/27] SDL3 Input: Complete backport with Gamepad support and hardening Consolidated all work from the test/sdl3-backport branch into a single atomic commit: - Centralized input management via SDL3InputManager. - Hardened Ani/RIFF cursor loading with robust bounds checking. - Native Gamepad support with analogue stick-to-mouse emulation and custom RTS mappings. - Modernized focus and capture handling for better stability during Alt-Tab. - Standardized and secured string operations throughout the SDL3 path. --- CMakeLists.txt | 1 + Core/GameEngineDevice/CMakeLists.txt | 19 + .../Include/SDL3Device/GameClient/SDL3Input.h | 204 +++ .../GameEngineDevice/Include/SDL3GameEngine.h | 91 ++ .../SDL3Device/GameClient/SDL3Input.cpp | 1109 +++++++++++++++++ .../Source/SDL3GameEngine.cpp | 382 ++++++ .../GameEngine/Include/GameClient/Display.h | 5 + .../W3DDevice/GameClient/W3DGameClient.h | 30 +- GeneralsMD/Code/Main/WinMain.cpp | 94 +- cmake/config-build.cmake | 10 + cmake/sdl3.cmake | 95 ++ 11 files changed, 2031 insertions(+), 9 deletions(-) create mode 100644 Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h create mode 100644 Core/GameEngineDevice/Include/SDL3GameEngine.h create mode 100644 Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp create mode 100644 Core/GameEngineDevice/Source/SDL3GameEngine.cpp create mode 100644 cmake/sdl3.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 28ce09560e9..0c7dfa916b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,7 @@ if((WIN32 OR "${CMAKE_SYSTEM}" MATCHES "Windows") AND ${CMAKE_SIZEOF_VOID_P} EQU include(cmake/miles.cmake) include(cmake/bink.cmake) include(cmake/dx8.cmake) + include(cmake/sdl3.cmake) endif() # Define a dummy stlport target when not on VC6. diff --git a/Core/GameEngineDevice/CMakeLists.txt b/Core/GameEngineDevice/CMakeLists.txt index 74b040200ae..a98ccadde9b 100644 --- a/Core/GameEngineDevice/CMakeLists.txt +++ b/Core/GameEngineDevice/CMakeLists.txt @@ -192,6 +192,16 @@ set(GAMEENGINEDEVICE_SRC Source/Win32Device/GameClient/Win32Mouse.cpp ) +# Add Core-level SDL3 implementation +if(SAGE_USE_SDL3 AND NOT IS_VS6_BUILD) + list(APPEND GAMEENGINEDEVICE_SRC + Include/SDL3GameEngine.h + Include/SDL3Device/GameClient/SDL3Input.h + Source/SDL3GameEngine.cpp + Source/SDL3Device/GameClient/SDL3Input.cpp + ) +endif() + # Add C++ 17 FileSystem implementation for non-VS6 builds if(NOT IS_VS6_BUILD) list(APPEND GAMEENGINEDEVICE_SRC @@ -227,6 +237,15 @@ target_link_libraries(corei_gameenginedevice_public INTERFACE milesstub ) +# Export SDL3 dependencies for modern builds +if(SAGE_USE_SDL3 AND NOT IS_VS6_BUILD) + target_link_libraries(corei_gameenginedevice_public INTERFACE + SDL3::Headers + SDL3::SDL3-static + SDL3_image::SDL3_image-static + ) +endif() + if(RTS_BUILD_OPTION_FFMPEG) find_package(FFMPEG REQUIRED) diff --git a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h new file mode 100644 index 00000000000..29f8fdcf17f --- /dev/null +++ b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h @@ -0,0 +1,204 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +#include "Lib/BaseType.h" + +// SYSTEM INCLUDES +#include +#include +#include +#include + +// USER INCLUDES +#include "GameClient/Mouse.h" +#include "GameClient/Keyboard.h" +#include "GameClient/KeyDefs.h" + +// FORWARD REFERENCES +struct AnimatedCursor; +class SDL3InputManager; + +// GLOBALS --------------------------------------------------------------------- +extern SDL3InputManager* TheSDL3InputManager; + +// TYPE DEFINES ---------------------------------------------------------------- +typedef KeyDefType KeyVal; + +// SDL3Mouse ------------------------------------------------------------------ +/** Mouse interface using SDL3 APIs */ +//----------------------------------------------------------------------------- +class SDL3Mouse : public Mouse +{ +public: + SDL3Mouse(SDL_Window* window); + virtual ~SDL3Mouse(void); + + // SubsystemInterface + virtual void init(void) override; + virtual void reset(void) override; + virtual void update(void) override; + virtual void initCursorResources(void) override; + + // Mouse interface + virtual void setCursor(MouseCursor cursor) override; + virtual void setVisibility(Bool visible) override; + virtual void loseFocus() override; + virtual void regainFocus() override; + + // SDL3-specific methods + void addSDLEvent(SDL_Event *event); + +protected: + virtual void capture(void) override; + virtual void releaseCapture(void) override; + virtual UnsignedByte getMouseEvent(MouseIO *result, Bool flush) override; + +private: + // Event translation from SDL_Event (Clean Slate implementation) + void translateEvent(const SDL_Event& event, MouseIO *result); + + // Scale raw SDL window coordinates to game internal resolution + void scaleMouseCoordinates(int rawX, int rawY, Uint32 windowID, int& scaledX, int& scaledY); + + // Load cursor from ANI file (fighter19 pattern) + AnimatedCursor* loadCursorFromFile(const char* filepath); + + SDL_Window* m_Window; + Bool m_IsCaptured; + Bool m_IsVisible; + Bool m_LostFocus; + + Uint32 m_LeftButtonDownTime; + Uint32 m_RightButtonDownTime; + Uint32 m_MiddleButtonDownTime; + UnsignedInt m_LastFrameNumber; + + ICoord2D m_LeftButtonDownPos; + ICoord2D m_RightButtonDownPos; + ICoord2D m_MiddleButtonDownPos; + + Int m_directionFrame; + UnsignedInt m_inputFrame; + + float m_accumulatedDeltaX; + float m_accumulatedDeltaY; + + SDL_Cursor* m_activeSDLCursor; + Bool m_cursorDirty; +}; + +// SDL3Keyboard --------------------------------------------------------------- +/** Keyboard interface using SDL3 APIs */ +//----------------------------------------------------------------------------- +class SDL3Keyboard : public Keyboard +{ +public: + SDL3Keyboard(void); + virtual ~SDL3Keyboard(void); + + // SubsystemInterface + virtual void init(void) override; + virtual void reset(void) override; + virtual void update(void) override; + + // Keyboard interface + virtual Bool getCapsState(void) override; + + // SDL3-specific methods + void addSDLEvent(SDL_Event *event); + +protected: + virtual void getKey(KeyboardIO *key) override; + virtual KeyVal translateScanCodeToKeyVal(unsigned char scan); + +private: + void translateKeyEvent(const SDL_KeyboardEvent& event); +}; + +// SDL3InputManager ----------------------------------------------------------- +/** Unified manager for SDL3 input events */ +//----------------------------------------------------------------------------- +class SDL3InputManager +{ +public: + SDL3InputManager(); + virtual ~SDL3InputManager(); + + void update(); + + // Buffer access + Bool getNextMouseEvent(SDL_Event& outEvent); + Bool getNextKeyboardEvent(SDL_Event& outEvent); + + void addMouseSDLEvent(const SDL_Event& event); + void addKeyboardSDLEvent(const SDL_Event& event); + + Bool isQuitting() const { return m_isQuitting; } + + // Constants + static constexpr float AXIS_MAX = 32767.0f; + static constexpr int TRIGGER_THRESHOLD = 16384; + static constexpr float DEFAULT_DEADZONE = 0.15f; + static constexpr float DEFAULT_CURSOR_SPEED = 800.0f; + +private: + struct GamepadState { + bool buttonState[SDL_GAMEPAD_BUTTON_COUNT]; + bool stickLeft, stickRight, stickUp, stickDown; + bool ltDown, rtDown; + + GamepadState() { + memset(buttonState, 0, sizeof(buttonState)); + stickLeft = stickRight = stickUp = stickDown = false; + ltDown = rtDown = false; + } + }; + +private: + // Gamepad management + void openFirstGamepad(); + void closeGamepad(); + void processGamepadInput(); + void handleGamepadButton(SDL_GamepadButton button, bool& currentState, bool isDown, std::function action); + + // Virtual event injection + void virtualPulseKey(SDL_Scancode scancode, bool down); + void virtualPulseMouse(Uint8 button, bool down); + + // Event buffers + static const UnsignedInt MAX_MOUSE_EVENTS = 256; + static const UnsignedInt MAX_KEY_EVENTS = 256; + + SDL_Event m_mouseEvents[MAX_MOUSE_EVENTS]; + UnsignedInt m_mouseNextFree; + UnsignedInt m_mouseNextGet; + + SDL_Event m_keyEvents[MAX_KEY_EVENTS]; + UnsignedInt m_keyNextFree; + UnsignedInt m_keyNextGet; + + // Gamepad state + SDL_Gamepad* m_gamepad; + GamepadState m_state; + + Bool m_precisionMode; + Uint64 m_lastUpdateTime; + Bool m_isQuitting; +}; diff --git a/Core/GameEngineDevice/Include/SDL3GameEngine.h b/Core/GameEngineDevice/Include/SDL3GameEngine.h new file mode 100644 index 00000000000..a85a6b575c2 --- /dev/null +++ b/Core/GameEngineDevice/Include/SDL3GameEngine.h @@ -0,0 +1,91 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +#include "Lib/BaseType.h" + +#include "Common/GameEngine.h" +#include + +// EXTERNALS +// SDL3 window typically provided by WinMain integration +extern SDL_Window* TheSDL3Window; + +// Forward declarations for base classes +class AudioManager; +class Mouse; +class Keyboard; +class GameWindow; +class LocalFileSystem; +class ArchiveFileSystem; +class ThingFactory; +class ModuleFactory; +class FunctionLexicon; +class Radar; +class WebBrowser; +class ParticleSystemManager; + +/** + * SDL3GameEngine + * + * GameEngine subclass that uses SDL3 for windowing and input. + * Replaces or supplements Win32-specific window handling with SDL3. + */ +class SDL3GameEngine : public GameEngine +{ +public: + SDL3GameEngine(); + virtual ~SDL3GameEngine(); + + // GameEngine interface + virtual void init(void) override; + virtual void reset(void) override; + virtual void update(void) override; + virtual void serviceWindowsOS(void) override; + virtual Bool isActive(void) override; + virtual void setIsActive(Bool isActive) override; + + // Factory methods (override GameEngine) + virtual LocalFileSystem *createLocalFileSystem(void) override; + virtual ArchiveFileSystem *createArchiveFileSystem(void) override; + virtual GameLogic *createGameLogic(void) override; + virtual GameClient *createGameClient(void) override; + virtual ModuleFactory *createModuleFactory(void) override; + virtual ThingFactory *createThingFactory(void) override; + virtual FunctionLexicon *createFunctionLexicon(void) override; + virtual Radar *createRadar(Bool dummy) override; + virtual WebBrowser *createWebBrowser(void) override; + virtual ParticleSystemManager* createParticleSystemManager(Bool dummy) override; + virtual AudioManager *createAudioManager(Bool dummy) override; + + // SDL3 specific + virtual SDL_Window* getSDLWindow(void) const { return m_SDLWindow; } + virtual void forwardTextInputEvent(const char* utf8Text); + +protected: + SDL_Window* m_SDLWindow; + Bool m_IsInitialized; + Bool m_IsActive; + Bool m_IsTextInputActive; + GameWindow* m_TextInputFocusWindow; + + // Event processing + void pollSDL3Events(void); + void updateTextInputState(void); +}; diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp new file mode 100644 index 00000000000..44291b80076 --- /dev/null +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp @@ -0,0 +1,1109 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "Lib/BaseType.h" + +#define _USE_MATH_DEFINES +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // For timeGetTime() + +#include "SDL3Device/GameClient/SDL3Input.h" +#include "Common/Debug.h" +#include "Common/file.h" +#include "Common/FileSystem.h" +#include "Common/GameEngine.h" +#include "Common/MessageStream.h" +#include "GameClient/Display.h" +#include "GameClient/InGameUI.h" +#include "GameLogic/GameLogic.h" +#include "SDL3GameEngine.h" + +// GLOBALS --------------------------------------------------------------------- +SDL3InputManager* TheSDL3InputManager = nullptr; + +// ============================================================================ +// SDL3MOUSE IMPLEMENTATION +// ============================================================================ + +/** + * AnimatedCursor - Helper struct for cursor animation + */ +struct AnimatedCursor { + std::array m_frameCursors; + std::array m_frameSurfaces; + int m_currentFrame = 0; + int m_frameCount = 0; + int m_frameRate = 0; // the time a frame is displayed in 1/60th of a second + + AnimatedCursor() + { + m_frameCursors.fill(nullptr); + m_frameSurfaces.fill(nullptr); + } + + ~AnimatedCursor() + { + for (int i = 0; i < MAX_2D_CURSOR_ANIM_FRAMES; i++) + { + if (m_frameCursors[i]) + { + SDL_DestroyCursor(m_frameCursors[i]); + m_frameCursors[i] = nullptr; + } + if (m_frameSurfaces[i]) + { + SDL_DestroySurface(m_frameSurfaces[i]); + m_frameSurfaces[i] = nullptr; + } + } + } + + /** + * Get the active frame cursor based on current system time + */ + SDL_Cursor* getActiveFrame() const + { + if (m_frameCount <= 0) return nullptr; + if (m_frameCount == 1) return m_frameCursors[0]; + + Uint64 now = SDL_GetTicks(); + size_t index = (m_frameRate > 0) + ? (size_t)((now * 60 / 1000) / m_frameRate) % m_frameCount + : 0; + return m_frameCursors[index]; + } +}; + +// Global cursor resources array +static AnimatedCursor* cursorResources[Mouse::NUM_MOUSE_CURSORS][MAX_2D_CURSOR_DIRECTIONS]; + +// RIFF/ANI parsing helpers +typedef std::array FourCC; +constexpr FourCC riff_id = {'R', 'I', 'F', 'F'}; +constexpr FourCC acon_id = {'A', 'C', 'O', 'N'}; +constexpr FourCC anih_id = {'a', 'n', 'i', 'h'}; +constexpr FourCC fram_id = {'f', 'r', 'a', 'm'}; +constexpr FourCC icon_id = {'i', 'c', 'o', 'n'}; +constexpr FourCC list_id = {'L', 'I', 'S', 'T'}; + +struct ANIHeader +{ + uint32_t size; + uint32_t frames; + uint32_t steps; + uint32_t width; + uint32_t height; + uint32_t bitsPerPixel; + uint32_t planes; + uint32_t displayRate; + uint32_t flags; +}; + +struct RIFFChunk +{ + FourCC id; + uint32_t size; + FourCC type; +}; + +static RIFFChunk* getNextChunk(RIFFChunk* chunk, const char* buffer_end) +{ + if (!chunk) return nullptr; + + // Size check: Chunk header is at least 8 bytes (ID + Size). + char* next = (char*)chunk + 8 + chunk->size; + + // RIFF chunks are padded to 2 bytes + if (chunk->size % 2 != 0) next++; + + if (next >= buffer_end) return nullptr; + return (RIFFChunk*)next; +} + +static void* getChunkData(RIFFChunk* chunk) +{ + // For LIST and RIFF, type is at +8, data starts at +12 + if (chunk->id == list_id || chunk->id == riff_id) + return (char*)chunk + 12; + + // For others, data starts at +8 + return (char*)chunk + 8; +} + +/** + * loadANI - Dedicated standalone RIFF/ANI parser (Hardened) + */ +static AnimatedCursor* loadANI(const char* filepath) +{ + File* file = TheFileSystem->openFile(filepath, File::READ | File::BINARY); + if (!file) + { + DEBUG_LOG(("loadANI: Failed to open ANI cursor [%s]", filepath)); + return nullptr; + } + + Int size = file->size(); + if (size < (Int)sizeof(RIFFChunk)) + { + DEBUG_LOG(("loadANI: File too small [%s]", filepath)); + file->close(); + return nullptr; + } + + std::unique_ptr file_buffer(new char[size]); + if (file->read(file_buffer.get(), size) != size) + { + DEBUG_LOG(("loadANI: Failed to read ANI cursor [%s]", filepath)); + file->close(); + return nullptr; + } + file->close(); + + char* buffer_start = file_buffer.get(); + char* buffer_end = buffer_start + size; + + RIFFChunk *riff_header = (RIFFChunk*)buffer_start; + if (riff_header->id != riff_id || riff_header->type != acon_id) + { + DEBUG_LOG(("loadANI: Not a valid RIFF/ACON file [%s]", filepath)); + return nullptr; + } + + DEBUG_LOG(("loadANI: Loading %s", filepath)); + std::unique_ptr cursor(new AnimatedCursor()); + + // Top level chunks start after the RIFF header (8 bytes + 'ACON' = 12 bytes) + RIFFChunk* chunk = (RIFFChunk*)(buffer_start + 12); + + while (chunk != nullptr && (char *)chunk + 8 <= buffer_end) + { + if (chunk->id == anih_id) + { + if (chunk->size < sizeof(ANIHeader)) + { + DEBUG_LOG(("loadANI: Invalid ANI header size")); + return nullptr; + } + + ANIHeader *ani_header = (ANIHeader*)getChunkData(chunk); + cursor->m_frameCount = ani_header->frames; + cursor->m_frameRate = ani_header->displayRate; + } + else if (chunk->id == list_id && chunk->type == fram_id) + { + int frame_index = 0; + // Sub-chunks in LIST start after the header + type (12 bytes) + RIFFChunk *frame = (RIFFChunk*)((char *)chunk + 12); + char* list_end = (char*)chunk + 8 + chunk->size; + if (list_end > buffer_end) list_end = buffer_end; + + while (frame != nullptr && (char *)frame + 8 <= list_end) + { + if (frame->id == icon_id) + { + if ((char*)frame + 8 + frame->size <= list_end) + { + const void *frame_buffer = getChunkData(frame); + SDL_IOStream *io_stream = SDL_IOFromConstMem(frame_buffer, frame->size); + if (io_stream) + { + SDL_Surface *surface = cursor->m_frameSurfaces[frame_index] = IMG_LoadTyped_IO(io_stream, true, "ico"); + if (surface) + { + SDL_PropertiesID props = SDL_GetSurfaceProperties(surface); + int hot_spot_x = (int)SDL_GetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_X_NUMBER, 0); + int hot_spot_y = (int)SDL_GetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER, 0); + + cursor->m_frameCursors[frame_index++] = SDL_CreateColorCursor(surface, hot_spot_x, hot_spot_y); + } + } + } + } + + if (frame_index >= MAX_2D_CURSOR_ANIM_FRAMES) break; + frame = getNextChunk(frame, list_end); + } + } + + chunk = getNextChunk(chunk, buffer_end); + } + + return cursor.release(); +} + +/** + * Constructor - Initialize SDL3Mouse with window handle + */ +SDL3Mouse::SDL3Mouse(SDL_Window* window) + : Mouse(), + m_Window(window), + m_IsCaptured(false), + m_IsVisible(true), + m_LostFocus(false), + m_LeftButtonDownTime(0), + m_RightButtonDownTime(0), + m_MiddleButtonDownTime(0), + m_LastFrameNumber(0), + m_directionFrame(0), + m_inputFrame(0), + m_accumulatedDeltaX(0.0f), + m_accumulatedDeltaY(0.0f), + m_activeSDLCursor(nullptr), + m_cursorDirty(false) +{ + m_LeftButtonDownPos.x = 0; + m_LeftButtonDownPos.y = 0; + m_RightButtonDownPos.x = 0; + m_RightButtonDownPos.y = 0; + m_MiddleButtonDownPos.x = 0; + m_MiddleButtonDownPos.y = 0; +} + +/** + * Destructor + */ +SDL3Mouse::~SDL3Mouse(void) +{ + releaseCapture(); +} + +/** + * Initialize mouse subsystem + */ +void SDL3Mouse::init(void) +{ + Mouse::init(); + + m_inputMovesAbsolute = TRUE; + + // Show cursor by default + setVisibility(TRUE); +} + +/** + * Reset mouse to default state + */ +void SDL3Mouse::reset(void) +{ + Mouse::reset(); + + releaseCapture(); + setVisibility(TRUE); +} + +/** + * Update mouse state (called per-frame) + */ +void SDL3Mouse::update(void) +{ + Mouse::update(); + + m_inputFrame++; + + if (m_LostFocus) + { + return; + } + + MouseCursor cursor = m_currentCursor; + + if (cursor != NONE && cursor != INVALID_MOUSE_CURSOR && m_cursorInfo[cursor].numDirections > 1) + { + float dx = 0.0f; + float dy = 0.0f; + bool hasMovement = false; + + if (cursor == SCROLL && TheInGameUI && TheInGameUI->isScrolling()) + { + Coord2D scroll = TheInGameUI->getScrollAmount(); + if (scroll.x != 0.0f || scroll.y != 0.0f) + { + dx = scroll.x; + dy = scroll.y; + hasMovement = true; + } + } + + if (!hasMovement) + { + if (SDL_fabsf(m_accumulatedDeltaX) > 0.01f || SDL_fabsf(m_accumulatedDeltaY) > 0.01f) + { + dx = m_accumulatedDeltaX; + dy = m_accumulatedDeltaY; + hasMovement = true; + + m_accumulatedDeltaX = 0.0f; + m_accumulatedDeltaY = 0.0f; + } + } + + if (hasMovement) + { + float angle = atan2f(dy, dx); + if (angle < 0) angle += 2.0f * (float)M_PI; + float segmentAngle = 2.0f * (float)M_PI / (float)m_cursorInfo[cursor].numDirections; + m_directionFrame = (int)((angle + (segmentAngle / 2.0f)) / segmentAngle) % m_cursorInfo[cursor].numDirections; + } + } + else + { + m_directionFrame = 0; + } + + SDL_Cursor* requestedHandle = nullptr; + bool bUseDefaultCursor = false; + + if (cursor == NONE || cursor == INVALID_MOUSE_CURSOR || !m_IsVisible) + { + bUseDefaultCursor = true; + } + else + { + AnimatedCursor* animated = cursorResources[cursor][m_directionFrame]; + if (animated) + { + requestedHandle = animated->getActiveFrame(); + } + else + { + bUseDefaultCursor = true; + } + } + + if (bUseDefaultCursor) + { + if (cursorResources[NORMAL][0]) + { + requestedHandle = cursorResources[NORMAL][0]->m_frameCursors[0]; + } + else + { + requestedHandle = SDL_GetDefaultCursor(); + } + } + + if (requestedHandle != m_activeSDLCursor) + { + SDL_SetCursor(requestedHandle); + m_activeSDLCursor = requestedHandle; + } + + m_cursorDirty = false; +} + +/** + * Initialize cursor resources (load cursor images from ANI files) + */ +void SDL3Mouse::initCursorResources(void) +{ + for (Int cursor=FIRST_CURSOR; cursor 1) + snprintf(resourcePath, sizeof(resourcePath), "Data/Cursors/%s%d.ani", m_cursorInfo[cursor].textureName.str(), direction); + else + snprintf(resourcePath, sizeof(resourcePath), "Data/Cursors/%s.ani", m_cursorInfo[cursor].textureName.str()); + + cursorResources[cursor][direction]=loadCursorFromFile(resourcePath); + DEBUG_ASSERTCRASH(cursorResources[cursor][direction], ("MissingCursor %s\n",resourcePath)); + } + } + } +} + +AnimatedCursor* SDL3Mouse::loadCursorFromFile(const char* filepath) +{ + return loadANI(filepath); +} + +/** + * Set mouse cursor type + */ +void SDL3Mouse::setCursor(MouseCursor cursor) +{ + if (m_currentCursor == cursor) + { + return; + } + + Mouse::setCursor( cursor ); + m_currentCursor = cursor; + m_cursorDirty = true; +} + +/** + * Set cursor visibility + */ +void SDL3Mouse::setVisibility(Bool visible) +{ + Mouse::setVisibility(visible); + + if (visible) { + SDL_ShowCursor(); + } else { + SDL_HideCursor(); + } +} + +/** + * Handle window losing focus + */ +void SDL3Mouse::loseFocus() +{ + Mouse::loseFocus(); + releaseCapture(); +} + +/** + * Handle window regaining focus + */ +void SDL3Mouse::regainFocus() +{ + Mouse::regainFocus(); +} + +/** + * Capture mouse (confine to window) + */ +void SDL3Mouse::capture(void) +{ + if (!m_Window || m_isCursorCaptured) { + return; + } + + SDL_CaptureMouse(true); + SDL_SetWindowMouseGrab(m_Window, true); + onCursorCaptured(true); +} + +/** + * Release mouse capture + */ +void SDL3Mouse::releaseCapture(void) +{ + if (!m_isCursorCaptured) { + return; + } + + SDL_CaptureMouse(false); + if (m_Window) { + SDL_SetWindowMouseGrab(m_Window, false); + } + + onCursorCaptured(false); +} + +/** + * Get next mouse event from the centralized input manager + */ +UnsignedByte SDL3Mouse::getMouseEvent(MouseIO *result, Bool flush) +{ + if (!TheSDL3InputManager) { + return MOUSE_NONE; + } + + SDL_Event nextEvent; + if (!TheSDL3InputManager->getNextMouseEvent(nextEvent)) { + return MOUSE_NONE; + } + + translateEvent(nextEvent, result); + + return MOUSE_OK; +} + +void SDL3Mouse::addSDLEvent(SDL_Event *event) +{ + if (TheSDL3InputManager && event) { + TheSDL3InputManager->addMouseSDLEvent(*event); + } +} + +//----------------------------------------------------------------------------- +/** Unified event translation (Clean Slate Rewrite) */ +//----------------------------------------------------------------------------- +void SDL3Mouse::translateEvent(const SDL_Event& event, MouseIO *result) +{ + if (!result) return; + + // Reset state + result->leftState = result->rightState = result->middleState = MBS_None; + result->wheelPos = 0; + result->deltaPos.x = result->deltaPos.y = 0; + + // Common timestamp (SDL3 uses nanoseconds, SAGE usually wants ms) + result->time = (Uint32)(event.common.timestamp / 1000000); + + int rawX = 0; + int rawY = 0; + Uint32 windowID = 0; + + switch (event.type) { + case SDL_EVENT_MOUSE_MOTION: + rawX = (int)event.motion.x; + rawY = (int)event.motion.y; + windowID = event.motion.windowID; + result->deltaPos.x = (Int)event.motion.xrel; + result->deltaPos.y = (Int)event.motion.yrel; + + m_accumulatedDeltaX += event.motion.xrel; + m_accumulatedDeltaY += event.motion.yrel; + break; + + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: + { + rawX = (int)event.button.x; + rawY = (int)event.button.y; + windowID = event.button.windowID; + + MouseButtonState state = event.button.down ? MBS_Down : MBS_Up; + if (event.button.down && event.button.clicks >= 2) state = MBS_DoubleClick; + + if (event.button.button == SDL_BUTTON_LEFT) result->leftState = state; + else if (event.button.button == SDL_BUTTON_RIGHT) result->rightState = state; + else if (event.button.button == SDL_BUTTON_MIDDLE) result->middleState = state; + break; + } + + case SDL_EVENT_MOUSE_WHEEL: + { + // For wheel events, use current mouse position + float mx, my; + SDL_GetMouseState(&mx, &my); + rawX = (int)mx; + rawY = (int)my; + windowID = event.wheel.windowID; + result->wheelPos = (Int)(event.wheel.y * 120); // MOUSE_WHEEL_DELTA + break; + } + + default: + return; + } + + // Dynamic Scaling Guard + int scaledX, scaledY; + scaleMouseCoordinates(rawX, rawY, windowID, scaledX, scaledY); + result->pos.x = scaledX; + result->pos.y = scaledY; +} + +void SDL3Mouse::scaleMouseCoordinates(int rawX, int rawY, Uint32 windowID, int& scaledX, int& scaledY) +{ + SDL_Window* window = SDL_GetWindowFromID(windowID); + if (!window || !TheDisplay) { + scaledX = rawX; + scaledY = rawY; + return; + } + + int winW = 0, winH = 0; + SDL_GetWindowSizeInPixels(window, &winW, &winH); + + int intW = TheDisplay->getWidth(); + int intH = TheDisplay->getHeight(); + + // Guard: If we are at native resolution, bypass all math + if (winW == intW && winH == intH) { + scaledX = rawX; + scaledY = rawY; + return; + } + + // Handle Viewport/Letterboxing if active + int pbX, pbY, pbW, pbH; + if (TheDisplay->getViewportRect(pbX, pbY, pbW, pbH)) { + int cx = std::max(0, std::min(pbW, rawX - pbX)); + int cy = std::max(0, std::min(pbH, rawY - pbY)); + scaledX = (int)(cx * (float)intW / pbW); + scaledY = (int)(cy * (float)intH / pbH); + } else { + scaledX = (int)(rawX * (float)intW / winW); + scaledY = (int)(rawY * (float)intH / winH); + } +} + +// ============================================================================ +// SDL3KEYBOARD IMPLEMENTATION +// ============================================================================ + +/** + * Lifecycle + */ +SDL3Keyboard::SDL3Keyboard(void) : Keyboard() {} +SDL3Keyboard::~SDL3Keyboard(void) {} + +/** + * SubsystemInterface + */ +void SDL3Keyboard::init(void) { Keyboard::init(); } +void SDL3Keyboard::reset(void) { Keyboard::reset(); } +void SDL3Keyboard::update(void) { Keyboard::update(); } + +/** + * Keyboard Interface + */ +Bool SDL3Keyboard::getCapsState(void) { return FALSE; } + +/** + * SDL3-specific internal methods + */ +void SDL3Keyboard::getKey(KeyboardIO *key) +{ + if (!TheSDL3InputManager) { + key->key = KEY_NONE; + key->status = KeyboardIO::STATUS_UNUSED; + return; + } + + SDL_Event nextEvent; + if (!TheSDL3InputManager->getNextKeyboardEvent(nextEvent)) { + key->key = KEY_NONE; + key->status = KeyboardIO::STATUS_UNUSED; + return; + } + + const SDL_KeyboardEvent& keyEvent = nextEvent.key; + KeyDefType keyDef = translateScanCodeToKeyVal(keyEvent.scancode); + + key->key = keyDef; + key->status = KeyboardIO::STATUS_UNUSED; + key->state = keyEvent.down ? KEY_STATE_DOWN : KEY_STATE_UP; + key->keyDownTimeMsec = keyEvent.down ? timeGetTime() : 0; + + SDL_Keymod mod = keyEvent.mod; + if (mod & SDL_KMOD_LSHIFT) key->state |= KEY_STATE_LSHIFT; + if (mod & SDL_KMOD_RSHIFT) key->state |= KEY_STATE_RSHIFT; + if (mod & SDL_KMOD_LCTRL) key->state |= KEY_STATE_LCONTROL; + if (mod & SDL_KMOD_RCTRL) key->state |= KEY_STATE_RCONTROL; + if (mod & SDL_KMOD_LALT) key->state |= KEY_STATE_LALT; + if (mod & SDL_KMOD_RALT) key->state |= KEY_STATE_RALT; + if (mod & SDL_KMOD_CAPS) key->state |= KEY_STATE_CAPSLOCK; + + if (keyDef == KEY_LSHIFT) key->state &= ~KEY_STATE_LSHIFT; + if (keyDef == KEY_RSHIFT) key->state &= ~KEY_STATE_RSHIFT; + if (keyDef == KEY_LCTRL) key->state &= ~KEY_STATE_LCONTROL; + if (keyDef == KEY_RCTRL) key->state &= ~KEY_STATE_RCONTROL; + if (keyDef == KEY_LALT) key->state &= ~KEY_STATE_LALT; + if (keyDef == KEY_RALT) key->state &= ~KEY_STATE_RALT; +} + +void SDL3Keyboard::addSDLEvent(SDL_Event *event) +{ + if (TheSDL3InputManager && event) { + TheSDL3InputManager->addKeyboardSDLEvent(*event); + } +} + +KeyVal SDL3Keyboard::translateScanCodeToKeyVal(unsigned char scan) +{ + switch ((SDL_Scancode)scan) { + case SDL_SCANCODE_ESCAPE: return KEY_ESC; + case SDL_SCANCODE_RETURN: return KEY_ENTER; + case SDL_SCANCODE_KP_ENTER: return KEY_KPENTER; + case SDL_SCANCODE_SPACE: return KEY_SPACE; + case SDL_SCANCODE_TAB: return KEY_TAB; + case SDL_SCANCODE_BACKSPACE: return KEY_BACKSPACE; + case SDL_SCANCODE_DELETE: return KEY_DEL; + case SDL_SCANCODE_HOME: return KEY_HOME; + case SDL_SCANCODE_END: return KEY_END; + case SDL_SCANCODE_PAGEUP: return KEY_PGUP; + case SDL_SCANCODE_PAGEDOWN: return KEY_PGDN; + case SDL_SCANCODE_INSERT: return KEY_INS; + case SDL_SCANCODE_LSHIFT: return KEY_LSHIFT; + case SDL_SCANCODE_RSHIFT: return KEY_RSHIFT; + case SDL_SCANCODE_LCTRL: return KEY_LCTRL; + case SDL_SCANCODE_RCTRL: return KEY_RCTRL; + case SDL_SCANCODE_LALT: return KEY_LALT; + case SDL_SCANCODE_RALT: return KEY_RALT; + case SDL_SCANCODE_UP: return KEY_UP; + case SDL_SCANCODE_DOWN: return KEY_DOWN; + case SDL_SCANCODE_LEFT: return KEY_LEFT; + case SDL_SCANCODE_RIGHT: return KEY_RIGHT; + case SDL_SCANCODE_F1: return KEY_F1; + case SDL_SCANCODE_F2: return KEY_F2; + case SDL_SCANCODE_F3: return KEY_F3; + case SDL_SCANCODE_F4: return KEY_F4; + case SDL_SCANCODE_F5: return KEY_F5; + case SDL_SCANCODE_F6: return KEY_F6; + case SDL_SCANCODE_F7: return KEY_F7; + case SDL_SCANCODE_F8: return KEY_F8; + case SDL_SCANCODE_F9: return KEY_F9; + case SDL_SCANCODE_F10: return KEY_F10; + case SDL_SCANCODE_F11: return KEY_F11; + case SDL_SCANCODE_F12: return KEY_F12; + case SDL_SCANCODE_1: return KEY_1; + case SDL_SCANCODE_2: return KEY_2; + case SDL_SCANCODE_3: return KEY_3; + case SDL_SCANCODE_4: return KEY_4; + case SDL_SCANCODE_5: return KEY_5; + case SDL_SCANCODE_6: return KEY_6; + case SDL_SCANCODE_7: return KEY_7; + case SDL_SCANCODE_8: return KEY_8; + case SDL_SCANCODE_9: return KEY_9; + case SDL_SCANCODE_0: return KEY_0; + case SDL_SCANCODE_A: return KEY_A; + case SDL_SCANCODE_B: return KEY_B; + case SDL_SCANCODE_C: return KEY_C; + case SDL_SCANCODE_D: return KEY_D; + case SDL_SCANCODE_E: return KEY_E; + case SDL_SCANCODE_F: return KEY_F; + case SDL_SCANCODE_G: return KEY_G; + case SDL_SCANCODE_H: return KEY_H; + case SDL_SCANCODE_I: return KEY_I; + case SDL_SCANCODE_J: return KEY_J; + case SDL_SCANCODE_K: return KEY_K; + case SDL_SCANCODE_L: return KEY_L; + case SDL_SCANCODE_M: return KEY_M; + case SDL_SCANCODE_N: return KEY_N; + case SDL_SCANCODE_O: return KEY_O; + case SDL_SCANCODE_P: return KEY_P; + case SDL_SCANCODE_Q: return KEY_Q; + case SDL_SCANCODE_R: return KEY_R; + case SDL_SCANCODE_S: return KEY_S; + case SDL_SCANCODE_T: return KEY_T; + case SDL_SCANCODE_U: return KEY_U; + case SDL_SCANCODE_V: return KEY_V; + case SDL_SCANCODE_W: return KEY_W; + case SDL_SCANCODE_X: return KEY_X; + case SDL_SCANCODE_Y: return KEY_Y; + case SDL_SCANCODE_Z: return KEY_Z; + case SDL_SCANCODE_MINUS: return KEY_MINUS; + case SDL_SCANCODE_EQUALS: return KEY_EQUAL; + case SDL_SCANCODE_LEFTBRACKET: return KEY_LBRACKET; + case SDL_SCANCODE_RIGHTBRACKET: return KEY_RBRACKET; + case SDL_SCANCODE_SEMICOLON: return KEY_SEMICOLON; + case SDL_SCANCODE_APOSTROPHE: return KEY_APOSTROPHE; + case SDL_SCANCODE_GRAVE: return KEY_TICK; + case SDL_SCANCODE_COMMA: return KEY_COMMA; + case SDL_SCANCODE_PERIOD: return KEY_PERIOD; + case SDL_SCANCODE_SLASH: return KEY_SLASH; + case SDL_SCANCODE_BACKSLASH: return KEY_BACKSLASH; + case SDL_SCANCODE_KP_1: return KEY_KP1; + case SDL_SCANCODE_KP_2: return KEY_KP2; + case SDL_SCANCODE_KP_3: return KEY_KP3; + case SDL_SCANCODE_KP_4: return KEY_KP4; + case SDL_SCANCODE_KP_5: return KEY_KP5; + case SDL_SCANCODE_KP_6: return KEY_KP6; + case SDL_SCANCODE_KP_7: return KEY_KP7; + case SDL_SCANCODE_KP_8: return KEY_KP8; + case SDL_SCANCODE_KP_9: return KEY_KP9; + case SDL_SCANCODE_KP_0: return KEY_KP0; + case SDL_SCANCODE_KP_PLUS: return KEY_KPPLUS; + case SDL_SCANCODE_KP_MINUS: return KEY_KPMINUS; + case SDL_SCANCODE_KP_MULTIPLY: return KEY_KPSTAR; + case SDL_SCANCODE_KP_DIVIDE: return KEY_KPSLASH; + case SDL_SCANCODE_KP_PERIOD: return KEY_KPDEL; + case SDL_SCANCODE_CAPSLOCK: return KEY_CAPS; + case SDL_SCANCODE_NUMLOCKCLEAR: return KEY_NUM; + case SDL_SCANCODE_SCROLLLOCK: return KEY_SCROLL; + case SDL_SCANCODE_PRINTSCREEN: return KEY_SYSREQ; + default: return KEY_NONE; + } +} + +// ============================================================================ +// SDL3INPUTMANAGER IMPLEMENTATION +// ============================================================================ + +/** + * Lifecycle + */ +SDL3InputManager::SDL3InputManager() + : m_mouseNextFree(0), + m_mouseNextGet(0), + m_keyNextFree(0), + m_keyNextGet(0), + m_gamepad(nullptr), + m_precisionMode(FALSE), + m_lastUpdateTime(0), + m_isQuitting(FALSE) +{ + memset(m_mouseEvents, 0, sizeof(m_mouseEvents)); + memset(m_keyEvents, 0, sizeof(m_keyEvents)); + TheSDL3InputManager = this; + + openFirstGamepad(); + m_lastUpdateTime = SDL_GetTicks(); +} + +SDL3InputManager::~SDL3InputManager() +{ + closeGamepad(); + TheSDL3InputManager = nullptr; +} + +/** + * Unified Event Loop + */ +void SDL3InputManager::update() +{ + SDL_Event event; + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_EVENT_QUIT: + case SDL_EVENT_WINDOW_CLOSE_REQUESTED: + m_isQuitting = true; + break; + + case SDL_EVENT_GAMEPAD_ADDED: + if (!m_gamepad) openFirstGamepad(); + break; + + case SDL_EVENT_GAMEPAD_REMOVED: + if (m_gamepad && event.gdevice.which == SDL_GetGamepadID(m_gamepad)) closeGamepad(); + break; + + case SDL_EVENT_WINDOW_FOCUS_GAINED: + if (TheMouse) { + TheMouse->regainFocus(); + TheMouse->refreshCursorCapture(); + } + break; + + case SDL_EVENT_WINDOW_FOCUS_LOST: + if (TheMouse) TheMouse->loseFocus(); + break; + + case SDL_EVENT_WINDOW_MOUSE_ENTER: + if (TheMouse) TheMouse->onCursorMovedInside(); + break; + + case SDL_EVENT_WINDOW_MOUSE_LEAVE: + if (TheMouse) TheMouse->onCursorMovedOutside(); + break; + + case SDL_EVENT_MOUSE_MOTION: + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: + case SDL_EVENT_MOUSE_WHEEL: + addMouseSDLEvent(event); + break; + + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: + if (!event.key.repeat) addKeyboardSDLEvent(event); + break; + + case SDL_EVENT_TEXT_INPUT: + if (TheGameEngine) { + SDL3GameEngine* engine = dynamic_cast(TheGameEngine); + if (engine) engine->forwardTextInputEvent(event.text.text); + } + break; + + default: + break; + } + } + + processGamepadInput(); +} + +/** + * Buffer Management + */ +Bool SDL3InputManager::getNextMouseEvent(SDL_Event& outEvent) +{ + if (m_mouseEvents[m_mouseNextGet].type == SDL_EVENT_FIRST) return FALSE; + + SDL_Event* event = &m_mouseEvents[m_mouseNextGet]; + m_mouseNextGet = (m_mouseNextGet + 1) % MAX_MOUSE_EVENTS; + + outEvent = *event; + event->type = SDL_EVENT_FIRST; + return TRUE; +} + +Bool SDL3InputManager::getNextKeyboardEvent(SDL_Event& outEvent) +{ + if (m_keyEvents[m_keyNextGet].type == SDL_EVENT_FIRST) return FALSE; + + SDL_Event* event = &m_keyEvents[m_keyNextGet]; + m_keyNextGet = (m_keyNextGet + 1) % MAX_KEY_EVENTS; + + outEvent = *event; + event->type = SDL_EVENT_FIRST; + return TRUE; +} + +void SDL3InputManager::addMouseSDLEvent(const SDL_Event& event) +{ + UnsignedInt nextFree = (m_mouseNextFree + 1) % MAX_MOUSE_EVENTS; + if (nextFree == m_mouseNextGet) return; + m_mouseEvents[m_mouseNextFree] = event; + m_mouseNextFree = nextFree; +} + +void SDL3InputManager::addKeyboardSDLEvent(const SDL_Event& event) +{ + UnsignedInt nextFree = (m_keyNextFree + 1) % MAX_KEY_EVENTS; + if (nextFree == m_keyNextGet) return; + m_keyEvents[m_keyNextFree] = event; + m_keyNextFree = nextFree; +} + +/** + * Gamepad Logic + */ +void SDL3InputManager::openFirstGamepad() +{ + int count = 0; + SDL_JoystickID* joysticks = SDL_GetGamepads(&count); + if (joysticks) { + for (int i = 0; i < count; ++i) { + m_gamepad = SDL_OpenGamepad(joysticks[i]); + if (m_gamepad) { + DEBUG_LOG(("SDL3InputManager: Opened gamepad: %s", SDL_GetGamepadName(m_gamepad))); + break; + } + } + SDL_free(joysticks); + } +} + +void SDL3InputManager::closeGamepad() +{ + if (m_gamepad) { + SDL_CloseGamepad(m_gamepad); + m_gamepad = nullptr; + } +} + +void SDL3InputManager::virtualPulseKey(SDL_Scancode scancode, bool down) +{ + SDL_Event keyEvent; + memset(&keyEvent, 0, sizeof(keyEvent)); + keyEvent.type = down ? SDL_EVENT_KEY_DOWN : SDL_EVENT_KEY_UP; + keyEvent.key.scancode = scancode; + keyEvent.key.down = down; + + if (scancode == SDL_SCANCODE_LCTRL) keyEvent.key.mod = SDL_KMOD_LCTRL; + else if (scancode == SDL_SCANCODE_LSHIFT) keyEvent.key.mod = SDL_KMOD_LSHIFT; + else if (scancode == SDL_SCANCODE_LALT) keyEvent.key.mod = SDL_KMOD_LALT; + + addKeyboardSDLEvent(keyEvent); +} + +void SDL3InputManager::virtualPulseMouse(Uint8 button, bool down) +{ + SDL_Event clickEvent; + memset(&clickEvent, 0, sizeof(clickEvent)); + clickEvent.type = down ? SDL_EVENT_MOUSE_BUTTON_DOWN : SDL_EVENT_MOUSE_BUTTON_UP; + clickEvent.button.button = button; + clickEvent.button.clicks = 1; + clickEvent.button.down = down; + + float mx, my; + SDL_GetMouseState(&mx, &my); + clickEvent.button.x = mx; + clickEvent.button.y = my; + + addMouseSDLEvent(clickEvent); +} + +void SDL3InputManager::handleGamepadButton(SDL_GamepadButton button, bool& currentState, bool isDown, std::function action) +{ + if (isDown != currentState) { + action(isDown); + currentState = isDown; + } +} + +void SDL3InputManager::processGamepadInput() +{ + if (!m_gamepad) return; + + Uint64 now = SDL_GetTicks(); + float deltaTime = (now - m_lastUpdateTime) / 1000.0f; + m_lastUpdateTime = now; + + const float DEADZONE = DEFAULT_DEADZONE; + const float CURSOR_SPEED = DEFAULT_CURSOR_SPEED; + + // 1. TRIGGERS (Modifiers & Precision) + bool ltPressed = SDL_GetGamepadAxis(m_gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER) > TRIGGER_THRESHOLD; + if (ltPressed != m_state.ltDown) { + m_state.ltDown = ltPressed; + m_precisionMode = m_state.ltDown; + } + + bool rtPressed = SDL_GetGamepadAxis(m_gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) > TRIGGER_THRESHOLD; + if (rtPressed != m_state.rtDown) { + m_state.rtDown = rtPressed; + virtualPulseKey(SDL_SCANCODE_LCTRL, m_state.rtDown); + } + + // 2. STICKS (Movement & Panning) + float lx = SDL_GetGamepadAxis(m_gamepad, SDL_GAMEPAD_AXIS_LEFTX) / AXIS_MAX; + float ly = SDL_GetGamepadAxis(m_gamepad, SDL_GAMEPAD_AXIS_LEFTY) / AXIS_MAX; + + if (SDL_fabsf(lx) > DEADZONE || SDL_fabsf(ly) > DEADZONE) { + float speed = CURSOR_SPEED; + if (m_precisionMode) speed *= 0.3f; + + SDL_Event motionEvent; + memset(&motionEvent, 0, sizeof(motionEvent)); + motionEvent.type = SDL_EVENT_MOUSE_MOTION; + motionEvent.motion.xrel = lx * speed * deltaTime; + motionEvent.motion.yrel = ly * speed * deltaTime; + + float mx, my; + SDL_GetMouseState(&mx, &my); + motionEvent.motion.x = mx + motionEvent.motion.xrel; + motionEvent.motion.y = my + motionEvent.motion.yrel; + + addMouseSDLEvent(motionEvent); + SDL_WarpMouseInWindow(NULL, motionEvent.motion.x, motionEvent.motion.y); + } + + float rx = SDL_GetGamepadAxis(m_gamepad, SDL_GAMEPAD_AXIS_RIGHTX) / AXIS_MAX; + float ry = SDL_GetGamepadAxis(m_gamepad, SDL_GAMEPAD_AXIS_RIGHTY) / AXIS_MAX; + + handleGamepadButton(SDL_GAMEPAD_BUTTON_INVALID, m_state.stickLeft, rx < -DEADZONE, [&](bool d){ virtualPulseKey(SDL_SCANCODE_LEFT, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_INVALID, m_state.stickRight, rx > DEADZONE, [&](bool d){ virtualPulseKey(SDL_SCANCODE_RIGHT, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_INVALID, m_state.stickUp, ry < -DEADZONE, [&](bool d){ virtualPulseKey(SDL_SCANCODE_UP, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_INVALID, m_state.stickDown, ry > DEADZONE, [&](bool d){ virtualPulseKey(SDL_SCANCODE_DOWN, d); }); + + // 3. BUTTONS & D-PAD (Actions & Hotkeys) + handleGamepadButton(SDL_GAMEPAD_BUTTON_SOUTH, m_state.buttonState[SDL_GAMEPAD_BUTTON_SOUTH], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_SOUTH), [&](bool d){ virtualPulseMouse(SDL_BUTTON_LEFT, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_EAST, m_state.buttonState[SDL_GAMEPAD_BUTTON_EAST], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_EAST), [&](bool d){ virtualPulseMouse(SDL_BUTTON_RIGHT, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_WEST, m_state.buttonState[SDL_GAMEPAD_BUTTON_WEST], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_WEST), [&](bool d){ virtualPulseKey(SDL_SCANCODE_A, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_NORTH, m_state.buttonState[SDL_GAMEPAD_BUTTON_NORTH], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_NORTH), [&](bool d){ if (d) TheMessageStream->appendMessage(GameMessage::MSG_META_STOP); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, m_state.buttonState[SDL_GAMEPAD_BUTTON_LEFT_SHOULDER], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), [&](bool d){ virtualPulseKey(SDL_SCANCODE_Q, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, m_state.buttonState[SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), [&](bool d){ virtualPulseKey(SDL_SCANCODE_LSHIFT, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_START, m_state.buttonState[SDL_GAMEPAD_BUTTON_START], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_START), [&](bool d){ virtualPulseKey(SDL_SCANCODE_ESCAPE, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_BACK, m_state.buttonState[SDL_GAMEPAD_BUTTON_BACK], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_BACK), [&](bool d){ virtualPulseKey(SDL_SCANCODE_SPACE, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_UP, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_UP], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP), [&](bool d){ virtualPulseKey(SDL_SCANCODE_1, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_DOWN, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_DOWN], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN), [&](bool d){ virtualPulseKey(SDL_SCANCODE_2, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_LEFT, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_LEFT], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_LEFT), [&](bool d){ virtualPulseKey(SDL_SCANCODE_3, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_RIGHT, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_RIGHT], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT), [&](bool d){ virtualPulseKey(SDL_SCANCODE_4, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_LEFT_STICK, m_state.buttonState[SDL_GAMEPAD_BUTTON_LEFT_STICK], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_LEFT_STICK), [&](bool d){ if (d) TheMessageStream->appendMessage(GameMessage::MSG_META_SELECT_NEXT_IDLE_WORKER); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_RIGHT_STICK, m_state.buttonState[SDL_GAMEPAD_BUTTON_RIGHT_STICK], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_RIGHT_STICK), [&](bool d){ if (d) TheMessageStream->appendMessage(GameMessage::MSG_META_VIEW_COMMAND_CENTER); }); +} diff --git a/Core/GameEngineDevice/Source/SDL3GameEngine.cpp b/Core/GameEngineDevice/Source/SDL3GameEngine.cpp new file mode 100644 index 00000000000..fe9dd205247 --- /dev/null +++ b/Core/GameEngineDevice/Source/SDL3GameEngine.cpp @@ -0,0 +1,382 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "Lib/BaseType.h" + +#include +#include +#include +#include + +#include "Common/GameEngine.h" +#include "SDL3GameEngine.h" +#include "SDL3Device/GameClient/SDL3Input.h" +#include "MilesAudioDevice/MilesAudioManager.h" +#include "GameClient/Mouse.h" +#include "GameClient/Keyboard.h" +#include "GameClient/GameWindow.h" +#include "GameClient/GameWindowManager.h" +#include "GameClient/Gadget.h" +#include "GameNetwork/LANAPICallbacks.h" +#include "GameNetwork/NetworkInterface.h" +#include "GameLogic/GameLogic.h" +#include "W3DDevice/GameLogic/W3DGameLogic.h" +#include "W3DDevice/GameClient/W3DGameClient.h" +#include "W3DDevice/Common/W3DModuleFactory.h" +#include "W3DDevice/Common/W3DThingFactory.h" +#include "W3DDevice/Common/W3DFunctionLexicon.h" +#include "W3DDevice/Common/W3DRadar.h" +#include "W3DDevice/GameClient/W3DParticleSys.h" +#include "W3DDevice/GameClient/W3DWebBrowser.h" +#include "StdDevice/Common/StdLocalFileSystem.h" +#include "StdDevice/Common/StdBIGFileSystem.h" + +// Extern globals for input devices (set by GameClient) +extern Mouse *TheMouse; +extern Keyboard *TheKeyboard; +extern GameWindowManager *TheWindowManager; + +namespace { + +Bool DecodeNextUtf8Codepoint(const char* text, size_t length, size_t& offset, UnsignedInt& outCodepoint) +{ + outCodepoint = 0; + if (!text || offset >= length) { + return false; + } + + const unsigned char first = static_cast(text[offset]); + if (first == 0) { + return false; + } + + if (first < 0x80) { + outCodepoint = first; + offset += 1; + return true; + } + + if ((first & 0xE0) == 0xC0 && offset + 1 < length) { + const unsigned char second = static_cast(text[offset + 1]); + if ((second & 0xC0) == 0x80) { + outCodepoint = ((first & 0x1F) << 6) | (second & 0x3F); + offset += 2; + return true; + } + } + + if ((first & 0xF0) == 0xE0 && offset + 2 < length) { + const unsigned char second = static_cast(text[offset + 1]); + const unsigned char third = static_cast(text[offset + 2]); + if ((second & 0xC0) == 0x80 && (third & 0xC0) == 0x80) { + outCodepoint = ((first & 0x0F) << 12) | ((second & 0x3F) << 6) | (third & 0x3F); + offset += 3; + return true; + } + } + + if ((first & 0xF8) == 0xF0 && offset + 3 < length) { + const unsigned char second = static_cast(text[offset + 1]); + const unsigned char third = static_cast(text[offset + 2]); + const unsigned char fourth = static_cast(text[offset + 3]); + if ((second & 0xC0) == 0x80 && (third & 0xC0) == 0x80 && (fourth & 0xC0) == 0x80) { + outCodepoint = ((first & 0x07) << 18) | ((second & 0x3F) << 12) | ((third & 0x3F) << 6) | (fourth & 0x3F); + offset += 4; + return true; + } + } + + // Invalid UTF-8 sequence: skip one byte and keep processing. + offset += 1; + return false; +} + +} + +/** + * Constructor: Initialize SDL3 game engine state + */ +SDL3GameEngine::SDL3GameEngine() + : GameEngine(), + m_SDLWindow(nullptr), + m_IsInitialized(false), + m_IsActive(false), + m_IsTextInputActive(false), + m_TextInputFocusWindow(nullptr) +{ +} + +/** + * Destructor: Cleanup SDL3 resources + */ +SDL3GameEngine::~SDL3GameEngine() +{ + if (m_SDLWindow && m_IsTextInputActive) { + SDL_StopTextInput(m_SDLWindow); + m_IsTextInputActive = false; + m_TextInputFocusWindow = nullptr; + } + + if (TheSDL3InputManager) { + delete TheSDL3InputManager; + } +} + +/** + * From GameEngine: init() - initialize subsystems + */ +void SDL3GameEngine::init(void) +{ + // Verify window was created by SDL3Main integration + extern SDL_Window* TheSDL3Window; + extern HWND ApplicationHWnd; + + if (!TheSDL3Window || !ApplicationHWnd) { + return; + } + + // Store window reference locally + m_SDLWindow = TheSDL3Window; + m_IsInitialized = true; + m_IsActive = true; + + // Initialize the unified input manager + if (!TheSDL3InputManager) { + NEW SDL3InputManager(); + } + + // Call parent init to initialize game subsystems + GameEngine::init(); +} + +/** + * From GameEngine: reset() - reset system to starting state + */ +void SDL3GameEngine::reset(void) +{ + if (m_SDLWindow && m_IsTextInputActive) { + SDL_StopTextInput(m_SDLWindow); + m_IsTextInputActive = false; + m_TextInputFocusWindow = nullptr; + } + GameEngine::reset(); +} + +/** + * From GameEngine: update() - per-frame update + */ +void SDL3GameEngine::update(void) +{ + pollSDL3Events(); + GameEngine::update(); + + // If the window is minimized, enter a throttled loop to save resources + // while keeping the network connection alive, matching legacy Win32 behavior. + if (m_SDLWindow && (SDL_GetWindowFlags(m_SDLWindow) & SDL_WINDOW_MINIMIZED)) + { + while (m_SDLWindow && (SDL_GetWindowFlags(m_SDLWindow) & SDL_WINDOW_MINIMIZED)) + { + // Prevent CPU/GPU pinning while alt-tabbed + SDL_Delay(5); + + // Stay responsive to events (so we can see when we're un-minimized) + pollSDL3Events(); + + // Keep the LAN subsystem alive to prevent multiplayer disconnects + if (TheLAN != nullptr) { + TheLAN->setIsActive(isActive()); + TheLAN->update(); + } + + // If we are in a network game, we must NOT stay in this loop, + // as the engine needs to keep pumping logic frames to avoid desyncs. + if (getQuitting() || (TheGameLogic && (TheGameLogic->isInInternetGame() || TheGameLogic->isInLanGame()))) { + break; + } + } + } +} + +/** + * From GameEngine: serviceWindowsOS() - native OS service + */ +void SDL3GameEngine::serviceWindowsOS(void) +{ + pollSDL3Events(); +} + +/** + * Check if game has OS focus + */ +Bool SDL3GameEngine::isActive(void) +{ + return m_IsActive; +} + +/** + * Set OS focus status + */ +void SDL3GameEngine::setIsActive(Bool isActive) +{ + m_IsActive = isActive; +} + +/** + * Poll and process SDL3 events + */ +void SDL3GameEngine::pollSDL3Events(void) +{ + if (!m_SDLWindow || !TheSDL3InputManager) { + return; + } + + updateTextInputState(); + + // Process all events via the dedicated manager + TheSDL3InputManager->update(); + + // Check if we should quit + if (TheSDL3InputManager->isQuitting()) { + m_quitting = true; + } +} + +void SDL3GameEngine::updateTextInputState(void) +{ + if (!m_SDLWindow || !TheWindowManager) { + return; + } + + GameWindow* focusedWindow = TheWindowManager->winGetFocus(); + const Bool wantsTextInput = + focusedWindow != nullptr && BitIsSet(focusedWindow->winGetStyle(), GWS_ENTRY_FIELD); + + if (wantsTextInput) { + if (!m_IsTextInputActive) { + if (SDL_StartTextInput(m_SDLWindow)) { + m_IsTextInputActive = true; + } + } + m_TextInputFocusWindow = focusedWindow; + } else { + if (m_IsTextInputActive) { + SDL_StopTextInput(m_SDLWindow); + m_IsTextInputActive = false; + } + m_TextInputFocusWindow = nullptr; + } +} + +void SDL3GameEngine::forwardTextInputEvent(const char* utf8Text) +{ + if (!utf8Text || !TheWindowManager) { + return; + } + + GameWindow* targetWindow = m_TextInputFocusWindow; + if (!targetWindow || !BitIsSet(targetWindow->winGetStyle(), GWS_ENTRY_FIELD)) { + return; + } + + const size_t textLength = strlen(utf8Text); + size_t offset = 0; + while (offset < textLength) { + UnsignedInt codepoint = 0; + if (!DecodeNextUtf8Codepoint(utf8Text, textLength, offset, codepoint)) { + continue; + } + + if (codepoint == 0 || codepoint > 0x10FFFFU) { + continue; + } + + if (codepoint >= 0xD800U && codepoint <= 0xDFFFU) { + continue; + } + + if (codepoint > 0xFFFFU) { + continue; + } + + const WideChar wideCharacter = static_cast(codepoint); + TheWindowManager->winSendInputMsg(targetWindow, GWM_IME_CHAR, static_cast(wideCharacter), 0); + } +} + +/** + * Factory Methods for GameEngine subsystems + */ + +LocalFileSystem *SDL3GameEngine::createLocalFileSystem(void) +{ + return NEW StdLocalFileSystem; +} + +ArchiveFileSystem *SDL3GameEngine::createArchiveFileSystem(void) +{ + return NEW StdBIGFileSystem; +} + +GameLogic *SDL3GameEngine::createGameLogic(void) +{ + return NEW W3DGameLogic; +} + +GameClient *SDL3GameEngine::createGameClient(void) +{ + return NEW W3DGameClient; +} + +ModuleFactory *SDL3GameEngine::createModuleFactory(void) +{ + return NEW W3DModuleFactory; +} + +ThingFactory *SDL3GameEngine::createThingFactory(void) +{ + return NEW W3DThingFactory; +} + +FunctionLexicon *SDL3GameEngine::createFunctionLexicon(void) +{ + return NEW W3DFunctionLexicon; +} + +Radar *SDL3GameEngine::createRadar(Bool dummy) +{ + (void)dummy; + return NEW W3DRadar; +} + +ParticleSystemManager* SDL3GameEngine::createParticleSystemManager(Bool dummy) +{ + (void)dummy; + return NEW W3DParticleSystemManager; +} + +WebBrowser *SDL3GameEngine::createWebBrowser(void) +{ + return nullptr; +} + +AudioManager *SDL3GameEngine::createAudioManager(Bool dummy) +{ + if (dummy) + return NEW MilesAudioManagerDummy; + return NEW MilesAudioManager; +} diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/Display.h b/GeneralsMD/Code/GameEngine/Include/GameClient/Display.h index 8c022244206..038d84b1260 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/Display.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/Display.h @@ -80,6 +80,11 @@ class Display : public SubsystemInterface virtual UnsignedInt getBitDepth() { return m_bitDepth; } virtual void setWindowed( Bool windowed ) { m_windowed = windowed; } ///< set windowed/fullscreen flag virtual Bool getWindowed() { return m_windowed; } ///< return widowed/fullscreen flag + +#if SAGE_USE_SDL3 + virtual Bool getViewportRect( Int& x, Int& y, Int& width, Int& height ) const { return FALSE; } +#endif + virtual Bool setDisplayMode( UnsignedInt xres, UnsignedInt yres, UnsignedInt bitdepth, Bool windowed ); /// + #include "SDL3GameEngine.h" + #include "GameClient/Keyboard.h" + SDL_Window* TheSDL3Window = nullptr; +#else + #include "Win32Device/Common/Win32GameEngine.h" + #include "Win32Device/GameClient/Win32Mouse.h" +#endif + #include "GeneratedVersion.h" #include "resource.h" @@ -87,6 +96,9 @@ static Bool gDoPaint = true; static Bool isWinMainActive = false; static HBITMAP gLoadScreenBitmap = nullptr; +#if SAGE_USE_SDL3 +static SDL_Surface* gLoadScreenSurface = nullptr; +#endif //#define DEBUG_WINDOWS_MESSAGES @@ -855,14 +867,23 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, if (fileImage) { fclose(fileImage); gLoadScreenBitmap = (HBITMAP)LoadImage(hInstance, filePath, IMAGE_BITMAP, 0, 0, LR_SHARED|LR_LOADFROMFILE); +#if SAGE_USE_SDL3 + gLoadScreenSurface = SDL_LoadBMP(filePath); +#endif } else { gLoadScreenBitmap = (HBITMAP)LoadImage(hInstance, fileName, IMAGE_BITMAP, 0, 0, LR_SHARED|LR_LOADFROMFILE); +#if SAGE_USE_SDL3 + gLoadScreenSurface = SDL_LoadBMP(fileName); +#endif } #else // in release, the file only ever lives in the root dir gLoadScreenBitmap = (HBITMAP)LoadImage(hInstance, "Install_Final.bmp", IMAGE_BITMAP, 0, 0, LR_SHARED|LR_LOADFROMFILE); +#if SAGE_USE_SDL3 + gLoadScreenSurface = SDL_LoadBMP("Install_Final.bmp"); +#endif #endif CommandLine::parseCommandLineForStartup(); @@ -872,18 +893,78 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, #endif // register windows class and create application window +#if SAGE_USE_SDL3 + if (!TheGlobalData->m_headless) + { + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS | SDL_INIT_GAMEPAD) == 0) + { + DEBUG_LOG(("SDL_Init failed: %s", SDL_GetError())); + return exitcode; + } + + Uint32 flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE; + if (!TheGlobalData->m_windowed) flags |= SDL_WINDOW_FULLSCREEN; + + TheSDL3Window = SDL_CreateWindow("Command & Conquer Generals", 800, 600, flags); + if (!TheSDL3Window) + { + DEBUG_LOG(("SDL_CreateWindow failed: %s", SDL_GetError())); + return exitcode; + } + + // Retrieve the native HWND from the SDL window for D3D compatibility + SDL_PropertiesID props = SDL_GetWindowProperties(TheSDL3Window); + ApplicationHWnd = (HWND)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); + + // Set initial window size from global data if available + Int startWidth = TheGlobalData->m_xResolution; + Int startHeight = TheGlobalData->m_yResolution; + if (startWidth > 0 && startHeight > 0) + { + SDL_SetWindowSize(TheSDL3Window, startWidth, startHeight); + } + + SDL_ShowWindow(TheSDL3Window); + isWinMainActive = true; + + // Draw the splash screen immediately for SDL3 using safe software surface (8-line modernization) + if (gLoadScreenSurface != nullptr) + { + SDL_Surface* screen = SDL_GetWindowSurface(TheSDL3Window); + if (screen) + { + SDL_ClearSurface(screen, 0.0f, 0.0f, 0.0f, 1.0f); + float bitmapAspect = 800.0f / 600.0f; + int drawWidth = (float)screen->w / screen->h > bitmapAspect ? (int)(screen->h * bitmapAspect) : screen->w; + int drawHeight = (float)screen->w / screen->h > bitmapAspect ? screen->h : (int)(screen->w / bitmapAspect); + SDL_Rect destRect = { (screen->w - drawWidth) / 2, (screen->h - drawHeight) / 2, drawWidth, drawHeight }; + SDL_BlitSurfaceScaled(gLoadScreenSurface, NULL, screen, &destRect, SDL_SCALEMODE_LINEAR); + SDL_UpdateWindowSurface(TheSDL3Window); + } + } + + } +#else if(!TheGlobalData->m_headless && initializeAppWindows(hInstance, nCmdShow, TheGlobalData->m_windowed) == false) { return exitcode; } +#endif // save our application instance for future use ApplicationHInstance = hInstance; - if (gLoadScreenBitmap!=nullptr) { +#if SAGE_USE_SDL3 + if (gLoadScreenSurface != nullptr) { + SDL_DestroySurface(gLoadScreenSurface); + gLoadScreenSurface = nullptr; + } +#else + if (gLoadScreenBitmap != nullptr) { ::DeleteObject(gLoadScreenBitmap); gLoadScreenBitmap = nullptr; } +#endif // BGC - initialize COM @@ -957,6 +1038,11 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, //============================================================================= GameEngine *CreateGameEngine() { +#if SAGE_USE_SDL3 + SDL3GameEngine *engine = NEW SDL3GameEngine; + engine->setIsActive(isWinMainActive); + return engine; +#else Win32GameEngine *engine; engine = NEW Win32GameEngine; @@ -965,5 +1051,5 @@ GameEngine *CreateGameEngine() engine->setIsActive(isWinMainActive); return engine; - +#endif } diff --git a/cmake/config-build.cmake b/cmake/config-build.cmake index b28f5c0b760..923be92abe2 100644 --- a/cmake/config-build.cmake +++ b/cmake/config-build.cmake @@ -9,6 +9,16 @@ option(RTS_BUILD_OPTION_ASAN "Build code with Address Sanitizer." OFF) option(RTS_BUILD_OPTION_VC6_FULL_DEBUG "Build VC6 with full debug info." OFF) option(RTS_BUILD_OPTION_FFMPEG "Enable FFmpeg support" OFF) +# Enable SDL3 by default for modern builds +if(NOT IS_VS6_BUILD) + option(SAGE_USE_SDL3 "Enable SDL3 input/window backend" ON) + if(SAGE_USE_SDL3) + target_compile_definitions(core_config INTERFACE SAGE_USE_SDL3=1) + endif() +else() + set(SAGE_USE_SDL3 OFF CACHE BOOL "Enable SDL3 input/window backend" FORCE) +endif() + if(NOT RTS_BUILD_ZEROHOUR AND NOT RTS_BUILD_GENERALS) set(RTS_BUILD_ZEROHOUR TRUE) message("You must select one project to build, building Zero Hour by default.") diff --git a/cmake/sdl3.cmake b/cmake/sdl3.cmake new file mode 100644 index 00000000000..8a5ed3728b8 --- /dev/null +++ b/cmake/sdl3.cmake @@ -0,0 +1,95 @@ +include(FetchContent) + +# GeneralsX @build felipebraz 17/04/2026 SDL3 Dependency +# Download and build SDL3 from source as a static library. +# This avoids manual installation and keeps the repository clean. + +set(SDL_TESTS OFF CACHE BOOL "" FORCE) +set(SDL_EXAMPLES OFF CACHE BOOL "" FORCE) +set(SDL_INSTALL OFF CACHE BOOL "" FORCE) +set(SDL_STATIC ON CACHE BOOL "" FORCE) +set(SDL_SHARED OFF CACHE BOOL "" FORCE) + +# Minimal build for Generals/Zero Hour engine integration. + +# Disable Subsystems +set(SDL_RENDER OFF CACHE BOOL "" FORCE) # Disables all hardware renderers (D3D11, D3D12, Vulkan, GL) +set(SDL_HAPTIC OFF CACHE BOOL "" FORCE) +set(SDL_POWER OFF CACHE BOOL "" FORCE) +set(SDL_SENSOR OFF CACHE BOOL "" FORCE) +set(SDL_HIDAPI OFF CACHE BOOL "" FORCE) + +# Disable External Platform Support +set(SDL_X11 OFF CACHE BOOL "" FORCE) +set(SDL_WAYLAND OFF CACHE BOOL "" FORCE) +set(SDL_VULKAN OFF CACHE BOOL "" FORCE) +set(SDL_METAL OFF CACHE BOOL "" FORCE) + +# Disable Misc Features +set(SDL_CAMERA OFF CACHE BOOL "" FORCE) +set(SDL_DIALOG OFF CACHE BOOL "" FORCE) +set(SDL_LOCALE OFF CACHE BOOL "" FORCE) +set(SDL_MISC OFF CACHE BOOL "" FORCE) +set(SDL_OFFSCREEN OFF CACHE BOOL "" FORCE) +set(SDL_VIRTUAL_JOYSTICK OFF CACHE BOOL "" FORCE) + +# SDL3 - Core library (v3.4.2) +FetchContent_Declare( + SDL3 + URL https://github.com/libsdl-org/SDL/releases/download/release-3.4.2/SDL3-3.4.2.tar.gz + URL_HASH SHA256=ef39a2e3f9a8a78296c40da701967dd1b0d0d6e267e483863ce70f8a03b4050c +) + +# SDL3_image - Image loading support (v3.4.0) +# --- SDL3_IMAGE CATEGORIES --- + +# Disable Metadata/Packaging +set(SDLIMAGE_SAMPLES OFF CACHE BOOL "" FORCE) +set(SDLIMAGE_TESTS OFF CACHE BOOL "" FORCE) +set(SDLIMAGE_INSTALL OFF CACHE BOOL "" FORCE) +set(SDLIMAGE_VENDORED OFF CACHE BOOL "" FORCE) +set(SDLIMAGE_BACKEND_WIC OFF CACHE BOOL "" FORCE) # Avoid LNK2005 + +# Disable Codecs (minimal set) +set(SDLIMAGE_BACKEND_STB OFF CACHE BOOL "" FORCE) +set(SDLIMAGE_JPG OFF CACHE BOOL "" FORCE) +set(SDLIMAGE_PNG OFF CACHE BOOL "" FORCE) +set(SDLIMAGE_APNG OFF CACHE BOOL "" FORCE) # Fixes 'APNG_ENABLED not defined' warning +set(SDLIMAGE_WEBP OFF CACHE BOOL "" FORCE) +set(SDLIMAGE_TIF OFF CACHE BOOL "" FORCE) +set(SDLIMAGE_AVIF OFF CACHE BOOL "" FORCE) +set(SDLIMAGE_JXL OFF CACHE BOOL "" FORCE) +set(SDLIMAGE_QOI OFF CACHE BOOL "" FORCE) +set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) # Ensure static for SDL3_image + +FetchContent_Declare( + SDL3_image + URL https://github.com/libsdl-org/SDL_image/releases/download/release-3.4.0/SDL3_image-3.4.0.tar.gz + URL_HASH SHA256=2ceb75eab4235c2c7e93dafc3ef3268ad368ca5de40892bf8cffdd510f29d9d8 +) + +FetchContent_MakeAvailable(SDL3) + +# Trick SDL3_image into thinking SDL3 is already found to avoid the broken find_package() in the build tree. +# SDL3_image specifically checks for SDL3::Headers and SDL3::SDL3 (for static builds). +if(NOT TARGET SDL3::Headers) + if(TARGET SDL3_Headers) + add_library(SDL3::Headers ALIAS SDL3_Headers) + else() + add_library(SDL3::Headers INTERFACE IMPORTED GLOBAL) + target_include_directories(SDL3::Headers INTERFACE "${sdl3_SOURCE_DIR}/include") + endif() +endif() + +if(NOT TARGET SDL3::SDL3) + if(TARGET SDL3-static) + add_library(SDL3::SDL3 ALIAS SDL3-static) + elseif(TARGET SDL3-shared) + add_library(SDL3::SDL3 ALIAS SDL3-shared) + endif() +endif() + +set(SDL3_FOUND TRUE CACHE BOOL "" FORCE) +set(SDL3_VERSION "3.4.2" CACHE STRING "" FORCE) + +FetchContent_MakeAvailable(SDL3_image) From 8df9b8678160a474547c56bbee5315c174bd0a12 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:00:36 +0200 Subject: [PATCH 02/27] SDL3 Input: Complete backport with Gamepad support and hardening Consolidated all work from the test/sdl3-backport branch into a single atomic commit: - Centralized input management via SDL3InputManager. - Hardened Ani/RIFF cursor loading with robust bounds checking. - Native Gamepad support with analogue stick-to-mouse emulation and custom RTS mappings. - Modernized focus and capture handling for better stability during Alt-Tab. - Standardized and secured string operations throughout the SDL3 path. - Updated credit attribution (fbraz3). --- .../Include/SDL3Device/GameClient/SDL3Input.h | 4 ++++ Core/GameEngineDevice/Include/SDL3GameEngine.h | 4 ++++ .../Source/SDL3Device/GameClient/SDL3Input.cpp | 4 ++++ Core/GameEngineDevice/Source/SDL3GameEngine.cpp | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h index 29f8fdcf17f..74d7cb8ac03 100644 --- a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h +++ b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h @@ -16,6 +16,10 @@ ** along with this program. If not, see . */ +/* +** Derived from the GeneralsX branch by fbraz3 +*/ + #pragma once #include "Lib/BaseType.h" diff --git a/Core/GameEngineDevice/Include/SDL3GameEngine.h b/Core/GameEngineDevice/Include/SDL3GameEngine.h index a85a6b575c2..1c86d77b0a9 100644 --- a/Core/GameEngineDevice/Include/SDL3GameEngine.h +++ b/Core/GameEngineDevice/Include/SDL3GameEngine.h @@ -16,6 +16,10 @@ ** along with this program. If not, see . */ +/* +** Derived from the GeneralsX branch by fbraz3 +*/ + #pragma once #include "Lib/BaseType.h" diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp index 44291b80076..7889d343e0b 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp @@ -16,6 +16,10 @@ ** along with this program. If not, see . */ +/* +** Derived from the GeneralsX branch by fbraz3 +*/ + #include "Lib/BaseType.h" #define _USE_MATH_DEFINES diff --git a/Core/GameEngineDevice/Source/SDL3GameEngine.cpp b/Core/GameEngineDevice/Source/SDL3GameEngine.cpp index fe9dd205247..f6c96c4904d 100644 --- a/Core/GameEngineDevice/Source/SDL3GameEngine.cpp +++ b/Core/GameEngineDevice/Source/SDL3GameEngine.cpp @@ -16,6 +16,10 @@ ** along with this program. If not, see . */ +/* +** Derived from the GeneralsX branch by fbraz3 +*/ + #include "Lib/BaseType.h" #include From e3b674b2aed78cb060e0c1c2107427818bb0701e Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:09:32 +0200 Subject: [PATCH 03/27] added build guards --- CMakeLists.txt | 5 ++++- cmake/sdl3.cmake | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c7dfa916b0..56d559d7d83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,7 +53,6 @@ if((WIN32 OR "${CMAKE_SYSTEM}" MATCHES "Windows") AND ${CMAKE_SIZEOF_VOID_P} EQU include(cmake/miles.cmake) include(cmake/bink.cmake) include(cmake/dx8.cmake) - include(cmake/sdl3.cmake) endif() # Define a dummy stlport target when not on VC6. @@ -67,6 +66,10 @@ include(cmake/config.cmake) include(cmake/gamespy.cmake) include(cmake/lzhl.cmake) +if(SAGE_USE_SDL3 AND NOT IS_VS6_BUILD) + include(cmake/sdl3.cmake) +endif() + if (IS_VS6_BUILD) # The original max sdk does not compile against a modern compiler. # If there is a desire to make this work, then a fixed max sdk needs to be created. diff --git a/cmake/sdl3.cmake b/cmake/sdl3.cmake index 8a5ed3728b8..ebb4e16bc25 100644 --- a/cmake/sdl3.cmake +++ b/cmake/sdl3.cmake @@ -1,5 +1,9 @@ include(FetchContent) +if(NOT SAGE_USE_SDL3 OR IS_VS6_BUILD) + return() +endif() + # GeneralsX @build felipebraz 17/04/2026 SDL3 Dependency # Download and build SDL3 from source as a static library. # This avoids manual installation and keeps the repository clean. From 2499e0b8e7e51f210bd078cb8958a8f99f0ceddd Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:18:08 +0200 Subject: [PATCH 04/27] greptile feedback --- .../Include/SDL3Device/GameClient/SDL3Input.h | 2 +- Core/GameEngineDevice/Include/SDL3GameEngine.h | 2 +- .../Source/SDL3Device/GameClient/SDL3Input.cpp | 9 ++++----- Core/GameEngineDevice/Source/SDL3GameEngine.cpp | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h index 74d7cb8ac03..57e3eca463a 100644 --- a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h +++ b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h @@ -1,6 +1,6 @@ /* ** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 TheSuperHackers +** Copyright 2026 TheSuperHackers ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by diff --git a/Core/GameEngineDevice/Include/SDL3GameEngine.h b/Core/GameEngineDevice/Include/SDL3GameEngine.h index 1c86d77b0a9..8b854886993 100644 --- a/Core/GameEngineDevice/Include/SDL3GameEngine.h +++ b/Core/GameEngineDevice/Include/SDL3GameEngine.h @@ -1,6 +1,6 @@ /* ** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 TheSuperHackers +** Copyright 2026 TheSuperHackers ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp index 7889d343e0b..889b4d81128 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp @@ -1,6 +1,6 @@ /* ** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 TheSuperHackers +** Copyright 2026 TheSuperHackers ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by @@ -58,7 +58,6 @@ SDL3InputManager* TheSDL3InputManager = nullptr; struct AnimatedCursor { std::array m_frameCursors; std::array m_frameSurfaces; - int m_currentFrame = 0; int m_frameCount = 0; int m_frameRate = 0; // the time a frame is displayed in 1/60th of a second @@ -95,7 +94,7 @@ struct AnimatedCursor { Uint64 now = SDL_GetTicks(); size_t index = (m_frameRate > 0) - ? (size_t)((now * 60 / 1000) / m_frameRate) % m_frameCount + ? (size_t)((now * 60 / 1000) / m_frameRate) % (size_t)std::min((int)m_frameCount, MAX_2D_CURSOR_ANIM_FRAMES) : 0; return m_frameCursors[index]; } @@ -213,7 +212,7 @@ static AnimatedCursor* loadANI(const char* filepath) } ANIHeader *ani_header = (ANIHeader*)getChunkData(chunk); - cursor->m_frameCount = ani_header->frames; + cursor->m_frameCount = (int)std::min((unsigned int)ani_header->frames, (unsigned int)MAX_2D_CURSOR_ANIM_FRAMES); cursor->m_frameRate = ani_header->displayRate; } else if (chunk->id == list_id && chunk->type == fram_id) @@ -1084,7 +1083,7 @@ void SDL3InputManager::processGamepadInput() motionEvent.motion.y = my + motionEvent.motion.yrel; addMouseSDLEvent(motionEvent); - SDL_WarpMouseInWindow(NULL, motionEvent.motion.x, motionEvent.motion.y); + SDL_WarpMouseInWindow(nullptr, motionEvent.motion.x, motionEvent.motion.y); } float rx = SDL_GetGamepadAxis(m_gamepad, SDL_GAMEPAD_AXIS_RIGHTX) / AXIS_MAX; diff --git a/Core/GameEngineDevice/Source/SDL3GameEngine.cpp b/Core/GameEngineDevice/Source/SDL3GameEngine.cpp index f6c96c4904d..03025768e05 100644 --- a/Core/GameEngineDevice/Source/SDL3GameEngine.cpp +++ b/Core/GameEngineDevice/Source/SDL3GameEngine.cpp @@ -1,6 +1,6 @@ /* ** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 TheSuperHackers +** Copyright 2026 TheSuperHackers ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by From f8fb43f6a604cb14574c3851dfb19888724ef2ae Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:29:45 +0200 Subject: [PATCH 05/27] greptile feedback --- .../Include/SDL3Device/GameClient/SDL3Input.h | 1 + .../SDL3Device/GameClient/SDL3Input.cpp | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h index 57e3eca463a..66a583ec9f6 100644 --- a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h +++ b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h @@ -59,6 +59,7 @@ class SDL3Mouse : public Mouse virtual void reset(void) override; virtual void update(void) override; virtual void initCursorResources(void) override; + static void freeCursorResources(void); // Mouse interface virtual void setCursor(MouseCursor cursor) override; diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp index 889b4d81128..33837ac80d9 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp @@ -439,6 +439,21 @@ void SDL3Mouse::initCursorResources(void) } } +void SDL3Mouse::freeCursorResources(void) +{ + for (Int cursor = 0; cursor < Mouse::NUM_MOUSE_CURSORS; cursor++) + { + for (Int direction = 0; direction < MAX_2D_CURSOR_DIRECTIONS; direction++) + { + if (cursorResources[cursor][direction]) + { + delete cursorResources[cursor][direction]; + cursorResources[cursor][direction] = nullptr; + } + } + } +} + AnimatedCursor* SDL3Mouse::loadCursorFromFile(const char* filepath) { return loadANI(filepath); @@ -859,6 +874,7 @@ SDL3InputManager::SDL3InputManager() SDL3InputManager::~SDL3InputManager() { closeGamepad(); + SDL3Mouse::freeCursorResources(); TheSDL3InputManager = nullptr; } @@ -1081,6 +1097,11 @@ void SDL3InputManager::processGamepadInput() SDL_GetMouseState(&mx, &my); motionEvent.motion.x = mx + motionEvent.motion.xrel; motionEvent.motion.y = my + motionEvent.motion.yrel; + + if (m_SDLWindow) + { + motionEvent.motion.windowID = SDL_GetWindowID(m_SDLWindow); + } addMouseSDLEvent(motionEvent); SDL_WarpMouseInWindow(nullptr, motionEvent.motion.x, motionEvent.motion.y); From 4ac91e0d79056222fce381cadc0a41f791b7a52d Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:54:18 +0200 Subject: [PATCH 06/27] fixed build error --- .../Include/SDL3Device/GameClient/SDL3Input.h | 6 ++++-- .../Source/SDL3Device/GameClient/SDL3Input.cpp | 9 +++++---- Core/GameEngineDevice/Source/SDL3GameEngine.cpp | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h index 66a583ec9f6..5d574178115 100644 --- a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h +++ b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h @@ -143,7 +143,7 @@ class SDL3Keyboard : public Keyboard class SDL3InputManager { public: - SDL3InputManager(); + SDL3InputManager(SDL_Window* window); virtual ~SDL3InputManager(); void update(); @@ -180,6 +180,9 @@ class SDL3InputManager // Gamepad management void openFirstGamepad(); void closeGamepad(); + + SDL_Window* m_window; + SDL_Gamepad* m_gamepad; void processGamepadInput(); void handleGamepadButton(SDL_GamepadButton button, bool& currentState, bool isDown, std::function action); @@ -200,7 +203,6 @@ class SDL3InputManager UnsignedInt m_keyNextGet; // Gamepad state - SDL_Gamepad* m_gamepad; GamepadState m_state; Bool m_precisionMode; diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp index 33837ac80d9..7d8e45a9b26 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp @@ -853,8 +853,9 @@ KeyVal SDL3Keyboard::translateScanCodeToKeyVal(unsigned char scan) /** * Lifecycle */ -SDL3InputManager::SDL3InputManager() - : m_mouseNextFree(0), +SDL3InputManager::SDL3InputManager(SDL_Window* window) + : m_window(window), + m_mouseNextFree(0), m_mouseNextGet(0), m_keyNextFree(0), m_keyNextGet(0), @@ -1098,9 +1099,9 @@ void SDL3InputManager::processGamepadInput() motionEvent.motion.x = mx + motionEvent.motion.xrel; motionEvent.motion.y = my + motionEvent.motion.yrel; - if (m_SDLWindow) + if (m_window) { - motionEvent.motion.windowID = SDL_GetWindowID(m_SDLWindow); + motionEvent.motion.windowID = SDL_GetWindowID(m_window); } addMouseSDLEvent(motionEvent); diff --git a/Core/GameEngineDevice/Source/SDL3GameEngine.cpp b/Core/GameEngineDevice/Source/SDL3GameEngine.cpp index 03025768e05..c719c43b19d 100644 --- a/Core/GameEngineDevice/Source/SDL3GameEngine.cpp +++ b/Core/GameEngineDevice/Source/SDL3GameEngine.cpp @@ -161,7 +161,7 @@ void SDL3GameEngine::init(void) // Initialize the unified input manager if (!TheSDL3InputManager) { - NEW SDL3InputManager(); + TheSDL3InputManager = new SDL3InputManager(m_SDLWindow); } // Call parent init to initialize game subsystems From a0ac8f4c067550a2c0c3e12fd257e7fcdcaa0ebb Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Mon, 20 Apr 2026 18:05:18 +0200 Subject: [PATCH 07/27] greptile feedback --- .../Source/SDL3Device/GameClient/SDL3Input.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp index 7d8e45a9b26..be1daa3bce4 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp @@ -1044,6 +1044,11 @@ void SDL3InputManager::virtualPulseMouse(Uint8 button, bool down) SDL_GetMouseState(&mx, &my); clickEvent.button.x = mx; clickEvent.button.y = my; + + if (m_window) + { + clickEvent.button.windowID = SDL_GetWindowID(m_window); + } addMouseSDLEvent(clickEvent); } From d9d9ff0c2a486cfd915b1031b04ecf315ec091c7 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Mon, 20 Apr 2026 20:24:35 +0200 Subject: [PATCH 08/27] added vcpkg --- Core/GameEngineDevice/CMakeLists.txt | 5 +- cmake/sdl3.cmake | 131 ++++++++------------------- vcpkg-lock.json | 109 ++++++++++++---------- vcpkg.json | 29 +++++- 4 files changed, 125 insertions(+), 149 deletions(-) diff --git a/Core/GameEngineDevice/CMakeLists.txt b/Core/GameEngineDevice/CMakeLists.txt index a98ccadde9b..69ae180a1de 100644 --- a/Core/GameEngineDevice/CMakeLists.txt +++ b/Core/GameEngineDevice/CMakeLists.txt @@ -240,9 +240,8 @@ target_link_libraries(corei_gameenginedevice_public INTERFACE # Export SDL3 dependencies for modern builds if(SAGE_USE_SDL3 AND NOT IS_VS6_BUILD) target_link_libraries(corei_gameenginedevice_public INTERFACE - SDL3::Headers - SDL3::SDL3-static - SDL3_image::SDL3_image-static + SDL3::SDL3 + SDL3_image::SDL3_image ) endif() diff --git a/cmake/sdl3.cmake b/cmake/sdl3.cmake index ebb4e16bc25..ec34f4b1466 100644 --- a/cmake/sdl3.cmake +++ b/cmake/sdl3.cmake @@ -1,99 +1,40 @@ -include(FetchContent) - -if(NOT SAGE_USE_SDL3 OR IS_VS6_BUILD) - return() +# Standardized vcpkg integration: Try find_package first, fallback to source build if not found. +find_package(SDL3 CONFIG QUIET) +find_package(SDL3_image CONFIG QUIET) + +if(NOT SDL3_FOUND OR NOT SDL3_image_FOUND) + message(STATUS "SDL3 not found via vcpkg/find_package, falling back to source build (FetchContent)...") + include(FetchContent) + + FetchContent_Declare( + SDL3 + URL https://github.com/libsdl-org/SDL/releases/download/release-3.4.4/SDL3-3.4.4.tar.gz + URL_HASH SHA256=EE712DBE6A89BB140BBFC2CE72358FB5EE5CC2240ABEABD54855012DB30B3864 + OVERRIDE_FIND_PACKAGE + ) + + FetchContent_Declare( + SDL3_image + URL https://github.com/libsdl-org/SDL_image/releases/download/release-3.4.2/SDL3_image-3.4.2.tar.gz + URL_HASH SHA256=82fdb88cf1a9cbdc1c77797aaa3292e6d22ce12586be718c8ea43530df1536b4 + ) + + # Official SDL configuration for a unified build tree + set(SDL_SHARED ON CACHE BOOL "" FORCE) + set(SDL_STATIC OFF CACHE BOOL "" FORCE) + set(SDLIMAGE_VENDORED OFF CACHE BOOL "" FORCE) + set(SDLIMAGE_ZLIB ON CACHE BOOL "" FORCE) + set(SDLIMAGE_PNG ON CACHE BOOL "" FORCE) + set(SDLIMAGE_APNG ON CACHE BOOL "" FORCE) + + # Making them available in order ensures SDL3_image can see the SDL3 targets + FetchContent_MakeAvailable(SDL3 SDL3_image) endif() -# GeneralsX @build felipebraz 17/04/2026 SDL3 Dependency -# Download and build SDL3 from source as a static library. -# This avoids manual installation and keeps the repository clean. - -set(SDL_TESTS OFF CACHE BOOL "" FORCE) -set(SDL_EXAMPLES OFF CACHE BOOL "" FORCE) -set(SDL_INSTALL OFF CACHE BOOL "" FORCE) -set(SDL_STATIC ON CACHE BOOL "" FORCE) -set(SDL_SHARED OFF CACHE BOOL "" FORCE) - -# Minimal build for Generals/Zero Hour engine integration. - -# Disable Subsystems -set(SDL_RENDER OFF CACHE BOOL "" FORCE) # Disables all hardware renderers (D3D11, D3D12, Vulkan, GL) -set(SDL_HAPTIC OFF CACHE BOOL "" FORCE) -set(SDL_POWER OFF CACHE BOOL "" FORCE) -set(SDL_SENSOR OFF CACHE BOOL "" FORCE) -set(SDL_HIDAPI OFF CACHE BOOL "" FORCE) - -# Disable External Platform Support -set(SDL_X11 OFF CACHE BOOL "" FORCE) -set(SDL_WAYLAND OFF CACHE BOOL "" FORCE) -set(SDL_VULKAN OFF CACHE BOOL "" FORCE) -set(SDL_METAL OFF CACHE BOOL "" FORCE) - -# Disable Misc Features -set(SDL_CAMERA OFF CACHE BOOL "" FORCE) -set(SDL_DIALOG OFF CACHE BOOL "" FORCE) -set(SDL_LOCALE OFF CACHE BOOL "" FORCE) -set(SDL_MISC OFF CACHE BOOL "" FORCE) -set(SDL_OFFSCREEN OFF CACHE BOOL "" FORCE) -set(SDL_VIRTUAL_JOYSTICK OFF CACHE BOOL "" FORCE) - -# SDL3 - Core library (v3.4.2) -FetchContent_Declare( - SDL3 - URL https://github.com/libsdl-org/SDL/releases/download/release-3.4.2/SDL3-3.4.2.tar.gz - URL_HASH SHA256=ef39a2e3f9a8a78296c40da701967dd1b0d0d6e267e483863ce70f8a03b4050c -) - -# SDL3_image - Image loading support (v3.4.0) -# --- SDL3_IMAGE CATEGORIES --- - -# Disable Metadata/Packaging -set(SDLIMAGE_SAMPLES OFF CACHE BOOL "" FORCE) -set(SDLIMAGE_TESTS OFF CACHE BOOL "" FORCE) -set(SDLIMAGE_INSTALL OFF CACHE BOOL "" FORCE) -set(SDLIMAGE_VENDORED OFF CACHE BOOL "" FORCE) -set(SDLIMAGE_BACKEND_WIC OFF CACHE BOOL "" FORCE) # Avoid LNK2005 - -# Disable Codecs (minimal set) -set(SDLIMAGE_BACKEND_STB OFF CACHE BOOL "" FORCE) -set(SDLIMAGE_JPG OFF CACHE BOOL "" FORCE) -set(SDLIMAGE_PNG OFF CACHE BOOL "" FORCE) -set(SDLIMAGE_APNG OFF CACHE BOOL "" FORCE) # Fixes 'APNG_ENABLED not defined' warning -set(SDLIMAGE_WEBP OFF CACHE BOOL "" FORCE) -set(SDLIMAGE_TIF OFF CACHE BOOL "" FORCE) -set(SDLIMAGE_AVIF OFF CACHE BOOL "" FORCE) -set(SDLIMAGE_JXL OFF CACHE BOOL "" FORCE) -set(SDLIMAGE_QOI OFF CACHE BOOL "" FORCE) -set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) # Ensure static for SDL3_image - -FetchContent_Declare( - SDL3_image - URL https://github.com/libsdl-org/SDL_image/releases/download/release-3.4.0/SDL3_image-3.4.0.tar.gz - URL_HASH SHA256=2ceb75eab4235c2c7e93dafc3ef3268ad368ca5de40892bf8cffdd510f29d9d8 -) - -FetchContent_MakeAvailable(SDL3) - -# Trick SDL3_image into thinking SDL3 is already found to avoid the broken find_package() in the build tree. -# SDL3_image specifically checks for SDL3::Headers and SDL3::SDL3 (for static builds). -if(NOT TARGET SDL3::Headers) - if(TARGET SDL3_Headers) - add_library(SDL3::Headers ALIAS SDL3_Headers) - else() - add_library(SDL3::Headers INTERFACE IMPORTED GLOBAL) - target_include_directories(SDL3::Headers INTERFACE "${sdl3_SOURCE_DIR}/include") - endif() +# Uniform aliases to ensure linking works across both discovery methods +if(TARGET SDL3::SDL3-shared AND NOT TARGET SDL3::SDL3) + add_library(SDL3::SDL3 ALIAS SDL3::SDL3-shared) endif() - -if(NOT TARGET SDL3::SDL3) - if(TARGET SDL3-static) - add_library(SDL3::SDL3 ALIAS SDL3-static) - elseif(TARGET SDL3-shared) - add_library(SDL3::SDL3 ALIAS SDL3-shared) - endif() +if(TARGET SDL3::SDL3-static AND NOT TARGET SDL3::SDL3) + add_library(SDL3::SDL3 ALIAS SDL3::SDL3-static) endif() - -set(SDL3_FOUND TRUE CACHE BOOL "" FORCE) -set(SDL3_VERSION "3.4.2" CACHE STRING "" FORCE) - -FetchContent_MakeAvailable(SDL3_image) diff --git a/vcpkg-lock.json b/vcpkg-lock.json index 422998f0378..c6ef82b1101 100644 --- a/vcpkg-lock.json +++ b/vcpkg-lock.json @@ -1,48 +1,65 @@ { - "version": 1, - "dependencies": [ - { - "name": "ffmpeg", - "version-string": "7.1.1", - "port-version": 1, - "git-tree": "6ff75f1f596ada519241989f44077cda442480b2" - }, - { - "name": "pkgconf", - "version-string": "2.3.0", - "port-version": 0, - "git-tree": "ae3886d8a627ec99dd18890389b6d5d331e29799" - }, - { - "name": "vcpkg-cmake", - "version-string": "2024-04-23", - "port-version": 0, - "git-tree": "e74aa1e8f93278a8e71372f1fa08c3df420eb840" - }, - { - "name": "vcpkg-cmake-get-vars", - "version-string": "2024-09-22", - "port-version": 0, - "git-tree": "f23148add155147f3d95ae622d3b0031beb25acf" - }, - { - "name": "vcpkg-pkgconfig-get-modules", - "version-string": "2024-04-03", - "port-version": 0, - "git-tree": "6845369c8cb7d3c318e8e3ae92fd2b7570a756ca" - }, - { - "name": "vcpkg-tool-meson", - "version-string": "1.6.1", - "port-version": 0, - "git-tree": "dc948c67d7f1359319f801078422e996b0a89fd0" - }, - { - "name": "zlib", - "version-string": "1.3.1", - "port-version": 0, - "git-tree": "3f05e04b9aededb96786a911a16193cdb711f0c9" - } - ] + "version": 1, + "dependencies": [ + { + "name": "ffmpeg", + "version-string": "7.1.1", + "port-version": 1, + "git-tree": "6ff75f1f596ada519241989f44077cda442480b2" + }, + { + "name": "pkgconf", + "version-string": "2.3.0", + "port-version": 0, + "git-tree": "ae3886d8a627ec99dd18890389b6d5d331e29799" + }, + { + "name": "sdl3", + "version-string": "3.4.4", + "port-version": 0, + "git-tree": "c3ea8e6cf352b01ab5bf9035850e72f674af2433" + }, + { + "name": "sdl3-image", + "version-string": "3.4.2", + "port-version": 0, + "git-tree": "2621596cc09e39b1ab98298f4f9e126c1764a6c4" + }, + { + "name": "vcpkg-cmake", + "version-string": "2024-04-23", + "port-version": 0, + "git-tree": "e74aa1e8f93278a8e71372f1fa08c3df420eb840" + }, + { + "name": "vcpkg-cmake-config", + "version-string": "2024-05-23", + "port-version": 0, + "git-tree": "97a63e4bc1a17422ffe4eff71da53b4b561a7841" + }, + { + "name": "vcpkg-cmake-get-vars", + "version-string": "2024-09-22", + "port-version": 0, + "git-tree": "f23148add155147f3d95ae622d3b0031beb25acf" + }, + { + "name": "vcpkg-pkgconfig-get-modules", + "version-string": "2024-04-03", + "port-version": 0, + "git-tree": "6845369c8cb7d3c318e8e3ae92fd2b7570a756ca" + }, + { + "name": "vcpkg-tool-meson", + "version-string": "1.6.1", + "port-version": 0, + "git-tree": "dc948c67d7f1359319f801078422e996b0a89fd0" + }, + { + "name": "zlib", + "version-string": "1.3.1", + "port-version": 0, + "git-tree": "3f05e04b9aededb96786a911a16193cdb711f0c9" + } + ] } - diff --git a/vcpkg.json b/vcpkg.json index 011b913c8aa..b3543b926b8 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,8 +1,27 @@ { "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", - "builtin-baseline": "b02e341c927f16d991edbd915d8ea43eac52096c", "dependencies": [ - "zlib", - "ffmpeg" - ] - } \ No newline at end of file + "zlib", + "ffmpeg", + "sdl3", + "sdl3-image" + ], + "configuration": { + "default-registry": { + "kind": "git", + "repository": "https://github.com/microsoft/vcpkg", + "baseline": "b02e341c927f16d991edbd915d8ea43eac52096c" + }, + "registries": [ + { + "kind": "git", + "repository": "https://github.com/microsoft/vcpkg", + "baseline": "256acc64012b23a13041d8705805e1f23b43a024", + "packages": [ + "sdl3", + "sdl3-image" + ] + } + ] + } +} \ No newline at end of file From d048867a7c0ebc6258b04803e25a3b5bd076ad60 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Mon, 20 Apr 2026 21:08:38 +0200 Subject: [PATCH 09/27] vcpkg fix --- vcpkg-configuration.json | 19 +++++++++++++++++++ vcpkg.json | 32 +++++++------------------------- 2 files changed, 26 insertions(+), 25 deletions(-) create mode 100644 vcpkg-configuration.json diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json new file mode 100644 index 00000000000..1561529b76b --- /dev/null +++ b/vcpkg-configuration.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg-configuration.schema.json", + "default-registry": { + "kind": "git", + "repository": "https://github.com/microsoft/vcpkg", + "baseline": "b02e341c927f16d991edbd915d8ea43eac52096c" + }, + "registries": [ + { + "kind": "git", + "repository": "https://github.com/microsoft/vcpkg", + "baseline": "256acc64012b23a13041d8705805e1f23b43a024", + "packages": [ + "sdl3", + "sdl3-image" + ] + } + ] +} \ No newline at end of file diff --git a/vcpkg.json b/vcpkg.json index b3543b926b8..c6e9380319a 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,27 +1,9 @@ { - "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", - "dependencies": [ - "zlib", - "ffmpeg", - "sdl3", - "sdl3-image" - ], - "configuration": { - "default-registry": { - "kind": "git", - "repository": "https://github.com/microsoft/vcpkg", - "baseline": "b02e341c927f16d991edbd915d8ea43eac52096c" - }, - "registries": [ - { - "kind": "git", - "repository": "https://github.com/microsoft/vcpkg", - "baseline": "256acc64012b23a13041d8705805e1f23b43a024", - "packages": [ - "sdl3", - "sdl3-image" - ] - } - ] - } + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", + "dependencies": [ + "zlib", + "ffmpeg", + "sdl3", + "sdl3-image" + ] } \ No newline at end of file From 534e694154153bed081dca5eef29564435e88c55 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Mon, 20 Apr 2026 21:49:58 +0200 Subject: [PATCH 10/27] changed dpad group numbers --- .../Source/SDL3Device/GameClient/SDL3Input.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp index be1daa3bce4..b8732ab6c99 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp @@ -1130,10 +1130,10 @@ void SDL3InputManager::processGamepadInput() handleGamepadButton(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, m_state.buttonState[SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), [&](bool d){ virtualPulseKey(SDL_SCANCODE_LSHIFT, d); }); handleGamepadButton(SDL_GAMEPAD_BUTTON_START, m_state.buttonState[SDL_GAMEPAD_BUTTON_START], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_START), [&](bool d){ virtualPulseKey(SDL_SCANCODE_ESCAPE, d); }); handleGamepadButton(SDL_GAMEPAD_BUTTON_BACK, m_state.buttonState[SDL_GAMEPAD_BUTTON_BACK], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_BACK), [&](bool d){ virtualPulseKey(SDL_SCANCODE_SPACE, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_UP, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_UP], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP), [&](bool d){ virtualPulseKey(SDL_SCANCODE_1, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_DOWN, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_DOWN], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN), [&](bool d){ virtualPulseKey(SDL_SCANCODE_2, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_LEFT, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_LEFT], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_LEFT), [&](bool d){ virtualPulseKey(SDL_SCANCODE_3, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_RIGHT, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_RIGHT], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT), [&](bool d){ virtualPulseKey(SDL_SCANCODE_4, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_LEFT, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_LEFT], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_LEFT), [&](bool d){ virtualPulseKey(SDL_SCANCODE_1, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_UP, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_UP], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP), [&](bool d){ virtualPulseKey(SDL_SCANCODE_2, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_RIGHT, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_RIGHT], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT), [&](bool d){ virtualPulseKey(SDL_SCANCODE_3, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_DOWN, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_DOWN], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN), [&](bool d){ virtualPulseKey(SDL_SCANCODE_4, d); }); handleGamepadButton(SDL_GAMEPAD_BUTTON_LEFT_STICK, m_state.buttonState[SDL_GAMEPAD_BUTTON_LEFT_STICK], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_LEFT_STICK), [&](bool d){ if (d) TheMessageStream->appendMessage(GameMessage::MSG_META_SELECT_NEXT_IDLE_WORKER); }); handleGamepadButton(SDL_GAMEPAD_BUTTON_RIGHT_STICK, m_state.buttonState[SDL_GAMEPAD_BUTTON_RIGHT_STICK], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_RIGHT_STICK), [&](bool d){ if (d) TheMessageStream->appendMessage(GameMessage::MSG_META_VIEW_COMMAND_CENTER); }); } From 09fd4e68ac2e47a220d87a2eeefa892f45411f22 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Mon, 20 Apr 2026 23:06:25 +0200 Subject: [PATCH 11/27] switch back to static linking --- cmake/sdl3.cmake | 30 +++++++++++++++++++++++------- triplets/x86-windows.cmake | 4 ++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/cmake/sdl3.cmake b/cmake/sdl3.cmake index ec34f4b1466..dfc5d3026f1 100644 --- a/cmake/sdl3.cmake +++ b/cmake/sdl3.cmake @@ -20,15 +20,19 @@ if(NOT SDL3_FOUND OR NOT SDL3_image_FOUND) ) # Official SDL configuration for a unified build tree - set(SDL_SHARED ON CACHE BOOL "" FORCE) - set(SDL_STATIC OFF CACHE BOOL "" FORCE) + set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) + set(SDL_SHARED OFF CACHE BOOL "" FORCE) + set(SDL_STATIC ON CACHE BOOL "" FORCE) set(SDLIMAGE_VENDORED OFF CACHE BOOL "" FORCE) - set(SDLIMAGE_ZLIB ON CACHE BOOL "" FORCE) - set(SDLIMAGE_PNG ON CACHE BOOL "" FORCE) - set(SDLIMAGE_APNG ON CACHE BOOL "" FORCE) + set(SDLIMAGE_SHARED OFF CACHE BOOL "" FORCE) + set(SDLIMAGE_STATIC ON CACHE BOOL "" FORCE) + set(SDLIMAGE_ZLIB OFF CACHE BOOL "" FORCE) + set(SDLIMAGE_PNG OFF CACHE BOOL "" FORCE) + set(SDLIMAGE_APNG OFF CACHE BOOL "" FORCE) - # Making them available in order ensures SDL3_image can see the SDL3 targets - FetchContent_MakeAvailable(SDL3 SDL3_image) + # Populate SDL3 and SDL3_image + FetchContent_MakeAvailable(SDL3) + FetchContent_MakeAvailable(SDL3_image) endif() # Uniform aliases to ensure linking works across both discovery methods @@ -38,3 +42,15 @@ endif() if(TARGET SDL3::SDL3-static AND NOT TARGET SDL3::SDL3) add_library(SDL3::SDL3 ALIAS SDL3::SDL3-static) endif() + +# Centralized dependency restoration for SDL3 static builds. +# We apply these directly to the SDL3-static target so it correctly handles its own needs. +if(TARGET SDL3-static) + target_link_libraries(SDL3-static INTERFACE + ws2_32.lib + winmm.lib + imm32.lib + version.lib + setupapi.lib + ) +endif() diff --git a/triplets/x86-windows.cmake b/triplets/x86-windows.cmake index 106900a72d7..ab214b8bf09 100644 --- a/triplets/x86-windows.cmake +++ b/triplets/x86-windows.cmake @@ -3,6 +3,10 @@ set(VCPKG_TARGET_ARCHITECTURE x86) set(VCPKG_CRT_LINKAGE dynamic) set(VCPKG_LIBRARY_LINKAGE dynamic) +if(PORT MATCHES "sdl3") + set(VCPKG_LIBRARY_LINKAGE static) +endif() + # Exclude compiler version from ABI hash so that weekly GitHub runner image # updates don't invalidate the binary cache. Minor MSVC version bumps do not # cause ABI incompatibilities for this project. From 9b0262c9ec41e3b123d200f9941bcba62c33f606 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:40:52 +0200 Subject: [PATCH 12/27] move SDL3_image out SDL3input --- Core/GameEngineDevice/CMakeLists.txt | 2 + .../SDL3Device/GameClient/SDL3Cursor.h | 70 +++++ .../Include/SDL3Device/GameClient/SDL3Input.h | 8 - .../SDL3Device/GameClient/SDL3Cursor.cpp | 254 +++++++++++++++++ .../SDL3Device/GameClient/SDL3Input.cpp | 267 +----------------- 5 files changed, 335 insertions(+), 266 deletions(-) create mode 100644 Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h create mode 100644 Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp diff --git a/Core/GameEngineDevice/CMakeLists.txt b/Core/GameEngineDevice/CMakeLists.txt index 69ae180a1de..7d630f42230 100644 --- a/Core/GameEngineDevice/CMakeLists.txt +++ b/Core/GameEngineDevice/CMakeLists.txt @@ -197,8 +197,10 @@ if(SAGE_USE_SDL3 AND NOT IS_VS6_BUILD) list(APPEND GAMEENGINEDEVICE_SRC Include/SDL3GameEngine.h Include/SDL3Device/GameClient/SDL3Input.h + Include/SDL3Device/GameClient/SDL3Cursor.h Source/SDL3GameEngine.cpp Source/SDL3Device/GameClient/SDL3Input.cpp + Source/SDL3Device/GameClient/SDL3Cursor.cpp ) endif() diff --git a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h new file mode 100644 index 00000000000..0e41b3855f8 --- /dev/null +++ b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h @@ -0,0 +1,70 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2026 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* +** Derived from the GeneralsX branch by fbraz3 +*/ + +#pragma once + +#include "Lib/BaseType.h" +#include +#include + +// USER INCLUDES +#include "GameClient/Mouse.h" + +/** + * AnimatedCursor - Wrapper for SDL3 native animated cursors + */ +struct AnimatedCursor { + SDL_Cursor* m_cursor; + + AnimatedCursor() : m_cursor(nullptr) {} + ~AnimatedCursor() + { + if (m_cursor) + { + SDL_DestroyCursor(m_cursor); + m_cursor = nullptr; + } + } + + SDL_Cursor* getCursor() const { return m_cursor; } +}; + +/** + * SDL3CursorManager - Manages loading and lifecycle of cursors + */ +class SDL3CursorManager +{ +public: + static void init(); + static void shutdown(); + + static SDL_Cursor* getCursor(Mouse::MouseCursor cursor, int direction); + + // Internal loader used by Mouse implementation + static void initResources(Mouse* mouse); + +private: + static AnimatedCursor* loadCursorFromFile(const char* filepath); + static AnimatedCursor* loadANI(const char* filepath); + + static AnimatedCursor* m_cursorResources[Mouse::NUM_MOUSE_CURSORS][MAX_2D_CURSOR_DIRECTIONS]; +}; diff --git a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h index 5d574178115..43b46609f17 100644 --- a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h +++ b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h @@ -26,7 +26,6 @@ // SYSTEM INCLUDES #include -#include #include #include @@ -36,7 +35,6 @@ #include "GameClient/KeyDefs.h" // FORWARD REFERENCES -struct AnimatedCursor; class SDL3InputManager; // GLOBALS --------------------------------------------------------------------- @@ -82,9 +80,6 @@ class SDL3Mouse : public Mouse // Scale raw SDL window coordinates to game internal resolution void scaleMouseCoordinates(int rawX, int rawY, Uint32 windowID, int& scaledX, int& scaledY); - // Load cursor from ANI file (fighter19 pattern) - AnimatedCursor* loadCursorFromFile(const char* filepath); - SDL_Window* m_Window; Bool m_IsCaptured; Bool m_IsVisible; @@ -93,20 +88,17 @@ class SDL3Mouse : public Mouse Uint32 m_LeftButtonDownTime; Uint32 m_RightButtonDownTime; Uint32 m_MiddleButtonDownTime; - UnsignedInt m_LastFrameNumber; ICoord2D m_LeftButtonDownPos; ICoord2D m_RightButtonDownPos; ICoord2D m_MiddleButtonDownPos; Int m_directionFrame; - UnsignedInt m_inputFrame; float m_accumulatedDeltaX; float m_accumulatedDeltaY; SDL_Cursor* m_activeSDLCursor; - Bool m_cursorDirty; }; // SDL3Keyboard --------------------------------------------------------------- diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp new file mode 100644 index 00000000000..d114205ad14 --- /dev/null +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp @@ -0,0 +1,254 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2026 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +/* +** Derived from the GeneralsX branch by fbraz3 +*/ + +#include "SDL3Device/GameClient/SDL3Cursor.h" +#include +#include +#include +#include +#include +#include + +#include "Common/Debug.h" +#include "Common/file.h" +#include "Common/FileSystem.h" + +// Initialize static member +AnimatedCursor* SDL3CursorManager::m_cursorResources[Mouse::NUM_MOUSE_CURSORS][MAX_2D_CURSOR_DIRECTIONS] = { nullptr }; + +// RIFF/ANI parsing helpers (moved from SDL3Input.cpp) +typedef std::array FourCC; +constexpr FourCC riff_id = {'R', 'I', 'F', 'F'}; +constexpr FourCC acon_id = {'A', 'C', 'O', 'N'}; +constexpr FourCC anih_id = {'a', 'n', 'i', 'h'}; +constexpr FourCC fram_id = {'f', 'r', 'a', 'm'}; +constexpr FourCC icon_id = {'i', 'c', 'o', 'n'}; +constexpr FourCC list_id = {'L', 'I', 'S', 'T'}; + +struct ANIHeader +{ + uint32_t size; + uint32_t frames; + uint32_t steps; + uint32_t width; + uint32_t height; + uint32_t bitsPerPixel; + uint32_t planes; + uint32_t displayRate; + uint32_t flags; +}; + +struct RIFFChunk +{ + FourCC id; + uint32_t size; + FourCC type; +}; + +static RIFFChunk* getNextChunk(RIFFChunk* chunk, const char* buffer_end) +{ + if (!chunk) return nullptr; + char* next = (char*)chunk + 8 + chunk->size; + if (chunk->size % 2 != 0) next++; + if (next >= buffer_end) return nullptr; + return (RIFFChunk*)next; +} + +static void* getChunkData(RIFFChunk* chunk) +{ + if (chunk->id == list_id || chunk->id == riff_id) + return (char*)chunk + 12; + return (char*)chunk + 8; +} + +void SDL3CursorManager::init() +{ + // Cursors are typically initialized via initResources when the Mouse device is ready + shutdown(); +} + +void SDL3CursorManager::shutdown() +{ + for (int i = 0; i < Mouse::NUM_MOUSE_CURSORS; ++i) + { + for (int j = 0; j < MAX_2D_CURSOR_DIRECTIONS; ++j) + { + if (m_cursorResources[i][j]) + { + delete m_cursorResources[i][j]; + m_cursorResources[i][j] = nullptr; + } + } + } +} + +SDL_Cursor* SDL3CursorManager::getCursor(Mouse::MouseCursor cursor, int direction) +{ + if (cursor < 0 || cursor >= Mouse::NUM_MOUSE_CURSORS) return nullptr; + if (direction < 0 || direction >= MAX_2D_CURSOR_DIRECTIONS) direction = 0; + + AnimatedCursor* anim = m_cursorResources[cursor][direction]; + return anim ? anim->getCursor() : nullptr; +} + +void SDL3CursorManager::initResources(Mouse* mouse) +{ + if (!mouse) return; + + for (Int cursor = Mouse::FIRST_CURSOR; cursor < Mouse::NUM_MOUSE_CURSORS; cursor++) + { + for (Int direction = 0; direction < mouse->m_cursorInfo[cursor].numDirections; direction++) + { + if (!m_cursorResources[cursor][direction] && !mouse->m_cursorInfo[cursor].textureName.isEmpty()) + { + char resourcePath[256]; + if (mouse->m_cursorInfo[cursor].numDirections > 1) + snprintf(resourcePath, sizeof(resourcePath), "Data/Cursors/%s%d.ani", mouse->m_cursorInfo[cursor].textureName.str(), direction); + else + snprintf(resourcePath, sizeof(resourcePath), "Data/Cursors/%s.ani", mouse->m_cursorInfo[cursor].textureName.str()); + + m_cursorResources[cursor][direction] = loadANI(resourcePath); + DEBUG_ASSERTCRASH(m_cursorResources[cursor][direction], ("MissingCursor %s\n", resourcePath)); + } + } + } +} + +AnimatedCursor* SDL3CursorManager::loadANI(const char* filepath) +{ + File* file = TheFileSystem->openFile(filepath, File::READ | File::BINARY); + if (!file) + { + DEBUG_LOG(("loadANI: Failed to open ANI cursor [%s]", filepath)); + return nullptr; + } + + Int size = file->size(); + if (size < (Int)sizeof(RIFFChunk)) + { + DEBUG_LOG(("loadANI: File too small [%s]", filepath)); + file->close(); + return nullptr; + } + + std::unique_ptr file_buffer(new char[size]); + if (file->read(file_buffer.get(), size) != size) + { + DEBUG_LOG(("loadANI: Failed to read ANI cursor [%s]", filepath)); + file->close(); + return nullptr; + } + file->close(); + + char* buffer_start = file_buffer.get(); + char* buffer_end = buffer_start + size; + + RIFFChunk *riff_header = (RIFFChunk*)buffer_start; + if (riff_header->id != riff_id || riff_header->type != acon_id) + { + DEBUG_LOG(("loadANI: Not a valid RIFF/ACON file [%s]", filepath)); + return nullptr; + } + + DEBUG_LOG(("loadANI: Loading %s", filepath)); + + std::vector frames; + int frameRate = 0; + int hot_spot_x = 0; + int hot_spot_y = 0; + bool hot_spot_set = false; + + // Top level chunks start after the RIFF header (8 bytes + 'ACON' = 12 bytes) + RIFFChunk* chunk = (RIFFChunk*)(buffer_start + 12); + + while (chunk != nullptr && (char *)chunk + 8 <= buffer_end) + { + if (chunk->id == anih_id) + { + if (chunk->size >= sizeof(ANIHeader)) + { + ANIHeader *ani_header = (ANIHeader*)getChunkData(chunk); + frameRate = ani_header->displayRate; // Display rate in 1/60th of a second + } + } + else if (chunk->id == list_id && chunk->type == fram_id) + { + RIFFChunk *frame = (RIFFChunk*)((char *)chunk + 12); + char* list_end = (char*)chunk + 8 + chunk->size; + if (list_end > buffer_end) list_end = buffer_end; + + while (frame != nullptr && (char *)frame + 8 <= list_end) + { + if (frame->id == icon_id) + { + if ((char*)frame + 8 + frame->size <= list_end) + { + const void *frame_buffer = getChunkData(frame); + SDL_IOStream *io_stream = SDL_IOFromConstMem(frame_buffer, frame->size); + if (io_stream) + { + SDL_Surface *surface = IMG_LoadTyped_IO(io_stream, true, "ico"); + if (surface) + { + if (!hot_spot_set) + { + SDL_PropertiesID props = SDL_GetSurfaceProperties(surface); + hot_spot_x = (int)SDL_GetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_X_NUMBER, 0); + hot_spot_y = (int)SDL_GetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER, 0); + hot_spot_set = true; + } + + SDL_CursorFrameInfo info; + info.surface = surface; + // SAGE's displayRate is in 1/60th of a second. SDL3 wants milliseconds. + info.duration = (frameRate * 1000) / 60; + frames.push_back(info); + } + } + } + } + frame = getNextChunk(frame, list_end); + } + } + chunk = getNextChunk(chunk, buffer_end); + } + + if (frames.empty()) return nullptr; + + std::unique_ptr cursor(new AnimatedCursor()); + if (frames.size() == 1) + { + cursor->m_cursor = SDL_CreateColorCursor(frames[0].surface, hot_spot_x, hot_spot_y); + } + else + { + cursor->m_cursor = SDL_CreateAnimatedCursor(frames.data(), (int)frames.size(), hot_spot_x, hot_spot_y); + } + + // Clean up all surfaces + for (auto& f : frames) + { + SDL_DestroySurface(f.surface); + } + + return cursor.release(); +} diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp index b8732ab6c99..4dda8ee30d5 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp @@ -31,7 +31,7 @@ #include #include #include -#include +#include "SDL3Device/GameClient/SDL3Cursor.h" #include // For timeGetTime() #include "SDL3Device/GameClient/SDL3Input.h" @@ -48,215 +48,10 @@ // GLOBALS --------------------------------------------------------------------- SDL3InputManager* TheSDL3InputManager = nullptr; -// ============================================================================ +/// ============================================================================ // SDL3MOUSE IMPLEMENTATION // ============================================================================ -/** - * AnimatedCursor - Helper struct for cursor animation - */ -struct AnimatedCursor { - std::array m_frameCursors; - std::array m_frameSurfaces; - int m_frameCount = 0; - int m_frameRate = 0; // the time a frame is displayed in 1/60th of a second - - AnimatedCursor() - { - m_frameCursors.fill(nullptr); - m_frameSurfaces.fill(nullptr); - } - - ~AnimatedCursor() - { - for (int i = 0; i < MAX_2D_CURSOR_ANIM_FRAMES; i++) - { - if (m_frameCursors[i]) - { - SDL_DestroyCursor(m_frameCursors[i]); - m_frameCursors[i] = nullptr; - } - if (m_frameSurfaces[i]) - { - SDL_DestroySurface(m_frameSurfaces[i]); - m_frameSurfaces[i] = nullptr; - } - } - } - - /** - * Get the active frame cursor based on current system time - */ - SDL_Cursor* getActiveFrame() const - { - if (m_frameCount <= 0) return nullptr; - if (m_frameCount == 1) return m_frameCursors[0]; - - Uint64 now = SDL_GetTicks(); - size_t index = (m_frameRate > 0) - ? (size_t)((now * 60 / 1000) / m_frameRate) % (size_t)std::min((int)m_frameCount, MAX_2D_CURSOR_ANIM_FRAMES) - : 0; - return m_frameCursors[index]; - } -}; - -// Global cursor resources array -static AnimatedCursor* cursorResources[Mouse::NUM_MOUSE_CURSORS][MAX_2D_CURSOR_DIRECTIONS]; - -// RIFF/ANI parsing helpers -typedef std::array FourCC; -constexpr FourCC riff_id = {'R', 'I', 'F', 'F'}; -constexpr FourCC acon_id = {'A', 'C', 'O', 'N'}; -constexpr FourCC anih_id = {'a', 'n', 'i', 'h'}; -constexpr FourCC fram_id = {'f', 'r', 'a', 'm'}; -constexpr FourCC icon_id = {'i', 'c', 'o', 'n'}; -constexpr FourCC list_id = {'L', 'I', 'S', 'T'}; - -struct ANIHeader -{ - uint32_t size; - uint32_t frames; - uint32_t steps; - uint32_t width; - uint32_t height; - uint32_t bitsPerPixel; - uint32_t planes; - uint32_t displayRate; - uint32_t flags; -}; - -struct RIFFChunk -{ - FourCC id; - uint32_t size; - FourCC type; -}; - -static RIFFChunk* getNextChunk(RIFFChunk* chunk, const char* buffer_end) -{ - if (!chunk) return nullptr; - - // Size check: Chunk header is at least 8 bytes (ID + Size). - char* next = (char*)chunk + 8 + chunk->size; - - // RIFF chunks are padded to 2 bytes - if (chunk->size % 2 != 0) next++; - - if (next >= buffer_end) return nullptr; - return (RIFFChunk*)next; -} - -static void* getChunkData(RIFFChunk* chunk) -{ - // For LIST and RIFF, type is at +8, data starts at +12 - if (chunk->id == list_id || chunk->id == riff_id) - return (char*)chunk + 12; - - // For others, data starts at +8 - return (char*)chunk + 8; -} - -/** - * loadANI - Dedicated standalone RIFF/ANI parser (Hardened) - */ -static AnimatedCursor* loadANI(const char* filepath) -{ - File* file = TheFileSystem->openFile(filepath, File::READ | File::BINARY); - if (!file) - { - DEBUG_LOG(("loadANI: Failed to open ANI cursor [%s]", filepath)); - return nullptr; - } - - Int size = file->size(); - if (size < (Int)sizeof(RIFFChunk)) - { - DEBUG_LOG(("loadANI: File too small [%s]", filepath)); - file->close(); - return nullptr; - } - - std::unique_ptr file_buffer(new char[size]); - if (file->read(file_buffer.get(), size) != size) - { - DEBUG_LOG(("loadANI: Failed to read ANI cursor [%s]", filepath)); - file->close(); - return nullptr; - } - file->close(); - - char* buffer_start = file_buffer.get(); - char* buffer_end = buffer_start + size; - - RIFFChunk *riff_header = (RIFFChunk*)buffer_start; - if (riff_header->id != riff_id || riff_header->type != acon_id) - { - DEBUG_LOG(("loadANI: Not a valid RIFF/ACON file [%s]", filepath)); - return nullptr; - } - - DEBUG_LOG(("loadANI: Loading %s", filepath)); - std::unique_ptr cursor(new AnimatedCursor()); - - // Top level chunks start after the RIFF header (8 bytes + 'ACON' = 12 bytes) - RIFFChunk* chunk = (RIFFChunk*)(buffer_start + 12); - - while (chunk != nullptr && (char *)chunk + 8 <= buffer_end) - { - if (chunk->id == anih_id) - { - if (chunk->size < sizeof(ANIHeader)) - { - DEBUG_LOG(("loadANI: Invalid ANI header size")); - return nullptr; - } - - ANIHeader *ani_header = (ANIHeader*)getChunkData(chunk); - cursor->m_frameCount = (int)std::min((unsigned int)ani_header->frames, (unsigned int)MAX_2D_CURSOR_ANIM_FRAMES); - cursor->m_frameRate = ani_header->displayRate; - } - else if (chunk->id == list_id && chunk->type == fram_id) - { - int frame_index = 0; - // Sub-chunks in LIST start after the header + type (12 bytes) - RIFFChunk *frame = (RIFFChunk*)((char *)chunk + 12); - char* list_end = (char*)chunk + 8 + chunk->size; - if (list_end > buffer_end) list_end = buffer_end; - - while (frame != nullptr && (char *)frame + 8 <= list_end) - { - if (frame->id == icon_id) - { - if ((char*)frame + 8 + frame->size <= list_end) - { - const void *frame_buffer = getChunkData(frame); - SDL_IOStream *io_stream = SDL_IOFromConstMem(frame_buffer, frame->size); - if (io_stream) - { - SDL_Surface *surface = cursor->m_frameSurfaces[frame_index] = IMG_LoadTyped_IO(io_stream, true, "ico"); - if (surface) - { - SDL_PropertiesID props = SDL_GetSurfaceProperties(surface); - int hot_spot_x = (int)SDL_GetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_X_NUMBER, 0); - int hot_spot_y = (int)SDL_GetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER, 0); - - cursor->m_frameCursors[frame_index++] = SDL_CreateColorCursor(surface, hot_spot_x, hot_spot_y); - } - } - } - } - - if (frame_index >= MAX_2D_CURSOR_ANIM_FRAMES) break; - frame = getNextChunk(frame, list_end); - } - } - - chunk = getNextChunk(chunk, buffer_end); - } - - return cursor.release(); -} - /** * Constructor - Initialize SDL3Mouse with window handle */ @@ -269,13 +64,10 @@ SDL3Mouse::SDL3Mouse(SDL_Window* window) m_LeftButtonDownTime(0), m_RightButtonDownTime(0), m_MiddleButtonDownTime(0), - m_LastFrameNumber(0), m_directionFrame(0), - m_inputFrame(0), m_accumulatedDeltaX(0.0f), m_accumulatedDeltaY(0.0f), - m_activeSDLCursor(nullptr), - m_cursorDirty(false) + m_activeSDLCursor(nullptr) { m_LeftButtonDownPos.x = 0; m_LeftButtonDownPos.y = 0; @@ -324,8 +116,6 @@ void SDL3Mouse::update(void) { Mouse::update(); - m_inputFrame++; - if (m_LostFocus) { return; @@ -385,12 +175,8 @@ void SDL3Mouse::update(void) } else { - AnimatedCursor* animated = cursorResources[cursor][m_directionFrame]; - if (animated) - { - requestedHandle = animated->getActiveFrame(); - } - else + requestedHandle = SDL3CursorManager::getCursor(cursor, m_directionFrame); + if (!requestedHandle) { bUseDefaultCursor = true; } @@ -398,11 +184,8 @@ void SDL3Mouse::update(void) if (bUseDefaultCursor) { - if (cursorResources[NORMAL][0]) - { - requestedHandle = cursorResources[NORMAL][0]->m_frameCursors[0]; - } - else + requestedHandle = SDL3CursorManager::getCursor(NORMAL, 0); + if (!requestedHandle) { requestedHandle = SDL_GetDefaultCursor(); } @@ -413,8 +196,6 @@ void SDL3Mouse::update(void) SDL_SetCursor(requestedHandle); m_activeSDLCursor = requestedHandle; } - - m_cursorDirty = false; } /** @@ -422,41 +203,12 @@ void SDL3Mouse::update(void) */ void SDL3Mouse::initCursorResources(void) { - for (Int cursor=FIRST_CURSOR; cursor 1) - snprintf(resourcePath, sizeof(resourcePath), "Data/Cursors/%s%d.ani", m_cursorInfo[cursor].textureName.str(), direction); - else - snprintf(resourcePath, sizeof(resourcePath), "Data/Cursors/%s.ani", m_cursorInfo[cursor].textureName.str()); - - cursorResources[cursor][direction]=loadCursorFromFile(resourcePath); - DEBUG_ASSERTCRASH(cursorResources[cursor][direction], ("MissingCursor %s\n",resourcePath)); - } - } - } + SDL3CursorManager::initResources(this); } void SDL3Mouse::freeCursorResources(void) { - for (Int cursor = 0; cursor < Mouse::NUM_MOUSE_CURSORS; cursor++) - { - for (Int direction = 0; direction < MAX_2D_CURSOR_DIRECTIONS; direction++) - { - if (cursorResources[cursor][direction]) - { - delete cursorResources[cursor][direction]; - cursorResources[cursor][direction] = nullptr; - } - } - } -} - -AnimatedCursor* SDL3Mouse::loadCursorFromFile(const char* filepath) -{ - return loadANI(filepath); + SDL3CursorManager::shutdown(); } /** @@ -471,7 +223,6 @@ void SDL3Mouse::setCursor(MouseCursor cursor) Mouse::setCursor( cursor ); m_currentCursor = cursor; - m_cursorDirty = true; } /** From 3a40b11f623639128add22c0e82c522933d69477 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:50:28 +0200 Subject: [PATCH 13/27] properly fail on SDL_INIT failure --- GeneralsMD/Code/Main/WinMain.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index cce81e16c8e..6cb6d57cca6 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -896,7 +896,7 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, #if SAGE_USE_SDL3 if (!TheGlobalData->m_headless) { - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS | SDL_INIT_GAMEPAD) == 0) + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS | SDL_INIT_GAMEPAD)) { DEBUG_LOG(("SDL_Init failed: %s", SDL_GetError())); return exitcode; From 4d82b8137b89716cc511f38d8bb11bb99e79ac3d Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Tue, 21 Apr 2026 18:09:29 +0200 Subject: [PATCH 14/27] removed custom ICO decoder --- .../SDL3Device/GameClient/SDL3Cursor.cpp | 156 ++++-------------- 1 file changed, 31 insertions(+), 125 deletions(-) diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp index d114205ad14..45cd031df3a 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp @@ -35,51 +35,6 @@ // Initialize static member AnimatedCursor* SDL3CursorManager::m_cursorResources[Mouse::NUM_MOUSE_CURSORS][MAX_2D_CURSOR_DIRECTIONS] = { nullptr }; -// RIFF/ANI parsing helpers (moved from SDL3Input.cpp) -typedef std::array FourCC; -constexpr FourCC riff_id = {'R', 'I', 'F', 'F'}; -constexpr FourCC acon_id = {'A', 'C', 'O', 'N'}; -constexpr FourCC anih_id = {'a', 'n', 'i', 'h'}; -constexpr FourCC fram_id = {'f', 'r', 'a', 'm'}; -constexpr FourCC icon_id = {'i', 'c', 'o', 'n'}; -constexpr FourCC list_id = {'L', 'I', 'S', 'T'}; - -struct ANIHeader -{ - uint32_t size; - uint32_t frames; - uint32_t steps; - uint32_t width; - uint32_t height; - uint32_t bitsPerPixel; - uint32_t planes; - uint32_t displayRate; - uint32_t flags; -}; - -struct RIFFChunk -{ - FourCC id; - uint32_t size; - FourCC type; -}; - -static RIFFChunk* getNextChunk(RIFFChunk* chunk, const char* buffer_end) -{ - if (!chunk) return nullptr; - char* next = (char*)chunk + 8 + chunk->size; - if (chunk->size % 2 != 0) next++; - if (next >= buffer_end) return nullptr; - return (RIFFChunk*)next; -} - -static void* getChunkData(RIFFChunk* chunk) -{ - if (chunk->id == list_id || chunk->id == riff_id) - return (char*)chunk + 12; - return (char*)chunk + 8; -} - void SDL3CursorManager::init() { // Cursors are typically initialized via initResources when the Mouse device is ready @@ -143,9 +98,9 @@ AnimatedCursor* SDL3CursorManager::loadANI(const char* filepath) } Int size = file->size(); - if (size < (Int)sizeof(RIFFChunk)) + if (size <= 0) { - DEBUG_LOG(("loadANI: File too small [%s]", filepath)); + DEBUG_LOG(("loadANI: File is empty [%s]", filepath)); file->close(); return nullptr; } @@ -159,96 +114,47 @@ AnimatedCursor* SDL3CursorManager::loadANI(const char* filepath) } file->close(); - char* buffer_start = file_buffer.get(); - char* buffer_end = buffer_start + size; - - RIFFChunk *riff_header = (RIFFChunk*)buffer_start; - if (riff_header->id != riff_id || riff_header->type != acon_id) - { - DEBUG_LOG(("loadANI: Not a valid RIFF/ACON file [%s]", filepath)); - return nullptr; - } - DEBUG_LOG(("loadANI: Loading %s", filepath)); - std::vector frames; - int frameRate = 0; - int hot_spot_x = 0; - int hot_spot_y = 0; - bool hot_spot_set = false; + SDL_IOStream *io = SDL_IOFromConstMem(file_buffer.get(), (size_t)size); + if (!io) return nullptr; - // Top level chunks start after the RIFF header (8 bytes + 'ACON' = 12 bytes) - RIFFChunk* chunk = (RIFFChunk*)(buffer_start + 12); + // Use SDL3_image to load the animation (handles RIFF/ANI container and frame decoding) + IMG_Animation *anim = IMG_LoadAnimation_IO(io, true); + if (!anim) + { + DEBUG_LOG(("loadANI: IMG_LoadAnimation_IO failed for [%s]: %s", filepath, SDL_GetError())); + return nullptr; + } - while (chunk != nullptr && (char *)chunk + 8 <= buffer_end) - { - if (chunk->id == anih_id) - { - if (chunk->size >= sizeof(ANIHeader)) - { - ANIHeader *ani_header = (ANIHeader*)getChunkData(chunk); - frameRate = ani_header->displayRate; // Display rate in 1/60th of a second - } - } - else if (chunk->id == list_id && chunk->type == fram_id) - { - RIFFChunk *frame = (RIFFChunk*)((char *)chunk + 12); - char* list_end = (char*)chunk + 8 + chunk->size; - if (list_end > buffer_end) list_end = buffer_end; - - while (frame != nullptr && (char *)frame + 8 <= list_end) - { - if (frame->id == icon_id) - { - if ((char*)frame + 8 + frame->size <= list_end) - { - const void *frame_buffer = getChunkData(frame); - SDL_IOStream *io_stream = SDL_IOFromConstMem(frame_buffer, frame->size); - if (io_stream) - { - SDL_Surface *surface = IMG_LoadTyped_IO(io_stream, true, "ico"); - if (surface) - { - if (!hot_spot_set) - { - SDL_PropertiesID props = SDL_GetSurfaceProperties(surface); - hot_spot_x = (int)SDL_GetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_X_NUMBER, 0); - hot_spot_y = (int)SDL_GetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER, 0); - hot_spot_set = true; - } - - SDL_CursorFrameInfo info; - info.surface = surface; - // SAGE's displayRate is in 1/60th of a second. SDL3 wants milliseconds. - info.duration = (frameRate * 1000) / 60; - frames.push_back(info); - } - } - } - } - frame = getNextChunk(frame, list_end); - } - } - chunk = getNextChunk(chunk, buffer_end); - } + if (anim->count == 0) + { + IMG_FreeAnimation(anim); + return nullptr; + } - if (frames.empty()) return nullptr; + // Get hotspots from the first frame's properties (SDL3_image sets these for ICO/CUR) + SDL_PropertiesID props = SDL_GetSurfaceProperties(anim->frames[0]); + int hot_spot_x = (int)SDL_GetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_X_NUMBER, 0); + int hot_spot_y = (int)SDL_GetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER, 0); + // Create the animated cursor resource std::unique_ptr cursor(new AnimatedCursor()); - if (frames.size() == 1) + if (anim->count == 1) { - cursor->m_cursor = SDL_CreateColorCursor(frames[0].surface, hot_spot_x, hot_spot_y); + cursor->m_cursor = SDL_CreateColorCursor(anim->frames[0], hot_spot_x, hot_spot_y); } else { - cursor->m_cursor = SDL_CreateAnimatedCursor(frames.data(), (int)frames.size(), hot_spot_x, hot_spot_y); - } - - // Clean up all surfaces - for (auto& f : frames) - { - SDL_DestroySurface(f.surface); + std::vector frames(anim->count); + for (int i = 0; i < anim->count; i++) + { + frames[i].surface = anim->frames[i]; + frames[i].duration = (Uint32)anim->delays[i]; + } + cursor->m_cursor = SDL_CreateAnimatedCursor(frames.data(), anim->count, hot_spot_x, hot_spot_y); } + IMG_FreeAnimation(anim); return cursor.release(); } From 884218bc36f97724dc445eeb4c3c07367277a852 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Tue, 21 Apr 2026 18:48:17 +0200 Subject: [PATCH 15/27] fixes SDL_WarpMouseInWindow mismatch --- .../GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp index 4dda8ee30d5..608219e695e 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp @@ -861,7 +861,7 @@ void SDL3InputManager::processGamepadInput() } addMouseSDLEvent(motionEvent); - SDL_WarpMouseInWindow(nullptr, motionEvent.motion.x, motionEvent.motion.y); + SDL_WarpMouseInWindow(m_window, motionEvent.motion.x, motionEvent.motion.y); } float rx = SDL_GetGamepadAxis(m_gamepad, SDL_GAMEPAD_AXIS_RIGHTX) / AXIS_MAX; From b268cdae9bed0ef800644bf28c1617a06de849d9 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Tue, 21 Apr 2026 19:09:48 +0200 Subject: [PATCH 16/27] removed some dead declarations --- .../Include/SDL3Device/GameClient/SDL3Cursor.h | 2 -- .../Include/SDL3Device/GameClient/SDL3Input.h | 8 -------- .../Source/SDL3Device/GameClient/SDL3Input.cpp | 12 +----------- 3 files changed, 1 insertion(+), 21 deletions(-) diff --git a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h index 0e41b3855f8..fe0e4b738ea 100644 --- a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h +++ b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h @@ -63,8 +63,6 @@ class SDL3CursorManager static void initResources(Mouse* mouse); private: - static AnimatedCursor* loadCursorFromFile(const char* filepath); static AnimatedCursor* loadANI(const char* filepath); - static AnimatedCursor* m_cursorResources[Mouse::NUM_MOUSE_CURSORS][MAX_2D_CURSOR_DIRECTIONS]; }; diff --git a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h index 43b46609f17..14ce24203f1 100644 --- a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h +++ b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h @@ -85,14 +85,6 @@ class SDL3Mouse : public Mouse Bool m_IsVisible; Bool m_LostFocus; - Uint32 m_LeftButtonDownTime; - Uint32 m_RightButtonDownTime; - Uint32 m_MiddleButtonDownTime; - - ICoord2D m_LeftButtonDownPos; - ICoord2D m_RightButtonDownPos; - ICoord2D m_MiddleButtonDownPos; - Int m_directionFrame; float m_accumulatedDeltaX; diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp index 608219e695e..cffbcbb8fbe 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp @@ -32,7 +32,6 @@ #include #include #include "SDL3Device/GameClient/SDL3Cursor.h" -#include // For timeGetTime() #include "SDL3Device/GameClient/SDL3Input.h" #include "Common/Debug.h" @@ -61,20 +60,11 @@ SDL3Mouse::SDL3Mouse(SDL_Window* window) m_IsCaptured(false), m_IsVisible(true), m_LostFocus(false), - m_LeftButtonDownTime(0), - m_RightButtonDownTime(0), - m_MiddleButtonDownTime(0), m_directionFrame(0), m_accumulatedDeltaX(0.0f), m_accumulatedDeltaY(0.0f), m_activeSDLCursor(nullptr) { - m_LeftButtonDownPos.x = 0; - m_LeftButtonDownPos.y = 0; - m_RightButtonDownPos.x = 0; - m_RightButtonDownPos.y = 0; - m_MiddleButtonDownPos.x = 0; - m_MiddleButtonDownPos.y = 0; } /** @@ -464,7 +454,7 @@ void SDL3Keyboard::getKey(KeyboardIO *key) key->key = keyDef; key->status = KeyboardIO::STATUS_UNUSED; key->state = keyEvent.down ? KEY_STATE_DOWN : KEY_STATE_UP; - key->keyDownTimeMsec = keyEvent.down ? timeGetTime() : 0; + key->keyDownTimeMsec = keyEvent.down ? (Uint32)SDL_GetTicks() : 0; SDL_Keymod mod = keyEvent.mod; if (mod & SDL_KMOD_LSHIFT) key->state |= KEY_STATE_LSHIFT; From a7883e8291a3bc5fed939cdf917d9f8ccbe77a26 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Tue, 21 Apr 2026 22:55:55 +0200 Subject: [PATCH 17/27] temporary re-added custom decoder --- .../SDL3Device/GameClient/SDL3Cursor.cpp | 117 +++++++++++------- 1 file changed, 74 insertions(+), 43 deletions(-) diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp index 45cd031df3a..b58793b75ff 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp @@ -93,68 +93,99 @@ AnimatedCursor* SDL3CursorManager::loadANI(const char* filepath) File* file = TheFileSystem->openFile(filepath, File::READ | File::BINARY); if (!file) { - DEBUG_LOG(("loadANI: Failed to open ANI cursor [%s]", filepath)); return nullptr; } Int size = file->size(); - if (size <= 0) - { - DEBUG_LOG(("loadANI: File is empty [%s]", filepath)); - file->close(); - return nullptr; - } - - std::unique_ptr file_buffer(new char[size]); - if (file->read(file_buffer.get(), size) != size) - { - DEBUG_LOG(("loadANI: Failed to read ANI cursor [%s]", filepath)); - file->close(); - return nullptr; - } + std::vector buf(size); + file->read(buf.data(), size); file->close(); - DEBUG_LOG(("loadANI: Loading %s", filepath)); - - SDL_IOStream *io = SDL_IOFromConstMem(file_buffer.get(), (size_t)size); - if (!io) return nullptr; + std::vector frames; + int hot_spot_x = 0, hot_spot_y = 0; + Uint32 rate = 1; - // Use SDL3_image to load the animation (handles RIFF/ANI container and frame decoding) - IMG_Animation *anim = IMG_LoadAnimation_IO(io, true); - if (!anim) + // Detect RIFF/ACON container + if (buf.size() >= 12 && memcmp(buf.data(), "RIFF", 4) == 0 && memcmp(buf.data() + 8, "ACON", 4) == 0) { - DEBUG_LOG(("loadANI: IMG_LoadAnimation_IO failed for [%s]: %s", filepath, SDL_GetError())); - return nullptr; + char* p = buf.data() + 12; + char* end = buf.data() + buf.size(); + while (p + 8 <= end) + { + Uint32 id, sz; + memcpy(&id, p, 4); + memcpy(&sz, p + 4, 4); + p += 8; + + if (id == *(Uint32*)"anih" && sz >= 36) + { + memcpy(&rate, p + 28, 4); + } + else if (id == *(Uint32*)"LIST" && sz >= 4 && memcmp(p, "fram", 4) == 0) + { + char* lp = p + 4; + char* le = p + sz; + while (lp + 8 <= le) + { + Uint32 fid, fsz; + memcpy(&fid, lp, 4); + memcpy(&fsz, lp + 4, 4); + lp += 8; + + if (fid == *(Uint32*)"icon") + { + SDL_IOStream* io = SDL_IOFromConstMem(lp, fsz); + SDL_Surface* s = IMG_LoadTyped_IO(io, true, "ico"); + if (s) + { + if (frames.empty()) + { + SDL_PropertiesID pr = SDL_GetSurfaceProperties(s); + hot_spot_x = (int)SDL_GetNumberProperty(pr, SDL_PROP_SURFACE_HOTSPOT_X_NUMBER, 0); + hot_spot_y = (int)SDL_GetNumberProperty(pr, SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER, 0); + } + frames.push_back({ s, (Uint32)(rate * 1000 / 60) }); + } + } + lp += (fsz + (fsz & 1)); + } + } + p += (sz + (sz & 1)); + } + } + else + { + // Fallback for direct ICO/CUR files + SDL_IOStream* io = SDL_IOFromConstMem(buf.data(), buf.size()); + SDL_Surface* s = IMG_LoadTyped_IO(io, true, "ico"); + if (s) + { + SDL_PropertiesID pr = SDL_GetSurfaceProperties(s); + hot_spot_x = (int)SDL_GetNumberProperty(pr, SDL_PROP_SURFACE_HOTSPOT_X_NUMBER, 0); + hot_spot_y = (int)SDL_GetNumberProperty(pr, SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER, 0); + frames.push_back({ s, 16 }); + } } - if (anim->count == 0) + if (frames.empty()) { - IMG_FreeAnimation(anim); return nullptr; } - // Get hotspots from the first frame's properties (SDL3_image sets these for ICO/CUR) - SDL_PropertiesID props = SDL_GetSurfaceProperties(anim->frames[0]); - int hot_spot_x = (int)SDL_GetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_X_NUMBER, 0); - int hot_spot_y = (int)SDL_GetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER, 0); - - // Create the animated cursor resource std::unique_ptr cursor(new AnimatedCursor()); - if (anim->count == 1) + if (frames.size() > 1) { - cursor->m_cursor = SDL_CreateColorCursor(anim->frames[0], hot_spot_x, hot_spot_y); + cursor->m_cursor = SDL_CreateAnimatedCursor(frames.data(), (int)frames.size(), hot_spot_x, hot_spot_y); } else { - std::vector frames(anim->count); - for (int i = 0; i < anim->count; i++) - { - frames[i].surface = anim->frames[i]; - frames[i].duration = (Uint32)anim->delays[i]; - } - cursor->m_cursor = SDL_CreateAnimatedCursor(frames.data(), anim->count, hot_spot_x, hot_spot_y); + cursor->m_cursor = SDL_CreateColorCursor(frames[0].surface, hot_spot_x, hot_spot_y); + } + + for (auto& f : frames) + { + SDL_DestroySurface(f.surface); } - IMG_FreeAnimation(anim); - return cursor.release(); + return cursor.release(); } From 038aea09581dd8f6d4b78edc3066e7141eda5b0e Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Wed, 22 Apr 2026 20:15:54 +0200 Subject: [PATCH 18/27] dont early return for headless mode --- .../Source/SDL3GameEngine.cpp | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Core/GameEngineDevice/Source/SDL3GameEngine.cpp b/Core/GameEngineDevice/Source/SDL3GameEngine.cpp index c719c43b19d..b8b7b2c2dc7 100644 --- a/Core/GameEngineDevice/Source/SDL3GameEngine.cpp +++ b/Core/GameEngineDevice/Source/SDL3GameEngine.cpp @@ -149,19 +149,18 @@ void SDL3GameEngine::init(void) // Verify window was created by SDL3Main integration extern SDL_Window* TheSDL3Window; extern HWND ApplicationHWnd; - - if (!TheSDL3Window || !ApplicationHWnd) { - return; - } - - // Store window reference locally - m_SDLWindow = TheSDL3Window; - m_IsInitialized = true; - m_IsActive = true; - // Initialize the unified input manager - if (!TheSDL3InputManager) { - TheSDL3InputManager = new SDL3InputManager(m_SDLWindow); + if (TheSDL3Window && ApplicationHWnd) + { + // Store window reference locally + m_SDLWindow = TheSDL3Window; + m_IsInitialized = true; + m_IsActive = true; + + // Initialize the unified input manager + if (!TheSDL3InputManager) { + TheSDL3InputManager = new SDL3InputManager(m_SDLWindow); + } } // Call parent init to initialize game subsystems From 390e2ee581d12c2f27528a1871d77c4d7d91683d Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Thu, 14 May 2026 18:10:22 +0200 Subject: [PATCH 19/27] applied clang to files new in this PR --- .../SDL3Device/GameClient/SDL3Cursor.h | 43 +- .../Include/SDL3Device/GameClient/SDL3Input.h | 34 +- .../GameEngineDevice/Include/SDL3GameEngine.h | 30 +- .../SDL3Device/GameClient/SDL3Cursor.cpp | 251 +++---- .../SDL3Device/GameClient/SDL3Input.cpp | 617 +++++++++++------- .../Source/SDL3GameEngine.cpp | 138 ++-- 6 files changed, 661 insertions(+), 452 deletions(-) diff --git a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h index fe0e4b738ea..f9850b99538 100644 --- a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h +++ b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h @@ -32,20 +32,23 @@ /** * AnimatedCursor - Wrapper for SDL3 native animated cursors */ -struct AnimatedCursor { - SDL_Cursor* m_cursor; +struct AnimatedCursor +{ + SDL_Cursor* m_cursor; - AnimatedCursor() : m_cursor(nullptr) {} - ~AnimatedCursor() - { - if (m_cursor) - { - SDL_DestroyCursor(m_cursor); - m_cursor = nullptr; - } - } + AnimatedCursor() + : m_cursor(nullptr) + {} + ~AnimatedCursor() + { + if (m_cursor) + { + SDL_DestroyCursor(m_cursor); + m_cursor = nullptr; + } + } - SDL_Cursor* getCursor() const { return m_cursor; } + SDL_Cursor* getCursor() const { return m_cursor; } }; /** @@ -54,15 +57,15 @@ struct AnimatedCursor { class SDL3CursorManager { public: - static void init(); - static void shutdown(); + static void init(); + static void shutdown(); + + static SDL_Cursor* getCursor(Mouse::MouseCursor cursor, int direction); - static SDL_Cursor* getCursor(Mouse::MouseCursor cursor, int direction); - - // Internal loader used by Mouse implementation - static void initResources(Mouse* mouse); + // Internal loader used by Mouse implementation + static void initResources(Mouse* mouse); private: - static AnimatedCursor* loadANI(const char* filepath); - static AnimatedCursor* m_cursorResources[Mouse::NUM_MOUSE_CURSORS][MAX_2D_CURSOR_DIRECTIONS]; + static AnimatedCursor* loadANI(const char* filepath); + static AnimatedCursor* m_cursorResources[Mouse::NUM_MOUSE_CURSORS][MAX_2D_CURSOR_DIRECTIONS]; }; diff --git a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h index 14ce24203f1..9dfb55d55a0 100644 --- a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h +++ b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h @@ -66,30 +66,30 @@ class SDL3Mouse : public Mouse virtual void regainFocus() override; // SDL3-specific methods - void addSDLEvent(SDL_Event *event); - + void addSDLEvent(SDL_Event* event); + protected: virtual void capture(void) override; virtual void releaseCapture(void) override; - virtual UnsignedByte getMouseEvent(MouseIO *result, Bool flush) override; + virtual UnsignedByte getMouseEvent(MouseIO* result, Bool flush) override; private: // Event translation from SDL_Event (Clean Slate implementation) - void translateEvent(const SDL_Event& event, MouseIO *result); + void translateEvent(const SDL_Event& event, MouseIO* result); // Scale raw SDL window coordinates to game internal resolution void scaleMouseCoordinates(int rawX, int rawY, Uint32 windowID, int& scaledX, int& scaledY); - + SDL_Window* m_Window; Bool m_IsCaptured; Bool m_IsVisible; Bool m_LostFocus; - + Int m_directionFrame; float m_accumulatedDeltaX; float m_accumulatedDeltaY; - + SDL_Cursor* m_activeSDLCursor; }; @@ -109,12 +109,12 @@ class SDL3Keyboard : public Keyboard // Keyboard interface virtual Bool getCapsState(void) override; - + // SDL3-specific methods - void addSDLEvent(SDL_Event *event); + void addSDLEvent(SDL_Event* event); protected: - virtual void getKey(KeyboardIO *key) override; + virtual void getKey(KeyboardIO* key) override; virtual KeyVal translateScanCodeToKeyVal(unsigned char scan); private: @@ -131,11 +131,11 @@ class SDL3InputManager virtual ~SDL3InputManager(); void update(); - + // Buffer access Bool getNextMouseEvent(SDL_Event& outEvent); Bool getNextKeyboardEvent(SDL_Event& outEvent); - + void addMouseSDLEvent(const SDL_Event& event); void addKeyboardSDLEvent(const SDL_Event& event); @@ -148,12 +148,14 @@ class SDL3InputManager static constexpr float DEFAULT_CURSOR_SPEED = 800.0f; private: - struct GamepadState { + struct GamepadState + { bool buttonState[SDL_GAMEPAD_BUTTON_COUNT]; bool stickLeft, stickRight, stickUp, stickDown; bool ltDown, rtDown; - GamepadState() { + GamepadState() + { memset(buttonState, 0, sizeof(buttonState)); stickLeft = stickRight = stickUp = stickDown = false; ltDown = rtDown = false; @@ -169,7 +171,7 @@ class SDL3InputManager SDL_Gamepad* m_gamepad; void processGamepadInput(); void handleGamepadButton(SDL_GamepadButton button, bool& currentState, bool isDown, std::function action); - + // Virtual event injection void virtualPulseKey(SDL_Scancode scancode, bool down); void virtualPulseMouse(Uint8 button, bool down); @@ -177,7 +179,7 @@ class SDL3InputManager // Event buffers static const UnsignedInt MAX_MOUSE_EVENTS = 256; static const UnsignedInt MAX_KEY_EVENTS = 256; - + SDL_Event m_mouseEvents[MAX_MOUSE_EVENTS]; UnsignedInt m_mouseNextFree; UnsignedInt m_mouseNextGet; diff --git a/Core/GameEngineDevice/Include/SDL3GameEngine.h b/Core/GameEngineDevice/Include/SDL3GameEngine.h index 8b854886993..a21f076b85a 100644 --- a/Core/GameEngineDevice/Include/SDL3GameEngine.h +++ b/Core/GameEngineDevice/Include/SDL3GameEngine.h @@ -66,28 +66,28 @@ class SDL3GameEngine : public GameEngine virtual void setIsActive(Bool isActive) override; // Factory methods (override GameEngine) - virtual LocalFileSystem *createLocalFileSystem(void) override; - virtual ArchiveFileSystem *createArchiveFileSystem(void) override; - virtual GameLogic *createGameLogic(void) override; - virtual GameClient *createGameClient(void) override; - virtual ModuleFactory *createModuleFactory(void) override; - virtual ThingFactory *createThingFactory(void) override; - virtual FunctionLexicon *createFunctionLexicon(void) override; - virtual Radar *createRadar(Bool dummy) override; - virtual WebBrowser *createWebBrowser(void) override; + virtual LocalFileSystem* createLocalFileSystem(void) override; + virtual ArchiveFileSystem* createArchiveFileSystem(void) override; + virtual GameLogic* createGameLogic(void) override; + virtual GameClient* createGameClient(void) override; + virtual ModuleFactory* createModuleFactory(void) override; + virtual ThingFactory* createThingFactory(void) override; + virtual FunctionLexicon* createFunctionLexicon(void) override; + virtual Radar* createRadar(Bool dummy) override; + virtual WebBrowser* createWebBrowser(void) override; virtual ParticleSystemManager* createParticleSystemManager(Bool dummy) override; - virtual AudioManager *createAudioManager(Bool dummy) override; + virtual AudioManager* createAudioManager(Bool dummy) override; // SDL3 specific virtual SDL_Window* getSDLWindow(void) const { return m_SDLWindow; } virtual void forwardTextInputEvent(const char* utf8Text); protected: - SDL_Window* m_SDLWindow; - Bool m_IsInitialized; - Bool m_IsActive; - Bool m_IsTextInputActive; - GameWindow* m_TextInputFocusWindow; + SDL_Window* m_SDLWindow; + Bool m_IsInitialized; + Bool m_IsActive; + Bool m_IsTextInputActive; + GameWindow* m_TextInputFocusWindow; // Event processing void pollSDL3Events(void); diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp index b58793b75ff..90bed14a174 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp @@ -33,59 +33,62 @@ #include "Common/FileSystem.h" // Initialize static member -AnimatedCursor* SDL3CursorManager::m_cursorResources[Mouse::NUM_MOUSE_CURSORS][MAX_2D_CURSOR_DIRECTIONS] = { nullptr }; +AnimatedCursor* SDL3CursorManager::m_cursorResources[Mouse::NUM_MOUSE_CURSORS][MAX_2D_CURSOR_DIRECTIONS] = {nullptr}; void SDL3CursorManager::init() { - // Cursors are typically initialized via initResources when the Mouse device is ready - shutdown(); + // Cursors are typically initialized via initResources when the Mouse device is ready + shutdown(); } void SDL3CursorManager::shutdown() { - for (int i = 0; i < Mouse::NUM_MOUSE_CURSORS; ++i) - { - for (int j = 0; j < MAX_2D_CURSOR_DIRECTIONS; ++j) - { - if (m_cursorResources[i][j]) - { - delete m_cursorResources[i][j]; - m_cursorResources[i][j] = nullptr; - } - } - } + for (int i = 0; i < Mouse::NUM_MOUSE_CURSORS; ++i) + { + for (int j = 0; j < MAX_2D_CURSOR_DIRECTIONS; ++j) + { + if (m_cursorResources[i][j]) + { + delete m_cursorResources[i][j]; + m_cursorResources[i][j] = nullptr; + } + } + } } SDL_Cursor* SDL3CursorManager::getCursor(Mouse::MouseCursor cursor, int direction) { - if (cursor < 0 || cursor >= Mouse::NUM_MOUSE_CURSORS) return nullptr; - if (direction < 0 || direction >= MAX_2D_CURSOR_DIRECTIONS) direction = 0; + if (cursor < 0 || cursor >= Mouse::NUM_MOUSE_CURSORS) + return nullptr; + if (direction < 0 || direction >= MAX_2D_CURSOR_DIRECTIONS) + direction = 0; - AnimatedCursor* anim = m_cursorResources[cursor][direction]; - return anim ? anim->getCursor() : nullptr; + AnimatedCursor* anim = m_cursorResources[cursor][direction]; + return anim ? anim->getCursor() : nullptr; } void SDL3CursorManager::initResources(Mouse* mouse) { - if (!mouse) return; - - for (Int cursor = Mouse::FIRST_CURSOR; cursor < Mouse::NUM_MOUSE_CURSORS; cursor++) - { - for (Int direction = 0; direction < mouse->m_cursorInfo[cursor].numDirections; direction++) - { - if (!m_cursorResources[cursor][direction] && !mouse->m_cursorInfo[cursor].textureName.isEmpty()) - { - char resourcePath[256]; - if (mouse->m_cursorInfo[cursor].numDirections > 1) - snprintf(resourcePath, sizeof(resourcePath), "Data/Cursors/%s%d.ani", mouse->m_cursorInfo[cursor].textureName.str(), direction); - else - snprintf(resourcePath, sizeof(resourcePath), "Data/Cursors/%s.ani", mouse->m_cursorInfo[cursor].textureName.str()); - - m_cursorResources[cursor][direction] = loadANI(resourcePath); - DEBUG_ASSERTCRASH(m_cursorResources[cursor][direction], ("MissingCursor %s\n", resourcePath)); - } - } - } + if (!mouse) + return; + + for (Int cursor = Mouse::FIRST_CURSOR; cursor < Mouse::NUM_MOUSE_CURSORS; cursor++) + { + for (Int direction = 0; direction < mouse->m_cursorInfo[cursor].numDirections; direction++) + { + if (!m_cursorResources[cursor][direction] && !mouse->m_cursorInfo[cursor].textureName.isEmpty()) + { + char resourcePath[256]; + if (mouse->m_cursorInfo[cursor].numDirections > 1) + snprintf(resourcePath, sizeof(resourcePath), "Data/Cursors/%s%d.ani", mouse->m_cursorInfo[cursor].textureName.str(), direction); + else + snprintf(resourcePath, sizeof(resourcePath), "Data/Cursors/%s.ani", mouse->m_cursorInfo[cursor].textureName.str()); + + m_cursorResources[cursor][direction] = loadANI(resourcePath); + DEBUG_ASSERTCRASH(m_cursorResources[cursor][direction], ("MissingCursor %s\n", resourcePath)); + } + } + } } AnimatedCursor* SDL3CursorManager::loadANI(const char* filepath) @@ -101,91 +104,91 @@ AnimatedCursor* SDL3CursorManager::loadANI(const char* filepath) file->read(buf.data(), size); file->close(); - std::vector frames; - int hot_spot_x = 0, hot_spot_y = 0; - Uint32 rate = 1; - - // Detect RIFF/ACON container - if (buf.size() >= 12 && memcmp(buf.data(), "RIFF", 4) == 0 && memcmp(buf.data() + 8, "ACON", 4) == 0) - { - char* p = buf.data() + 12; - char* end = buf.data() + buf.size(); - while (p + 8 <= end) - { - Uint32 id, sz; - memcpy(&id, p, 4); - memcpy(&sz, p + 4, 4); - p += 8; - - if (id == *(Uint32*)"anih" && sz >= 36) - { - memcpy(&rate, p + 28, 4); - } - else if (id == *(Uint32*)"LIST" && sz >= 4 && memcmp(p, "fram", 4) == 0) - { - char* lp = p + 4; - char* le = p + sz; - while (lp + 8 <= le) - { - Uint32 fid, fsz; - memcpy(&fid, lp, 4); - memcpy(&fsz, lp + 4, 4); - lp += 8; - - if (fid == *(Uint32*)"icon") - { - SDL_IOStream* io = SDL_IOFromConstMem(lp, fsz); - SDL_Surface* s = IMG_LoadTyped_IO(io, true, "ico"); - if (s) - { - if (frames.empty()) - { - SDL_PropertiesID pr = SDL_GetSurfaceProperties(s); - hot_spot_x = (int)SDL_GetNumberProperty(pr, SDL_PROP_SURFACE_HOTSPOT_X_NUMBER, 0); - hot_spot_y = (int)SDL_GetNumberProperty(pr, SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER, 0); - } - frames.push_back({ s, (Uint32)(rate * 1000 / 60) }); - } - } - lp += (fsz + (fsz & 1)); - } - } - p += (sz + (sz & 1)); - } - } - else - { - // Fallback for direct ICO/CUR files - SDL_IOStream* io = SDL_IOFromConstMem(buf.data(), buf.size()); - SDL_Surface* s = IMG_LoadTyped_IO(io, true, "ico"); - if (s) - { - SDL_PropertiesID pr = SDL_GetSurfaceProperties(s); - hot_spot_x = (int)SDL_GetNumberProperty(pr, SDL_PROP_SURFACE_HOTSPOT_X_NUMBER, 0); - hot_spot_y = (int)SDL_GetNumberProperty(pr, SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER, 0); - frames.push_back({ s, 16 }); - } - } - - if (frames.empty()) - { - return nullptr; - } - - std::unique_ptr cursor(new AnimatedCursor()); - if (frames.size() > 1) - { - cursor->m_cursor = SDL_CreateAnimatedCursor(frames.data(), (int)frames.size(), hot_spot_x, hot_spot_y); - } - else - { - cursor->m_cursor = SDL_CreateColorCursor(frames[0].surface, hot_spot_x, hot_spot_y); - } - - for (auto& f : frames) - { - SDL_DestroySurface(f.surface); - } - - return cursor.release(); + std::vector frames; + int hot_spot_x = 0, hot_spot_y = 0; + Uint32 rate = 1; + + // Detect RIFF/ACON container + if (buf.size() >= 12 && memcmp(buf.data(), "RIFF", 4) == 0 && memcmp(buf.data() + 8, "ACON", 4) == 0) + { + char* p = buf.data() + 12; + char* end = buf.data() + buf.size(); + while (p + 8 <= end) + { + Uint32 id, sz; + memcpy(&id, p, 4); + memcpy(&sz, p + 4, 4); + p += 8; + + if (id == *(Uint32*)"anih" && sz >= 36) + { + memcpy(&rate, p + 28, 4); + } + else if (id == *(Uint32*)"LIST" && sz >= 4 && memcmp(p, "fram", 4) == 0) + { + char* lp = p + 4; + char* le = p + sz; + while (lp + 8 <= le) + { + Uint32 fid, fsz; + memcpy(&fid, lp, 4); + memcpy(&fsz, lp + 4, 4); + lp += 8; + + if (fid == *(Uint32*)"icon") + { + SDL_IOStream* io = SDL_IOFromConstMem(lp, fsz); + SDL_Surface* s = IMG_LoadTyped_IO(io, true, "ico"); + if (s) + { + if (frames.empty()) + { + SDL_PropertiesID pr = SDL_GetSurfaceProperties(s); + hot_spot_x = (int)SDL_GetNumberProperty(pr, SDL_PROP_SURFACE_HOTSPOT_X_NUMBER, 0); + hot_spot_y = (int)SDL_GetNumberProperty(pr, SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER, 0); + } + frames.push_back({s, (Uint32)(rate * 1000 / 60)}); + } + } + lp += (fsz + (fsz & 1)); + } + } + p += (sz + (sz & 1)); + } + } + else + { + // Fallback for direct ICO/CUR files + SDL_IOStream* io = SDL_IOFromConstMem(buf.data(), buf.size()); + SDL_Surface* s = IMG_LoadTyped_IO(io, true, "ico"); + if (s) + { + SDL_PropertiesID pr = SDL_GetSurfaceProperties(s); + hot_spot_x = (int)SDL_GetNumberProperty(pr, SDL_PROP_SURFACE_HOTSPOT_X_NUMBER, 0); + hot_spot_y = (int)SDL_GetNumberProperty(pr, SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER, 0); + frames.push_back({s, 16}); + } + } + + if (frames.empty()) + { + return nullptr; + } + + std::unique_ptr cursor(new AnimatedCursor()); + if (frames.size() > 1) + { + cursor->m_cursor = SDL_CreateAnimatedCursor(frames.data(), (int)frames.size(), hot_spot_x, hot_spot_y); + } + else + { + cursor->m_cursor = SDL_CreateColorCursor(frames[0].surface, hot_spot_x, hot_spot_y); + } + + for (auto& f : frames) + { + SDL_DestroySurface(f.surface); + } + + return cursor.release(); } diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp index cffbcbb8fbe..fe27ef60326 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp @@ -55,15 +55,15 @@ SDL3InputManager* TheSDL3InputManager = nullptr; * Constructor - Initialize SDL3Mouse with window handle */ SDL3Mouse::SDL3Mouse(SDL_Window* window) - : Mouse(), - m_Window(window), - m_IsCaptured(false), - m_IsVisible(true), - m_LostFocus(false), - m_directionFrame(0), - m_accumulatedDeltaX(0.0f), - m_accumulatedDeltaY(0.0f), - m_activeSDLCursor(nullptr) + : Mouse() + , m_Window(window) + , m_IsCaptured(false) + , m_IsVisible(true) + , m_LostFocus(false) + , m_directionFrame(0) + , m_accumulatedDeltaX(0.0f) + , m_accumulatedDeltaY(0.0f) + , m_activeSDLCursor(nullptr) { } @@ -112,7 +112,7 @@ void SDL3Mouse::update(void) } MouseCursor cursor = m_currentCursor; - + if (cursor != NONE && cursor != INVALID_MOUSE_CURSOR && m_cursorInfo[cursor].numDirections > 1) { float dx = 0.0f; @@ -137,7 +137,7 @@ void SDL3Mouse::update(void) dx = m_accumulatedDeltaX; dy = m_accumulatedDeltaY; hasMovement = true; - + m_accumulatedDeltaX = 0.0f; m_accumulatedDeltaY = 0.0f; } @@ -146,7 +146,8 @@ void SDL3Mouse::update(void) if (hasMovement) { float angle = atan2f(dy, dx); - if (angle < 0) angle += 2.0f * (float)M_PI; + if (angle < 0) + angle += 2.0f * (float)M_PI; float segmentAngle = 2.0f * (float)M_PI / (float)m_cursorInfo[cursor].numDirections; m_directionFrame = (int)((angle + (segmentAngle / 2.0f)) / segmentAngle) % m_cursorInfo[cursor].numDirections; } @@ -211,7 +212,7 @@ void SDL3Mouse::setCursor(MouseCursor cursor) return; } - Mouse::setCursor( cursor ); + Mouse::setCursor(cursor); m_currentCursor = cursor; } @@ -222,9 +223,12 @@ void SDL3Mouse::setVisibility(Bool visible) { Mouse::setVisibility(visible); - if (visible) { + if (visible) + { SDL_ShowCursor(); - } else { + } + else + { SDL_HideCursor(); } } @@ -251,7 +255,8 @@ void SDL3Mouse::regainFocus() */ void SDL3Mouse::capture(void) { - if (!m_Window || m_isCursorCaptured) { + if (!m_Window || m_isCursorCaptured) + { return; } @@ -265,12 +270,14 @@ void SDL3Mouse::capture(void) */ void SDL3Mouse::releaseCapture(void) { - if (!m_isCursorCaptured) { + if (!m_isCursorCaptured) + { return; } SDL_CaptureMouse(false); - if (m_Window) { + if (m_Window) + { SDL_SetWindowMouseGrab(m_Window, false); } @@ -280,14 +287,16 @@ void SDL3Mouse::releaseCapture(void) /** * Get next mouse event from the centralized input manager */ -UnsignedByte SDL3Mouse::getMouseEvent(MouseIO *result, Bool flush) +UnsignedByte SDL3Mouse::getMouseEvent(MouseIO* result, Bool flush) { - if (!TheSDL3InputManager) { + if (!TheSDL3InputManager) + { return MOUSE_NONE; } SDL_Event nextEvent; - if (!TheSDL3InputManager->getNextMouseEvent(nextEvent)) { + if (!TheSDL3InputManager->getNextMouseEvent(nextEvent)) + { return MOUSE_NONE; } @@ -296,9 +305,10 @@ UnsignedByte SDL3Mouse::getMouseEvent(MouseIO *result, Bool flush) return MOUSE_OK; } -void SDL3Mouse::addSDLEvent(SDL_Event *event) +void SDL3Mouse::addSDLEvent(SDL_Event* event) { - if (TheSDL3InputManager && event) { + if (TheSDL3InputManager && event) + { TheSDL3InputManager->addMouseSDLEvent(*event); } } @@ -306,9 +316,10 @@ void SDL3Mouse::addSDLEvent(SDL_Event *event) //----------------------------------------------------------------------------- /** Unified event translation (Clean Slate Rewrite) */ //----------------------------------------------------------------------------- -void SDL3Mouse::translateEvent(const SDL_Event& event, MouseIO *result) +void SDL3Mouse::translateEvent(const SDL_Event& event, MouseIO* result) { - if (!result) return; + if (!result) + return; // Reset state result->leftState = result->rightState = result->middleState = MBS_None; @@ -322,14 +333,15 @@ void SDL3Mouse::translateEvent(const SDL_Event& event, MouseIO *result) int rawY = 0; Uint32 windowID = 0; - switch (event.type) { + switch (event.type) + { case SDL_EVENT_MOUSE_MOTION: rawX = (int)event.motion.x; rawY = (int)event.motion.y; windowID = event.motion.windowID; result->deltaPos.x = (Int)event.motion.xrel; result->deltaPos.y = (Int)event.motion.yrel; - + m_accumulatedDeltaX += event.motion.xrel; m_accumulatedDeltaY += event.motion.yrel; break; @@ -340,13 +352,17 @@ void SDL3Mouse::translateEvent(const SDL_Event& event, MouseIO *result) rawX = (int)event.button.x; rawY = (int)event.button.y; windowID = event.button.windowID; - - MouseButtonState state = event.button.down ? MBS_Down : MBS_Up; - if (event.button.down && event.button.clicks >= 2) state = MBS_DoubleClick; - if (event.button.button == SDL_BUTTON_LEFT) result->leftState = state; - else if (event.button.button == SDL_BUTTON_RIGHT) result->rightState = state; - else if (event.button.button == SDL_BUTTON_MIDDLE) result->middleState = state; + MouseButtonState state = event.button.down ? MBS_Down : MBS_Up; + if (event.button.down && event.button.clicks >= 2) + state = MBS_DoubleClick; + + if (event.button.button == SDL_BUTTON_LEFT) + result->leftState = state; + else if (event.button.button == SDL_BUTTON_RIGHT) + result->rightState = state; + else if (event.button.button == SDL_BUTTON_MIDDLE) + result->middleState = state; break; } @@ -358,7 +374,7 @@ void SDL3Mouse::translateEvent(const SDL_Event& event, MouseIO *result) rawX = (int)mx; rawY = (int)my; windowID = event.wheel.windowID; - result->wheelPos = (Int)(event.wheel.y * 120); // MOUSE_WHEEL_DELTA + result->wheelPos = (Int)(event.wheel.y * 120); // MOUSE_WHEEL_DELTA break; } @@ -376,7 +392,8 @@ void SDL3Mouse::translateEvent(const SDL_Event& event, MouseIO *result) void SDL3Mouse::scaleMouseCoordinates(int rawX, int rawY, Uint32 windowID, int& scaledX, int& scaledY) { SDL_Window* window = SDL_GetWindowFromID(windowID); - if (!window || !TheDisplay) { + if (!window || !TheDisplay) + { scaledX = rawX; scaledY = rawY; return; @@ -389,7 +406,8 @@ void SDL3Mouse::scaleMouseCoordinates(int rawX, int rawY, Uint32 windowID, int& int intH = TheDisplay->getHeight(); // Guard: If we are at native resolution, bypass all math - if (winW == intW && winH == intH) { + if (winW == intW && winH == intH) + { scaledX = rawX; scaledY = rawY; return; @@ -397,12 +415,15 @@ void SDL3Mouse::scaleMouseCoordinates(int rawX, int rawY, Uint32 windowID, int& // Handle Viewport/Letterboxing if active int pbX, pbY, pbW, pbH; - if (TheDisplay->getViewportRect(pbX, pbY, pbW, pbH)) { + if (TheDisplay->getViewportRect(pbX, pbY, pbW, pbH)) + { int cx = std::max(0, std::min(pbW, rawX - pbX)); int cy = std::max(0, std::min(pbH, rawY - pbY)); scaledX = (int)(cx * (float)intW / pbW); scaledY = (int)(cy * (float)intH / pbH); - } else { + } + else + { scaledX = (int)(rawX * (float)intW / winW); scaledY = (int)(rawY * (float)intH / winH); } @@ -415,7 +436,9 @@ void SDL3Mouse::scaleMouseCoordinates(int rawX, int rawY, Uint32 windowID, int& /** * Lifecycle */ -SDL3Keyboard::SDL3Keyboard(void) : Keyboard() {} +SDL3Keyboard::SDL3Keyboard(void) + : Keyboard() +{} SDL3Keyboard::~SDL3Keyboard(void) {} /** @@ -433,157 +456,275 @@ Bool SDL3Keyboard::getCapsState(void) { return FALSE; } /** * SDL3-specific internal methods */ -void SDL3Keyboard::getKey(KeyboardIO *key) +void SDL3Keyboard::getKey(KeyboardIO* key) { - if (!TheSDL3InputManager) { + if (!TheSDL3InputManager) + { key->key = KEY_NONE; key->status = KeyboardIO::STATUS_UNUSED; return; } SDL_Event nextEvent; - if (!TheSDL3InputManager->getNextKeyboardEvent(nextEvent)) { + if (!TheSDL3InputManager->getNextKeyboardEvent(nextEvent)) + { key->key = KEY_NONE; key->status = KeyboardIO::STATUS_UNUSED; return; } - + const SDL_KeyboardEvent& keyEvent = nextEvent.key; KeyDefType keyDef = translateScanCodeToKeyVal(keyEvent.scancode); - + key->key = keyDef; key->status = KeyboardIO::STATUS_UNUSED; key->state = keyEvent.down ? KEY_STATE_DOWN : KEY_STATE_UP; key->keyDownTimeMsec = keyEvent.down ? (Uint32)SDL_GetTicks() : 0; SDL_Keymod mod = keyEvent.mod; - if (mod & SDL_KMOD_LSHIFT) key->state |= KEY_STATE_LSHIFT; - if (mod & SDL_KMOD_RSHIFT) key->state |= KEY_STATE_RSHIFT; - if (mod & SDL_KMOD_LCTRL) key->state |= KEY_STATE_LCONTROL; - if (mod & SDL_KMOD_RCTRL) key->state |= KEY_STATE_RCONTROL; - if (mod & SDL_KMOD_LALT) key->state |= KEY_STATE_LALT; - if (mod & SDL_KMOD_RALT) key->state |= KEY_STATE_RALT; - if (mod & SDL_KMOD_CAPS) key->state |= KEY_STATE_CAPSLOCK; - - if (keyDef == KEY_LSHIFT) key->state &= ~KEY_STATE_LSHIFT; - if (keyDef == KEY_RSHIFT) key->state &= ~KEY_STATE_RSHIFT; - if (keyDef == KEY_LCTRL) key->state &= ~KEY_STATE_LCONTROL; - if (keyDef == KEY_RCTRL) key->state &= ~KEY_STATE_RCONTROL; - if (keyDef == KEY_LALT) key->state &= ~KEY_STATE_LALT; - if (keyDef == KEY_RALT) key->state &= ~KEY_STATE_RALT; + if (mod & SDL_KMOD_LSHIFT) + key->state |= KEY_STATE_LSHIFT; + if (mod & SDL_KMOD_RSHIFT) + key->state |= KEY_STATE_RSHIFT; + if (mod & SDL_KMOD_LCTRL) + key->state |= KEY_STATE_LCONTROL; + if (mod & SDL_KMOD_RCTRL) + key->state |= KEY_STATE_RCONTROL; + if (mod & SDL_KMOD_LALT) + key->state |= KEY_STATE_LALT; + if (mod & SDL_KMOD_RALT) + key->state |= KEY_STATE_RALT; + if (mod & SDL_KMOD_CAPS) + key->state |= KEY_STATE_CAPSLOCK; + + if (keyDef == KEY_LSHIFT) + key->state &= ~KEY_STATE_LSHIFT; + if (keyDef == KEY_RSHIFT) + key->state &= ~KEY_STATE_RSHIFT; + if (keyDef == KEY_LCTRL) + key->state &= ~KEY_STATE_LCONTROL; + if (keyDef == KEY_RCTRL) + key->state &= ~KEY_STATE_RCONTROL; + if (keyDef == KEY_LALT) + key->state &= ~KEY_STATE_LALT; + if (keyDef == KEY_RALT) + key->state &= ~KEY_STATE_RALT; } -void SDL3Keyboard::addSDLEvent(SDL_Event *event) +void SDL3Keyboard::addSDLEvent(SDL_Event* event) { - if (TheSDL3InputManager && event) { + if (TheSDL3InputManager && event) + { TheSDL3InputManager->addKeyboardSDLEvent(*event); } } KeyVal SDL3Keyboard::translateScanCodeToKeyVal(unsigned char scan) { - switch ((SDL_Scancode)scan) { - case SDL_SCANCODE_ESCAPE: return KEY_ESC; - case SDL_SCANCODE_RETURN: return KEY_ENTER; - case SDL_SCANCODE_KP_ENTER: return KEY_KPENTER; - case SDL_SCANCODE_SPACE: return KEY_SPACE; - case SDL_SCANCODE_TAB: return KEY_TAB; - case SDL_SCANCODE_BACKSPACE: return KEY_BACKSPACE; - case SDL_SCANCODE_DELETE: return KEY_DEL; - case SDL_SCANCODE_HOME: return KEY_HOME; - case SDL_SCANCODE_END: return KEY_END; - case SDL_SCANCODE_PAGEUP: return KEY_PGUP; - case SDL_SCANCODE_PAGEDOWN: return KEY_PGDN; - case SDL_SCANCODE_INSERT: return KEY_INS; - case SDL_SCANCODE_LSHIFT: return KEY_LSHIFT; - case SDL_SCANCODE_RSHIFT: return KEY_RSHIFT; - case SDL_SCANCODE_LCTRL: return KEY_LCTRL; - case SDL_SCANCODE_RCTRL: return KEY_RCTRL; - case SDL_SCANCODE_LALT: return KEY_LALT; - case SDL_SCANCODE_RALT: return KEY_RALT; - case SDL_SCANCODE_UP: return KEY_UP; - case SDL_SCANCODE_DOWN: return KEY_DOWN; - case SDL_SCANCODE_LEFT: return KEY_LEFT; - case SDL_SCANCODE_RIGHT: return KEY_RIGHT; - case SDL_SCANCODE_F1: return KEY_F1; - case SDL_SCANCODE_F2: return KEY_F2; - case SDL_SCANCODE_F3: return KEY_F3; - case SDL_SCANCODE_F4: return KEY_F4; - case SDL_SCANCODE_F5: return KEY_F5; - case SDL_SCANCODE_F6: return KEY_F6; - case SDL_SCANCODE_F7: return KEY_F7; - case SDL_SCANCODE_F8: return KEY_F8; - case SDL_SCANCODE_F9: return KEY_F9; - case SDL_SCANCODE_F10: return KEY_F10; - case SDL_SCANCODE_F11: return KEY_F11; - case SDL_SCANCODE_F12: return KEY_F12; - case SDL_SCANCODE_1: return KEY_1; - case SDL_SCANCODE_2: return KEY_2; - case SDL_SCANCODE_3: return KEY_3; - case SDL_SCANCODE_4: return KEY_4; - case SDL_SCANCODE_5: return KEY_5; - case SDL_SCANCODE_6: return KEY_6; - case SDL_SCANCODE_7: return KEY_7; - case SDL_SCANCODE_8: return KEY_8; - case SDL_SCANCODE_9: return KEY_9; - case SDL_SCANCODE_0: return KEY_0; - case SDL_SCANCODE_A: return KEY_A; - case SDL_SCANCODE_B: return KEY_B; - case SDL_SCANCODE_C: return KEY_C; - case SDL_SCANCODE_D: return KEY_D; - case SDL_SCANCODE_E: return KEY_E; - case SDL_SCANCODE_F: return KEY_F; - case SDL_SCANCODE_G: return KEY_G; - case SDL_SCANCODE_H: return KEY_H; - case SDL_SCANCODE_I: return KEY_I; - case SDL_SCANCODE_J: return KEY_J; - case SDL_SCANCODE_K: return KEY_K; - case SDL_SCANCODE_L: return KEY_L; - case SDL_SCANCODE_M: return KEY_M; - case SDL_SCANCODE_N: return KEY_N; - case SDL_SCANCODE_O: return KEY_O; - case SDL_SCANCODE_P: return KEY_P; - case SDL_SCANCODE_Q: return KEY_Q; - case SDL_SCANCODE_R: return KEY_R; - case SDL_SCANCODE_S: return KEY_S; - case SDL_SCANCODE_T: return KEY_T; - case SDL_SCANCODE_U: return KEY_U; - case SDL_SCANCODE_V: return KEY_V; - case SDL_SCANCODE_W: return KEY_W; - case SDL_SCANCODE_X: return KEY_X; - case SDL_SCANCODE_Y: return KEY_Y; - case SDL_SCANCODE_Z: return KEY_Z; - case SDL_SCANCODE_MINUS: return KEY_MINUS; - case SDL_SCANCODE_EQUALS: return KEY_EQUAL; - case SDL_SCANCODE_LEFTBRACKET: return KEY_LBRACKET; - case SDL_SCANCODE_RIGHTBRACKET: return KEY_RBRACKET; - case SDL_SCANCODE_SEMICOLON: return KEY_SEMICOLON; - case SDL_SCANCODE_APOSTROPHE: return KEY_APOSTROPHE; - case SDL_SCANCODE_GRAVE: return KEY_TICK; - case SDL_SCANCODE_COMMA: return KEY_COMMA; - case SDL_SCANCODE_PERIOD: return KEY_PERIOD; - case SDL_SCANCODE_SLASH: return KEY_SLASH; - case SDL_SCANCODE_BACKSLASH: return KEY_BACKSLASH; - case SDL_SCANCODE_KP_1: return KEY_KP1; - case SDL_SCANCODE_KP_2: return KEY_KP2; - case SDL_SCANCODE_KP_3: return KEY_KP3; - case SDL_SCANCODE_KP_4: return KEY_KP4; - case SDL_SCANCODE_KP_5: return KEY_KP5; - case SDL_SCANCODE_KP_6: return KEY_KP6; - case SDL_SCANCODE_KP_7: return KEY_KP7; - case SDL_SCANCODE_KP_8: return KEY_KP8; - case SDL_SCANCODE_KP_9: return KEY_KP9; - case SDL_SCANCODE_KP_0: return KEY_KP0; - case SDL_SCANCODE_KP_PLUS: return KEY_KPPLUS; - case SDL_SCANCODE_KP_MINUS: return KEY_KPMINUS; - case SDL_SCANCODE_KP_MULTIPLY: return KEY_KPSTAR; - case SDL_SCANCODE_KP_DIVIDE: return KEY_KPSLASH; - case SDL_SCANCODE_KP_PERIOD: return KEY_KPDEL; - case SDL_SCANCODE_CAPSLOCK: return KEY_CAPS; - case SDL_SCANCODE_NUMLOCKCLEAR: return KEY_NUM; - case SDL_SCANCODE_SCROLLLOCK: return KEY_SCROLL; - case SDL_SCANCODE_PRINTSCREEN: return KEY_SYSREQ; - default: return KEY_NONE; + switch ((SDL_Scancode)scan) + { + case SDL_SCANCODE_ESCAPE: + return KEY_ESC; + case SDL_SCANCODE_RETURN: + return KEY_ENTER; + case SDL_SCANCODE_KP_ENTER: + return KEY_KPENTER; + case SDL_SCANCODE_SPACE: + return KEY_SPACE; + case SDL_SCANCODE_TAB: + return KEY_TAB; + case SDL_SCANCODE_BACKSPACE: + return KEY_BACKSPACE; + case SDL_SCANCODE_DELETE: + return KEY_DEL; + case SDL_SCANCODE_HOME: + return KEY_HOME; + case SDL_SCANCODE_END: + return KEY_END; + case SDL_SCANCODE_PAGEUP: + return KEY_PGUP; + case SDL_SCANCODE_PAGEDOWN: + return KEY_PGDN; + case SDL_SCANCODE_INSERT: + return KEY_INS; + case SDL_SCANCODE_LSHIFT: + return KEY_LSHIFT; + case SDL_SCANCODE_RSHIFT: + return KEY_RSHIFT; + case SDL_SCANCODE_LCTRL: + return KEY_LCTRL; + case SDL_SCANCODE_RCTRL: + return KEY_RCTRL; + case SDL_SCANCODE_LALT: + return KEY_LALT; + case SDL_SCANCODE_RALT: + return KEY_RALT; + case SDL_SCANCODE_UP: + return KEY_UP; + case SDL_SCANCODE_DOWN: + return KEY_DOWN; + case SDL_SCANCODE_LEFT: + return KEY_LEFT; + case SDL_SCANCODE_RIGHT: + return KEY_RIGHT; + case SDL_SCANCODE_F1: + return KEY_F1; + case SDL_SCANCODE_F2: + return KEY_F2; + case SDL_SCANCODE_F3: + return KEY_F3; + case SDL_SCANCODE_F4: + return KEY_F4; + case SDL_SCANCODE_F5: + return KEY_F5; + case SDL_SCANCODE_F6: + return KEY_F6; + case SDL_SCANCODE_F7: + return KEY_F7; + case SDL_SCANCODE_F8: + return KEY_F8; + case SDL_SCANCODE_F9: + return KEY_F9; + case SDL_SCANCODE_F10: + return KEY_F10; + case SDL_SCANCODE_F11: + return KEY_F11; + case SDL_SCANCODE_F12: + return KEY_F12; + case SDL_SCANCODE_1: + return KEY_1; + case SDL_SCANCODE_2: + return KEY_2; + case SDL_SCANCODE_3: + return KEY_3; + case SDL_SCANCODE_4: + return KEY_4; + case SDL_SCANCODE_5: + return KEY_5; + case SDL_SCANCODE_6: + return KEY_6; + case SDL_SCANCODE_7: + return KEY_7; + case SDL_SCANCODE_8: + return KEY_8; + case SDL_SCANCODE_9: + return KEY_9; + case SDL_SCANCODE_0: + return KEY_0; + case SDL_SCANCODE_A: + return KEY_A; + case SDL_SCANCODE_B: + return KEY_B; + case SDL_SCANCODE_C: + return KEY_C; + case SDL_SCANCODE_D: + return KEY_D; + case SDL_SCANCODE_E: + return KEY_E; + case SDL_SCANCODE_F: + return KEY_F; + case SDL_SCANCODE_G: + return KEY_G; + case SDL_SCANCODE_H: + return KEY_H; + case SDL_SCANCODE_I: + return KEY_I; + case SDL_SCANCODE_J: + return KEY_J; + case SDL_SCANCODE_K: + return KEY_K; + case SDL_SCANCODE_L: + return KEY_L; + case SDL_SCANCODE_M: + return KEY_M; + case SDL_SCANCODE_N: + return KEY_N; + case SDL_SCANCODE_O: + return KEY_O; + case SDL_SCANCODE_P: + return KEY_P; + case SDL_SCANCODE_Q: + return KEY_Q; + case SDL_SCANCODE_R: + return KEY_R; + case SDL_SCANCODE_S: + return KEY_S; + case SDL_SCANCODE_T: + return KEY_T; + case SDL_SCANCODE_U: + return KEY_U; + case SDL_SCANCODE_V: + return KEY_V; + case SDL_SCANCODE_W: + return KEY_W; + case SDL_SCANCODE_X: + return KEY_X; + case SDL_SCANCODE_Y: + return KEY_Y; + case SDL_SCANCODE_Z: + return KEY_Z; + case SDL_SCANCODE_MINUS: + return KEY_MINUS; + case SDL_SCANCODE_EQUALS: + return KEY_EQUAL; + case SDL_SCANCODE_LEFTBRACKET: + return KEY_LBRACKET; + case SDL_SCANCODE_RIGHTBRACKET: + return KEY_RBRACKET; + case SDL_SCANCODE_SEMICOLON: + return KEY_SEMICOLON; + case SDL_SCANCODE_APOSTROPHE: + return KEY_APOSTROPHE; + case SDL_SCANCODE_GRAVE: + return KEY_TICK; + case SDL_SCANCODE_COMMA: + return KEY_COMMA; + case SDL_SCANCODE_PERIOD: + return KEY_PERIOD; + case SDL_SCANCODE_SLASH: + return KEY_SLASH; + case SDL_SCANCODE_BACKSLASH: + return KEY_BACKSLASH; + case SDL_SCANCODE_KP_1: + return KEY_KP1; + case SDL_SCANCODE_KP_2: + return KEY_KP2; + case SDL_SCANCODE_KP_3: + return KEY_KP3; + case SDL_SCANCODE_KP_4: + return KEY_KP4; + case SDL_SCANCODE_KP_5: + return KEY_KP5; + case SDL_SCANCODE_KP_6: + return KEY_KP6; + case SDL_SCANCODE_KP_7: + return KEY_KP7; + case SDL_SCANCODE_KP_8: + return KEY_KP8; + case SDL_SCANCODE_KP_9: + return KEY_KP9; + case SDL_SCANCODE_KP_0: + return KEY_KP0; + case SDL_SCANCODE_KP_PLUS: + return KEY_KPPLUS; + case SDL_SCANCODE_KP_MINUS: + return KEY_KPMINUS; + case SDL_SCANCODE_KP_MULTIPLY: + return KEY_KPSTAR; + case SDL_SCANCODE_KP_DIVIDE: + return KEY_KPSLASH; + case SDL_SCANCODE_KP_PERIOD: + return KEY_KPDEL; + case SDL_SCANCODE_CAPSLOCK: + return KEY_CAPS; + case SDL_SCANCODE_NUMLOCKCLEAR: + return KEY_NUM; + case SDL_SCANCODE_SCROLLLOCK: + return KEY_SCROLL; + case SDL_SCANCODE_PRINTSCREEN: + return KEY_SYSREQ; + default: + return KEY_NONE; } } @@ -595,20 +736,20 @@ KeyVal SDL3Keyboard::translateScanCodeToKeyVal(unsigned char scan) * Lifecycle */ SDL3InputManager::SDL3InputManager(SDL_Window* window) - : m_window(window), - m_mouseNextFree(0), - m_mouseNextGet(0), - m_keyNextFree(0), - m_keyNextGet(0), - m_gamepad(nullptr), - m_precisionMode(FALSE), - m_lastUpdateTime(0), - m_isQuitting(FALSE) + : m_window(window) + , m_mouseNextFree(0) + , m_mouseNextGet(0) + , m_keyNextFree(0) + , m_keyNextGet(0) + , m_gamepad(nullptr) + , m_precisionMode(FALSE) + , m_lastUpdateTime(0) + , m_isQuitting(FALSE) { memset(m_mouseEvents, 0, sizeof(m_mouseEvents)); memset(m_keyEvents, 0, sizeof(m_keyEvents)); TheSDL3InputManager = this; - + openFirstGamepad(); m_lastUpdateTime = SDL_GetTicks(); } @@ -626,38 +767,46 @@ SDL3InputManager::~SDL3InputManager() void SDL3InputManager::update() { SDL_Event event; - while (SDL_PollEvent(&event)) { - switch (event.type) { + while (SDL_PollEvent(&event)) + { + switch (event.type) + { case SDL_EVENT_QUIT: case SDL_EVENT_WINDOW_CLOSE_REQUESTED: m_isQuitting = true; break; case SDL_EVENT_GAMEPAD_ADDED: - if (!m_gamepad) openFirstGamepad(); + if (!m_gamepad) + openFirstGamepad(); break; case SDL_EVENT_GAMEPAD_REMOVED: - if (m_gamepad && event.gdevice.which == SDL_GetGamepadID(m_gamepad)) closeGamepad(); + if (m_gamepad && event.gdevice.which == SDL_GetGamepadID(m_gamepad)) + closeGamepad(); break; case SDL_EVENT_WINDOW_FOCUS_GAINED: - if (TheMouse) { + if (TheMouse) + { TheMouse->regainFocus(); TheMouse->refreshCursorCapture(); } break; case SDL_EVENT_WINDOW_FOCUS_LOST: - if (TheMouse) TheMouse->loseFocus(); + if (TheMouse) + TheMouse->loseFocus(); break; case SDL_EVENT_WINDOW_MOUSE_ENTER: - if (TheMouse) TheMouse->onCursorMovedInside(); + if (TheMouse) + TheMouse->onCursorMovedInside(); break; case SDL_EVENT_WINDOW_MOUSE_LEAVE: - if (TheMouse) TheMouse->onCursorMovedOutside(); + if (TheMouse) + TheMouse->onCursorMovedOutside(); break; case SDL_EVENT_MOUSE_MOTION: @@ -669,13 +818,16 @@ void SDL3InputManager::update() case SDL_EVENT_KEY_DOWN: case SDL_EVENT_KEY_UP: - if (!event.key.repeat) addKeyboardSDLEvent(event); + if (!event.key.repeat) + addKeyboardSDLEvent(event); break; case SDL_EVENT_TEXT_INPUT: - if (TheGameEngine) { + if (TheGameEngine) + { SDL3GameEngine* engine = dynamic_cast(TheGameEngine); - if (engine) engine->forwardTextInputEvent(event.text.text); + if (engine) + engine->forwardTextInputEvent(event.text.text); } break; @@ -692,11 +844,12 @@ void SDL3InputManager::update() */ Bool SDL3InputManager::getNextMouseEvent(SDL_Event& outEvent) { - if (m_mouseEvents[m_mouseNextGet].type == SDL_EVENT_FIRST) return FALSE; + if (m_mouseEvents[m_mouseNextGet].type == SDL_EVENT_FIRST) + return FALSE; SDL_Event* event = &m_mouseEvents[m_mouseNextGet]; m_mouseNextGet = (m_mouseNextGet + 1) % MAX_MOUSE_EVENTS; - + outEvent = *event; event->type = SDL_EVENT_FIRST; return TRUE; @@ -704,7 +857,8 @@ Bool SDL3InputManager::getNextMouseEvent(SDL_Event& outEvent) Bool SDL3InputManager::getNextKeyboardEvent(SDL_Event& outEvent) { - if (m_keyEvents[m_keyNextGet].type == SDL_EVENT_FIRST) return FALSE; + if (m_keyEvents[m_keyNextGet].type == SDL_EVENT_FIRST) + return FALSE; SDL_Event* event = &m_keyEvents[m_keyNextGet]; m_keyNextGet = (m_keyNextGet + 1) % MAX_KEY_EVENTS; @@ -717,7 +871,8 @@ Bool SDL3InputManager::getNextKeyboardEvent(SDL_Event& outEvent) void SDL3InputManager::addMouseSDLEvent(const SDL_Event& event) { UnsignedInt nextFree = (m_mouseNextFree + 1) % MAX_MOUSE_EVENTS; - if (nextFree == m_mouseNextGet) return; + if (nextFree == m_mouseNextGet) + return; m_mouseEvents[m_mouseNextFree] = event; m_mouseNextFree = nextFree; } @@ -725,7 +880,8 @@ void SDL3InputManager::addMouseSDLEvent(const SDL_Event& event) void SDL3InputManager::addKeyboardSDLEvent(const SDL_Event& event) { UnsignedInt nextFree = (m_keyNextFree + 1) % MAX_KEY_EVENTS; - if (nextFree == m_keyNextGet) return; + if (nextFree == m_keyNextGet) + return; m_keyEvents[m_keyNextFree] = event; m_keyNextFree = nextFree; } @@ -737,10 +893,13 @@ void SDL3InputManager::openFirstGamepad() { int count = 0; SDL_JoystickID* joysticks = SDL_GetGamepads(&count); - if (joysticks) { - for (int i = 0; i < count; ++i) { + if (joysticks) + { + for (int i = 0; i < count; ++i) + { m_gamepad = SDL_OpenGamepad(joysticks[i]); - if (m_gamepad) { + if (m_gamepad) + { DEBUG_LOG(("SDL3InputManager: Opened gamepad: %s", SDL_GetGamepadName(m_gamepad))); break; } @@ -751,7 +910,8 @@ void SDL3InputManager::openFirstGamepad() void SDL3InputManager::closeGamepad() { - if (m_gamepad) { + if (m_gamepad) + { SDL_CloseGamepad(m_gamepad); m_gamepad = nullptr; } @@ -764,10 +924,13 @@ void SDL3InputManager::virtualPulseKey(SDL_Scancode scancode, bool down) keyEvent.type = down ? SDL_EVENT_KEY_DOWN : SDL_EVENT_KEY_UP; keyEvent.key.scancode = scancode; keyEvent.key.down = down; - - if (scancode == SDL_SCANCODE_LCTRL) keyEvent.key.mod = SDL_KMOD_LCTRL; - else if (scancode == SDL_SCANCODE_LSHIFT) keyEvent.key.mod = SDL_KMOD_LSHIFT; - else if (scancode == SDL_SCANCODE_LALT) keyEvent.key.mod = SDL_KMOD_LALT; + + if (scancode == SDL_SCANCODE_LCTRL) + keyEvent.key.mod = SDL_KMOD_LCTRL; + else if (scancode == SDL_SCANCODE_LSHIFT) + keyEvent.key.mod = SDL_KMOD_LSHIFT; + else if (scancode == SDL_SCANCODE_LALT) + keyEvent.key.mod = SDL_KMOD_LALT; addKeyboardSDLEvent(keyEvent); } @@ -780,7 +943,7 @@ void SDL3InputManager::virtualPulseMouse(Uint8 button, bool down) clickEvent.button.button = button; clickEvent.button.clicks = 1; clickEvent.button.down = down; - + float mx, my; SDL_GetMouseState(&mx, &my); clickEvent.button.x = mx; @@ -790,13 +953,14 @@ void SDL3InputManager::virtualPulseMouse(Uint8 button, bool down) { clickEvent.button.windowID = SDL_GetWindowID(m_window); } - + addMouseSDLEvent(clickEvent); } void SDL3InputManager::handleGamepadButton(SDL_GamepadButton button, bool& currentState, bool isDown, std::function action) { - if (isDown != currentState) { + if (isDown != currentState) + { action(isDown); currentState = isDown; } @@ -804,7 +968,8 @@ void SDL3InputManager::handleGamepadButton(SDL_GamepadButton button, bool& curre void SDL3InputManager::processGamepadInput() { - if (!m_gamepad) return; + if (!m_gamepad) + return; Uint64 now = SDL_GetTicks(); float deltaTime = (now - m_lastUpdateTime) / 1000.0f; @@ -815,13 +980,15 @@ void SDL3InputManager::processGamepadInput() // 1. TRIGGERS (Modifiers & Precision) bool ltPressed = SDL_GetGamepadAxis(m_gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER) > TRIGGER_THRESHOLD; - if (ltPressed != m_state.ltDown) { + if (ltPressed != m_state.ltDown) + { m_state.ltDown = ltPressed; m_precisionMode = m_state.ltDown; } bool rtPressed = SDL_GetGamepadAxis(m_gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) > TRIGGER_THRESHOLD; - if (rtPressed != m_state.rtDown) { + if (rtPressed != m_state.rtDown) + { m_state.rtDown = rtPressed; virtualPulseKey(SDL_SCANCODE_LCTRL, m_state.rtDown); } @@ -830,16 +997,18 @@ void SDL3InputManager::processGamepadInput() float lx = SDL_GetGamepadAxis(m_gamepad, SDL_GAMEPAD_AXIS_LEFTX) / AXIS_MAX; float ly = SDL_GetGamepadAxis(m_gamepad, SDL_GAMEPAD_AXIS_LEFTY) / AXIS_MAX; - if (SDL_fabsf(lx) > DEADZONE || SDL_fabsf(ly) > DEADZONE) { + if (SDL_fabsf(lx) > DEADZONE || SDL_fabsf(ly) > DEADZONE) + { float speed = CURSOR_SPEED; - if (m_precisionMode) speed *= 0.3f; + if (m_precisionMode) + speed *= 0.3f; SDL_Event motionEvent; memset(&motionEvent, 0, sizeof(motionEvent)); motionEvent.type = SDL_EVENT_MOUSE_MOTION; motionEvent.motion.xrel = lx * speed * deltaTime; motionEvent.motion.yrel = ly * speed * deltaTime; - + float mx, my; SDL_GetMouseState(&mx, &my); motionEvent.motion.x = mx + motionEvent.motion.xrel; @@ -849,7 +1018,7 @@ void SDL3InputManager::processGamepadInput() { motionEvent.motion.windowID = SDL_GetWindowID(m_window); } - + addMouseSDLEvent(motionEvent); SDL_WarpMouseInWindow(m_window, motionEvent.motion.x, motionEvent.motion.y); } @@ -857,24 +1026,24 @@ void SDL3InputManager::processGamepadInput() float rx = SDL_GetGamepadAxis(m_gamepad, SDL_GAMEPAD_AXIS_RIGHTX) / AXIS_MAX; float ry = SDL_GetGamepadAxis(m_gamepad, SDL_GAMEPAD_AXIS_RIGHTY) / AXIS_MAX; - handleGamepadButton(SDL_GAMEPAD_BUTTON_INVALID, m_state.stickLeft, rx < -DEADZONE, [&](bool d){ virtualPulseKey(SDL_SCANCODE_LEFT, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_INVALID, m_state.stickRight, rx > DEADZONE, [&](bool d){ virtualPulseKey(SDL_SCANCODE_RIGHT, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_INVALID, m_state.stickUp, ry < -DEADZONE, [&](bool d){ virtualPulseKey(SDL_SCANCODE_UP, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_INVALID, m_state.stickDown, ry > DEADZONE, [&](bool d){ virtualPulseKey(SDL_SCANCODE_DOWN, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_INVALID, m_state.stickLeft, rx < -DEADZONE, [&](bool d) { virtualPulseKey(SDL_SCANCODE_LEFT, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_INVALID, m_state.stickRight, rx > DEADZONE, [&](bool d) { virtualPulseKey(SDL_SCANCODE_RIGHT, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_INVALID, m_state.stickUp, ry < -DEADZONE, [&](bool d) { virtualPulseKey(SDL_SCANCODE_UP, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_INVALID, m_state.stickDown, ry > DEADZONE, [&](bool d) { virtualPulseKey(SDL_SCANCODE_DOWN, d); }); // 3. BUTTONS & D-PAD (Actions & Hotkeys) - handleGamepadButton(SDL_GAMEPAD_BUTTON_SOUTH, m_state.buttonState[SDL_GAMEPAD_BUTTON_SOUTH], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_SOUTH), [&](bool d){ virtualPulseMouse(SDL_BUTTON_LEFT, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_EAST, m_state.buttonState[SDL_GAMEPAD_BUTTON_EAST], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_EAST), [&](bool d){ virtualPulseMouse(SDL_BUTTON_RIGHT, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_WEST, m_state.buttonState[SDL_GAMEPAD_BUTTON_WEST], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_WEST), [&](bool d){ virtualPulseKey(SDL_SCANCODE_A, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_NORTH, m_state.buttonState[SDL_GAMEPAD_BUTTON_NORTH], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_NORTH), [&](bool d){ if (d) TheMessageStream->appendMessage(GameMessage::MSG_META_STOP); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, m_state.buttonState[SDL_GAMEPAD_BUTTON_LEFT_SHOULDER], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), [&](bool d){ virtualPulseKey(SDL_SCANCODE_Q, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, m_state.buttonState[SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), [&](bool d){ virtualPulseKey(SDL_SCANCODE_LSHIFT, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_START, m_state.buttonState[SDL_GAMEPAD_BUTTON_START], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_START), [&](bool d){ virtualPulseKey(SDL_SCANCODE_ESCAPE, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_BACK, m_state.buttonState[SDL_GAMEPAD_BUTTON_BACK], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_BACK), [&](bool d){ virtualPulseKey(SDL_SCANCODE_SPACE, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_LEFT, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_LEFT], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_LEFT), [&](bool d){ virtualPulseKey(SDL_SCANCODE_1, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_UP, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_UP], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP), [&](bool d){ virtualPulseKey(SDL_SCANCODE_2, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_RIGHT, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_RIGHT], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT), [&](bool d){ virtualPulseKey(SDL_SCANCODE_3, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_DOWN, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_DOWN], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN), [&](bool d){ virtualPulseKey(SDL_SCANCODE_4, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_LEFT_STICK, m_state.buttonState[SDL_GAMEPAD_BUTTON_LEFT_STICK], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_LEFT_STICK), [&](bool d){ if (d) TheMessageStream->appendMessage(GameMessage::MSG_META_SELECT_NEXT_IDLE_WORKER); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_RIGHT_STICK, m_state.buttonState[SDL_GAMEPAD_BUTTON_RIGHT_STICK], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_RIGHT_STICK), [&](bool d){ if (d) TheMessageStream->appendMessage(GameMessage::MSG_META_VIEW_COMMAND_CENTER); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_SOUTH, m_state.buttonState[SDL_GAMEPAD_BUTTON_SOUTH], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_SOUTH), [&](bool d) { virtualPulseMouse(SDL_BUTTON_LEFT, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_EAST, m_state.buttonState[SDL_GAMEPAD_BUTTON_EAST], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_EAST), [&](bool d) { virtualPulseMouse(SDL_BUTTON_RIGHT, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_WEST, m_state.buttonState[SDL_GAMEPAD_BUTTON_WEST], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_WEST), [&](bool d) { virtualPulseKey(SDL_SCANCODE_A, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_NORTH, m_state.buttonState[SDL_GAMEPAD_BUTTON_NORTH], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_NORTH), [&](bool d) { if (d) TheMessageStream->appendMessage(GameMessage::MSG_META_STOP); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, m_state.buttonState[SDL_GAMEPAD_BUTTON_LEFT_SHOULDER], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), [&](bool d) { virtualPulseKey(SDL_SCANCODE_Q, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, m_state.buttonState[SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), [&](bool d) { virtualPulseKey(SDL_SCANCODE_LSHIFT, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_START, m_state.buttonState[SDL_GAMEPAD_BUTTON_START], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_START), [&](bool d) { virtualPulseKey(SDL_SCANCODE_ESCAPE, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_BACK, m_state.buttonState[SDL_GAMEPAD_BUTTON_BACK], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_BACK), [&](bool d) { virtualPulseKey(SDL_SCANCODE_SPACE, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_LEFT, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_LEFT], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_LEFT), [&](bool d) { virtualPulseKey(SDL_SCANCODE_1, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_UP, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_UP], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP), [&](bool d) { virtualPulseKey(SDL_SCANCODE_2, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_RIGHT, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_RIGHT], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT), [&](bool d) { virtualPulseKey(SDL_SCANCODE_3, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_DOWN, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_DOWN], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN), [&](bool d) { virtualPulseKey(SDL_SCANCODE_4, d); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_LEFT_STICK, m_state.buttonState[SDL_GAMEPAD_BUTTON_LEFT_STICK], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_LEFT_STICK), [&](bool d) { if (d) TheMessageStream->appendMessage(GameMessage::MSG_META_SELECT_NEXT_IDLE_WORKER); }); + handleGamepadButton(SDL_GAMEPAD_BUTTON_RIGHT_STICK, m_state.buttonState[SDL_GAMEPAD_BUTTON_RIGHT_STICK], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_RIGHT_STICK), [&](bool d) { if (d) TheMessageStream->appendMessage(GameMessage::MSG_META_VIEW_COMMAND_CENTER); }); } diff --git a/Core/GameEngineDevice/Source/SDL3GameEngine.cpp b/Core/GameEngineDevice/Source/SDL3GameEngine.cpp index b8b7b2c2dc7..004621c45ef 100644 --- a/Core/GameEngineDevice/Source/SDL3GameEngine.cpp +++ b/Core/GameEngineDevice/Source/SDL3GameEngine.cpp @@ -51,54 +51,64 @@ #include "StdDevice/Common/StdBIGFileSystem.h" // Extern globals for input devices (set by GameClient) -extern Mouse *TheMouse; -extern Keyboard *TheKeyboard; -extern GameWindowManager *TheWindowManager; +extern Mouse* TheMouse; +extern Keyboard* TheKeyboard; +extern GameWindowManager* TheWindowManager; -namespace { +namespace +{ Bool DecodeNextUtf8Codepoint(const char* text, size_t length, size_t& offset, UnsignedInt& outCodepoint) { outCodepoint = 0; - if (!text || offset >= length) { + if (!text || offset >= length) + { return false; } const unsigned char first = static_cast(text[offset]); - if (first == 0) { + if (first == 0) + { return false; } - if (first < 0x80) { + if (first < 0x80) + { outCodepoint = first; offset += 1; return true; } - if ((first & 0xE0) == 0xC0 && offset + 1 < length) { + if ((first & 0xE0) == 0xC0 && offset + 1 < length) + { const unsigned char second = static_cast(text[offset + 1]); - if ((second & 0xC0) == 0x80) { + if ((second & 0xC0) == 0x80) + { outCodepoint = ((first & 0x1F) << 6) | (second & 0x3F); offset += 2; return true; } } - if ((first & 0xF0) == 0xE0 && offset + 2 < length) { + if ((first & 0xF0) == 0xE0 && offset + 2 < length) + { const unsigned char second = static_cast(text[offset + 1]); const unsigned char third = static_cast(text[offset + 2]); - if ((second & 0xC0) == 0x80 && (third & 0xC0) == 0x80) { + if ((second & 0xC0) == 0x80 && (third & 0xC0) == 0x80) + { outCodepoint = ((first & 0x0F) << 12) | ((second & 0x3F) << 6) | (third & 0x3F); offset += 3; return true; } } - if ((first & 0xF8) == 0xF0 && offset + 3 < length) { + if ((first & 0xF8) == 0xF0 && offset + 3 < length) + { const unsigned char second = static_cast(text[offset + 1]); const unsigned char third = static_cast(text[offset + 2]); const unsigned char fourth = static_cast(text[offset + 3]); - if ((second & 0xC0) == 0x80 && (third & 0xC0) == 0x80 && (fourth & 0xC0) == 0x80) { + if ((second & 0xC0) == 0x80 && (third & 0xC0) == 0x80 && (fourth & 0xC0) == 0x80) + { outCodepoint = ((first & 0x07) << 18) | ((second & 0x3F) << 12) | ((third & 0x3F) << 6) | (fourth & 0x3F); offset += 4; return true; @@ -110,18 +120,18 @@ Bool DecodeNextUtf8Codepoint(const char* text, size_t length, size_t& offset, Un return false; } -} +} // namespace /** * Constructor: Initialize SDL3 game engine state */ SDL3GameEngine::SDL3GameEngine() - : GameEngine(), - m_SDLWindow(nullptr), - m_IsInitialized(false), - m_IsActive(false), - m_IsTextInputActive(false), - m_TextInputFocusWindow(nullptr) + : GameEngine() + , m_SDLWindow(nullptr) + , m_IsInitialized(false) + , m_IsActive(false) + , m_IsTextInputActive(false) + , m_TextInputFocusWindow(nullptr) { } @@ -130,13 +140,15 @@ SDL3GameEngine::SDL3GameEngine() */ SDL3GameEngine::~SDL3GameEngine() { - if (m_SDLWindow && m_IsTextInputActive) { + if (m_SDLWindow && m_IsTextInputActive) + { SDL_StopTextInput(m_SDLWindow); m_IsTextInputActive = false; m_TextInputFocusWindow = nullptr; } - if (TheSDL3InputManager) { + if (TheSDL3InputManager) + { delete TheSDL3InputManager; } } @@ -158,7 +170,8 @@ void SDL3GameEngine::init(void) m_IsActive = true; // Initialize the unified input manager - if (!TheSDL3InputManager) { + if (!TheSDL3InputManager) + { TheSDL3InputManager = new SDL3InputManager(m_SDLWindow); } } @@ -172,7 +185,8 @@ void SDL3GameEngine::init(void) */ void SDL3GameEngine::reset(void) { - if (m_SDLWindow && m_IsTextInputActive) { + if (m_SDLWindow && m_IsTextInputActive) + { SDL_StopTextInput(m_SDLWindow); m_IsTextInputActive = false; m_TextInputFocusWindow = nullptr; @@ -196,20 +210,22 @@ void SDL3GameEngine::update(void) { // Prevent CPU/GPU pinning while alt-tabbed SDL_Delay(5); - + // Stay responsive to events (so we can see when we're un-minimized) pollSDL3Events(); // Keep the LAN subsystem alive to prevent multiplayer disconnects - if (TheLAN != nullptr) { + if (TheLAN != nullptr) + { TheLAN->setIsActive(isActive()); TheLAN->update(); } // If we are in a network game, we must NOT stay in this loop, // as the engine needs to keep pumping logic frames to avoid desyncs. - if (getQuitting() || (TheGameLogic && (TheGameLogic->isInInternetGame() || TheGameLogic->isInLanGame()))) { - break; + if (getQuitting() || (TheGameLogic && (TheGameLogic->isInInternetGame() || TheGameLogic->isInLanGame()))) + { + break; } } } @@ -244,7 +260,8 @@ void SDL3GameEngine::setIsActive(Bool isActive) */ void SDL3GameEngine::pollSDL3Events(void) { - if (!m_SDLWindow || !TheSDL3InputManager) { + if (!m_SDLWindow || !TheSDL3InputManager) + { return; } @@ -254,14 +271,16 @@ void SDL3GameEngine::pollSDL3Events(void) TheSDL3InputManager->update(); // Check if we should quit - if (TheSDL3InputManager->isQuitting()) { + if (TheSDL3InputManager->isQuitting()) + { m_quitting = true; } } void SDL3GameEngine::updateTextInputState(void) { - if (!m_SDLWindow || !TheWindowManager) { + if (!m_SDLWindow || !TheWindowManager) + { return; } @@ -269,15 +288,21 @@ void SDL3GameEngine::updateTextInputState(void) const Bool wantsTextInput = focusedWindow != nullptr && BitIsSet(focusedWindow->winGetStyle(), GWS_ENTRY_FIELD); - if (wantsTextInput) { - if (!m_IsTextInputActive) { - if (SDL_StartTextInput(m_SDLWindow)) { + if (wantsTextInput) + { + if (!m_IsTextInputActive) + { + if (SDL_StartTextInput(m_SDLWindow)) + { m_IsTextInputActive = true; } } m_TextInputFocusWindow = focusedWindow; - } else { - if (m_IsTextInputActive) { + } + else + { + if (m_IsTextInputActive) + { SDL_StopTextInput(m_SDLWindow); m_IsTextInputActive = false; } @@ -287,32 +312,39 @@ void SDL3GameEngine::updateTextInputState(void) void SDL3GameEngine::forwardTextInputEvent(const char* utf8Text) { - if (!utf8Text || !TheWindowManager) { + if (!utf8Text || !TheWindowManager) + { return; } GameWindow* targetWindow = m_TextInputFocusWindow; - if (!targetWindow || !BitIsSet(targetWindow->winGetStyle(), GWS_ENTRY_FIELD)) { + if (!targetWindow || !BitIsSet(targetWindow->winGetStyle(), GWS_ENTRY_FIELD)) + { return; } const size_t textLength = strlen(utf8Text); size_t offset = 0; - while (offset < textLength) { + while (offset < textLength) + { UnsignedInt codepoint = 0; - if (!DecodeNextUtf8Codepoint(utf8Text, textLength, offset, codepoint)) { + if (!DecodeNextUtf8Codepoint(utf8Text, textLength, offset, codepoint)) + { continue; } - if (codepoint == 0 || codepoint > 0x10FFFFU) { + if (codepoint == 0 || codepoint > 0x10FFFFU) + { continue; } - if (codepoint >= 0xD800U && codepoint <= 0xDFFFU) { + if (codepoint >= 0xD800U && codepoint <= 0xDFFFU) + { continue; } - if (codepoint > 0xFFFFU) { + if (codepoint > 0xFFFFU) + { continue; } @@ -325,42 +357,42 @@ void SDL3GameEngine::forwardTextInputEvent(const char* utf8Text) * Factory Methods for GameEngine subsystems */ -LocalFileSystem *SDL3GameEngine::createLocalFileSystem(void) +LocalFileSystem* SDL3GameEngine::createLocalFileSystem(void) { return NEW StdLocalFileSystem; } -ArchiveFileSystem *SDL3GameEngine::createArchiveFileSystem(void) +ArchiveFileSystem* SDL3GameEngine::createArchiveFileSystem(void) { return NEW StdBIGFileSystem; } -GameLogic *SDL3GameEngine::createGameLogic(void) +GameLogic* SDL3GameEngine::createGameLogic(void) { return NEW W3DGameLogic; } -GameClient *SDL3GameEngine::createGameClient(void) +GameClient* SDL3GameEngine::createGameClient(void) { return NEW W3DGameClient; } -ModuleFactory *SDL3GameEngine::createModuleFactory(void) +ModuleFactory* SDL3GameEngine::createModuleFactory(void) { return NEW W3DModuleFactory; } -ThingFactory *SDL3GameEngine::createThingFactory(void) +ThingFactory* SDL3GameEngine::createThingFactory(void) { return NEW W3DThingFactory; } -FunctionLexicon *SDL3GameEngine::createFunctionLexicon(void) +FunctionLexicon* SDL3GameEngine::createFunctionLexicon(void) { return NEW W3DFunctionLexicon; } -Radar *SDL3GameEngine::createRadar(Bool dummy) +Radar* SDL3GameEngine::createRadar(Bool dummy) { (void)dummy; return NEW W3DRadar; @@ -372,12 +404,12 @@ ParticleSystemManager* SDL3GameEngine::createParticleSystemManager(Bool dummy) return NEW W3DParticleSystemManager; } -WebBrowser *SDL3GameEngine::createWebBrowser(void) +WebBrowser* SDL3GameEngine::createWebBrowser(void) { return nullptr; } -AudioManager *SDL3GameEngine::createAudioManager(Bool dummy) +AudioManager* SDL3GameEngine::createAudioManager(Bool dummy) { if (dummy) return NEW MilesAudioManagerDummy; From ed87bcb69990f533597f7e42abd079f38669f6aa Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Mon, 1 Jun 2026 19:55:45 +0200 Subject: [PATCH 20/27] updated SDL to latest version removed most ANI cursor hacks --- .../SDL3Device/GameClient/SDL3Cursor.cpp | 106 +++++++----------- cmake/sdl3.cmake | 8 +- vcpkg-configuration.json | 2 +- vcpkg.json | 4 + 4 files changed, 49 insertions(+), 71 deletions(-) diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp index 90bed14a174..0a9f5bf825c 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp @@ -104,91 +104,65 @@ AnimatedCursor* SDL3CursorManager::loadANI(const char* filepath) file->read(buf.data(), size); file->close(); - std::vector frames; - int hot_spot_x = 0, hot_spot_y = 0; - Uint32 rate = 1; - - // Detect RIFF/ACON container - if (buf.size() >= 12 && memcmp(buf.data(), "RIFF", 4) == 0 && memcmp(buf.data() + 8, "ACON", 4) == 0) + // thesuperhackers @info + // Command & Conquer Generals .ani files write the total file size as the `cbSize` + // field inside the RIFF header (offset 4) rather than `size - 8` bytes. + // The native SDL3_image ANI parser strictly validates that (offset + chunk_size) <= cbSize, + // causing it to fail at EOF with a truncated data error. + if (size >= 12 && memcmp(buf.data(), "RIFF", 4) == 0 && memcmp(buf.data() + 8, "ACON", 4) == 0) { - char* p = buf.data() + 12; - char* end = buf.data() + buf.size(); - while (p + 8 <= end) - { - Uint32 id, sz; - memcpy(&id, p, 4); - memcpy(&sz, p + 4, 4); - p += 8; + Uint32 correct_cbSize = (Uint32)(size - 8); + memcpy(buf.data() + 4, &correct_cbSize, 4); + } - if (id == *(Uint32*)"anih" && sz >= 36) - { - memcpy(&rate, p + 28, 4); - } - else if (id == *(Uint32*)"LIST" && sz >= 4 && memcmp(p, "fram", 4) == 0) - { - char* lp = p + 4; - char* le = p + sz; - while (lp + 8 <= le) - { - Uint32 fid, fsz; - memcpy(&fid, lp, 4); - memcpy(&fsz, lp + 4, 4); - lp += 8; - - if (fid == *(Uint32*)"icon") - { - SDL_IOStream* io = SDL_IOFromConstMem(lp, fsz); - SDL_Surface* s = IMG_LoadTyped_IO(io, true, "ico"); - if (s) - { - if (frames.empty()) - { - SDL_PropertiesID pr = SDL_GetSurfaceProperties(s); - hot_spot_x = (int)SDL_GetNumberProperty(pr, SDL_PROP_SURFACE_HOTSPOT_X_NUMBER, 0); - hot_spot_y = (int)SDL_GetNumberProperty(pr, SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER, 0); - } - frames.push_back({s, (Uint32)(rate * 1000 / 60)}); - } - } - lp += (fsz + (fsz & 1)); - } - } - p += (sz + (sz & 1)); - } + SDL_IOStream* io = SDL_IOFromConstMem(buf.data(), buf.size()); + if (!io) + { + return nullptr; } - else + + IMG_Animation* anim = IMG_LoadAnimation_IO(io, true); + if (!anim) { - // Fallback for direct ICO/CUR files - SDL_IOStream* io = SDL_IOFromConstMem(buf.data(), buf.size()); - SDL_Surface* s = IMG_LoadTyped_IO(io, true, "ico"); - if (s) - { - SDL_PropertiesID pr = SDL_GetSurfaceProperties(s); - hot_spot_x = (int)SDL_GetNumberProperty(pr, SDL_PROP_SURFACE_HOTSPOT_X_NUMBER, 0); - hot_spot_y = (int)SDL_GetNumberProperty(pr, SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER, 0); - frames.push_back({s, 16}); - } + return nullptr; } - if (frames.empty()) + if (anim->count <= 0) { + IMG_FreeAnimation(anim); return nullptr; } + int hot_spot_x = 0, hot_spot_y = 0; + if (anim->frames && anim->frames[0]) + { + SDL_PropertiesID pr = SDL_GetSurfaceProperties(anim->frames[0]); + hot_spot_x = (int)SDL_GetNumberProperty(pr, SDL_PROP_SURFACE_HOTSPOT_X_NUMBER, 0); + hot_spot_y = (int)SDL_GetNumberProperty(pr, SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER, 0); + } + std::unique_ptr cursor(new AnimatedCursor()); - if (frames.size() > 1) + + if (anim->count > 1) { - cursor->m_cursor = SDL_CreateAnimatedCursor(frames.data(), (int)frames.size(), hot_spot_x, hot_spot_y); + std::vector sdl_frames(anim->count); + for (int i = 0; i < anim->count; ++i) + { + sdl_frames[i].surface = anim->frames[i]; + sdl_frames[i].duration = anim->delays[i]; + } + cursor->m_cursor = SDL_CreateAnimatedCursor(sdl_frames.data(), anim->count, hot_spot_x, hot_spot_y); } else { - cursor->m_cursor = SDL_CreateColorCursor(frames[0].surface, hot_spot_x, hot_spot_y); + cursor->m_cursor = SDL_CreateColorCursor(anim->frames[0], hot_spot_x, hot_spot_y); } - for (auto& f : frames) + if (!cursor->m_cursor) { - SDL_DestroySurface(f.surface); + DEBUG_LOG(("loadANI: Failed to create cursor from %s. hot=(%d, %d), count=%d. Error: %s", filepath, hot_spot_x, hot_spot_y, anim->count, SDL_GetError())); } + IMG_FreeAnimation(anim); return cursor.release(); } diff --git a/cmake/sdl3.cmake b/cmake/sdl3.cmake index dfc5d3026f1..a97f7829e87 100644 --- a/cmake/sdl3.cmake +++ b/cmake/sdl3.cmake @@ -8,15 +8,15 @@ if(NOT SDL3_FOUND OR NOT SDL3_image_FOUND) FetchContent_Declare( SDL3 - URL https://github.com/libsdl-org/SDL/releases/download/release-3.4.4/SDL3-3.4.4.tar.gz - URL_HASH SHA256=EE712DBE6A89BB140BBFC2CE72358FB5EE5CC2240ABEABD54855012DB30B3864 + URL https://github.com/libsdl-org/SDL/releases/download/release-3.4.10/SDL3-3.4.10.tar.gz + URL_HASH SHA256=12b34280415ec8418c864408b93d008a20a6530687ee613d60bfbd20411f2785 OVERRIDE_FIND_PACKAGE ) FetchContent_Declare( SDL3_image - URL https://github.com/libsdl-org/SDL_image/releases/download/release-3.4.2/SDL3_image-3.4.2.tar.gz - URL_HASH SHA256=82fdb88cf1a9cbdc1c77797aaa3292e6d22ce12586be718c8ea43530df1536b4 + URL https://github.com/libsdl-org/SDL_image/releases/download/release-3.4.4/SDL3_image-3.4.4.tar.gz + URL_HASH SHA256=29751304a13d25ac513f24305fa25b06a6edd9607718c90129b8350d35fc5573 ) # Official SDL configuration for a unified build tree diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json index 1561529b76b..73657ba677e 100644 --- a/vcpkg-configuration.json +++ b/vcpkg-configuration.json @@ -9,7 +9,7 @@ { "kind": "git", "repository": "https://github.com/microsoft/vcpkg", - "baseline": "256acc64012b23a13041d8705805e1f23b43a024", + "baseline": "f9ffbaa46ad8e284b2b74919f7e0ba259564d424", "packages": [ "sdl3", "sdl3-image" diff --git a/vcpkg.json b/vcpkg.json index c6e9380319a..79744630dc3 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -5,5 +5,9 @@ "ffmpeg", "sdl3", "sdl3-image" + ], + "overrides": [ + { "name": "sdl3", "version": "3.4.10" }, + { "name": "sdl3-image", "version": "3.4.4" } ] } \ No newline at end of file From c0f22b94c15cd2fa6e60a24b0399862721b679d6 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Mon, 1 Jun 2026 20:10:49 +0200 Subject: [PATCH 21/27] renamed SAGE_USE_SDL3 to RTS_SDL3_ENABLE --- CMakeLists.txt | 2 +- Core/GameEngine/Include/GameClient/Display.h | 2 +- Core/GameEngineDevice/CMakeLists.txt | 4 ++-- .../Include/W3DDevice/GameClient/W3DGameClient.h | 6 +++--- GeneralsMD/Code/Main/WinMain.cpp | 16 ++++++++-------- cmake/config-build.cmake | 8 ++++---- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 56d559d7d83..8e2162f2a1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,7 @@ include(cmake/config.cmake) include(cmake/gamespy.cmake) include(cmake/lzhl.cmake) -if(SAGE_USE_SDL3 AND NOT IS_VS6_BUILD) +if(RTS_SDL3_ENABLE AND NOT IS_VS6_BUILD) include(cmake/sdl3.cmake) endif() diff --git a/Core/GameEngine/Include/GameClient/Display.h b/Core/GameEngine/Include/GameClient/Display.h index 038d84b1260..2794ab24928 100644 --- a/Core/GameEngine/Include/GameClient/Display.h +++ b/Core/GameEngine/Include/GameClient/Display.h @@ -81,7 +81,7 @@ class Display : public SubsystemInterface virtual void setWindowed( Bool windowed ) { m_windowed = windowed; } ///< set windowed/fullscreen flag virtual Bool getWindowed() { return m_windowed; } ///< return widowed/fullscreen flag -#if SAGE_USE_SDL3 +#if RTS_SDL3_ENABLE virtual Bool getViewportRect( Int& x, Int& y, Int& width, Int& height ) const { return FALSE; } #endif diff --git a/Core/GameEngineDevice/CMakeLists.txt b/Core/GameEngineDevice/CMakeLists.txt index ef8c4b6b381..b3475000002 100644 --- a/Core/GameEngineDevice/CMakeLists.txt +++ b/Core/GameEngineDevice/CMakeLists.txt @@ -195,7 +195,7 @@ set(GAMEENGINEDEVICE_SRC ) # Add Core-level SDL3 implementation -if(SAGE_USE_SDL3 AND NOT IS_VS6_BUILD) +if(RTS_SDL3_ENABLE AND NOT IS_VS6_BUILD) list(APPEND GAMEENGINEDEVICE_SRC Include/SDL3GameEngine.h Include/SDL3Device/GameClient/SDL3Input.h @@ -242,7 +242,7 @@ target_link_libraries(corei_gameenginedevice_public INTERFACE ) # Export SDL3 dependencies for modern builds -if(SAGE_USE_SDL3 AND NOT IS_VS6_BUILD) +if(RTS_SDL3_ENABLE AND NOT IS_VS6_BUILD) target_link_libraries(corei_gameenginedevice_public INTERFACE SDL3::SDL3 SDL3_image::SDL3_image diff --git a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DGameClient.h b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DGameClient.h index dbdf23b3028..7e032d37023 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DGameClient.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DGameClient.h @@ -52,7 +52,7 @@ #include "VideoDevice/FFmpeg/FFmpegVideoPlayer.h" #endif -#if SAGE_USE_SDL3 +#if RTS_SDL3_ENABLE #include "SDL3Device/GameClient/SDL3Input.h" extern SDL_Window* TheSDL3Window; #else @@ -136,7 +136,7 @@ class W3DGameClient : public GameClient inline Keyboard *W3DGameClient::createKeyboard() { -#if SAGE_USE_SDL3 +#if RTS_SDL3_ENABLE return NEW SDL3Keyboard; #else return NEW DirectInputKeyboard; @@ -145,7 +145,7 @@ inline Keyboard *W3DGameClient::createKeyboard() inline Mouse *W3DGameClient::createMouse() { -#if SAGE_USE_SDL3 +#if RTS_SDL3_ENABLE return NEW SDL3Mouse(TheSDL3Window); #else //return new DirectInputMouse; diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index f7570d8b116..2d786e94526 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -63,7 +63,7 @@ #include "Common/version.h" #include "BuildVersion.h" -#if SAGE_USE_SDL3 +#if RTS_SDL3_ENABLE #include #include "SDL3GameEngine.h" #include "GameClient/Keyboard.h" @@ -96,7 +96,7 @@ static Bool gDoPaint = true; static Bool isWinMainActive = false; static HBITMAP gLoadScreenBitmap = nullptr; -#if SAGE_USE_SDL3 +#if RTS_SDL3_ENABLE static SDL_Surface* gLoadScreenSurface = nullptr; #endif @@ -874,13 +874,13 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, if (fileImage) { fclose(fileImage); gLoadScreenBitmap = (HBITMAP)LoadImage(hInstance, filePath, IMAGE_BITMAP, 0, 0, LR_SHARED|LR_LOADFROMFILE); -#if SAGE_USE_SDL3 +#if RTS_SDL3_ENABLE gLoadScreenSurface = SDL_LoadBMP(filePath); #endif } else { gLoadScreenBitmap = (HBITMAP)LoadImage(hInstance, fileName, IMAGE_BITMAP, 0, 0, LR_SHARED|LR_LOADFROMFILE); -#if SAGE_USE_SDL3 +#if RTS_SDL3_ENABLE gLoadScreenSurface = SDL_LoadBMP(fileName); #endif } @@ -888,7 +888,7 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, // in release, the file only ever lives in the root dir gLoadScreenBitmap = (HBITMAP)LoadImage(hInstance, "Install_Final.bmp", IMAGE_BITMAP, 0, 0, LR_SHARED|LR_LOADFROMFILE); -#if SAGE_USE_SDL3 +#if RTS_SDL3_ENABLE gLoadScreenSurface = SDL_LoadBMP("Install_Final.bmp"); #endif #endif @@ -900,7 +900,7 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, #endif // register windows class and create application window -#if SAGE_USE_SDL3 +#if RTS_SDL3_ENABLE if (!TheGlobalData->m_headless) { if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS | SDL_INIT_GAMEPAD)) @@ -961,7 +961,7 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, // save our application instance for future use ApplicationHInstance = hInstance; -#if SAGE_USE_SDL3 +#if RTS_SDL3_ENABLE if (gLoadScreenSurface != nullptr) { SDL_DestroySurface(gLoadScreenSurface); gLoadScreenSurface = nullptr; @@ -1045,7 +1045,7 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, //============================================================================= GameEngine *CreateGameEngine() { -#if SAGE_USE_SDL3 +#if RTS_SDL3_ENABLE SDL3GameEngine *engine = NEW SDL3GameEngine; engine->setIsActive(isWinMainActive); return engine; diff --git a/cmake/config-build.cmake b/cmake/config-build.cmake index b07d9204b13..922a9fc6d85 100644 --- a/cmake/config-build.cmake +++ b/cmake/config-build.cmake @@ -12,12 +12,12 @@ option(RTS_BUILD_OPTION_FFMPEG "Enable FFmpeg support" OFF) # Enable SDL3 by default for modern builds if(NOT IS_VS6_BUILD) - option(SAGE_USE_SDL3 "Enable SDL3 input/window backend" ON) - if(SAGE_USE_SDL3) - target_compile_definitions(core_config INTERFACE SAGE_USE_SDL3=1) + option(RTS_SDL3_ENABLE "Enable SDL3 input/window backend" ON) + if(RTS_SDL3_ENABLE) + target_compile_definitions(core_config INTERFACE RTS_SDL3_ENABLE=1) endif() else() - set(SAGE_USE_SDL3 OFF CACHE BOOL "Enable SDL3 input/window backend" FORCE) + set(RTS_SDL3_ENABLE OFF CACHE BOOL "Enable SDL3 input/window backend" FORCE) endif() if(NOT RTS_BUILD_ZEROHOUR AND NOT RTS_BUILD_GENERALS) From e578a8bc188e0946ffa730b1b5eefb3333da9c33 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Mon, 1 Jun 2026 20:32:11 +0200 Subject: [PATCH 22/27] cleaned up comments --- .../SDL3Device/GameClient/SDL3Cursor.h | 10 +-- .../Include/SDL3Device/GameClient/SDL3Input.h | 15 +--- .../GameEngineDevice/Include/SDL3GameEngine.h | 10 +-- .../SDL3Device/GameClient/SDL3Cursor.cpp | 6 +- .../SDL3Device/GameClient/SDL3Input.cpp | 72 +------------------ .../Source/SDL3GameEngine.cpp | 35 +-------- 6 files changed, 7 insertions(+), 141 deletions(-) diff --git a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h index f9850b99538..19005f40873 100644 --- a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h +++ b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h @@ -16,9 +16,7 @@ ** along with this program. If not, see . */ -/* -** Derived from the GeneralsX branch by fbraz3 -*/ +// Derived from the GeneralsX branch by fbraz3 #pragma once @@ -29,9 +27,6 @@ // USER INCLUDES #include "GameClient/Mouse.h" -/** - * AnimatedCursor - Wrapper for SDL3 native animated cursors - */ struct AnimatedCursor { SDL_Cursor* m_cursor; @@ -51,9 +46,6 @@ struct AnimatedCursor SDL_Cursor* getCursor() const { return m_cursor; } }; -/** - * SDL3CursorManager - Manages loading and lifecycle of cursors - */ class SDL3CursorManager { public: diff --git a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h index 9dfb55d55a0..d24a6b78e27 100644 --- a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h +++ b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h @@ -16,9 +16,7 @@ ** along with this program. If not, see . */ -/* -** Derived from the GeneralsX branch by fbraz3 -*/ +// Derived from the GeneralsX branch by fbraz3 #pragma once @@ -37,15 +35,10 @@ // FORWARD REFERENCES class SDL3InputManager; -// GLOBALS --------------------------------------------------------------------- extern SDL3InputManager* TheSDL3InputManager; -// TYPE DEFINES ---------------------------------------------------------------- typedef KeyDefType KeyVal; -// SDL3Mouse ------------------------------------------------------------------ -/** Mouse interface using SDL3 APIs */ -//----------------------------------------------------------------------------- class SDL3Mouse : public Mouse { public: @@ -93,9 +86,6 @@ class SDL3Mouse : public Mouse SDL_Cursor* m_activeSDLCursor; }; -// SDL3Keyboard --------------------------------------------------------------- -/** Keyboard interface using SDL3 APIs */ -//----------------------------------------------------------------------------- class SDL3Keyboard : public Keyboard { public: @@ -121,9 +111,6 @@ class SDL3Keyboard : public Keyboard void translateKeyEvent(const SDL_KeyboardEvent& event); }; -// SDL3InputManager ----------------------------------------------------------- -/** Unified manager for SDL3 input events */ -//----------------------------------------------------------------------------- class SDL3InputManager { public: diff --git a/Core/GameEngineDevice/Include/SDL3GameEngine.h b/Core/GameEngineDevice/Include/SDL3GameEngine.h index a21f076b85a..53fe0f292f1 100644 --- a/Core/GameEngineDevice/Include/SDL3GameEngine.h +++ b/Core/GameEngineDevice/Include/SDL3GameEngine.h @@ -16,9 +16,7 @@ ** along with this program. If not, see . */ -/* -** Derived from the GeneralsX branch by fbraz3 -*/ +// Derived from the GeneralsX branch by fbraz3 #pragma once @@ -45,12 +43,6 @@ class Radar; class WebBrowser; class ParticleSystemManager; -/** - * SDL3GameEngine - * - * GameEngine subclass that uses SDL3 for windowing and input. - * Replaces or supplements Win32-specific window handling with SDL3. - */ class SDL3GameEngine : public GameEngine { public: diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp index 0a9f5bf825c..8cd1cbb2dc7 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp @@ -16,9 +16,7 @@ ** along with this program. If not, see . */ -/* -** Derived from the GeneralsX branch by fbraz3 -*/ +// Derived from the GeneralsX branch by fbraz3 #include "SDL3Device/GameClient/SDL3Cursor.h" #include @@ -32,12 +30,10 @@ #include "Common/file.h" #include "Common/FileSystem.h" -// Initialize static member AnimatedCursor* SDL3CursorManager::m_cursorResources[Mouse::NUM_MOUSE_CURSORS][MAX_2D_CURSOR_DIRECTIONS] = {nullptr}; void SDL3CursorManager::init() { - // Cursors are typically initialized via initResources when the Mouse device is ready shutdown(); } diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp index fe27ef60326..76fe2785c51 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp @@ -16,9 +16,7 @@ ** along with this program. If not, see . */ -/* -** Derived from the GeneralsX branch by fbraz3 -*/ +// Derived from the GeneralsX branch by fbraz3 #include "Lib/BaseType.h" @@ -44,16 +42,12 @@ #include "GameLogic/GameLogic.h" #include "SDL3GameEngine.h" -// GLOBALS --------------------------------------------------------------------- SDL3InputManager* TheSDL3InputManager = nullptr; /// ============================================================================ // SDL3MOUSE IMPLEMENTATION // ============================================================================ -/** - * Constructor - Initialize SDL3Mouse with window handle - */ SDL3Mouse::SDL3Mouse(SDL_Window* window) : Mouse() , m_Window(window) @@ -67,17 +61,11 @@ SDL3Mouse::SDL3Mouse(SDL_Window* window) { } -/** - * Destructor - */ SDL3Mouse::~SDL3Mouse(void) { releaseCapture(); } -/** - * Initialize mouse subsystem - */ void SDL3Mouse::init(void) { Mouse::init(); @@ -88,9 +76,6 @@ void SDL3Mouse::init(void) setVisibility(TRUE); } -/** - * Reset mouse to default state - */ void SDL3Mouse::reset(void) { Mouse::reset(); @@ -99,9 +84,6 @@ void SDL3Mouse::reset(void) setVisibility(TRUE); } -/** - * Update mouse state (called per-frame) - */ void SDL3Mouse::update(void) { Mouse::update(); @@ -189,9 +171,6 @@ void SDL3Mouse::update(void) } } -/** - * Initialize cursor resources (load cursor images from ANI files) - */ void SDL3Mouse::initCursorResources(void) { SDL3CursorManager::initResources(this); @@ -202,9 +181,6 @@ void SDL3Mouse::freeCursorResources(void) SDL3CursorManager::shutdown(); } -/** - * Set mouse cursor type - */ void SDL3Mouse::setCursor(MouseCursor cursor) { if (m_currentCursor == cursor) @@ -216,9 +192,6 @@ void SDL3Mouse::setCursor(MouseCursor cursor) m_currentCursor = cursor; } -/** - * Set cursor visibility - */ void SDL3Mouse::setVisibility(Bool visible) { Mouse::setVisibility(visible); @@ -233,26 +206,17 @@ void SDL3Mouse::setVisibility(Bool visible) } } -/** - * Handle window losing focus - */ void SDL3Mouse::loseFocus() { Mouse::loseFocus(); releaseCapture(); } -/** - * Handle window regaining focus - */ void SDL3Mouse::regainFocus() { Mouse::regainFocus(); } -/** - * Capture mouse (confine to window) - */ void SDL3Mouse::capture(void) { if (!m_Window || m_isCursorCaptured) @@ -265,9 +229,6 @@ void SDL3Mouse::capture(void) onCursorCaptured(true); } -/** - * Release mouse capture - */ void SDL3Mouse::releaseCapture(void) { if (!m_isCursorCaptured) @@ -284,9 +245,6 @@ void SDL3Mouse::releaseCapture(void) onCursorCaptured(false); } -/** - * Get next mouse event from the centralized input manager - */ UnsignedByte SDL3Mouse::getMouseEvent(MouseIO* result, Bool flush) { if (!TheSDL3InputManager) @@ -313,9 +271,7 @@ void SDL3Mouse::addSDLEvent(SDL_Event* event) } } -//----------------------------------------------------------------------------- -/** Unified event translation (Clean Slate Rewrite) */ -//----------------------------------------------------------------------------- +// Unified event translation (Clean Slate Rewrite) void SDL3Mouse::translateEvent(const SDL_Event& event, MouseIO* result) { if (!result) @@ -433,29 +389,17 @@ void SDL3Mouse::scaleMouseCoordinates(int rawX, int rawY, Uint32 windowID, int& // SDL3KEYBOARD IMPLEMENTATION // ============================================================================ -/** - * Lifecycle - */ SDL3Keyboard::SDL3Keyboard(void) : Keyboard() {} SDL3Keyboard::~SDL3Keyboard(void) {} -/** - * SubsystemInterface - */ void SDL3Keyboard::init(void) { Keyboard::init(); } void SDL3Keyboard::reset(void) { Keyboard::reset(); } void SDL3Keyboard::update(void) { Keyboard::update(); } -/** - * Keyboard Interface - */ Bool SDL3Keyboard::getCapsState(void) { return FALSE; } -/** - * SDL3-specific internal methods - */ void SDL3Keyboard::getKey(KeyboardIO* key) { if (!TheSDL3InputManager) @@ -732,9 +676,6 @@ KeyVal SDL3Keyboard::translateScanCodeToKeyVal(unsigned char scan) // SDL3INPUTMANAGER IMPLEMENTATION // ============================================================================ -/** - * Lifecycle - */ SDL3InputManager::SDL3InputManager(SDL_Window* window) : m_window(window) , m_mouseNextFree(0) @@ -761,9 +702,6 @@ SDL3InputManager::~SDL3InputManager() TheSDL3InputManager = nullptr; } -/** - * Unified Event Loop - */ void SDL3InputManager::update() { SDL_Event event; @@ -839,9 +777,6 @@ void SDL3InputManager::update() processGamepadInput(); } -/** - * Buffer Management - */ Bool SDL3InputManager::getNextMouseEvent(SDL_Event& outEvent) { if (m_mouseEvents[m_mouseNextGet].type == SDL_EVENT_FIRST) @@ -886,9 +821,6 @@ void SDL3InputManager::addKeyboardSDLEvent(const SDL_Event& event) m_keyNextFree = nextFree; } -/** - * Gamepad Logic - */ void SDL3InputManager::openFirstGamepad() { int count = 0; diff --git a/Core/GameEngineDevice/Source/SDL3GameEngine.cpp b/Core/GameEngineDevice/Source/SDL3GameEngine.cpp index 004621c45ef..bbfd74cf950 100644 --- a/Core/GameEngineDevice/Source/SDL3GameEngine.cpp +++ b/Core/GameEngineDevice/Source/SDL3GameEngine.cpp @@ -16,9 +16,7 @@ ** along with this program. If not, see . */ -/* -** Derived from the GeneralsX branch by fbraz3 -*/ +// Derived from the GeneralsX branch by fbraz3 #include "Lib/BaseType.h" @@ -122,9 +120,6 @@ Bool DecodeNextUtf8Codepoint(const char* text, size_t length, size_t& offset, Un } // namespace -/** - * Constructor: Initialize SDL3 game engine state - */ SDL3GameEngine::SDL3GameEngine() : GameEngine() , m_SDLWindow(nullptr) @@ -135,9 +130,6 @@ SDL3GameEngine::SDL3GameEngine() { } -/** - * Destructor: Cleanup SDL3 resources - */ SDL3GameEngine::~SDL3GameEngine() { if (m_SDLWindow && m_IsTextInputActive) @@ -153,9 +145,6 @@ SDL3GameEngine::~SDL3GameEngine() } } -/** - * From GameEngine: init() - initialize subsystems - */ void SDL3GameEngine::init(void) { // Verify window was created by SDL3Main integration @@ -180,9 +169,6 @@ void SDL3GameEngine::init(void) GameEngine::init(); } -/** - * From GameEngine: reset() - reset system to starting state - */ void SDL3GameEngine::reset(void) { if (m_SDLWindow && m_IsTextInputActive) @@ -194,9 +180,6 @@ void SDL3GameEngine::reset(void) GameEngine::reset(); } -/** - * From GameEngine: update() - per-frame update - */ void SDL3GameEngine::update(void) { pollSDL3Events(); @@ -231,33 +214,21 @@ void SDL3GameEngine::update(void) } } -/** - * From GameEngine: serviceWindowsOS() - native OS service - */ void SDL3GameEngine::serviceWindowsOS(void) { pollSDL3Events(); } -/** - * Check if game has OS focus - */ Bool SDL3GameEngine::isActive(void) { return m_IsActive; } -/** - * Set OS focus status - */ void SDL3GameEngine::setIsActive(Bool isActive) { m_IsActive = isActive; } -/** - * Poll and process SDL3 events - */ void SDL3GameEngine::pollSDL3Events(void) { if (!m_SDLWindow || !TheSDL3InputManager) @@ -353,10 +324,6 @@ void SDL3GameEngine::forwardTextInputEvent(const char* utf8Text) } } -/** - * Factory Methods for GameEngine subsystems - */ - LocalFileSystem* SDL3GameEngine::createLocalFileSystem(void) { return NEW StdLocalFileSystem; From 5928aa02b7669fb2ed0fa8062039cd85e97b4b3a Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Wed, 10 Jun 2026 00:20:50 +0200 Subject: [PATCH 23/27] build: upgrade SDL_image and clean up SDL3Cursor loadANI - Switched SDL3_image to the latest GitHub main branch commit to natively resolve the RIFF/ANI odd-chunk parsing bug. - Removed the legacy cbSize header patching workaround in SDL3CursorManager::loadANI. - Kept base SDL3 library pinned to stable release-3.4.10. - Cleaned up transient code-level header comments. Co-authored-by: fbraz3 Co-authored-by: Fighter19 Co-authored-by: feliwir --- .../Include/SDL3Device/GameClient/SDL3Cursor.h | 2 -- .../Include/SDL3Device/GameClient/SDL3Input.h | 2 -- Core/GameEngineDevice/Include/SDL3GameEngine.h | 2 -- .../Source/SDL3Device/GameClient/SDL3Cursor.cpp | 15 --------------- .../Source/SDL3Device/GameClient/SDL3Input.cpp | 2 -- Core/GameEngineDevice/Source/SDL3GameEngine.cpp | 2 -- cmake/sdl3.cmake | 5 +++-- 7 files changed, 3 insertions(+), 27 deletions(-) diff --git a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h index 19005f40873..362d72094da 100644 --- a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h +++ b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h @@ -16,8 +16,6 @@ ** along with this program. If not, see . */ -// Derived from the GeneralsX branch by fbraz3 - #pragma once #include "Lib/BaseType.h" diff --git a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h index d24a6b78e27..8676850937b 100644 --- a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h +++ b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h @@ -16,8 +16,6 @@ ** along with this program. If not, see . */ -// Derived from the GeneralsX branch by fbraz3 - #pragma once #include "Lib/BaseType.h" diff --git a/Core/GameEngineDevice/Include/SDL3GameEngine.h b/Core/GameEngineDevice/Include/SDL3GameEngine.h index 53fe0f292f1..7eb7a63e5eb 100644 --- a/Core/GameEngineDevice/Include/SDL3GameEngine.h +++ b/Core/GameEngineDevice/Include/SDL3GameEngine.h @@ -16,8 +16,6 @@ ** along with this program. If not, see . */ -// Derived from the GeneralsX branch by fbraz3 - #pragma once #include "Lib/BaseType.h" diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp index 8cd1cbb2dc7..7ca53b7fba6 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp @@ -16,13 +16,9 @@ ** along with this program. If not, see . */ -// Derived from the GeneralsX branch by fbraz3 - #include "SDL3Device/GameClient/SDL3Cursor.h" #include #include -#include -#include #include #include @@ -100,17 +96,6 @@ AnimatedCursor* SDL3CursorManager::loadANI(const char* filepath) file->read(buf.data(), size); file->close(); - // thesuperhackers @info - // Command & Conquer Generals .ani files write the total file size as the `cbSize` - // field inside the RIFF header (offset 4) rather than `size - 8` bytes. - // The native SDL3_image ANI parser strictly validates that (offset + chunk_size) <= cbSize, - // causing it to fail at EOF with a truncated data error. - if (size >= 12 && memcmp(buf.data(), "RIFF", 4) == 0 && memcmp(buf.data() + 8, "ACON", 4) == 0) - { - Uint32 correct_cbSize = (Uint32)(size - 8); - memcpy(buf.data() + 4, &correct_cbSize, 4); - } - SDL_IOStream* io = SDL_IOFromConstMem(buf.data(), buf.size()); if (!io) { diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp index 76fe2785c51..2ad50f6e90c 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp @@ -16,8 +16,6 @@ ** along with this program. If not, see . */ -// Derived from the GeneralsX branch by fbraz3 - #include "Lib/BaseType.h" #define _USE_MATH_DEFINES diff --git a/Core/GameEngineDevice/Source/SDL3GameEngine.cpp b/Core/GameEngineDevice/Source/SDL3GameEngine.cpp index bbfd74cf950..ba94c1d11d6 100644 --- a/Core/GameEngineDevice/Source/SDL3GameEngine.cpp +++ b/Core/GameEngineDevice/Source/SDL3GameEngine.cpp @@ -16,8 +16,6 @@ ** along with this program. If not, see . */ -// Derived from the GeneralsX branch by fbraz3 - #include "Lib/BaseType.h" #include diff --git a/cmake/sdl3.cmake b/cmake/sdl3.cmake index a97f7829e87..8e2dfc4c6a3 100644 --- a/cmake/sdl3.cmake +++ b/cmake/sdl3.cmake @@ -15,8 +15,9 @@ if(NOT SDL3_FOUND OR NOT SDL3_image_FOUND) FetchContent_Declare( SDL3_image - URL https://github.com/libsdl-org/SDL_image/releases/download/release-3.4.4/SDL3_image-3.4.4.tar.gz - URL_HASH SHA256=29751304a13d25ac513f24305fa25b06a6edd9607718c90129b8350d35fc5573 + # Pin to commit with ANI loader RIFF word-alignment chunk size parsing fix + GIT_REPOSITORY https://github.com/libsdl-org/SDL_image.git + GIT_TAG 0e2eaa923ddea285dfa35c4bf0c0092d3799e2ee ) # Official SDL configuration for a unified build tree From 9b60f0f5df64cc2cf13aeb3a8dfa723c726882d9 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Wed, 10 Jun 2026 00:35:30 +0200 Subject: [PATCH 24/27] style: remove legacy empty parameter list style (void) -> () - Replaced (void) with empty parentheses () in SDL3Input and SDL3GameEngine declarations and definitions. Co-authored-by: fbraz3 Co-authored-by: Fighter19 Co-authored-by: feliwir --- .../Include/SDL3Device/GameClient/SDL3Input.h | 28 ++++++++-------- .../GameEngineDevice/Include/SDL3GameEngine.h | 32 +++++++++---------- .../SDL3Device/GameClient/SDL3Input.cpp | 28 ++++++++-------- .../Source/SDL3GameEngine.cpp | 30 ++++++++--------- 4 files changed, 59 insertions(+), 59 deletions(-) diff --git a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h index 8676850937b..4919d0b50d9 100644 --- a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h +++ b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h @@ -41,14 +41,14 @@ class SDL3Mouse : public Mouse { public: SDL3Mouse(SDL_Window* window); - virtual ~SDL3Mouse(void); + virtual ~SDL3Mouse(); // SubsystemInterface - virtual void init(void) override; - virtual void reset(void) override; - virtual void update(void) override; - virtual void initCursorResources(void) override; - static void freeCursorResources(void); + virtual void init() override; + virtual void reset() override; + virtual void update() override; + virtual void initCursorResources() override; + static void freeCursorResources(); // Mouse interface virtual void setCursor(MouseCursor cursor) override; @@ -60,8 +60,8 @@ class SDL3Mouse : public Mouse void addSDLEvent(SDL_Event* event); protected: - virtual void capture(void) override; - virtual void releaseCapture(void) override; + virtual void capture() override; + virtual void releaseCapture() override; virtual UnsignedByte getMouseEvent(MouseIO* result, Bool flush) override; private: @@ -87,16 +87,16 @@ class SDL3Mouse : public Mouse class SDL3Keyboard : public Keyboard { public: - SDL3Keyboard(void); - virtual ~SDL3Keyboard(void); + SDL3Keyboard(); + virtual ~SDL3Keyboard(); // SubsystemInterface - virtual void init(void) override; - virtual void reset(void) override; - virtual void update(void) override; + virtual void init() override; + virtual void reset() override; + virtual void update() override; // Keyboard interface - virtual Bool getCapsState(void) override; + virtual Bool getCapsState() override; // SDL3-specific methods void addSDLEvent(SDL_Event* event); diff --git a/Core/GameEngineDevice/Include/SDL3GameEngine.h b/Core/GameEngineDevice/Include/SDL3GameEngine.h index 7eb7a63e5eb..b44d1ef38ed 100644 --- a/Core/GameEngineDevice/Include/SDL3GameEngine.h +++ b/Core/GameEngineDevice/Include/SDL3GameEngine.h @@ -48,28 +48,28 @@ class SDL3GameEngine : public GameEngine virtual ~SDL3GameEngine(); // GameEngine interface - virtual void init(void) override; - virtual void reset(void) override; - virtual void update(void) override; - virtual void serviceWindowsOS(void) override; - virtual Bool isActive(void) override; + virtual void init() override; + virtual void reset() override; + virtual void update() override; + virtual void serviceWindowsOS() override; + virtual Bool isActive() override; virtual void setIsActive(Bool isActive) override; // Factory methods (override GameEngine) - virtual LocalFileSystem* createLocalFileSystem(void) override; - virtual ArchiveFileSystem* createArchiveFileSystem(void) override; - virtual GameLogic* createGameLogic(void) override; - virtual GameClient* createGameClient(void) override; - virtual ModuleFactory* createModuleFactory(void) override; - virtual ThingFactory* createThingFactory(void) override; - virtual FunctionLexicon* createFunctionLexicon(void) override; + virtual LocalFileSystem* createLocalFileSystem() override; + virtual ArchiveFileSystem* createArchiveFileSystem() override; + virtual GameLogic* createGameLogic() override; + virtual GameClient* createGameClient() override; + virtual ModuleFactory* createModuleFactory() override; + virtual ThingFactory* createThingFactory() override; + virtual FunctionLexicon* createFunctionLexicon() override; virtual Radar* createRadar(Bool dummy) override; - virtual WebBrowser* createWebBrowser(void) override; + virtual WebBrowser* createWebBrowser() override; virtual ParticleSystemManager* createParticleSystemManager(Bool dummy) override; virtual AudioManager* createAudioManager(Bool dummy) override; // SDL3 specific - virtual SDL_Window* getSDLWindow(void) const { return m_SDLWindow; } + virtual SDL_Window* getSDLWindow() const { return m_SDLWindow; } virtual void forwardTextInputEvent(const char* utf8Text); protected: @@ -80,6 +80,6 @@ class SDL3GameEngine : public GameEngine GameWindow* m_TextInputFocusWindow; // Event processing - void pollSDL3Events(void); - void updateTextInputState(void); + void pollSDL3Events(); + void updateTextInputState(); }; diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp index 2ad50f6e90c..c86ef1b71cc 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp @@ -59,12 +59,12 @@ SDL3Mouse::SDL3Mouse(SDL_Window* window) { } -SDL3Mouse::~SDL3Mouse(void) +SDL3Mouse::~SDL3Mouse() { releaseCapture(); } -void SDL3Mouse::init(void) +void SDL3Mouse::init() { Mouse::init(); @@ -74,7 +74,7 @@ void SDL3Mouse::init(void) setVisibility(TRUE); } -void SDL3Mouse::reset(void) +void SDL3Mouse::reset() { Mouse::reset(); @@ -82,7 +82,7 @@ void SDL3Mouse::reset(void) setVisibility(TRUE); } -void SDL3Mouse::update(void) +void SDL3Mouse::update() { Mouse::update(); @@ -169,12 +169,12 @@ void SDL3Mouse::update(void) } } -void SDL3Mouse::initCursorResources(void) +void SDL3Mouse::initCursorResources() { SDL3CursorManager::initResources(this); } -void SDL3Mouse::freeCursorResources(void) +void SDL3Mouse::freeCursorResources() { SDL3CursorManager::shutdown(); } @@ -215,7 +215,7 @@ void SDL3Mouse::regainFocus() Mouse::regainFocus(); } -void SDL3Mouse::capture(void) +void SDL3Mouse::capture() { if (!m_Window || m_isCursorCaptured) { @@ -227,7 +227,7 @@ void SDL3Mouse::capture(void) onCursorCaptured(true); } -void SDL3Mouse::releaseCapture(void) +void SDL3Mouse::releaseCapture() { if (!m_isCursorCaptured) { @@ -387,16 +387,16 @@ void SDL3Mouse::scaleMouseCoordinates(int rawX, int rawY, Uint32 windowID, int& // SDL3KEYBOARD IMPLEMENTATION // ============================================================================ -SDL3Keyboard::SDL3Keyboard(void) +SDL3Keyboard::SDL3Keyboard() : Keyboard() {} -SDL3Keyboard::~SDL3Keyboard(void) {} +SDL3Keyboard::~SDL3Keyboard() {} -void SDL3Keyboard::init(void) { Keyboard::init(); } -void SDL3Keyboard::reset(void) { Keyboard::reset(); } -void SDL3Keyboard::update(void) { Keyboard::update(); } +void SDL3Keyboard::init() { Keyboard::init(); } +void SDL3Keyboard::reset() { Keyboard::reset(); } +void SDL3Keyboard::update() { Keyboard::update(); } -Bool SDL3Keyboard::getCapsState(void) { return FALSE; } +Bool SDL3Keyboard::getCapsState() { return FALSE; } void SDL3Keyboard::getKey(KeyboardIO* key) { diff --git a/Core/GameEngineDevice/Source/SDL3GameEngine.cpp b/Core/GameEngineDevice/Source/SDL3GameEngine.cpp index ba94c1d11d6..f91c93562e6 100644 --- a/Core/GameEngineDevice/Source/SDL3GameEngine.cpp +++ b/Core/GameEngineDevice/Source/SDL3GameEngine.cpp @@ -143,7 +143,7 @@ SDL3GameEngine::~SDL3GameEngine() } } -void SDL3GameEngine::init(void) +void SDL3GameEngine::init() { // Verify window was created by SDL3Main integration extern SDL_Window* TheSDL3Window; @@ -167,7 +167,7 @@ void SDL3GameEngine::init(void) GameEngine::init(); } -void SDL3GameEngine::reset(void) +void SDL3GameEngine::reset() { if (m_SDLWindow && m_IsTextInputActive) { @@ -178,7 +178,7 @@ void SDL3GameEngine::reset(void) GameEngine::reset(); } -void SDL3GameEngine::update(void) +void SDL3GameEngine::update() { pollSDL3Events(); GameEngine::update(); @@ -212,12 +212,12 @@ void SDL3GameEngine::update(void) } } -void SDL3GameEngine::serviceWindowsOS(void) +void SDL3GameEngine::serviceWindowsOS() { pollSDL3Events(); } -Bool SDL3GameEngine::isActive(void) +Bool SDL3GameEngine::isActive() { return m_IsActive; } @@ -227,7 +227,7 @@ void SDL3GameEngine::setIsActive(Bool isActive) m_IsActive = isActive; } -void SDL3GameEngine::pollSDL3Events(void) +void SDL3GameEngine::pollSDL3Events() { if (!m_SDLWindow || !TheSDL3InputManager) { @@ -246,7 +246,7 @@ void SDL3GameEngine::pollSDL3Events(void) } } -void SDL3GameEngine::updateTextInputState(void) +void SDL3GameEngine::updateTextInputState() { if (!m_SDLWindow || !TheWindowManager) { @@ -322,37 +322,37 @@ void SDL3GameEngine::forwardTextInputEvent(const char* utf8Text) } } -LocalFileSystem* SDL3GameEngine::createLocalFileSystem(void) +LocalFileSystem* SDL3GameEngine::createLocalFileSystem() { return NEW StdLocalFileSystem; } -ArchiveFileSystem* SDL3GameEngine::createArchiveFileSystem(void) +ArchiveFileSystem* SDL3GameEngine::createArchiveFileSystem() { return NEW StdBIGFileSystem; } -GameLogic* SDL3GameEngine::createGameLogic(void) +GameLogic* SDL3GameEngine::createGameLogic() { return NEW W3DGameLogic; } -GameClient* SDL3GameEngine::createGameClient(void) +GameClient* SDL3GameEngine::createGameClient() { return NEW W3DGameClient; } -ModuleFactory* SDL3GameEngine::createModuleFactory(void) +ModuleFactory* SDL3GameEngine::createModuleFactory() { return NEW W3DModuleFactory; } -ThingFactory* SDL3GameEngine::createThingFactory(void) +ThingFactory* SDL3GameEngine::createThingFactory() { return NEW W3DThingFactory; } -FunctionLexicon* SDL3GameEngine::createFunctionLexicon(void) +FunctionLexicon* SDL3GameEngine::createFunctionLexicon() { return NEW W3DFunctionLexicon; } @@ -369,7 +369,7 @@ ParticleSystemManager* SDL3GameEngine::createParticleSystemManager(Bool dummy) return NEW W3DParticleSystemManager; } -WebBrowser* SDL3GameEngine::createWebBrowser(void) +WebBrowser* SDL3GameEngine::createWebBrowser() { return nullptr; } From 4470104b58bdfd7069ca80407990c1f4993b9d8e Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Wed, 10 Jun 2026 01:04:15 +0200 Subject: [PATCH 25/27] build: update vcpkg dependencies and fix SDL3 input scancode translation - Updated vcpkg.json overrides and regenerated vcpkg-lock.json (sdl3 to 3.4.10, sdl3-image to 3.4.4). - Normalized formatting on vcpkg.json using the official vcpkg format-manifest tool. - Changed translateScanCodeToKeyVal parameter type to SDL_Scancode to prevent high-range media keys from wrapping into valid letter keycodes. - Documented SDL3_image FetchContent Git pin reasoning in cmake/sdl3.cmake. Co-authored-by: fbraz3 Co-authored-by: Fighter19 Co-authored-by: feliwir --- .../Include/SDL3Device/GameClient/SDL3Input.h | 2 +- .../Source/SDL3Device/GameClient/SDL3Input.cpp | 4 ++-- cmake/sdl3.cmake | 4 +++- vcpkg-lock.json | 8 ++++---- vcpkg.json | 16 +++++++++++----- 5 files changed, 21 insertions(+), 13 deletions(-) diff --git a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h index 4919d0b50d9..e409786e67e 100644 --- a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h +++ b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h @@ -103,7 +103,7 @@ class SDL3Keyboard : public Keyboard protected: virtual void getKey(KeyboardIO* key) override; - virtual KeyVal translateScanCodeToKeyVal(unsigned char scan); + virtual KeyVal translateScanCodeToKeyVal(SDL_Scancode scan); private: void translateKeyEvent(const SDL_KeyboardEvent& event); diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp index c86ef1b71cc..41ee52cc478 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp @@ -461,9 +461,9 @@ void SDL3Keyboard::addSDLEvent(SDL_Event* event) } } -KeyVal SDL3Keyboard::translateScanCodeToKeyVal(unsigned char scan) +KeyVal SDL3Keyboard::translateScanCodeToKeyVal(SDL_Scancode scan) { - switch ((SDL_Scancode)scan) + switch (scan) { case SDL_SCANCODE_ESCAPE: return KEY_ESC; diff --git a/cmake/sdl3.cmake b/cmake/sdl3.cmake index 8e2dfc4c6a3..9231c9115e7 100644 --- a/cmake/sdl3.cmake +++ b/cmake/sdl3.cmake @@ -15,7 +15,9 @@ if(NOT SDL3_FOUND OR NOT SDL3_image_FOUND) FetchContent_Declare( SDL3_image - # Pin to commit with ANI loader RIFF word-alignment chunk size parsing fix + # Pin to commit with the ANI loader RIFF word-alignment fix and legacy parsing relaxation. + # NOTE: The legacy asset parsing relaxation fix (commit 67da91c / 0e2eaa9) is not yet in the + # official SDL_image 3.4.4 release. vcpkg builds will inherit this fix once 3.4.5+ is packaged. GIT_REPOSITORY https://github.com/libsdl-org/SDL_image.git GIT_TAG 0e2eaa923ddea285dfa35c4bf0c0092d3799e2ee ) diff --git a/vcpkg-lock.json b/vcpkg-lock.json index c6ef82b1101..f77c128f168 100644 --- a/vcpkg-lock.json +++ b/vcpkg-lock.json @@ -15,15 +15,15 @@ }, { "name": "sdl3", - "version-string": "3.4.4", + "version-string": "3.4.10", "port-version": 0, - "git-tree": "c3ea8e6cf352b01ab5bf9035850e72f674af2433" + "git-tree": "ca8f0f2fec751398b72e40cb25af47f13bf22d63" }, { "name": "sdl3-image", - "version-string": "3.4.2", + "version-string": "3.4.4", "port-version": 0, - "git-tree": "2621596cc09e39b1ab98298f4f9e126c1764a6c4" + "git-tree": "7d64d0dd26d1a025b99b83429838551fde4a65cf" }, { "name": "vcpkg-cmake", diff --git a/vcpkg.json b/vcpkg.json index 79744630dc3..29c422cd7b7 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,13 +1,19 @@ { "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", "dependencies": [ - "zlib", "ffmpeg", "sdl3", - "sdl3-image" + "sdl3-image", + "zlib" ], "overrides": [ - { "name": "sdl3", "version": "3.4.10" }, - { "name": "sdl3-image", "version": "3.4.4" } + { + "name": "sdl3", + "version": "3.4.10" + }, + { + "name": "sdl3-image", + "version": "3.4.4" + } ] -} \ No newline at end of file +} From 45412557c54de1b0521c010e2aaf8b1b84cf6ce5 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Wed, 10 Jun 2026 18:13:04 +0200 Subject: [PATCH 26/27] move cmake to RTS_BUILD_OPTION_SDL3, fix sorting --- CMakeLists.txt | 2 +- Core/GameEngineDevice/CMakeLists.txt | 4 ++-- cmake/config-build.cmake | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e2162f2a1d..13ae014ac38 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,7 @@ include(cmake/config.cmake) include(cmake/gamespy.cmake) include(cmake/lzhl.cmake) -if(RTS_SDL3_ENABLE AND NOT IS_VS6_BUILD) +if(RTS_BUILD_OPTION_SDL3 AND NOT IS_VS6_BUILD) include(cmake/sdl3.cmake) endif() diff --git a/Core/GameEngineDevice/CMakeLists.txt b/Core/GameEngineDevice/CMakeLists.txt index b3475000002..0d9c36ccc65 100644 --- a/Core/GameEngineDevice/CMakeLists.txt +++ b/Core/GameEngineDevice/CMakeLists.txt @@ -195,7 +195,7 @@ set(GAMEENGINEDEVICE_SRC ) # Add Core-level SDL3 implementation -if(RTS_SDL3_ENABLE AND NOT IS_VS6_BUILD) +if(RTS_BUILD_OPTION_SDL3 AND NOT IS_VS6_BUILD) list(APPEND GAMEENGINEDEVICE_SRC Include/SDL3GameEngine.h Include/SDL3Device/GameClient/SDL3Input.h @@ -242,7 +242,7 @@ target_link_libraries(corei_gameenginedevice_public INTERFACE ) # Export SDL3 dependencies for modern builds -if(RTS_SDL3_ENABLE AND NOT IS_VS6_BUILD) +if(RTS_BUILD_OPTION_SDL3 AND NOT IS_VS6_BUILD) target_link_libraries(corei_gameenginedevice_public INTERFACE SDL3::SDL3 SDL3_image::SDL3_image diff --git a/cmake/config-build.cmake b/cmake/config-build.cmake index 922a9fc6d85..f9e339118b7 100644 --- a/cmake/config-build.cmake +++ b/cmake/config-build.cmake @@ -9,15 +9,14 @@ option(RTS_BUILD_OPTION_DEBUG "Build code with the \"Debug\" configuration." OFF option(RTS_BUILD_OPTION_ASAN "Build code with Address Sanitizer." OFF) option(RTS_BUILD_OPTION_VC6_FULL_DEBUG "Build VC6 with full debug info." OFF) option(RTS_BUILD_OPTION_FFMPEG "Enable FFmpeg support" OFF) +option(RTS_BUILD_OPTION_SDL3 "Enable SDL3 input/window backend" ON) -# Enable SDL3 by default for modern builds -if(NOT IS_VS6_BUILD) - option(RTS_SDL3_ENABLE "Enable SDL3 input/window backend" ON) - if(RTS_SDL3_ENABLE) - target_compile_definitions(core_config INTERFACE RTS_SDL3_ENABLE=1) - endif() -else() - set(RTS_SDL3_ENABLE OFF CACHE BOOL "Enable SDL3 input/window backend" FORCE) +if(IS_VS6_BUILD) + set(RTS_BUILD_OPTION_SDL3 OFF CACHE BOOL "Enable SDL3 input/window backend" FORCE) +endif() + +if(RTS_BUILD_OPTION_SDL3) + target_compile_definitions(core_config INTERFACE RTS_SDL3_ENABLE=1) endif() if(NOT RTS_BUILD_ZEROHOUR AND NOT RTS_BUILD_GENERALS) @@ -34,6 +33,7 @@ add_feature_info(DebugBuild RTS_BUILD_OPTION_DEBUG "Building as a \"Debug\" buil add_feature_info(AddressSanitizer RTS_BUILD_OPTION_ASAN "Building with address sanitizer") add_feature_info(Vc6FullDebug RTS_BUILD_OPTION_VC6_FULL_DEBUG "Building VC6 with full debug info") add_feature_info(FFmpegSupport RTS_BUILD_OPTION_FFMPEG "Building with FFmpeg support") +add_feature_info(Sdl3Backend RTS_BUILD_OPTION_SDL3 "Building with SDL3 input/window backend") set(RTS_BUILD_OUTPUT_SUFFIX "" CACHE STRING "Suffix appended to output names of installable targets") From 0921b16dabaea113f9bce3159fd3dc89317e38c2 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Fri, 12 Jun 2026 02:06:26 +0200 Subject: [PATCH 27/27] Move SDL3GameEngine files to Common directory. Replace legacy block comment banners with line comments. Group and sort header includes. Break long gamepad input lines in SDL3Input.cpp with newlines. Remove Sage mentions. --- Core/GameEngineDevice/CMakeLists.txt | 4 +- .../{ => SDL3Device/Common}/SDL3GameEngine.h | 4 +- .../SDL3Device/GameClient/SDL3Cursor.h | 4 +- .../Common}/SDL3GameEngine.cpp | 28 ++-- .../SDL3Device/GameClient/SDL3Cursor.cpp | 6 +- .../SDL3Device/GameClient/SDL3Input.cpp | 148 ++++++++++++++---- GeneralsMD/Code/Main/WinMain.cpp | 2 +- 7 files changed, 140 insertions(+), 56 deletions(-) rename Core/GameEngineDevice/Include/{ => SDL3Device/Common}/SDL3GameEngine.h (99%) rename Core/GameEngineDevice/Source/{ => SDL3Device/Common}/SDL3GameEngine.cpp (99%) diff --git a/Core/GameEngineDevice/CMakeLists.txt b/Core/GameEngineDevice/CMakeLists.txt index 0d9c36ccc65..48b2b793890 100644 --- a/Core/GameEngineDevice/CMakeLists.txt +++ b/Core/GameEngineDevice/CMakeLists.txt @@ -197,10 +197,10 @@ set(GAMEENGINEDEVICE_SRC # Add Core-level SDL3 implementation if(RTS_BUILD_OPTION_SDL3 AND NOT IS_VS6_BUILD) list(APPEND GAMEENGINEDEVICE_SRC - Include/SDL3GameEngine.h + Include/SDL3Device/Common/SDL3GameEngine.h Include/SDL3Device/GameClient/SDL3Input.h Include/SDL3Device/GameClient/SDL3Cursor.h - Source/SDL3GameEngine.cpp + Source/SDL3Device/Common/SDL3GameEngine.cpp Source/SDL3Device/GameClient/SDL3Input.cpp Source/SDL3Device/GameClient/SDL3Cursor.cpp ) diff --git a/Core/GameEngineDevice/Include/SDL3GameEngine.h b/Core/GameEngineDevice/Include/SDL3Device/Common/SDL3GameEngine.h similarity index 99% rename from Core/GameEngineDevice/Include/SDL3GameEngine.h rename to Core/GameEngineDevice/Include/SDL3Device/Common/SDL3GameEngine.h index b44d1ef38ed..de76e23cf45 100644 --- a/Core/GameEngineDevice/Include/SDL3GameEngine.h +++ b/Core/GameEngineDevice/Include/SDL3Device/Common/SDL3GameEngine.h @@ -20,10 +20,10 @@ #include "Lib/BaseType.h" -#include "Common/GameEngine.h" #include -// EXTERNALS +#include "Common/GameEngine.h" + // SDL3 window typically provided by WinMain integration extern SDL_Window* TheSDL3Window; diff --git a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h index 362d72094da..e6be47868b4 100644 --- a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h +++ b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h @@ -19,10 +19,10 @@ #pragma once #include "Lib/BaseType.h" -#include + #include +#include -// USER INCLUDES #include "GameClient/Mouse.h" struct AnimatedCursor diff --git a/Core/GameEngineDevice/Source/SDL3GameEngine.cpp b/Core/GameEngineDevice/Source/SDL3Device/Common/SDL3GameEngine.cpp similarity index 99% rename from Core/GameEngineDevice/Source/SDL3GameEngine.cpp rename to Core/GameEngineDevice/Source/SDL3Device/Common/SDL3GameEngine.cpp index f91c93562e6..94a61176d71 100644 --- a/Core/GameEngineDevice/Source/SDL3GameEngine.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/Common/SDL3GameEngine.cpp @@ -18,33 +18,33 @@ #include "Lib/BaseType.h" -#include #include #include #include +#include #include "Common/GameEngine.h" -#include "SDL3GameEngine.h" -#include "SDL3Device/GameClient/SDL3Input.h" -#include "MilesAudioDevice/MilesAudioManager.h" -#include "GameClient/Mouse.h" -#include "GameClient/Keyboard.h" +#include "GameClient/Gadget.h" #include "GameClient/GameWindow.h" #include "GameClient/GameWindowManager.h" -#include "GameClient/Gadget.h" +#include "GameClient/Keyboard.h" +#include "GameClient/Mouse.h" +#include "GameLogic/GameLogic.h" #include "GameNetwork/LANAPICallbacks.h" #include "GameNetwork/NetworkInterface.h" -#include "GameLogic/GameLogic.h" -#include "W3DDevice/GameLogic/W3DGameLogic.h" -#include "W3DDevice/GameClient/W3DGameClient.h" -#include "W3DDevice/Common/W3DModuleFactory.h" -#include "W3DDevice/Common/W3DThingFactory.h" +#include "MilesAudioDevice/MilesAudioManager.h" +#include "SDL3Device/Common/SDL3GameEngine.h" +#include "SDL3Device/GameClient/SDL3Input.h" +#include "StdDevice/Common/StdBIGFileSystem.h" +#include "StdDevice/Common/StdLocalFileSystem.h" #include "W3DDevice/Common/W3DFunctionLexicon.h" +#include "W3DDevice/Common/W3DModuleFactory.h" #include "W3DDevice/Common/W3DRadar.h" +#include "W3DDevice/Common/W3DThingFactory.h" +#include "W3DDevice/GameClient/W3DGameClient.h" #include "W3DDevice/GameClient/W3DParticleSys.h" #include "W3DDevice/GameClient/W3DWebBrowser.h" -#include "StdDevice/Common/StdLocalFileSystem.h" -#include "StdDevice/Common/StdBIGFileSystem.h" +#include "W3DDevice/GameLogic/W3DGameLogic.h" // Extern globals for input devices (set by GameClient) extern Mouse* TheMouse; diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp index 7ca53b7fba6..b806d55a5ed 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp @@ -16,15 +16,15 @@ ** along with this program. If not, see . */ -#include "SDL3Device/GameClient/SDL3Cursor.h" -#include #include -#include #include +#include +#include #include "Common/Debug.h" #include "Common/file.h" #include "Common/FileSystem.h" +#include "SDL3Device/GameClient/SDL3Cursor.h" AnimatedCursor* SDL3CursorManager::m_cursorResources[Mouse::NUM_MOUSE_CURSORS][MAX_2D_CURSOR_DIRECTIONS] = {nullptr}; diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp index 41ee52cc478..a92cfd2f4ed 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp @@ -19,15 +19,14 @@ #include "Lib/BaseType.h" #define _USE_MATH_DEFINES +#include +#include #include #include #include -#include -#include #include #include #include -#include "SDL3Device/GameClient/SDL3Cursor.h" #include "SDL3Device/GameClient/SDL3Input.h" #include "Common/Debug.h" @@ -38,13 +37,12 @@ #include "GameClient/Display.h" #include "GameClient/InGameUI.h" #include "GameLogic/GameLogic.h" -#include "SDL3GameEngine.h" +#include "SDL3Device/Common/SDL3GameEngine.h" +#include "SDL3Device/GameClient/SDL3Cursor.h" SDL3InputManager* TheSDL3InputManager = nullptr; -/// ============================================================================ -// SDL3MOUSE IMPLEMENTATION -// ============================================================================ +// SDL3Mouse implementation SDL3Mouse::SDL3Mouse(SDL_Window* window) : Mouse() @@ -280,7 +278,7 @@ void SDL3Mouse::translateEvent(const SDL_Event& event, MouseIO* result) result->wheelPos = 0; result->deltaPos.x = result->deltaPos.y = 0; - // Common timestamp (SDL3 uses nanoseconds, SAGE usually wants ms) + // Common timestamp (SDL3 uses nanoseconds, engine usually wants ms) result->time = (Uint32)(event.common.timestamp / 1000000); int rawX = 0; @@ -383,9 +381,7 @@ void SDL3Mouse::scaleMouseCoordinates(int rawX, int rawY, Uint32 windowID, int& } } -// ============================================================================ -// SDL3KEYBOARD IMPLEMENTATION -// ============================================================================ +// SDL3Keyboard implementation SDL3Keyboard::SDL3Keyboard() : Keyboard() @@ -670,9 +666,7 @@ KeyVal SDL3Keyboard::translateScanCodeToKeyVal(SDL_Scancode scan) } } -// ============================================================================ -// SDL3INPUTMANAGER IMPLEMENTATION -// ============================================================================ +// SDL3InputManager implementation SDL3InputManager::SDL3InputManager(SDL_Window* window) : m_window(window) @@ -956,24 +950,114 @@ void SDL3InputManager::processGamepadInput() float rx = SDL_GetGamepadAxis(m_gamepad, SDL_GAMEPAD_AXIS_RIGHTX) / AXIS_MAX; float ry = SDL_GetGamepadAxis(m_gamepad, SDL_GAMEPAD_AXIS_RIGHTY) / AXIS_MAX; - handleGamepadButton(SDL_GAMEPAD_BUTTON_INVALID, m_state.stickLeft, rx < -DEADZONE, [&](bool d) { virtualPulseKey(SDL_SCANCODE_LEFT, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_INVALID, m_state.stickRight, rx > DEADZONE, [&](bool d) { virtualPulseKey(SDL_SCANCODE_RIGHT, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_INVALID, m_state.stickUp, ry < -DEADZONE, [&](bool d) { virtualPulseKey(SDL_SCANCODE_UP, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_INVALID, m_state.stickDown, ry > DEADZONE, [&](bool d) { virtualPulseKey(SDL_SCANCODE_DOWN, d); }); + handleGamepadButton( + SDL_GAMEPAD_BUTTON_INVALID, + m_state.stickLeft, + rx < -DEADZONE, + [&](bool d) { virtualPulseKey(SDL_SCANCODE_LEFT, d); } + ); + handleGamepadButton( + SDL_GAMEPAD_BUTTON_INVALID, + m_state.stickRight, + rx > DEADZONE, + [&](bool d) { virtualPulseKey(SDL_SCANCODE_RIGHT, d); } + ); + handleGamepadButton( + SDL_GAMEPAD_BUTTON_INVALID, + m_state.stickUp, + ry < -DEADZONE, + [&](bool d) { virtualPulseKey(SDL_SCANCODE_UP, d); } + ); + handleGamepadButton( + SDL_GAMEPAD_BUTTON_INVALID, + m_state.stickDown, + ry > DEADZONE, + [&](bool d) { virtualPulseKey(SDL_SCANCODE_DOWN, d); } + ); // 3. BUTTONS & D-PAD (Actions & Hotkeys) - handleGamepadButton(SDL_GAMEPAD_BUTTON_SOUTH, m_state.buttonState[SDL_GAMEPAD_BUTTON_SOUTH], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_SOUTH), [&](bool d) { virtualPulseMouse(SDL_BUTTON_LEFT, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_EAST, m_state.buttonState[SDL_GAMEPAD_BUTTON_EAST], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_EAST), [&](bool d) { virtualPulseMouse(SDL_BUTTON_RIGHT, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_WEST, m_state.buttonState[SDL_GAMEPAD_BUTTON_WEST], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_WEST), [&](bool d) { virtualPulseKey(SDL_SCANCODE_A, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_NORTH, m_state.buttonState[SDL_GAMEPAD_BUTTON_NORTH], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_NORTH), [&](bool d) { if (d) TheMessageStream->appendMessage(GameMessage::MSG_META_STOP); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, m_state.buttonState[SDL_GAMEPAD_BUTTON_LEFT_SHOULDER], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), [&](bool d) { virtualPulseKey(SDL_SCANCODE_Q, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, m_state.buttonState[SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), [&](bool d) { virtualPulseKey(SDL_SCANCODE_LSHIFT, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_START, m_state.buttonState[SDL_GAMEPAD_BUTTON_START], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_START), [&](bool d) { virtualPulseKey(SDL_SCANCODE_ESCAPE, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_BACK, m_state.buttonState[SDL_GAMEPAD_BUTTON_BACK], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_BACK), [&](bool d) { virtualPulseKey(SDL_SCANCODE_SPACE, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_LEFT, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_LEFT], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_LEFT), [&](bool d) { virtualPulseKey(SDL_SCANCODE_1, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_UP, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_UP], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP), [&](bool d) { virtualPulseKey(SDL_SCANCODE_2, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_RIGHT, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_RIGHT], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT), [&](bool d) { virtualPulseKey(SDL_SCANCODE_3, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_DOWN, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_DOWN], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN), [&](bool d) { virtualPulseKey(SDL_SCANCODE_4, d); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_LEFT_STICK, m_state.buttonState[SDL_GAMEPAD_BUTTON_LEFT_STICK], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_LEFT_STICK), [&](bool d) { if (d) TheMessageStream->appendMessage(GameMessage::MSG_META_SELECT_NEXT_IDLE_WORKER); }); - handleGamepadButton(SDL_GAMEPAD_BUTTON_RIGHT_STICK, m_state.buttonState[SDL_GAMEPAD_BUTTON_RIGHT_STICK], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_RIGHT_STICK), [&](bool d) { if (d) TheMessageStream->appendMessage(GameMessage::MSG_META_VIEW_COMMAND_CENTER); }); + handleGamepadButton( + SDL_GAMEPAD_BUTTON_SOUTH, + m_state.buttonState[SDL_GAMEPAD_BUTTON_SOUTH], + SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_SOUTH), + [&](bool d) { virtualPulseMouse(SDL_BUTTON_LEFT, d); } + ); + handleGamepadButton( + SDL_GAMEPAD_BUTTON_EAST, + m_state.buttonState[SDL_GAMEPAD_BUTTON_EAST], + SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_EAST), + [&](bool d) { virtualPulseMouse(SDL_BUTTON_RIGHT, d); } + ); + handleGamepadButton( + SDL_GAMEPAD_BUTTON_WEST, + m_state.buttonState[SDL_GAMEPAD_BUTTON_WEST], + SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_WEST), + [&](bool d) { virtualPulseKey(SDL_SCANCODE_A, d); } + ); + handleGamepadButton( + SDL_GAMEPAD_BUTTON_NORTH, + m_state.buttonState[SDL_GAMEPAD_BUTTON_NORTH], + SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_NORTH), + [&](bool d) { if (d) TheMessageStream->appendMessage(GameMessage::MSG_META_STOP); } + ); + handleGamepadButton( + SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, + m_state.buttonState[SDL_GAMEPAD_BUTTON_LEFT_SHOULDER], + SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), + [&](bool d) { virtualPulseKey(SDL_SCANCODE_Q, d); } + ); + handleGamepadButton( + SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, + m_state.buttonState[SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER], + SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), + [&](bool d) { virtualPulseKey(SDL_SCANCODE_LSHIFT, d); } + ); + handleGamepadButton( + SDL_GAMEPAD_BUTTON_START, + m_state.buttonState[SDL_GAMEPAD_BUTTON_START], + SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_START), + [&](bool d) { virtualPulseKey(SDL_SCANCODE_ESCAPE, d); } + ); + handleGamepadButton( + SDL_GAMEPAD_BUTTON_BACK, + m_state.buttonState[SDL_GAMEPAD_BUTTON_BACK], + SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_BACK), + [&](bool d) { virtualPulseKey(SDL_SCANCODE_SPACE, d); } + ); + handleGamepadButton( + SDL_GAMEPAD_BUTTON_DPAD_LEFT, + m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_LEFT], + SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_LEFT), + [&](bool d) { virtualPulseKey(SDL_SCANCODE_1, d); } + ); + handleGamepadButton( + SDL_GAMEPAD_BUTTON_DPAD_UP, + m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_UP], + SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP), + [&](bool d) { virtualPulseKey(SDL_SCANCODE_2, d); } + ); + handleGamepadButton( + SDL_GAMEPAD_BUTTON_DPAD_RIGHT, + m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_RIGHT], + SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT), + [&](bool d) { virtualPulseKey(SDL_SCANCODE_3, d); } + ); + handleGamepadButton( + SDL_GAMEPAD_BUTTON_DPAD_DOWN, + m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_DOWN], + SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN), + [&](bool d) { virtualPulseKey(SDL_SCANCODE_4, d); } + ); + handleGamepadButton( + SDL_GAMEPAD_BUTTON_LEFT_STICK, + m_state.buttonState[SDL_GAMEPAD_BUTTON_LEFT_STICK], + SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_LEFT_STICK), + [&](bool d) { if (d) TheMessageStream->appendMessage(GameMessage::MSG_META_SELECT_NEXT_IDLE_WORKER); } + ); + handleGamepadButton( + SDL_GAMEPAD_BUTTON_RIGHT_STICK, + m_state.buttonState[SDL_GAMEPAD_BUTTON_RIGHT_STICK], + SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_RIGHT_STICK), + [&](bool d) { if (d) TheMessageStream->appendMessage(GameMessage::MSG_META_VIEW_COMMAND_CENTER); } + ); } diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index 2d786e94526..50b241cfa06 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -65,7 +65,7 @@ #if RTS_SDL3_ENABLE #include - #include "SDL3GameEngine.h" + #include "SDL3Device/Common/SDL3GameEngine.h" #include "GameClient/Keyboard.h" SDL_Window* TheSDL3Window = nullptr; #else