diff options
-rw-r--r-- | docs/release-notes.md | 1 | ||||
-rw-r--r-- | src/SMAPI/Framework/ContentCoordinator.cs | 16 | ||||
-rw-r--r-- | src/SMAPI/Framework/ContentManagers/BaseContentManager.cs | 14 | ||||
-rw-r--r-- | src/SMAPI/Framework/ContentManagers/IContentManager.cs | 4 | ||||
-rw-r--r-- | src/SMAPI/Metadata/CoreAssetPropagator.cs | 30 |
5 files changed, 46 insertions, 19 deletions
diff --git a/docs/release-notes.md b/docs/release-notes.md index df8f6676..95c822bd 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,6 +4,7 @@ * Fixed cryptic error when running the installer from inside a zip file on Windows. * For modders: + * Reloading a map asset will now automatically update affected locations. * Fixed newlines in most manifest fields not being ignored. * For the web UI: diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 9eb7b5f9..08a32a9b 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -238,28 +238,30 @@ namespace StardewModdingAPI.Framework public IEnumerable<string> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false) { // invalidate cache - HashSet<string> removedAssetNames = new HashSet<string>(); + IDictionary<string, Type> removedAssetNames = new Dictionary<string, Type>(StringComparer.InvariantCultureIgnoreCase); foreach (IContentManager contentManager in this.ContentManagers) { - foreach (string name in contentManager.InvalidateCache(predicate, dispose)) - removedAssetNames.Add(name); + foreach (Tuple<string, Type> asset in contentManager.InvalidateCache(predicate, dispose)) + removedAssetNames[asset.Item1] = asset.Item2; } // reload core game assets int reloaded = 0; - foreach (string key in removedAssetNames) + foreach (var pair in removedAssetNames) { - if (this.CoreAssets.Propagate(this.MainContentManager, key)) // use an intercepted content manager + string key = pair.Key; + Type type = pair.Value; + if (this.CoreAssets.Propagate(this.MainContentManager, key, type)) // use an intercepted content manager reloaded++; } // report result if (removedAssetNames.Any()) - this.Monitor.Log($"Invalidated {removedAssetNames.Count} asset names: {string.Join(", ", removedAssetNames.OrderBy(p => p, StringComparer.InvariantCultureIgnoreCase))}. Reloaded {reloaded} core assets.", LogLevel.Trace); + this.Monitor.Log($"Invalidated {removedAssetNames.Count} asset names: {string.Join(", ", removedAssetNames.Keys.OrderBy(p => p, StringComparer.InvariantCultureIgnoreCase))}. Reloaded {reloaded} core assets.", LogLevel.Trace); else this.Monitor.Log("Invalidated 0 cache entries.", LogLevel.Trace); - return removedAssetNames; + return removedAssetNames.Keys; } /// <summary>Dispose held resources.</summary> diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index 18aae05b..ed08f11c 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -200,23 +200,25 @@ namespace StardewModdingAPI.Framework.ContentManagers /// <summary>Purge matched assets from the cache.</summary> /// <param name="predicate">Matches the asset keys to invalidate.</param> /// <param name="dispose">Whether to dispose invalidated assets. This should only be <c>true</c> when they're being invalidated as part of a dispose, to avoid crashing the game.</param> - /// <returns>Returns the number of invalidated assets.</returns> - public IEnumerable<string> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false) + /// <returns>Returns the invalidated asset names and types.</returns> + public IEnumerable<Tuple<string, Type>> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false) { - HashSet<string> removeAssetNames = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase); + Dictionary<string, Type> removeAssetNames = new Dictionary<string, Type>(StringComparer.InvariantCultureIgnoreCase); this.Cache.Remove((key, type) => { this.ParseCacheKey(key, out string assetName, out _); - if (removeAssetNames.Contains(assetName) || predicate(assetName, type)) + if (removeAssetNames.ContainsKey(assetName)) + return true; + if (predicate(assetName, type)) { - removeAssetNames.Add(assetName); + removeAssetNames[assetName] = type; return true; } return false; }); - return removeAssetNames; + return removeAssetNames.Select(p => Tuple.Create(p.Key, p.Value)); } /// <summary>Dispose held resources.</summary> diff --git a/src/SMAPI/Framework/ContentManagers/IContentManager.cs b/src/SMAPI/Framework/ContentManagers/IContentManager.cs index 1eb8b0ac..17618edd 100644 --- a/src/SMAPI/Framework/ContentManagers/IContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/IContentManager.cs @@ -80,7 +80,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// <summary>Purge matched assets from the cache.</summary> /// <param name="predicate">Matches the asset keys to invalidate.</param> /// <param name="dispose">Whether to dispose invalidated assets. This should only be <c>true</c> when they're being invalidated as part of a dispose, to avoid crashing the game.</param> - /// <returns>Returns the number of invalidated assets.</returns> - IEnumerable<string> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false); + /// <returns>Returns the invalidated asset names and types.</returns> + IEnumerable<Tuple<string, Type>> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false); } } diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 8487b6be..841e6d64 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -13,6 +13,7 @@ using StardewValley.Menus; using StardewValley.Objects; using StardewValley.Projectiles; using StardewValley.TerrainFeatures; +using xTile; using xTile.Tiles; namespace StardewModdingAPI.Metadata @@ -45,10 +46,11 @@ namespace StardewModdingAPI.Metadata /// <summary>Reload one of the game's core assets (if applicable).</summary> /// <param name="content">The content manager through which to reload the asset.</param> /// <param name="key">The asset key to reload.</param> + /// <param name="type">The asset type to reload.</param> /// <returns>Returns whether an asset was reloaded.</returns> - public bool Propagate(LocalizedContentManager content, string key) + public bool Propagate(LocalizedContentManager content, string key, Type type) { - object result = this.PropagateImpl(content, key); + object result = this.PropagateImpl(content, key, type); if (result is bool b) return b; return result != null; @@ -61,9 +63,12 @@ namespace StardewModdingAPI.Metadata /// <summary>Reload one of the game's core assets (if applicable).</summary> /// <param name="content">The content manager through which to reload the asset.</param> /// <param name="key">The asset key to reload.</param> - /// <returns>Returns any non-null value to indicate an asset was loaded.</returns> - private object PropagateImpl(LocalizedContentManager content, string key) + /// <param name="type">The asset type to reload.</param> + /// <returns>Returns whether an asset was loaded. The return value may be true or false, or a non-null value for true.</returns> + private object PropagateImpl(LocalizedContentManager content, string key, Type type) { + key = this.GetNormalisedPath(key); + /**** ** Special case: current map tilesheet ** We only need to do this for the current location, since tilesheets are reloaded when you enter a location. @@ -79,6 +84,23 @@ namespace StardewModdingAPI.Metadata } /**** + ** Propagate map changes + ****/ + if (type == typeof(Map)) + { + bool anyChanged = false; + foreach (GameLocation location in this.GetLocations()) + { + if (this.GetNormalisedPath(location.mapPath.Value) == key) + { + this.Reflection.GetMethod(location, "reloadMap").Invoke(); + anyChanged = true; + } + } + return anyChanged; + } + + /**** ** Propagate by key ****/ Reflector reflection = this.Reflection; |