1 /**
2     Controller
3 
4     Copyright: (c) Enalye 2017
5     License: Zlib
6     Authors: Enalye
7 */
8 
9 module atelier.common.controller;
10 
11 import bindbc.sdl;
12 import atelier.core;
13 import atelier.common.resource;
14 
15 import std..string;
16 import std.file : exists;
17 import std.stdio : writeln, printf;
18 import std.path;
19 
20 /// List of controller buttons.
21 enum ControllerButton {
22     unknown = SDL_CONTROLLER_BUTTON_INVALID,
23     a = SDL_CONTROLLER_BUTTON_A,
24     b = SDL_CONTROLLER_BUTTON_B,
25     x = SDL_CONTROLLER_BUTTON_X,
26     y = SDL_CONTROLLER_BUTTON_Y,
27     back = SDL_CONTROLLER_BUTTON_BACK,
28     guide = SDL_CONTROLLER_BUTTON_GUIDE,
29     start = SDL_CONTROLLER_BUTTON_START,
30     leftStick = SDL_CONTROLLER_BUTTON_LEFTSTICK,
31     rightStick = SDL_CONTROLLER_BUTTON_RIGHTSTICK,
32     leftShoulder = SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
33     rightShoulder = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
34     up = SDL_CONTROLLER_BUTTON_DPAD_UP,
35     down = SDL_CONTROLLER_BUTTON_DPAD_DOWN,
36     left = SDL_CONTROLLER_BUTTON_DPAD_LEFT,
37     right = SDL_CONTROLLER_BUTTON_DPAD_RIGHT
38 }
39 
40 /// List of controller axis.
41 enum ControllerAxis {
42     unknown = SDL_CONTROLLER_AXIS_INVALID,
43     leftX = SDL_CONTROLLER_AXIS_LEFTX,
44     leftY = SDL_CONTROLLER_AXIS_LEFTY,
45     rightX = SDL_CONTROLLER_AXIS_RIGHTX,
46     rightY = SDL_CONTROLLER_AXIS_RIGHTY,
47     leftTrigger = SDL_CONTROLLER_AXIS_TRIGGERLEFT,
48     rightTrigger = SDL_CONTROLLER_AXIS_TRIGGERRIGHT
49 }
50 
51 private struct Controller {
52     SDL_GameController* sdlController;
53     SDL_Joystick* sdlJoystick;
54     int index, joystickId;
55 }
56 
57 private {
58     Controller[] _controllers;
59     Timer[6] _analogTimers, _analogTimeoutTimers;
60     bool[ControllerButton.max + 1] _buttons1, _buttons2;
61     float[ControllerAxis.max + 1] _axis = 0f;
62 }
63 
64 /// Open all the connected controllers
65 void initializeControllers() {
66     foreach (index; 0 .. SDL_NumJoysticks())
67         addController(index);
68     SDL_GameControllerEventState(SDL_ENABLE);
69 }
70 
71 /// Close all the connected controllers
72 void destroyControllers() {
73     foreach (ref controller; _controllers)
74         SDL_GameControllerClose(controller.sdlController);
75 }
76 
77 /// Register all controller definitions in a file, must be a valid format.
78 void addControllerMappingsFromFile(string filePath) {
79     if (!exists(filePath))
80         throw new Exception("Could not find \'" ~ filePath ~ "\'.");
81     if (-1 == SDL_GameControllerAddMappingsFromFile(toStringz(filePath)))
82         throw new Exception("Invalid mapping file \'" ~ filePath ~ "\'.");
83 }
84 
85 /// Register a controller definition, must be a valid format.
86 void addControllerMapping(string mapping) {
87     if (-1 == SDL_GameControllerAddMapping(toStringz(mapping)))
88         throw new Exception("Invalid mapping.");
89 }
90 
91 /// Update the state of the controllers
92 void updateControllers(float deltaTime) {
93     foreach (axisIndex; 0 .. 4) {
94         _analogTimers[axisIndex].update(deltaTime);
95         _analogTimeoutTimers[axisIndex].update(deltaTime);
96     }
97 }
98 
99 /// Attempt to connect a new controller
100 void addController(int index) {
101     //writeln("Detected device at index ", index, ".");
102 
103     auto c = SDL_JoystickNameForIndex(index);
104     auto d = fromStringz(c);
105     //writeln("Device name: ", d);
106 
107     if (!SDL_IsGameController(index)) {
108         //writeln("The device is not recognised as a game controller.");
109         auto stick = SDL_JoystickOpen(index);
110         auto guid = SDL_JoystickGetGUID(stick);
111         //writeln("The device guid is: ");
112         //foreach (i; 0 .. 16)
113         //    printf("%02x", guid.data[i]);
114         //writeln("");
115         return;
116     }
117     //writeln("The device has been detected as a game controller.");
118     foreach (ref controller; _controllers) {
119         if (controller.index == index) {
120             //writeln("The controller is already open, aborted.");
121             return;
122         }
123     }
124 
125     auto sdlController = SDL_GameControllerOpen(index);
126     if (!sdlController) {
127         //writeln("Could not connect the game controller.");
128         return;
129     }
130 
131     Controller controller;
132     controller.sdlController = sdlController;
133     controller.index = index;
134     controller.sdlJoystick = SDL_GameControllerGetJoystick(controller.sdlController);
135     controller.joystickId = SDL_JoystickInstanceID(controller.sdlJoystick);
136     _controllers ~= controller;
137 
138     //writeln("The game controller is now connected.");
139 }
140 
141 /// Remove a connected controller
142 void removeController(int joystickId) {
143     //writeln("Controller disconnected: ", joystickId);
144 
145     int index;
146     bool isControllerPresent;
147     foreach (ref controller; _controllers) {
148         if (controller.joystickId == joystickId) {
149             isControllerPresent = true;
150             break;
151         }
152         index++;
153     }
154 
155     if (!isControllerPresent)
156         return;
157 
158     SDL_GameControllerClose(_controllers[index].sdlController);
159 
160     //Remove from list
161     if (index + 1 == _controllers.length)
162         _controllers.length--;
163     else if (index == 0)
164         _controllers = _controllers[1 .. $];
165     else
166         _controllers = _controllers[0 .. index] ~ _controllers[(index + 1) .. $];
167 }
168 
169 /// Called upon remapping
170 void remapController(int joystickId) {
171     //writeln("Controller remapped: ", joystickId);
172 }
173 
174 /// Change the value of a controller axis.
175 void setControllerAxis(SDL_GameControllerAxis axis, short value) {
176     if (axis > ControllerAxis.max)
177         return;
178     const auto v = rlerp(-32_768, 32_767, cast(float) value) * 2f - 1f;
179     _axis[axis] = v;
180 }
181 
182 /// Handle the timing of the axis
183 private bool updateAnalogTimer(int axisIndex, float x, float y) {
184     if (axisIndex == -1)
185         return false;
186 
187     enum deadzone = .5f;
188     if ((x < deadzone && x > -deadzone) && (y < deadzone && y > -deadzone)) {
189         _analogTimeoutTimers[axisIndex].stop();
190         return false;
191     }
192     else {
193         if (_analogTimers[axisIndex].isRunning)
194             return false;
195         _analogTimers[axisIndex].start(_analogTimeoutTimers[axisIndex].isRunning ? .15f : .35f);
196         _analogTimeoutTimers[axisIndex].start(5f);
197     }
198     return true;
199 }
200 
201 /// Change the value of a controller button.
202 void setControllerButton(SDL_GameControllerButton button, bool state) {
203     if (button > ControllerButton.max)
204         return;
205     _buttons1[button] = state;
206     _buttons2[button] = state;
207 }
208 
209 /// Check whether the button associated with the ID is pressed. \
210 /// Do not reset the value.
211 bool isButtonDown(ControllerButton button) {
212     return _buttons1[button];
213 }
214 
215 /// Check whether the button associated with the ID is pressed. \
216 /// This function resets the value to false.
217 bool getButtonDown(ControllerButton button) {
218     const bool value = _buttons2[button];
219     _buttons2[button] = false;
220     return value;
221 }
222 
223 /// Return the current state of the axis.
224 float getAxis(ControllerAxis axis) {
225     return _axis[axis];
226 }
227 /+
228 /// Returns the left stick x-axis as a button.
229 bool getControllerInputSingleLeftX() {
230     return updateAnalogTimer(0, _left.x, 0f);
231 }
232 
233 /// Returns the left stick y-axis as a button.
234 bool getControllerInputSingleLeftY() {
235     return updateAnalogTimer(1, 0f, _left.y);
236 }
237 
238 /// Returns the left stick x and y axis as a button.
239 bool getControllerInputSingleLeftXY() {
240     return updateAnalogTimer(2, _left.x, _left.y);
241 }
242 
243 /// Returns the right stick x-axis as a button.
244 bool getControllerInputSingleRightX() {
245     return updateAnalogTimer(3, _right.x, 0f);
246 }
247 
248 /// Returns the right stick y-axis as a button.
249 bool getControllerInputSingleRightY() {
250     return updateAnalogTimer(4, 0f, _right.y);
251 }
252 
253 /// Returns the right stick x and y axis as a button.
254 bool getControllerInputSingleRightXY() {
255     return updateAnalogTimer(5, _right.x, _right.y);
256 }+/