Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/consolecommands3/src/libraries/core/input/KeyBinder.cc @ 7220

Last change on this file since 7220 was 6536, checked in by rgrieder, 15 years ago

Merged revisions 6430-6440 from the gamestate branch to the trunk.
This adds keybindings merging functionality.

(from log of r6437)
When running development builds, the keybinder will merge the local file and the one from the data folder.
Catch: if you want to remove a binding, you'll have to write "NoBinding" (not case sensitive) to override the default command

The keybind command already does that for you though.

  • Property svn:eol-style set to native
File size: 21.7 KB
Line 
1/*
2 *   ORXONOX - the hottest 3D action shooter ever to exist
3 *                    > www.orxonox.net <
4 *
5 *
6 *   License notice:
7 *
8 *   This program is free software; you can redistribute it and/or
9 *   modify it under the terms of the GNU General Public License
10 *   as published by the Free Software Foundation; either version 2
11 *   of the License, or (at your option) any later version.
12 *
13 *   This program is distributed in the hope that it will be useful,
14 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *   GNU General Public License for more details.
17 *
18 *   You should have received a copy of the GNU General Public License
19 *   along with this program; if not, write to the Free Software
20 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 *
22 *   Author:
23 *      Reto Grieder
24 *   Co-authors:
25 *      ...
26 *
27 */
28
29#include "KeyBinder.h"
30
31#include <algorithm>
32#include <sstream>
33#include "util/Convert.h"
34#include "util/Debug.h"
35#include "util/Exception.h"
36#include "core/ConfigValueIncludes.h"
37#include "core/CoreIncludes.h"
38#include "core/ConfigFileManager.h"
39#include "core/PathConfig.h"
40#include "InputCommands.h"
41#include "JoyStick.h"
42
43namespace orxonox
44{
45    /**
46    @brief
47        Constructor that does as little as necessary.
48    */
49    KeyBinder::KeyBinder(const std::string& filename)
50        : deriveTime_(0.0f)
51        , filename_(filename)
52        , configFile_(NULL)
53        , fallbackConfigFile_(NULL)
54    {
55        mouseRelative_[0] = 0;
56        mouseRelative_[1] = 0;
57        mousePosition_[0] = 0.0;
58        mousePosition_[1] = 0.0;
59
60        RegisterRootObject(KeyBinder);
61
62        // initialise all buttons and half axes to avoid creating everything with 'new'
63        // keys
64        for (unsigned int i = 0; i < KeyCode::numberOfKeys; i++)
65        {
66            const std::string& keyname = KeyCode::ByString[i];
67            if (!keyname.empty())
68                keys_[i].name_ = std::string("Key") + keyname;
69            else
70                keys_[i].name_.clear();
71            keys_[i].paramCommandBuffer_ = &paramCommandBuffer_;
72            keys_[i].groupName_ = "Keys";
73        }
74        // mouse buttons plus 4 mouse wheel buttons only 'generated' by KeyBinder
75        const char* const mouseWheelNames[] = { "Wheel1Down", "Wheel1Up", "Wheel2Down", "Wheel2Up" };
76        for (unsigned int i = 0; i < numberOfMouseButtons_; i++)
77        {
78            std::string nameSuffix;
79            if (i < MouseButtonCode::numberOfButtons)
80                nameSuffix = MouseButtonCode::ByString[i];
81            else
82                nameSuffix = mouseWheelNames[i - MouseButtonCode::numberOfButtons];
83            mouseButtons_[i].name_ = nameSuffix;
84            mouseButtons_[i].paramCommandBuffer_ = &paramCommandBuffer_;
85            mouseButtons_[i].groupName_ = "MouseButtons";
86        }
87        // mouse axes
88        for (unsigned int i = 0; i < MouseAxisCode::numberOfAxes * 2; i++)
89        {
90            mouseAxes_[i].name_ = MouseAxisCode::ByString[i / 2];
91            if (i & 1)
92                mouseAxes_[i].name_ += "Pos";
93            else
94                mouseAxes_[i].name_ += "Neg";
95            mouseAxes_[i].paramCommandBuffer_ = &paramCommandBuffer_;
96            mouseAxes_[i].groupName_ = "MouseAxes";
97        }
98
99        // initialise joy sticks separatly to allow for reloading
100        this->JoyStickQuantityChanged(this->getJoyStickList());
101
102        // set them here to use allHalfAxes_
103        setConfigValues();
104
105        // Load the bindings if filename was given
106        if (!this->filename_.empty())
107            this->loadBindings();
108    }
109
110    /**
111    @brief
112        Destructor
113    */
114    KeyBinder::~KeyBinder()
115    {
116        // almost no destructors required because most of the arrays are static.
117        clearBindings(); // does some destruction work
118        if (this->configFile_)
119            delete this->configFile_;
120        if (this->fallbackConfigFile_)
121            delete this->fallbackConfigFile_;
122    }
123
124    /**
125    @brief
126        Loader for the key bindings, managed by config values.
127    */
128    void KeyBinder::setConfigValues()
129    {
130        SetConfigValue(analogThreshold_, 0.05f)
131            .description("Threshold for analog axes until which the state is 0.");
132        SetConfigValue(bFilterAnalogNoise_, false)
133            .description("Specifies whether to filter small analog values like joy stick fluctuations.");
134        SetConfigValue(mouseSensitivity_, 3.0f)
135            .description("Mouse sensitivity.");
136        this->totalMouseSensitivity_ = this->mouseSensitivity_ / this->mouseClippingSize_;
137        SetConfigValue(bDeriveMouseInput_, false)
138            .description("Whether or not to derive moues movement for the absolute value.");
139        SetConfigValue(derivePeriod_, 0.05f)
140            .description("Accuracy of the mouse input deriver. The higher the more precise, but laggier.");
141        SetConfigValue(mouseSensitivityDerived_, 1.0f)
142            .description("Mouse sensitivity if mouse input is derived.");
143        SetConfigValue(mouseWheelStepSize_, 120)
144            .description("Equals one step of the mousewheel.");
145        SetConfigValue(buttonThreshold_, 0.80f)
146            .description("Threshold for analog axes until which the button is not pressed.")
147            .callback(this, &KeyBinder::buttonThresholdChanged);
148    }
149
150    void KeyBinder::buttonThresholdChanged()
151    {
152        for (unsigned int i = 0; i < allHalfAxes_.size(); i++)
153            if (!allHalfAxes_[i]->bButtonThresholdUser_)
154                allHalfAxes_[i]->buttonThreshold_ = this->buttonThreshold_;
155    }
156
157    void KeyBinder::JoyStickQuantityChanged(const std::vector<JoyStick*>& joyStickList)
158    {
159        unsigned int oldValue = joySticks_.size();
160        joySticks_ = joyStickList;
161
162        // initialise joy stick bindings
163        initialiseJoyStickBindings();
164
165        // collect all Buttons and HalfAxes again
166        compilePointerLists();
167
168        // load the bindings if required
169        if (configFile_ != NULL)
170        {
171            for (unsigned int iDev = oldValue; iDev < joySticks_.size(); ++iDev)
172            {
173                for (unsigned int i = 0; i < JoyStickButtonCode::numberOfButtons; ++i)
174                    (*joyStickButtons_[iDev])[i].readBinding(this->configFile_, this->fallbackConfigFile_);
175                for (unsigned int i = 0; i < JoyStickAxisCode::numberOfAxes * 2; ++i)
176                    (*joyStickAxes_[iDev])[i].readBinding(this->configFile_, this->fallbackConfigFile_);
177            }
178        }
179
180        // Set the button threshold for potential new axes
181        buttonThresholdChanged();
182    }
183
184    void KeyBinder::initialiseJoyStickBindings()
185    {
186        while (joyStickAxes_.size() < joySticks_.size())
187            joyStickAxes_.push_back(shared_ptr<JoyStickAxisVector>(new JoyStickAxisVector()));
188        while (joyStickButtons_.size() < joySticks_.size())
189            joyStickButtons_.push_back(shared_ptr<JoyStickButtonVector>(new JoyStickButtonVector()));
190        // For the case the new size is smaller
191        this->joyStickAxes_.resize(joySticks_.size());
192        this->joyStickButtons_.resize(joySticks_.size());
193
194        // reinitialise all joy stick bindings (doesn't overwrite the old ones)
195        for (unsigned int iDev = 0; iDev < joySticks_.size(); iDev++)
196        {
197            const std::string& deviceName = joySticks_[iDev]->getDeviceName();
198            // joy stick buttons
199            for (unsigned int i = 0; i < JoyStickButtonCode::numberOfButtons; i++)
200            {
201                (*joyStickButtons_[iDev])[i].name_ = JoyStickButtonCode::ByString[i];
202                (*joyStickButtons_[iDev])[i].paramCommandBuffer_ = &paramCommandBuffer_;
203                (*joyStickButtons_[iDev])[i].groupName_ = "JoyStickButtons_" + deviceName;
204            }
205            // joy stick axes
206            for (unsigned int i = 0; i < JoyStickAxisCode::numberOfAxes * 2; i++)
207            {
208                (*joyStickAxes_[iDev])[i].name_ = JoyStickAxisCode::ByString[i / 2];
209                if (i & 1)
210                    (*joyStickAxes_[iDev])[i].name_ += "Pos";
211                else
212                    (*joyStickAxes_[iDev])[i].name_ += "Neg";
213                (*joyStickAxes_[iDev])[i].paramCommandBuffer_ = &paramCommandBuffer_;
214                (*joyStickAxes_[iDev])[i].groupName_ = "JoyStickAxes_" + deviceName;
215            }
216        }
217    }
218
219    void KeyBinder::compilePointerLists()
220    {
221        allButtons_.clear();
222        allHalfAxes_.clear();
223
224        // Note: Don't include the dummy keys which don't actually exist in OIS but have a number
225        for (unsigned int i = 0; i < KeyCode::numberOfKeys; i++)
226            if (!keys_[i].name_.empty())
227                allButtons_[keys_[i].groupName_ + '.' + keys_[i].name_] = keys_ + i;
228        for (unsigned int i = 0; i < numberOfMouseButtons_; i++)
229            allButtons_[mouseButtons_[i].groupName_ + '.' + mouseButtons_[i].name_] = mouseButtons_ + i;
230        for (unsigned int i = 0; i < MouseAxisCode::numberOfAxes * 2; i++)
231        {
232            allButtons_[mouseAxes_[i].groupName_ + '.' + mouseAxes_[i].name_] = mouseAxes_ + i;
233            allHalfAxes_.push_back(mouseAxes_ + i);
234        }
235        for (unsigned int iDev = 0; iDev < joySticks_.size(); iDev++)
236        {
237            for (unsigned int i = 0; i < JoyStickButtonCode::numberOfButtons; i++)
238                allButtons_[(*joyStickButtons_[iDev])[i].groupName_ + '.' + (*joyStickButtons_[iDev])[i].name_] = &((*joyStickButtons_[iDev])[i]);
239            for (unsigned int i = 0; i < JoyStickAxisCode::numberOfAxes * 2; i++)
240            {
241                allButtons_[(*joyStickAxes_[iDev])[i].groupName_ + '.' + (*joyStickAxes_[iDev])[i].name_] = &((*joyStickAxes_[iDev])[i]);
242                allHalfAxes_.push_back(&((*joyStickAxes_[iDev])[i]));
243            }
244        }
245    }
246
247    /**
248    @brief
249        Loads the key and button bindings.
250    */
251    void KeyBinder::loadBindings()
252    {
253        COUT(3) << "KeyBinder: Loading key bindings..." << std::endl;
254
255        this->configFile_ = new ConfigFile(this->filename_, !PathConfig::isDevelopmentRun());
256        this->configFile_->load();
257
258        if (PathConfig::isDevelopmentRun())
259        {
260            // Dev users should have combined key bindings files
261            std::string defaultFilepath(PathConfig::getDataPathString() + ConfigFile::DEFAULT_CONFIG_FOLDER + '/' + this->filename_);
262            std::ifstream file(defaultFilepath.c_str());
263            if (file.is_open())
264            {
265                file.close();
266                // Open the default file for later use (use absolute path!)
267                this->fallbackConfigFile_ = new ConfigFile(defaultFilepath, false);
268                this->fallbackConfigFile_->load();
269            }
270        }
271
272        // Parse bindings and create the ConfigValueContainers if necessary
273        for (std::map<std::string, Button*>::const_iterator it = allButtons_.begin(); it != allButtons_.end(); ++it)
274        {
275            it->second->readBinding(this->configFile_, this->fallbackConfigFile_);
276            addButtonToCommand(it->second->bindingString_, it->second);
277        }
278
279        COUT(3) << "KeyBinder: Loading key bindings done." << std::endl;
280    }
281
282    bool KeyBinder::setBinding(const std::string& binding, const std::string& name, bool bTemporary)
283    {
284        std::map<std::string, Button*>::iterator it = allButtons_.find(name);
285        if (it != allButtons_.end())
286        {
287            addButtonToCommand(binding, it->second);
288            std::string str = binding;
289            if (PathConfig::isDevelopmentRun() && binding.empty())
290                str = "NoBinding";
291            it->second->setBinding(this->configFile_, this->fallbackConfigFile_, binding, bTemporary);
292            return true;
293        }
294        else
295        {
296            COUT(2) << "Could not find key/button/axis with name '" << name << "'." << std::endl;
297            return false;
298        }
299    }
300
301     void KeyBinder::addButtonToCommand(const std::string& command, Button* button)
302     {
303        std::ostringstream stream;
304        stream << button->groupName_  << '.' << button->name_;
305
306        std::vector<std::string>& oldKeynames = this->allCommands_[button->bindingString_];
307        std::vector<std::string>::iterator it = std::find(oldKeynames.begin(), oldKeynames.end(), stream.str());
308        if (it != oldKeynames.end())
309            oldKeynames.erase(it);
310
311        if (!command.empty())
312        {
313            std::vector<std::string>& keynames = this->allCommands_[command];
314            if (std::find(keynames.begin(), keynames.end(), stream.str()) == keynames.end())
315                this->allCommands_[command].push_back(stream.str());
316        }
317     }
318
319    /**
320    @brief
321        Return the first key name for a specific command
322    */
323    const std::string& KeyBinder::getBinding(const std::string& commandName)
324    {
325        if (this->allCommands_.find(commandName) != this->allCommands_.end())
326        {
327            std::vector<std::string>& keynames = this->allCommands_[commandName];
328            return keynames.front();
329        }
330
331        return BLANKSTRING;
332    }
333
334    /**
335    @brief
336        Return the key name for a specific command at a given index.
337    @param commandName
338        The command name the key name is returned for.
339    @param index
340        The index at which the key name is returned for.
341    */
342    const std::string& KeyBinder::getBinding(const std::string& commandName, unsigned int index)
343    {
344        if (this->allCommands_.find(commandName) != this->allCommands_.end())
345        {
346            std::vector<std::string>& keynames = this->allCommands_[commandName];
347            if (index < keynames.size())
348                return keynames[index];
349
350            return BLANKSTRING;
351        }
352
353        return BLANKSTRING;
354    }
355
356    /**
357    @brief
358        Get the number of different key bindings of a specific command.
359    @param commandName
360        The command.
361    */
362    unsigned int KeyBinder::getNumberOfBindings(const std::string& commandName)
363    {
364        if (this->allCommands_.find(commandName) != this->allCommands_.end())
365        {
366            std::vector<std::string>& keynames = this->allCommands_[commandName];
367            return keynames.size();
368        }
369
370        return 0;
371    }
372
373    /**
374    @brief
375        Overwrites all bindings with ""
376    */
377    void KeyBinder::clearBindings()
378    {
379        for (std::map<std::string, Button*>::const_iterator it = allButtons_.begin(); it != allButtons_.end(); ++it)
380            it->second->clear();
381
382        for (unsigned int i = 0; i < paramCommandBuffer_.size(); i++)
383            delete paramCommandBuffer_[i];
384        paramCommandBuffer_.clear();
385    }
386
387    void KeyBinder::resetJoyStickAxes()
388    {
389        for (unsigned int iDev = 0; iDev < joySticks_.size(); ++iDev)
390        {
391            for (unsigned int i = 0; i < JoyStickAxisCode::numberOfAxes * 2; i++)
392            {
393                (*joyStickAxes_[iDev])[i].absVal_ = 0.0f;
394                (*joyStickAxes_[iDev])[i].relVal_ = 0.0f;
395            }
396        }
397    }
398
399    void KeyBinder::mouseUpdated(float dt)
400    {
401        if (bDeriveMouseInput_)
402        {
403            // only update when derivation dt has passed
404            if (deriveTime_ > derivePeriod_)
405            {
406                for (int i = 0; i < 2; i++)
407                {
408                    if (mouseRelative_[i] < 0)
409                    {
410                        mouseAxes_[2*i + 0].absVal_
411                            = -mouseRelative_[i] / deriveTime_ * 0.0005f * mouseSensitivityDerived_;
412                        mouseAxes_[2*i + 1].absVal_ = 0.0f;
413                    }
414                    else if (mouseRelative_[i] > 0)
415                    {
416                        mouseAxes_[2*i + 0].absVal_ = 0.0f;
417                        mouseAxes_[2*i + 1].absVal_
418                            =  mouseRelative_[i] / deriveTime_ * 0.0005f * mouseSensitivityDerived_;
419                    }
420                    else
421                    {
422                        mouseAxes_[2*i + 0].absVal_ = 0.0f;
423                        mouseAxes_[2*i + 1].absVal_ = 0.0f;
424                    }
425                    mouseRelative_[i] = 0;
426                    mouseAxes_[2*i + 0].hasChanged_ = true;
427                    mouseAxes_[2*i + 1].hasChanged_ = true;
428                }
429                deriveTime_ = 0.0f;
430            }
431            else
432                deriveTime_ += dt;
433        }
434
435        for (unsigned int i = 0; i < MouseAxisCode::numberOfAxes * 2; i++)
436        {
437            // Why dividing relative value by dt? The reason lies in the simple fact, that when you
438            // press a button that has relative movement, that value has to be multiplied by dt to be
439            // frame rate independent. This can easily (and only) be done in updateInput(float).
440            // Hence we need to divide by dt here for the mouse to compensate, because the relative
441            // move movements have nothing to do with dt.
442            if (dt != 0.0f)
443            {
444                // just ignore if dt == 0.0 because we have multiplied by 0.0 anyway..
445                mouseAxes_[i].relVal_ /= dt;
446            }
447
448            tickHalfAxis(mouseAxes_[i]);
449        }
450    }
451
452    void KeyBinder::joyStickUpdated(unsigned int joyStick, float dt)
453    {
454        for (unsigned int i = 0; i < JoyStickAxisCode::numberOfAxes * 2; i++)
455        {
456            tickHalfAxis((*joyStickAxes_[joyStick])[i]);
457        }
458    }
459
460    void KeyBinder::tickHalfAxis(HalfAxis& halfAxis)
461    {
462        // button mode
463        // TODO: optimize out all the half axes that don't act as a button at the moment
464        if (halfAxis.hasChanged_)
465        {
466            if (!halfAxis.pressed_ && halfAxis.absVal_ > halfAxis.buttonThreshold_)
467            {
468                // key pressed event
469                halfAxis.pressed_ = true;
470                if (halfAxis.nCommands_[KeybindMode::OnPress])
471                    halfAxis.execute(KeybindMode::OnPress);
472            }
473            else if (halfAxis.pressed_ && halfAxis.absVal_ < halfAxis.buttonThreshold_)
474            {
475                // key released event
476                halfAxis.pressed_ = false;
477                if (halfAxis.nCommands_[KeybindMode::OnRelease])
478                    halfAxis.execute(KeybindMode::OnRelease);
479            }
480            halfAxis.hasChanged_ = false;
481        }
482
483        if (halfAxis.pressed_)
484        {
485            // key held event
486            if (halfAxis.nCommands_[KeybindMode::OnHold])
487                halfAxis.execute(KeybindMode::OnHold);
488        }
489
490        // these are the actually useful axis bindings for analog input
491        halfAxis.execute();
492    }
493
494    /**
495    @brief
496        Event handler for the mouseMoved Event.
497    @param e
498        Mouse state information
499    */
500    void KeyBinder::mouseMoved(IntVector2 abs_, IntVector2 rel_, IntVector2 clippingSize)
501    {
502        // y axis of mouse input is inverted
503        int rel[] = { rel_.x, -rel_.y };
504
505        if (bDeriveMouseInput_)
506        {
507            mouseRelative_[0] += rel[0];
508            mouseRelative_[1] += rel[1];
509        }
510        else
511        {
512            for (int i = 0; i < 2; i++)
513            {
514                if (rel[i]) // performance opt. for the case that rel[i] == 0
515                {
516                    // write absolute values
517                    mouseAxes_[2*i + 0].hasChanged_ = true;
518                    mouseAxes_[2*i + 1].hasChanged_ = true;
519                    mousePosition_[i] += rel[i] * totalMouseSensitivity_;
520
521                    // clip absolute position
522                    if (mousePosition_[i] > 1.0)
523                        mousePosition_[i] =  1.0;
524                    if (mousePosition_[i] < -1.0)
525                        mousePosition_[i] = -1.0;
526
527                    if (mousePosition_[i] < 0.0)
528                    {
529                        mouseAxes_[2*i + 0].absVal_ = -mousePosition_[i];
530                        mouseAxes_[2*i + 1].absVal_ = 0.0f;
531                    }
532                    else
533                    {
534                        mouseAxes_[2*i + 0].absVal_ = 0.0f;
535                        mouseAxes_[2*i + 1].absVal_ =  mousePosition_[i];
536                    }
537                }
538            }
539        }
540
541        // relative
542        for (int i = 0; i < 2; i++)
543        {
544            if (rel[i] < 0)
545                mouseAxes_[0 + 2*i].relVal_ = -rel[i] * totalMouseSensitivity_;
546            else
547                mouseAxes_[1 + 2*i].relVal_ =  rel[i] * totalMouseSensitivity_;
548        }
549    }
550
551    /**
552    @brief Event handler for the mouseScrolled Event.
553    @param e Mouse state information
554    */
555    void KeyBinder::mouseScrolled(int abs, int rel)
556    {
557        if (rel < 0)
558            for (int i = 0; i < -rel/mouseWheelStepSize_; i++)
559                mouseButtons_[8].execute(KeybindMode::OnPress, static_cast<float>(abs)/mouseWheelStepSize_);
560        else
561            for (int i = 0; i < rel/mouseWheelStepSize_; i++)
562                mouseButtons_[9].execute(KeybindMode::OnPress, static_cast<float>(abs)/mouseWheelStepSize_);
563    }
564
565    void KeyBinder::axisMoved(unsigned int device, unsigned int axisID, float value)
566    {
567        // Filter analog noise
568        if (this->bFilterAnalogNoise_ && std::abs(value) < this->analogThreshold_)
569            value = 0.0;
570        int i = axisID * 2;
571        JoyStickAxisVector& axis = *joyStickAxes_[device];
572        if (value < 0)
573        {
574            axis[i].absVal_ = -value;
575            axis[i].relVal_ = -value;
576            axis[i].hasChanged_ = true;
577            if (axis[i + 1].absVal_ > 0.0f)
578            {
579                axis[i + 1].absVal_ = -0.0f;
580                axis[i + 1].relVal_ = -0.0f;
581                axis[i + 1].hasChanged_ = true;
582            }
583        }
584        else
585        {
586            axis[i + 1].absVal_ = value;
587            axis[i + 1].relVal_ = value;
588            axis[i + 1].hasChanged_ = true;
589            if (axis[i].absVal_ > 0.0f)
590            {
591                axis[i].absVal_ = -0.0f;
592                axis[i].relVal_ = -0.0f;
593                axis[i].hasChanged_ = true;
594            }
595        }
596    }
597}
Note: See TracBrowser for help on using the repository browser.