From 9c840724f24a4671ae8f51182a1924ae6cc099ae Mon Sep 17 00:00:00 2001 From: Lukas Werling Date: Sat, 13 Feb 2016 18:42:23 +0100 Subject: [PATCH 01/17] Re-enable gamepad controls --- src/control/C4PlayerControl.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/control/C4PlayerControl.cpp b/src/control/C4PlayerControl.cpp index 8392c0cdc..9103adc53 100644 --- a/src/control/C4PlayerControl.cpp +++ b/src/control/C4PlayerControl.cpp @@ -734,21 +734,6 @@ void C4PlayerControlAssignmentSets::CompileFunc(StdCompiler *pComp) } pComp->Value(mkNamingAdapt(clear_previous, "ClearPrevious", false)); pComp->Value(mkSTLContainerAdapt(Sets, StdCompiler::SEP_NONE)); - - // Remove all sets that have gamepad controls, since gamepad - // support is broken at the moment. Disable this once we have gamepad - // support again! - if (pComp->isCompiler()) - { - AssignmentSetList::iterator iter = Sets.begin(); - for (AssignmentSetList::iterator iter = Sets.begin(); iter != Sets.end(); ) - { - if (iter->HasGamepad()) - iter = Sets.erase(iter); - else - ++iter; - } - } } bool C4PlayerControlAssignmentSets::operator ==(const C4PlayerControlAssignmentSets &cmp) const From bd3f0200686de176092945250000f73c35689ab6 Mon Sep 17 00:00:00 2001 From: Lukas Werling Date: Sat, 13 Feb 2016 19:41:35 +0100 Subject: [PATCH 02/17] Replace Joystick controls with SDL GameController --- CMakeLists.txt | 4 +- src/platform/C4AppSDL.cpp | 8 +- src/platform/C4GamePadCon.cpp | 255 ++++++---------------------------- src/platform/C4GamePadCon.h | 35 +---- src/platform/StdJoystick.cpp | 135 ------------------ src/platform/StdJoystick.h | 56 -------- 6 files changed, 50 insertions(+), 443 deletions(-) delete mode 100644 src/platform/StdJoystick.cpp delete mode 100644 src/platform/StdJoystick.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d0db8741f..f9add2b76 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -305,7 +305,7 @@ endif() # SDL if(USE_SDL_MAINLOOP) find_package(SDL2 REQUIRED) -elseif(NOT WIN32) +else() # for gamepads find_package(SDL2) endif() @@ -911,8 +911,6 @@ elseif(USE_WIN32_WINDOWS) list(APPEND OC_GUI_SOURCES src/editor/C4ConsoleWin32.cpp src/platform/C4WindowWin32.cpp - src/platform/StdJoystick.cpp - src/platform/StdJoystick.h ) elseif(USE_COCOA) list(APPEND OC_GUI_SOURCES diff --git a/src/platform/C4AppSDL.cpp b/src/platform/C4AppSDL.cpp index d16c11c8c..fd4ea7786 100644 --- a/src/platform/C4AppSDL.cpp +++ b/src/platform/C4AppSDL.cpp @@ -162,11 +162,9 @@ void C4AbstractApp::HandleSDLEvent(SDL_Event& e) SDL_GetMouseState(&x, &y); C4GUI::MouseMove(C4MC_Button_Wheel, x, y, flags, NULL); break; - case SDL_JOYAXISMOTION: - case SDL_JOYHATMOTION: - case SDL_JOYBALLMOTION: - case SDL_JOYBUTTONDOWN: - case SDL_JOYBUTTONUP: + case SDL_CONTROLLERAXISMOTION: + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: Application.pGamePadControl->FeedEvent(e); break; } diff --git a/src/platform/C4GamePadCon.cpp b/src/platform/C4GamePadCon.cpp index 6b9ac054f..a44c6559e 100644 --- a/src/platform/C4GamePadCon.cpp +++ b/src/platform/C4GamePadCon.cpp @@ -32,148 +32,7 @@ void C4GamePadControl::DoAxisInput() Execute(true); } -#ifdef USE_WIN32_WINDOWS - -C4GamePadControl *C4GamePadControl::pInstance = NULL; - -C4GamePadControl::C4GamePadControl() -{ - for (int i=0; iSetCalibration(&(Config.Gamepads[id].AxisMin[0]), &(Config.Gamepads[id].AxisMax[0]), &(Config.Gamepads[id].AxisCalibrated[0])); - ++iNumGamepads; - } -} - -void C4GamePadControl::CloseGamepad(int id) -{ - if (!Inside(id, 0, CStdGamepad_MaxGamePad-1)) return; - // del gamepad ref - if (!--Gamepads[id].iRefCount) - { - Gamepads[id].pGamepad->GetCalibration(&(Config.Gamepads[id].AxisMin[0]), &(Config.Gamepads[id].AxisMax[0]), &(Config.Gamepads[id].AxisCalibrated[0])); - delete Gamepads[id].pGamepad; Gamepads[id].pGamepad = NULL; - --iNumGamepads; - } -} - -int C4GamePadControl::GetGamePadCount() -{ - JOYINFOEX joy; - ZeroMem(&joy, sizeof(JOYINFOEX)); joy.dwSize = sizeof(JOYINFOEX); joy.dwFlags = JOY_RETURNALL; - int iCnt=0; - while (iCntUpdate()) continue; - for (int iAxis = 0; iAxis < CStdGamepad_MaxAxis; ++iAxis) - { - int32_t iStrength = 100; - CStdGamePad::AxisPos eAxisPos = rPad.pGamepad->GetAxisPos(iAxis, &iStrength), ePrevAxisPos = rPad.AxisPosis[iAxis]; - int32_t iPrevStrength = rPad.AxisStrengths[iAxis]; - // Evaluate changes and pass single controls - // this is a generic Gamepad-control: Create events - if (eAxisPos != ePrevAxisPos || (send_axis_strength_changes && Abs(iPrevStrength-iStrength) > AxisStrengthChangeThreshold)) - { - rPad.AxisPosis[iAxis] = eAxisPos; - rPad.AxisStrengths[iAxis] = iStrength; - if (ePrevAxisPos != CStdGamePad::Mid && eAxisPos != ePrevAxisPos) - Game.DoKeyboardInput(KEY_Gamepad(idGamepad, KEY_JOY_Axis(iAxis, (ePrevAxisPos==CStdGamePad::High))), KEYEV_Up, false, false, false, false); - // it's tempting to send fRepeated here for eAxisPos == ePrevAxisPos, but it would cause the key to be ignored for sync controls - // might improve the check in sync controls so they accept repeated keys if strength is updated? - if (eAxisPos != CStdGamePad::Mid) - Game.DoKeyboardInput(KEY_Gamepad(idGamepad, KEY_JOY_Axis(iAxis, (eAxisPos==CStdGamePad::High))), KEYEV_Down, false, false, false, false, NULL, false, iStrength); - } - } - uint32_t Buttons = rPad.pGamepad->GetButtons(); - uint32_t PrevButtons = rPad.Buttons; - if (Buttons != PrevButtons) - { - rPad.Buttons = Buttons; - for (int iButton = 0; iButton < MaxGamePadButton; ++iButton) - if ((Buttons & (1 << iButton)) != (PrevButtons & (1 << iButton))) - { - bool fRelease = ((Buttons & (1 << iButton)) == 0); - Game.DoKeyboardInput(KEY_Gamepad(idGamepad, KEY_JOY_Button(iButton)), fRelease ? KEYEV_Up : KEYEV_Down, false, false, false, false); - } - } - } -} - -bool C4GamePadControl::AnyButtonDown() -{ - return false; -} - -C4GamePadOpener::C4GamePadOpener(int iGamepad) -{ - assert(C4GamePadControl::pInstance); - this->iGamePad = iGamepad; - C4GamePadControl::pInstance->OpenGamepad(iGamePad); -} - -C4GamePadOpener::~C4GamePadOpener() -{ - if (C4GamePadControl::pInstance) - C4GamePadControl::pInstance->CloseGamepad(iGamePad); -} - -void C4GamePadOpener::SetGamePad(int iNewGamePad) -{ - if (iNewGamePad == iGamePad) return; - assert(C4GamePadControl::pInstance); - C4GamePadControl::pInstance->CloseGamepad(iGamePad); - C4GamePadControl::pInstance->OpenGamepad(iGamePad = iNewGamePad); -} - -#elif defined(HAVE_SDL) && !defined(USE_CONSOLE) +#if defined(HAVE_SDL) && !defined(USE_CONSOLE) #include @@ -184,16 +43,15 @@ bool C4GamePadControl::AnyButtonDown() C4GamePadControl::C4GamePadControl() { - // FIXME: Port to SDL_INIT_GAMECONTROLLER - if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_EVENTS) != 0) - LogF("SDL_InitSubSystem(SDL_INIT_JOYSTICK): %s", SDL_GetError()); - SDL_JoystickEventState(SDL_ENABLE); - if (!SDL_NumJoysticks()) Log("No Gamepad found"); + if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_EVENTS) != 0) + LogF("SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER): %s", SDL_GetError()); + SDL_GameControllerEventState(SDL_ENABLE); + if (!GetGamePadCount()) Log("No Gamepad found"); } C4GamePadControl::~C4GamePadControl() { - SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_EVENTS); + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_EVENTS); } void C4GamePadControl::Execute(bool) @@ -204,11 +62,9 @@ void C4GamePadControl::Execute(bool) { switch (event.type) { - case SDL_JOYAXISMOTION: - case SDL_JOYBALLMOTION: - case SDL_JOYHATMOTION: - case SDL_JOYBUTTONDOWN: - case SDL_JOYBUTTONUP: + case SDL_CONTROLLERAXISMOTION: + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: FeedEvent(event); break; } @@ -234,55 +90,18 @@ void C4GamePadControl::FeedEvent(SDL_Event& event) { switch (event.type) { - case SDL_JOYHATMOTION: + case SDL_CONTROLLERAXISMOTION: { - SDL_Event fakeX; - fakeX.jaxis.type = SDL_JOYAXISMOTION; - fakeX.jaxis.which = event.jhat.which; - fakeX.jaxis.axis = event.jhat.hat * 2 + 6; /* *magic*number* */ - fakeX.jaxis.value = 0; - SDL_Event fakeY = fakeX; - fakeY.jaxis.axis += 1; - switch (event.jhat.value) - { - case SDL_HAT_LEFTUP: fakeX.jaxis.value = amplify(-1); fakeY.jaxis.value = amplify(-1); break; - case SDL_HAT_LEFT: fakeX.jaxis.value = amplify(-1); break; - case SDL_HAT_LEFTDOWN: fakeX.jaxis.value = amplify(-1); fakeY.jaxis.value = amplify(+1); break; - case SDL_HAT_UP: fakeY.jaxis.value = amplify(-1); break; - case SDL_HAT_DOWN: fakeY.jaxis.value = amplify(+1); break; - case SDL_HAT_RIGHTUP: fakeX.jaxis.value = amplify(+1); fakeY.jaxis.value = amplify(-1); break; - case SDL_HAT_RIGHT: fakeX.jaxis.value = amplify(+1); break; - case SDL_HAT_RIGHTDOWN: fakeX.jaxis.value = amplify(+1); fakeY.jaxis.value = amplify(+1); break; - } - FeedEvent(fakeX); - FeedEvent(fakeY); - return; - } - case SDL_JOYBALLMOTION: - { - SDL_Event fake; - fake.jaxis.type = SDL_JOYAXISMOTION; - fake.jaxis.which = event.jball.which; - fake.jaxis.axis = event.jball.ball * 2 + 12; /* *magic*number* */ - fake.jaxis.value = amplify(event.jball.xrel); - FeedEvent(event); - fake.jaxis.axis += 1; - fake.jaxis.value = amplify(event.jball.yrel); - FeedEvent(event); - return; - } - case SDL_JOYAXISMOTION: - { - C4KeyCode minCode = KEY_Gamepad(event.jaxis.which, KEY_JOY_Axis(event.jaxis.axis, false)); - C4KeyCode maxCode = KEY_Gamepad(event.jaxis.which, KEY_JOY_Axis(event.jaxis.axis, true)); + C4KeyCode minCode = KEY_Gamepad(event.caxis.which, KEY_JOY_Axis(event.caxis.axis, false)); + C4KeyCode maxCode = KEY_Gamepad(event.caxis.which, KEY_JOY_Axis(event.caxis.axis, true)); // FIXME: This assumes that the axis really rests around (0, 0) if it is not used, which is not always true. - if (event.jaxis.value < -deadZone) + if (event.caxis.value < -deadZone) { if (PressedAxis.count(minCode) == 0) { Game.DoKeyboardInput( - KEY_Gamepad(event.jaxis.which, minCode), + KEY_Gamepad(event.caxis.which, minCode), KEYEV_Down, false, false, false, false); PressedAxis.insert(minCode); } @@ -292,17 +111,17 @@ void C4GamePadControl::FeedEvent(SDL_Event& event) if (PressedAxis.count(minCode) != 0) { Game.DoKeyboardInput( - KEY_Gamepad(event.jaxis.which, minCode), + KEY_Gamepad(event.caxis.which, minCode), KEYEV_Up, false, false, false, false); PressedAxis.erase(minCode); } } - if (event.jaxis.value > +deadZone) + if (event.caxis.value > +deadZone) { if (PressedAxis.count(maxCode) == 0) { Game.DoKeyboardInput( - KEY_Gamepad(event.jaxis.which, maxCode), + KEY_Gamepad(event.caxis.which, maxCode), KEYEV_Down, false, false, false, false); PressedAxis.insert(maxCode); } @@ -312,21 +131,21 @@ void C4GamePadControl::FeedEvent(SDL_Event& event) if (PressedAxis.count(maxCode) != 0) { Game.DoKeyboardInput( - KEY_Gamepad(event.jaxis.which, maxCode), + KEY_Gamepad(event.caxis.which, maxCode), KEYEV_Up, false, false, false, false); PressedAxis.erase(maxCode); } } break; } - case SDL_JOYBUTTONDOWN: + case SDL_CONTROLLERBUTTONDOWN: Game.DoKeyboardInput( - KEY_Gamepad(event.jbutton.which, KEY_JOY_Button(event.jbutton.button)), + KEY_Gamepad(event.cbutton.which, KEY_JOY_Button(event.cbutton.button)), KEYEV_Down, false, false, false, false); break; - case SDL_JOYBUTTONUP: + case SDL_CONTROLLERBUTTONUP: Game.DoKeyboardInput( - KEY_Gamepad(event.jbutton.which, KEY_JOY_Button(event.jbutton.button)), + KEY_Gamepad(event.cbutton.which, KEY_JOY_Button(event.cbutton.button)), KEYEV_Up, false, false, false, false); break; } @@ -334,27 +153,37 @@ void C4GamePadControl::FeedEvent(SDL_Event& event) int C4GamePadControl::GetGamePadCount() { - return(SDL_NumJoysticks()); + // Not all Joysticks are game controllers. + int count = 0; + for (int i = 0; i < SDL_NumJoysticks(); i++) + if (SDL_IsGameController(i)) + count++; + return count; } C4GamePadOpener::C4GamePadOpener(int iGamepad) { - Joy = SDL_JoystickOpen(iGamepad); - if (!Joy) LogF("SDL: %s", SDL_GetError()); + int n = iGamepad; + for (int i = 0; i < SDL_NumJoysticks(); i++) + if (SDL_IsGameController(i) && n-- == 0) + { + controller = SDL_GameControllerOpen(i); + if (!controller) LogF("SDL: %s", SDL_GetError()); + break; + } + + if (!controller) LogF("Gamepad %d not available", iGamepad); } C4GamePadOpener::~C4GamePadOpener() { - if (Joy) SDL_JoystickClose(Joy); + if (controller) SDL_GameControllerClose(controller); } void C4GamePadOpener::SetGamePad(int iGamepad) { - if (Joy) - SDL_JoystickClose(Joy); - Joy = SDL_JoystickOpen(iGamepad); - if (!Joy) - LogF("SDL: %s", SDL_GetError()); + // TODO: why do we need this? + LogF("SetGamePad: Not implemented yet"); } #else @@ -371,4 +200,4 @@ C4GamePadOpener::C4GamePadOpener(int iGamepad) { } C4GamePadOpener::~C4GamePadOpener() {} void C4GamePadOpener::SetGamePad(int iGamepad) { } -#endif //_WIN32 +#endif diff --git a/src/platform/C4GamePadCon.h b/src/platform/C4GamePadCon.h index 44243e393..e492b7341 100644 --- a/src/platform/C4GamePadCon.h +++ b/src/platform/C4GamePadCon.h @@ -20,43 +20,20 @@ #ifndef INC_C4GamePadCon #define INC_C4GamePadCon -#ifdef USE_WIN32_WINDOWS -#include -#endif - #ifdef HAVE_SDL #include #include #endif -struct _SDL_Joystick; -typedef struct _SDL_Joystick SDL_Joystick; +struct _SDL_GameController; +typedef struct _SDL_GameController SDL_GameController; union SDL_Event; typedef union SDL_Event SDL_Event; class C4GamePadControl { -#ifdef USE_WIN32_WINDOWS -private: - struct Pad - { - CStdGamePad *pGamepad; - int iRefCount; - uint32_t Buttons; - CStdGamePad::AxisPos AxisPosis[CStdGamepad_MaxAxis]; - int32_t AxisStrengths[CStdGamepad_MaxAxis]; - }; - Pad Gamepads[CStdGamepad_MaxGamePad]; - int iNumGamepads; - - enum { AxisStrengthChangeThreshold = 2 }; // if axis strength change > this value, a new control is issued - -public: - void OpenGamepad(int id); // add gamepad ref - void CloseGamepad(int id); // del gamepad ref - static C4GamePadControl *pInstance; // singleton -#elif defined(HAVE_SDL) +#ifdef HAVE_SDL public: void FeedEvent(SDL_Event& e); private: @@ -74,16 +51,12 @@ public: class C4GamePadOpener { -#ifdef USE_WIN32_WINDOWS - int iGamePad; - int GetGamePadIndex() const { return iGamePad; } -#endif public: C4GamePadOpener(int iGamePad); ~C4GamePadOpener(); void SetGamePad(int iNewGamePad); #ifdef HAVE_SDL - SDL_Joystick *Joy; + SDL_GameController *controller; #endif }; diff --git a/src/platform/StdJoystick.cpp b/src/platform/StdJoystick.cpp deleted file mode 100644 index 80abe3fd7..000000000 --- a/src/platform/StdJoystick.cpp +++ /dev/null @@ -1,135 +0,0 @@ -/* - * OpenClonk, http://www.openclonk.org - * - * Copyright (c) 1998-2000, Matthes Bender - * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ - * Copyright (c) 2010-2013, The OpenClonk Team and contributors - * - * Distributed under the terms of the ISC license; see accompanying file - * "COPYING" for details. - * - * "Clonk" is a registered trademark of Matthes Bender, used with permission. - * See accompanying file "TRADEMARK" for details. - * - * To redistribute this file separately, substitute the full license texts - * for the above references. - */ - -/* Simple joystick handling with DirectInput 1 */ - -#include "C4Include.h" -#include - -#include -#include - -uint32_t POV2Position(DWORD dwPOV, bool fVertical) -{ - // POV value is a 360° angle multiplied by 100 - double dAxis; - // Centered - if (dwPOV == JOY_POVCENTERED) - dAxis = 0.0; - // Angle: convert to linear value -100 to +100 - else - dAxis = (fVertical ? -cos((dwPOV/100) * M_PI / 180.0) : sin((dwPOV/100) * M_PI / 180.0)) * 100.0; - // Gamepad configuration wants unsigned and gets 0 to 200 - return (uint32_t) (dAxis + 100.0); -} - -CStdGamePad::CStdGamePad(int id) : id(id) -{ - ResetCalibration(); -} - -void CStdGamePad::ResetCalibration() -{ - // no calibration yet - for (int i=0; i=CStdGamepad_MaxAxis) return Mid; // wrong axis - // get raw axis data - if (idAxis dwCenter + dwThresh) - { - if (out_strength && dwRange) *out_strength = (dwPos-dwCenter)*100/dwRange; - return High; - } - } - else - { - // init it - dwAxisMin[idAxis] = dwAxisMax[idAxis] = dwPos; - fAxisCalibrated[idAxis] = true; - } - } - else - { - // It's a POV head - DWORD dwPos = POV2Position(joynfo.dwPOV, idAxis==PAD_Axis_POVy); - if (out_strength) *out_strength = Abs(int32_t(dwPos) - 100); - if (dwPos > 130) return High; else if (dwPos < 70) return Low; - } - return Mid; -} - diff --git a/src/platform/StdJoystick.h b/src/platform/StdJoystick.h deleted file mode 100644 index a16a99451..000000000 --- a/src/platform/StdJoystick.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * OpenClonk, http://www.openclonk.org - * - * Copyright (c) 1998-2000, Matthes Bender - * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ - * Copyright (c) 2009-2013, The OpenClonk Team and contributors - * - * Distributed under the terms of the ISC license; see accompanying file - * "COPYING" for details. - * - * "Clonk" is a registered trademark of Matthes Bender, used with permission. - * See accompanying file "TRADEMARK" for details. - * - * To redistribute this file separately, substitute the full license texts - * for the above references. - */ - -/* Simple joystick handling with DirectInput 1 */ - -#ifndef INC_StdJoystick -#define INC_StdJoystick - -#include -#include - -const int32_t PAD_Axis_POVx = 6; -const int32_t PAD_Axis_POVy = 7; // virtual axises of the coolie hat - -const int CStdGamepad_MaxGamePad = 15, // maximum number of supported gamepads - CStdGamepad_MaxCalAxis = 6, // maximum number of calibrated axises - CStdGamepad_MaxAxis = 8; // number of axises plus coolie hat axises - -class CStdGamePad -{ -public: - enum AxisPos { Low, Mid, High, }; // quantized axis positions -private: - int id; // gamepad number - JOYINFOEX joynfo; // WIN32 gamepad info - -public: - uint32_t dwAxisMin[CStdGamepad_MaxCalAxis], dwAxisMax[CStdGamepad_MaxCalAxis]; // axis ranges - auto calibrated - bool fAxisCalibrated[CStdGamepad_MaxCalAxis]; // set if an initial value for axis borders has been determined already - - CStdGamePad(int id); // ctor - - void ResetCalibration(); // resets axis min and max - void SetCalibration(uint32_t *pdwAxisMin, uint32_t *pdwAxisMax, bool *pfAxisCalibrated); - void GetCalibration(uint32_t *pdwAxisMin, uint32_t *pdwAxisMax, bool *pfAxisCalibrated); - - bool Update(); // read current gamepad data - uint32_t GetButtons(); // returns bitmask of pressed buttons for last retrieved info - AxisPos GetAxisPos(int idAxis, int32_t *out_strength=NULL); // return axis extension - mid for error or center position -}; - -#endif From 937ddaf7221459f606a648354d9d8efd76d0cf2a Mon Sep 17 00:00:00 2001 From: Lukas Werling Date: Sat, 13 Feb 2016 23:28:00 +0100 Subject: [PATCH 03/17] Update key code strings for game controllers With the SDL_GameController interface, buttons and axes have actual names we can refer to. This also allows for advanced mappings using both sticks (this probably needs script changes) as well as the triggers. --- planet/System.ocg/PlayerControls.txt | 84 +++++++++++----- src/gui/C4KeyboardInput.cpp | 138 ++++++++++++++++----------- 2 files changed, 140 insertions(+), 82 deletions(-) diff --git a/planet/System.ocg/PlayerControls.txt b/planet/System.ocg/PlayerControls.txt index 264914742..dc18ec884 100644 --- a/planet/System.ocg/PlayerControls.txt +++ b/planet/System.ocg/PlayerControls.txt @@ -836,31 +836,41 @@ # Contents Menu [Assignment] - Key=Joy1F + Key=Controller1b GUIGroup=50 Control=Contents + [Assignment] + Key=Controller1leftshoulder + GUIGroup=50 + Control=InventoryShiftBackward + + [Assignment] + Key=Controller1rightshoulder + GUIGroup=50 + Control=InventoryShiftForward + # Menu [Assignment] - Key=Joy1J + Key=Controller1y GUIGroup=50 Control=PlayerMenu [Assignment] - Key=Joy1C + Key=Controller1a GUIGroup=50 Control=MenuOK [Assignment] - Key=Joy1B + Key=Controller1b GUIGroup=50 Control=MenuCancel # Movement [Assignment] - Key=Joy1Left + Key=Controller1AxisleftxMin Priority=50 GUIName=$KEY_Left$ GUIDesc=$KEY_Left_Desc$ @@ -868,7 +878,7 @@ Control=Left [Assignment] - Key=Joy1Right + Key=Controller1AxisleftxMax Priority=50 GUIName=$KEY_Right$ GUIDesc=$KEY_Right_Desc$ @@ -876,7 +886,7 @@ Control=Right [Assignment] - Key=Joy1Down + Key=Controller1AxisleftyMax Priority=50 GUIName=$KEY_Down$ GUIDesc=$KEY_Down_Desc$ @@ -884,7 +894,7 @@ Control=Down [Assignment] - Key=Joy1Up + Key=Controller1AxisleftyMin Priority=50 GUIName=$KEY_Up$ GUIDesc=$KEY_Up_Desc$ @@ -892,34 +902,40 @@ Control=Up [Assignment] - Key=Joy1C + Key=Controller1rightstick Priority=10 GUIGroup=10 Control=Jump [Assignment] - Key=Joy1Axis1Min + Key=Controller1dpdown + Priority=10 + GUIGroup=10 + Control=FallThrough + + [Assignment] + Key=Controller1AxisrightxMin GUIDesc=$KEY_AimAxis_Desc$ Priority=80 GUIGroup=30 Control=AimAxisLeft [Assignment] - Key=Joy1Axis1Max + Key=Controller1AxisrightxMax GUIDesc=$KEY_AimAxis_Desc$ Priority=80 GUIGroup=30 Control=AimAxisRight [Assignment] - Key=Joy1Axis2Max + Key=Controller1AxisrightyMax GUIDesc=$KEY_AimAxis_Desc$ Priority=80 GUIGroup=30 Control=AimAxisDown [Assignment] - Key=Joy1Axis2Min + Key=Controller1AxisrightyMin GUIDesc=$KEY_AimAxis_Desc$ Priority=80 GUIGroup=30 @@ -928,41 +944,57 @@ # Object interaction [Assignment] - Key=Joy1B + Key=Controller1a Priority=35 GUIGroup=40 Control=Interact + [Assignment] + Key=Controller1x + Control=PickUp + GUIGroup=50 + # Crew [Assignment] - Key=Joy1I + Key=Controller1dpright Control=NextCrew GUIGroup=70 + [Assignment] + Key=Controller1dpleft + Control=PreviousCrew + GUIGroup=70 + # Use, Throw, Drop [Assignment] - Key=Joy1A + Key=Controller1AxisrighttriggerMax GUIName=$KEY_GamepadUse$ GUIDesc=$KEY_GamepadUse_Desc$ GUIGroup=20 Priority=100 Control=UseDelayed - # Zoom - [Assignment] - Key=Joy1H + Key=Controller1AxislefttriggerMax + GUIGroup=20 Priority=100 - GUIGroup=60 - Control=ZoomIn + Control=ThrowDelayed - [Assignment] - Key=Joy1G - Priority=100 - GUIGroup=60 - Control=ZoomOut + # TODO: Zoom + + #[Assignment] + #Key=Joy1H + #Priority=100 + #GUIGroup=60 + #Control=ZoomIn + + #[Assignment] + #Key=Joy1G + #Priority=100 + #GUIGroup=60 + #Control=ZoomOut diff --git a/src/gui/C4KeyboardInput.cpp b/src/gui/C4KeyboardInput.cpp index 7bc9f3af3..5fccfde38 100644 --- a/src/gui/C4KeyboardInput.cpp +++ b/src/gui/C4KeyboardInput.cpp @@ -31,8 +31,10 @@ #endif #include +#include +#include -#ifdef USE_SDL_MAINLOOP +#ifdef HAVE_SDL #include #endif @@ -215,47 +217,32 @@ C4KeyCode C4KeyCodeEx::String2KeyCode(const StdStrBuf &sName) // scan code if (*sName.getData() == '$') return GetKeyByScanCode(sName.getData()); // direct gamepad code -#ifdef _WIN32 - if (!strnicmp(sName.getData(), "Joy", 3)) -#else - if (!strncasecmp(sName.getData(), "Joy", 3)) -#endif + std::regex controller_re(R"/(^Controller(\d+)(Axis)?([a-z]+)(Min|Max)?$)/"); + std::cmatch matches; + if (std::regex_match(sName.getData(), matches, controller_re)) { - int iGamepad; - if (sscanf(sName.getData(), "Joy%d", &iGamepad) == 1) +#ifdef HAVE_SDL + int iGamepad = std::stoi(matches[1]); + if (matches[2] == "Axis") { - // skip Joy[number] - const char *key_str = sName.getData()+4; - while (isdigit(*key_str)) ++key_str; - // check for button (single, uppercase letter) (e.g. Joy1A) - if (*key_str && !key_str[1]) + int axis = SDL_GameControllerGetAxisFromString(matches[3].str().c_str()); + if (axis != SDL_CONTROLLER_AXIS_INVALID) { - char cGamepadButton = toupper(*key_str); - if (Inside(cGamepadButton, 'A', 'Z')) - { - cGamepadButton = cGamepadButton - 'A'; - return KEY_Gamepad(iGamepad-1, KEY_JOY_Button(cGamepadButton)); - } - } - else - { - // check for standard axis (e.g. Joy1Left) - if (!stricmp(key_str, "Left")) return KEY_Gamepad(iGamepad-1, KEY_JOY_Left); - if (!stricmp(key_str, "Up")) return KEY_Gamepad(iGamepad-1, KEY_JOY_Up); - if (!stricmp(key_str, "Down")) return KEY_Gamepad(iGamepad-1, KEY_JOY_Down); - if (!stricmp(key_str, "Right")) return KEY_Gamepad(iGamepad-1, KEY_JOY_Right); - // check for specific axis (e.g. Joy1Axis1Min) - int iAxis; - if (sscanf(key_str, "Axis%d", &iAxis) == 1 && iAxis>0) - { - --iAxis; // axis is 0-based internally but written 1-based in config - key_str += 5; - while (isdigit(*key_str)) ++key_str; - if (!stricmp(key_str, "Min")) return KEY_Gamepad(iGamepad-1, KEY_JOY_Axis(iAxis, false)); - if (!stricmp(key_str, "Max")) return KEY_Gamepad(iGamepad-1, KEY_JOY_Axis(iAxis, true)); - } + if (matches[4] == "Min") return KEY_Gamepad(iGamepad-1, KEY_JOY_Axis(axis, false)); + if (matches[4] == "Max") return KEY_Gamepad(iGamepad-1, KEY_JOY_Axis(axis, true)); } } + else + { + int gamepad_button = SDL_GameControllerGetButtonFromString(matches[3].str().c_str()); + if (gamepad_button != SDL_CONTROLLER_BUTTON_INVALID) + { + return KEY_Gamepad(iGamepad-1, KEY_JOY_Button(gamepad_button)); + } + } +#else + return KEY_Undefined; +#endif } bool is_mouse_key; #ifdef _WIN32 @@ -327,30 +314,69 @@ StdStrBuf C4KeyCodeEx::KeyCode2String(C4KeyCode wCode, bool fHumanReadable, bool if (Key_IsGamepad(wCode)) { int iGamepad = Key_GetGamepad(wCode); - int gamepad_event = Key_GetGamepadEvent(wCode); - switch (gamepad_event) + if (Key_IsGamepadAxis(wCode)) { - case KEY_JOY_Left: return FormatString("Joy%dLeft", iGamepad+1); - case KEY_JOY_Up: return FormatString("Joy%dUp", iGamepad+1); - case KEY_JOY_Down: return FormatString("Joy%dDown", iGamepad+1); - case KEY_JOY_Right: return FormatString("Joy%dRight", iGamepad+1); - default: - if (Key_IsGamepadAxis(wCode)) + int index = Key_GetGamepadAxisIndex(wCode); + const char *axis = "Unknown"; + if (fHumanReadable) { - if (fHumanReadable) - // This is still not great, but it is not really possible to assign unknown axes to "left/right" "up/down"... - return FormatString("[%d] %s", int(1 + Key_GetGamepadAxisIndex(wCode)), Key_IsGamepadAxisHigh(wCode) ? "Max" : "Min"); - else - return FormatString("Joy%dAxis%d%s", iGamepad+1, static_cast(Key_GetGamepadAxisIndex(wCode)+1), Key_IsGamepadAxisHigh(wCode) ? "Max" : "Min"); +#ifdef HAVE_SDL + switch (index) + { + case SDL_CONTROLLER_AXIS_LEFTX: axis = "Left Stick X"; break; + case SDL_CONTROLLER_AXIS_LEFTY: axis = "Left Stick Y"; break; + case SDL_CONTROLLER_AXIS_RIGHTX: axis = "Right Stick X"; break; + case SDL_CONTROLLER_AXIS_RIGHTY: axis = "Right Stick Y"; break; + case SDL_CONTROLLER_AXIS_TRIGGERLEFT: axis = "Left Trigger"; break; + case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: axis = "Right Trigger"; break; + } +#endif + // This is still not great, but it is not really possible to assign unknown axes to "left/right" "up/down"... + return FormatString("[%s] %s", axis, Key_IsGamepadAxisHigh(wCode) ? "Max" : "Min"); } else { - // button - if (fHumanReadable) - // If there should be gamepads around with A B C D... on the buttons, we might create a display option to show letters instead... - return FormatString("< %d >", int(1 + Key_GetGamepadButtonIndex(wCode))); - else - return FormatString("Joy%d%c", iGamepad+1, static_cast(Key_GetGamepadButtonIndex(wCode) + 'A')); +#ifdef HAVE_SDL + axis = SDL_GameControllerGetStringForAxis((SDL_GameControllerAxis) index); +#endif + return FormatString("Controller%dAxis%s%s", iGamepad+1, axis, Key_IsGamepadAxisHigh(wCode) ? "Max" : "Min"); + } + } + else + { + // button + const char *button = "Unknown"; + int index = Key_GetGamepadButtonIndex(wCode); + if (fHumanReadable) + { +#ifdef HAVE_SDL + switch (index) + { + case SDL_CONTROLLER_BUTTON_A: button = "A"; break; + case SDL_CONTROLLER_BUTTON_B: button = "B"; break; + case SDL_CONTROLLER_BUTTON_X: button = "X"; break; + case SDL_CONTROLLER_BUTTON_Y: button = "Y"; break; + case SDL_CONTROLLER_BUTTON_BACK: button = "Back"; break; + case SDL_CONTROLLER_BUTTON_GUIDE: button = "Guide"; break; + case SDL_CONTROLLER_BUTTON_START: button = "Start"; break; + case SDL_CONTROLLER_BUTTON_LEFTSTICK: button = "Left Stick Click"; break; + case SDL_CONTROLLER_BUTTON_RIGHTSTICK: button = "Right Stick Click"; break; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: button = "Left Shoulder"; break; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: button = "Right Shoulder"; break; + case SDL_CONTROLLER_BUTTON_DPAD_UP: button = "D-pad Up"; break; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: button = "D-pad Down"; break; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: button = "D-pad Left"; break; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: button = "D-pad Right"; break; + } +#endif + return FormatString("< %s >", button); + } + else + { +#ifdef HAVE_SDL + button = SDL_GameControllerGetStringForButton((SDL_GameControllerButton) index); +#endif + return FormatString("Controller%d%s", iGamepad+1, button); } } } From 8dd1450e94d52ccb4295d8c347f0214443db410b Mon Sep 17 00:00:00 2001 From: Lukas Werling Date: Mon, 15 Feb 2016 16:20:37 +0100 Subject: [PATCH 04/17] Implement "gamepad stick moved" events for analog input Analog moved events are only sent once per control frame. --- planet/System.ocg/PlayerControl.c | 8 ++- src/control/C4Control.cpp | 2 +- src/control/C4Control.h | 12 ++-- src/control/C4PlayerControl.cpp | 67 +++++++++++------- src/control/C4PlayerControl.h | 15 ++-- src/gamescript/C4GameScript.cpp | 6 ++ src/gui/C4KeyboardInput.h | 31 +++++---- src/platform/C4AppSDL.cpp | 2 +- src/platform/C4GamePadCon.cpp | 112 +++++++++++++----------------- src/platform/C4GamePadCon.h | 13 +++- 10 files changed, 147 insertions(+), 121 deletions(-) diff --git a/planet/System.ocg/PlayerControl.c b/planet/System.ocg/PlayerControl.c index 71cbc3450..9bcb16ec0 100644 --- a/planet/System.ocg/PlayerControl.c +++ b/planet/System.ocg/PlayerControl.c @@ -14,9 +14,11 @@ static g_player_cursor_pos; // array of [x,y] pos arrays; indexed by player. las // Called by engine whenever a control is issued // Forwards control to special handler or cursor // Return whether handled -global func PlayerControl(int plr, int ctrl, id spec_id, int x, int y, int strength, bool repeat, bool release) +global func PlayerControl(int plr, int ctrl, id spec_id, int x, int y, int strength, bool repeat, int status) { - //Log("%d, %s, %i, %d, %d, %d, %v, %v", plr, GetPlayerControlName(ctrl), spec_id, x,y,strength, repeat, release); + var release = status == CONS_Up; + //Log("%d, %s, %i, %d, %d, %d, %v, %v", plr, GetPlayerControlName(ctrl), spec_id, x,y,strength, repeat, status); + if (status == CONS_Moved) return false; // Control handled by definition? Forward if (spec_id) return spec_id->PlayerControl(plr, ctrl, x, y, strength, repeat, release); @@ -550,4 +552,4 @@ global func Library_ClonkInventoryControl_Sort_Priority(int x_position) var priority_x = GetX() - x_position; if (priority_x < 0) priority_x += 1000; return priority_x; -} \ No newline at end of file +} diff --git a/src/control/C4Control.cpp b/src/control/C4Control.cpp index edad7aa01..8adb93d05 100644 --- a/src/control/C4Control.cpp +++ b/src/control/C4Control.cpp @@ -423,7 +423,7 @@ void C4ControlPlayerControl::ControlItem::CompileFunc(StdCompiler *pComp) void C4ControlPlayerControl::CompileFunc(StdCompiler *pComp) { pComp->Value(mkNamingAdapt(mkIntPackAdapt(iPlr), "Player", -1)); - pComp->Value(mkNamingAdapt(fRelease, "Release", false)); + pComp->Value(mkNamingAdapt(mkIntAdapt(state), "State", 0)); pComp->Value(mkNamingAdapt(ExtraData, "ExtraData", C4KeyEventData())); pComp->Value(mkNamingAdapt(mkSTLContainerAdapt(ControlItems), "Controls", ControlItemVec())); C4ControlPacket::CompileFunc(pComp); diff --git a/src/control/C4Control.h b/src/control/C4Control.h index 8de70160c..9a358f75d 100644 --- a/src/control/C4Control.h +++ b/src/control/C4Control.h @@ -203,11 +203,11 @@ public: class C4ControlPlayerControl : public C4ControlPacket // sync { public: - C4ControlPlayerControl() : iPlr(-1), fRelease(false) {} - C4ControlPlayerControl(int32_t iPlr, bool fRelease, const C4KeyEventData &rExtraData) - : iPlr(iPlr), fRelease(fRelease), ExtraData(rExtraData) { } + C4ControlPlayerControl() : iPlr(-1), state(C4PlayerControl::CONS_Down) {} + C4ControlPlayerControl(int32_t iPlr, C4PlayerControl::ControlState state, const C4KeyEventData &rExtraData) + : iPlr(iPlr), state(state), ExtraData(rExtraData) { } C4ControlPlayerControl(int32_t iPlr, int32_t iControl, int32_t iExtraData) // old-style menu com emulation - : iPlr(iPlr), fRelease(false), ExtraData(iExtraData,0,0,0,0) { AddControl(iControl,0); } + : iPlr(iPlr), state(C4PlayerControl::CONS_Down), ExtraData(iExtraData,0,0,0,0) { AddControl(iControl,0); } struct ControlItem { @@ -221,7 +221,7 @@ public: typedef std::vector ControlItemVec; protected: int32_t iPlr; - bool fRelease; + int32_t state; C4KeyEventData ExtraData; ControlItemVec ControlItems; public: @@ -229,7 +229,7 @@ public: void AddControl(int32_t iControl, int32_t iTriggerMode) { ControlItems.push_back(ControlItem(iControl, iTriggerMode)); } const ControlItemVec &GetControlItems() const { return ControlItems; } - bool IsReleaseControl() const { return fRelease; } + C4PlayerControl::ControlState GetState() const { return static_cast(state); } const C4KeyEventData &GetExtraData() const { return ExtraData; } void SetExtraData(const C4KeyEventData &new_extra_data) { ExtraData = new_extra_data; } }; diff --git a/src/control/C4PlayerControl.cpp b/src/control/C4PlayerControl.cpp index 9103adc53..20df02927 100644 --- a/src/control/C4PlayerControl.cpp +++ b/src/control/C4PlayerControl.cpp @@ -994,12 +994,12 @@ void C4PlayerControl::CompileFunc(StdCompiler *pComp) pComp->Value(mkNamingAdapt(Sync, "PlayerControl", DefaultSync)); } -bool C4PlayerControl::ProcessKeyEvent(const C4KeyCodeEx &pressed_key, const C4KeyCodeEx &matched_key, bool fUp, const C4KeyEventData &rKeyExtraData, bool reset_down_states_only, bool *clear_recent_keys) +bool C4PlayerControl::ProcessKeyEvent(const C4KeyCodeEx &pressed_key, const C4KeyCodeEx &matched_key, ControlState state, const C4KeyEventData &rKeyExtraData, bool reset_down_states_only, bool *clear_recent_keys) { // collect all matching keys C4PlayerControlAssignmentPVec Matches; assert(pControlSet); // shouldn't get this callback for players without control set - pControlSet->GetAssignmentsByKey(ControlDefs, matched_key, fUp, &Matches, DownKeys, RecentKeys); + pControlSet->GetAssignmentsByKey(ControlDefs, matched_key, state != CONS_Down, &Matches, DownKeys, RecentKeys); // process async controls C4ControlPlayerControl *pControlPacket = NULL; for (C4PlayerControlAssignmentPVec::const_iterator i = Matches.begin(); i != Matches.end(); ++i) @@ -1008,7 +1008,7 @@ bool C4PlayerControl::ProcessKeyEvent(const C4KeyCodeEx &pressed_key, const C4Ke assert(pAssignment); int32_t iControlIndex = pAssignment->GetControl(); const C4PlayerControlDef *pControlDef = ControlDefs.GetControlByIndex(iControlIndex); - if (pControlDef && pControlDef->IsValid() && !Sync.IsControlDisabled(iControlIndex) && (!fUp || pControlDef->IsHoldKey())) + if (pControlDef && pControlDef->IsValid() && !Sync.IsControlDisabled(iControlIndex) && (state == CONS_Down || pControlDef->IsHoldKey())) { // clear RecentKeys if requested by this assignment. Must be done before sync queue, so multiple combos can be issued in a single control frame. if (clear_recent_keys && (pAssignment->GetTriggerMode() & C4PlayerControlAssignment::CTM_ClearRecentKeys)) *clear_recent_keys = true; @@ -1016,7 +1016,7 @@ bool C4PlayerControl::ProcessKeyEvent(const C4KeyCodeEx &pressed_key, const C4Ke if (pControlDef->IsAsync() && !pControlPacket) { if (pControlDef->IsSendCursorPos()) IsCursorPosRequested = true; // async cursor pos request - doesn't really make sense to set this flag for async controls - if (ExecuteControl(iControlIndex, fUp, rKeyExtraData, pAssignment->GetTriggerMode(), pressed_key.IsRepeated(), reset_down_states_only)) + if (ExecuteControl(iControlIndex, state, rKeyExtraData, pAssignment->GetTriggerMode(), pressed_key.IsRepeated(), reset_down_states_only)) return true; } else @@ -1026,7 +1026,7 @@ bool C4PlayerControl::ProcessKeyEvent(const C4KeyCodeEx &pressed_key, const C4Ke if (pressed_key.IsRepeated()) return false; // sync control has higher priority - no more async execution then // build a control packet and add control data instead. even for async controls later in chain, as they may be blocked by a sync handler - if (!pControlPacket) pControlPacket = new C4ControlPlayerControl(iPlr, fUp, rKeyExtraData); + if (!pControlPacket) pControlPacket = new C4ControlPlayerControl(iPlr, state, rKeyExtraData); int32_t extra_trigger_mode = 0; if (reset_down_states_only) extra_trigger_mode |= C4PlayerControlAssignment::CTM_HandleDownStatesOnly; pControlPacket->AddControl(iControlIndex, pAssignment->GetTriggerMode() | extra_trigger_mode); @@ -1056,7 +1056,7 @@ bool C4PlayerControl::ProcessKeyDown(const C4KeyCodeEx &pressed_key, const C4Key } // process! bool clear_recent_keys = false; - bool fResult = ProcessKeyEvent(pressed_key, matched_key, false, Game.KeyboardInput.GetLastKeyExtraData(), false, &clear_recent_keys); + bool fResult = ProcessKeyEvent(pressed_key, matched_key, CONS_Down, Game.KeyboardInput.GetLastKeyExtraData(), false, &clear_recent_keys); // unless assignment requests a clear, always add keys to recent list even if not handled if (clear_recent_keys) RecentKeys.clear(); @@ -1075,7 +1075,13 @@ bool C4PlayerControl::ProcessKeyUp(const C4KeyCodeEx &pressed_key, const C4KeyCo if (i != DownKeys.end()) DownKeys.erase(i); } // process! - return ProcessKeyEvent(pressed_key, matched_key, true, Game.KeyboardInput.GetLastKeyExtraData()); + return ProcessKeyEvent(pressed_key, matched_key, CONS_Up, Game.KeyboardInput.GetLastKeyExtraData()); +} + +bool C4PlayerControl::ProcessKeyMoved(const C4KeyCodeEx &pressed_key, const C4KeyCodeEx &matched_key) +{ + // process! + return ProcessKeyEvent(pressed_key, matched_key, CONS_Moved, Game.KeyboardInput.GetLastKeyExtraData()); } void C4PlayerControl::ExecuteControlPacket(const class C4ControlPlayerControl *pCtrl) @@ -1096,10 +1102,10 @@ void C4PlayerControl::ExecuteControlPacket(const class C4ControlPlayerControl *p AddDbgRec(RCT_PlrCom, &rItem.iControl, sizeof(rItem.iControl)); } } - if (ExecuteControl(rItem.iControl, pCtrl->IsReleaseControl(), pCtrl->GetExtraData(), rItem.iTriggerMode, false, fHandleDownStateOnly)) + if (ExecuteControl(rItem.iControl, pCtrl->GetState(), pCtrl->GetExtraData(), rItem.iTriggerMode, false, fHandleDownStateOnly)) if (pCtrlDef->IsSync()) { - if (pCtrl->IsReleaseControl()) + if (pCtrl->GetState() == CONS_Up) { // control processed. however, for key releases, overriden keys are released silently so following down events aren't handled as key repeats // note this does not affect CTM_Hold/CTM_Release, because they ignore release controls anyway @@ -1114,7 +1120,7 @@ void C4PlayerControl::ExecuteControlPacket(const class C4ControlPlayerControl *p } } -bool C4PlayerControl::ExecuteControl(int32_t iControl, bool fUp, const C4KeyEventData &rKeyExtraData, int32_t iTriggerMode, bool fRepeated, bool fHandleDownStateOnly) +bool C4PlayerControl::ExecuteControl(int32_t iControl, ControlState state, const C4KeyEventData &rKeyExtraData, int32_t iTriggerMode, bool fRepeated, bool fHandleDownStateOnly) { // execute single control. return if handled const C4PlayerControlDef *pControlDef = ControlDefs.GetControlByIndex(iControl); @@ -1132,7 +1138,7 @@ bool C4PlayerControl::ExecuteControl(int32_t iControl, bool fUp, const C4KeyEven { if (eAction != C4PlayerControlDef::CDA_Script) return false; if (!pControlDef->IsHoldKey()) return false; - if (fUp) return false; // hold triggers have no "up"-event + if (state == CONS_Up) return false; // hold triggers have no "up"-event // perform hold/release if (fWasDown) { @@ -1142,7 +1148,7 @@ bool C4PlayerControl::ExecuteControl(int32_t iControl, bool fUp, const C4KeyEven KeyExtraData.iStrength = 0; Sync.SetControlDownState(iControl, KeyExtraData, Game.FrameCounter, false); // now process as a regular "Up" event - fUp = true; + state = CONS_Up; fRepeated = false; } else @@ -1171,7 +1177,7 @@ bool C4PlayerControl::ExecuteControl(int32_t iControl, bool fUp, const C4KeyEven } } } - else if (fUp) + else if (state == CONS_Up) { // regular ControlUp: Only valid if that control was down if (!fWasDown) return false; @@ -1179,14 +1185,19 @@ bool C4PlayerControl::ExecuteControl(int32_t iControl, bool fUp, const C4KeyEven } else if (pControlDef->IsHoldKey()) { - // regular ControlDown on Hold Key: Set in down list - Sync.SetControlDownState(iControl, KeyExtraData, Game.FrameCounter, true); - fRepeated = fWasDown; + if (state == CONS_Moved) + fRepeated = true; + else + { + // regular ControlDown on Hold Key: Set in down list + Sync.SetControlDownState(iControl, KeyExtraData, Game.FrameCounter, true); + fRepeated = fWasDown; + } } // down state handling done if (fHandleDownStateOnly) return false; // perform action for this control - bool fHandled = ExecuteControlAction(iControl, eAction, pControlDef->GetExtraData(), fUp, KeyExtraData, fRepeated); + bool fHandled = ExecuteControlAction(iControl, eAction, pControlDef->GetExtraData(), state, KeyExtraData, fRepeated); // handled controls hide control display C4Player *pPlr; if ((pPlr = ::Players.Get(iPlr))) if (pPlr->ShowStartup) pPlr->ShowStartup = false; @@ -1194,8 +1205,10 @@ bool C4PlayerControl::ExecuteControl(int32_t iControl, bool fUp, const C4KeyEven return fHandled && !(iTriggerMode & C4PlayerControlAssignment::CTM_AlwaysUnhandled); } -bool C4PlayerControl::ExecuteControlAction(int32_t iControl, C4PlayerControlDef::Actions eAction, C4ID idControlExtraData, bool fUp, const C4KeyEventData &rKeyExtraData, bool fRepeated) +bool C4PlayerControl::ExecuteControlAction(int32_t iControl, C4PlayerControlDef::Actions eAction, C4ID idControlExtraData, ControlState state, const C4KeyEventData &rKeyExtraData, bool fRepeated) { + // moved events don't make sense for menus and are only handled by script + if (state == CONS_Moved && eAction != C4PlayerControlDef::CDA_Script) return false; // get affected player C4Player *pPlr = NULL; C4Viewport *pVP; @@ -1208,12 +1221,13 @@ bool C4PlayerControl::ExecuteControlAction(int32_t iControl, C4PlayerControlDef: pCursor = pPlr->Cursor; if (pCursor && pCursor->Menu && pCursor->Menu->IsActive()) pCursorMenu = pCursor->Menu; } + bool fUp = state == CONS_Up; // exec action (on player) switch (eAction) { // scripted player control case C4PlayerControlDef::CDA_Script: - return ExecuteControlScript(iControl, idControlExtraData, fUp, rKeyExtraData, fRepeated); + return ExecuteControlScript(iControl, idControlExtraData, state, rKeyExtraData, fRepeated); // menu controls case C4PlayerControlDef::CDA_Menu: if (!pPlr || fUp) return false; if (pPlr->Menu.IsActive()) pPlr->Menu.TryClose(false, true); else pPlr->ActivateMenuMain(); return true; // toggle @@ -1241,15 +1255,16 @@ bool C4PlayerControl::ExecuteControlAction(int32_t iControl, C4PlayerControlDef: } } -bool C4PlayerControl::ExecuteControlScript(int32_t iControl, C4ID idControlExtraData, bool fUp, const C4KeyEventData &rKeyExtraData, bool fRepeated) +bool C4PlayerControl::ExecuteControlScript(int32_t iControl, C4ID idControlExtraData, ControlState state, const C4KeyEventData &rKeyExtraData, bool fRepeated) { C4Player *pPlr = ::Players.Get(iPlr); if (pPlr) { // Not for eliminated (checked again in DirectCom, but make sure no control is generated for eliminated players!) if (pPlr->Eliminated) return false; - // control count for statistics - pPlr->CountControl(C4Player::PCID_DirectCom, iControl*2+fUp); + // control count for statistics (but don't count analog stick wiggles) + if (state != CONS_Moved) + pPlr->CountControl(C4Player::PCID_DirectCom, iControl*2+state); } else if (iPlr > -1) { @@ -1270,7 +1285,7 @@ bool C4PlayerControl::ExecuteControlScript(int32_t iControl, C4ID idControlExtra C4Value vx = (x == C4KeyEventData::KeyPos_None) ? C4VNull : C4VInt(x); C4Value vy = (y == C4KeyEventData::KeyPos_None) ? C4VNull : C4VInt(y); // exec control function - C4AulParSet Pars(iPlr, iControl, C4Id2Def(idControlExtraData), vx, vy, rKeyExtraData.iStrength, fRepeated, fUp); + C4AulParSet Pars(iPlr, iControl, C4Id2Def(idControlExtraData), vx, vy, rKeyExtraData.iStrength, fRepeated, C4VInt(state)); return ::ScriptEngine.GetPropList()->Call(PSF_PlayerControl, &Pars).getBool(); } @@ -1295,7 +1310,7 @@ void C4PlayerControl::Execute() if (!((iFrameDiff-iCtrlInitialRepeatDelay) % iCtrlRepeatDelay)) { // it's RepeatTime for this key! - ExecuteControlAction(i, pCtrlDef->GetAction(), pCtrlDef->GetExtraData(), false, pControlDownState->DownState, true); + ExecuteControlAction(i, pCtrlDef->GetAction(), pCtrlDef->GetExtraData(), CONS_Down, pControlDownState->DownState, true); } } } @@ -1348,7 +1363,7 @@ void C4PlayerControl::AddKeyBinding(const C4KeyCodeEx &key, bool fHoldKey, int32 { KeyBindings.push_back(new C4KeyBinding( key, FormatString("PlrKey%02d", idx).getData(), KEYSCOPE_Control, - new C4KeyCBExPassKey(*this, key, &C4PlayerControl::ProcessKeyDown, fHoldKey ? &C4PlayerControl::ProcessKeyUp : NULL), + new C4KeyCBExPassKey(*this, key, &C4PlayerControl::ProcessKeyDown, fHoldKey ? &C4PlayerControl::ProcessKeyUp : NULL, NULL, fHoldKey ? &C4PlayerControl::ProcessKeyMoved : NULL), C4CustomKey::PRIO_PlrControl)); } @@ -1440,7 +1455,7 @@ void C4PlayerControl::PrepareInput() ev.iStrength = 0; ev.vp_x = x; ev.vp_y = y; ev.game_x = game_x; ev.game_y = game_y; - C4ControlPlayerControl *pControlPacket = new C4ControlPlayerControl(iPlr, false, ev); + C4ControlPlayerControl *pControlPacket = new C4ControlPlayerControl(iPlr, CONS_Down, ev); pControlPacket->AddControl(ControlDefs.InternalCons.CON_CursorPos, C4PlayerControlAssignment::CTM_Default); // make sure it's added at head, because controls that have SendCursorPos=1 set will follow, which will rely // on the cursor pos being known diff --git a/src/control/C4PlayerControl.h b/src/control/C4PlayerControl.h index 5fc6950b4..eb204001b 100644 --- a/src/control/C4PlayerControl.h +++ b/src/control/C4PlayerControl.h @@ -333,6 +333,12 @@ class C4PlayerControl public: enum { MaxRecentKeyLookback = 3000, MaxSequenceKeyDelay = 800 }; // milliseconds: Time to press key combos + enum ControlState { + CONS_Down = 0, + CONS_Up, + CONS_Moved, + }; + private: C4PlayerControlDefs &ControlDefs; // shortcut @@ -386,14 +392,15 @@ private: CSync Sync; // callbacks from Game.KeyboardInput - bool ProcessKeyEvent(const C4KeyCodeEx &pressed_key, const C4KeyCodeEx &matched_key, bool fUp, const C4KeyEventData &rKeyExtraData, bool reset_down_states_only=false, bool *clear_recent_keys=NULL); + bool ProcessKeyEvent(const C4KeyCodeEx &pressed_key, const C4KeyCodeEx &matched_key, ControlState state, const C4KeyEventData &rKeyExtraData, bool reset_down_states_only=false, bool *clear_recent_keys=NULL); bool ProcessKeyDown(const C4KeyCodeEx &pressed_key, const C4KeyCodeEx &matched_key); bool ProcessKeyUp(const C4KeyCodeEx &pressed_key, const C4KeyCodeEx &matched_key); + bool ProcessKeyMoved(const C4KeyCodeEx &pressed_key, const C4KeyCodeEx &matched_key); // execute single control. return if handled. - bool ExecuteControl(int32_t iControl, bool fUp, const C4KeyEventData &rKeyExtraData, int32_t iTriggerMode, bool fRepeated, bool fHandleDownStateOnly); - bool ExecuteControlAction(int32_t iControl, C4PlayerControlDef::Actions eAction, C4ID idControlExtraData, bool fUp, const C4KeyEventData &rKeyExtraData, bool fRepeated); - bool ExecuteControlScript(int32_t iControl, C4ID idControlExtraData, bool fUp, const C4KeyEventData &rKeyExtraData, bool fRepeated); + bool ExecuteControl(int32_t iControl, ControlState state, const C4KeyEventData &rKeyExtraData, int32_t iTriggerMode, bool fRepeated, bool fHandleDownStateOnly); + bool ExecuteControlAction(int32_t iControl, C4PlayerControlDef::Actions eAction, C4ID idControlExtraData, ControlState state, const C4KeyEventData &rKeyExtraData, bool fRepeated); + bool ExecuteControlScript(int32_t iControl, C4ID idControlExtraData, ControlState state, const C4KeyEventData &rKeyExtraData, bool fRepeated); // init void AddKeyBinding(const C4KeyCodeEx &key, bool fHoldKey, int32_t idx); diff --git a/src/gamescript/C4GameScript.cpp b/src/gamescript/C4GameScript.cpp index 952f67173..e69c1245e 100644 --- a/src/gamescript/C4GameScript.cpp +++ b/src/gamescript/C4GameScript.cpp @@ -3079,6 +3079,12 @@ C4ScriptConstDef C4ScriptGameConstMap[]= { "GUI_Multiple" ,C4V_Int, C4ScriptGuiWindowStyleFlag::Multiple }, { "GUI_IgnoreMouse" ,C4V_Int, C4ScriptGuiWindowStyleFlag::IgnoreMouse }, { "GUI_NoCrop" ,C4V_Int, C4ScriptGuiWindowStyleFlag::NoCrop }, + + // control states + { "CONS_Down" ,C4V_Int, C4PlayerControl::CONS_Down }, + { "CONS_Up" ,C4V_Int, C4PlayerControl::CONS_Up }, + { "CONS_Moved" ,C4V_Int, C4PlayerControl::CONS_Moved }, + { NULL, C4V_Nil, 0} }; diff --git a/src/gui/C4KeyboardInput.h b/src/gui/C4KeyboardInput.h index f22ecab43..b4be29d05 100644 --- a/src/gui/C4KeyboardInput.h +++ b/src/gui/C4KeyboardInput.h @@ -46,7 +46,8 @@ enum C4KeyEventType KEYEV_None = 0, // no event KEYEV_Down = 1, // in response to WM_KEYDOWN or joypad button pressed KEYEV_Up = 2, // in response to WM_KEYUP or joypad button released - KEYEV_Pressed = 3 // in response to WM_KEYPRESSED + KEYEV_Pressed = 3, // in response to WM_KEYPRESSED + KEYEV_Moved = 4, // when moving a gamepad stick }; // keyboard code @@ -265,7 +266,7 @@ public: protected: TargetClass &rTarget; - CallbackFunc pFuncDown, pFuncUp, pFuncPressed; + CallbackFunc pFuncDown, pFuncUp, pFuncPressed, pFuncMoved; protected: virtual bool OnKeyEvent(const C4KeyCodeEx &key, C4KeyEventType eEv) @@ -276,6 +277,7 @@ protected: case KEYEV_Down: return pFuncDown ? (rTarget.*pFuncDown)() : false; case KEYEV_Up: return pFuncUp ? (rTarget.*pFuncUp)() : false; case KEYEV_Pressed: return pFuncPressed ? (rTarget.*pFuncPressed)() : false; + case KEYEV_Moved: return pFuncMoved ? (rTarget.*pFuncMoved)() : false; default: return false; } } @@ -283,8 +285,8 @@ protected: virtual bool CheckCondition() { return true; } public: - C4KeyCB(TargetClass &rTarget, CallbackFunc pFuncDown, CallbackFunc pFuncUp=NULL, CallbackFunc pFuncPressed=NULL) - : rTarget(rTarget), pFuncDown(pFuncDown), pFuncUp(pFuncUp), pFuncPressed(pFuncPressed) {} + C4KeyCB(TargetClass &rTarget, CallbackFunc pFuncDown, CallbackFunc pFuncUp=NULL, CallbackFunc pFuncPressed=NULL, CallbackFunc pFuncMoved=NULL) + : rTarget(rTarget), pFuncDown(pFuncDown), pFuncUp(pFuncUp), pFuncPressed(pFuncPressed), pFuncMoved(pFuncMoved) {} }; // callback interface that passes the pressed key as a parameter @@ -295,7 +297,7 @@ public: protected: TargetClass &rTarget; - CallbackFunc pFuncDown, pFuncUp, pFuncPressed; + CallbackFunc pFuncDown, pFuncUp, pFuncPressed, pFuncMoved; protected: virtual bool OnKeyEvent(const C4KeyCodeEx &key, C4KeyEventType eEv) @@ -306,6 +308,7 @@ protected: case KEYEV_Down: return pFuncDown ? (rTarget.*pFuncDown)(key) : false; case KEYEV_Up: return pFuncUp ? (rTarget.*pFuncUp)(key) : false; case KEYEV_Pressed: return pFuncPressed ? (rTarget.*pFuncPressed)(key) : false; + case KEYEV_Moved: return pFuncMoved ? (rTarget.*pFuncMoved)(key) : false; default: return false; } } @@ -313,8 +316,8 @@ protected: virtual bool CheckCondition() { return true; } public: - C4KeyCBPassKey(TargetClass &rTarget, CallbackFunc pFuncDown, CallbackFunc pFuncUp=NULL, CallbackFunc pFuncPressed=NULL) - : rTarget(rTarget), pFuncDown(pFuncDown), pFuncUp(pFuncUp), pFuncPressed(pFuncPressed) {} + C4KeyCBPassKey(TargetClass &rTarget, CallbackFunc pFuncDown, CallbackFunc pFuncUp=NULL, CallbackFunc pFuncPressed=NULL, CallbackFunc pFuncMoved=NULL) + : rTarget(rTarget), pFuncDown(pFuncDown), pFuncUp(pFuncUp), pFuncPressed(pFuncPressed), pFuncMoved(pFuncMoved) {} }; // parameterized callback interface @@ -325,7 +328,7 @@ public: protected: TargetClass &rTarget; - CallbackFunc pFuncDown, pFuncUp, pFuncPressed; + CallbackFunc pFuncDown, pFuncUp, pFuncPressed, pFuncMoved; ParameterType par; protected: @@ -337,6 +340,7 @@ protected: case KEYEV_Down: return pFuncDown ? (rTarget.*pFuncDown)(par) : false; case KEYEV_Up: return pFuncUp ? (rTarget.*pFuncUp)(par) : false; case KEYEV_Pressed: return pFuncPressed ? (rTarget.*pFuncPressed)(par) : false; + case KEYEV_Moved: return pFuncMoved ? (rTarget.*pFuncMoved)(par) : false; default: return false; } } @@ -344,8 +348,8 @@ protected: virtual bool CheckCondition() { return true; } public: - C4KeyCBEx(TargetClass &rTarget, const ParameterType &par, CallbackFunc pFuncDown, CallbackFunc pFuncUp=NULL, CallbackFunc pFuncPressed=NULL) - : rTarget(rTarget), pFuncDown(pFuncDown), pFuncUp(pFuncUp), pFuncPressed(pFuncPressed), par(par) {} + C4KeyCBEx(TargetClass &rTarget, const ParameterType &par, CallbackFunc pFuncDown, CallbackFunc pFuncUp=NULL, CallbackFunc pFuncPressed=NULL, CallbackFunc pFuncMoved=NULL) + : rTarget(rTarget), pFuncDown(pFuncDown), pFuncUp(pFuncUp), pFuncPressed(pFuncPressed), pFuncMoved(pFuncMoved), par(par) {} }; template class C4KeyCBExPassKey : public C4KeyboardCallbackInterface @@ -355,7 +359,7 @@ public: protected: TargetClass &rTarget; - CallbackFunc pFuncDown, pFuncUp, pFuncPressed; + CallbackFunc pFuncDown, pFuncUp, pFuncPressed, pFuncMoved; ParameterType par; protected: @@ -367,6 +371,7 @@ protected: case KEYEV_Down: return pFuncDown ? (rTarget.*pFuncDown)(key, par) : false; case KEYEV_Up: return pFuncUp ? (rTarget.*pFuncUp)(key, par) : false; case KEYEV_Pressed: return pFuncPressed ? (rTarget.*pFuncPressed)(key, par) : false; + case KEYEV_Moved: return pFuncMoved ? (rTarget.*pFuncMoved)(key, par) : false; default: return false; } } @@ -374,8 +379,8 @@ protected: virtual bool CheckCondition() { return true; } public: - C4KeyCBExPassKey(TargetClass &rTarget, const ParameterType &par, CallbackFunc pFuncDown, CallbackFunc pFuncUp=NULL, CallbackFunc pFuncPressed=NULL) - : rTarget(rTarget), pFuncDown(pFuncDown), pFuncUp(pFuncUp), pFuncPressed(pFuncPressed), par(par) {} + C4KeyCBExPassKey(TargetClass &rTarget, const ParameterType &par, CallbackFunc pFuncDown, CallbackFunc pFuncUp=NULL, CallbackFunc pFuncPressed=NULL, CallbackFunc pFuncMoved=NULL) + : rTarget(rTarget), pFuncDown(pFuncDown), pFuncUp(pFuncUp), pFuncPressed(pFuncPressed), pFuncMoved(pFuncMoved), par(par) {} }; // one mapped keyboard entry diff --git a/src/platform/C4AppSDL.cpp b/src/platform/C4AppSDL.cpp index fd4ea7786..b1a9334e3 100644 --- a/src/platform/C4AppSDL.cpp +++ b/src/platform/C4AppSDL.cpp @@ -165,7 +165,7 @@ void C4AbstractApp::HandleSDLEvent(SDL_Event& e) case SDL_CONTROLLERAXISMOTION: case SDL_CONTROLLERBUTTONDOWN: case SDL_CONTROLLERBUTTONUP: - Application.pGamePadControl->FeedEvent(e); + Application.pGamePadControl->FeedEvent(e, C4GamePadControl::FEED_BUTTONS); break; } } diff --git a/src/platform/C4GamePadCon.cpp b/src/platform/C4GamePadCon.cpp index a44c6559e..61f1d5863 100644 --- a/src/platform/C4GamePadCon.cpp +++ b/src/platform/C4GamePadCon.cpp @@ -25,13 +25,6 @@ #include #include -// regardless of WIN32 or SDL -void C4GamePadControl::DoAxisInput() -{ - // Send axis strength changes - Execute(true); -} - #if defined(HAVE_SDL) && !defined(USE_CONSOLE) #include @@ -54,7 +47,7 @@ C4GamePadControl::~C4GamePadControl() SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_EVENTS); } -void C4GamePadControl::Execute(bool) +void C4GamePadControl::Execute() { #ifndef USE_SDL_MAINLOOP SDL_Event event; @@ -65,7 +58,7 @@ void C4GamePadControl::Execute(bool) case SDL_CONTROLLERAXISMOTION: case SDL_CONTROLLERBUTTONDOWN: case SDL_CONTROLLERBUTTONUP: - FeedEvent(event); + FeedEvent(event, FEED_BUTTONS); break; } } @@ -74,19 +67,10 @@ void C4GamePadControl::Execute(bool) namespace { - const int deadZone = 13337; - - int amplify(int i) - { - if (i < 0) - return -(deadZone + 1); - if (i > 0) - return deadZone + 1; - return 0; - } + const int deadZone = 8000; } -void C4GamePadControl::FeedEvent(SDL_Event& event) +void C4GamePadControl::FeedEvent(const SDL_Event& event, int feed) { switch (event.type) { @@ -94,63 +78,62 @@ void C4GamePadControl::FeedEvent(SDL_Event& event) { C4KeyCode minCode = KEY_Gamepad(event.caxis.which, KEY_JOY_Axis(event.caxis.axis, false)); C4KeyCode maxCode = KEY_Gamepad(event.caxis.which, KEY_JOY_Axis(event.caxis.axis, true)); + int32_t value = std::abs(event.caxis.value); + uint8_t which = event.caxis.which; + C4KeyCode keyCode = event.caxis.value >= 0 ? maxCode : minCode; - // FIXME: This assumes that the axis really rests around (0, 0) if it is not used, which is not always true. - if (event.caxis.value < -deadZone) + auto doInput = [&](C4KeyEventType event, int32_t strength) { - if (PressedAxis.count(minCode) == 0) + Game.DoKeyboardInput( + KEY_Gamepad(which, keyCode), event, + false, false, false, false, NULL, false, strength); + }; + + if (feed & FEED_BUTTONS) + { + // Also emulate button presses. + if (PressedAxis.count(keyCode) && value <= deadZone) { - Game.DoKeyboardInput( - KEY_Gamepad(event.caxis.which, minCode), - KEYEV_Down, false, false, false, false); - PressedAxis.insert(minCode); - } - } - else - { - if (PressedAxis.count(minCode) != 0) - { - Game.DoKeyboardInput( - KEY_Gamepad(event.caxis.which, minCode), - KEYEV_Up, false, false, false, false); - PressedAxis.erase(minCode); - } - } - if (event.caxis.value > +deadZone) - { - if (PressedAxis.count(maxCode) == 0) - { - Game.DoKeyboardInput( - KEY_Gamepad(event.caxis.which, maxCode), - KEYEV_Down, false, false, false, false); - PressedAxis.insert(maxCode); - } - } - else - { - if (PressedAxis.count(maxCode) != 0) - { - Game.DoKeyboardInput( - KEY_Gamepad(event.caxis.which, maxCode), - KEYEV_Up, false, false, false, false); - PressedAxis.erase(maxCode); + PressedAxis.erase(keyCode); + doInput(KEYEV_Up, -1); + } + else if (!PressedAxis.count(keyCode) && value > deadZone) + { + PressedAxis.insert(keyCode); + doInput(KEYEV_Down, -1); } } + if (feed & FEED_MOVED) + doInput(KEYEV_Moved, value); + + AxisEvents[keyCode] = event; + break; } case SDL_CONTROLLERBUTTONDOWN: - Game.DoKeyboardInput( - KEY_Gamepad(event.cbutton.which, KEY_JOY_Button(event.cbutton.button)), - KEYEV_Down, false, false, false, false); + if (feed & FEED_BUTTONS) + Game.DoKeyboardInput( + KEY_Gamepad(event.cbutton.which, KEY_JOY_Button(event.cbutton.button)), + KEYEV_Down, false, false, false, false); break; case SDL_CONTROLLERBUTTONUP: - Game.DoKeyboardInput( - KEY_Gamepad(event.cbutton.which, KEY_JOY_Button(event.cbutton.button)), - KEYEV_Up, false, false, false, false); + if (feed & FEED_BUTTONS) + Game.DoKeyboardInput( + KEY_Gamepad(event.cbutton.which, KEY_JOY_Button(event.cbutton.button)), + KEYEV_Up, false, false, false, false); break; } } +void C4GamePadControl::DoAxisInput() +{ + for (auto const &e : AxisEvents) + { + FeedEvent(e.second, FEED_MOVED); + } + AxisEvents.clear(); +} + int C4GamePadControl::GetGamePadCount() { // Not all Joysticks are game controllers. @@ -192,7 +175,8 @@ void C4GamePadOpener::SetGamePad(int iGamepad) C4GamePadControl::C4GamePadControl() { Log("WARNING: Engine without Gamepad support"); } C4GamePadControl::~C4GamePadControl() { } -void C4GamePadControl::Execute(bool) { } +void C4GamePadControl::Execute() { } +void C4GamePadControl::DoAxisInput() { } int C4GamePadControl::GetGamePadCount() { return 0; } bool C4GamePadControl::AnyButtonDown() { return false; } diff --git a/src/platform/C4GamePadCon.h b/src/platform/C4GamePadCon.h index e492b7341..f280d9868 100644 --- a/src/platform/C4GamePadCon.h +++ b/src/platform/C4GamePadCon.h @@ -23,6 +23,7 @@ #ifdef HAVE_SDL #include #include +#include #endif struct _SDL_GameController; @@ -35,16 +36,22 @@ class C4GamePadControl { #ifdef HAVE_SDL public: - void FeedEvent(SDL_Event& e); + enum { + FEED_BUTTONS = 1, + FEED_MOVED = 2, + }; + // Called from C4AppSDL + void FeedEvent(const SDL_Event& e, int feed); private: - std::set PressedAxis; + std::set PressedAxis; // for button emulation + std::map AxisEvents; // for analog movement events #endif public: C4GamePadControl(); ~C4GamePadControl(); void Clear(); int GetGamePadCount(); - void Execute(bool send_axis_strength_changes=false); + void Execute(); void DoAxisInput(); // period axis strength update controls sent on each control frame creation static bool AnyButtonDown(); }; From 1147d1a154f9e0966e360d3f27cf11fdac7ae9ef Mon Sep 17 00:00:00 2001 From: Lukas Werling Date: Mon, 15 Feb 2016 17:30:24 +0100 Subject: [PATCH 05/17] Fix openclonk-server build SDL usage is masked with HAVE_SDL checks so it should work without SDL, but SDL has to be linked when it's available. --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index f9add2b76..359dcc3d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1130,6 +1130,7 @@ target_link_libraries(openclonk-server ${PNG_LIBRARIES} ${JPEG_LIBRARIES} ${EXECINFO_LIBRARY} + ${SDL2_LIBRARIES} ${READLINE_LIBRARIES} ${Audio_LIBRARIES} ${GETOPT_LIBRARIES} From 985f1b99e5678df2f10558081f2e09cd20bf0017 Mon Sep 17 00:00:00 2001 From: Lukas Werling Date: Tue, 16 Feb 2016 00:21:19 +0100 Subject: [PATCH 06/17] Make GetPlayerControlState() query the current controller state To keep compatibility with scripts which expect only binary buttons, this adds a third parameter to the function which enables the new functionality. Bonus /script to test the controller stick x axis: Schedule(GetCursor(), "Message(\"%d / %d\", GetPlayerControlState(0, CON_Left, true), GetPlayerControlState(0, CON_Right, true))", 10, 100000000) Note that the values will be inconsistent if multiple analog sticks are bound to the same control, as values from one stick will overwrite those from the other one. This can happen even if you move only one stick. --- src/control/C4PlayerControl.cpp | 20 +++++++++++++++++++- src/control/C4PlayerControl.h | 5 +++-- src/gamescript/C4GameScript.cpp | 6 +++--- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/control/C4PlayerControl.cpp b/src/control/C4PlayerControl.cpp index 20df02927..625bc3a4d 100644 --- a/src/control/C4PlayerControl.cpp +++ b/src/control/C4PlayerControl.cpp @@ -893,14 +893,18 @@ void C4PlayerControl::CSync::ControlDownState::CompileFunc(StdCompiler *pComp) { pComp->Value(DownState); pComp->Separator(); + pComp->Value(MovedState); + pComp->Separator(); pComp->Value(iDownFrame); pComp->Separator(); + pComp->Value(iMovedFrame); + pComp->Separator(); pComp->Value(fDownByUser); } bool C4PlayerControl::CSync::ControlDownState::operator ==(const ControlDownState &cmp) const { - return DownState == cmp.DownState && iDownFrame == cmp.iDownFrame && fDownByUser == cmp.fDownByUser; + return DownState == cmp.DownState && MovedState == cmp.MovedState && iDownFrame == cmp.iDownFrame && iMovedFrame == cmp.iMovedFrame && fDownByUser == cmp.fDownByUser; } const C4PlayerControl::CSync::ControlDownState *C4PlayerControl::CSync::GetControlDownState(int32_t iControl) const @@ -928,6 +932,16 @@ void C4PlayerControl::CSync::SetControlDownState(int32_t iControl, const C4KeyEv rState.fDownByUser = fDownByUser; } +void C4PlayerControl::CSync::SetControlMovedState(int32_t iControl, const C4KeyEventData &rMovedState, int32_t iMovedFrame) +{ + // update state + if (iControl < 0) return; + if (iControl >= int32_t(ControlDownStates.size())) ControlDownStates.resize(iControl+1); + ControlDownState &rState = ControlDownStates[iControl]; + rState.MovedState = rMovedState; + rState.iMovedFrame = iMovedFrame; +} + bool C4PlayerControl::CSync::SetControlDisabled(int32_t iControl, int32_t iVal) { // disable control @@ -948,6 +962,7 @@ void C4PlayerControl::CSync::ResetControlDownState(int32_t iControl) C4KeyEventData KeyDownState = pDownState->DownState; KeyDownState.iStrength = 0; SetControlDownState(iControl, KeyDownState, 0, false); + SetControlMovedState(iControl, KeyDownState, 0); } } @@ -1186,7 +1201,10 @@ bool C4PlayerControl::ExecuteControl(int32_t iControl, ControlState state, const else if (pControlDef->IsHoldKey()) { if (state == CONS_Moved) + { + Sync.SetControlMovedState(iControl, KeyExtraData, Game.FrameCounter); fRepeated = true; + } else { // regular ControlDown on Hold Key: Set in down list diff --git a/src/control/C4PlayerControl.h b/src/control/C4PlayerControl.h index eb204001b..e42ba64e8 100644 --- a/src/control/C4PlayerControl.h +++ b/src/control/C4PlayerControl.h @@ -359,8 +359,8 @@ public: { struct ControlDownState { - C4KeyEventData DownState; // control is down if DownState.iStrength>0 - int32_t iDownFrame; // frame when control was pressed + C4KeyEventData DownState, MovedState; // control is down if DownState.iStrength>0 + int32_t iDownFrame, iMovedFrame; // frame when control was pressed bool fDownByUser; // if true, the key is actually pressed. Otherwise, it's triggered as down by another key ControlDownState(const C4KeyEventData &rDownState, int32_t iDownFrame, bool fDownByUser) : DownState(rDownState), iDownFrame(iDownFrame), fDownByUser(fDownByUser) {} @@ -379,6 +379,7 @@ public: int32_t GetControlDisabled(int32_t iControl) const; bool IsControlDisabled(int32_t iControl) const { return GetControlDisabled(iControl)>0; } void SetControlDownState(int32_t iControl, const C4KeyEventData &rDownState, int32_t iDownFrame, bool fDownByUser); + void SetControlMovedState(int32_t iControl, const C4KeyEventData &rMovedState, int32_t iMovedFrame); void ResetControlDownState(int32_t iControl); bool SetControlDisabled(int32_t iControl, int32_t iVal); diff --git a/src/gamescript/C4GameScript.cpp b/src/gamescript/C4GameScript.cpp index e69c1245e..d37577896 100644 --- a/src/gamescript/C4GameScript.cpp +++ b/src/gamescript/C4GameScript.cpp @@ -2588,7 +2588,7 @@ static bool FnSetNextMission(C4PropList * _this, C4String *szNextMission, C4Stri return true; } -static long FnGetPlayerControlState(C4PropList * _this, long iPlr, long iControl) +static long FnGetPlayerControlState(C4PropList * _this, long iPlr, long iControl, bool fMovedState) { // get control set to check C4PlayerControl *pCheckCtrl = NULL; @@ -2606,8 +2606,8 @@ static long FnGetPlayerControlState(C4PropList * _this, long iPlr, long iControl const C4PlayerControl::CSync::ControlDownState *pControlState = pCheckCtrl->GetControlDownState(iControl); // no state means not down if (!pControlState) return 0; - // otherwise take down-value - return pControlState->DownState.iStrength; + // otherwise take either down-value or moved-value + return fMovedState ? pControlState->MovedState.iStrength : pControlState->DownState.iStrength; } // undocumented! From 24622f3a9c8a7fdd676975c4764f7725b05f3b2e Mon Sep 17 00:00:00 2001 From: Lukas Werling Date: Wed, 17 Feb 2016 20:58:12 +0100 Subject: [PATCH 07/17] Update main menu GUI gamepad control bindings It is now possible to control all GUI menus using the left stick or the dpad, along with the A and B buttons on the controller. This also doubles the button emulation dead zone to make navigating the menus with the stick easier. --- src/game/C4Game.cpp | 14 +++--- src/gui/C4GuiButton.cpp | 2 +- src/gui/C4GuiCheckBox.cpp | 2 +- src/gui/C4GuiComboBox.cpp | 6 +-- src/gui/C4GuiDialogs.cpp | 8 ++-- src/gui/C4GuiEdit.cpp | 2 +- src/gui/C4GuiListBox.cpp | 10 ++--- src/gui/C4GuiMenu.cpp | 12 +++--- src/gui/C4GuiTabular.cpp | 4 +- src/gui/C4KeyboardInput.cpp | 25 +++-------- src/gui/C4KeyboardInput.h | 79 +++++++++++++++++++++++----------- src/gui/C4StartupMainDlg.cpp | 4 +- src/gui/C4StartupPlrSelDlg.cpp | 2 +- src/platform/C4GamePadCon.cpp | 10 ++--- 14 files changed, 98 insertions(+), 82 deletions(-) diff --git a/src/game/C4Game.cpp b/src/game/C4Game.cpp index 88fc1e8a8..8fa12a6d0 100644 --- a/src/game/C4Game.cpp +++ b/src/game/C4Game.cpp @@ -2966,25 +2966,25 @@ bool C4Game::InitKeyboard() // fullscreen menu Keys.clear(); Keys.push_back(C4KeyCodeEx(K_LEFT)); - if (Config.Controls.GamepadGuiControl) Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_Left))); + if (Config.Controls.GamepadGuiControl) ControllerKeys::Left(Keys); KeyboardInput.RegisterKey(new C4CustomKey(Keys, "FullscreenMenuLeft", KEYSCOPE_FullSMenu, new C4KeyCBEx (FullScreen, COM_MenuLeft, &C4FullScreen::MenuKeyControl))); Keys.clear(); Keys.push_back(C4KeyCodeEx(K_RIGHT)); - if (Config.Controls.GamepadGuiControl) Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_Right))); + if (Config.Controls.GamepadGuiControl) ControllerKeys::Right(Keys); KeyboardInput.RegisterKey(new C4CustomKey(Keys, "FullscreenMenuRight", KEYSCOPE_FullSMenu, new C4KeyCBEx (FullScreen, COM_MenuRight, &C4FullScreen::MenuKeyControl))); Keys.clear(); Keys.push_back(C4KeyCodeEx(K_UP)); - if (Config.Controls.GamepadGuiControl) Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_Up))); + if (Config.Controls.GamepadGuiControl) ControllerKeys::Up(Keys); KeyboardInput.RegisterKey(new C4CustomKey(Keys, "FullscreenMenuUp", KEYSCOPE_FullSMenu, new C4KeyCBEx (FullScreen, COM_MenuUp, &C4FullScreen::MenuKeyControl))); Keys.clear(); Keys.push_back(C4KeyCodeEx(K_DOWN)); - if (Config.Controls.GamepadGuiControl) Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_Down))); + if (Config.Controls.GamepadGuiControl) ControllerKeys::Down(Keys); KeyboardInput.RegisterKey(new C4CustomKey(Keys, "FullscreenMenuDown", KEYSCOPE_FullSMenu, new C4KeyCBEx (FullScreen, COM_MenuDown, &C4FullScreen::MenuKeyControl))); Keys.clear(); Keys.push_back(C4KeyCodeEx(K_SPACE)); Keys.push_back(C4KeyCodeEx(K_RETURN)); - if (Config.Controls.GamepadGuiControl) Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_AnyLowButton))); + if (Config.Controls.GamepadGuiControl) ControllerKeys::Ok(Keys); KeyboardInput.RegisterKey(new C4CustomKey(Keys, "FullscreenMenuOK", KEYSCOPE_FullSMenu, new C4KeyCBEx (FullScreen, COM_MenuEnter, &C4FullScreen::MenuKeyControl))); // name used by PlrControlKeyName Keys.clear(); Keys.push_back(C4KeyCodeEx(K_ESCAPE)); - if (Config.Controls.GamepadGuiControl) Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_AnyHighButton))); + if (Config.Controls.GamepadGuiControl) ControllerKeys::Cancel(Keys); KeyboardInput.RegisterKey(new C4CustomKey(Keys, "FullscreenMenuCancel", KEYSCOPE_FullSMenu, new C4KeyCBEx (FullScreen, COM_MenuClose, &C4FullScreen::MenuKeyControl))); // name used by PlrControlKeyName Keys.clear(); Keys.push_back(C4KeyCodeEx(K_SPACE)); - if (Config.Controls.GamepadGuiControl) Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_AnyButton))); + if (Config.Controls.GamepadGuiControl) ControllerKeys::Any(Keys); KeyboardInput.RegisterKey(new C4CustomKey(Keys, "FullscreenMenuOpen", KEYSCOPE_FreeView, new C4KeyCB (FullScreen, &C4FullScreen::ActivateMenuMain))); // name used by C4MainMenu! KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_RIGHT ), "FilmNextPlayer", KEYSCOPE_FilmView, new C4KeyCB (::Viewports, &C4ViewportList::ViewportNextPlayer))); diff --git a/src/gui/C4GuiButton.cpp b/src/gui/C4GuiButton.cpp index 53b13fb2c..124fff641 100644 --- a/src/gui/C4GuiButton.cpp +++ b/src/gui/C4GuiButton.cpp @@ -39,7 +39,7 @@ namespace C4GUI keys.push_back(C4KeyCodeEx(K_SPACE)); keys.push_back(C4KeyCodeEx(K_RETURN)); if (Config.Controls.GamepadGuiControl) - keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_AnyLowButton))); + ControllerKeys::Ok(keys); pKeyButton = new C4KeyBinding(keys, "GUIButtonPress", KEYSCOPE_Gui, new ControlKeyCB