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 }+/