Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ include(cmake/config.cmake)
include(cmake/gamespy.cmake)
include(cmake/lzhl.cmake)

if(SAGE_USE_SDL3 AND NOT IS_VS6_BUILD)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does the SAGE_ prefix come from? We have not used that before for anything else.

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.
Expand Down
18 changes: 18 additions & 0 deletions Core/GameEngineDevice/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,16 @@ set(GAMEENGINEDEVICE_SRC
Source/Win32Device/GameClient/Win32Mouse.cpp
)

# Add Core-level SDL3 implementation
if(SAGE_USE_SDL3 AND NOT IS_VS6_BUILD)
list(APPEND GAMEENGINEDEVICE_SRC
Include/SDL3GameEngine.h
Include/SDL3Device/GameClient/SDL3Input.h
Source/SDL3GameEngine.cpp
Source/SDL3Device/GameClient/SDL3Input.cpp
)
endif()

# Add C++ 17 FileSystem implementation for non-VS6 builds
if(NOT IS_VS6_BUILD)
list(APPEND GAMEENGINEDEVICE_SRC
Expand Down Expand Up @@ -227,6 +237,14 @@ target_link_libraries(corei_gameenginedevice_public INTERFACE
milesstub
)

# Export SDL3 dependencies for modern builds
if(SAGE_USE_SDL3 AND NOT IS_VS6_BUILD)
target_link_libraries(corei_gameenginedevice_public INTERFACE
SDL3::SDL3
SDL3_image::SDL3_image
)
endif()

if(RTS_BUILD_OPTION_FFMPEG)
find_package(FFMPEG REQUIRED)

Expand Down
211 changes: 211 additions & 0 deletions Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/

/*
** Derived from the GeneralsX branch by fbraz3
*/

#pragma once

#include "Lib/BaseType.h"

// SYSTEM INCLUDES
#include <SDL3/SDL.h>
#include <SDL3_image/SDL_image.h>
#include <array>
#include <functional>

// 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;
static void freeCursorResources(void);

// 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 */
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style: Avoid /**/ block comments

//-----------------------------------------------------------------------------
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(SDL_Window* window);
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 {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style:

{
}

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();

SDL_Window* m_window;
SDL_Gamepad* m_gamepad;
void processGamepadInput();
void handleGamepadButton(SDL_GamepadButton button, bool& currentState, bool isDown, std::function<void(bool)> 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
GamepadState m_state;

Bool m_precisionMode;
Uint64 m_lastUpdateTime;
Bool m_isQuitting;
};
95 changes: 95 additions & 0 deletions Core/GameEngineDevice/Include/SDL3GameEngine.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/

/*
** Derived from the GeneralsX branch by fbraz3
*/

#pragma once

#include "Lib/BaseType.h"

#include "Common/GameEngine.h"
#include <SDL3/SDL.h>

// 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);
};
Loading
Loading