summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2020-03-08 11:45:55 -0400
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2020-03-08 11:45:55 -0400
commit29fdf9ae4a91131f758035ab79fbfe0595ff1eca (patch)
tree4986787e17dc7d7da76679fc14c1c724802cf2bf /src
parent1b282f950ad068fef581fbebba493ca9f952a5c7 (diff)
downloadSMAPI-29fdf9ae4a91131f758035ab79fbfe0595ff1eca.tar.gz
SMAPI-29fdf9ae4a91131f758035ab79fbfe0595ff1eca.tar.bz2
SMAPI-29fdf9ae4a91131f758035ab79fbfe0595ff1eca.zip
rework input handling to allow sending custom input to the game/mods
That will let Virtual Keyboard on Android work with the future multi-key binding API, and with mods that check input state directly (e.g. Pathoschild/StardewMods#520). It might also be useful as a public API in future versions.
Diffstat (limited to 'src')
-rw-r--r--src/SMAPI/Events/ButtonPressedEventArgs.cs6
-rw-r--r--src/SMAPI/Events/ButtonReleasedEventArgs.cs6
-rw-r--r--src/SMAPI/Framework/Input/GamePadStateBuilder.cs120
-rw-r--r--src/SMAPI/Framework/Input/KeyboardStateBuilder.cs51
-rw-r--r--src/SMAPI/Framework/Input/MouseStateBuilder.cs74
-rw-r--r--src/SMAPI/Framework/Input/SInputState.cs232
-rw-r--r--src/SMAPI/Framework/ModHelpers/InputHelper.cs4
-rw-r--r--src/SMAPI/Framework/SGame.cs4
-rw-r--r--src/SMAPI/Framework/WatcherCore.cs2
9 files changed, 316 insertions, 183 deletions
diff --git a/src/SMAPI/Events/ButtonPressedEventArgs.cs b/src/SMAPI/Events/ButtonPressedEventArgs.cs
index 5d922666..1b30fd23 100644
--- a/src/SMAPI/Events/ButtonPressedEventArgs.cs
+++ b/src/SMAPI/Events/ButtonPressedEventArgs.cs
@@ -37,17 +37,17 @@ namespace StardewModdingAPI.Events
this.InputState = inputState;
}
- /// <summary>Whether a mod has indicated the key was already handled, so the game should handle it.</summary>
+ /// <summary>Get whether a mod has indicated the key was already handled, so the game shouldn't handle it.</summary>
public bool IsSuppressed()
{
return this.IsSuppressed(this.Button);
}
- /// <summary>Whether a mod has indicated the key was already handled, so the game should handle it.</summary>
+ /// <summary>Get whether a mod has indicated the key was already handled, so the game shouldn't handle it.</summary>
/// <param name="button">The button to check.</param>
public bool IsSuppressed(SButton button)
{
- return this.InputState.SuppressButtons.Contains(button);
+ return this.InputState.IsSuppressed(button);
}
/// <summary>Get whether a given button was pressed or held.</summary>
diff --git a/src/SMAPI/Events/ButtonReleasedEventArgs.cs b/src/SMAPI/Events/ButtonReleasedEventArgs.cs
index f5282230..40ec1cc1 100644
--- a/src/SMAPI/Events/ButtonReleasedEventArgs.cs
+++ b/src/SMAPI/Events/ButtonReleasedEventArgs.cs
@@ -37,17 +37,17 @@ namespace StardewModdingAPI.Events
this.InputState = inputState;
}
- /// <summary>Whether a mod has indicated the key was already handled, so the game should handle it.</summary>
+ /// <summary>Get whether a mod has indicated the key was already handled, so the game shouldn't handle it.</summary>
public bool IsSuppressed()
{
return this.IsSuppressed(this.Button);
}
- /// <summary>Whether a mod has indicated the key was already handled, so the game should handle it.</summary>
+ /// <summary>Get whether a mod has indicated the key was already handled, so the game shouldn't handle it.</summary>
/// <param name="button">The button to check.</param>
public bool IsSuppressed(SButton button)
{
- return this.InputState.SuppressButtons.Contains(button);
+ return this.InputState.IsSuppressed(button);
}
/// <summary>Get whether a given button was pressed or held.</summary>
diff --git a/src/SMAPI/Framework/Input/GamePadStateBuilder.cs b/src/SMAPI/Framework/Input/GamePadStateBuilder.cs
index a20e1248..315aa920 100644
--- a/src/SMAPI/Framework/Input/GamePadStateBuilder.cs
+++ b/src/SMAPI/Framework/Input/GamePadStateBuilder.cs
@@ -4,7 +4,7 @@ using Microsoft.Xna.Framework.Input;
namespace StardewModdingAPI.Framework.Input
{
- /// <summary>An abstraction for manipulating controller state.</summary>
+ /// <summary>Manipulates controller state.</summary>
internal class GamePadStateBuilder
{
/*********
@@ -30,7 +30,7 @@ namespace StardewModdingAPI.Framework.Input
** Public methods
*********/
/// <summary>Construct an instance.</summary>
- /// <param name="state">The initial controller state.</param>
+ /// <param name="state">The initial state.</param>
public GamePadStateBuilder(GamePadState state)
{
this.ButtonStates = new Dictionary<SButton, ButtonState>
@@ -58,74 +58,64 @@ namespace StardewModdingAPI.Framework.Input
this.RightStickPos = state.ThumbSticks.Right;
}
- /// <summary>Mark all matching buttons unpressed.</summary>
- /// <param name="buttons">The buttons.</param>
- public void SuppressButtons(IEnumerable<SButton> buttons)
+ /// <summary>Override the states for a set of buttons.</summary>
+ /// <param name="overrides">The button state overrides.</param>
+ public GamePadStateBuilder OverrideButtons(IDictionary<SButton, SButtonState> overrides)
{
- 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)
+ foreach (var pair in overrides)
{
- // 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;
+ bool isDown = pair.Value.IsDown();
+ switch (pair.Key)
+ {
+ // left thumbstick
+ case SButton.LeftThumbstickUp:
+ this.LeftStickPos.Y = isDown ? 1 : 0;
+ break;
+ case SButton.LeftThumbstickDown:
+ this.LeftStickPos.Y = isDown ? 1 : 0;
+ break;
+ case SButton.LeftThumbstickLeft:
+ this.LeftStickPos.X = isDown ? 1 : 0;
+ break;
+ case SButton.LeftThumbstickRight:
+ this.LeftStickPos.X = isDown ? 1 : 0;
+ break;
+
+ // right thumbstick
+ case SButton.RightThumbstickUp:
+ this.RightStickPos.Y = isDown ? 1 : 0;
+ break;
+ case SButton.RightThumbstickDown:
+ this.RightStickPos.Y = isDown ? 1 : 0;
+ break;
+ case SButton.RightThumbstickLeft:
+ this.RightStickPos.X = isDown ? 1 : 0;
+ break;
+ case SButton.RightThumbstickRight:
+ this.RightStickPos.X = isDown ? 1 : 0;
+ break;
+
+ // triggers
+ case SButton.LeftTrigger:
+ this.LeftTrigger = isDown ? 1 : 0;
+ break;
+ case SButton.RightTrigger:
+ this.RightTrigger = isDown ? 1 : 0;
+ break;
+
+ // buttons
+ default:
+ if (this.ButtonStates.ContainsKey(pair.Key))
+ this.ButtonStates[pair.Key] = isDown ? ButtonState.Pressed : ButtonState.Released;
+ break;
+ }
}
+
+ return this;
}
- /// <summary>Construct an equivalent gamepad state.</summary>
- public GamePadState ToGamePadState()
+ /// <summary>Construct an equivalent state.</summary>
+ public GamePadState ToState()
{
return new GamePadState(
leftThumbStick: this.LeftStickPos,
diff --git a/src/SMAPI/Framework/Input/KeyboardStateBuilder.cs b/src/SMAPI/Framework/Input/KeyboardStateBuilder.cs
new file mode 100644
index 00000000..12d780f6
--- /dev/null
+++ b/src/SMAPI/Framework/Input/KeyboardStateBuilder.cs
@@ -0,0 +1,51 @@
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Xna.Framework.Input;
+
+namespace StardewModdingAPI.Framework.Input
+{
+ /// <summary>Manipulates keyboard state.</summary>
+ internal class KeyboardStateBuilder
+ {
+ /*********
+ ** Fields
+ *********/
+ /// <summary>The pressed buttons.</summary>
+ private readonly HashSet<Keys> PressedButtons;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="state">The initial state.</param>
+ public KeyboardStateBuilder(KeyboardState state)
+ {
+ this.PressedButtons = new HashSet<Keys>(state.GetPressedKeys());
+ }
+
+ /// <summary>Override the states for a set of buttons.</summary>
+ /// <param name="overrides">The button state overrides.</param>
+ public KeyboardStateBuilder OverrideButtons(IDictionary<SButton, SButtonState> overrides)
+ {
+ foreach (var pair in overrides)
+ {
+ if (pair.Key.TryGetKeyboard(out Keys key))
+ {
+ if (pair.Value.IsDown())
+ this.PressedButtons.Add(key);
+ else
+ this.PressedButtons.Remove(key);
+ }
+ }
+
+ return this;
+ }
+
+ /// <summary>Build an equivalent state.</summary>
+ public KeyboardState ToState()
+ {
+ return new KeyboardState(this.PressedButtons.ToArray());
+ }
+ }
+}
diff --git a/src/SMAPI/Framework/Input/MouseStateBuilder.cs b/src/SMAPI/Framework/Input/MouseStateBuilder.cs
new file mode 100644
index 00000000..9c6f6f95
--- /dev/null
+++ b/src/SMAPI/Framework/Input/MouseStateBuilder.cs
@@ -0,0 +1,74 @@
+using System.Collections.Generic;
+using Microsoft.Xna.Framework.Input;
+
+namespace StardewModdingAPI.Framework.Input
+{
+ /// <summary>Manipulates mouse state.</summary>
+ internal class MouseStateBuilder
+ {
+ /*********
+ ** Fields
+ *********/
+ /// <summary>The current button states.</summary>
+ private readonly IDictionary<SButton, ButtonState> ButtonStates;
+
+ /// <summary>The X cursor position.</summary>
+ private readonly int X;
+
+ /// <summary>The Y cursor position.</summary>
+ private readonly int Y;
+
+ /// <summary>The mouse wheel scroll value.</summary>
+ private readonly int ScrollWheelValue;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="state">The initial state.</param>
+ public MouseStateBuilder(MouseState state)
+ {
+ this.ButtonStates = new Dictionary<SButton, ButtonState>
+ {
+ [SButton.MouseLeft] = state.LeftButton,
+ [SButton.MouseMiddle] = state.MiddleButton,
+ [SButton.MouseRight] = state.RightButton,
+ [SButton.MouseX1] = state.XButton1,
+ [SButton.MouseX2] = state.XButton2
+ };
+ this.X = state.X;
+ this.Y = state.Y;
+ this.ScrollWheelValue = state.ScrollWheelValue;
+ }
+
+ /// <summary>Override the states for a set of buttons.</summary>
+ /// <param name="overrides">The button state overrides.</param>
+ public MouseStateBuilder OverrideButtons(IDictionary<SButton, SButtonState> overrides)
+ {
+ foreach (var pair in overrides)
+ {
+ bool isDown = pair.Value.IsDown();
+ if (this.ButtonStates.ContainsKey(pair.Key))
+ this.ButtonStates[pair.Key] = isDown ? ButtonState.Pressed : ButtonState.Released;
+ }
+
+ return this;
+ }
+
+ /// <summary>Construct an equivalent mouse state.</summary>
+ public MouseState ToMouseState()
+ {
+ return new MouseState(
+ x: this.X,
+ y: this.Y,
+ scrollWheel: this.ScrollWheelValue,
+ leftButton: this.ButtonStates[SButton.MouseLeft],
+ middleButton: this.ButtonStates[SButton.MouseMiddle],
+ rightButton: this.ButtonStates[SButton.MouseRight],
+ xButton1: this.ButtonStates[SButton.MouseX1],
+ xButton2: this.ButtonStates[SButton.MouseX2]
+ );
+ }
+ }
+}
diff --git a/src/SMAPI/Framework/Input/SInputState.cs b/src/SMAPI/Framework/Input/SInputState.cs
index 4eaa9ca6..06a7ac3b 100644
--- a/src/SMAPI/Framework/Input/SInputState.cs
+++ b/src/SMAPI/Framework/Input/SInputState.cs
@@ -23,37 +23,34 @@ namespace StardewModdingAPI.Framework.Input
/// <summary>The player's last known tile position.</summary>
private Vector2? LastPlayerTile;
+ /// <summary>The buttons to press until the game next handles input.</summary>
+ private readonly HashSet<SButton> CustomPressedKeys = new HashSet<SButton>();
+
+ /// <summary>The buttons to consider released until the actual button is released.</summary>
+ private readonly HashSet<SButton> CustomReleasedKeys = new HashSet<SButton>();
+
+ /// <summary>The buttons which were actually down as of the last update, ignoring overrides.</summary>
+ private HashSet<SButton> LastRealButtonPresses = new HashSet<SButton>();
+
/*********
** Accessors
*********/
/// <summary>The controller state as of the last update.</summary>
- public GamePadState RealController { get; private set; }
+ public GamePadState LastController { get; private set; }
/// <summary>The keyboard state as of the last update.</summary>
- public KeyboardState RealKeyboard { get; private set; }
+ public KeyboardState LastKeyboard { 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; }
+ public MouseState LastMouse { 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 buttons which were pressed, held, or released as of the last update.</summary>
+ public IDictionary<SButton, SButtonState> LastButtonStates { get; private set; } = new Dictionary<SButton, SButtonState>();
/// <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, SButtonState> ActiveButtons { get; private set; } = new Dictionary<SButton, SButtonState>();
-
- /// <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
@@ -63,14 +60,38 @@ namespace StardewModdingAPI.Framework.Input
{
return new SInputState
{
- ActiveButtons = this.ActiveButtons,
- RealController = this.RealController,
- RealKeyboard = this.RealKeyboard,
- RealMouse = this.RealMouse,
+ LastButtonStates = this.LastButtonStates,
+ LastController = this.LastController,
+ LastKeyboard = this.LastKeyboard,
+ LastMouse = this.LastMouse,
CursorPositionImpl = this.CursorPositionImpl
};
}
+ /// <summary>Override the state for a button.</summary>
+ /// <param name="button">The button to override.</param>
+ /// <param name="setDown">Whether to mark it pressed; else mark it released.</param>
+ public void OverrideButton(SButton button, bool setDown)
+ {
+ if (setDown)
+ {
+ this.CustomPressedKeys.Add(button);
+ this.CustomReleasedKeys.Remove(button);
+ }
+ else
+ {
+ this.CustomPressedKeys.Remove(button);
+ this.CustomReleasedKeys.Add(button);
+ }
+ }
+
+ /// <summary>Get whether a mod has indicated the key was already handled, so the game shouldn't handle it.</summary>
+ /// <param name="button">The button to check.</param>
+ public bool IsSuppressed(SButton button)
+ {
+ return this.CustomReleasedKeys.Contains(button);
+ }
+
/// <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() { }
@@ -82,28 +103,47 @@ namespace StardewModdingAPI.Framework.Input
{
float zoomMultiplier = (1f / Game1.options.zoomLevel);
- // get new states
- GamePadState realController = GamePad.GetState(PlayerIndex.One);
- KeyboardState realKeyboard = Keyboard.GetState();
- MouseState realMouse = Mouse.GetState();
- var activeButtons = this.DeriveStates(this.ActiveButtons, realKeyboard, realMouse, realController);
- Vector2 cursorAbsolutePos = new Vector2((realMouse.X * zoomMultiplier) + Game1.viewport.X, (realMouse.Y * zoomMultiplier) + Game1.viewport.Y);
+ // get real values
+ GamePadState controller = GamePad.GetState(PlayerIndex.One);
+ KeyboardState keyboard = Keyboard.GetState();
+ MouseState mouse = Mouse.GetState();
+ Vector2 cursorAbsolutePos = new Vector2((mouse.X * zoomMultiplier) + Game1.viewport.X, (mouse.Y * zoomMultiplier) + Game1.viewport.Y);
Vector2? playerTilePos = Context.IsPlayerFree ? Game1.player.getTileLocation() : (Vector2?)null;
+ HashSet<SButton> reallyDown = new HashSet<SButton>(this.GetPressedButtons(keyboard, mouse, controller));
- // update real states
- this.ActiveButtons = activeButtons;
- this.RealController = realController;
- this.RealKeyboard = realKeyboard;
- this.RealMouse = realMouse;
+ // apply overrides
+ bool hasOverrides = false;
+ if (this.CustomPressedKeys.Count > 0 || this.CustomReleasedKeys.Count > 0)
+ {
+ // reset overrides that no longer apply
+ this.CustomPressedKeys.RemoveWhere(key => reallyDown.Contains(key));
+ this.CustomReleasedKeys.RemoveWhere(key => !reallyDown.Contains(key));
+
+ // apply overrides
+ if (this.ApplyOverrides(this.CustomPressedKeys, this.CustomReleasedKeys, ref keyboard, ref mouse, ref controller))
+ hasOverrides = true;
+
+ // remove pressed keys
+ this.CustomPressedKeys.Clear();
+ }
+
+ // get button states
+ var pressedButtons = hasOverrides
+ ? new HashSet<SButton>(this.GetPressedButtons(keyboard, mouse, controller))
+ : reallyDown;
+ var activeButtons = this.DeriveStates(this.LastButtonStates, pressedButtons, keyboard, mouse, controller);
+
+ // update
+ this.LastController = controller;
+ this.LastKeyboard = keyboard;
+ this.LastMouse = mouse;
+ this.LastButtonStates = activeButtons;
+ this.LastRealButtonPresses = reallyDown;
if (cursorAbsolutePos != this.CursorPositionImpl?.AbsolutePixels || playerTilePos != this.LastPlayerTile)
{
this.LastPlayerTile = playerTilePos;
- this.CursorPositionImpl = this.GetCursorPosition(realMouse, cursorAbsolutePos, zoomMultiplier);
+ this.CursorPositionImpl = this.GetCursorPosition(mouse, cursorAbsolutePos, zoomMultiplier);
}
-
- // update suppressed states
- this.SuppressButtons.RemoveWhere(p => !this.GetState(activeButtons, p).IsDown());
- this.UpdateSuppression();
}
catch (InvalidOperationException)
{
@@ -111,18 +151,18 @@ namespace StardewModdingAPI.Framework.Input
}
}
- /// <summary>Apply input suppression to current input.</summary>
- public void UpdateSuppression()
+ /// <summary>Apply input overrides to the current state.</summary>
+ public void ApplyOverrides()
{
- GamePadState suppressedController = this.RealController;
- KeyboardState suppressedKeyboard = this.RealKeyboard;
- MouseState suppressedMouse = this.RealMouse;
+ GamePadState newController = this.LastController;
+ KeyboardState newKeyboard = this.LastKeyboard;
+ MouseState newMouse = this.LastMouse;
- this.SuppressGivenStates(this.ActiveButtons, ref suppressedKeyboard, ref suppressedMouse, ref suppressedController);
+ this.ApplyOverrides(pressed: this.CustomPressedKeys, released: this.CustomReleasedKeys, ref newKeyboard, ref newMouse, ref newController);
- this.SuppressedController = suppressedController;
- this.SuppressedKeyboard = suppressedKeyboard;
- this.SuppressedMouse = suppressedMouse;
+ this.LastController = newController;
+ this.LastKeyboard = newKeyboard;
+ this.LastMouse = newMouse;
}
/// <summary>Get the gamepad state visible to the game.</summary>
@@ -130,36 +170,30 @@ namespace StardewModdingAPI.Framework.Input
public override GamePadState GetGamePadState()
{
if (Game1.options.gamepadMode == Options.GamepadModes.ForceOff)
- return base.GetGamePadState();
+ return new GamePadState();
- return this.ShouldSuppressNow()
- ? this.SuppressedController
- : this.RealController;
+ return this.LastController;
}
/// <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;
+ return this.LastKeyboard;
}
/// <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;
+ return this.LastMouse;
}
/// <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.GetState(this.ActiveButtons, button).IsDown();
+ return this.GetState(this.LastButtonStates, button).IsDown();
}
/// <summary>Get whether any of the given buttons were pressed or held.</summary>
@@ -173,7 +207,7 @@ namespace StardewModdingAPI.Framework.Input
/// <param name="button">The button to check.</param>
public SButtonState GetState(SButton button)
{
- return this.GetState(this.ActiveButtons, button);
+ return this.GetState(this.LastButtonStates, button);
}
@@ -194,76 +228,60 @@ namespace StardewModdingAPI.Framework.Input
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>
+ /// <summary>Apply input overrides to the given states.</summary>
+ /// <param name="pressed">The buttons to mark pressed.</param>
+ /// <param name="released">The buttons to mark released.</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, SButtonState> activeButtons, ref KeyboardState keyboardState, ref MouseState mouseState, ref GamePadState gamePadState)
+ /// <returns>Returns whether any overrides were applied.</returns>
+ private bool ApplyOverrides(ISet<SButton> pressed, ISet<SButton> released, 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 (pressed.Count == 0 && released.Count == 0)
+ return false;
+
+ // group keys by type
+ IDictionary<SButton, SButtonState> keyboardOverrides = new Dictionary<SButton, SButtonState>();
+ IDictionary<SButton, SButtonState> controllerOverrides = new Dictionary<SButton, SButtonState>();
+ IDictionary<SButton, SButtonState> mouseOverrides = new Dictionary<SButton, SButtonState>();
+ foreach (var button in pressed.Concat(released))
{
+ var newState = this.DeriveState(
+ oldState: this.GetState(button),
+ isDown: pressed.Contains(button)
+ );
+
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);
+ mouseOverrides[button] = newState;
+ else if (button.TryGetKeyboard(out Keys _))
+ keyboardOverrides[button] = newState;
else if (gamePadState.IsConnected && button.TryGetController(out Buttons _))
- suppressButtons.Add(button);
+ controllerOverrides[button] = newState;
}
- // suppress keyboard keys
- if (keyboardState.GetPressedKeys().Any() && suppressKeys.Any())
- keyboardState = new KeyboardState(keyboardState.GetPressedKeys().Except(suppressKeys).ToArray());
+ // override states
+ if (keyboardOverrides.Any())
+ keyboardState = new KeyboardStateBuilder(keyboardState).OverrideButtons(keyboardOverrides).ToState();
+ if (gamePadState.IsConnected && controllerOverrides.Any())
+ gamePadState = new GamePadStateBuilder(gamePadState).OverrideButtons(controllerOverrides).ToState();
+ if (mouseOverrides.Any())
+ mouseState = new MouseStateBuilder(mouseState).OverrideButtons(mouseOverrides).ToMouseState();
- // 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
- );
- }
+ return true;
}
/// <summary>Get the state of all pressed or released buttons relative to their previous state.</summary>
/// <param name="previousStates">The previous button states.</param>
+ /// <param name="pressedButtons">The currently pressed buttons.</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, SButtonState> DeriveStates(IDictionary<SButton, SButtonState> previousStates, KeyboardState keyboard, MouseState mouse, GamePadState controller)
+ private IDictionary<SButton, SButtonState> DeriveStates(IDictionary<SButton, SButtonState> previousStates, HashSet<SButton> pressedButtons, KeyboardState keyboard, MouseState mouse, GamePadState controller)
{
IDictionary<SButton, SButtonState> activeButtons = new Dictionary<SButton, SButtonState>();
// handle pressed keys
- SButton[] down = this.GetPressedButtons(keyboard, mouse, controller).ToArray();
- foreach (SButton button in down)
+ foreach (SButton button in pressedButtons)
activeButtons[button] = this.DeriveState(this.GetState(previousStates, button), isDown: true);
// handle released keys
diff --git a/src/SMAPI/Framework/ModHelpers/InputHelper.cs b/src/SMAPI/Framework/ModHelpers/InputHelper.cs
index f8ff0355..134ba8d1 100644
--- a/src/SMAPI/Framework/ModHelpers/InputHelper.cs
+++ b/src/SMAPI/Framework/ModHelpers/InputHelper.cs
@@ -41,14 +41,14 @@ namespace StardewModdingAPI.Framework.ModHelpers
/// <param name="button">The button.</param>
public bool IsSuppressed(SButton button)
{
- return this.InputState.SuppressButtons.Contains(button);
+ return this.InputState.IsSuppressed(button);
}
/// <summary>Prevent the game from handling a button press. This doesn't prevent other mods from receiving the event.</summary>
/// <param name="button">The button to suppress.</param>
public void Suppress(SButton button)
{
- this.InputState.SuppressButtons.Add(button);
+ this.InputState.OverrideButton(button, setDown: false);
}
/// <summary>Get the state of a button.</summary>
diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs
index d09e1e93..2a30b595 100644
--- a/src/SMAPI/Framework/SGame.cs
+++ b/src/SMAPI/Framework/SGame.cs
@@ -653,7 +653,7 @@ namespace StardewModdingAPI.Framework
}
// raise input button events
- foreach (var pair in inputState.ActiveButtons)
+ foreach (var pair in inputState.LastButtonStates)
{
SButton button = pair.Key;
SButtonState status = pair.Value;
@@ -824,7 +824,7 @@ namespace StardewModdingAPI.Framework
events.OneSecondUpdateTicking.RaiseEmpty();
try
{
- this.Input.UpdateSuppression();
+ this.Input.ApplyOverrides(); // if mods added any new overrides since the update, process them now
SGame.TicksElapsed++;
base.Update(gameTime);
}
diff --git a/src/SMAPI/Framework/WatcherCore.cs b/src/SMAPI/Framework/WatcherCore.cs
index 32b7fdc6..c89efa44 100644
--- a/src/SMAPI/Framework/WatcherCore.cs
+++ b/src/SMAPI/Framework/WatcherCore.cs
@@ -60,7 +60,7 @@ namespace StardewModdingAPI.Framework
{
// init watchers
this.CursorWatcher = WatcherFactory.ForEquatable(() => inputState.CursorPosition);
- this.MouseWheelScrollWatcher = WatcherFactory.ForEquatable(() => inputState.RealMouse.ScrollWheelValue);
+ this.MouseWheelScrollWatcher = WatcherFactory.ForEquatable(() => inputState.LastMouse.ScrollWheelValue);
this.SaveIdWatcher = WatcherFactory.ForEquatable(() => Game1.hasLoadedGame ? Game1.uniqueIDForThisGame : 0);
this.WindowSizeWatcher = WatcherFactory.ForEquatable(() => new Point(Game1.viewport.Width, Game1.viewport.Height));
this.TimeWatcher = WatcherFactory.ForEquatable(() => Game1.timeOfDay);