diff options
author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2019-06-14 01:16:50 -0400 |
---|---|---|
committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2019-09-14 18:04:11 -0400 |
commit | d3209b17de4e46ee1d604aac24642af80ce855cc (patch) | |
tree | cdbb14eed386129b031d9a8c0abf0321a6bba4de /src/SMAPI/Framework/StateTracking | |
parent | fdf221addee883f4b1ddcd42f92876f7a816c7a1 (diff) | |
download | SMAPI-d3209b17de4e46ee1d604aac24642af80ce855cc.tar.gz SMAPI-d3209b17de4e46ee1d604aac24642af80ce855cc.tar.bz2 SMAPI-d3209b17de4e46ee1d604aac24642af80ce855cc.zip |
decouple updating watchers & raising event to fix some mod changes not being tracked correctly (#648)
Diffstat (limited to 'src/SMAPI/Framework/StateTracking')
6 files changed, 238 insertions, 34 deletions
diff --git a/src/SMAPI/Framework/StateTracking/PlayerTracker.cs b/src/SMAPI/Framework/StateTracking/PlayerTracker.cs index abb4fa24..6302a889 100644 --- a/src/SMAPI/Framework/StateTracking/PlayerTracker.cs +++ b/src/SMAPI/Framework/StateTracking/PlayerTracker.cs @@ -5,7 +5,6 @@ using StardewModdingAPI.Enums; using StardewModdingAPI.Events; using StardewModdingAPI.Framework.StateTracking.FieldWatchers; using StardewValley; -using StardewValley.Locations; using ChangeType = StardewModdingAPI.Events.ChangeType; namespace StardewModdingAPI.Framework.StateTracking @@ -38,9 +37,6 @@ namespace StardewModdingAPI.Framework.StateTracking /// <summary>The player's current location.</summary> public IValueWatcher<GameLocation> LocationWatcher { get; } - /// <summary>The player's current mine level.</summary> - public IValueWatcher<int> MineLevelWatcher { get; } - /// <summary>Tracks changes to the player's skill levels.</summary> public IDictionary<SkillType, IValueWatcher<int>> SkillWatchers { get; } @@ -58,7 +54,6 @@ namespace StardewModdingAPI.Framework.StateTracking // init trackers this.LocationWatcher = WatcherFactory.ForReference(this.GetCurrentLocation); - this.MineLevelWatcher = WatcherFactory.ForEquatable(() => this.LastValidLocation is MineShaft mine ? mine.mineLevel : 0); this.SkillWatchers = new Dictionary<SkillType, IValueWatcher<int>> { [SkillType.Combat] = WatcherFactory.ForNetValue(player.combatLevel), @@ -70,11 +65,7 @@ namespace StardewModdingAPI.Framework.StateTracking }; // track watchers for convenience - this.Watchers.AddRange(new IWatcher[] - { - this.LocationWatcher, - this.MineLevelWatcher - }); + this.Watchers.Add(this.LocationWatcher); this.Watchers.AddRange(this.SkillWatchers.Values); } @@ -124,30 +115,6 @@ namespace StardewModdingAPI.Framework.StateTracking } } - /// <summary>Get the player skill levels which changed.</summary> - public IEnumerable<KeyValuePair<SkillType, IValueWatcher<int>>> GetChangedSkills() - { - return this.SkillWatchers.Where(p => p.Value.IsChanged); - } - - /// <summary>Get the player's new location if it changed.</summary> - /// <param name="location">The player's current location.</param> - /// <returns>Returns whether it changed.</returns> - public bool TryGetNewLocation(out GameLocation location) - { - location = this.LocationWatcher.CurrentValue; - return this.LocationWatcher.IsChanged; - } - - /// <summary>Get the player's new mine level if it changed.</summary> - /// <param name="mineLevel">The player's current mine level.</param> - /// <returns>Returns whether it changed.</returns> - public bool TryGetNewMineLevel(out int mineLevel) - { - mineLevel = this.MineLevelWatcher.CurrentValue; - return this.MineLevelWatcher.IsChanged; - } - /// <summary>Stop watching the player fields and release all references.</summary> public void Dispose() { diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs new file mode 100644 index 00000000..d3029540 --- /dev/null +++ b/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using StardewValley; +using StardewValley.Buildings; +using StardewValley.TerrainFeatures; + +namespace StardewModdingAPI.Framework.StateTracking.Snapshots +{ + /// <summary>A frozen snapshot of a tracked game location.</summary> + internal class LocationSnapshot + { + /********* + ** Accessors + *********/ + /// <summary>The tracked location.</summary> + public GameLocation Location { get; } + + /// <summary>Tracks added or removed buildings.</summary> + public SnapshotListDiff<Building> Buildings { get; } = new SnapshotListDiff<Building>(); + + /// <summary>Tracks added or removed debris.</summary> + public SnapshotListDiff<Debris> Debris { get; } = new SnapshotListDiff<Debris>(); + + /// <summary>Tracks added or removed large terrain features.</summary> + public SnapshotListDiff<LargeTerrainFeature> LargeTerrainFeatures { get; } = new SnapshotListDiff<LargeTerrainFeature>(); + + /// <summary>Tracks added or removed NPCs.</summary> + public SnapshotListDiff<NPC> Npcs { get; } = new SnapshotListDiff<NPC>(); + + /// <summary>Tracks added or removed objects.</summary> + public SnapshotListDiff<KeyValuePair<Vector2, Object>> Objects { get; } = new SnapshotListDiff<KeyValuePair<Vector2, Object>>(); + + /// <summary>Tracks added or removed terrain features.</summary> + public SnapshotListDiff<KeyValuePair<Vector2, TerrainFeature>> TerrainFeatures { get; } = new SnapshotListDiff<KeyValuePair<Vector2, TerrainFeature>>(); + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="location">The tracked location.</param> + public LocationSnapshot(GameLocation location) + { + this.Location = location; + } + + /// <summary>Update the tracked values.</summary> + /// <param name="watcher">The watcher to snapshot.</param> + public void Update(LocationTracker watcher) + { + this.Buildings.Update(watcher.BuildingsWatcher); + this.Debris.Update(watcher.DebrisWatcher); + this.LargeTerrainFeatures.Update(watcher.LargeTerrainFeaturesWatcher); + this.Npcs.Update(watcher.NpcsWatcher); + this.Objects.Update(watcher.ObjectsWatcher); + this.TerrainFeatures.Update(watcher.TerrainFeaturesWatcher); + } + } +} diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/PlayerSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/PlayerSnapshot.cs new file mode 100644 index 00000000..7bcd9f82 --- /dev/null +++ b/src/SMAPI/Framework/StateTracking/Snapshots/PlayerSnapshot.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using StardewModdingAPI.Enums; +using StardewModdingAPI.Events; +using StardewValley; + +namespace StardewModdingAPI.Framework.StateTracking.Snapshots +{ + /// <summary>A frozen snapshot of a tracked player.</summary> + internal class PlayerSnapshot + { + /********* + ** Accessors + *********/ + /// <summary>The player being tracked.</summary> + public Farmer Player { get; } + + /// <summary>The player's current location.</summary> + public SnapshotDiff<GameLocation> Location { get; } = new SnapshotDiff<GameLocation>(); + + /// <summary>Tracks changes to the player's skill levels.</summary> + public IDictionary<SkillType, SnapshotDiff<int>> Skills { get; } = + Enum + .GetValues(typeof(SkillType)) + .Cast<SkillType>() + .ToDictionary(skill => skill, skill => new SnapshotDiff<int>()); + + /// <summary>Get a list of inventory changes.</summary> + public IEnumerable<ItemStackChange> InventoryChanges { get; private set; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="player">The player being tracked.</param> + public PlayerSnapshot(Farmer player) + { + this.Player = player; + } + + /// <summary>Update the tracked values.</summary> + /// <param name="watcher">The player watcher to snapshot.</param> + public void Update(PlayerTracker watcher) + { + this.Location.Update(watcher.LocationWatcher); + foreach (var pair in this.Skills) + pair.Value.Update(watcher.SkillWatchers[pair.Key]); + this.InventoryChanges = watcher.GetInventoryChanges().ToArray(); + } + } +} diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/WatcherSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/WatcherSnapshot.cs new file mode 100644 index 00000000..cf51e040 --- /dev/null +++ b/src/SMAPI/Framework/StateTracking/Snapshots/WatcherSnapshot.cs @@ -0,0 +1,66 @@ +using Microsoft.Xna.Framework; +using StardewValley; +using StardewValley.Menus; + +namespace StardewModdingAPI.Framework.StateTracking.Snapshots +{ + /// <summary>A frozen snapshot of the game state watchers.</summary> + internal class WatcherSnapshot + { + /********* + ** Accessors + *********/ + /// <summary>Tracks changes to the window size.</summary> + public SnapshotDiff<Point> WindowSize { get; } = new SnapshotDiff<Point>(); + + /// <summary>Tracks changes to the current player.</summary> + public PlayerSnapshot CurrentPlayer { get; private set; } + + /// <summary>Tracks changes to the time of day (in 24-hour military format).</summary> + public SnapshotDiff<int> Time { get; } = new SnapshotDiff<int>(); + + /// <summary>Tracks changes to the save ID.</summary> + public SnapshotDiff<ulong> SaveID { get; } = new SnapshotDiff<ulong>(); + + /// <summary>Tracks changes to the game's locations.</summary> + public WorldLocationsSnapshot Locations { get; } = new WorldLocationsSnapshot(); + + /// <summary>Tracks changes to <see cref="Game1.activeClickableMenu"/>.</summary> + public SnapshotDiff<IClickableMenu> ActiveMenu { get; } = new SnapshotDiff<IClickableMenu>(); + + /// <summary>Tracks changes to the cursor position.</summary> + public SnapshotDiff<ICursorPosition> Cursor { get; } = new SnapshotDiff<ICursorPosition>(); + + /// <summary>Tracks changes to the mouse wheel scroll.</summary> + public SnapshotDiff<int> MouseWheelScroll { get; } = new SnapshotDiff<int>(); + + /// <summary>Tracks changes to the content locale.</summary> + public SnapshotDiff<LocalizedContentManager.LanguageCode> Locale { get; } = new SnapshotDiff<LocalizedContentManager.LanguageCode>(); + + + /********* + ** Public methods + *********/ + /// <summary>Update the tracked values.</summary> + /// <param name="watchers">The watchers to snapshot.</param> + public void Update(WatcherCore watchers) + { + // update player instance + if (watchers.CurrentPlayerTracker == null) + this.CurrentPlayer = null; + else if (watchers.CurrentPlayerTracker.Player != this.CurrentPlayer?.Player) + this.CurrentPlayer = new PlayerSnapshot(watchers.CurrentPlayerTracker.Player); + + // update snapshots + this.WindowSize.Update(watchers.WindowSizeWatcher); + this.Locale.Update(watchers.LocaleWatcher); + this.CurrentPlayer?.Update(watchers.CurrentPlayerTracker); + this.Time.Update(watchers.TimeWatcher); + this.SaveID.Update(watchers.SaveIdWatcher); + this.Locations.Update(watchers.LocationsWatcher); + this.ActiveMenu.Update(watchers.ActiveMenuWatcher); + this.Cursor.Update(watchers.CursorWatcher); + this.MouseWheelScroll.Update(watchers.MouseWheelScrollWatcher); + } + } +} diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/WorldLocationsSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/WorldLocationsSnapshot.cs new file mode 100644 index 00000000..73ed2d8f --- /dev/null +++ b/src/SMAPI/Framework/StateTracking/Snapshots/WorldLocationsSnapshot.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using System.Linq; +using StardewModdingAPI.Framework.StateTracking.Comparers; +using StardewValley; + +namespace StardewModdingAPI.Framework.StateTracking.Snapshots +{ + /// <summary>A frozen snapshot of the tracked game locations.</summary> + internal class WorldLocationsSnapshot + { + /********* + ** Fields + *********/ + /// <summary>A map of tracked locations.</summary> + private readonly Dictionary<GameLocation, LocationSnapshot> LocationsDict = new Dictionary<GameLocation, LocationSnapshot>(new ObjectReferenceComparer<GameLocation>()); + + + /********* + ** Accessors + *********/ + /// <summary>Tracks changes to the location list.</summary> + public SnapshotListDiff<GameLocation> LocationList { get; } = new SnapshotListDiff<GameLocation>(); + + /// <summary>The tracked locations.</summary> + public IEnumerable<LocationSnapshot> Locations => this.LocationsDict.Values; + + + /********* + ** Public methods + *********/ + /// <summary>Update the tracked values.</summary> + /// <param name="watcher">The watcher to snapshot.</param> + public void Update(WorldLocationsTracker watcher) + { + // update location list + this.LocationList.Update(watcher.IsLocationListChanged, watcher.Added, watcher.Removed); + + // remove missing locations + foreach (var key in this.LocationsDict.Keys.Where(key => !watcher.HasLocationTracker(key)).ToArray()) + this.LocationsDict.Remove(key); + + // update locations + foreach (LocationTracker locationWatcher in watcher.Locations) + { + if (!this.LocationsDict.TryGetValue(locationWatcher.Location, out LocationSnapshot snapshot)) + this.LocationsDict[locationWatcher.Location] = snapshot = new LocationSnapshot(locationWatcher.Location); + + snapshot.Update(locationWatcher); + } + } + } +} diff --git a/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs b/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs index f09c69c1..303a4f3a 100644 --- a/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs +++ b/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs @@ -117,6 +117,13 @@ namespace StardewModdingAPI.Framework.StateTracking watcher.Reset(); } + /// <summary>Get whether the given location is tracked.</summary> + /// <param name="location">The location to check.</param> + public bool HasLocationTracker(GameLocation location) + { + return this.LocationDict.ContainsKey(location); + } + /// <summary>Stop watching the player fields and release all references.</summary> public void Dispose() { |