Merge pull request #686 from wartech0/chest-tracking
Add chest items changed event
This commit is contained in:
commit
00932a335c
|
@ -29,15 +29,16 @@
|
|||
* Fixed main sidebar link pointing to wiki instead of home page.
|
||||
|
||||
* For modders:
|
||||
* Added `World.ChestInventoryChanged` event (thanks to collaboration with wartech0!).
|
||||
* Added asset propagation for...
|
||||
* grass textures;
|
||||
* winter flooring textures;
|
||||
* `Data\Bundles` changes (for added bundles only).
|
||||
* `Data\Bundles` changes (for added bundles only);
|
||||
* `Characters\Farmer\farmer_girl_base_bald`.
|
||||
* Added direct `Console` access to paranoid mode warnings.
|
||||
* Improved error messages for `TargetParameterCountException` when using the reflection API.
|
||||
* `helper.Read/WriteSaveData` can now be used while a save is being loaded (e.g. within a `Specialized.LoadStageChanged` event).
|
||||
* Fixed private textures loaded from content packs not having their `Name` field set.
|
||||
* Fixed asset propagation for `Characters\Farmer\farmer_girl_base_bald`.
|
||||
|
||||
* For SMAPI developers:
|
||||
* You can now run local environments without configuring Amazon, Azure, MongoDB, and Pastebin accounts.
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using StardewValley;
|
||||
using StardewValley.Objects;
|
||||
|
||||
namespace StardewModdingAPI.Events
|
||||
{
|
||||
/// <summary>Event arguments for a <see cref="IWorldEvents.ChestInventoryChanged"/> event.</summary>
|
||||
public class ChestInventoryChangedEventArgs : EventArgs
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The chest whose inventory changed.</summary>
|
||||
public Chest Chest { get; }
|
||||
|
||||
/// <summary>The location containing the chest.</summary>
|
||||
public GameLocation Location { get; }
|
||||
|
||||
/// <summary>The added item stacks.</summary>
|
||||
public IEnumerable<Item> Added { get; }
|
||||
|
||||
/// <summary>The removed item stacks.</summary>
|
||||
public IEnumerable<Item> Removed { get; }
|
||||
|
||||
/// <summary>The item stacks whose size changed.</summary>
|
||||
public IEnumerable<ItemStackSizeChange> QuantityChanged { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="chest">The chest whose inventory changed.</param>
|
||||
/// <param name="location">The location containing the chest.</param>
|
||||
/// <param name="added">The added item stacks.</param>
|
||||
/// <param name="removed">The removed item stacks.</param>
|
||||
/// <param name="quantityChanged">The item stacks whose size changed.</param>
|
||||
internal ChestInventoryChangedEventArgs(Chest chest, GameLocation location, Item[] added, Item[] removed, ItemStackSizeChange[] quantityChanged)
|
||||
{
|
||||
this.Location = location;
|
||||
this.Chest = chest;
|
||||
this.Added = added;
|
||||
this.Removed = removed;
|
||||
this.QuantityChanged = quantityChanged;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,9 @@ namespace StardewModdingAPI.Events
|
|||
/// <summary>Raised after objects are added or removed in a location.</summary>
|
||||
event EventHandler<ObjectListChangedEventArgs> ObjectListChanged;
|
||||
|
||||
/// <summary>Raised after items are added or removed from a chest.</summary>
|
||||
event EventHandler<ChestInventoryChangedEventArgs> ChestInventoryChanged;
|
||||
|
||||
/// <summary>Raised after terrain features (like floors and trees) are added or removed in a location.</summary>
|
||||
event EventHandler<TerrainFeatureListChangedEventArgs> TerrainFeatureListChanged;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Events
|
||||
|
@ -14,13 +13,13 @@ namespace StardewModdingAPI.Events
|
|||
/// <summary>The player whose inventory changed.</summary>
|
||||
public Farmer Player { get; }
|
||||
|
||||
/// <summary>The added items.</summary>
|
||||
/// <summary>The added item stacks.</summary>
|
||||
public IEnumerable<Item> Added { get; }
|
||||
|
||||
/// <summary>The removed items.</summary>
|
||||
/// <summary>The removed item stacks.</summary>
|
||||
public IEnumerable<Item> Removed { get; }
|
||||
|
||||
/// <summary>The items whose stack sizes changed, with the relative change.</summary>
|
||||
/// <summary>The item stacks whose size changed.</summary>
|
||||
public IEnumerable<ItemStackSizeChange> QuantityChanged { get; }
|
||||
|
||||
/// <summary>Whether the affected player is the local one.</summary>
|
||||
|
@ -32,28 +31,15 @@ namespace StardewModdingAPI.Events
|
|||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="player">The player whose inventory changed.</param>
|
||||
/// <param name="changedItems">The inventory changes.</param>
|
||||
internal InventoryChangedEventArgs(Farmer player, ItemStackChange[] changedItems)
|
||||
/// <param name="added">The added item stacks.</param>
|
||||
/// <param name="removed">The removed item stacks.</param>
|
||||
/// <param name="quantityChanged">The item stacks whose size changed.</param>
|
||||
internal InventoryChangedEventArgs(Farmer player, Item[] added, Item[] removed, ItemStackSizeChange[] quantityChanged)
|
||||
{
|
||||
this.Player = player;
|
||||
this.Added = changedItems
|
||||
.Where(n => n.ChangeType == ChangeType.Added)
|
||||
.Select(p => p.Item)
|
||||
.ToArray();
|
||||
|
||||
this.Removed = changedItems
|
||||
.Where(n => n.ChangeType == ChangeType.Removed)
|
||||
.Select(p => p.Item)
|
||||
.ToArray();
|
||||
|
||||
this.QuantityChanged = changedItems
|
||||
.Where(n => n.ChangeType == ChangeType.StackChange)
|
||||
.Select(change => new ItemStackSizeChange(
|
||||
item: change.Item,
|
||||
oldSize: change.Item.Stack - change.StackChange,
|
||||
newSize: change.Item.Stack
|
||||
))
|
||||
.ToArray();
|
||||
this.Added = added;
|
||||
this.Removed = removed;
|
||||
this.QuantityChanged = quantityChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Events
|
||||
{
|
||||
/// <summary>Represents an inventory slot that changed.</summary>
|
||||
public class ItemStackChange
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The item in the slot.</summary>
|
||||
public Item Item { get; set; }
|
||||
|
||||
/// <summary>The amount by which the item's stack size changed.</summary>
|
||||
public int StackChange { get; set; }
|
||||
|
||||
/// <summary>How the inventory slot changed.</summary>
|
||||
public ChangeType ChangeType { get; set; }
|
||||
}
|
||||
}
|
|
@ -148,6 +148,9 @@ namespace StardewModdingAPI.Framework.Events
|
|||
/// <summary>Raised after objects are added or removed in a location.</summary>
|
||||
public readonly ManagedEvent<ObjectListChangedEventArgs> ObjectListChanged;
|
||||
|
||||
/// <summary>Raised after items are added or removed from a chest.</summary>
|
||||
public readonly ManagedEvent<ChestInventoryChangedEventArgs> ChestInventoryChanged;
|
||||
|
||||
/// <summary>Raised after terrain features (like floors and trees) are added or removed in a location.</summary>
|
||||
public readonly ManagedEvent<TerrainFeatureListChangedEventArgs> TerrainFeatureListChanged;
|
||||
|
||||
|
@ -221,6 +224,7 @@ namespace StardewModdingAPI.Framework.Events
|
|||
this.LocationListChanged = ManageEventOf<LocationListChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.BuildingListChanged));
|
||||
this.NpcListChanged = ManageEventOf<NpcListChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.NpcListChanged));
|
||||
this.ObjectListChanged = ManageEventOf<ObjectListChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.ObjectListChanged));
|
||||
this.ChestInventoryChanged = ManageEventOf<ChestInventoryChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.ChestInventoryChanged));
|
||||
this.TerrainFeatureListChanged = ManageEventOf<TerrainFeatureListChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.TerrainFeatureListChanged));
|
||||
|
||||
this.LoadStageChanged = ManageEventOf<LoadStageChangedEventArgs>(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.LoadStageChanged));
|
||||
|
|
|
@ -51,6 +51,13 @@ namespace StardewModdingAPI.Framework.Events
|
|||
remove => this.EventManager.ObjectListChanged.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised after items are added or removed from a chest.</summary>
|
||||
public event EventHandler<ChestInventoryChangedEventArgs> ChestInventoryChanged
|
||||
{
|
||||
add => this.EventManager.ChestInventoryChanged.Add(value);
|
||||
remove => this.EventManager.ChestInventoryChanged.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>Raised after terrain features (like floors and trees) are added or removed in a location.</summary>
|
||||
public event EventHandler<TerrainFeatureListChangedEventArgs> TerrainFeatureListChanged
|
||||
{
|
||||
|
|
|
@ -702,6 +702,16 @@ namespace StardewModdingAPI.Framework
|
|||
if (locState.Objects.IsChanged)
|
||||
events.ObjectListChanged.Raise(new ObjectListChangedEventArgs(location, locState.Objects.Added, locState.Objects.Removed));
|
||||
|
||||
// chest items changed
|
||||
if (events.ChestInventoryChanged.HasListeners())
|
||||
{
|
||||
foreach (var pair in locState.ChestItems)
|
||||
{
|
||||
SnapshotItemListDiff diff = pair.Value;
|
||||
events.ChestInventoryChanged.Raise(new ChestInventoryChangedEventArgs(pair.Key, location, added: diff.Added, removed: diff.Removed, quantityChanged: diff.QuantityChanged));
|
||||
}
|
||||
}
|
||||
|
||||
// terrain features changed
|
||||
if (locState.TerrainFeatures.IsChanged)
|
||||
events.TerrainFeatureListChanged.Raise(new TerrainFeatureListChangedEventArgs(location, locState.TerrainFeatures.Added, locState.TerrainFeatures.Removed));
|
||||
|
@ -740,12 +750,13 @@ namespace StardewModdingAPI.Framework
|
|||
}
|
||||
|
||||
// raise player inventory changed
|
||||
ItemStackChange[] changedItems = playerState.InventoryChanges.ToArray();
|
||||
if (changedItems.Any())
|
||||
if (playerState.Inventory.IsChanged)
|
||||
{
|
||||
var inventory = playerState.Inventory;
|
||||
|
||||
if (this.Monitor.IsVerbose)
|
||||
this.Monitor.Log("Events: player inventory changed.", LogLevel.Trace);
|
||||
events.InventoryChanged.Raise(new InventoryChangedEventArgs(player, changedItems));
|
||||
events.InventoryChanged.Raise(new InventoryChangedEventArgs(player, added: inventory.Added, removed: inventory.Removed, quantityChanged: inventory.QuantityChanged));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using StardewModdingAPI.Events;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Framework
|
||||
{
|
||||
/// <summary>A snapshot of a tracked item list.</summary>
|
||||
internal class SnapshotItemListDiff
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>Whether the item list changed.</summary>
|
||||
public bool IsChanged { get; }
|
||||
|
||||
/// <summary>The removed values.</summary>
|
||||
public Item[] Removed { get; }
|
||||
|
||||
/// <summary>The added values.</summary>
|
||||
public Item[] Added { get; }
|
||||
|
||||
/// <summary>The items whose stack sizes changed.</summary>
|
||||
public ItemStackSizeChange[] QuantityChanged { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Update the snapshot.</summary>
|
||||
/// <param name="added">The added values.</param>
|
||||
/// <param name="removed">The removed values.</param>
|
||||
/// <param name="sizesChanged">The items whose stack sizes changed.</param>
|
||||
public SnapshotItemListDiff(Item[] added, Item[] removed, ItemStackSizeChange[] sizesChanged)
|
||||
{
|
||||
this.Removed = removed;
|
||||
this.Added = added;
|
||||
this.QuantityChanged = sizesChanged;
|
||||
|
||||
this.IsChanged = removed.Length > 0 || added.Length > 0 || sizesChanged.Length > 0;
|
||||
}
|
||||
|
||||
/// <summary>Get a snapshot diff if anything changed in the given data.</summary>
|
||||
/// <param name="added">The added item stacks.</param>
|
||||
/// <param name="removed">The removed item stacks.</param>
|
||||
/// <param name="stackSizes">The items with their previous stack sizes.</param>
|
||||
/// <param name="changes">The inventory changes, or <c>null</c> if nothing changed.</param>
|
||||
/// <returns>Returns whether anything changed.</returns>
|
||||
public static bool TryGetChanges(ISet<Item> added, ISet<Item> removed, IDictionary<Item, int> stackSizes, out SnapshotItemListDiff changes)
|
||||
{
|
||||
KeyValuePair<Item, int>[] sizesChanged = stackSizes.Where(p => p.Key.Stack != p.Value).ToArray();
|
||||
if (sizesChanged.Any() || added.Any() || removed.Any())
|
||||
{
|
||||
changes = new SnapshotItemListDiff(
|
||||
added: added.ToArray(),
|
||||
removed: removed.ToArray(),
|
||||
sizesChanged: sizesChanged.Select(p => new ItemStackSizeChange(p.Key, p.Value, p.Key.Stack)).ToArray()
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
changes = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using StardewModdingAPI.Framework.StateTracking.Comparers;
|
||||
using StardewModdingAPI.Framework.StateTracking.FieldWatchers;
|
||||
using StardewValley;
|
||||
using StardewValley.Objects;
|
||||
|
||||
namespace StardewModdingAPI.Framework.StateTracking
|
||||
{
|
||||
/// <summary>Tracks changes to a chest's items.</summary>
|
||||
internal class ChestTracker : IDisposable
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The item stack sizes as of the last update.</summary>
|
||||
private readonly IDictionary<Item, int> StackSizes;
|
||||
|
||||
/// <summary>Items added since the last update.</summary>
|
||||
private readonly HashSet<Item> Added = new HashSet<Item>(new ObjectReferenceComparer<Item>());
|
||||
|
||||
/// <summary>Items removed since the last update.</summary>
|
||||
private readonly HashSet<Item> Removed = new HashSet<Item>(new ObjectReferenceComparer<Item>());
|
||||
|
||||
/// <summary>The underlying inventory watcher.</summary>
|
||||
private readonly ICollectionWatcher<Item> InventoryWatcher;
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The chest being tracked.</summary>
|
||||
public Chest Chest { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="chest">The chest being tracked.</param>
|
||||
public ChestTracker(Chest chest)
|
||||
{
|
||||
this.Chest = chest;
|
||||
this.InventoryWatcher = WatcherFactory.ForNetList(chest.items);
|
||||
|
||||
this.StackSizes = this.Chest.items
|
||||
.Where(n => n != null)
|
||||
.Distinct()
|
||||
.ToDictionary(n => n, n => n.Stack);
|
||||
}
|
||||
|
||||
/// <summary>Update the current values if needed.</summary>
|
||||
public void Update()
|
||||
{
|
||||
// update watcher
|
||||
this.InventoryWatcher.Update();
|
||||
foreach (Item item in this.InventoryWatcher.Added)
|
||||
this.Added.Add(item);
|
||||
foreach (Item item in this.InventoryWatcher.Removed)
|
||||
{
|
||||
if (!this.Added.Remove(item)) // item didn't change if it was both added and removed, so remove it from both lists
|
||||
this.Removed.Add(item);
|
||||
}
|
||||
|
||||
// stop tracking removed stacks
|
||||
foreach (Item item in this.Removed)
|
||||
this.StackSizes.Remove(item);
|
||||
}
|
||||
|
||||
/// <summary>Reset all trackers so their current values are the baseline.</summary>
|
||||
public void Reset()
|
||||
{
|
||||
// update stack sizes
|
||||
foreach (Item item in this.StackSizes.Keys.ToArray().Concat(this.Added))
|
||||
this.StackSizes[item] = item.Stack;
|
||||
|
||||
// update watcher
|
||||
this.InventoryWatcher.Reset();
|
||||
this.Added.Clear();
|
||||
this.Removed.Clear();
|
||||
}
|
||||
|
||||
/// <summary>Get the inventory changes since the last update, if anything changed.</summary>
|
||||
/// <param name="changes">The inventory changes, or <c>null</c> if nothing changed.</param>
|
||||
/// <returns>Returns whether anything changed.</returns>
|
||||
public bool TryGetInventoryChanges(out SnapshotItemListDiff changes)
|
||||
{
|
||||
return SnapshotItemListDiff.TryGetChanges(added: this.Added, removed: this.Removed, stackSizes: this.StackSizes, out changes);
|
||||
}
|
||||
|
||||
/// <summary>Release watchers and resources.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.StackSizes.Clear();
|
||||
this.Added.Clear();
|
||||
this.Removed.Clear();
|
||||
this.InventoryWatcher.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
using System.Collections.Generic;
|
||||
using Netcode;
|
||||
using StardewModdingAPI.Framework.StateTracking.Comparers;
|
||||
|
||||
namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
|
||||
{
|
||||
/// <summary>A watcher which detects changes to a net list field.</summary>
|
||||
/// <typeparam name="TValue">The list value type.</typeparam>
|
||||
internal class NetListWatcher<TValue> : BaseDisposableWatcher, ICollectionWatcher<TValue>
|
||||
where TValue : class, INetObject<INetSerializable>
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The field being watched.</summary>
|
||||
private readonly NetList<TValue, NetRef<TValue>> Field;
|
||||
|
||||
/// <summary>The pairs added since the last reset.</summary>
|
||||
private readonly ISet<TValue> AddedImpl = new HashSet<TValue>(new ObjectReferenceComparer<TValue>());
|
||||
|
||||
/// <summary>The pairs removed since the last reset.</summary>
|
||||
private readonly ISet<TValue> RemovedImpl = new HashSet<TValue>(new ObjectReferenceComparer<TValue>());
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>Whether the collection changed since the last reset.</summary>
|
||||
public bool IsChanged => this.AddedImpl.Count > 0 || this.RemovedImpl.Count > 0;
|
||||
|
||||
/// <summary>The values added since the last reset.</summary>
|
||||
public IEnumerable<TValue> Added => this.AddedImpl;
|
||||
|
||||
/// <summary>The values removed since the last reset.</summary>
|
||||
public IEnumerable<TValue> Removed => this.RemovedImpl;
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="field">The field to watch.</param>
|
||||
public NetListWatcher(NetList<TValue, NetRef<TValue>> field)
|
||||
{
|
||||
this.Field = field;
|
||||
field.OnElementChanged += this.OnElementChanged;
|
||||
field.OnArrayReplaced += this.OnArrayReplaced;
|
||||
}
|
||||
|
||||
/// <summary>Set the current value as the baseline.</summary>
|
||||
public void Reset()
|
||||
{
|
||||
this.AddedImpl.Clear();
|
||||
this.RemovedImpl.Clear();
|
||||
}
|
||||
|
||||
/// <summary>Update the current value if needed.</summary>
|
||||
public void Update()
|
||||
{
|
||||
this.AssertNotDisposed();
|
||||
}
|
||||
|
||||
/// <summary>Stop watching the field and release all references.</summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
if (!this.IsDisposed)
|
||||
{
|
||||
this.Field.OnElementChanged -= this.OnElementChanged;
|
||||
this.Field.OnArrayReplaced -= this.OnArrayReplaced;
|
||||
}
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>A callback invoked when the value list is replaced.</summary>
|
||||
/// <param name="list">The net field whose values changed.</param>
|
||||
/// <param name="oldValues">The previous list of values.</param>
|
||||
/// <param name="newValues">The new list of values.</param>
|
||||
private void OnArrayReplaced(NetList<TValue, NetRef<TValue>> list, IList<TValue> oldValues, IList<TValue> newValues)
|
||||
{
|
||||
ISet<TValue> oldSet = new HashSet<TValue>(oldValues, new ObjectReferenceComparer<TValue>());
|
||||
ISet<TValue> changed = new HashSet<TValue>(newValues, new ObjectReferenceComparer<TValue>());
|
||||
|
||||
foreach (TValue value in oldSet)
|
||||
{
|
||||
if (!changed.Contains(value))
|
||||
this.Remove(value);
|
||||
}
|
||||
foreach (TValue value in changed)
|
||||
{
|
||||
if (!oldSet.Contains(value))
|
||||
this.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>A callback invoked when an entry is replaced.</summary>
|
||||
/// <param name="list">The net field whose values changed.</param>
|
||||
/// <param name="index">The list index which changed.</param>
|
||||
/// <param name="oldValue">The previous value.</param>
|
||||
/// <param name="newValue">The new value.</param>
|
||||
private void OnElementChanged(NetList<TValue, NetRef<TValue>> list, int index, TValue oldValue, TValue newValue)
|
||||
{
|
||||
this.Remove(oldValue);
|
||||
this.Add(newValue);
|
||||
}
|
||||
|
||||
/// <summary>Track an added item.</summary>
|
||||
/// <param name="value">The value that was added.</param>
|
||||
private void Add(TValue value)
|
||||
{
|
||||
if (value == null)
|
||||
return;
|
||||
|
||||
if (this.RemovedImpl.Contains(value))
|
||||
{
|
||||
this.AddedImpl.Remove(value);
|
||||
this.RemovedImpl.Remove(value);
|
||||
}
|
||||
else
|
||||
this.AddedImpl.Add(value);
|
||||
}
|
||||
|
||||
/// <summary>Track a removed item.</summary>
|
||||
/// <param name="value">The value that was removed.</param>
|
||||
private void Remove(TValue value)
|
||||
{
|
||||
if (value == null)
|
||||
return;
|
||||
|
||||
if (this.AddedImpl.Contains(value))
|
||||
{
|
||||
this.AddedImpl.Remove(value);
|
||||
this.RemovedImpl.Remove(value);
|
||||
}
|
||||
else
|
||||
this.RemovedImpl.Add(value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -82,6 +82,14 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
|
|||
return new NetCollectionWatcher<T>(collection);
|
||||
}
|
||||
|
||||
/// <summary>Get a watcher for a net list.</summary>
|
||||
/// <typeparam name="T">The value type.</typeparam>
|
||||
/// <param name="collection">The net list.</param>
|
||||
public static ICollectionWatcher<T> ForNetList<T>(NetList<T, NetRef<T>> collection) where T : class, INetObject<INetSerializable>
|
||||
{
|
||||
return new NetListWatcher<T>(collection);
|
||||
}
|
||||
|
||||
/// <summary>Get a watcher for a net dictionary.</summary>
|
||||
/// <typeparam name="TKey">The dictionary key type.</typeparam>
|
||||
/// <typeparam name="TValue">The dictionary value type.</typeparam>
|
||||
|
|
|
@ -5,8 +5,9 @@ using StardewModdingAPI.Framework.StateTracking.FieldWatchers;
|
|||
using StardewValley;
|
||||
using StardewValley.Buildings;
|
||||
using StardewValley.Locations;
|
||||
using StardewValley.Objects;
|
||||
using StardewValley.TerrainFeatures;
|
||||
using Object = StardewValley.Object;
|
||||
using SObject = StardewValley.Object;
|
||||
|
||||
namespace StardewModdingAPI.Framework.StateTracking
|
||||
{
|
||||
|
@ -42,11 +43,14 @@ namespace StardewModdingAPI.Framework.StateTracking
|
|||
public ICollectionWatcher<NPC> NpcsWatcher { get; }
|
||||
|
||||
/// <summary>Tracks added or removed objects.</summary>
|
||||
public IDictionaryWatcher<Vector2, Object> ObjectsWatcher { get; }
|
||||
public IDictionaryWatcher<Vector2, SObject> ObjectsWatcher { get; }
|
||||
|
||||
/// <summary>Tracks added or removed terrain features.</summary>
|
||||
public IDictionaryWatcher<Vector2, TerrainFeature> TerrainFeaturesWatcher { get; }
|
||||
|
||||
/// <summary>Tracks items added or removed to chests.</summary>
|
||||
public IDictionary<Vector2, ChestTracker> ChestWatchers { get; } = new Dictionary<Vector2, ChestTracker>();
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
|
@ -74,13 +78,8 @@ namespace StardewModdingAPI.Framework.StateTracking
|
|||
this.ObjectsWatcher,
|
||||
this.TerrainFeaturesWatcher
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Stop watching the player fields and release all references.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (IWatcher watcher in this.Watchers)
|
||||
watcher.Dispose();
|
||||
this.UpdateChestWatcherList(added: location.Objects.Pairs, removed: new KeyValuePair<Vector2, SObject>[0]);
|
||||
}
|
||||
|
||||
/// <summary>Update the current value if needed.</summary>
|
||||
|
@ -88,6 +87,11 @@ namespace StardewModdingAPI.Framework.StateTracking
|
|||
{
|
||||
foreach (IWatcher watcher in this.Watchers)
|
||||
watcher.Update();
|
||||
|
||||
this.UpdateChestWatcherList(added: this.ObjectsWatcher.Added, removed: this.ObjectsWatcher.Removed);
|
||||
|
||||
foreach (var watcher in this.ChestWatchers)
|
||||
watcher.Value.Update();
|
||||
}
|
||||
|
||||
/// <summary>Set the current value as the baseline.</summary>
|
||||
|
@ -95,6 +99,46 @@ namespace StardewModdingAPI.Framework.StateTracking
|
|||
{
|
||||
foreach (IWatcher watcher in this.Watchers)
|
||||
watcher.Reset();
|
||||
|
||||
foreach (var watcher in this.ChestWatchers)
|
||||
watcher.Value.Reset();
|
||||
}
|
||||
|
||||
/// <summary>Stop watching the player fields and release all references.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (IWatcher watcher in this.Watchers)
|
||||
watcher.Dispose();
|
||||
|
||||
foreach (var watcher in this.ChestWatchers.Values)
|
||||
watcher.Dispose();
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Update the watcher list for added or removed chests.</summary>
|
||||
/// <param name="added">The objects added to the location.</param>
|
||||
/// <param name="removed">The objects removed from the location.</param>
|
||||
private void UpdateChestWatcherList(IEnumerable<KeyValuePair<Vector2, SObject>> added, IEnumerable<KeyValuePair<Vector2, SObject>> removed)
|
||||
{
|
||||
// remove unused watchers
|
||||
foreach (KeyValuePair<Vector2, SObject> pair in removed)
|
||||
{
|
||||
if (pair.Value is Chest && this.ChestWatchers.TryGetValue(pair.Key, out ChestTracker watcher))
|
||||
{
|
||||
watcher.Dispose();
|
||||
this.ChestWatchers.Remove(pair.Key);
|
||||
}
|
||||
}
|
||||
|
||||
// add new watchers
|
||||
foreach (KeyValuePair<Vector2, SObject> pair in added)
|
||||
{
|
||||
if (pair.Value is Chest chest && !this.ChestWatchers.ContainsKey(pair.Key))
|
||||
this.ChestWatchers.Add(pair.Key, new ChestTracker(chest));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,9 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using StardewModdingAPI.Enums;
|
||||
using StardewModdingAPI.Events;
|
||||
using StardewModdingAPI.Framework.StateTracking.Comparers;
|
||||
using StardewModdingAPI.Framework.StateTracking.FieldWatchers;
|
||||
using StardewValley;
|
||||
using ChangeType = StardewModdingAPI.Events.ChangeType;
|
||||
|
||||
namespace StardewModdingAPI.Framework.StateTracking
|
||||
{
|
||||
|
@ -99,25 +98,32 @@ namespace StardewModdingAPI.Framework.StateTracking
|
|||
return this.Player.currentLocation ?? this.LastValidLocation;
|
||||
}
|
||||
|
||||
/// <summary>Get the player inventory changes between two states.</summary>
|
||||
public IEnumerable<ItemStackChange> GetInventoryChanges()
|
||||
/// <summary>Get the inventory changes since the last update, if anything changed.</summary>
|
||||
/// <param name="changes">The inventory changes, or <c>null</c> if nothing changed.</param>
|
||||
/// <returns>Returns whether anything changed.</returns>
|
||||
public bool TryGetInventoryChanges(out SnapshotItemListDiff changes)
|
||||
{
|
||||
IDictionary<Item, int> previous = this.PreviousInventory;
|
||||
IDictionary<Item, int> current = this.GetInventory();
|
||||
foreach (Item item in previous.Keys.Union(current.Keys))
|
||||
|
||||
ISet<Item> added = new HashSet<Item>(new ObjectReferenceComparer<Item>());
|
||||
ISet<Item> removed = new HashSet<Item>(new ObjectReferenceComparer<Item>());
|
||||
foreach (Item item in this.PreviousInventory.Keys.Union(current.Keys))
|
||||
{
|
||||
if (!previous.TryGetValue(item, out int prevStack))
|
||||
yield return new ItemStackChange { Item = item, StackChange = item.Stack, ChangeType = ChangeType.Added };
|
||||
else if (!current.TryGetValue(item, out int newStack))
|
||||
yield return new ItemStackChange { Item = item, StackChange = -item.Stack, ChangeType = ChangeType.Removed };
|
||||
else if (prevStack != newStack)
|
||||
yield return new ItemStackChange { Item = item, StackChange = newStack - prevStack, ChangeType = ChangeType.StackChange };
|
||||
if (!this.PreviousInventory.ContainsKey(item))
|
||||
added.Add(item);
|
||||
else if (!current.ContainsKey(item))
|
||||
removed.Add(item);
|
||||
}
|
||||
|
||||
return SnapshotItemListDiff.TryGetChanges(added: added, removed: removed, stackSizes: this.PreviousInventory, out changes);
|
||||
}
|
||||
|
||||
/// <summary>Stop watching the player fields and release all references.</summary>
|
||||
/// <summary>Release watchers and resources.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.PreviousInventory.Clear();
|
||||
this.CurrentInventory?.Clear();
|
||||
|
||||
foreach (IWatcher watcher in this.Watchers)
|
||||
watcher.Dispose();
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ using System.Collections.Generic;
|
|||
using Microsoft.Xna.Framework;
|
||||
using StardewValley;
|
||||
using StardewValley.Buildings;
|
||||
using StardewValley.Objects;
|
||||
using StardewValley.TerrainFeatures;
|
||||
|
||||
namespace StardewModdingAPI.Framework.StateTracking.Snapshots
|
||||
|
@ -33,6 +34,9 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots
|
|||
/// <summary>Tracks added or removed terrain features.</summary>
|
||||
public SnapshotListDiff<KeyValuePair<Vector2, TerrainFeature>> TerrainFeatures { get; } = new SnapshotListDiff<KeyValuePair<Vector2, TerrainFeature>>();
|
||||
|
||||
/// <summary>Tracks changed chest inventories.</summary>
|
||||
public IDictionary<Chest, SnapshotItemListDiff> ChestItems { get; } = new Dictionary<Chest, SnapshotItemListDiff>();
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
|
@ -48,12 +52,21 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots
|
|||
/// <param name="watcher">The watcher to snapshot.</param>
|
||||
public void Update(LocationTracker watcher)
|
||||
{
|
||||
// main lists
|
||||
this.Buildings.Update(watcher.BuildingsWatcher);
|
||||
this.Debris.Update(watcher.DebrisWatcher);
|
||||
this.LargeTerrainFeatures.Update(watcher.LargeTerrainFeaturesWatcher);
|
||||
this.Npcs.Update(watcher.NpcsWatcher);
|
||||
this.Objects.Update(watcher.ObjectsWatcher);
|
||||
this.TerrainFeatures.Update(watcher.TerrainFeaturesWatcher);
|
||||
|
||||
// chest inventories
|
||||
this.ChestItems.Clear();
|
||||
foreach (ChestTracker tracker in watcher.ChestWatchers.Values)
|
||||
{
|
||||
if (tracker.TryGetInventoryChanges(out SnapshotItemListDiff changes))
|
||||
this.ChestItems[tracker.Chest] = changes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,13 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots
|
|||
/// <summary>A frozen snapshot of a tracked player.</summary>
|
||||
internal class PlayerSnapshot
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>An empty item list diff.</summary>
|
||||
private readonly SnapshotItemListDiff EmptyItemListDiff = new SnapshotItemListDiff(new Item[0], new Item[0], new ItemStackSizeChange[0]);
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
|
@ -27,7 +34,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots
|
|||
.ToDictionary(skill => skill, skill => new SnapshotDiff<int>());
|
||||
|
||||
/// <summary>Get a list of inventory changes.</summary>
|
||||
public IEnumerable<ItemStackChange> InventoryChanges { get; private set; }
|
||||
public SnapshotItemListDiff Inventory { get; private set; }
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -47,7 +54,11 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots
|
|||
this.Location.Update(watcher.LocationWatcher);
|
||||
foreach (var pair in this.Skills)
|
||||
pair.Value.Update(watcher.SkillWatchers[pair.Key]);
|
||||
this.InventoryChanges = watcher.GetInventoryChanges().ToArray();
|
||||
|
||||
this.Inventory = watcher.TryGetInventoryChanges(out SnapshotItemListDiff itemChanges)
|
||||
? itemChanges
|
||||
: this.EmptyItemListDiff;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue