From 1d3c99cc25f6c0d504fd5e43ea71ef327b6e9066 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 27 Mar 2022 13:42:14 -0400 Subject: split helper.Content API into game/mod content APIs --- src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 9 +- .../Framework/ModHelpers/GameContentHelper.cs | 129 +++++++++++++++++++++ src/SMAPI/Framework/ModHelpers/ModContentHelper.cs | 75 ++++++++++++ src/SMAPI/Framework/ModHelpers/ModHelper.cs | 38 +++++- 4 files changed, 240 insertions(+), 11 deletions(-) create mode 100644 src/SMAPI/Framework/ModHelpers/GameContentHelper.cs create mode 100644 src/SMAPI/Framework/ModHelpers/ModContentHelper.cs (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index 3a5c8938..b0064532 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -13,6 +13,7 @@ using StardewValley; namespace StardewModdingAPI.Framework.ModHelpers { /// Provides an API for loading content assets. + [Obsolete] internal class ContentHelper : BaseHelper, IContentHelper { /********* @@ -44,11 +45,9 @@ namespace StardewModdingAPI.Framework.ModHelpers public LocalizedContentManager.LanguageCode CurrentLocaleConstant => this.GameContentManager.Language; /// The observable implementation of . - [Obsolete] internal ObservableCollection ObservableAssetEditors { get; } = new(); /// The observable implementation of . - [Obsolete] internal ObservableCollection ObservableAssetLoaders { get; } = new(); /// @@ -105,12 +104,6 @@ namespace StardewModdingAPI.Framework.ModHelpers this.Monitor = monitor; } - /// - public IAssetName ParseAssetName(string rawName) - { - return this.ContentCore.ParseAssetName(rawName); - } - /// public T Load(string key, ContentSource source = ContentSource.ModFolder) { diff --git a/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs b/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs new file mode 100644 index 00000000..42a4de20 --- /dev/null +++ b/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs @@ -0,0 +1,129 @@ +using System; +using System.Linq; +using StardewModdingAPI.Framework.Content; +using StardewModdingAPI.Framework.ContentManagers; +using StardewModdingAPI.Framework.Exceptions; +using StardewValley; + +namespace StardewModdingAPI.Framework.ModHelpers +{ + /// + internal class GameContentHelper : BaseHelper, IGameContentHelper + { + /********* + ** Fields + *********/ + /// SMAPI's core content logic. + private readonly ContentCoordinator ContentCore; + + /// The underlying game content manager. + private readonly IContentManager GameContentManager; + + /// The friendly mod name for use in errors. + private readonly string ModName; + + /// Encapsulates monitoring and logging. + private readonly IMonitor Monitor; + + + /********* + ** Accessors + *********/ + /// + public string CurrentLocale => this.GameContentManager.GetLocale(); + + /// + public LocalizedContentManager.LanguageCode CurrentLocaleConstant => this.GameContentManager.Language; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// SMAPI's core content logic. + /// The unique ID of the relevant mod. + /// The friendly mod name for use in errors. + /// Encapsulates monitoring and logging. + public GameContentHelper(ContentCoordinator contentCore, string modID, string modName, IMonitor monitor) + : base(modID) + { + string managedAssetPrefix = contentCore.GetManagedAssetPrefix(modID); + + this.ContentCore = contentCore; + this.GameContentManager = contentCore.CreateGameContentManager(managedAssetPrefix + ".content"); + this.ModName = modName; + this.Monitor = monitor; + } + + /// + public IAssetName ParseAssetName(string rawName) + { + return this.ContentCore.ParseAssetName(rawName); + } + + /// + public T Load(string key) + { + IAssetName assetName = this.ContentCore.ParseAssetName(key); + return this.Load(assetName); + } + + /// + public T Load(IAssetName assetName) + { + try + { + return this.GameContentManager.LoadLocalized(assetName, this.CurrentLocaleConstant, useCache: false); + } + catch (Exception ex) when (ex is not SContentLoadException) + { + throw new SContentLoadException($"{this.ModName} failed loading content asset '{assetName}' from the game content.", ex); + } + } + + /// + public bool InvalidateCache(string key) + { + IAssetName assetName = this.ParseAssetName(key); + return this.InvalidateCache(assetName); + } + + /// + public bool InvalidateCache(IAssetName assetName) + { + this.Monitor.Log($"Requested cache invalidation for '{assetName}'."); + return this.ContentCore.InvalidateCache(asset => asset.Name.IsEquivalentTo(assetName)).Any(); + } + + /// + public bool InvalidateCache() + { + this.Monitor.Log($"Requested cache invalidation for all assets of type {typeof(T)}. This is an expensive operation and should be avoided if possible."); + return this.ContentCore.InvalidateCache((_, _, type) => typeof(T).IsAssignableFrom(type)).Any(); + } + + /// + public bool InvalidateCache(Func predicate) + { + this.Monitor.Log("Requested cache invalidation for all assets matching a predicate."); + return this.ContentCore.InvalidateCache(predicate).Any(); + } + + /// + public IAssetData GetPatchHelper(T data, string assetName = null) + { + if (data == null) + throw new ArgumentNullException(nameof(data), "Can't get a patch helper for a null value."); + + assetName ??= $"temp/{Guid.NewGuid():N}"; + + return new AssetDataForObject(this.CurrentLocale, this.ContentCore.ParseAssetName(assetName), data, key => this.ParseAssetName(key).Name); + } + + /// Get the underlying game content manager. + internal IContentManager GetUnderlyingContentManager() + { + return this.GameContentManager; + } + } +} diff --git a/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs new file mode 100644 index 00000000..45899dd7 --- /dev/null +++ b/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs @@ -0,0 +1,75 @@ +using System; +using StardewModdingAPI.Framework.Content; +using StardewModdingAPI.Framework.ContentManagers; +using StardewModdingAPI.Framework.Exceptions; + +namespace StardewModdingAPI.Framework.ModHelpers +{ + /// + internal class ModContentHelper : BaseHelper, IModContentHelper + { + /********* + ** Fields + *********/ + /// SMAPI's core content logic. + private readonly ContentCoordinator ContentCore; + + /// A content manager for this mod which manages files from the mod's folder. + private readonly ModContentManager ModContentManager; + + /// The friendly mod name for use in errors. + private readonly string ModName; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// SMAPI's core content logic. + /// The absolute path to the mod folder. + /// The unique ID of the relevant mod. + /// The friendly mod name for use in errors. + /// The game content manager used for map tilesheets not provided by the mod. + public ModContentHelper(ContentCoordinator contentCore, string modFolderPath, string modID, string modName, IContentManager gameContentManager) + : base(modID) + { + string managedAssetPrefix = contentCore.GetManagedAssetPrefix(modID); + + this.ContentCore = contentCore; + this.ModContentManager = contentCore.CreateModContentManager(managedAssetPrefix, modName, modFolderPath, gameContentManager); + this.ModName = modName; + } + + /// + public T Load(string relativePath) + { + IAssetName assetName = this.ContentCore.ParseAssetName(relativePath); + + try + { + return this.ModContentManager.LoadExact(assetName, useCache: false); + } + catch (Exception ex) when (ex is not SContentLoadException) + { + throw new SContentLoadException($"{this.ModName} failed loading content asset '{relativePath}' from its mod folder.", ex); + } + } + + /// + public IAssetName GetInternalAssetName(string relativePath) + { + return this.ModContentManager.GetInternalAssetKey(relativePath); + } + + /// + public IAssetData GetPatchHelper(T data, string relativePath = null) + { + if (data == null) + throw new ArgumentNullException(nameof(data), "Can't get a patch helper for a null value."); + + relativePath ??= $"temp/{Guid.NewGuid():N}"; + + return new AssetDataForObject(this.ContentCore.GetLocale(), this.ContentCore.ParseAssetName(relativePath), data, key => this.ContentCore.ParseAssetName(key).Name); + } + } +} diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index 058bff83..d28faacc 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -8,6 +8,14 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Provides simplified APIs for writing mods. internal class ModHelper : BaseHelper, IModHelper, IDisposable { + /********* + ** Fields + *********/ + /// The backing field for . + [Obsolete] + private readonly IContentHelper ContentImpl; + + /********* ** Accessors *********/ @@ -18,7 +26,27 @@ namespace StardewModdingAPI.Framework.ModHelpers public IModEvents Events { get; } /// - public IContentHelper Content { get; } + [Obsolete] + public IContentHelper Content + { + get + { + SCore.DeprecationManager.Warn( + source: SCore.DeprecationManager.GetSourceName(this.ModID), + nounPhrase: $"{nameof(IModHelper)}.{nameof(IModHelper.Content)}", + version: "3.14.0", + severity: DeprecationLevel.Notice + ); + + return this.ContentImpl; + } + } + + /// + public IGameContentHelper GameContent { get; } + + /// + public IModContentHelper ModContent { get; } /// public IContentPackHelper ContentPacks { get; } @@ -54,6 +82,8 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Manages the game's input state for the current player instance. That may not be the main player in split-screen mode. /// Manages access to events raised by SMAPI. /// An API for loading content assets. + /// An API for loading content assets from the game's Content folder or via . + /// An API for loading content assets from your mod's files. /// An API for managing content packs. /// An API for managing console commands. /// An API for reading and writing persistent mod data. @@ -63,7 +93,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// An API for reading translations stored in the mod's i18n folder. /// An argument is null or empty. /// The path does not exist on disk. - public ModHelper(string modID, string modDirectory, Func currentInputState, IModEvents events, IContentHelper contentHelper, IContentPackHelper contentPackHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper) + public ModHelper(string modID, string modDirectory, Func currentInputState, IModEvents events, IContentHelper contentHelper, IGameContentHelper gameContentHelper, IModContentHelper modContentHelper, IContentPackHelper contentPackHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper) : base(modID) { // validate directory @@ -74,7 +104,9 @@ namespace StardewModdingAPI.Framework.ModHelpers // initialize this.DirectoryPath = modDirectory; - this.Content = contentHelper ?? throw new ArgumentNullException(nameof(contentHelper)); + this.ContentImpl = contentHelper ?? throw new ArgumentNullException(nameof(contentHelper)); + this.GameContent = gameContentHelper ?? throw new ArgumentNullException(nameof(gameContentHelper)); + this.ModContent = modContentHelper ?? throw new ArgumentNullException(nameof(modContentHelper)); this.ContentPacks = contentPackHelper ?? throw new ArgumentNullException(nameof(contentPackHelper)); this.Data = dataHelper ?? throw new ArgumentNullException(nameof(dataHelper)); this.Input = new InputHelper(modID, currentInputState); -- cgit