From e406fca9c2a532718cc19cc1634aa47ba955fe1e 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/34] 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/GameEngine/Include/GameClient/Display.h | 5 + 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 ++++++ .../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 39062e3fbe6..5bd20e2bd7d 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/GameEngine/Include/GameClient/Display.h b/Core/GameEngine/Include/GameClient/Display.h index 9fc937bfaa2..895945000d8 100644 --- a/Core/GameEngine/Include/GameClient/Display.h +++ b/Core/GameEngine/Include/GameClient/Display.h @@ -90,6 +90,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 ); ///. +*/ + +#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/GameEngineDevice/Include/W3DDevice/GameClient/W3DGameClient.h b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DGameClient.h index fa06cf092f7..dbdf23b3028 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DGameClient.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DGameClient.h @@ -46,18 +46,26 @@ #include "W3DDevice/GameClient/W3DGameFont.h" #include "W3DDevice/GameClient/W3DDisplayStringManager.h" #include "VideoDevice/Bink/BinkVideoPlayer.h" +#include "W3DDevice/GameClient/W3DSnow.h" + #ifdef RTS_HAS_FFMPEG #include "VideoDevice/FFmpeg/FFmpegVideoPlayer.h" #endif + +#if SAGE_USE_SDL3 +#include "SDL3Device/GameClient/SDL3Input.h" +extern SDL_Window* TheSDL3Window; +#else +#include "W3DDevice/GameClient/W3DMouse.h" #include "Win32Device/GameClient/Win32DIKeyboard.h" #include "Win32Device/GameClient/Win32DIMouse.h" #include "Win32Device/GameClient/Win32Mouse.h" -#include "W3DDevice/GameClient/W3DMouse.h" -#include "W3DDevice/GameClient/W3DSnow.h" +extern Win32Mouse *TheWin32Mouse; +#endif -class ThingTemplate; -extern Win32Mouse *TheWin32Mouse; + +class ThingTemplate; /////////////////////////////////////////////////////////////////////////////// // PROTOTYPES ///////////////////////////////////////////////////////////////// @@ -126,11 +134,23 @@ class W3DGameClient : public GameClient }; -inline Keyboard *W3DGameClient::createKeyboard() { return NEW DirectInputKeyboard; } +inline Keyboard *W3DGameClient::createKeyboard() +{ +#if SAGE_USE_SDL3 + return NEW SDL3Keyboard; +#else + return NEW DirectInputKeyboard; +#endif +} + inline Mouse *W3DGameClient::createMouse() { +#if SAGE_USE_SDL3 + return NEW SDL3Mouse(TheSDL3Window); +#else //return new DirectInputMouse; Win32Mouse * mouse = NEW W3DMouse; TheWin32Mouse = mouse; ///< global cheat for the WndProc() return mouse; +#endif } diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index 0d37cab5933..ad174146315 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -60,10 +60,19 @@ #include "GameLogic/GameLogic.h" ///< @todo for demo, remove #include "GameClient/Mouse.h" #include "GameClient/IMEManager.h" -#include "Win32Device/GameClient/Win32Mouse.h" -#include "Win32Device/Common/Win32GameEngine.h" #include "Common/version.h" #include "BuildVersion.h" + +#if SAGE_USE_SDL3 + #include + #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 @@ -862,14 +874,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(); @@ -879,18 +900,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 @@ -964,6 +1045,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; @@ -972,5 +1058,5 @@ GameEngine *CreateGameEngine() engine->setIsActive(isWinMainActive); return engine; - +#endif } diff --git a/cmake/config-build.cmake b/cmake/config-build.cmake index 7973f5a1f03..b07d9204b13 100644 --- a/cmake/config-build.cmake +++ b/cmake/config-build.cmake @@ -10,6 +10,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 17f18169a8a9385993c032496db711225a58ccec 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/34] 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 bd9921f83711c8a7c6eee89c009dd39d1bda4b87 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/34] 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 5bd20e2bd7d..b8851e0a5fd 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. @@ -68,6 +67,10 @@ include(cmake/gamespy.cmake) include(cmake/lzhl.cmake) include(cmake/stb.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 86f996b549679ed88039dab0f4b7107b0f4b41b3 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/34] 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 43beb414beab52c10e181fb6559ac828925af047 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/34] 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 96d0b3d6c994129edee7fd4958d7e4bcf92056e3 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/34] 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 47b934c4bfa3a1da018979d99ecf3b5424c8ef0d 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/34] 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 8cfcef4f055ad0fdc28191e827265a29e4a7237c 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/34] added vcpkg --- Core/GameEngineDevice/CMakeLists.txt | 5 +- cmake/sdl3.cmake | 131 ++++++++------------------- vcpkg-lock.json | 109 ++++++++++++---------- vcpkg.json | 28 ++++-- 4 files changed, 121 insertions(+), 152 deletions(-) diff --git a/Core/GameEngineDevice/CMakeLists.txt b/Core/GameEngineDevice/CMakeLists.txt index bf53515c68d..3e576c458de 100644 --- a/Core/GameEngineDevice/CMakeLists.txt +++ b/Core/GameEngineDevice/CMakeLists.txt @@ -246,9 +246,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 9ce3c6667c3..d84f17cf0f7 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,9 +1,21 @@ { - "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", - "builtin-baseline": "b02e341c927f16d991edbd915d8ea43eac52096c", - "dependencies": [ - "zlib", - "ffmpeg", - "stb" - ] - } \ No newline at end of file + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", + "builtin-baseline": "b02e341c927f16d991edbd915d8ea43eac52096c", + "dependencies": [ + "ffmpeg", + "sdl3", + "sdl3-image", + "stb", + "zlib" + ], + "overrides": [ + { + "name": "sdl3", + "version": "3.4.10" + }, + { + "name": "sdl3-image", + "version": "3.4.4" + } + ] +} From ebab5858e2c5597b7fd814503453b3db25542b86 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/34] vcpkg fix --- vcpkg-configuration.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) 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 From 2774a7873745c076bba3f990d4a9020a30de7461 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/34] 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 186eaf74e8f3064cc0904199b0b666fcc250636a 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/34] 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 6b3bec107b28bb5c4aba249115778fb3c5dac720 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/34] 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 3e576c458de..7bbaefb2880 100644 --- a/Core/GameEngineDevice/CMakeLists.txt +++ b/Core/GameEngineDevice/CMakeLists.txt @@ -202,8 +202,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 d7758169e24136019666d5aa7cb9596ce2bbe69f 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/34] 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 ad174146315..f7570d8b116 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -903,7 +903,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 40d480cb606a170f86efc530f9c974f574e09f49 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/34] 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 a959ecbd7ad19ce5024db9d9ccb55a159788c011 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/34] 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 ea63a098b2f40f38a021307e14689b2798035046 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/34] 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 13f1f66d7cbc94792da129b3349fb9e27be0a566 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/34] 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 94d3f4bff338e5b5a9e8746e24a848da31327cde 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/34] 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 c75d6326d7abee948adb4f7b17fc60a7887406b6 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/34] 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 4b87f56200976f4d52bfde3f2a75efea4439f266 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/34] updated SDL to latest version removed most ANI cursor hacks --- .../SDL3Device/GameClient/SDL3Cursor.cpp | 106 +++++++----------- cmake/sdl3.cmake | 8 +- vcpkg-configuration.json | 2 +- 3 files changed, 45 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" From 2a55d239b1d129ef3be7dd82f6c72d7b42b493ea 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/34] 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 b8851e0a5fd..ecf122f630d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,7 +67,7 @@ include(cmake/gamespy.cmake) include(cmake/lzhl.cmake) include(cmake/stb.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 895945000d8..d025e9a73d4 100644 --- a/Core/GameEngine/Include/GameClient/Display.h +++ b/Core/GameEngine/Include/GameClient/Display.h @@ -91,7 +91,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 7bbaefb2880..55e7e611df5 100644 --- a/Core/GameEngineDevice/CMakeLists.txt +++ b/Core/GameEngineDevice/CMakeLists.txt @@ -198,7 +198,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 @@ -246,7 +246,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 853376096994489bb41bb413da4b11ff9c07271f 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/34] 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 25d8a4214caf8e390a97d3476408c10c15a55e22 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/34] 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 57bcdf9af50c69774756b0848185b4615da77787 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/34] 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 5e4c4d7887c2ab4fe58f9b4b9f3c40dd8dd6dcec 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/34] 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 ++++---- 4 files changed, 10 insertions(+), 8 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", From 808f8d0e0d7cd1016333026ea42ca1d8a6772327 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/34] 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 ecf122f630d..9c42722fa09 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,7 +67,7 @@ include(cmake/gamespy.cmake) include(cmake/lzhl.cmake) include(cmake/stb.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 55e7e611df5..a59b2ef71a8 100644 --- a/Core/GameEngineDevice/CMakeLists.txt +++ b/Core/GameEngineDevice/CMakeLists.txt @@ -198,7 +198,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 @@ -246,7 +246,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 81bf8f93619b94b0ad0bf22f39a608e16b44bfcb 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/34] 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 a59b2ef71a8..ac041375d07 100644 --- a/Core/GameEngineDevice/CMakeLists.txt +++ b/Core/GameEngineDevice/CMakeLists.txt @@ -200,10 +200,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 From 785d2133ae399e46e6ab9e1ea4af7a4210945ae6 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Thu, 23 Jul 2026 08:24:14 +0200 Subject: [PATCH 28/34] style/fix: address PR review feedback on SDL3 input and engine device --- .../SDL3Device/Common/SDL3GameEngine.cpp | 6 +- .../SDL3Device/GameClient/SDL3Input.cpp | 58 +++++++++---------- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/Core/GameEngineDevice/Source/SDL3Device/Common/SDL3GameEngine.cpp b/Core/GameEngineDevice/Source/SDL3Device/Common/SDL3GameEngine.cpp index 94a61176d71..a67c819dff9 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/Common/SDL3GameEngine.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/Common/SDL3GameEngine.cpp @@ -359,13 +359,15 @@ FunctionLexicon* SDL3GameEngine::createFunctionLexicon() Radar* SDL3GameEngine::createRadar(Bool dummy) { - (void)dummy; + if (dummy) + return NEW RadarDummy; return NEW W3DRadar; } ParticleSystemManager* SDL3GameEngine::createParticleSystemManager(Bool dummy) { - (void)dummy; + if (dummy) + return NEW ParticleSystemManagerDummy; return NEW W3DParticleSystemManager; } diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp index a92cfd2f4ed..8f7a94312a2 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp @@ -66,10 +66,10 @@ void SDL3Mouse::init() { Mouse::init(); - m_inputMovesAbsolute = TRUE; + m_inputMovesAbsolute = true; // Show cursor by default - setVisibility(TRUE); + setVisibility(true); } void SDL3Mouse::reset() @@ -77,7 +77,7 @@ void SDL3Mouse::reset() Mouse::reset(); releaseCapture(); - setVisibility(TRUE); + setVisibility(true); } void SDL3Mouse::update() @@ -326,7 +326,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 * MOUSE_WHEEL_DELTA); break; } @@ -392,7 +392,7 @@ void SDL3Keyboard::init() { Keyboard::init(); } void SDL3Keyboard::reset() { Keyboard::reset(); } void SDL3Keyboard::update() { Keyboard::update(); } -Bool SDL3Keyboard::getCapsState() { return FALSE; } +Bool SDL3Keyboard::getCapsState() { return false; } void SDL3Keyboard::getKey(KeyboardIO* key) { @@ -675,9 +675,9 @@ SDL3InputManager::SDL3InputManager(SDL_Window* window) , m_keyNextFree(0) , m_keyNextGet(0) , m_gamepad(nullptr) - , m_precisionMode(FALSE) + , m_precisionMode(false) , m_lastUpdateTime(0) - , m_isQuitting(FALSE) + , m_isQuitting(false) { memset(m_mouseEvents, 0, sizeof(m_mouseEvents)); memset(m_keyEvents, 0, sizeof(m_keyEvents)); @@ -772,27 +772,27 @@ void SDL3InputManager::update() Bool SDL3InputManager::getNextMouseEvent(SDL_Event& outEvent) { if (m_mouseEvents[m_mouseNextGet].type == SDL_EVENT_FIRST) - return FALSE; + 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; + return true; } Bool SDL3InputManager::getNextKeyboardEvent(SDL_Event& outEvent) { if (m_keyEvents[m_keyNextGet].type == SDL_EVENT_FIRST) - return FALSE; + 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; + return true; } void SDL3InputManager::addMouseSDLEvent(const SDL_Event& event) @@ -954,25 +954,25 @@ void SDL3InputManager::processGamepadInput() SDL_GAMEPAD_BUTTON_INVALID, m_state.stickLeft, rx < -DEADZONE, - [&](bool d) { virtualPulseKey(SDL_SCANCODE_LEFT, d); } + [this](bool d) { virtualPulseKey(SDL_SCANCODE_LEFT, d); } ); handleGamepadButton( SDL_GAMEPAD_BUTTON_INVALID, m_state.stickRight, rx > DEADZONE, - [&](bool d) { virtualPulseKey(SDL_SCANCODE_RIGHT, d); } + [this](bool d) { virtualPulseKey(SDL_SCANCODE_RIGHT, d); } ); handleGamepadButton( SDL_GAMEPAD_BUTTON_INVALID, m_state.stickUp, ry < -DEADZONE, - [&](bool d) { virtualPulseKey(SDL_SCANCODE_UP, d); } + [this](bool d) { virtualPulseKey(SDL_SCANCODE_UP, d); } ); handleGamepadButton( SDL_GAMEPAD_BUTTON_INVALID, m_state.stickDown, ry > DEADZONE, - [&](bool d) { virtualPulseKey(SDL_SCANCODE_DOWN, d); } + [this](bool d) { virtualPulseKey(SDL_SCANCODE_DOWN, d); } ); // 3. BUTTONS & D-PAD (Actions & Hotkeys) @@ -980,84 +980,84 @@ void SDL3InputManager::processGamepadInput() 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); } + [this](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); } + [this](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); } + [this](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); } + [this](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); } + [this](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); } + [this](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); } + [this](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); } + [this](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); } + [this](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); } + [this](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); } + [this](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); } + [this](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); } + [this](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); } + [this](bool d) { if (d) TheMessageStream->appendMessage(GameMessage::MSG_META_VIEW_COMMAND_CENTER); } ); } From 78f7e15ae95b85aabceb6d83d81c25935d45cb8e Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Thu, 23 Jul 2026 09:08:22 +0200 Subject: [PATCH 29/34] Refactor entry points: extract AppMain lifecycle and isolate SDL3Main --- Core/Main/AppMain.cpp | 197 ++++++++++++++++++++++++++++ Core/Main/AppMain.h | 60 +++++++++ Core/Main/SDL3Main.cpp | 150 +++++++++++++++++++++ Core/Main/SDL3Main.h | 33 +++++ Generals/Code/Main/CMakeLists.txt | 17 ++- Generals/Code/Main/WinMain.cpp | 70 ++-------- GeneralsMD/Code/Main/CMakeLists.txt | 17 ++- GeneralsMD/Code/Main/WinMain.cpp | 172 ++---------------------- 8 files changed, 494 insertions(+), 222 deletions(-) create mode 100644 Core/Main/AppMain.cpp create mode 100644 Core/Main/AppMain.h create mode 100644 Core/Main/SDL3Main.cpp create mode 100644 Core/Main/SDL3Main.h diff --git a/Core/Main/AppMain.cpp b/Core/Main/AppMain.cpp new file mode 100644 index 00000000000..083f3a62a84 --- /dev/null +++ b/Core/Main/AppMain.cpp @@ -0,0 +1,197 @@ +/* +** 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 . +*/ + +#include "AppMain.h" + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include + +#include "Lib/BaseType.h" +#include "Common/CommandLine.h" +#include "Common/CriticalSection.h" +#include "Common/Debug.h" +#include "Common/GameEngine.h" +#include "Common/GameMemory.h" +#include "Common/GlobalData.h" +#include "Common/StackDump.h" +#include "Common/version.h" +#include "BuildVersion.h" +#include "GeneratedVersion.h" +#include "GameClient/ClientInstance.h" + +#include "Common/Registry.h" + +#ifdef RTS_ENABLE_CRASHDUMP +#include "Common/MiniDumper.h" +#endif + +// Global game data paths & prefixes required by GameText +const Char *g_strFile = "data\\Generals.str"; +const Char *g_csfFile = "data\\%s\\Generals.csf"; +const char *gAppPrefix = ""; + +class Win32Mouse; +Win32Mouse *TheWin32Mouse = nullptr; + +// Critical sections for memory manager and string systems +static CriticalSection critSec1, critSec2, critSec3, critSec4, critSec5; + +namespace AppMain +{ + +Bool initBeforeWindow() +{ + TheAsciiStringCriticalSection = &critSec1; + TheUnicodeStringCriticalSection = &critSec2; + TheDmaCriticalSection = &critSec3; + TheMemoryPoolCriticalSection = &critSec4; + TheDebugLogCriticalSection = &critSec5; + + // Initialize memory manager early + initMemoryManager(); + + // Set working directory to executable folder + Char buffer[_MAX_PATH]; + GetModuleFileName(nullptr, buffer, sizeof(buffer)); + if (Char* pEnd = strrchr(buffer, '\\')) + { + *pEnd = 0; + } + ::SetCurrentDirectory(buffer); + +#ifdef RTS_DEBUG + int tmpFlag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + tmpFlag |= (_CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF); + tmpFlag &= ~_CRTDBG_CHECK_CRT_DF; + _CrtSetDbgFlag(tmpFlag); +#endif + + CommandLine::parseCommandLineForStartup(); + +#ifdef RTS_ENABLE_CRASHDUMP + MiniDumper::initMiniDumper(TheGlobalData->getPath_UserData()); +#endif + + return true; +} + +Bool initAfterWindow() +{ + // Setup version info + TheVersion = NEW Version; + TheVersion->setVersion( + VERSION_MAJOR, VERSION_MINOR, VERSION_BUILDNUM, VERSION_LOCALBUILDNUM, + AsciiString(VERSION_BUILDUSER), AsciiString(VERSION_BUILDLOC), + AsciiString(__TIME__), AsciiString(__DATE__) + ); + + // Single instance mutex check + if (!rts::ClientInstance::initialize()) + { + HWND ccwindow = FindWindow(rts::ClientInstance::getFirstInstanceName(), nullptr); + if (ccwindow) + { + SetForegroundWindow(ccwindow); + ShowWindow(ccwindow, SW_RESTORE); + } + + DEBUG_LOG(("Generals is already running...Bail!")); + delete TheVersion; + TheVersion = nullptr; + shutdownMemoryManager(); + return false; + } + DEBUG_LOG(("Create Generals Mutex okay.")); + + return true; +} + +static Bool s_isAppActive = false; + +void setAppActive(Bool active) +{ + s_isAppActive = active; + if (TheGameEngine) + { + TheGameEngine->setIsActive(active); + } +} + +Bool isAppActive() +{ + return s_isAppActive; +} + +void getInitialWindowBounds(Int& outWidth, Int& outHeight) +{ + outWidth = (TheGlobalData && TheGlobalData->m_xResolution > 0) ? TheGlobalData->m_xResolution : 800; + outHeight = (TheGlobalData && TheGlobalData->m_yResolution > 0) ? TheGlobalData->m_yResolution : 600; +} + +Int run() +{ + return GameMain(); +} + +void getSplashFilePath(Char* outBuffer, UnsignedInt bufferSize) +{ + if (!outBuffer || bufferSize == 0) + return; + + const char* fileName = "Install_Final.bmp"; + snprintf(outBuffer, bufferSize, "Data/%s/%s", GetRegistryLanguage().str(), fileName); + FILE* fileImage = fopen(outBuffer, "rb"); + if (fileImage) + { + fclose(fileImage); + } + else + { + snprintf(outBuffer, bufferSize, "%s", fileName); + } +} + +void shutdown() +{ + delete TheVersion; + TheVersion = nullptr; + +#ifdef MEMORYPOOL_DEBUG + TheMemoryPoolFactory->debugMemoryReport(REPORT_POOLINFO | REPORT_POOL_OVERFLOW | REPORT_SIMPLE_LEAKS, 0, 0); +#endif +#if defined(RTS_DEBUG) + TheMemoryPoolFactory->memoryPoolUsageReport("AAAMemStats"); +#endif + + shutdownMemoryManager(); + +#ifdef RTS_ENABLE_CRASHDUMP + MiniDumper::shutdownMiniDumper(); +#endif + + TheAsciiStringCriticalSection = nullptr; + TheUnicodeStringCriticalSection = nullptr; + TheDmaCriticalSection = nullptr; + TheMemoryPoolCriticalSection = nullptr; + TheDebugLogCriticalSection = nullptr; +} + +} // namespace AppMain diff --git a/Core/Main/AppMain.h b/Core/Main/AppMain.h new file mode 100644 index 00000000000..3dcd4d214e9 --- /dev/null +++ b/Core/Main/AppMain.h @@ -0,0 +1,60 @@ +/* +** 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 . +*/ + +#pragma once + +#include "Lib/BaseType.h" + +// Forward declarations +class GameEngine; + +/** + * Shared application lifecycle management for both Win32 and SDL3 entry points. + */ +namespace AppMain +{ + /** Initialize core subsystems before window creation (memory, working dir, debug flags, command line) */ + Bool initBeforeWindow(); + + /** Initialize subsystems after window creation (version info, single-instance mutex check) */ + Bool initAfterWindow(); + + /** Execute the main game loop */ + Int run(); + + /** Get initial window dimensions from GlobalData or defaults (800x600) */ + void getInitialWindowBounds(Int& outWidth, Int& outHeight); + + /** Update application focus active state across backends */ + void setAppActive(Bool active); + + /** Get current application focus active state */ + Bool isAppActive(); + + /** Get the localized or fallback splash screen bitmap file path */ + void getSplashFilePath(Char* outBuffer, UnsignedInt bufferSize); + + /** Shutdown core subsystems (version, minidump, memory manager) */ + void shutdown(); +} + +/** Engine creation helper */ +GameEngine* CreateGameEngine(); + +/** Main engine loop */ +Int GameMain(); diff --git a/Core/Main/SDL3Main.cpp b/Core/Main/SDL3Main.cpp new file mode 100644 index 00000000000..0815b982249 --- /dev/null +++ b/Core/Main/SDL3Main.cpp @@ -0,0 +1,150 @@ +/* +** 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 . +*/ + +#include "SDL3Main.h" + +#include +#include +#include + +#include "AppMain.h" +#include "Lib/BaseType.h" +#include "Common/CommandLine.h" +#include "Common/Debug.h" +#include "Common/GlobalData.h" +#include "Common/Registry.h" +#include "SDL3Device/Common/SDL3GameEngine.h" + +SDL_Window* TheSDL3Window = nullptr; +#ifdef _WIN32 +HINSTANCE ApplicationHInstance = nullptr; +HWND ApplicationHWnd = nullptr; +#endif + +static SDL_Surface* gLoadScreenSurface = nullptr; + +GameEngine* CreateGameEngine() +{ + SDL3GameEngine* engine = NEW SDL3GameEngine; + engine->setIsActive(true); + return engine; +} + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + + Int exitcode = 1; + + if (!AppMain::initBeforeWindow()) + { + return exitcode; + } + +#ifdef _WIN32 + ApplicationHInstance = GetModuleHandle(nullptr); +#endif + + // Load splash screen surface + char filePath[512]; + AppMain::getSplashFilePath(filePath, sizeof(filePath)); + gLoadScreenSurface = SDL_LoadBMP(filePath); + + if (!TheGlobalData->m_headless) + { + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS | SDL_INIT_GAMEPAD)) + { + DEBUG_LOG(("SDL_Init failed: %s", SDL_GetError())); + AppMain::shutdown(); + return exitcode; + } + + Uint32 flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE; + if (!TheGlobalData->m_windowed) + { + flags |= SDL_WINDOW_FULLSCREEN; + } + + Int startWidth, startHeight; + AppMain::getInitialWindowBounds(startWidth, startHeight); + + TheSDL3Window = SDL_CreateWindow("Command & Conquer Generals", startWidth, startHeight, flags); + if (!TheSDL3Window) + { + DEBUG_LOG(("SDL_CreateWindow failed: %s", SDL_GetError())); + SDL_Quit(); + AppMain::shutdown(); + return exitcode; + } + +#ifdef _WIN32 + SDL_PropertiesID props = SDL_GetWindowProperties(TheSDL3Window); + ApplicationHWnd = (HWND)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr); +#endif + + SDL_ShowWindow(TheSDL3Window); + AppMain::setAppActive(true); + + // Render splash screen onto SDL window surface + 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, nullptr, screen, &destRect, SDL_SCALEMODE_LINEAR); + SDL_UpdateWindowSurface(TheSDL3Window); + } + } + } + + if (gLoadScreenSurface != nullptr) + { + SDL_DestroySurface(gLoadScreenSurface); + gLoadScreenSurface = nullptr; + } + + if (!AppMain::initAfterWindow()) + { + if (TheSDL3Window) + { + SDL_DestroyWindow(TheSDL3Window); + TheSDL3Window = nullptr; + } + SDL_Quit(); + return exitcode; + } + + exitcode = AppMain::run(); + + if (TheSDL3Window) + { + SDL_DestroyWindow(TheSDL3Window); + TheSDL3Window = nullptr; + } + SDL_Quit(); + + AppMain::shutdown(); + + return exitcode; +} diff --git a/Core/Main/SDL3Main.h b/Core/Main/SDL3Main.h new file mode 100644 index 00000000000..c349f9b3633 --- /dev/null +++ b/Core/Main/SDL3Main.h @@ -0,0 +1,33 @@ +/* +** 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 . +*/ + +#pragma once + +#include + +#ifdef _WIN32 +#include +#endif + +// Global SDL3 window pointer +extern SDL_Window* TheSDL3Window; + +#ifdef _WIN32 +// Native HWND export for DirectX compatibility +extern HWND ApplicationHWnd; +#endif diff --git a/Generals/Code/Main/CMakeLists.txt b/Generals/Code/Main/CMakeLists.txt index f40c7434bdf..45c7c0c5e36 100644 --- a/Generals/Code/Main/CMakeLists.txt +++ b/Generals/Code/Main/CMakeLists.txt @@ -10,6 +10,7 @@ endif() target_link_libraries(g_generals PRIVATE binkstub comctl32 + corei_main d3d8 d3dx8 dinput8 @@ -65,10 +66,22 @@ target_include_directories(g_generals PRIVATE ) target_sources(g_generals PRIVATE - WinMain.cpp - WinMain.h + ${CMAKE_SOURCE_DIR}/Core/Main/AppMain.cpp + ${CMAKE_SOURCE_DIR}/Core/Main/AppMain.h ) +if(RTS_BUILD_OPTION_SDL3) + target_sources(g_generals PRIVATE + ${CMAKE_SOURCE_DIR}/Core/Main/SDL3Main.cpp + ${CMAKE_SOURCE_DIR}/Core/Main/SDL3Main.h + ) +else() + target_sources(g_generals PRIVATE + WinMain.cpp + WinMain.h + ) +endif() + # RC files are optional for MinGW builds # Icon: LoadIcon() handles missing resource gracefully (uses default system icon) # Manifest: DPI awareness metadata (nice to have but not essential) diff --git a/Generals/Code/Main/WinMain.cpp b/Generals/Code/Main/WinMain.cpp index c8e9bb9961d..a367369306f 100644 --- a/Generals/Code/Main/WinMain.cpp +++ b/Generals/Code/Main/WinMain.cpp @@ -40,6 +40,7 @@ #include // USER INCLUDES ////////////////////////////////////////////////////////////// +#include "AppMain.h" #include "WinMain.h" #include "Lib/BaseType.h" #include "Common/CommandLine.h" @@ -59,8 +60,8 @@ #include "GameLogic/GameLogic.h" ///< @todo for demo, remove #include "GameClient/Mouse.h" #include "GameClient/IMEManager.h" -#include "Win32Device/GameClient/Win32Mouse.h" #include "Win32Device/Common/Win32GameEngine.h" +#include "Win32Device/GameClient/Win32Mouse.h" #include "Common/version.h" #include "BuildVersion.h" #include "GeneratedVersion.h" @@ -469,9 +470,7 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, // paths that take care of that. isWinMainActive = (BOOL) wParam; - - if (TheGameEngine) - TheGameEngine->setIsActive(isWinMainActive); + AppMain::setAppActive(isWinMainActive); if (isWinMainActive) { @@ -841,19 +840,13 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, // WWDebug_Install_Message_Handler(WWDebug_Message_Callback); // WWDebug_Install_Assert_Handler(WWAssert_Callback); - // [SKB: Jun 24 2003 @ 1:50pm] : - // Force to be loaded from a file, not a resource so same exe can be used in germany and retail. - gLoadScreenBitmap = (HBITMAP)LoadImage(hInstance, "Install_Final.bmp", IMAGE_BITMAP, 0, 0, LR_SHARED|LR_LOADFROMFILE); + char filePath[_MAX_PATH]; + AppMain::getSplashFilePath(filePath, sizeof(filePath)); + gLoadScreenBitmap = (HBITMAP)LoadImage(hInstance, filePath, IMAGE_BITMAP, 0, 0, LR_SHARED | LR_LOADFROMFILE); - CommandLine::parseCommandLineForStartup(); - -#ifdef RTS_ENABLE_CRASHDUMP - // Initialize minidump facilities - requires TheGlobalData so performed after parseCommandLineForStartup - MiniDumper::initMiniDumper(TheGlobalData->getPath_UserData()); -#endif - // register windows class and create application window if(!TheGlobalData->m_headless && initializeAppWindows(hInstance, nCmdShow, TheGlobalData->m_windowed) == false) { + AppMain::shutdown(); return exitcode; } @@ -865,55 +858,16 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, gLoadScreenBitmap = nullptr; } - - // BGC - initialize COM - // OleInitialize(nullptr); - - - - // Set up version info - TheVersion = NEW Version; - TheVersion->setVersion(VERSION_MAJOR, VERSION_MINOR, VERSION_BUILDNUM, VERSION_LOCALBUILDNUM, - AsciiString(VERSION_BUILDUSER), AsciiString(VERSION_BUILDLOC), - AsciiString(__TIME__), AsciiString(__DATE__)); - - // TheSuperHackers @refactor The instance mutex now lives in its own class. - - if (!rts::ClientInstance::initialize()) + if (!AppMain::initAfterWindow()) { - HWND ccwindow = FindWindow(rts::ClientInstance::getFirstInstanceName(), nullptr); - if (ccwindow) - { - SetForegroundWindow(ccwindow); - ShowWindow(ccwindow, SW_RESTORE); - } - - DEBUG_LOG(("Generals is already running...Bail!")); - delete TheVersion; - TheVersion = nullptr; - shutdownMemoryManager(); + AppMain::shutdown(); return exitcode; } - DEBUG_LOG(("Create Generals Mutex okay.")); - DEBUG_LOG(("CRC message is %d", GameMessage::MSG_LOGIC_CRC)); // run the game main loop - exitcode = GameMain(); - - delete TheVersion; - TheVersion = nullptr; - - #ifdef MEMORYPOOL_DEBUG - TheMemoryPoolFactory->debugMemoryReport(REPORT_POOLINFO | REPORT_POOL_OVERFLOW | REPORT_SIMPLE_LEAKS, 0, 0); - #endif - #if defined(RTS_DEBUG) - TheMemoryPoolFactory->memoryPoolUsageReport("AAAMemStats"); - #endif - - shutdownMemoryManager(); + exitcode = AppMain::run(); - // BGC - shut down COM - // OleUninitialize(); + AppMain::shutdown(); } catch (...) { @@ -942,7 +896,7 @@ GameEngine *CreateGameEngine() engine = NEW Win32GameEngine; //game engine may not have existed when app got focus so make sure it //knows about current focus state. - engine->setIsActive(isWinMainActive); + engine->setIsActive(AppMain::isAppActive()); return engine; diff --git a/GeneralsMD/Code/Main/CMakeLists.txt b/GeneralsMD/Code/Main/CMakeLists.txt index 0aa9b6df91d..29c1673409c 100644 --- a/GeneralsMD/Code/Main/CMakeLists.txt +++ b/GeneralsMD/Code/Main/CMakeLists.txt @@ -12,6 +12,7 @@ target_link_libraries(z_generals PRIVATE comctl32 core_debug core_profile_legacy + corei_main d3d8 d3dx8 dinput8 @@ -55,10 +56,22 @@ target_include_directories(z_generals PRIVATE ) target_sources(z_generals PRIVATE - WinMain.cpp - WinMain.h + ${CMAKE_SOURCE_DIR}/Core/Main/AppMain.cpp + ${CMAKE_SOURCE_DIR}/Core/Main/AppMain.h ) +if(RTS_BUILD_OPTION_SDL3) + target_sources(z_generals PRIVATE + ${CMAKE_SOURCE_DIR}/Core/Main/SDL3Main.cpp + ${CMAKE_SOURCE_DIR}/Core/Main/SDL3Main.h + ) +else() + target_sources(z_generals PRIVATE + WinMain.cpp + WinMain.h + ) +endif() + # RC files optional for MinGW builds # Icon: LoadIcon() handles missing resource gracefully (uses default system icon) # Manifest: DPI awareness metadata (nice to have but not essential) diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index 50b241cfa06..71591167ed7 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -40,6 +40,7 @@ #include // USER INCLUDES ////////////////////////////////////////////////////////////// +#include "AppMain.h" #include "WinMain.h" #include "Lib/BaseType.h" #include "Common/CommandLine.h" @@ -62,16 +63,8 @@ #include "GameClient/IMEManager.h" #include "Common/version.h" #include "BuildVersion.h" - -#if RTS_SDL3_ENABLE - #include - #include "SDL3Device/Common/SDL3GameEngine.h" - #include "GameClient/Keyboard.h" - SDL_Window* TheSDL3Window = nullptr; -#else - #include "Win32Device/Common/Win32GameEngine.h" - #include "Win32Device/GameClient/Win32Mouse.h" -#endif +#include "Win32Device/Common/Win32GameEngine.h" +#include "Win32Device/GameClient/Win32Mouse.h" #include "GeneratedVersion.h" #include "resource.h" @@ -483,9 +476,7 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, // paths that take care of that. isWinMainActive = (BOOL) wParam; - - if (TheGameEngine) - TheGameEngine->setIsActive(isWinMainActive); + AppMain::setAppActive(isWinMainActive); if (isWinMainActive) { @@ -862,167 +853,34 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, // Force "splash image" to be loaded from a file, not a resource so same exe can be used in different localizations. -#if defined(RTS_DEBUG) || defined RTS_PROFILE_LEGACY - - // check both localized directory and root dir char filePath[_MAX_PATH]; - const char *fileName = "Install_Final.bmp"; - static const char *localizedPathFormat = "Data/%s/"; - sprintf(filePath,localizedPathFormat, GetRegistryLanguage().str()); - strlcat(filePath, fileName, ARRAY_SIZE(filePath)); - FILE *fileImage = fopen(filePath, "r"); - if (fileImage) { - fclose(fileImage); - gLoadScreenBitmap = (HBITMAP)LoadImage(hInstance, filePath, IMAGE_BITMAP, 0, 0, LR_SHARED|LR_LOADFROMFILE); -#if RTS_SDL3_ENABLE - gLoadScreenSurface = SDL_LoadBMP(filePath); -#endif - } - else { - gLoadScreenBitmap = (HBITMAP)LoadImage(hInstance, fileName, IMAGE_BITMAP, 0, 0, LR_SHARED|LR_LOADFROMFILE); -#if RTS_SDL3_ENABLE - 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 RTS_SDL3_ENABLE - gLoadScreenSurface = SDL_LoadBMP("Install_Final.bmp"); -#endif -#endif - - CommandLine::parseCommandLineForStartup(); -#ifdef RTS_ENABLE_CRASHDUMP - // Initialize minidump facilities - requires TheGlobalData so performed after parseCommandLineForStartup - MiniDumper::initMiniDumper(TheGlobalData->getPath_UserData()); -#endif - - // register windows class and create application window -#if RTS_SDL3_ENABLE - if (!TheGlobalData->m_headless) - { - 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; - } - - 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); - } - } + AppMain::getSplashFilePath(filePath, sizeof(filePath)); + gLoadScreenBitmap = (HBITMAP)LoadImage(hInstance, filePath, IMAGE_BITMAP, 0, 0, LR_SHARED | LR_LOADFROMFILE); - } -#else if(!TheGlobalData->m_headless && initializeAppWindows(hInstance, nCmdShow, TheGlobalData->m_windowed) == false) { + AppMain::shutdown(); return exitcode; } -#endif // save our application instance for future use ApplicationHInstance = hInstance; -#if RTS_SDL3_ENABLE - if (gLoadScreenSurface != nullptr) { - SDL_DestroySurface(gLoadScreenSurface); - gLoadScreenSurface = nullptr; - } -#else if (gLoadScreenBitmap != nullptr) { ::DeleteObject(gLoadScreenBitmap); gLoadScreenBitmap = nullptr; } -#endif - - - // BGC - initialize COM - // OleInitialize(nullptr); - - - // Set up version info - TheVersion = NEW Version; - TheVersion->setVersion(VERSION_MAJOR, VERSION_MINOR, VERSION_BUILDNUM, VERSION_LOCALBUILDNUM, - AsciiString(VERSION_BUILDUSER), AsciiString(VERSION_BUILDLOC), - AsciiString(__TIME__), AsciiString(__DATE__)); - - // TheSuperHackers @refactor The instance mutex now lives in its own class. - - if (!rts::ClientInstance::initialize()) + if (!AppMain::initAfterWindow()) { - HWND ccwindow = FindWindow(rts::ClientInstance::getFirstInstanceName(), nullptr); - if (ccwindow) - { - SetForegroundWindow(ccwindow); - ShowWindow(ccwindow, SW_RESTORE); - } - - DEBUG_LOG(("Generals is already running...Bail!")); - delete TheVersion; - TheVersion = nullptr; - shutdownMemoryManager(); + AppMain::shutdown(); return exitcode; } - DEBUG_LOG(("Create Generals Mutex okay.")); - - DEBUG_LOG(("CRC message is %d", GameMessage::MSG_LOGIC_CRC)); // run the game main loop - exitcode = GameMain(); - - delete TheVersion; - TheVersion = nullptr; - - #ifdef MEMORYPOOL_DEBUG - TheMemoryPoolFactory->debugMemoryReport(REPORT_POOLINFO | REPORT_POOL_OVERFLOW | REPORT_SIMPLE_LEAKS, 0, 0); - #endif - #if defined(RTS_DEBUG) - TheMemoryPoolFactory->memoryPoolUsageReport("AAAMemStats"); - #endif - - shutdownMemoryManager(); + exitcode = AppMain::run(); - // BGC - shut down COM - // OleUninitialize(); + AppMain::shutdown(); } catch (...) { @@ -1045,18 +903,12 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, //============================================================================= GameEngine *CreateGameEngine() { -#if RTS_SDL3_ENABLE - SDL3GameEngine *engine = NEW SDL3GameEngine; - engine->setIsActive(isWinMainActive); - return engine; -#else Win32GameEngine *engine; engine = NEW Win32GameEngine; //game engine may not have existed when app got focus so make sure it //knows about current focus state. - engine->setIsActive(isWinMainActive); + engine->setIsActive(AppMain::isAppActive()); return engine; -#endif } From 6640a477bfbab2e7d645d81efbe1118e10181819 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Thu, 23 Jul 2026 09:34:09 +0200 Subject: [PATCH 30/34] fix(build): correct include ordering in AppMain and SDL3Main for VC6 compatibility --- Core/Main/AppMain.cpp | 9 +++------ Core/Main/SDL3Main.cpp | 3 +-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Core/Main/AppMain.cpp b/Core/Main/AppMain.cpp index 083f3a62a84..391bf5e979f 100644 --- a/Core/Main/AppMain.cpp +++ b/Core/Main/AppMain.cpp @@ -16,15 +16,12 @@ ** along with this program. If not, see . */ +#include "Lib/BaseType.h" #include "AppMain.h" #define WIN32_LEAN_AND_MEAN #include -#include -#include #include - -#include "Lib/BaseType.h" #include "Common/CommandLine.h" #include "Common/CriticalSection.h" #include "Common/Debug.h" @@ -157,7 +154,7 @@ void getSplashFilePath(Char* outBuffer, UnsignedInt bufferSize) return; const char* fileName = "Install_Final.bmp"; - snprintf(outBuffer, bufferSize, "Data/%s/%s", GetRegistryLanguage().str(), fileName); + sprintf(outBuffer, "Data/%s/%s", GetRegistryLanguage().str(), fileName); FILE* fileImage = fopen(outBuffer, "rb"); if (fileImage) { @@ -165,7 +162,7 @@ void getSplashFilePath(Char* outBuffer, UnsignedInt bufferSize) } else { - snprintf(outBuffer, bufferSize, "%s", fileName); + sprintf(outBuffer, "%s", fileName); } } diff --git a/Core/Main/SDL3Main.cpp b/Core/Main/SDL3Main.cpp index 0815b982249..563339b326d 100644 --- a/Core/Main/SDL3Main.cpp +++ b/Core/Main/SDL3Main.cpp @@ -16,14 +16,13 @@ ** along with this program. If not, see . */ +#include "Lib/BaseType.h" #include "SDL3Main.h" -#include #include #include #include "AppMain.h" -#include "Lib/BaseType.h" #include "Common/CommandLine.h" #include "Common/Debug.h" #include "Common/GlobalData.h" From a19098f6874f0b3fe653a6ed02439c625362a04f Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Thu, 23 Jul 2026 19:47:20 +0200 Subject: [PATCH 31/34] fix(sdl3): address PR review feedback on SDL3 input and app lifecycle - Fix symbol collisions for g_strFile, g_csfFile, gAppPrefix, and TheWin32Mouse in WinMain - Call AppMain::initBeforeWindow() in WinMain prior to accessing splash screen and TheGlobalData - Use timeGetTime() for key down timestamping in SDL3Input to match Keyboard::checkKeyRepeat() - Release active synthetic inputs and reset state on gamepad close in SDL3InputManager - Read live CapsLock toggle state from SDL via SDL_GetModState() in getCapsState() Co-authored-by: Bobby Battista --- .../SDL3Device/GameClient/SDL3Input.cpp | 29 +++++++++++++++++-- Generals/Code/Main/WinMain.cpp | 13 ++++++--- GeneralsMD/Code/Main/WinMain.cpp | 12 +++++--- 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp index 8f7a94312a2..69f9d99b8af 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp @@ -392,7 +392,10 @@ void SDL3Keyboard::init() { Keyboard::init(); } void SDL3Keyboard::reset() { Keyboard::reset(); } void SDL3Keyboard::update() { Keyboard::update(); } -Bool SDL3Keyboard::getCapsState() { return false; } +Bool SDL3Keyboard::getCapsState() +{ + return (SDL_GetModState() & SDL_KMOD_CAPS) != 0; +} void SDL3Keyboard::getKey(KeyboardIO* key) { @@ -417,7 +420,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 ? (Uint32)SDL_GetTicks() : 0; + key->keyDownTimeMsec = keyEvent.down ? timeGetTime() : 0; SDL_Keymod mod = keyEvent.mod; if (mod & SDL_KMOD_LSHIFT) @@ -836,6 +839,28 @@ void SDL3InputManager::closeGamepad() { if (m_gamepad) { + if (m_state.stickLeft) virtualPulseKey(SDL_SCANCODE_LEFT, false); + if (m_state.stickRight) virtualPulseKey(SDL_SCANCODE_RIGHT, false); + if (m_state.stickUp) virtualPulseKey(SDL_SCANCODE_UP, false); + if (m_state.stickDown) virtualPulseKey(SDL_SCANCODE_DOWN, false); + + if (m_state.ltDown) virtualPulseKey(SDL_SCANCODE_PAGEUP, false); + if (m_state.rtDown) virtualPulseKey(SDL_SCANCODE_PAGEDOWN, false); + + if (m_state.buttonState[SDL_GAMEPAD_BUTTON_SOUTH]) virtualPulseKey(SDL_SCANCODE_RETURN, false); + if (m_state.buttonState[SDL_GAMEPAD_BUTTON_EAST]) virtualPulseKey(SDL_SCANCODE_ESCAPE, false); + if (m_state.buttonState[SDL_GAMEPAD_BUTTON_WEST]) virtualPulseKey(SDL_SCANCODE_X, false); + if (m_state.buttonState[SDL_GAMEPAD_BUTTON_NORTH]) virtualPulseKey(SDL_SCANCODE_E, false); + if (m_state.buttonState[SDL_GAMEPAD_BUTTON_LEFT_SHOULDER]) virtualPulseKey(SDL_SCANCODE_Q, false); + if (m_state.buttonState[SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER]) virtualPulseKey(SDL_SCANCODE_E, false); + if (m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_UP]) virtualPulseKey(SDL_SCANCODE_1, false); + if (m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_DOWN]) virtualPulseKey(SDL_SCANCODE_2, false); + if (m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_LEFT]) virtualPulseKey(SDL_SCANCODE_3, false); + if (m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_RIGHT]) virtualPulseKey(SDL_SCANCODE_4, false); + + m_state = GamepadState(); + m_lastUpdateTime = 0; + SDL_CloseGamepad(m_gamepad); m_gamepad = nullptr; } diff --git a/Generals/Code/Main/WinMain.cpp b/Generals/Code/Main/WinMain.cpp index a367369306f..41c9f68d08e 100644 --- a/Generals/Code/Main/WinMain.cpp +++ b/Generals/Code/Main/WinMain.cpp @@ -74,12 +74,12 @@ // GLOBALS //////////////////////////////////////////////////////////////////// HINSTANCE ApplicationHInstance = nullptr; ///< our application instance HWND ApplicationHWnd = nullptr; ///< our application window handle -Win32Mouse *TheWin32Mouse = nullptr; ///< for the WndProc() only +extern Win32Mouse *TheWin32Mouse; ///< defined in AppMain.cpp DWORD TheMessageTime = 0; ///< For getting the time that a message was posted from Windows. -const Char *g_strFile = "data\\Generals.str"; -const Char *g_csfFile = "data\\%s\\Generals.csf"; -const char *gAppPrefix = ""; /// So WB can have a different debug log file name. +extern const Char *g_strFile; +extern const Char *g_csfFile; +extern const char *gAppPrefix; static Bool gInitializing = false; static Bool gDoPaint = true; @@ -840,6 +840,11 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, // WWDebug_Install_Message_Handler(WWDebug_Message_Callback); // WWDebug_Install_Assert_Handler(WWAssert_Callback); + if (!AppMain::initBeforeWindow()) + { + return exitcode; + } + char filePath[_MAX_PATH]; AppMain::getSplashFilePath(filePath, sizeof(filePath)); gLoadScreenBitmap = (HBITMAP)LoadImage(hInstance, filePath, IMAGE_BITMAP, 0, 0, LR_SHARED | LR_LOADFROMFILE); diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index 71591167ed7..7bcb64a6c3d 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -77,12 +77,12 @@ // GLOBALS //////////////////////////////////////////////////////////////////// HINSTANCE ApplicationHInstance = nullptr; ///< our application instance HWND ApplicationHWnd = nullptr; ///< our application window handle -Win32Mouse *TheWin32Mouse = nullptr; ///< for the WndProc() only +extern Win32Mouse *TheWin32Mouse; ///< defined in AppMain.cpp DWORD TheMessageTime = 0; ///< For getting the time that a message was posted from Windows. -const Char *g_strFile = "data\\Generals.str"; -const Char *g_csfFile = "data\\%s\\Generals.csf"; -const char *gAppPrefix = ""; /// So WB can have a different debug log file name. +extern const Char *g_strFile; +extern const Char *g_csfFile; +extern const char *gAppPrefix; static Bool gInitializing = false; static Bool gDoPaint = true; @@ -851,6 +851,10 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, // WWDebug_Install_Message_Handler(WWDebug_Message_Callback); // WWDebug_Install_Assert_Handler(WWAssert_Callback); + if (!AppMain::initBeforeWindow()) + { + return exitcode; + } // Force "splash image" to be loaded from a file, not a resource so same exe can be used in different localizations. char filePath[_MAX_PATH]; From 3d7a346160a7e0fcdc8333cb95319fce3ef37f71 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Thu, 23 Jul 2026 20:30:14 +0200 Subject: [PATCH 32/34] feat(sdl3): backport gamepad stick acceleration and camera modulation - Add radial deadzone removal, smoothstep response curve, and velocity acceleration for analog sticks - Retire precision mode in favor of continuous stick acceleration (LT is currently unmapped) - Add getControllerScrollScale() to scale camera panning when zoomed close to ground - Suppress mouse edge scrolling automatically while gamepad input is active - Expose getMinHeightAboveGround and getMaxHeightAboveGround on View Co-authored-by: stm <14291421+stephanmeesters@users.noreply.github.com> --- .../Include/GameClient/LookAtXlat.h | 3 + Core/GameEngine/Include/GameClient/View.h | 2 + .../GameClient/MessageStream/LookAtXlat.cpp | 60 ++++++++----- .../Include/SDL3Device/GameClient/SDL3Input.h | 10 ++- .../SDL3Device/GameClient/SDL3Input.cpp | 85 ++++++++++++++++--- 5 files changed, 125 insertions(+), 35 deletions(-) diff --git a/Core/GameEngine/Include/GameClient/LookAtXlat.h b/Core/GameEngine/Include/GameClient/LookAtXlat.h index e799c32e9b3..9116294c0e7 100644 --- a/Core/GameEngine/Include/GameClient/LookAtXlat.h +++ b/Core/GameEngine/Include/GameClient/LookAtXlat.h @@ -53,6 +53,7 @@ class LookAtTranslator : public GameMessageTranslator Bool hasMouseMovedRecently(); void setCurrentPos( const ICoord2D& pos ); void setScreenEdgeScrollMode(ScreenEdgeScrollMode mode); + void setControllerInputActive(Bool active); void resetModes(); //Used when disabling input, so when we reenable it we aren't stuck in a mode. @@ -82,11 +83,13 @@ class LookAtTranslator : public GameMessageTranslator ViewLocation m_viewLocation[ MAX_VIEW_LOCS ]; ScrollType m_scrollType; ScreenEdgeScrollMode m_screenEdgeScrollMode; + Bool m_controllerInputActive; UnsignedInt m_lastMouseMoveTimeMsec; // real-time in milliseconds when mouse last moved void setScrolling( ScrollType scrollType ); void stopScrolling(); Bool canScrollAtScreenEdge() const; + Real getControllerScrollScale() const; }; extern LookAtTranslator *TheLookAtTranslator; diff --git a/Core/GameEngine/Include/GameClient/View.h b/Core/GameEngine/Include/GameClient/View.h index ccc33949f64..f3834960dda 100644 --- a/Core/GameEngine/Include/GameClient/View.h +++ b/Core/GameEngine/Include/GameClient/View.h @@ -204,6 +204,8 @@ class View : public Snapshot virtual Real getZoom() { return m_zoom; } virtual void setZoom(Real z) { m_zoom = z; } virtual Real getHeightAboveGround() { return m_heightAboveGround; } + Real getMinHeightAboveGround() const { return m_minHeightAboveGround; } + Real getMaxHeightAboveGround() const { return m_maxHeightAboveGround; } virtual void setHeightAboveGround(Real z); virtual void zoom( Real height ); ///< Zoom in/out, closer to the ground, limit to min, or farther away from the ground, limit to max virtual void setZoomToDefault() { m_zoom = 1.0f; } ///< Set zoom to default value diff --git a/Core/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp b/Core/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp index b4ef8bfdbbb..fa887e152c7 100644 --- a/Core/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp +++ b/Core/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp @@ -76,37 +76,30 @@ static Bool scrollDir[4] = { false, false, false, false }; constexpr const Real SCROLL_MULTIPLIER = 2.0f; constexpr const Real SCROLL_AMT = 100.0f * SCROLL_MULTIPLIER; +constexpr const Real MIN_CONTROLLER_SCROLL_SCALE = 0.4f; static const Int edgeScrollSize = 3; static Mouse::MouseCursor prevCursor = Mouse::ARROW; //----------------------------------------------------------------------------- -void LookAtTranslator::setScrolling(ScrollType scrollType) +//----------------------------------------------------------------------------- +void LookAtTranslator::setScrolling( ScrollType scrollType ) { - if (!TheInGameUI->getInputEnabled()) - return; + if (!m_isScrolling) + TheStatsCollector->startScrollTime(); - prevCursor = TheMouse->getMouseCursor(); m_isScrolling = true; - TheInGameUI->setScrolling( TRUE ); - TheTacticalView->setMouseLock( TRUE ); m_scrollType = scrollType; - if(TheStatsCollector) - TheStatsCollector->startScrollTime(); } //----------------------------------------------------------------------------- void LookAtTranslator::stopScrolling() { m_isScrolling = false; - TheInGameUI->setScrolling( FALSE ); - TheTacticalView->setMouseLock( FALSE ); - TheMouse->setCursor(prevCursor); m_scrollType = SCROLL_NONE; - // increment the stats if we have a stats collector - if(TheStatsCollector) + if (m_isScrolling) TheStatsCollector->endScrollTime(); } @@ -114,6 +107,9 @@ void LookAtTranslator::stopScrolling() //----------------------------------------------------------------------------- Bool LookAtTranslator::canScrollAtScreenEdge() const { + if (m_controllerInputActive) + return false; + if (!TheMouse->isCursorCaptured()) return false; @@ -131,6 +127,22 @@ Bool LookAtTranslator::canScrollAtScreenEdge() const return true; } +//----------------------------------------------------------------------------- +Real LookAtTranslator::getControllerScrollScale() const +{ + if (!m_controllerInputActive || !TheTacticalView) + return 1.0f; + + const Real minHeight = TheTacticalView->getMinHeightAboveGround(); + const Real maxHeight = TheTacticalView->getMaxHeightAboveGround(); + const Real heightRange = maxHeight - minHeight; + if (heightRange <= 0.0f) + return 1.0f; + + const Real zoomOutFraction = clamp(0.0f, (TheTacticalView->getHeightAboveGround() - minHeight) / heightRange, 1.0f); + return MIN_CONTROLLER_SCROLL_SCALE + zoomOutFraction * (1.0f - MIN_CONTROLLER_SCROLL_SCALE); +} + //----------------------------------------------------------------------------- LookAtTranslator::LookAtTranslator() : m_isScrolling(false), @@ -140,8 +152,9 @@ LookAtTranslator::LookAtTranslator() : m_isChangingFOV(false), m_middleButtonDownTimeMsec(0), m_lastPlaneID(INVALID_DRAWABLE_ID), - m_lastMouseMoveTimeMsec(0), - m_scrollType(SCROLL_NONE) + m_scrollType(SCROLL_NONE), + m_controllerInputActive(false), + m_lastMouseMoveTimeMsec(0) { m_anchor.x = m_anchor.y = 0; m_currentPos.x = m_currentPos.y = 0; @@ -190,6 +203,14 @@ void LookAtTranslator::setScreenEdgeScrollMode(ScreenEdgeScrollMode mode) m_screenEdgeScrollMode = mode; } +void LookAtTranslator::setControllerInputActive(Bool active) +{ + m_controllerInputActive = active; + + if (active && m_isScrolling && m_scrollType == SCROLL_SCREENEDGE) + stopScrolling(); +} + //----------------------------------------------------------------------------- /** * The LookAt Translator is responsible for camera movements. It is directly responsible for @@ -485,21 +506,22 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage break; case SCROLL_KEY: { + const Real scrollAmount = SCROLL_AMT * getControllerScrollScale(); if (scrollDir[DIR_UP]) { - offset.y -= TheGlobalData->m_verticalScrollSpeedFactor * fpsRatio * SCROLL_AMT * TheGlobalData->m_keyboardScrollFactor; + offset.y -= TheGlobalData->m_verticalScrollSpeedFactor * fpsRatio * scrollAmount * TheGlobalData->m_keyboardScrollFactor; } if (scrollDir[DIR_DOWN]) { - offset.y += TheGlobalData->m_verticalScrollSpeedFactor * fpsRatio * SCROLL_AMT * TheGlobalData->m_keyboardScrollFactor; + offset.y += TheGlobalData->m_verticalScrollSpeedFactor * fpsRatio * scrollAmount * TheGlobalData->m_keyboardScrollFactor; } if (scrollDir[DIR_LEFT]) { - offset.x -= TheGlobalData->m_horizontalScrollSpeedFactor * fpsRatio * SCROLL_AMT * TheGlobalData->m_keyboardScrollFactor; + offset.x -= TheGlobalData->m_horizontalScrollSpeedFactor * fpsRatio * scrollAmount * TheGlobalData->m_keyboardScrollFactor; } if (scrollDir[DIR_RIGHT]) { - offset.x += TheGlobalData->m_horizontalScrollSpeedFactor * fpsRatio * SCROLL_AMT * TheGlobalData->m_keyboardScrollFactor; + offset.x += TheGlobalData->m_horizontalScrollSpeedFactor * fpsRatio * scrollAmount * TheGlobalData->m_keyboardScrollFactor; } } break; diff --git a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h index e409786e67e..c1686eb390a 100644 --- a/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h +++ b/Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h @@ -130,7 +130,10 @@ class SDL3InputManager 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; + static constexpr float DESIGNED_WINDOW_HEIGHT = 1440.0f; + static constexpr float DEFAULT_CURSOR_SPEED = 1226.6667f; + static constexpr float DEFAULT_CURSOR_ACCELERATION = 7200.0f; + static constexpr float DEFAULT_CURSOR_DECELERATION = 14400.0f; private: struct GamepadState @@ -176,7 +179,10 @@ class SDL3InputManager // Gamepad state GamepadState m_state; - Bool m_precisionMode; Uint64 m_lastUpdateTime; + float m_cursorVelocityX; + float m_cursorVelocityY; + float m_cursorRemainderX; + float m_cursorRemainderY; Bool m_isQuitting; }; diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp index 69f9d99b8af..b3f80b7461a 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp @@ -36,6 +36,7 @@ #include "Common/MessageStream.h" #include "GameClient/Display.h" #include "GameClient/InGameUI.h" +#include "GameClient/LookAtXlat.h" #include "GameLogic/GameLogic.h" #include "SDL3Device/Common/SDL3GameEngine.h" #include "SDL3Device/GameClient/SDL3Cursor.h" @@ -678,8 +679,11 @@ SDL3InputManager::SDL3InputManager(SDL_Window* window) , m_keyNextFree(0) , m_keyNextGet(0) , m_gamepad(nullptr) - , m_precisionMode(false) , m_lastUpdateTime(0) + , m_cursorVelocityX(0.0f) + , m_cursorVelocityY(0.0f) + , m_cursorRemainderX(0.0f) + , m_cursorRemainderY(0.0f) , m_isQuitting(false) { memset(m_mouseEvents, 0, sizeof(m_mouseEvents)); @@ -837,6 +841,9 @@ void SDL3InputManager::openFirstGamepad() void SDL3InputManager::closeGamepad() { + if (m_state.rtDown) + virtualPulseKey(SDL_SCANCODE_LCTRL, false); + if (m_gamepad) { if (m_state.stickLeft) virtualPulseKey(SDL_SCANCODE_LEFT, false); @@ -844,9 +851,6 @@ void SDL3InputManager::closeGamepad() if (m_state.stickUp) virtualPulseKey(SDL_SCANCODE_UP, false); if (m_state.stickDown) virtualPulseKey(SDL_SCANCODE_DOWN, false); - if (m_state.ltDown) virtualPulseKey(SDL_SCANCODE_PAGEUP, false); - if (m_state.rtDown) virtualPulseKey(SDL_SCANCODE_PAGEDOWN, false); - if (m_state.buttonState[SDL_GAMEPAD_BUTTON_SOUTH]) virtualPulseKey(SDL_SCANCODE_RETURN, false); if (m_state.buttonState[SDL_GAMEPAD_BUTTON_EAST]) virtualPulseKey(SDL_SCANCODE_ESCAPE, false); if (m_state.buttonState[SDL_GAMEPAD_BUTTON_WEST]) virtualPulseKey(SDL_SCANCODE_X, false); @@ -864,6 +868,14 @@ void SDL3InputManager::closeGamepad() SDL_CloseGamepad(m_gamepad); m_gamepad = nullptr; } + + m_cursorVelocityX = 0.0f; + m_cursorVelocityY = 0.0f; + m_cursorRemainderX = 0.0f; + m_cursorRemainderY = 0.0f; + + if (TheLookAtTranslator) + TheLookAtTranslator->setControllerInputActive(false); } void SDL3InputManager::virtualPulseKey(SDL_Scancode scancode, bool down) @@ -920,19 +932,31 @@ void SDL3InputManager::processGamepadInput() if (!m_gamepad) return; + if (TheLookAtTranslator) + TheLookAtTranslator->setControllerInputActive(true); + Uint64 now = SDL_GetTicks(); float deltaTime = (now - m_lastUpdateTime) / 1000.0f; m_lastUpdateTime = now; + if (deltaTime > 0.1f) + deltaTime = 0.1f; const float DEADZONE = DEFAULT_DEADZONE; - const float CURSOR_SPEED = DEFAULT_CURSOR_SPEED; + float resolutionScale = 1.0f; + int windowWidth = 0; + int windowHeight = 0; + if (m_window && SDL_GetWindowSizeInPixels(m_window, &windowWidth, &windowHeight) && windowHeight > 0) + resolutionScale = windowHeight / DESIGNED_WINDOW_HEIGHT; + + const float CURSOR_SPEED = DEFAULT_CURSOR_SPEED * resolutionScale; + const float CURSOR_ACCELERATION = DEFAULT_CURSOR_ACCELERATION * resolutionScale; + const float CURSOR_DECELERATION = DEFAULT_CURSOR_DECELERATION * resolutionScale; - // 1. TRIGGERS (Modifiers & Precision) + // 1. TRIGGERS (Modifiers: RT = Force Attack / LCtrl, LT = Unmapped) 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; @@ -945,23 +969,56 @@ void SDL3InputManager::processGamepadInput() // 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; + float stickMagnitude = sqrtf(lx * lx + ly * ly); + if (stickMagnitude > 1.0f) + stickMagnitude = 1.0f; - if (SDL_fabsf(lx) > DEADZONE || SDL_fabsf(ly) > DEADZONE) + float targetVelocityX = 0.0f; + float targetVelocityY = 0.0f; + if (stickMagnitude > DEADZONE) { float speed = CURSOR_SPEED; - if (m_precisionMode) - speed *= 0.3f; + // Radially remove the deadzone, then use a smoothstep response curve. + float response = (stickMagnitude - DEADZONE) / (1.0f - DEADZONE); + response = response * response * (3.0f - 2.0f * response); + targetVelocityX = (lx / stickMagnitude) * speed * response; + targetVelocityY = (ly / stickMagnitude) * speed * response; + } + + float velocityDeltaX = targetVelocityX - m_cursorVelocityX; + float velocityDeltaY = targetVelocityY - m_cursorVelocityY; + float velocityDeltaLength = sqrtf(velocityDeltaX * velocityDeltaX + velocityDeltaY * velocityDeltaY); + float acceleration = stickMagnitude > DEADZONE ? CURSOR_ACCELERATION : CURSOR_DECELERATION; + float maxVelocityChange = acceleration * deltaTime; + if (velocityDeltaLength > maxVelocityChange && velocityDeltaLength > 0.0f) + { + float changeScale = maxVelocityChange / velocityDeltaLength; + velocityDeltaX *= changeScale; + velocityDeltaY *= changeScale; + } + m_cursorVelocityX += velocityDeltaX; + m_cursorVelocityY += velocityDeltaY; + + m_cursorRemainderX += m_cursorVelocityX * deltaTime; + m_cursorRemainderY += m_cursorVelocityY * deltaTime; + int cursorDeltaX = (int)m_cursorRemainderX; + int cursorDeltaY = (int)m_cursorRemainderY; + m_cursorRemainderX -= cursorDeltaX; + m_cursorRemainderY -= cursorDeltaY; + + if (cursorDeltaX != 0 || cursorDeltaY != 0) + { 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; + motionEvent.motion.xrel = (float)cursorDeltaX; + motionEvent.motion.yrel = (float)cursorDeltaY; float mx, my; SDL_GetMouseState(&mx, &my); - motionEvent.motion.x = mx + motionEvent.motion.xrel; - motionEvent.motion.y = my + motionEvent.motion.yrel; + motionEvent.motion.x = mx + cursorDeltaX; + motionEvent.motion.y = my + cursorDeltaY; if (m_window) { From 826c951c84c94b0ec37fb86a4656f09f5541f806 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Thu, 23 Jul 2026 21:17:30 +0200 Subject: [PATCH 33/34] fix(sdl3): use logical window points in scaleMouseCoordinates - Replace SDL_GetWindowSizeInPixels with SDL_GetWindowSize in scaleMouseCoordinates - Fixes HiDPI/Retina coordinate space mismatch where mouse & gamepad motion events delivered in logical points were scaled by physical pixel dimensions Co-authored-by: Bobby Battista --- .../Source/SDL3Device/GameClient/SDL3Input.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp index b3f80b7461a..0c44e5a10ff 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp @@ -353,7 +353,8 @@ void SDL3Mouse::scaleMouseCoordinates(int rawX, int rawY, Uint32 windowID, int& } int winW = 0, winH = 0; - SDL_GetWindowSizeInPixels(window, &winW, &winH); + // Mouse events are delivered in logical window points, so scale against logical window size + SDL_GetWindowSize(window, &winW, &winH); int intW = TheDisplay->getWidth(); int intH = TheDisplay->getHeight(); From 3bd8573ccc51f3c6d5ccb26c82b2223bdd246318 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Thu, 23 Jul 2026 21:41:57 +0200 Subject: [PATCH 34/34] feat(sdl3): gamepad camera scrolling and double-click Guard mode - Fix camera panning and scroll state handling for gamepads - Fix double-click detection for gamepad button pulses to trigger Guard mode orders --- .../GameClient/MessageStream/LookAtXlat.cpp | 9 ++++----- .../SDL3Device/GameClient/SDL3Input.cpp | 19 ++++++++++++++++++- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/Core/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp b/Core/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp index fa887e152c7..cf71664872e 100644 --- a/Core/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp +++ b/Core/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp @@ -86,7 +86,7 @@ static Mouse::MouseCursor prevCursor = Mouse::ARROW; //----------------------------------------------------------------------------- void LookAtTranslator::setScrolling( ScrollType scrollType ) { - if (!m_isScrolling) + if (!m_isScrolling && TheStatsCollector != nullptr) TheStatsCollector->startScrollTime(); m_isScrolling = true; @@ -96,12 +96,11 @@ void LookAtTranslator::setScrolling( ScrollType scrollType ) //----------------------------------------------------------------------------- void LookAtTranslator::stopScrolling() { - m_isScrolling = false; - m_scrollType = SCROLL_NONE; - - if (m_isScrolling) + if (m_isScrolling && TheStatsCollector != nullptr) TheStatsCollector->endScrollTime(); + m_isScrolling = false; + m_scrollType = SCROLL_NONE; } //----------------------------------------------------------------------------- diff --git a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp index 0c44e5a10ff..ec88bfdf869 100644 --- a/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp +++ b/Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp @@ -899,11 +899,28 @@ void SDL3InputManager::virtualPulseKey(SDL_Scancode scancode, bool down) void SDL3InputManager::virtualPulseMouse(Uint8 button, bool down) { + static Uint64 lastClickTime[3] = {0, 0, 0}; + Uint64 now = SDL_GetTicks(); + 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; + + int buttonIdx = (button == SDL_BUTTON_LEFT) ? 0 : ((button == SDL_BUTTON_RIGHT) ? 1 : 2); + if (down) + { + if (now - lastClickTime[buttonIdx] < 350) + clickEvent.button.clicks = 2; + else + clickEvent.button.clicks = 1; + lastClickTime[buttonIdx] = now; + } + else + { + clickEvent.button.clicks = 1; + } + clickEvent.button.down = down; float mx, my;