Merge button and stick input gracefully in GameControllerManager
[supertux.git] / src / control / joystick_manager.cpp
1 //  SuperTux
2 //  Copyright (C) 2013 Ingo Ruhnke <grumbel@gmx.de>
3 //
4 //  This program is free software: you can redistribute it and/or modify
5 //  it under the terms of the GNU General Public License as published by
6 //  the Free Software Foundation, either version 3 of the License, or
7 //  (at your option) any later version.
8 //
9 //  This program is distributed in the hope that it will be useful,
10 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //  GNU General Public License for more details.
13 //
14 //  You should have received a copy of the GNU General Public License
15 //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 #include "control/joystick_manager.hpp"
18
19 #include <iostream>
20 #include <algorithm>
21
22 #include "control/input_manager.hpp"
23 #include "lisp/list_iterator.hpp"
24 #include "supertux/menu/joystick_menu.hpp"
25 #include "supertux/menu/menu_storage.hpp"
26 #include "util/gettext.hpp"
27 #include "util/log.hpp"
28 #include "util/writer.hpp"
29
30 JoystickManager::JoystickManager(InputManager* parent) :
31   parent(parent),
32   joy_button_map(),
33   joy_axis_map(),
34   joy_hat_map(),
35   dead_zone(8000),
36   min_joybuttons(),
37   max_joybuttons(),
38   max_joyaxis(),
39   max_joyhats(),
40   hat_state(0),
41   jump_with_up_joy(false),
42   wait_for_joystick(-1),
43   joysticks()
44 {
45   // Default joystick button configuration
46   bind_joybutton(0, 0, Controller::JUMP);
47   bind_joybutton(0, 1, Controller::ACTION);
48   // 6 or more Buttons
49   if( min_joybuttons > 5 ){
50     bind_joybutton(0, 4, Controller::PEEK_LEFT);
51     bind_joybutton(0, 5, Controller::PEEK_RIGHT);
52     // 8 or more
53     if(min_joybuttons > 7)
54       bind_joybutton(0, min_joybuttons-1, Controller::PAUSE_MENU);
55   } else {
56     // map the last 2 buttons to menu and pause
57     if(min_joybuttons > 2)
58       bind_joybutton(0, min_joybuttons-1, Controller::PAUSE_MENU);
59     // map all remaining joystick buttons to MENU_SELECT
60     for(int i = 2; i < max_joybuttons; ++i) {
61       if(i != min_joybuttons-1)
62         bind_joybutton(0, i, Controller::MENU_SELECT);
63     }
64   }
65
66   // Default joystick axis configuration
67   bind_joyaxis(0, -1, Controller::LEFT);
68   bind_joyaxis(0, 1, Controller::RIGHT);
69   bind_joyaxis(0, -2, Controller::UP);
70   bind_joyaxis(0, 2, Controller::DOWN);
71 }
72
73 JoystickManager::~JoystickManager()
74 {
75   for(auto joy : joysticks)
76   {
77     SDL_JoystickClose(joy);
78   }
79 }
80
81 void
82 JoystickManager::on_joystick_added(int joystick_index)
83 {
84   std::cout << "joydeviceadded: " << joystick_index << std::endl;
85   SDL_Joystick* joystick = SDL_JoystickOpen(joystick_index);
86   if (!joystick)
87   {
88     log_warning << "failed to open joystick: " << joystick_index
89                 << ": " << SDL_GetError() << std::endl;
90   }
91   else
92   {
93     joysticks.push_back(joystick);
94   }
95
96   if(min_joybuttons < 0 || SDL_JoystickNumButtons(joystick) < min_joybuttons)
97     min_joybuttons = SDL_JoystickNumButtons(joystick);
98
99   if(SDL_JoystickNumButtons(joystick) > max_joybuttons)
100     max_joybuttons = SDL_JoystickNumButtons(joystick);
101
102   if(SDL_JoystickNumAxes(joystick) > max_joyaxis)
103     max_joyaxis = SDL_JoystickNumAxes(joystick);
104
105   if(SDL_JoystickNumHats(joystick) > max_joyhats)
106     max_joyhats = SDL_JoystickNumHats(joystick);
107 }
108
109 void
110 JoystickManager::on_joystick_removed(int instance_id)
111 {
112   std::cout << "joydeviceremoved: " << static_cast<int>(instance_id) << std::endl;
113   for(auto& joy : joysticks)
114   {
115     SDL_JoystickID id = SDL_JoystickInstanceID(joy);
116     if (id == instance_id)
117     {
118       SDL_JoystickClose(joy);
119       joy = nullptr;
120     }
121   }
122
123   joysticks.erase(std::remove(joysticks.begin(), joysticks.end(), nullptr),
124                   joysticks.end());
125 }
126
127 void
128 JoystickManager::process_hat_event(const SDL_JoyHatEvent& jhat)
129 {
130   Uint8 changed = hat_state ^ jhat.value;
131
132   if (wait_for_joystick >= 0)
133   {
134     if (changed & SDL_HAT_UP && jhat.value & SDL_HAT_UP)
135       bind_joyhat(jhat.which, SDL_HAT_UP, Controller::Control(wait_for_joystick));
136
137     if (changed & SDL_HAT_DOWN && jhat.value & SDL_HAT_DOWN)
138       bind_joyhat(jhat.which, SDL_HAT_DOWN, Controller::Control(wait_for_joystick));
139
140     if (changed & SDL_HAT_LEFT && jhat.value & SDL_HAT_LEFT)
141       bind_joyhat(jhat.which, SDL_HAT_LEFT, Controller::Control(wait_for_joystick));
142
143     if (changed & SDL_HAT_RIGHT && jhat.value & SDL_HAT_RIGHT)
144       bind_joyhat(jhat.which, SDL_HAT_RIGHT, Controller::Control(wait_for_joystick));
145
146     MenuStorage::get_joystick_options_menu()->update();
147     wait_for_joystick = -1;
148   }
149   else
150   {
151     if (changed & SDL_HAT_UP)
152     {
153       HatMap::iterator it = joy_hat_map.find(std::make_pair(jhat.which, SDL_HAT_UP));
154       if (it != joy_hat_map.end())
155         set_joy_controls(it->second, jhat.value & SDL_HAT_UP);
156     }
157
158     if (changed & SDL_HAT_DOWN)
159     {
160       HatMap::iterator it = joy_hat_map.find(std::make_pair(jhat.which, SDL_HAT_DOWN));
161       if (it != joy_hat_map.end())
162         set_joy_controls(it->second, jhat.value & SDL_HAT_DOWN);
163     }
164
165     if (changed & SDL_HAT_LEFT)
166     {
167       HatMap::iterator it = joy_hat_map.find(std::make_pair(jhat.which, SDL_HAT_LEFT));
168       if (it != joy_hat_map.end())
169         set_joy_controls(it->second, jhat.value & SDL_HAT_LEFT);
170     }
171
172     if (changed & SDL_HAT_RIGHT)
173     {
174       HatMap::iterator it = joy_hat_map.find(std::make_pair(jhat.which, SDL_HAT_RIGHT));
175       if (it != joy_hat_map.end())
176         set_joy_controls(it->second, jhat.value & SDL_HAT_RIGHT);
177     }
178   }
179
180   hat_state = jhat.value;
181 }
182
183 void
184 JoystickManager::process_axis_event(const SDL_JoyAxisEvent& jaxis)
185 {
186   if (wait_for_joystick >= 0)
187   {
188     if (abs(jaxis.value) > dead_zone) {
189       if (jaxis.value < 0)
190         bind_joyaxis(jaxis.which, -(jaxis.axis + 1), Controller::Control(wait_for_joystick));
191       else
192         bind_joyaxis(jaxis.which, jaxis.axis + 1, Controller::Control(wait_for_joystick));
193
194       MenuStorage::get_joystick_options_menu()->update();
195       wait_for_joystick = -1;
196     }
197   }
198   else
199   {
200     // Split the axis into left and right, so that both can be
201     // mapped separately (needed for jump/down vs up/down)
202     int axis = jaxis.axis + 1;
203
204     AxisMap::iterator left  = joy_axis_map.find(std::make_pair(jaxis.which, -axis));
205     AxisMap::iterator right = joy_axis_map.find(std::make_pair(jaxis.which, axis));
206
207     if(left == joy_axis_map.end()) {
208       // std::cout << "Unmapped joyaxis " << (int)jaxis.axis << " moved" << std::endl;
209     } else {
210       if (jaxis.value < -dead_zone)
211         set_joy_controls(left->second,  true);
212       else
213         set_joy_controls(left->second, false);
214     }
215
216     if(right == joy_axis_map.end()) {
217       // std::cout << "Unmapped joyaxis " << (int)jaxis.axis << " moved" << std::endl;
218     } else {
219       if (jaxis.value > dead_zone)
220         set_joy_controls(right->second, true);
221       else
222         set_joy_controls(right->second, false);
223     }
224   }
225 }
226
227 void
228 JoystickManager::process_button_event(const SDL_JoyButtonEvent& jbutton)
229 {
230   if(wait_for_joystick >= 0)
231   {
232     if(jbutton.state == SDL_PRESSED)
233     {
234       bind_joybutton(jbutton.which, jbutton.button, (Controller::Control)wait_for_joystick);
235       MenuStorage::get_joystick_options_menu()->update();
236       parent->reset();
237       wait_for_joystick = -1;
238     }
239   }
240   else
241   {
242     ButtonMap::iterator i = joy_button_map.find(std::make_pair(jbutton.which, jbutton.button));
243     if(i == joy_button_map.end()) {
244       log_debug << "Unmapped joybutton " << (int)jbutton.button << " pressed" << std::endl;
245     } else {
246       set_joy_controls(i->second, (jbutton.state == SDL_PRESSED));
247     }
248   }
249 }
250
251
252 int
253 JoystickManager::reversemap_joyaxis(Controller::Control c)
254 {
255   for(AxisMap::iterator i = joy_axis_map.begin(); i != joy_axis_map.end(); ++i) {
256     if(i->second == c)
257       return i->first.second;
258   }
259
260   return 0;
261 }
262
263 int
264 JoystickManager::reversemap_joybutton(Controller::Control c)
265 {
266   for(ButtonMap::iterator i = joy_button_map.begin(); i != joy_button_map.end(); ++i) {
267     if(i->second == c)
268       return i->first.second;
269   }
270
271   return -1;
272 }
273
274 int
275 JoystickManager::reversemap_joyhat(Controller::Control c)
276 {
277   for(HatMap::iterator i = joy_hat_map.begin(); i != joy_hat_map.end(); ++i) {
278     if(i->second == c)
279       return i->first.second;
280   }
281
282   return -1;
283 }
284
285 void
286 JoystickManager::print_joystick_mappings()
287 {
288   std::cout << _("Joystick Mappings") << std::endl;
289   std::cout << "-----------------" << std::endl;
290   for(AxisMap::iterator i = joy_axis_map.begin(); i != joy_axis_map.end(); ++i) {
291     std::cout << "Axis: " << i->first.second << " -> " << i->second << std::endl;
292   }
293
294   for(ButtonMap::iterator i = joy_button_map.begin(); i != joy_button_map.end(); ++i) {
295     std::cout << "Button: " << i->first.second << " -> " << i->second << std::endl;
296   }
297
298   for(HatMap::iterator i = joy_hat_map.begin(); i != joy_hat_map.end(); ++i) {
299     std::cout << "Hat: " << i->first.second << " -> " << i->second << std::endl;
300   }
301   std::cout << std::endl;
302 }
303
304 void
305 JoystickManager::unbind_joystick_control(Controller::Control control)
306 {
307   // remove all previous mappings for that control
308   for(AxisMap::iterator i = joy_axis_map.begin(); i != joy_axis_map.end(); /* no ++i */) {
309     if(i->second == control)
310       joy_axis_map.erase(i++);
311     else
312       ++i;
313   }
314
315   for(ButtonMap::iterator i = joy_button_map.begin(); i != joy_button_map.end(); /* no ++i */) {
316     if(i->second == control)
317       joy_button_map.erase(i++);
318     else
319       ++i;
320   }
321
322   for(HatMap::iterator i = joy_hat_map.begin();  i != joy_hat_map.end(); /* no ++i */) {
323     if(i->second == control)
324       joy_hat_map.erase(i++);
325     else
326       ++i;
327   }
328 }
329
330 void
331 JoystickManager::bind_joyaxis(JoyId joy_id, int axis, Controller::Control control)
332 {
333   // axis isn't the SDL axis number, but axisnumber + 1 with sign
334   // changed depending on if the positive or negative end is to be
335   // used (negative axis 0 becomes -1, positive axis 2 becomes +3,
336   // etc.)
337
338   unbind_joystick_control(control);
339
340   // add new mapping
341   joy_axis_map[std::make_pair(joy_id, axis)] = control;
342 }
343
344 void
345 JoystickManager::bind_joyhat(JoyId joy_id, int dir, Controller::Control c)
346 {
347   unbind_joystick_control(c);
348
349   // add new mapping
350   joy_hat_map[std::make_pair(joy_id, dir)] = c;
351 }
352
353 void
354 JoystickManager::bind_joybutton(JoyId joy_id, int button, Controller::Control control)
355 {
356   unbind_joystick_control(control);
357
358   // add new mapping
359   joy_button_map[std::make_pair(joy_id, button)] = control;
360 }
361
362 void
363 JoystickManager::read(const lisp::Lisp* joystick_lisp)
364 {
365   joystick_lisp->get("dead-zone", dead_zone);
366   joystick_lisp->get("jump-with-up", jump_with_up_joy);
367   lisp::ListIterator iter(joystick_lisp);
368   while(iter.next()) {
369     if(iter.item() == _("map")) {
370       int button = -1;
371       int axis   = 0;
372       int hat    = -1;
373       std::string control;
374       const lisp::Lisp* map = iter.lisp();
375
376       map->get("control", control);
377       int i = 0;
378       for(i = 0; Controller::controlNames[i] != 0; ++i) {
379         if(control == Controller::controlNames[i])
380           break;
381       }
382       if(Controller::controlNames[i] == 0) {
383         log_info << "Invalid control '" << control << "' in buttonmap" << std::endl;
384         continue;
385       }
386
387       bool js_available = joysticks.size() > 0;
388
389       if (map->get("button", button)) {
390         if(js_available && (button < 0 || button >= max_joybuttons)) {
391           log_info << "Invalid button '" << button << "' in buttonmap" << std::endl;
392           continue;
393         }
394         bind_joybutton(0, button, Controller::Control(i));
395       }
396
397       if (map->get("axis",   axis)) {
398         if (js_available && (axis == 0 || abs(axis) > max_joyaxis)) {
399           log_info << "Invalid axis '" << axis << "' in axismap" << std::endl;
400           continue;
401         }
402         bind_joyaxis(0, axis, Controller::Control(i));
403       }
404
405       if (map->get("hat",   hat)) {
406         if (js_available        &&
407             hat != SDL_HAT_UP   &&
408             hat != SDL_HAT_DOWN &&
409             hat != SDL_HAT_LEFT &&
410             hat != SDL_HAT_RIGHT) {
411           log_info << "Invalid axis '" << axis << "' in axismap" << std::endl;
412           continue;
413         } else {
414           bind_joyhat(0, hat, Controller::Control(i));
415         }
416       }
417     }
418   }
419 }
420
421 void
422 JoystickManager::write(Writer& writer)
423 {
424   writer.write("dead-zone", dead_zone);
425   writer.write("jump-with-up", jump_with_up_joy);
426
427   for(ButtonMap::iterator i = joy_button_map.begin(); i != joy_button_map.end();
428       ++i) {
429     writer.start_list("map");
430     writer.write("button", i->first.second);
431     writer.write("control", Controller::controlNames[i->second]);
432     writer.end_list("map");
433   }
434
435   for(HatMap::iterator i = joy_hat_map.begin(); i != joy_hat_map.end(); ++i) {
436     writer.start_list("map");
437     writer.write("hat", i->first.second);
438     writer.write("control", Controller::controlNames[i->second]);
439     writer.end_list("map");
440   }
441
442   for(AxisMap::iterator i = joy_axis_map.begin(); i != joy_axis_map.end(); ++i) {
443     writer.start_list("map");
444     writer.write("axis", i->first.second);
445     writer.write("control", Controller::controlNames[i->second]);
446     writer.end_list("map");
447   }
448 }
449
450 void
451 JoystickManager::set_joy_controls(Controller::Control id, bool value)
452 {
453   if (jump_with_up_joy && id == Controller::UP)
454   {
455     parent->get_controller()->set_control(Controller::JUMP, value);
456   }
457
458   parent->get_controller()->set_control(id, value);
459 }
460
461 /* EOF */