/*
* InputState.cs
* Authors: August Zinsser
*
* Copyright Matthew Belmonte 2007
*/
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
namespace tAC_Engine
{
///
/// Buffers the keyboard input from the input logic cycles until the next logic cycle.
/// The input logic must be updated at the target accuracy rate of input, but the regular update
/// logic can be updated more slowly.
/// Mouse clicks are polled at the same rate as the keyboard keys to allow accurate click logging,
/// but mouse location is only updated once per call to MouseLocation.
/// Clients of this class can encode keys with experimental codes. If a key is pressed, during
/// the next input logic cycle that code is logged.
/// The keystates are buffered until the next logic update, allowing client games to use this class
/// in place of XNA's keyboardstate.
///
public static class InputState
{
public enum KeyState { Up, Down };
private static bool mLocked = false; // When true, all keys are reported as "Up". Logging is also disabled.
private static Dictionary mPreviousKeyboardStateSnapshot; // The buffered keyboard state (this is considered current to client classes)
private static Dictionary mTwicePreviousKeyboardStateSnapshot; // The buffer from 2 logic updates ago (useful for comparing keystate changes)
private static Dictionary mNextKeyboardStateSnapshot; // The buffer to store keystates between logic updates (about once per frame)
private static Dictionary mPreviousTickKeyboardState; // Polls the keyboard state once per input update (very often)
private static Dictionary mEncodedKeyUps; // Which keys have a code attached to them
private static Dictionary mEncodedKeyDowns; // ''
private static bool mLogSnapshots = false; // Logger setting
private static bool mLogInputUpdateTimes = false; // ''
private static bool mLogKeyChanges = false; // ''
private static bool mCapsLocked = false; // State of Caps lock. May be wrong if the game was started while caps lock was on. Can't seem to get around this with XNA.
private static MouseState mMouseState; // Hold a reference to the mouse state
private const Keys mMouseLeft = Keys.MediaNextTrack; // Map this obscure key to the mouse click
private const Keys mMouseRight = Keys.MediaPreviousTrack; // ''
private const Keys mMouseMiddle = Keys.MediaStop; // ''
private static int mLastScrollWheelValue; // Tracks the change in the scroll wheel
private static int mDeltaScrollWheel; // ''
///
/// Whether or not to record all input snapshots in the input log file
///
public static bool LogSnapshots { set { mLogSnapshots = value; } get { return mLogSnapshots; } }
///
/// Whether or not to record the timestamp at each input check
///
public static bool LogInputUpdateTime { set { mLogInputUpdateTimes = value; } get { return mLogInputUpdateTimes; } }
///
/// When locked, all keys and mouse buttons are reported as Up. Useful for tutorials or debugging.
///
public static bool Locked { set { mLocked = value; } get { return mLocked; } }
public static Vector2 MouseLocation
{
get
{
mMouseState = Mouse.GetState();
return new Vector2(mMouseState.X, mMouseState.Y);
}
}
///
/// Constructor
///
static InputState()
{
// Variable declarations
mPreviousKeyboardStateSnapshot = new Dictionary();
mTwicePreviousKeyboardStateSnapshot = new Dictionary();
mNextKeyboardStateSnapshot = new Dictionary();
mPreviousTickKeyboardState = new Dictionary();
mEncodedKeyUps = new Dictionary();
mEncodedKeyDowns = new Dictionary();
// Register all keys that the game may use (keys not listed cannot be logged)
// (Don't register all keys just to make input a bit more efficient)
mPreviousKeyboardStateSnapshot.Add(Keys.Space, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.A, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.B, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.C, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.D, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.E, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.F, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.G, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.H, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.I, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.J, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.K, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.L, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.M, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.N, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.O, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.P, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.Q, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.R, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.S, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.T, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.U, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.V, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.W, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.X, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.Y, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.Z, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.D0, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.D1, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.D2, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.D3, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.D4, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.D5, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.D6, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.D7, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.D8, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.D9, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.Left, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.Right, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.Up, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.Down, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.Enter, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.LeftShift, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.RightShift, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.LeftAlt, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.RightAlt, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.LeftControl, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.RightControl, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.Escape, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.Back, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.Delete, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.CapsLock, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.OemTilde, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.OemSemicolon, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.OemQuotes, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.OemComma, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.OemOpenBrackets, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.OemCloseBrackets, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.OemPeriod, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.OemPipe, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.OemPlus, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.OemMinus, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.Multiply, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.Divide, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(Keys.OemQuestion, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(mMouseLeft, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(mMouseRight, KeyState.Up);
mPreviousKeyboardStateSnapshot.Add(mMouseMiddle, KeyState.Up);
foreach (KeyValuePair kvp in mPreviousKeyboardStateSnapshot)
{
mNextKeyboardStateSnapshot.Add(kvp.Key, kvp.Value);
mPreviousTickKeyboardState.Add(kvp.Key, kvp.Value);
}
}
///
/// Returns the value of the specified key for this logic update
///
///
///
public static bool IsKeyDown(Keys value)
{
// All keys are "up" if the input state is locked
if (mLocked)
return false;
return mPreviousKeyboardStateSnapshot[value] == KeyState.Down;
}
///
/// Returns the value of the specified key for the last logic update
///
///
///
public static bool WasKeyDown(Keys value)
{
// All keys are "up" if the input state is locked
if (mLocked)
return false;
return mTwicePreviousKeyboardStateSnapshot[value] == KeyState.Down;
}
///
/// Returns the value of the specified key for this logic update
///
///
///
public static bool IsKeyUp(Keys value)
{
// All keys are "up" if the input state is locked
if (mLocked)
return true;
return mPreviousKeyboardStateSnapshot[value] == KeyState.Up;
}
///
/// Returns the value of the specified key for the last logic update
///
///
///
public static bool WasKeyUp(Keys value)
{
// All keys are "up" if the input state is locked
if (mLocked)
return true;
return mTwicePreviousKeyboardStateSnapshot[value] == KeyState.Up;
}
public static bool IsMouseLeftDown
{
get { return IsKeyDown(mMouseLeft); }
}
public static bool IsMouseLeftUp
{
get { return IsKeyUp(mMouseLeft); }
}
public static bool IsMouseRightDown
{
get { return IsKeyDown(mMouseRight); }
}
public static bool IsMouseRightUp
{
get { return IsKeyUp(mMouseRight); }
}
public static bool IsMouseMiddleUp
{
get { return IsKeyUp(mMouseMiddle); }
}
public static bool IsMouseMiddleDown
{
get { return IsKeyDown(mMouseMiddle); }
}
public static bool WasMouseLeftUp
{
get { return WasKeyUp(mMouseLeft); }
}
public static bool WasMouseLeftDown
{
get { return WasKeyDown(mMouseLeft); }
}
public static bool WasMouseRightUp
{
get { return WasKeyUp(mMouseRight); }
}
public static bool WasMouseRightDown
{
get { return WasKeyDown(mMouseRight); }
}
public static bool WasMouseMiddleUp
{
get { return WasKeyUp(mMouseMiddle); }
}
public static bool WasMouseMiddleDown
{
get { return WasKeyDown(mMouseMiddle); }
}
///
/// The change in the cumulative value on the scroll wheel since the last logic update
///
public static int DeltaScrollWheel
{
get { return mDeltaScrollWheel; }
}
///
/// Returns a string almost as if the user was typing into a text box since the last update cycle. Note that
/// only keys registered in the InputState constructor are captured. Also note that keys are not repeated
/// if held down.
///
///
public static string GetTextInput()
{
// This function is not very elegant, but as of this coding XNA does not support text entry
string retStr = "";
bool shifted = (IsKeyDown(Keys.LeftShift) || IsKeyDown(Keys.RightShift));
bool caps = mCapsLocked ^ shifted;
foreach (KeyValuePair kvp in mPreviousKeyboardStateSnapshot)
{
// Make sure the key isn't being held down
if (mTwicePreviousKeyboardStateSnapshot[kvp.Key] == KeyState.Down)
continue;
if (kvp.Value == KeyState.Down)
{
// Check for the keys that can be returned as their enum names
if (kvp.Key == Keys.A ||
kvp.Key == Keys.B ||
kvp.Key == Keys.C ||
kvp.Key == Keys.D ||
kvp.Key == Keys.E ||
kvp.Key == Keys.F ||
kvp.Key == Keys.G ||
kvp.Key == Keys.H ||
kvp.Key == Keys.I ||
kvp.Key == Keys.J ||
kvp.Key == Keys.K ||
kvp.Key == Keys.L ||
kvp.Key == Keys.M ||
kvp.Key == Keys.N ||
kvp.Key == Keys.O ||
kvp.Key == Keys.P ||
kvp.Key == Keys.Q ||
kvp.Key == Keys.R ||
kvp.Key == Keys.S ||
kvp.Key == Keys.T ||
kvp.Key == Keys.U ||
kvp.Key == Keys.V ||
kvp.Key == Keys.W ||
kvp.Key == Keys.X ||
kvp.Key == Keys.Y ||
kvp.Key == Keys.Z)
{
if (caps)
retStr += kvp.Key.ToString();
else
retStr += kvp.Key.ToString().ToLower();
}
// Now check the other typable characters
if (kvp.Key == Keys.Space)
retStr += " ";
if (kvp.Key == Keys.D0 && !shifted || kvp.Key == Keys.NumPad0)
retStr += "0";
if (kvp.Key == Keys.D1 && !shifted || kvp.Key == Keys.NumPad1)
retStr += "1";
if (kvp.Key == Keys.D2 && !shifted || kvp.Key == Keys.NumPad2)
retStr += "2";
if (kvp.Key == Keys.D3 && !shifted || kvp.Key == Keys.NumPad3)
retStr += "3";
if (kvp.Key == Keys.D4 && !shifted || kvp.Key == Keys.NumPad4)
retStr += "4";
if (kvp.Key == Keys.D5 && !shifted || kvp.Key == Keys.NumPad5)
retStr += "5";
if (kvp.Key == Keys.D6 && !shifted || kvp.Key == Keys.NumPad6)
retStr += "6";
if (kvp.Key == Keys.D7 && !shifted || kvp.Key == Keys.NumPad7)
retStr += "7";
if (kvp.Key == Keys.D8 && !shifted || kvp.Key == Keys.NumPad8)
retStr += "8";
if (kvp.Key == Keys.D9 && !shifted || kvp.Key == Keys.NumPad9)
retStr += "9";
if (kvp.Key == Keys.D0 && shifted)
retStr += ")";
if (kvp.Key == Keys.D1 && shifted)
retStr += "!";
if (kvp.Key == Keys.D2 && shifted)
retStr += "@";
if (kvp.Key == Keys.D3 && shifted)
retStr += "#";
if (kvp.Key == Keys.D4 && shifted)
retStr += "$";
if (kvp.Key == Keys.D5 && shifted)
retStr += "%";
if (kvp.Key == Keys.D6 && shifted)
retStr += "^";
if (kvp.Key == Keys.D7 && shifted)
retStr += "&";
if (kvp.Key == Keys.D8 && shifted)
retStr += "*";
if (kvp.Key == Keys.D9 && shifted)
retStr += "(";
if (kvp.Key == Keys.OemSemicolon)
if (shifted)
retStr += ":";
else
retStr += ";";
if (kvp.Key == Keys.OemQuotes)
if (shifted)
retStr += "\"";
else
retStr += "'";
if (kvp.Key == Keys.OemComma)
if (shifted)
retStr += "<";
else
retStr += ",";
if (kvp.Key == Keys.OemOpenBrackets)
if (shifted)
retStr += "{";
else
retStr += "[";
if (kvp.Key == Keys.OemCloseBrackets)
if (shifted)
retStr += "}";
else
retStr += "]";
if (kvp.Key == Keys.OemPeriod)
if (shifted)
retStr += ">";
else
retStr += ".";
if (kvp.Key == Keys.OemPipe)
if (shifted)
retStr += "|";
else
retStr += "\\";
if (kvp.Key == Keys.OemPlus)
if (shifted)
retStr += "+";
else
retStr += "=";
if (kvp.Key == Keys.OemMinus)
if (shifted)
retStr += "_";
else
retStr += "-";
if (kvp.Key == Keys.Multiply)
retStr += "*";
if (kvp.Key == Keys.Divide)
retStr += "/";
if (kvp.Key == Keys.OemQuestion)
if (shifted)
retStr += "?";
else
retStr += "/";
}
}
return retStr;
}
///
/// Assigns an experimental code to a specific key so that code is logged when the key is pushed/released
///
/// Which key to encode
/// Encode it for keyup or keydown
/// The code tagged the key
public static void Encode(Keys key, KeyState state, Logger.ExperimentalCode code)
{
if (state == KeyState.Up)
{
Decode(key, state);
mEncodedKeyUps.Add(key, code);
}
else
{
Decode(key, state);
mEncodedKeyDowns.Add(key, code);
}
}
///
/// Assigns an experimental code to the left mouse button so that code is logged when it is pushed/released
///
public static void EncodeMouseLeft(KeyState state, Logger.ExperimentalCode code)
{
Encode(mMouseLeft, state, code);
}
///
/// Assigns an experimental code to the right mouse button so that code is logged when it is pushed/released
///
public static void EncodeMouseRight(KeyState state, Logger.ExperimentalCode code)
{
Encode(mMouseRight, state, code);
}
///
/// Assigns an experimental code to the middle mouse button so that code is logged when it is pushed/released
///
public static void EncodeMouseMiddle(KeyState state, Logger.ExperimentalCode code)
{
Encode(mMouseMiddle, state, code);
}
///
/// Removes any encoding on the specified key event
///
///
///
public static void Decode(Keys key, KeyState state)
{
if (state == KeyState.Up)
{
if (mEncodedKeyUps.ContainsKey(key))
mEncodedKeyUps.Remove(key);
}
else
{
if (mEncodedKeyDowns.ContainsKey(key))
mEncodedKeyDowns.Remove(key);
}
}
///
/// Removes any encoding on the specified key event
///
public static void DecodeMouseLeft(KeyState state)
{
Decode(mMouseLeft, state);
}
///
/// Removes any encoding on the specified key event
///
public static void DecodeMouseRight(KeyState state)
{
Decode(mMouseRight, state);
}
///
/// Removes any encoding on the specified key event
///
public static void DecodeMouseMiddle(KeyState state)
{
Decode(mMouseMiddle, state);
}
///
/// Removes all encoding from all keys
///
public static void DecodeAll()
{
List deathRow = new List();
foreach (KeyValuePair kvp in mEncodedKeyUps)
deathRow.Add(kvp.Key);
foreach (Keys key in deathRow)
mEncodedKeyUps.Remove(key);
deathRow.Clear();
foreach (KeyValuePair kvp in mEncodedKeyDowns)
deathRow.Add(kvp.Key);
foreach (Keys key in deathRow)
mEncodedKeyDowns.Remove(key);
}
///
/// Call this once per logic update from the main game manager.
/// Copies all buffered keypresses so the next regular logic update cycle can have access
/// to any keys pressed since the last logic update cycle.
///
public static void TakeSnapshot()
{
// Toggle lock settings
if (mPreviousKeyboardStateSnapshot[Keys.CapsLock] != mNextKeyboardStateSnapshot[Keys.CapsLock])
mCapsLocked = !mCapsLocked;
// Update snapshots
List downers = new List();
foreach (KeyValuePair kvp in mPreviousKeyboardStateSnapshot)
{
mTwicePreviousKeyboardStateSnapshot[kvp.Key] = kvp.Value;
}
foreach (KeyValuePair kvp in mPreviousTickKeyboardState)
{
if (mNextKeyboardStateSnapshot[kvp.Key] == KeyState.Down)
downers.Add(kvp.Key);
mPreviousKeyboardStateSnapshot[kvp.Key] = mNextKeyboardStateSnapshot[kvp.Key];
if (mPreviousTickKeyboardState[kvp.Key] == KeyState.Up)
mNextKeyboardStateSnapshot[kvp.Key] = KeyState.Up;
}
if (mLogSnapshots)
Logger.LogSnapshot(downers);
// Update scroll wheel
MouseState mouse = Mouse.GetState();
mDeltaScrollWheel = mouse.ScrollWheelValue - mLastScrollWheelValue;
mLastScrollWheelValue = mouse.ScrollWheelValue;
}
///
/// Check for input at each input update. These values are buffered and are not accessible
/// to client classes until TakeSnapshot() is called.
///
public static void Update()
{
// Next snapshot needs to retain any pressed keys until the following snapshot
// Releasing a key only counts if it was up on the last snapshot
// If flagged, log each input state at each update cycle
if (mLogInputUpdateTimes)
Logger.LogInputCheck();
KeyboardState currentTickKeyboardState = Keyboard.GetState();
MouseState currentTickMouseState = Mouse.GetState();
ButtonState currentTickLeft = currentTickMouseState.LeftButton;
ButtonState currentTickRight = currentTickMouseState.RightButton;
ButtonState currentTickMiddle = currentTickMouseState.MiddleButton;
// Update input for the game
foreach(KeyValuePair kvp in mPreviousKeyboardStateSnapshot)
{
if (mPreviousTickKeyboardState[kvp.Key] == KeyState.Up &&
(currentTickKeyboardState.IsKeyDown(kvp.Key) || (kvp.Key == mMouseLeft && currentTickLeft == ButtonState.Pressed)
|| (kvp.Key == mMouseRight && currentTickRight == ButtonState.Pressed)
|| (kvp.Key == mMouseMiddle && currentTickMiddle == ButtonState.Pressed)))
{
// This key was just pressed, so hold it until the next snapshot
mNextKeyboardStateSnapshot[kvp.Key] = KeyState.Down;
if (mLogKeyChanges)
Logger.LogKeyDown(kvp.Key);
// Log the code (if encoded)
if (mEncodedKeyDowns.ContainsKey(kvp.Key))
Logger.LogCode(mEncodedKeyDowns[kvp.Key]);
}
if (mPreviousTickKeyboardState[kvp.Key] == KeyState.Down &&
(currentTickKeyboardState.IsKeyUp(kvp.Key) || (kvp.Key == mMouseLeft && currentTickLeft == ButtonState.Released
|| kvp.Key == mMouseRight && currentTickRight == ButtonState.Released
|| kvp.Key == mMouseMiddle && currentTickMiddle == ButtonState.Released)))
{
// This key was just released, but only count the release if it is a change from the last snapshot
if (mPreviousKeyboardStateSnapshot[kvp.Key] == KeyState.Down)
mNextKeyboardStateSnapshot[kvp.Key] = KeyState.Up;
if (mLogKeyChanges)
Logger.LogKeyUp(kvp.Key);
// Log the code (if encoded)
if (mEncodedKeyUps.ContainsKey(kvp.Key))
Logger.LogCode(mEncodedKeyUps[kvp.Key]);
}
mPreviousTickKeyboardState[kvp.Key] = (InputState.KeyState)currentTickKeyboardState[kvp.Key];
}
}
}
}