#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using StardewModdingAPI.Enums;
using StardewModdingAPI.Framework.StateTracking.Comparers;
using StardewModdingAPI.Framework.StateTracking.FieldWatchers;
using StardewValley;
namespace StardewModdingAPI.Framework.StateTracking
{
/// Tracks changes to a player's data.
internal class PlayerTracker : IDisposable
{
/*********
** Fields
*********/
/// The player's inventory as of the last reset.
private IDictionary- PreviousInventory;
/// The player's inventory change as of the last update.
private IDictionary
- CurrentInventory;
/// The player's last valid location.
private GameLocation LastValidLocation;
/// The underlying watchers.
private readonly List Watchers = new();
/*********
** Accessors
*********/
/// The player being tracked.
public Farmer Player { get; }
/// The player's current location.
public IValueWatcher LocationWatcher { get; }
/// Tracks changes to the player's skill levels.
public IDictionary> SkillWatchers { get; }
/*********
** Public methods
*********/
/// Construct an instance.
/// The player to track.
public PlayerTracker(Farmer player)
{
// init player data
this.Player = player;
this.PreviousInventory = this.GetInventory();
// init trackers
this.LocationWatcher = WatcherFactory.ForReference(this.GetCurrentLocation);
this.SkillWatchers = new Dictionary>
{
[SkillType.Combat] = WatcherFactory.ForNetValue(player.combatLevel),
[SkillType.Farming] = WatcherFactory.ForNetValue(player.farmingLevel),
[SkillType.Fishing] = WatcherFactory.ForNetValue(player.fishingLevel),
[SkillType.Foraging] = WatcherFactory.ForNetValue(player.foragingLevel),
[SkillType.Luck] = WatcherFactory.ForNetValue(player.luckLevel),
[SkillType.Mining] = WatcherFactory.ForNetValue(player.miningLevel)
};
// track watchers for convenience
this.Watchers.Add(this.LocationWatcher);
this.Watchers.AddRange(this.SkillWatchers.Values);
}
/// Update the current values if needed.
public void Update()
{
// update valid location
this.LastValidLocation = this.GetCurrentLocation();
// update watchers
foreach (IWatcher watcher in this.Watchers)
watcher.Update();
// update inventory
this.CurrentInventory = this.GetInventory();
}
/// Reset all trackers so their current values are the baseline.
public void Reset()
{
foreach (IWatcher watcher in this.Watchers)
watcher.Reset();
this.PreviousInventory = this.CurrentInventory;
}
/// Get the player's current location, ignoring temporary null values.
/// The game will set to null in some cases, e.g. when they're a secondary player in multiplayer and transition to a location that hasn't been synced yet. While that's happening, this returns the player's last valid location instead.
public GameLocation GetCurrentLocation()
{
return this.Player.currentLocation ?? this.LastValidLocation;
}
/// 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(out SnapshotItemListDiff changes)
{
IDictionary
- current = this.GetInventory();
ISet
- added = new HashSet
- (new ObjectReferenceComparer
- ());
ISet
- removed = new HashSet
- (new ObjectReferenceComparer
- ());
foreach (Item item in this.PreviousInventory.Keys.Union(current.Keys))
{
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);
}
/// Release watchers and resources.
public void Dispose()
{
this.PreviousInventory.Clear();
this.CurrentInventory?.Clear();
foreach (IWatcher watcher in this.Watchers)
watcher.Dispose();
}
/*********
** Private methods
*********/
/// Get the player's current inventory.
private IDictionary
- GetInventory()
{
return this.Player.Items
.Where(n => n != null)
.Distinct()
.ToDictionary(n => n, n => n.Stack);
}
}
}