summaryrefslogtreecommitdiff
path: root/src/SMAPI
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI')
-rw-r--r--src/SMAPI/Events/ControlEvents.cs36
-rw-r--r--src/SMAPI/Events/EventArgsInput.cs10
-rw-r--r--src/SMAPI/Events/EventArgsLocationObjectsChanged.cs1
-rw-r--r--src/SMAPI/Events/IInputEvents.cs20
-rw-r--r--src/SMAPI/Events/IModEvents.cs12
-rw-r--r--src/SMAPI/Events/IWorldEvents.cs26
-rw-r--r--src/SMAPI/Events/InputButtonPressedEventArgs.cs60
-rw-r--r--src/SMAPI/Events/InputButtonReleasedEventArgs.cs60
-rw-r--r--src/SMAPI/Events/InputCursorMovedEventArgs.cs30
-rw-r--r--src/SMAPI/Events/InputEvents.cs8
-rw-r--r--src/SMAPI/Events/InputMouseWheelScrolledEventArgs.cs38
-rw-r--r--src/SMAPI/Events/LocationEvents.cs12
-rw-r--r--src/SMAPI/Events/WorldBuildingListChangedEventArgs.cs39
-rw-r--r--src/SMAPI/Events/WorldLargeTerrainFeatureListChangedEventArgs.cs39
-rw-r--r--src/SMAPI/Events/WorldLocationListChangedEventArgs.cs33
-rw-r--r--src/SMAPI/Events/WorldNpcListChangedEventArgs.cs38
-rw-r--r--src/SMAPI/Events/WorldObjectListChangedEventArgs.cs40
-rw-r--r--src/SMAPI/Events/WorldTerrainFeatureListChangedEventArgs.cs40
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs81
-rw-r--r--src/SMAPI/Framework/ContentManagers/BaseContentManager.cs29
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs2
-rw-r--r--src/SMAPI/Framework/ContentManagers/IContentManager.cs5
-rw-r--r--src/SMAPI/Framework/CursorPosition.cs7
-rw-r--r--src/SMAPI/Framework/Events/EventManager.cs118
-rw-r--r--src/SMAPI/Framework/Events/ManagedEvent.cs20
-rw-r--r--src/SMAPI/Framework/Events/ManagedEventBase.cs9
-rw-r--r--src/SMAPI/Framework/Events/ModEvents.cs30
-rw-r--r--src/SMAPI/Framework/Events/ModEventsBase.cs28
-rw-r--r--src/SMAPI/Framework/Events/ModInputEvents.cs50
-rw-r--r--src/SMAPI/Framework/Events/ModWorldEvents.cs64
-rw-r--r--src/SMAPI/Framework/Input/SInputState.cs28
-rw-r--r--src/SMAPI/Framework/ModHelpers/InputHelper.cs54
-rw-r--r--src/SMAPI/Framework/ModHelpers/ModHelper.cs14
-rw-r--r--src/SMAPI/Framework/SGame.cs227
-rw-r--r--src/SMAPI/Framework/StateTracking/LocationTracker.cs24
-rw-r--r--src/SMAPI/IInputHelper.cs21
-rw-r--r--src/SMAPI/IModHelper.cs5
-rw-r--r--src/SMAPI/Program.cs5
-rw-r--r--src/SMAPI/StardewModdingAPI.csproj19
-rw-r--r--src/SMAPI/StardewModdingAPI.metadata.json11
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
},