/* * 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]; } } } }