using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using StardewModdingAPI.Framework.StateTracking.Comparers;
using StardewModdingAPI.Framework.StateTracking.FieldWatchers;
using StardewValley;
using StardewValley.Buildings;
using StardewValley.Locations;
namespace StardewModdingAPI.Framework.StateTracking
{
/// Detects changes to the game's locations.
internal class WorldLocationsTracker : IWatcher
{
/*********
** Fields
*********/
/// Tracks changes to the location list.
private readonly ICollectionWatcher LocationListWatcher;
/// Tracks changes to the list of active mine locations.
private readonly ICollectionWatcher MineLocationListWatcher;
/// Tracks changes to the list of active volcano locations.
private readonly ICollectionWatcher VolcanoLocationListWatcher;
/// A lookup of the tracked locations.
private Dictionary LocationDict { get; } = new(new ObjectReferenceComparer());
/// A lookup of registered buildings and their indoor location.
private readonly Dictionary BuildingIndoors = new(new ObjectReferenceComparer());
/*********
** Accessors
*********/
///
public string Name => nameof(WorldLocationsTracker);
/// Whether locations were added or removed since the last reset.
public bool IsLocationListChanged => this.Added.Any() || this.Removed.Any();
///
public bool IsChanged => this.IsLocationListChanged || this.Locations.Any(p => p.IsChanged);
/// The tracked locations.
public IEnumerable Locations => this.LocationDict.Values;
/// The locations removed since the last update.
public ICollection Added { get; } = new HashSet(new ObjectReferenceComparer());
/// The locations added since the last update.
public ICollection Removed { get; } = new HashSet(new ObjectReferenceComparer());
/*********
** Public methods
*********/
/// Construct an instance.
/// The game's list of locations.
/// The game's list of active mine locations.
/// The game's list of active volcano locations.
public WorldLocationsTracker(ObservableCollection locations, IList activeMineLocations, IList activeVolcanoLocations)
{
this.LocationListWatcher = WatcherFactory.ForObservableCollection($"{this.Name}.{nameof(locations)}", locations);
this.MineLocationListWatcher = WatcherFactory.ForReferenceList($"{this.Name}.{nameof(activeMineLocations)}", activeMineLocations);
this.VolcanoLocationListWatcher = WatcherFactory.ForReferenceList($"{this.Name}.{nameof(activeVolcanoLocations)}", activeVolcanoLocations);
}
///
public void Update()
{
// update watchers
this.LocationListWatcher.Update();
this.MineLocationListWatcher.Update();
this.VolcanoLocationListWatcher.Update();
foreach (LocationTracker watcher in this.Locations)
watcher.Update();
// detect added/removed locations
if (this.LocationListWatcher.IsChanged)
{
this.Remove(this.LocationListWatcher.Removed);
this.Add(this.LocationListWatcher.Added);
}
if (this.MineLocationListWatcher.IsChanged)
{
this.Remove(this.MineLocationListWatcher.Removed);
this.Add(this.MineLocationListWatcher.Added);
}
if (this.VolcanoLocationListWatcher.IsChanged)
{
this.Remove(this.VolcanoLocationListWatcher.Removed);
this.Add(this.VolcanoLocationListWatcher.Added);
}
// detect building changed
foreach (LocationTracker watcher in this.Locations.Where(p => p.BuildingsWatcher.IsChanged).ToArray())
{
this.Remove(watcher.BuildingsWatcher.Removed);
this.Add(watcher.BuildingsWatcher.Added);
}
// detect building interiors changed (e.g. construction completed)
foreach ((Building building, GameLocation? oldIndoors) in this.BuildingIndoors.Where(p => !object.Equals(p.Key.indoors.Value, p.Value)))
{
GameLocation? newIndoors = building.indoors.Value;
if (oldIndoors != null)
this.Added.Add(oldIndoors);
if (newIndoors != null)
this.Removed.Add(newIndoors);
}
}
/// Set the current location list as the baseline.
public void ResetLocationList()
{
this.Removed.Clear();
this.Added.Clear();
this.LocationListWatcher.Reset();
this.MineLocationListWatcher.Reset();
this.VolcanoLocationListWatcher.Reset();
}
///
public void Reset()
{
this.ResetLocationList();
foreach (IWatcher watcher in this.GetWatchers())
watcher.Reset();
}
/// Get whether the given location is tracked.
/// The location to check.
public bool HasLocationTracker(GameLocation location)
{
return this.LocationDict.ContainsKey(location);
}
///
public void Dispose()
{
foreach (IWatcher watcher in this.GetWatchers())
watcher.Dispose();
}
/*********
** Private methods
*********/
/****
** Enumerable wrappers
****/
/// Add the given buildings.
/// The buildings to add.
public void Add(IEnumerable buildings)
{
foreach (Building building in buildings)
this.Add(building);
}
/// Add the given locations.
/// The locations to add.
public void Add(IEnumerable locations)
{
foreach (GameLocation location in locations)
this.Add(location);
}
/// Remove the given buildings.
/// The buildings to remove.
public void Remove(IEnumerable buildings)
{
foreach (Building building in buildings)
this.Remove(building);
}
/// Remove the given locations.
/// The locations to remove.
public void Remove(IEnumerable locations)
{
foreach (GameLocation location in locations)
this.Remove(location);
}
/****
** Main add/remove logic
****/
/// Add the given building.
/// The building to add.
public void Add(Building? building)
{
if (building == null)
return;
GameLocation? indoors = building.indoors.Value;
this.BuildingIndoors[building] = indoors;
this.Add(indoors);
}
/// Add the given location.
/// The location to add.
public void Add(GameLocation? location)
{
if (location == null)
return;
// remove old location if needed
this.Remove(location);
// add location
this.Added.Add(location);
this.LocationDict[location] = new LocationTracker(location);
// add buildings
if (location is BuildableGameLocation buildableLocation)
this.Add(buildableLocation.buildings);
}
/// Remove the given building.
/// The building to remove.
public void Remove(Building? building)
{
if (building == null)
return;
this.BuildingIndoors.Remove(building);
this.Remove(building.indoors.Value);
}
/// Remove the given location.
/// The location to remove.
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);
}
}
/****
** Helpers
****/
/// The underlying watchers.
private IEnumerable GetWatchers()
{
yield return this.LocationListWatcher;
yield return this.MineLocationListWatcher;
yield return this.VolcanoLocationListWatcher;
foreach (LocationTracker watcher in this.Locations)
yield return watcher;
}
}
}