src/Graphics/Input.cc
author Tero Marttila <terom@fixme.fi>
Thu, 22 Jan 2009 00:28:26 +0200
branchnew_graphics
changeset 416 38cba347a3a9
parent 414 cede5463b845
permissions -rw-r--r--
clean up InputHandler/GameView to use signals for input

#include "Input.hh"
#include "../Error.hh"
#include "../Config.hh"

#include <cassert>

namespace graphics
{

InputKeymapEntry<PlayerInputBit> INPUT_PLAYER_KEYMAP[] = {
    {   INPUT_AIM_UP,       INPUT_FLAG_UNLIMITED,   { -CL_KEY_ENTER,    CL_KEY_UP       } },
    {   INPUT_AIM_DOWN,     INPUT_FLAG_UNLIMITED,   { -CL_KEY_ENTER,    CL_KEY_DOWN     } },
    {   INPUT_MOVE_LEFT,    INPUT_FLAG_UNLIMITED,   { -CL_KEY_ENTER,    CL_KEY_LEFT     } },
    {   INPUT_MOVE_RIGHT,   INPUT_FLAG_UNLIMITED,   { -CL_KEY_ENTER,    CL_KEY_RIGHT    } },
    {   INPUT_JUMP,         INPUT_FLAG_SLOWREPEAT,  { -CL_KEY_ENTER,    CL_KEY_RSHIFT   } },
    {   INPUT_DIG,          INPUT_FLAG_NOREPEAT,    { CL_KEY_LEFT,      CL_KEY_RIGHT    } },
    {   INPUT_SHOOT,        INPUT_FLAG_UNLIMITED,   { CL_KEY_RCONTROL,  0               } },
    {   INPUT_CHANGE_PREV,  INPUT_FLAG_SLOWREPEAT,  { CL_KEY_ENTER,     CL_KEY_LEFT     } },
    {   INPUT_CHANGE_NEXT,  INPUT_FLAG_SLOWREPEAT,  { CL_KEY_ENTER,     CL_KEY_RIGHT    } },
    {   INPUT_ROPE,         INPUT_FLAG_NOREPEAT,    { CL_KEY_ENTER,     CL_KEY_RSHIFT   } },
    {   INPUT_UNROPE,       INPUT_FLAG_SLOWREPEAT,  { -CL_KEY_ENTER,    CL_KEY_RSHIFT   } },
    {   INPUT_ROPE_UP,      INPUT_FLAG_UNLIMITED,   { CL_KEY_ENTER,     CL_KEY_UP       } },
    {   INPUT_ROPE_DOWN,    INPUT_FLAG_UNLIMITED,   { CL_KEY_ENTER,     CL_KEY_DOWN     } },
    {   INPUT_SUICIDE,      INPUT_FLAG_NOREPEAT,    { CL_KEY_LCONTROL,  CL_KEY_K        } },
    {   INPUT_NONE,         0,                      { 0,                0               } }
};

InputKeymapEntry<GuiInputBit> INPUT_GUI_KEYMAP[] = {
    {   GUI_INPUT_QUIT,                 0,                  { CL_KEY_ESCAPE,    0               } },
    {   GUI_INPUT_DISPLAY_WEAPON,       0,                  { CL_KEY_ENTER,     0               } },
    {   GUI_INPUT_DEBUG_PLAYER,         0,                  { CL_KEY_I,         0               } },
    {   GUI_INPUT_TOGGLE_FULLSCREEN,    INPUT_FLAG_NOREPEAT,{ CL_KEY_LCONTROL,  CL_KEY_F        } },
    {   GUI_INPUT_NONE,                 0,                  { 0,                0,              } }
};

/*
 * InputKeyRepeatEntry
 */
template <typename BitEnumType> 
InputKeyRepeatEntry<BitEnumType>::InputKeyRepeatEntry (BitEnumType value, TimeMS expire) : 
    value(value), expire(expire) 
{

}

template <typename BitEnumType> 
bool InputKeyRepeatEntry<BitEnumType>::operator< (const struct InputKeyRepeatEntry &other) {
    return other.expire > expire;
}

template <typename BitEnumType> 
bool InputKeyRepeatEntry<BitEnumType>::updateExpired (TimeMS dt) {
    if (expire == 0)
        return false;

    expire -= dt;

    return (expire <= 0);
}

/*
 * InputKeyRepeatQueue
 */
template <typename BitEnumType> 
InputKeyRepeatQueue<BitEnumType>::InputKeyRepeatQueue (TimeMS expire) :
    expire(expire)
{

}

template <typename BitEnumType> 
void InputKeyRepeatQueue<BitEnumType>::push (BitEnumType bit, bool expire) {
    list.push_back(InputKeyRepeatEntry<BitEnumType>(bit, expire ? this->expire : 0));
}

template <typename BitEnumType> 
void InputKeyRepeatQueue<BitEnumType>::forget (BitEnumType bit) {
    // go through the list, looking for it
    for (list_iterator it = list.begin(); it != list.end(); it++) {
        if (it->value == bit) {
            // found, erase it and return
            list.erase(it);
            
            return;
        }
    }
}

template <typename BitEnumType> 
bool InputKeyRepeatQueue<BitEnumType>::find (BitEnumType bit) {
    for (list_iterator it = list.begin(); it != list.end(); it++) {
        if (it->value == bit)
            return true;
    }

    return false;
}

template <typename BitEnumType> 
void InputKeyRepeatQueue<BitEnumType>::update (TimeMS dt) {
    list_iterator it = list.begin();
    
    // go through each entry, updateExpired and remove if expired
    while (it != list.end()) {
        if (it->updateExpired(dt))
            it = list.erase(it);
        else
            it++;
    }
}

template <typename BitEnumType> 
void InputKeyRepeatQueue<BitEnumType>::clear (void) {
    // just clear our list of events
    list.clear();
}

/*
 * InputHandler
 */
template <typename BitEnumType, typename BitMaskType> 
InputHandler<BitEnumType, BitMaskType>::InputHandler (CL_InputDevice &keyboard, InputKeymapEntry<BitEnumType> *keymap, TimeMS keyrepeat_expire) :
    keyboard(keyboard), 
    keymap(keymap), 
    value(0), 
    prev_value(0),
    dt(0),
    queue(keyrepeat_expire),
    _enabled(false)
{

}

template <typename BitEnumType, typename BitMaskType> 
bool InputHandler<BitEnumType, BitMaskType>::checkKeycode (int keycode) {
    if (keycode > 0)
        return keyboard.get_keycode(keycode);

    else if (keycode < 0)
        return !keyboard.get_keycode(-keycode);

    else // == 0
        return true;
}
 
template <typename BitEnumType, typename BitMaskType> 
void InputHandler<BitEnumType, BitMaskType>::update (TimeMS dt) {
    // ignore if not enabled
    if (!_enabled)
        return;

    // all bits that are held down, even those ignored
    BitMaskType raw_value = 0, this_value = 0;

    // update the key-repeat queue
    queue.update(dt);

    // then go through the keymap
    for (InputKeymapEntry<BitEnumType> *e = keymap; e->input != 0; e++) {
        // check if we've got the correct keycodes
        if (checkKeycode(e->keycodes[0]) && checkKeycode(e->keycodes[1])) {
            // set raw_value
            raw_value |= e->input;

            if (e->flags & INPUT_FLAG_SLOWREPEAT) {
                // repeat, but slowly
                if (!(prev_value & e->input)) {
                    // we've released the key earlier, move it to the back of the queue
                    queue.forget(e->input);
                    queue.push(e->input);

                } else if (queue.find(e->input)) {
                    // it's still in the queue, so ignore, but set it in ignore_value
                    continue;

                } else {
                    // ok, but add it to the queue
                    queue.push(e->input);
                }

            } else if (e->flags & INPUT_FLAG_NOREPEAT) {
                // do not repeat at all
                if (prev_value & e->input) {
                    // ignore repeats
                    continue;
                }
            }

            // set bit in value mask
            this_value |= e->input;
        }
    }

    // signal unless value was and remains zero
    if (this_value || prev_value) {
        // trigger signal
        _sig_input(this_value, dt);
    } 

    // update prev_value, also adding ignored values
    prev_value = raw_value;

    // update our collective value + dt for use with readValue
    // XXX: remove this functionality?
    value |= this_value;
    this->dt += dt;
}
 
template <typename BitEnumType, typename BitMaskType> 
void InputHandler<BitEnumType, BitMaskType>::readValue (BitMaskType &mask, TimeMS &dt) {
    // throw exception if disabled
    if (!_enabled)
        throw Error("InputHandler is disabled");

    // copy to args
    mask = this->value;
    dt = this->dt;

    this->value = 0;
    this->dt = 0;
}
       
/**
 * Input
 */
Input::Input (CL_InputDevice &keyboard) :
    keyboard(keyboard),
    update_timer(INPUT_POLL_INTERVAL),
    player(keyboard, INPUT_PLAYER_KEYMAP, INPUT_REPEAT_DELAY),
    gui(keyboard, INPUT_GUI_KEYMAP, INPUT_REPEAT_DELAY)
{
    // connect timer 
    slots.connect(update_timer.sig_tick(), &player, &InputHandler<PlayerInputBit, PlayerInput>::update);
    slots.connect(update_timer.sig_tick(), &gui, &InputHandler<GuiInputBit, GuiInput>::update);

    // enable timer
    update_timer.start();
}

void Input::readPlayerInput (PlayerInput &mask, TimeMS &dt) {
    player.readValue(mask, dt);
}

GuiInput Input::readGuiInput (void) {
    GuiInput mask;
    TimeMS dt;

    gui.readValue(mask, dt);

    return mask;
}

}