irrConfigController - controls to be configured by players

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Brainsaw
Posts: 1176
Joined: Wed Jan 07, 2004 12:57 pm
Location: Bavaria

irrConfigController - controls to be configured by players

Post by Brainsaw »

This is just a little helper that allows the Irrlicht game developer to define some controls that the player can later on configure in any way he wants to. Keyboard input, mouse and joystick are supported for user input, and the central class provides a GUI that enables the user to configure the controls. You can either copy and paste the code shown below or download a package from one of my homepages (http://bulletbyte.de/products.php?sub=irr&show=irrcc). The package does also contain a little demo application and a small finite state machine used in the demo app (I might put some more work into the FSM and post it as another code snipped ... we'll see), The code for converting the irrlicht key code to a string is taken from this topic: http://irrlicht.sourceforge.net/phpBB2/ ... hp?t=33795. Thanks to Seven for providing this piece of code.

Here is the code:

irrCC.h

Code: Select all

#ifndef _IRR_CONFIG_CONTROLLER
  #define _IRR_CONFIG_CONTROLLER

  #include <irrlicht.h>

using namespace irr;

using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;

/**
 * @class CIrrCC
 * @brief The central class of IrrConfigController
 * This class is used to provide the use configurable Irrlicht controller. Controller items can be
 * defined, and the user will be able to change the controls using a GUI provided by this class. Controls
 * can be grouped into several sets, but this does only influence the creation of the GUI.
 * @author Christian Keimel / dustbin::games
 */
class CIrrCC : public IEventReceiver {
  public:
    enum eControllerType {
      eCtrlAxis,            //the controller is an axis
      eCtrlToggleButton,    //the controller is a toggle button (state changes when button is pushed, e.g. drop a bomb
      eCtrlButton,          //the controller is a normal button (state remains the same, e.g. boost
      eCtrlFader
    };

  private:
    /**
     * The supported controller types
     * @see CIrrCC::CIrrCCItem::m_iType
     */
    enum IrrConfigControlTypes {
      CC_TYPE_NUL,  /**< NULL type ... not used */
      CC_TYPE_KEY,  /**< keyboard type */
      CC_TYPE_JOY,  /**< joystick axis type */
      CC_TYPE_MOU,  /**< mouse movement type */
      CC_TYPE_MBT,  /**< mouse button type */
      CC_TYPE_JBT,  /**< joystick button type */
      CC_TYPE_POV   /**< joystick POV type */
    };

    /**
     * @class CIrrCC::CIrrCCItem
     * @brief this class represents the controllers the user can configure
     * @see CIrrCC::m_aItems
     */
    class CIrrCCItem {
      private:
        stringw keyCodeToString(EKEY_CODE e); /**< helper function to convert a keycode to a string */
        IrrlichtDevice  *m_pDevice;           /**< the Irrlicht device */
        IGUIEnvironment *m_pGuiEnv;           /**< the Irrlicht gui environment */
        ICursorControl  *m_pCrsCtrl;          /**< the cursor control */
        IGUIStaticText  *m_pCtrlText;         /**< the static text that displays the control */
        IGUITab         *m_pGuiElement;       /**< the GUI element that contains the control */

        void updateMouse();

      public:
        IrrConfigControlTypes m_iType;    /**< type of control, e.g. key or joystick */

        stringw m_sName;                  /**< name of the control */
        EKEY_CODE m_eKey;                 /**< the key code of the control */
        eControllerType m_eType;          /**< type of control */

        u32 m_iSet,                 /**< The set of the control. */
            m_iIdx,                 /**< The index of the control in CIrrCC::m_aItems */
            m_iMouseAxis,           /**< This control's mouse axis */
            m_iMouseBtn,            /**< This control's mouse button */
            m_iJoyAxis,             /**< This control's joystick axis */
            m_iJoyButton,           /**< This control's joystick button */
            m_iFaderStep,           /**< time step for fader */
            m_iFaderLastStep;       /**< the last step time of the fader */
        u8  m_iJoyId;               /**< This control's joystick .. irrlicht joystick ID */
        u16 m_iPov;                 /**< This control's POV position */
        s32 m_iMouseDirection,      /**< This control's mouse direction */
            m_iJoyDirection;        /**< This control's joystick direction */
        f32 m_fValue,               /**< This control's value. Should be queried using CIrrCCItem::get() */
            m_fDetectedValue,       /**< The last detected value. CIrrCCItem::set does not influence this */
            m_fMouseSensitivity,    /**< This control's mouse sensitivity */
            m_fFaderDiff,           /**< increase / decrease per time step for fader */
            m_fFaderValue;          /**< the value of the fader */

        CIrrCCItem *m_pBrother;     /**< the other CCItem with forms an axis with this one */

        bool m_bConflict;           /**< does this control have a conflict with another control? */
        bool isButton();            /**< is this controller a button? */

        /**
         * The item's constructor.
         * @param sName name of the item
         * @param pDevice irrlicht device for the item
         * @param iSet set of the item
         */
        CIrrCCItem(stringw sName, IrrlichtDevice *pDevice, u32 iSet);

        /**
         * The item's destructor
         */
        ~CIrrCCItem();

        /**
         * This method constructs a GUI element for changing this control item
         * @param pParent parent GUI element
         * @param cPos position of the item's element
         * @param cEditPos position of the item's static text that can change
         */
        IGUITab *getGuiElement(IGUIElement *pParent, position2di cPos, position2di cEditPos);

        /**
         * Set the item to "CC_TYPE_KEY" using the given key
         * @param e the key for this item
         */
        void setKey(EKEY_CODE e);

        /**
         * Set the item to "CC_TYPE_MOU" using the given mouse axis in the given direction
         * @param iAxis the mouse axis
         * @param iDirection the direction of this item
         */
        void setMouseMove(u32 iAxis, s32 iDirection);

        /**
         * Set the item to "CC_TYPE_MBT" using the given button number
         * @param iBtn button number to use (0=left, 1=right, 2=middle)
         */
        void setMouseButton(u32 iBtn);

        /**
         * Set the item to "CC_TYPE_JOY" using the given joystick, axis and direction
         * @param id the irrlicht joystick id
         * @param iAxis the axis for this item
         * @param iDirection the direction for this item
         */
        void setJoyAxis(u8 id, u32 iAxis, s32 iDirection);

        /**
         * Set the item to "CC_TYPE_JBT" using the given joystick and joystick button
         * @param id the irrlicht joystick id
         * @param iBtn the joystick button to use
         */
        void setJoyButton(u8 id, u32 iBtn);

        /**
         * Set the item to "CC_TYPE_POV" using the given joystick and POV position
         * @param id the irrlicht joystick id
         * @param iPov the POV position for this item
         */
        void setPov(u8 id, u16 iPov);

        /**
         * Update the item's text. This method is called after the control has changed
         */
        void updateCtrlText();

        /**
         * Get the item's text
         * @return the item's text
         */
        IGUIStaticText *getCtrlText();

        /**
         * Does this item have a conflict with another item?
         * @return Does this item have a conflict with another item?
         */
        bool hasConflict();

        /**
         * Set the item's conflict flag
         * @param b "true" if a conflict was found, "false" otherwise
         */
        void setConflict(bool b);

        /**
         * Check whether or not this item is in conflict with another one
         * @param pOther the other item
         * @return "true" if both of the items use the same control, "false" otherwise
         */
        bool conflicts(CIrrCCItem *pOther);

        /**
         * Set the item's mouse sensitivity. This is use for items that use mouse movement as control
         * @param f the item's mouse sensitivity
         */
        void setMouseSensitivity(f32 f);

        /**
         * Get the item's current position. You could also just take the item's public "m_fValue" property,
         * but mouse movement items calculate this value in this method
         */
        f32 get();

        f32 getRaw();

        /**
         * Set the item's value. Can be called from outside to reset e.g. a button state
         * @param f the new value
         */
        void set(f32 f);

        /**
         * Set the item's value. Is called by CIrrCC::OnEvent
         * @param f the new value
         */
        void setWithDetected(f32 f);
    };

    /**
     * @class CJoyState
     * @brief This class is used for detecting joystick movement when changing an item's control
     */
    class CJoyState {
      public:
        u16 iAxis[6];   /**< the axis positions of the joystick */
        u32 iButtons;   /**< the button states of the joystick  */
        u16 iPov;       /**< the POV position of the joystick   */

        /**
         * Constructor
         */
        CJoyState() {
          for (u32 i=0; i<6; i++) iAxis[i]=0;
          iButtons=0;
          iPov=0;
        }

        /**
         * Check whether or not a button was pressed
         * @param iBtn the button number
         * @return "true" if the button was pressed, "false" otherwise
         */
        bool buttonPressed(u32 iBtn) {
          u32 i=1<<iBtn;
          return (iButtons&i)!=0;
        }
    };

    IrrlichtDevice *m_pDevice;            /**< the Irrlicht device */
    u32 m_iGuiItemHeight;                 /**< the height of the items using the current font */
    s32 m_iFocusedIndex;                  /**< index of the currently focused GUI element */
    f32 m_fMouseSensitivity;              /**< the mouse sensitivity */
    IGUIStaticText *m_pFocused;           /**< the focused text element */
    ICursorControl *m_pCrsCtrl;           /**< the cursor control object */
    position2di m_cMousePos;              /**< the position of the mouse */
    array<CIrrCCItem *> m_aItems;         /**< the array with the control items */
    array<stringw> m_aMaxLenText;         /**< the texts with the maximum length of all sets */
    array<SJoystickInfo> m_aJoysticks;    /**< the joystick info array */
    array<CJoyState *> m_aJoyState;       /**< array with the current joystick states */
    bool m_bAllowMouse,                   /**< is mouse movement allowed for items? */
         m_bAllowFKeys,                   /**< are "Fxx" keys allowed for items? */
         m_bAllowJoystick,                /**< is joystick allows for items? */
         m_bLoaded,                       /**< is the control set already loaded from the XML file? */
         m_bSetsCanConflict;              /**< do same controls of different sets cause a conflict? */

  public:
    /**
     * The constructor
     * @param pDevice the irrlicht device
     */
    CIrrCC(IrrlichtDevice *pDevice);

    /**
     * destructor
     */
    virtual ~CIrrCC();

    /**
     * add a new control item
     * @param iSet set of the item
     * @param sName name of the item
     * @param eDefault default control key of the item
     * @param bIsButton is this a button or part of an axis?
     * @return index of the item
     */
    u32 addItem(u32 iSet, stringw sName, EKEY_CODE eDefault, eControllerType eType);

    /**
     * Create a gui to configure the control items
     * @param iSet set of items
     * @param pParent parent GUI element
     * @param cPos position of the newly created gui
     */
    void addGui(u32 iSet, IGUIElement *pParent, position2di cPos);

    /**
     * Set the "allow mouse" flag
     * @param b new value for the "allow mouse" flag
     */
    void setAllowMouse(bool b);

    /**
     * Set the "allow F-Keys" flag
     * @param b new value for the "allow F-Keys" flag
     */
    void setAllowFKeys(bool b);

    /**
     * Set the "allow joystick" flag
     * @param b new value for the "allow joystick" flag
     */
    void setAllowJoystick(bool b);

    /**
     * Set the mouse sensitivity
     * @param f new value for the mouse sensitivity
     */
    void setMouseSensitivity(f32 f);

    /**
     * Get the status of a specific item. This value is always between "0" and "1" (except for mouse movement)
     * @param idx index of the item
     * @return status of the item
     */
    f32 get(u32 idx);

    void set(u32 idx, f32 f);

    /**
     * This method must be called from the GUI's event receiver. It handles configuration of the control items
     * @param event the event to be handled
     * @return "true" if the event was processed, "false" otherwise
     */
    bool ConfigEvent(const SEvent &event);

    void reset();

    /**
     * This is the event handler to update the item's values. You can either set the instance of the class
     * as irrlicht event receiver or call this method from your own event receiving code.
     * @param event the event to be handled
     * @return "true" if the event was processed, "false" otherwise
     */
    virtual bool OnEvent (const SEvent &event);

    /**
     * decide whether or not the various defined sets cause conflicts or not
     * @param b true if the sets can cause conflicts, false otherwise
     */
    void setSetsCanConflict(bool b) { m_bSetsCanConflict=b; }

    /**
     * Can the various defined sets cause conflicts?
     * @return Can the various defined sets cause conflicts?
     */
    bool setsCanConflict() { return m_bSetsCanConflict; }

    /**
     * Create an axis of two controls defined as eCtrlAxis. If you query the value of the first
     * control item you will get <position of idx1>-<position of idx2>, if you query the second
     * item you'll get <position of idx2>-<position of idx1>.
     * @param idx1 the first index of the axis (value returned by CIrrCC::addItem)
     * @param idx2 the second index of the axis (value returned by CIrrCC::addItem)
     * @see eControllerType
     */
    void createAxis(u32 idx1, u32 idx2);

    void createFader(u32 idx1, u32 idx2, u32 iStep, f32 iInc);

    /**
     * Save the controls to an XML file
     * @param sFileName name of the XML file
     */
    void save(c8 *sFileName);

    /**
     * Load the controls from an XML file
     * @param sFileName name of the XML file
     */
    void load(c8 *sFileName);
};

#endif
irrCC.cpp

Code: Select all

  #include <irrCC.h>

CIrrCC::CIrrCCItem::CIrrCCItem(stringw sName, IrrlichtDevice *pDevice, u32 iSet) {
  m_pDevice=pDevice;
  m_sName=sName;
  m_iSet=iSet;
  m_iType=CC_TYPE_NUL;
  m_eKey=KEY_KEY_CODES_COUNT;
  m_eType=eCtrlAxis;

  m_pGuiEnv=m_pDevice->getGUIEnvironment();
  m_bConflict=false;
  m_fValue=0.0f;
  m_pCrsCtrl=pDevice->getCursorControl();
  m_fMouseSensitivity=10.0f;
  m_pCtrlText=NULL;
  m_pBrother=NULL;

  m_iFaderStep=0;
  m_iFaderLastStep=0;
  m_fFaderDiff=0.0f;
  m_fFaderValue=0.0f;
}

CIrrCC::CIrrCCItem::~CIrrCCItem() {
}

bool CIrrCC::CIrrCCItem::isButton() {
  return m_eType==CIrrCC::eCtrlButton || m_eType==CIrrCC::eCtrlToggleButton;
}

stringw CIrrCC::CIrrCCItem::keyCodeToString(EKEY_CODE e) {
  if (e == KEY_LBUTTON)    return stringw(L"KEY_LBUTTON");
  if (e == KEY_RBUTTON)    return stringw(L"KEY_RBUTTON");
  if (e == KEY_CANCEL)     return stringw(L"KEY_CANCEL");
  if (e == KEY_MBUTTON)    return stringw(L"KEY_MBUTTON");
  if (e == KEY_XBUTTON1)   return stringw(L"KEY_XBUTTON1");
  if (e == KEY_XBUTTON2)   return stringw(L"KEY_XBUTTON2");
  if (e == KEY_BACK)       return stringw(L"KEY_BACK");
  if (e == KEY_TAB)        return stringw(L"KEY_TAB");
  if (e == KEY_CLEAR)      return stringw(L"KEY_CLEAR");
  if (e == KEY_RETURN)     return stringw(L"KEY_RETURN");
  if (e == KEY_SHIFT)      return stringw(L"KEY_SHIFT");
  if (e == KEY_CONTROL)    return stringw(L"KEY_CONTROL");
  if (e == KEY_MENU)       return stringw(L"KEY_MENU");
  if (e == KEY_PAUSE)      return stringw(L"KEY_PAUSE");
  if (e == KEY_CAPITAL)    return stringw(L"KEY_CAPITAL");
  if (e == KEY_KANA)       return stringw(L"KEY_KANA");
  if (e == KEY_HANGUEL)    return stringw(L"KEY_HANGUEL");
  if (e == KEY_HANGUL)     return stringw(L"KEY_HANGUL");
  if (e == KEY_JUNJA)      return stringw(L"KEY_JUNJA");
  if (e == KEY_FINAL)      return stringw(L"KEY_FINAL");
  if (e == KEY_HANJA)      return stringw(L"KEY_HANJA");
  if (e == KEY_KANJI)      return stringw(L"KEY_KANJI");
  if (e == KEY_ESCAPE)     return stringw(L"KEY_ESCAPE");
  if (e == KEY_CONVERT)    return stringw(L"KEY_CONVERT");
  if (e == KEY_NONCONVERT) return stringw(L"KEY_NONCONVERT");
  if (e == KEY_ACCEPT)     return stringw(L"KEY_ACCEPT");
  if (e == KEY_MODECHANGE) return stringw(L"KEY_MODECHANGE");
  if (e == KEY_SPACE)      return stringw(L"KEY_SPACE");
  if (e == KEY_PRIOR)      return stringw(L"KEY_PRIOR");
  if (e == KEY_NEXT)       return stringw(L"KEY_NEXT");
  if (e == KEY_END)        return stringw(L"KEY_END");
  if (e == KEY_HOME)       return stringw(L"KEY_HOME");
  if (e == KEY_LEFT)       return stringw(L"KEY_LEFT");
  if (e == KEY_UP)         return stringw(L"KEY_UP");
  if (e == KEY_RIGHT)      return stringw(L"KEY_RIGHT");
  if (e == KEY_DOWN)       return stringw(L"KEY_DOWN");
  if (e == KEY_SELECT)     return stringw(L"KEY_SELECT");
  if (e == KEY_PRINT)      return stringw(L"KEY_PRINT");
  if (e == KEY_EXECUT)     return stringw(L"KEY_EXECUT");
  if (e == KEY_SNAPSHOT)   return stringw(L"KEY_SNAPSHOT");
  if (e == KEY_INSERT)     return stringw(L"KEY_INSERT");
  if (e == KEY_DELETE)     return stringw(L"KEY_DELETE");
  if (e == KEY_HELP)       return stringw(L"KEY_HELP");
  if (e == KEY_KEY_0)      return stringw(L"KEY_KEY_0");
  if (e == KEY_KEY_1)      return stringw(L"KEY_KEY_1");
  if (e == KEY_KEY_2)      return stringw(L"KEY_KEY_2");
  if (e == KEY_KEY_3)      return stringw(L"KEY_KEY_3");
  if (e == KEY_KEY_4)      return stringw(L"KEY_KEY_4");
  if (e == KEY_KEY_5)      return stringw(L"KEY_KEY_5");
  if (e == KEY_KEY_6)      return stringw(L"KEY_KEY_6");
  if (e == KEY_KEY_7)      return stringw(L"KEY_KEY_7");
  if (e == KEY_KEY_8)      return stringw(L"KEY_KEY_8");
  if (e == KEY_KEY_9)      return stringw(L"KEY_KEY_9");
  if (e == KEY_KEY_A)      return stringw(L"KEY_KEY_A");
  if (e == KEY_KEY_B)      return stringw(L"KEY_KEY_B");
  if (e == KEY_KEY_C)      return stringw(L"KEY_KEY_C");
  if (e == KEY_KEY_D)      return stringw(L"KEY_KEY_D");
  if (e == KEY_KEY_E)      return stringw(L"KEY_KEY_E");
  if (e == KEY_KEY_F)      return stringw(L"KEY_KEY_F");
  if (e == KEY_KEY_G)      return stringw(L"KEY_KEY_G");
  if (e == KEY_KEY_H)      return stringw(L"KEY_KEY_H");
  if (e == KEY_KEY_I)      return stringw(L"KEY_KEY_I");
  if (e == KEY_KEY_J)      return stringw(L"KEY_KEY_J");
  if (e == KEY_KEY_K)      return stringw(L"KEY_KEY_K");
  if (e == KEY_KEY_L)      return stringw(L"KEY_KEY_L");
  if (e == KEY_KEY_M)      return stringw(L"KEY_KEY_M");
  if (e == KEY_KEY_N)      return stringw(L"KEY_KEY_N");
  if (e == KEY_KEY_O)      return stringw(L"KEY_KEY_O");
  if (e == KEY_KEY_P)      return stringw(L"KEY_KEY_P");
  if (e == KEY_KEY_Q)      return stringw(L"KEY_KEY_Q");
  if (e == KEY_KEY_R)      return stringw(L"KEY_KEY_R");
  if (e == KEY_KEY_S)      return stringw(L"KEY_KEY_S");
  if (e == KEY_KEY_T)      return stringw(L"KEY_KEY_T");
  if (e == KEY_KEY_U)      return stringw(L"KEY_KEY_U");
  if (e == KEY_KEY_V)      return stringw(L"KEY_KEY_V");
  if (e == KEY_KEY_W)      return stringw(L"KEY_KEY_W");
  if (e == KEY_KEY_X)      return stringw(L"KEY_KEY_X");
  if (e == KEY_KEY_Y)      return stringw(L"KEY_KEY_Y");
  if (e == KEY_KEY_Z)      return stringw(L"KEY_KEY_Z");
  if (e == KEY_LWIN)       return stringw(L"KEY_LWIN");
  if (e == KEY_RWIN)       return stringw(L"KEY_RWIN");
  if (e == KEY_APPS)       return stringw(L"KEY_APPS");
  if (e == KEY_SLEEP)      return stringw(L"KEY_SLEEP");
  if (e == KEY_NUMPAD0)    return stringw(L"KEY_NUMPAD0");
  if (e == KEY_NUMPAD1)    return stringw(L"KEY_NUMPAD1");
  if (e == KEY_NUMPAD2)    return stringw(L"KEY_NUMPAD2");
  if (e == KEY_NUMPAD3)    return stringw(L"KEY_NUMPAD3");
  if (e == KEY_NUMPAD4)    return stringw(L"KEY_NUMPAD4");
  if (e == KEY_NUMPAD5)    return stringw(L"KEY_NUMPAD5");
  if (e == KEY_NUMPAD6)    return stringw(L"KEY_NUMPAD6");
  if (e == KEY_NUMPAD7)    return stringw(L"KEY_NUMPAD7");
  if (e == KEY_NUMPAD8)    return stringw(L"KEY_NUMPAD8");
  if (e == KEY_NUMPAD9)    return stringw(L"KEY_NUMPAD9");
  if (e == KEY_MULTIPLY)   return stringw(L"KEY_MULTIPLY");
  if (e == KEY_ADD)        return stringw(L"KEY_ADD");
  if (e == KEY_SEPARATOR)  return stringw(L"KEY_SEPARATOR");
  if (e == KEY_SUBTRACT)   return stringw(L"KEY_SUBTRACT");
  if (e == KEY_DECIMAL)    return stringw(L"KEY_DECIMAL");
  if (e == KEY_DIVIDE)     return stringw(L"KEY_DIVIDE");
  if (e == KEY_F1)         return stringw(L"KEY_F1");
  if (e == KEY_F2)         return stringw(L"KEY_F2");
  if (e == KEY_F3)         return stringw(L"KEY_F3");
  if (e == KEY_F4)         return stringw(L"KEY_F4");
  if (e == KEY_F5)         return stringw(L"KEY_F5");
  if (e == KEY_F6)         return stringw(L"KEY_F6");
  if (e == KEY_F7)         return stringw(L"KEY_F7");
  if (e == KEY_F8)         return stringw(L"KEY_F8");
  if (e == KEY_F9)         return stringw(L"KEY_F9");
  if (e == KEY_F10)        return stringw(L"KEY_F10");
  if (e == KEY_F11)        return stringw(L"KEY_F11");
  if (e == KEY_F12)        return stringw(L"KEY_F12");
  if (e == KEY_F13)        return stringw(L"KEY_F13");
  if (e == KEY_F14)        return stringw(L"KEY_F14");
  if (e == KEY_F15)        return stringw(L"KEY_F15");
  if (e == KEY_F16)        return stringw(L"KEY_F16");
  if (e == KEY_F17)        return stringw(L"KEY_F17");
  if (e == KEY_F18)        return stringw(L"KEY_F18");
  if (e == KEY_F19)        return stringw(L"KEY_F19");
  if (e == KEY_F20)        return stringw(L"KEY_F20");
  if (e == KEY_F21)        return stringw(L"KEY_F21");
  if (e == KEY_F22)        return stringw(L"KEY_F22");
  if (e == KEY_F23)        return stringw(L"KEY_F23");
  if (e == KEY_F24)        return stringw(L"KEY_F24");
  if (e == KEY_NUMLOCK)    return stringw(L"KEY_NUMLOCK");
  if (e == KEY_SCROLL)     return stringw(L"KEY_SCROLL");
  if (e == KEY_LSHIFT)     return stringw(L"KEY_LSHIFT");
  if (e == KEY_RSHIFT)     return stringw(L"KEY_RSHIFT");
  if (e == KEY_LCONTROL)   return stringw(L"KEY_LCONTROL");
  if (e == KEY_RCONTROL)   return stringw(L"KEY_RCONTROL");
  if (e == KEY_LMENU)      return stringw(L"KEY_LMENU");
  if (e == KEY_RMENU)      return stringw(L"KEY_RMENU");
  if (e == KEY_PLUS)       return stringw(L"KEY_PLUS");
  if (e == KEY_COMMA)      return stringw(L"KEY_COMMA");
  if (e == KEY_MINUS)      return stringw(L"KEY_MINUS");
  if (e == KEY_PERIOD)     return stringw(L"KEY_PERIOD");
  if (e == KEY_ATTN)       return stringw(L"KEY_ATTN");
  if (e == KEY_CRSEL)      return stringw(L"KEY_CRSEL");
  if (e == KEY_EXSEL)      return stringw(L"KEY_EXSEL");
  if (e == KEY_EREOF)      return stringw(L"KEY_EREOF");
  if (e == KEY_PLAY)       return stringw(L"KEY_PLAY");
  if (e == KEY_ZOOM)       return stringw(L"KEY_ZOOM");
  if (e == KEY_PA1)        return stringw(L"KEY_PA1");
  if (e == KEY_OEM_CLEAR)  return stringw(L"KEY_OEM_CLEAR");
  return stringw(L"UNKNOWN");
}

IGUITab *CIrrCC::CIrrCCItem::getGuiElement(IGUIElement *pParent, position2di cPos, position2di cEditPos) {
  IGUIFont *pFont=m_pGuiEnv->getSkin()->getFont();
  dimension2du dim=pFont->getDimension(m_sName.c_str());

  position2di ulc(cPos.X,cPos.Y),lrc(cPos.Y+dim.Width+cEditPos.X+250,cPos.Y+dim.Height+10);

  m_pGuiElement=m_pGuiEnv->addTab(rect<s32>(ulc.X,ulc.Y,lrc.X,lrc.Y),pParent,-1);
  m_pGuiEnv->addStaticText(m_sName.c_str(),rect<s32>(0,0,dim.Width+10,dim.Height+10),false,true,m_pGuiElement);
  m_pCtrlText=m_pGuiEnv->addStaticText(L"Not set",rect<s32>(cEditPos.X,cEditPos.Y,cEditPos.X+200,cEditPos.Y+dim.Height+2),true,true,m_pGuiElement,65536+m_iIdx);
  this->updateCtrlText();
  return m_pGuiElement;
}

void CIrrCC::CIrrCCItem::setKey(EKEY_CODE e) {
  m_eKey=e;
  m_iType=CC_TYPE_KEY;
}

void CIrrCC::CIrrCCItem::setMouseMove(u32 iAxis, s32 iDirection) {
  m_iMouseAxis=iAxis;
  m_iMouseDirection=iDirection;
  m_iType=CC_TYPE_MOU;
}

void CIrrCC::CIrrCCItem::setMouseButton(u32 iBtn) {
  m_iMouseBtn=iBtn;
  m_iType=CC_TYPE_MBT;
}

void CIrrCC::CIrrCCItem::setJoyAxis(u8 id, u32 iAxis, s32 iDirection) {
  m_iJoyId=id;
  m_iJoyAxis=iAxis;
  m_iJoyDirection=iDirection;
  m_iType=CC_TYPE_JOY;
}

void CIrrCC::CIrrCCItem::setJoyButton(u8 id, u32 iBtn) {
  m_iJoyId=id;
  m_iJoyButton=iBtn;
  m_iType=CC_TYPE_JBT;
}

void CIrrCC::CIrrCCItem::setPov(u8 id, u16 iPov) {
  m_iPov=iPov;
  m_iJoyId=id;
  m_iType=CC_TYPE_POV;
}

IGUIStaticText *CIrrCC::CIrrCCItem::getCtrlText() {
  return m_pCtrlText;
}

bool CIrrCC::CIrrCCItem::hasConflict() {
  return m_bConflict;
}

void CIrrCC::CIrrCCItem::setConflict(bool b) {
  m_bConflict=b;
}

void CIrrCC::CIrrCCItem::updateCtrlText() {
  if (!m_pCtrlText) return;
  wchar_t s[0xFF];

  switch (m_iType) {
    case CC_TYPE_KEY:
      m_pCtrlText->setText(keyCodeToString(m_eKey).c_str());
      break;

    case CC_TYPE_MOU:
      swprintf(s,0xFF,L"Mouse %s %s",m_iMouseAxis==0?L"X":L"Y",m_iMouseDirection>0?L"+":L"-");
      m_pCtrlText->setText(s);
      break;

    case CC_TYPE_MBT:
      swprintf(s,0xFF,L"Mouse Button %i",m_iMouseBtn);
      m_pCtrlText->setText(s);
      break;

    case CC_TYPE_JOY:
      swprintf(s,0xFF,L"Joy %i Axis %i %s",m_iJoyId,m_iJoyAxis,m_iJoyDirection>0?L"+":L"-");
      m_pCtrlText->setText(s);
      break;

    case CC_TYPE_JBT:
      swprintf(s,0xFF,L"Joy %i Button %i",m_iJoyId,m_iJoyButton);
      m_pCtrlText->setText(s);
      break;

    case CC_TYPE_POV:
      swprintf(s,0xFF,L"Joy %i POV %u",m_iJoyId,m_iPov/100);
      m_pCtrlText->setText(s);
      break;

    default:
      break;
  }
}

bool CIrrCC::CIrrCCItem::conflicts(CIrrCCItem *pOther) {
  bool bRet=m_iType==pOther->m_iType;

  if (bRet) {
    switch (m_iType) {
      case CC_TYPE_KEY:
        bRet=m_eKey==pOther->m_eKey;
        break;

      case CC_TYPE_MOU:
        bRet=m_iMouseAxis==pOther->m_iMouseAxis && m_iMouseDirection==pOther->m_iMouseDirection;
        break;

      case CC_TYPE_MBT:
        bRet=m_iMouseBtn==pOther->m_iMouseBtn;
        break;

      case CC_TYPE_JOY:
        bRet=m_iJoyId==pOther->m_iJoyId && m_iJoyAxis==pOther->m_iJoyAxis && m_iJoyDirection==pOther->m_iJoyDirection;
        break;

      case CC_TYPE_JBT:
        bRet=m_iJoyId==pOther->m_iJoyId && m_iJoyButton==pOther->m_iJoyButton;
        break;

      case CC_TYPE_POV:
        bRet=m_iJoyId==pOther->m_iJoyId && m_iPov==pOther->m_iPov;
        break;

      default:
        break;
    }
  }

  return bRet;
}

void CIrrCC::CIrrCCItem::setMouseSensitivity(f32 f) {
  m_fMouseSensitivity=f;
}

void CIrrCC::CIrrCCItem::updateMouse() {
  m_fValue=0.0f;
  position2di pos=m_pCrsCtrl->getPosition();
  if (m_iMouseAxis==0) {
    if (m_iMouseDirection>0 && pos.X>320) {
      m_fValue=((f32)pos.X-320)/m_fMouseSensitivity;
      m_fDetectedValue=m_fValue;
      pos.X=320;
    }
    if (m_iMouseDirection<0 && pos.X<320) {
      m_fValue=((f32)320-pos.X)/m_fMouseSensitivity;
      m_fDetectedValue=m_fValue;
      pos.X=320;
    }
  }

  if (m_iMouseAxis==1) {
    if (m_iMouseDirection>0 && pos.Y>200) {
      m_fValue=((f32)pos.Y-200)/m_fMouseSensitivity;
      m_fDetectedValue=m_fValue;
      pos.Y=200;
    }
    if (m_iMouseDirection<0 && pos.Y<200) {
      m_fValue=((f32)200-pos.Y)/m_fMouseSensitivity;
      m_fDetectedValue=m_fValue;
      pos.Y=200;
    }
  }

  if (m_fValue) m_pCrsCtrl->setPosition(pos);
}

f32 CIrrCC::CIrrCCItem::get() {
  if (m_iType==CC_TYPE_MOU) updateMouse();
  return m_pBrother==NULL?m_fValue:m_fValue-m_pBrother->getRaw();
}

f32 CIrrCC::CIrrCCItem::getRaw() {
  if (m_iType==CC_TYPE_MOU) updateMouse();
  return m_fValue;
}

void CIrrCC::CIrrCCItem::set(f32 f) {
  m_fValue=f;
}

void CIrrCC::CIrrCCItem::setWithDetected(f32 f) {
  if (m_eType==eCtrlToggleButton && f==m_fDetectedValue) return;
  m_fValue=f;
  m_fDetectedValue=f;
}

CIrrCC::CIrrCC(IrrlichtDevice *pDevice) {
  m_pDevice=pDevice;
  m_iGuiItemHeight=pDevice->getGUIEnvironment()->getSkin()->getFont()->getDimension(L"Hello World").Height;
  m_pFocused=NULL;
  m_iFocusedIndex=-1;
  m_pDevice->activateJoysticks(m_aJoysticks);

  for (u32 i=0; i<m_aJoysticks.size(); i++) {
    CJoyState *p=new CJoyState();
    m_aJoyState.push_back(p);
  }

  m_bAllowMouse=true;
  m_bAllowFKeys=true;
  m_bAllowJoystick=true;
  m_bLoaded=false;
  m_pCrsCtrl=m_pDevice->getCursorControl();
  m_fMouseSensitivity=10.0f;
}

CIrrCC::~CIrrCC() {
  for (u32 i=0; i<m_aItems.size(); i++) delete m_aItems[i];
  m_aItems.clear();
  m_bSetsCanConflict=true;
}

u32 CIrrCC::addItem(u32 iSet,stringw sName, EKEY_CODE eDefault, eControllerType eType) {
  u32 iRet=0;

  if (m_aMaxLenText.size()<=iSet)
    m_aMaxLenText.push_back(sName);
  else
    if (m_aMaxLenText[iSet].size()<sName.size())
      m_aMaxLenText[iSet]=sName;

  CIrrCCItem *item=new CIrrCCItem(sName,m_pDevice,iSet);
  item->setKey(eDefault);
  item->m_eType=eType;
  item->setMouseSensitivity(m_fMouseSensitivity);
  m_aItems.push_back(item);

  iRet=m_aItems.size()-1;
	item->m_iIdx=iRet;

  return iRet;
}

void CIrrCC::addGui(u32 iSet, IGUIElement *pParent, position2di cPos) {
  u32 editX=m_pDevice->getGUIEnvironment()->getSkin()->getFont()->getDimension(m_aMaxLenText[iSet].c_str()).Width+10;

  for (u32 i=0; i<m_aItems.size(); i++)
    if (m_aItems[i]->m_iSet==iSet) {
      m_aItems[i]->getGuiElement(pParent,cPos,position2di(editX,0));
      cPos.Y+=(s32)(2.5f*m_iGuiItemHeight);
    }
}

bool CIrrCC::ConfigEvent (const SEvent &event) {
  bool bRet=false;
  stringw s;
  u32 iItem=m_iFocusedIndex-65536;

  if (event.EventType==EET_GUI_EVENT && event.GUIEvent.EventType==EGET_ELEMENT_FOCUSED) {
    m_cMousePos=m_pDevice->getCursorControl()->getPosition();
		if (event.GUIEvent.Caller->getID()>=65536) {
      if (m_pFocused) m_pFocused->setBackgroundColor(SColor(128,192,192,192));
      s=stringw(event.GUIEvent.Caller->getText());
      m_pFocused=(IGUIStaticText *)event.GUIEvent.Caller;
      m_pFocused->setBackgroundColor(SColor(128,0,255,0));
      m_iFocusedIndex=m_pFocused->getID();
    }
  }

  if (m_pFocused) {
    CIrrCCItem *pItem=iItem<m_aItems.size()?m_aItems[iItem]:NULL,
               *pBrother=pItem!=NULL?pItem->m_pBrother:NULL;

    if (event.EventType==EET_KEY_INPUT_EVENT) {

			if ((m_bAllowFKeys || event.KeyInput.Key<KEY_F1 || event.KeyInput.Key>KEY_F24) && event.KeyInput.Key!=KEY_ESCAPE) {
        pItem->setKey(event.KeyInput.Key);
				bRet=true;
			}

      for (u32 i=0; i<m_aItems.size(); i++) {
        m_aItems[i]->setConflict(false);
        m_aItems[i]->getCtrlText()->setBackgroundColor(SColor(128,192,192,192));
      }
    }

    if (event.EventType==EET_MOUSE_INPUT_EVENT && m_bAllowMouse) {
      position2di mPos=m_pDevice->getCursorControl()->getPosition();
      if ((mPos.X>m_cMousePos.X+50 || mPos.X<m_cMousePos.X-50) && !pItem->isButton()) {
        s32 iDirection=mPos.X>m_cMousePos.X+50?1:-1;
        pItem->setMouseMove(0,iDirection);
        if (pBrother!=NULL) {
          if (pBrother->m_iMouseAxis!=pItem->m_iMouseAxis && pBrother->m_iMouseDirection!=-pItem->m_iMouseDirection) {
            pBrother->setMouseMove(0,-iDirection);
          }
        }
        bRet=true;
      }

      if ((mPos.Y>m_cMousePos.Y+50 || mPos.Y<m_cMousePos.Y-50) && !pItem->isButton()) {
        s32 iDirection=mPos.Y>m_cMousePos.Y+50?1:-1;
        pItem->setMouseMove(1,iDirection);
        if (pBrother->m_iMouseAxis!=pItem->m_iMouseAxis && pBrother->m_iMouseDirection!=-pItem->m_iMouseDirection) {
          pBrother->setMouseMove(1,-iDirection);
        }
        bRet=true;
      }

      if (event.MouseInput.Event==EMIE_LMOUSE_PRESSED_DOWN) {
        pItem->setMouseButton(0);
        bRet=true;
      }

      if (event.MouseInput.Event==EMIE_RMOUSE_PRESSED_DOWN) {
        pItem->setMouseButton(1);
        bRet=true;
      }

      if (event.MouseInput.Event==EMIE_MMOUSE_PRESSED_DOWN) {
        pItem->setMouseButton(2);
        bRet=true;
      }
    }

    if (event.EventType==EET_JOYSTICK_INPUT_EVENT && m_bAllowJoystick) {
      u8 joyId=event.JoystickEvent.Joystick;
      for (u32 i=0; i<6; i++) {
        s16 pos1=(m_aJoyState[joyId]->iAxis[i]),
            pos2=event.JoystickEvent.Axis[i];

				if (pos2<16000 && pos2>-16000) pos2=0;
        if (pos1!=pos2 && !pItem->isButton()) {
          s32 iDirection=pos1>pos2?1:-1;
          pItem->setJoyAxis(joyId,i,iDirection);
          if (pBrother!=NULL) {
            if (pItem->m_iJoyId!=pBrother->m_iJoyId || pItem->m_iJoyAxis!=pBrother->m_iJoyAxis)
              pBrother->setJoyAxis(joyId,i,-iDirection);
          }
          bRet=true;
        }

        if (m_aJoyState[joyId]->iPov!=event.JoystickEvent.POV) {
          pItem->setPov(joyId,event.JoystickEvent.POV);
          bRet=true;
        }

        if (m_aJoyState[joyId]->iButtons!=event.JoystickEvent.ButtonStates)
          for (u32 i=0; i<32; i++)
            if (m_aJoyState[joyId]->buttonPressed(i)!=event.JoystickEvent.IsButtonPressed(i)) {
              pItem->setJoyButton(joyId,i);
              bRet=true;
            }
      }
    }

    if (bRet) {
      pItem->updateCtrlText();
      pItem->setConflict(false);

      if (pBrother!=NULL) {
        pBrother->updateCtrlText();
        pBrother->setConflict(false);
        pBrother->getCtrlText()->setBackgroundColor(SColor(128,192,192,192));
      }

      m_pFocused->setBackgroundColor(SColor(128,192,192,192));
      m_pFocused=NULL;
      m_iFocusedIndex=-1;
      m_pDevice->getGUIEnvironment()->setFocus(NULL);
    }
  }

  for (u32 i=0; i<m_aItems.size(); i++) {
    for (u32 j=i+1; j<m_aItems.size(); j++) {
      if (m_aItems[i]->conflicts(m_aItems[j]) && (m_aItems[i]->m_iSet==m_aItems[j]->m_iSet || m_bSetsCanConflict)) {
        m_aItems[j]->getCtrlText()->setBackgroundColor(SColor(128,255,0,0)); m_aItems[j]->setConflict(true);
        m_aItems[i]->getCtrlText()->setBackgroundColor(SColor(128,255,0,0)); m_aItems[i]->setConflict(true);
      }
    }
  }

  if (!m_pFocused) {
    if (event.EventType==EET_JOYSTICK_INPUT_EVENT) {
      for (u32 i=0; i<6; i++) {
        s16 pos=event.JoystickEvent.Axis[i];
				if (pos<16000 && pos>-16000) pos=0;
        m_aJoyState[event.JoystickEvent.Joystick]->iAxis[i]=pos;
      }

      m_aJoyState[event.JoystickEvent.Joystick]->iPov=event.JoystickEvent.POV;
    }
  }

  return bRet;
}

void CIrrCC::setAllowMouse(bool b) {
  m_bAllowMouse=b;
}

void CIrrCC::setAllowFKeys(bool b) {
  m_bAllowFKeys=b;
}

void CIrrCC::setAllowJoystick(bool b) {
  m_bAllowJoystick=b;
}

f32 CIrrCC::get(u32 idx) {
  //if the queried item is a fader we have to do something special.
  if (m_aItems[idx]->m_eType==eCtrlFader && m_aItems[idx]->m_pBrother!=NULL) {
    //get the two items that make the fader
    CIrrCCItem *p1=m_aItems[idx],*p2=m_aItems[idx]->m_pBrother;
    u32 iTime=m_pDevice->getTimer()->getTime();

    //if the fader is a joy or mouse axis we simply return it's value
    if (p1->m_iType==CC_TYPE_JOY || p1->m_iType==CC_TYPE_MOU) {
      p1->m_fFaderValue=p1->get();
      p2->m_fFaderValue=p1->m_fFaderValue;
    }
    else {
      //if if's a button or a key we need to calculate it's current
      //value from using it's time step and it's diff per step value

      //in case the value is queried for the first time we need to initialize the timer stuff...
      if (p1->m_iFaderLastStep==0) {
        p1->m_iFaderLastStep=iTime;
        p2->m_iFaderLastStep=iTime;
      }
      else {
        //otherwise we calculate and return it's value
        f32 fValue=p1->get();
        while (p1->m_iFaderLastStep<iTime) {
          p1->m_fFaderValue+=fValue*p1->m_fFaderDiff;
          p1->m_iFaderLastStep+=p1->m_iFaderStep;
        }
        if (p1->m_fFaderValue> 1.0f) p1->m_fFaderValue= 1.0f;
        if (p1->m_fFaderValue<-1.0f) p1->m_fFaderValue=-1.0f;
        p2->m_fFaderValue=p1->m_fFaderValue;
      }
    }

    return p1->m_fFaderValue;
  }

  //if it's not a fader we simply return it's value
  return m_aItems[idx]->get();
}

void CIrrCC::set(u32 idx, f32 f) {
  m_aItems[idx]->set(f);
}

bool CIrrCC::OnEvent (const SEvent &event) {
  bool bRet=false;
  u32 i,j;

  if (event.EventType==EET_KEY_INPUT_EVENT)
    for (i=0; i<m_aItems.size(); i++) {
      CIrrCCItem *p=m_aItems[i];
      if (p->m_iType==CC_TYPE_KEY && p->m_eKey==event.KeyInput.Key) {
        p->setWithDetected(event.KeyInput.PressedDown?1.0f:0.0f);
        bRet=true;
      }
    }

  if (event.EventType==EET_JOYSTICK_INPUT_EVENT)
    for (i=0; i<m_aItems.size(); i++) {
      CIrrCCItem *p=m_aItems[i];
      switch (p->m_iType) {
        case CC_TYPE_JOY:
          if (p->m_iJoyId==event.JoystickEvent.Joystick)
            for (j=0; j<6; j++)
              if (p->m_iJoyAxis==j) {
                f32 pos=((f32)event.JoystickEvent.Axis[j])/256.0f;
                if (pos>10.0f) {
                  pos/=127;
                  if (p->m_iJoyDirection<0) p->setWithDetected(pos);
                }
                else
								  if (pos<-10.0f) {
                    pos/=128;
                    if (p->m_iJoyDirection>0) p->setWithDetected(-pos);
                  }
									else p->setWithDetected(0.0f);

                bRet=true;
              }
          break;

        case CC_TYPE_JBT:
          if (p->m_iJoyId==event.JoystickEvent.Joystick)
            p->setWithDetected(event.JoystickEvent.IsButtonPressed(p->m_iJoyButton)?1.0f:0.0f);
          break;

        case CC_TYPE_POV:
          if (p->m_iJoyId==event.JoystickEvent.Joystick) {
            p->setWithDetected(event.JoystickEvent.POV==p->m_iPov?1.0f:0.0f);
            bRet=true;
          }
          break;

        default:
          break;
      }
    }

  if (event.EventType==EET_MOUSE_INPUT_EVENT)
    for (i=0; i<m_aItems.size(); i++) {
      CIrrCCItem *p=m_aItems[i];
      if (p->m_iType==CC_TYPE_MBT) {
        if (p->m_iMouseBtn==0) {
          if (event.MouseInput.Event==EMIE_LMOUSE_PRESSED_DOWN) { p->setWithDetected(1.0f); bRet=true; }
          if (event.MouseInput.Event==EMIE_LMOUSE_LEFT_UP     ) { p->setWithDetected(0.0f); bRet=true; }
        }
        if (p->m_iMouseBtn==1) {
          if (event.MouseInput.Event==EMIE_RMOUSE_PRESSED_DOWN) { p->setWithDetected(1.0f); bRet=true; }
          if (event.MouseInput.Event==EMIE_RMOUSE_LEFT_UP     ) { p->setWithDetected(0.0f); bRet=true; }
        }
        if (p->m_iMouseBtn==2) {
          if (event.MouseInput.Event==EMIE_MMOUSE_PRESSED_DOWN) { p->setWithDetected(1.0f); bRet=true; }
          if (event.MouseInput.Event==EMIE_MMOUSE_LEFT_UP     ) { p->setWithDetected(0.0f); bRet=true; }
        }
      }
    }

  return bRet;
}

void CIrrCC::setMouseSensitivity(f32 f) {
  m_fMouseSensitivity=f;
  for (u32 i=0; i<m_aItems.size(); i++) m_aItems[i]->setMouseSensitivity(f);
}

void CIrrCC::reset() {
  for (u32 i=0; i<m_aItems.size(); i++) {
    m_aItems[i]->m_fValue=0.0f;
    m_aItems[i]->m_fDetectedValue=0.0f;
  }
}

void CIrrCC::createAxis(u32 idx1, u32 idx2) {
  m_aItems[idx1]->m_pBrother=m_aItems[idx2];
  m_aItems[idx2]->m_pBrother=m_aItems[idx1];
}

void CIrrCC::createFader(u32 idx1, u32 idx2, u32 iStep, f32 fInc) {
  CIrrCCItem *item1=idx1<m_aItems.size()?m_aItems[idx1]:NULL,
             *item2=idx2<m_aItems.size()?m_aItems[idx2]:NULL;

  if (item1==NULL || item2==NULL) return;

  item1->m_pBrother=item2;
  item2->m_pBrother=item1;
  item1->m_iFaderStep=iStep;
  item2->m_iFaderStep=iStep;
  item1->m_fFaderDiff= fInc;
  item2->m_fFaderDiff=-fInc;
  item1->m_fFaderValue=0.0f;
  item2->m_fFaderValue=0.0f;
}

void CIrrCC::save(c8 *sFileName) {
  IXMLWriter *pXml=m_pDevice->getFileSystem()->createXMLWriter(sFileName);

  if (pXml) {
    pXml->writeXMLHeader();
    pXml->writeElement(L"IrrConfigControl");
    pXml->writeLineBreak();

    for (u32 i=0; i<m_aItems.size(); i++) {
      CIrrCCItem *p=m_aItems[i];

      array<stringw> names,values;
      wchar_t s[0xFF];

                                         names.push_back(stringw(L"name")); values.push_back(p->m_sName.c_str());
      swprintf(s,0xFF,L"%u",p->m_iSet ); names.push_back(stringw(L"set" )); values.push_back(s                 );
      swprintf(s,0xFF,L"%u",p->m_iType); names.push_back(stringw(L"type")); values.push_back(s                 );

      switch (p->m_iType) {
        case CC_TYPE_KEY:
          names.push_back(stringw(L"key")); swprintf(s,0xFF,L"%u",(u32)p->m_eKey); values.push_back(s);
          break;

        case CC_TYPE_MOU:
          names.push_back(stringw(L"m_axis")); swprintf(s,0xFF,L"%u",p->m_iMouseAxis     ); values.push_back(s);
          names.push_back(stringw(L"m_dir" )); swprintf(s,0xFF,L"%i",p->m_iMouseDirection); values.push_back(s);
          break;

        case CC_TYPE_MBT:
          names.push_back(stringw(L"m_btn")); swprintf(s,0xFF,L"%u",p->m_iMouseBtn); values.push_back(s);
          break;

        case CC_TYPE_JOY:
          names.push_back(stringw(L"joy"   )); swprintf(s,0xFF,L"%u",p->m_iJoyId       ); values.push_back(s);
          names.push_back(stringw(L"j_axis")); swprintf(s,0xFF,L"%u",p->m_iJoyAxis     ); values.push_back(s);
          names.push_back(stringw(L"j_dir" )); swprintf(s,0xFF,L"%i",p->m_iJoyDirection); values.push_back(s);
          break;

        case CC_TYPE_JBT:
          names.push_back(stringw(L"joy"  )); swprintf(s,0xFF,L"%u",p->m_iJoyId    ); values.push_back(s);
          names.push_back(stringw(L"j_btn")); swprintf(s,0xFF,L"%u",p->m_iJoyButton); values.push_back(s);
          break;

        case CC_TYPE_POV:
          names.push_back(stringw(L"joy")); swprintf(s,0xFF,L"%u",p->m_iJoyId); values.push_back(s);
          names.push_back(stringw(L"pov")); swprintf(s,0xFF,L"%u",p->m_iPov  ); values.push_back(s);
          break;

        default:
          break;
      }

      pXml->writeElement(L"control",true,names,values);
      pXml->writeLineBreak();
    }

    pXml->writeClosingTag(L"IrrConfigControl");
    pXml->writeLineBreak();
    pXml->drop();
  }
}

void CIrrCC::load(c8 *sFileName) {
  if (m_bLoaded) return;
  IXMLReaderUTF8 *pXml=m_pDevice->getFileSystem()->createXMLReaderUTF8(sFileName);
  u32 iNum=0;

  if (pXml)
    while (pXml->read() && iNum<m_aItems.size()) {
      if (!strcmp(pXml->getNodeName(),"control")) {
        CIrrCCItem *p=m_aItems[iNum];
        p->m_iType=(IrrConfigControlTypes)(atoi(pXml->getAttributeValue("type")));
        p->m_iSet =atoi(pXml->getAttributeValue("set"));

        switch (p->m_iType) {
          case CC_TYPE_KEY:
            p->setKey((EKEY_CODE)atoi(pXml->getAttributeValue("key")));
            break;

          case CC_TYPE_MOU:
            p->setMouseMove(atoi(pXml->getAttributeValue("m_axis")),atoi(pXml->getAttributeValue("m_dir")));
            break;

          case CC_TYPE_MBT:
            p->setMouseButton(atoi(pXml->getAttributeValue("m_btn")));
            break;

          case CC_TYPE_JOY:
            p->setJoyAxis(atoi(pXml->getAttributeValue("joy")),atoi(pXml->getAttributeValue("j_axis")),atoi(pXml->getAttributeValue("j_dir")));
            break;

          case CC_TYPE_JBT:
            p->setJoyButton(atoi(pXml->getAttributeValue("joy")),atoi(pXml->getAttributeValue("j_btn")));
            break;

          case CC_TYPE_POV:
            p->setPov(atoi(pXml->getAttributeValue("joy")),atoi(pXml->getAttributeValue("pov")));
            break;

          default:
            break;
        }

        p->updateCtrlText();
        iNum++;
      }
    }

  m_bLoaded=true;
}
Last edited by Brainsaw on Mon May 31, 2010 1:38 pm, edited 2 times in total.
Dustbin::Games on the web: https://www.dustbin-online.de/

Dustbin::Games on facebook: https://www.facebook.com/dustbingames/
Dustbin::Games on twitter: https://twitter.com/dustbingames
t0rt0r0
Posts: 76
Joined: Fri May 22, 2009 11:02 am

Post by t0rt0r0 »

Wow nice work ! I will try to implement this on my game, it's the last big missing part =)
Lonesome Ducky
Competition winner
Posts: 1123
Joined: Sun Jun 10, 2007 11:14 pm

Post by Lonesome Ducky »

Looks like you put a lot of work into this, and I'm glad you did :D
d3jake
Posts: 198
Joined: Sat Mar 22, 2008 7:49 pm
Location: United States of America

Post by d3jake »

Dude! This is one of the things I wanted in place before I implemented Physics and continued with powerups. I have to look into it further, but this looks very useful.
The Open Descent Foundation is always looking for programmers! http://www.odf-online.org
"I'll find out if what I deleted was vital here shortly..." -d3jake
Brainsaw
Posts: 1176
Joined: Wed Jan 07, 2004 12:57 pm
Location: Bavaria

Post by Brainsaw »

Thanks for the answers. I know it's not perfect, but I guess it's a good starting point.
Dustbin::Games on the web: https://www.dustbin-online.de/

Dustbin::Games on facebook: https://www.facebook.com/dustbingames/
Dustbin::Games on twitter: https://twitter.com/dustbingames
JP
Posts: 4526
Joined: Tue Sep 13, 2005 2:56 pm
Location: UK
Contact:

Post by JP »

keyCodeToString... why a load of if statements? Why not a switch or if-else? When calling that function it has to execute every single check when really it's generally going to have to do many fewer....
Image Image Image
Josh1billion
Posts: 125
Joined: Thu Dec 11, 2008 9:50 pm
Location: Wisconsin
Contact:

Post by Josh1billion »

Good work, I might use it. :)
JP wrote:keyCodeToString... why a load of if statements? Why not a switch or if-else? When calling that function it has to execute every single check when really it's generally going to have to do many fewer....
The return statements take care of that.
www.JoshForde.com

Latest release: Super Orbulite World.
In development: Season of Dreams and others
Brainsaw
Posts: 1176
Joined: Wed Jan 07, 2004 12:57 pm
Location: Bavaria

Post by Brainsaw »

@JP: I just took the code from the thread I linked in the initial post and didn't mind it's performance. I simply didn't want to write that on my own, so I left it the way it was ;) .

@Josh1billion: thanks. I added it to the game I am working on, and it worked fine. Have to try it with Joypad tonight, but as the demo app works without problems it should not be an issue.
Dustbin::Games on the web: https://www.dustbin-online.de/

Dustbin::Games on facebook: https://www.facebook.com/dustbingames/
Dustbin::Games on twitter: https://twitter.com/dustbingames
JP
Posts: 4526
Joined: Tue Sep 13, 2005 2:56 pm
Location: UK
Contact:

Post by JP »

Josh1billion wrote:Good work, I might use it. :)
JP wrote:keyCodeToString... why a load of if statements? Why not a switch or if-else? When calling that function it has to execute every single check when really it's generally going to have to do many fewer....
The return statements take care of that.
Dur! *shoots self in face* I was tired ok? :oops:
Image Image Image
Halifax
Posts: 1424
Joined: Sun Apr 29, 2007 10:40 pm
Location: $9D95

Post by Halifax »

JP wrote: Dur! *shoots self in face* I was tired ok? :oops:
You're still write about the performance. That function has a worst case of O(n) and a best case of O(1), when it should have a worst and best case of O(1). :wink:

If you implement it in a switch statement as you said, it's almost gauranteed to be optimized down to a jump table which is O(1) in every case.

Not to highjack your thread, but I know someone will want confirmation, so here you go. Take these two examples that do the same thing, seemingly:
Example #1:

Code: Select all

int func1(int a)
{
	if( a == 0 ) return 1;
	if( a == 1 ) return 2;
	if( a == 2 ) return 3;
	if( a == 3 ) return 4;
	if( a == 4 ) return 5;
	return 0;
}
Example #2:

Code: Select all

int func2(int a)
{
	switch( a )
	{
	case 0: return 1;
	case 1: return 2;
	case 2: return 3;
	case 3: return 4;
	case 4: return 5;
	}
	return 0;
}
They may appear to do the same thing, but they most certainly don't. Let's check the disassembly:
Example #1 Disassembly:

Code: Select all

?func1@@YAHH@Z PROC
	test	eax, eax
	jne	SHORT $LN5@func1
	mov	eax, 1
	ret	0
$LN5@func1:
	cmp	eax, 1
	jne	SHORT $LN4@func1
	mov	eax, 2
	ret	0
$LN4@func1:
	cmp	eax, 2
	jne	SHORT $LN3@func1
	mov	eax, 3
	ret	0
$LN3@func1:
	cmp	eax, 3
	jne	SHORT $LN2@func1
	mov	eax, 4
	ret	0
$LN2@func1:
	xor	ecx, ecx
	cmp	eax, 4
	setne	cl
	dec	ecx
	and	ecx, 5
	mov	eax, ecx
	ret	0
?func1@@YAHH@Z ENDP					; func1
Example #2 Disassembly:

Code: Select all

?func2@@YAHH@Z PROC
	cmp	eax, 4
	ja	SHORT $LN6@func2
	jmp	DWORD PTR $LN10@func2[eax*4]
$LN5@func2:
	mov	eax, 1
	ret	0
$LN4@func2:
	mov	eax, 2
	ret	0
$LN3@func2:
	mov	eax, 3
	ret	0
$LN2@func2:
	mov	eax, 4
	ret	0
$LN1@func2:
	mov	eax, 5
	ret	0
$LN6@func2:
	xor	eax, eax
	ret	0
	npad	3
$LN10@func2:
	DD	$LN5@func2
	DD	$LN4@func2
	DD	$LN3@func2
	DD	$LN2@func2
	DD	$LN1@func2
?func2@@YAHH@Z ENDP					; func2
Notice the jump table at the end of the code in 'func2'. Example #1 chains all the comparisions so unneeded comparisions and jumps are done. Example #2 is clearly more efficient in speed and size.

Now let's throw you for a little shocker:

Code: Select all

int func3(int a)
{
	if( a == 0 ) return 1;
	else if( a == 1 ) return 2;
	else if( a == 2 ) return 3;
	else if( a == 3 ) return 4;
	else if( a == 4 ) return 5;
	return 0;
}
Generates:

Code: Select all

?func3@@YAHH@Z PROC
	test	eax, eax
	jne	SHORT $LN9@func3
	mov	eax, 1
	ret	0
$LN9@func3:
	cmp	eax, 1
	jne	SHORT $LN7@func3
	mov	eax, 2
	ret	0
$LN7@func3:
	cmp	eax, 2
	jne	SHORT $LN5@func3
	mov	eax, 3
	ret	0
$LN5@func3:
	cmp	eax, 3
	jne	SHORT $LN3@func3
	mov	eax, 4
	ret	0
$LN3@func3:
	xor	ecx, ecx
	cmp	eax, 4
	setne	cl
	dec	ecx
	and	ecx, 5
	mov	eax, ecx
	ret	0
?func3@@YAHH@Z ENDP					; func3
Does that look familiar? Yes, it generates the same code as Example #1! That's bad!

This brings me to my conclusion. Use switch, they made it for a reason, it's more efficient 99.9% of the time, and don't second guess JP! :lol:
TheQuestion = 2B || !2B
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

And, as always, don't forget to enable optimization. Third case results in

Code: Select all

        .type   func3, @function
func3:
        pushl   %ebp
        movl    $1, %eax
        movl    %esp, %ebp
        movl    8(%ebp), %edx
        testl   %edx, %edx
        je      .L1
        movl    $2, %eax
        cmpl    $1, %edx
        je      .L1
        movl    $3, %eax
        cmpl    $2, %edx
        je      .L1
        movl    $4, %eax
        cmpl    $3, %edx
        je      .L1
        cmpl    $4, %edx
        sete    %dl
        andl    $255, %edx
        leal    (%edx,%edx,4), %eax
.L1:
        popl    %ebp
        ret
When optimization is used, which is definitely not doing more tests than necessary. Moreover, it's even faster than the case version for the first two or three cases, because the switch version needs to set up the comparison table first. Still, it should be preferred for consecutive int comparisons for its better readability.
Brainsaw
Posts: 1176
Joined: Wed Jan 07, 2004 12:57 pm
Location: Bavaria

Post by Brainsaw »

Well, if I had written this myself I think I would have chosen a switch statement ... I like it better. But as I took this part of the code from another thread, and it is not that performance-critical (I mean it's only called on loading a controller set and when you change a control) I'm not going to put any work into that ... that assembly code is interesting nevertheless ... haven't used that kind of stuff for some time ;)
Dustbin::Games on the web: https://www.dustbin-online.de/

Dustbin::Games on facebook: https://www.facebook.com/dustbingames/
Dustbin::Games on twitter: https://twitter.com/dustbingames
Halifax
Posts: 1424
Joined: Sun Apr 29, 2007 10:40 pm
Location: $9D95

Post by Halifax »

Hybrid, I don't exactly know what you're talking about. First off, it only optimized out the return statement, that's it. Second of all, it's still chaining the statements which results in O(n) behavior when you call the function with an input of '4'. Third of all, a jump table does not need to be "setup", it's a constant in memory at the point that the program is executed.

If you check the assembly code I posted for the switch version it takes your input, transforms it into an index for the jump table (i.e. multiplies by 4), and then calls that address by indirection. It's O(1)...constant.

Furthermore I had VC running on /O2 which is speed optimization. /Ox (full optimization) makes no difference. On another note about optimization, most compilers should optimized non-sequential switch-cases by adding padding to the jump table if warranted.

By the way, you're using GCC (at least that's what your disassembly suggest) so there will be some differences.

At any rate, there's no reason to chain if-elseif statements, it's unnecessary.

@Brainsaw: We understand where you are coming. It's just a friendly discussion about coding practices.
TheQuestion = 2B || !2B
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

Ah yes, you're right. It does all the comparisons, just in the negated way with fall-through. However, the gcc disassembly of the switch has some more statements as you found. That's why the else-if code (for gcc) is faster with one or two comparisons. But that might also change with the gcc versions, so no one should use this scheme probably.
sio2
Competition winner
Posts: 1003
Joined: Thu Sep 21, 2006 5:33 pm
Location: UK

Post by sio2 »

Halifax wrote:

Code: Select all

int func1(int a)
{
	if( a == 0 ) return 1;
	if( a == 1 ) return 2;
	if( a == 2 ) return 3;
	if( a == 3 ) return 4;
	if( a == 4 ) return 5;
	return 0;
}
This has contiguous indices so of course it can be reduced to a jump table. But key codes are not necessarily contiguous indices. [I don't know if the compiler can boil the list down to sets of contiguous indices though].

Quickest way would be to fill in the unused indices with null strings and index into an array: return keycodeStringList[e] (with error checking of "e"'s range of course).

This is all moot though unless this call has been highlighted in a profile. Premature optimisation, and all that...
Post Reply