summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework/Input
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework/Input')
-rw-r--r--src/SMAPI/Framework/Input/GamePadStateBuilder.cs162
-rw-r--r--src/SMAPI/Framework/Input/InputState.cs163
-rw-r--r--src/SMAPI/Framework/Input/SInputState.cs382
3 files changed, 544 insertions, 163 deletions
diff --git a/src/SMAPI/Framework/Input/GamePadStateBuilder.cs b/src/SMAPI/Framework/Input/GamePadStateBuilder.cs
new file mode 100644
index 00000000..33557385
--- /dev/null
+++ b/src/SMAPI/Framework/Input/GamePadStateBuilder.cs
@@ -0,0 +1,162 @@
+using System.Collections.Generic;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Input;
+
+namespace StardewModdingAPI.Framework.Input
+{
+ /// <summary>An abstraction for manipulating controller state.</summary>
+ internal class GamePadStateBuilder
+ {
+ /*********
+ ** Properties
+ *********/
+ /// <summary>The current button states.</summary>
+ private readonly IDictionary<SButton, ButtonState> ButtonStates;
+
+ /// <summary>The left trigger value.</summary>
+ private float LeftTrigger;
+
+ /// <summary>The right trigger value.</summary>
+ private float RightTrigger;
+
+ /// <summary>The left thumbstick position.</summary>
+ private Vector2 LeftStickPos;
+
+ /// <summary>The left thumbstick position.</summary>
+ private Vector2 RightStickPos;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="state">The initial controller state.</param>
+ public GamePadStateBuilder(GamePadState state)
+ {
+ this.ButtonStates = new Dictionary<SButton, ButtonState>
+ {
+ [SButton.DPadUp] = state.DPad.Up,
+ [SButton.DPadDown] = state.DPad.Down,
+ [SButton.DPadLeft] = state.DPad.Left,
+ [SButton.DPadRight] = state.DPad.Right,
+
+ [SButton.ControllerA] = state.Buttons.A,
+ [SButton.ControllerB] = state.Buttons.B,
+ [SButton.ControllerX] = state.Buttons.X,
+ [SButton.ControllerY] = state.Buttons.Y,
+ [SButton.LeftStick] = state.Buttons.LeftStick,
+ [SButton.RightStick] = state.Buttons.RightStick,
+ [SButton.LeftShoulder] = state.Buttons.LeftShoulder,
+ [SButton.RightShoulder] = state.Buttons.RightShoulder,
+ [SButton.ControllerBack] = state.Buttons.Back,
+ [SButton.ControllerStart] = state.Buttons.Start,
+ [SButton.BigButton] = state.Buttons.BigButton
+ };
+ this.LeftTrigger = state.Triggers.Left;
+ this.RightTrigger = state.Triggers.Right;
+ this.LeftStickPos = state.ThumbSticks.Left;
+ this.RightStickPos = state.ThumbSticks.Right;
+ }
+
+ /// <summary>Mark all matching buttons unpressed.</summary>
+ /// <param name="buttons">The buttons.</param>
+ public void SuppressButtons(IEnumerable<SButton> buttons)
+ {
+ foreach (SButton button in buttons)
+ this.SuppressButton(button);
+ }
+
+ /// <summary>Mark a button unpressed.</summary>
+ /// <param name="button">The button.</param>
+ public void SuppressButton(SButton button)
+ {
+ switch (button)
+ {
+ // left thumbstick
+ case SButton.LeftThumbstickUp:
+ if (this.LeftStickPos.Y > 0)
+ this.LeftStickPos.Y = 0;
+ break;
+ case SButton.LeftThumbstickDown:
+ if (this.LeftStickPos.Y < 0)
+ this.LeftStickPos.Y = 0;
+ break;
+ case SButton.LeftThumbstickLeft:
+ if (this.LeftStickPos.X < 0)
+ this.LeftStickPos.X = 0;
+ break;
+ case SButton.LeftThumbstickRight:
+ if (this.LeftStickPos.X > 0)
+ this.LeftStickPos.X = 0;
+ break;
+
+ // right thumbstick
+ case SButton.RightThumbstickUp:
+ if (this.RightStickPos.Y > 0)
+ this.RightStickPos.Y = 0;
+ break;
+ case SButton.RightThumbstickDown:
+ if (this.RightStickPos.Y < 0)
+ this.RightStickPos.Y = 0;
+ break;
+ case SButton.RightThumbstickLeft:
+ if (this.RightStickPos.X < 0)
+ this.RightStickPos.X = 0;
+ break;
+ case SButton.RightThumbstickRight:
+ if (this.RightStickPos.X > 0)
+ this.RightStickPos.X = 0;
+ break;
+
+ // triggers
+ case SButton.LeftTrigger:
+ this.LeftTrigger = 0;
+ break;
+ case SButton.RightTrigger:
+ this.RightTrigger = 0;
+ break;
+
+ // buttons
+ default:
+ if (this.ButtonStates.ContainsKey(button))
+ this.ButtonStates[button] = ButtonState.Released;
+ break;
+ }
+ }
+
+ /// <summary>Construct an equivalent gamepad state.</summary>
+ public GamePadState ToGamePadState()
+ {
+ return new GamePadState(
+ leftThumbStick: this.LeftStickPos,
+ rightThumbStick: this.RightStickPos,
+ leftTrigger: this.LeftTrigger,
+ rightTrigger: this.RightTrigger,
+ buttons: this.GetBitmask(this.GetPressedButtons()) // MonoDevelop requires one bitmask here; don't specify multiple values
+ );
+ }
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Get all pressed buttons.</summary>
+ private IEnumerable<Buttons> GetPressedButtons()
+ {
+ foreach (var pair in this.ButtonStates)
+ {
+ if (pair.Value == ButtonState.Pressed && pair.Key.TryGetController(out Buttons button))
+ yield return button;
+ }
+ }
+
+ /// <summary>Get a bitmask representing the given buttons.</summary>
+ /// <param name="buttons">The buttons to represent.</param>
+ private Buttons GetBitmask(IEnumerable<Buttons> buttons)
+ {
+ Buttons flag = 0;
+ foreach (Buttons button in buttons)
+ flag |= button;
+ return flag;
+ }
+ }
+}
diff --git a/src/SMAPI/Framework/Input/InputState.cs b/src/SMAPI/Framework/Input/InputState.cs
deleted file mode 100644
index 8b0108ae..00000000
--- a/src/SMAPI/Framework/Input/InputState.cs
+++ /dev/null
@@ -1,163 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using Microsoft.Xna.Framework;
-using Microsoft.Xna.Framework.Input;
-using StardewValley;
-
-namespace StardewModdingAPI.Framework.Input
-{
- /// <summary>A summary of input changes during an update frame.</summary>
- internal class InputState
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The underlying controller state.</summary>
- public GamePadState ControllerState { get; }
-
- /// <summary>The underlying keyboard state.</summary>
- public KeyboardState KeyboardState { get; }
-
- /// <summary>The underlying mouse state.</summary>
- public MouseState MouseState { get; }
-
- /// <summary>The mouse position on the screen adjusted for the zoom level.</summary>
- public Point MousePosition { get; }
-
- /// <summary>The buttons which were pressed, held, or released.</summary>
- public IDictionary<SButton, InputStatus> ActiveButtons { get; } = new Dictionary<SButton, InputStatus>();
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an empty instance.</summary>
- public InputState() { }
-
- /// <summary>Construct an instance.</summary>
- /// <param name="previousState">The previous input state.</param>
- /// <param name="controllerState">The current controller state.</param>
- /// <param name="keyboardState">The current keyboard state.</param>
- /// <param name="mouseState">The current mouse state.</param>
- public InputState(InputState previousState, GamePadState controllerState, KeyboardState keyboardState, MouseState mouseState)
- {
- // init properties
- this.ControllerState = controllerState;
- this.KeyboardState = keyboardState;
- this.MouseState = mouseState;
- this.MousePosition = new Point((int)(mouseState.X * (1.0 / Game1.options.zoomLevel)), (int)(mouseState.Y * (1.0 / Game1.options.zoomLevel))); // derived from Game1::getMouseX
-
- // get button states
- SButton[] down = InputState.GetPressedButtons(keyboardState, mouseState, controllerState).ToArray();
- foreach (SButton button in down)
- this.ActiveButtons[button] = this.GetStatus(previousState.GetStatus(button), isDown: true);
- foreach (KeyValuePair<SButton, InputStatus> prev in previousState.ActiveButtons)
- {
- if (prev.Value.IsDown() && !this.ActiveButtons.ContainsKey(prev.Key))
- this.ActiveButtons[prev.Key] = InputStatus.Released;
- }
- }
-
- /// <summary>Get the status of a button.</summary>
- /// <param name="button">The button to check.</param>
- public InputStatus GetStatus(SButton button)
- {
- return this.ActiveButtons.TryGetValue(button, out InputStatus status) ? status : InputStatus.None;
- }
-
- /// <summary>Get whether a given button was pressed or held.</summary>
- /// <param name="button">The button to check.</param>
- public bool IsDown(SButton button)
- {
- return this.GetStatus(button).IsDown();
- }
-
- /// <summary>Get the current input state.</summary>
- /// <param name="previousState">The previous input state.</param>
- public static InputState GetState(InputState previousState)
- {
- GamePadState controllerState = GamePad.GetState(PlayerIndex.One);
- KeyboardState keyboardState = Keyboard.GetState();
- MouseState mouseState = Mouse.GetState();
-
- return new InputState(previousState, controllerState, keyboardState, mouseState);
- }
-
- /*********
- ** Private methods
- *********/
- /// <summary>Get the status of a button.</summary>
- /// <param name="oldStatus">The previous button status.</param>
- /// <param name="isDown">Whether the button is currently down.</param>
- public InputStatus GetStatus(InputStatus oldStatus, bool isDown)
- {
- if (isDown && oldStatus.IsDown())
- return InputStatus.Held;
- if (isDown)
- return InputStatus.Pressed;
- return InputStatus.Released;
- }
-
- /// <summary>Get the buttons pressed in the given stats.</summary>
- /// <param name="keyboard">The keyboard state.</param>
- /// <param name="mouse">The mouse state.</param>
- /// <param name="controller">The controller state.</param>
- private static IEnumerable<SButton> GetPressedButtons(KeyboardState keyboard, MouseState mouse, GamePadState controller)
- {
- // keyboard
- foreach (Keys key in keyboard.GetPressedKeys())
- yield return key.ToSButton();
-
- // mouse
- if (mouse.LeftButton == ButtonState.Pressed)
- yield return SButton.MouseLeft;
- if (mouse.RightButton == ButtonState.Pressed)
- yield return SButton.MouseRight;
- if (mouse.MiddleButton == ButtonState.Pressed)
- yield return SButton.MouseMiddle;
- if (mouse.XButton1 == ButtonState.Pressed)
- yield return SButton.MouseX1;
- if (mouse.XButton2 == ButtonState.Pressed)
- yield return SButton.MouseX2;
-
- // controller
- if (controller.IsConnected)
- {
- if (controller.Buttons.A == ButtonState.Pressed)
- yield return SButton.ControllerA;
- if (controller.Buttons.B == ButtonState.Pressed)
- yield return SButton.ControllerB;
- if (controller.Buttons.Back == ButtonState.Pressed)
- yield return SButton.ControllerBack;
- if (controller.Buttons.BigButton == ButtonState.Pressed)
- yield return SButton.BigButton;
- if (controller.Buttons.LeftShoulder == ButtonState.Pressed)
- yield return SButton.LeftShoulder;
- if (controller.Buttons.LeftStick == ButtonState.Pressed)
- yield return SButton.LeftStick;
- if (controller.Buttons.RightShoulder == ButtonState.Pressed)
- yield return SButton.RightShoulder;
- if (controller.Buttons.RightStick == ButtonState.Pressed)
- yield return SButton.RightStick;
- if (controller.Buttons.Start == ButtonState.Pressed)
- yield return SButton.ControllerStart;
- if (controller.Buttons.X == ButtonState.Pressed)
- yield return SButton.ControllerX;
- if (controller.Buttons.Y == ButtonState.Pressed)
- yield return SButton.ControllerY;
- if (controller.DPad.Up == ButtonState.Pressed)
- yield return SButton.DPadUp;
- if (controller.DPad.Down == ButtonState.Pressed)
- yield return SButton.DPadDown;
- if (controller.DPad.Left == ButtonState.Pressed)
- yield return SButton.DPadLeft;
- if (controller.DPad.Right == ButtonState.Pressed)
- yield return SButton.DPadRight;
- if (controller.Triggers.Left > 0.2f)
- yield return SButton.LeftTrigger;
- if (controller.Triggers.Right > 0.2f)
- yield return SButton.RightTrigger;
- }
- }
- }
-}
diff --git a/src/SMAPI/Framework/Input/SInputState.cs b/src/SMAPI/Framework/Input/SInputState.cs
new file mode 100644
index 00000000..0228db0d
--- /dev/null
+++ b/src/SMAPI/Framework/Input/SInputState.cs
@@ -0,0 +1,382 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Input;
+using StardewValley;
+
+#pragma warning disable 809 // obsolete override of non-obsolete method (this is deliberate)
+namespace StardewModdingAPI.Framework.Input
+{
+ /// <summary>Manages the game's input state.</summary>
+ internal sealed class SInputState : InputState
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The maximum amount of direction to ignore for the left thumbstick.</summary>
+ private const float LeftThumbstickDeadZone = 0.2f;
+
+ /// <summary>The cursor position on the screen adjusted for the zoom level.</summary>
+ private CursorPosition CursorPositionImpl;
+
+
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The controller state as of the last update.</summary>
+ public GamePadState RealController { get; private set; }
+
+ /// <summary>The keyboard state as of the last update.</summary>
+ public KeyboardState RealKeyboard { get; private set; }
+
+ /// <summary>The mouse state as of the last update.</summary>
+ public MouseState RealMouse { get; private set; }
+
+ /// <summary>A derivative of <see cref="RealController"/> which suppresses the buttons in <see cref="SuppressButtons"/>.</summary>
+ public GamePadState SuppressedController { get; private set; }
+
+ /// <summary>A derivative of <see cref="RealKeyboard"/> which suppresses the buttons in <see cref="SuppressButtons"/>.</summary>
+ public KeyboardState SuppressedKeyboard { get; private set; }
+
+ /// <summary>A derivative of <see cref="RealMouse"/> which suppresses the buttons in <see cref="SuppressButtons"/>.</summary>
+ public MouseState SuppressedMouse { get; private set; }
+
+ /// <summary>The cursor position on the screen adjusted for the zoom level.</summary>
+ public ICursorPosition CursorPosition => this.CursorPositionImpl;
+
+ /// <summary>The buttons which were pressed, held, or released.</summary>
+ public IDictionary<SButton, InputStatus> ActiveButtons { get; private set; } = new Dictionary<SButton, InputStatus>();
+
+ /// <summary>The buttons to suppress when the game next handles input. Each button is suppressed until it's released.</summary>
+ public HashSet<SButton> SuppressButtons { get; } = new HashSet<SButton>();
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Get a copy of the current state.</summary>
+ public SInputState Clone()
+ {
+ return new SInputState
+ {
+ ActiveButtons = this.ActiveButtons,
+ RealController = this.RealController,
+ RealKeyboard = this.RealKeyboard,
+ RealMouse = this.RealMouse,
+ CursorPositionImpl = this.CursorPositionImpl
+ };
+ }
+
+ /// <summary>This method is called by the game, and does nothing since SMAPI will already have updated by that point.</summary>
+ [Obsolete("This method should only be called by the game itself.")]
+ public override void Update() { }
+
+ /// <summary>Update the current button statuses for the given tick.</summary>
+ public void TrueUpdate()
+ {
+ try
+ {
+ // get new states
+ GamePadState realController = GamePad.GetState(PlayerIndex.One);
+ KeyboardState realKeyboard = Keyboard.GetState();
+ MouseState realMouse = Mouse.GetState();
+ var activeButtons = this.DeriveStatuses(this.ActiveButtons, realKeyboard, realMouse, realController);
+ Vector2 cursorAbsolutePos = new Vector2(realMouse.X + Game1.viewport.X, realMouse.Y + Game1.viewport.Y);
+
+ // update real states
+ this.ActiveButtons = activeButtons;
+ this.RealController = realController;
+ this.RealKeyboard = realKeyboard;
+ this.RealMouse = realMouse;
+ if (this.CursorPositionImpl?.AbsolutePixels != cursorAbsolutePos)
+ this.CursorPositionImpl = this.GetCursorPosition(realMouse, cursorAbsolutePos);
+
+ // update suppressed states
+ this.SuppressButtons.RemoveWhere(p => !this.GetStatus(activeButtons, p).IsDown());
+ this.UpdateSuppression();
+ }
+ catch (InvalidOperationException)
+ {
+ // GetState() may crash for some players if window doesn't have focus but game1.IsActive == true
+ }
+ }
+
+ /// <summary>Apply input suppression to current input.</summary>
+ public void UpdateSuppression()
+ {
+ GamePadState suppressedController = this.RealController;
+ KeyboardState suppressedKeyboard = this.RealKeyboard;
+ MouseState suppressedMouse = this.RealMouse;
+
+ this.SuppressGivenStates(this.ActiveButtons, ref suppressedKeyboard, ref suppressedMouse, ref suppressedController);
+
+ this.SuppressedController = suppressedController;
+ this.SuppressedKeyboard = suppressedKeyboard;
+ this.SuppressedMouse = suppressedMouse;
+ }
+
+ /// <summary>Get the gamepad state visible to the game.</summary>
+ [Obsolete("This method should only be called by the game itself.")]
+ public override GamePadState GetGamePadState()
+ {
+ return this.ShouldSuppressNow()
+ ? this.SuppressedController
+ : this.RealController;
+ }
+
+ /// <summary>Get the keyboard state visible to the game.</summary>
+ [Obsolete("This method should only be called by the game itself.")]
+ public override KeyboardState GetKeyboardState()
+ {
+ return this.ShouldSuppressNow()
+ ? this.SuppressedKeyboard
+ : this.RealKeyboard;
+ }
+
+ /// <summary>Get the keyboard state visible to the game.</summary>
+ [Obsolete("This method should only be called by the game itself.")]
+ public override MouseState GetMouseState()
+ {
+ return this.ShouldSuppressNow()
+ ? this.SuppressedMouse
+ : this.RealMouse;
+ }
+
+ /// <summary>Get whether a given button was pressed or held.</summary>
+ /// <param name="button">The button to check.</param>
+ public bool IsDown(SButton button)
+ {
+ return this.GetStatus(this.ActiveButtons, button).IsDown();
+ }
+
+ /// <summary>Get whether any of the given buttons were pressed or held.</summary>
+ /// <param name="buttons">The buttons to check.</param>
+ public bool IsAnyDown(InputButton[] buttons)
+ {
+ return buttons.Any(button => this.IsDown(button.ToSButton()));
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Get the current cursor position.</summary>
+ /// <param name="mouseState">The current mouse state.</param>
+ /// <param name="absolutePixels">The absolute pixel position relative to the map.</param>
+ private CursorPosition GetCursorPosition(MouseState mouseState, Vector2 absolutePixels)
+ {
+ Vector2 rawPixels = new Vector2(mouseState.X, mouseState.Y);
+ Vector2 screenPixels = rawPixels * new Vector2((float)1.0 / Game1.options.zoomLevel); // derived from Game1::getMouseX
+ Vector2 tile = new Vector2((int)((Game1.viewport.X + screenPixels.X) / Game1.tileSize), (int)((Game1.viewport.Y + screenPixels.Y) / Game1.tileSize));
+ Vector2 grabTile = (Game1.mouseCursorTransparency > 0 && Utility.tileWithinRadiusOfPlayer((int)tile.X, (int)tile.Y, 1, Game1.player)) // derived from Game1.pressActionButton
+ ? tile
+ : Game1.player.GetGrabTile();
+ return new CursorPosition(absolutePixels, screenPixels, tile, grabTile);
+ }
+
+ /// <summary>Whether input should be suppressed in the current context.</summary>
+ private bool ShouldSuppressNow()
+ {
+ return Game1.chatBox == null || !Game1.chatBox.isActive();
+ }
+
+ /// <summary>Apply input suppression to the given input states.</summary>
+ /// <param name="activeButtons">The current button states to check.</param>
+ /// <param name="keyboardState">The game's keyboard state for the current tick.</param>
+ /// <param name="mouseState">The game's mouse state for the current tick.</param>
+ /// <param name="gamePadState">The game's controller state for the current tick.</param>
+ private void SuppressGivenStates(IDictionary<SButton, InputStatus> activeButtons, ref KeyboardState keyboardState, ref MouseState mouseState, ref GamePadState gamePadState)
+ {
+ if (this.SuppressButtons.Count == 0)
+ return;
+
+ // gather info
+ HashSet<Keys> suppressKeys = new HashSet<Keys>();
+ HashSet<SButton> suppressButtons = new HashSet<SButton>();
+ HashSet<SButton> suppressMouse = new HashSet<SButton>();
+ foreach (SButton button in this.SuppressButtons)
+ {
+ if (button == SButton.MouseLeft || button == SButton.MouseMiddle || button == SButton.MouseRight || button == SButton.MouseX1 || button == SButton.MouseX2)
+ suppressMouse.Add(button);
+ else if (button.TryGetKeyboard(out Keys key))
+ suppressKeys.Add(key);
+ else if (gamePadState.IsConnected && button.TryGetController(out Buttons _))
+ suppressButtons.Add(button);
+ }
+
+ // suppress keyboard keys
+ if (keyboardState.GetPressedKeys().Any() && suppressKeys.Any())
+ keyboardState = new KeyboardState(keyboardState.GetPressedKeys().Except(suppressKeys).ToArray());
+
+ // suppress controller keys
+ if (gamePadState.IsConnected && suppressButtons.Any())
+ {
+ GamePadStateBuilder builder = new GamePadStateBuilder(gamePadState);
+ builder.SuppressButtons(suppressButtons);
+ gamePadState = builder.ToGamePadState();
+ }
+
+ // suppress mouse buttons
+ if (suppressMouse.Any())
+ {
+ mouseState = new MouseState(
+ x: mouseState.X,
+ y: mouseState.Y,
+ scrollWheel: mouseState.ScrollWheelValue,
+ leftButton: suppressMouse.Contains(SButton.MouseLeft) ? ButtonState.Released : mouseState.LeftButton,
+ middleButton: suppressMouse.Contains(SButton.MouseMiddle) ? ButtonState.Released : mouseState.MiddleButton,
+ rightButton: suppressMouse.Contains(SButton.MouseRight) ? ButtonState.Released : mouseState.RightButton,
+ xButton1: suppressMouse.Contains(SButton.MouseX1) ? ButtonState.Released : mouseState.XButton1,
+ xButton2: suppressMouse.Contains(SButton.MouseX2) ? ButtonState.Released : mouseState.XButton2
+ );
+ }
+ }
+
+ /// <summary>Get the status of all pressed or released buttons relative to their previous status.</summary>
+ /// <param name="previousStatuses">The previous button statuses.</param>
+ /// <param name="keyboard">The keyboard state.</param>
+ /// <param name="mouse">The mouse state.</param>
+ /// <param name="controller">The controller state.</param>
+ private IDictionary<SButton, InputStatus> DeriveStatuses(IDictionary<SButton, InputStatus> previousStatuses, KeyboardState keyboard, MouseState mouse, GamePadState controller)
+ {
+ IDictionary<SButton, InputStatus> activeButtons = new Dictionary<SButton, InputStatus>();
+
+ // handle pressed keys
+ SButton[] down = this.GetPressedButtons(keyboard, mouse, controller).ToArray();
+ foreach (SButton button in down)
+ activeButtons[button] = this.DeriveStatus(this.GetStatus(previousStatuses, button), isDown: true);
+
+ // handle released keys
+ foreach (KeyValuePair<SButton, InputStatus> prev in previousStatuses)
+ {
+ if (prev.Value.IsDown() && !activeButtons.ContainsKey(prev.Key))
+ activeButtons[prev.Key] = InputStatus.Released;
+ }
+
+ return activeButtons;
+ }
+
+ /// <summary>Get the status of a button relative to its previous status.</summary>
+ /// <param name="oldStatus">The previous button status.</param>
+ /// <param name="isDown">Whether the button is currently down.</param>
+ private InputStatus DeriveStatus(InputStatus oldStatus, bool isDown)
+ {
+ if (isDown && oldStatus.IsDown())
+ return InputStatus.Held;
+ if (isDown)
+ return InputStatus.Pressed;
+ return InputStatus.Released;
+ }
+
+ /// <summary>Get the status of a button.</summary>
+ /// <param name="activeButtons">The current button states to check.</param>
+ /// <param name="button">The button to check.</param>
+ private InputStatus GetStatus(IDictionary<SButton, InputStatus> activeButtons, SButton button)
+ {
+ return activeButtons.TryGetValue(button, out InputStatus status) ? status : InputStatus.None;
+ }
+
+ /// <summary>Get the buttons pressed in the given stats.</summary>
+ /// <param name="keyboard">The keyboard state.</param>
+ /// <param name="mouse">The mouse state.</param>
+ /// <param name="controller">The controller state.</param>
+ /// <remarks>Thumbstick direction logic derived from <see cref="ButtonCollection"/>.</remarks>
+ private IEnumerable<SButton> GetPressedButtons(KeyboardState keyboard, MouseState mouse, GamePadState controller)
+ {
+ // keyboard
+ foreach (Keys key in keyboard.GetPressedKeys())
+ yield return key.ToSButton();
+
+ // mouse
+ if (mouse.LeftButton == ButtonState.Pressed)
+ yield return SButton.MouseLeft;
+ if (mouse.RightButton == ButtonState.Pressed)
+ yield return SButton.MouseRight;
+ if (mouse.MiddleButton == ButtonState.Pressed)
+ yield return SButton.MouseMiddle;
+ if (mouse.XButton1 == ButtonState.Pressed)
+ yield return SButton.MouseX1;
+ if (mouse.XButton2 == ButtonState.Pressed)
+ yield return SButton.MouseX2;
+
+ // controller
+ if (controller.IsConnected)
+ {
+ // main buttons
+ if (controller.Buttons.A == ButtonState.Pressed)
+ yield return SButton.ControllerA;
+ if (controller.Buttons.B == ButtonState.Pressed)
+ yield return SButton.ControllerB;
+ if (controller.Buttons.X == ButtonState.Pressed)
+ yield return SButton.ControllerX;
+ if (controller.Buttons.Y == ButtonState.Pressed)
+ yield return SButton.ControllerY;
+ if (controller.Buttons.LeftStick == ButtonState.Pressed)
+ yield return SButton.LeftStick;
+ if (controller.Buttons.RightStick == ButtonState.Pressed)
+ yield return SButton.RightStick;
+ if (controller.Buttons.Start == ButtonState.Pressed)
+ yield return SButton.ControllerStart;
+
+ // directional pad
+ if (controller.DPad.Up == ButtonState.Pressed)
+ yield return SButton.DPadUp;
+ if (controller.DPad.Down == ButtonState.Pressed)
+ yield return SButton.DPadDown;
+ if (controller.DPad.Left == ButtonState.Pressed)
+ yield return SButton.DPadLeft;
+ if (controller.DPad.Right == ButtonState.Pressed)
+ yield return SButton.DPadRight;
+
+ // secondary buttons
+ if (controller.Buttons.Back == ButtonState.Pressed)
+ yield return SButton.ControllerBack;
+ if (controller.Buttons.BigButton == ButtonState.Pressed)
+ yield return SButton.BigButton;
+
+ // shoulders
+ if (controller.Buttons.LeftShoulder == ButtonState.Pressed)
+ yield return SButton.LeftShoulder;
+ if (controller.Buttons.RightShoulder == ButtonState.Pressed)
+ yield return SButton.RightShoulder;
+
+ // triggers
+ if (controller.Triggers.Left > 0.2f)
+ yield return SButton.LeftTrigger;
+ if (controller.Triggers.Right > 0.2f)
+ yield return SButton.RightTrigger;
+
+ // left thumbstick direction
+ if (controller.ThumbSticks.Left.Y > SInputState.LeftThumbstickDeadZone)
+ yield return SButton.LeftThumbstickUp;
+ if (controller.ThumbSticks.Left.Y < -SInputState.LeftThumbstickDeadZone)
+ yield return SButton.LeftThumbstickDown;
+ if (controller.ThumbSticks.Left.X > SInputState.LeftThumbstickDeadZone)
+ yield return SButton.LeftThumbstickRight;
+ if (controller.ThumbSticks.Left.X < -SInputState.LeftThumbstickDeadZone)
+ yield return SButton.LeftThumbstickLeft;
+
+ // right thumbstick direction
+ if (this.IsRightThumbstickOutsideDeadZone(controller.ThumbSticks.Right))
+ {
+ if (controller.ThumbSticks.Right.Y > 0)
+ yield return SButton.RightThumbstickUp;
+ if (controller.ThumbSticks.Right.Y < 0)
+ yield return SButton.RightThumbstickDown;
+ if (controller.ThumbSticks.Right.X > 0)
+ yield return SButton.RightThumbstickRight;
+ if (controller.ThumbSticks.Right.X < 0)
+ yield return SButton.RightThumbstickLeft;
+ }
+ }
+ }
+
+ /// <summary>Get whether the right thumbstick should be considered outside the dead zone.</summary>
+ /// <param name="direction">The right thumbstick value.</param>
+ private bool IsRightThumbstickOutsideDeadZone(Vector2 direction)
+ {
+ return direction.Length() > 0.9f;
+ }
+ }
+}