summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/SMAPI/Framework/Input/InputState.cs220
-rw-r--r--src/SMAPI/Framework/Input/SInputState.cs359
-rw-r--r--src/SMAPI/Framework/SGame.cs113
-rw-r--r--src/SMAPI/SModHooks.cs48
-rw-r--r--src/SMAPI/StardewModdingAPI.csproj3
5 files changed, 376 insertions, 367 deletions
diff --git a/src/SMAPI/Framework/Input/InputState.cs b/src/SMAPI/Framework/Input/InputState.cs
deleted file mode 100644
index 62337a6c..00000000
--- a/src/SMAPI/Framework/Input/InputState.cs
+++ /dev/null
@@ -1,220 +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 maximum amount of direction to ignore for the left thumbstick.</summary>
- private const float LeftThumbstickDeadZone = 0.2f;
-
- /// <summary>The maximum amount of direction to ignore for the right thumbstick.</summary>
- private const float RightThumbstickDeadZone = 0f;
-
-
- /*********
- ** 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 = this.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 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()));
- }
-
- /// <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>
- /// <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 > InputState.LeftThumbstickDeadZone)
- yield return SButton.LeftThumbstickUp;
- if (controller.ThumbSticks.Left.Y < -InputState.LeftThumbstickDeadZone)
- yield return SButton.LeftThumbstickDown;
- if (controller.ThumbSticks.Left.X > InputState.LeftThumbstickDeadZone)
- yield return SButton.LeftThumbstickRight;
- if (controller.ThumbSticks.Left.X < -InputState.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;
- }
- }
-}
diff --git a/src/SMAPI/Framework/Input/SInputState.cs b/src/SMAPI/Framework/Input/SInputState.cs
new file mode 100644
index 00000000..62defa9f
--- /dev/null
+++ b/src/SMAPI/Framework/Input/SInputState.cs
@@ -0,0 +1,359 @@
+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>A summary of input changes during an update frame.</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;
+
+
+ /*********
+ ** 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 mouse position on the screen adjusted for the zoom level.</summary>
+ public Point MousePosition { get; private set; }
+
+ /// <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,
+ MousePosition = this.MousePosition
+ };
+ }
+
+ /// <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();
+ Point mousePosition = new Point((int)(this.RealMouse.X * (1.0 / Game1.options.zoomLevel)), (int)(this.RealMouse.Y * (1.0 / Game1.options.zoomLevel))); // derived from Game1::getMouseX
+ var activeButtons = this.DeriveStatuses(this.ActiveButtons, realKeyboard, realMouse, realController);
+
+ // get suppressed states
+ GamePadState suppressedController = realController;
+ KeyboardState suppressedKeyboard = realKeyboard;
+ MouseState suppressedMouse = realMouse;
+ if (this.SuppressButtons.Count > 0)
+ this.UpdateSuppression(activeButtons, ref suppressedKeyboard, ref suppressedMouse, ref suppressedController);
+
+ // update
+ this.ActiveButtons = activeButtons;
+ this.RealController = realController;
+ this.RealKeyboard = realKeyboard;
+ this.RealMouse = realMouse;
+ this.SuppressedController = suppressedController;
+ this.SuppressedKeyboard = suppressedKeyboard;
+ this.SuppressedMouse = suppressedMouse;
+ this.MousePosition = mousePosition;
+ }
+ catch (InvalidOperationException)
+ {
+ // GetState() may crash for some players if window doesn't have focus but game1.IsActive == true
+ }
+ }
+
+ /// <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()));
+ }
+
+ /// <summary>Apply input suppression for 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>
+ public void UpdateSuppression(IDictionary<SButton, InputStatus> activeButtons, ref KeyboardState keyboardState, ref MouseState mouseState, ref GamePadState gamePadState)
+ {
+ // stop suppressing buttons once released
+ if (this.SuppressButtons.Count != 0)
+ this.SuppressButtons.RemoveWhere(p => !this.GetStatus(activeButtons, p).IsDown());
+ if (this.SuppressButtons.Count == 0)
+ return;
+
+ // gather info
+ HashSet<Keys> keyboardButtons = new HashSet<Keys>();
+ HashSet<SButton> controllerButtons = new HashSet<SButton>();
+ HashSet<SButton> mouseButtons = 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)
+ mouseButtons.Add(button);
+ else if (button.TryGetKeyboard(out Keys key))
+ keyboardButtons.Add(key);
+ else if (gamePadState.IsConnected && button.TryGetController(out Buttons _))
+ controllerButtons.Add(button);
+ }
+
+ // suppress keyboard keys
+ if (keyboardState.GetPressedKeys().Any() && keyboardButtons.Any())
+ keyboardState = new KeyboardState(keyboardState.GetPressedKeys().Except(keyboardButtons).ToArray());
+
+ // suppress controller keys
+ if (gamePadState.IsConnected && controllerButtons.Any())
+ {
+ GamePadStateBuilder builder = new GamePadStateBuilder(gamePadState);
+ builder.SuppressButtons(controllerButtons);
+ gamePadState = builder.ToGamePadState();
+ }
+
+ // suppress mouse buttons
+ if (mouseButtons.Any())
+ {
+ mouseState = new MouseState(
+ x: mouseState.X,
+ y: mouseState.Y,
+ scrollWheel: mouseState.ScrollWheelValue,
+ leftButton: mouseButtons.Contains(SButton.MouseLeft) ? ButtonState.Pressed : mouseState.LeftButton,
+ middleButton: mouseButtons.Contains(SButton.MouseMiddle) ? ButtonState.Pressed : mouseState.MiddleButton,
+ rightButton: mouseButtons.Contains(SButton.MouseRight) ? ButtonState.Pressed : mouseState.RightButton,
+ xButton1: mouseButtons.Contains(SButton.MouseX1) ? ButtonState.Pressed : mouseState.XButton1,
+ xButton2: mouseButtons.Contains(SButton.MouseX2) ? ButtonState.Pressed : mouseState.XButton2
+ );
+ }
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Whether input should be suppressed in the current context.</summary>
+ private bool ShouldSuppressNow()
+ {
+ return Game1.chatBox != null && !Game1.chatBox.isActive();
+ }
+
+ /// <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 activeButtons)
+ {
+ 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;
+ }
+ }
+}
diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs
index 4f5bd96b..182b90fc 100644
--- a/src/SMAPI/Framework/SGame.cs
+++ b/src/SMAPI/Framework/SGame.cs
@@ -53,6 +53,9 @@ namespace StardewModdingAPI.Framework
/// <summary>Manages SMAPI events for mods.</summary>
private readonly EventManager Events;
+ /// <summary>Manages input visible to the game.</summary>
+ private SInputState Input => (SInputState)Game1.input;
+
/// <summary>The maximum number of consecutive attempts SMAPI should make to recover from a draw error.</summary>
private readonly Countdown DrawCrashTimer = new Countdown(60); // 60 ticks = roughly one second
@@ -78,9 +81,6 @@ namespace StardewModdingAPI.Framework
/****
** Game state
****/
- /// <summary>The player input as of the previous tick.</summary>
- private InputState PreviousInput = new InputState();
-
/// <summary>The underlying watchers for convenience. These are accessible individually as separate properties.</summary>
private readonly List<IWatcher> Watchers = new List<IWatcher>();
@@ -120,9 +120,6 @@ namespace StardewModdingAPI.Framework
/// <summary>Simplifies access to private game code.</summary>
private readonly Reflector Reflection;
- /// <summary>The buttons to suppress when the game next handles input. Each button is suppressed until it's released.</summary>
- private readonly HashSet<SButton> SuppressButtons = new HashSet<SButton>();
-
/*********
** Accessors
@@ -157,7 +154,7 @@ namespace StardewModdingAPI.Framework
this.OnGameExiting = onGameExiting;
if (this.ContentCore == null) // shouldn't happen since CreateContentManager is called first, but let's init here just in case
this.ContentCore = new ContentCore(this.Content.ServiceProvider, this.Content.RootDirectory, Thread.CurrentThread.CurrentUICulture, this.Monitor, reflection);
- Game1.hooks = new SModHooks(this.UpdateControlInput);
+ Game1.input = new SInputState();
// init watchers
Game1.locations = new ObservableCollection<GameLocation>();
@@ -289,7 +286,7 @@ namespace StardewModdingAPI.Framework
if (this.FirstUpdate)
this.OnGameInitialised();
-
+
/*********
** Update context
*********/
@@ -390,16 +387,9 @@ namespace StardewModdingAPI.Framework
*********/
if (Game1.game1.IsActive)
{
- // get input state
- InputState inputState;
- try
- {
- inputState = InputState.GetState(this.PreviousInput);
- }
- catch (InvalidOperationException) // GetState() may crash for some players if window doesn't have focus but game1.IsActive == true
- {
- inputState = this.PreviousInput;
- }
+ SInputState previousInputState = this.Input.Clone();
+ SInputState inputState = this.Input;
+ inputState.TrueUpdate();
// raise events
bool isChatInput = Game1.IsChatting || (Context.IsMultiplayer && Context.IsWorldReady && Game1.activeClickableMenu == null && Game1.currentMinigame == null && inputState.IsAnyDown(Game1.options.chatButton));
@@ -425,7 +415,7 @@ namespace StardewModdingAPI.Framework
if (status == InputStatus.Pressed)
{
- this.Events.Input_ButtonPressed.Raise(new EventArgsInput(button, cursor, button.IsActionButton(), button.IsUseToolButton(), this.SuppressButtons));
+ this.Events.Input_ButtonPressed.Raise(new EventArgsInput(button, cursor, button.IsActionButton(), button.IsUseToolButton(), inputState.SuppressButtons));
// legacy events
if (button.TryGetKeyboard(out Keys key))
@@ -436,14 +426,14 @@ namespace StardewModdingAPI.Framework
else if (button.TryGetController(out Buttons controllerButton))
{
if (controllerButton == Buttons.LeftTrigger || controllerButton == Buttons.RightTrigger)
- this.Events.Control_ControllerTriggerPressed.Raise(new EventArgsControllerTriggerPressed(PlayerIndex.One, controllerButton, controllerButton == Buttons.LeftTrigger ? inputState.ControllerState.Triggers.Left : inputState.ControllerState.Triggers.Right));
+ this.Events.Control_ControllerTriggerPressed.Raise(new EventArgsControllerTriggerPressed(PlayerIndex.One, controllerButton, controllerButton == Buttons.LeftTrigger ? inputState.RealController.Triggers.Left : inputState.RealController.Triggers.Right));
else
this.Events.Control_ControllerButtonPressed.Raise(new EventArgsControllerButtonPressed(PlayerIndex.One, controllerButton));
}
}
else if (status == InputStatus.Released)
{
- this.Events.Input_ButtonReleased.Raise(new EventArgsInput(button, cursor, button.IsActionButton(), button.IsUseToolButton(), this.SuppressButtons));
+ this.Events.Input_ButtonReleased.Raise(new EventArgsInput(button, cursor, button.IsActionButton(), button.IsUseToolButton(), inputState.SuppressButtons));
// legacy events
if (button.TryGetKeyboard(out Keys key))
@@ -454,7 +444,7 @@ namespace StardewModdingAPI.Framework
else if (button.TryGetController(out Buttons controllerButton))
{
if (controllerButton == Buttons.LeftTrigger || controllerButton == Buttons.RightTrigger)
- this.Events.Control_ControllerTriggerReleased.Raise(new EventArgsControllerTriggerReleased(PlayerIndex.One, controllerButton, controllerButton == Buttons.LeftTrigger ? inputState.ControllerState.Triggers.Left : inputState.ControllerState.Triggers.Right));
+ this.Events.Control_ControllerTriggerReleased.Raise(new EventArgsControllerTriggerReleased(PlayerIndex.One, controllerButton, controllerButton == Buttons.LeftTrigger ? inputState.RealController.Triggers.Left : inputState.RealController.Triggers.Right));
else
this.Events.Control_ControllerButtonReleased.Raise(new EventArgsControllerButtonReleased(PlayerIndex.One, controllerButton));
}
@@ -462,14 +452,11 @@ namespace StardewModdingAPI.Framework
}
// raise legacy state-changed events
- if (inputState.KeyboardState != this.PreviousInput.KeyboardState)
- this.Events.Control_KeyboardChanged.Raise(new EventArgsKeyboardStateChanged(this.PreviousInput.KeyboardState, inputState.KeyboardState));
- if (inputState.MouseState != this.PreviousInput.MouseState)
- this.Events.Control_MouseChanged.Raise(new EventArgsMouseStateChanged(this.PreviousInput.MouseState, inputState.MouseState, this.PreviousInput.MousePosition, inputState.MousePosition));
+ if (inputState.RealKeyboard != previousInputState.RealKeyboard)
+ this.Events.Control_KeyboardChanged.Raise(new EventArgsKeyboardStateChanged(previousInputState.RealKeyboard, inputState.RealKeyboard));
+ if (inputState.RealMouse != previousInputState.RealMouse)
+ this.Events.Control_MouseChanged.Raise(new EventArgsMouseStateChanged(previousInputState.RealMouse, inputState.RealMouse, previousInputState.MousePosition, inputState.MousePosition));
}
-
- // track state
- this.PreviousInput = inputState;
}
/*********
@@ -624,17 +611,6 @@ namespace StardewModdingAPI.Framework
}
}
- /// <summary>Read the current input state for handling.</summary>
- /// <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>
- /// <param name="defaultLogic">The game's default logic.</param>
- public void UpdateControlInput(ref KeyboardState keyboardState, ref MouseState mouseState, ref GamePadState gamePadState, Action defaultLogic)
- {
- this.ApplySuppression(ref keyboardState, ref mouseState, ref gamePadState);
- defaultLogic();
- }
-
/// <summary>The method called to draw everything to the screen.</summary>
/// <param name="gameTime">A snapshot of the game timing state.</param>
protected override void Draw(GameTime gameTime)
@@ -1256,63 +1232,6 @@ namespace StardewModdingAPI.Framework
/****
** Methods
****/
- /// <summary>Apply input suppression for the given input states.</summary>
- /// <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 ApplySuppression(ref KeyboardState keyboardState, ref MouseState mouseState, ref GamePadState gamePadState)
- {
- // stop suppressing buttons once released
- if (this.SuppressButtons.Count != 0)
- {
- InputState inputState = new InputState(this.PreviousInput, gamePadState, keyboardState, mouseState);
- this.SuppressButtons.RemoveWhere(p => !inputState.IsDown(p));
- }
- if (this.SuppressButtons.Count == 0)
- return;
-
- // gather info
- HashSet<Keys> keyboardButtons = new HashSet<Keys>();
- HashSet<SButton> controllerButtons = new HashSet<SButton>();
- HashSet<SButton> mouseButtons = 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)
- mouseButtons.Add(button);
- else if (button.TryGetKeyboard(out Keys key))
- keyboardButtons.Add(key);
- else if (gamePadState.IsConnected && button.TryGetController(out Buttons _))
- controllerButtons.Add(button);
- }
-
- // suppress keyboard keys
- if (keyboardState.GetPressedKeys().Any() && keyboardButtons.Any())
- keyboardState = new KeyboardState(keyboardState.GetPressedKeys().Except(keyboardButtons).ToArray());
-
- // suppress controller keys
- if (gamePadState.IsConnected && controllerButtons.Any())
- {
- GamePadStateBuilder builder = new GamePadStateBuilder(gamePadState);
- builder.SuppressButtons(controllerButtons);
- gamePadState = builder.ToGamePadState();
- }
-
- // suppress mouse buttons
- if (mouseButtons.Any())
- {
- mouseState = new MouseState(
- x: mouseState.X,
- y: mouseState.Y,
- scrollWheel: mouseState.ScrollWheelValue,
- leftButton: mouseButtons.Contains(SButton.MouseLeft) ? ButtonState.Pressed : mouseState.LeftButton,
- middleButton: mouseButtons.Contains(SButton.MouseMiddle) ? ButtonState.Pressed : mouseState.MiddleButton,
- rightButton: mouseButtons.Contains(SButton.MouseRight) ? ButtonState.Pressed : mouseState.RightButton,
- xButton1: mouseButtons.Contains(SButton.MouseX1) ? ButtonState.Pressed : mouseState.XButton1,
- xButton2: mouseButtons.Contains(SButton.MouseX2) ? ButtonState.Pressed : mouseState.XButton2
- );
- }
- }
-
/// <summary>Perform any cleanup needed when the player unloads a save and returns to the title screen.</summary>
private void CleanupAfterReturnToTitle()
{
diff --git a/src/SMAPI/SModHooks.cs b/src/SMAPI/SModHooks.cs
deleted file mode 100644
index 1b88d606..00000000
--- a/src/SMAPI/SModHooks.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using System;
-using Microsoft.Xna.Framework.Input;
-using StardewValley;
-
-namespace StardewModdingAPI
-{
- /// <summary>Intercepts predefined Stardew Valley mod hooks.</summary>
- internal class SModHooks : ModHooks
- {
- /*********
- ** Delegates
- *********/
- /// <summary>A delegate invoked by the <see cref="SModHooks.OnGame1_UpdateControlInput"/> hook.</summary>
- /// <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>
- /// <param name="action">The game's default logic.</param>
- public delegate void UpdateControlInputDelegate(ref KeyboardState keyboardState, ref MouseState mouseState, ref GamePadState gamePadState, Action action);
-
-
- /*********
- ** Properties
- *********/
- /// <summary>The callback for <see cref="OnGame1_UpdateControlInput"/>.</summary>
- private readonly UpdateControlInputDelegate UpdateControlInputHandler;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="updateControlInputHandler">The callback for <see cref="OnGame1_UpdateControlInput"/>.</param>
- public SModHooks(UpdateControlInputDelegate updateControlInputHandler)
- {
- this.UpdateControlInputHandler = updateControlInputHandler;
- }
-
- /// <summary>A hook invoked before the game processes player input.</summary>
- /// <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>
- /// <param name="action">The game's default logic.</param>
- public override void OnGame1_UpdateControlInput(ref KeyboardState keyboardState, ref MouseState mouseState, ref GamePadState gamePadState, Action action)
- {
- this.UpdateControlInputHandler(ref keyboardState, ref mouseState, ref gamePadState, action);
- }
- }
-}
diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj
index d7719e27..560d7bf4 100644
--- a/src/SMAPI/StardewModdingAPI.csproj
+++ b/src/SMAPI/StardewModdingAPI.csproj
@@ -92,7 +92,7 @@
<Compile Include="Framework\Content\ContentCache.cs" />
<Compile Include="Framework\Events\ManagedEventBase.cs" />
<Compile Include="Framework\Input\GamePadStateBuilder.cs" />
- <Compile Include="Framework\Input\InputState.cs" />
+ <Compile Include="Framework\Input\SInputState.cs" />
<Compile Include="Framework\Input\InputStatus.cs" />
<Compile Include="Framework\LegacyManifestVersion.cs" />
<Compile Include="Framework\ModData\ModDatabase.cs" />
@@ -263,7 +263,6 @@
<Compile Include="Framework\SGame.cs" />
<Compile Include="IReflectionHelper.cs" />
<Compile Include="SemanticVersion.cs" />
- <Compile Include="SModHooks.cs" />
<Compile Include="Translation.cs" />
<Compile Include="ICursorPosition.cs" />
<Compile Include="Utilities\SDate.cs" />