summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/release-notes.md2
-rw-r--r--src/SMAPI/Framework/Input/GamePadStateBuilder.cs31
-rw-r--r--src/SMAPI/Framework/Input/IInputStateBuilder.cs4
-rw-r--r--src/SMAPI/Framework/Input/KeyboardStateBuilder.cs17
-rw-r--r--src/SMAPI/Framework/Input/MouseStateBuilder.cs39
-rw-r--r--src/SMAPI/Framework/Input/SInputState.cs157
-rw-r--r--src/SMAPI/Framework/SCore.cs6
-rw-r--r--src/SMAPI/Framework/WatcherCore.cs2
8 files changed, 109 insertions, 149 deletions
diff --git a/docs/release-notes.md b/docs/release-notes.md
index 6e531dbd..ae636153 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -9,7 +9,7 @@
## Upcoming release
* For players:
- * Added heuristic compatibility rewrites, which fix some mods previously incompatible with Android or newer game versions.
+ * Added heuristic compatibility rewrites. (This fixes some mods previously broken on Android, and improves compatibility with future game updates.)
* Tweaked the rules for showing update alerts (see _for SMAPI developers_ below for details).
* Fixed crossplatform compatibility for mods which use the `[HarmonyPatch(type)]` attribute (thanks to spacechase0!).
* Fixed map tile rotation broken when you return to the title screen and reload a save.
diff --git a/src/SMAPI/Framework/Input/GamePadStateBuilder.cs b/src/SMAPI/Framework/Input/GamePadStateBuilder.cs
index 2657fd12..f5f2d916 100644
--- a/src/SMAPI/Framework/Input/GamePadStateBuilder.cs
+++ b/src/SMAPI/Framework/Input/GamePadStateBuilder.cs
@@ -20,7 +20,7 @@ namespace StardewModdingAPI.Framework.Input
private GamePadState? State;
/// <summary>The current button states.</summary>
- private IDictionary<SButton, ButtonState> ButtonStates;
+ private readonly IDictionary<SButton, ButtonState> ButtonStates;
/// <summary>The left trigger value.</summary>
private float LeftTrigger;
@@ -39,33 +39,26 @@ namespace StardewModdingAPI.Framework.Input
** Accessors
*********/
/// <summary>Whether the gamepad is currently connected.</summary>
- public bool IsConnected { get; private set; }
+ public bool IsConnected { get; }
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
- /// <param name="state">The initial state, or <c>null</c> to get the latest state.</param>
- public GamePadStateBuilder(GamePadState? state = null)
+ /// <param name="state">The initial state.</param>
+ public GamePadStateBuilder(GamePadState state)
{
- this.Reset(state);
- }
-
- /// <summary>Reset the tracked state.</summary>
- /// <param name="state">The state from which to reset, or <c>null</c> to get the latest state.</param>
- public GamePadStateBuilder Reset(GamePadState? state = null)
- {
- this.State = state ??= GamePad.GetState(PlayerIndex.One);
- this.IsConnected = state.Value.IsConnected;
+ this.State = state;
+ this.IsConnected = state.IsConnected;
if (!this.IsConnected)
- return this;
+ return;
- GamePadDPad pad = state.Value.DPad;
- GamePadButtons buttons = state.Value.Buttons;
- GamePadTriggers triggers = state.Value.Triggers;
- GamePadThumbSticks sticks = state.Value.ThumbSticks;
+ GamePadDPad pad = state.DPad;
+ GamePadButtons buttons = state.Buttons;
+ GamePadTriggers triggers = state.Triggers;
+ GamePadThumbSticks sticks = state.ThumbSticks;
this.ButtonStates = new Dictionary<SButton, ButtonState>
{
[SButton.DPadUp] = pad.Up,
@@ -89,8 +82,6 @@ namespace StardewModdingAPI.Framework.Input
this.RightTrigger = triggers.Right;
this.LeftStickPos = sticks.Left;
this.RightStickPos = sticks.Right;
-
- return this;
}
/// <summary>Override the states for a set of buttons.</summary>
diff --git a/src/SMAPI/Framework/Input/IInputStateBuilder.cs b/src/SMAPI/Framework/Input/IInputStateBuilder.cs
index 193e5216..28d62439 100644
--- a/src/SMAPI/Framework/Input/IInputStateBuilder.cs
+++ b/src/SMAPI/Framework/Input/IInputStateBuilder.cs
@@ -12,10 +12,6 @@ namespace StardewModdingAPI.Framework.Input
/*********
** Methods
*********/
- /// <summary>Reset the tracked state.</summary>
- /// <param name="state">The state from which to reset, or <c>null</c> to get the latest state.</param>
- THandler Reset(TState? state = null);
-
/// <summary>Override the states for a set of buttons.</summary>
/// <param name="overrides">The button state overrides.</param>
THandler OverrideButtons(IDictionary<SButton, SButtonState> overrides);
diff --git a/src/SMAPI/Framework/Input/KeyboardStateBuilder.cs b/src/SMAPI/Framework/Input/KeyboardStateBuilder.cs
index f95a28bf..620ad442 100644
--- a/src/SMAPI/Framework/Input/KeyboardStateBuilder.cs
+++ b/src/SMAPI/Framework/Input/KeyboardStateBuilder.cs
@@ -21,23 +21,14 @@ namespace StardewModdingAPI.Framework.Input
** Public methods
*********/
/// <summary>Construct an instance.</summary>
- /// <param name="state">The initial state, or <c>null</c> to get the latest state.</param>
- public KeyboardStateBuilder(KeyboardState? state = null)
+ /// <param name="state">The initial state.</param>
+ public KeyboardStateBuilder(KeyboardState state)
{
- this.Reset(state);
- }
-
- /// <summary>Reset the tracked state.</summary>
- /// <param name="state">The state from which to reset, or <c>null</c> to get the latest state.</param>
- public KeyboardStateBuilder Reset(KeyboardState? state = null)
- {
- this.State = state ??= Keyboard.GetState();
+ this.State = state;
this.PressedButtons.Clear();
- foreach (var button in state.Value.GetPressedKeys())
+ foreach (var button in state.GetPressedKeys())
this.PressedButtons.Add(button);
-
- return this;
}
/// <summary>Override the states for a set of buttons.</summary>
diff --git a/src/SMAPI/Framework/Input/MouseStateBuilder.cs b/src/SMAPI/Framework/Input/MouseStateBuilder.cs
index 1cc16ca9..a1ac5492 100644
--- a/src/SMAPI/Framework/Input/MouseStateBuilder.cs
+++ b/src/SMAPI/Framework/Input/MouseStateBuilder.cs
@@ -13,51 +13,42 @@ namespace StardewModdingAPI.Framework.Input
private MouseState? State;
/// <summary>The current button states.</summary>
- private IDictionary<SButton, ButtonState> ButtonStates;
+ private readonly IDictionary<SButton, ButtonState> ButtonStates;
/// <summary>The mouse wheel scroll value.</summary>
- private int ScrollWheelValue;
+ private readonly int ScrollWheelValue;
/*********
** Accessors
*********/
/// <summary>The X cursor position.</summary>
- public int X { get; private set; }
+ public int X { get; }
/// <summary>The Y cursor position.</summary>
- public int Y { get; private set; }
+ public int Y { get; }
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
- /// <param name="state">The initial state, or <c>null</c> to get the latest state.</param>
- public MouseStateBuilder(MouseState? state = null)
+ /// <param name="state">The initial state.</param>
+ public MouseStateBuilder(MouseState state)
{
- this.Reset(state);
- }
-
- /// <summary>Reset the tracked state.</summary>
- /// <param name="state">The state from which to reset, or <c>null</c> to get the latest state.</param>
- public MouseStateBuilder Reset(MouseState? state = null)
- {
- this.State = state ??= Mouse.GetState();
+ this.State = state;
this.ButtonStates = new Dictionary<SButton, ButtonState>
{
- [SButton.MouseLeft] = state.Value.LeftButton,
- [SButton.MouseMiddle] = state.Value.MiddleButton,
- [SButton.MouseRight] = state.Value.RightButton,
- [SButton.MouseX1] = state.Value.XButton1,
- [SButton.MouseX2] = state.Value.XButton2
+ [SButton.MouseLeft] = state.LeftButton,
+ [SButton.MouseMiddle] = state.MiddleButton,
+ [SButton.MouseRight] = state.RightButton,
+ [SButton.MouseX1] = state.XButton1,
+ [SButton.MouseX2] = state.XButton2
};
- this.X = state.Value.X;
- this.Y = state.Value.Y;
- this.ScrollWheelValue = state.Value.ScrollWheelValue;
-
- return this;
+ this.X = state.X;
+ this.Y = state.Y;
+ this.ScrollWheelValue = state.ScrollWheelValue;
}
/// <summary>Override the states for a set of buttons.</summary>
diff --git a/src/SMAPI/Framework/Input/SInputState.cs b/src/SMAPI/Framework/Input/SInputState.cs
index 333f5726..3dfeb152 100644
--- a/src/SMAPI/Framework/Input/SInputState.cs
+++ b/src/SMAPI/Framework/Input/SInputState.cs
@@ -29,21 +29,24 @@ namespace StardewModdingAPI.Framework.Input
/// <summary>Whether there are new overrides in <see cref="CustomPressedKeys"/> or <see cref="CustomReleasedKeys"/> that haven't been applied to the previous state.</summary>
private bool HasNewOverrides;
+ /// <summary>The game tick when the input state was last updated.</summary>
+ private uint? LastUpdateTick;
+
/*********
** Accessors
*********/
- /// <summary>The controller state as of the last update.</summary>
- public GamePadState LastController { get; private set; }
+ /// <summary>The controller state as of the last update, with overrides applied.</summary>
+ public GamePadState ControllerState { get; private set; }
- /// <summary>The keyboard state as of the last update.</summary>
- public KeyboardState LastKeyboard { get; private set; }
+ /// <summary>The keyboard state as of the last update, with overrides applied.</summary>
+ public KeyboardState KeyboardState { get; private set; }
- /// <summary>The mouse state as of the last update.</summary>
- public MouseState LastMouse { get; private set; }
+ /// <summary>The mouse state as of the last update, with overrides applied.</summary>
+ public MouseState MouseState { 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>();
+ public IDictionary<SButton, SButtonState> ButtonStates { 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;
@@ -52,54 +55,26 @@ namespace StardewModdingAPI.Framework.Input
/*********
** Public methods
*********/
- /// <summary>Get a copy of the current state.</summary>
- public SInputState Clone()
- {
- return new SInputState
- {
- 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)
+ /// <summary>Update the current button states for the given tick. This does nothing if the input has already been updated for this tick (e.g. because SMAPI updated it before the game update).</summary>
+ public override void Update()
{
- bool changed = setDown
- ? this.CustomPressedKeys.Add(button) | this.CustomReleasedKeys.Remove(button)
- : this.CustomPressedKeys.Remove(button) | this.CustomReleasedKeys.Add(button);
+ // skip if already updated
+ if (this.LastUpdateTick == SCore.TicksElapsed)
+ return;
+ this.LastUpdateTick = SCore.TicksElapsed;
- if (changed)
- this.HasNewOverrides = true;
- }
+ // update base state
+ base.Update();
- /// <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() { }
-
- /// <summary>Update the current button states for the given tick.</summary>
- public void TrueUpdate()
- {
+ // update SMAPI extended data
try
{
float zoomMultiplier = (1f / Game1.options.zoomLevel);
// get real values
- var controller = new GamePadStateBuilder();
- var keyboard = new KeyboardStateBuilder();
- var mouse = new MouseStateBuilder();
+ var controller = new GamePadStateBuilder(base.GetGamePadState());
+ var keyboard = new KeyboardStateBuilder(base.GetKeyboardState());
+ var mouse = new MouseStateBuilder(base.GetMouseState());
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));
@@ -124,18 +99,18 @@ namespace StardewModdingAPI.Framework.Input
var pressedButtons = hasOverrides
? new HashSet<SButton>(this.GetPressedButtons(keyboard, mouse, controller))
: reallyDown;
- var activeButtons = this.DeriveStates(this.LastButtonStates, pressedButtons);
+ var activeButtons = this.DeriveStates(this.ButtonStates, pressedButtons);
// update
this.HasNewOverrides = false;
- this.LastController = controller.GetState();
- this.LastKeyboard = keyboard.GetState();
- this.LastMouse = mouse.GetState();
- this.LastButtonStates = activeButtons;
+ this.ControllerState = controller.GetState();
+ this.KeyboardState = keyboard.GetState();
+ this.MouseState = mouse.GetState();
+ this.ButtonStates = activeButtons;
if (cursorAbsolutePos != this.CursorPositionImpl?.AbsolutePixels || playerTilePos != this.LastPlayerTile)
{
this.LastPlayerTile = playerTilePos;
- this.CursorPositionImpl = this.GetCursorPosition(this.LastMouse, cursorAbsolutePos, zoomMultiplier);
+ this.CursorPositionImpl = this.GetCursorPosition(this.MouseState, cursorAbsolutePos, zoomMultiplier);
}
}
catch (InvalidOperationException)
@@ -144,53 +119,67 @@ namespace StardewModdingAPI.Framework.Input
}
}
- /// <summary>Apply input overrides to the current state.</summary>
- public void ApplyOverrides()
- {
- if (this.HasNewOverrides)
- {
- var controller = new GamePadStateBuilder(this.LastController);
- var keyboard = new KeyboardStateBuilder(this.LastKeyboard);
- var mouse = new MouseStateBuilder(this.LastMouse);
-
- if (this.ApplyOverrides(pressed: this.CustomPressedKeys, released: this.CustomReleasedKeys, controller, keyboard, mouse))
- {
- this.LastController = controller.GetState();
- this.LastKeyboard = keyboard.GetState();
- this.LastMouse = mouse.GetState();
- }
- }
- }
-
/// <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()
{
- if (Game1.options.gamepadMode == Options.GamepadModes.ForceOff)
- return new GamePadState();
-
- return this.LastController;
+ return this.ControllerState;
}
/// <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.LastKeyboard;
+ return this.KeyboardState;
}
/// <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.LastMouse;
+ return this.MouseState;
+ }
+
+ /// <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)
+ {
+ bool changed = setDown
+ ? this.CustomPressedKeys.Add(button) | this.CustomReleasedKeys.Remove(button)
+ : this.CustomPressedKeys.Remove(button) | this.CustomReleasedKeys.Add(button);
+
+ if (changed)
+ this.HasNewOverrides = true;
+ }
+
+ /// <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>Apply input overrides to the current state.</summary>
+ public void ApplyOverrides()
+ {
+ if (this.HasNewOverrides)
+ {
+ var controller = new GamePadStateBuilder(this.ControllerState);
+ var keyboard = new KeyboardStateBuilder(this.KeyboardState);
+ var mouse = new MouseStateBuilder(this.MouseState);
+
+ if (this.ApplyOverrides(pressed: this.CustomPressedKeys, released: this.CustomReleasedKeys, controller, keyboard, mouse))
+ {
+ this.ControllerState = controller.GetState();
+ this.KeyboardState = keyboard.GetState();
+ this.MouseState = mouse.GetState();
+ }
+ }
}
/// <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.LastButtonStates, button).IsDown();
+ return this.GetState(this.ButtonStates, button).IsDown();
}
/// <summary>Get whether any of the given buttons were pressed or held.</summary>
@@ -204,7 +193,7 @@ namespace StardewModdingAPI.Framework.Input
/// <param name="button">The button to check.</param>
public SButtonState GetState(SButton button)
{
- return this.GetState(this.LastButtonStates, button);
+ return this.GetState(this.ButtonStates, button);
}
@@ -305,7 +294,9 @@ namespace StardewModdingAPI.Framework.Input
/// <param name="button">The button to check.</param>
private SButtonState GetState(IDictionary<SButton, SButtonState> activeButtons, SButton button)
{
- return activeButtons.TryGetValue(button, out SButtonState state) ? state : SButtonState.None;
+ return activeButtons.TryGetValue(button, out SButtonState state)
+ ? state
+ : SButtonState.None;
}
/// <summary>Get the buttons pressed in the given stats.</summary>
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index eedbfc64..bfe6e277 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -423,7 +423,7 @@ namespace StardewModdingAPI.Framework
private void OnGameInitialized()
{
// set initial state
- this.Input.TrueUpdate();
+ this.Input.Update();
// init watchers
this.Watchers = new WatcherCore(this.Input, this.Game.GetObservableLocations());
@@ -492,7 +492,7 @@ namespace StardewModdingAPI.Framework
// user from doing anything on the overnight shipping screen.
SInputState inputState = this.Input;
if (this.Game.IsActive)
- inputState.TrueUpdate();
+ inputState.Update();
/*********
** Special cases
@@ -795,7 +795,7 @@ namespace StardewModdingAPI.Framework
}
// raise input button events
- foreach (var pair in inputState.LastButtonStates)
+ foreach (var pair in inputState.ButtonStates)
{
SButton button = pair.Key;
SButtonState status = pair.Value;
diff --git a/src/SMAPI/Framework/WatcherCore.cs b/src/SMAPI/Framework/WatcherCore.cs
index 2a5d1ee6..393f6a37 100644
--- a/src/SMAPI/Framework/WatcherCore.cs
+++ b/src/SMAPI/Framework/WatcherCore.cs
@@ -61,7 +61,7 @@ namespace StardewModdingAPI.Framework
{
// init watchers
this.CursorWatcher = WatcherFactory.ForEquatable(() => inputState.CursorPosition);
- this.MouseWheelScrollWatcher = WatcherFactory.ForEquatable(() => inputState.LastMouse.ScrollWheelValue);
+ this.MouseWheelScrollWatcher = WatcherFactory.ForEquatable(() => inputState.MouseState.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);