diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/SMAPI/Framework/Input/InputState.cs | 220 | ||||
-rw-r--r-- | src/SMAPI/Framework/Input/SInputState.cs | 359 | ||||
-rw-r--r-- | src/SMAPI/Framework/SGame.cs | 113 | ||||
-rw-r--r-- | src/SMAPI/SModHooks.cs | 48 | ||||
-rw-r--r-- | src/SMAPI/StardewModdingAPI.csproj | 3 |
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" /> |