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.cs4
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs71
-rw-r--r--src/SMAPI/Framework/ContentManagers/ModContentManager.cs27
3 files changed, 44 insertions, 58 deletions
diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
index e4695588..575d252e 100644
--- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
@@ -198,9 +198,9 @@ namespace StardewModdingAPI.Framework.ContentManagers
// NOTE: the game checks for ContentLoadException to handle invalid keys, so avoid
// throwing other types like ArgumentException here.
if (string.IsNullOrWhiteSpace(assetName))
- throw new SContentLoadException("The asset key or local path is empty.");
+ throw new SContentLoadException(ContentLoadErrorType.InvalidName, "The asset key or local path is empty.");
if (assetName.Intersect(Path.GetInvalidPathChars()).Any())
- throw new SContentLoadException("The asset key or local path contains invalid characters.");
+ throw new SContentLoadException(ContentLoadErrorType.InvalidName, "The asset key or local path contains invalid characters.");
return this.Cache.NormalizeKey(assetName);
}
diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
index c53040e1..2aa50542 100644
--- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
@@ -75,15 +75,19 @@ namespace StardewModdingAPI.Framework.ContentManagers
// custom asset from a loader
string locale = this.GetLocale();
IAssetInfo info = new AssetInfo(locale, assetName, typeof(T), this.AssertAndNormalizeAssetName);
- AssetLoadOperation[] loaders = this.GetLoaders<object>(info).ToArray();
-
- if (!this.AssertMaxOneRequiredLoader(info, loaders, out string? error))
+ AssetOperationGroup? operations = this.Coordinator.GetAssetOperations<object>(info);
+ if (operations?.LoadOperations.Count > 0)
{
- this.Monitor.Log(error, LogLevel.Warn);
- return false;
+ if (!this.AssertMaxOneRequiredLoader(info, operations.LoadOperations, out string? error))
+ {
+ this.Monitor.Log(error, LogLevel.Warn);
+ return false;
+ }
+
+ return true;
}
- return loaders.Any();
+ return false;
}
/// <inheritdoc />
@@ -121,10 +125,11 @@ namespace StardewModdingAPI.Framework.ContentManagers
data = this.AssetsBeingLoaded.Track(assetName.Name, () =>
{
IAssetInfo info = new AssetInfo(assetName.LocaleCode, assetName, typeof(T), this.AssertAndNormalizeAssetName);
+ AssetOperationGroup? operations = this.Coordinator.GetAssetOperations<T>(info);
IAssetData asset =
- this.ApplyLoader<T>(info)
+ this.ApplyLoader<T>(info, operations?.LoadOperations)
?? new AssetDataForObject(info, this.RawLoad<T>(assetName, useCache), this.AssertAndNormalizeAssetName, this.Reflection);
- asset = this.ApplyEditors<T>(info, asset);
+ asset = this.ApplyEditors<T>(info, asset, operations?.EditOperations);
return (T)asset.Data;
});
}
@@ -149,25 +154,23 @@ namespace StardewModdingAPI.Framework.ContentManagers
*********/
/// <summary>Load the initial asset from the registered loaders.</summary>
/// <param name="info">The basic asset metadata.</param>
+ /// <param name="loadOperations">The load operations to apply to the asset.</param>
/// <returns>Returns the loaded asset metadata, or <c>null</c> if no loader matched.</returns>
- private IAssetData? ApplyLoader<T>(IAssetInfo info)
+ private IAssetData? ApplyLoader<T>(IAssetInfo info, List<AssetLoadOperation>? loadOperations)
where T : notnull
{
// find matching loader
- AssetLoadOperation? loader;
+ AssetLoadOperation? loader = null;
+ if (loadOperations?.Count > 0)
{
- AssetLoadOperation[] loaders = this.GetLoaders<T>(info).OrderByDescending(p => p.Priority).ToArray();
-
- if (!this.AssertMaxOneRequiredLoader(info, loaders, out string? error))
+ if (!this.AssertMaxOneRequiredLoader(info, loadOperations, out string? error))
{
this.Monitor.Log(error, LogLevel.Warn);
return null;
}
- loader = loaders.FirstOrDefault();
+ loader = loadOperations.OrderByDescending(p => p.Priority).FirstOrDefault();
}
-
- // no loader found
if (loader == null)
return null;
@@ -195,9 +198,13 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <typeparam name="T">The asset type.</typeparam>
/// <param name="info">The basic asset metadata.</param>
/// <param name="asset">The loaded asset.</param>
- private IAssetData ApplyEditors<T>(IAssetInfo info, IAssetData asset)
+ /// <param name="editOperations">The edit operations to apply to the asset.</param>
+ private IAssetData ApplyEditors<T>(IAssetInfo info, IAssetData asset, List<AssetEditOperation>? editOperations)
where T : notnull
{
+ if (editOperations?.Count is not > 0)
+ return asset;
+
IAssetData GetNewData(object data) => new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName, this.Reflection);
// special case: if the asset was loaded with a more general type like 'object', call editors with the actual type instead.
@@ -210,12 +217,12 @@ namespace StardewModdingAPI.Framework.ContentManagers
return (IAssetData)this.GetType()
.GetMethod(nameof(this.ApplyEditors), BindingFlags.NonPublic | BindingFlags.Instance)!
.MakeGenericMethod(actualType)
- .Invoke(this, new object[] { info, asset })!;
+ .Invoke(this, new object[] { info, asset, editOperations })!;
}
}
// edit asset
- AssetEditOperation[] editors = this.GetEditors<T>(info).OrderBy(p => p.Priority).ToArray();
+ AssetEditOperation[] editors = editOperations.OrderBy(p => p.Priority).ToArray();
foreach (AssetEditOperation editor in editors)
{
IModMetadata mod = editor.Mod;
@@ -250,34 +257,12 @@ namespace StardewModdingAPI.Framework.ContentManagers
return asset;
}
- /// <summary>Get the asset loaders which handle an asset.</summary>
- /// <typeparam name="T">The asset type.</typeparam>
- /// <param name="info">The basic asset metadata.</param>
- private IEnumerable<AssetLoadOperation> GetLoaders<T>(IAssetInfo info)
- where T : notnull
- {
- return this.Coordinator
- .GetAssetOperations<T>(info)
- .SelectMany(p => p.LoadOperations);
- }
-
- /// <summary>Get the asset editors to apply to an asset.</summary>
- /// <typeparam name="T">The asset type.</typeparam>
- /// <param name="info">The basic asset metadata.</param>
- private IEnumerable<AssetEditOperation> GetEditors<T>(IAssetInfo info)
- where T : notnull
- {
- return this.Coordinator
- .GetAssetOperations<T>(info)
- .SelectMany(p => p.EditOperations);
- }
-
/// <summary>Assert that at most one loader will be applied to an asset.</summary>
/// <param name="info">The basic asset metadata.</param>
/// <param name="loaders">The asset loaders to apply.</param>
/// <param name="error">The error message to show to the user, if the method returns false.</param>
/// <returns>Returns true if only one loader will apply, else false.</returns>
- private bool AssertMaxOneRequiredLoader(IAssetInfo info, AssetLoadOperation[] loaders, [NotNullWhen(false)] out string? error)
+ private bool AssertMaxOneRequiredLoader(IAssetInfo info, List<AssetLoadOperation> loaders, [NotNullWhen(false)] out string? error)
{
AssetLoadOperation[] required = loaders.Where(p => p.Priority == AssetLoadPriority.Exclusive).ToArray();
if (required.Length <= 1)
@@ -295,7 +280,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
? $"Multiple mods want to provide the '{info.Name}' asset: {string.Join(", ", loaderNames)}"
: $"The '{loaderNames[0]}' mod wants to provide the '{info.Name}' asset multiple times";
- error = $"{errorPhrase}. An asset can't be loaded multiple times, so 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.)";
+ error = $"{errorPhrase}. An asset can't be loaded multiple times, so SMAPI will use the default asset instead. Uninstall one of the mods to fix this. (Message for modders: you should avoid {nameof(AssetLoadPriority)}.{nameof(AssetLoadPriority.Exclusive)} and {nameof(IAssetLoader)} if possible to avoid conflicts.)";
return false;
}
diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
index 7cac8f36..85e109c8 100644
--- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
@@ -94,7 +94,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string? contentManagerID, out IAssetName? relativePath))
{
if (contentManagerID != this.Name)
- throw this.GetLoadError(assetName, "can't load a different mod's managed asset key through this mod content manager.");
+ throw this.GetLoadError(assetName, ContentLoadErrorType.AccessDenied, "can't load a different mod's managed asset key through this mod content manager.");
assetName = relativePath;
}
}
@@ -106,7 +106,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
// get file
FileInfo file = this.GetModFile<T>(assetName.Name);
if (!file.Exists)
- throw this.GetLoadError(assetName, "the specified path doesn't exist.");
+ throw this.GetLoadError(assetName, ContentLoadErrorType.AssetDoesNotExist, "the specified path doesn't exist.");
// load content
asset = file.Extension.ToLower() switch
@@ -121,7 +121,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
}
catch (Exception ex) when (ex is not SContentLoadException)
{
- throw this.GetLoadError(assetName, "an unexpected occurred.", ex);
+ throw this.GetLoadError(assetName, ContentLoadErrorType.Other, "an unexpected occurred.", ex);
}
// track & return asset
@@ -157,7 +157,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
{
// validate
if (!typeof(T).IsAssignableFrom(typeof(XmlSource)))
- throw this.GetLoadError(assetName, $"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(XmlSource)}'.");
+ throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(XmlSource)}'.");
// load
string source = File.ReadAllText(file.FullName);
@@ -171,7 +171,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
private T LoadDataFile<T>(IAssetName assetName, FileInfo file)
{
if (!this.JsonHelper.ReadJsonFileIfExists(file.FullName, out T? asset))
- throw this.GetLoadError(assetName, "the JSON file is invalid."); // should never happen since we check for file existence before calling this method
+ throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, "the JSON file is invalid."); // should never happen since we check for file existence before calling this method
return asset;
}
@@ -184,7 +184,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
{
// validate
if (typeof(T) != typeof(Texture2D))
- throw this.GetLoadError(assetName, $"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Texture2D)}'.");
+ throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Texture2D)}'.");
// load
using FileStream stream = File.OpenRead(file.FullName);
@@ -201,7 +201,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
{
// validate
if (typeof(T) != typeof(Map))
- throw this.GetLoadError(assetName, $"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Map)}'.");
+ throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Map)}'.");
// load
FormatManager formatManager = FormatManager.Instance;
@@ -239,16 +239,17 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <param name="file">The file to load.</param>
private T HandleUnknownFileType<T>(IAssetName assetName, FileInfo file)
{
- throw this.GetLoadError(assetName, $"unknown file extension '{file.Extension}'; must be one of '.fnt', '.json', '.png', '.tbin', '.tmx', or '.xnb'.");
+ throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidName, $"unknown file extension '{file.Extension}'; must be one of '.fnt', '.json', '.png', '.tbin', '.tmx', or '.xnb'.");
}
/// <summary>Get an error which indicates that an asset couldn't be loaded.</summary>
+ /// <param name="errorType">Why loading an asset through the content pipeline failed.</param>
/// <param name="assetName">The asset name that failed to load.</param>
/// <param name="reasonPhrase">The reason the file couldn't be loaded.</param>
/// <param name="exception">The underlying exception, if applicable.</param>
- private SContentLoadException GetLoadError(IAssetName assetName, string reasonPhrase, Exception? exception = null)
+ private SContentLoadException GetLoadError(IAssetName assetName, ContentLoadErrorType errorType, string reasonPhrase, Exception? exception = null)
{
- return new($"Failed loading asset '{assetName}' from {this.Name}: {reasonPhrase}", exception);
+ return new(errorType, $"Failed loading asset '{assetName}' from {this.Name}: {reasonPhrase}", exception);
}
/// <summary>Get a file from the mod folder.</summary>
@@ -328,13 +329,13 @@ namespace StardewModdingAPI.Framework.ContentManagers
// validate tilesheet path
string errorPrefix = $"{this.ModName} loaded map '{relativeMapPath}' with invalid tilesheet path '{imageSource}'.";
if (Path.IsPathRooted(imageSource) || PathUtilities.GetSegments(imageSource).Contains(".."))
- throw new SContentLoadException($"{errorPrefix} Tilesheet paths must be a relative path without directory climbing (../).");
+ throw new SContentLoadException(ContentLoadErrorType.InvalidData, $"{errorPrefix} Tilesheet paths must be a relative path without directory climbing (../).");
// load best match
try
{
if (!this.TryGetTilesheetAssetName(relativeMapFolder, imageSource, out IAssetName? assetName, out string? error))
- throw new SContentLoadException($"{errorPrefix} {error}");
+ throw new SContentLoadException(ContentLoadErrorType.InvalidData, $"{errorPrefix} {error}");
if (assetName is not null)
{
@@ -346,7 +347,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
}
catch (Exception ex) when (ex is not SContentLoadException)
{
- throw new SContentLoadException($"{errorPrefix} The tilesheet couldn't be loaded.", ex);
+ throw new SContentLoadException(ContentLoadErrorType.InvalidData, $"{errorPrefix} The tilesheet couldn't be loaded.", ex);
}
}
}