update NPC pathfinding cache when map warps change
This commit is contained in:
parent
bb88e42f54
commit
c39b2b1766
|
@ -13,9 +13,11 @@
|
||||||
* Disabled aggressive memory optimization (added in 3.9.2) by default. The option reduces errors for a subset of players who use certain mods, but may cause crashes for farmhands in multiplayer. You can edit `smapi-internal/config.json` to enable it if you experience frequent `OutOfMemoryException` errors.
|
* Disabled aggressive memory optimization (added in 3.9.2) by default. The option reduces errors for a subset of players who use certain mods, but may cause crashes for farmhands in multiplayer. You can edit `smapi-internal/config.json` to enable it if you experience frequent `OutOfMemoryException` errors.
|
||||||
|
|
||||||
* For mod authors:
|
* For mod authors:
|
||||||
* Added asset propagation for interior door sprites.
|
* Improved asset propagation:
|
||||||
|
* Added for interior door sprites.
|
||||||
|
* SMAPI now updates the NPC pathfinding cache when map warps are changed through the content API.
|
||||||
* Reduced performance impact of invalidating cached assets before a save is loaded.
|
* Reduced performance impact of invalidating cached assets before a save is loaded.
|
||||||
* Fixed assets changed by a mod not reapplied if playing in non-English, the changes are only applicable after the save is loaded, the player returns to title and reloads a save, and the game reloads the target asset before the save is loaded.
|
* Fixed asset changes not reapplied in the edge case where you're playing in non-English, and the changes are only applied after the save is loaded, and the player returns to title and reloads a save, and the game reloads the target asset before the save is loaded.
|
||||||
|
|
||||||
## 3.9.4
|
## 3.9.4
|
||||||
Released 07 March 2021 for Stardew Valley 1.5.4 or later.
|
Released 07 March 2021 for Stardew Valley 1.5.4 or later.
|
||||||
|
|
|
@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Microsoft.Xna.Framework.Content;
|
using Microsoft.Xna.Framework.Content;
|
||||||
using StardewModdingAPI.Framework.Content;
|
using StardewModdingAPI.Framework.Content;
|
||||||
|
@ -341,13 +342,31 @@ namespace StardewModdingAPI.Framework
|
||||||
// reload core game assets
|
// reload core game assets
|
||||||
if (removedAssets.Any())
|
if (removedAssets.Any())
|
||||||
{
|
{
|
||||||
IDictionary<string, bool> propagated = this.CoreAssets.Propagate(removedAssets.ToDictionary(p => p.Key, p => p.Value), ignoreWorld: Context.IsWorldFullyUnloaded);
|
// propagate changes to the game
|
||||||
|
this.CoreAssets.Propagate(
|
||||||
|
assets: removedAssets.ToDictionary(p => p.Key, p => p.Value),
|
||||||
|
ignoreWorld: Context.IsWorldFullyUnloaded,
|
||||||
|
out IDictionary<string, bool> propagated,
|
||||||
|
out bool updatedNpcWarps
|
||||||
|
);
|
||||||
|
|
||||||
|
// log summary
|
||||||
|
StringBuilder report = new StringBuilder();
|
||||||
|
{
|
||||||
string[] invalidatedKeys = removedAssets.Keys.ToArray();
|
string[] invalidatedKeys = removedAssets.Keys.ToArray();
|
||||||
string[] propagatedKeys = propagated.Where(p => p.Value).Select(p => p.Key).ToArray();
|
string[] propagatedKeys = propagated.Where(p => p.Value).Select(p => p.Key).ToArray();
|
||||||
|
|
||||||
string FormatKeyList(IEnumerable<string> keys) => string.Join(", ", keys.OrderBy(p => p, StringComparer.OrdinalIgnoreCase));
|
string FormatKeyList(IEnumerable<string> keys) => string.Join(", ", keys.OrderBy(p => p, StringComparer.OrdinalIgnoreCase));
|
||||||
this.Monitor.Log($"Invalidated {invalidatedKeys.Length} asset names ({FormatKeyList(invalidatedKeys)}); propagated {propagatedKeys.Length} core assets ({FormatKeyList(propagatedKeys)}).");
|
|
||||||
|
report.AppendLine($"Invalidated {invalidatedKeys.Length} asset names ({FormatKeyList(invalidatedKeys)}).");
|
||||||
|
report.AppendLine(propagated.Count > 0
|
||||||
|
? $"Propagated {propagatedKeys.Length} core assets ({FormatKeyList(propagatedKeys)})."
|
||||||
|
: "Propagated 0 core assets."
|
||||||
|
);
|
||||||
|
if (updatedNpcWarps)
|
||||||
|
report.AppendLine("Updated NPC pathfinding cache.");
|
||||||
|
}
|
||||||
|
this.Monitor.Log(report.ToString().TrimEnd());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
this.Monitor.Log("Invalidated 0 cache entries.");
|
this.Monitor.Log("Invalidated 0 cache entries.");
|
||||||
|
|
|
@ -482,6 +482,7 @@ namespace StardewModdingAPI.Framework
|
||||||
+ ")"
|
+ ")"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
+ "."
|
||||||
);
|
);
|
||||||
|
|
||||||
// reload affected assets
|
// reload affected assets
|
||||||
|
|
|
@ -80,8 +80,9 @@ namespace StardewModdingAPI.Metadata
|
||||||
/// <summary>Reload one of the game's core assets (if applicable).</summary>
|
/// <summary>Reload one of the game's core assets (if applicable).</summary>
|
||||||
/// <param name="assets">The asset keys and types to reload.</param>
|
/// <param name="assets">The asset keys and types to reload.</param>
|
||||||
/// <param name="ignoreWorld">Whether the in-game world is fully unloaded (e.g. on the title screen), so there's no need to propagate changes into the world.</param>
|
/// <param name="ignoreWorld">Whether the in-game world is fully unloaded (e.g. on the title screen), so there's no need to propagate changes into the world.</param>
|
||||||
/// <returns>Returns a lookup of asset names to whether they've been propagated.</returns>
|
/// <param name="propagatedAssets">A lookup of asset names to whether they've been propagated.</param>
|
||||||
public IDictionary<string, bool> Propagate(IDictionary<string, Type> assets, bool ignoreWorld)
|
/// <param name="updatedNpcWarps">Whether the NPC pathfinding cache was reloaded.</param>
|
||||||
|
public void Propagate(IDictionary<string, Type> assets, bool ignoreWorld, out IDictionary<string, bool> propagatedAssets, out bool updatedNpcWarps)
|
||||||
{
|
{
|
||||||
// group into optimized lists
|
// group into optimized lists
|
||||||
var buckets = assets.GroupBy(p =>
|
var buckets = assets.GroupBy(p =>
|
||||||
|
@ -96,28 +97,36 @@ namespace StardewModdingAPI.Metadata
|
||||||
});
|
});
|
||||||
|
|
||||||
// reload assets
|
// reload assets
|
||||||
IDictionary<string, bool> propagated = assets.ToDictionary(p => p.Key, _ => false, StringComparer.OrdinalIgnoreCase);
|
propagatedAssets = assets.ToDictionary(p => p.Key, _ => false, StringComparer.OrdinalIgnoreCase);
|
||||||
|
updatedNpcWarps = false;
|
||||||
foreach (var bucket in buckets)
|
foreach (var bucket in buckets)
|
||||||
{
|
{
|
||||||
switch (bucket.Key)
|
switch (bucket.Key)
|
||||||
{
|
{
|
||||||
case AssetBucket.Sprite:
|
case AssetBucket.Sprite:
|
||||||
if (!ignoreWorld)
|
if (!ignoreWorld)
|
||||||
this.ReloadNpcSprites(bucket.Select(p => p.Key), propagated);
|
this.ReloadNpcSprites(bucket.Select(p => p.Key), propagatedAssets);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AssetBucket.Portrait:
|
case AssetBucket.Portrait:
|
||||||
if (!ignoreWorld)
|
if (!ignoreWorld)
|
||||||
this.ReloadNpcPortraits(bucket.Select(p => p.Key), propagated);
|
this.ReloadNpcPortraits(bucket.Select(p => p.Key), propagatedAssets);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
foreach (var entry in bucket)
|
foreach (var entry in bucket)
|
||||||
propagated[entry.Key] = this.PropagateOther(entry.Key, entry.Value, ignoreWorld);
|
{
|
||||||
|
bool changed = this.PropagateOther(entry.Key, entry.Value, ignoreWorld, out bool curChangedMapWarps);
|
||||||
|
propagatedAssets[entry.Key] = changed;
|
||||||
|
updatedNpcWarps = updatedNpcWarps || curChangedMapWarps;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return propagated;
|
|
||||||
|
// reload NPC pathfinding cache if any map changed
|
||||||
|
if (updatedNpcWarps)
|
||||||
|
NPC.populateRoutesFromLocationToLocationList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -128,12 +137,14 @@ namespace StardewModdingAPI.Metadata
|
||||||
/// <param name="key">The asset key to reload.</param>
|
/// <param name="key">The asset key to reload.</param>
|
||||||
/// <param name="type">The asset type to reload.</param>
|
/// <param name="type">The asset type to reload.</param>
|
||||||
/// <param name="ignoreWorld">Whether the in-game world is fully unloaded (e.g. on the title screen), so there's no need to propagate changes into the world.</param>
|
/// <param name="ignoreWorld">Whether the in-game world is fully unloaded (e.g. on the title screen), so there's no need to propagate changes into the world.</param>
|
||||||
|
/// <param name="changedWarps">Whether any map warps were changed as part of this propagation.</param>
|
||||||
/// <returns>Returns whether an asset was loaded. The return value may be true or false, or a non-null value for true.</returns>
|
/// <returns>Returns whether an asset was loaded. The return value may be true or false, or a non-null value for true.</returns>
|
||||||
[SuppressMessage("ReSharper", "StringLiteralTypo", Justification = "These deliberately match the asset names.")]
|
[SuppressMessage("ReSharper", "StringLiteralTypo", Justification = "These deliberately match the asset names.")]
|
||||||
private bool PropagateOther(string key, Type type, bool ignoreWorld)
|
private bool PropagateOther(string key, Type type, bool ignoreWorld, out bool changedWarps)
|
||||||
{
|
{
|
||||||
var content = this.MainContentManager;
|
var content = this.MainContentManager;
|
||||||
key = this.AssertAndNormalizeAssetName(key);
|
key = this.AssertAndNormalizeAssetName(key);
|
||||||
|
changedWarps = false;
|
||||||
|
|
||||||
/****
|
/****
|
||||||
** Special case: current map tilesheet
|
** Special case: current map tilesheet
|
||||||
|
@ -162,7 +173,18 @@ namespace StardewModdingAPI.Metadata
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(location.mapPath.Value) && this.NormalizeAssetNameIgnoringEmpty(location.mapPath.Value) == key)
|
if (!string.IsNullOrWhiteSpace(location.mapPath.Value) && this.NormalizeAssetNameIgnoringEmpty(location.mapPath.Value) == key)
|
||||||
{
|
{
|
||||||
|
static ISet<string> GetWarpSet(GameLocation location)
|
||||||
|
{
|
||||||
|
return new HashSet<string>(
|
||||||
|
location.warps.Select(p => $"{p.X} {p.Y} {p.TargetName} {p.TargetX} {p.TargetY}")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldWarps = GetWarpSet(location);
|
||||||
this.ReloadMap(location);
|
this.ReloadMap(location);
|
||||||
|
var newWarps = GetWarpSet(location);
|
||||||
|
|
||||||
|
changedWarps = changedWarps || oldWarps.Count != newWarps.Count || oldWarps.Any(p => !newWarps.Contains(p));
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue