From 915e6d22f199354ef69a20e47f13731379b46306 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 24 Aug 2020 22:23:02 -0400 Subject: minor tweaks --- src/SMAPI/Framework/WatcherCore.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/SMAPI/Framework/WatcherCore.cs') diff --git a/src/SMAPI/Framework/WatcherCore.cs b/src/SMAPI/Framework/WatcherCore.cs index c89efa44..2a5d1ee6 100644 --- a/src/SMAPI/Framework/WatcherCore.cs +++ b/src/SMAPI/Framework/WatcherCore.cs @@ -56,7 +56,8 @@ namespace StardewModdingAPI.Framework *********/ /// Construct an instance. /// Manages input visible to the game. - public WatcherCore(SInputState inputState) + /// The observable list of game locations. + public WatcherCore(SInputState inputState, ObservableCollection gameLocations) { // init watchers this.CursorWatcher = WatcherFactory.ForEquatable(() => inputState.CursorPosition); @@ -65,7 +66,7 @@ namespace StardewModdingAPI.Framework this.WindowSizeWatcher = WatcherFactory.ForEquatable(() => new Point(Game1.viewport.Width, Game1.viewport.Height)); this.TimeWatcher = WatcherFactory.ForEquatable(() => Game1.timeOfDay); this.ActiveMenuWatcher = WatcherFactory.ForReference(() => Game1.activeClickableMenu); - this.LocationsWatcher = new WorldLocationsTracker((ObservableCollection)Game1.locations, MineShaft.activeMines); + this.LocationsWatcher = new WorldLocationsTracker(gameLocations, MineShaft.activeMines); this.LocaleWatcher = WatcherFactory.ForGenericEquality(() => LocalizedContentManager.CurrentLanguageCode); this.Watchers.AddRange(new IWatcher[] { -- cgit From f57feb7319725513fadde8b14d55f4e8e4b82c24 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 4 Sep 2020 20:56:27 -0400 Subject: extend game's input logic instead of replacing it --- docs/release-notes.md | 2 +- src/SMAPI/Framework/Input/GamePadStateBuilder.cs | 31 ++--- src/SMAPI/Framework/Input/IInputStateBuilder.cs | 4 - src/SMAPI/Framework/Input/KeyboardStateBuilder.cs | 17 +-- src/SMAPI/Framework/Input/MouseStateBuilder.cs | 39 +++--- src/SMAPI/Framework/Input/SInputState.cs | 157 ++++++++++------------ src/SMAPI/Framework/SCore.cs | 6 +- src/SMAPI/Framework/WatcherCore.cs | 2 +- 8 files changed, 109 insertions(+), 149 deletions(-) (limited to 'src/SMAPI/Framework/WatcherCore.cs') 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; /// The current button states. - private IDictionary ButtonStates; + private readonly IDictionary ButtonStates; /// The left trigger value. private float LeftTrigger; @@ -39,33 +39,26 @@ namespace StardewModdingAPI.Framework.Input ** Accessors *********/ /// Whether the gamepad is currently connected. - public bool IsConnected { get; private set; } + public bool IsConnected { get; } /********* ** Public methods *********/ /// Construct an instance. - /// The initial state, or null to get the latest state. - public GamePadStateBuilder(GamePadState? state = null) + /// The initial state. + public GamePadStateBuilder(GamePadState state) { - this.Reset(state); - } - - /// Reset the tracked state. - /// The state from which to reset, or null to get the latest state. - 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.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; } /// Override the states for a set of buttons. 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 *********/ - /// Reset the tracked state. - /// The state from which to reset, or null to get the latest state. - THandler Reset(TState? state = null); - /// Override the states for a set of buttons. /// The button state overrides. THandler OverrideButtons(IDictionary 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 *********/ /// Construct an instance. - /// The initial state, or null to get the latest state. - public KeyboardStateBuilder(KeyboardState? state = null) + /// The initial state. + public KeyboardStateBuilder(KeyboardState state) { - this.Reset(state); - } - - /// Reset the tracked state. - /// The state from which to reset, or null to get the latest state. - 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; } /// Override the states for a set of buttons. 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; /// The current button states. - private IDictionary ButtonStates; + private readonly IDictionary ButtonStates; /// The mouse wheel scroll value. - private int ScrollWheelValue; + private readonly int ScrollWheelValue; /********* ** Accessors *********/ /// The X cursor position. - public int X { get; private set; } + public int X { get; } /// The Y cursor position. - public int Y { get; private set; } + public int Y { get; } /********* ** Public methods *********/ /// Construct an instance. - /// The initial state, or null to get the latest state. - public MouseStateBuilder(MouseState? state = null) + /// The initial state. + public MouseStateBuilder(MouseState state) { - this.Reset(state); - } - - /// Reset the tracked state. - /// The state from which to reset, or null to get the latest state. - public MouseStateBuilder Reset(MouseState? state = null) - { - this.State = state ??= Mouse.GetState(); + this.State = state; this.ButtonStates = new Dictionary { - [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; } /// Override the states for a set of buttons. 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 /// Whether there are new overrides in or that haven't been applied to the previous state. private bool HasNewOverrides; + /// The game tick when the input state was last updated. + private uint? LastUpdateTick; + /********* ** Accessors *********/ - /// The controller state as of the last update. - public GamePadState LastController { get; private set; } + /// The controller state as of the last update, with overrides applied. + public GamePadState ControllerState { get; private set; } - /// The keyboard state as of the last update. - public KeyboardState LastKeyboard { get; private set; } + /// The keyboard state as of the last update, with overrides applied. + public KeyboardState KeyboardState { get; private set; } - /// The mouse state as of the last update. - public MouseState LastMouse { get; private set; } + /// The mouse state as of the last update, with overrides applied. + public MouseState MouseState { get; private set; } /// The buttons which were pressed, held, or released as of the last update. - public IDictionary LastButtonStates { get; private set; } = new Dictionary(); + public IDictionary ButtonStates { get; private set; } = new Dictionary(); /// The cursor position on the screen adjusted for the zoom level. public ICursorPosition CursorPosition => this.CursorPositionImpl; @@ -52,54 +55,26 @@ namespace StardewModdingAPI.Framework.Input /********* ** Public methods *********/ - /// Get a copy of the current state. - public SInputState Clone() - { - return new SInputState - { - LastButtonStates = this.LastButtonStates, - LastController = this.LastController, - LastKeyboard = this.LastKeyboard, - LastMouse = this.LastMouse, - CursorPositionImpl = this.CursorPositionImpl - }; - } - - /// Override the state for a button. - /// The button to override. - /// Whether to mark it pressed; else mark it released. - public void OverrideButton(SButton button, bool setDown) + /// 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). + 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(); - /// Get whether a mod has indicated the key was already handled, so the game shouldn't handle it. - /// The button to check. - public bool IsSuppressed(SButton button) - { - return this.CustomReleasedKeys.Contains(button); - } - - /// This method is called by the game, and does nothing since SMAPI will already have updated by that point. - [Obsolete("This method should only be called by the game itself.")] - public override void Update() { } - - /// Update the current button states for the given tick. - 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 reallyDown = new HashSet(this.GetPressedButtons(keyboard, mouse, controller)); @@ -124,18 +99,18 @@ namespace StardewModdingAPI.Framework.Input var pressedButtons = hasOverrides ? new HashSet(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 } } - /// Apply input overrides to the current state. - 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(); - } - } - } - /// Get the gamepad state visible to the game. - [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; } /// Get the keyboard state visible to the game. - [Obsolete("This method should only be called by the game itself.")] public override KeyboardState GetKeyboardState() { - return this.LastKeyboard; + return this.KeyboardState; } /// Get the keyboard state visible to the game. - [Obsolete("This method should only be called by the game itself.")] public override MouseState GetMouseState() { - return this.LastMouse; + return this.MouseState; + } + + /// Override the state for a button. + /// The button to override. + /// Whether to mark it pressed; else mark it released. + 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; + } + + /// Get whether a mod has indicated the key was already handled, so the game shouldn't handle it. + /// The button to check. + public bool IsSuppressed(SButton button) + { + return this.CustomReleasedKeys.Contains(button); + } + + /// Apply input overrides to the current state. + 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(); + } + } } /// Get whether a given button was pressed or held. /// The button to check. public bool IsDown(SButton button) { - return this.GetState(this.LastButtonStates, button).IsDown(); + return this.GetState(this.ButtonStates, button).IsDown(); } /// Get whether any of the given buttons were pressed or held. @@ -204,7 +193,7 @@ namespace StardewModdingAPI.Framework.Input /// The button to check. 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 /// The button to check. private SButtonState GetState(IDictionary activeButtons, SButton button) { - return activeButtons.TryGetValue(button, out SButtonState state) ? state : SButtonState.None; + return activeButtons.TryGetValue(button, out SButtonState state) + ? state + : SButtonState.None; } /// Get the buttons pressed in the given stats. 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); -- cgit