summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework/ContentManagers
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework/ContentManagers')
-rw-r--r--src/SMAPI/Framework/ContentManagers/BaseContentManager.cs51
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs94
-rw-r--r--src/SMAPI/Framework/ContentManagers/IContentManager.cs3
-rw-r--r--src/SMAPI/Framework/ContentManagers/ModContentManager.cs26
4 files changed, 54 insertions, 120 deletions
diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
index 5645c0fa..be892b33 100644
--- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
@@ -160,14 +160,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
return this.IsNormalizedKeyLoaded(assetName, language);
}
- /// <inheritdoc />
- public IEnumerable<string> GetAssetKeys()
- {
- return this.Cache.Keys
- .Select(this.GetAssetName)
- .Distinct();
- }
-
/****
** Cache invalidation
****/
@@ -177,13 +169,13 @@ namespace StardewModdingAPI.Framework.ContentManagers
IDictionary<string, object> removeAssets = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
this.Cache.Remove((key, asset) =>
{
- this.ParseCacheKey(key, out string assetName, out _);
+ string baseAssetName = this.Coordinator.ParseAssetName(key).BaseName;
// check if asset should be removed
- bool remove = removeAssets.ContainsKey(assetName);
- if (!remove && predicate(assetName, asset.GetType()))
+ bool remove = removeAssets.ContainsKey(baseAssetName);
+ if (!remove && predicate(baseAssetName, asset.GetType()))
{
- removeAssets[assetName] = asset;
+ removeAssets[baseAssetName] = asset;
remove = true;
}
@@ -275,44 +267,9 @@ namespace StardewModdingAPI.Framework.ContentManagers
this.BaseDisposableReferences.Clear();
}
- /// <summary>Parse a cache key into its component parts.</summary>
- /// <param name="cacheKey">The input cache key.</param>
- /// <param name="assetName">The original asset name.</param>
- /// <param name="localeCode">The asset locale code (or <c>null</c> if not localized).</param>
- protected void ParseCacheKey(string cacheKey, out string assetName, out string localeCode)
- {
- // handle localized key
- if (!string.IsNullOrWhiteSpace(cacheKey))
- {
- int lastSepIndex = cacheKey.LastIndexOf(".", StringComparison.Ordinal);
- if (lastSepIndex >= 0)
- {
- string suffix = cacheKey.Substring(lastSepIndex + 1, cacheKey.Length - lastSepIndex - 1);
- if (this.Coordinator.TryGetLanguageEnum(suffix, out _))
- {
- assetName = cacheKey.Substring(0, lastSepIndex);
- localeCode = cacheKey.Substring(lastSepIndex + 1, cacheKey.Length - lastSepIndex - 1);
- return;
- }
- }
- }
-
- // handle simple key
- assetName = cacheKey;
- localeCode = null;
- }
-
/// <summary>Get whether an asset has already been loaded.</summary>
/// <param name="normalizedAssetName">The normalized asset name.</param>
/// <param name="language">The language to check.</param>
protected abstract bool IsNormalizedKeyLoaded(string normalizedAssetName, LanguageCode language);
-
- /// <summary>Get the asset name from a cache key.</summary>
- /// <param name="cacheKey">The input cache key.</param>
- private string GetAssetName(string cacheKey)
- {
- this.ParseCacheKey(cacheKey, out string assetName, out string _);
- return assetName;
- }
}
}
diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
index ab198076..0ca9e277 100644
--- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
@@ -73,46 +73,46 @@ namespace StardewModdingAPI.Framework.ContentManagers
}
// normalize asset name
- assetName = this.AssertAndNormalizeAssetName(assetName);
- if (this.TryParseExplicitLanguageAssetKey(assetName, out string newAssetName, out LanguageCode newLanguage))
- return this.Load<T>(newAssetName, newLanguage, useCache);
+ IAssetName parsedName = this.Coordinator.ParseAssetName(assetName);
+ if (parsedName.LanguageCode.HasValue)
+ return this.Load<T>(parsedName.BaseName, parsedName.LanguageCode.Value, useCache);
// get from cache
- if (useCache && this.IsLoaded(assetName, language))
- return this.RawLoad<T>(assetName, language, useCache: true);
+ if (useCache && this.IsLoaded(parsedName.Name, language))
+ return this.RawLoad<T>(parsedName.Name, language, useCache: true);
// get managed asset
- if (this.Coordinator.TryParseManagedAssetKey(assetName, out string contentManagerID, out string relativePath))
+ if (this.Coordinator.TryParseManagedAssetKey(parsedName.Name, out string contentManagerID, out string relativePath))
{
T managedAsset = this.Coordinator.LoadManagedAsset<T>(contentManagerID, relativePath);
- this.TrackAsset(assetName, managedAsset, language, useCache);
+ this.TrackAsset(parsedName.Name, managedAsset, language, useCache);
return managedAsset;
}
// load asset
T data;
- if (this.AssetsBeingLoaded.Contains(assetName))
+ if (this.AssetsBeingLoaded.Contains(parsedName.Name))
{
- this.Monitor.Log($"Broke loop while loading asset '{assetName}'.", LogLevel.Warn);
+ this.Monitor.Log($"Broke loop while loading asset '{parsedName.Name}'.", LogLevel.Warn);
this.Monitor.Log($"Bypassing mod loaders for this asset. Stack trace:\n{Environment.StackTrace}");
- data = this.RawLoad<T>(assetName, language, useCache);
+ data = this.RawLoad<T>(parsedName.Name, language, useCache);
}
else
{
- data = this.AssetsBeingLoaded.Track(assetName, () =>
+ data = this.AssetsBeingLoaded.Track(parsedName.Name, () =>
{
string locale = this.GetLocale(language);
- IAssetInfo info = new AssetInfo(locale, assetName, typeof(T), this.AssertAndNormalizeAssetName);
+ IAssetInfo info = new AssetInfo(locale, parsedName, typeof(T), this.AssertAndNormalizeAssetName);
IAssetData asset =
this.ApplyLoader<T>(info)
- ?? new AssetDataForObject(info, this.RawLoad<T>(assetName, language, useCache), this.AssertAndNormalizeAssetName);
+ ?? new AssetDataForObject(info, this.RawLoad<T>(parsedName.Name, language, useCache), this.AssertAndNormalizeAssetName);
asset = this.ApplyEditors<T>(info, asset);
return (T)asset.Data;
});
}
// update cache & return data
- this.TrackAsset(assetName, data, language, useCache);
+ this.TrackAsset(parsedName.Name, data, language, useCache);
return data;
}
@@ -124,13 +124,16 @@ namespace StardewModdingAPI.Framework.ContentManagers
// find assets for which a translatable version was loaded
HashSet<string> removeAssetNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (string key in this.LocalizedAssetNames.Where(p => p.Key != p.Value).Select(p => p.Key))
- removeAssetNames.Add(this.TryParseExplicitLanguageAssetKey(key, out string assetName, out _) ? assetName : key);
+ {
+ IAssetName assetName = this.Coordinator.ParseAssetName(key);
+ removeAssetNames.Add(assetName.BaseName);
+ }
// invalidate translatable assets
string[] invalidated = this
.InvalidateCache((key, type) =>
removeAssetNames.Contains(key)
- || (this.TryParseExplicitLanguageAssetKey(key, out string assetName, out _) && removeAssetNames.Contains(assetName))
+ || removeAssetNames.Contains(this.Coordinator.ParseAssetName(key).BaseName)
)
.Select(p => p.Key)
.OrderBy(p => p, StringComparer.OrdinalIgnoreCase)
@@ -168,9 +171,10 @@ namespace StardewModdingAPI.Framework.ContentManagers
{
// handle explicit language in asset name
{
- if (this.TryParseExplicitLanguageAssetKey(assetName, out string newAssetName, out LanguageCode newLanguage))
+ IAssetName parsedName = this.Coordinator.ParseAssetName(assetName);
+ if (parsedName.LanguageCode.HasValue)
{
- this.TrackAsset(newAssetName, value, newLanguage, useCache);
+ this.TrackAsset(parsedName.BaseName, value, parsedName.LanguageCode.Value, useCache);
return;
}
}
@@ -238,30 +242,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
}
}
- /// <summary>Parse an asset key that contains an explicit language into its asset name and language, if applicable.</summary>
- /// <param name="rawAsset">The asset key to parse.</param>
- /// <param name="assetName">The asset name without the language code.</param>
- /// <param name="language">The language code removed from the asset name.</param>
- /// <returns>Returns whether the asset key contains an explicit language and was successfully parsed.</returns>
- private bool TryParseExplicitLanguageAssetKey(string rawAsset, out string assetName, out LanguageCode language)
- {
- if (string.IsNullOrWhiteSpace(rawAsset))
- throw new SContentLoadException("The asset key is empty.");
-
- // extract language code
- int splitIndex = rawAsset.LastIndexOf('.');
- if (splitIndex != -1 && this.Coordinator.TryGetLanguageEnum(rawAsset.Substring(splitIndex + 1), out language))
- {
- assetName = rawAsset.Substring(0, splitIndex);
- return true;
- }
-
- // no explicit language code found
- assetName = rawAsset;
- language = this.Language;
- return false;
- }
-
/// <summary>Load the initial asset from the registered <see cref="Loaders"/>.</summary>
/// <param name="info">The basic asset metadata.</param>
/// <returns>Returns the loaded asset metadata, or <c>null</c> if no loader matched.</returns>
@@ -277,7 +257,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
}
catch (Exception ex)
{
- entry.Mod.LogAsMod($"Mod failed when checking whether it could load asset '{info.AssetName}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
+ entry.Mod.LogAsMod($"Mod failed when checking whether it could load asset '{info.Name}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
return false;
}
})
@@ -289,7 +269,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
if (loaders.Length > 1)
{
string[] loaderNames = loaders.Select(p => p.Mod.DisplayName).ToArray();
- this.Monitor.Log($"Multiple mods want to provide the '{info.AssetName}' asset ({string.Join(", ", loaderNames)}), but an asset can't be loaded multiple times. SMAPI will use the default asset instead; uninstall one of the mods to fix this. (Message for modders: you should usually use {typeof(IAssetEditor)} instead to avoid conflicts.)", LogLevel.Warn);
+ this.Monitor.Log($"Multiple mods want to provide the '{info.Name}' asset ({string.Join(", ", loaderNames)}), but an asset can't be loaded multiple times. SMAPI will use the default asset instead; uninstall one of the mods to fix this. (Message for modders: you should usually use {typeof(IAssetEditor)} instead to avoid conflicts.)", LogLevel.Warn);
return null;
}
@@ -300,11 +280,11 @@ namespace StardewModdingAPI.Framework.ContentManagers
try
{
data = loader.Load<T>(info);
- this.Monitor.Log($"{mod.DisplayName} loaded asset '{info.AssetName}'.", LogLevel.Trace);
+ this.Monitor.Log($"{mod.DisplayName} loaded asset '{info.Name}'.", LogLevel.Trace);
}
catch (Exception ex)
{
- mod.LogAsMod($"Mod crashed when loading asset '{info.AssetName}'. SMAPI will use the default asset instead. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
+ mod.LogAsMod($"Mod crashed when loading asset '{info.Name}'. SMAPI will use the default asset instead. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
return null;
}
@@ -349,7 +329,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
}
catch (Exception ex)
{
- mod.LogAsMod($"Mod crashed when checking whether it could edit asset '{info.AssetName}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
+ mod.LogAsMod($"Mod crashed when checking whether it could edit asset '{info.Name}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
continue;
}
@@ -358,22 +338,22 @@ namespace StardewModdingAPI.Framework.ContentManagers
try
{
editor.Edit<T>(asset);
- this.Monitor.Log($"{mod.DisplayName} edited {info.AssetName}.");
+ this.Monitor.Log($"{mod.DisplayName} edited {info.Name}.");
}
catch (Exception ex)
{
- mod.LogAsMod($"Mod crashed when editing asset '{info.AssetName}', which may cause errors in-game. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
+ mod.LogAsMod($"Mod crashed when editing asset '{info.Name}', which may cause errors in-game. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
}
// validate edit
if (asset.Data == null)
{
- mod.LogAsMod($"Mod incorrectly set asset '{info.AssetName}' to a null value; ignoring override.", LogLevel.Warn);
+ mod.LogAsMod($"Mod incorrectly set asset '{info.Name}' to a null value; ignoring override.", LogLevel.Warn);
asset = GetNewData(prevAsset);
}
else if (!(asset.Data is T))
{
- mod.LogAsMod($"Mod incorrectly set asset '{asset.AssetName}' to incompatible type '{asset.Data.GetType()}', expected '{typeof(T)}'; ignoring override.", LogLevel.Warn);
+ mod.LogAsMod($"Mod incorrectly set asset '{asset.Name}' to incompatible type '{asset.Data.GetType()}', expected '{typeof(T)}'; ignoring override.", LogLevel.Warn);
asset = GetNewData(prevAsset);
}
}
@@ -393,21 +373,21 @@ namespace StardewModdingAPI.Framework.ContentManagers
// can't load a null asset
if (data == null)
{
- mod.LogAsMod($"SMAPI blocked asset replacement for '{info.AssetName}': mod incorrectly set asset to a null value.", LogLevel.Error);
+ mod.LogAsMod($"SMAPI blocked asset replacement for '{info.Name}': mod incorrectly set asset to a null value.", LogLevel.Error);
return false;
}
// when replacing a map, the vanilla tilesheets must have the same order and IDs
if (data is Map loadedMap)
{
- TilesheetReference[] vanillaTilesheetRefs = this.Coordinator.GetVanillaTilesheetIds(info.AssetName);
+ TilesheetReference[] vanillaTilesheetRefs = this.Coordinator.GetVanillaTilesheetIds(info.Name.Name);
foreach (TilesheetReference vanillaSheet in vanillaTilesheetRefs)
{
// add missing tilesheet
if (loadedMap.GetTileSheet(vanillaSheet.Id) == null)
{
mod.Monitor.LogOnce("SMAPI fixed maps loaded by this mod to prevent errors. See the log file for details.", LogLevel.Warn);
- this.Monitor.Log($"Fixed broken map replacement: {mod.DisplayName} loaded '{info.AssetName}' without a required tilesheet (id: {vanillaSheet.Id}, source: {vanillaSheet.ImageSource}).");
+ this.Monitor.Log($"Fixed broken map replacement: {mod.DisplayName} loaded '{info.Name}' without a required tilesheet (id: {vanillaSheet.Id}, source: {vanillaSheet.ImageSource}).");
loadedMap.AddTileSheet(new TileSheet(vanillaSheet.Id, loadedMap, vanillaSheet.ImageSource, vanillaSheet.SheetSize, vanillaSheet.TileSize));
}
@@ -417,17 +397,17 @@ namespace StardewModdingAPI.Framework.ContentManagers
{
// only show warning if not farm map
// This is temporary: mods shouldn't do this for any vanilla map, but these are the ones we know will crash. Showing a warning for others instead gives modders time to update their mods, while still simplifying troubleshooting.
- bool isFarmMap = info.AssetNameEquals("Maps/Farm") || info.AssetNameEquals("Maps/Farm_Combat") || info.AssetNameEquals("Maps/Farm_Fishing") || info.AssetNameEquals("Maps/Farm_Foraging") || info.AssetNameEquals("Maps/Farm_FourCorners") || info.AssetNameEquals("Maps/Farm_Island") || info.AssetNameEquals("Maps/Farm_Mining");
+ bool isFarmMap = info.Name.IsEquivalentTo("Maps/Farm") || info.Name.IsEquivalentTo("Maps/Farm_Combat") || info.Name.IsEquivalentTo("Maps/Farm_Fishing") || info.Name.IsEquivalentTo("Maps/Farm_Foraging") || info.Name.IsEquivalentTo("Maps/Farm_FourCorners") || info.Name.IsEquivalentTo("Maps/Farm_Island") || info.Name.IsEquivalentTo("Maps/Farm_Mining");
string reason = $"mod reordered the original tilesheets, which {(isFarmMap ? "would cause a crash" : "often causes crashes")}.\nTechnical details for mod author: Expected order: {string.Join(", ", vanillaTilesheetRefs.Select(p => p.Id))}. See https://stardewvalleywiki.com/Modding:Maps#Tilesheet_order for help.";
SCore.DeprecationManager.PlaceholderWarn("3.8.2", DeprecationLevel.PendingRemoval);
if (isFarmMap)
{
- mod.LogAsMod($"SMAPI blocked '{info.AssetName}' map load: {reason}", LogLevel.Error);
+ mod.LogAsMod($"SMAPI blocked '{info.Name}' map load: {reason}", LogLevel.Error);
return false;
}
- mod.LogAsMod($"SMAPI found an issue with '{info.AssetName}' map load: {reason}", LogLevel.Warn);
+ mod.LogAsMod($"SMAPI found an issue with '{info.Name}' map load: {reason}", LogLevel.Warn);
}
}
}
diff --git a/src/SMAPI/Framework/ContentManagers/IContentManager.cs b/src/SMAPI/Framework/ContentManagers/IContentManager.cs
index d7963305..ba7dbc06 100644
--- a/src/SMAPI/Framework/ContentManagers/IContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/IContentManager.cs
@@ -58,9 +58,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <param name="language">The language.</param>
bool IsLoaded(string assetName, LocalizedContentManager.LanguageCode language);
- /// <summary>Get the cached asset keys.</summary>
- IEnumerable<string> GetAssetKeys();
-
/// <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>
diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
index beb90a5d..21f88d47 100644
--- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
@@ -80,7 +80,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
{
// normalize key
bool isXnbFile = Path.GetExtension(assetName).ToLower() == ".xnb";
- assetName = this.AssertAndNormalizeAssetName(assetName);
+ IAssetName parsedName = this.Coordinator.ParseAssetName(assetName);
// disable caching
// This is necessary to avoid assets being shared between content managers, which can
@@ -97,21 +97,21 @@ namespace StardewModdingAPI.Framework.ContentManagers
// resolve managed asset key
{
- if (this.Coordinator.TryParseManagedAssetKey(assetName, out string contentManagerID, out string relativePath))
+ if (this.Coordinator.TryParseManagedAssetKey(parsedName.Name, out string contentManagerID, out string relativePath))
{
if (contentManagerID != this.Name)
- throw new SContentLoadException($"Can't load managed asset key '{assetName}' through content manager '{this.Name}' for a different mod.");
- assetName = relativePath;
+ throw new SContentLoadException($"Can't load managed asset key '{parsedName}' through content manager '{this.Name}' for a different mod.");
+ parsedName = this.Coordinator.ParseAssetName(relativePath);
}
}
// get local asset
- SContentLoadException GetContentError(string reasonPhrase) => new SContentLoadException($"Failed loading asset '{assetName}' from {this.Name}: {reasonPhrase}");
+ SContentLoadException GetContentError(string reasonPhrase) => new SContentLoadException($"Failed loading asset '{parsedName}' from {this.Name}: {reasonPhrase}");
T asset;
try
{
// get file
- FileInfo file = this.GetModFile(isXnbFile ? $"{assetName}.xnb" : assetName); // .xnb extension is stripped from asset names passed to the content manager
+ FileInfo file = this.GetModFile(isXnbFile ? $"{parsedName}.xnb" : parsedName.Name); // .xnb extension is stripped from asset names passed to the content manager
if (!file.Exists)
throw GetContentError("the specified path doesn't exist.");
@@ -121,11 +121,11 @@ namespace StardewModdingAPI.Framework.ContentManagers
// XNB file
case ".xnb":
{
- asset = this.RawLoad<T>(assetName, useCache: false);
+ asset = this.RawLoad<T>(parsedName.Name, useCache: false);
if (asset is Map map)
{
- map.assetPath = assetName;
- this.FixTilesheetPaths(map, relativeMapPath: assetName, fixEagerPathPrefixes: true);
+ map.assetPath = parsedName.Name;
+ this.FixTilesheetPaths(map, relativeMapPath: parsedName.Name, fixEagerPathPrefixes: true);
}
}
break;
@@ -173,8 +173,8 @@ namespace StardewModdingAPI.Framework.ContentManagers
// fetch & cache
FormatManager formatManager = FormatManager.Instance;
Map map = formatManager.LoadMap(file.FullName);
- map.assetPath = assetName;
- this.FixTilesheetPaths(map, relativeMapPath: assetName, fixEagerPathPrefixes: false);
+ map.assetPath = parsedName.Name;
+ this.FixTilesheetPaths(map, relativeMapPath: parsedName.Name, fixEagerPathPrefixes: false);
asset = (T)(object)map;
}
break;
@@ -185,11 +185,11 @@ namespace StardewModdingAPI.Framework.ContentManagers
}
catch (Exception ex) when (!(ex is SContentLoadException))
{
- throw new SContentLoadException($"The content manager failed loading content asset '{assetName}' from {this.Name}.", ex);
+ throw new SContentLoadException($"The content manager failed loading content asset '{parsedName}' from {this.Name}.", ex);
}
// track & return asset
- this.TrackAsset(assetName, asset, language, useCache);
+ this.TrackAsset(parsedName.Name, asset, language, useCache);
return asset;
}