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