summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs')
-rw-r--r--src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs221
1 files changed, 221 insertions, 0 deletions
diff --git a/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs b/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs
new file mode 100644
index 00000000..d9090c08
--- /dev/null
+++ b/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs
@@ -0,0 +1,221 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using StardewModdingAPI.Framework.StateTracking.FieldWatchers;
+using StardewValley;
+using StardewValley.Buildings;
+using StardewValley.Locations;
+
+namespace StardewModdingAPI.Framework.StateTracking
+{
+ /// <summary>Detects changes to the game's locations.</summary>
+ internal class WorldLocationsTracker : IWatcher
+ {
+ /*********
+ ** Properties
+ *********/
+ /// <summary>Tracks changes to the location list.</summary>
+ private readonly ICollectionWatcher<GameLocation> LocationListWatcher;
+
+ /// <summary>A lookup of the tracked locations.</summary>
+ private IDictionary<GameLocation, LocationTracker> LocationDict { get; } = new Dictionary<GameLocation, LocationTracker>();
+
+ /// <summary>A lookup of registered buildings and their indoor location.</summary>
+ private readonly IDictionary<Building, GameLocation> BuildingIndoors = new Dictionary<Building, GameLocation>();
+
+
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>Whether locations were added or removed since the last reset.</summary>
+ public bool IsLocationListChanged => this.Added.Any() || this.Removed.Any();
+
+ /// <summary>Whether any tracked location data changed since the last reset.</summary>
+ public bool IsChanged => this.IsLocationListChanged || this.Locations.Any(p => p.IsChanged);
+
+ /// <summary>The tracked locations.</summary>
+ public IEnumerable<LocationTracker> Locations => this.LocationDict.Values;
+
+ /// <summary>The locations removed since the last update.</summary>
+ public ICollection<GameLocation> Added { get; } = new HashSet<GameLocation>();
+
+ /// <summary>The locations added since the last update.</summary>
+ public ICollection<GameLocation> Removed { get; } = new HashSet<GameLocation>();
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="locations">The game's list of locations.</param>
+ public WorldLocationsTracker(ObservableCollection<GameLocation> locations)
+ {
+ this.LocationListWatcher = WatcherFactory.ForObservableCollection(locations);
+ }
+
+ /// <summary>Update the current value if needed.</summary>
+ public void Update()
+ {
+ // detect location changes
+ if (this.LocationListWatcher.IsChanged)
+ {
+ this.Remove(this.LocationListWatcher.Removed);
+ this.Add(this.LocationListWatcher.Added);
+ }
+
+ // detect building changes
+ foreach (LocationTracker watcher in this.Locations.ToArray())
+ {
+ if (watcher.BuildingsWatcher.IsChanged)
+ {
+ this.Remove(watcher.BuildingsWatcher.Removed);
+ this.Add(watcher.BuildingsWatcher.Added);
+ }
+ }
+
+ // detect building interior changed (e.g. construction completed)
+ foreach (KeyValuePair<Building, GameLocation> pair in this.BuildingIndoors.Where(p => !object.Equals(p.Key.indoors.Value, p.Value)))
+ {
+ GameLocation oldIndoors = pair.Value;
+ GameLocation newIndoors = pair.Key.indoors.Value;
+
+ if (oldIndoors != null)
+ this.Added.Add(oldIndoors);
+ if (newIndoors != null)
+ this.Removed.Add(newIndoors);
+ }
+
+ // update watchers
+ foreach (IWatcher watcher in this.Locations)
+ watcher.Update();
+ }
+
+ /// <summary>Set the current location list as the baseline.</summary>
+ public void ResetLocationList()
+ {
+ this.Removed.Clear();
+ this.Added.Clear();
+ this.LocationListWatcher.Reset();
+ }
+
+ /// <summary>Set the current value as the baseline.</summary>
+ public void Reset()
+ {
+ this.ResetLocationList();
+ foreach (IWatcher watcher in this.Locations)
+ watcher.Reset();
+ }
+
+ /// <summary>Stop watching the player fields and release all references.</summary>
+ public void Dispose()
+ {
+ this.LocationListWatcher.Dispose();
+ foreach (IWatcher watcher in this.Locations)
+ watcher.Dispose();
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /****
+ ** Enumerable wrappers
+ ****/
+ /// <summary>Add the given buildings.</summary>
+ /// <param name="buildings">The buildings to add.</param>
+ public void Add(IEnumerable<Building> buildings)
+ {
+ foreach (Building building in buildings)
+ this.Add(building);
+ }
+
+ /// <summary>Add the given locations.</summary>
+ /// <param name="locations">The locations to add.</param>
+ public void Add(IEnumerable<GameLocation> locations)
+ {
+ foreach (GameLocation location in locations)
+ this.Add(location);
+ }
+
+ /// <summary>Remove the given buildings.</summary>
+ /// <param name="buildings">The buildings to remove.</param>
+ public void Remove(IEnumerable<Building> buildings)
+ {
+ foreach (Building building in buildings)
+ this.Remove(building);
+ }
+
+ /// <summary>Remove the given locations.</summary>
+ /// <param name="locations">The locations to remove.</param>
+ public void Remove(IEnumerable<GameLocation> locations)
+ {
+ foreach (GameLocation location in locations)
+ this.Remove(location);
+ }
+
+ /****
+ ** Main add/remove logic
+ ****/
+ /// <summary>Add the given building.</summary>
+ /// <param name="building">The building to add.</param>
+ public void Add(Building building)
+ {
+ if (building == null)
+ return;
+
+ GameLocation indoors = building.indoors.Value;
+ this.BuildingIndoors[building] = indoors;
+ this.Add(indoors);
+ }
+
+ /// <summary>Add the given location.</summary>
+ /// <param name="location">The location to add.</param>
+ public void Add(GameLocation location)
+ {
+ if (location == null)
+ return;
+
+ // remove old location if needed
+ this.Remove(location);
+
+ // track change
+ this.Added.Add(location);
+
+ // add
+ this.LocationDict[location] = new LocationTracker(location);
+ if (location is BuildableGameLocation buildableLocation)
+ this.Add(buildableLocation.buildings);
+ }
+
+ /// <summary>Remove the given building.</summary>
+ /// <param name="building">The building to remove.</param>
+ public void Remove(Building building)
+ {
+ if (building == null)
+ return;
+
+ this.BuildingIndoors.Remove(building);
+ this.Remove(building.indoors.Value);
+ }
+
+ /// <summary>Remove the given location.</summary>
+ /// <param name="location">The location to remove.</param>
+ public void Remove(GameLocation location)
+ {
+ if (location == null)
+ return;
+
+ if (this.LocationDict.TryGetValue(location, out LocationTracker watcher))
+ {
+ // track change
+ this.Removed.Add(location);
+
+ // remove
+ this.LocationDict.Remove(location);
+ watcher.Dispose();
+ if (location is BuildableGameLocation buildableLocation)
+ this.Remove(buildableLocation.buildings);
+ }
+ }
+ }
+}