Merge branch 'controller-input'

This commit is contained in:
Daniel Gibson 2024-01-22 16:41:53 +01:00
commit c743d21f08
15 changed files with 1339 additions and 105 deletions

View file

@ -23,6 +23,8 @@ Note: Numbers starting with a "#" like #330 refer to the bugreport with that num
(0 = TGA, still the default, 1 = BMP, 2 = PNG, 3 = JPG). `r_screenshotJpgQuality` and
`r_screenshotPngCompression` allow configuring how JPG/PNG are compressed.
Thanks *eezstreet (Nick Whitlock)*!
* Support for gamepads (based on code from [Quadrilateral Cowboy](https://github.com/blendogames/quadrilateralcowboy),
but heavily expanded). See [Configuration.md](./Configuration.md#using-gamepads) for more information.
1.5.2 (2022-06-13)
------------------------------------------------------------------------

143
Configuration.md Normal file
View file

@ -0,0 +1,143 @@
# Configuration
This document explains some dhewm3-specific configuration options.
For general Doom3 configuration see for example [this list of CVars](https://modwiki.dhewm3.org/CVars_%28Doom_3%29)
and [this list of Console Commands](https://modwiki.dhewm3.org/Commands_%28Doom_3%29).
**CVars** are set by entering `cvarName value` in the console, for example `com_showFPS 1`.
They can also be set as commandline arguments when starting dhewm3, for example `./dhewm3 +set r_fullscreen 0`.
Just entering a CVar's name (without a value) will show its current value, its default value
and a short description of what it does.
Starting dhewm3 with the commandline argument `-h` (for example `dhewm3.exe -h`) will show some
useful commandline arguments, for example how to tell dhewm3 where the game data can be found on your system.
## The Console
Like most id Software games from Quake 1 on, Doom3 has a console that allows entering commands
and setting Console Variables ("CVars"), often for advanced configuration or to aid development,
see also https://modwiki.dhewm3.org/Console.
Unlike in original Doom3, in dhewm3 the console is always available (no need to set `com_allowconsole 1`
or similar), and **can be opened with the key combination `Shift + Esc`**.
The classic "console key" (the one between `Esc`, `Tab` and `1`) should also still work with
most keyboard layouts. However you can disable that, so you can bind that key like any other key
(for example to select the chainsaw), by setting `in_ignoreConsoleKey 1`.
## Using Gamepads
Starting with 1.5.3 (or the git commits preceding the one adding this document), dhewm3 supports
using gamepads, as long as they're supported by SDL2.
This includes XBox Controllers (and compatible ones), Playstation 3-5 controllers,
Nintendo Switch Pro Controllers, many thirdparty controllers for those consoles, and lots of other
gamepads for PC.
Some notes:
* By default, no bindings for the gamepad exist, so you need to configure them once in the
Settings -> Controls menu.
- You need to bind *Turn Left*, *Turn Right*, *Look Up* and *Look Down* to the corresponding
directions of one stick to use it to look around or aim.
- Similarly, you need to bind *Forward*, *Backpedal*, *Move Left* and *Move Right* to the
corresponding directions of a stick to use it for player movement.
* The "Start" button ("+" on Nintendo gamepads, "Options" on Playstation 4/5 controllers) acts
like the Escape key, so it will **open/close the menu** and can not be bound.
The other buttons, axes and triggers can be bound to arbitrary actions in the Controls menu,
except for the Home button, which can't be used by dhewm3 at all (because it opens Steam when that is running).
* In **menus**, either stick will move the cursor, and the button you assign to *attack* (fire) acts
like the left mouse button, and so does the lower face button (A on XBox controllers, B on Nintendo
controllers, Cross on PS controllers) and the upper face button (Y on XBox, X on Nintendo, Triangle on PS).
* The layout of the controller (XBox-like, Nintendo-like, Playstation-like) should be automatically
detected and is used to display the button names according to the layout. If yours isn't detected
correctly, you can overwrite it with the `joy_gamepadLayout` CVar.
* Requires SDL2, layout detection requires SDL 2.0.12 or newer.
* Only one gamepad is supported or, more specifically, if multiple are connected, they all behave the same
and you can't bind their buttons/axes to different actions, and the auto-layout detection will use the
last gamepad it found to determine the layout.
* You can disable gamepads by setting the `in_useGamepad` CVar to `0`.
* There are several CVars to tweak the behavior:
- `joy_deadZone` Deadzone of the sticks, where `1.0` would be "stick moved fully in one direction".
This means that values below this register as 0. If you move or look around ingame even though
you're not moving a stick, try increasing the `joy_deadZone` value (default is `0.25`).
- `joy_triggerThreshold` Basically the deadzone for triggers. If your trigger triggers without
being touched, try increasing this value (default is `0.05`).
- `joy_gamepadLayout` overwrite automatically detected layout (XBox, Nintendo, PS), see above.
- `joy_pitchSpeed` How fast you look up/down (when the stick is at a maximum position)
- `joy_yawSpeed` Same for turning left/right
- `joy_invertLook` Inverts the controls for looking up/down (like in a flight simulator)
- `joy_gammaLook` If set to `1`, use a log curve instead of a power curve for looking around,
affects how fast you turn (or look up/down) when the stick is between center and maximum.
- `joy_powerScale` If `joy_gammaLook` is `0`, this is the exponent used for the power curve.
- `joy_dampenLook` if enabled (`1`), somehow reduced the speed of looking around, depending on
`joy_deltaPerMSLook`.
I created gamepad configs for the base game and d3xp (Resurrection of Evil), based on the standard bindings
of Doom3 BFG, see gamepad.cfg and gamepad-d3xp.cfg in the [base/ directory](./base/).
Put them in your base/ folder, open the console and enter `exec gamepad.cfg` for the base game,
or `exec gamepad-d3xp.cfg` for Resurrection of Evil (probably also works for Doom3: Lost Mission).
**_Note_** that in *configs* (or `bind` commands in the console), the following names are used for
gamepad buttons, sticks and triggers:
<details><summary>Click to see the list of gamepad button/stick/trigger names</summary>
* "JOY_BTN_SOUTH" - `A` button on XBox-style gamepads, `B` on Nintendo-style gamepads or `Cross` on Playstation-style gamepads
* "JOY_BTN_EAST" - `B` (XBox), `A` (Nintendo), `Circle` (Playstation)
* "JOY_BTN_WEST" - `X` (XBox), `Y` (Nintendo), `Square` (Playstation)
* "JOY_BTN_NORTH" - `Y` (XBox), `X` (Nintendo), `Triangle` (Playstation)
* "JOY_BTN_BACK" - The `Back` button, aka `-` (Nintendo) or `Select`/`Share` (Playstation)
* "JOY_BTN_LSTICK" - Pressing the Left Stick down
* "JOY_BTN_RSTICK" - Pressing the Right Stick down
* "JOY_BTN_LSHOULDER" - Left Shoulder Button
* "JOY_BTN_RSHOULDER" - Right Shoulder button
* "JOY_DPAD_UP" - DPad Up
* "JOY_DPAD_DOWN" - DPad Down
* "JOY_DPAD_LEFT" - DPad Left
* "JOY_DPAD_RIGHT" - DPad Right
* "JOY_BTN_MISC1" - misc. additional button, like Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button, Amazon Luna microphone button
* "JOY_BTN_RPADDLE1" - Upper or primary paddle, under your right hand (e.g. Xbox Elite paddle P1)
* "JOY_BTN_LPADDLE1" - Upper or primary paddle, under your left hand (e.g. Xbox Elite paddle P3)
* "JOY_BTN_RPADDLE2" - Lower or secondary paddle, under your right hand (e.g. Xbox Elite paddle P2)
* "JOY_BTN_LPADDLE2" - Lower or secondary paddle, under your left hand (e.g. Xbox Elite paddle P4
* "JOY_STICK1_UP" - Moving Left Stick up
* "JOY_STICK1_DOWN" - Moving Left Stick down
* "JOY_STICK1_LEFT" - Moving Left Stick to the left
* "JOY_STICK1_RIGHT" - Moving Left Stick to the right
* "JOY_STICK2_UP" - Moving Right Stick up
* "JOY_STICK2_DOWN" - Moving Right Stick down
* "JOY_STICK2_LEFT" - Moving Right Stick to the left
* "JOY_STICK2_RIGHT" - Moving Right Stick to the right
* "JOY_TRIGGER1" - Pressing the Left Trigger
* "JOY_TRIGGER2" - Pressing the Right Trigger
</details>
## Screenshot configuration
Doom3 always supported taking screenshots, but dhewm3 (from 1.5.3 on) supports using different
formats than TGA.
This can be configured with the following CVars:
- `r_screenshotFormat` What format screenshots should be in:
`0` = TGA (default), `1` = BMP, `2` = PNG, `3` = JPG
- `r_screenshotJpgQuality` Quality when using JPG screenshots (`0` - `100`)
- `r_screenshotPngCompression` Compression level when using PNG screenshots (`0` - `9`)
## CVars added in dhewm3 that I'm currently too lazy to document more thoroughly
- g_hitEffect
- in_nograb
- in_grabKeyboard
- in_tty
- in_kbd
- r_fullscreenDesktop
- r_fillWindowAlphaChan
- r_useCarmacksReverse
- r_useStencilOpSeparate
- s_alReverbGain

View file

@ -31,6 +31,7 @@ Compared to the original _DOOM 3_, the changes of _dhewm 3_ worth mentioning are
- SDL for low-level OS support, OpenGL and input handling
- OpenAL for audio output, all OS-specific audio backends are gone
- OpenAL EFX for EAX reverb effects (read: EAX-like sound effects on all platforms/hardware)
- Gamepad support
- Better support for widescreen (and arbitrary display resolutions)
- A portable build system based on CMake
- (Cross-)compilation with MinGW-w64
@ -55,6 +56,11 @@ https://store.steampowered.com/app/208200/DOOM_3/
See https://dhewm3.org/#how-to-install for game data installation instructions.
## Configuration
See [Configuration.md](./Configuration.md) for dhewm3-specific configuration, especially for
using gamepads.
## Compiling
The build system is based on CMake: http://cmake.org/

21
base/gamepad-d3xp.cfg Executable file
View file

@ -0,0 +1,21 @@
bind "JOY_BTN_SOUTH" "_moveUp"
bind "JOY_BTN_WEST" "_impulse13"
bind "JOY_BTN_BACK" "_impulse19"
bind "JOY_BTN_LSTICK" "_speed"
bind "JOY_BTN_RSTICK" "_moveDown"
bind "JOY_BTN_LSHOULDER" "_impulse15"
bind "JOY_BTN_RSHOULDER" "_impulse14"
bind "JOY_DPAD_UP" "_impulse8"
bind "JOY_DPAD_DOWN" "_impulse11"
bind "JOY_DPAD_LEFT" "_impulse12"
bind "JOY_DPAD_RIGHT" "_impulse1"
bind "JOY_STICK1_UP" "_forward"
bind "JOY_STICK1_DOWN" "_back"
bind "JOY_STICK1_LEFT" "_moveLeft"
bind "JOY_STICK1_RIGHT" "_moveRight"
bind "JOY_STICK2_UP" "_lookUp"
bind "JOY_STICK2_DOWN" "_lookDown"
bind "JOY_STICK2_LEFT" "_left"
bind "JOY_STICK2_RIGHT" "_right"
bind "JOY_TRIGGER1" "_impulse0"
bind "JOY_TRIGGER2" "_attack"

21
base/gamepad.cfg Executable file
View file

@ -0,0 +1,21 @@
bind "JOY_BTN_SOUTH" "_moveUp"
bind "JOY_BTN_WEST" "_impulse13"
bind "JOY_BTN_BACK" "_impulse19"
bind "JOY_BTN_LSTICK" "_speed"
bind "JOY_BTN_RSTICK" "_moveDown"
bind "JOY_BTN_LSHOULDER" "_impulse15"
bind "JOY_BTN_RSHOULDER" "_impulse14"
bind "JOY_DPAD_UP" "_impulse5"
bind "JOY_DPAD_DOWN" "_impulse8"
bind "JOY_DPAD_LEFT" "_impulse9"
bind "JOY_DPAD_RIGHT" "_impulse0"
bind "JOY_STICK1_UP" "_forward"
bind "JOY_STICK1_DOWN" "_back"
bind "JOY_STICK1_LEFT" "_moveLeft"
bind "JOY_STICK1_RIGHT" "_moveRight"
bind "JOY_STICK2_UP" "_lookUp"
bind "JOY_STICK2_DOWN" "_lookDown"
bind "JOY_STICK2_LEFT" "_left"
bind "JOY_STICK2_RIGHT" "_right"
bind "JOY_TRIGGER1" "_impulse11"
bind "JOY_TRIGGER2" "_attack"

View file

@ -2919,8 +2919,14 @@ void idCommonLocal::Init( int argc, char **argv ) {
#endif
#endif
if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_JOYSTICK)) // init joystick to work around SDL 2.0.9 bug #4391
#if SDL_VERSION_ATLEAST(2, 0, 0)
if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER)) // init joystick to work around SDL 2.0.9 bug #4391
#else
if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO)) // no gamecontroller support in SDL1
#endif
{
Sys_Error("Error while initializing SDL: %s", SDL_GetError());
}
Sys_InitThreads();

View file

@ -109,38 +109,41 @@ static const keyname_t keynames[] =
{"MWHEELUP", K_MWHEELUP, "#str_07131"},
{"MWHEELDOWN", K_MWHEELDOWN, "#str_07132"},
{"JOY1", K_JOY1, "#str_07062"},
{"JOY2", K_JOY2, "#str_07063"},
{"JOY3", K_JOY3, "#str_07064"},
{"JOY4", K_JOY4, "#str_07065"},
{"JOY5", K_JOY5, "#str_07066"},
{"JOY6", K_JOY6, "#str_07067"},
{"JOY7", K_JOY7, "#str_07068"},
{"JOY8", K_JOY8, "#str_07069"},
{"JOY9", K_JOY9, "#str_07070"},
{"JOY10", K_JOY10, "#str_07071"},
{"JOY11", K_JOY11, "#str_07072"},
{"JOY12", K_JOY12, "#str_07073"},
{"JOY13", K_JOY13, "#str_07074"},
{"JOY14", K_JOY14, "#str_07075"},
{"JOY15", K_JOY15, "#str_07076"},
{"JOY16", K_JOY16, "#str_07077"},
{"JOY17", K_JOY17, "#str_07078"},
{"JOY18", K_JOY18, "#str_07079"},
{"JOY19", K_JOY19, "#str_07080"},
{"JOY20", K_JOY20, "#str_07081"},
{"JOY21", K_JOY21, "#str_07082"},
{"JOY22", K_JOY22, "#str_07083"},
{"JOY23", K_JOY23, "#str_07084"},
{"JOY24", K_JOY24, "#str_07085"},
{"JOY25", K_JOY25, "#str_07086"},
{"JOY26", K_JOY26, "#str_07087"},
{"JOY27", K_JOY27, "#str_07088"},
{"JOY28", K_JOY28, "#str_07089"},
{"JOY29", K_JOY29, "#str_07090"},
{"JOY30", K_JOY30, "#str_07091"},
{"JOY31", K_JOY31, "#str_07092"},
{"JOY32", K_JOY32, "#str_07093"},
// Note: for localized gamepad key names, we use Sys_GetLocalizedJoyKeyName()
// so the last column is just NULL
{"JOY_BTN_SOUTH", K_JOY_BTN_SOUTH, NULL},
{"JOY_BTN_EAST", K_JOY_BTN_EAST, NULL},
{"JOY_BTN_WEST", K_JOY_BTN_WEST, NULL},
{"JOY_BTN_NORTH", K_JOY_BTN_NORTH, NULL},
{"JOY_BTN_BACK", K_JOY_BTN_BACK, NULL},
// leaving out K_JOY_BTN_GUIDE, as I think it shouldn't be used (might open Steam or similar)
{"JOY_BTN_START", K_JOY_BTN_START, NULL},
{"JOY_BTN_LSTICK", K_JOY_BTN_LSTICK, NULL},
{"JOY_BTN_RSTICK", K_JOY_BTN_RSTICK, NULL},
{"JOY_BTN_LSHOULDER", K_JOY_BTN_LSHOULDER, NULL},
{"JOY_BTN_RSHOULDER", K_JOY_BTN_RSHOULDER, NULL},
{"JOY_DPAD_UP", K_JOY_DPAD_UP, NULL},
{"JOY_DPAD_DOWN", K_JOY_DPAD_DOWN, NULL},
{"JOY_DPAD_LEFT", K_JOY_DPAD_LEFT, NULL},
{"JOY_DPAD_RIGHT", K_JOY_DPAD_RIGHT, NULL},
{"JOY_BTN_MISC1", K_JOY_BTN_MISC1, NULL},
{"JOY_BTN_RPADDLE1", K_JOY_BTN_RPADDLE1, NULL},
{"JOY_BTN_LPADDLE1", K_JOY_BTN_LPADDLE1, NULL},
{"JOY_BTN_RPADDLE2", K_JOY_BTN_RPADDLE2, NULL},
{"JOY_BTN_LPADDLE2", K_JOY_BTN_LPADDLE2, NULL},
{"JOY_STICK1_UP", K_JOY_STICK1_UP, NULL},
{"JOY_STICK1_DOWN", K_JOY_STICK1_DOWN, NULL},
{"JOY_STICK1_LEFT", K_JOY_STICK1_LEFT, NULL},
{"JOY_STICK1_RIGHT", K_JOY_STICK1_RIGHT, NULL},
{"JOY_STICK2_UP", K_JOY_STICK2_UP, NULL},
{"JOY_STICK2_DOWN", K_JOY_STICK2_DOWN, NULL},
{"JOY_STICK2_LEFT", K_JOY_STICK2_LEFT, NULL},
{"JOY_STICK2_RIGHT", K_JOY_STICK2_RIGHT, NULL},
{"JOY_TRIGGER1", K_JOY_TRIGGER1, NULL},
{"JOY_TRIGGER2", K_JOY_TRIGGER2, NULL},
{"AUX1", K_AUX1, "#str_07094"},
{"AUX2", K_AUX2, "#str_07095"},
@ -409,6 +412,12 @@ const char *idKeyInput::KeyNumToString( int keynum, bool localized ) {
}
}
if ( localized && keynum >= K_FIRST_JOY && keynum <= K_LAST_JOY ) {
const char* jname = Sys_GetLocalizedJoyKeyName(keynum);
if(jname != NULL)
return jname;
}
// check for a key string
for ( kn = keynames; kn->name; kn++ ) {
if ( keynum == kn->keynum ) {

View file

@ -138,39 +138,51 @@ typedef enum {
K_MWHEELDOWN = 195,
K_MWHEELUP,
K_JOY1 = 197,
K_JOY2,
K_JOY3,
K_JOY4,
K_JOY5,
K_JOY6,
K_JOY7,
K_JOY8,
K_JOY9,
K_JOY10,
K_JOY11,
K_JOY12,
K_JOY13,
K_JOY14,
K_JOY15,
K_JOY16,
K_JOY17,
K_JOY18,
K_JOY19,
K_JOY20,
K_JOY21,
K_JOY22,
K_JOY23,
K_JOY24,
K_JOY25,
K_JOY26,
K_JOY27,
K_GRAVE_A = 224, // lowercase a with grave accent
K_JOY28,
K_JOY29,
K_JOY30,
K_JOY31,
K_JOY32,
//------------------------
// K_JOY codes must be contiguous, too, and K_JOY_BTN_* should be kept in sync with J_BTN_* of sys_jEvents
//------------------------
K_FIRST_JOY = 197,
K_JOY_BTN_SOUTH = K_FIRST_JOY, // bottom face button, like Xbox A
K_JOY_BTN_EAST, // right face button, like Xbox B
K_JOY_BTN_WEST, // left face button, like Xbox X
K_JOY_BTN_NORTH, // top face button, like Xbox Y
K_JOY_BTN_BACK,
K_JOY_BTN_GUIDE, // Note: this one should probably not be used?
K_JOY_BTN_START, // hardcoded to generate Esc to open/close menu
K_JOY_BTN_LSTICK, // press left stick
K_JOY_BTN_RSTICK, // press right stick
K_JOY_BTN_LSHOULDER,
K_JOY_BTN_RSHOULDER,
K_JOY_DPAD_UP,
K_JOY_DPAD_DOWN,
K_JOY_DPAD_LEFT,
K_JOY_DPAD_RIGHT,
K_JOY_BTN_MISC1, // Additional button (e.g. Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button, Amazon Luna microphone button)
K_JOY_BTN_RPADDLE1, // Upper or primary paddle, under your right hand (e.g. Xbox Elite paddle P1)
K_JOY_BTN_LPADDLE1, // Upper or primary paddle, under your left hand (e.g. Xbox Elite paddle P3)
K_JOY_BTN_RPADDLE2, // Lower or secondary paddle, under your right hand (e.g. Xbox Elite paddle P2)
K_JOY_BTN_LPADDLE2, // Lower or secondary paddle, under your left hand (e.g. Xbox Elite paddle P4)
K_JOY_STICK1_UP,
K_JOY_STICK1_DOWN,
K_JOY_STICK1_LEFT,
K_JOY_STICK1_RIGHT,
K_JOY_STICK2_UP,
K_JOY_STICK2_DOWN,
K_JOY_STICK2_LEFT,
K_JOY_STICK2_RIGHT,
K_JOY_TRIGGER1,
K_JOY_TRIGGER2,
K_LAST_JOY = K_JOY_TRIGGER2,
K_GRAVE_A = 229, // lowercase a with grave accent FIXME: used to be 224; this probably isn't used anyway
K_AUX1 = 230,
K_CEDILLA_C = 231, // lowercase c with Cedilla

View file

@ -1447,6 +1447,9 @@ void idSessionLocal::UnloadMap() {
}
mapSpawned = false;
// DG: that state needs to be reset now
Sys_SetInteractiveIngameGuiActive( false, NULL );
}
/*

View file

@ -39,6 +39,8 @@ If you have questions concerning this license or the applicable additional terms
idCVar idSessionLocal::gui_configServerRate( "gui_configServerRate", "0", CVAR_GUI | CVAR_ARCHIVE | CVAR_ROM | CVAR_INTEGER, "" );
extern idCVar joy_gamepadLayout; // DG: used here to update bindings window when cvar is changed
// implements the setup for, and commands from, the main menu
/*
@ -1209,6 +1211,14 @@ void idSessionLocal::GuiFrameEvents() {
sysEvent_t ev;
idUserInterface *gui;
// DG: if joy_gamepadLayout changes, the binding names in the main/controls menu must be updated
if ( joy_gamepadLayout.IsModified() ) {
if ( guiMainMenu != NULL ) {
guiMainMenu->SetKeyBindingNames();
}
joy_gamepadLayout.ClearModified();
}
// stop generating move and button commands when a local console or menu is active
// running here so SP, async networking and no game all go through it
if ( console->Active() || guiActive ) {

View file

@ -93,7 +93,7 @@ typedef enum {
UB_BUTTON6,
UB_BUTTON7,
UB_ATTACK,
UB_ATTACK, // NOTE: this value (20) is hardcoded in idUserInterfaceLocal::HandleEvent() !
UB_SPEED,
UB_ZOOM,
UB_SHOWSCORES,
@ -350,7 +350,10 @@ private:
bool Inhibited( void );
void AdjustAngles( void );
void KeyMove( void );
void CircleToSquare( float & axis_x, float & axis_y ) const;
void HandleJoystickAxis( int keyNum, float unclampedValue, float threshold, bool positive );
void JoystickMove( void );
void JoystickFakeMouse(float axis_x, float axis_y, float deadzone);
void MouseMove( void );
void CmdButtons( void );
@ -384,7 +387,14 @@ private:
bool mouseDown;
int mouseDx, mouseDy; // added to by mouse events
int joystickAxis[MAX_JOYSTICK_AXIS]; // set by joystick events
float joystickAxis[MAX_JOYSTICK_AXIS]; // set by joystick events
int pollTime;
int lastPollTime;
float lastLookValuePitch;
float lastLookValueYaw;
bool heldJump; // TODO: ???
static idCVar in_yawSpeed;
static idCVar in_pitchSpeed;
@ -419,6 +429,22 @@ idCVar idUsercmdGenLocal::m_smooth( "m_smooth", "1", CVAR_SYSTEM | CVAR_ARCHIVE
idCVar idUsercmdGenLocal::m_strafeSmooth( "m_strafeSmooth", "4", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_INTEGER, "number of samples blended for mouse moving", 1, 8, idCmdSystem::ArgCompletion_Integer<1,8> );
idCVar idUsercmdGenLocal::m_showMouseRate( "m_showMouseRate", "0", CVAR_SYSTEM | CVAR_BOOL, "shows mouse movement" );
idCVar joy_triggerThreshold( "joy_triggerThreshold", "0.05", CVAR_FLOAT | CVAR_ARCHIVE, "how far the joystick triggers have to be pressed before they register as down" );
idCVar joy_deadZone( "joy_deadZone", "0.25", CVAR_FLOAT | CVAR_ARCHIVE, "specifies how large the dead-zone is on the joystick" );
idCVar joy_gammaLook( "joy_gammaLook", "1", CVAR_INTEGER | CVAR_ARCHIVE, "use a log curve instead of a power curve for movement" );
idCVar joy_powerScale( "joy_powerScale", "2", CVAR_FLOAT | CVAR_ARCHIVE, "Raise joystick values to this power" );
idCVar joy_pitchSpeed( "joy_pitchSpeed", "130", CVAR_ARCHIVE | CVAR_FLOAT, "pitch speed when pressing up or down on the joystick", 60, 600 );
idCVar joy_yawSpeed( "joy_yawSpeed", "240", CVAR_ARCHIVE | CVAR_FLOAT, "pitch speed when pressing left or right on the joystick", 60, 600 );
idCVar joy_invertLook( "joy_invertLook", "0", CVAR_ARCHIVE | CVAR_BOOL, "inverts the look controls so the forward looks up (flight controls) - the proper way to play games!" );
// these were a bad idea!
idCVar joy_dampenLook( "joy_dampenLook", "1", CVAR_BOOL | CVAR_ARCHIVE, "Do not allow full acceleration on look" );
idCVar joy_deltaPerMSLook( "joy_deltaPerMSLook", "0.003", CVAR_FLOAT | CVAR_ARCHIVE, "Max amount to be added on look per MS" );
idCVar in_useGamepad( "in_useGamepad", "1", CVAR_ARCHIVE | CVAR_BOOL, "enables/disables the gamepad for PC use" );
// TODO idCVar in_mouseInvertLook( "in_mouseInvertLook", "0", CVAR_ARCHIVE | CVAR_BOOL, "inverts the look controls so the forward looks up (flight controls) - the proper way to play games!" );
static idUsercmdGenLocal localUsercmdGen;
idUsercmdGen *usercmdGen = &localUsercmdGen;
@ -439,6 +465,8 @@ idUsercmdGenLocal::idUsercmdGenLocal( void ) {
toggled_zoom.Clear();
toggled_run.on = in_alwaysRun.GetBool();
lastLookValuePitch = lastLookValueYaw = 0.0f;
ClearAngles();
Clear();
}
@ -573,9 +601,17 @@ void idUsercmdGenLocal::KeyMove( void ) {
forward += KEY_MOVESPEED * ButtonState( UB_FORWARD );
forward -= KEY_MOVESPEED * ButtonState( UB_BACK );
cmd.forwardmove = idMath::ClampChar( forward );
cmd.rightmove = idMath::ClampChar( side );
cmd.upmove = idMath::ClampChar( up );
// only set each movement variable if its unset at this point.
// NOTE: joystick input happens before this.
if (cmd.forwardmove == 0) {
cmd.forwardmove = idMath::ClampChar( forward );
}
if (cmd.rightmove == 0) {
cmd.rightmove = idMath::ClampChar( side );
}
if (cmd.upmove == 0) {
cmd.upmove = idMath::ClampChar( up );
}
}
/*
@ -672,29 +708,233 @@ void idUsercmdGenLocal::MouseMove( void ) {
}
}
/*
========================
idUsercmdGenLocal::CircleToSquare
========================
*/
void idUsercmdGenLocal::CircleToSquare( float & axis_x, float & axis_y ) const {
// bring everything in the first quadrant
bool flip_x = false;
if ( axis_x < 0.0f ) {
flip_x = true;
axis_x *= -1.0f;
}
bool flip_y = false;
if ( axis_y < 0.0f ) {
flip_y = true;
axis_y *= -1.0f;
}
// swap the two axes so we project against the vertical line X = 1
bool swap = false;
if ( axis_y > axis_x ) {
float tmp = axis_x;
axis_x = axis_y;
axis_y = tmp;
swap = true;
}
if ( axis_x < 0.001f ) {
// on one of the axes where no correction is needed
return;
}
// length (max 1.0f at the unit circle)
float len = idMath::Sqrt( axis_x * axis_x + axis_y * axis_y );
if ( len > 1.0f ) {
len = 1.0f;
}
// thales
float axis_y_us = axis_y / axis_x;
// use a power curve to shift the correction to happen closer to the unit circle
float correctionRatio = Square( len );
axis_x += correctionRatio * ( len - axis_x );
axis_y += correctionRatio * ( axis_y_us - axis_y );
// go back through the symmetries
if ( swap ) {
float tmp = axis_x;
axis_x = axis_y;
axis_y = tmp;
}
if ( flip_x ) {
axis_x *= -1.0f;
}
if ( flip_y ) {
axis_y *= -1.0f;
}
}
/*
========================
idUsercmdGenLocal::HandleJoystickAxis
========================
*/
void idUsercmdGenLocal::HandleJoystickAxis( int keyNum, float unclampedValue, float threshold, bool positive ) {
if ( ( unclampedValue > 0.0f ) && !positive ) {
return;
}
if ( ( unclampedValue < 0.0f ) && positive ) {
return;
}
float value = 0.0f;
bool pressed = false;
if ( unclampedValue > threshold ) {
value = idMath::Fabs( ( unclampedValue - threshold ) / ( 1.0f - threshold ) );
pressed = true;
} else if ( unclampedValue < -threshold ) {
value = idMath::Fabs( ( unclampedValue + threshold ) / ( 1.0f - threshold ) );
pressed = true;
}
int action = idKeyInput::GetUsercmdAction( keyNum );
if ( action >= UB_ATTACK ) {
Key( keyNum, pressed );
return;
}
if ( !pressed ) {
return;
}
float lookValue = 0.0f;
if ( joy_gammaLook.GetBool() ) {
lookValue = idMath::Pow( 1.04712854805f, value * 100.0f ) * 0.01f;
} else {
lookValue = idMath::Pow( value, joy_powerScale.GetFloat() );
}
#if 0 // TODO: aim assist maybe.
idGame * game = common->Game();
if ( game != NULL ) {
lookValue *= game->GetAimAssistSensitivity();
}
#endif
switch ( action ) {
case UB_FORWARD: {
float move = (float)cmd.forwardmove + ( KEY_MOVESPEED * value );
cmd.forwardmove = idMath::ClampChar( idMath::Ftoi( move ) );
break;
}
case UB_BACK: {
float move = (float)cmd.forwardmove - ( KEY_MOVESPEED * value );
cmd.forwardmove = idMath::ClampChar( idMath::Ftoi( move ) );
break;
}
case UB_MOVELEFT: {
float move = (float)cmd.rightmove - ( KEY_MOVESPEED * value );
cmd.rightmove = idMath::ClampChar( idMath::Ftoi( move ) );
break;
}
case UB_MOVERIGHT: {
float move = (float)cmd.rightmove + ( KEY_MOVESPEED * value );
cmd.rightmove = idMath::ClampChar( idMath::Ftoi( move ) );
break;
}
case UB_LOOKUP: {
if ( joy_dampenLook.GetBool() ) {
lookValue = Min( lookValue, ( pollTime - lastPollTime ) * joy_deltaPerMSLook.GetFloat() + lastLookValuePitch );
lastLookValuePitch = lookValue;
}
float invertPitch = joy_invertLook.GetBool() ? -1.0f : 1.0f;
viewangles[PITCH] -= MS2SEC( pollTime - lastPollTime ) * lookValue * joy_pitchSpeed.GetFloat() * invertPitch;
break;
}
case UB_LOOKDOWN: {
if ( joy_dampenLook.GetBool() ) {
lookValue = Min( lookValue, ( pollTime - lastPollTime ) * joy_deltaPerMSLook.GetFloat() + lastLookValuePitch );
lastLookValuePitch = lookValue;
}
float invertPitch = joy_invertLook.GetBool() ? -1.0f : 1.0f;
viewangles[PITCH] += MS2SEC( pollTime - lastPollTime ) * lookValue * joy_pitchSpeed.GetFloat() * invertPitch;
break;
}
case UB_LEFT: {
if ( joy_dampenLook.GetBool() ) {
lookValue = Min( lookValue, ( pollTime - lastPollTime ) * joy_deltaPerMSLook.GetFloat() + lastLookValueYaw );
lastLookValueYaw = lookValue;
}
viewangles[YAW] += MS2SEC( pollTime - lastPollTime ) * lookValue * joy_yawSpeed.GetFloat();
break;
}
case UB_RIGHT: {
if ( joy_dampenLook.GetBool() ) {
lookValue = Min( lookValue, ( pollTime - lastPollTime ) * joy_deltaPerMSLook.GetFloat() + lastLookValueYaw );
lastLookValueYaw = lookValue;
}
viewangles[YAW] -= MS2SEC( pollTime - lastPollTime ) * lookValue * joy_yawSpeed.GetFloat();
break;
}
}
}
static float joyAxisToMouseDelta(float axis, float deadzone)
{
float ret = 0.0f;
float val = fabsf(axis); // calculations below require a positive value
if(val > deadzone) {
// from deadzone .. 1 to 0 .. 1-deadzone
val -= deadzone;
// and then to 0..1
val = val * (1.0f / (1.0f - deadzone));
// make it exponential curve - exp(val*3) should return sth between 1 and 20;
// then turning that into 0.5 .. 10
ret = expf( val * 3.0f ) * 0.5f;
if(axis < 0.0f) // restore sign
ret = -ret;
}
return ret;
}
extern bool D3_IN_interactiveIngameGuiActive; // from sys/events.cpp
void idUsercmdGenLocal::JoystickFakeMouse(float axis_x, float axis_y, float deadzone)
{
if ( D3_IN_interactiveIngameGuiActive ) {
float x = joyAxisToMouseDelta(axis_x, deadzone);
float y = joyAxisToMouseDelta(axis_y, deadzone);
continuousMouseX += x;
continuousMouseY += y;
}
}
/*
=================
idUsercmdGenLocal::JoystickMove
=================
*/
void idUsercmdGenLocal::JoystickMove( void ) {
float anglespeed;
void idUsercmdGenLocal::JoystickMove() {
float threshold = joy_deadZone.GetFloat();
float triggerThreshold = joy_triggerThreshold.GetFloat();
if ( toggled_run.on ^ ( in_alwaysRun.GetBool() && idAsyncNetwork::IsActive() ) ) {
anglespeed = idMath::M_MS2SEC * USERCMD_MSEC * in_angleSpeedKey.GetFloat();
} else {
anglespeed = idMath::M_MS2SEC * USERCMD_MSEC;
}
float axis_y = joystickAxis[ AXIS_LEFT_Y ];
float axis_x = joystickAxis[ AXIS_LEFT_X ];
CircleToSquare( axis_x, axis_y );
if ( !ButtonState( UB_STRAFE ) ) {
viewangles[YAW] += anglespeed * in_yawSpeed.GetFloat() * joystickAxis[AXIS_SIDE];
viewangles[PITCH] += anglespeed * in_pitchSpeed.GetFloat() * joystickAxis[AXIS_FORWARD];
} else {
cmd.rightmove = idMath::ClampChar( cmd.rightmove + joystickAxis[AXIS_SIDE] );
cmd.forwardmove = idMath::ClampChar( cmd.forwardmove + joystickAxis[AXIS_FORWARD] );
}
HandleJoystickAxis( K_JOY_STICK1_UP, axis_y, threshold, false );
HandleJoystickAxis( K_JOY_STICK1_DOWN, axis_y, threshold, true );
HandleJoystickAxis( K_JOY_STICK1_LEFT, axis_x, threshold, false );
HandleJoystickAxis( K_JOY_STICK1_RIGHT, axis_x, threshold, true );
cmd.upmove = idMath::ClampChar( cmd.upmove + joystickAxis[AXIS_UP] );
JoystickFakeMouse( axis_x, axis_y, threshold );
axis_y = joystickAxis[ AXIS_RIGHT_Y ];
axis_x = joystickAxis[ AXIS_RIGHT_X ];
CircleToSquare( axis_x, axis_y );
HandleJoystickAxis( K_JOY_STICK2_UP, axis_y, threshold, false );
HandleJoystickAxis( K_JOY_STICK2_DOWN, axis_y, threshold, true );
HandleJoystickAxis( K_JOY_STICK2_LEFT, axis_x, threshold, false );
HandleJoystickAxis( K_JOY_STICK2_RIGHT, axis_x, threshold, true );
JoystickFakeMouse( axis_x, axis_y, threshold );
HandleJoystickAxis( K_JOY_TRIGGER1, joystickAxis[ AXIS_LEFT_TRIG ], triggerThreshold, true );
HandleJoystickAxis( K_JOY_TRIGGER2, joystickAxis[ AXIS_RIGHT_TRIG ], triggerThreshold, true );
}
/*
@ -778,6 +1018,9 @@ void idUsercmdGenLocal::MakeCurrent( void ) {
// keyboard angle adjustment
AdjustAngles();
// get basic movement from joystick
JoystickMove();
// set button bits
CmdButtons();
@ -787,9 +1030,6 @@ void idUsercmdGenLocal::MakeCurrent( void ) {
// get basic movement from mouse
MouseMove();
// get basic movement from joystick
JoystickMove();
// check to make sure the angles haven't wrapped
if ( viewangles[PITCH] - oldAngles[PITCH] > 90 ) {
viewangles[PITCH] = oldAngles[PITCH] + 90;
@ -877,6 +1117,7 @@ void idUsercmdGenLocal::Clear( void ) {
// clears all key states
memset( buttonState, 0, sizeof( buttonState ) );
memset( keyState, false, sizeof( keyState ) );
memset( joystickAxis, 0, sizeof( joystickAxis ) );
inhibitCommands = false;
@ -939,6 +1180,8 @@ void idUsercmdGenLocal::Key( int keyNum, bool down ) {
int action = idKeyInput::GetUsercmdAction( keyNum );
// TODO: if action == 0 return ?
if ( down ) {
buttonState[ action ]++;
@ -1039,7 +1282,25 @@ idUsercmdGenLocal::Joystick
===============
*/
void idUsercmdGenLocal::Joystick( void ) {
memset( joystickAxis, 0, sizeof( joystickAxis ) );
int numEvents = Sys_PollJoystickInputEvents( 0 );
// Study each of the buffer elements and process them.
for ( int i = 0; i < numEvents; i++ ) {
int action;
int value;
if ( Sys_ReturnJoystickInputEvent( i, action, value ) ) {
if ( action >= J_ACTION_FIRST && action <= J_ACTION_MAX ) {
int joyButton = K_FIRST_JOY + ( action - J_ACTION_FIRST );
Key( joyButton, ( value != 0 ) );
} else if ( ( action >= J_AXIS_MIN ) && ( action <= J_AXIS_MAX ) ) {
joystickAxis[ action - J_AXIS_MIN ] = static_cast<float>( value ) / 32767.0f;
} else {
//assert( !"Unknown joystick event" );
}
}
}
Sys_EndJoystickInputEvents();
}
/*
@ -1065,7 +1326,9 @@ void idUsercmdGenLocal::UsercmdInterrupt( void ) {
Keyboard();
// process the system joystick events
Joystick();
if ( in_useGamepad.GetBool() ) {
Joystick();
}
// create the usercmd for com_ticNumber+1
MakeCurrent();
@ -1095,6 +1358,11 @@ idUsercmdGenLocal::GetDirectUsercmd
*/
usercmd_t idUsercmdGenLocal::GetDirectUsercmd( void ) {
pollTime = Sys_Milliseconds();
if ( pollTime - lastPollTime > 100 ) {
lastPollTime = pollTime - 100;
}
// initialize current usercmd
InitCurrent();
@ -1105,12 +1373,15 @@ usercmd_t idUsercmdGenLocal::GetDirectUsercmd( void ) {
Keyboard();
// process the system joystick events
Joystick();
if ( in_useGamepad.GetBool() ) {
Joystick();
}
// create the usercmd
MakeCurrent();
cmd.duplicateCount = 0;
lastPollTime = pollTime;
return cmd;
}

View file

@ -96,8 +96,8 @@ public:
signed char rightmove; // left/right movement
signed char upmove; // up/down movement
short angles[3]; // view angles
short mx; // mouse delta x
short my; // mouse delta y
short mx; // mouse delta x - DG: not really delta, but from continuousMouseX which accumulates
short my; // mouse delta y - DG: same but from continuousMouseY
signed char impulse; // impulse command
byte flags; // additional flags
int sequence; // just for debugging

View file

@ -60,6 +60,9 @@ If you have questions concerning this license or the applicable additional terms
#define SDLK_PRINTSCREEN SDLK_PRINT
#endif
extern idCVar in_useGamepad; // from UsercmdGen.cpp
extern idCVar joy_deadZone; // ditto
// NOTE: g++-4.7 doesn't like when this is static (for idCmdSystem::ArgCompletion_String<kbdNames>)
const char *_in_kbdNames[] = {
#if SDL_VERSION_ATLEAST(2, 0, 0) // auto-detection is only available for SDL2
@ -77,11 +80,21 @@ static idCVar in_nograb("in_nograb", "0", CVAR_SYSTEM | CVAR_NOCHEAT, "prevents
static idCVar in_grabKeyboard("in_grabKeyboard", "0", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_NOCHEAT | CVAR_BOOL,
"if enabled, grabs all keyboard input if mouse is grabbed (so keyboard shortcuts from the OS like Alt-Tab or Windows Key won't work)");
idCVar joy_gamepadLayout("joy_gamepadLayout", "-1", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_NOCHEAT | CVAR_INTEGER,
"Button layout of gamepad. -1: auto (needs SDL 2.0.12 or newer), 0: XBox-style, 1: Nintendo-style, 2: PS4/5-style, 3: PS2/3-style", idCmdSystem::ArgCompletion_Integer<-1, 3> );
// set in handleMouseGrab(), used in Sys_GetEvent() to decide what kind of internal mouse event to generate
static bool in_relativeMouseMode = true;
// set in Sys_GetEvent() on window focus gained/lost events
static bool in_hasFocus = true;
static enum D3_Gamepad_Type {
D3_GAMEPAD_XINPUT, // XBox/XInput standard, the default
D3_GAMEPAD_NINTENDO, // nintendo-like (A/B and X/Y are switched)
D3_GAMEPAD_PLAYSTATION, // PS-like (geometric symbols instead of A/B/X/Y)
D3_GAMEPAD_PLAYSTATION_OLD // PS2/PS3-like: the back button is called "select" instead of "share"
} gamepadType = D3_GAMEPAD_XINPUT;
struct kbd_poll_t {
int key;
bool state;
@ -108,8 +121,26 @@ struct mouse_poll_t {
}
};
struct joystick_poll_t {
int action;
int value;
joystick_poll_t() : action(0), value(0) {} // TODO: or -1?
joystick_poll_t(int a, int v) {
action = a;
value = v;
}
};
static idList<kbd_poll_t> kbd_polls;
static idList<mouse_poll_t> mouse_polls;
static idList<joystick_poll_t> joystick_polls;
static bool buttonStates[K_LAST_KEY];
static float joyAxis[MAX_JOYSTICK_AXIS];
static idList<sysEvent_t> event_overflow;
#if SDL_VERSION_ATLEAST(2, 0, 0)
// for utf8ToISO8859_1() - used for non-ascii text input and Sys_GetLocalizedScancodeName()
@ -171,7 +202,7 @@ static scancodename_t scancodemappings[] = {
D3_SC_MAPPING(COMMA),
D3_SC_MAPPING(PERIOD),
D3_SC_MAPPING(SLASH),
// leaving out lots of key incl. from keypad, we already handle them as normal keys
// leaving out lots of keys incl. from keypad, we already handle them as normal keys
D3_SC_MAPPING(NONUSBACKSLASH),
D3_SC_MAPPING(INTERNATIONAL1), /**< used on Asian keyboards, see footnotes in USB doc */
D3_SC_MAPPING(INTERNATIONAL2),
@ -246,6 +277,137 @@ static bool utf8ToISO8859_1(const char* inbuf, char* outbuf, size_t outsize) {
}
#endif // SDL2
const char* Sys_GetLocalizedJoyKeyName( int key ) {
// Note: trying to keep the returned names short, because the Doom3 binding window doesn't have much space for names..
#if SDL_VERSION_ATLEAST(2, 0, 0) // gamecontroller/gamepad not supported in SDL1
if (key >= K_FIRST_JOY && key <= K_LAST_JOY) {
if (key <= K_JOY_BTN_BACK) {
#if SDL_VERSION_ATLEAST(3, 0, 0)
// TODO: or use the SDL2 code and just set joy_gamepadLayout automatically based on SDL_GetGamepadType() ?
SDL_GamepadButton gpbtn = SDL_GAMEPAD_BUTTON_SOUTH + (key - K_JOY_BTN_SOUTH);
SDL_GamepadButtonLabel label = SDL_GetGamepadButtonLabelForType(TODO, gpbtn);
switch(label) {
case SDL_GAMEPAD_BUTTON_LABEL_A:
return "Pad A";
case SDL_GAMEPAD_BUTTON_LABEL_B:
return "Pad B";
case SDL_GAMEPAD_BUTTON_LABEL_X:
return "Pad X";
case SDL_GAMEPAD_BUTTON_LABEL_Y:
return "Pad Y";
case SDL_GAMEPAD_BUTTON_LABEL_CROSS:
return "Pad Cross";
case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE:
return "Pad Circle";
case SDL_GAMEPAD_BUTTON_LABEL_SQUARE:
return "Pad Square";
case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE:
return "Pad Triangle";
}
#else // SDL2
// South, East, West, North Back
static const char* xboxBtnNames[5] = { "Pad A", "Pad B", "Pad X", "Pad Y", "Pad Back" };
static const char* nintendoBtnNames[5] = { "Pad B", "Pad A", "Pad Y", "Pad X", "Pad -" };
static const char* psBtnNames[5] = { "Pad Cross", "Pad Circle", "Pad Square", "Pad Triangle", "Pad Share" };
int layout = joy_gamepadLayout.GetInteger();
if ( layout == -1 ) {
layout = gamepadType;
}
unsigned btnIdx = key - K_JOY_BTN_SOUTH;
assert(btnIdx < 5);
switch( layout ) {
default:
common->Warning( "joy_gamepadLayout has invalid value %d !\n", joy_gamepadLayout.GetInteger() );
// fall-through
case D3_GAMEPAD_XINPUT:
return xboxBtnNames[btnIdx];
case D3_GAMEPAD_NINTENDO:
return nintendoBtnNames[btnIdx];
case D3_GAMEPAD_PLAYSTATION_OLD:
if ( key == K_JOY_BTN_BACK )
return "Pad Select";
// the other button names are identical for PS2/3 and PS4/5
// fall-through
case D3_GAMEPAD_PLAYSTATION:
return psBtnNames[btnIdx];
}
#endif // face button names for SDL2
}
// the labels for the remaining keys are the same for SDL2 and SDL3 (and all controllers)
switch(key) {
case K_JOY_BTN_GUIDE: // can't be used in dhewm3, because it opens steam on some systems
case K_JOY_BTN_START: // can't be used for bindings, because it's hardcoded to generate Esc
return NULL;
case K_JOY_BTN_LSTICK:
return "Pad LStick";
case K_JOY_BTN_RSTICK:
return "Pad RStick";
case K_JOY_BTN_LSHOULDER:
return "Pad LShoulder";
case K_JOY_BTN_RSHOULDER:
return "Pad RShoulder";
case K_JOY_DPAD_UP:
return "DPad Up";
case K_JOY_DPAD_DOWN:
return "DPad Down";
case K_JOY_DPAD_LEFT:
return "DPad Left";
case K_JOY_DPAD_RIGHT:
return "DPad Right";
case K_JOY_BTN_MISC1:
return "Pad Misc";
case K_JOY_BTN_RPADDLE1:
return "Pad P1";
case K_JOY_BTN_LPADDLE1:
return "Pad P3";
case K_JOY_BTN_RPADDLE2:
return "Pad P2";
case K_JOY_BTN_LPADDLE2:
return "Pad P4";
// Note: Would be nicer with "Pad " (or even "Gamepad ") at the beginning,
// but then it's too long for the keybinding window :-/
case K_JOY_STICK1_UP:
return "Stick1 Up";
case K_JOY_STICK1_DOWN:
return "Stick1 Down";
case K_JOY_STICK1_LEFT:
return "Stick1 Left";
case K_JOY_STICK1_RIGHT:
return "Stick1 Right";
case K_JOY_STICK2_UP:
return "Stick2 Up";
case K_JOY_STICK2_DOWN:
return "Stick2 Down";
case K_JOY_STICK2_LEFT:
return "Stick2 Left";
case K_JOY_STICK2_RIGHT:
return "Stick2 Right";
case K_JOY_TRIGGER1:
return "Trigger 1";
case K_JOY_TRIGGER2:
return "Trigger 2";
default:
assert(0 && "missing a case in Sys_GetLocalizedJoyKeyName()!");
}
}
#endif // SDL2+
return NULL;
}
// returns localized name of the key (between K_FIRST_SCANCODE and K_LAST_SCANCODE),
// regarding the current keyboard layout - if that name is in ASCII or corresponds
// to a "High-ASCII" char supported by Doom3.
@ -477,6 +639,162 @@ static byte mapkey(SDL_Keycode key) {
return 0;
}
#if SDL_VERSION_ATLEAST(2, 0, 0)
#if ! SDL_VERSION_ATLEAST(2, 0, 14)
// Hack: to support newer SDL2 runtime versions than the one built against,
// define these controller buttons if needed
enum {
SDL_CONTROLLER_BUTTON_MISC1 = 15, /* Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button, Amazon Luna microphone button */
SDL_CONTROLLER_BUTTON_PADDLE1, /* Xbox Elite paddle P1 */
SDL_CONTROLLER_BUTTON_PADDLE2, /* Xbox Elite paddle P3 */
SDL_CONTROLLER_BUTTON_PADDLE3, /* Xbox Elite paddle P2 */
SDL_CONTROLLER_BUTTON_PADDLE4, /* Xbox Elite paddle P4 */
SDL_CONTROLLER_BUTTON_TOUCHPAD, /* PS4/PS5 touchpad button */
};
#endif // ! SDL_VERSION_ATLEAST(2, 0, 14)
static sys_jEvents mapjoybutton(SDL_GameControllerButton button) {
switch (button)
{
case SDL_CONTROLLER_BUTTON_A:
return J_BTN_SOUTH;
case SDL_CONTROLLER_BUTTON_B:
return J_BTN_EAST;
case SDL_CONTROLLER_BUTTON_X:
return J_BTN_WEST;
case SDL_CONTROLLER_BUTTON_Y:
return J_BTN_NORTH;
case SDL_CONTROLLER_BUTTON_BACK:
return J_BTN_BACK;
case SDL_CONTROLLER_BUTTON_GUIDE:
// TODO: this one should probably not be bindable?
//return J_BTN_GUIDE;
break;
case SDL_CONTROLLER_BUTTON_START:
return J_BTN_START;
case SDL_CONTROLLER_BUTTON_LEFTSTICK:
return J_BTN_LSTICK;
case SDL_CONTROLLER_BUTTON_RIGHTSTICK:
return J_BTN_RSTICK;
case SDL_CONTROLLER_BUTTON_LEFTSHOULDER:
return J_BTN_LSHOULDER;
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER:
return J_BTN_RSHOULDER;
case SDL_CONTROLLER_BUTTON_DPAD_UP:
return J_DPAD_UP;
case SDL_CONTROLLER_BUTTON_DPAD_DOWN:
return J_DPAD_DOWN;
case SDL_CONTROLLER_BUTTON_DPAD_LEFT:
return J_DPAD_LEFT;
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
return J_DPAD_RIGHT;
case SDL_CONTROLLER_BUTTON_MISC1:
return J_BTN_MISC1;
case SDL_CONTROLLER_BUTTON_PADDLE1:
return J_BTN_RPADDLE1;
case SDL_CONTROLLER_BUTTON_PADDLE2:
return J_BTN_RPADDLE2;
case SDL_CONTROLLER_BUTTON_PADDLE3:
return J_BTN_LPADDLE1;
case SDL_CONTROLLER_BUTTON_PADDLE4:
return J_BTN_LPADDLE2;
default:
common->Warning("unknown game controller button %u", button);
break;
}
return MAX_JOY_EVENT;
}
static sys_jEvents mapjoyaxis(SDL_GameControllerAxis axis) {
switch (axis)
{
case SDL_CONTROLLER_AXIS_LEFTX:
return J_AXIS_LEFT_X;
case SDL_CONTROLLER_AXIS_LEFTY:
return J_AXIS_LEFT_Y;
case SDL_CONTROLLER_AXIS_RIGHTX:
return J_AXIS_RIGHT_X;
case SDL_CONTROLLER_AXIS_RIGHTY:
return J_AXIS_RIGHT_Y;
case SDL_CONTROLLER_AXIS_TRIGGERLEFT:
return J_AXIS_LEFT_TRIG;
case SDL_CONTROLLER_AXIS_TRIGGERRIGHT:
return J_AXIS_RIGHT_TRIG;
default:
common->Warning("unknown game controller axis %u", axis);
break;
}
return J_AXIS_MAX;
}
#if ! SDL_VERSION_ATLEAST(2, 24, 0)
// Hack: to support newer SDL2 runtime versions than the one compiled against,
// define some controller types that were added after 2.0.12
enum {
#if ! SDL_VERSION_ATLEAST(2, 0, 14)
SDL_CONTROLLER_TYPE_PS5 = 7,
#endif
// leaving out luna and stadia (from 2.0.16)
// and nvidia shield (from 2.24), they're similar enough to XBox/XInput
// the following were added in 2.24
SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT = 11,
SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT,
SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR
};
#endif // ! SDL_VERSION_ATLEAST(2, 24, 0)
static void setGamepadType( SDL_GameController* gc )
{
#if SDL_VERSION_ATLEAST(2, 0, 12)
const char* typestr = NULL;
switch( SDL_GameControllerGetType( gc ) ) {
default: // the other controller like luna, stadia, whatever, have a very similar layout
case SDL_CONTROLLER_TYPE_UNKNOWN:
case SDL_CONTROLLER_TYPE_XBOX360:
case SDL_CONTROLLER_TYPE_XBOXONE:
gamepadType = D3_GAMEPAD_XINPUT;
typestr = "XBox-like";
break;
case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO:
case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT:
case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT:
case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR:
gamepadType = D3_GAMEPAD_NINTENDO;
typestr = "Nintendo-like";
break;
case SDL_CONTROLLER_TYPE_PS3:
gamepadType = D3_GAMEPAD_PLAYSTATION_OLD;
typestr = "Playstation2/3-like";
break;
case SDL_CONTROLLER_TYPE_PS4:
case SDL_CONTROLLER_TYPE_PS5:
gamepadType = D3_GAMEPAD_PLAYSTATION;
typestr = "Playstation-like";
break;
}
common->Printf( "Detected Gamepad %s as type %s\n", SDL_GameControllerName( gc ), typestr );
SDL_Joystick* joy = SDL_GameControllerGetJoystick( gc );
SDL_JoystickGUID guid = SDL_JoystickGetGUID( joy );
char guidstr[34] = {};
SDL_JoystickGetGUIDString( guid, guidstr, sizeof(guidstr) );
Uint16 vendor = SDL_GameControllerGetVendor( gc );
Uint16 product = SDL_GameControllerGetProduct( gc );
const char* joyname = SDL_JoystickName( joy );
common->Printf( " USB IDs: %.4hx:%.4hx Joystick Name: \"%s\" GUID: %s\n", vendor, product, joyname, guidstr );
#endif // SDL_VERSION_ATLEAST(2, 0, 12)
}
#endif // SDL2+ gamecontroller code
static void PushConsoleEvent(const char *s) {
char *b;
size_t len;
@ -531,6 +849,28 @@ void Sys_InitInput() {
#else // SDL1.2 doesn't support this
in_grabKeyboard.ClearModified();
#endif
joystick_polls.SetGranularity(64);
event_overflow.SetGranularity(64);
memset( buttonStates, 0, sizeof( buttonStates ) );
memset( joyAxis, 0, sizeof( joyAxis ) );
#if SDL_VERSION_ATLEAST(2, 0, 0) // gamecontroller/gamepad not supported in SDL1
// use button positions instead of button labels,
// Sys_GetLocalizedJoyKeyName() will do the translation
// (I think this also was the default before 2.0.12?)
SDL_SetHint("SDL_GAMECONTROLLER_USE_BUTTON_LABELS", "0");
const int NumJoysticks = SDL_NumJoysticks();
for( int i = 0; i < NumJoysticks; ++i )
{
SDL_GameController* gc = SDL_GameControllerOpen( i );
if ( gc != NULL ) {
setGamepadType( gc );
}
}
#endif
}
/*
@ -541,6 +881,8 @@ Sys_ShutdownInput
void Sys_ShutdownInput() {
kbd_polls.Clear();
mouse_polls.Clear();
joystick_polls.Clear();
event_overflow.Clear();
#if SDL_VERSION_ATLEAST(2, 0, 0)
SDL_iconv_close( iconvDesc ); // used by utf8ToISO8859_1()
iconvDesc = ( SDL_iconv_t ) -1;
@ -666,6 +1008,67 @@ void Sys_GrabMouseCursor(bool grabIt) {
GLimp_GrabInput(flags);
}
/*
===============
Sys_SetInteractiveIngameGuiActive
Tell the input system that currently an interactive *ingame* UI has focus,
so there is an active cursor.
Used for an ungodly hack to make gamepad button south (A) behave like
left mouse button in that case, so "clicking" with gamepad in the PDA
(and ingame GUIs) works as expected.
Not set for proper menus like main menu etc - the gamepad hacks for that
are in idUserInterfaceLocal::HandleEvent().
Call with ui = NULL to clear the state.
I hope this won't explode in my face :-p
===============
*/
bool D3_IN_interactiveIngameGuiActive = false;
void Sys_SetInteractiveIngameGuiActive( bool active, idUserInterface* ui )
{
static idList<idUserInterface*> lastuis;
if ( ui == NULL ) {
// special case for clearing
D3_IN_interactiveIngameGuiActive = false;
lastuis.Clear();
return;
}
int idx = lastuis.FindIndex( ui );
if ( sessLocal.GetActiveMenu() == NULL && active ) {
// add ui to lastuis, if it has been activated and no proper menu
// (like main menu) is currently open
lastuis.Append( ui );
} else if ( idx != -1 ) {
// if the UI is in lastuis and has been deactivated, or there
// is a proper menu opened, remove it from the list.
// this both handles the regular deactivate case and also works around
// main-menu-in-multiplayer weirdness: that menu calls idUserInterface::Activate()
// with activate = true twice, but on first call sessLocal.GetActiveMenu() is NULL
// so we want to remove it once we realize that it really is a "proper" menu after all.
// And because it's possible that we have an ingame UI focussed while opening
// the multiplayer-main-menu, we keep a list of lastuis, instead of just one,
// so D3_IN_interactiveIngameGuiActive remains true in that case
// (the ingame UI is still in the list)
lastuis.RemoveIndex( idx );
}
D3_IN_interactiveIngameGuiActive = lastuis.Num() != 0;
}
static void PushButton( int key, bool value ) {
// So we don't keep sending the same SE_KEY message over and over again
if ( buttonStates[key] != value ) {
buttonStates[key] = value;
sysEvent_t res = { SE_KEY, key, value ? 1 : 0, 0, NULL };
// this is done to generate two events per controller axis event
// one SE_JOYSTICK and one SE_KEY
event_overflow.Append(res);
}
}
/*
================
Sys_GetEvent
@ -678,6 +1081,14 @@ sysEvent_t Sys_GetEvent() {
static const sysEvent_t res_none = { SE_NONE, 0, 0, 0, NULL };
// process any overflow.
if (event_overflow.Num() > 0)
{
res = event_overflow[0];
event_overflow.RemoveIndex(0);
return res;
}
#if SDL_VERSION_ATLEAST(2, 0, 0)
static char s[SDL_TEXTINPUTEVENT_TEXT_SIZE] = {0};
static size_t s_pos = 0;
@ -958,6 +1369,120 @@ sysEvent_t Sys_GetEvent() {
return res;
#if SDL_VERSION_ATLEAST(2, 0, 0) // gamecontroller/gamepad not supported in SDL1
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
{
if ( !in_useGamepad.GetBool() ) {
common->Warning( "Gamepad support is disabled! Set the in_useGamepad CVar to 1 to enable it!\n" );
continue;
}
res.evType = SE_KEY;
res.evValue2 = ev.cbutton.state == SDL_PRESSED ? 1 : 0;
// special case: always treat the start button as escape so it opens/closes the menu
// (also makes that button non-bindable)
if ( ev.cbutton.button == SDL_CONTROLLER_BUTTON_START ) {
res.evValue = K_ESCAPE;
return res;
} else if( (ev.cbutton.button == SDL_CONTROLLER_BUTTON_A || ev.cbutton.button == SDL_CONTROLLER_BUTTON_Y)
&& D3_IN_interactiveIngameGuiActive && sessLocal.GetActiveMenu() == NULL )
{
// ugly hack: currently an interactive ingame GUI (with a cursor) is active/focused
// so pretend that the gamepads A (south) or Y (north, used by D3BFG to click ingame GUIs) button
// is the left mouse button so it can be used for "clicking"..
mouse_polls.Append( mouse_poll_t(M_ACTION1, res.evValue2) );
res.evValue = K_MOUSE1;
return res;
}
sys_jEvents jEvent = mapjoybutton( (SDL_GameControllerButton)ev.cbutton.button );
joystick_polls.Append( joystick_poll_t(jEvent, ev.cbutton.state == SDL_PRESSED ? 1 : 0) );
if ( ( jEvent >= J_ACTION_FIRST ) && ( jEvent <= J_ACTION_MAX ) ) {
res.evValue = K_FIRST_JOY + ( jEvent - J_ACTION_FIRST );
return res;
}
continue; // try to get a decent event.
}
case SDL_CONTROLLERAXISMOTION:
{
const int range = 16384;
if ( !in_useGamepad.GetBool() ) {
// not printing a message here, I guess we get lots of spurious axis events..
// TODO: or print a message if value is big enough?
continue;
}
sys_jEvents jEvent = mapjoyaxis( (SDL_GameControllerAxis)ev.caxis.axis);
joystick_polls.Append(joystick_poll_t( jEvent, ev.caxis.value) );
if ( jEvent == J_AXIS_LEFT_X ) {
PushButton( K_JOY_STICK1_LEFT, ( ev.caxis.value < -range ) );
PushButton( K_JOY_STICK1_RIGHT, ( ev.caxis.value > range ) );
} else if ( jEvent == J_AXIS_LEFT_Y ) {
PushButton( K_JOY_STICK1_UP, ( ev.caxis.value < -range ) );
PushButton( K_JOY_STICK1_DOWN, ( ev.caxis.value > range ) );
} else if ( jEvent == J_AXIS_RIGHT_X ) {
PushButton( K_JOY_STICK2_LEFT, ( ev.caxis.value < -range ) );
PushButton( K_JOY_STICK2_RIGHT, ( ev.caxis.value > range ) );
} else if ( jEvent == J_AXIS_RIGHT_Y ) {
PushButton( K_JOY_STICK2_UP, ( ev.caxis.value < -range ) );
PushButton( K_JOY_STICK2_DOWN, ( ev.caxis.value > range ) );
} else if ( jEvent == J_AXIS_LEFT_TRIG ) {
PushButton( K_JOY_TRIGGER1, ( ev.caxis.value > range ) );
} else if ( jEvent == J_AXIS_RIGHT_TRIG ) {
PushButton( K_JOY_TRIGGER2, ( ev.caxis.value > range ) );
}
if ( jEvent >= J_AXIS_MIN && jEvent <= J_AXIS_MAX ) {
// NOTE: the stuff set here is only used to move the cursor in menus
// ingame movement is done via joystick_polls
int axis = jEvent - J_AXIS_MIN;
float dz = joy_deadZone.GetFloat();
float val = fabsf(ev.caxis.value * (1.0f / 32767.0f));
if(val < dz) {
val = 0.0f;
} else {
// from deadzone .. 1 to 0 .. 1-deadzone
val -= dz;
// and then to 0..1
val = val * (1.0f / (1.0f - dz));
if( ev.caxis.value < 0 ) {
val = -val;
}
}
joyAxis[axis] = val;
}
// handle next event; joy axis events are generated below,
// when there are no further SDL events
continue;
}
break;
case SDL_JOYDEVICEADDED:
{
SDL_GameController* gc = SDL_GameControllerOpen( ev.jdevice.which );
if ( gc != NULL ) {
setGamepadType( gc );
}
// TODO: hot swapping maybe.
//lbOnControllerPlugIn(event.jdevice.which);
break;
}
case SDL_JOYDEVICEREMOVED:
// TODO: hot swapping maybe.
//lbOnControllerUnPlug(event.jdevice.which);
break;
#endif // SDL2+
case SDL_QUIT:
PushConsoleEvent("quit");
return res_none;
@ -980,6 +1505,29 @@ sysEvent_t Sys_GetEvent() {
}
}
// before returning res_none for "these were all events for now",
// first return joyaxis events, if gamepad is enabled and 16ms are over
// (or we haven't returned the values for all axis yet)
if ( in_useGamepad.GetBool() ) {
static unsigned int lastMS = 0;
static int joyAxisToSend = 0;
unsigned int nowMS = Sys_Milliseconds();
if ( nowMS - lastMS >= 16 ) {
int val = joyAxis[joyAxisToSend] * 100; // float to percent
res.evType = SE_JOYSTICK;
res.evValue = joyAxisToSend;
res.evValue2 = val;
++joyAxisToSend;
if(joyAxisToSend == MAX_JOYSTICK_AXIS) {
// we're done for this frame, so update lastMS and reset joyAxisToSend
joyAxisToSend = 0;
lastMS = nowMS;
}
return res;
}
}
return res_none;
}
@ -996,6 +1544,12 @@ void Sys_ClearEvents() {
kbd_polls.SetNum(0, false);
mouse_polls.SetNum(0, false);
joystick_polls.SetNum(0, false);
memset( buttonStates, 0, sizeof( buttonStates ) );
memset( joyAxis, 0, sizeof( joyAxis ) );
event_overflow.SetNum(0, false);
}
static void handleMouseGrab() {
@ -1146,3 +1700,35 @@ Sys_EndMouseInputEvents
void Sys_EndMouseInputEvents() {
mouse_polls.SetNum(0, false);
}
/*
================
Joystick Input Methods
================
*/
void Sys_SetRumble( int device, int low, int hi ) {
// TODO: support multiple controllers.
assert(device == 0);
// TODO: support rumble maybe.
assert(0);
}
int Sys_PollJoystickInputEvents( int deviceNum ) {
// TODO: support multiple controllers.
assert(deviceNum == 0);
return joystick_polls.Num();
}
int Sys_ReturnJoystickInputEvent( const int n, int &action, int &value ) {
if (n >= joystick_polls.Num())
return 0;
action = joystick_polls[n].action;
value = joystick_polls[n].value;
return 1;
}
void Sys_EndJoystickInputEvents() {
joystick_polls.SetNum(0, false);
}

View file

@ -44,12 +44,12 @@ typedef enum {
} cpuidSimd_t;
typedef enum {
AXIS_SIDE,
AXIS_FORWARD,
AXIS_UP,
AXIS_ROLL,
AXIS_YAW,
AXIS_PITCH,
AXIS_LEFT_X,
AXIS_LEFT_Y,
AXIS_RIGHT_X,
AXIS_RIGHT_Y,
AXIS_LEFT_TRIG,
AXIS_RIGHT_TRIG,
MAX_JOYSTICK_AXIS
} joystickAxis_t;
@ -59,7 +59,7 @@ typedef enum {
SE_CHAR, // evValue is an ascii char
SE_MOUSE, // evValue and evValue2 are relative signed x / y moves
SE_MOUSE_ABS, // evValue and evValue2 are absolute x / y coordinates in the window
SE_JOYSTICK_AXIS, // evValue is an axis number and evValue2 is the current state (-127 to 127)
SE_JOYSTICK, // evValue is an axis number and evValue2 is the current state (-127 to 127)
SE_CONSOLE // evPtr is a char*, from typing something at a non-game console
} sysEventType_t;
@ -77,10 +77,52 @@ typedef enum {
M_DELTAZ
} sys_mEvents;
typedef enum {
J_ACTION_FIRST,
// these names are similar to the SDL3 SDL_GamepadButton names
J_BTN_SOUTH = J_ACTION_FIRST, // bottom face button, like Xbox A
J_BTN_EAST, // right face button, like Xbox B
J_BTN_WEST, // left face button, like Xbox X
J_BTN_NORTH, // top face button, like Xbox Y
J_BTN_BACK,
J_BTN_GUIDE, // Note: this one should probably not be used?
J_BTN_START,
J_BTN_LSTICK, // press left stick
J_BTN_RSTICK, // press right stick
J_BTN_LSHOULDER,
J_BTN_RSHOULDER,
J_DPAD_UP,
J_DPAD_DOWN,
J_DPAD_LEFT,
J_DPAD_RIGHT,
J_BTN_MISC1, // Additional button (e.g. Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button, Amazon Luna microphone button)
J_BTN_RPADDLE1, // Upper or primary paddle, under your right hand (e.g. Xbox Elite paddle P1)
J_BTN_LPADDLE1, // Upper or primary paddle, under your left hand (e.g. Xbox Elite paddle P3)
J_BTN_RPADDLE2, // Lower or secondary paddle, under your right hand (e.g. Xbox Elite paddle P2)
J_BTN_LPADDLE2, // Lower or secondary paddle, under your left hand (e.g. Xbox Elite paddle P4)
J_ACTION_MAX = J_BTN_LPADDLE2,
// leaving some space here for about 12 additional J_ACTIONs, if needed
J_AXIS_MIN = 32,
J_AXIS_LEFT_X = J_AXIS_MIN + AXIS_LEFT_X,
J_AXIS_LEFT_Y = J_AXIS_MIN + AXIS_LEFT_Y,
J_AXIS_RIGHT_X = J_AXIS_MIN + AXIS_RIGHT_X,
J_AXIS_RIGHT_Y = J_AXIS_MIN + AXIS_RIGHT_Y,
J_AXIS_LEFT_TRIG = J_AXIS_MIN + AXIS_LEFT_TRIG,
J_AXIS_RIGHT_TRIG = J_AXIS_MIN + AXIS_RIGHT_TRIG,
J_AXIS_MAX = J_AXIS_MIN + MAX_JOYSTICK_AXIS - 1,
MAX_JOY_EVENT
} sys_jEvents;
struct sysEvent_t {
sysEventType_t evType;
int evValue;
int evValue2;
int evValue; // for keys: K_* or ASCII code; for joystick: axis; for mouse: mouseX
int evValue2; // for keys: 0/1 for up/down; for axis: value; for mouse: mouseY
int evPtrLength; // bytes of data pointed to by evPtr, for journaling
void * evPtr; // this must be manually freed if not NULL
};
@ -179,6 +221,12 @@ const char* Sys_GetLocalizedScancodeName( int key );
// returns keyNum_t (K_SC_* constant) for given scancode name (like "SC_A")
int Sys_GetKeynumForScancodeName( const char* name );
// returns display name of the key (between K_FIRST_JOY and K_LAST_JOY)
// With SDL2 it'll return the name in the SDL_GameController standard layout
// (which is based on XBox/XInput => on Nintendo gamepads, A/B and X/Y will be flipped),
// with SDL3 it will return the "real" button name
const char* Sys_GetLocalizedJoyKeyName( int key );
// keyboard input polling
int Sys_PollKeyboardInputEvents( void );
int Sys_ReturnKeyboardInputEvent( const int n, int &ch, bool &state );
@ -189,11 +237,23 @@ int Sys_PollMouseInputEvents( void );
int Sys_ReturnMouseInputEvent( const int n, int &action, int &value );
void Sys_EndMouseInputEvents( void );
// joystick input polling
void Sys_SetRumble( int device, int low, int hi );
int Sys_PollJoystickInputEvents( int deviceNum );
int Sys_ReturnJoystickInputEvent( const int n, int &action, int &value );
void Sys_EndJoystickInputEvents();
// when the console is down, or the game is about to perform a lengthy
// operation like map loading, the system can release the mouse cursor
// when in windowed mode
void Sys_GrabMouseCursor( bool grabIt );
// DG: added this for an ungodly hack for gamepad support
// active = true means "currently a GUI with a cursor is active/focused"
// active = false means "that GUI is not active anymore"
class idUserInterface;
void Sys_SetInteractiveIngameGuiActive( bool active, idUserInterface* ui );
void Sys_ShowWindow( bool show );
bool Sys_IsWindowVisible( void );
void Sys_ShowConsole( int visLevel, bool quitOnClose );

View file

@ -118,6 +118,9 @@ void idUserInterfaceManagerLocal::EndLevelLoad() {
}
}
}
// DG: this should probably be reset at this point
Sys_SetInteractiveIngameGuiActive( false, NULL );
}
void idUserInterfaceManagerLocal::Reload( bool all ) {
@ -344,6 +347,9 @@ const char *idUserInterfaceLocal::HandleEvent( const sysEvent_t *event, int _tim
return ret;
}
// DG: used to translate gamepad input into events the UI system is familiar with
sysEvent_t fakedEvent = {};
if ( event->evType == SE_MOUSE || event->evType == SE_MOUSE_ABS ) {
if ( !desktop || (desktop->GetFlags() & WIN_MENUGUI) ) {
// DG: this is a fullscreen GUI, scale the mousedelta added to cursorX/Y
@ -401,6 +407,80 @@ const char *idUserInterfaceLocal::HandleEvent( const sysEvent_t *event, int _tim
cursorY = 0;
}
}
else if ( event->evType == SE_JOYSTICK && event->evValue2 != 0 && event->evValue < 4 )
{
// evValue: axis = jEvent - J_AXIS_MIN;
// evValue2: percent (-100 to 100)
// currently uses both sticks for cursor movement
// TODO could use one stick for scrolling (maybe by generating K_UPARROW/DOWNARROW events?)
float addVal = expf( fabsf(event->evValue2 * 0.03f) ) * 0.5f;
if(event->evValue2 < 0)
addVal = -addVal;
if( event->evValue == 0 || event->evValue == 2 ) {
cursorX += addVal;
} else if( event->evValue == 1 || event->evValue == 3 ) {
cursorY += addVal;
}
if (cursorX < 0) {
cursorX = 0;
}
if (cursorY < 0) {
cursorY = 0;
}
// some things like highlighting hovered UI elements need a mouse event,
// so create a fake mouse event
fakedEvent.evType = SE_MOUSE;
// the coordinates (evValue/evValue2) aren't used, but keeping them at 0
// (as default-initialized above) shouldn't hurt either way
event = &fakedEvent;
}
else if ( event->evType == SE_KEY && event->evValue >= K_FIRST_JOY && event->evValue <= K_LAST_JOY )
{
// map some gamepad buttons to SE_KEY events that the UI already knows how to use
int key = 0;
if( idKeyInput::GetUsercmdAction( event->evValue ) == 20 /* UB_ATTACK*/ ) {
// if this button is bound to _attack (fire), treat it as left mouse button
key = K_MOUSE1;
} else {
switch(event->evValue) {
// emulate mouse buttons
case K_JOY_BTN_SOUTH: // A on xbox controller
key = K_MOUSE1;
break;
case K_JOY_BTN_EAST: // B on xbox controller
key = K_MOUSE2;
break;
// emulate cursor keys (sometimes used for scrolling or selecting in a list)
case K_JOY_DPAD_UP:
key = K_UPARROW;
break;
case K_JOY_DPAD_DOWN:
key = K_DOWNARROW;
break;
case K_JOY_DPAD_LEFT:
key = K_LEFTARROW;
break;
case K_JOY_DPAD_RIGHT:
key = K_RIGHTARROW;
break;
// enter is useful after selecting something with cursor keys (or dpad)
// in a list, like selecting a savegame - I guess left trigger is suitable for that?
// (right trigger is often used for shooting, which we use as K_MOUSE1 here)
case K_JOY_TRIGGER1:
key = K_ENTER;
break;
}
}
if (key != 0) {
fakedEvent = *event;
fakedEvent.evValue = key;
event = &fakedEvent;
}
}
if ( desktop ) {
return desktop->HandleEvent( event, updateVisuals );
@ -507,6 +587,10 @@ const char *idUserInterfaceLocal::Activate(bool activate, int _time) {
time = _time;
active = activate;
if ( desktop ) {
// DG: added this hack for gamepad input
if ( interactive ) {
Sys_SetInteractiveIngameGuiActive( activate, this );
} // DG end
activateStr = "";
desktop->Activate( activate, activateStr );
return activateStr;