make Input have its own timer, and add key-repeat handling, and fix some warnings
--- a/src/Config.hh Mon Dec 08 21:15:52 2008 +0000
+++ b/src/Config.hh Mon Dec 08 21:18:08 2008 +0000
@@ -25,6 +25,12 @@
// Simulation
const uint16_t PHYSICS_TICK_MS = 10;
+/** Input handling keyboard poll interval */
+const TimeMS INPUT_POLL_INTERVAL = 10;
+
+/** How long to block INPUT_FLAG_NOREPEAT for */
+const TimeMS INPUT_REPEAT_DELAY = 250;
+
/**
* The force of gravity on objects, exerted on the Y axis
*/
--- a/src/Graphics.cc Mon Dec 08 21:15:52 2008 +0000
+++ b/src/Graphics.cc Mon Dec 08 21:18:08 2008 +0000
@@ -20,9 +20,10 @@
update_timer.start();
}
-void Graphics::check_input (TimeMS dt) {
+void Graphics::check_input (void) {
LocalPlayer *player;
- PlayerInput input_mask = 0;
+ PlayerInput input_mask;
+ TimeMS input_dt;
// update gui flags
this->flags = input.readGuiInput();
@@ -43,11 +44,11 @@
player->printDebugInfo();
// build input_mask
- input_mask = input.readPlayerInput();
+ input.readPlayerInput(input_mask, input_dt);
// apply input if there was any
if (input_mask)
- player->handleInput(input_mask, dt);
+ player->handleInput(input_mask, input_dt);
}
static PixelDimension value_between (PixelDimension low, PixelDimension value, PixelDimension high) {
@@ -91,8 +92,10 @@
}
void Graphics::on_update (TimeMS tick_length) {
+ (void) tick_length;
+
// check keyboard input
- check_input(tick_length);
+ check_input();
// redraw display
do_redraw();
--- a/src/Graphics.hh Mon Dec 08 21:15:52 2008 +0000
+++ b/src/Graphics.hh Mon Dec 08 21:18:08 2008 +0000
@@ -42,7 +42,7 @@
CL_Font& getSimpleFont (void) { return simple_font; }
private:
- void check_input (TimeMS dt);
+ void check_input (void);
void do_redraw (void);
void on_update (TimeMS tick_length);
--- a/src/Input.cc Mon Dec 08 21:15:52 2008 +0000
+++ b/src/Input.cc Mon Dec 08 21:18:08 2008 +0000
@@ -1,44 +1,108 @@
-
-#define INPUT_CC
#include "Input.hh"
-
-template <typename BitEnumType> struct InputKeymapEntry {
- BitEnumType input;
- int keycode1, keycode2;
-};
+#include "Config.hh"
InputKeymapEntry<PlayerInputBit> INPUT_PLAYER_KEYMAP[] = {
- { INPUT_AIM_UP, -CL_KEY_ENTER, CL_KEY_UP },
- { INPUT_AIM_DOWN, -CL_KEY_ENTER, CL_KEY_DOWN },
- { INPUT_MOVE_LEFT, -CL_KEY_ENTER, CL_KEY_LEFT },
- { INPUT_MOVE_RIGHT, -CL_KEY_ENTER, CL_KEY_RIGHT },
- { INPUT_JUMP, -CL_KEY_ENTER, CL_KEY_RSHIFT },
- { INPUT_DIG, CL_KEY_LEFT, CL_KEY_RIGHT },
- { INPUT_SHOOT, CL_KEY_RCONTROL, 0 },
- { INPUT_CHANGE_PREV, CL_KEY_ENTER, CL_KEY_LEFT },
- { INPUT_CHANGE_NEXT, CL_KEY_ENTER, CL_KEY_RIGHT },
- { INPUT_ROPE, CL_KEY_ENTER, CL_KEY_RSHIFT },
- { INPUT_UNROPE, -CL_KEY_ENTER, CL_KEY_RSHIFT },
- { INPUT_ROPE_UP, CL_KEY_ENTER, CL_KEY_UP },
- { INPUT_ROPE_DOWN, CL_KEY_ENTER, CL_KEY_DOWN },
- { INPUT_SUICIDE, CL_KEY_LCONTROL, CL_KEY_K },
- { INPUT_NONE, 0, 0 }
+ { INPUT_AIM_UP, INPUT_FLAG_REPEAT, { -CL_KEY_ENTER, CL_KEY_UP } },
+ { INPUT_AIM_DOWN, INPUT_FLAG_REPEAT, { -CL_KEY_ENTER, CL_KEY_DOWN } },
+ { INPUT_MOVE_LEFT, INPUT_FLAG_REPEAT, { -CL_KEY_ENTER, CL_KEY_LEFT } },
+ { INPUT_MOVE_RIGHT, INPUT_FLAG_REPEAT, { -CL_KEY_ENTER, CL_KEY_RIGHT } },
+ { INPUT_JUMP, INPUT_FLAG_NOREPEAT, { -CL_KEY_ENTER, CL_KEY_RSHIFT } },
+ { INPUT_DIG, INPUT_FLAG_NOREPEAT, { CL_KEY_LEFT, CL_KEY_RIGHT } },
+ { INPUT_SHOOT, INPUT_FLAG_REPEAT, { CL_KEY_RCONTROL, 0 } },
+ { INPUT_CHANGE_PREV, INPUT_FLAG_NOREPEAT, { CL_KEY_ENTER, CL_KEY_LEFT } },
+ { INPUT_CHANGE_NEXT, INPUT_FLAG_NOREPEAT, { CL_KEY_ENTER, CL_KEY_RIGHT } },
+ { INPUT_ROPE, INPUT_FLAG_NOREPEAT, { CL_KEY_ENTER, CL_KEY_RSHIFT } },
+ { INPUT_UNROPE, INPUT_FLAG_NOREPEAT, { -CL_KEY_ENTER, CL_KEY_RSHIFT } },
+ { INPUT_ROPE_UP, INPUT_FLAG_REPEAT, { CL_KEY_ENTER, CL_KEY_UP } },
+ { INPUT_ROPE_DOWN, INPUT_FLAG_REPEAT, { 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, CL_KEY_ESCAPE, 0 },
- { GUI_INPUT_DISPLAY_WEAPON, CL_KEY_ENTER, 0 },
- { GUI_INPUT_DEBUG_PLAYER, CL_KEY_I, 0 },
+ { 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_NONE, 0, { 0, 0, } }
};
-Input::Input (CL_InputDevice &keyboard) :
- keyboard(keyboard)
+/*
+ * 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) {
+ 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) {
+ list.push_back(InputKeyRepeatEntry<BitEnumType>(bit, expire));
+}
+
+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();
-bool Input::checkKeycode (int keycode) {
+ // go through each entry, updateExpired and remove if expired
+ while (it != list.end()) {
+ if (it->updateExpired(dt))
+ it = list.erase(it);
+ else
+ it++;
+ }
+}
+
+/*
+ * 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),
+ dt(0),
+ queue(keyrepeat_expire)
+{
+
+}
+
+template <typename BitEnumType, typename BitMaskType>
+bool InputHandler<BitEnumType, BitMaskType>::checkKeycode (int keycode) {
if (keycode > 0)
return keyboard.get_keycode(keycode);
@@ -48,23 +112,71 @@
else // == 0
return true;
}
-
-template <typename BitEnumType, typename BitMaskType> BitMaskType Input::buildMask (InputKeymapEntry<BitEnumType> *keymap) {
- BitMaskType input_mask = 0;
+
+template <typename BitEnumType, typename BitMaskType>
+void InputHandler<BitEnumType, BitMaskType>::readValue (BitMaskType &mask, TimeMS &dt) {
+ // copy to args
+ mask = this->value;
+ dt = this->dt;
- for (InputKeymapEntry<BitEnumType> *e = keymap; (e->keycode1 || e->keycode2) && e->input; e++) {
- if (checkKeycode(e->keycode1) && checkKeycode(e->keycode2))
- input_mask |= e->input;
- }
-
- return input_mask;
+ this->value = 0;
+ this->dt = 0;
}
-PlayerInput Input::readPlayerInput (void) {
- return buildMask<PlayerInputBit, PlayerInput>(INPUT_PLAYER_KEYMAP);
+template <typename BitEnumType, typename BitMaskType>
+void InputHandler<BitEnumType, BitMaskType>::update (TimeMS dt) {
+ // 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])) {
+ // should we do keyrepeats?
+ if (e->flags & INPUT_FLAG_NOREPEAT) {
+ // if it's in the queue, ignore, else add it
+ if (queue.find(e->input))
+ continue;
+ else
+ queue.push(e->input);
+ }
+
+ // set bit in mask
+ this->value |= e->input;
+ }
+ }
+
+ // then increment our dt
+ this->dt += dt;
+}
+
+/**
+ * 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) {
- return buildMask<GuiInputBit, GuiInput>(INPUT_GUI_KEYMAP);
+ GuiInput mask;
+ TimeMS dt;
+
+ gui.readValue(mask, dt);
+
+ return mask;
}
--- a/src/Input.hh Mon Dec 08 21:15:52 2008 +0000
+++ b/src/Input.hh Mon Dec 08 21:18:08 2008 +0000
@@ -1,13 +1,25 @@
#ifndef INPUT_HH
#define INPUT_HH
-#include <stdint.h>
+#include "Timer.hh"
+#include "Types.hh"
+
#include <ClanLib/Display/input_device.h>
#include <ClanLib/Display/keys.h>
+#include <queue>
// const TimeMS INPUT_INTERVAL_MS = 20;
/**
+ * Flags to control input behaviour
+ */
+enum InputFlagBits {
+ /** Default */
+ INPUT_FLAG_REPEAT = 0x0000,
+ INPUT_FLAG_NOREPEAT = 0x0001,
+};
+
+/**
* The bits used in the PlayerInput bitmask, each represents a separate action handled by LocalPlayer::handleInput.
*
* @see LocalPlayer::handleInput
@@ -38,12 +50,19 @@
* The bits used in the GuiInput bitmask, each represents something handled locally by Graphics.
*/
enum GuiInputBit {
+ GUI_INPUT_NONE = 0x0000,
+
GUI_INPUT_QUIT = 0x0001,
GUI_INPUT_DISPLAY_WEAPON = 0x0002,
GUI_INPUT_DEBUG_PLAYER = 0x0004,
};
/**
+ * Bitmask of InputFlagBits
+ */
+typedef uint8_t InputFlags;
+
+/**
* Bitmask of PlayerInputBits
*
* @see PlayerInputBit
@@ -58,29 +77,191 @@
typedef uint16_t GuiInput;
/**
- * Keymap definition used in Input.cc
+ * Keymap definition struct
*/
-template <typename BitEnumType> struct InputKeymapEntry;
+template <typename BitEnumType> struct InputKeymapEntry {
+ /**
+ * The input bit to set if present
+ */
+ BitEnumType input;
+
+ /**
+ * Flags to use
+ *
+ * @see InputFlagBits
+ */
+ InputFlags flags;
+
+ /**
+ * Up to two keycodes to check
+ */
+ int keycodes[2];
+};
+
+
+/**
+ * A InputKeyRepeatQueue entry, this contains the input bit value itself, and then the remaining expire time
+ */
+template <typename BitEnumType> struct InputKeyRepeatEntry {
+ BitEnumType value;
+ TimeMS expire;
+
+ public:
+ InputKeyRepeatEntry (BitEnumType value, TimeMS expire);
+
+ /**
+ * Since priority_queue always gives the greatest item, the one with the longest expire is the least item
+ */
+ bool operator< (const struct InputKeyRepeatEntry &other);
+
+ /**
+ * Decrements expire, returning true if it has now expired, false otherwise
+ */
+ bool updateExpired (TimeMS dt);
+};
+
+/**
+ * A InputKeyRepeatQueue maintains a list of InputKeyRepeatEntry's, lets you add new input values, find old ones,
+ * and update the list
+ */
+template <typename BitEnumType> class InputKeyRepeatQueue {
+ private:
+ TimeMS expire;
+
+ typedef InputKeyRepeatEntry<BitEnumType> entry_type;
+
+ std::list<entry_type> list;
+
+ typedef typename std::list<entry_type>::iterator list_iterator;
+
+ public:
+ /**
+ * Constructs this queue to contain entries with the given expiry time
+ */
+ InputKeyRepeatQueue (TimeMS expire);
+
+ /**
+ * Push a new input bit onto the queue
+ */
+ void push (BitEnumType bit);
+
+ /**
+ * Checks if the given input is in the queue
+ */
+ bool find (BitEnumType bit);
+
+ /**
+ * Updates the list, removing expired items
+ */
+ void update (TimeMS dt);
+};
+
+/**
+ * An InputHandler uses an InputKeymapEntry to maintain a BitMaskType of current inputs
+ */
+template <typename BitEnumType, typename BitMaskType> class InputHandler {
+ private:
+ /**
+ * The keyboard that we read input from
+ */
+ CL_InputDevice &keyboard;
+
+ /**
+ * The keymap that we use
+ */
+ InputKeymapEntry<BitEnumType> *keymap;
+
+ /**
+ * The current bitmask value
+ */
+ BitMaskType value;
+
+ /**
+ * How long the bitmask was held...
+ */
+ TimeMS dt;
+
+ /**
+ * The KeyRepeatQueue
+ */
+ InputKeyRepeatQueue<BitEnumType> queue;
+
+ public:
+ /**
+ * Constructs the InputHandler using the given keyboard, keymap and key-repeat expire time
+ */
+ InputHandler (CL_InputDevice &keyboard, InputKeymapEntry<BitEnumType> *keymap, TimeMS keyrepeat_expire);
+
+ private:
+ /**
+ * Returns true if the keycode is valid, false if not.
+ *
+ * A positive keycode requires that the keycode be active, a negative keycode that the keycode be inactive,
+ * and a zero keycode always returns true.
+ *
+ * @param keycode A positive keycode to check that it's set, negative keycode to check that it's not set, or zero
+ * @returns bool true if positive+set/negavtive+notset/zero, false otherwise
+ */
+ bool checkKeycode (int keycode);
+
+ public:
+ /**
+ * Updates the keyrepeat queue
+ */
+ void update (TimeMS dt);
+
+ /**
+ * Reads the current input mask value and length into mask and dt, and reset ours to zero
+ *
+ * @param mask our BitMaskType value is returned using this
+ * @param dt how long the input was held for
+ */
+ void readValue (BitMaskType &mask, TimeMS &dt);
+
+};
/**
* Handles reading input from a keyboard and mapping it to PlayerInput/GuiInput bitmasks
*/
class Input {
protected:
+ /**
+ * The keyboard device that we use
+ */
CL_InputDevice &keyboard;
+ /**
+ * Our update timer
+ */
+ Timer update_timer;
+
+ CL_SlotContainer slots;
+
+ /**
+ * Our PlayerInput
+ */
+ InputHandler<PlayerInputBit, PlayerInput> player;
+
+ /**
+ * Our GuiInput
+ */
+ InputHandler<GuiInputBit, GuiInput> gui;
+
public:
+ /**
+ * Build the input handler using the given keyboard and the default keymaps
+ */
Input (CL_InputDevice &keyboard);
-
- private:
- bool checkKeycode (int keycode);
- template <typename BitEnumType, typename BitMaskType> BitMaskType buildMask (InputKeymapEntry<BitEnumType> *keymap);
public:
- /*
- * Reads the keyboard to determine current state of the Player/Gui input
+ /**
+ * Reads the current PlayerInput value via mask, and the length of the input via dt
*/
- PlayerInput readPlayerInput (void);
+ void readPlayerInput (PlayerInput &mask, TimeMS &dt);
+
+ /**
+ * Reads the current GuiInput mask and returns it
+ */
GuiInput readGuiInput (void);
};
--- a/src/Projectile.cc Mon Dec 08 21:15:52 2008 +0000
+++ b/src/Projectile.cc Mon Dec 08 21:18:08 2008 +0000
@@ -80,13 +80,14 @@
if (visible) {
PixelCoordinate pos = getCoordinate() - camera;
+ // XXX: scale
PixelDimension radius = (unsigned int) weapon->getRadius();
CL_Quad projectile(
- pos.x, pos.y - weapon->getRadius(),
- pos.x + weapon->getRadius(), pos.y,
- pos.x, pos.y + weapon->getRadius(),
- pos.x - weapon->getRadius(), pos.y
+ pos.x, pos.y - radius,
+ pos.x + radius, pos.y,
+ pos.x, pos.y + radius,
+ pos.x - radius, pos.y
);
gc->fill_quad(projectile, CL_Color::green);
--- a/src/Rope.cc Mon Dec 08 21:15:52 2008 +0000
+++ b/src/Rope.cc Mon Dec 08 21:18:08 2008 +0000
@@ -6,7 +6,10 @@
#include <stdexcept>
Rope::Rope(Player &player) :
- PhysicsObject(player.state.world, ROPE_MASS, Vector(0,0), Vector(0,0), ROPE, 0.00, false), player(player), state(ROPE_FOLDED), pivotObject(NULL)
+ PhysicsObject(player.state.world, ROPE_MASS, Vector(0,0), Vector(0,0), ROPE, 0.00, false),
+ player(player),
+ pivotObject(NULL),
+ state(ROPE_FOLDED)
{
// XXX: better shape
std::vector<Vector> shape(4);
@@ -38,7 +41,6 @@
}
void Rope::onCollision (Vector collisionPoint, PhysicsObject *other) {
-
if (other != NULL) {
if (other->getType() == PLAYER) {
Player *target = dynamic_cast<Player*>(other);
--- a/src/Types.hh Mon Dec 08 21:15:52 2008 +0000
+++ b/src/Types.hh Mon Dec 08 21:18:08 2008 +0000
@@ -32,11 +32,11 @@
/**
* A time interval, measured in real milliseconds
*/
-typedef unsigned long TimeMS;
+typedef signed long TimeMS;
/**
* Abstract tick count, defined as a number of Timer::interval's against real time
*/
-typedef uint32_t TickCount;
+typedef int32_t TickCount;
#endif