From 2a5cce543bfd265f327b796b28904584a8db1e95 Mon Sep 17 00:00:00 2001 From: Marcus Minhorst <136136617+the-phinet@users.noreply.github.com> Date: Sat, 5 Jul 2025 16:41:40 -0400 Subject: [PATCH] Gamepad Improvements * Cleanup: Alignment, long lines, Replace 0 with SDLK_UNKNOWN * Gamecontroller api analogue input * Added some button handling * Added mapping for other buttons * Added trigger events * Added force_joystick flag * Removed force_joystick flag Rationale: 1. It was actually broken lol 2. I cannot think of a case where enabling this would be a useful thing for gzdoom. If the user is using a gamecontroller, it is pointless. If they are not using a gamecontroller, it will just default to using the joystick api. If they are not using a gamecontroller, but SDL thinks they are, it is an SDL bug, and will be reported and fixed * Modified default mapping * Added analogue to digital threshold * Added analogue response curve * Per axis settings * Fixed controller reconnect * Added threshold and curve to IJoystickConfig Enabled saving of settings * Added stubs * Cleanup Constants are no longer defines. Constants are mostly shared between backends. Moved some logic to m_joy * Implemented xinput stubs * Implemented dinput stubs * Implemented ps2 stubs (untested) * Fixed inclusive check * Implemented osx stubs (untested) * Fixed curve implementation No longer savable, I screwed the curve function up. I though it needed 2 control points, but it needs 4. Need to re-do controller settings :( * Now using CubicBezier struct * Fixed SetDefaultConfig to match xinput behavior * Expanded gamepad CCMD * Rename enum JoyResponseCurve to EJoyCurve * Initial menu implementation * Fixed SDL controller setting saving * SDL gamepads can now actually be disabled * Fixed initial controller connect of some versions of SDL * Spelling error * Enable gamepad by default * Fixed segfault on some versions of SDL * Only block keydown --- src/common/console/c_bind.cpp | 4 +- src/common/console/keydef.h | 9 +- src/common/engine/m_joy.cpp | 330 ++++++++++++++- src/common/engine/m_joy.h | 45 +- src/common/menu/joystickmenu.cpp | 50 +++ src/common/menu/menu.cpp | 2 +- .../platform/posix/cocoa/i_joystick.cpp | 112 ++++- src/common/platform/posix/sdl/i_input.cpp | 250 ++++++++---- src/common/platform/posix/sdl/i_input.h | 13 + src/common/platform/posix/sdl/i_joystick.cpp | 386 ++++++++++++++---- src/common/platform/win32/i_dijoy.cpp | 168 +++++++- src/common/platform/win32/i_rawps2.cpp | 160 +++++++- src/common/platform/win32/i_xinput.cpp | 167 +++++++- wadsrc/static/menudef.txt | 9 + .../zscript/engine/ui/menu/joystickmenu.zs | 106 ++++- wadsrc/static/zscript/engine/ui/menu/menu.zs | 19 + 16 files changed, 1614 insertions(+), 216 deletions(-) create mode 100644 src/common/platform/posix/sdl/i_input.h diff --git a/src/common/console/c_bind.cpp b/src/common/console/c_bind.cpp index fe794c736..078a9a2b1 100644 --- a/src/common/console/c_bind.cpp +++ b/src/common/console/c_bind.cpp @@ -147,7 +147,9 @@ const char *KeyNames[NUM_KEYS] = "DPadUp","DPadDown","DPadLeft","DPadRight", // Gamepad buttons "Pad_Start","Pad_Back","LThumb","RThumb", "LShoulder","RShoulder","LTrigger","RTrigger", - "Pad_A", "Pad_B", "Pad_X", "Pad_Y" + "Pad_A", "Pad_B", "Pad_X", "Pad_Y", + "Paddle_1", "Paddle_2", "Paddle_3", "Paddle_4", + "Guide", "Pad_Misc", "Pad_Touchpad" }; FKeyBindings Bindings; diff --git a/src/common/console/keydef.h b/src/common/console/keydef.h index 108806774..620b5f5e7 100644 --- a/src/common/console/keydef.h +++ b/src/common/console/keydef.h @@ -134,8 +134,15 @@ enum EKeyCodes KEY_PAD_B = 0x1C1, KEY_PAD_X = 0x1C2, KEY_PAD_Y = 0x1C3, + KEY_PAD_PADDLE1 = 0x1C4, + KEY_PAD_PADDLE2 = 0x1C5, + KEY_PAD_PADDLE3 = 0x1C6, + KEY_PAD_PADDLE4 = 0x1C7, + KEY_PAD_GUIDE = 0x1C8, + KEY_PAD_MISC1 = 0x1C9, + KEY_PAD_TOUCHPAD = 0x1CA, - NUM_KEYS = 0x1C4, + NUM_KEYS = 0x1CB, NUM_JOYAXISBUTTONS = 8, }; diff --git a/src/common/engine/m_joy.cpp b/src/common/engine/m_joy.cpp index 1e4b2f3e0..9df33efe9 100644 --- a/src/common/engine/m_joy.cpp +++ b/src/common/engine/m_joy.cpp @@ -33,6 +33,11 @@ // HEADER FILES ------------------------------------------------------------ #include +#include "c_dispatch.h" +#include "gain_analysis.h" +#include "keydef.h" +#include "name.h" +#include "tarray.h" #include "vectors.h" #include "m_joy.h" #include "configfile.h" @@ -40,6 +45,7 @@ #include "d_eventbase.h" #include "cmdlib.h" #include "printf.h" +#include "zstring.h" // MACROS ------------------------------------------------------------------ @@ -57,9 +63,26 @@ EXTERN_CVAR(Bool, joy_ps2raw) EXTERN_CVAR(Bool, joy_dinput) EXTERN_CVAR(Bool, joy_xinput) +extern const float JOYDEADZONE_DEFAULT = 0.1; // reduced from 0.25 + +extern const float JOYSENSITIVITY_DEFAULT = 1.0; + +extern const float JOYTHRESH_DEFAULT = 0.05; +extern const float JOYTHRESH_TRIGGER = 0.05; +extern const float JOYTHRESH_STICK_X = 0.65; +extern const float JOYTHRESH_STICK_Y = 0.35; + +extern const CubicBezier JOYCURVE[NUM_JOYCURVE] = { + {{0.3, 0.0, 0.7, 0.4}}, // DEFAULT -> QUADRATIC + + {{0.0, 0.0, 1.0, 1.0}}, // LINEAR + {{0.3, 0.0, 0.7, 0.4}}, // QUADRATIC + {{0.5, 0.0, 0.7, 0.2}}, // CUBIC +}; + // PUBLIC DATA DEFINITIONS ------------------------------------------------- -CUSTOM_CVARD(Bool, use_joystick, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG|CVAR_NOINITCALL, "enables input from the joystick if it is present") +CUSTOM_CVARD(Bool, use_joystick, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG|CVAR_NOINITCALL, "enables input from the joystick if it is present") { #ifdef _WIN32 joy_ps2raw->Callback(); @@ -163,6 +186,48 @@ bool M_LoadJoystickConfig(IJoystickConfig *joy) joy->SetAxisScale(i, (float)atof(value)); } + mysnprintf(key + axislen, countof(key) - axislen, "threshold"); + value = GameConfig->GetValueForKey(key); + if (value) + { + joy->SetAxisDigitalThreshold(i, (float)atof(value)); + } + + mysnprintf(key + axislen, countof(key) - axislen, "curve"); + value = GameConfig->GetValueForKey(key); + if (value) + { + joy->SetAxisResponseCurve(i, (EJoyCurve)clamp(atoi(value), (int)JOYCURVE_CUSTOM, (int)NUM_JOYCURVE-1)); + } + + mysnprintf(key + axislen, countof(key) - axislen, "curve-x1"); + value = GameConfig->GetValueForKey(key); + if (value) + { + joy->SetAxisResponseCurvePoint(i, 0, (float)atof(value)); + } + + mysnprintf(key + axislen, countof(key) - axislen, "curve-y1"); + value = GameConfig->GetValueForKey(key); + if (value) + { + joy->SetAxisResponseCurvePoint(i, 1, (float)atof(value)); + } + + mysnprintf(key + axislen, countof(key) - axislen, "curve-x2"); + value = GameConfig->GetValueForKey(key); + if (value) + { + joy->SetAxisResponseCurvePoint(i, 2, (float)atof(value)); + } + + mysnprintf(key + axislen, countof(key) - axislen, "curve-y2"); + value = GameConfig->GetValueForKey(key); + if (value) + { + joy->SetAxisResponseCurvePoint(i, 3, (float)atof(value)); + } + mysnprintf(key + axislen, countof(key) - axislen, "map"); value = GameConfig->GetValueForKey(key); if (value) @@ -227,6 +292,33 @@ void M_SaveJoystickConfig(IJoystickConfig *joy) mysnprintf(value, countof(value), "%g", joy->GetAxisScale(i)); GameConfig->SetValueForKey(key, value); } + if (!joy->IsAxisDigitalThresholdDefault(i)) + { + mysnprintf(key + axislen, countof(key) - axislen, "threshold"); + mysnprintf(value, countof(value), "%g", joy->GetAxisDigitalThreshold(i)); + GameConfig->SetValueForKey(key, value); + } + if (!joy->IsAxisResponseCurveDefault(i)) + { + mysnprintf(key + axislen, countof(key) - axislen, "curve"); + mysnprintf(value, countof(value), "%d", joy->GetAxisResponseCurve(i)); + GameConfig->SetValueForKey(key, value); + } + if (joy->GetAxisResponseCurve(i) == JOYCURVE_CUSTOM) + { + mysnprintf(key + axislen, countof(key) - axislen, "curve-x1"); + mysnprintf(value, countof(value), "%g", joy->GetAxisResponseCurvePoint(i, 0)); + GameConfig->SetValueForKey(key, value); + mysnprintf(key + axislen, countof(key) - axislen, "curve-y1"); + mysnprintf(value, countof(value), "%g", joy->GetAxisResponseCurvePoint(i, 1)); + GameConfig->SetValueForKey(key, value); + mysnprintf(key + axislen, countof(key) - axislen, "curve-x2"); + mysnprintf(value, countof(value), "%g", joy->GetAxisResponseCurvePoint(i, 2)); + GameConfig->SetValueForKey(key, value); + mysnprintf(key + axislen, countof(key) - axislen, "curve-y2"); + mysnprintf(value, countof(value), "%g", joy->GetAxisResponseCurvePoint(i, 3)); + GameConfig->SetValueForKey(key, value); + } if (!joy->IsAxisMapDefault(i)) { mysnprintf(key + axislen, countof(key) - axislen, "map"); @@ -243,6 +335,174 @@ void M_SaveJoystickConfig(IJoystickConfig *joy) } } +CCMD (gamepad) +{ + int COMMAND = 1, IDENTIFIER = 2, VALUE = 3; + int argc = argv.argc()-1; + + TArray sticks; + I_GetJoysticks(sticks); + + auto usage = []() + { + Printf( + "usage:" + "\n\tgamepad list" + "\n\tgamepad reset pad" + "\n\tgamepad enabled pad [0|1]" + "\n\tgamepad background pad [0|1]" + "\n\tgamepad sensitivity pad [float]" + "\n\tgamepad deadzone pad.axis [float]" + "\n\tgamepad scale pad.axis [float]" + "\n\tgamepad threshold pad.axis [float]" + "\n\tgamepad curve pad.axis [-1|0|1|2|3]" + "\n\tgamepad curve-x1 pad.axis [float]" + "\n\tgamepad curve-y1 pad.axis [float]" + "\n\tgamepad curve-x2 pad.axis [float]" + "\n\tgamepad curve-y2 pad.axis [float]" + "\n\tgamepad map pad.axis [-1|0|1|2|3|4]" + "\n" + ); + }; + + if (argc < COMMAND) + { + return usage(); + }; + + FName command = argv[COMMAND]; + + if (argc < IDENTIFIER) + { + if (command == "list") + { + for (int i = 0; i < sticks.SSize(); i++) + { + Printf("%d: '%s'\n", i, sticks[i]->GetName().GetChars()); + for (int j = 0; j < sticks[i]->GetNumAxes(); j++) + { + Printf(" %d.%d: '%s'\n", i, j, sticks[i]->GetAxisName(j)); + } + } + return; + } + return usage(); + } + + const char * id = argv[IDENTIFIER]; + const char * hasAxis = strchr(id, '.'); + + int pad, axis; + + try { + pad = (int)std::stod(id); + + if (pad < 0 || pad >= sticks.SSize()) + { + return (void) Printf("Pad # out of range\n"); + } + } catch (...) { + return (void) Printf("Failed to parse pad #\n"); + } + + if (hasAxis) + { + try { + axis = (int)std::stod(hasAxis+1); + + if (axis < 0 || axis >= sticks[pad]->GetNumAxes()) + { + return (void) Printf("Axis # out of range\n"); + } + } catch (...) { + return (void) Printf("Failed to parse axis #\n"); + } + } + + float value = 0; + bool set = argc >= VALUE; + + if (set) + { + try { + value = std::stod(argv[VALUE]); + } catch (...) { + return (void) Printf("Failed to parse args\n"); + } + } + + if (command == "reset") + { + if (set) return usage(); + sticks[pad]->SetDefaultConfig(); + sticks[pad]->SetEnabled(true); + sticks[pad]->SetEnabledInBackground(sticks[pad]->AllowsEnabledInBackground()); + sticks[pad]->SetSensitivity(1); + return; + } + if (command == "enabled") + { + if (set) sticks[pad]->SetEnabled((int)value); + return (void) Printf("%d\n", sticks[pad]->GetEnabled()); + } + if (command == "background") + { + if (set) sticks[pad]->SetEnabledInBackground((int)value); + return (void) Printf("%d\n", sticks[pad]->GetEnabledInBackground()); + } + if (command == "sensitivity") + { + if (set) sticks[pad]->SetSensitivity(value); + return (void) Printf("%g\n", sticks[pad]->GetSensitivity()); + } + if (command == "deadzone") + { + if (set) sticks[pad]->SetAxisDeadZone(axis, value); + return (void) Printf("%g\n", sticks[pad]->GetAxisDeadZone(axis)); + } + if (command == "scale") + { + if (set) sticks[pad]->SetAxisScale(axis, value); + return (void) Printf("%g\n", sticks[pad]->GetAxisScale(axis)); + } + if (command == "threshold") + { + if (set) sticks[pad]->SetAxisDigitalThreshold(axis, value); + return (void) Printf("%g\n", sticks[pad]->GetAxisDigitalThreshold(axis)); + } + if (command == "curve") + { + if (set) sticks[pad]->SetAxisResponseCurve(axis, (EJoyCurve)value); + return (void) Printf("%d\n", sticks[pad]->GetAxisResponseCurve(axis)); + } + if (command == "curve-x1") + { + if (set) sticks[pad]->SetAxisResponseCurvePoint(axis, 0, value); + return (void) Printf("%g\n", sticks[pad]->GetAxisResponseCurvePoint(axis, 0)); + } + if (command == "curve-y1") + { + if (set) sticks[pad]->SetAxisResponseCurvePoint(axis, 1, value); + return (void) Printf("%g\n", sticks[pad]->GetAxisResponseCurvePoint(axis, 1)); + } + if (command == "curve-x2") + { + if (set) sticks[pad]->SetAxisResponseCurvePoint(axis, 2, value); + return (void) Printf("%g\n", sticks[pad]->GetAxisResponseCurvePoint(axis, 2)); + } + if (command == "curve-y2") + { + if (set) sticks[pad]->SetAxisResponseCurvePoint(axis, 3, value); + return (void) Printf("%g\n", sticks[pad]->GetAxisResponseCurvePoint(axis, 3)); + } + if (command == "map") + { + if (set) sticks[pad]->SetAxisMap(axis, (EJoyAxis)value); + return (void) Printf("%d\n", sticks[pad]->GetAxisMap(axis)); + } + + return usage(); +} //=========================================================================== // @@ -278,6 +538,48 @@ double Joy_RemoveDeadZone(double axisval, double deadzone, uint8_t *buttons) return axisval; } +//=========================================================================== +// +// Joy_ApplyResponseCurveBezier +// +// Applies cubic bezier easing function +// Curve is defined by control points [(0,0) (x1,y1) (x2,y2) (1,1)] +// https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function/cubic-bezier +// +//=========================================================================== + +double Joy_ApplyResponseCurveBezier(const CubicBezier &curve, double input) +{ + // clamp + trivial cases + if (input == 0) return 0; + double sign = (input >= 0)? 1.0: -1.0; + input = abs(input); + input = (input > 1.0)? 1.0: input; + if (input == 1.0) return sign*input; + + double t = input, T; + float x1 = curve.x1, y1 = curve.y1, x2 = curve.x2, y2 = curve.y2; + + const int max_iter = 4; + for (auto i = 0; i < max_iter; i++) + { + T = 1-t; + + double x = 3*T*T*t*x1 + 3*T*t*t*x2 + t*t*t; + double dx = 3*T*T*x1 + 6*T*t*(x2-x1) + 3*t*t*(1-x2); + + // no div by 0 + if (abs(dx) < 0.00001) break; + + t = clamp(t - (x-input)/dx, 0.0, 1.0); + } + + T = 1-t; + t = 3*T*T*t*y1 + 3*T*t*t*y2 + t*t*t; + + return sign*t; +} + //=========================================================================== // // Joy_XYAxesToButtons @@ -315,6 +617,22 @@ int Joy_XYAxesToButtons(double x, double y) return JoyAngleButtons[int(rad) & 7]; } +//=========================================================================== +// +// Joy_GenerateButtonEvent +// +// Send either a button up or button down event for supplied key code +// +//=========================================================================== + +void Joy_GenerateButtonEvent(bool down, EKeyCodes which) +{ + event_t event = { 0,0,0,0,0,0,0 }; + event.type = down ? EV_KeyDown : EV_KeyUp; + event.data1 = which; + D_PostEvent(&event); +} + //=========================================================================== // // Joy_GenerateButtonEvents @@ -330,15 +648,12 @@ void Joy_GenerateButtonEvents(int oldbuttons, int newbuttons, int numbuttons, in int changed = oldbuttons ^ newbuttons; if (changed != 0) { - event_t ev = { 0, 0, 0, 0, 0, 0, 0 }; int mask = 1; for (int j = 0; j < numbuttons; mask <<= 1, ++j) { if (changed & mask) { - ev.data1 = base + j; - ev.type = (newbuttons & mask) ? EV_KeyDown : EV_KeyUp; - D_PostEvent(&ev); + Joy_GenerateButtonEvent(newbuttons & mask, static_cast(base + j)); } } } @@ -349,15 +664,12 @@ void Joy_GenerateButtonEvents(int oldbuttons, int newbuttons, int numbuttons, co int changed = oldbuttons ^ newbuttons; if (changed != 0) { - event_t ev = { 0, 0, 0, 0, 0, 0, 0 }; int mask = 1; for (int j = 0; j < numbuttons; mask <<= 1, ++j) { if (changed & mask) { - ev.data1 = keys[j]; - ev.type = (newbuttons & mask) ? EV_KeyDown : EV_KeyUp; - D_PostEvent(&ev); + Joy_GenerateButtonEvent(newbuttons & mask, static_cast(keys[j])); } } } diff --git a/src/common/engine/m_joy.h b/src/common/engine/m_joy.h index e8d9d3b13..64be2e164 100644 --- a/src/common/engine/m_joy.h +++ b/src/common/engine/m_joy.h @@ -2,9 +2,30 @@ #define M_JOY_H #include "basics.h" +#include "keydef.h" #include "tarray.h" #include "c_cvars.h" +union CubicBezier { + struct { + float x1; + float y1; + float x2; + float y2; + }; + float pts[4]; +}; + +enum EJoyCurve { + JOYCURVE_CUSTOM = -1, + JOYCURVE_DEFAULT, + JOYCURVE_LINEAR, + JOYCURVE_QUADRATIC, + JOYCURVE_CUBIC, + + NUM_JOYCURVE +}; + enum EJoyAxis { JOYAXIS_None = -1, @@ -13,10 +34,22 @@ enum EJoyAxis JOYAXIS_Forward, JOYAXIS_Side, JOYAXIS_Up, -// JOYAXIS_Roll, // Ha ha. No roll for you. + // JOYAXIS_Roll, // Ha ha. No roll for you. NUM_JOYAXIS, }; +extern const float JOYDEADZONE_DEFAULT; + +extern const float JOYSENSITIVITY_DEFAULT; + +extern const float JOYTHRESH_DEFAULT; + +extern const float JOYTHRESH_TRIGGER; +extern const float JOYTHRESH_STICK_X; +extern const float JOYTHRESH_STICK_Y; + +extern const CubicBezier JOYCURVE[NUM_JOYCURVE]; + // Generic configuration interface for a controller. struct IJoystickConfig { @@ -31,10 +64,16 @@ struct IJoystickConfig virtual EJoyAxis GetAxisMap(int axis) = 0; virtual const char *GetAxisName(int axis) = 0; virtual float GetAxisScale(int axis) = 0; + virtual float GetAxisDigitalThreshold(int axis) = 0; + virtual EJoyCurve GetAxisResponseCurve(int axis) = 0; + virtual float GetAxisResponseCurvePoint(int axis, int point) = 0; virtual void SetAxisDeadZone(int axis, float zone) = 0; virtual void SetAxisMap(int axis, EJoyAxis gameaxis) = 0; virtual void SetAxisScale(int axis, float scale) = 0; + virtual void SetAxisDigitalThreshold(int axis, float threshold) = 0; + virtual void SetAxisResponseCurve(int axis, EJoyCurve preset) = 0; + virtual void SetAxisResponseCurvePoint(int axis, int point, float value) = 0; virtual bool GetEnabled() = 0; virtual void SetEnabled(bool enabled) = 0; @@ -48,6 +87,8 @@ struct IJoystickConfig virtual bool IsAxisDeadZoneDefault(int axis) = 0; virtual bool IsAxisMapDefault(int axis) = 0; virtual bool IsAxisScaleDefault(int axis) = 0; + virtual bool IsAxisDigitalThresholdDefault(int axis) = 0; + virtual bool IsAxisResponseCurveDefault(int axis) = 0; virtual void SetDefaultConfig() = 0; virtual FString GetIdentifier() = 0; @@ -58,10 +99,12 @@ EXTERN_CVAR(Bool, use_joystick); bool M_LoadJoystickConfig(IJoystickConfig *joy); void M_SaveJoystickConfig(IJoystickConfig *joy); +void Joy_GenerateButtonEvent(bool down, EKeyCodes which); void Joy_GenerateButtonEvents(int oldbuttons, int newbuttons, int numbuttons, int base); void Joy_GenerateButtonEvents(int oldbuttons, int newbuttons, int numbuttons, const int *keys); int Joy_XYAxesToButtons(double x, double y); double Joy_RemoveDeadZone(double axisval, double deadzone, uint8_t *buttons); +double Joy_ApplyResponseCurveBezier(const CubicBezier &curve, double input); // These ought to be provided by a system-specific i_input.cpp. void I_GetAxes(float axes[NUM_JOYAXIS]); diff --git a/src/common/menu/joystickmenu.cpp b/src/common/menu/joystickmenu.cpp index 3d3e881b1..ad86dd985 100644 --- a/src/common/menu/joystickmenu.cpp +++ b/src/common/menu/joystickmenu.cpp @@ -84,6 +84,56 @@ DEFINE_ACTION_FUNCTION(IJoystickConfig, SetAxisDeadZone) return 0; } +DEFINE_ACTION_FUNCTION(IJoystickConfig, GetAxisDigitalThreshold) +{ + PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); + PARAM_INT(axis); + ACTION_RETURN_FLOAT(self->GetAxisDigitalThreshold(axis)); +} + +DEFINE_ACTION_FUNCTION(IJoystickConfig, SetAxisDigitalThreshold) +{ + PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); + PARAM_INT(axis); + PARAM_FLOAT(dt); + self->SetAxisDigitalThreshold(axis, (float)dt); + return 0; +} + +DEFINE_ACTION_FUNCTION(IJoystickConfig, GetAxisResponseCurve) +{ + PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); + PARAM_INT(axis); + ACTION_RETURN_INT(self->GetAxisResponseCurve(axis)); +} + +DEFINE_ACTION_FUNCTION(IJoystickConfig, SetAxisResponseCurve) +{ + PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); + PARAM_INT(axis); + PARAM_INT(curve); + self->SetAxisResponseCurve(axis, (EJoyCurve)curve); + return 0; +} + +DEFINE_ACTION_FUNCTION(IJoystickConfig, GetAxisResponseCurvePoint) +{ + PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); + PARAM_INT(axis); + PARAM_INT(point); + ACTION_RETURN_INT(self->GetAxisResponseCurvePoint(axis, point)); +} + +DEFINE_ACTION_FUNCTION(IJoystickConfig, SetAxisResponseCurvePoint) +{ + PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); + PARAM_INT(axis); + PARAM_INT(point); + PARAM_FLOAT(value); + self->SetAxisResponseCurvePoint(axis, point, value); + return 0; +} + DEFINE_ACTION_FUNCTION(IJoystickConfig, GetAxisMap) { PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); diff --git a/src/common/menu/menu.cpp b/src/common/menu/menu.cpp index a07dfd36f..6fbe1dd1b 100644 --- a/src/common/menu/menu.cpp +++ b/src/common/menu/menu.cpp @@ -680,7 +680,7 @@ bool M_Responder (event_t *ev) else if (menuactive != MENU_WaitKey && (ev->type == EV_KeyDown || ev->type == EV_KeyUp)) { // eat blocked controller events without dispatching them. - if (ev->data1 >= KEY_FIRSTJOYBUTTON && m_blockcontrollers) return true; + if (ev->data1 >= KEY_FIRSTJOYBUTTON && m_blockcontrollers && ev->type == EV_KeyDown) return true; keyup = ev->type == EV_KeyUp; diff --git a/src/common/platform/posix/cocoa/i_joystick.cpp b/src/common/platform/posix/cocoa/i_joystick.cpp index 44d1fb961..ffb9f90a0 100644 --- a/src/common/platform/posix/cocoa/i_joystick.cpp +++ b/src/common/platform/posix/cocoa/i_joystick.cpp @@ -94,15 +94,23 @@ public: virtual EJoyAxis GetAxisMap(int axis); virtual const char* GetAxisName(int axis); virtual float GetAxisScale(int axis); + float GetAxisDigitalThreshold(int axis); + EJoyCurve GetAxisResponseCurve(int axis); + float GetAxisResponseCurvePoint(int axis, int point); virtual void SetAxisDeadZone(int axis, float deadZone); virtual void SetAxisMap(int axis, EJoyAxis gameAxis); virtual void SetAxisScale(int axis, float scale); + void SetAxisDigitalThreshold(int axis, float threshold); + void SetAxisResponseCurve(int axis, EJoyCurve preset); + void SetAxisResponseCurvePoint(int axis, int point, float value); virtual bool IsSensitivityDefault(); virtual bool IsAxisDeadZoneDefault(int axis); virtual bool IsAxisMapDefault(int axis); virtual bool IsAxisScaleDefault(int axis); + bool IsAxisDigitalThresholdDefault(int axis); + bool IsAxisResponseCurveDefault(int axis); virtual bool GetEnabled(); virtual void SetEnabled(bool enabled); @@ -146,6 +154,11 @@ private: float defaultDeadZone; float sensitivity; float defaultSensitivity; + float digitalThreshold; + float defaultDigitalThreshold; + EJoyCurve responseCurvePreset; + EJoyCurve defaultResponseCurvePreset; + CubicBezier responseCurve; EJoyAxis gameAxis; EJoyAxis defaultGameAxis; @@ -177,10 +190,6 @@ private: io_object_t m_notification; - - static const float DEFAULT_DEADZONE; - static const float DEFAULT_SENSITIVITY; - void ProcessAxes(); bool ProcessAxis (const IOHIDEventStruct& event); bool ProcessButton(const IOHIDEventStruct& event); @@ -200,10 +209,6 @@ private: }; -const float IOKitJoystick::DEFAULT_DEADZONE = 0.25f; -const float IOKitJoystick::DEFAULT_SENSITIVITY = 1.0f; - - IOHIDDeviceInterface** CreateDeviceInterface(const io_object_t device) { IOCFPlugInInterface** plugInInterface = NULL; @@ -286,7 +291,7 @@ IOHIDQueueInterface** CreateDeviceQueue(IOHIDDeviceInterface** const interface) IOKitJoystick::IOKitJoystick(const io_object_t device) : m_interface(CreateDeviceInterface(device)) , m_queue(CreateDeviceQueue(m_interface)) -, m_sensitivity(DEFAULT_SENSITIVITY) +, m_sensitivity(JOYSENSITIVITY_DEFAULT) , m_enabled(true) , m_useAxesPolling(true) , m_notification(0) @@ -386,6 +391,21 @@ float IOKitJoystick::GetAxisScale(int axis) return IS_AXIS_VALID ? m_axes[axis].sensitivity : 0.0f; } +float IOKitJoystick::GetAxisDigitalThreshold(int axis) +{ + return IS_AXIS_VALID ? m_axes[axis].digitalThreshold : JOYTHRESH_DEFAULT; +} + +EJoyCurve IOKitJoystick::GetAxisResponseCurve(int axis) +{ + return IS_AXIS_VALID ? m_axes[axis].responseCurvePreset : JOYCURVE_DEFAULT; +} + +float IOKitJoystick::GetAxisResponseCurvePoint(int axis, int point) +{ + return ( IS_AXIS_VALID && unsigned(point) < 4 )? m_axes[axis].responseCurve.pts[point] : 0; +} + void IOKitJoystick::SetAxisDeadZone(int axis, float deadZone) { if (IS_AXIS_VALID) @@ -412,10 +432,37 @@ void IOKitJoystick::SetAxisScale(int axis, float scale) } } +void IOKitJoystick::SetAxisDigitalThreshold(int axis, float threshold) +{ + if (IS_AXIS_VALID) + { + m_axes[axis].digitalThreshold = threshold; + } +} + +void IOKitJoystick::SetAxisResponseCurve(int axis, EJoyCurve preset) +{ + if (IS_AXIS_VALID) + { + if (preset >= NUM_JOYCURVE || preset < JOYCURVE_CUSTOM) return; + m_axes[axis].responseCurvePreset = preset; + if (preset == JOYCURVE_CUSTOM) return; + m_axes[axis].responseCurve = JOYCURVE[preset]; + } +} + +void IOKitJoystick::SetAxisResponseCurvePoint(int axis, int point, float value) +{ + if (IS_AXIS_VALID && unsigned(point) < 4) + { + m_axes[axis].responseCurvePreset = JOYCURVE_CUSTOM; + m_axes[axis].responseCurve.pts[point] = value; + } +} bool IOKitJoystick::IsSensitivityDefault() { - return DEFAULT_SENSITIVITY == m_sensitivity; + return JOYSENSITIVITY_DEFAULT == m_sensitivity; } bool IOKitJoystick::IsAxisDeadZoneDefault(int axis) @@ -439,7 +486,19 @@ bool IOKitJoystick::IsAxisScaleDefault(int axis) : true; } +bool IOKitJoystick::IsAxisDigitalThresholdDefault(int axis) +{ + return IS_AXIS_VALID + ? (m_axes[axis].digitalThreshold == m_axes[axis].defaultDigitalThreshold) + : true; +} +bool IOKitJoystick::IsAxisResponseCurveDefault(int axis) +{ + return IS_AXIS_VALID + ? m_axes[axis].responseCurvePreset == m_axes[axis].defaultResponseCurvePreset + : true; +} bool IOKitJoystick::GetEnabled() { @@ -454,15 +513,18 @@ void IOKitJoystick::SetEnabled(bool enabled) void IOKitJoystick::SetDefaultConfig() { - m_sensitivity = DEFAULT_SENSITIVITY; + m_sensitivity = JOYSENSITIVITY_DEFAULT; const size_t axisCount = m_axes.Size(); for (size_t i = 0; i < axisCount; ++i) { - m_axes[i].deadZone = DEFAULT_DEADZONE; - m_axes[i].sensitivity = DEFAULT_SENSITIVITY; + m_axes[i].deadZone = JOYDEADZONE_DEFAULT; + m_axes[i].sensitivity = JOYSENSITIVITY_DEFAULT; m_axes[i].gameAxis = JOYAXIS_None; + m_axes[i].digitalThreshold = JOYTHRESH_DEFAULT; + m_axes[i].responseCurvePreset = JOYCURVE_DEFAULT; + m_axes[i].responseCurve = JOYCURVE[JOYCURVE_DEFAULT]; } // Two axes? Horizontal is yaw and vertical is forward. @@ -470,7 +532,10 @@ void IOKitJoystick::SetDefaultConfig() if (2 == axisCount) { m_axes[0].gameAxis = JOYAXIS_Yaw; + m_axes[0].digitalThreshold = JOYTHRESH_STICK_X; + m_axes[1].gameAxis = JOYAXIS_Forward; + m_axes[1].digitalThreshold = JOYTHRESH_STICK_Y; } // Three axes? First two are movement, third is yaw. @@ -478,8 +543,13 @@ void IOKitJoystick::SetDefaultConfig() else if (axisCount >= 3) { m_axes[0].gameAxis = JOYAXIS_Side; + m_axes[0].digitalThreshold = JOYTHRESH_STICK_X; + m_axes[1].gameAxis = JOYAXIS_Forward; + m_axes[1].digitalThreshold = JOYTHRESH_STICK_Y; + m_axes[2].gameAxis = JOYAXIS_Yaw; + m_axes[2].digitalThreshold = JOYTHRESH_STICK_X; // Four axes? First two are movement, last two are looking around. @@ -487,12 +557,14 @@ void IOKitJoystick::SetDefaultConfig() { m_axes[3].gameAxis = JOYAXIS_Pitch; // ??? m_axes[3].sensitivity = 0.75f; + m_axes[3].digitalThreshold = JOYTHRESH_STICK_Y; // Five axes? Use the fifth one for moving up and down. if (axisCount >= 5) { m_axes[4].gameAxis = JOYAXIS_Up; + m_axes[4].digitalThreshold = JOYTHRESH_STICK_Y; } } } @@ -504,9 +576,11 @@ void IOKitJoystick::SetDefaultConfig() for (size_t i = 0; i < axisCount; ++i) { - m_axes[i].defaultDeadZone = m_axes[i].deadZone; - m_axes[i].defaultSensitivity = m_axes[i].sensitivity; - m_axes[i].defaultGameAxis = m_axes[i].gameAxis; + m_axes[i].defaultDeadZone = m_axes[i].deadZone; + m_axes[i].defaultSensitivity = m_axes[i].sensitivity; + m_axes[i].defaultGameAxis = m_axes[i].gameAxis; + m_axes[i].defaultDigitalThreshold = m_axes[i].digitalThreshold; + m_axes[i].defaultResponseCurvePreset = m_axes[i].responseCurvePreset; } } @@ -602,8 +676,9 @@ void IOKitJoystick::ProcessAxes() const double scaledValue = scaledMin + (event.value - axis.minValue) * (scaledMax - scaledMin) / (axis.maxValue - axis.minValue); const double filteredValue = Joy_RemoveDeadZone(scaledValue, axis.deadZone, NULL); + const double smoothedValue = Joy_ApplyResponseCurveBezier(axis.responseCurve, filteredValue); - axis.value = static_cast(filteredValue * m_sensitivity * axis.sensitivity); + axis.value = static_cast(smoothedValue * m_sensitivity * axis.sensitivity); } else { @@ -635,8 +710,9 @@ bool IOKitJoystick::ProcessAxis(const IOHIDEventStruct& event) const double scaledValue = scaledMin + (event.value - axis.minValue) * (scaledMax - scaledMin) / (axis.maxValue - axis.minValue); const double filteredValue = Joy_RemoveDeadZone(scaledValue, axis.deadZone, NULL); + const double smoothedValue = Joy_ApplyResponseCurveBezier(axis.responseCurve, filteredValue); - axis.value = static_cast(filteredValue * m_sensitivity * axis.sensitivity); + axis.value = static_cast(smoothedValue * m_sensitivity * axis.sensitivity); return true; } diff --git a/src/common/platform/posix/sdl/i_input.cpp b/src/common/platform/posix/sdl/i_input.cpp index 0148eaad2..da7ac59c2 100644 --- a/src/common/platform/posix/sdl/i_input.cpp +++ b/src/common/platform/posix/sdl/i_input.cpp @@ -31,11 +31,14 @@ ** */ #include +#include +#include "i_input.h" +#include "c_cvars.h" +#include "dobject.h" #include "m_argv.h" #include "m_joy.h" #include "v_video.h" -#include "d_eventbase.h" #include "d_gui.h" #include "c_buttons.h" #include "c_console.h" @@ -47,88 +50,147 @@ #include "engineerrors.h" #include "i_interface.h" - -static void I_CheckGUICapture (); -static void I_CheckNativeMouse (); - bool GUICapture; static bool NativeMouse = true; CVAR (Bool, use_mouse, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) - extern int WaitingForKey; static const SDL_Keycode DIKToKeySym[256] = { - 0, SDLK_ESCAPE, SDLK_1, SDLK_2, SDLK_3, SDLK_4, SDLK_5, SDLK_6, - SDLK_7, SDLK_8, SDLK_9, SDLK_0,SDLK_MINUS, SDLK_EQUALS, SDLK_BACKSPACE, SDLK_TAB, - SDLK_q, SDLK_w, SDLK_e, SDLK_r, SDLK_t, SDLK_y, SDLK_u, SDLK_i, - SDLK_o, SDLK_p, SDLK_LEFTBRACKET, SDLK_RIGHTBRACKET, SDLK_RETURN, SDLK_LCTRL, SDLK_a, SDLK_s, - SDLK_d, SDLK_f, SDLK_g, SDLK_h, SDLK_j, SDLK_k, SDLK_l, SDLK_SEMICOLON, - SDLK_QUOTE, SDLK_BACKQUOTE, SDLK_LSHIFT, SDLK_BACKSLASH, SDLK_z, SDLK_x, SDLK_c, SDLK_v, - SDLK_b, SDLK_n, SDLK_m, SDLK_COMMA, SDLK_PERIOD, SDLK_SLASH, SDLK_RSHIFT, SDLK_KP_MULTIPLY, - SDLK_LALT, SDLK_SPACE, SDLK_CAPSLOCK, SDLK_F1, SDLK_F2, SDLK_F3, SDLK_F4, SDLK_F5, - SDLK_F6, SDLK_F7, SDLK_F8, SDLK_F9, SDLK_F10, SDLK_NUMLOCKCLEAR, SDLK_SCROLLLOCK, SDLK_KP_7, - SDLK_KP_8, SDLK_KP_9, SDLK_KP_MINUS, SDLK_KP_4, SDLK_KP_5, SDLK_KP_6, SDLK_KP_PLUS, SDLK_KP_1, - SDLK_KP_2, SDLK_KP_3, SDLK_KP_0, SDLK_KP_PERIOD, 0, 0, 0, SDLK_F11, - SDLK_F12, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, SDLK_F13, SDLK_F14, SDLK_F15, SDLK_F16, - SDLK_F17, SDLK_F18, SDLK_F19, SDLK_F20, SDLK_F21, SDLK_F22, SDLK_F23, SDLK_F24, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, SDLK_KP_EQUALS, 0, 0, - 0, SDLK_AT, SDLK_COLON, 0, 0, 0, 0, 0, - 0, 0, 0, 0, SDLK_KP_ENTER, SDLK_RCTRL, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, SDLK_KP_COMMA, 0, SDLK_KP_DIVIDE, 0, SDLK_SYSREQ, - SDLK_RALT, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, SDLK_PAUSE, 0, SDLK_HOME, - SDLK_UP, SDLK_PAGEUP, 0, SDLK_LEFT, 0, SDLK_RIGHT, 0, SDLK_END, - SDLK_DOWN, SDLK_PAGEDOWN, SDLK_INSERT, SDLK_DELETE, 0, 0, 0, 0, - 0, 0, 0, SDLK_LGUI, SDLK_RGUI, SDLK_MENU, SDLK_POWER, SDLK_SLEEP, - 0, 0, 0, 0, 0, SDLK_AC_SEARCH, SDLK_AC_BOOKMARKS, SDLK_AC_REFRESH, - SDLK_AC_STOP, SDLK_AC_FORWARD, SDLK_AC_BACK, SDLK_COMPUTER, SDLK_MAIL, SDLK_MEDIASELECT, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 + SDLK_UNKNOWN, SDLK_ESCAPE, SDLK_1, SDLK_2, + SDLK_3, SDLK_4, SDLK_5, SDLK_6, + SDLK_7, SDLK_8, SDLK_9, SDLK_0, + SDLK_MINUS, SDLK_EQUALS, SDLK_BACKSPACE, SDLK_TAB, + SDLK_q, SDLK_w, SDLK_e, SDLK_r, + SDLK_t, SDLK_y, SDLK_u, SDLK_i, + SDLK_o, SDLK_p, SDLK_LEFTBRACKET, SDLK_RIGHTBRACKET, + SDLK_RETURN, SDLK_LCTRL, SDLK_a, SDLK_s, + SDLK_d, SDLK_f, SDLK_g, SDLK_h, + SDLK_j, SDLK_k, SDLK_l, SDLK_SEMICOLON, + SDLK_QUOTE, SDLK_BACKQUOTE, SDLK_LSHIFT, SDLK_BACKSLASH, + SDLK_z, SDLK_x, SDLK_c, SDLK_v, + SDLK_b, SDLK_n, SDLK_m, SDLK_COMMA, + SDLK_PERIOD, SDLK_SLASH, SDLK_RSHIFT, SDLK_KP_MULTIPLY, + SDLK_LALT, SDLK_SPACE, SDLK_CAPSLOCK, SDLK_F1, + SDLK_F2, SDLK_F3, SDLK_F4, SDLK_F5, + SDLK_F6, SDLK_F7, SDLK_F8, SDLK_F9, + SDLK_F10, SDLK_NUMLOCKCLEAR, SDLK_SCROLLLOCK, SDLK_KP_7, + SDLK_KP_8, SDLK_KP_9, SDLK_KP_MINUS, SDLK_KP_4, + SDLK_KP_5, SDLK_KP_6, SDLK_KP_PLUS, SDLK_KP_1, + SDLK_KP_2, SDLK_KP_3, SDLK_KP_0, SDLK_KP_PERIOD, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_F11, + SDLK_F12, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_F13, SDLK_F14, SDLK_F15, SDLK_F16, + SDLK_F17, SDLK_F18, SDLK_F19, SDLK_F20, + SDLK_F21, SDLK_F22, SDLK_F23, SDLK_F24, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_KP_EQUALS, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_AT, SDLK_COLON, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_KP_ENTER, SDLK_RCTRL, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_KP_COMMA, + SDLK_UNKNOWN, SDLK_KP_DIVIDE, SDLK_UNKNOWN, SDLK_SYSREQ, + SDLK_RALT, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_PAUSE, SDLK_UNKNOWN, SDLK_HOME, + SDLK_UP, SDLK_PAGEUP, SDLK_UNKNOWN, SDLK_LEFT, + SDLK_UNKNOWN, SDLK_RIGHT, SDLK_UNKNOWN, SDLK_END, + SDLK_DOWN, SDLK_PAGEDOWN, SDLK_INSERT, SDLK_DELETE, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_LGUI, + SDLK_RGUI, SDLK_MENU, SDLK_POWER, SDLK_SLEEP, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_AC_SEARCH, SDLK_AC_BOOKMARKS, SDLK_AC_REFRESH, + SDLK_AC_STOP, SDLK_AC_FORWARD, SDLK_AC_BACK, SDLK_COMPUTER, + SDLK_MAIL, SDLK_MEDIASELECT, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, + SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN, SDLK_UNKNOWN }; static const SDL_Scancode DIKToKeyScan[256] = { - SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_ESCAPE, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_3, SDL_SCANCODE_4, SDL_SCANCODE_5, SDL_SCANCODE_6, - SDL_SCANCODE_7, SDL_SCANCODE_8, SDL_SCANCODE_9, SDL_SCANCODE_0 ,SDL_SCANCODE_MINUS, SDL_SCANCODE_EQUALS, SDL_SCANCODE_BACKSPACE, SDL_SCANCODE_TAB, - SDL_SCANCODE_Q, SDL_SCANCODE_W, SDL_SCANCODE_E, SDL_SCANCODE_R, SDL_SCANCODE_T, SDL_SCANCODE_Y, SDL_SCANCODE_U, SDL_SCANCODE_I, - SDL_SCANCODE_O, SDL_SCANCODE_P, SDL_SCANCODE_LEFTBRACKET, SDL_SCANCODE_RIGHTBRACKET, SDL_SCANCODE_RETURN, SDL_SCANCODE_LCTRL, SDL_SCANCODE_A, SDL_SCANCODE_S, - SDL_SCANCODE_D, SDL_SCANCODE_F, SDL_SCANCODE_G, SDL_SCANCODE_H, SDL_SCANCODE_J, SDL_SCANCODE_K, SDL_SCANCODE_L, SDL_SCANCODE_SEMICOLON, - SDL_SCANCODE_APOSTROPHE, SDL_SCANCODE_GRAVE, SDL_SCANCODE_LSHIFT, SDL_SCANCODE_BACKSLASH, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_C, SDL_SCANCODE_V, - SDL_SCANCODE_B, SDL_SCANCODE_N, SDL_SCANCODE_M, SDL_SCANCODE_COMMA, SDL_SCANCODE_PERIOD, SDL_SCANCODE_SLASH, SDL_SCANCODE_RSHIFT, SDL_SCANCODE_KP_MULTIPLY, - SDL_SCANCODE_LALT, SDL_SCANCODE_SPACE, SDL_SCANCODE_CAPSLOCK, SDL_SCANCODE_F1, SDL_SCANCODE_F2, SDL_SCANCODE_F3, SDL_SCANCODE_F4, SDL_SCANCODE_F5, - SDL_SCANCODE_F6, SDL_SCANCODE_F7, SDL_SCANCODE_F8, SDL_SCANCODE_F9, SDL_SCANCODE_F10, SDL_SCANCODE_NUMLOCKCLEAR, SDL_SCANCODE_SCROLLLOCK, SDL_SCANCODE_KP_7, - SDL_SCANCODE_KP_8, SDL_SCANCODE_KP_9, SDL_SCANCODE_KP_MINUS, SDL_SCANCODE_KP_4, SDL_SCANCODE_KP_5, SDL_SCANCODE_KP_6, SDL_SCANCODE_KP_PLUS, SDL_SCANCODE_KP_1, - SDL_SCANCODE_KP_2, SDL_SCANCODE_KP_3, SDL_SCANCODE_KP_0, SDL_SCANCODE_KP_PERIOD, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_F11, - SDL_SCANCODE_F12, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, - SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_F13, SDL_SCANCODE_F14, SDL_SCANCODE_F15, SDL_SCANCODE_F16, - SDL_SCANCODE_F17, SDL_SCANCODE_F18, SDL_SCANCODE_F19, SDL_SCANCODE_F20, SDL_SCANCODE_F21, SDL_SCANCODE_F22, SDL_SCANCODE_F23, SDL_SCANCODE_F24, - SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, - SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, - SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, - SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_KP_EQUALS, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, - SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, - SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_KP_ENTER, SDL_SCANCODE_RCTRL, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, - SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, - SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, - SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_KP_COMMA, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_KP_DIVIDE, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_SYSREQ, - SDL_SCANCODE_RALT, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, - SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_PAUSE, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_HOME, - SDL_SCANCODE_UP, SDL_SCANCODE_PAGEUP, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_RIGHT, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_END, - SDL_SCANCODE_DOWN, SDL_SCANCODE_PAGEDOWN, SDL_SCANCODE_INSERT, SDL_SCANCODE_DELETE, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, - SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_LGUI, SDL_SCANCODE_RGUI, SDL_SCANCODE_MENU, SDL_SCANCODE_POWER, SDL_SCANCODE_SLEEP, - SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_AC_SEARCH, SDL_SCANCODE_AC_BOOKMARKS, SDL_SCANCODE_AC_REFRESH, - SDL_SCANCODE_AC_STOP, SDL_SCANCODE_AC_FORWARD, SDL_SCANCODE_AC_BACK, SDL_SCANCODE_COMPUTER, SDL_SCANCODE_MAIL, SDL_SCANCODE_MEDIASELECT, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, - SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, - SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_ESCAPE, SDL_SCANCODE_1, SDL_SCANCODE_2, + SDL_SCANCODE_3, SDL_SCANCODE_4, SDL_SCANCODE_5, SDL_SCANCODE_6, + SDL_SCANCODE_7, SDL_SCANCODE_8, SDL_SCANCODE_9, SDL_SCANCODE_0, + SDL_SCANCODE_MINUS, SDL_SCANCODE_EQUALS, SDL_SCANCODE_BACKSPACE, SDL_SCANCODE_TAB, + SDL_SCANCODE_Q, SDL_SCANCODE_W, SDL_SCANCODE_E, SDL_SCANCODE_R, + SDL_SCANCODE_T, SDL_SCANCODE_Y, SDL_SCANCODE_U, SDL_SCANCODE_I, + SDL_SCANCODE_O, SDL_SCANCODE_P, SDL_SCANCODE_LEFTBRACKET, SDL_SCANCODE_RIGHTBRACKET, + SDL_SCANCODE_RETURN, SDL_SCANCODE_LCTRL, SDL_SCANCODE_A, SDL_SCANCODE_S, + SDL_SCANCODE_D, SDL_SCANCODE_F, SDL_SCANCODE_G, SDL_SCANCODE_H, + SDL_SCANCODE_J, SDL_SCANCODE_K, SDL_SCANCODE_L, SDL_SCANCODE_SEMICOLON, + SDL_SCANCODE_APOSTROPHE, SDL_SCANCODE_GRAVE, SDL_SCANCODE_LSHIFT, SDL_SCANCODE_BACKSLASH, + SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_C, SDL_SCANCODE_V, + SDL_SCANCODE_B, SDL_SCANCODE_N, SDL_SCANCODE_M, SDL_SCANCODE_COMMA, + SDL_SCANCODE_PERIOD, SDL_SCANCODE_SLASH, SDL_SCANCODE_RSHIFT, SDL_SCANCODE_KP_MULTIPLY, + SDL_SCANCODE_LALT, SDL_SCANCODE_SPACE, SDL_SCANCODE_CAPSLOCK, SDL_SCANCODE_F1, + SDL_SCANCODE_F2, SDL_SCANCODE_F3, SDL_SCANCODE_F4, SDL_SCANCODE_F5, + SDL_SCANCODE_F6, SDL_SCANCODE_F7, SDL_SCANCODE_F8, SDL_SCANCODE_F9, + SDL_SCANCODE_F10, SDL_SCANCODE_NUMLOCKCLEAR, SDL_SCANCODE_SCROLLLOCK, SDL_SCANCODE_KP_7, + SDL_SCANCODE_KP_8, SDL_SCANCODE_KP_9, SDL_SCANCODE_KP_MINUS, SDL_SCANCODE_KP_4, + SDL_SCANCODE_KP_5, SDL_SCANCODE_KP_6, SDL_SCANCODE_KP_PLUS, SDL_SCANCODE_KP_1, + SDL_SCANCODE_KP_2, SDL_SCANCODE_KP_3, SDL_SCANCODE_KP_0, SDL_SCANCODE_KP_PERIOD, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_F11, + SDL_SCANCODE_F12, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_F13, SDL_SCANCODE_F14, SDL_SCANCODE_F15, SDL_SCANCODE_F16, + SDL_SCANCODE_F17, SDL_SCANCODE_F18, SDL_SCANCODE_F19, SDL_SCANCODE_F20, + SDL_SCANCODE_F21, SDL_SCANCODE_F22, SDL_SCANCODE_F23, SDL_SCANCODE_F24, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_KP_EQUALS, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_KP_ENTER, SDL_SCANCODE_RCTRL, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_KP_COMMA, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_KP_DIVIDE, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_SYSREQ, + SDL_SCANCODE_RALT, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_PAUSE, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_HOME, + SDL_SCANCODE_UP, SDL_SCANCODE_PAGEUP, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_LEFT, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_RIGHT, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_END, + SDL_SCANCODE_DOWN, SDL_SCANCODE_PAGEDOWN, SDL_SCANCODE_INSERT, SDL_SCANCODE_DELETE, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_LGUI, + SDL_SCANCODE_RGUI, SDL_SCANCODE_MENU, SDL_SCANCODE_POWER, SDL_SCANCODE_SLEEP, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_AC_SEARCH, SDL_SCANCODE_AC_BOOKMARKS, SDL_SCANCODE_AC_REFRESH, + SDL_SCANCODE_AC_STOP, SDL_SCANCODE_AC_FORWARD, SDL_SCANCODE_AC_BACK, SDL_SCANCODE_COMPUTER, + SDL_SCANCODE_MAIL, SDL_SCANCODE_MEDIASELECT, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN }; static TMap InitKeySymMap () @@ -463,14 +525,60 @@ void MessagePump (const SDL_Event &sev) case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONUP: + if (SDL_GameControllerFromInstanceID(sev.jdevice.which)) + break; // let SDL_CONTROLLERBUTTON* handle this event.type = sev.type == SDL_JOYBUTTONDOWN ? EV_KeyDown : EV_KeyUp; event.data1 = KEY_FIRSTJOYBUTTON + sev.jbutton.button; if(event.data1 != 0) - D_PostEvent(&event); + I_JoyConsumeEvent(sev.jdevice.which, &event); + break; + + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + event.type = sev.type == SDL_CONTROLLERBUTTONDOWN ? EV_KeyDown : EV_KeyUp; + switch (sev.cbutton.button) + { + case SDL_CONTROLLER_BUTTON_A: event.data1 = KEY_PAD_A; break; + case SDL_CONTROLLER_BUTTON_B: event.data1 = KEY_PAD_B; break; + case SDL_CONTROLLER_BUTTON_X: event.data1 = KEY_PAD_X; break; + case SDL_CONTROLLER_BUTTON_Y: event.data1 = KEY_PAD_Y; break; + case SDL_CONTROLLER_BUTTON_BACK: event.data1 = KEY_PAD_BACK; break; + case SDL_CONTROLLER_BUTTON_GUIDE: event.data1 = KEY_PAD_GUIDE; break; + case SDL_CONTROLLER_BUTTON_START: event.data1 = KEY_PAD_START; break; + case SDL_CONTROLLER_BUTTON_LEFTSTICK: event.data1 = KEY_PAD_LTHUMB; break; + case SDL_CONTROLLER_BUTTON_RIGHTSTICK: event.data1 = KEY_PAD_RTHUMB; break; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: event.data1 = KEY_PAD_LSHOULDER; break; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: event.data1 = KEY_PAD_RSHOULDER; break; + case SDL_CONTROLLER_BUTTON_DPAD_UP: event.data1 = KEY_PAD_DPAD_UP; break; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: event.data1 = KEY_PAD_DPAD_DOWN; break; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: event.data1 = KEY_PAD_DPAD_LEFT; break; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: event.data1 = KEY_PAD_DPAD_RIGHT; break; + case SDL_CONTROLLER_BUTTON_MISC1: event.data1 = KEY_PAD_MISC1; break; + case SDL_CONTROLLER_BUTTON_PADDLE1: event.data1 = KEY_PAD_PADDLE1; break; + case SDL_CONTROLLER_BUTTON_PADDLE2: event.data1 = KEY_PAD_PADDLE2; break; + case SDL_CONTROLLER_BUTTON_PADDLE3: event.data1 = KEY_PAD_PADDLE3; break; + case SDL_CONTROLLER_BUTTON_PADDLE4: event.data1 = KEY_PAD_PADDLE4; break; + case SDL_CONTROLLER_BUTTON_TOUCHPAD: event.data1 = KEY_PAD_TOUCHPAD; break; + default: event.data1 = 0; + } + if(event.data1 != 0) + I_JoyConsumeEvent(sev.cbutton.which, &event); break; case SDL_JOYDEVICEADDED: + case SDL_CONTROLLERDEVICEADDED: + if (sev.type == SDL_JOYDEVICEADDED && SDL_IsGameController(sev.jdevice.which)) // DeviceIndex Here + break; // skip double event + I_UpdateDeviceList(); + event.type = EV_DeviceChange; + D_PostEvent (&event); + break; + case SDL_JOYDEVICEREMOVED: + case SDL_CONTROLLERDEVICEREMOVED: + case SDL_CONTROLLERDEVICEREMAPPED: + if (sev.type == SDL_JOYDEVICEREMOVED && SDL_GameControllerFromInstanceID(sev.jdevice.which)) + break; // skip double event I_UpdateDeviceList(); event.type = EV_DeviceChange; D_PostEvent (&event); diff --git a/src/common/platform/posix/sdl/i_input.h b/src/common/platform/posix/sdl/i_input.h new file mode 100644 index 000000000..6ca2e05aa --- /dev/null +++ b/src/common/platform/posix/sdl/i_input.h @@ -0,0 +1,13 @@ +#ifndef I_INPUT_H +#define I_INPUT_H + +#include "d_eventbase.h" + +extern int WaitingForKey; + +static void I_CheckGUICapture (); +static void I_CheckNativeMouse (); + +void I_JoyConsumeEvent(int instanceID, event_t * event); + +#endif diff --git a/src/common/platform/posix/sdl/i_joystick.cpp b/src/common/platform/posix/sdl/i_joystick.cpp index 8dc9fd8ac..d37b4f3a6 100644 --- a/src/common/platform/posix/sdl/i_joystick.cpp +++ b/src/common/platform/posix/sdl/i_joystick.cpp @@ -31,47 +31,80 @@ ** */ #include +#include +#include #include "basics.h" #include "cmdlib.h" +#include "d_eventbase.h" +#include "i_input.h" #include "m_joy.h" -#include "keydef.h" - -#define DEFAULT_DEADZONE 0.25f; - -// Very small deadzone so that floating point magic doesn't happen -#define MIN_DEADZONE 0.000001f class SDLInputJoystick: public IJoystickConfig { public: - SDLInputJoystick(int DeviceIndex) : DeviceIndex(DeviceIndex), Multiplier(1.0f) , Enabled(true) + SDLInputJoystick(int DeviceIndex) : + DeviceIndex(DeviceIndex), + InstanceID(SDL_JoystickGetDeviceInstanceID(DeviceIndex)), + Multiplier(JOYSENSITIVITY_DEFAULT), + Enabled(true), + SettingsChanged(false) { - Device = SDL_JoystickOpen(DeviceIndex); - if(Device != NULL) + if (SDL_IsGameController(DeviceIndex)) { - NumAxes = SDL_JoystickNumAxes(Device); - NumHats = SDL_JoystickNumHats(Device); + Mapping = SDL_GameControllerOpen(DeviceIndex); + Device = NULL; - SetDefaultConfig(); + DefaultAxes = DefaultControllerAxes; + DefaultAxesCount = sizeof(DefaultControllerAxes) / sizeof(DefaultAxisConfig); + + if(Mapping != NULL) + { + NumAxes = SDL_CONTROLLER_AXIS_MAX; + NumHats = 0; + + SetDefaultConfig(); + } } + else + { + Device = SDL_JoystickOpen(DeviceIndex); + Mapping = NULL; + + DefaultAxes = DefaultJoystickAxes; + DefaultAxesCount = sizeof(DefaultJoystickAxes) / sizeof(DefaultAxisConfig); + + if(Device != NULL) + { + NumAxes = SDL_JoystickNumAxes(Device); + NumHats = SDL_JoystickNumHats(Device); + + SetDefaultConfig(); + } + } + M_LoadJoystickConfig(this); } ~SDLInputJoystick() { - if(Device != NULL) + if(IsValid() && SettingsChanged) M_SaveJoystickConfig(this); - SDL_JoystickClose(Device); + if (Mapping) + SDL_GameControllerClose(Mapping); + if (Device) + SDL_JoystickClose(Device); } bool IsValid() const { - return Device != NULL; + return Device != NULL || Mapping != NULL; } FString GetName() { - return SDL_JoystickName(Device); + return (Mapping) + ? SDL_GameControllerName(Mapping) + : SDL_JoystickName(Device); } float GetSensitivity() { @@ -79,6 +112,7 @@ public: } void SetSensitivity(float scale) { + SettingsChanged = true; Multiplier = scale; } @@ -102,58 +136,145 @@ public: { return Axes[axis].Multiplier; } + float GetAxisDigitalThreshold(int axis) + { + return Axes[axis].DigitalThreshold; + } + EJoyCurve GetAxisResponseCurve(int axis) + { + return Axes[axis].ResponseCurvePreset; + } + float GetAxisResponseCurvePoint(int axis, int point) + { + return unsigned(point) < 4 + ? Axes[axis].ResponseCurve.pts[point] + : 0; + }; void SetAxisDeadZone(int axis, float zone) { - Axes[axis].DeadZone = clamp(zone, MIN_DEADZONE, 1.f); + SettingsChanged = true; + Axes[axis].DeadZone = clamp(zone, 0.f, 1.f); } void SetAxisMap(int axis, EJoyAxis gameaxis) { + SettingsChanged = true; Axes[axis].GameAxis = gameaxis; } void SetAxisScale(int axis, float scale) { + SettingsChanged = true; Axes[axis].Multiplier = scale; } + void SetAxisDigitalThreshold(int axis, float threshold) + { + SettingsChanged = true; + Axes[axis].DigitalThreshold = threshold; + } + void SetAxisResponseCurve(int axis, EJoyCurve preset) + { + if (preset >= NUM_JOYCURVE || preset < JOYCURVE_CUSTOM) return; + SettingsChanged = true; + Axes[axis].ResponseCurvePreset = preset; + if (preset == JOYCURVE_CUSTOM) return; + Axes[axis].ResponseCurve = JOYCURVE[preset]; + } + void SetAxisResponseCurvePoint(int axis, int point, float value) + { + if (unsigned(point) < 4) + { + SettingsChanged = true; + Axes[axis].ResponseCurvePreset = JOYCURVE_CUSTOM; + Axes[axis].ResponseCurve.pts[point] = value; + } + } // Used by the saver to not save properties that are at their defaults. bool IsSensitivityDefault() { - return Multiplier == 1.0f; + return Multiplier == JOYSENSITIVITY_DEFAULT; } bool IsAxisDeadZoneDefault(int axis) { - return Axes[axis].DeadZone <= MIN_DEADZONE; + if(axis >= DefaultAxesCount) + return Axes[axis].DeadZone == JOYDEADZONE_DEFAULT; + return Axes[axis].DeadZone == DefaultAxes[axis].DeadZone; } bool IsAxisMapDefault(int axis) { - if(axis >= 5) + if(axis >= DefaultAxesCount) return Axes[axis].GameAxis == JOYAXIS_None; - return Axes[axis].GameAxis == DefaultAxes[axis]; + return Axes[axis].GameAxis == DefaultAxes[axis].GameAxis; } bool IsAxisScaleDefault(int axis) { - return Axes[axis].Multiplier == 1.0f; + if(axis >= DefaultAxesCount) + return Axes[axis].Multiplier == JOYSENSITIVITY_DEFAULT; + return Axes[axis].Multiplier == DefaultAxes[axis].Multiplier; + } + bool IsAxisDigitalThresholdDefault(int axis) + { + if(axis >= DefaultAxesCount) + return Axes[axis].DigitalThreshold == JOYTHRESH_DEFAULT; + return Axes[axis].DigitalThreshold == DefaultAxes[axis].DigitalThreshold; + } + bool IsAxisResponseCurveDefault(int axis) + { + if(axis >= DefaultAxesCount) + return Axes[axis].ResponseCurvePreset == JOYCURVE_DEFAULT; + return Axes[axis].ResponseCurvePreset == DefaultAxes[axis].ResponseCurvePreset; } void SetDefaultConfig() { + if (Axes.size() == 0) + { + for(int i = 0;i < GetNumAxes();i++) + { + Axes.Push({}); + } + } + for(int i = 0;i < GetNumAxes();i++) { - AxisInfo info; - if(i < NumAxes) - info.Name.Format("Axis %d", i+1); + if (Mapping) { + switch(i) { + case SDL_CONTROLLER_AXIS_LEFTX: Axes[i].Name = "Left Stick X"; break; + case SDL_CONTROLLER_AXIS_LEFTY: Axes[i].Name = "Left Stick Y"; break; + case SDL_CONTROLLER_AXIS_RIGHTX: Axes[i].Name = "Right Stick X"; break; + case SDL_CONTROLLER_AXIS_RIGHTY: Axes[i].Name = "Right Stick Y"; break; + case SDL_CONTROLLER_AXIS_TRIGGERLEFT: Axes[i].Name = "Left Trigger"; break; + case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: Axes[i].Name = "Right Trigger"; break; + default: Axes[i].Name.Format("Axis %d", i+1); break; + } + } else { + if(i < NumAxes) + Axes[i].Name.Format("Axis %d", i+1); + else + Axes[i].Name.Format("Hat %d (%c)", (i-NumAxes)/2 + 1, (i-NumAxes)%2 == 0 ? 'x' : 'y'); + } + + Axes[i].Value = 0.0; + Axes[i].ButtonValue = 0; + + if (i < DefaultAxesCount) + { + Axes[i].GameAxis = DefaultAxes[i].GameAxis; + Axes[i].DeadZone = DefaultAxes[i].DeadZone; + Axes[i].Multiplier = DefaultAxes[i].Multiplier; + Axes[i].DigitalThreshold = DefaultAxes[i].DigitalThreshold; + Axes[i].ResponseCurvePreset = DefaultAxes[i].ResponseCurvePreset; + Axes[i].ResponseCurve = JOYCURVE[DefaultAxes[i].ResponseCurvePreset]; + } else - info.Name.Format("Hat %d (%c)", (i-NumAxes)/2 + 1, (i-NumAxes)%2 == 0 ? 'x' : 'y'); - info.DeadZone = DEFAULT_DEADZONE; - info.Multiplier = 1.0f; - info.Value = 0.0; - info.ButtonValue = 0; - if(i >= 5) - info.GameAxis = JOYAXIS_None; - else - info.GameAxis = DefaultAxes[i]; - Axes.Push(info); + { + Axes[i].GameAxis = JOYAXIS_None; + Axes[i].DeadZone = JOYDEADZONE_DEFAULT; + Axes[i].Multiplier = JOYSENSITIVITY_DEFAULT; + Axes[i].DigitalThreshold = JOYTHRESH_DEFAULT; + Axes[i].ResponseCurvePreset = JOYCURVE_DEFAULT; + Axes[i].ResponseCurve = JOYCURVE[JOYCURVE_DEFAULT]; + } } } @@ -161,9 +282,10 @@ public: { return Enabled; } - + void SetEnabled(bool enabled) { + SettingsChanged = true; Enabled = enabled; } @@ -188,63 +310,101 @@ public: } } - void ProcessInput() - { + void ProcessInput() { uint8_t buttonstate; - for (int i = 0; i < NumAxes; ++i) + if (Mapping) { - buttonstate = 0; + // GameController API available - Axes[i].Value = SDL_JoystickGetAxis(Device, i)/32767.0; - Axes[i].Value = Joy_RemoveDeadZone(Axes[i].Value, Axes[i].DeadZone, &buttonstate); + auto lastTriggerL = Axes[SDL_CONTROLLER_AXIS_TRIGGERLEFT].Value > Axes[SDL_CONTROLLER_AXIS_TRIGGERLEFT].DigitalThreshold; + auto lastTriggerR = Axes[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].Value > Axes[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].DigitalThreshold; - // Map button to axis - // X and Y are handled differently so if we have 2 or more axes then we'll use that code instead. - if (NumAxes == 1 || (i >= 2 && i < NUM_JOYAXISBUTTONS)) + for (auto i = 0; i < SDL_CONTROLLER_AXIS_MAX && i < NumAxes; ++i) { - Joy_GenerateButtonEvents(Axes[i].ButtonValue, buttonstate, 2, KEY_JOYAXIS1PLUS + i*2); - Axes[i].ButtonValue = buttonstate; - } - } + buttonstate = 0; - if(NumAxes > 1) - { - buttonstate = Joy_XYAxesToButtons(Axes[0].Value, Axes[1].Value); + Axes[i].Value = SDL_GameControllerGetAxis(Mapping, static_cast(i))/32767.0; + Axes[i].Value = Joy_RemoveDeadZone(Axes[i].Value, Axes[i].DeadZone, &buttonstate); + Axes[i].Value = Joy_ApplyResponseCurveBezier(Axes[i].ResponseCurve, Axes[i].Value); + } + + auto currTriggerL = Axes[SDL_CONTROLLER_AXIS_TRIGGERLEFT].Value > Axes[SDL_CONTROLLER_AXIS_TRIGGERLEFT].DigitalThreshold; + auto currTriggerR = Axes[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].Value > Axes[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].DigitalThreshold; + + if (lastTriggerL != currTriggerL) Joy_GenerateButtonEvent(currTriggerL, KEY_PAD_LTRIGGER); + if (lastTriggerR != currTriggerR) Joy_GenerateButtonEvent(currTriggerR, KEY_PAD_RTRIGGER); + + // todo: right stick + buttonstate = Joy_XYAxesToButtons( + abs(Axes[0].Value) < Axes[0].DigitalThreshold ? 0 : Axes[0].Value, + abs(Axes[1].Value) < Axes[1].DigitalThreshold ? 0 : Axes[1].Value + ); Joy_GenerateButtonEvents(Axes[0].ButtonValue, buttonstate, 4, KEY_JOYAXIS1PLUS); Axes[0].ButtonValue = buttonstate; } - - // Map POV hats to buttons and axes. Why axes? Well apparently I have - // a gamepad where the left control stick is a POV hat (instead of the - // d-pad like you would expect, no that's pressure sensitive). Also - // KDE's joystick dialog maps them to axes as well. - for (int i = 0; i < NumHats; ++i) + else { - AxisInfo &x = Axes[NumAxes + i*2]; - AxisInfo &y = Axes[NumAxes + i*2 + 1]; + // Joystick API fallback - buttonstate = SDL_JoystickGetHat(Device, i); - - // If we're going to assume that we can pass SDL's value into - // Joy_GenerateButtonEvents then we might as well assume the format here. - if(buttonstate & 0x1) // Up - y.Value = -1.0; - else if(buttonstate & 0x4) // Down - y.Value = 1.0; - else - y.Value = 0.0; - if(buttonstate & 0x2) // Left - x.Value = 1.0; - else if(buttonstate & 0x8) // Right - x.Value = -1.0; - else - x.Value = 0.0; - - if(i < 4) + for (int i = 0; i < NumAxes; ++i) { - Joy_GenerateButtonEvents(x.ButtonValue, buttonstate, 4, KEY_JOYPOV1_UP + i*4); - x.ButtonValue = buttonstate; + buttonstate = 0; + + Axes[i].Value = SDL_JoystickGetAxis(Device, i)/32767.0; + Axes[i].Value = Joy_RemoveDeadZone(Axes[i].Value, Axes[i].DeadZone, &buttonstate); + Axes[i].Value = Joy_ApplyResponseCurveBezier(Axes[i].ResponseCurve, Axes[i].Value); + + // Map button to axis + // X and Y are handled differently so if we have 2 or more axes then we'll use that code instead. + if (NumAxes == 1 || (i >= 2 && i < NUM_JOYAXISBUTTONS)) + { + Joy_GenerateButtonEvents(Axes[i].ButtonValue, buttonstate, 2, KEY_JOYAXIS1PLUS + i*2); + Axes[i].ButtonValue = buttonstate; + } + } + + if(NumAxes > 1) + { + buttonstate = Joy_XYAxesToButtons( + abs(Axes[0].Value) < Axes[0].DigitalThreshold ? 0 : Axes[0].Value, + abs(Axes[1].Value) < Axes[1].DigitalThreshold ? 0 : Axes[1].Value + ); + Joy_GenerateButtonEvents(Axes[0].ButtonValue, buttonstate, 4, KEY_JOYAXIS1PLUS); + Axes[0].ButtonValue = buttonstate; + } + + // Map POV hats to buttons and axes. Why axes? Well apparently I have + // a gamepad where the left control stick is a POV hat (instead of the + // d-pad like you would expect, no that's pressure sensitive). Also + // KDE's joystick dialog maps them to axes as well. + for (int i = 0; i < NumHats; ++i) + { + AxisInfo &x = Axes[NumAxes + i*2]; + AxisInfo &y = Axes[NumAxes + i*2 + 1]; + + buttonstate = SDL_JoystickGetHat(Device, i); + + // If we're going to assume that we can pass SDL's value into + // Joy_GenerateButtonEvents then we might as well assume the format here. + if(buttonstate & 0x1) // Up + y.Value = -1.0; + else if(buttonstate & 0x4) // Down + y.Value = 1.0; + else + y.Value = 0.0; + if(buttonstate & 0x2) // Left + x.Value = 1.0; + else if(buttonstate & 0x8) // Right + x.Value = -1.0; + else + x.Value = 0.0; + + if(i < 4) + { + Joy_GenerateButtonEvents(x.ButtonValue, buttonstate, 4, KEY_JOYPOV1_UP + i*4); + x.ButtonValue = buttonstate; + } } } } @@ -255,33 +415,66 @@ protected: FString Name; float DeadZone; float Multiplier; + float DigitalThreshold; + EJoyCurve ResponseCurvePreset; + CubicBezier ResponseCurve; EJoyAxis GameAxis; double Value; uint8_t ButtonValue; }; - static const EJoyAxis DefaultAxes[5]; + struct DefaultAxisConfig + { + float DeadZone; + EJoyAxis GameAxis; + float Multiplier; + float DigitalThreshold; + EJoyCurve ResponseCurvePreset; + }; + static const DefaultAxisConfig DefaultJoystickAxes[5]; + static const DefaultAxisConfig DefaultControllerAxes[6]; + const DefaultAxisConfig * DefaultAxes; + int DefaultAxesCount; int DeviceIndex; + int InstanceID; SDL_Joystick *Device; + SDL_GameController *Mapping; float Multiplier; bool Enabled; TArray Axes; int NumAxes; int NumHats; + bool SettingsChanged; friend class SDLInputJoystickManager; }; // [Nash 4 Feb 2024] seems like on Linux, the third axis is actually the Left Trigger, resulting in the player uncontrollably looking upwards. -const EJoyAxis SDLInputJoystick::DefaultAxes[5] = {JOYAXIS_Side, JOYAXIS_Forward, JOYAXIS_None, JOYAXIS_Yaw, JOYAXIS_Pitch}; +const SDLInputJoystick::DefaultAxisConfig SDLInputJoystick::DefaultJoystickAxes[5] = { + {JOYDEADZONE_DEFAULT, JOYAXIS_Side, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT}, + {JOYDEADZONE_DEFAULT, JOYAXIS_Forward, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT}, + {JOYDEADZONE_DEFAULT, JOYAXIS_None, JOYSENSITIVITY_DEFAULT, JOYTHRESH_DEFAULT, JOYCURVE_DEFAULT}, + {JOYDEADZONE_DEFAULT, JOYAXIS_Yaw, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT}, + {JOYDEADZONE_DEFAULT, JOYAXIS_Pitch, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT} +}; + +// Defaults if we have access to the GameController API for this device +const SDLInputJoystick::DefaultAxisConfig SDLInputJoystick::DefaultControllerAxes[6] = { + {JOYDEADZONE_DEFAULT, JOYAXIS_Side, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT}, + {JOYDEADZONE_DEFAULT, JOYAXIS_Forward, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT}, + {JOYDEADZONE_DEFAULT, JOYAXIS_Yaw, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT}, + {JOYDEADZONE_DEFAULT, JOYAXIS_Pitch, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT}, + {JOYDEADZONE_DEFAULT, JOYAXIS_None, JOYSENSITIVITY_DEFAULT, JOYTHRESH_TRIGGER, JOYCURVE_DEFAULT}, + {JOYDEADZONE_DEFAULT, JOYAXIS_None, JOYSENSITIVITY_DEFAULT, JOYTHRESH_TRIGGER, JOYCURVE_DEFAULT}, +}; class SDLInputJoystickManager { public: SDLInputJoystickManager() { - this->UpdateDeviceList(); + UpdateDeviceList(); } void UpdateDeviceList() @@ -318,6 +511,19 @@ public: if(Joysticks[i]->Enabled) Joysticks[i]->ProcessInput(); } + bool IsJoystickEnabled(int instanceID) + { + for(unsigned int i = 0; i < Joysticks.Size(); i++) + { + if (Joysticks[i]->InstanceID != instanceID) + { + continue; + } + return Joysticks[i]->Enabled; + } + return false; + } + protected: TDeletingArray Joysticks; }; @@ -326,7 +532,7 @@ static SDLInputJoystickManager *JoystickManager; void I_StartupJoysticks() { #ifndef NO_SDL_JOYSTICK - if(SDL_InitSubSystem(SDL_INIT_JOYSTICK) >= 0) + if(SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) >= 0) JoystickManager = new SDLInputJoystickManager(); #endif } @@ -335,7 +541,7 @@ void I_ShutdownInput() if(JoystickManager) { delete JoystickManager; - SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); } } @@ -365,6 +571,16 @@ void I_ProcessJoysticks() JoystickManager->ProcessInput(); } +void I_JoyConsumeEvent(int instanceID, event_t * event) +{ + if (event->type == EV_KeyDown) + { + bool okay = use_joystick && JoystickManager && JoystickManager->IsJoystickEnabled(instanceID); + if (!okay) return; + } + D_PostEvent(event); +} + IJoystickConfig *I_UpdateDeviceList() { JoystickManager->UpdateDeviceList(); diff --git a/src/common/platform/win32/i_dijoy.cpp b/src/common/platform/win32/i_dijoy.cpp index 33c4574c9..2340bfc84 100644 --- a/src/common/platform/win32/i_dijoy.cpp +++ b/src/common/platform/win32/i_dijoy.cpp @@ -146,7 +146,6 @@ public: // MACROS ------------------------------------------------------------------ -#define DEFAULT_DEADZONE 0.25f // TYPES ------------------------------------------------------------------- @@ -170,15 +169,23 @@ public: EJoyAxis GetAxisMap(int axis); const char *GetAxisName(int axis); float GetAxisScale(int axis); + float GetAxisDigitalThreshold(int axis); + EJoyCurve GetAxisResponseCurve(int axis); + float GetAxisResponseCurvePoint(int axis, int point); void SetAxisDeadZone(int axis, float deadzone); void SetAxisMap(int axis, EJoyAxis gameaxis); void SetAxisScale(int axis, float scale); + void SetAxisDigitalThreshold(int axis, float threshold); + void SetAxisResponseCurve(int axis, EJoyCurve preset); + void SetAxisResponseCurvePoint(int axis, int point, float value); bool IsSensitivityDefault(); bool IsAxisDeadZoneDefault(int axis); bool IsAxisMapDefault(int axis); bool IsAxisScaleDefault(int axis); + bool IsAxisDigitalThresholdDefault(int axis); + bool IsAxisResponseCurveDefault(int axis); bool GetEnabled(); void SetEnabled(bool enabled); @@ -201,6 +208,9 @@ protected: float Value; float DeadZone, DefaultDeadZone; float Multiplier, DefaultMultiplier; + float DigitalThreshold, DefaultDigitalThreshold; + EJoyCurve ResponseCurvePreset, DefaultResponseCurvePreset; + CubicBezier ResponseCurve; EJoyAxis GameAxis, DefaultGameAxis; uint8_t ButtonValue; }; @@ -454,16 +464,21 @@ void FDInputJoystick::ProcessInput() axisval = (value - info->Min) * 2.0 / (info->Max - info->Min) - 1.0; // Cancel out dead zone axisval = Joy_RemoveDeadZone(axisval, info->DeadZone, &buttonstate); + axisval = Joy_ApplyResponseCurveBezier(info->ResponseCurve, axisval); info->Value = float(axisval); if (i < NUM_JOYAXISBUTTONS && (i > 2 || Axes.Size() == 1)) { + if (abs(axisval) < info->DigitalThreshold) buttonstate = 0; Joy_GenerateButtonEvents(info->ButtonValue, buttonstate, 2, KEY_JOYAXIS1PLUS + i*2); } else if (i == 1) { // Since we sorted the axes, we know that the first two are definitely X and Y. // They are probably a single stick, so use angular position to determine buttons. - buttonstate = Joy_XYAxesToButtons(Axes[0].Value, axisval); + buttonstate = Joy_XYAxesToButtons( + (abs(Axes[0].Value) < Axes[0].DigitalThreshold) ? 0 : Axes[0].Value, + (abs(axisval) < info->DigitalThreshold) ? 0 : axisval + ); Joy_GenerateButtonEvents(info->ButtonValue, buttonstate, 4, KEY_JOYAXIS1PLUS); } info->ButtonValue = buttonstate; @@ -761,38 +776,54 @@ void FDInputJoystick::SetDefaultConfig() { unsigned i; - Multiplier = 1; + Multiplier = JOYSENSITIVITY_DEFAULT; for (i = 0; i < Axes.Size(); ++i) { - Axes[i].DeadZone = DEFAULT_DEADZONE; - Axes[i].Multiplier = 1; + Axes[i].DeadZone = JOYDEADZONE_DEFAULT; + Axes[i].Multiplier = JOYSENSITIVITY_DEFAULT; Axes[i].GameAxis = JOYAXIS_None; + Axes[i].DigitalThreshold = JOYTHRESH_DEFAULT; + Axes[i].ResponseCurvePreset = JOYCURVE_DEFAULT; + Axes[i].ResponseCurve = JOYCURVE[JOYCURVE_DEFAULT]; } // Triggers on a 360 controller have a much smaller deadzone. if (Axes.Size() == 5 && Axes[4].Guid == GUID_ZAxis) { Axes[4].DeadZone = 30 / 256.f; + Axes[4].DigitalThreshold = JOYTHRESH_TRIGGER; } // Two axes? Horizontal is yaw and vertical is forward. if (Axes.Size() == 2) { Axes[0].GameAxis = JOYAXIS_Yaw; + Axes[0].DigitalThreshold = JOYTHRESH_STICK_X; + Axes[1].GameAxis = JOYAXIS_Forward; + Axes[1].DigitalThreshold = JOYTHRESH_STICK_Y; } // Three axes? First two are movement, third is yaw. else if (Axes.Size() >= 3) { Axes[0].GameAxis = JOYAXIS_Side; + Axes[0].DigitalThreshold = JOYTHRESH_STICK_X; + Axes[1].GameAxis = JOYAXIS_Forward; + Axes[1].DigitalThreshold = JOYTHRESH_STICK_Y; + Axes[2].GameAxis = JOYAXIS_Yaw; + Axes[2].DigitalThreshold = JOYTHRESH_STICK_X; + // Four axes? First two are movement, last two are looking around. if (Axes.Size() >= 4) { - Axes[3].GameAxis = JOYAXIS_Pitch; Axes[3].Multiplier = 0.75f; + Axes[3].GameAxis = JOYAXIS_Pitch; + // Axes[3].Multiplier = 0.75f; + Axes[3].DigitalThreshold = JOYTHRESH_STICK_Y; // Five axes? Use the fifth one for moving up and down. if (Axes.Size() >= 5) { Axes[4].GameAxis = JOYAXIS_Up; + Axes[4].DigitalThreshold = JOYTHRESH_STICK_Y; } } } @@ -805,6 +836,8 @@ void FDInputJoystick::SetDefaultConfig() Axes[i].DefaultDeadZone = Axes[i].DeadZone; Axes[i].DefaultMultiplier = Axes[i].Multiplier; Axes[i].DefaultGameAxis = Axes[i].GameAxis; + Axes[i].DefaultDigitalThreshold = Axes[i].DigitalThreshold; + Axes[i].DefaultResponseCurvePreset = Axes[i].ResponseCurvePreset; } } @@ -849,7 +882,7 @@ void FDInputJoystick::SetSensitivity(float scale) bool FDInputJoystick::IsSensitivityDefault() { - return Multiplier == 1; + return Multiplier == JOYSENSITIVITY_DEFAULT; } //=========================================================================== @@ -923,6 +956,51 @@ float FDInputJoystick::GetAxisScale(int axis) return Axes[axis].Multiplier; } +//=========================================================================== +// +// FDInputJoystick :: GetAxisDigitalThreshold +// +//=========================================================================== + +float FDInputJoystick::GetAxisDigitalThreshold(int axis) +{ + if (unsigned(axis) >= Axes.Size()) + { + return JOYTHRESH_DEFAULT; + } + return Axes[axis].DigitalThreshold; +} + +//=========================================================================== +// +// FDInputJoystick :: GetAxisResponseCurve +// +//=========================================================================== + +EJoyCurve FDInputJoystick::GetAxisResponseCurve(int axis) +{ + if (unsigned(axis) >= Axes.Size()) + { + return JOYCURVE_DEFAULT; + } + return Axes[axis].ResponseCurvePreset; +} + +//=========================================================================== +// +// FDInputJoystick :: GetAxisResponseCurvePoint +// +//=========================================================================== + +float FDInputJoystick::GetAxisResponseCurvePoint(int axis, int point) +{ + if (unsigned(axis) >= Axes.Size() || unsigned(point) >= 4) + { + return 0; + } + return Axes[axis].ResponseCurve.pts[point]; +} + //=========================================================================== // // FDInputJoystick :: SetAxisDeadZone @@ -965,6 +1043,52 @@ void FDInputJoystick::SetAxisScale(int axis, float scale) } } +//=========================================================================== +// +// FDInputJoystick :: SetAxisDigitalThreshold +// +//=========================================================================== + +void FDInputJoystick::SetAxisDigitalThreshold(int axis, float threshold) +{ + if (unsigned(axis) < Axes.Size()) + { + Axes[axis].DigitalThreshold = threshold; + } +} + +//=========================================================================== +// +// FDInputJoystick :: SetAxisResponseCurve +// +//=========================================================================== + +void FDInputJoystick::SetAxisResponseCurve(int axis, EJoyCurve preset) +{ + if (unsigned(axis) < Axes.Size()) + { + if (preset >= NUM_JOYCURVE || preset < JOYCURVE_CUSTOM) return; + Axes[axis].ResponseCurvePreset = preset; + if (preset == JOYCURVE_CUSTOM) return; + Axes[axis].ResponseCurve = JOYCURVE[preset]; + } +} + +//=========================================================================== +// +// FDInputJoystick :: SetAxisResponseCurvePoint +// +//=========================================================================== + +void FDInputJoystick::SetAxisResponseCurvePoint(int axis, int point, float value) +{ + if (unsigned(axis) < Axes.Size() && unsigned(point) < 4) + { + Axes[axis].ResponseCurvePreset = JOYCURVE_CUSTOM; + Axes[axis].ResponseCurve.pts[point] = value; + } +} + //=========================================================================== // // FDInputJoystick :: IsAxisDeadZoneDefault @@ -995,6 +1119,36 @@ bool FDInputJoystick::IsAxisScaleDefault(int axis) return true; } +//=========================================================================== +// +// FDInputJoystick :: IsAxisDigitalThresholdDefault +// +//=========================================================================== + +bool FDInputJoystick::IsAxisDigitalThresholdDefault(int axis) +{ + if (unsigned(axis) < Axes.Size()) + { + return Axes[axis].DigitalThreshold == Axes[axis].DefaultDigitalThreshold; + } + return true; +} + +//=========================================================================== +// +// FDInputJoystick :: IsAxisResponseCurveDefault +// +//=========================================================================== + +bool FDInputJoystick::IsAxisResponseCurveDefault(int axis) +{ + if (unsigned(axis) < Axes.Size()) + { + return Axes[axis].ResponseCurvePreset == Axes[axis].DefaultResponseCurvePreset; + } + return true; +} + //=========================================================================== // // FDInputJoystick :: GetEnabled diff --git a/src/common/platform/win32/i_rawps2.cpp b/src/common/platform/win32/i_rawps2.cpp index 2f89739bd..09bb314b0 100644 --- a/src/common/platform/win32/i_rawps2.cpp +++ b/src/common/platform/win32/i_rawps2.cpp @@ -48,7 +48,6 @@ // MACROS ------------------------------------------------------------------ -#define DEFAULT_DEADZONE 0.25f #define STATUS_SWITCH_TIME 3 #define VID_PLAY_COM 0x0b43 @@ -104,15 +103,23 @@ public: EJoyAxis GetAxisMap(int axis); const char *GetAxisName(int axis); float GetAxisScale(int axis); + float GetAxisDigitalThreshold(int axis); + EJoyCurve GetAxisResponseCurve(int axis); + float GetAxisResponseCurvePoint(int axis, int point); void SetAxisDeadZone(int axis, float deadzone); void SetAxisMap(int axis, EJoyAxis gameaxis); void SetAxisScale(int axis, float scale); + void SetAxisDigitalThreshold(int axis, float threshold); + void SetAxisResponseCurve(int axis, EJoyCurve preset); + void SetAxisResponseCurvePoint(int axis, int point, float value); bool IsSensitivityDefault(); bool IsAxisDeadZoneDefault(int axis); bool IsAxisMapDefault(int axis); bool IsAxisScaleDefault(int axis); + bool IsAxisDigitalThresholdDefault(int axis); + bool IsAxisResponseCurveDefault(int axis); bool GetEnabled(); void SetEnabled(bool enabled); @@ -130,6 +137,9 @@ protected: float Value; float DeadZone; float Multiplier; + float DigitalThreshold; + EJoyCurve ResponseCurvePreset; + CubicBezier ResponseCurve; EJoyAxis GameAxis; uint8_t ButtonValue; }; @@ -137,6 +147,8 @@ protected: { EJoyAxis GameAxis; float Multiplier; + float DigitalThreshold; + EJoyCurve ResponseCurvePreset; }; enum { @@ -357,11 +369,11 @@ static const char *AxisNames[] = FRawPS2Controller::DefaultAxisConfig FRawPS2Controller::DefaultAxes[NUM_AXES] = { - // Game axis, multiplier - { JOYAXIS_Side, 1 }, // ThumbLX - { JOYAXIS_Forward, 1 }, // ThumbLY - { JOYAXIS_Yaw, 1 }, // ThumbRX - { JOYAXIS_Pitch, 0.75 }, // ThumbRY + // Game axis, multiplier, digital threshold, response curve A, response curve B + { JOYAXIS_Side, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT }, // ThumbLX + { JOYAXIS_Forward, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT }, // ThumbLY + { JOYAXIS_Yaw, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT }, // ThumbRX + { JOYAXIS_Pitch, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT }, // ThumbRY }; // CODE -------------------------------------------------------------------- @@ -552,11 +564,16 @@ void FRawPS2Controller::ProcessThumbstick(int value1, AxisInfo *axis1, int value axisval2 = value2 * (2.0 / 255) - 1.0; axisval1 = Joy_RemoveDeadZone(axisval1, axis1->DeadZone, NULL); axisval2 = Joy_RemoveDeadZone(axisval2, axis2->DeadZone, NULL); + axisval1 = Joy_ApplyResponseCurveBezier(axis1->ResponseCurve, axisval1); + axisval2 = Joy_ApplyResponseCurveBezier(axis2->ResponseCurve, axisval2); axis1->Value = float(axisval1); axis2->Value = float(axisval2); // We store all four buttons in the first axis and ignore the second. - buttonstate = Joy_XYAxesToButtons(axisval1, axisval2); + buttonstate = Joy_XYAxesToButtons( + (abs(axisval1) < axis1->DigitalThreshold) ? 0 : axisval1, + (abs(axisval2) < axis2->DigitalThreshold) ? 0 : axisval2 + ); Joy_GenerateButtonEvents(axis1->ButtonValue, buttonstate, 4, base); axis1->ButtonValue = buttonstate; } @@ -644,10 +661,10 @@ void FRawPS2Controller::AddAxes(float axes[NUM_JOYAXIS]) void FRawPS2Controller::SetDefaultConfig() { - Multiplier = 1; + Multiplier = JOYSENSITIVITY_DEFAULT; for (int i = 0; i < NUM_AXES; ++i) { - Axes[i].DeadZone = DEFAULT_DEADZONE; + Axes[i].DeadZone = JOYDEADZONE_DEFAULT; Axes[i].GameAxis = DefaultAxes[i].GameAxis; Axes[i].Multiplier = DefaultAxes[i].Multiplier; } @@ -712,7 +729,7 @@ void FRawPS2Controller::SetSensitivity(float scale) bool FRawPS2Controller::IsSensitivityDefault() { - return Multiplier == 1; + return Multiplier == JOYSENSITIVITY_DEFAULT; } //========================================================================== @@ -786,6 +803,51 @@ float FRawPS2Controller::GetAxisScale(int axis) return 0; } +//========================================================================== +// +// FRawPS2Controller :: GetAxisDigitalThreshold +// +//========================================================================== + +float FRawPS2Controller::GetAxisDigitalThreshold(int axis) +{ + if (unsigned(axis) < NUM_AXES) + { + return Axes[axis].DigitalThreshold; + } + return JOYTHRESH_DEFAULT; +} + +//========================================================================== +// +// FRawPS2Controller :: GetAxisResponseCurve +// +//========================================================================== + +EJoyCurve FRawPS2Controller::GetAxisResponseCurve(int axis) +{ + if (unsigned(axis) < NUM_AXES) + { + return Axes[axis].ResponseCurvePreset; + } + return JOYCURVE_DEFAULT; +} + +//========================================================================== +// +// FRawPS2Controller :: GetAxisResponseCurvePoint +// +//========================================================================== + +float FRawPS2Controller::GetAxisResponseCurvePoint(int axis, int point) +{ + if (unsigned(axis) < NUM_AXES && unsigned(point) < 4) + { + return Axes[axis].ResponseCurve.pts[point]; + } + return 0; +} + //========================================================================== // // FRawPS2Controller :: SetAxisDeadZone @@ -828,6 +890,52 @@ void FRawPS2Controller::SetAxisScale(int axis, float scale) } } +//========================================================================== +// +// FRawPS2Controller :: SetAxisDigitalThreshold +// +//========================================================================== + +void FRawPS2Controller::SetAxisDigitalThreshold(int axis, float threshold) +{ + if (unsigned(axis) < NUM_AXES) + { + Axes[axis].DigitalThreshold = threshold; + } +} + +//========================================================================== +// +// FRawPS2Controller :: SetAxisResponseCurve +// +//========================================================================== + +void FRawPS2Controller::SetAxisResponseCurve(int axis, EJoyCurve preset) +{ + if (unsigned(axis) < NUM_AXES) + { + if (preset >= NUM_JOYCURVE || preset < JOYCURVE_CUSTOM) return; + Axes[axis].ResponseCurvePreset = preset; + if (preset == JOYCURVE_CUSTOM) return; + Axes[axis].ResponseCurve = JOYCURVE[preset]; + } +} + +//========================================================================== +// +// FRawPS2Controller :: SetAxisResponseCurvePoint +// +//========================================================================== + +void FRawPS2Controller::SetAxisResponseCurvePoint(int axis, int point, float value) +{ + if (unsigned(axis) < NUM_AXES && unsigned(point) < 4) + { + Axes[axis].ResponseCurvePreset = JOYCURVE_CUSTOM; + Axes[axis].ResponseCurve.pts[point] = value; + } +} + //=========================================================================== // // FRawPS2Controller :: IsAxisDeadZoneDefault @@ -838,7 +946,7 @@ bool FRawPS2Controller::IsAxisDeadZoneDefault(int axis) { if (unsigned(axis) < NUM_AXES) { - return Axes[axis].DeadZone == DEFAULT_DEADZONE; + return Axes[axis].DeadZone == JOYDEADZONE_DEFAULT; } return true; } @@ -858,6 +966,36 @@ bool FRawPS2Controller::IsAxisScaleDefault(int axis) return true; } +//=========================================================================== +// +// FRawPS2Controller :: IsAxisDigitalThresholdDefault +// +//=========================================================================== + +bool FRawPS2Controller::IsAxisDigitalThresholdDefault(int axis) +{ + if (unsigned(axis) < NUM_AXES) + { + return Axes[axis].DigitalThreshold == DefaultAxes[axis].DigitalThreshold; + } + return true; +} + +//=========================================================================== +// +// FRawPS2Controller :: IsAxisResponseCurveDefault +// +//=========================================================================== + +bool FRawPS2Controller::IsAxisResponseCurveDefault(int axis) +{ + if (unsigned(axis) < NUM_AXES) + { + return Axes[axis].ResponseCurvePreset == DefaultAxes[axis].ResponseCurvePreset; + } + return true; +} + //=========================================================================== // // FRawPS2Controller :: GetEnabled diff --git a/src/common/platform/win32/i_xinput.cpp b/src/common/platform/win32/i_xinput.cpp index 0f3c19237..890888a4b 100644 --- a/src/common/platform/win32/i_xinput.cpp +++ b/src/common/platform/win32/i_xinput.cpp @@ -94,15 +94,23 @@ public: EJoyAxis GetAxisMap(int axis); const char *GetAxisName(int axis); float GetAxisScale(int axis); + float GetAxisDigitalThreshold(int axis); + EJoyCurve GetAxisResponseCurve(int axis); + float GetAxisResponseCurvePoint(int axis, int point); void SetAxisDeadZone(int axis, float deadzone); void SetAxisMap(int axis, EJoyAxis gameaxis); void SetAxisScale(int axis, float scale); + void SetAxisDigitalThreshold(int axis, float threshold); + void SetAxisResponseCurve(int axis, EJoyCurve preset); + void SetAxisResponseCurvePoint(int axis, int point, float value); bool IsSensitivityDefault(); bool IsAxisDeadZoneDefault(int axis); bool IsAxisMapDefault(int axis); bool IsAxisScaleDefault(int axis); + bool IsAxisDigitalThresholdDefault(int axis); + bool IsAxisResponseCurveDefault(int axis); bool GetEnabled(); void SetEnabled(bool enabled); @@ -122,12 +130,17 @@ protected: float Multiplier; EJoyAxis GameAxis; uint8_t ButtonValue; + float DigitalThreshold; + EJoyCurve ResponseCurvePreset; + CubicBezier ResponseCurve; }; struct DefaultAxisConfig { float DeadZone; EJoyAxis GameAxis; float Multiplier; + float DigitalThreshold; + EJoyCurve ResponseCurvePreset; }; enum { @@ -211,13 +224,13 @@ static const char *AxisNames[] = FXInputController::DefaultAxisConfig FXInputController::DefaultAxes[NUM_AXES] = { - // Dead zone, game axis, multiplier - { XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE / 32768.f, JOYAXIS_Side, 1 }, // ThumbLX - { XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE / 32768.f, JOYAXIS_Forward, 1 }, // ThumbLY - { XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE / 32768.f, JOYAXIS_Yaw, 1 }, // ThumbRX - { XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE / 32768.f, JOYAXIS_Pitch, 0.75 }, // ThumbRY - { XINPUT_GAMEPAD_TRIGGER_THRESHOLD / 256.f, JOYAXIS_None, 0 }, // LeftTrigger - { XINPUT_GAMEPAD_TRIGGER_THRESHOLD / 256.f, JOYAXIS_None, 0 } // RightTrigger + // Dead zone, game axis, multiplier, digitalthreshold, curveA, curveB + { XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE / 32768.f, JOYAXIS_Side, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT }, // ThumbLX + { XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE / 32768.f, JOYAXIS_Forward, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT }, // ThumbLY + { XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE / 32768.f, JOYAXIS_Yaw, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT }, // ThumbRX + { XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE / 32768.f, JOYAXIS_Pitch, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT }, // ThumbRY + { XINPUT_GAMEPAD_TRIGGER_THRESHOLD / 256.f, JOYAXIS_None, JOYSENSITIVITY_DEFAULT, JOYTHRESH_TRIGGER, JOYCURVE_DEFAULT }, // LeftTrigger + { XINPUT_GAMEPAD_TRIGGER_THRESHOLD / 256.f, JOYAXIS_None, JOYSENSITIVITY_DEFAULT, JOYTHRESH_TRIGGER, JOYCURVE_DEFAULT } // RightTrigger }; // CODE -------------------------------------------------------------------- @@ -327,11 +340,16 @@ void FXInputController::ProcessThumbstick(int value1, AxisInfo *axis1, axisval2 = (value2 - SHRT_MIN) * 2.0 / 65536 - 1.0; axisval1 = Joy_RemoveDeadZone(axisval1, axis1->DeadZone, NULL); axisval2 = Joy_RemoveDeadZone(axisval2, axis2->DeadZone, NULL); + axisval1 = Joy_ApplyResponseCurveBezier(axis1->ResponseCurve, axisval1); + axisval2 = Joy_ApplyResponseCurveBezier(axis2->ResponseCurve, axisval2); axis1->Value = float(axisval1); axis2->Value = float(axisval2); // We store all four buttons in the first axis and ignore the second. - buttonstate = Joy_XYAxesToButtons(axisval1, axisval2); + buttonstate = Joy_XYAxesToButtons( + abs(axisval1) < axis1->DigitalThreshold ? 0: axisval1, + abs(axisval2) < axis2->DigitalThreshold ? 0: axisval2 + ); Joy_GenerateButtonEvents(axis1->ButtonValue, buttonstate, 4, base); axis1->ButtonValue = buttonstate; } @@ -351,6 +369,11 @@ void FXInputController::ProcessTrigger(int value, AxisInfo *axis, int base) double axisval; axisval = Joy_RemoveDeadZone(value / 256.0, axis->DeadZone, &buttonstate); + axisval = Joy_ApplyResponseCurveBezier(axis->ResponseCurve, axisval); + + // TODO: probably just put this into Joy_RemoveDeadZone + if (abs(axisval) < axis->DigitalThreshold) buttonstate = 0; + Joy_GenerateButtonEvents(axis->ButtonValue, buttonstate, 1, base); axis->ButtonValue = buttonstate; axis->Value = float(axisval); @@ -431,12 +454,15 @@ void FXInputController::AddAxes(float axes[NUM_JOYAXIS]) void FXInputController::SetDefaultConfig() { - Multiplier = 1; + Multiplier = JOYSENSITIVITY_DEFAULT; for (int i = 0; i < NUM_AXES; ++i) { Axes[i].DeadZone = DefaultAxes[i].DeadZone; Axes[i].GameAxis = DefaultAxes[i].GameAxis; Axes[i].Multiplier = DefaultAxes[i].Multiplier; + Axes[i].DigitalThreshold = DefaultAxes[i].DigitalThreshold; + Axes[i].ResponseCurve = JOYCURVE[DefaultAxes[i].ResponseCurvePreset]; + Axes[i].ResponseCurvePreset = DefaultAxes[i].ResponseCurvePreset; } } @@ -494,7 +520,7 @@ void FXInputController::SetSensitivity(float scale) bool FXInputController::IsSensitivityDefault() { - return Multiplier == 1; + return Multiplier == JOYSENSITIVITY_DEFAULT; } //========================================================================== @@ -565,6 +591,51 @@ float FXInputController::GetAxisScale(int axis) { return Axes[axis].Multiplier; } + return JOYSENSITIVITY_DEFAULT; +} + +//========================================================================== +// +// FXInputController :: GetAxisDigitalThreshold +// +//========================================================================== + +float FXInputController::GetAxisDigitalThreshold(int axis) +{ + if (unsigned(axis) < NUM_AXES) + { + return Axes[axis].DigitalThreshold; + } + return JOYTHRESH_DEFAULT; +} + +//========================================================================== +// +// FXInputController :: GetAxisResponseCurve +// +//========================================================================== + +EJoyCurve FXInputController::GetAxisResponseCurve(int axis) +{ + if (unsigned(axis) < NUM_AXES) + { + return Axes[axis].ResponseCurvePreset; + } + return JOYCURVE_DEFAULT; +} + +//========================================================================== +// +// FXInputController :: GetAxisResponseCurvePoint +// +//========================================================================== + +float FXInputController::GetAxisResponseCurvePoint(int axis, int point) +{ + if (unsigned(axis) < NUM_AXES && unsigned(point) < 4) + { + return Axes[axis].ResponseCurve.pts[point]; + } return 0; } @@ -610,6 +681,52 @@ void FXInputController::SetAxisScale(int axis, float scale) } } +//========================================================================== +// +// FXInputController :: SetAxisDigitalThreshold +// +//========================================================================== + +void FXInputController::SetAxisDigitalThreshold(int axis, float threshold) +{ + if (unsigned(axis) < NUM_AXES) + { + Axes[axis].DigitalThreshold = threshold; + } +} + +//========================================================================== +// +// FXInputController :: SetAxisResponseCurve +// +//========================================================================== + +void FXInputController::SetAxisResponseCurve(int axis, EJoyCurve preset) +{ + if (unsigned(axis) < NUM_AXES) + { + if (preset >= NUM_JOYCURVE || preset < JOYCURVE_CUSTOM) return; + Axes[axis].ResponseCurvePreset = preset; + if (preset == JOYCURVE_CUSTOM) return; + Axes[axis].ResponseCurve = JOYCURVE[preset]; + } +} + +//========================================================================== +// +// FXInputController :: SetAxisResponseCurvePoint +// +//========================================================================== + +void FXInputController::SetAxisResponseCurvePoint(int axis, int point, float value) +{ + if (unsigned(axis) < NUM_AXES && unsigned(point) < 4) + { + Axes[axis].ResponseCurvePreset = JOYCURVE_CUSTOM; + Axes[axis].ResponseCurve.pts[point] = value; + } +} + //=========================================================================== // // FXInputController :: IsAxisDeadZoneDefault @@ -640,6 +757,36 @@ bool FXInputController::IsAxisScaleDefault(int axis) return true; } +//=========================================================================== +// +// FXInputController :: IsAxisDigitalThresholdDefault +// +//=========================================================================== + +bool FXInputController::IsAxisDigitalThresholdDefault(int axis) +{ + if (unsigned(axis) < NUM_AXES) + { + return Axes[axis].DigitalThreshold == DefaultAxes[axis].DigitalThreshold; + } + return true; +} + +//=========================================================================== +// +// FXInputController :: IsAxisResponseCurveDefault +// +//=========================================================================== + +bool FXInputController::IsAxisResponseCurveDefault(int axis) +{ + if (unsigned(axis) < NUM_AXES) + { + return Axes[axis].ResponseCurvePreset == DefaultAxes[axis].ResponseCurvePreset; + } + return true; +} + //=========================================================================== // // FXInputController :: GetEnabled diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index 046a7f1f6..2577b9aa7 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -837,6 +837,15 @@ OptionMenu "JoystickOptions" protected Title "$JOYMNU_OPTIONS" } +OptionValue "JoyAxisCurveNames" +{ + -1, "$OPTVAL_CUSTOM" + 0, "$OPTVAL_DEFAULT" + 1, "$OPTVAL_LINEAR" + 2, "$OPTVAL_QUADRATIC" + 3, "$OPTVAL_CUBIC" +} + OptionValue "JoyAxisMapNames" { -1, "$OPTVAL_NONE" diff --git a/wadsrc/static/zscript/engine/ui/menu/joystickmenu.zs b/wadsrc/static/zscript/engine/ui/menu/joystickmenu.zs index 02669c1a3..1b813818f 100644 --- a/wadsrc/static/zscript/engine/ui/menu/joystickmenu.zs +++ b/wadsrc/static/zscript/engine/ui/menu/joystickmenu.zs @@ -130,7 +130,94 @@ class OptionMenuSliderJoyDeadZone : OptionMenuSliderBase //============================================================================= // -// +// +// +//============================================================================= + +class OptionMenuSliderJoyDigitalThreshold : OptionMenuSliderBase +{ + int mAxis; + int mNeg; + JoystickConfig mJoy; + + OptionMenuSliderJoyDigitalThreshold Init(String label, int axis, double min, double max, double step, int showval, JoystickConfig joy) + { + Super.Init(label, min, max, step, showval); + mAxis = axis; + mNeg = 1; + mJoy = joy; + return self; + } + + override double GetSliderValue() + { + double d = mJoy.GetAxisDigitalThreshold(mAxis); + mNeg = d < 0? -1:1; + return d; + } + + override void SetSliderValue(double val) + { + mJoy.SetAxisDigitalThreshold(mAxis, val * mNeg); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +class OptionMenuItemJoyCurve : OptionMenuItemOptionBase +{ + int mAxis; + JoystickConfig mJoy; + + OptionMenuItemJoyCurve Init(String label, int axis, Name values, int center, JoystickConfig joy) + { + Super.Init(label, 'none', values, null, center); + mAxis = axis; + mJoy = joy; + return self; + } + + override int GetSelection() + { + double f = mJoy.GetAxisResponseCurve(mAxis); + let opt = OptionValues.GetCount(mValues); + if (opt > 0) + { + // Map from joystick curve to menu selection. + for(int i = 0; i < opt; i++) + { + if (f ~== OptionValues.GetValue(mValues, i)) + { + return i; + } + } + } + return JoystickConfig.JOYCURVE_CUSTOM; + } + + override void SetSelection(int selection) + { + let opt = OptionValues.GetCount(mValues); + // Map from menu selection to joystick curve. + if (opt == 0 || selection >= opt) + { + selection = JoystickConfig.JOYCURVE_DEFAULT; + } + else + { + selection = int(OptionValues.GetValue(mValues, selection)); + } + mJoy.setAxisResponseCurve(mAxis, selection); + } +} + +//============================================================================= +// +// // //============================================================================= @@ -340,8 +427,25 @@ class OptionMenuItemJoyConfigMenu : OptionMenuItemSubmenu opt.mItems.Push(it); it = new("OptionMenuItemInverter").Init("$JOYMNU_INVERT", i, false, joy); opt.mItems.Push(it); + it = new("OptionMenuItemJoyCurve").Init("$JOYMNU_CURVE", i, "JoyAxisCurveNames", false, joy); + opt.mItems.Push(it); + // // is there a way to do something like this? + // // add extra options if the selected value is something specific? + // if (joy.GetAxisResponseCurve(i) == JoystickConfig.JOYCURVE_CUSTOM) + // { + // it = new("OptionMenuItemStaticText").Init("$JOYMNU_CURVE_X1", false); + // opt.mItems.Push(it); + // it = new("OptionMenuItemStaticText").Init("$JOYMNU_CURVE_Y1", false); + // opt.mItems.Push(it); + // it = new("OptionMenuItemStaticText").Init("$JOYMNU_CURVE_X2", false); + // opt.mItems.Push(it); + // it = new("OptionMenuItemStaticText").Init("$JOYMNU_CURVE_Y2", false); + // opt.mItems.Push(it); + // } it = new("OptionMenuSliderJoyDeadZone").Init("$JOYMNU_DEADZONE", i, 0, 0.9, 0.05, 3, joy); opt.mItems.Push(it); + it = new("OptionMenuSliderJoyDigitalThreshold").Init("$JOYMNU_THRESHOLD", i, 0, 0.9, 0.05, 3, joy); + opt.mItems.Push(it); } } else diff --git a/wadsrc/static/zscript/engine/ui/menu/menu.zs b/wadsrc/static/zscript/engine/ui/menu/menu.zs index 7a0ef2036..ed98a97e9 100644 --- a/wadsrc/static/zscript/engine/ui/menu/menu.zs +++ b/wadsrc/static/zscript/engine/ui/menu/menu.zs @@ -68,6 +68,16 @@ struct JoystickConfig native version("2.4") NUM_JOYAXIS, }; + enum EJoyCurve { + JOYCURVE_CUSTOM = -1, + JOYCURVE_DEFAULT, + JOYCURVE_LINEAR, + JOYCURVE_QUADRATIC, + JOYCURVE_CUBIC, + + NUM_JOYCURVE + }; + native float GetSensitivity(); native void SetSensitivity(float scale); @@ -77,6 +87,15 @@ struct JoystickConfig native version("2.4") native float GetAxisDeadZone(int axis); native void SetAxisDeadZone(int axis, float zone); + native float GetAxisDigitalThreshold(int axis); + native void SetAxisDigitalThreshold(int axis, float thresh); + + native int GetAxisResponseCurve(int axis); + native void SetAxisResponseCurve(int axis, int preset); + + native float GetAxisResponseCurvePoint(int axis, int point); + native void SetAxisResponseCurvePoint(int axis, int point, float value); + native int GetAxisMap(int axis); native void SetAxisMap(int axis, int gameaxis);