summaryrefslogtreecommitdiff
path: root/src/SMAPI
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI')
-rw-r--r--src/SMAPI/Constants.cs2
-rw-r--r--src/SMAPI/Events/AssetRequestedEventArgs.cs43
-rw-r--r--src/SMAPI/Framework/Content/AssetEditOperation.cs39
-rw-r--r--src/SMAPI/Framework/Content/AssetInfo.cs6
-rw-r--r--src/SMAPI/Framework/Content/AssetLoadOperation.cs39
-rw-r--r--src/SMAPI/Framework/Content/AssetOperationGroup.cs33
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs63
-rw-r--r--src/SMAPI/Framework/ContentManagers/ModContentManager.cs39
-rw-r--r--src/SMAPI/Framework/ContentPack.cs37
-rw-r--r--src/SMAPI/Framework/ModHelpers/CommandHelper.cs2
-rw-r--r--src/SMAPI/Framework/ModHelpers/ContentHelper.cs2
-rw-r--r--src/SMAPI/Framework/ModHelpers/ContentPackHelper.cs10
-rw-r--r--src/SMAPI/Framework/ModHelpers/ModContentHelper.cs16
-rw-r--r--src/SMAPI/Framework/ModHelpers/ModHelper.cs6
-rw-r--r--src/SMAPI/Framework/ModLoading/AssemblyLoader.cs14
-rw-r--r--src/SMAPI/Framework/ModLoading/ModResolver.cs10
-rw-r--r--src/SMAPI/Framework/Models/SConfig.cs21
-rw-r--r--src/SMAPI/Framework/SCore.cs91
-rw-r--r--src/SMAPI/SMAPI.config.json16
19 files changed, 211 insertions, 278 deletions
diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs
index b1a9cc82..a289ce4b 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.1";
+ internal static string RawApiVersion = "3.14.2";
}
/// <summary>Contains SMAPI's constants and assumptions.</summary>
diff --git a/src/SMAPI/Events/AssetRequestedEventArgs.cs b/src/SMAPI/Events/AssetRequestedEventArgs.cs
index 3bcf83b9..d0aef1db 100644
--- a/src/SMAPI/Events/AssetRequestedEventArgs.cs
+++ b/src/SMAPI/Events/AssetRequestedEventArgs.cs
@@ -19,19 +19,22 @@ namespace StardewModdingAPI.Events
/// <summary>Get the mod metadata for a content pack, if it's a valid content pack for the mod.</summary>
private readonly Func<IModMetadata, string?, string, IModMetadata?> GetOnBehalfOf;
+ /// <summary>The asset info being requested.</summary>
+ private readonly IAssetInfo AssetInfo;
+
/*********
** Accessors
*********/
/// <summary>The name of the asset being requested.</summary>
- public IAssetName Name { get; }
+ public IAssetName Name => this.AssetInfo.Name;
/// <summary>The <see cref="Name"/> with any locale codes stripped.</summary>
/// <remarks>For example, if <see cref="Name"/> contains a locale like <c>Data/Bundles.fr-FR</c>, this will be the name without locale like <c>Data/Bundles</c>. If the name has no locale, this field is equivalent.</remarks>
- public IAssetName NameWithoutLocale { get; }
+ public IAssetName NameWithoutLocale => this.AssetInfo.NameWithoutLocale;
/// <summary>The requested data type.</summary>
- public Type DataType { get; }
+ public Type DataType => this.AssetInfo.DataType;
/// <summary>The load operations requested by the event handler.</summary>
internal IList<AssetLoadOperation> LoadOperations { get; } = new List<AssetLoadOperation>();
@@ -45,16 +48,12 @@ namespace StardewModdingAPI.Events
*********/
/// <summary>Construct an instance.</summary>
/// <param name="mod">The mod handling the event.</param>
- /// <param name="name">The name of the asset being requested.</param>
- /// <param name="dataType">The requested data type.</param>
- /// <param name="nameWithoutLocale">The <paramref name="name"/> with any locale codes stripped.</param>
+ /// <param name="assetInfo">The asset info being requested.</param>
/// <param name="getOnBehalfOf">Get the mod metadata for a content pack, if it's a valid content pack for the mod.</param>
- internal AssetRequestedEventArgs(IModMetadata mod, IAssetName name, IAssetName nameWithoutLocale, Type dataType, Func<IModMetadata, string?, string, IModMetadata?> getOnBehalfOf)
+ internal AssetRequestedEventArgs(IModMetadata mod, IAssetInfo assetInfo, Func<IModMetadata, string?, string, IModMetadata?> getOnBehalfOf)
{
this.Mod = mod;
- this.Name = name;
- this.NameWithoutLocale = nameWithoutLocale;
- this.DataType = dataType;
+ this.AssetInfo = assetInfo;
this.GetOnBehalfOf = getOnBehalfOf;
}
@@ -73,10 +72,10 @@ namespace StardewModdingAPI.Events
{
this.LoadOperations.Add(
new AssetLoadOperation(
- mod: this.Mod,
- priority: priority,
- onBehalfOf: this.GetOnBehalfOf(this.Mod, onBehalfOf, "load assets"),
- getData: _ => load()
+ Mod: this.Mod,
+ OnBehalfOf: this.GetOnBehalfOf(this.Mod, onBehalfOf, "load assets"),
+ Priority: priority,
+ GetData: _ => load()
)
);
}
@@ -97,10 +96,10 @@ namespace StardewModdingAPI.Events
{
this.LoadOperations.Add(
new AssetLoadOperation(
- mod: this.Mod,
- priority: priority,
- onBehalfOf: null,
- _ => this.Mod.Mod!.Helper.ModContent.Load<TAsset>(relativePath)
+ Mod: this.Mod,
+ OnBehalfOf: null,
+ Priority: priority,
+ GetData: _ => this.Mod.Mod!.Helper.ModContent.Load<TAsset>(relativePath)
)
);
}
@@ -120,10 +119,10 @@ namespace StardewModdingAPI.Events
{
this.EditOperations.Add(
new AssetEditOperation(
- mod: this.Mod,
- priority: priority,
- onBehalfOf: this.GetOnBehalfOf(this.Mod, onBehalfOf, "edit assets"),
- apply
+ Mod: this.Mod,
+ Priority: priority,
+ OnBehalfOf: this.GetOnBehalfOf(this.Mod, onBehalfOf, "edit assets"),
+ ApplyEdit: apply
)
);
}
diff --git a/src/SMAPI/Framework/Content/AssetEditOperation.cs b/src/SMAPI/Framework/Content/AssetEditOperation.cs
index 464948b0..11b8811b 100644
--- a/src/SMAPI/Framework/Content/AssetEditOperation.cs
+++ b/src/SMAPI/Framework/Content/AssetEditOperation.cs
@@ -4,38 +4,9 @@ using StardewModdingAPI.Events;
namespace StardewModdingAPI.Framework.Content
{
/// <summary>An edit to apply to an asset when it's requested from the content pipeline.</summary>
- internal class AssetEditOperation
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The mod applying the edit.</summary>
- public IModMetadata Mod { get; }
-
- /// <summary>If there are multiple edits that apply to the same asset, the priority with which this one should be applied.</summary>
- public AssetEditPriority Priority { get; }
-
- /// <summary>The content pack on whose behalf the edit is being applied, if any.</summary>
- public IModMetadata? OnBehalfOf { get; }
-
- /// <summary>Apply the edit to an asset.</summary>
- public Action<IAssetData> ApplyEdit { get; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="mod">The mod applying the edit.</param>
- /// <param name="priority">If there are multiple edits that apply to the same asset, the priority with which this one should be applied.</param>
- /// <param name="onBehalfOf">The content pack on whose behalf the edit is being applied, if any.</param>
- /// <param name="applyEdit">Apply the edit to an asset.</param>
- public AssetEditOperation(IModMetadata mod, AssetEditPriority priority, IModMetadata? onBehalfOf, Action<IAssetData> applyEdit)
- {
- this.Mod = mod;
- this.Priority = priority;
- this.OnBehalfOf = onBehalfOf;
- this.ApplyEdit = applyEdit;
- }
- }
+ /// <param name="Mod">The mod applying the edit.</param>
+ /// <param name="Priority">If there are multiple edits that apply to the same asset, the priority with which this one should be applied.</param>
+ /// <param name="OnBehalfOf">The content pack on whose behalf the edit is being applied, if any.</param>
+ /// <param name="ApplyEdit">Apply the edit to an asset.</param>
+ internal record AssetEditOperation(IModMetadata Mod, AssetEditPriority Priority, IModMetadata? OnBehalfOf, Action<IAssetData> ApplyEdit);
}
diff --git a/src/SMAPI/Framework/Content/AssetInfo.cs b/src/SMAPI/Framework/Content/AssetInfo.cs
index 363fffb3..a4f0a408 100644
--- a/src/SMAPI/Framework/Content/AssetInfo.cs
+++ b/src/SMAPI/Framework/Content/AssetInfo.cs
@@ -13,6 +13,9 @@ namespace StardewModdingAPI.Framework.Content
/// <summary>Normalizes an asset key to match the cache key.</summary>
protected readonly Func<string, string> GetNormalizedPath;
+ /// <summary>The backing field for <see cref="NameWithoutLocale"/>.</summary>
+ private IAssetName? NameWithoutLocaleImpl;
+
/*********
** Accessors
@@ -24,7 +27,7 @@ namespace StardewModdingAPI.Framework.Content
public IAssetName Name { get; }
/// <inheritdoc />
- public IAssetName NameWithoutLocale { get; }
+ public IAssetName NameWithoutLocale => this.NameWithoutLocaleImpl ??= this.Name.GetBaseAssetName();
/// <inheritdoc />
[Obsolete($"Use {nameof(AssetInfo.Name)} or {nameof(AssetInfo.NameWithoutLocale)} instead. This property will be removed in SMAPI 4.0.0.")]
@@ -64,7 +67,6 @@ namespace StardewModdingAPI.Framework.Content
{
this.Locale = locale;
this.Name = assetName;
- this.NameWithoutLocale = assetName.GetBaseAssetName();
this.DataType = type;
this.GetNormalizedPath = getNormalizedPath;
}
diff --git a/src/SMAPI/Framework/Content/AssetLoadOperation.cs b/src/SMAPI/Framework/Content/AssetLoadOperation.cs
index b6cdec27..7af07dfd 100644
--- a/src/SMAPI/Framework/Content/AssetLoadOperation.cs
+++ b/src/SMAPI/Framework/Content/AssetLoadOperation.cs
@@ -4,38 +4,9 @@ using StardewModdingAPI.Events;
namespace StardewModdingAPI.Framework.Content
{
/// <summary>An operation which provides the initial instance of an asset when it's requested from the content pipeline.</summary>
- internal class AssetLoadOperation
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The mod loading the asset.</summary>
- public IModMetadata Mod { get; }
-
- /// <summary>The content pack on whose behalf the asset is being loaded, if any.</summary>
- public IModMetadata? OnBehalfOf { get; }
-
- /// <summary>If there are multiple loads that apply to the same asset, the priority with which this one should be applied.</summary>
- public AssetLoadPriority Priority { get; }
-
- /// <summary>Load the initial value for an asset.</summary>
- public Func<IAssetInfo, object> GetData { get; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="mod">The mod applying the edit.</param>
- /// <param name="priority">If there are multiple loads that apply to the same asset, the priority with which this one should be applied.</param>
- /// <param name="onBehalfOf">The content pack on whose behalf the asset is being loaded, if any.</param>
- /// <param name="getData">Load the initial value for an asset.</param>
- public AssetLoadOperation(IModMetadata mod, AssetLoadPriority priority, IModMetadata? onBehalfOf, Func<IAssetInfo, object> getData)
- {
- this.Mod = mod;
- this.Priority = priority;
- this.OnBehalfOf = onBehalfOf;
- this.GetData = getData;
- }
- }
+ /// <param name="Mod">The mod applying the edit.</param>
+ /// <param name="Priority">If there are multiple loads that apply to the same asset, the priority with which this one should be applied.</param>
+ /// <param name="OnBehalfOf">The content pack on whose behalf the asset is being loaded, if any.</param>
+ /// <param name="GetData">Load the initial value for an asset.</param>
+ internal record AssetLoadOperation(IModMetadata Mod, IModMetadata? OnBehalfOf, AssetLoadPriority Priority, Func<IAssetInfo, object> GetData);
}
diff --git a/src/SMAPI/Framework/Content/AssetOperationGroup.cs b/src/SMAPI/Framework/Content/AssetOperationGroup.cs
index a2fcb722..1566a8f0 100644
--- a/src/SMAPI/Framework/Content/AssetOperationGroup.cs
+++ b/src/SMAPI/Framework/Content/AssetOperationGroup.cs
@@ -1,33 +1,8 @@
namespace StardewModdingAPI.Framework.Content
{
/// <summary>A set of operations to apply to an asset for a given <see cref="IAssetEditor"/> or <see cref="IAssetLoader"/> implementation.</summary>
- internal class AssetOperationGroup
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The mod applying the changes.</summary>
- public IModMetadata Mod { get; }
-
- /// <summary>The load operations to apply.</summary>
- public AssetLoadOperation[] LoadOperations { get; }
-
- /// <summary>The edit operations to apply.</summary>
- public AssetEditOperation[] EditOperations { get; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="mod">The mod applying the changes.</param>
- /// <param name="loadOperations">The load operations to apply.</param>
- /// <param name="editOperations">The edit operations to apply.</param>
- public AssetOperationGroup(IModMetadata mod, AssetLoadOperation[] loadOperations, AssetEditOperation[] editOperations)
- {
- this.Mod = mod;
- this.LoadOperations = loadOperations;
- this.EditOperations = editOperations;
- }
- }
+ /// <param name="Mod">The mod applying the changes.</param>
+ /// <param name="LoadOperations">The load operations to apply.</param>
+ /// <param name="EditOperations">The edit operations to apply.</param>
+ internal record AssetOperationGroup(IModMetadata Mod, AssetLoadOperation[] LoadOperations, AssetEditOperation[] EditOperations);
}
diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs
index 4f52d57e..6702f5e6 100644
--- a/src/SMAPI/Framework/ContentCoordinator.cs
+++ b/src/SMAPI/Framework/ContentCoordinator.cs
@@ -32,8 +32,8 @@ namespace StardewModdingAPI.Framework
/// <summary>An asset key prefix for assets from SMAPI mod folders.</summary>
private readonly string ManagedPrefix = "SMAPI";
- /// <summary>Get a file path lookup for the given directory.</summary>
- private readonly Func<string, IFilePathLookup> GetFilePathLookup;
+ /// <summary>Get a file lookup for the given directory.</summary>
+ private readonly Func<string, IFileLookup> GetFileLookup;
/// <summary>Encapsulates monitoring and logging.</summary>
private readonly IMonitor Monitor;
@@ -79,14 +79,14 @@ namespace StardewModdingAPI.Framework
private Lazy<Dictionary<string, LocalizedContentManager.LanguageCode>> LocaleCodes;
/// <summary>The cached asset load/edit operations to apply, indexed by asset name.</summary>
- private readonly TickCacheDictionary<IAssetName, AssetOperationGroup[]> AssetOperationsByKey = new();
+ private readonly TickCacheDictionary<IAssetName, IList<AssetOperationGroup>> AssetOperationsByKey = new();
/// <summary>A cache of asset operation groups created for legacy <see cref="IAssetLoader"/> implementations.</summary>
- [Obsolete]
+ [Obsolete("This only exists to support legacy code and will be removed in SMAPI 4.0.0.")]
private readonly Dictionary<IAssetLoader, Dictionary<Type, AssetOperationGroup>> LegacyLoaderCache = new(ReferenceEqualityComparer.Instance);
/// <summary>A cache of asset operation groups created for legacy <see cref="IAssetEditor"/> implementations.</summary>
- [Obsolete]
+ [Obsolete("This only exists to support legacy code and will be removed in SMAPI 4.0.0.")]
private readonly Dictionary<IAssetEditor, Dictionary<Type, AssetOperationGroup>> LegacyEditorCache = new(ReferenceEqualityComparer.Instance);
@@ -100,11 +100,11 @@ namespace StardewModdingAPI.Framework
public LocalizedContentManager.LanguageCode Language => this.MainContentManager.Language;
/// <summary>Interceptors which provide the initial versions of matching assets.</summary>
- [Obsolete]
+ [Obsolete("This only exists to support legacy code and will be removed in SMAPI 4.0.0.")]
public IList<ModLinked<IAssetLoader>> Loaders { get; } = new List<ModLinked<IAssetLoader>>();
/// <summary>Interceptors which edit matching assets after they're loaded.</summary>
- [Obsolete]
+ [Obsolete("This only exists to support legacy code and will be removed in SMAPI 4.0.0.")]
public IList<ModLinked<IAssetEditor>> Editors { get; } = new List<ModLinked<IAssetEditor>>();
/// <summary>The absolute path to the <see cref="ContentManager.RootDirectory"/>.</summary>
@@ -123,12 +123,12 @@ namespace StardewModdingAPI.Framework
/// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param>
/// <param name="onLoadingFirstAsset">A callback to invoke the first time *any* game content manager loads an asset.</param>
/// <param name="onAssetLoaded">A callback to invoke when an asset is fully loaded.</param>
- /// <param name="getFilePathLookup">Get a file path lookup for the given directory.</param>
+ /// <param name="getFileLookup">Get a file lookup for the given directory.</param>
/// <param name="onAssetsInvalidated">A callback to invoke when any asset names have been invalidated from the cache.</param>
/// <param name="requestAssetOperations">Get the load/edit operations to apply to an asset by querying registered <see cref="IContentEvents.AssetRequested"/> event handlers.</param>
- public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, Action<BaseContentManager, IAssetName> onAssetLoaded, Func<string, IFilePathLookup> getFilePathLookup, Action<IList<IAssetName>> onAssetsInvalidated, Func<IAssetInfo, IList<AssetOperationGroup>> requestAssetOperations)
+ public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, Action<BaseContentManager, IAssetName> onAssetLoaded, Func<string, IFileLookup> getFileLookup, Action<IList<IAssetName>> onAssetsInvalidated, Func<IAssetInfo, IList<AssetOperationGroup>> requestAssetOperations)
{
- this.GetFilePathLookup = getFilePathLookup;
+ this.GetFileLookup = getFileLookup;
this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor));
this.Reflection = reflection;
this.JsonHelper = jsonHelper;
@@ -200,7 +200,7 @@ namespace StardewModdingAPI.Framework
reflection: this.Reflection,
jsonHelper: this.JsonHelper,
onDisposing: this.OnDisposing,
- relativePathLookup: this.GetFilePathLookup(rootDirectory)
+ fileLookup: this.GetFileLookup(rootDirectory)
);
this.ContentManagers.Add(manager);
return manager;
@@ -449,12 +449,16 @@ namespace StardewModdingAPI.Framework
/// <summary>Get the asset load and edit operations to apply to a given asset if it's (re)loaded now.</summary>
/// <typeparam name="T">The asset type.</typeparam>
/// <param name="info">The asset info to load or edit.</param>
- public IEnumerable<AssetOperationGroup> GetAssetOperations<T>(IAssetInfo info)
+ public IList<AssetOperationGroup> GetAssetOperations<T>(IAssetInfo info)
where T : notnull
{
return this.AssetOperationsByKey.GetOrSet(
info.Name,
- () => this.GetAssetOperationsWithoutCache<T>(info).ToArray()
+#pragma warning disable CS0612, CS0618 // deprecated code
+ () => this.Editors.Count > 0 || this.Loaders.Count > 0
+ ? this.GetAssetOperationsIncludingLegacyWithoutCache<T>(info).ToArray()
+#pragma warning restore CS0612, CS0618
+ : this.RequestAssetOperations(info)
);
}
@@ -580,7 +584,8 @@ namespace StardewModdingAPI.Framework
/// <summary>Get the asset load and edit operations to apply to a given asset if it's (re)loaded now, ignoring the <see cref="AssetOperationsByKey"/> cache.</summary>
/// <typeparam name="T">The asset type.</typeparam>
/// <param name="info">The asset info to load or edit.</param>
- private IEnumerable<AssetOperationGroup> GetAssetOperationsWithoutCache<T>(IAssetInfo info)
+ [Obsolete("This only exists to support legacy code and will be removed in SMAPI 4.0.0.")]
+ private IEnumerable<AssetOperationGroup> GetAssetOperationsIncludingLegacyWithoutCache<T>(IAssetInfo info)
where T : notnull
{
IAssetInfo legacyInfo = this.GetLegacyAssetInfo(info);
@@ -590,7 +595,6 @@ namespace StardewModdingAPI.Framework
yield return group;
// legacy load operations
-#pragma warning disable CS0612, CS0618 // deprecated code
foreach (ModLinked<IAssetLoader> loader in this.Loaders)
{
// check if loader applies
@@ -611,19 +615,19 @@ namespace StardewModdingAPI.Framework
editor: loader.Data,
dataType: info.DataType,
createGroup: () => new AssetOperationGroup(
- mod: loader.Mod,
- loadOperations: new[]
+ Mod: loader.Mod,
+ LoadOperations: new[]
{
new AssetLoadOperation(
- mod: loader.Mod,
- priority: AssetLoadPriority.Exclusive,
- onBehalfOf: null,
- getData: assetInfo => loader.Data.Load<T>(
+ Mod: loader.Mod,
+ OnBehalfOf: null,
+ Priority: AssetLoadPriority.Exclusive,
+ GetData: assetInfo => loader.Data.Load<T>(
this.GetLegacyAssetInfo(assetInfo)
)
)
},
- editOperations: Array.Empty<AssetEditOperation>()
+ EditOperations: Array.Empty<AssetEditOperation>()
)
);
}
@@ -670,15 +674,15 @@ namespace StardewModdingAPI.Framework
editor: editor.Data,
dataType: info.DataType,
createGroup: () => new AssetOperationGroup(
- mod: editor.Mod,
- loadOperations: Array.Empty<AssetLoadOperation>(),
- editOperations: new[]
+ Mod: editor.Mod,
+ LoadOperations: Array.Empty<AssetLoadOperation>(),
+ EditOperations: new[]
{
new AssetEditOperation(
- mod: editor.Mod,
- priority: priority,
- onBehalfOf: null,
- applyEdit: assetData => editor.Data.Edit<T>(
+ Mod: editor.Mod,
+ OnBehalfOf: null,
+ Priority: priority,
+ ApplyEdit: assetData => editor.Data.Edit<T>(
this.GetLegacyAssetData(assetData)
)
)
@@ -686,7 +690,6 @@ namespace StardewModdingAPI.Framework
)
);
}
-#pragma warning restore CS0612, CS0618
}
/// <summary>Get a cached asset operation group for a legacy <see cref="IAssetLoader"/> or <see cref="IAssetEditor"/> instance, creating it if needed.</summary>
diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
index 65dffd8b..7cac8f36 100644
--- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -33,11 +34,11 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>The game content manager used for map tilesheets not provided by the mod.</summary>
private readonly IContentManager GameContentManager;
- /// <summary>A lookup for relative paths within the <see cref="ContentManager.RootDirectory"/>.</summary>
- private readonly IFilePathLookup RelativePathLookup;
+ /// <summary>A lookup for files within the <see cref="ContentManager.RootDirectory"/>.</summary>
+ private readonly IFileLookup FileLookup;
/// <summary>If a map tilesheet's image source has no file extensions, the file extensions to check for in the local mod folder.</summary>
- private readonly string[] LocalTilesheetExtensions = { ".png", ".xnb" };
+ private static readonly HashSet<string> LocalTilesheetExtensions = new(StringComparer.OrdinalIgnoreCase) { ".png", ".xnb" };
/*********
@@ -55,12 +56,12 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <param name="reflection">Simplifies access to private code.</param>
/// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param>
/// <param name="onDisposing">A callback to invoke when the content manager is being disposed.</param>
- /// <param name="relativePathLookup">A lookup for relative paths within the <paramref name="rootDirectory"/>.</param>
- public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string modName, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action<BaseContentManager> onDisposing, IFilePathLookup relativePathLookup)
+ /// <param name="fileLookup">A lookup for files within the <paramref name="rootDirectory"/>.</param>
+ public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string modName, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action<BaseContentManager> onDisposing, IFileLookup fileLookup)
: base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: true)
{
this.GameContentManager = gameContentManager;
- this.RelativePathLookup = relativePathLookup;
+ this.FileLookup = fileLookup;
this.JsonHelper = jsonHelper;
this.ModName = modName;
@@ -73,7 +74,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
if (base.DoesAssetExist<T>(assetName))
return true;
- FileInfo file = this.GetModFile(assetName.Name);
+ FileInfo file = this.GetModFile<T>(assetName.Name);
return file.Exists;
}
@@ -103,7 +104,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
try
{
// get file
- FileInfo file = this.GetModFile(assetName.Name);
+ FileInfo file = this.GetModFile<T>(assetName.Name);
if (!file.Exists)
throw this.GetLoadError(assetName, "the specified path doesn't exist.");
@@ -139,9 +140,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <exception cref="ArgumentException">The <paramref name="key"/> is empty or contains invalid characters.</exception>
public IAssetName GetInternalAssetKey(string key)
{
- FileInfo file = this.GetModFile(key);
- string relativePath = Path.GetRelativePath(this.RootDirectory, file.FullName);
- string internalKey = Path.Combine(this.Name, relativePath);
+ string internalKey = Path.Combine(this.Name, PathUtilities.NormalizeAssetName(key));
return this.Coordinator.ParseAssetName(internalKey, allowLocales: false);
}
@@ -253,19 +252,17 @@ namespace StardewModdingAPI.Framework.ContentManagers
}
/// <summary>Get a file from the mod folder.</summary>
+ /// <typeparam name="T">The expected asset type.</typeparam>
/// <param name="path">The asset path relative to the content folder.</param>
- private FileInfo GetModFile(string path)
+ private FileInfo GetModFile<T>(string path)
{
- // map to case-insensitive path if needed
- path = this.RelativePathLookup.GetFilePath(path);
+ // get exact file
+ FileInfo file = this.FileLookup.GetFile(path);
- // try exact match
- FileInfo file = new(Path.Combine(this.FullRootDirectory, path));
-
- // try with default extension
- if (!file.Exists)
+ // try with default image extensions
+ if (!file.Exists && typeof(Texture2D).IsAssignableFrom(typeof(T)) && !ModContentManager.LocalTilesheetExtensions.Contains(file.Extension))
{
- foreach (string extension in this.LocalTilesheetExtensions)
+ foreach (string extension in ModContentManager.LocalTilesheetExtensions)
{
FileInfo result = new(file.FullName + extension);
if (result.Exists)
@@ -385,7 +382,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
// get relative to map file
{
string localKey = Path.Combine(modRelativeMapFolder, relativePath);
- if (this.GetModFile(localKey).Exists)
+ if (this.GetModFile<Texture2D>(localKey).Exists)
{
assetName = this.GetInternalAssetKey(localKey);
return true;
diff --git a/src/SMAPI/Framework/ContentPack.cs b/src/SMAPI/Framework/ContentPack.cs
index 9503a0e6..70fe51f8 100644
--- a/src/SMAPI/Framework/ContentPack.cs
+++ b/src/SMAPI/Framework/ContentPack.cs
@@ -16,8 +16,8 @@ namespace StardewModdingAPI.Framework
/// <summary>Encapsulates SMAPI's JSON file parsing.</summary>
private readonly JsonHelper JsonHelper;
- /// <summary>A lookup for relative paths within the <see cref="DirectoryPath"/>.</summary>
- private readonly IFilePathLookup RelativePathCache;
+ /// <summary>A lookup for files within the <see cref="DirectoryPath"/>.</summary>
+ private readonly IFileLookup FileLookup;
/*********
@@ -48,15 +48,15 @@ namespace StardewModdingAPI.Framework
/// <param name="content">Provides an API for loading content assets from the content pack's folder.</param>
/// <param name="translation">Provides translations stored in the content pack's <c>i18n</c> folder.</param>
/// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param>
- /// <param name="relativePathCache">A lookup for relative paths within the <paramref name="directoryPath"/>.</param>
- public ContentPack(string directoryPath, IManifest manifest, IModContentHelper content, TranslationHelper translation, JsonHelper jsonHelper, IFilePathLookup relativePathCache)
+ /// <param name="fileLookup">A lookup for files within the <paramref name="directoryPath"/>.</param>
+ public ContentPack(string directoryPath, IManifest manifest, IModContentHelper content, TranslationHelper translation, JsonHelper jsonHelper, IFileLookup fileLookup)
{
this.DirectoryPath = directoryPath;
this.Manifest = manifest;
this.ModContent = content;
this.TranslationImpl = translation;
this.JsonHelper = jsonHelper;
- this.RelativePathCache = relativePathCache;
+ this.FileLookup = fileLookup;
}
/// <inheritdoc />
@@ -83,14 +83,21 @@ namespace StardewModdingAPI.Framework
{
path = PathUtilities.NormalizePath(path);
- FileInfo file = this.GetFile(path, out path);
+ FileInfo file = this.GetFile(path);
+ bool didExist = file.Exists;
+
this.JsonHelper.WriteJsonFile(file.FullName, data);
- this.RelativePathCache.Add(path);
+ if (!didExist)
+ {
+ this.FileLookup.Add(
+ Path.GetRelativePath(this.DirectoryPath, file.FullName)
+ );
+ }
}
/// <inheritdoc />
- [Obsolete]
+ [Obsolete($"Use {nameof(IContentPack.ModContent)}.{nameof(IModContentHelper.Load)} instead. This method will be removed in SMAPI 4.0.0.")]
public T LoadAsset<T>(string key)
where T : notnull
{
@@ -98,7 +105,7 @@ namespace StardewModdingAPI.Framework
}
/// <inheritdoc />
- [Obsolete]
+ [Obsolete($"Use {nameof(IContentPack.ModContent)}.{nameof(IModContentHelper.GetInternalAssetName)} instead. This method will be removed in SMAPI 4.0.0.")]
public string GetActualAssetKey(string key)
{
return this.ModContent.GetInternalAssetName(key).Name;
@@ -112,20 +119,10 @@ namespace StardewModdingAPI.Framework
/// <param name="relativePath">The normalized file path relative to the content pack directory.</param>
private FileInfo GetFile(string relativePath)
{
- return this.GetFile(relativePath, out _);
- }
-
- /// <summary>Get the underlying file info.</summary>
- /// <param name="relativePath">The normalized file path relative to the content pack directory.</param>
- /// <param name="actualRelativePath">The relative path after case-insensitive matching.</param>
- private FileInfo GetFile(string relativePath, out string actualRelativePath)
- {
if (!PathUtilities.IsSafeRelativePath(relativePath))
throw new InvalidOperationException($"You must call {nameof(IContentPack)} methods with a relative path.");