diff options
Diffstat (limited to 'src')
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}'."); |