using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using StardewModdingAPI.Framework.StateTracking.FieldWatchers;
using StardewValley;
using StardewValley.Buildings;
using StardewValley.Locations;
using StardewValley.Objects;
using StardewValley.TerrainFeatures;
using SObject = StardewValley.Object;
namespace StardewModdingAPI.Framework.StateTracking
{
/// Tracks changes to a location's data.
internal class LocationTracker : IWatcher
{
/*********
** Fields
*********/
/// The underlying watchers.
private readonly List Watchers = new();
/*********
** Accessors
*********/
/// Whether the value changed since the last reset.
public bool IsChanged => this.Watchers.Any(p => p.IsChanged);
/// The tracked location.
public GameLocation Location { get; }
/// Tracks added or removed buildings.
public ICollectionWatcher BuildingsWatcher { get; }
/// Tracks added or removed debris.
public ICollectionWatcher DebrisWatcher { get; }
/// Tracks added or removed large terrain features.
public ICollectionWatcher LargeTerrainFeaturesWatcher { get; }
/// Tracks added or removed NPCs.
public ICollectionWatcher NpcsWatcher { get; }
/// Tracks added or removed objects.
public IDictionaryWatcher ObjectsWatcher { get; }
/// Tracks added or removed terrain features.
public IDictionaryWatcher TerrainFeaturesWatcher { get; }
/// Tracks added or removed furniture.
public ICollectionWatcher FurnitureWatcher { get; }
/// Tracks items added or removed to chests.
public IDictionary ChestWatchers { get; } = new Dictionary();
/*********
** Public methods
*********/
/// Construct an instance.
/// The location to track.
public LocationTracker(GameLocation location)
{
this.Location = location;
// init watchers
this.BuildingsWatcher = location is BuildableGameLocation buildableLocation ? WatcherFactory.ForNetCollection(buildableLocation.buildings) : WatcherFactory.ForImmutableCollection();
this.DebrisWatcher = WatcherFactory.ForNetCollection(location.debris);
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.FurnitureWatcher = WatcherFactory.ForNetCollection(location.furniture);
this.Watchers.AddRange(new IWatcher[]
{
this.BuildingsWatcher,
this.DebrisWatcher,
this.LargeTerrainFeaturesWatcher,
this.NpcsWatcher,
this.ObjectsWatcher,
this.TerrainFeaturesWatcher,
this.FurnitureWatcher
});
this.UpdateChestWatcherList(added: location.Objects.Pairs, removed: Array.Empty>());
}
/// Update the current value if needed.
public void Update()
{
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();
}
/// Set the current value as the baseline.
public void Reset()
{
foreach (IWatcher watcher in this.Watchers)
watcher.Reset();
foreach (var watcher in this.ChestWatchers)
watcher.Value.Reset();
}
/// Stop watching the player fields and release all references.
public void Dispose()
{
foreach (IWatcher watcher in this.Watchers)
watcher.Dispose();
foreach (var watcher in this.ChestWatchers.Values)
watcher.Dispose();
}
/*********
** Private methods
*********/
/// Update the watcher list for added or removed chests.
/// The objects added to the location.
/// The objects removed from the location.
private void UpdateChestWatcherList(IEnumerable> added, IEnumerable> removed)
{
// remove unused watchers
foreach ((Vector2 tile, SObject? obj) in removed)
{
if (obj is Chest && this.ChestWatchers.TryGetValue(tile, out ChestTracker? watcher))
{
watcher.Dispose();
this.ChestWatchers.Remove(tile);
}
}
// add new watchers
foreach ((Vector2 tile, SObject? obj) in added)
{
if (obj is Chest chest && !this.ChestWatchers.ContainsKey(tile))
this.ChestWatchers.Add(tile, new ChestTracker(chest));
}
}
}
}