blob: 5a25966343c8b70b8969386f259074558f2670ff (
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
|
using System;
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
{
/// <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>(new ObjectReferenceComparer<GameLocation>());
/// <summary>A lookup of registered buildings and their indoor location.</summary>
private readonly IDictionary<Building, GameLocation> BuildingIndoors = new Dictionary<Building, GameLocation>(new ObjectReferenceComparer<Building>());
/*********
** 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>(new ObjectReferenceComparer<GameLocation>());
/// <summary>The locations added since the last update.</summary>
public ICollection<GameLocation> Removed { get; } = new HashSet<GameLocation>(new ObjectReferenceComparer<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);
}
}
}
}
|