1 /*
2 JOYSTICK_SDL.CPP
3 
4         Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc.
5         and the "Aleph One" developers.
6 
7         This program is free software; you can redistribute it and/or modify
8         it under the terms of the GNU General Public License as published by
9         the Free Software Foundation; either version 3 of the License, or
10         (at your option) any later version.
11 
12         This program is distributed in the hope that it will be useful,
13         but WITHOUT ANY WARRANTY; without even the implied warranty of
14         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15         GNU General Public License for more details.
16 
17         This license is contained in the file "COPYING",
18         which is included with this source code; it is available online at
19         http://www.gnu.org/licenses/gpl.html
20 
21 May 18, 2009 (Eric Peterson):
22     Initial revision.
23 
24 */
25 
26 #include <SDL.h>
27 #include <boost/ptr_container/ptr_map.hpp>
28 
29 #include "player.h" // for mask_in_absolute_positioning_information
30 #include "preferences.h"
31 #include "joystick.h"
32 #include "Logging.h"
33 #include "FileHandler.h"
34 
35 // internal handles
36 int joystick_active = true;
37 static boost::ptr_map<int, SDL_GameController*> active_instances;
38 int axis_values[SDL_CONTROLLER_AXIS_MAX] = {};
39 bool button_values[NUM_SDL_JOYSTICK_BUTTONS] = {};
40 
initialize_joystick(void)41 void initialize_joystick(void) {
42 	// Look for "gamecontrollerdb.txt" in default search path
43 	FileSpecifier fs;
44 	if (fs.SetNameWithPath("gamecontrollerdb.txt")) {
45 		SDL_GameControllerAddMappingsFromFile(fs.GetPath());
46 	}
47 
48 	SDL_GameControllerEventState(SDL_ENABLE);
49 	for (int i = 0; i < SDL_NumJoysticks(); ++i)
50 		joystick_added(i);
51 }
52 
enter_joystick(void)53 void enter_joystick(void) {
54 	joystick_active = true;
55 }
56 
exit_joystick(void)57 void exit_joystick(void) {
58 	joystick_active = false;
59 }
60 
joystick_added(int device_index)61 void joystick_added(int device_index) {
62 	if (!SDL_IsGameController(device_index)) {
63 		SDL_Joystick *joystick = SDL_JoystickOpen(device_index);
64 		char guidStr[255] = "";
65 		SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(joystick), guidStr, 255);
66 		logWarning("No mapping found for controller \"%s\" (%s)",
67 				   SDL_JoystickName(joystick), guidStr);
68 		return;
69 	}
70 	SDL_GameController *controller = SDL_GameControllerOpen(device_index);
71 	if (!controller)
72 		return;
73 	int instance_id = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller));
74 	active_instances[instance_id] = controller;
75 }
76 
joystick_removed(int instance_id)77 void joystick_removed(int instance_id) {
78 	SDL_GameController *controller = active_instances[instance_id];
79 	if (controller) {
80 		SDL_GameControllerClose(controller);
81 		active_instances.erase(instance_id);
82 	}
83 }
84 
joystick_axis_moved(int instance_id,int axis,int value)85 void joystick_axis_moved(int instance_id, int axis, int value) {
86 	switch (axis) {
87 		case SDL_CONTROLLER_AXIS_LEFTY:
88 		case SDL_CONTROLLER_AXIS_RIGHTY:
89 			// flip Y axes to better match default movement
90 			axis_values[axis] = value * -1;
91 			break;
92 		default:
93 			axis_values[axis] = value;
94 			break;
95 	}
96 	button_values[AO_SCANCODE_BASE_JOYSTICK_AXIS_POSITIVE - AO_SCANCODE_BASE_JOYSTICK_BUTTON + axis] = (value >= 16384);
97 	button_values[AO_SCANCODE_BASE_JOYSTICK_AXIS_NEGATIVE - AO_SCANCODE_BASE_JOYSTICK_BUTTON + axis] = (value <= -16384);
98 }
joystick_button_pressed(int instance_id,int button,bool down)99 void joystick_button_pressed(int instance_id, int button, bool down) {
100 	if (button >= 0 && button < NUM_SDL_JOYSTICK_BUTTONS)
101 		button_values[button] = down;
102 }
103 
104 enum {
105 	_flags_yaw,
106 	_flags_pitch,
107 	NUMBER_OF_ABSOLUTE_POSITION_VALUES
108 };
109 
110 typedef struct AxisInfo {
111 	int key_binding_index;
112 	int abs_pos_index;
113 	bool negative;
114 
115 } AxisInfo;
116 
117 static const std::vector<AxisInfo> axis_mappings = {
118 	{ 2, _flags_yaw, true },
119 	{ 3, _flags_yaw, false },
120 	{ 8, _flags_pitch, true },
121 	{ 9, _flags_pitch, false }
122 };
123 
124 static const float axis_limits[NUMBER_OF_ABSOLUTE_POSITION_VALUES] = {
125 	0.5f - 1.f / (1<<ABSOLUTE_YAW_BITS),
126 	0.5f - 1.f / (1<<ABSOLUTE_PITCH_BITS)
127 };
128 
axis_mapped_to_action(int action,bool * negative)129 static int axis_mapped_to_action(int action, bool* negative) {
130 	auto codeset = input_preferences->key_bindings[action];
131 	for (auto it = codeset.begin(); it != codeset.end(); ++it) {
132 		const SDL_Scancode code = *it;
133 
134 		if (code < AO_SCANCODE_BASE_JOYSTICK_AXIS_POSITIVE)
135 			continue;
136 		if (code > (AO_SCANCODE_BASE_JOYSTICK_BUTTON + NUM_SDL_JOYSTICK_BUTTONS))
137 			continue;
138 
139 		*negative = false;
140 		int axis = code - AO_SCANCODE_BASE_JOYSTICK_AXIS_POSITIVE;
141 		if (code >= AO_SCANCODE_BASE_JOYSTICK_AXIS_NEGATIVE) {
142 			*negative = true;
143 			axis = code - AO_SCANCODE_BASE_JOYSTICK_AXIS_NEGATIVE;
144 		}
145 		return axis;
146 	}
147 	return -1;
148 }
149 
joystick_buttons_become_keypresses(Uint8 * ioKeyMap)150 void joystick_buttons_become_keypresses(Uint8* ioKeyMap) {
151     // if we're not using the joystick, avoid this
152     if (!joystick_active)
153         return;
154 	if (active_instances.empty())
155 		return;
156 
157 	std::set<int> buttons_to_avoid;
158 	if (input_preferences->controller_analog) {
159 		// avoid setting buttons mapped to analog aiming
160 		for (auto it = axis_mappings.begin(); it != axis_mappings.end(); ++it) {
161 			const AxisInfo info = *it;
162 			bool negative = false;
163 			int axis = axis_mapped_to_action(info.key_binding_index, &negative);
164 			if (axis >= 0) {
165 				buttons_to_avoid.insert(axis + (negative ? AO_SCANCODE_BASE_JOYSTICK_AXIS_NEGATIVE : AO_SCANCODE_BASE_JOYSTICK_AXIS_POSITIVE));
166 			}
167 		}
168 	}
169 
170 	for (int i = 0; i < NUM_SDL_JOYSTICK_BUTTONS; ++i) {
171 		int code = AO_SCANCODE_BASE_JOYSTICK_BUTTON + i;
172 		if (buttons_to_avoid.count(code) == 0)
173 			ioKeyMap[code] = button_values[i];
174     }
175 
176     return;
177 }
178 
process_joystick_axes(int flags,int tick)179 int process_joystick_axes(int flags, int tick) {
180     if (!joystick_active)
181         return flags;
182 	if (active_instances.empty())
183 		return flags;
184 	if (!input_preferences->controller_analog)
185 		return flags;
186 
187 	float angular_deltas[NUMBER_OF_ABSOLUTE_POSITION_VALUES] = { 0, 0 };
188 	for (auto it = axis_mappings.begin(); it != axis_mappings.end(); ++it) {
189 		const AxisInfo info = *it;
190 		bool negative = false;
191 		int axis = axis_mapped_to_action(info.key_binding_index, &negative);
192 		if (axis < 0)
193 			continue;
194 
195 		int val = axis_values[axis] * (negative ? -1 : 1);
196 		if (val > input_preferences->controller_deadzone) {
197 			float norm = val/32767.f * (static_cast<float>(input_preferences->controller_sensitivity) / FIXED_ONE);
198 			const float angle_per_norm = 768/63.f;
199 			angular_deltas[info.abs_pos_index] += norm * (info.negative ? -1.0 : 1.0) * angle_per_norm;
200 		}
201 	}
202 
203 	// return this tick's action flags augmented with movement data
204 	const fixed_angle dyaw = static_cast<fixed_angle>(angular_deltas[_flags_yaw] * FIXED_ONE);
205 	const fixed_angle dpitch = static_cast<fixed_angle>(angular_deltas[_flags_pitch] * FIXED_ONE);
206 	if (dyaw != 0 || dpitch != 0)
207 		flags = process_aim_input(flags, {dyaw, dpitch});
208 	return flags;
209 }
210