rewrite input suppression (#453)
This lets SMAPI intercept all input using the new Game1.hooks in SDV 1.3.0.32. However, intercepting mouse clicks needs a few more changes in the game code.
This commit is contained in:
parent
902814d308
commit
5e7eaf9f75
|
@ -22,7 +22,8 @@
|
|||
* Added prerelease versions to the mod update-check API response where available (GitHub only).
|
||||
* Added support for beta releases on the home page.
|
||||
* Split mod DB out of `StardewModdingAPI.config.json`, so we can load config earlier and reduce unnecessary memory usage later.
|
||||
* Overhauled world/player state tracking:
|
||||
* Rewrote input suppression using new SDV 1.3 APIs.
|
||||
* Rewrote world/player state tracking:
|
||||
* much more efficient than previous method;
|
||||
* uses net field events where available;
|
||||
* lays groundwork for tracking events for multiple players.
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using StardewValley;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StardewModdingAPI.Events
|
||||
{
|
||||
/// <summary>Event arguments when a button is pressed or released.</summary>
|
||||
public class EventArgsInput : EventArgs
|
||||
{
|
||||
/*********
|
||||
** Properties
|
||||
*********/
|
||||
/// <summary>The buttons to suppress.</summary>
|
||||
private readonly HashSet<SButton> SuppressButtons;
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
|
@ -25,7 +29,7 @@ namespace StardewModdingAPI.Events
|
|||
public bool IsUseToolButton { get; }
|
||||
|
||||
/// <summary>Whether a mod has indicated the key was already handled.</summary>
|
||||
public bool IsSuppressed { get; private set; }
|
||||
public bool IsSuppressed => this.SuppressButtons.Contains(this.Button);
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -36,12 +40,14 @@ namespace StardewModdingAPI.Events
|
|||
/// <param name="cursor">The cursor position.</param>
|
||||
/// <param name="isActionButton">Whether the input should trigger actions on the affected tile.</param>
|
||||
/// <param name="isUseToolButton">Whether the input should use tools on the affected tile.</param>
|
||||
public EventArgsInput(SButton button, ICursorPosition cursor, bool isActionButton, bool isUseToolButton)
|
||||
/// <param name="suppressButtons">The buttons to suppress.</param>
|
||||
public EventArgsInput(SButton button, ICursorPosition cursor, bool isActionButton, bool isUseToolButton, HashSet<SButton> suppressButtons)
|
||||
{
|
||||
this.Button = button;
|
||||
this.Cursor = cursor;
|
||||
this.IsActionButton = isActionButton;
|
||||
this.IsUseToolButton = isUseToolButton;
|
||||
this.SuppressButtons = suppressButtons;
|
||||
}
|
||||
|
||||
/// <summary>Prevent the game from handling the current button press. This doesn't prevent other mods from receiving the event.</summary>
|
||||
|
@ -54,96 +60,7 @@ namespace StardewModdingAPI.Events
|
|||
/// <param name="button">The button to suppress.</param>
|
||||
public void SuppressButton(SButton button)
|
||||
{
|
||||
if (button == this.Button)
|
||||
this.IsSuppressed = true;
|
||||
|
||||
// keyboard
|
||||
if (button.TryGetKeyboard(out Keys key))
|
||||
Game1.oldKBState = new KeyboardState(Game1.oldKBState.GetPressedKeys().Union(new[] { key }).ToArray());
|
||||
|
||||
// controller
|
||||
else if (button.TryGetController(out Buttons controllerButton))
|
||||
{
|
||||
var newState = GamePad.GetState(PlayerIndex.One);
|
||||
var thumbsticks = Game1.oldPadState.ThumbSticks;
|
||||
var triggers = Game1.oldPadState.Triggers;
|
||||
var buttons = Game1.oldPadState.Buttons;
|
||||
var dpad = Game1.oldPadState.DPad;
|
||||
|
||||
switch (controllerButton)
|
||||
{
|
||||
// d-pad
|
||||
case Buttons.DPadDown:
|
||||
dpad = new GamePadDPad(dpad.Up, newState.DPad.Down, dpad.Left, dpad.Right);
|
||||
break;
|
||||
case Buttons.DPadLeft:
|
||||
dpad = new GamePadDPad(dpad.Up, dpad.Down, newState.DPad.Left, dpad.Right);
|
||||
break;
|
||||
case Buttons.DPadRight:
|
||||
dpad = new GamePadDPad(dpad.Up, dpad.Down, dpad.Left, newState.DPad.Right);
|
||||
break;
|
||||
case Buttons.DPadUp:
|
||||
dpad = new GamePadDPad(newState.DPad.Up, dpad.Down, dpad.Left, dpad.Right);
|
||||
break;
|
||||
|
||||
// trigger
|
||||
case Buttons.LeftTrigger:
|
||||
triggers = new GamePadTriggers(newState.Triggers.Left, triggers.Right);
|
||||
break;
|
||||
case Buttons.RightTrigger:
|
||||
triggers = new GamePadTriggers(triggers.Left, newState.Triggers.Right);
|
||||
break;
|
||||
|
||||
// thumbstick
|
||||
case Buttons.LeftThumbstickDown:
|
||||
case Buttons.LeftThumbstickLeft:
|
||||
case Buttons.LeftThumbstickRight:
|
||||
case Buttons.LeftThumbstickUp:
|
||||
thumbsticks = new GamePadThumbSticks(newState.ThumbSticks.Left, thumbsticks.Right);
|
||||
break;
|
||||
case Buttons.RightThumbstickDown:
|
||||
case Buttons.RightThumbstickLeft:
|
||||
case Buttons.RightThumbstickRight:
|
||||
case Buttons.RightThumbstickUp:
|
||||
thumbsticks = new GamePadThumbSticks(newState.ThumbSticks.Right, thumbsticks.Left);
|
||||
break;
|
||||
|
||||
// buttons
|
||||
default:
|
||||
var mask =
|
||||
(buttons.A == ButtonState.Pressed ? Buttons.A : 0)
|
||||
| (buttons.B == ButtonState.Pressed ? Buttons.B : 0)
|
||||
| (buttons.Back == ButtonState.Pressed ? Buttons.Back : 0)
|
||||
| (buttons.BigButton == ButtonState.Pressed ? Buttons.BigButton : 0)
|
||||
| (buttons.LeftShoulder == ButtonState.Pressed ? Buttons.LeftShoulder : 0)
|
||||
| (buttons.LeftStick == ButtonState.Pressed ? Buttons.LeftStick : 0)
|
||||
| (buttons.RightShoulder == ButtonState.Pressed ? Buttons.RightShoulder : 0)
|
||||
| (buttons.RightStick == ButtonState.Pressed ? Buttons.RightStick : 0)
|
||||
| (buttons.Start == ButtonState.Pressed ? Buttons.Start : 0)
|
||||
| (buttons.X == ButtonState.Pressed ? Buttons.X : 0)
|
||||
| (buttons.Y == ButtonState.Pressed ? Buttons.Y : 0);
|
||||
mask = mask ^ controllerButton;
|
||||
buttons = new GamePadButtons(mask);
|
||||
break;
|
||||
}
|
||||
|
||||
Game1.oldPadState = new GamePadState(thumbsticks, triggers, buttons, dpad);
|
||||
}
|
||||
|
||||
// mouse
|
||||
else if (button == SButton.MouseLeft || button == SButton.MouseMiddle || button == SButton.MouseRight || button == SButton.MouseX1 || button == SButton.MouseX2)
|
||||
{
|
||||
Game1.oldMouseState = new MouseState(
|
||||
x: Game1.oldMouseState.X,
|
||||
y: Game1.oldMouseState.Y,
|
||||
scrollWheel: Game1.oldMouseState.ScrollWheelValue,
|
||||
leftButton: button == SButton.MouseLeft ? ButtonState.Pressed : Game1.oldMouseState.LeftButton,
|
||||
middleButton: button == SButton.MouseMiddle ? ButtonState.Pressed : Game1.oldMouseState.MiddleButton,
|
||||
rightButton: button == SButton.MouseRight ? ButtonState.Pressed : Game1.oldMouseState.RightButton,
|
||||
xButton1: button == SButton.MouseX1 ? ButtonState.Pressed : Game1.oldMouseState.XButton1,
|
||||
xButton2: button == SButton.MouseX2 ? ButtonState.Pressed : Game1.oldMouseState.XButton2
|
||||
);
|
||||
}
|
||||
this.SuppressButtons.Add(button);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Input
|
||||
{
|
||||
/// <summary>An abstraction for manipulating controller state.</summary>
|
||||
internal class GamePadStateBuilder
|
||||
{
|
||||
/*********
|
||||
** Properties
|
||||
*********/
|
||||
/// <summary>The current button states.</summary>
|
||||
private readonly IDictionary<SButton, ButtonState> ButtonStates;
|
||||
|
||||
/// <summary>The left trigger value.</summary>
|
||||
private float LeftTrigger;
|
||||
|
||||
/// <summary>The right trigger value.</summary>
|
||||
private float RightTrigger;
|
||||
|
||||
/// <summary>The left thumbstick position.</summary>
|
||||
private Vector2 LeftStickPos;
|
||||
|
||||
/// <summary>The left thumbstick position.</summary>
|
||||
private Vector2 RightStickPos;
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="state">The initial controller state.</param>
|
||||
public GamePadStateBuilder(GamePadState state)
|
||||
{
|
||||
this.ButtonStates = new Dictionary<SButton, ButtonState>
|
||||
{
|
||||
[SButton.DPadUp] = state.DPad.Up,
|
||||
[SButton.DPadDown] = state.DPad.Down,
|
||||
[SButton.DPadLeft] = state.DPad.Left,
|
||||
[SButton.DPadRight] = state.DPad.Right,
|
||||
|
||||
[SButton.ControllerA] = state.Buttons.A,
|
||||
[SButton.ControllerB] = state.Buttons.B,
|
||||
[SButton.ControllerX] = state.Buttons.X,
|
||||
[SButton.ControllerY] = state.Buttons.Y,
|
||||
[SButton.LeftStick] = state.Buttons.LeftStick,
|
||||
[SButton.RightStick] = state.Buttons.RightStick,
|
||||
[SButton.LeftShoulder] = state.Buttons.LeftShoulder,
|
||||
[SButton.RightShoulder] = state.Buttons.RightShoulder,
|
||||
[SButton.ControllerBack] = state.Buttons.Back,
|
||||
[SButton.ControllerStart] = state.Buttons.Start,
|
||||
[SButton.BigButton] = state.Buttons.BigButton
|
||||
};
|
||||
this.LeftTrigger = state.Triggers.Left;
|
||||
this.RightTrigger = state.Triggers.Right;
|
||||
this.LeftStickPos = state.ThumbSticks.Left;
|
||||
this.RightStickPos = state.ThumbSticks.Right;
|
||||
}
|
||||
|
||||
/// <summary>Mark all matching buttons unpressed.</summary>
|
||||
/// <param name="buttons">The buttons.</param>
|
||||
public void SuppressButtons(IEnumerable<SButton> buttons)
|
||||
{
|
||||
foreach (SButton button in buttons)
|
||||
this.SuppressButton(button);
|
||||
}
|
||||
|
||||
/// <summary>Mark a button unpressed.</summary>
|
||||
/// <param name="button">The button.</param>
|
||||
public void SuppressButton(SButton button)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
// left thumbstick
|
||||
case SButton.LeftThumbstickUp:
|
||||
if (this.LeftStickPos.Y > 0)
|
||||
this.LeftStickPos.Y = 0;
|
||||
break;
|
||||
case SButton.LeftThumbstickDown:
|
||||
if (this.LeftStickPos.Y < 0)
|
||||
this.LeftStickPos.Y = 0;
|
||||
break;
|
||||
case SButton.LeftThumbstickLeft:
|
||||
if (this.LeftStickPos.X < 0)
|
||||
this.LeftStickPos.X = 0;
|
||||
break;
|
||||
case SButton.LeftThumbstickRight:
|
||||
if (this.LeftStickPos.X > 0)
|
||||
this.LeftStickPos.X = 0;
|
||||
break;
|
||||
|
||||
// right thumbstick
|
||||
case SButton.RightThumbstickUp:
|
||||
if (this.RightStickPos.Y > 0)
|
||||
this.RightStickPos.Y = 0;
|
||||
break;
|
||||
case SButton.RightThumbstickDown:
|
||||
if (this.RightStickPos.Y < 0)
|
||||
this.RightStickPos.Y = 0;
|
||||
break;
|
||||
case SButton.RightThumbstickLeft:
|
||||
if (this.RightStickPos.X < 0)
|
||||
this.RightStickPos.X = 0;
|
||||
break;
|
||||
case SButton.RightThumbstickRight:
|
||||
if (this.RightStickPos.X > 0)
|
||||
this.RightStickPos.X = 0;
|
||||
break;
|
||||
|
||||
// triggers
|
||||
case SButton.LeftTrigger:
|
||||
this.LeftTrigger = 0;
|
||||
break;
|
||||
case SButton.RightTrigger:
|
||||
this.RightTrigger = 0;
|
||||
break;
|
||||
|
||||
// buttons
|
||||
default:
|
||||
if (this.ButtonStates.ContainsKey(button))
|
||||
this.ButtonStates[button] = ButtonState.Released;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Construct an equivalent gamepad state.</summary>
|
||||
public GamePadState ToGamePadState()
|
||||
{
|
||||
return new GamePadState(
|
||||
leftThumbStick: this.LeftStickPos,
|
||||
rightThumbStick: this.RightStickPos,
|
||||
leftTrigger: this.LeftTrigger,
|
||||
rightTrigger: this.RightTrigger,
|
||||
buttons: this.GetPressedButtons().ToArray()
|
||||
);
|
||||
}
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Get all pressed buttons.</summary>
|
||||
private IEnumerable<Buttons> GetPressedButtons()
|
||||
{
|
||||
foreach (var pair in this.ButtonStates)
|
||||
{
|
||||
if (pair.Value == ButtonState.Pressed && pair.Key.TryGetController(out Buttons button))
|
||||
yield return button;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,16 @@ 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
|
||||
*********/
|
||||
|
@ -48,7 +58,7 @@ namespace StardewModdingAPI.Framework.Input
|
|||
this.MousePosition = new Point((int)(mouseState.X * (1.0 / Game1.options.zoomLevel)), (int)(mouseState.Y * (1.0 / Game1.options.zoomLevel))); // derived from Game1::getMouseX
|
||||
|
||||
// get button states
|
||||
SButton[] down = InputState.GetPressedButtons(keyboardState, mouseState, controllerState).ToArray();
|
||||
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)
|
||||
|
@ -109,7 +119,8 @@ namespace StardewModdingAPI.Framework.Input
|
|||
/// <param name="keyboard">The keyboard state.</param>
|
||||
/// <param name="mouse">The mouse state.</param>
|
||||
/// <param name="controller">The controller state.</param>
|
||||
private static IEnumerable<SButton> GetPressedButtons(KeyboardState keyboard, MouseState mouse, GamePadState controller)
|
||||
/// <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())
|
||||
|
@ -130,28 +141,23 @@ namespace StardewModdingAPI.Framework.Input
|
|||
// 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.Back == ButtonState.Pressed)
|
||||
yield return SButton.ControllerBack;
|
||||
if (controller.Buttons.BigButton == ButtonState.Pressed)
|
||||
yield return SButton.BigButton;
|
||||
if (controller.Buttons.LeftShoulder == ButtonState.Pressed)
|
||||
yield return SButton.LeftShoulder;
|
||||
if (controller.Buttons.LeftStick == ButtonState.Pressed)
|
||||
yield return SButton.LeftStick;
|
||||
if (controller.Buttons.RightShoulder == ButtonState.Pressed)
|
||||
yield return SButton.RightShoulder;
|
||||
if (controller.Buttons.RightStick == ButtonState.Pressed)
|
||||
yield return SButton.RightStick;
|
||||
if (controller.Buttons.Start == ButtonState.Pressed)
|
||||
yield return SButton.ControllerStart;
|
||||
if (controller.Buttons.X == ButtonState.Pressed)
|
||||
yield return SButton.ControllerX;
|
||||
if (controller.Buttons.Y == ButtonState.Pressed)
|
||||
yield return SButton.ControllerY;
|
||||
if (controller.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)
|
||||
|
@ -160,11 +166,55 @@ namespace StardewModdingAPI.Framework.Input
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,6 +120,9 @@ 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
|
||||
|
@ -154,6 +157,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);
|
||||
|
||||
// init watchers
|
||||
Game1.locations = new ObservableCollection<GameLocation>();
|
||||
|
@ -420,7 +424,7 @@ namespace StardewModdingAPI.Framework
|
|||
|
||||
if (status == InputStatus.Pressed)
|
||||
{
|
||||
this.Events.Input_ButtonPressed.Raise(new EventArgsInput(button, cursor, button.IsActionButton(), button.IsUseToolButton()));
|
||||
this.Events.Input_ButtonPressed.Raise(new EventArgsInput(button, cursor, button.IsActionButton(), button.IsUseToolButton(), this.SuppressButtons));
|
||||
|
||||
// legacy events
|
||||
if (button.TryGetKeyboard(out Keys key))
|
||||
|
@ -438,7 +442,7 @@ namespace StardewModdingAPI.Framework
|
|||
}
|
||||
else if (status == InputStatus.Released)
|
||||
{
|
||||
this.Events.Input_ButtonReleased.Raise(new EventArgsInput(button, cursor, button.IsActionButton(), button.IsUseToolButton()));
|
||||
this.Events.Input_ButtonReleased.Raise(new EventArgsInput(button, cursor, button.IsActionButton(), button.IsUseToolButton(), this.SuppressButtons));
|
||||
|
||||
// legacy events
|
||||
if (button.TryGetKeyboard(out Keys key))
|
||||
|
@ -539,7 +543,7 @@ namespace StardewModdingAPI.Framework
|
|||
if (curPlayer.TryGetLocationChanges(out IDictionaryWatcher<Vector2, SObject> _))
|
||||
{
|
||||
if (this.VerboseLogging)
|
||||
this.Monitor.Log($"Context: current location objects changed.", LogLevel.Trace);
|
||||
this.Monitor.Log("Context: current location objects changed.", LogLevel.Trace);
|
||||
|
||||
this.Events.Location_LocationObjectsChanged.Raise(new EventArgsLocationObjectsChanged(curPlayer.GetCurrentLocation().objects.FieldDict));
|
||||
}
|
||||
|
@ -619,6 +623,17 @@ 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)
|
||||
|
@ -1240,6 +1255,63 @@ 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()
|
||||
{
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -91,6 +91,7 @@
|
|||
<Compile Include="Framework\ContentPack.cs" />
|
||||
<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\InputStatus.cs" />
|
||||
<Compile Include="Framework\LegacyManifestVersion.cs" />
|
||||
|
@ -262,6 +263,7 @@
|
|||
<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" />
|
||||
|
|
Loading…
Reference in New Issue