using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewModdingAPI.Framework.StateTracking.Comparers; using StardewModdingAPI.Framework.StateTracking.FieldWatchers; using StardewValley; using StardewValley.Objects; namespace StardewModdingAPI.Framework.StateTracking { /// Tracks changes to a chest's items. internal class ChestTracker : IDisposable { /********* ** Fields *********/ /// The item stack sizes as of the last update. private readonly IDictionary StackSizes; /// Items added since the last update. private readonly HashSet Added = new(new ObjectReferenceComparer()); /// Items removed since the last update. private readonly HashSet Removed = new(new ObjectReferenceComparer()); /// The underlying inventory watcher. private readonly ICollectionWatcher InventoryWatcher; /********* ** Accessors *********/ /// The chest being tracked. public Chest Chest { get; } /********* ** Public methods *********/ /// Construct an instance. /// A name which identifies what the watcher is watching, used for troubleshooting. /// The chest being tracked. public ChestTracker(string name, Chest chest) { this.Chest = chest; this.InventoryWatcher = WatcherFactory.ForNetList($"{name}.{nameof(chest.items)}", chest.items); this.StackSizes = this.Chest.items .Where(n => n != null) .Distinct() .ToDictionary(n => n, n => n.Stack); } /// Update the current values if needed. 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); } /// Reset all trackers so their current values are the baseline. 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(); } /// Get the inventory changes since the last update, if anything changed. /// The inventory changes, or null if nothing changed. /// Returns whether anything changed. public bool TryGetInventoryChanges([NotNullWhen(true)] out SnapshotItemListDiff? changes) { return SnapshotItemListDiff.TryGetChanges(added: this.Added, removed: this.Removed, stackSizes: this.StackSizes, out changes); } /// Release watchers and resources. public void Dispose() { this.StackSizes.Clear(); this.Added.Clear(); this.Removed.Clear(); this.InventoryWatcher.Dispose(); } } }