1 /**
2  * @file
3  */
4 
5 /*
6 Copyright (C) 2002-2007 ioQuake3 team.
7 Copyright (C) 2002-2013 UFO: Alien Invasion.
8 
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 
18 See the GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23 
24 */
25 
26 #include "cl_joystick.h"
27 #include "../client.h"
28 #include "cl_input.h"
29 #include "../ui/ui_main.h"
30 #include "../ui/ui_nodes.h"
31 #include "../ui/node/ui_node_abstractoption.h"
32 
33 static SDL_Joystick* stick = nullptr;
34 static cvar_t* in_joystick;
35 static cvar_t* in_joystickNo;
36 static cvar_t* in_joystickThreshold;
37 static cvar_t* in_joystickSpeed;
38 
39 #if SDL_VERSION_ATLEAST(2,0,0)
40 #define SDL_JoystickName SDL_JoystickNameForIndex
41 #endif
42 
43 static struct {
44 	bool buttons[16];
45 	unsigned int oldaxes;
46 	unsigned int oldhats;
47 } stick_state;
48 
49 /* We translate axes movement into keypresses */
50 static const int joy_keys[16] = {
51 	K_LEFTARROW, K_RIGHTARROW,
52 	K_UPARROW, K_DOWNARROW,
53 	K_JOY16, K_JOY17,
54 	K_JOY18, K_JOY19,
55 	K_JOY20, K_JOY21,
56 	K_JOY22, K_JOY23,
57 
58 	K_JOY24, K_JOY25,
59 	K_JOY26, K_JOY27
60 };
61 
62 /* translate hat events into keypresses
63  * the 4 highest buttons are used for the first hat ... */
64 static const int hat_keys[16] = {
65 	K_JOY29, K_JOY30,
66 	K_JOY31, K_JOY32,
67 	K_JOY25, K_JOY26,
68 	K_JOY27, K_JOY28,
69 	K_JOY21, K_JOY22,
70 	K_JOY23, K_JOY24,
71 	K_JOY17, K_JOY18,
72 	K_JOY19, K_JOY20
73 };
74 
IN_JoystickMove(void)75 void IN_JoystickMove (void)
76 {
77 	bool joy_pressed[lengthof(joy_keys)];
78 	unsigned int axes = 0;
79 	unsigned int hats = 0;
80 	int total = 0;
81 	int i = 0;
82 
83 	/* check whether a user has changed the joystick number */
84 	if (in_joystickNo->modified)
85 		IN_StartupJoystick();
86 	/* check whether joysticks are disabled */
87 	if (!in_joystick->integer)
88 		return;
89 
90 	if (!stick)
91 		return;
92 
93 	SDL_JoystickUpdate();
94 
95 	OBJZERO(joy_pressed);
96 
97 	/* update the ball state */
98 	total = SDL_JoystickNumBalls(stick);
99 	if (total > 0) {
100 		int balldx = 0;
101 		int balldy = 0;
102 		for (i = 0; i < total; i++) {
103 			int dx = 0;
104 			int dy = 0;
105 			SDL_JoystickGetBall(stick, i, &dx, &dy);
106 			balldx += dx;
107 			balldy += dy;
108 		}
109 		if (balldx || balldy) {
110 			mousePosX = balldx / viddef.rx;
111 			mousePosY = balldy / viddef.ry;
112 		}
113 	}
114 
115 	/* now query the stick buttons... */
116 	total = SDL_JoystickNumButtons(stick);
117 	if (total > 0) {
118 		if (total > lengthof(stick_state.buttons))
119 			total = lengthof(stick_state.buttons);
120 		for (i = 0; i < total; i++) {
121 			const bool pressed = (SDL_JoystickGetButton(stick, i) != 0);
122 			if (pressed != stick_state.buttons[i]) {
123 				IN_EventEnqueue(K_JOY1 + i, 0, pressed);
124 				stick_state.buttons[i] = pressed;
125 			}
126 		}
127 	}
128 
129 	/* look at the hats... */
130 	total = SDL_JoystickNumHats(stick);
131 	if (total > 0) {
132 		if (total > 4)
133 			total = 4;
134 		for (i = 0; i < total; i++)
135 			((Uint8 *)&hats)[i] = SDL_JoystickGetHat(stick, i);
136 	}
137 
138 	/* update hat state */
139 	if (hats != stick_state.oldhats) {
140 		for (i = 0; i < 4; i++) {
141 			if (((Uint8 *)&hats)[i] != ((Uint8 *)&stick_state.oldhats)[i]) {
142 				/* release event */
143 				switch (((Uint8 *)&stick_state.oldhats)[i]) {
144 				case SDL_HAT_UP:
145 					IN_EventEnqueue(hat_keys[4 * i + 0], 0, false);
146 					break;
147 				case SDL_HAT_RIGHT:
148 					IN_EventEnqueue(hat_keys[4 * i + 1], 0, false);
149 					break;
150 				case SDL_HAT_DOWN:
151 					IN_EventEnqueue(hat_keys[4 * i + 2], 0, false);
152 					break;
153 				case SDL_HAT_LEFT:
154 					IN_EventEnqueue(hat_keys[4 * i + 3], 0, false);
155 					break;
156 				case SDL_HAT_RIGHTUP:
157 					IN_EventEnqueue(hat_keys[4 * i + 0], 0, false);
158 					IN_EventEnqueue(hat_keys[4 * i + 1], 0, false);
159 					break;
160 				case SDL_HAT_RIGHTDOWN:
161 					IN_EventEnqueue(hat_keys[4 * i + 2], 0, false);
162 					IN_EventEnqueue(hat_keys[4 * i + 1], 0, false);
163 					break;
164 				case SDL_HAT_LEFTUP:
165 					IN_EventEnqueue(hat_keys[4 * i + 0], 0, false);
166 					IN_EventEnqueue(hat_keys[4 * i + 3], 0, false);
167 					break;
168 				case SDL_HAT_LEFTDOWN:
169 					IN_EventEnqueue(hat_keys[4 * i + 2], 0, false);
170 					IN_EventEnqueue(hat_keys[4 * i + 3], 0, false);
171 					break;
172 				default:
173 					break;
174 				}
175 				/* press event */
176 				switch (((Uint8 *)&hats)[i]) {
177 				case SDL_HAT_UP:
178 					IN_EventEnqueue(hat_keys[4 * i + 0], 0, true);
179 					break;
180 				case SDL_HAT_RIGHT:
181 					IN_EventEnqueue(hat_keys[4 * i + 1], 0, true);
182 					break;
183 				case SDL_HAT_DOWN:
184 					IN_EventEnqueue(hat_keys[4 * i + 2], 0, true);
185 					break;
186 				case SDL_HAT_LEFT:
187 					IN_EventEnqueue(hat_keys[4 * i + 3], 0, true);
188 					break;
189 				case SDL_HAT_RIGHTUP:
190 					IN_EventEnqueue(hat_keys[4 * i + 0], 0, true);
191 					IN_EventEnqueue(hat_keys[4 * i + 1], 0, true);
192 					break;
193 				case SDL_HAT_RIGHTDOWN:
194 					IN_EventEnqueue(hat_keys[4 * i + 2], 0, true);
195 					IN_EventEnqueue(hat_keys[4 * i + 1], 0, true);
196 					break;
197 				case SDL_HAT_LEFTUP:
198 					IN_EventEnqueue(hat_keys[4 * i + 0], 0, true);
199 					IN_EventEnqueue(hat_keys[4 * i + 3], 0, true);
200 					break;
201 				case SDL_HAT_LEFTDOWN:
202 					IN_EventEnqueue(hat_keys[4 * i + 2], 0, true);
203 					IN_EventEnqueue(hat_keys[4 * i + 3], 0, true);
204 					break;
205 				default:
206 					break;
207 				}
208 			}
209 		}
210 	}
211 
212 	/* save hat state */
213 	stick_state.oldhats = hats;
214 
215 	/* finally, look at the axes... */
216 	total = SDL_JoystickNumAxes(stick);
217 	if (total >= 2) {
218 		/* the first two axes are used for the cursor movement */
219 		for (i = 0; i < 2; i++) {
220 			const Sint16 axis = SDL_JoystickGetAxis(stick, i);
221 			const float velocity = ((float) axis) / 32767.0f;
222 			if (velocity > -in_joystickThreshold->value && velocity < in_joystickThreshold->value)
223 				continue;
224 
225 			if (i & 1) {
226 				mousePosY += in_joystickSpeed->value * velocity;
227 				if (mousePosY > (int)viddef.context.height)
228 					mousePosY = (int)viddef.context.height;
229 				else if (mousePosY < 0)
230 					mousePosY = 0;
231 			} else {
232 				mousePosX += in_joystickSpeed->value * velocity;
233 				if (mousePosX > (int)viddef.context.width)
234 					mousePosX = (int)viddef.context.width;
235 				else if (mousePosX < 0)
236 					mousePosX = 0;
237 			}
238 		}
239 	}
240 
241 
242 	if (total > 2) {
243 		if (total > 16)
244 			total = 16;
245 		/* every axis except the first two can be normally bound to an action */
246 		for (i = 2; i < total; i++) {
247 			const Sint16 axis = SDL_JoystickGetAxis(stick, i);
248 			const float f = ((float) axis) / 32767.0f;
249 			if (f < -in_joystickThreshold->value) {
250 				axes |= (1 << (i * 2));
251 			} else if (f > in_joystickThreshold->value) {
252 				axes |= (1 << ((i * 2) + 1));
253 			}
254 		}
255 	}
256 
257 
258 	/* Time to update axes state based on old vs. new. */
259 	if (axes != stick_state.oldaxes) {
260 		for (i = 2; i < 16; i++) {
261 			if ((axes & (1 << i)) && !(stick_state.oldaxes & (1 << i)))
262 				IN_EventEnqueue(joy_keys[i], 0, true);
263 
264 			if (!(axes & (1 << i)) && (stick_state.oldaxes & (1 << i)))
265 				IN_EventEnqueue(joy_keys[i], 0, false);
266 		}
267 	}
268 
269 	/* Save for future generations. */
270 	stick_state.oldaxes = axes;
271 }
272 
273 /**
274  * @brief Adds joysticks to the options menu
275  */
IN_JoystickInitMenu(void)276 void IN_JoystickInitMenu (void)
277 {
278 	uiNode_t* joystickOptions = nullptr;
279 	const int total = SDL_NumJoysticks();
280 
281 	if (total == 0) {
282 		UI_AddOption(&joystickOptions, "", _("None"), "0");
283 	} else {
284 		for (int i = 0; i < total; i++)
285 			UI_AddOption(&joystickOptions, "", SDL_JoystickName(i), va("%i", i));
286 	}
287 	UI_RegisterOption(OPTION_JOYSTICKS, joystickOptions);
288 }
289 
290 /**
291  * @brief Init available joysticks
292  */
IN_StartupJoystick(void)293 void IN_StartupJoystick (void)
294 {
295 	int i = 0;
296 	int total = 0;
297 
298 	in_joystick = Cvar_Get("in_joystick", "0", CVAR_ARCHIVE, "Activate or deactivate the use of a joystick");
299 	in_joystickNo = Cvar_Get("in_joystickNo", "0", CVAR_ARCHIVE, "Joystick to use - 0 is the first - 1 is the second ...");
300 	in_joystickThreshold = Cvar_Get("in_joystickThreshold", "0.05", CVAR_ARCHIVE, "The threshold for the joystick axes");
301 	in_joystickSpeed = Cvar_Get("in_joystickSpeed", "20", CVAR_ARCHIVE, "The joystick speed for the cursor");
302 
303 	if (stick != nullptr) {
304 		Com_Printf("... closing already initialized joystick\n");
305 		SDL_JoystickClose(stick);
306 	}
307 
308 	stick = nullptr;
309 	OBJZERO(stick_state);
310 
311 	if (!SDL_WasInit(SDL_INIT_JOYSTICK)) {
312 		Com_DPrintf(DEBUG_CLIENT, "Calling SDL_Init(SDL_INIT_JOYSTICK)...\n");
313 		if (SDL_Init(SDL_INIT_JOYSTICK) == -1) {
314 			Com_DPrintf(DEBUG_CLIENT, "SDL_Init(SDL_INIT_JOYSTICK) failed: %s\n", SDL_GetError());
315 			return;
316 		}
317 		Com_DPrintf(DEBUG_CLIENT, "SDL_Init(SDL_INIT_JOYSTICK) passed.\n");
318 	}
319 
320 	total = SDL_NumJoysticks();
321 	Com_Printf("%d possible joysticks\n", total);
322 	for (i = 0; i < total; i++)
323 		Com_DPrintf(DEBUG_CLIENT, "[%d] %s\n", i, SDL_JoystickName(i));
324 
325 	if (in_joystickNo->integer < 0 || in_joystickNo->integer >= total)
326 		Cvar_Set("in_joystickNo", "0");
327 	in_joystickNo->modified = false;
328 
329 	stick = SDL_JoystickOpen(in_joystickNo->integer);
330 
331 	if (stick == nullptr) {
332 		Com_Printf("no joystick found.\n");
333 		return;
334 	}
335 
336 	Com_Printf("joystick %d opened - set cvar in_joystickNo to change this\n", in_joystickNo->integer);
337 	Com_Printf("... name: %s\n", SDL_JoystickName(in_joystickNo->integer));
338 	Com_Printf("... axes: %d\n", SDL_JoystickNumAxes(stick));
339 	Com_Printf("... hats: %d\n", SDL_JoystickNumHats(stick));
340 	Com_Printf("... buttons: %d\n", SDL_JoystickNumButtons(stick));
341 	Com_Printf("... balls: %d\n", SDL_JoystickNumBalls(stick));
342 
343 	SDL_JoystickEventState(SDL_QUERY);
344 }
345