1 /*
2 C-Dogs SDL
3 A port of the legendary (and fun) action/arcade cdogs.
4
5 Copyright (c) 2013-2015, 2018 Cong Xu
6 All rights reserved.
7
8 Redistribution and use in source and binary forms, with or without
9 modification, are permitted provided that the following conditions are met:
10
11 Redistributions of source code must retain the above copyright notice, this
12 list of conditions and the following disclaimer.
13 Redistributions in binary form must reproduce the above copyright notice,
14 this list of conditions and the following disclaimer in the documentation
15 and/or other materials provided with the distribution.
16
17 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 POSSIBILITY OF SUCH DAMAGE.
28 */
29 #include "joystick.h"
30
31 #include <string.h>
32 #include <stdio.h>
33
34 #include "defs.h"
35 #include "events.h"
36 #include "log.h"
37 #include "SDL_JoystickButtonNames/SDL_joystickbuttonnames.h"
38
39
JoyInit(CArray * joys)40 void JoyInit(CArray *joys)
41 {
42 char buf[CDOGS_PATH_MAX];
43 GetDataFilePath(buf, "data/gamecontrollerdb.txt");
44 if (SDL_GameControllerAddMappingsFromFile(buf) == -1)
45 {
46 LOG(LM_INPUT, LL_ERROR, "cannot load controller mappings file: %s",
47 SDL_GetError());
48 }
49 GetDataFilePath(buf, "data/gamecontrollerbuttondb.txt");
50 if (SDLJBN_AddMappingsFromFile(buf) == -1)
51 {
52 LOG(LM_INPUT, LL_ERROR, "cannot load button mappings file: %s",
53 SDLJBN_GetError());
54 }
55
56 CArrayInit(joys, sizeof(Joystick));
57
58 // Detect all current controllers
59 const int n = SDL_NumJoysticks();
60 LOG(LM_INPUT, LL_INFO, "%d controllers found", n);
61 if (n == 0)
62 {
63 return;
64 }
65 for (int i = 0; i < n; i++)
66 {
67 JoyAdded(i);
68 }
69 }
70
JoyReset(CArray * joys)71 void JoyReset(CArray *joys)
72 {
73 CA_FOREACH(Joystick, j, *joys)
74 j->currentCmd = j->pressedCmd = j->previousCmd = 0;
75 CA_FOREACH_END()
76 }
77
JoyTerminateOne(Joystick * j)78 static void JoyTerminateOne(Joystick *j)
79 {
80 SDL_GameControllerClose(j->gc);
81 SDL_HapticClose(j->haptic);
82 }
83
JoyTerminate(CArray * joys)84 void JoyTerminate(CArray *joys)
85 {
86 CA_FOREACH(Joystick, j, *joys)
87 JoyTerminateOne(j);
88 CA_FOREACH_END()
89 CArrayTerminate(joys);
90 }
91
GetJoystick(const SDL_JoystickID id)92 static Joystick *GetJoystick(const SDL_JoystickID id)
93 {
94 CA_FOREACH(Joystick, j, gEventHandlers.joysticks)
95 if (j->id == id) return j;
96 CA_FOREACH_END()
97 return NULL;
98 }
99
JoyIsDown(const SDL_JoystickID id,const int cmd)100 bool JoyIsDown(const SDL_JoystickID id, const int cmd)
101 {
102 return !!(GetJoystick(id)->currentCmd & cmd);
103 }
104
JoyIsPressed(const SDL_JoystickID id,const int cmd)105 bool JoyIsPressed(const SDL_JoystickID id, const int cmd)
106 {
107 return JoyIsDown(id, cmd) && !(GetJoystick(id)->previousCmd & cmd);
108 }
109
JoyGetPressed(const SDL_JoystickID id)110 int JoyGetPressed(const SDL_JoystickID id)
111 {
112 const Joystick *j = GetJoystick(id);
113 // Pressed is current and not previous, so take bitwise complement
114 return j->currentCmd & ~j->previousCmd;
115 }
116
JoyPrePoll(CArray * joys)117 void JoyPrePoll(CArray *joys)
118 {
119 CA_FOREACH(Joystick, j, *joys)
120 j->pressedCmd = 0;
121 j->previousCmd = j->currentCmd;
122 CA_FOREACH_END()
123 }
124
JoyAdded(const Sint32 which)125 SDL_JoystickID JoyAdded(const Sint32 which)
126 {
127 if (!SDL_IsGameController(which))
128 {
129 return -1;
130 }
131
132 Joystick j;
133 memset(&j, 0, sizeof j);
134 j.hapticEffectId = -1;
135 j.gc = SDL_GameControllerOpen(which);
136 if (j.gc == NULL)
137 {
138 LOG(LM_INPUT, LL_ERROR, "Failed to open game controller: %s",
139 SDL_GetError());
140 return -1;
141 }
142 j.j = SDL_GameControllerGetJoystick(j.gc);
143 if (j.j == NULL)
144 {
145 LOG(LM_INPUT, LL_ERROR, "Failed to open joystick: %s",
146 SDL_GetError());
147 return -1;
148 }
149 j.id = SDL_JoystickInstanceID(j.j);
150 if (j.id == -1)
151 {
152 LOG(LM_INPUT, LL_ERROR, "Failed to get joystick instance ID: %s",
153 SDL_GetError());
154 return -1;
155 }
156 const int isHaptic = SDL_JoystickIsHaptic(j.j);
157 if (isHaptic > 0)
158 {
159 j.haptic = SDL_HapticOpenFromJoystick(j.j);
160 if (j.haptic == NULL)
161 {
162 LOG(LM_INPUT, LL_ERROR, "Failed to open haptic: %s",
163 SDL_GetError());
164 }
165 else
166 {
167 if (SDL_HapticRumbleInit(j.haptic) != 0)
168 {
169 LOG(LM_INPUT, LL_ERROR, "Failed to init rumble: %s",
170 SDL_GetError());
171 }
172 const int hapticQuery = SDL_HapticQuery(j.haptic);
173 LOG(LM_INPUT, LL_INFO, "Haptic support: %x", hapticQuery);
174 if (hapticQuery & SDL_HAPTIC_CONSTANT)
175 {
176 SDL_HapticEffect he;
177 memset(&he, 0, sizeof he);
178 he.type = SDL_HAPTIC_CONSTANT;
179 he.constant.length = 100;
180 he.constant.level = 20000; // 20000/32767 strength
181 j.hapticEffectId = SDL_HapticNewEffect(j.haptic, &he);
182 if (j.hapticEffectId == -1)
183 {
184 LOG(LM_INPUT, LL_ERROR,
185 "Failed to create haptic effect: %s", SDL_GetError());
186 }
187 }
188 }
189 }
190 else if (isHaptic < 0)
191 {
192 LOG(LM_INPUT, LL_ERROR, "Failed to query haptic: %s", SDL_GetError());
193 }
194 CArrayPushBack(&gEventHandlers.joysticks, &j);
195 LOG(LM_INPUT, LL_INFO, "Added joystick index %d id %d", which, j.id);
196 return j.id;
197 }
JoyRemoved(const Sint32 which)198 void JoyRemoved(const Sint32 which)
199 {
200 LOG(LM_INPUT, LL_INFO, "Removed joystick id %d", which);
201 CA_FOREACH(Joystick, j, gEventHandlers.joysticks)
202 if (j->id == which)
203 {
204 JoyTerminateOne(j);
205 CArrayDelete(&gEventHandlers.joysticks, _ca_index);
206 return;
207 }
208 CA_FOREACH_END()
209 CASSERT(false, "Cannot find joystick");
210 }
211
212 static void JoyOnCmd(Joystick *j, const int cmd, const bool isDown);
213
214 int ControllerButtonToCmd(const Uint8 button);
JoyOnButtonDown(const SDL_ControllerButtonEvent e)215 void JoyOnButtonDown(const SDL_ControllerButtonEvent e)
216 {
217 LOG(LM_INPUT, LL_DEBUG, "Joystick %d button down %d", e.which, e.button);
218 JoyOnCmd(GetJoystick(e.which), ControllerButtonToCmd(e.button), true);
219 }
JoyOnButtonUp(const SDL_ControllerButtonEvent e)220 void JoyOnButtonUp(const SDL_ControllerButtonEvent e)
221 {
222 LOG(LM_INPUT, LL_DEBUG, "Joystick %d button up %d", e.which, e.button);
223 JoyOnCmd(GetJoystick(e.which), ControllerButtonToCmd(e.button), false);
224 }
ControllerButtonToCmd(const Uint8 button)225 int ControllerButtonToCmd(const Uint8 button)
226 {
227 switch (button)
228 {
229 case SDL_CONTROLLER_BUTTON_A: return CMD_BUTTON1;
230 case SDL_CONTROLLER_BUTTON_B: return CMD_BUTTON2;
231 case SDL_CONTROLLER_BUTTON_BACK: return CMD_MAP;
232 case SDL_CONTROLLER_BUTTON_START: return CMD_ESC;
233 case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: return CMD_GRENADE;
234 case SDL_CONTROLLER_BUTTON_DPAD_UP: return CMD_UP;
235 case SDL_CONTROLLER_BUTTON_DPAD_DOWN: return CMD_DOWN;
236 case SDL_CONTROLLER_BUTTON_DPAD_LEFT: return CMD_LEFT;
237 case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return CMD_RIGHT;
238 default: return 0;
239 }
240 }
241 #define DEADZONE 16384
JoyOnAxis(const SDL_ControllerAxisEvent e)242 void JoyOnAxis(const SDL_ControllerAxisEvent e)
243 {
244 LOG(LM_INPUT, LL_DEBUG, "Joystick %d received axis %d, %d",
245 e.which, e.axis, e.value);
246 Joystick *j = GetJoystick(e.which);
247 switch (e.axis)
248 {
249 case SDL_CONTROLLER_AXIS_LEFTX:
250 JoyOnCmd(j, CMD_LEFT, e.value < -DEADZONE);
251 JoyOnCmd(j, CMD_RIGHT, e.value > DEADZONE);
252 break;
253 case SDL_CONTROLLER_AXIS_LEFTY:
254 JoyOnCmd(j, CMD_UP, e.value < -DEADZONE);
255 JoyOnCmd(j, CMD_DOWN, e.value > DEADZONE);
256 break;
257 case SDL_CONTROLLER_AXIS_TRIGGERRIGHT:
258 // Right trigger fire
259 JoyOnCmd(j, CMD_BUTTON1, e.value > DEADZONE);
260 break;
261 default:
262 // Ignore axis
263 break;
264 }
265 }
266
JoyOnCmd(Joystick * j,const int cmd,const bool isDown)267 static void JoyOnCmd(Joystick *j, const int cmd, const bool isDown)
268 {
269 if (isDown)
270 {
271 j->currentCmd |= cmd;
272 }
273 else
274 {
275 if ((j->currentCmd & cmd) && !(j->previousCmd & cmd))
276 {
277 j->pressedCmd |= cmd;
278 }
279 j->currentCmd &= ~cmd;
280 }
281 }
282
JoyRumble(const SDL_JoystickID id,const float strength,const Uint32 length)283 void JoyRumble(
284 const SDL_JoystickID id, const float strength, const Uint32 length)
285 {
286 Joystick *j = GetJoystick(id);
287 if (j->haptic == NULL) return;
288 if (SDL_HapticRumblePlay(j->haptic, strength, length) < 0)
289 {
290 LOG(LM_INPUT, LL_ERROR, "Failed to rumble: %s", SDL_GetError());
291 }
292 }
JoyImpact(const SDL_JoystickID id)293 void JoyImpact(const SDL_JoystickID id)
294 {
295 Joystick *j = GetJoystick(id);
296 if (j->hapticEffectId != -1 &&
297 SDL_HapticRunEffect(j->haptic, j->hapticEffectId, 1) != 0)
298 {
299 LOG(LM_INPUT, LL_ERROR, "Failed to run haptic effect: %s",
300 SDL_GetError());
301 }
302 }
303
JoyName(const SDL_JoystickID id)304 const char *JoyName(const SDL_JoystickID id)
305 {
306 const Joystick *j = GetJoystick(id);
307 const char *controllerName = SDL_GameControllerName(j->gc);
308 const char *joystickName = SDL_JoystickName(j->j);
309 // Try to use the more specific name
310 if (joystickName != NULL &&
311 strcmp(controllerName, "Generic DirectInput Controller") == 0)
312 {
313 return joystickName;
314 }
315 return controllerName;
316 }
317
318 static int CmdToControllerButton(const int cmd);
JoyButtonNameColor(const SDL_JoystickID id,const int cmd,char * buf,color_t * color)319 void JoyButtonNameColor(
320 const SDL_JoystickID id, const int cmd, char *buf, color_t *color)
321 {
322 Joystick *j = GetJoystick(id);
323 const int button = CmdToControllerButton(cmd);
324 int res;
325 if (color != NULL)
326 {
327 res = SDLJBN_GetButtonNameAndColor(
328 j->j, button, buf, &color->r, &color->g, &color->b);
329 }
330 else
331 {
332 res = SDLJBN_GetButtonName(j->j, button, buf);
333 }
334 if (res < 0)
335 {
336 LOG(LM_INPUT, LL_WARN, "Could not get button name/colour: %s",
337 SDLJBN_GetError());
338 strcpy(buf, "?");
339 }
340 }
CmdToControllerButton(const int cmd)341 static int CmdToControllerButton(const int cmd)
342 {
343 switch (cmd)
344 {
345 case CMD_BUTTON1: return SDL_CONTROLLER_BUTTON_A;
346 case CMD_BUTTON2: return SDL_CONTROLLER_BUTTON_B;
347 case CMD_GRENADE: return SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
348 case CMD_MAP: return SDL_CONTROLLER_BUTTON_BACK;
349 case CMD_ESC: return SDL_CONTROLLER_BUTTON_START;
350 case CMD_UP: return SDL_CONTROLLER_BUTTON_DPAD_UP;
351 case CMD_DOWN: return SDL_CONTROLLER_BUTTON_DPAD_DOWN;
352 case CMD_LEFT: return SDL_CONTROLLER_BUTTON_DPAD_LEFT;
353 case CMD_RIGHT: return SDL_CONTROLLER_BUTTON_DPAD_RIGHT;
354 default: return SDL_CONTROLLER_BUTTON_INVALID;
355 }
356 }
357