summaryrefslogtreecommitdiff
path: root/src/SMAPI
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2022-05-22 14:38:57 -0400
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2022-05-22 14:38:57 -0400
commit2ab2182645179129997eac3fccb63f6f0683dbe1 (patch)
treeddea4c6e4531a10c698d9757a57ae24c6732bff7 /src/SMAPI
parent5731b015a0c548ac72e0d7ce9c4153aa52da3562 (diff)
parent336cc1cc0f250c96ee23d45e1e08569b67a2e562 (diff)
downloadSMAPI-2ab2182645179129997eac3fccb63f6f0683dbe1.tar.gz
SMAPI-2ab2182645179129997eac3fccb63f6f0683dbe1.tar.bz2
SMAPI-2ab2182645179129997eac3fccb63f6f0683dbe1.zip
Merge branch 'develop' into stable
Diffstat (limited to 'src/SMAPI')
-rw-r--r--src/SMAPI/Constants.cs2
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs36
-rw-r--r--src/SMAPI/Framework/ContentManagers/BaseContentManager.cs27
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs2
-rw-r--r--src/SMAPI/Framework/ContentManagers/IContentManager.cs11
-rw-r--r--src/SMAPI/Framework/Utilities/TickCacheDictionary.cs26
-rw-r--r--src/SMAPI/Metadata/CoreAssetPropagator.cs607
7 files changed, 407 insertions, 304 deletions
diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs
index 357b8db8..b2916a8d 100644
--- a/src/SMAPI/Constants.cs
+++ b/src/SMAPI/Constants.cs
@@ -50,7 +50,7 @@ namespace StardewModdingAPI
internal static int? LogScreenId { get; set; }
/// <summary>SMAPI's current raw semantic version.</summary>
- internal static string RawApiVersion = "3.14.4";
+ internal static string RawApiVersion = "3.14.5";
}
/// <summary>Contains SMAPI's constants and assumptions.</summary>
diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs
index 2b13f57a..fc61b44b 100644
--- a/src/SMAPI/Framework/ContentCoordinator.cs
+++ b/src/SMAPI/Framework/ContentCoordinator.cs
@@ -151,8 +151,23 @@ namespace StardewModdingAPI.Framework
onAssetLoaded: onAssetLoaded
)
);
+
+ var contentManagerForAssetPropagation = new GameContentManagerForAssetPropagation(
+ name: nameof(GameContentManagerForAssetPropagation),
+ serviceProvider: serviceProvider,
+ rootDirectory: rootDirectory,
+ currentCulture: currentCulture,
+ coordinator: this,
+ monitor: monitor,
+ reflection: reflection,
+ onDisposing: this.OnDisposing,
+ onLoadingFirstAsset: onLoadingFirstAsset,
+ onAssetLoaded: onAssetLoaded
+ );
+ this.ContentManagers.Add(contentManagerForAssetPropagation);
+
this.VanillaContentManager = new LocalizedContentManager(serviceProvider, rootDirectory);
- this.CoreAssets = new CoreAssetPropagator(this.MainContentManager, this.Monitor, reflection, name => this.ParseAssetName(name, allowLocales: true));
+ this.CoreAssets = new CoreAssetPropagator(this.MainContentManager, contentManagerForAssetPropagation, this.Monitor, reflection, name => this.ParseAssetName(name, allowLocales: true));
this.LocaleCodes = new Lazy<Dictionary<string, LocalizedContentManager.LanguageCode>>(() => this.GetLocaleCodes(customLanguages: Enumerable.Empty<ModLanguage>()));
}
@@ -379,14 +394,31 @@ namespace StardewModdingAPI.Framework
// cached assets
foreach (IContentManager contentManager in this.ContentManagers)
{
- foreach ((string key, object asset) in contentManager.InvalidateCache((key, type) => predicate(contentManager, key, type), dispose))
+ foreach ((string key, object asset) in contentManager.GetCachedAssets())
{
+ if (!predicate(contentManager, key, asset.GetType()))
+ continue;
+
AssetName assetName = this.ParseAssetName(key, allowLocales: true);
+ contentManager.InvalidateCache(assetName, dispose);
+
if (!invalidatedAssets.ContainsKey(assetName))
invalidatedAssets[assetName] = asset.GetType();
}
}
+ // forget localized flags
+ // A mod might provide a localized variant of a normally non-localized asset (like
+ // `Maps/MovieTheater.fr-FR`). When the asset is invalidated, we need to recheck
+ // whether the asset is localized in case it stops providing it.
+ foreach (IAssetName assetName in invalidatedAssets.Keys)
+ {
+ LocalizedContentManager.localizedAssetNames.Remove(assetName.Name);
+
+ if (LocalizedContentManager.localizedAssetNames.TryGetValue(assetName.BaseName, out string? targetForBaseKey) && targetForBaseKey == assetName.Name)
+ LocalizedContentManager.localizedAssetNames.Remove(assetName.BaseName);
+ }
+
// special case: maps may be loaded through a temporary content manager that's removed while the map is still in use.
// This notably affects the town and farmhouse maps.
if (Game1.locations != null)
diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
index 575d252e..ddc02a8c 100644
--- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
@@ -231,24 +231,21 @@ namespace StardewModdingAPI.Framework.ContentManagers
** Cache invalidation
****/
/// <inheritdoc />
- public IDictionary<string, object> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false)
+ public IEnumerable<KeyValuePair<string, object>> GetCachedAssets()
{
- IDictionary<string, object> removeAssets = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
- this.Cache.Remove((key, asset) =>
- {
- string baseAssetName = this.Coordinator.ParseAssetName(key, allowLocales: this.TryLocalizeKeys).BaseName;
+ foreach (string key in this.Cache.Keys)
+ yield return new(key, this.Cache[key]);
+ }
- // check if asset should be removed
- bool remove = removeAssets.ContainsKey(baseAssetName);
- if (!remove && predicate(baseAssetName, asset.GetType()))
- {
- removeAssets[baseAssetName] = asset;
- remove = true;
- }
- return remove;
- }, dispose);
+ /// <inheritdoc />
+ public bool InvalidateCache(IAssetName assetName, bool dispose = false)
+ {
+ if (!this.Cache.ContainsKey(assetName.Name))
+ return false;
- return removeAssets;
+ // remove from cache
+ this.Cache.Remove(assetName.Name, dispose);
+ return true;
}
/// <inheritdoc />
diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
index 1c603f85..4390d472 100644
--- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
@@ -75,7 +75,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
// custom asset from a loader
string locale = this.GetLocale();
IAssetInfo info = new AssetInfo(locale, assetName, typeof(T), this.AssertAndNormalizeAssetName);
- AssetOperationGroup? operations = this.Coordinator.GetAssetOperations<object>(info);
+ AssetOperationGroup? operations = this.Coordinator.GetAssetOperations<T>(info);
if (operations?.LoadOperations.Count > 0)
{
if (!this.AssertMaxOneRequiredLoader(info, operations.LoadOperations, out string? error))
diff --git a/src/SMAPI/Framework/ContentManagers/IContentManager.cs b/src/SMAPI/Framework/ContentManagers/IContentManager.cs
index ac67cad5..f2e3b9f0 100644
--- a/src/SMAPI/Framework/ContentManagers/IContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/IContentManager.cs
@@ -32,7 +32,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <typeparam name="T">The expected asset type.</typeparam>
/// <param name="assetName">The normalized asset name.</param>
bool DoesAssetExist<T>(IAssetName assetName)
- where T: notnull;
+ where T : notnull;
/// <summary>Load an asset through the content pipeline, using a localized variant of the <paramref name="assetName"/> if available.</summary>
/// <typeparam name="T">The type of asset to load.</typeparam>
@@ -65,10 +65,13 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
bool IsLoaded(IAssetName assetName);
+ /// <summary>Get all assets in the cache.</summary>
+ IEnumerable<KeyValuePair<string, object>> GetCachedAssets();
+
/// <summary>Purge matched assets from the cache.</summary>
- /// <param name="predicate">Matches the asset keys to invalidate.</param>
+ /// <param name="assetName">The asset name to dispose.</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 invalidated asset names and instances.</returns>
- IDictionary<string, object> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false);
+ /// <returns>Returns whether the asset was in the cache.</returns>
+ bool InvalidateCache(IAssetName assetName, bool dispose = false);
}
}
diff --git a/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs b/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs
index 20d206e2..7732ace8 100644
--- a/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs
+++ b/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs
@@ -48,4 +48,30 @@ namespace StardewModdingAPI.Framework.Utilities
return this.Cache.Remove(cacheKey);
}
}
+
+ /// <summary>An in-memory dictionary cache that stores data for the duration of a game update tick.</summary>
+ /// <typeparam name="TKey">The dictionary key type.</typeparam>
+ internal class TickCacheDictionary<TKey> : TickCacheDictionary<TKey, object>
+ where TKey : notnull
+ {
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Get a value from the cache, fetching it first if it's not cached yet.</summary>
+ /// <param name="cacheKey">The unique key for the cached value.</param>
+ /// <param name="get">Get the latest data if it's not in the cache yet.</param>
+ public TValue GetOrSet<TValue>(TKey cacheKey, Func<TValue> get)
+ {
+ object? value = base.GetOrSet(cacheKey, () => get()!);
+
+ try
+ {
+ return (TValue)value;
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidCastException($"Can't cast value of the '{cacheKey}' cache entry from {value?.GetType().FullName ?? "null"} to {typeof(TValue).FullName}.", ex);
+ }
+ }
+ }
}
diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs
index e014f9a9..8ed6b591 100644
--- a/src/SMAPI/Metadata/CoreAssetPropagator.cs
+++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs
@@ -5,7 +5,9 @@ using System.IO;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
+using StardewModdingAPI.Framework.ContentManagers;
using StardewModdingAPI.Framework.Reflection;
+using StardewModdingAPI.Framework.Utilities;
using StardewModdingAPI.Internal;
using StardewModdingAPI.Toolkit.Utilities;
using StardewValley;
@@ -32,6 +34,9 @@ namespace StardewModdingAPI.Metadata
/// <summary>The main content manager through which to reload assets.</summary>
private readonly LocalizedContentManager MainContentManager;
+ /// <summary>An internal content manager used only for asset propagation. See remarks on <see cref="GameContentManagerForAssetPropagation"/>.</summary>
+ private readonly GameContentManagerForAssetPropagation DisposableContentManager;
+
/// <summary>Writes messages to the console.</summary>
private readonly IMonitor Monitor;
@@ -54,18 +59,23 @@ namespace StardewModdingAPI.Metadata
Other
};
+ /// <summary>A cache of world data fetched for the current tick.</summary>
+ private readonly TickCacheDictionary<string> WorldCache = new();
+
/*********
** Public methods
*********/
/// <summary>Initialize the core asset data.</summary>
/// <param name="mainContent">The main content manager through which to reload assets.</param>
+ /// <param name="disposableContent">An internal content manager used only for asset propagation.</param>
/// <param name="monitor">Writes messages to the console.</param>
/// <param name="reflection">Simplifies access to private code.</param>
/// <param name="parseAssetName">Parse a raw asset name.</param>
- public CoreAssetPropagator(LocalizedContentManager mainContent, IMonitor monitor, Reflector reflection, Func<string, IAssetName> parseAssetName)
+ public CoreAssetPropagator(LocalizedContentManager mainContent, GameContentManagerForAssetPropagation disposableContent, IMonitor monitor, Reflector reflection, Func<string, IAssetName> parseAssetName)
{
this.MainContentManager = mainContent;
+ this.DisposableContentManager = disposableContent;
this.Monitor = monitor;
this.Reflection = reflection;
this.ParseAssetName = parseAssetName;
@@ -104,12 +114,12 @@ namespace StardewModdingAPI.Metadata
{
case AssetBucket.Sprite:
if (!ignoreWorld)
- this.ReloadNpcSprites(propagatedAssets);
+ this.UpdateNpcSprites(propagatedAssets);
break;
case AssetBucket.Portrait:
if (!ignoreWorld)
- this.ReloadNpcPortraits(propagatedAssets);
+ this.UpdateNpcPortraits(propagatedAssets);
break;
default:
@@ -192,7 +202,7 @@ namespace StardewModdingAPI.Metadata
}
var oldWarps = GetWarpSet(location);
- this.ReloadMap(info);
+ this.UpdateMap(info);
var newWarps = GetWarpSet(location);
changedWarps = changedWarps || oldWarps.Count != newWarps.Count || oldWarps.Any(p => !newWarps.Contains(p));
@@ -213,7 +223,7 @@ namespace StardewModdingAPI.Metadata
** Animals
****/
case "animals/horse":
- return !ignoreWorld && this.ReloadPetOrHorseSprites<Horse>(content, assetName);
+ return !ignoreWorld && this.UpdatePetOrHorseSprites<Horse>(assetName);
/****
** Buildings
@@ -243,7 +253,7 @@ namespace StardewModdingAPI.Metadata
case "characters/farmer/farmer_base_bald":
case "characters/farmer/farmer_girl_base":
case "characters/farmer/farmer_girl_base_bald":
- return !ignoreWorld && this.ReloadPlayerSprites(assetName);
+ return !ignoreWorld && this.UpdatePlayerSprites(assetName);
case "characters/farmer/hairstyles": // Game1.LoadContent
FarmerRenderer.hairStylesTexture = this.LoadTexture(key);
@@ -295,10 +305,10 @@ namespace StardewModdingAPI.Metadata
return true;
case "data/farmanimals": // FarmAnimal constructor
- return !ignoreWorld && this.ReloadFarmAnimalData();
+ return !ignoreWorld && this.UpdateFarmAnimalData();
case "data/hairdata": // Farmer.GetHairStyleMetadataFile
- return this.ReloadHairData();
+ return this.UpdateHairData();
case "data/movies": // MovieTheater.GetMovieData
case "data/moviesreactions": // MovieTheater.GetMovieReactions
@@ -306,7 +316,7 @@ namespace StardewModdingAPI.Metadata
return true;
case "data/npcdispositions": // NPC constructor
- return !ignoreWorld && this.ReloadNpcDispositions(content, assetName);
+ return !ignoreWorld && this.UpdateNpcDispositions(content, assetName);
case "data/npcgifttastes": // Game1.LoadContent
Game1.NPCGiftTastes = content.Load<Dictionary<string, string>>(key);
@@ -386,7 +396,7 @@ namespace StardewModdingAPI.Metadata
}
if (!ignoreWorld)
- this.ReloadDoorSprites(content, assetName);
+ this.UpdateDoorSprites(content, assetName);
return true;
case "loosesprites/cursors2": // Game1.LoadContent
@@ -418,7 +428,7 @@ namespace StardewModdingAPI.Metadata
return true;
case "loosesprites/suspensionbridge": // SuspensionBridge constructor
- return !ignoreWorld && this.ReloadSuspensionBridges(content, assetName);
+ return !ignoreWorld && this.UpdateSuspensionBridges(content, assetName);
/****
** Content\Maps
@@ -449,13 +459,13 @@ namespace StardewModdingAPI.Metadata
return false;
case "minigames/titlebuttons": // TitleMenu
- return this.ReloadTitleButtons(content, assetName);
+ return this.UpdateTitleButtons(content, assetName);
/****
** Content\Strings
****/
case "strings/stringsfromcsfiles":
- return this.ReloadStringsFromCsFiles(content);
+ return this.UpdateStringsFromCsFiles(content);
/****
** Content\TileSheets
@@ -473,14 +483,14 @@ namespace StardewModdingAPI.Metadata
return true;
case "tilesheets/chairtiles": // Game1.LoadContent
- return this.ReloadChairTiles(content, assetName, ignoreWorld);
+ return this.UpdateChairTiles(content, assetName, ignoreWorld);
case "tilesheets/craftables": // Game1.LoadContent
Game1.bigCraftableSpriteSheet = content.Load<Texture2D>(key);
return true;
case "tilesheets/critters": // Critter constructor
- return !ignoreWorld && this.ReloadCritterTextures(content, assetName) > 0;
+ return !ignoreWorld && this.UpdateCritterTextures(assetName);
case "tilesheets/crops": // Game1.LoadContent
Game1.cropSpriteSheet = content.Load<Texture2D>(key);
@@ -534,7 +544,7 @@ namespace StardewModdingAPI.Metadata
return true;
case "terrainfeatures/grass": // from Grass
- return !ignoreWorld && this.ReloadGrassTextures(content, assetName);
+ return !ignoreWorld && this.UpdateGrassTextures(content, assetName);
case "terrainfeatures/hoedirt": // from HoeDirt
HoeDirt.lightTexture = content.Load<Texture2D>(key);
@@ -549,27 +559,27 @@ namespace StardewModdingAPI.Metadata
return true;
case "terrainfeatures/mushroom_tree": // from Tree
- return !ignoreWorld && this.ReloadTreeTextures(content, assetName, Tree.mushroomTree);
+ return !ignoreWorld && this.UpdateTreeTextures(Tree.mushroomTree);
case "terrainfeatures/tree_palm": // from Tree
- return !ignoreWorld && this.ReloadTreeTextures(content, assetName, Tree.palmTree);
+ return !ignoreWorld && this.UpdateTreeTextures(Tree.palmTree);
case "terrainfeatures/tree1_fall": // from Tree
case "terrainfeatures/tree1_spring": // from Tree
case "terrainfeatures/tree1_summer": // from Tree
case "terrainfeatures/tree1_winter": // from Tree
- return !ignoreWorld && this.ReloadTreeTextures(content, assetName, Tree.bushyTree);
+ return !ignoreWorld && this.UpdateTreeTextures(Tree.bushyTree);
case "terrainfeatures/tree2_fall": // from Tree
case "terrainfeatures/tree2_spring": // from Tree
case "terrainfeatures/tree2_summer": // from Tree
case "terrainfeatures/tree2_winter": // from Tree
- return !ignoreWorld && this.ReloadTreeTextures(content, assetName, Tree.leafyTree);
+ return !ignoreWorld && this.UpdateTreeTextures(Tree.leafyTree);
case "terrainfeatures/tree3_fall": // from Tree
case "terrainfeatures/tree3_spring": // from Tree
case "terrainfeatures/tree3_winter": // from Tree
- return !ignoreWorld && this.ReloadTreeTextures(content, assetName, Tree.pineTree);
+ return !ignoreWorld && this.UpdateTreeTextures(Tree.pineTree);
}
/****
@@ -579,24 +589,24 @@ namespace StardewModdingAPI.Metadata
{
// dynamic textures
if (assetName.StartsWith("animals/cat"))
- return this.ReloadPetOrHorseSprites<Cat>(content, assetName);
+ return this.UpdatePetOrHorseSprites<Cat>(assetName);
if (assetName.StartsWith("animals/dog"))
- return this.ReloadPetOrHorseSprites<Dog>(content, assetName);
+ return this.UpdatePetOrHorseSprites<Dog>(assetName);
if (assetName.IsDirectlyUnderPath("Animals"))
- return this.ReloadFarmAnimalSprites(content, assetName);
+ return this.UpdateFarmAnimalSprites(assetName);
if (assetName.IsDirectlyUnderPath("Buildings"))
- return this.ReloadBuildings(assetName);
+ return this.UpdateBuildings(assetName);
if (assetName.StartsWith("LooseSprites/Fence"))
- return this.ReloadFenceTextures(assetName);
+ return this.UpdateFenceTextures(assetName);
// dynamic data
if (assetName.IsDirectlyUnderPath("Characters/Dialogue"))
- return this.ReloadNpcDialogue(assetName);
+ return this.UpdateNpcDialogue(assetName);
if (assetName.IsDirectlyUnderPath("Characters/schedules"))
- return this.ReloadNpcSchedules(assetName);
+ return this.UpdateNpcSchedules(assetName);
}
return false;
@@ -607,14 +617,14 @@ namespace StardewModdingAPI.Metadata
** Private methods
*********/
/****
- ** Reload texture methods
+ ** Update texture methods
****/
- /// <summary>Reload buttons on the title screen.</summary>
- /// <param name="content">The content manager through which to reload the asset.</param>
- /// <param name="assetName">The asset name to reload.</param>
- /// <returns>Returns whether any textures were reloaded.</returns>
+ /// <summary>Update buttons on the title screen.</summary>
+ /// <param name="content">The content manager through which to update the asset.</param>
+ /// <param name="assetName">The asset name to update.</param>
+ /// <returns>Returns whether any references were updated.</returns>
/// <remarks>Derived from the <see cref="TitleMenu"/> constructor and <see cref="TitleMenu.setUpIcons"/>.</remarks>
- private bool ReloadTitleButtons(LocalizedContentManager content, IAssetName assetName)
+ private bool UpdateTitleButtons(LocalizedContentManager content, IAssetName assetName)
{
if (Game1.activeClickableMenu is TitleMenu titleMenu)
{
@@ -635,35 +645,31 @@ namespace StardewModdingAPI.Metadata
return false;
}
- /// <summary>Reload the sprites for matching pets or horses.</summary>
+ /// <summary>Update the sprites for matching pets or horses.</summary>
/// <typeparam name="TAnimal">The animal type.</typeparam>
- /// <param name="content">The content manager through which to reload the asset.</param>
- /// <param name="assetName">The asset name to reload.</param>
- /// <returns>Returns whether any textures were reloaded.</returns>
- private bool ReloadPetOrHorseSprites<TAnimal>(LocalizedContentManager content, IAssetName assetName)
+ /// <param name="assetName">The asset name to update.</param>
+ /// <returns>Returns whether any references were updated.</returns>
+ private bool UpdatePetOrHorseSprites<TAnimal>(IAssetName assetName)
where TAnimal : NPC
{
// find matches
TAnimal[] animals = this.GetCharacters()
.OfType<TAnimal>()
- .Where(p => this.IsSameBaseName(assetName, p.Sprite?.Texture?.Name))
+ .Where(p => this.IsSameBaseName(assetName, p.Sprite?.spriteTexture?.Name))
.ToArray();
- if (!animals.Any())
- return false;
// update sprites
- Texture2D texture = content.Load<Texture2D>(assetName.BaseName);
+ bool changed = false;
foreach (TAnimal animal in animals)
- animal.Sprite.spriteTexture = texture;
- return true;
+ changed |= this.MarkSpriteDirty(animal.Sprite);
+ return changed;
}
- /// <summary>Reload the sprites for matching farm animals.</summary>
- /// <param name="content">The content manager through which to reload the asset.</param>
- /// <param name="assetName">The asset name to reload.</param>
- /// <returns>Returns whether any textures were reloaded.</returns>
+ /// <summary>Update the sprites for matching farm animals.</summary>
+ /// <param name="assetName">The asset name to update.</param>
+ /// <returns>Returns whether any references were updated.</returns>
/// <remarks>Derived from <see cref="FarmAnimal.reload"/>.</remarks>
- private bool ReloadFarmAnimalSprites(LocalizedContentManager content, IAssetName assetName)
+ private bool UpdateFarmAnimalSprites(IAssetName assetName)
{
// find matches
FarmAnimal[] animals = this.GetFarmAnimals().ToArray();
@@ -671,7 +677,7 @@ namespace StardewModdingAPI.Metadata
return false;
// update sprites
- Lazy<Texture2D> texture = new Lazy<Texture2D>(() => content.Load<Texture2D>(assetName.BaseName));
+ bool changed = true;
foreach (FarmAnimal animal in animals)
{
// get expected key
@@ -684,15 +690,15 @@ namespace StardewModdingAPI.Metadata
// reload asset
if (this.IsSameBaseName(assetName, expectedKey))
- animal.Sprite.spriteTexture = texture.Value;
+ changed |= this.MarkSpriteDirty(animal.Sprite);
}
- return texture.IsValueCreated;
+ return changed;
}
- /// <summary>Reload building textures.</summary>
- /// <param name="assetName">The asset name to reload.</param>
- /// <returns>Returns whether any textures were reloaded.</returns>
- private bool ReloadBuildings(IAssetName assetName)
+ /// <summary>Update building textures.</summary>
+ /// <param name="assetName">The asset name to update.</param>
+ /// <returns>Returns whether any references were updated.</returns>
+ private bool UpdateBuildings(IAssetName assetName)
{
// get paint mask info
const string paintMaskSuffix = "_PaintMask";
@@ -701,7 +707,7 @@ namespace StardewModdingAPI.Metadata
// get building type
string type = Path.GetFileName(assetName.BaseName);
if (isPaintMask)
- type = type.Substring(0, type.Length - paintMaskSuffix.Length);
+ type = type[..^paintMaskSuffix.Length];
// get buildings
Building[] buildings = this.GetLocations(buildingInteriors: false)
@@ -725,12 +731,12 @@ namespace StardewModdingAPI.Metadata
return removedFromCache;
}
- /// <summary>Reload map seat textures.</summary>
+ /// <summary>Update map seat textures.</summary>
/// <param name="content">The content manager through which to reload the asset.</param>
- /// <param name="assetName">The asset name to reload.</param>
+ /// <param name="assetName">The asset name to update.</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 whether any textures were reloaded.</returns>
- private bool ReloadChairTiles(LocalizedContentManager content, IAssetName assetName, bool ignoreWorld)
+ /// <returns>Returns whether any references were updated.</returns>
+ private bool UpdateChairTiles(LocalizedContentManager content, IAssetName assetName, bool ignoreWorld)
{
MapSeat.mapChairTexture = content.Load<Texture2D>(assetName.BaseName);
@@ -741,7 +747,7 @@ namespace StardewModdingAPI.Metadata
foreach (MapSeat seat in location.mapSeats.Where(p => p != null))
{
if (this.IsSameBaseName(assetName, seat._loadedTextureFile))
- seat.overlayTexture = MapSeat.mapChairTexture;
+ seat._loadedTextureFile = null;
}
}
}
@@ -749,11 +755,10 @@ namespace StardewModdingAPI.Metadata
return true;
}
- /// <summary>Reload critter textures.</summary>
- /// <param name="content">The content manager through which to reload the asset.</param>
- /// <param name="assetName">The asset name to reload.</param>
- /// <returns>Returns the number of reloaded assets.</returns>
- private int ReloadCritterTextures(LocalizedContentManager content, IAssetName assetName)
+ /// <summary>Update critter textures.</summary>
+ /// <param name="assetName">The asset name to update.</param>
+ /// <returns>Returns whether any references were updated.</returns>
+ private bool UpdateCritterTextures(IAssetName assetName)
{
// get critters
Critter[] critters =
@@ -761,26 +766,23 @@ namespace StardewModdingAPI.Metadata
from location in this.GetLocations()
where location.critters != null
from Critter critter in location.critters
- where this.IsSameBaseName(assetName, critter.sprite?.Texture?.Name)
+ where this.IsSameBaseName(assetName, critter.sprite?.spriteTexture?.Name)
select critter
)
.ToArray();
- if (!critters.Any())
- return 0;
// update sprites
- Texture2D texture = content.Load<Texture2D>(assetName.BaseName);
+ bool changed = false;
foreach (Critter entry in critters)
- entry.sprite.spriteTexture = texture;
-
- return critters.Length;
+ changed |= this.MarkSpriteDirty(entry.sprite);
+ return changed;
}
- /// <summary>Reload the sprites for interior doors.</summary>
+ /// <summary>Update the sprites for interior doors.</summary>
/// <param name="content">The content manager through which to reload the asset.</param>
- /// <param name="assetName">The asset name to reload.</param>
- /// <returns>Returns whether any doors were affected.</returns>
- private bool ReloadDoorSprites(LocalizedContentManager content, IAssetName assetName)
+ /// <param name="assetName">The asset name to update.</param>
+ /// <returns>Returns whether any references were updated.</returns>
+ private void UpdateDoorSprites(LocalizedContentManager content, IAssetName assetName)
{
Lazy<Texture2D> texture = new Lazy<Texture2D>(() => content.Load<Texture2D>(assetName.BaseName));
@@ -800,32 +802,15 @@ namespace StardewModdingAPI.Metadata
door.Sprite.texture = texture.Value;
}
}
-
- return texture.IsValueCreated;
}
- /// <summary>Reload the data for matching farm animals.</summary>
- /// <returns>Returns whether any farm animals were affected.</returns>
- /// <remarks>Derived from the <see cref="FarmAnimal"/> constructor.</remarks>
- private bool ReloadFarmAnimalData()
- {
- bool changed = false;
- foreach (FarmAnimal animal in this.GetFarmAnimals())
- {
- animal.reloadData();
- changed = true;
- }
-
- return changed;
- }
-
- /// <summary>Reload the sprites for a fence type.</summary>
- /// <param name="assetName">The asset name to reload.</param>
- /// <returns>Returns whether any textures were reloaded.</returns>
- private bool ReloadFenceTextures(IAssetName assetName)
+ /// <summary>Update the sprites for a fence type.</summary>
+ /// <param name="assetName">The asset name to update.</param>
+ /// <returns>Returns whether any references were updated.</returns>
+ private bool UpdateFenceTextures(IAssetName assetName)
{
// get fence type (e.g. LooseSprites/Fence3 => 3)
- if (!int.TryParse(this.GetSegments(assetName.BaseName)[1].Substring("Fence".Length), out int fenceType))
+ if (!int.TryParse(this.GetSegments(assetName.BaseName)[1]["Fence".Length..], out int fenceType))
return false;
// get fences
@@ -841,132 +826,69 @@ namespace StardewModdingAPI.Metadata
.ToArray();
// update fence textures
+ bool changed = false;
foreach (Fence fence in fences)
- fence.fenceTexture = new Lazy<Texture2D>(fence.loadFenceTexture);
- return true;
+ {
+ if (fence.fenceTexture.IsValueCreated)
+ {
+ fence.fenceTexture = new Lazy<Texture2D>(fence.loadFenceTexture);
+ changed = true;
+ }
+ }
+ return changed;
}
- /// <summary>Reload tree textures.</summary>
+ /// <summary>Update tree textures.</summary>
/// <param name="content">The content manager through which to reload the asset.</param>
- /// <param name="assetName">The asset name to reload.</param>
- /// <returns>Returns whether any textures were reloaded.</returns>
- private bool ReloadGrassTextures(LocalizedContentManager content, IAssetName assetName)
+ /// <param name="assetName">The asset name to update.</param>
+ /// <returns>Returns whether any references were updated.</returns>
+ private bool UpdateGrassTextures(LocalizedContentManager content, IAssetName assetName)
{
Grass[] grasses =
(
- from location in this.GetLocations()
- from grass in location.terrainFeatures.Values.OfType<Grass>()
+ from grass in this.GetTerrainFeatures().OfType<Grass>()
where this.IsSameBaseName(assetName, grass.textureName())
select grass
)
.ToArray();
- if (grasses.Any())
- {
- Lazy<Texture2D> texture = new Lazy<Texture2D>(() => content.Load<Texture2D>(assetName.BaseName));
- foreach (Grass grass in grasses)
- grass.texture = texture;
- return true;
- }
-
- return false;
- }
-
- /// <summary>Reload hair style metadata.</summary>
- /// <returns>Returns whether any assets were reloaded.</returns>
- /// <remarks>Derived from the <see cref="Farmer.GetHairStyleMetadataFile"/> and <see cref="Farmer.GetHairStyleMetadata"/>.</remarks>