OpenClonk
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
C4GamePadCon.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 1998-2000, Matthes Bender
5  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
6  * Copyright (c) 2010-2016, The OpenClonk Team and contributors
7  *
8  * Distributed under the terms of the ISC license; see accompanying file
9  * "COPYING" for details.
10  *
11  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12  * See accompanying file "TRADEMARK" for details.
13  *
14  * To redistribute this file separately, substitute the full license texts
15  * for the above references.
16  */
17 
18 /* Gamepad control */
19 
20 #include "C4Include.h"
22 #include "platform/C4GamePadCon.h"
23 
24 #include "config/C4Config.h"
25 #include "object/C4ObjectCom.h"
26 #include "lib/C4Log.h"
27 #include "game/C4Application.h"
28 #include "game/C4Game.h"
29 
30 #if defined(HAVE_SDL) && !defined(USE_CONSOLE)
31 
32 #include <SDL.h>
33 
35 {
36  // SDL2 will only report events when the window has focus, so set
37  // this hint as we don't have a window
38  SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
39 
40  if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC | SDL_INIT_EVENTS) != 0)
41  LogF("SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER): %s", SDL_GetError());
42  SDL_GameControllerEventState(SDL_ENABLE);
43  if (!GetGamePadCount()) Log("No Gamepad found");
44 }
45 
47 {
48  // All gamepads have to be released before quitting SDL.
49  Gamepads.clear();
50  SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_EVENTS);
51 }
52 
54 {
55 #ifdef USE_SDL_MAINLOOP
56  if (!Application.isEditor) return;
57 #endif
58  SDL_Event event;
59  while (SDL_PollEvent(&event))
60  {
61  switch (event.type)
62  {
63  case SDL_CONTROLLERAXISMOTION:
64  case SDL_CONTROLLERBUTTONDOWN:
65  case SDL_CONTROLLERBUTTONUP:
66  FeedEvent(event, FEED_BUTTONS);
67  break;
68  case SDL_JOYDEVICEADDED:
69  case SDL_CONTROLLERDEVICEADDED:
70  case SDL_CONTROLLERDEVICEREMOVED:
71  CheckGamePad(event);
72  break;
73  }
74  }
75 }
76 
77 namespace
78 {
79  const int deadZone = 16000;
80 
81  // Axis strength uses the full signed 16 bit integer range. As we're
82  // splitting axes in left/right and up/down, it's preferable to have
83  // symmetrical ranges [0, 2^15 - 1] in both directions.
84  inline int32_t abs_strength(int32_t strength)
85  {
86  return strength >= 0 ? strength : -(strength + 1);
87  }
88 }
89 
90 void C4GamePadControl::FeedEvent(const SDL_Event& event, int feed)
91 {
92  switch (event.type)
93  {
94  case SDL_CONTROLLERAXISMOTION:
95  {
96  C4KeyCode minCode = KEY_Gamepad(KEY_CONTROLLER_Axis(event.caxis.axis, false));
97  C4KeyCode maxCode = KEY_Gamepad(KEY_CONTROLLER_Axis(event.caxis.axis, true));
98  int32_t value = abs_strength(event.caxis.value);
99  uint8_t which = event.caxis.which;
100  C4KeyCode keyCode = event.caxis.value >= 0 ? maxCode : minCode;
101 
102  auto doInput = [&](C4KeyEventType event, int32_t strength)
103  {
105  C4KeyCodeEx(KEY_Gamepad(keyCode), KEYS_None, false, which),
106  event, nullptr, false, strength);
107  };
108 
109  if (feed & FEED_BUTTONS)
110  {
111  // Also emulate button presses.
112  if (PressedAxis.count(keyCode) && value <= deadZone)
113  {
114  PressedAxis.erase(keyCode);
115  doInput(KEYEV_Up, -1);
116  }
117  else if (!PressedAxis.count(keyCode) && value > deadZone)
118  {
119  PressedAxis.insert(keyCode);
120  doInput(KEYEV_Down, -1);
121  }
122  }
123  if (feed & FEED_MOVED)
124  doInput(KEYEV_Moved, value);
125 
126  AxisEvents[keyCode] = event;
127 
128  break;
129  }
130  case SDL_CONTROLLERBUTTONDOWN:
131  if (feed & FEED_BUTTONS)
133  C4KeyCodeEx(KEY_Gamepad(KEY_CONTROLLER_Button(event.cbutton.button)), KEYS_None, false, event.cbutton.which),
134  KEYEV_Down);
135  break;
136  case SDL_CONTROLLERBUTTONUP:
137  if (feed & FEED_BUTTONS)
139  C4KeyCodeEx(KEY_Gamepad(KEY_CONTROLLER_Button(event.cbutton.button)), KEYS_None, false, event.cbutton.which),
140  KEYEV_Up);
141  break;
142  }
143 }
144 
145 void C4GamePadControl::CheckGamePad(const SDL_Event& e)
146 {
147  switch (e.type)
148  {
149  case SDL_JOYDEVICEADDED:
150  // Report that an unsupported joystick device has been detected, to help with controller issues.
151  if (!SDL_IsGameController(e.jdevice.which))
152  LogF("Gamepad %s isn't supported.", SDL_JoystickNameForIndex(e.jdevice.which));
153  break;
154  case SDL_CONTROLLERDEVICEADDED:
155  {
156  auto device = std::make_shared<C4GamePadOpener>(e.cdevice.which);
157  Gamepads[device->GetID()] = device;
158  LogF("Gamepad #%d connected: %s", device->GetID(), SDL_JoystickNameForIndex(e.cdevice.which));
159  break;
160  }
161  case SDL_CONTROLLERDEVICEREMOVED:
162  LogF("Gamepad #%d disconnected.", e.cdevice.which);
163  Gamepads.erase(e.cdevice.which);
164  break;
165  }
166 }
167 
169 {
170  for (auto const &e : AxisEvents)
171  {
172  FeedEvent(e.second, FEED_MOVED);
173  }
174  AxisEvents.clear();
175 }
176 
178 {
179  // Not all Joysticks are game controllers.
180  int count = 0;
181  for (int i = 0; i < SDL_NumJoysticks(); i++)
182  if (SDL_IsGameController(i))
183  count++;
184  return count;
185 }
186 
187 std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetGamePad(int gamepad)
188 {
189  if (gamepad >= 0)
190  for (const auto& p : Gamepads)
191  if (gamepad-- == 0)
192  return p.second;
193  return nullptr;
194 }
195 
196 std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetGamePadByID(int32_t id)
197 {
198  auto it = Gamepads.find(id);
199  if (it != Gamepads.end())
200  return it->second;
201  return nullptr;
202 }
203 
204 std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetAvailableGamePad()
205 {
206  for (const auto& p : Gamepads)
207  if (p.second->GetPlayer() < 0)
208  return p.second;
209  return nullptr;
210 }
211 
213 {
214  int n = iGamepad;
215  for (int i = 0; i < SDL_NumJoysticks(); i++)
216  if (SDL_IsGameController(i) && n-- == 0)
217  {
218  controller = SDL_GameControllerOpen(i);
219  if (!controller) LogF("SDL: %s", SDL_GetError());
220  SDL_Joystick *joystick = SDL_GameControllerGetJoystick(controller);
221  haptic = SDL_HapticOpenFromJoystick(joystick);
222  if (haptic && SDL_HapticRumbleSupported(haptic))
223  SDL_HapticRumbleInit(haptic);
224  else
225  LogF("Gamepad #%d %s does not support rumbling.", SDL_JoystickInstanceID(joystick), SDL_JoystickName(joystick));
226  break;
227  }
228 
229  if (!controller) LogF("Gamepad %d not available", iGamepad);
230 }
231 
233 {
234  if (haptic) SDL_HapticClose(haptic);
235  if (controller) SDL_GameControllerClose(controller);
236 }
237 
238 int32_t C4GamePadOpener::GetID()
239 {
240  return SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller));
241 }
242 
244 {
245  return !!SDL_GameControllerGetAttached(controller);
246 }
247 
248 void C4GamePadOpener::PlayRumble(float strength, uint32_t length)
249 {
250  if (SDL_HapticRumbleSupported(haptic))
251  SDL_HapticRumblePlay(haptic, strength, length);
252 }
253 
255 {
256  if (SDL_HapticRumbleSupported(haptic))
257  SDL_HapticRumbleStop(haptic);
258 }
259 
260 #else
261 
262 // Dedicated server and everything else with neither Win32 nor SDL.
263 
264 C4GamePadControl::C4GamePadControl() { Log("WARNING: Engine without Gamepad support"); }
269 std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetGamePad(int gamepad) { return nullptr; }
270 std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetGamePadByID(int32_t id) { return nullptr; }
271 std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetAvailableGamePad() { return nullptr; }
272 
275 int32_t C4GamePadOpener::GetID() { return -1; }
276 bool C4GamePadOpener::IsAttached() { return false; }
277 void C4GamePadOpener::PlayRumble(float strength, uint32_t length) { }
279 
280 #endif
C4Game Game
Definition: C4Globals.cpp:52
std::shared_ptr< C4GamePadOpener > GetAvailableGamePad()
uint8_t KEY_CONTROLLER_Button(uint8_t idx)
bool DoKeyboardInput(C4KeyCode vk_code, C4KeyEventType eEventType, bool fAlt, bool fCtrl, bool fShift, bool fRepeated, class C4GUI::Dialog *pForDialog=nullptr, bool fPlrCtrlOnly=false, int32_t iStrength=-1)
Definition: C4Game.cpp:1869
C4KeyCode KEY_Gamepad(uint8_t idButton)
C4GamePadOpener(int iGamePad)
void PlayRumble(float strength, uint32_t length)
uint8_t KEY_CONTROLLER_Axis(uint8_t idx, bool fMax)
C4KeyEventType
std::shared_ptr< C4GamePadOpener > GetGamePadByID(int32_t id)
bool Log(const char *szMessage)
Definition: C4Log.cpp:195
bool LogF(const char *strMessage,...)
Definition: C4Log.cpp:253
unsigned long C4KeyCode
std::shared_ptr< C4GamePadOpener > GetGamePad(int gamepad)
C4Application Application
Definition: C4Globals.cpp:44