diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index b255dae1..fbbbe2d2 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -296,6 +296,22 @@ namespace StardewModdingAPI.Framework return Path.Combine(this.ManagedPrefix, modID.ToLower()); } + /// Get whether an asset from a mod folder exists. + /// The unique name for the content manager which should load this asset. + /// The asset name within the mod folder. + public bool DoesManagedAssetExist(string contentManagerID, IAssetName assetName) + { + // get content manager + IContentManager contentManager = this.ContentManagerLock.InReadLock(() => + this.ContentManagers.FirstOrDefault(p => p.IsNamespaced && p.Name == contentManagerID) + ); + if (contentManager == null) + throw new InvalidOperationException($"The '{contentManagerID}' prefix isn't handled by any mod."); + + // get whether the asset exists + return contentManager.DoesAssetExist(assetName); + } + /// Get a copy of an asset from a mod folder. /// The asset type. /// The unique name for the content manager which should load this asset. diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index 3efc33bb..030c60a7 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -92,6 +92,12 @@ namespace StardewModdingAPI.Framework.ContentManagers this.BaseDisposableReferences = reflection.GetField>(this, "disposableAssets").GetValue(); } + /// + public virtual bool DoesAssetExist(IAssetName assetName) + { + return this.Cache.ContainsKey(assetName.Name); + } + /// [Obsolete("This method is implemented for the base game and should not be used directly. To load an asset from the underlying content manager directly, use " + nameof(BaseContentManager.RawLoad) + " instead.")] public override T LoadBase(string assetName) diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index 9f686f97..e7fb0c5f 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -62,6 +62,29 @@ namespace StardewModdingAPI.Framework.ContentManagers this.OnLoadingFirstAsset = onLoadingFirstAsset; } + /// + public override bool DoesAssetExist(IAssetName assetName) + { + if (base.DoesAssetExist(assetName)) + return true; + + // managed asset + if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string contentManagerID, out IAssetName relativePath)) + return this.Coordinator.DoesManagedAssetExist(contentManagerID, relativePath); + + // else check for loaders + string locale = this.GetLocale(); + IAssetInfo info = new AssetInfo(locale, assetName, typeof(object), this.AssertAndNormalizeAssetName); + ModLinked[] loaders = this.GetLoaders(info).ToArray(); + if (loaders.Length > 1) + { + string[] loaderNames = loaders.Select(p => p.Mod.DisplayName).ToArray(); + this.Monitor.Log($"Multiple mods want to provide the '{info.Name}' asset ({string.Join(", ", loaderNames)}), but an asset can't be loaded multiple times. SMAPI will use the default asset instead; uninstall one of the mods to fix this. (Message for modders: you should usually use {typeof(IAssetEditor)} instead to avoid conflicts.)", LogLevel.Warn); + } + + return loaders.Length == 1; + } + /// public override T Load(IAssetName assetName, LanguageCode language, bool useCache) { @@ -246,20 +269,7 @@ namespace StardewModdingAPI.Framework.ContentManagers private IAssetData ApplyLoader(IAssetInfo info) { // find matching loaders - var loaders = this.Loaders - .Where(entry => - { - try - { - return entry.Data.CanLoad(info); - } - catch (Exception ex) - { - entry.Mod.LogAsMod($"Mod failed when checking whether it could load asset '{info.Name}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); - return false; - } - }) - .ToArray(); + var loaders = this.GetLoaders(info).ToArray(); // validate loaders if (!loaders.Any()) @@ -360,6 +370,26 @@ namespace StardewModdingAPI.Framework.ContentManagers return asset; } + /// Get the asset loaders which handle the asset. + /// The asset type. + /// The basic asset metadata. + private IEnumerable> GetLoaders(IAssetInfo info) + { + return this.Loaders + .Where(entry => + { + try + { + return entry.Data.CanLoad(info); + } + catch (Exception ex) + { + entry.Mod.LogAsMod($"Mod failed when checking whether it could load asset '{info.Name}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); + return false; + } + }); + } + /// Validate that an asset loaded by a mod is valid and won't cause issues, and fix issues if possible. /// The asset type. /// The basic asset metadata. diff --git a/src/SMAPI/Framework/ContentManagers/IContentManager.cs b/src/SMAPI/Framework/ContentManagers/IContentManager.cs index fe0519b6..6d71472f 100644 --- a/src/SMAPI/Framework/ContentManagers/IContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/IContentManager.cs @@ -28,6 +28,10 @@ namespace StardewModdingAPI.Framework.ContentManagers /********* ** Methods *********/ + /// Get whether an asset exists and can be loaded. + /// The normalized asset name. + bool DoesAssetExist(IAssetName assetName); + /// Load an asset that has been processed by the content pipeline. /// The type of asset to load. /// The asset name relative to the loader root directory. diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 267146ad..90836fcf 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -63,6 +63,16 @@ namespace StardewModdingAPI.Framework.ContentManagers this.ModName = modName; } + /// + public override bool DoesAssetExist(IAssetName assetName) + { + if (base.DoesAssetExist(assetName)) + return true; + + FileInfo file = this.GetModFile(assetName.Name); + return file.Exists; + } + /// public override T Load(string assetName) {