1 #include "controllermanager.hpp" 2 3 #include <MyGUI_Button.h> 4 #include <MyGUI_InputManager.h> 5 #include <MyGUI_Widget.h> 6 7 #include <components/debug/debuglog.hpp> 8 9 #include "../mwbase/environment.hpp" 10 #include "../mwbase/inputmanager.hpp" 11 #include "../mwbase/statemanager.hpp" 12 #include "../mwbase/windowmanager.hpp" 13 #include "../mwbase/world.hpp" 14 15 #include "../mwworld/player.hpp" 16 17 #include "actions.hpp" 18 #include "actionmanager.hpp" 19 #include "bindingsmanager.hpp" 20 #include "mousemanager.hpp" 21 #include "sdlmappings.hpp" 22 23 namespace MWInput 24 { ControllerManager(BindingsManager * bindingsManager,ActionManager * actionManager,MouseManager * mouseManager,const std::string & userControllerBindingsFile,const std::string & controllerBindingsFile)25 ControllerManager::ControllerManager(BindingsManager* bindingsManager, 26 ActionManager* actionManager, 27 MouseManager* mouseManager, 28 const std::string& userControllerBindingsFile, 29 const std::string& controllerBindingsFile) 30 : mBindingsManager(bindingsManager) 31 , mActionManager(actionManager) 32 , mMouseManager(mouseManager) 33 , mJoystickEnabled (Settings::Manager::getBool("enable controller", "Input")) 34 , mGamepadCursorSpeed(Settings::Manager::getFloat("gamepad cursor speed", "Input")) 35 , mSneakToggleShortcutTimer(0.f) 36 , mGamepadZoom(0) 37 , mGamepadGuiCursorEnabled(true) 38 , mGuiCursorEnabled(true) 39 , mJoystickLastUsed(false) 40 , mSneakGamepadShortcut(false) 41 , mGamepadPreviewMode(false) 42 { 43 if (!controllerBindingsFile.empty()) 44 { 45 SDL_GameControllerAddMappingsFromFile(controllerBindingsFile.c_str()); 46 } 47 48 if (!userControllerBindingsFile.empty()) 49 { 50 SDL_GameControllerAddMappingsFromFile(userControllerBindingsFile.c_str()); 51 } 52 53 // Open all presently connected sticks 54 int numSticks = SDL_NumJoysticks(); 55 for (int i = 0; i < numSticks; i++) 56 { 57 if (SDL_IsGameController(i)) 58 { 59 SDL_ControllerDeviceEvent evt; 60 evt.which = i; 61 static const int fakeDeviceID = 1; 62 controllerAdded(fakeDeviceID, evt); 63 Log(Debug::Info) << "Detected game controller: " << SDL_GameControllerNameForIndex(i); 64 } 65 else 66 { 67 Log(Debug::Info) << "Detected unusable controller: " << SDL_JoystickNameForIndex(i); 68 } 69 } 70 71 float deadZoneRadius = Settings::Manager::getFloat("joystick dead zone", "Input"); 72 deadZoneRadius = std::min(std::max(deadZoneRadius, 0.0f), 0.5f); 73 mBindingsManager->setJoystickDeadZone(deadZoneRadius); 74 } 75 processChangedSettings(const Settings::CategorySettingVector & changed)76 void ControllerManager::processChangedSettings(const Settings::CategorySettingVector& changed) 77 { 78 for (const auto& setting : changed) 79 { 80 if (setting.first == "Input" && setting.second == "enable controller") 81 mJoystickEnabled = Settings::Manager::getBool("enable controller", "Input"); 82 } 83 } 84 update(float dt)85 bool ControllerManager::update(float dt) 86 { 87 mGamepadPreviewMode = mActionManager->isPreviewModeEnabled(); 88 89 if (mGuiCursorEnabled && !(mJoystickLastUsed && !mGamepadGuiCursorEnabled)) 90 { 91 float xAxis = mBindingsManager->getActionValue(A_MoveLeftRight) * 2.0f - 1.0f; 92 float yAxis = mBindingsManager->getActionValue(A_MoveForwardBackward) * 2.0f - 1.0f; 93 float zAxis = mBindingsManager->getActionValue(A_LookUpDown) * 2.0f - 1.0f; 94 95 xAxis *= (1.5f - mBindingsManager->getActionValue(A_Use)); 96 yAxis *= (1.5f - mBindingsManager->getActionValue(A_Use)); 97 98 // We keep track of our own mouse position, so that moving the mouse while in 99 // game mode does not move the position of the GUI cursor 100 float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor(); 101 float xMove = xAxis * dt * 1500.0f / uiScale * mGamepadCursorSpeed; 102 float yMove = yAxis * dt * 1500.0f / uiScale * mGamepadCursorSpeed; 103 104 float mouseWheelMove = -zAxis * dt * 1500.0f; 105 if (xMove != 0 || yMove != 0 || mouseWheelMove != 0) 106 { 107 mMouseManager->injectMouseMove(xMove, yMove, mouseWheelMove); 108 mMouseManager->warpMouse(); 109 MWBase::Environment::get().getWindowManager()->setCursorActive(true); 110 } 111 } 112 113 // Disable movement in Gui mode 114 if (MWBase::Environment::get().getWindowManager()->isGuiMode() 115 || MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running) 116 { 117 mGamepadZoom = 0; 118 return false; 119 } 120 121 MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); 122 bool triedToMove = false; 123 124 // Configure player movement according to controller input. Actual movement will 125 // be done in the physics system. 126 if (MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) 127 { 128 float xAxis = mBindingsManager->getActionValue(A_MoveLeftRight); 129 float yAxis = mBindingsManager->getActionValue(A_MoveForwardBackward); 130 if (xAxis != 0.5) 131 { 132 triedToMove = true; 133 player.setLeftRight((xAxis - 0.5f) * 2); 134 } 135 136 if (yAxis != 0.5) 137 { 138 triedToMove = true; 139 player.setAutoMove (false); 140 player.setForwardBackward((0.5f - yAxis) * 2); 141 } 142 143 if (triedToMove) 144 { 145 mJoystickLastUsed = true; 146 MWBase::Environment::get().getInputManager()->resetIdleTime(); 147 } 148 149 static const bool isToggleSneak = Settings::Manager::getBool("toggle sneak", "Input"); 150 if (!isToggleSneak) 151 { 152 if (mJoystickLastUsed) 153 { 154 if (mBindingsManager->actionIsActive(A_Sneak)) 155 { 156 if (mSneakToggleShortcutTimer) // New Sneak Button Press 157 { 158 if (mSneakToggleShortcutTimer <= 0.3f) 159 { 160 mSneakGamepadShortcut = true; 161 mActionManager->toggleSneaking(); 162 } 163 else 164 mSneakGamepadShortcut = false; 165 } 166 167 if (!mActionManager->isSneaking()) 168 mActionManager->toggleSneaking(); 169 mSneakToggleShortcutTimer = 0.f; 170 } 171 else 172 { 173 if (!mSneakGamepadShortcut && mActionManager->isSneaking()) 174 mActionManager->toggleSneaking(); 175 if (mSneakToggleShortcutTimer <= 0.3f) 176 mSneakToggleShortcutTimer += dt; 177 } 178 } 179 else 180 player.setSneak(mBindingsManager->actionIsActive(A_Sneak)); 181 } 182 } 183 184 if (MWBase::Environment::get().getInputManager()->getControlSwitch("playerviewswitch")) 185 { 186 if (!mBindingsManager->actionIsActive(A_TogglePOV)) 187 mGamepadZoom = 0; 188 189 if (mGamepadZoom) 190 MWBase::Environment::get().getWorld()->adjustCameraDistance(-mGamepadZoom); 191 } 192 193 return triedToMove; 194 } 195 buttonPressed(int deviceID,const SDL_ControllerButtonEvent & arg)196 void ControllerManager::buttonPressed(int deviceID, const SDL_ControllerButtonEvent &arg) 197 { 198 if (!mJoystickEnabled || mBindingsManager->isDetectingBindingState()) 199 return; 200 201 mJoystickLastUsed = true; 202 if (MWBase::Environment::get().getWindowManager()->isGuiMode()) 203 { 204 if (gamepadToGuiControl(arg)) 205 return; 206 207 if (mGamepadGuiCursorEnabled) 208 { 209 // Temporary mouse binding until keyboard controls are available: 210 if (arg.button == SDL_CONTROLLER_BUTTON_A) // We'll pretend that A is left click. 211 { 212 bool mousePressSuccess = mMouseManager->injectMouseButtonPress(SDL_BUTTON_LEFT); 213 if (MyGUI::InputManager::getInstance().getMouseFocusWidget()) 214 { 215 MyGUI::Button* b = MyGUI::InputManager::getInstance().getMouseFocusWidget()->castType<MyGUI::Button>(false); 216 if (b && b->getEnabled()) 217 MWBase::Environment::get().getWindowManager()->playSound("Menu Click"); 218 } 219 220 mBindingsManager->setPlayerControlsEnabled(!mousePressSuccess); 221 } 222 } 223 } 224 else 225 mBindingsManager->setPlayerControlsEnabled(true); 226 227 //esc, to leave initial movie screen 228 auto kc = sdlKeyToMyGUI(SDLK_ESCAPE); 229 mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyPress(kc, 0)); 230 231 if (!MWBase::Environment::get().getInputManager()->controlsDisabled()) 232 mBindingsManager->controllerButtonPressed(deviceID, arg); 233 } 234 buttonReleased(int deviceID,const SDL_ControllerButtonEvent & arg)235 void ControllerManager::buttonReleased(int deviceID, const SDL_ControllerButtonEvent &arg) 236 { 237 if (mBindingsManager->isDetectingBindingState()) 238 { 239 mBindingsManager->controllerButtonReleased(deviceID, arg); 240 return; 241 } 242 243 if (!mJoystickEnabled || MWBase::Environment::get().getInputManager()->controlsDisabled()) 244 return; 245 246 mJoystickLastUsed = true; 247 if (MWBase::Environment::get().getWindowManager()->isGuiMode()) 248 { 249 if (mGamepadGuiCursorEnabled) 250 { 251 // Temporary mouse binding until keyboard controls are available: 252 if (arg.button == SDL_CONTROLLER_BUTTON_A) // We'll pretend that A is left click. 253 { 254 bool mousePressSuccess = mMouseManager->injectMouseButtonRelease(SDL_BUTTON_LEFT); 255 if (mBindingsManager->isDetectingBindingState()) // If the player just triggered binding, don't let button release bind. 256 return; 257 258 mBindingsManager->setPlayerControlsEnabled(!mousePressSuccess); 259 } 260 } 261 } 262 else 263 mBindingsManager->setPlayerControlsEnabled(true); 264 265 //esc, to leave initial movie screen 266 auto kc = sdlKeyToMyGUI(SDLK_ESCAPE); 267 mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyRelease(kc)); 268 269 mBindingsManager->controllerButtonReleased(deviceID, arg); 270 } 271 axisMoved(int deviceID,const SDL_ControllerAxisEvent & arg)272 void ControllerManager::axisMoved(int deviceID, const SDL_ControllerAxisEvent &arg) 273 { 274 if (!mJoystickEnabled || MWBase::Environment::get().getInputManager()->controlsDisabled()) 275 return; 276 277 mJoystickLastUsed = true; 278 if (MWBase::Environment::get().getWindowManager()->isGuiMode()) 279 { 280 gamepadToGuiControl(arg); 281 } 282 else 283 { 284 if (mGamepadPreviewMode) // Preview Mode Gamepad Zooming 285 { 286 if (arg.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT) 287 { 288 mGamepadZoom = arg.value * 0.85f / 1000.f / 12.f; 289 return; // Do not propagate event. 290 } 291 else if (arg.axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT) 292 { 293 mGamepadZoom = -arg.value * 0.85f / 1000.f / 12.f; 294 return; // Do not propagate event. 295 } 296 } 297 } 298 mBindingsManager->controllerAxisMoved(deviceID, arg); 299 } 300 controllerAdded(int deviceID,const SDL_ControllerDeviceEvent & arg)301 void ControllerManager::controllerAdded(int deviceID, const SDL_ControllerDeviceEvent &arg) 302 { 303 mBindingsManager->controllerAdded(deviceID, arg); 304 } 305 controllerRemoved(const SDL_ControllerDeviceEvent & arg)306 void ControllerManager::controllerRemoved(const SDL_ControllerDeviceEvent &arg) 307 { 308 mBindingsManager->controllerRemoved(arg); 309 } 310 gamepadToGuiControl(const SDL_ControllerButtonEvent & arg)311 bool ControllerManager::gamepadToGuiControl(const SDL_ControllerButtonEvent &arg) 312 { 313 // Presumption of GUI mode will be removed in the future. 314 // MyGUI KeyCodes *may* change. 315 MyGUI::KeyCode key = MyGUI::KeyCode::None; 316 switch (arg.button) 317 { 318 case SDL_CONTROLLER_BUTTON_DPAD_UP: 319 key = MyGUI::KeyCode::ArrowUp; 320 break; 321 case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: 322 key = MyGUI::KeyCode::ArrowRight; 323 break; 324 case SDL_CONTROLLER_BUTTON_DPAD_DOWN: 325 key = MyGUI::KeyCode::ArrowDown; 326 break; 327 case SDL_CONTROLLER_BUTTON_DPAD_LEFT: 328 key = MyGUI::KeyCode::ArrowLeft; 329 break; 330 case SDL_CONTROLLER_BUTTON_A: 331 // If we are using the joystick as a GUI mouse, A must be handled via mouse. 332 if (mGamepadGuiCursorEnabled) 333 return false; 334 key = MyGUI::KeyCode::Space; 335 break; 336 case SDL_CONTROLLER_BUTTON_B: 337 if (MyGUI::InputManager::getInstance().isModalAny()) 338 MWBase::Environment::get().getWindowManager()->exitCurrentModal(); 339 else 340 MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); 341 return true; 342 case SDL_CONTROLLER_BUTTON_X: 343 key = MyGUI::KeyCode::Semicolon; 344 break; 345 case SDL_CONTROLLER_BUTTON_Y: 346 key = MyGUI::KeyCode::Apostrophe; 347 break; 348 case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: 349 key = MyGUI::KeyCode::Period; 350 break; 351 case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: 352 key = MyGUI::KeyCode::Slash; 353 break; 354 case SDL_CONTROLLER_BUTTON_LEFTSTICK: 355 mGamepadGuiCursorEnabled = !mGamepadGuiCursorEnabled; 356 MWBase::Environment::get().getWindowManager()->setCursorActive(mGamepadGuiCursorEnabled); 357 return true; 358 default: 359 return false; 360 } 361 362 // Some keys will work even when Text Input windows/modals are in focus. 363 if (SDL_IsTextInputActive()) 364 return false; 365 366 MWBase::Environment::get().getWindowManager()->injectKeyPress(key, 0, false); 367 return true; 368 } 369 gamepadToGuiControl(const SDL_ControllerAxisEvent & arg)370 bool ControllerManager::gamepadToGuiControl(const SDL_ControllerAxisEvent &arg) 371 { 372 switch (arg.axis) 373 { 374 case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: 375 if (arg.value == 32767) // Treat like a button. 376 MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Minus, 0, false); 377 break; 378 case SDL_CONTROLLER_AXIS_TRIGGERLEFT: 379 if (arg.value == 32767) // Treat like a button. 380 MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Equals, 0, false); 381 break; 382 case SDL_CONTROLLER_AXIS_LEFTX: 383 case SDL_CONTROLLER_AXIS_LEFTY: 384 case SDL_CONTROLLER_AXIS_RIGHTX: 385 case SDL_CONTROLLER_AXIS_RIGHTY: 386 // If we are using the joystick as a GUI mouse, process mouse movement elsewhere. 387 if (mGamepadGuiCursorEnabled) 388 return false; 389 break; 390 default: 391 return false; 392 } 393 394 return true; 395 } 396 } 397