diff options
Diffstat (limited to 'src/SMAPI')
40 files changed, 1198 insertions, 195 deletions
diff --git a/src/SMAPI/Events/ControlEvents.cs b/src/SMAPI/Events/ControlEvents.cs index 973bb245..a3994d1d 100644 --- a/src/SMAPI/Events/ControlEvents.cs +++ b/src/SMAPI/Events/ControlEvents.cs @@ -20,57 +20,57 @@ namespace StardewModdingAPI.Events /// <summary>Raised when the <see cref="KeyboardState"/> changes. That happens when the player presses or releases a key.</summary> public static event EventHandler<EventArgsKeyboardStateChanged> KeyboardChanged { - add => ControlEvents.EventManager.Control_KeyboardChanged.Add(value); - remove => ControlEvents.EventManager.Control_KeyboardChanged.Remove(value); + add => ControlEvents.EventManager.Legacy_Control_KeyboardChanged.Add(value); + remove => ControlEvents.EventManager.Legacy_Control_KeyboardChanged.Remove(value); } - /// <summary>Raised when the player presses a keyboard key.</summary> + /// <summary>Raised after the player presses a keyboard key.</summary> public static event EventHandler<EventArgsKeyPressed> KeyPressed { - add => ControlEvents.EventManager.Control_KeyPressed.Add(value); - remove => ControlEvents.EventManager.Control_KeyPressed.Remove(value); + add => ControlEvents.EventManager.Legacy_Control_KeyPressed.Add(value); + remove => ControlEvents.EventManager.Legacy_Control_KeyPressed.Remove(value); } - /// <summary>Raised when the player releases a keyboard key.</summary> + /// <summary>Raised after the player releases a keyboard key.</summary> public static event EventHandler<EventArgsKeyPressed> KeyReleased { - add => ControlEvents.EventManager.Control_KeyReleased.Add(value); - remove => ControlEvents.EventManager.Control_KeyReleased.Remove(value); + add => ControlEvents.EventManager.Legacy_Control_KeyReleased.Add(value); + remove => ControlEvents.EventManager.Legacy_Control_KeyReleased.Remove(value); } /// <summary>Raised when the <see cref="MouseState"/> changes. That happens when the player moves the mouse, scrolls the mouse wheel, or presses/releases a button.</summary> public static event EventHandler<EventArgsMouseStateChanged> MouseChanged { - add => ControlEvents.EventManager.Control_MouseChanged.Add(value); - remove => ControlEvents.EventManager.Control_MouseChanged.Remove(value); + add => ControlEvents.EventManager.Legacy_Control_MouseChanged.Add(value); + remove => ControlEvents.EventManager.Legacy_Control_MouseChanged.Remove(value); } /// <summary>The player pressed a controller button. This event isn't raised for trigger buttons.</summary> public static event EventHandler<EventArgsControllerButtonPressed> ControllerButtonPressed { - add => ControlEvents.EventManager.Control_ControllerButtonPressed.Add(value); - remove => ControlEvents.EventManager.Control_ControllerButtonPressed.Remove(value); + add => ControlEvents.EventManager.Legacy_Control_ControllerButtonPressed.Add(value); + remove => ControlEvents.EventManager.Legacy_Control_ControllerButtonPressed.Remove(value); } /// <summary>The player released a controller button. This event isn't raised for trigger buttons.</summary> public static event EventHandler<EventArgsControllerButtonReleased> ControllerButtonReleased { - add => ControlEvents.EventManager.Control_ControllerButtonReleased.Add(value); - remove => ControlEvents.EventManager.Control_ControllerButtonReleased.Remove(value); + add => ControlEvents.EventManager.Legacy_Control_ControllerButtonReleased.Add(value); + remove => ControlEvents.EventManager.Legacy_Control_ControllerButtonReleased.Remove(value); } /// <summary>The player pressed a controller trigger button.</summary> public static event EventHandler<EventArgsControllerTriggerPressed> ControllerTriggerPressed { - add => ControlEvents.EventManager.Control_ControllerTriggerPressed.Add(value); - remove => ControlEvents.EventManager.Control_ControllerTriggerPressed.Remove(value); + add => ControlEvents.EventManager.Legacy_Control_ControllerTriggerPressed.Add(value); + remove => ControlEvents.EventManager.Legacy_Control_ControllerTriggerPressed.Remove(value); } /// <summary>The player released a controller trigger button.</summary> public static event EventHandler<EventArgsControllerTriggerReleased> ControllerTriggerReleased { - add => ControlEvents.EventManager.Control_ControllerTriggerReleased.Add(value); - remove => ControlEvents.EventManager.Control_ControllerTriggerReleased.Remove(value); + add => ControlEvents.EventManager.Legacy_Control_ControllerTriggerReleased.Add(value); + remove => ControlEvents.EventManager.Legacy_Control_ControllerTriggerReleased.Remove(value); } diff --git a/src/SMAPI/Events/EventArgsInput.cs b/src/SMAPI/Events/EventArgsInput.cs index d60f4017..0cafdba5 100644 --- a/src/SMAPI/Events/EventArgsInput.cs +++ b/src/SMAPI/Events/EventArgsInput.cs @@ -23,10 +23,10 @@ namespace StardewModdingAPI.Events public ICursorPosition Cursor { get; } /// <summary>Whether the input should trigger actions on the affected tile.</summary> - public bool IsActionButton { get; } + public bool IsActionButton => this.Button.IsActionButton(); /// <summary>Whether the input should use tools on the affected tile.</summary> - public bool IsUseToolButton { get; } + public bool IsUseToolButton => this.Button.IsUseToolButton(); /// <summary>Whether a mod has indicated the key was already handled.</summary> public bool IsSuppressed => this.SuppressButtons.Contains(this.Button); @@ -38,15 +38,11 @@ namespace StardewModdingAPI.Events /// <summary>Construct an instance.</summary> /// <param name="button">The button on the controller, keyboard, or mouse.</param> /// <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> /// <param name="suppressButtons">The buttons to suppress.</param> - public EventArgsInput(SButton button, ICursorPosition cursor, bool isActionButton, bool isUseToolButton, HashSet<SButton> suppressButtons) + public EventArgsInput(SButton button, ICursorPosition cursor, HashSet<SButton> suppressButtons) { this.Button = button; this.Cursor = cursor; - this.IsActionButton = isActionButton; - this.IsUseToolButton = isUseToolButton; this.SuppressButtons = suppressButtons; } diff --git a/src/SMAPI/Events/EventArgsLocationObjectsChanged.cs b/src/SMAPI/Events/EventArgsLocationObjectsChanged.cs index 410ef6e6..3bb387d5 100644 --- a/src/SMAPI/Events/EventArgsLocationObjectsChanged.cs +++ b/src/SMAPI/Events/EventArgsLocationObjectsChanged.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using Microsoft.Xna.Framework; -using Netcode; using StardewValley; using SObject = StardewValley.Object; diff --git a/src/SMAPI/Events/IInputEvents.cs b/src/SMAPI/Events/IInputEvents.cs new file mode 100644 index 00000000..64d82c57 --- /dev/null +++ b/src/SMAPI/Events/IInputEvents.cs @@ -0,0 +1,20 @@ +using System; + +namespace StardewModdingAPI.Events +{ + /// <summary>Events raised when the player provides input using a controller, keyboard, or mouse.</summary> + public interface IInputEvents + { + /// <summary>Raised after the player presses a button on the keyboard, controller, or mouse.</summary> + event EventHandler<InputButtonPressedArgsInput> ButtonPressed; + + /// <summary>Raised after the player releases a button on the keyboard, controller, or mouse.</summary> + event EventHandler<InputButtonReleasedArgsInput> ButtonReleased; + + /// <summary>Raised after the player moves the in-game cursor.</summary> + event EventHandler<InputCursorMovedArgsInput> CursorMoved; + + /// <summary>Raised after the player scrolls the mouse wheel.</summary> + event EventHandler<InputMouseWheelScrolledEventArgs> MouseWheelScrolled; + } +} diff --git a/src/SMAPI/Events/IModEvents.cs b/src/SMAPI/Events/IModEvents.cs new file mode 100644 index 00000000..16ec6557 --- /dev/null +++ b/src/SMAPI/Events/IModEvents.cs @@ -0,0 +1,12 @@ +namespace StardewModdingAPI.Events +{ + /// <summary>Manages access to events raised by SMAPI.</summary> + public interface IModEvents + { + /// <summary>Events raised when the player provides input using a controller, keyboard, or mouse.</summary> + IInputEvents Input { get; } + + /// <summary>Events raised when something changes in the world.</summary> + IWorldEvents World { get; } + } +} diff --git a/src/SMAPI/Events/IWorldEvents.cs b/src/SMAPI/Events/IWorldEvents.cs new file mode 100644 index 00000000..067a79bc --- /dev/null +++ b/src/SMAPI/Events/IWorldEvents.cs @@ -0,0 +1,26 @@ +using System; + +namespace StardewModdingAPI.Events +{ + /// <summary>Events raised when something changes in the world.</summary> + public interface IWorldEvents + { + /// <summary>Raised after a game location is added or removed.</summary> + event EventHandler<WorldLocationListChangedEventArgs> LocationListChanged; + + /// <summary>Raised after buildings are added or removed in a location.</summary> + event EventHandler<WorldBuildingListChangedEventArgs> BuildingListChanged; + + /// <summary>Raised after large terrain features (like bushes) are added or removed in a location.</summary> + event EventHandler<WorldLargeTerrainFeatureListChangedEventArgs> LargeTerrainFeatureListChanged; + + /// <summary>Raised after NPCs are added or removed in a location.</summary> + event EventHandler<WorldNpcListChangedEventArgs> NpcListChanged; + + /// <summary>Raised after objects are added or removed in a location.</summary> + event EventHandler<WorldObjectListChangedEventArgs> ObjectListChanged; + + /// <summary>Raised after terrain features (like floors and trees) are added or removed in a location.</summary> + event EventHandler<WorldTerrainFeatureListChangedEventArgs> TerrainFeatureListChanged; + } +} diff --git a/src/SMAPI/Events/InputButtonPressedEventArgs.cs b/src/SMAPI/Events/InputButtonPressedEventArgs.cs new file mode 100644 index 00000000..002f7cf1 --- /dev/null +++ b/src/SMAPI/Events/InputButtonPressedEventArgs.cs @@ -0,0 +1,60 @@ +using System; +using StardewModdingAPI.Framework.Input; + +namespace StardewModdingAPI.Events +{ + /// <summary>Event arguments when a button is pressed.</summary> + public class InputButtonPressedArgsInput : EventArgs + { + /********* + ** Properties + *********/ + /// <summary>The game's current input state.</summary> + private readonly SInputState InputState; + + + /********* + ** Accessors + *********/ + /// <summary>The button on the controller, keyboard, or mouse.</summary> + public SButton Button { get; } + + /// <summary>The current cursor position.</summary> + public ICursorPosition Cursor { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="button">The button on the controller, keyboard, or mouse.</param> + /// <param name="cursor">The cursor position.</param> + /// <param name="inputState">The game's current input state.</param> + internal InputButtonPressedArgsInput(SButton button, ICursorPosition cursor, SInputState inputState) + { + this.Button = button; + this.Cursor = cursor; + this.InputState = inputState; + } + + /// <summary>Whether a mod has indicated the key was already handled, so the game should 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> + /// <param name="button">The button to check.</param> + public bool IsSuppressed(SButton button) + { + return this.InputState.SuppressButtons.Contains(button); + } + + /// <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.InputState.IsDown(button); + } + } +} diff --git a/src/SMAPI/Events/InputButtonReleasedEventArgs.cs b/src/SMAPI/Events/InputButtonReleasedEventArgs.cs new file mode 100644 index 00000000..bc5e4a89 --- /dev/null +++ b/src/SMAPI/Events/InputButtonReleasedEventArgs.cs @@ -0,0 +1,60 @@ +using System; +using StardewModdingAPI.Framework.Input; + +namespace StardewModdingAPI.Events +{ + /// <summary>Event arguments when a button is released.</summary> + public class InputButtonReleasedArgsInput : EventArgs + { + /********* + ** Properties + *********/ + /// <summary>The game's current input state.</summary> + private readonly SInputState InputState; + + + /********* + ** Accessors + *********/ + /// <summary>The button on the controller, keyboard, or mouse.</summary> + public SButton Button { get; } + + /// <summary>The current cursor position.</summary> + public ICursorPosition Cursor { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="button">The button on the controller, keyboard, or mouse.</param> + /// <param name="cursor">The cursor position.</param> + /// <param name="inputState">The game's current input state.</param> + internal InputButtonReleasedArgsInput(SButton button, ICursorPosition cursor, SInputState inputState) + { + this.Button = button; + this.Cursor = cursor; + this.InputState = inputState; + } + + /// <summary>Whether a mod has indicated the key was already handled, so the game should 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> + /// <param name="button">The button to check.</param> + public bool IsSuppressed(SButton button) + { + return this.InputState.SuppressButtons.Contains(button); + } + + /// <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.InputState.IsDown(button); + } + } +} diff --git a/src/SMAPI/Events/InputCursorMovedEventArgs.cs b/src/SMAPI/Events/InputCursorMovedEventArgs.cs new file mode 100644 index 00000000..02e1ee2c --- /dev/null +++ b/src/SMAPI/Events/InputCursorMovedEventArgs.cs @@ -0,0 +1,30 @@ +using System; + +namespace StardewModdingAPI.Events +{ + /// <summary>Event arguments when the in-game cursor is moved.</summary> + public class InputCursorMovedArgsInput : EventArgs + { + /********* + ** Accessors + *********/ + /// <summary>The previous cursor position.</summary> + public ICursorPosition OldPosition { get; } + + /// <summary>The current cursor position.</summary> + public ICursorPosition NewPosition { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="oldPosition">The previous cursor position.</param> + /// <param name="newPosition">The new cursor position.</param> + public InputCursorMovedArgsInput(ICursorPosition oldPosition, ICursorPosition newPosition) + { + this.OldPosition = oldPosition; + this.NewPosition = newPosition; + } + } +} diff --git a/src/SMAPI/Events/InputEvents.cs b/src/SMAPI/Events/InputEvents.cs index 84d7ce5d..e62d6ee6 100644 --- a/src/SMAPI/Events/InputEvents.cs +++ b/src/SMAPI/Events/InputEvents.cs @@ -19,15 +19,15 @@ namespace StardewModdingAPI.Events /// <summary>Raised when the player presses a button on the keyboard, controller, or mouse.</summary> public static event EventHandler<EventArgsInput> ButtonPressed { - add => InputEvents.EventManager.Input_ButtonPressed.Add(value); - remove => InputEvents.EventManager.Input_ButtonPressed.Remove(value); + add => InputEvents.EventManager.Legacy_Input_ButtonPressed.Add(value); + remove => InputEvents.EventManager.Legacy_Input_ButtonPressed.Remove(value); } /// <summary>Raised when the player releases a keyboard key on the keyboard, controller, or mouse.</summary> public static event EventHandler<EventArgsInput> ButtonReleased { - add => InputEvents.EventManager.Input_ButtonReleased.Add(value); - remove => InputEvents.EventManager.Input_ButtonReleased.Remove(value); + add => InputEvents.EventManager.Legacy_Input_ButtonReleased.Add(value); + remove => InputEvents.EventManager.Legacy_Input_ButtonReleased.Remove(value); } diff --git a/src/SMAPI/Events/InputMouseWheelScrolledEventArgs.cs b/src/SMAPI/Events/InputMouseWheelScrolledEventArgs.cs new file mode 100644 index 00000000..9afab9cc --- /dev/null +++ b/src/SMAPI/Events/InputMouseWheelScrolledEventArgs.cs @@ -0,0 +1,38 @@ +using System; + +namespace StardewModdingAPI.Events +{ + /// <summary>Event arguments when the player scrolls the mouse wheel.</summary> + public class InputMouseWheelScrolledEventArgs : EventArgs + { + /********* + ** Accessors + *********/ + /// <summary>The cursor position.</summary> + public ICursorPosition Position { get; } + + /// <summary>The old scroll value.</summary> + public int OldValue { get; } + + /// <summary>The new scroll value.</summary> + public int NewValue { get; } + + /// <summary>The amount by which the scroll value changed.</summary> + public int Delta => this.NewValue - this.OldValue; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="position">The cursor position.</param> + /// <param name="oldValue">The old scroll value.</param> + /// <param name="newValue">The new scroll value.</param> + public InputMouseWheelScrolledEventArgs(ICursorPosition position, int oldValue, int newValue) + { + this.Position = position; + this.OldValue = oldValue; + this.NewValue = newValue; + } + } +} diff --git a/src/SMAPI/Events/LocationEvents.cs b/src/SMAPI/Events/LocationEvents.cs index ff75c619..e2108de0 100644 --- a/src/SMAPI/Events/LocationEvents.cs +++ b/src/SMAPI/Events/LocationEvents.cs @@ -19,22 +19,22 @@ namespace StardewModdingAPI.Events /// <summary>Raised after a game location is added or removed.</summary> public static event EventHandler<EventArgsLocationsChanged> LocationsChanged { - add => LocationEvents.EventManager.Location_LocationsChanged.Add(value); - remove => LocationEvents.EventManager.Location_LocationsChanged.Remove(value); + add => LocationEvents.EventManager.Legacy_Location_LocationsChanged.Add(value); + remove => LocationEvents.EventManager.Legacy_Location_LocationsChanged.Remove(value); } /// <summary>Raised after buildings are added or removed in a location.</summary> public static event EventHandler<EventArgsLocationBuildingsChanged> BuildingsChanged { - add => LocationEvents.EventManager.Location_BuildingsChanged.Add(value); - remove => LocationEvents.EventManager.Location_BuildingsChanged.Remove(value); + add => LocationEvents.EventManager.Legacy_Location_BuildingsChanged.Add(value); + remove => LocationEvents.EventManager.Legacy_Location_BuildingsChanged.Remove(value); } /// <summary>Raised after objects are added or removed in a location.</summary> public static event EventHandler<EventArgsLocationObjectsChanged> ObjectsChanged { - add => LocationEvents.EventManager.Location_ObjectsChanged.Add(value); - remove => LocationEvents.EventManager.Location_ObjectsChanged.Remove(value); + add => LocationEvents.EventManager.Legacy_Location_ObjectsChanged.Add(value); + remove => LocationEvents.EventManager.Legacy_Location_ObjectsChanged.Remove(value); } diff --git a/src/SMAPI/Events/WorldBuildingListChangedEventArgs.cs b/src/SMAPI/Events/WorldBuildingListChangedEventArgs.cs new file mode 100644 index 00000000..e73b9396 --- /dev/null +++ b/src/SMAPI/Events/WorldBuildingListChangedEventArgs.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using StardewValley; +using StardewValley.Buildings; + +namespace StardewModdingAPI.Events +{ + /// <summary>Event arguments for a <see cref="IWorldEvents.BuildingListChanged"/> event.</summary> + public class WorldBuildingListChangedEventArgs : EventArgs + { + /********* + ** Accessors + *********/ + /// <summary>The location which changed.</summary> + public GameLocation Location { get; } + + /// <summary>The buildings added to the location.</summary> + public IEnumerable<Building> Added { get; } + + /// <summary>The buildings removed from the location.</summary> + public IEnumerable<Building> Removed { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="location">The location which changed.</param> + /// <param name="added">The buildings added to the location.</param> + /// <param name="removed">The buildings removed from the location.</param> + public WorldBuildingListChangedEventArgs(GameLocation location, IEnumerable<Building> added, IEnumerable<Building> removed) + { + this.Location = location; + this.Added = added.ToArray(); + this.Removed = removed.ToArray(); + } + } +} diff --git a/src/SMAPI/Events/WorldLargeTerrainFeatureListChangedEventArgs.cs b/src/SMAPI/Events/WorldLargeTerrainFeatureListChangedEventArgs.cs new file mode 100644 index 00000000..053a0e41 --- /dev/null +++ b/src/SMAPI/Events/WorldLargeTerrainFeatureListChangedEventArgs.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using StardewValley; +using StardewValley.TerrainFeatures; + +namespace StardewModdingAPI.Events +{ + /// <summary>Event arguments for a <see cref="IWorldEvents.LargeTerrainFeatureListChanged"/> event.</summary> + public class WorldLargeTerrainFeatureListChangedEventArgs : EventArgs + { + /********* + ** Accessors + *********/ + /// <summary>The location which changed.</summary> + public GameLocation Location { get; } + + /// <summary>The large terrain features added to the location.</summary> + public IEnumerable<LargeTerrainFeature> Added { get; } + + /// <summary>The large terrain features removed from the location.</summary> + public IEnumerable<LargeTerrainFeature> Removed { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="location">The location which changed.</param> + /// <param name="added">The large terrain features added to the location.</param> + /// <param name="removed">The large terrain features removed from the location.</param> + public WorldLargeTerrainFeatureListChangedEventArgs(GameLocation location, IEnumerable<LargeTerrainFeature> added, IEnumerable<LargeTerrainFeature> removed) + { + this.Location = location; + this.Added = added.ToArray(); + this.Removed = removed.ToArray(); + } + } +} diff --git a/src/SMAPI/Events/WorldLocationListChangedEventArgs.cs b/src/SMAPI/Events/WorldLocationListChangedEventArgs.cs new file mode 100644 index 00000000..8bc26a43 --- /dev/null +++ b/src/SMAPI/Events/WorldLocationListChangedEventArgs.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using StardewValley; + +namespace StardewModdingAPI.Events +{ + /// <summary>Event arguments for a <see cref="IWorldEvents.LocationListChanged"/> event.</summary> + public class WorldLocationListChangedEventArgs : EventArgs + { + /********* + ** Accessors + *********/ + /// <summary>The added locations.</summary> + public IEnumerable<GameLocation> Added { get; } + + /// <summary>The removed locations.</summary> + public IEnumerable<GameLocation> Removed { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="added">The added locations.</param> + /// <param name="removed">The removed locations.</param> + public WorldLocationListChangedEventArgs(IEnumerable<GameLocation> added, IEnumerable<GameLocation> removed) + { + this.Added = added.ToArray(); + this.Removed = removed.ToArray(); + } + } +} diff --git a/src/SMAPI/Events/WorldNpcListChangedEventArgs.cs b/src/SMAPI/Events/WorldNpcListChangedEventArgs.cs new file mode 100644 index 00000000..e251f894 --- /dev/null +++ b/src/SMAPI/Events/WorldNpcListChangedEventArgs.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using StardewValley; + +namespace StardewModdingAPI.Events +{ + /// <summary>Event arguments for a <see cref="IWorldEvents.NpcListChanged"/> event.</summary> + public class WorldNpcListChangedEventArgs : EventArgs + { + /********* + ** Accessors + *********/ + /// <summary>The location which changed.</summary> + public GameLocation Location { get; } + + /// <summary>The NPCs added to the location.</summary> + public IEnumerable<NPC> Added { get; } + + /// <summary>The NPCs removed from the location.</summary> + public IEnumerable<NPC> Removed { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="location">The location which changed.</param> + /// <param name="added">The NPCs added to the location.</param> + /// <param name="removed">The NPCs removed from the location.</param> + public WorldNpcListChangedEventArgs(GameLocation location, IEnumerable<NPC> added, IEnumerable<NPC> removed) + { + this.Location = location; + this.Added = added.ToArray(); + this.Removed = removed.ToArray(); + } + } +} diff --git a/src/SMAPI/Events/WorldObjectListChangedEventArgs.cs b/src/SMAPI/Events/WorldObjectListChangedEventArgs.cs new file mode 100644 index 00000000..5623a49b --- /dev/null +++ b/src/SMAPI/Events/WorldObjectListChangedEventArgs.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Xna.Framework; +using StardewValley; +using Object = StardewValley.Object; + +namespace StardewModdingAPI.Events +{ + /// <summary>Event arguments for a <see cref="IWorldEvents.ObjectListChanged"/> event.</summary> + public class WorldObjectListChangedEventArgs : EventArgs + { + /********* + ** Accessors + *********/ + /// <summary>The location which changed.</summary> + public GameLocation Location { get; } + + /// <summary>The objects added to the location.</summary> + public IEnumerable<KeyValuePair<Vector2, Object>> Added { get; } + + /// <summary>The objects removed from the location.</summary> + public IEnumerable<KeyValuePair<Vector2, Object>> Removed { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="location">The location which changed.</param> + /// <param name="added">The objects added to the location.</param> + /// <param name="removed">The objects removed from the location.</param> + public WorldObjectListChangedEventArgs(GameLocation location, IEnumerable<KeyValuePair<Vector2, Object>> added, IEnumerable<KeyValuePair<Vector2, Object>> removed) + { + this.Location = location; + this.Added = added.ToArray(); + this.Removed = removed.ToArray(); + } + } +} diff --git a/src/SMAPI/Events/WorldTerrainFeatureListChangedEventArgs.cs b/src/SMAPI/Events/WorldTerrainFeatureListChangedEventArgs.cs new file mode 100644 index 00000000..cb089811 --- /dev/null +++ b/src/SMAPI/Events/WorldTerrainFeatureListChangedEventArgs.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Xna.Framework; +using StardewValley; +using StardewValley.TerrainFeatures; + +namespace StardewModdingAPI.Events +{ + /// <summary>Event arguments for a <see cref="IWorldEvents.TerrainFeatureListChanged"/> event.</summary> + public class WorldTerrainFeatureListChangedEventArgs : EventArgs + { + /********* + ** Accessors + *********/ + /// <summary>The location which changed.</summary> + public GameLocation Location { get; } + + /// <summary>The terrain features added to the location.</summary> + public IEnumerable<KeyValuePair<Vector2, TerrainFeature>> Added { get; } + + /// <summary>The terrain features removed from the location.</summary> + public IEnumerable<KeyValuePair<Vector2, TerrainFeature>> Removed { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="location">The location which changed.</param> + /// <param name="added">The terrain features added to the location.</param> + /// <param name="removed">The terrain features removed from the location.</param> + public WorldTerrainFeatureListChangedEventArgs(GameLocation location, IEnumerable<KeyValuePair<Vector2, TerrainFeature>> added, IEnumerable<KeyValuePair<Vector2, TerrainFeature>> removed) + { + this.Location = location; + this.Added = added.ToArray(); + this.Removed = removed.ToArray(); + } + } +} diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 1a57dd22..1336f3e9 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using System.Reflection; using Microsoft.Xna.Framework.Content; -using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.ContentManagers; using StardewModdingAPI.Framework.Reflection; @@ -156,27 +155,7 @@ namespace StardewModdingAPI.Framework // get cloned asset T data = contentManager.Load<T>(internalKey, language); - switch (data as object) - { - case Texture2D source: - { - int[] pixels = new int[source.Width * source.Height]; - source.GetData(pixels); - - Texture2D clone = new Texture2D(source.GraphicsDevice, source.Width, source.Height); - clone.SetData(pixels); - return (T)(object)clone; - } - - case Dictionary<string, string> source: - return (T)(object)new Dictionary<string, string>(source); - - case Dictionary<int, string> source: - return (T)(object)new Dictionary<int, string>(source); - - default: - return data; - } + return contentManager.CloneIfPossible(data); } /// <summary>Purge assets from the cache that match one of the interceptors.</summary> @@ -199,12 +178,36 @@ namespace StardewModdingAPI.Framework { // check loaders MethodInfo canLoadGeneric = canLoad.MakeGenericMethod(asset.DataType); - if (loaders.Any(loader => (bool)canLoadGeneric.Invoke(loader, new object[] { asset }))) - return true; + foreach (IAssetLoader loader in loaders) + { + try + { + if ((bool)canLoadGeneric.Invoke(loader, new object[] { asset })) + return true; + } + catch (Exception ex) + { + this.GetModFor(loader).LogAsMod($"Mod failed when checking whether it could load asset '{asset.AssetName}'. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); + } + } // check editors MethodInfo canEditGeneric = canEdit.MakeGenericMethod(asset.DataType); - return editors.Any(editor => (bool)canEditGeneric.Invoke(editor, new object[] { asset })); + foreach (IAssetEditor editor in editors) + { + try + { + if ((bool)canEditGeneric.Invoke(editor, new object[] { asset })) + return true; + } + catch (Exception ex) + { + this.GetModFor(editor).LogAsMod($"Mod failed when checking whether it could edit asset '{asset.AssetName}'. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); + } + } + + // asset not affected by a loader or editor + return false; }); } @@ -279,5 +282,33 @@ namespace StardewModdingAPI.Framework this.ContentManagers.Remove(contentManager); } + + /// <summary>Get the mod which registered an asset loader.</summary> + /// <param name="loader">The asset loader.</param> + /// <exception cref="KeyNotFoundException">The given loader couldn't be matched to a mod.</exception> + private IModMetadata GetModFor(IAssetLoader loader) + { + foreach (var pair in this.Loaders) + { + if (pair.Value.Contains(loader)) + return pair.Key; + } + + throw new KeyNotFoundException("This loader isn't associated with a known mod."); + } + + /// <summary>Get the mod which registered an asset editor.</summary> + /// <param name="editor">The asset editor.</param> + /// <exception cref="KeyNotFoundException">The given editor couldn't be matched to a mod.</exception> + private IModMetadata GetModFor(IAssetEditor editor) + { + foreach (var pair in this.Editors) + { + if (pair.Value.Contains(editor)) + return pair.Key; + } + + throw new KeyNotFoundException("This editor isn't associated with a known mod."); + } } } diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index ff0e2de4..18aae05b 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.IO; using System.Linq; using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Reflection; @@ -109,6 +110,34 @@ namespace StardewModdingAPI.Framework.ContentManagers } + /// <summary>Get a copy of the given asset if supported.</summary> + /// <typeparam name="T">The asset type.</typeparam> + /// <param name="asset">The asset to clone.</param> + public T CloneIfPossible<T>(T asset) + { + switch (asset as object) + { + case Texture2D source: + { + int[] pixels = new int[source.Width * source.Height]; + source.GetData(pixels); + + Texture2D clone = new Texture2D(source.GraphicsDevice, source.Width, source.Height); + clone.SetData(pixels); + return (T)(object)clone; + } + + case Dictionary<string, string> source: + return (T)(object)new Dictionary<string, string>(source); + + case Dictionary<int, string> source: + return (T)(object)new Dictionary<int, string>(source); + + default: + return asset; + } + } + /// <summary>Normalise path separators in a file path. For asset keys, see <see cref="AssertAndNormaliseAssetName"/> instead.</summary> /// <param name="path">The file path to normalise.</param> [Pure] diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index cfedb5af..a53840bc 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -161,7 +161,7 @@ namespace StardewModdingAPI.Framework.ContentManagers T data; try { - data = loader.Load<T>(info); + data = this.CloneIfPossible(loader.Load<T>(info)); this.Monitor.Log($"{mod.DisplayName} loaded asset '{info.AssetName}'.", LogLevel.Trace); } catch (Exception ex) diff --git a/src/SMAPI/Framework/ContentManagers/IContentManager.cs b/src/SMAPI/Framework/ContentManagers/IContentManager.cs index aa5be9b6..1eb8b0ac 100644 --- a/src/SMAPI/Framework/ContentManagers/IContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/IContentManager.cs @@ -47,6 +47,11 @@ namespace StardewModdingAPI.Framework.ContentManagers /// <param name="value">The asset value.</param> void Inject<T>(string assetName, T value); + /// <summary>Get a copy of the given asset if supported.</summary> + /// <typeparam name="T">The asset type.</typeparam> + /// <param name="asset">The asset to clone.</param> + T CloneIfPossible<T>(T asset); + /// <summary>Normalise path separators in a file path. For asset keys, see <see cref="AssertAndNormaliseAssetName"/> instead.</summary> /// <param name="path">The file path to normalise.</param> [Pure] diff --git a/src/SMAPI/Framework/CursorPosition.cs b/src/SMAPI/Framework/CursorPosition.cs index db02b3d1..6f716746 100644 --- a/src/SMAPI/Framework/CursorPosition.cs +++ b/src/SMAPI/Framework/CursorPosition.cs @@ -8,6 +8,9 @@ namespace StardewModdingAPI.Framework /********* ** Accessors *********/ + /// <summary>The raw pixel position, not adjusted for the game zoom.</summary> + public Vector2 RawPixels { get; } + /// <summary>The pixel position relative to the top-left corner of the visible screen.</summary> public Vector2 ScreenPixels { get; } @@ -22,11 +25,13 @@ namespace StardewModdingAPI.Framework ** Public methods *********/ /// <summary>Construct an instance.</summary> + /// <param name="rawPixels">The raw pixel position, not adjusted for the game zoom.</param> /// <param name="screenPixels">The pixel position relative to the top-left corner of the visible screen.</param> /// <param name="tile">The tile position relative to the top-left corner of the map.</param> /// <param name="grabTile">The tile position that the game considers under the cursor for purposes of clicking actions.</param> - public CursorPosition(Vector2 screenPixels, Vector2 tile, Vector2 grabTile) + public CursorPosition(Vector2 rawPixels, Vector2 screenPixels, Vector2 tile, Vector2 grabTile) { + this.RawPixels = rawPixels; this.ScreenPixels = screenPixels; this.Tile = tile; this.GrabTile = grabTile; diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs index 84036127..9f67244a 100644 --- a/src/SMAPI/Framework/Events/EventManager.cs +++ b/src/SMAPI/Framework/Events/EventManager.cs @@ -1,4 +1,3 @@ -using System; using System.Diagnostics.CodeAnalysis; using Microsoft.Xna.Framework.Input; using StardewModdingAPI.Events; @@ -10,7 +9,47 @@ namespace StardewModdingAPI.Framework.Events internal class EventManager { /********* - ** Properties + ** Events (new) + *********/ + /**** + ** World + ****/ + /// <summary>Raised after a game location is added or removed.</summary> + public readonly ManagedEvent<WorldLocationListChangedEventArgs> World_LocationListChanged; + + /// <summary>Raised after buildings are added or removed in a location.</summary> + public readonly ManagedEvent<WorldBuildingListChangedEventArgs> World_BuildingListChanged; + + /// <summary>Raised after large terrain features (like bushes) are added or removed in a location.</summary> + public readonly ManagedEvent<WorldLargeTerrainFeatureListChangedEventArgs> World_LargeTerrainFeatureListChanged; + + /// <summary>Raised after NPCs are added or removed in a location.</summary> + public readonly ManagedEvent<WorldNpcListChangedEventArgs> World_NpcListChanged; + + /// <summary>Raised after objects are added or removed in a location.</summary> + public readonly ManagedEvent<WorldObjectListChangedEventArgs> World_ObjectListChanged; + + /// <summary>Raised after terrain features (like floors and trees) are added or removed in a location.</summary> + public readonly ManagedEvent<WorldTerrainFeatureListChangedEventArgs> World_TerrainFeatureListChanged; + + /**** + ** Input + ****/ + /// <summary>Raised after the player presses a button on the keyboard, controller, or mouse.</summary> + public readonly ManagedEvent<InputButtonPressedArgsInput> Input_ButtonPressed; + + /// <summary>Raised after the player released a button on the keyboard, controller, or mouse.</summary> + public readonly ManagedEvent<InputButtonReleasedArgsInput> Input_ButtonReleased; + + /// <summary>Raised after the player moves the in-game cursor.</summary> + public readonly ManagedEvent<InputCursorMovedArgsInput> Input_CursorMoved; + + /// <summary>Raised after the player scrolls the mouse wheel.</summary> + public readonly ManagedEvent<InputMouseWheelScrolledEventArgs> Input_MouseWheelScrolled; + + + /********* + ** Events (old) *********/ /**** ** ContentEvents @@ -22,28 +61,28 @@ namespace StardewModdingAPI.Framework.Events ** ControlEvents ****/ /// <summary>Raised when the <see cref="KeyboardState"/> changes. That happens when the player presses or releases a key.</summary> - public readonly ManagedEvent<EventArgsKeyboardStateChanged> Control_KeyboardChanged; + public readonly ManagedEvent<EventArgsKeyboardStateChanged> Legacy_Control_KeyboardChanged; - /// <summary>Raised when the player presses a keyboard key.</summary> - public readonly ManagedEvent<EventArgsKeyPressed> Control_KeyPressed; + /// <summary>Raised after the player presses a keyboard key.</summary> + public readonly ManagedEvent<EventArgsKeyPressed> Legacy_Control_KeyPressed; - /// <summary>Raised when the player releases a keyboard key.</summary> - public readonly ManagedEvent<EventArgsKeyPressed> Control_KeyReleased; + /// <summary>Raised after the player releases a keyboard key.</summary> + public readonly ManagedEvent<EventArgsKeyPressed> Legacy_Control_KeyReleased; /// <summary>Raised when the <see cref="MouseState"/> changes. That happens when the player moves the mouse, scrolls the mouse wheel, or presses/releases a button.</summary> - public readonly ManagedEvent<EventArgsMouseStateChanged> Control_MouseChanged; + public readonly ManagedEvent<EventArgsMouseStateChanged> Legacy_Control_MouseChanged; /// <summary>The player pressed a controller button. This event isn't raised for trigger buttons.</summary> - public readonly ManagedEvent<EventArgsControllerButtonPressed> Control_ControllerButtonPressed; + public readonly ManagedEvent<EventArgsControllerButtonPressed> Legacy_Control_ControllerButtonPressed; /// <summary>The player released a controller button. This event isn't raised for trigger buttons.</summary> - public readonly ManagedEvent<EventArgsControllerButtonReleased> Control_ControllerButtonReleased; + public readonly ManagedEvent<EventArgsControllerButtonReleased> Legacy_Control_ControllerButtonReleased; /// <summary>The player pressed a controller trigger button.</summary> - public readonly ManagedEvent<EventArgsControllerTriggerPressed> Control_ControllerTriggerPressed; + public readonly ManagedEvent<EventArgsControllerTriggerPressed> Legacy_Control_ControllerTriggerPressed; /// <summary>The player released a controller trigger button.</summary> - public readonly ManagedEvent<EventArgsControllerTriggerReleased> Control_ControllerTriggerReleased; + public readonly ManagedEvent<EventArgsControllerTriggerReleased> Legacy_Control_ControllerTriggerReleased; /**** ** GameEvents @@ -99,23 +138,23 @@ namespace StardewModdingAPI.Framework.Events /**** ** InputEvents ****/ - /// <summary>Raised when the player presses a button on the keyboard, controller, or mouse.</summary> - public readonly ManagedEvent<EventArgsInput> Input_ButtonPressed; + /// <summary>Raised after the player presses a button on the keyboard, controller, or mouse.</summary> + public readonly ManagedEvent<EventArgsInput> Legacy_Input_ButtonPressed; - /// <summary>Raised when the player releases a keyboard key on the keyboard, controller, or mouse.</summary> - public readonly ManagedEvent<EventArgsInput> Input_ButtonReleased; + /// <summary>Raised after the player releases a keyboard key on the keyboard, controller, or mouse.</summary> + public readonly ManagedEvent<EventArgsInput> Legacy_Input_ButtonReleased; /**** ** LocationEvents ****/ /// <summary>Raised after a game location is added or removed.</summary> - public readonly ManagedEvent<EventArgsLocationsChanged> Location_LocationsChanged; + public readonly ManagedEvent<EventArgsLocationsChanged> Legacy_Location_LocationsChanged; /// <summary>Raised after buildings are added or removed in a location.</summary> - public readonly ManagedEvent<EventArgsLocationBuildingsChanged> Location_BuildingsChanged; + public readonly ManagedEvent<EventArgsLocationBuildingsChanged> Legacy_Location_BuildingsChanged; /// <summary>Raised after objects are added or removed in a location.</summary> - public readonly ManagedEvent<EventArgsLocationObjectsChanged> Location_ObjectsChanged; + public readonly ManagedEvent<EventArgsLocationObjectsChanged> Legacy_Location_ObjectsChanged; /**** ** MenuEvents @@ -209,17 +248,30 @@ namespace StardewModdingAPI.Framework.Events ManagedEvent<TEventArgs> ManageEventOf<TEventArgs>(string typeName, string eventName) => new ManagedEvent<TEventArgs>($"{typeName}.{eventName}", monitor, modRegistry); ManagedEvent ManageEvent(string typeName, string eventName) => new ManagedEvent($"{typeName}.{eventName}", monitor, modRegistry); - // init events + // init events (new) + this.Input_ButtonPressed = ManageEventOf<InputButtonPressedArgsInput>(nameof(IModEvents.Input), nameof(IInputEvents.ButtonPressed)); + this.Input_ButtonReleased = ManageEventOf<InputButtonReleasedArgsInput>(nameof(IModEvents.Input), nameof(IInputEvents.ButtonReleased)); + this.Input_CursorMoved = ManageEventOf<InputCursorMovedArgsInput>(nameof(IModEvents.Input), nameof(IInputEvents.CursorMoved)); + this.Input_MouseWheelScrolled = ManageEventOf<InputMouseWheelScrolledEventArgs>(nameof(IModEvents.Input), nameof(IInputEvents.MouseWheelScrolled)); + + this.World_BuildingListChanged = ManageEventOf<WorldBuildingListChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.LocationListChanged)); + this.World_LargeTerrainFeatureListChanged = ManageEventOf<WorldLargeTerrainFeatureListChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.LargeTerrainFeatureListChanged)); + this.World_LocationListChanged = ManageEventOf<WorldLocationListChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.BuildingListChanged)); + this.World_NpcListChanged = ManageEventOf<WorldNpcListChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.NpcListChanged)); + this.World_ObjectListChanged = ManageEventOf<WorldObjectListChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.ObjectListChanged)); + this.World_TerrainFeatureListChanged = ManageEventOf<WorldTerrainFeatureListChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.TerrainFeatureListChanged)); + + // init events (old) this.Content_LocaleChanged = ManageEventOf<EventArgsValueChanged<string>>(nameof(ContentEvents), nameof(ContentEvents.AfterLocaleChanged)); - this.Control_ControllerButtonPressed = ManageEventOf<EventArgsControllerButtonPressed>(nameof(ControlEvents), nameof(ControlEvents.ControllerButtonPressed)); - this.Control_ControllerButtonReleased = ManageEventOf<EventArgsControllerButtonReleased>(nameof(ControlEvents), nameof(ControlEvents.ControllerButtonReleased)); - this.Control_ControllerTriggerPressed = ManageEventOf<EventArgsControllerTriggerPressed>(nameof(ControlEvents), nameof(ControlEvents.ControllerTriggerPressed)); - this.Control_ControllerTriggerReleased = ManageEventOf<EventArgsControllerTriggerReleased>(nameof(ControlEvents), nameof(ControlEvents.ControllerTriggerReleased)); - this.Control_KeyboardChanged = ManageEventOf<EventArgsKeyboardStateChanged>(nameof(ControlEvents), nameof(ControlEvents.KeyboardChanged)); - this.Control_KeyPressed = ManageEventOf<EventArgsKeyPressed>(nameof(ControlEvents), nameof(ControlEvents.KeyPressed)); - this.Control_KeyReleased = ManageEventOf<EventArgsKeyPressed>(nameof(ControlEvents), nameof(ControlEvents.KeyReleased)); - this.Control_MouseChanged = ManageEventOf<EventArgsMouseStateChanged>(nameof(ControlEvents), nameof(ControlEvents.MouseChanged)); + this.Legacy_Control_ControllerButtonPressed = ManageEventOf<EventArgsControllerButtonPressed>(nameof(ControlEvents), nameof(ControlEvents.ControllerButtonPressed)); + this.Legacy_Control_ControllerButtonReleased = ManageEventOf<EventArgsControllerButtonReleased>(nameof(ControlEvents), nameof(ControlEvents.ControllerButtonReleased)); + this.Legacy_Control_ControllerTriggerPressed = ManageEventOf<EventArgsControllerTriggerPressed>(nameof(ControlEvents), nameof(ControlEvents.ControllerTriggerPressed)); + this.Legacy_Control_ControllerTriggerReleased = ManageEventOf<EventArgsControllerTriggerReleased>(nameof(ControlEvents), nameof(ControlEvents.ControllerTriggerReleased)); + this.Legacy_Control_KeyboardChanged = ManageEventOf<EventArgsKeyboardStateChanged>(nameof(ControlEvents), nameof(ControlEvents.KeyboardChanged)); + this.Legacy_Control_KeyPressed = ManageEventOf<EventArgsKeyPressed>(nameof(ControlEvents), nameof(ControlEvents.KeyPressed)); + this.Legacy_Control_KeyReleased = ManageEventOf<EventArgsKeyPressed>(nameof(ControlEvents), nameof(ControlEvents.KeyReleased)); + this.Legacy_Control_MouseChanged = ManageEventOf<EventArgsMouseStateChanged>(nameof(ControlEvents), nameof(ControlEvents.MouseChanged)); this.Game_FirstUpdateTick = ManageEvent(nameof(GameEvents), nameof(GameEvents.FirstUpdateTick)); this.Game_UpdateTick = ManageEvent(nameof(GameEvents), nameof(GameEvents.UpdateTick)); @@ -238,12 +290,12 @@ namespace StardewModdingAPI.Framework.Events this.Graphics_OnPreRenderGuiEvent = ManageEvent(nameof(GraphicsEvents), nameof(GraphicsEvents.OnPreRenderGuiEvent)); this.Graphics_OnPostRenderGuiEvent = ManageEvent(nameof(GraphicsEvents), nameof(GraphicsEvents.OnPostRenderGuiEvent)); - this.Input_ButtonPressed = ManageEventOf<EventArgsInput>(nameof(InputEvents), nameof(InputEvents.ButtonPressed)); - this.Input_ButtonReleased = ManageEventOf<EventArgsInput>(nameof(InputEvents), nameof(InputEvents.ButtonReleased)); + this.Legacy_Input_ButtonPressed = ManageEventOf<EventArgsInput>(nameof(InputEvents), nameof(InputEvents.ButtonPressed)); + this.Legacy_Input_ButtonReleased = ManageEventOf<EventArgsInput>(nameof(InputEvents), nameof(InputEvents.ButtonReleased)); - this.Location_LocationsChanged = ManageEventOf<EventArgsLocationsChanged>(nameof(LocationEvents), nameof(LocationEvents.LocationsChanged)); - this.Location_BuildingsChanged = ManageEventOf<EventArgsLocationBuildingsChanged>(nameof(LocationEvents), nameof(LocationEvents.BuildingsChanged)); - this.Location_ObjectsChanged = ManageEventOf<EventArgsLocationObjectsChanged>(nameof(LocationEvents), nameof(LocationEvents.ObjectsChanged)); + this.Legacy_Location_LocationsChanged = ManageEventOf<EventArgsLocationsChanged>(nameof(LocationEvents), nameof(LocationEvents.LocationsChanged)); + this.Legacy_Location_BuildingsChanged = ManageEventOf<EventArgsLocationBuildingsChanged>(nameof(LocationEvents), nameof(LocationEvents.BuildingsChanged)); + this.Legacy_Location_ObjectsChanged = ManageEventOf<EventArgsLocationObjectsChanged>(nameof(LocationEvents), nameof(LocationEvents.ObjectsChanged)); this.Menu_Changed = ManageEventOf<EventArgsClickableMenuChanged>(nameof(MenuEvents), nameof(MenuEvents.MenuChanged)); this.Menu_Closed = ManageEventOf<EventArgsClickableMenuClosed>(nameof(MenuEvents), nameof(MenuEvents.MenuClosed)); diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs index e54a4fd3..c1ebf6c7 100644 --- a/src/SMAPI/Framework/Events/ManagedEvent.cs +++ b/src/SMAPI/Framework/Events/ManagedEvent.cs @@ -28,8 +28,16 @@ namespace StardewModdingAPI.Framework.Events /// <param name="handler">The event handler.</param> public void Add(EventHandler<TEventArgs> handler) { + this.Add(handler, this.ModRegistry.GetFromStack()); + } + + /// <summary>Add an event handler.</summary> + /// <param name="handler">The event handler.</param> + /// <param name="mod">The mod which added the event handler.</param> + public void Add(EventHandler<TEventArgs> handler, IModMetadata mod) + { this.Event += handler; - this.AddTracking(handler, this.Event?.GetInvocationList().Cast<EventHandler<TEventArgs>>()); + this.AddTracking(mod, handler, this.Event?.GetInvocationList().Cast<EventHandler<TEventArgs>>()); } /// <summary>Remove an event handler.</summary> @@ -85,8 +93,16 @@ namespace StardewModdingAPI.Framework.Events /// <param name="handler">The event handler.</param> public void Add(EventHandler handler) { + this.Add(handler, this.ModRegistry.GetFromStack()); + } + + /// <summary>Add an event handler.</summary> + /// <param name="handler">The event handler.</param> + /// <param name="mod">The mod which added the event handler.</param> + public void Add(EventHandler handler, IModMetadata mod) + { this.Event += handler; - this.AddTracking(handler, this.Event?.GetInvocationList().Cast<EventHandler>()); + this.AddTracking(mod, handler, this.Event?.GetInvocationList().Cast<EventHandler>()); } /// <summary>Remove an event handler.</summary> diff --git a/src/SMAPI/Framework/Events/ManagedEventBase.cs b/src/SMAPI/Framework/Events/ManagedEventBase.cs index 7e42d613..f3a278dc 100644 --- a/src/SMAPI/Framework/Events/ManagedEventBase.cs +++ b/src/SMAPI/Framework/Events/ManagedEventBase.cs @@ -17,7 +17,7 @@ namespace StardewModdingAPI.Framework.Events private readonly IMonitor Monitor; /// <summary>The mod registry with which to identify mods.</summary> - private readonly ModRegistry ModRegistry; + protected readonly ModRegistry ModRegistry; /// <summary>The display names for the mods which added each delegate.</summary> private readonly IDictionary<TEventHandler, IModMetadata> SourceMods = new Dictionary<TEventHandler, IModMetadata>(); @@ -50,11 +50,12 @@ namespace StardewModdingAPI.Framework.Events } /// <summary>Track an event handler.</summary> + /// <param name="mod">The mod which added the handler.</param> /// <param name="handler">The event handler.</param> /// <param name="invocationList">The updated event invocation list.</param> - protected void AddTracking(TEventHandler handler, IEnumerable<TEventHandler> invocationList) + protected void AddTracking(IModMetadata mod, TEventHandler handler, IEnumerable<TEventHandler> invocationList) { - this.SourceMods[handler] = this.ModRegistry.GetFromStack(); + this.SourceMods[handler] = mod; this.CachedInvocationList = invocationList?.ToArray() ?? new TEventHandler[0]; } @@ -64,7 +65,7 @@ namespace StardewModdingAPI.Framework.Events protected void RemoveTracking(TEventHandler handler, IEnumerable<TEventHandler> invocationList) { this.CachedInvocationList = invocationList?.ToArray() ?? new TEventHandler[0]; - if(!this.CachedInvocationList.Contains(handler)) // don't remove if there's still a reference to the removed handler (e.g. it was added twice and removed once) + if (!this.CachedInvocationList.Contains(handler)) // don't remove if there's still a reference to the removed handler (e.g. it was added twice and removed once) this.SourceMods.Remove(handler); } diff --git a/src/SMAPI/Framework/Events/ModEvents.cs b/src/SMAPI/Framework/Events/ModEvents.cs new file mode 100644 index 00000000..90853141 --- /dev/null +++ b/src/SMAPI/Framework/Events/ModEvents.cs @@ -0,0 +1,30 @@ +using StardewModdingAPI.Events; + +namespace StardewModdingAPI.Framework.Events +{ + /// <summary>Manages access to events raised by SMAPI.</summary> + internal class ModEvents : IModEvents + { + /********* + ** Accessors + *********/ + /// <summary>Events raised when the player provides input using a controller, keyboard, or mouse.</summary> + public IInputEvents Input { get; } + + /// <summary>Events raised when something changes in the world.</summary> + public IWorldEvents World { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="mod">The mod which uses this instance.</param> + /// <param name="eventManager">The underlying event manager.</param> + public ModEvents(IModMetadata mod, EventManager eventManager) + { + this.Input = new ModInputEvents(mod, eventManager); + this.World = new ModWorldEvents(mod, eventManager); + } + } +} diff --git a/src/SMAPI/Framework/Events/ModEventsBase.cs b/src/SMAPI/Framework/Events/ModEventsBase.cs new file mode 100644 index 00000000..545c58a8 --- /dev/null +++ b/src/SMAPI/Framework/Events/ModEventsBase.cs @@ -0,0 +1,28 @@ +namespace StardewModdingAPI.Framework.Events +{ + /// <summary>An internal base class for event API classes.</summary> + internal abstract class ModEventsBase + { + /********* + ** Properties + *********/ + /// <summary>The underlying event manager.</summary> + protected readonly EventManager EventManager; + + /// <summary>The mod which uses this instance.</summary> + protected readonly IModMetadata Mod; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="mod">The mod which uses this instance.</param> + /// <param name="eventManager">The underlying event manager.</param> + internal ModEventsBase(IModMetadata mod, EventManager eventManager) + { + this.Mod = mod; + this.EventManager = eventManager; + } + } +} diff --git a/src/SMAPI/Framework/Events/ModInputEvents.cs b/src/SMAPI/Framework/Events/ModInputEvents.cs new file mode 100644 index 00000000..387ea87a --- /dev/null +++ b/src/SMAPI/Framework/Events/ModInputEvents.cs @@ -0,0 +1,50 @@ +using System; +using StardewModdingAPI.Events; + +namespace StardewModdingAPI.Framework.Events +{ + /// <summary>Events raised when the player provides input using a controller, keyboard, or mouse.</summary> + internal class ModInputEvents : ModEventsBase, IInputEvents + { + /********* + ** Accessors + *********/ + /// <summary>Raised after the player presses a button on the keyboard, controller, or mouse.</summary> + public event EventHandler<InputButtonPressedArgsInput> ButtonPressed + { + add => this.EventManager.Input_ButtonPressed.Add(value); + remove => this.EventManager.Input_ButtonPressed.Remove(value); + } + + /// <summary>Raised after the player releases a button on the keyboard, controller, or mouse.</summary> + public event EventHandler<InputButtonReleasedArgsInput> ButtonReleased + { + add => this.EventManager.Input_ButtonReleased.Add(value); + remove => this.EventManager.Input_ButtonReleased.Remove(value); + } + + /// <summary>Raised after the player moves the in-game cursor.</summary> + public event EventHandler<InputCursorMovedArgsInput> CursorMoved + { + add => this.EventManager.Input_CursorMoved.Add(value); + remove => this.EventManager.Input_CursorMoved.Remove(value); + } + + /// <summary>Raised after the player scrolls the mouse wheel.</summary> + public event EventHandler<InputMouseWheelScrolledEventArgs> MouseWheelScrolled + { + add => this.EventManager.Input_MouseWheelScrolled.Add(value); + remove => this.EventManager.Input_MouseWheelScrolled.Remove(value); + } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="mod">The mod which uses this instance.</param> + /// <param name="eventManager">The underlying event manager.</param> + internal ModInputEvents(IModMetadata mod, EventManager eventManager) + : base(mod, eventManager) { } + } +} diff --git a/src/SMAPI/Framework/Events/ModWorldEvents.cs b/src/SMAPI/Framework/Events/ModWorldEvents.cs new file mode 100644 index 00000000..e1a53e0c --- /dev/null +++ b/src/SMAPI/Framework/Events/ModWorldEvents.cs @@ -0,0 +1,64 @@ +using System; +using StardewModdingAPI.Events; + +namespace StardewModdingAPI.Framework.Events +{ + /// <summary>Events raised when something changes in the world.</summary> + internal class ModWorldEvents : ModEventsBase, IWorldEvents + { + /********* + ** Accessors + *********/ + /// <summary>Raised after a game location is added or removed.</summary> + public event EventHandler<WorldLocationListChangedEventArgs> LocationListChanged + { + add => this.EventManager.World_LocationListChanged.Add(value, this.Mod); + remove => this.EventManager.World_LocationListChanged.Remove(value); + } + + /// <summary>Raised after buildings are added or removed in a location.</summary> + public event EventHandler<WorldBuildingListChangedEventArgs> BuildingListChanged + { + add => this.EventManager.World_BuildingListChanged.Add(value, this.Mod); + remove => this.EventManager.World_BuildingListChanged.Remove(value); + } + + /// <summary>Raised after large terrain features (like bushes) are added or removed in a location.</summary> + public event EventHandler<WorldLargeTerrainFeatureListChangedEventArgs> LargeTerrainFeatureListChanged + { + add => this.EventManager.World_LargeTerrainFeatureListChanged.Add(value, this.Mod); + remove => this.EventManager.World_LargeTerrainFeatureListChanged.Remove(value); + } + + /// <summary>Raised after NPCs are added or removed in a location.</summary> + public event EventHandler<WorldNpcListChangedEventArgs> NpcListChanged + { + add => this.EventManager.World_NpcListChanged.Add(value); + remove => this.EventManager.World_NpcListChanged.Remove(value); + } + + /// <summary>Raised after objects are added or removed in a location.</summary> + public event EventHandler<WorldObjectListChangedEventArgs> ObjectListChanged + { + add => this.EventManager.World_ObjectListChanged.Add(value); + remove => this.EventManager.World_ObjectListChanged.Remove(value); + } + + /// <summary>Raised after terrain features (like floors and trees) are added or removed in a location.</summary> + public event EventHandler<WorldTerrainFeatureListChangedEventArgs> TerrainFeatureListChanged + { + add => this.EventManager.World_TerrainFeatureListChanged.Add(value); + remove => this.EventManager.World_TerrainFeatureListChanged.Remove(value); + } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="mod">The mod which uses this instance.</param> + /// <param name="eventManager">The underlying event manager.</param> + internal ModWorldEvents(IModMetadata mod, EventManager eventManager) + : base(mod, eventManager) { } + } +} diff --git a/src/SMAPI/Framework/Input/SInputState.cs b/src/SMAPI/Framework/Input/SInputState.cs index 27e40ab4..44fd0618 100644 --- a/src/SMAPI/Framework/Input/SInputState.cs +++ b/src/SMAPI/Framework/Input/SInputState.cs @@ -8,7 +8,7 @@ using StardewValley; #pragma warning disable 809 // obsolete override of non-obsolete method (this is deliberate) namespace StardewModdingAPI.Framework.Input { - /// <summary>A summary of input changes during an update frame.</summary> + /// <summary>Manages the game's input state.</summary> internal sealed class SInputState : InputState { /********* @@ -17,6 +17,9 @@ namespace StardewModdingAPI.Framework.Input /// <summary>The maximum amount of direction to ignore for the left thumbstick.</summary> private const float LeftThumbstickDeadZone = 0.2f; + /// <summary>The cursor position on the screen adjusted for the zoom level.</summary> + private CursorPosition CursorPositionImpl; + /********* ** Accessors @@ -39,8 +42,8 @@ namespace StardewModdingAPI.Framework.Input /// <summary>A derivative of <see cref="RealMouse"/> which suppresses the buttons in <see cref="SuppressButtons"/>.</summary> public MouseState SuppressedMouse { get; private set; } - /// <summary>The mouse position on the screen adjusted for the zoom level.</summary> - public Point MousePosition { get; private set; } + /// <summary>The 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, InputStatus> ActiveButtons { get; private set; } = new Dictionary<SButton, InputStatus>(); @@ -61,7 +64,7 @@ namespace StardewModdingAPI.Framework.Input RealController = this.RealController, RealKeyboard = this.RealKeyboard, RealMouse = this.RealMouse, - MousePosition = this.MousePosition + CursorPositionImpl = this.CursorPositionImpl }; } @@ -78,15 +81,16 @@ namespace StardewModdingAPI.Framework.Input GamePadState realController = GamePad.GetState(PlayerIndex.One); KeyboardState realKeyboard = Keyboard.GetState(); MouseState realMouse = Mouse.GetState(); - Point mousePosition = new Point((int)(this.RealMouse.X * (1.0 / Game1.options.zoomLevel)), (int)(this.RealMouse.Y * (1.0 / Game1.options.zoomLevel))); // derived from Game1::getMouseX var activeButtons = this.DeriveStatuses(this.ActiveButtons, realKeyboard, realMouse, realController); + Vector2 cursorRawPixelPos = new Vector2(this.RealMouse.X, this.RealMouse.Y); // update real states this.ActiveButtons = activeButtons; this.RealController = realController; this.RealKeyboard = realKeyboard; this.RealMouse = realMouse; - this.MousePosition = mousePosition; + if (this.CursorPositionImpl?.RawPixels != cursorRawPixelPos) + this.CursorPositionImpl = this.GetCursorPosition(cursorRawPixelPos); // update suppressed states this.SuppressButtons.RemoveWhere(p => !this.GetStatus(activeButtons, p).IsDown()); @@ -157,6 +161,18 @@ namespace StardewModdingAPI.Framework.Input /********* ** Private methods *********/ + /// <summary>Get the current cursor position.</summary> + /// <remarks>The raw pixel position from the mouse state.</remarks> + private CursorPosition GetCursorPosition(Vector2 rawPixelPos) + { + Vector2 screenPixels = new Vector2((int)(rawPixelPos.X * (1.0 / Game1.options.zoomLevel)), (int)(rawPixelPos.Y * (1.0 / Game1.options.zoomLevel))); // derived from Game1::getMouseX + Vector2 tile = new Vector2((int)((Game1.viewport.X + screenPixels.X) / Game1.tileSize), (int)((Game1.viewport.Y + screenPixels.Y) / Game1.tileSize)); + Vector2 grabTile = (Game1.mouseCursorTransparency > 0 && Utility.tileWithinRadiusOfPlayer((int)tile.X, (int)tile.Y, 1, Game1.player)) // derived from Game1.pressActionButton + ? tile + : Game1.player.GetGrabTile(); + return new CursorPosition(rawPixelPos, screenPixels, tile, grabTile); + } + /// <summary>Whether input should be suppressed in the current context.</summary> private bool ShouldSuppressNow() { diff --git a/src/SMAPI/Framework/ModHelpers/InputHelper.cs b/src/SMAPI/Framework/ModHelpers/InputHelper.cs new file mode 100644 index 00000000..f4cd12b6 --- /dev/null +++ b/src/SMAPI/Framework/ModHelpers/InputHelper.cs @@ -0,0 +1,54 @@ +using StardewModdingAPI.Framework.Input; + +namespace StardewModdingAPI.Framework.ModHelpers +{ + /// <summary>Provides an API for checking and changing input state.</summary> + internal class InputHelper : BaseHelper, IInputHelper + { + /********* + ** Accessors + *********/ + /// <summary>Manages the game's input state.</summary> + private readonly SInputState InputState; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="modID">The unique ID of the relevant mod.</param> + /// <param name="inputState">Manages the game's input state.</param> + public InputHelper(string modID, SInputState inputState) + : base(modID) + { + this.InputState = inputState; + } + + /// <summary>Get the current cursor position.</summary> + public ICursorPosition GetCursorPosition() + { + return this.InputState.CursorPosition; + } + + /// <summary>Get whether a button is currently pressed.</summary> + /// <param name="button">The button.</param> + public bool IsDown(SButton button) + { + return this.InputState.IsDown(button); + } + + /// <summary>Get whether a button is currently suppressed, so the game won't see it.</summary> + /// <param name="button">The button.</param> + public bool IsSuppressed(SButton button) + { + return this.InputState.SuppressButtons.Contains(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); + } + } +} diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index 61d2075c..e8726938 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using StardewModdingAPI.Events; +using StardewModdingAPI.Framework.Input; using StardewModdingAPI.Framework.Models; using StardewModdingAPI.Framework.Serialisation; using StardewModdingAPI.Toolkit.Utilities; @@ -33,9 +35,15 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <summary>The full path to the mod's folder.</summary> public string DirectoryPath { get; } + /// <summary>Manages access to events raised by SMAPI, which let your mod react when something happens in the game.</summary> + public IModEvents Events { get; } + /// <summary>An API for loading content assets.</summary> public IContentHelper Content { get; } + /// <summary>An API for checking and changing input state.</summary> + public IInputHelper Input { get; } + /// <summary>An API for accessing private game code.</summary> public IReflectionHelper Reflection { get; } @@ -59,6 +67,8 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <param name="modID">The mod's unique ID.</param> /// <param name="modDirectory">The full path to the mod's folder.</param> /// <param name="jsonHelper">Encapsulate SMAPI's JSON parsing.</param> + /// <param name="inputState">Manages the game's input state.</param> + /// <param name="events">Manages access to events raised by SMAPI.</param> /// <param name="contentHelper">An API for loading content assets.</param> /// <param name="commandHelper">An API for managing console commands.</param> /// <param name="modRegistry">an API for fetching metadata about loaded mods.</param> @@ -70,7 +80,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <param name="deprecationManager">Manages deprecation warnings.</param> /// <exception cref="ArgumentNullException">An argument is null or empty.</exception> /// <exception cref="InvalidOperationException">The <paramref name="modDirectory"/> path does not exist on disk.</exception> - public ModHelper(string modID, string modDirectory, JsonHelper jsonHelper, IContentHelper contentHelper, ICommandHelper commandHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper, IEnumerable<IContentPack> contentPacks, Func<string, IManifest, IContentPack> createContentPack, DeprecationManager deprecationManager) + public ModHelper(string modID, string modDirectory, JsonHelper jsonHelper, SInputState inputState, IModEvents events, IContentHelper contentHelper, ICommandHelper commandHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper, IEnumerable<IContentPack> contentPacks, Func<string, IManifest, IContentPack> createContentPack, DeprecationManager deprecationManager) : base(modID) { // validate directory @@ -83,6 +93,7 @@ namespace StardewModdingAPI.Framework.ModHelpers this.DirectoryPath = modDirectory; this.JsonHelper = jsonHelper ?? throw new ArgumentNullException(nameof(jsonHelper)); this.Content = contentHelper ?? throw new ArgumentNullException(nameof(contentHelper)); + this.Input = new InputHelper(modID, inputState); this.ModRegistry = modRegistry ?? throw new ArgumentNullException(nameof(modRegistry)); this.ConsoleCommands = commandHelper ?? throw new ArgumentNullException(nameof(commandHelper)); this.Reflection = reflectionHelper ?? throw new ArgumentNullException(nameof(reflectionHelper)); @@ -91,6 +102,7 @@ namespace StardewModdingAPI.Framework.ModHelpers this.ContentPacks = contentPacks.ToArray(); this.CreateContentPack = createContentPack; this.DeprecationManager = deprecationManager; + this.Events = events; } /**** diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 369f1f40..a4d149f3 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -18,11 +18,14 @@ using StardewModdingAPI.Framework.StateTracking.FieldWatchers; using StardewModdingAPI.Framework.Utilities; using StardewValley; using StardewValley.BellsAndWhistles; +using StardewValley.Buildings; using StardewValley.Locations; using StardewValley.Menus; +using StardewValley.TerrainFeatures; using StardewValley.Tools; using xTile.Dimensions; using xTile.Layers; +using Object = StardewValley.Object; namespace StardewModdingAPI.Framework { @@ -51,9 +54,6 @@ namespace StardewModdingAPI.Framework /// <summary>Manages SMAPI events for mods.</summary> private readonly EventManager Events; - /// <summary>Manages input visible to the game.</summary> - private SInputState Input => (SInputState)Game1.input; - /// <summary>The maximum number of consecutive attempts SMAPI should make to recover from a draw error.</summary> private readonly Countdown DrawCrashTimer = new Countdown(60); // 60 ticks = roughly one second @@ -73,6 +73,15 @@ namespace StardewModdingAPI.Framework /// <summary>Whether the game is creating the save file and SMAPI has already raised <see cref="SaveEvents.BeforeCreate"/>.</summary> private bool IsBetweenCreateEvents; + /// <summary>A callback to invoke after the game finishes initialising.</summary> + private readonly Action OnGameInitialised; + + /// <summary>A callback to invoke when the game exits.</summary> + private readonly Action OnGameExiting; + + /// <summary>Simplifies access to private game code.</summary> + private readonly Reflector Reflection; + /**** ** Game state ****/ @@ -80,41 +89,44 @@ namespace StardewModdingAPI.Framework private readonly List<IWatcher> Watchers = new List<IWatcher>(); /// <summary>Tracks changes to the window size.</summary> - private readonly IValueWatcher<Point> WindowSizeWatcher; + private IValueWatcher<Point> WindowSizeWatcher; /// <summary>Tracks changes to the current player.</summary> private PlayerTracker CurrentPlayerTracker; /// <summary>Tracks changes to the time of day (in 24-hour military format).</summary> - private readonly IValueWatcher<int> TimeWatcher; + private IValueWatcher<int> TimeWatcher; /// <summary>Tracks changes to the save ID.</summary> - private readonly IValueWatcher<ulong> SaveIdWatcher; + private IValueWatcher<ulong> SaveIdWatcher; /// <summary>Tracks changes to the game's locations.</summary> - private readonly WorldLocationsTracker LocationsWatcher; + private WorldLocationsTracker LocationsWatcher; /// <summary>Tracks changes to <see cref="Game1.activeClickableMenu"/>.</summary> - private readonly IValueWatcher<IClickableMenu> ActiveMenuWatcher; + private IValueWatcher<IClickableMenu> ActiveMenuWatcher; + + /// <summary>Tracks changes to the cursor position.</summary> + private IValueWatcher<Vector2> CursorWatcher; + + /// <summary>Tracks changes to the mouse wheel scroll.</summary> + private IValueWatcher<int> MouseWheelScrollWatcher; /// <summary>The previous content locale.</summary> private LocalizedContentManager.LanguageCode? PreviousLocale; + /// <summary>The previous cursor position.</summary> + private ICursorPosition PreviousCursorPosition; + /// <summary>An index incremented on every tick and reset every 60th tick (0–59).</summary> private int CurrentUpdateTick; + /// <summary>Whether post-game-startup initialisation has been performed.</summary> + private bool IsInitialised; + /// <summary>Whether this is the very first update tick since the game started.</summary> private bool FirstUpdate; - /// <summary>A callback to invoke after the game finishes initialising.</summary> - private readonly Action OnGameInitialised; - - /// <summary>A callback to invoke when the game exits.</summary> - private readonly Action OnGameExiting; - - /// <summary>Simplifies access to private game code.</summary> - private readonly Reflector Reflection; - /// <summary>Whether the next content manager requested by the game will be for <see cref="Game1.content"/>.</summary> private bool NextContentManagerIsMain; @@ -125,6 +137,9 @@ namespace StardewModdingAPI.Framework /// <summary>SMAPI's content manager.</summary> public ContentCoordinator ContentCore { get; private set; } + /// <summary>Manages input visible to the game.</summary> + public SInputState Input => (SInputState)Game1.input; + /// <summary>The game's core multiplayer utility.</summary> public SMultiplayer Multiplayer => (SMultiplayer)Game1.multiplayer; @@ -160,8 +175,19 @@ namespace StardewModdingAPI.Framework Game1.input = new SInputState(); Game1.multiplayer = new SMultiplayer(monitor, eventManager); - // init watchers + // init observables Game1.locations = new ObservableCollection<GameLocation>(); + } + + /// <summary>Initialise just before the game's first update tick.</summary> + private void InitialiseAfterGameStarted() + { + // set initial state + this.Input.TrueUpdate(); + + // init watchers + this.CursorWatcher = WatcherFactory.ForEquatable(() => this.Input.CursorPosition.ScreenPixels); + this.MouseWheelScrollWatcher = WatcherFactory.ForEquatable(() => this.Input.RealMouse.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); @@ -169,12 +195,17 @@ namespace StardewModdingAPI.Framework this.LocationsWatcher = new WorldLocationsTracker((ObservableCollection<GameLocation>)Game1.locations); this.Watchers.AddRange(new IWatcher[] { + this.CursorWatcher, + this.MouseWheelScrollWatcher, this.SaveIdWatcher, this.WindowSizeWatcher, this.TimeWatcher, this.ActiveMenuWatcher, this.LocationsWatcher }); + + // raise callback + this.OnGameInitialised(); } /// <summary>Perform cleanup logic when the game exits.</summary> @@ -223,19 +254,24 @@ namespace StardewModdingAPI.Framework try { /********* - ** Update input + ** Special cases *********/ - // This should *always* run, even when suppressing mod events, since the game uses - // this too. For example, doing this after mod event suppression would prevent the - // user from doing anything on the overnight shipping screen. - SInputState previousInputState = this.Input.Clone(); - SInputState inputState = this.Input; - if (this.IsActive) - inputState.TrueUpdate(); + // Perform first-tick initialisation. + if (!this.IsInitialised) + { + this.IsInitialised = true; + this.InitialiseAfterGameStarted(); + } - /********* - ** Load game synchronously - *********/ + // Abort if SMAPI is exiting. + if (this.Monitor.IsExiting) + { + this.Monitor.Log("SMAPI shutting down: aborting update.", LogLevel.Trace); + return; + } + + // Load saves synchronously to avoid issues due to mod events triggering + // concurrently with game code. if (Game1.gameMode == Game1.loadingMode) { this.Monitor.Log("Running game loader...", LogLevel.Trace); @@ -247,16 +283,6 @@ namespace StardewModdingAPI.Framework this.Monitor.Log("Game loader OK.", LogLevel.Trace); } - /********* - ** Skip conditions - *********/ - // SMAPI exiting, stop processing game updates - if (this.Monitor.IsExiting) - { - this.Monitor.Log("SMAPI shutting down: aborting update.", LogLevel.Trace); - return; - } - // While a background task is in progress, the game may make changes to the game // state while mods are running their code. This is risky, because data changes can // conflict (e.g. collection changed during enumeration errors) and data may change @@ -274,6 +300,17 @@ namespace StardewModdingAPI.Framework } /********* + ** Update input + *********/ + // This should *always* run, even when suppressing mod events, since the game uses + // this too. For example, doing this after mod event suppression would prevent the + // user from doing anything on the overnight shipping screen. + SInputState previousInputState = this.Input.Clone(); + SInputState inputState = this.Input; + if (this.IsActive) + inputState.TrueUpdate(); + + /********* ** Save events + suppress events during save *********/ // While the game is writing to the save file in the background, mods can unexpectedly @@ -321,12 +358,6 @@ namespace StardewModdingAPI.Framework } /********* - ** Notify SMAPI that game is initialised - *********/ - if (this.FirstUpdate) - this.OnGameInitialised(); - - /********* ** Update context *********/ if (Context.IsWorldReady && !Context.IsSaveLoaded) @@ -436,19 +467,27 @@ namespace StardewModdingAPI.Framework bool isChatInput = Game1.IsChatting || (Context.IsMultiplayer && Context.IsWorldReady && Game1.activeClickableMenu == null && Game1.currentMinigame == null && inputState.IsAnyDown(Game1.options.chatButton)); if (!isChatInput) { - // get cursor position - ICursorPosition cursor; + ICursorPosition cursor = this.Input.CursorPosition; + + // raise cursor moved event + if (this.CursorWatcher.IsChanged && this.PreviousCursorPosition != null) { - // cursor position - Vector2 screenPixels = new Vector2(Game1.getMouseX(), Game1.getMouseY()); - Vector2 tile = new Vector2((int)((Game1.viewport.X + screenPixels.X) / Game1.tileSize), (int)((Game1.viewport.Y + screenPixels.Y) / Game1.tileSize)); - Vector2 grabTile = (Game1.mouseCursorTransparency > 0 && Utility.tileWithinRadiusOfPlayer((int)tile.X, (int)tile.Y, 1, Game1.player)) // derived from Game1.pressActionButton - ? tile - : Game1.player.GetGrabTile(); - cursor = new CursorPosition(screenPixels, tile, grabTile); + this.CursorWatcher.Reset(); + this.Events.Input_CursorMoved.Raise(new InputCursorMovedArgsInput(this.PreviousCursorPosition, cursor)); } + this.PreviousCursorPosition = cursor; - // raise input events + // raise mouse wheel scrolled + if (this.MouseWheelScrollWatcher.IsChanged) + { + int oldValue = this.MouseWheelScrollWatcher.PreviousValue; + int newValue = this.MouseWheelScrollWatcher.CurrentValue; + this.MouseWheelScrollWatcher.Reset(); + + this.Events.Input_MouseWheelScrolled.Raise(new InputMouseWheelScrolledEventArgs(cursor, oldValue, newValue)); + } + + // raise input button events foreach (var pair in inputState.ActiveButtons) { SButton button = pair.Key; @@ -456,47 +495,49 @@ namespace StardewModdingAPI.Framework if (status == InputStatus.Pressed) { - this.Events.Input_ButtonPressed.Raise(new EventArgsInput(button, cursor, button.IsActionButton(), button.IsUseToolButton(), inputState.SuppressButtons)); + this.Events.Input_ButtonPressed.Raise(new InputButtonPressedArgsInput(button, cursor, inputState)); + this.Events.Legacy_Input_ButtonPressed.Raise(new EventArgsInput(button, cursor, inputState.SuppressButtons)); // legacy events if (button.TryGetKeyboard(out Keys key)) { if (key != Keys.None) - this.Events.Control_KeyPressed.Raise(new EventArgsKeyPressed(key)); + this.Events.Legacy_Control_KeyPressed.Raise(new EventArgsKeyPressed(key)); } else if (button.TryGetController(out Buttons controllerButton)) { if (controllerButton == Buttons.LeftTrigger || controllerButton == Buttons.RightTrigger) - this.Events.Control_ControllerTriggerPressed.Raise(new EventArgsControllerTriggerPressed(PlayerIndex.One, controllerButton, controllerButton == Buttons.LeftTrigger ? inputState.RealController.Triggers.Left : inputState.RealController.Triggers.Right)); + this.Events.Legacy_Control_ControllerTriggerPressed.Raise(new EventArgsControllerTriggerPressed(PlayerIndex.One, controllerButton, controllerButton == Buttons.LeftTrigger ? inputState.RealController.Triggers.Left : inputState.RealController.Triggers.Right)); else - this.Events.Control_ControllerButtonPressed.Raise(new EventArgsControllerButtonPressed(PlayerIndex.One, controllerButton)); + this.Events.Legacy_Control_ControllerButtonPressed.Raise(new EventArgsControllerButtonPressed(PlayerIndex.One, controllerButton)); } } else if (status == InputStatus.Released) { - this.Events.Input_ButtonReleased.Raise(new EventArgsInput(button, cursor, button.IsActionButton(), button.IsUseToolButton(), inputState.SuppressButtons)); + this.Events.Input_ButtonReleased.Raise(new InputButtonReleasedArgsInput(button, cursor, inputState)); + this.Events.Legacy_Input_ButtonReleased.Raise(new EventArgsInput(button, cursor, inputState.SuppressButtons)); // legacy events if (button.TryGetKeyboard(out Keys key)) { if (key != Keys.None) - this.Events.Control_KeyReleased.Raise(new EventArgsKeyPressed(key)); + this.Events.Legacy_Control_KeyReleased.Raise(new EventArgsKeyPressed(key)); } else if (button.TryGetController(out Buttons controllerButton)) { if (controllerButton == Buttons.LeftTrigger || controllerButton == Buttons.RightTrigger) - this.Events.Control_ControllerTriggerReleased.Raise(new EventArgsControllerTriggerReleased(PlayerIndex.One, controllerButton, controllerButton == Buttons.LeftTrigger ? inputState.RealController.Triggers.Left : inputState.RealController.Triggers.Right)); + this.Events.Legacy_Control_ControllerTriggerReleased.Raise(new EventArgsControllerTriggerReleased(PlayerIndex.One, controllerButton, controllerButton == Buttons.LeftTrigger ? inputState.RealController.Triggers.Left : inputState.RealController.Triggers.Right)); else - this.Events.Control_ControllerButtonReleased.Raise(new EventArgsControllerButtonReleased(PlayerIndex.One, controllerButton)); + this.Events.Legacy_Control_ControllerButtonReleased.Raise(new EventArgsControllerButtonReleased(PlayerIndex.One, controllerButton)); } } } // raise legacy state-changed events if (inputState.RealKeyboard != previousInputState.RealKeyboard) - this.Events.Control_KeyboardChanged.Raise(new EventArgsKeyboardStateChanged(previousInputState.RealKeyboard, inputState.RealKeyboard)); + this.Events.Legacy_Control_KeyboardChanged.Raise(new EventArgsKeyboardStateChanged(previousInputState.RealKeyboard, inputState.RealKeyboard)); if (inputState.RealMouse != previousInputState.RealMouse) - this.Events.Control_MouseChanged.Raise(new EventArgsMouseStateChanged(previousInputState.RealMouse, inputState.RealMouse, previousInputState.MousePosition, inputState.MousePosition)); + this.Events.Legacy_Control_MouseChanged.Raise(new EventArgsMouseStateChanged(previousInputState.RealMouse, inputState.RealMouse, new Point((int)previousInputState.CursorPosition.ScreenPixels.X, (int)previousInputState.CursorPosition.ScreenPixels.Y), new Point((int)inputState.CursorPosition.ScreenPixels.X, (int)inputState.CursorPosition.ScreenPixels.Y))); } } @@ -543,7 +584,8 @@ namespace StardewModdingAPI.Framework this.Monitor.Log($"Context: location list changed (added {addedText}; removed {removedText}).", LogLevel.Trace); } - this.Events.Location_LocationsChanged.Raise(new EventArgsLocationsChanged(added, removed)); + this.Events.World_LocationListChanged.Raise(new WorldLocationListChangedEventArgs(added, removed)); + this.Events.Legacy_Location_LocationsChanged.Raise(new EventArgsLocationsChanged(added, removed)); } // raise location contents changed @@ -551,26 +593,61 @@ namespace StardewModdingAPI.Framework { foreach (LocationTracker watcher in this.LocationsWatcher.Locations) { + // buildings changed + if (watcher.BuildingsWatcher.IsChanged) + { + GameLocation location = watcher.Location; + Building[] added = watcher.BuildingsWatcher.Added.ToArray(); + Building[] removed = watcher.BuildingsWatcher.Removed.ToArray(); + watcher.BuildingsWatcher.Reset(); + + this.Events.World_BuildingListChanged.Raise(new WorldBuildingListChangedEventArgs(location, added, removed)); + this.Events.Legacy_Location_BuildingsChanged.Raise(new EventArgsLocationBuildingsChanged(location, added, removed)); + } + + // large terrain features changed + if (watcher.LargeTerrainFeaturesWatcher.IsChanged) + { + GameLocation location = watcher.Location; + LargeTerrainFeature[] added = watcher.LargeTerrainFeaturesWatcher.Added.ToArray(); + LargeTerrainFeature[] removed = watcher.LargeTerrainFeaturesWatcher.Removed.ToArray(); + watcher.LargeTerrainFeaturesWatcher.Reset(); + + this.Events.World_LargeTerrainFeatureListChanged.Raise(new WorldLargeTerrainFeatureListChangedEventArgs(location, added, removed)); + } + + // NPCs changed + if (watcher.NpcsWatcher.IsChanged) + { + GameLocation location = watcher.Location; + NPC[] added = watcher.NpcsWatcher.Added.ToArray(); + NPC[] removed = watcher.NpcsWatcher.Removed.ToArray(); + watcher.NpcsWatcher.Reset(); + + this.Events.World_NpcListChanged.Raise(new WorldNpcListChangedEventArgs(location, added, removed)); + } + // objects changed if (watcher.ObjectsWatcher.IsChanged) { GameLocation location = watcher.Location; - var added = watcher.ObjectsWatcher.Added.ToArray(); - var removed = watcher.ObjectsWatcher.Removed.ToArray(); + KeyValuePair<Vector2, Object>[] added = watcher.ObjectsWatcher.Added.ToArray(); + KeyValuePair<Vector2, Object>[] removed = watcher.ObjectsWatcher.Removed.ToArray(); watcher.ObjectsWatcher.Reset(); - this.Events.Location_ObjectsChanged.Raise(new EventArgsLocationObjectsChanged(location, added, removed)); + this.Events.World_ObjectListChanged.Raise(new WorldObjectListChangedEventArgs(location, added, removed)); + this.Events.Legacy_Location_ObjectsChanged.Raise(new EventArgsLocationObjectsChanged(location, added, removed)); } - // buildings changed - if (watcher.BuildingsWatcher.IsChanged) + // terrain features changed + if (watcher.TerrainFeaturesWatcher.IsChanged) { GameLocation location = watcher.Location; - var added = watcher.BuildingsWatcher.Added.ToArray(); - var removed = watcher.BuildingsWatcher.Removed.ToArray(); - watcher.BuildingsWatcher.Reset(); + KeyValuePair<Vector2, TerrainFeature>[] added = watcher.TerrainFeaturesWatcher.Added.ToArray(); + KeyValuePair<Vector2, TerrainFeature>[] removed = watcher.TerrainFeaturesWatcher.Removed.ToArray(); + watcher.TerrainFeaturesWatcher.Reset(); - this.Events.Location_BuildingsChanged.Raise(new EventArgsLocationBuildingsChanged(location, added, removed)); + this.Events.World_TerrainFeatureListChanged.Raise(new WorldTerrainFeatureListChangedEventArgs(location, added, removed)); } } } diff --git a/src/SMAPI/Framework/StateTracking/LocationTracker.cs b/src/SMAPI/Framework/StateTracking/LocationTracker.cs index 07570401..1b4c0b19 100644 --- a/src/SMAPI/Framework/StateTracking/LocationTracker.cs +++ b/src/SMAPI/Framework/StateTracking/LocationTracker.cs @@ -6,6 +6,7 @@ using StardewModdingAPI.Framework.StateTracking.FieldWatchers; using StardewValley; using StardewValley.Buildings; using StardewValley.Locations; +using StardewValley.TerrainFeatures; using Object = StardewValley.Object; namespace StardewModdingAPI.Framework.StateTracking @@ -29,12 +30,21 @@ namespace StardewModdingAPI.Framework.StateTracking /// <summary>The tracked location.</summary> public GameLocation Location { get; } - /// <summary>Tracks changes to the location's buildings.</summary> + /// <summary>Tracks added or removed buildings.</summary> public ICollectionWatcher<Building> BuildingsWatcher { get; } - /// <summary>Tracks changes to the location's objects.</summary> + /// <summary>Tracks added or removed large terrain features.</summary> + public ICollectionWatcher<LargeTerrainFeature> LargeTerrainFeaturesWatcher { get; } + + /// <summary>Tracks added or removed NPCs.</summary> + public ICollectionWatcher<NPC> NpcsWatcher { get; } + + /// <summary>Tracks added or removed objects.</summary> public IDictionaryWatcher<Vector2, Object> ObjectsWatcher { get; } + /// <summary>Tracks added or removed terrain features.</summary> + public IDictionaryWatcher<Vector2, TerrainFeature> TerrainFeaturesWatcher { get; } + /********* ** Public methods @@ -46,15 +56,21 @@ namespace StardewModdingAPI.Framework.StateTracking this.Location = location; // init watchers - this.ObjectsWatcher = WatcherFactory.ForNetDictionary(location.netObjects); this.BuildingsWatcher = location is BuildableGameLocation buildableLocation ? WatcherFactory.ForNetCollection(buildableLocation.buildings) : (ICollectionWatcher<Building>)WatcherFactory.ForObservableCollection(new ObservableCollection<Building>()); + this.LargeTerrainFeaturesWatcher = WatcherFactory.ForNetCollection(location.largeTerrainFeatures); + this.NpcsWatcher = WatcherFactory.ForNetCollection(location.characters); + this.ObjectsWatcher = WatcherFactory.ForNetDictionary(location.netObjects); + this.TerrainFeaturesWatcher = WatcherFactory.ForNetDictionary(location.terrainFeatures); this.Watchers.AddRange(new IWatcher[] { this.BuildingsWatcher, - this.ObjectsWatcher + this.LargeTerrainFeaturesWatcher, + this.NpcsWatcher, + this.ObjectsWatcher, + this.TerrainFeaturesWatcher }); } diff --git a/src/SMAPI/IInputHelper.cs b/src/SMAPI/IInputHelper.cs new file mode 100644 index 00000000..328f504b --- /dev/null +++ b/src/SMAPI/IInputHelper.cs @@ -0,0 +1,21 @@ +namespace StardewModdingAPI +{ + /// <summary>Provides an API for checking and changing input state.</summary> + public interface IInputHelper : IModLinked + { + /// <summary>Get the current cursor position.</summary> + ICursorPosition GetCursorPosition(); + + /// <summary>Get whether a button is currently pressed.</summary> + /// <param name="button">The button.</param> + bool IsDown(SButton button); + + /// <summary>Get whether a button is currently suppressed, so the game won't see it.</summary> + /// <param name="button">The button.</param> + bool IsSuppressed(SButton 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> + void Suppress(SButton button); + } +} diff --git a/src/SMAPI/IModHelper.cs b/src/SMAPI/IModHelper.cs index 5e39161d..68c2f1c4 100644 --- a/src/SMAPI/IModHelper.cs +++ b/src/SMAPI/IModHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using StardewModdingAPI.Events; namespace StardewModdingAPI { @@ -12,6 +13,10 @@ namespace StardewModdingAPI /// <summary>The full path to the mod's folder.</summary> string DirectoryPath { get; } + /// <summary>Manages access to events raised by SMAPI, which let your mod react when something happens in the game.</summary> + [Obsolete("This is an experimental interface which may change at any time. Don't depend on this for released mods.")] + IModEvents Events { get; } + /// <summary>An API for loading content assets.</summary> IContentHelper Content { get; } diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index a1cfcf0c..d5f5fdcd 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -840,6 +840,7 @@ namespace StardewModdingAPI IMonitor monitor = this.GetSecondaryMonitor(metadata.DisplayName); IModHelper modHelper; { + IModEvents events = new ModEvents(metadata, this.EventManager); ICommandHelper commandHelper = new CommandHelper(manifest.UniqueID, metadata.DisplayName, this.CommandManager); IContentHelper contentHelper = new ContentHelper(contentCore, metadata.DirectoryPath, manifest.UniqueID, metadata.DisplayName, monitor); IReflectionHelper reflectionHelper = new ReflectionHelper(manifest.UniqueID, metadata.DisplayName, this.Reflection, this.DeprecationManager); @@ -854,7 +855,7 @@ namespace StardewModdingAPI return new ContentPack(packDirPath, packManifest, packContentHelper, this.JsonHelper); } - modHelper = new ModHelper(manifest.UniqueID, metadata.DirectoryPath, jsonHelper, contentHelper, commandHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper, contentPacks, CreateTransitionalContentPack, this.DeprecationManager); + modHelper = new ModHelper(manifest.UniqueID, metadata.DirectoryPath, jsonHelper, this.GameInstance.Input, events, contentHelper, commandHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper, contentPacks, CreateTransitionalContentPack, this.DeprecationManager); } // init mod @@ -936,7 +937,7 @@ namespace StardewModdingAPI { string[] warnings = this.GetWarningText(metadata.Warnings).ToArray(); if (warnings.Length == 1) - this.Monitor.Log($" {metadata.DisplayName}: {warnings[0]}", LogLevel.Warn); + this.Monitor.Log($" {metadata.DisplayName} {warnings[0]}", LogLevel.Warn); else { this.Monitor.Log($" {metadata.DisplayName}:", LogLevel.Warn); diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj index 3e6fdb24..96b3aa5b 100644 --- a/src/SMAPI/StardewModdingAPI.csproj +++ b/src/SMAPI/StardewModdingAPI.csproj @@ -85,19 +85,37 @@ <Compile Include="..\..\build\GlobalAssemblyInfo.cs"> <Link>Properties\GlobalAssemblyInfo.cs</Link> </Compile> + <Compile Include="Events\InputMouseWheelScrolledEventArgs.cs" /> + <Compile Include="Events\InputCursorMovedEventArgs.cs" /> + <Compile Include="Events\InputButtonReleasedEventArgs.cs" /> + <Compile Include="Events\InputButtonPressedEventArgs.cs" /> <Compile Include="Events\EventArgsLocationBuildingsChanged.cs" /> + <Compile Include="Events\IInputEvents.cs" /> + <Compile Include="Events\IWorldEvents.cs" /> <Compile Include="Events\MultiplayerEvents.cs" /> + <Compile Include="Events\WorldNpcListChangedEventArgs.cs" /> + <Compile Include="Events\WorldLargeTerrainFeatureListChangedEventArgs.cs" /> + <Compile Include="Events\WorldTerrainFeatureListChangedEventArgs.cs" /> + <Compile Include="Events\WorldBuildingListChangedEventArgs.cs" /> + <Compile Include="Events\WorldLocationListChangedEventArgs.cs" /> + <Compile Include="Events\WorldObjectListChangedEventArgs.cs" /> <Compile Include="Framework\ContentManagers\BaseContentManager.cs" /> <Compile Include="Framework\ContentManagers\GameContentManager.cs" /> <Compile Include="Framework\ContentManagers\IContentManager.cs" /> <Compile Include="Framework\ContentManagers\ModContentManager.cs" /> + <Compile Include="Framework\Events\ModEventsBase.cs" /> <Compile Include="Framework\Events\EventManager.cs" /> + <Compile Include="Events\IModEvents.cs" /> <Compile Include="Framework\Events\ManagedEvent.cs" /> <Compile Include="Events\SpecialisedEvents.cs" /> <Compile Include="Framework\ContentPack.cs" /> <Compile Include="Framework\Content\ContentCache.cs" /> <Compile Include="Framework\Events\ManagedEventBase.cs" /> + <Compile Include="Framework\Events\ModEvents.cs" /> + <Compile Include="Framework\Events\ModInputEvents.cs" /> <Compile Include="Framework\Input\GamePadStateBuilder.cs" /> + <Compile Include="Framework\ModHelpers\InputHelper.cs" /> + <Compile Include="IInputHelper.cs" /> <Compile Include="Framework\Input\SInputState.cs" /> <Compile Include="Framework\Input\InputStatus.cs" /> <Compile Include="Framework\LegacyManifestVersion.cs" /> @@ -129,6 +147,7 @@ <Compile Include="Framework\ModLoading\Rewriters\TypeReferenceRewriter.cs" /> <Compile Include="Framework\Exceptions\SAssemblyLoadFailedException.cs" /> <Compile Include="Framework\ModLoading\AssemblyLoadStatus.cs" /> + <Compile Include="Framework\Events\ModWorldEvents.cs" /> <Compile Include="Framework\Reflection\InterfaceProxyBuilder.cs" /> <Compile Include="Framework\Reflection\InterfaceProxyFactory.cs" /> <Compile Include="Framework\RewriteFacades\SpriteBatchMethods.cs" /> diff --git a/src/SMAPI/StardewModdingAPI.metadata.json b/src/SMAPI/StardewModdingAPI.metadata.json index 33568692..be9369f8 100644 --- a/src/SMAPI/StardewModdingAPI.metadata.json +++ b/src/SMAPI/StardewModdingAPI.metadata.json @@ -878,8 +878,8 @@ }, "Level Extender": { - "ID": "Devin Lematty.Level Extender", - "MapRemoteVersions": { "1.1": "1.0" }, // manifest not updated + "ID": "DevinLematty.LevelExtender", + "FormerIDs": "Devin Lematty.Level Extender", // changed in 1.3 "Default | UpdateKey": "Nexus:1471" }, @@ -1090,7 +1090,8 @@ }, "NPC Map Locations": { - "ID": "NPCMapLocationsMod", + "ID": "Bouhm.NPCMapLocations", + "FormerIDs": "NPCMapLocationsMod", // changed in 2.0 "Default | UpdateKey": "Nexus:239" }, @@ -1403,7 +1404,7 @@ }, "Siv's Marriage Mod": { - "ID": "6266959802", + "FormerIDs": "6266959802 | Siv.MarriageMod | medoli900.Siv's Marriage Mod", // changed in 1.2.3-unofficial versions "MapLocalVersions": { "0.0": "1.4" }, "Default | UpdateKey": "Nexus:366" }, @@ -1484,7 +1485,7 @@ "Sprint and Dash": { "ID": "SPDSprintAndDash", - "Default | UpdateKey": "Chucklefish:3531", + "Default | UpdateKey": "Nexus:235", "~1.0 | Status": "AssumeBroken" // broke in SDV 1.2 }, |