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.
This commit is contained in:
parent
1b282f950a
commit
29fdf9ae4a
|
@ -8,10 +8,14 @@
|
|||
* For modders:
|
||||
* Added support for flipped and rotated map tiles (in collaboration with Platonymous).
|
||||
* Added support for `.tmx` maps using zlib compression (thanks to Platonymous!).
|
||||
* Mods are no longer prevented from suppressing key presses in the chatbox. Use this power wisely.
|
||||
|
||||
* For the web UI:
|
||||
* Added option to upload files using a file picker.
|
||||
|
||||
* For SMAPI developers:
|
||||
* Added internal API to send custom input to the game/mods. This is mainly meant to support Virtual Keyboard on Android, but might be exposed as a public API in future versions.
|
||||
|
||||
## 3.3.2
|
||||
Released 22 February 2020 for Stardew Valley 1.4.1 or later.
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
foreach (var pair in overrides)
|
||||
{
|
||||
switch (button)
|
||||
bool isDown = pair.Value.IsDown();
|
||||
switch (pair.Key)
|
||||
{
|
||||
// left thumbstick
|
||||
case SButton.LeftThumbstickUp:
|
||||
if (this.LeftStickPos.Y > 0)
|
||||
this.LeftStickPos.Y = 0;
|
||||
this.LeftStickPos.Y = isDown ? 1 : 0;
|
||||
break;
|
||||
case SButton.LeftThumbstickDown:
|
||||
if (this.LeftStickPos.Y < 0)
|
||||
this.LeftStickPos.Y = 0;
|
||||
this.LeftStickPos.Y = isDown ? 1 : 0;
|
||||
break;
|
||||
case SButton.LeftThumbstickLeft:
|
||||
if (this.LeftStickPos.X < 0)
|
||||
this.LeftStickPos.X = 0;
|
||||
this.LeftStickPos.X = isDown ? 1 : 0;
|
||||
break;
|
||||
case SButton.LeftThumbstickRight:
|
||||
if (this.LeftStickPos.X > 0)
|
||||
this.LeftStickPos.X = 0;
|
||||
this.LeftStickPos.X = isDown ? 1 : 0;
|
||||
break;
|
||||
|
||||
// right thumbstick
|
||||
case SButton.RightThumbstickUp:
|
||||
if (this.RightStickPos.Y > 0)
|
||||
this.RightStickPos.Y = 0;
|
||||
this.RightStickPos.Y = isDown ? 1 : 0;
|
||||
break;
|
||||
case SButton.RightThumbstickDown:
|
||||
if (this.RightStickPos.Y < 0)
|
||||
this.RightStickPos.Y = 0;
|
||||
this.RightStickPos.Y = isDown ? 1 : 0;
|
||||
break;
|
||||
case SButton.RightThumbstickLeft:
|
||||
if (this.RightStickPos.X < 0)
|
||||
this.RightStickPos.X = 0;
|
||||
this.RightStickPos.X = isDown ? 1 : 0;
|
||||
break;
|
||||
case SButton.RightThumbstickRight:
|
||||
if (this.RightStickPos.X > 0)
|
||||
this.RightStickPos.X = 0;
|
||||
this.RightStickPos.X = isDown ? 1 : 0;
|
||||
break;
|
||||
|
||||
// triggers
|
||||
case SButton.LeftTrigger:
|
||||
this.LeftTrigger = 0;
|
||||
this.LeftTrigger = isDown ? 1 : 0;
|
||||
break;
|
||||
case SButton.RightTrigger:
|
||||
this.RightTrigger = 0;
|
||||
this.RightTrigger = isDown ? 1 : 0;
|
||||
break;
|
||||
|
||||
// buttons
|
||||
default:
|
||||
if (this.ButtonStates.ContainsKey(button))
|
||||
this.ButtonStates[button] = ButtonState.Released;
|
||||
if (this.ButtonStates.ContainsKey(pair.Key))
|
||||
this.ButtonStates[pair.Key] = isDown ? ButtonState.Pressed : ButtonState.Released;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Construct an equivalent gamepad state.</summary>
|
||||
public GamePadState ToGamePadState()
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Construct an equivalent state.</summary>
|
||||
public GamePadState ToState()
|
||||
{
|
||||
return new GamePadState(
|
||||
leftThumbStick: this.LeftStickPos,
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
public MouseState LastMouse { 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 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;
|
||||
if (pressed.Count == 0 && released.Count == 0)
|
||||
return false;
|
||||
|
||||
// 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)
|
||||
// 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))
|
||||
{
|
||||
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);
|
||||
else if (gamePadState.IsConnected && button.TryGetController(out Buttons _))
|
||||
suppressButtons.Add(button);
|
||||
}
|
||||
|
||||
// suppress keyboard keys
|
||||
if (keyboardState.GetPressedKeys().Any() && suppressKeys.Any())
|
||||
keyboardState = new KeyboardState(keyboardState.GetPressedKeys().Except(suppressKeys).ToArray());
|
||||
|
||||
// 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
|
||||
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)
|
||||
mouseOverrides[button] = newState;
|
||||
else if (button.TryGetKeyboard(out Keys _))
|
||||
keyboardOverrides[button] = newState;
|
||||
else if (gamePadState.IsConnected && button.TryGetController(out Buttons _))
|
||||
controllerOverrides[button] = newState;
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue