summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs89
-rw-r--r--src/SMAPI/Framework/ContentManagers/BaseContentManager.cs71
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs114
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs4
-rw-r--r--src/SMAPI/Framework/ContentManagers/IContentManager.cs14
-rw-r--r--src/SMAPI/Framework/ContentManagers/ModContentManager.cs32
-rw-r--r--src/SMAPI/Framework/ModHelpers/ContentHelper.cs4
7 files changed, 168 insertions, 160 deletions
diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs
index 8e7465de..a4d29068 100644
--- a/src/SMAPI/Framework/ContentCoordinator.cs
+++ b/src/SMAPI/Framework/ContentCoordinator.cs
@@ -61,9 +61,6 @@ namespace StardewModdingAPI.Framework
/// <summary>The loaded content managers (including the <see cref="MainContentManager"/>).</summary>
private readonly List<IContentManager> ContentManagers = new();
- /// <summary>The language code for language-agnostic mod assets.</summary>
- private readonly LocalizedContentManager.LanguageCode DefaultLanguage = Constants.DefaultLanguage;
-
/// <summary>Whether the content coordinator has been disposed.</summary>
private bool IsDisposed;
@@ -350,7 +347,7 @@ namespace StardewModdingAPI.Framework
throw new InvalidOperationException($"The '{contentManagerID}' prefix isn't handled by any mod.");
// get fresh asset
- return contentManager.Load<T>(relativePath, this.DefaultLanguage, useCache: false);
+ return contentManager.LoadExact<T>(relativePath, useCache: false);
}
/// <summary>Purge matched assets from the cache.</summary>
@@ -467,9 +464,9 @@ namespace StardewModdingAPI.Framework
return this.ContentManagerLock.InReadLock(() =>
{
List<object> values = new List<object>();
- foreach (IContentManager content in this.ContentManagers.Where(p => !p.IsNamespaced && p.IsLoaded(assetName, p.Language)))
+ foreach (IContentManager content in this.ContentManagers.Where(p => !p.IsNamespaced && p.IsLoaded(assetName)))
{
- object value = content.Load<object>(assetName, this.Language, useCache: true);
+ object value = content.LoadExact<object>(assetName, useCache: true);
values.Add(value);
}
return values;
@@ -582,6 +579,8 @@ namespace StardewModdingAPI.Framework
/// <param name="info">The asset info to load or edit.</param>
private IEnumerable<AssetOperationGroup> GetAssetOperationsWithoutCache<T>(IAssetInfo info)
{
+ IAssetInfo legacyInfo = this.GetLegacyAssetInfo(info);
+
// new content API
foreach (AssetOperationGroup group in this.RequestAssetOperations(info))
yield return group;
@@ -592,12 +591,12 @@ namespace StardewModdingAPI.Framework
// check if loader applies
try
{
- if (!loader.Data.CanLoad<T>(info))
+ if (!loader.Data.CanLoad<T>(legacyInfo))
continue;
}
catch (Exception ex)
{
- loader.Mod.LogAsMod($"Mod failed when checking whether it could load asset '{info.Name}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
+ loader.Mod.LogAsMod($"Mod failed when checking whether it could load asset '{legacyInfo.Name}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
continue;
}
@@ -610,7 +609,9 @@ namespace StardewModdingAPI.Framework
mod: loader.Mod,
priority: AssetLoadPriority.Exclusive,
onBehalfOf: null,
- getData: assetInfo => loader.Data.Load<T>(assetInfo)
+ getData: assetInfo => loader.Data.Load<T>(
+ this.GetLegacyAssetInfo(assetInfo)
+ )
)
},
editOperations: Array.Empty<AssetEditOperation>()
@@ -623,12 +624,12 @@ namespace StardewModdingAPI.Framework
// check if editor applies
try
{
- if (!editor.Data.CanEdit<T>(info))
+ if (!editor.Data.CanEdit<T>(legacyInfo))
continue;
}
catch (Exception ex)
{
- editor.Mod.LogAsMod($"Mod crashed when checking whether it could edit asset '{info.Name}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
+ editor.Mod.LogAsMod($"Mod crashed when checking whether it could edit asset '{legacyInfo.Name}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
continue;
}
@@ -642,11 +643,75 @@ namespace StardewModdingAPI.Framework
mod: editor.Mod,
priority: AssetEditPriority.Default,
onBehalfOf: null,
- applyEdit: assetData => editor.Data.Edit<T>(assetData)
+ applyEdit: assetData => editor.Data.Edit<T>(
+ this.GetLegacyAssetData(assetData)
+ )
)
}
);
}
}
+
+ /// <summary>Get an asset info compatible with legacy <see cref="IAssetLoader"/> and <see cref="IAssetEditor"/> instances, which always expect the base name.</summary>
+ /// <param name="asset">The asset info.</param>
+ private IAssetInfo GetLegacyAssetInfo(IAssetInfo asset)
+ {
+ if (!this.TryGetLegacyAssetName(asset.Name, out IAssetName legacyName))
+ return asset;
+
+ return new AssetInfo(
+ locale: null,
+ assetName: legacyName,
+ type: asset.DataType,
+ getNormalizedPath: this.MainContentManager.AssertAndNormalizeAssetName
+ );
+ }
+
+ /// <summary>Get an asset data compatible with legacy <see cref="IAssetLoader"/> and <see cref="IAssetEditor"/> instances, which always expect the base name.</summary>
+ /// <param name="asset">The asset data.</param>
+ private IAssetData GetLegacyAssetData(IAssetData asset)
+ {
+ if (!this.TryGetLegacyAssetName(asset.Name, out IAssetName legacyName))
+ return asset;
+
+ return asset.Name.LocaleCode == null
+ ? asset
+ : new AssetDataForObject(
+ locale: null,
+ assetName: legacyName,
+ data: asset.Data,
+ getNormalizedPath: this.MainContentManager.AssertAndNormalizeAssetName
+ );
+ }
+
+ /// <summary>Get an asset name compatible with legacy <see cref="IAssetLoader"/> and <see cref="IAssetEditor"/> instances, which always expect the base name.</summary>
+ /// <param name="asset">The asset name to map.</param>
+ /// <param name="newAsset">The legacy asset name (or the <paramref name="asset"/> if no change is needed).</param>
+ /// <returns>Returns whether any change is needed for legacy compatibility.</returns>
+ private bool TryGetLegacyAssetName(IAssetName asset, out IAssetName newAsset)
+ {
+ // strip _international suffix
+ const string internationalSuffix = "_international";
+ if (asset.Name.EndsWith(internationalSuffix))
+ {
+ newAsset = new AssetName(
+ baseName: asset.Name[..^internationalSuffix.Length],
+ localeCode: null,
+ languageCode: null
+ );
+ return true;
+ }
+
+ // else strip locale
+ if (asset.LocaleCode != null)
+ {
+ newAsset = new AssetName(asset.BaseName, null, null);
+ return true;
+ }
+
+ // else no change needed
+ newAsset = asset;
+ return false;
+ }
}
}
diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
index 030c60a7..b1ace259 100644
--- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
@@ -5,6 +5,7 @@ using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
using System.Linq;
+using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Framework.Content;
using StardewModdingAPI.Framework.Exceptions;
@@ -32,6 +33,9 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>Whether to enable more aggressive memory optimizations.</summary>
protected readonly bool AggressiveMemoryOptimizations;
+ /// <summary>Whether to automatically try resolving keys to a localized form if available.</summary>
+ protected bool TryLocalizeKeys = true;
+
/// <summary>Whether the content coordinator has been disposed.</summary>
private bool IsDisposed;
@@ -39,7 +43,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
private readonly Action<BaseContentManager> OnDisposing;
/// <summary>A list of disposable assets.</summary>
- private readonly List<WeakReference<IDisposable>> Disposables = new List<WeakReference<IDisposable>>();
+ private readonly List<WeakReference<IDisposable>> Disposables = new();
/// <summary>The disposable assets tracked by the base content manager.</summary>
/// <remarks>This should be kept empty to avoid keeping disposable assets referenced forever, which prevents garbage collection when they're unused. Disposable assets are tracked by <see cref="Disposables"/> instead, which avoids a hard reference.</remarks>
@@ -115,11 +119,51 @@ namespace StardewModdingAPI.Framework.ContentManagers
public override T Load<T>(string assetName, LanguageCode language)
{
IAssetName parsedName = this.Coordinator.ParseAssetName(assetName);
- return this.Load<T>(parsedName, language, useCache: true);
+ return this.LoadLocalized<T>(parsedName, language, useCache: true);
}
/// <inheritdoc />
- public abstract T Load<T>(IAssetName assetName, LanguageCode language, bool useCache);
+ public T LoadLocalized<T>(IAssetName assetName, LanguageCode language, bool useCache)
+ {
+ // ignore locale in English (or if disabled)
+ if (!this.TryLocalizeKeys || language == LocalizedContentManager.LanguageCode.en)
+ return this.LoadExact<T>(assetName, useCache: useCache);
+
+ // check for localized asset
+ if (!LocalizedContentManager.localizedAssetNames.TryGetValue(assetName.Name, out _))
+ {
+ string localeCode = this.LanguageCodeString(language);
+ IAssetName localizedName = new AssetName(baseName: assetName.BaseName, localeCode: localeCode, languageCode: language);
+
+ try
+ {
+ this.LoadExact<T>(localizedName, useCache: useCache);
+ LocalizedContentManager.localizedAssetNames[assetName.Name] = localizedName.Name;
+ }
+ catch (ContentLoadException)
+ {
+ localizedName = new AssetName(assetName.BaseName + "_international", null, null);
+ try
+ {
+ this.LoadExact<T>(localizedName, useCache: useCache);
+ LocalizedContentManager.localizedAssetNames[assetName.Name] = localizedName.Name;
+ }
+ catch (ContentLoadException)
+ {
+ LocalizedContentManager.localizedAssetNames[assetName.Name] = assetName.Name;
+ }
+ }
+ }
+
+ // use cached key
+ string rawName = LocalizedContentManager.localizedAssetNames[assetName.Name];
+ if (assetName.Name != rawName)
+ assetName = this.Coordinator.ParseAssetName(assetName.Name);
+ return this.LoadExact<T>(assetName, useCache: useCache);
+ }
+
+ /// <inheritdoc />
+ public abstract T LoadExact<T>(IAssetName assetName, bool useCache);
/// <inheritdoc />
public virtual void OnLocaleChanged() { }
@@ -154,7 +198,11 @@ namespace StardewModdingAPI.Framework.ContentManagers
}
/// <inheritdoc />
- public abstract bool IsLoaded(IAssetName assetName, LanguageCode language);
+ public bool IsLoaded(IAssetName assetName)
+ {
+ return this.Cache.ContainsKey(assetName.Name);
+ }
+
/****
** Cache invalidation
@@ -241,26 +289,29 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <typeparam name="T">The type of asset to load.</typeparam>
/// <param name="assetName">The normalized asset key.</param>
/// <param name="useCache">Whether to read/write the loaded asset to the asset cache.</param>
- protected virtual T RawLoad<T>(string assetName, bool useCache)
+ protected virtual T RawLoad<T>(IAssetName assetName, bool useCache)
{
return useCache
- ? base.LoadBase<T>(assetName)
- : base.ReadAsset<T>(assetName, disposable => this.Disposables.Add(new WeakReference<IDisposable>(disposable)));
+ ? base.LoadBase<T>(assetName.Name)
+ : base.ReadAsset<T>(assetName.Name, disposable => this.Disposables.Add(new WeakReference<IDisposable>(disposable)));
}
/// <summary>Add tracking data to an asset and add it to the cache.</summary>
/// <typeparam name="T">The type of asset to inject.</typeparam>
/// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
/// <param name="value">The asset value.</param>
- /// <param name="language">The language code for which to inject the asset.</param>
/// <param name="useCache">Whether to save the asset to the asset cache.</param>
- protected virtual void TrackAsset<T>(IAssetName assetName, T value, LanguageCode language, bool useCache)
+ protected virtual void TrackAsset<T>(IAssetName assetName, T value, bool useCache)
{
// track asset key
if (value is Texture2D texture)
texture.Name = assetName.Name;
- // cache asset
+ // save to cache
+ // Note: even if the asset was loaded and cached right before this method was called,
+ // we need to fully re-inject it because a mod editor may have changed the asset in a
+ // way that doesn't change the instance stored in the cache, e.g. using
+ // `asset.ReplaceWith`.
if (useCache)
this.Cache[assetName.Name] = value;
diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
index a121f4c0..500c0191 100644
--- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
@@ -4,11 +4,9 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
-using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Events;
using StardewModdingAPI.Framework.Content;
-using StardewModdingAPI.Framework.Exceptions;
using StardewModdingAPI.Framework.Reflection;
using StardewModdingAPI.Framework.Utilities;
using StardewModdingAPI.Internal;
@@ -91,7 +89,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
}
/// <inheritdoc />
- public override T Load<T>(IAssetName assetName, LanguageCode language, bool useCache)
+ public override T LoadExact<T>(IAssetName assetName, bool useCache)
{
// raise first-load callback
if (GameContentManager.IsFirstLoad)
@@ -100,19 +98,15 @@ namespace StardewModdingAPI.Framework.ContentManagers
this.OnLoadingFirstAsset();
}
- // normalize asset name
- if (assetName.LanguageCode.HasValue)
- return this.Load<T>(this.Coordinator.ParseAssetName(assetName.BaseName), assetName.LanguageCode.Value, useCache);
-
// get from cache
- if (useCache && this.IsLoaded(assetName, language))
- return this.RawLoad<T>(assetName, language, useCache: true);
+ if (useCache && this.IsLoaded(assetName))
+ return this.RawLoad<T>(assetName, useCache: true);
// get managed asset
if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string contentManagerID, out IAssetName relativePath))
{
T managedAsset = this.Coordinator.LoadManagedAsset<T>(contentManagerID, relativePath);
- this.TrackAsset(assetName, managedAsset, language, useCache);
+ this.TrackAsset(assetName, managedAsset, useCache);
return managedAsset;
}
@@ -122,24 +116,23 @@ namespace StardewModdingAPI.Framework.ContentManagers
{
this.Monitor.Log($"Broke loop while loading asset '{assetName}'.", 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>(assetName, useCache);
}
else
{
data = this.AssetsBeingLoaded.Track(assetName.Name, () =>
{
- string locale = this.GetLocale(language);
- IAssetInfo info = new AssetInfo(locale, assetName, typeof(T), this.AssertAndNormalizeAssetName);
+ IAssetInfo info = new AssetInfo(assetName.LocaleCode, assetName, 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>(assetName, useCache), this.AssertAndNormalizeAssetName);
asset = this.ApplyEditors<T>(info, asset);
return (T)asset.Data;
});
}
// update cache
- this.TrackAsset(assetName, data, language, useCache);
+ this.TrackAsset(assetName, data, useCache);
// raise event & return data
this.OnAssetLoaded(this, assetName);
@@ -147,20 +140,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
}
/// <inheritdoc />
- public override bool IsLoaded(IAssetName assetName, LanguageCode language)
- {
- string cachedKey = null;
- bool localized =
- language != LanguageCode.en
- && !this.Coordinator.IsManagedAssetKey(assetName)
- && this.LocalizedAssetNames.TryGetValue(assetName.Name, out cachedKey);
-
- return localized
- ? this.Cache.ContainsKey(cachedKey)
- : this.Cache.ContainsKey(assetName.Name);
- }
-
- /// <inheritdoc />
public override void OnLocaleChanged()
{
base.OnLocaleChanged();
@@ -175,7 +154,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
// invalidate translatable assets
string[] invalidated = this
- .InvalidateCache((key, type) =>
+ .InvalidateCache((key, _) =>
removeAssetNames.Contains(key)
|| removeAssetNames.Contains(this.Coordinator.ParseAssetName(key).BaseName)
)
@@ -196,81 +175,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
/*********
** Private methods
*********/
- /// <inheritdoc />
- protected override void TrackAsset<T>(IAssetName assetName, T value, LanguageCode language, bool useCache)
- {
- // handle explicit language in asset name
- {
- if (assetName.LanguageCode.HasValue)
- {
- this.TrackAsset(this.Coordinator.ParseAssetName(assetName.BaseName), value, assetName.LanguageCode.Value, useCache);
- return;
- }
- }
-
- // save to cache
- // Note: even if the asset was loaded and cached right before this method was called,
- // we need to fully re-inject it here for two reasons:
- // 1. So we can look up an asset by its base or localized key (the game/XNA logic
- // only caches by the most specific key).
- // 2. Because a mod asset loader/editor may have changed the asset in a way that
- // doesn't change the instance stored in the cache, e.g. using `asset.ReplaceWith`.
- if (useCache)
- {
- IAssetName translatedKey = new AssetName(assetName.Name, this.GetLocale(language), language);
- base.TrackAsset(assetName, value, language, useCache: true);
- if (this.Cache.ContainsKey(translatedKey.Name))
- base.TrackAsset(translatedKey, value, language, useCache: true);
-
- // track whether the injected asset is translatable for is-loaded lookups
- if (this.Cache.ContainsKey(translatedKey.Name))
- this.LocalizedAssetNames[assetName.Name] = translatedKey.Name;
- else if (this.Cache.ContainsKey(assetName.Name))
- this.LocalizedAssetNames[assetName.Name] = assetName.Name;
- else
- this.Monitor.Log($"Asset '{assetName}' could not be found in the cache immediately after injection.", LogLevel.Error);
- }
- }
-
- /// <summary>Load an asset file directly from the underlying content manager.</summary>
- /// <typeparam name="T">The type of asset to load.</typeparam>
- /// <param name="assetName">The normalized asset key.</param>
- /// <param name="language">The language code for which to load content.</param>
- /// <param name="useCache">Whether to read/write the loaded asset to the asset cache.</param>
- /// <remarks>Derived from <see cref="LocalizedContentManager.Load{T}(string, LocalizedContentManager.LanguageCode)"/>.</remarks>
- private T RawLoad<T>(IAssetName assetName, LanguageCode language, bool useCache)
- {
- try
- {
- // use cached key
- if (language == this.Language && this.LocalizedAssetNames.TryGetValue(assetName.Name, out string cachedKey))
- return base.RawLoad<T>(cachedKey, useCache);
-
- // try translated key
- if (language != LanguageCode.en)
- {
- string translatedKey = $"{assetName}.{this.GetLocale(language)}";
- try
- {
- T obj = base.RawLoad<T>(translatedKey, useCache);
- this.LocalizedAssetNames[assetName.Name] = translatedKey;
- return obj;
- }
- catch (ContentLoadException)
- {
- this.LocalizedAssetNames[assetName.Name] = assetName.Name;
- }
- }
-
- // try base asset
- return base.RawLoad<T>(assetName.Name, useCache);
- }
- catch (ContentLoadException ex) when (ex.InnerException is FileNotFoundException { InnerException: null })
- {
- throw new SContentLoadException($"Error loading \"{assetName}\": it isn't in the Content folder and no mod provided it.");
- }
- }
-
/// <summary>Load the initial asset from the registered 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>
diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs
index 847e2ce1..3f7188da 100644
--- a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs
+++ b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs
@@ -25,9 +25,9 @@ namespace StardewModdingAPI.Framework.ContentManagers
: base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, onLoadingFirstAsset, onAssetLoaded, aggressiveMemoryOptimizations) { }
/// <inheritdoc />
- public override T Load<T>(IAssetName assetName, LanguageCode language, bool useCache)
+ public override T LoadExact<T>(IAssetName assetName, bool useCache)
{
- T data = base.Load<T>(assetName, language, useCache);
+ T data = base.LoadExact<T>(assetName, useCache);
if (data is Texture2D texture)
texture.Tag = this.Tag;
diff --git a/src/SMAPI/Framework/ContentManagers/IContentManager.cs b/src/SMAPI/Framework/ContentManagers/IContentManager.cs
index 6d71472f..4de9a8c3 100644
--- a/src/SMAPI/Framework/ContentManagers/IContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/IContentManager.cs
@@ -32,12 +32,17 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <param name="assetName">The normalized asset name.</param>
bool DoesAssetExist(IAssetName assetName);
- /// <summary>Load an asset that has been processed by the content pipeline.</summary>
+ /// <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>
/// <param name="assetName">The asset name relative to the loader root directory.</param>
- /// <param name="language">The language code for which to load content.</param>
/// <param name="useCache">Whether to read/write the loaded asset to the asset cache.</param>
- T Load<T>(IAssetName assetName, LocalizedContentManager.LanguageCode language, bool useCache);
+ T LoadLocalized<T>(IAssetName assetName, LocalizedContentManager.LanguageCode language, bool useCache);
+
+ /// <summary>Load an asset through the content pipeline, using the exact asset name without checking for localized variants.</summary>
+ /// <typeparam name="T">The type of asset to load.</typeparam>
+ /// <param name="assetName">The asset name relative to the loader root directory.</param>
+ /// <param name="useCache">Whether to read/write the loaded asset to the asset cache.</param>
+ T LoadExact<T>(IAssetName assetName, bool useCache);
/// <summary>Assert that the given key has a valid format and return a normalized form consistent with the underlying cache.</summary>
/// <param name="assetName">The asset key to check.</param>
@@ -53,8 +58,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>Get whether the content manager has already loaded and cached the given asset.</summary>
/// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
- /// <param name="language">The language.</param>
- bool IsLoaded(IAssetName assetName, LocalizedContentManager.LanguageCode language);
+ bool IsLoaded(IAssetName assetName);
/// <summary>Purge matched assets from the cache.</summary>
/// <param name="predicate">Matches the asset keys to invalidate.</param>
diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
index 90836fcf..375b5e0e 100644
--- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
@@ -61,6 +61,8 @@ namespace StardewModdingAPI.Framework.ContentManagers
this.GameContentManager = gameContentManager;
this.JsonHelper = jsonHelper;
this.ModName = modName;
+
+ this.TryLocalizeKeys = false;
}
/// <inheritdoc />
@@ -80,14 +82,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
}
/// <inheritdoc />
- public override T Load<T>(string assetName, LanguageCode language)
- {
- IAssetName parsedName = this.Coordinator.ParseAssetName(assetName);
- return this.Load<T>(parsedName, language, useCache: false);
- }
-
- /// <inheritdoc />
- public override T Load<T>(IAssetName assetName, LanguageCode language, bool useCache)
+ public override T LoadExact<T>(IAssetName assetName, bool useCache)
{
// disable caching
// This is necessary to avoid assets being shared between content managers, which can
@@ -97,11 +92,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
if (useCache)
throw new InvalidOperationException("Mod content managers don't support asset caching.");
- // disable language handling
- // Mod files don't support automatic translation logic, so this should never happen.
- if (language != this.DefaultLanguage)
- throw new InvalidOperationException("Localized assets aren't supported by the mod content manager.");
-
// resolve managed asset key
{
if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string contentManagerID, out IAssetName relativePath))
@@ -130,14 +120,14 @@ namespace StardewModdingAPI.Framework.ContentManagers
{
// the underlying content manager adds a .xnb extension implicitly, so
// we need to strip it here to avoid trying to load a '.xnb.xnb' file.
- string loadName = assetName.Name[..^".xnb".Length];
+ IAssetName loadName = this.Coordinator.ParseAssetName(assetName.Name[..^".xnb".Length]);
// load asset
asset = this.RawLoad<T>(loadName, useCache: false);
if (asset is Map map)
{
- map.assetPath = loadName;
- this.FixTilesheetPaths(map, relativeMapPath: loadName, fixEagerPathPrefixes: true);
+ map.assetPath = loadName.Name;
+ this.FixTilesheetPaths(map, relativeMapPath: loadName.Name, fixEagerPathPrefixes: true);
}
}
break;
@@ -201,17 +191,11 @@ namespace StardewModdingAPI.Framework.ContentManagers
}
// track & return asset
- this.TrackAsset(assetName, asset, language, useCache);
+ this.TrackAsset(assetName, asset, useCache);
return asset;
}
/// <inheritdoc />
- public override bool IsLoaded(IAssetName assetName, LanguageCode language)
- {
- return this.Cache.ContainsKey(assetName.Name);
- }
-
- /// <inheritdoc />
public override LocalizedContentManager CreateTemporary()
{
throw new NotSupportedException("Can't create a temporary mod content manager.");
@@ -371,7 +355,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
IAssetName contentKey = this.Coordinator.ParseAssetName(this.GetContentKeyForTilesheetImageSource(relativePath));
try
{
- this.GameContentManager.Load<Texture2D>(contentKey, this.Language, useCache: true); // no need to bypass cache here, since we're not storing the asset
+ this.GameContentManager.LoadLocalized<Texture2D>(contentKey, this.GameContentManager.Language, useCache: true); // no need to bypass cache here, since we're not storing the asset
assetName = contentKey;
return true;
}
diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs
index 4e522c8d..3416c286 100644
--- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs
+++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs
@@ -88,10 +88,10 @@ namespace StardewModdingAPI.Framework.ModHelpers
switch (source)
{
case ContentSource.GameContent:
- return this.GameContentManager.Load<T>(assetName, this.CurrentLocaleConstant, useCache: false);
+ return this.GameContentManager.LoadLocalized<T>(assetName, this.CurrentLocaleConstant, useCache: false);
case ContentSource.ModFolder:
- return this.ModContentManager.Load<T>(assetName, Constants.DefaultLanguage, useCache: false);
+ return this.ModContentManager.LoadExact<T>(assetName, useCache: false);
default:
throw new SContentLoadException($"{this.ModName} failed loading content asset '{key}' from {source}: unknown content source '{source}'.");