OpenClonk
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 "game/C4Application.h"
25 #include "object/C4ObjectCom.h"
26 
27 #if defined(HAVE_SDL) && !defined(USE_CONSOLE)
28 
29 #include <SDL.h>
30 
32 {
33  // SDL2 will only report events when the window has focus, so set
34  // this hint as we don't have a window
35  SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
36 
37  if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC | SDL_INIT_EVENTS) != 0)
38  LogF("SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER): %s", SDL_GetError());
39  SDL_GameControllerEventState(SDL_ENABLE);
40  if (!GetGamePadCount()) Log("No Gamepad found");
41 }
42 
44 {
45  // All gamepads have to be released before quitting SDL.
46  Gamepads.clear();
47  SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_EVENTS);
48 }
49 
51 {
52 #ifdef USE_SDL_MAINLOOP
53  if (!Application.isEditor) return;
54 #endif
55  SDL_Event event;
56  while (SDL_PollEvent(&event))
57  {
58  switch (event.type)
59  {
60  case SDL_CONTROLLERAXISMOTION:
61  case SDL_CONTROLLERBUTTONDOWN:
62  case SDL_CONTROLLERBUTTONUP:
63  FeedEvent(event, FEED_BUTTONS);
64  break;
65  case SDL_JOYDEVICEADDED:
66  case SDL_CONTROLLERDEVICEADDED:
67  case SDL_CONTROLLERDEVICEREMOVED:
68  CheckGamePad(event);
69  break;
70  }
71  }
72 }
73 
74 namespace
75 {
76  const int deadZone = 16000;
77 
78  // Axis strength uses the full signed 16 bit integer range. As we're
79  // splitting axes in left/right and up/down, it's preferable to have
80  // symmetrical ranges [0, 2^15 - 1] in both directions.
81  inline int32_t abs_strength(int32_t strength)
82  {
83  return strength >= 0 ? strength : -(strength + 1);
84  }
85 }
86 
87 void C4GamePadControl::FeedEvent(const SDL_Event& event, int feed)
88 {
89  switch (event.type)
90  {
91  case SDL_CONTROLLERAXISMOTION:
92  {
93  C4KeyCode minCode = KEY_Gamepad(KEY_CONTROLLER_Axis(event.caxis.axis, false));
94  C4KeyCode maxCode = KEY_Gamepad(KEY_CONTROLLER_Axis(event.caxis.axis, true));
95  int32_t value = abs_strength(event.caxis.value);
96  uint8_t which = event.caxis.which;
97  C4KeyCode keyCode = event.caxis.value >= 0 ? maxCode : minCode;
98 
99  auto doInput = [&](C4KeyEventType event, int32_t strength)
100  {
102  C4KeyCodeEx(KEY_Gamepad(keyCode), KEYS_None, false, which),
103  event, nullptr, false, strength);
104  };
105 
106  if (feed & FEED_BUTTONS)
107  {
108  // Also emulate button presses.
109  if (PressedAxis.count(keyCode) && value <= deadZone)
110  {
111  PressedAxis.erase(keyCode);
112  doInput(KEYEV_Up, -1);
113  }
114  else if (!PressedAxis.count(keyCode) && value > deadZone)
115  {
116  PressedAxis.insert(keyCode);
117  doInput(KEYEV_Down, -1);
118  }
119  }
120  if (feed & FEED_MOVED)
121  doInput(KEYEV_Moved, value);
122 
123  AxisEvents[keyCode] = event;
124 
125  break;
126  }
127  case SDL_CONTROLLERBUTTONDOWN:
128  if (feed & FEED_BUTTONS)
130  C4KeyCodeEx(KEY_Gamepad(KEY_CONTROLLER_Button(event.cbutton.button)), KEYS_None, false, event.cbutton.which),
131  KEYEV_Down);
132  break;
133  case SDL_CONTROLLERBUTTONUP:
134  if (feed & FEED_BUTTONS)
136  C4KeyCodeEx(KEY_Gamepad(KEY_CONTROLLER_Button(event.cbutton.button)), KEYS_None, false, event.cbutton.which),
137  KEYEV_Up);
138  break;
139  }
140 }
141 
142 void C4GamePadControl::CheckGamePad(const SDL_Event& e)
143 {
144  switch (e.type)
145  {
146  case SDL_JOYDEVICEADDED:
147  // Report that an unsupported joystick device has been detected, to help with controller issues.
148  if (!SDL_IsGameController(e.jdevice.which))
149  LogF("Gamepad %s isn't supported.", SDL_JoystickNameForIndex(e.jdevice.which));
150  break;
151  case SDL_CONTROLLERDEVICEADDED:
152  {
153  auto device = std::make_shared<C4GamePadOpener>(e.cdevice.which);
154  Gamepads[device->GetID()] = device;
155  LogF("Gamepad #%d connected: %s", device->GetID(), SDL_JoystickNameForIndex(e.cdevice.which));
156  break;
157  }
158  case SDL_CONTROLLERDEVICEREMOVED:
159  LogF("Gamepad #%d disconnected.", e.cdevice.which);
160  Gamepads.erase(e.cdevice.which);
161  break;
162  }
163 }
164 
166 {
167  for (auto const &e : AxisEvents)
168  {
169  FeedEvent(e.second, FEED_MOVED);
170  }
171  AxisEvents.clear();
172 }
173 
175 {
176  // Not all Joysticks are game controllers.
177  int count = 0;
178  for (int i = 0; i < SDL_NumJoysticks(); i++)
179  if (SDL_IsGameController(i))
180  count++;
181  return count;
182 }
183 
184 std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetGamePad(int gamepad)
185 {
186  if (gamepad >= 0)
187  for (const auto& p : Gamepads)
188  if (gamepad-- == 0)
189  return p.second;
190  return nullptr;
191 }
192 
193 std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetGamePadByID(int32_t id)
194 {
195  auto it = Gamepads.find(id);
196  if (it != Gamepads.end())
197  return it->second;
198  return nullptr;
199 }
200 
201 std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetAvailableGamePad()
202 {
203  for (const auto& p : Gamepads)
204  if (p.second->GetPlayer() < 0)
205  return p.second;
206  return nullptr;
207 }
208 
210 {
211  int n = iGamepad;
212  for (int i = 0; i < SDL_NumJoysticks(); i++)
213  if (SDL_IsGameController(i) && n-- == 0)
214  {
215  controller = SDL_GameControllerOpen(i);
216  if (!controller) LogF("SDL: %s", SDL_GetError());
217  SDL_Joystick *joystick = SDL_GameControllerGetJoystick(controller);
218  haptic = SDL_HapticOpenFromJoystick(joystick);
219  if (haptic && SDL_HapticRumbleSupported(haptic))
220  SDL_HapticRumbleInit(haptic);
221  else
222  LogF("Gamepad #%d %s does not support rumbling.", SDL_JoystickInstanceID(joystick), SDL_JoystickName(joystick));
223  break;
224  }
225 
226  if (!controller) LogF("Gamepad %d not available", iGamepad);
227 }
228 
230 {
231  if (haptic) SDL_HapticClose(haptic);
232  if (controller) SDL_GameControllerClose(controller);
233 }
234 
235 int32_t C4GamePadOpener::GetID()
236 {
237  return SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller));
238 }
239 
241 {
242  return !!SDL_GameControllerGetAttached(controller);
243 }
244 
245 void C4GamePadOpener::PlayRumble(float strength, uint32_t length)
246 {
247  if (SDL_HapticRumbleSupported(haptic))
248  SDL_HapticRumblePlay(haptic, strength, length);
249 }
250 
252 {
253  if (SDL_HapticRumbleSupported(haptic))
254  SDL_HapticRumbleStop(haptic);
255 }
256 
257 #else
258 
259 // Dedicated server and everything else with neither Win32 nor SDL.
260 
261 C4GamePadControl::C4GamePadControl() { Log("WARNING: Engine without Gamepad support"); }
266 std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetGamePad(int gamepad) { return nullptr; }
267 std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetGamePadByID(int32_t id) { return nullptr; }
268 std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetAvailableGamePad() { return nullptr; }
269 
272 int32_t C4GamePadOpener::GetID() { return -1; }
273 bool C4GamePadOpener::IsAttached() { return false; }
274 void C4GamePadOpener::PlayRumble(float strength, uint32_t length) { }
276 
277 #endif
C4Game Game
Definition: C4Globals.cpp:52
C4Application Application
Definition: C4Globals.cpp:44
C4KeyCode KEY_Gamepad(uint8_t idButton)
uint8_t KEY_CONTROLLER_Button(uint8_t idx)
@ KEYS_None
C4KeyEventType
@ KEYEV_Up
@ KEYEV_Moved
@ KEYEV_Down
uint8_t KEY_CONTROLLER_Axis(uint8_t idx, bool fMax)
unsigned long C4KeyCode
bool Log(const char *szMessage)
Definition: C4Log.cpp:204
bool LogF(const char *strMessage,...)
Definition: C4Log.cpp:262
bool DoKeyboardInput(C4KeyCode vk_code, C4KeyEventType event_type, bool alt, bool ctrl, bool shift, bool repeated, class C4GUI::Dialog *for_dialog=nullptr, bool fPlrCtrlOnly=false, int32_t strength=-1)
Definition: C4Game.cpp:2288
std::shared_ptr< C4GamePadOpener > GetGamePad(int gamepad)
std::shared_ptr< C4GamePadOpener > GetAvailableGamePad()
std::shared_ptr< C4GamePadOpener > GetGamePadByID(int32_t id)
C4GamePadOpener(int iGamePad)
void PlayRumble(float strength, uint32_t length)