diff --git a/src/common/engine/m_joy.cpp b/src/common/engine/m_joy.cpp index 0fce7d994..9f1c3cae5 100644 --- a/src/common/engine/m_joy.cpp +++ b/src/common/engine/m_joy.cpp @@ -33,6 +33,7 @@ // HEADER FILES ------------------------------------------------------------ #include +#include "c_dispatch.h" #include "vectors.h" #include "m_joy.h" #include "configfile.h" @@ -163,6 +164,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 +270,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 +313,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(); +} //=========================================================================== // diff --git a/src/common/menu/joystickmenu.cpp b/src/common/menu/joystickmenu.cpp index 3d3e881b1..e3bb8544b 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_FLOAT(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/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..425b7dd67 100644 --- a/wadsrc/static/zscript/engine/ui/menu/joystickmenu.zs +++ b/wadsrc/static/zscript/engine/ui/menu/joystickmenu.zs @@ -100,6 +100,50 @@ class OptionMenuSliderJoyScale : OptionMenuSliderBase // //============================================================================= +class OptionMenuSliderJoyCurve : OptionMenuSliderBase +{ + int mAxis; + int mNeg; + int mPoint; + bool mShown; + JoystickConfig mJoy; + + OptionMenuSliderJoyCurve Init(String label, int axis, double min, double max, double step, int showval, JoystickConfig joy, int point) + { + Super.Init(label, min, max, step, showval); + mAxis = axis; + mNeg = 1; + mJoy = joy; + mPoint = point; + mShown = false; + return self; + } + + override bool Visible() + { + mShown |= mJoy.GetAxisResponseCurve(mAxis) == JoystickConfig.JOYCURVE_CUSTOM; + return mShown; + } + + override double GetSliderValue() + { + double d = mJoy.GetAxisResponseCurvePoint(mAxis, mPoint); + mNeg = d < 0? -1:1; + return d; + } + + override void SetSliderValue(double val) + { + mJoy.SetAxisResponseCurvePoint(mAxis, mPoint, val * mNeg); + } +} + +//============================================================================= +// +// +// +//============================================================================= + class OptionMenuSliderJoyDeadZone : OptionMenuSliderBase { int mAxis; @@ -130,7 +174,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 +471,20 @@ 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); + it = new("OptionMenuSliderJoyCurve").Init("$JOYMNU_CURVE_X1", i, 0, 0.9, 0.05, 3, joy, 0); + opt.mItems.Push(it); + it = new("OptionMenuSliderJoyCurve").Init("$JOYMNU_CURVE_Y1", i, 0, 0.9, 0.05, 3, joy, 1); + opt.mItems.Push(it); + it = new("OptionMenuSliderJoyCurve").Init("$JOYMNU_CURVE_X2", i, 0, 0.9, 0.05, 3, joy, 2); + opt.mItems.Push(it); + it = new("OptionMenuSliderJoyCurve").Init("$JOYMNU_CURVE_Y2", i, 0, 0.9, 0.05, 3, joy, 3); + 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);