diff options
author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2020-03-08 11:45:55 -0400 |
---|---|---|
committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2020-03-08 11:45:55 -0400 |
commit | 29fdf9ae4a91131f758035ab79fbfe0595ff1eca (patch) | |
tree | 4986787e17dc7d7da76679fc14c1c724802cf2bf /src | |
parent | 1b282f950ad068fef581fbebba493ca9f952a5c7 (diff) | |
download | SMAPI-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.cs | 6 | ||||
-rw-r--r-- | src/SMAPI/Events/ButtonReleasedEventArgs.cs | 6 | ||||
-rw-r--r-- | src/SMAPI/Framework/Input/GamePadStateBuilder.cs | 120 | ||||
-rw-r--r-- | src/SMAPI/Framework/Input/KeyboardStateBuilder.cs | 51 | ||||
-rw-r--r-- | src/SMAPI/Framework/Input/MouseStateBuilder.cs | 74 | ||||
-rw-r--r-- | src/SMAPI/Framework/Input/SInputState.cs | 232 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModHelpers/InputHelper.cs | 4 | ||||
-rw-r--r-- | src/SMAPI/Framework/SGame.cs | 4 | ||||
-rw-r--r-- | src/SMAPI/Framework/WatcherCore.cs | 2 |
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); |