summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/release-notes.md1
-rw-r--r--src/SMAPI/Events/EventArgsLocationObjectsChanged.cs26
-rw-r--r--src/SMAPI/Events/LocationEvents.cs8
-rw-r--r--src/SMAPI/Framework/Events/EventManager.cs6
-rw-r--r--src/SMAPI/Framework/SGame.cs64
-rw-r--r--src/SMAPI/Framework/StateTracking/LocationTracker.cs71
-rw-r--r--src/SMAPI/StardewModdingAPI.csproj1
7 files changed, 162 insertions, 15 deletions
diff --git a/docs/release-notes.md b/docs/release-notes.md
index 85776a06..558ed004 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -11,6 +11,7 @@
* For modders:
* Added code analysis to mod build config package to flag common issues as warnings.
+ * Added `LocationEvents.ObjectsChanged`, raised when an object is added/removed in any location.
* Added `Context.IsMultiplayer` and `Context.IsMainPlayer` flags.
* Added `Constants.TargetPlatform` which says whether the game is running on Linux, Mac, or Windows.
* Added `semanticVersion.IsPrerelease()` method.
diff --git a/src/SMAPI/Events/EventArgsLocationObjectsChanged.cs b/src/SMAPI/Events/EventArgsLocationObjectsChanged.cs
index de6bd365..a6138ddb 100644
--- a/src/SMAPI/Events/EventArgsLocationObjectsChanged.cs
+++ b/src/SMAPI/Events/EventArgsLocationObjectsChanged.cs
@@ -1,28 +1,46 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using Microsoft.Xna.Framework;
using Netcode;
-using Object = StardewValley.Object;
+using StardewValley;
+using SObject = StardewValley.Object;
namespace StardewModdingAPI.Events
{
- /// <summary>Event arguments for a <see cref="LocationEvents.LocationObjectsChanged"/> event.</summary>
+ /// <summary>Event arguments for a <see cref="LocationEvents.LocationObjectsChanged"/> or <see cref="LocationEvents.ObjectsChanged"/> event.</summary>
public class EventArgsLocationObjectsChanged : EventArgs
{
/*********
** Accessors
*********/
+ /// <summary>The location which changed.</summary>
+ public GameLocation Location { get; }
+
+ /// <summary>The objects added to the list.</summary>
+ public IEnumerable<KeyValuePair<Vector2, SObject>> Added { get; }
+
+ /// <summary>The objects removed from the list.</summary>
+ public IEnumerable<KeyValuePair<Vector2, SObject>> Removed { get; }
+
/// <summary>The current list of objects in the current location.</summary>
- public IDictionary<Vector2, NetRef<Object>> NewObjects { get; }
+ [Obsolete("Use " + nameof(EventArgsLocationObjectsChanged.Added))]
+ public IDictionary<Vector2, NetRef<SObject>> NewObjects { get; }
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
+ /// <param name="location">The location which changed.</param>
+ /// <param name="added">The objects added to the list.</param>
+ /// <param name="removed">The objects removed from the list.</param>
/// <param name="newObjects">The current list of objects in the current location.</param>
- public EventArgsLocationObjectsChanged(IDictionary<Vector2, NetRef<Object>> newObjects)
+ public EventArgsLocationObjectsChanged(GameLocation location, IEnumerable<KeyValuePair<Vector2, SObject>> added, IEnumerable<KeyValuePair<Vector2, SObject>> removed, IDictionary<Vector2, NetRef<SObject>> newObjects)
{
+ this.Location = location;
+ this.Added = added.ToArray();
+ this.Removed = removed.ToArray();
this.NewObjects = newObjects;
}
}
diff --git a/src/SMAPI/Events/LocationEvents.cs b/src/SMAPI/Events/LocationEvents.cs
index 81d13e9f..3ed09136 100644
--- a/src/SMAPI/Events/LocationEvents.cs
+++ b/src/SMAPI/Events/LocationEvents.cs
@@ -31,12 +31,20 @@ namespace StardewModdingAPI.Events
}
/// <summary>Raised after the list of objects in the current location changes (e.g. an object is added or removed).</summary>
+ [Obsolete("Use " + nameof(LocationEvents) + "." + nameof(LocationEvents.ObjectsChanged) + " instead")]
public static event EventHandler<EventArgsLocationObjectsChanged> LocationObjectsChanged
{
add => LocationEvents.EventManager.Location_LocationObjectsChanged.Add(value);
remove => LocationEvents.EventManager.Location_LocationObjectsChanged.Remove(value);
}
+ /// <summary>Raised after the list of objects in a location changes (e.g. an object is added or removed).</summary>
+ public static event EventHandler<EventArgsLocationObjectsChanged> ObjectsChanged
+ {
+ add => LocationEvents.EventManager.Location_ObjectsChanged.Add(value);
+ remove => LocationEvents.EventManager.Location_ObjectsChanged.Remove(value);
+ }
+
/*********
** Public methods
diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs
index 87ff760f..9030ba97 100644
--- a/src/SMAPI/Framework/Events/EventManager.cs
+++ b/src/SMAPI/Framework/Events/EventManager.cs
@@ -1,3 +1,4 @@
+using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Xna.Framework.Input;
using StardewModdingAPI.Events;
@@ -114,8 +115,12 @@ namespace StardewModdingAPI.Framework.Events
public readonly ManagedEvent<EventArgsGameLocationsChanged> Location_LocationsChanged;
/// <summary>Raised after the list of objects in the current location changes (e.g. an object is added or removed).</summary>
+ [Obsolete]
public readonly ManagedEvent<EventArgsLocationObjectsChanged> Location_LocationObjectsChanged;
+ /// <summary>Raised after the list of objects in a location changes (e.g. an object is added or removed).</summary>
+ public readonly ManagedEvent<EventArgsLocationObjectsChanged> Location_ObjectsChanged;
+
/****
** MenuEvents
****/
@@ -239,6 +244,7 @@ namespace StardewModdingAPI.Framework.Events
this.Location_CurrentLocationChanged = ManageEventOf<EventArgsCurrentLocationChanged>(nameof(LocationEvents), nameof(LocationEvents.CurrentLocationChanged));
this.Location_LocationsChanged = ManageEventOf<EventArgsGameLocationsChanged>(nameof(LocationEvents), nameof(LocationEvents.LocationsChanged));
this.Location_LocationObjectsChanged = ManageEventOf<EventArgsLocationObjectsChanged>(nameof(LocationEvents), nameof(LocationEvents.LocationObjectsChanged));
+ this.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/SGame.cs b/src/SMAPI/Framework/SGame.cs
index 78d07fbf..d2ba85f8 100644
--- a/src/SMAPI/Framework/SGame.cs
+++ b/src/SMAPI/Framework/SGame.cs
@@ -93,7 +93,10 @@ namespace StardewModdingAPI.Framework
private readonly IValueWatcher<ulong> SaveIdWatcher;
/// <summary>Tracks changes to the location list.</summary>
- private readonly ICollectionWatcher<GameLocation> LocationsWatcher;
+ private readonly ICollectionWatcher<GameLocation> LocationListWatcher;
+
+ /// <summary>Tracks changes to each location.</summary>
+ private readonly IDictionary<GameLocation, LocationTracker> LocationWatchers = new Dictionary<GameLocation, LocationTracker>();
/// <summary>Tracks changes to <see cref="Game1.activeClickableMenu"/>.</summary>
private readonly IValueWatcher<IClickableMenu> ActiveMenuWatcher;
@@ -162,14 +165,14 @@ namespace StardewModdingAPI.Framework
this.WindowSizeWatcher = WatcherFactory.ForEquatable(() => new Point(Game1.viewport.Width, Game1.viewport.Height));
this.TimeWatcher = WatcherFactory.ForEquatable(() => Game1.timeOfDay);
this.ActiveMenuWatcher = WatcherFactory.ForReference(() => Game1.activeClickableMenu);
- this.LocationsWatcher = WatcherFactory.ForObservableCollection((ObservableCollection<GameLocation>)Game1.locations);
+ this.LocationListWatcher = WatcherFactory.ForObservableCollection((ObservableCollection<GameLocation>)Game1.locations);
this.Watchers.AddRange(new IWatcher[]
{
this.SaveIdWatcher,
this.WindowSizeWatcher,
this.TimeWatcher,
this.ActiveMenuWatcher,
- this.LocationsWatcher
+ this.LocationListWatcher
});
}
@@ -349,6 +352,22 @@ namespace StardewModdingAPI.Framework
watcher.Update();
this.CurrentPlayerTracker?.Update();
+ // update location watchers
+ if (this.LocationListWatcher.IsChanged)
+ {
+ foreach (GameLocation location in this.LocationListWatcher.Removed.Union(this.LocationListWatcher.Added))
+ {
+ if (this.LocationWatchers.TryGetValue(location, out LocationTracker watcher))
+ {
+ this.Watchers.Remove(watcher);
+ this.LocationWatchers.Remove(location);
+ watcher.Dispose();
+ }
+ }
+ foreach (GameLocation location in this.LocationListWatcher.Added)
+ this.LocationWatchers[location] = new LocationTracker(location);
+ }
+
/*********
** Locale changed events
*********/
@@ -509,12 +528,12 @@ namespace StardewModdingAPI.Framework
}
// raise location list changed
- if (this.LocationsWatcher.IsChanged)
+ if (this.LocationListWatcher.IsChanged)
{
if (this.VerboseLogging)
{
- string added = this.LocationsWatcher.Added.Any() ? string.Join(", ", this.LocationsWatcher.Added.Select(p => p.Name)) : "none";
- string removed = this.LocationsWatcher.Removed.Any() ? string.Join(", ", this.LocationsWatcher.Removed.Select(p => p.Name)) : "none";
+ string added = this.LocationListWatcher.Added.Any() ? string.Join(", ", this.LocationListWatcher.Added.Select(p => p.Name)) : "none";
+ string removed = this.LocationListWatcher.Removed.Any() ? string.Join(", ", this.LocationListWatcher.Removed.Select(p => p.Name)) : "none";
this.Monitor.Log($"Context: location list changed (added {added}; removed {removed}).", LogLevel.Trace);
}
@@ -524,6 +543,20 @@ namespace StardewModdingAPI.Framework
// raise events that shouldn't be triggered on initial load
if (!this.SaveIdWatcher.IsChanged)
{
+ // raise location objects changed
+ foreach (LocationTracker watcher in this.LocationWatchers.Values)
+ {
+ if (watcher.LocationObjectsWatcher.IsChanged)
+ {
+ GameLocation location = watcher.Location;
+ var added = watcher.LocationObjectsWatcher.Added;
+ var removed = watcher.LocationObjectsWatcher.Removed;
+ watcher.Reset();
+
+ this.Events.Location_ObjectsChanged.Raise(new EventArgsLocationObjectsChanged(location, added, removed, watcher.Location.netObjects.FieldDict));
+ }
+ }
+
// raise player leveled up a skill
foreach (KeyValuePair<EventArgsLevelUp.LevelType, IValueWatcher<int>> pair in curPlayer.GetChangedSkills())
{
@@ -542,12 +575,16 @@ namespace StardewModdingAPI.Framework
}
// raise current location's object list changed
- if (curPlayer.TryGetLocationChanges(out IDictionaryWatcher<Vector2, SObject> _))
{
- if (this.VerboseLogging)
- this.Monitor.Log("Context: current location objects changed.", LogLevel.Trace);
+ if (curPlayer.TryGetLocationChanges(out IDictionaryWatcher<Vector2, SObject> watcher))
+ {
+ if (this.VerboseLogging)
+ this.Monitor.Log("Context: current location objects changed.", LogLevel.Trace);
+
+ GameLocation location = curPlayer.GetCurrentLocation();
- this.Events.Location_LocationObjectsChanged.Raise(new EventArgsLocationObjectsChanged(curPlayer.GetCurrentLocation().netObjects.FieldDict));
+ this.Events.Location_LocationObjectsChanged.Raise(new EventArgsLocationObjectsChanged(location, watcher.Added, watcher.Removed, location.netObjects.FieldDict));
+ }
}
// raise time changed
@@ -570,9 +607,14 @@ namespace StardewModdingAPI.Framework
// update state
this.CurrentPlayerTracker?.Reset();
- this.LocationsWatcher.Reset();
+ this.LocationListWatcher.Reset();
this.SaveIdWatcher.Reset();
this.TimeWatcher.Reset();
+ if (!Context.IsWorldReady)
+ {
+ foreach (LocationTracker watcher in this.LocationWatchers.Values)
+ watcher.Reset();
+ }
/*********
** Game update
diff --git a/src/SMAPI/Framework/StateTracking/LocationTracker.cs b/src/SMAPI/Framework/StateTracking/LocationTracker.cs
new file mode 100644
index 00000000..8cf4e7a2
--- /dev/null
+++ b/src/SMAPI/Framework/StateTracking/LocationTracker.cs
@@ -0,0 +1,71 @@
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Xna.Framework;
+using StardewModdingAPI.Framework.StateTracking.FieldWatchers;
+using StardewValley;
+using Object = StardewValley.Object;
+
+namespace StardewModdingAPI.Framework.StateTracking
+{
+ /// <summary>Tracks changes to a location's data.</summary>
+ internal class LocationTracker : IWatcher
+ {
+ /*********
+ ** Properties
+ *********/
+ /// <summary>The underlying watchers.</summary>
+ private readonly List<IWatcher> Watchers = new List<IWatcher>();
+
+
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>Whether the value changed since the last reset.</summary>
+ public bool IsChanged => this.Watchers.Any(p => p.IsChanged);
+
+ /// <summary>The tracked location.</summary>
+ public GameLocation Location { get; }
+
+ /// <summary>Tracks changes to the current location's objects.</summary>
+ public IDictionaryWatcher<Vector2, Object> LocationObjectsWatcher { get; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="location">The location to track.</param>
+ public LocationTracker(GameLocation location)
+ {
+ this.Location = location;
+
+ // init watchers
+ this.LocationObjectsWatcher = WatcherFactory.ForNetDictionary(location.netObjects);
+ this.Watchers.AddRange(new[]
+ {
+ this.LocationObjectsWatcher
+ });
+ }
+
+ /// <summary>Stop watching the player fields and release all references.</summary>
+ public void Dispose()
+ {
+ foreach (IWatcher watcher in this.Watchers)
+ watcher.Dispose();
+ }
+
+ /// <summary>Update the current value if needed.</summary>
+ public void Update()
+ {
+ foreach (IWatcher watcher in this.Watchers)
+ watcher.Update();
+ }
+
+ /// <summary>Set the current value as the baseline.</summary>
+ public void Reset()
+ {
+ foreach (IWatcher watcher in this.Watchers)
+ watcher.Reset();
+ }
+ }
+}
diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj
index 1822b134..9f04887c 100644
--- a/src/SMAPI/StardewModdingAPI.csproj
+++ b/src/SMAPI/StardewModdingAPI.csproj
@@ -148,6 +148,7 @@
<Compile Include="Framework\StateTracking\IDictionaryWatcher.cs" />
<Compile Include="Framework\StateTracking\IValueWatcher.cs" />
<Compile Include="Framework\StateTracking\IWatcher.cs" />
+ <Compile Include="Framework\StateTracking\LocationTracker.cs" />
<Compile Include="Framework\StateTracking\PlayerTracker.cs" />
<Compile Include="Framework\Utilities\ContextHash.cs" />
<Compile Include="Framework\Utilities\PathUtilities.cs" />