add LocationEvents.ObjectsChanged event

This commit is contained in:
Jesse Plamondon-Willard 2018-05-04 20:44:20 -04:00
parent 05f81cb85f
commit 8051862c7b
7 changed files with 162 additions and 15 deletions

View File

@ -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.

View File

@ -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;
}
}

View File

@ -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

View File

@ -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));

View File

@ -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);
this.Events.Location_LocationObjectsChanged.Raise(new EventArgsLocationObjectsChanged(curPlayer.GetCurrentLocation().netObjects.FieldDict));
GameLocation location = curPlayer.GetCurrentLocation();
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

View File

@ -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();
}
}
}

View File

@ -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" />