summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework/StateTracking/LocationTracker.cs
blob: ff72a19b134152e368b05a0687f4f395bc11357a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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
{
    /// <summary>Tracks changes to a location's data.</summary>
    internal class LocationTracker : IWatcher
    {
        /*********
        ** Fields
        *********/
        /// <summary>The underlying watchers.</summary>
        private readonly List<IWatcher> Watchers = new();


        /*********
        ** Accessors
        *********/
        /// <summary>Whether the value changed since the last reset.</summary>
        public bool IsChanged => this.Watchers.Any(p => p.IsChanged);

        /// <summary>The tracked location.</summary>
        public GameLocation Location { get; }

        /// <summary>Tracks added or removed buildings.</summary>
        public ICollectionWatcher<Building> BuildingsWatcher { get; }

        /// <summary>Tracks added or removed debris.</summary>
        public ICollectionWatcher<Debris> DebrisWatcher { get; }

        /// <summary>Tracks added or removed large terrain features.</summary>
        public ICollectionWatcher<LargeTerrainFeature> LargeTerrainFeaturesWatcher { get; }

        /// <summary>Tracks added or removed NPCs.</summary>
        public ICollectionWatcher<NPC> NpcsWatcher { get; }

        /// <summary>Tracks added or removed objects.</summary>
        public IDictionaryWatcher<Vector2, SObject> ObjectsWatcher { get; }

        /// <summary>Tracks added or removed terrain features.</summary>
        public IDictionaryWatcher<Vector2, TerrainFeature> TerrainFeaturesWatcher { get; }

        /// <summary>Tracks added or removed furniture.</summary>
        public ICollectionWatcher<Furniture> FurnitureWatcher { get; }

        /// <summary>Tracks items added or removed to chests.</summary>
        public IDictionary<Vector2, ChestTracker> ChestWatchers { get; } = new Dictionary<Vector2, ChestTracker>();


        /*********
        ** Public methods
        *********/
        /// <summary>Construct an instance.</summary>
        /// <param name="location">The location to track.</param>
        public LocationTracker(GameLocation location)
        {
            this.Location = location;

            // init watchers
            this.BuildingsWatcher = location is BuildableGameLocation buildableLocation ? WatcherFactory.ForNetCollection(buildableLocation.buildings) : WatcherFactory.ForImmutableCollection<Building>();
            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<KeyValuePair<Vector2, SObject>>());
        }

        /// <summary>Update the current value if needed.</summary>
        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();
        }

        /// <summary>Set the current value as the baseline.</summary>
        public void Reset()
        {
            foreach (IWatcher watcher in this.Watchers)
                watcher.Reset();

            foreach (var watcher in this.ChestWatchers)
                watcher.Value.Reset();
        }

        /// <summary>Stop watching the player fields and release all references.</summary>
        public void Dispose()
        {
            foreach (IWatcher watcher in this.Watchers)
                watcher.Dispose();

            foreach (var watcher in this.ChestWatchers.Values)
                watcher.Dispose();
        }


        /*********
        ** Private methods
        *********/
        /// <summary>Update the watcher list for added or removed chests.</summary>
        /// <param name="added">The objects added to the location.</param>
        /// <param name="removed">The objects removed from the location.</param>
        private void UpdateChestWatcherList(IEnumerable<KeyValuePair<Vector2, SObject>> added, IEnumerable<KeyValuePair<Vector2, SObject>> 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));
            }
        }
    }
}