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