diff options
author | Jesse Plamondon-Willard <github@jplamondonw.com> | 2018-02-18 14:18:41 -0500 |
---|---|---|
committer | Jesse Plamondon-Willard <github@jplamondonw.com> | 2018-02-18 14:18:41 -0500 |
commit | e7cb44efb7cc4fec1dc36c6110be0c948581be85 (patch) | |
tree | fa27e77f03415ddabb9c7060455c9a1eda099c8b /src/SMAPI/Framework | |
parent | 0c1bca3db044b6f228538f1738d52c31e4481e48 (diff) | |
parent | f0cddebbe956262cb3645c6e9d27ca0cfb056125 (diff) | |
download | SMAPI-e7cb44efb7cc4fec1dc36c6110be0c948581be85.tar.gz SMAPI-e7cb44efb7cc4fec1dc36c6110be0c948581be85.tar.bz2 SMAPI-e7cb44efb7cc4fec1dc36c6110be0c948581be85.zip |
Merge branch 'add-content-packs' into develop
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r-- | src/SMAPI/Framework/ContentPack.cs | 78 | ||||
-rw-r--r-- | src/SMAPI/Framework/IModMetadata.cs | 18 | ||||
-rw-r--r-- | src/SMAPI/Framework/InternalExtensions.cs | 2 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModHelpers/ModHelper.cs | 19 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModLoading/ModMetadata.cs | 29 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModLoading/ModResolver.cs | 151 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModRegistry.cs | 17 | ||||
-rw-r--r-- | src/SMAPI/Framework/Models/Manifest.cs | 8 | ||||
-rw-r--r-- | src/SMAPI/Framework/Models/ManifestContentPackFor.cs | 15 | ||||
-rw-r--r-- | src/SMAPI/Framework/Serialisation/JsonHelper.cs | 3 | ||||
-rw-r--r-- | src/SMAPI/Framework/Serialisation/SmapiConverters/ManifestContentPackForConverter.cs | 50 |
11 files changed, 336 insertions, 54 deletions
diff --git a/src/SMAPI/Framework/ContentPack.cs b/src/SMAPI/Framework/ContentPack.cs new file mode 100644 index 00000000..0a8f223e --- /dev/null +++ b/src/SMAPI/Framework/ContentPack.cs @@ -0,0 +1,78 @@ +using System; +using System.IO; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using StardewModdingAPI.Framework.Serialisation; +using xTile; + +namespace StardewModdingAPI.Framework +{ + /// <summary>Manages access to a content pack's metadata and files.</summary> + internal class ContentPack : IContentPack + { + /********* + ** Properties + *********/ + /// <summary>Provides an API for loading content assets.</summary> + private readonly IContentHelper Content; + + /// <summary>Encapsulates SMAPI's JSON file parsing.</summary> + private readonly JsonHelper JsonHelper; + + + /********* + ** Accessors + *********/ + /// <summary>The full path to the content pack's folder.</summary> + public string DirectoryPath { get; } + + /// <summary>The content pack's manifest.</summary> + public IManifest Manifest { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="directoryPath">The full path to the content pack's folder.</param> + /// <param name="manifest">The content pack's manifest.</param> + /// <param name="content">Provides an API for loading content assets.</param> + /// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param> + public ContentPack(string directoryPath, IManifest manifest, IContentHelper content, JsonHelper jsonHelper) + { + this.DirectoryPath = directoryPath; + this.Manifest = manifest; + this.Content = content; + this.JsonHelper = jsonHelper; + } + + /// <summary>Read a JSON file from the content pack folder.</summary> + /// <typeparam name="TModel">The model type.</typeparam> + /// <param name="path">The file path relative to the contnet directory.</param> + /// <returns>Returns the deserialised model, or <c>null</c> if the file doesn't exist or is empty.</returns> + public TModel ReadJsonFile<TModel>(string path) where TModel : class + { + path = Path.Combine(this.DirectoryPath, path); + return this.JsonHelper.ReadJsonFile<TModel>(path); + } + + /// <summary>Load content from the content pack folder (if not already cached), and return it. When loading a <c>.png</c> file, this must be called outside the game's draw loop.</summary> + /// <typeparam name="T">The expected data type. The main supported types are <see cref="Map"/>, <see cref="Texture2D"/>, and dictionaries; other types may be supported by the game's content pipeline.</typeparam> + /// <param name="key">The local path to a content file relative to the content pack folder.</param> + /// <exception cref="ArgumentException">The <paramref name="key"/> is empty or contains invalid characters.</exception> + /// <exception cref="ContentLoadException">The content asset couldn't be loaded (e.g. because it doesn't exist).</exception> + public T LoadAsset<T>(string key) + { + return this.Content.Load<T>(key, ContentSource.ModFolder); + } + + /// <summary>Get the underlying key in the game's content cache for an asset. This can be used to load custom map tilesheets, but should be avoided when you can use the content API instead. This does not validate whether the asset exists.</summary> + /// <param name="key">The the local path to a content file relative to the content pack folder.</param> + /// <exception cref="ArgumentException">The <paramref name="key"/> is empty or contains invalid characters.</exception> + public string GetActualAssetKey(string key) + { + return this.Content.GetActualAssetKey(key, ContentSource.ModFolder); + } + + } +} diff --git a/src/SMAPI/Framework/IModMetadata.cs b/src/SMAPI/Framework/IModMetadata.cs index a91b0a5b..d1e8eb7d 100644 --- a/src/SMAPI/Framework/IModMetadata.cs +++ b/src/SMAPI/Framework/IModMetadata.cs @@ -18,7 +18,7 @@ namespace StardewModdingAPI.Framework /// <summary>The mod manifest.</summary> IManifest Manifest { get; } - /// <summary>>Metadata about the mod from SMAPI's internal data (if any).</summary> + /// <summary>Metadata about the mod from SMAPI's internal data (if any).</summary> ParsedModDataRecord DataRecord { get; } /// <summary>The metadata resolution status.</summary> @@ -27,12 +27,21 @@ namespace StardewModdingAPI.Framework /// <summary>The reason the metadata is invalid, if any.</summary> string Error { get; } - /// <summary>The mod instance (if it was loaded).</summary> + /// <summary>The mod instance (if loaded and <see cref="IsContentPack"/> is false).</summary> IMod Mod { get; } + /// <summary>The content pack instance (if loaded and <see cref="IsContentPack"/> is true).</summary> + IContentPack ContentPack { get; } + + /// <summary>Writes messages to the console and log file as this mod.</summary> + IMonitor Monitor { get; } + /// <summary>The mod-provided API (if any).</summary> object Api { get; } + /// <summary>Whether the mod is a content pack.</summary> + bool IsContentPack { get; } + /********* ** Public methods @@ -47,6 +56,11 @@ namespace StardewModdingAPI.Framework /// <param name="mod">The mod instance to set.</param> IModMetadata SetMod(IMod mod); + /// <summary>Set the mod instance.</summary> + /// <param name="contentPack">The contentPack instance to set.</param> + /// <param name="monitor">Writes messages to the console and log file.</param> + IModMetadata SetMod(IContentPack contentPack, IMonitor monitor); + /// <summary>Set the mod-provided API instance.</summary> /// <param name="api">The mod-provided API.</param> IModMetadata SetApi(object api); diff --git a/src/SMAPI/Framework/InternalExtensions.cs b/src/SMAPI/Framework/InternalExtensions.cs index 0340a92d..71489627 100644 --- a/src/SMAPI/Framework/InternalExtensions.cs +++ b/src/SMAPI/Framework/InternalExtensions.cs @@ -94,7 +94,7 @@ namespace StardewModdingAPI.Framework /// <param name="level">The log severity level.</param> public static void LogAsMod(this IModMetadata metadata, string message, LogLevel level = LogLevel.Trace) { - metadata.Mod.Monitor.Log(message, level); + metadata.Monitor.Log(message, level); } /**** diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index 665b9cf4..c73dc307 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -1,5 +1,7 @@ -using System; +using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using StardewModdingAPI.Framework.Serialisation; namespace StardewModdingAPI.Framework.ModHelpers @@ -13,6 +15,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <summary>Encapsulates SMAPI's JSON file parsing.</summary> private readonly JsonHelper JsonHelper; + /// <summary>The content packs loaded for this mod.</summary> + private readonly IContentPack[] ContentPacks; + /********* ** Accessors @@ -48,9 +53,10 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <param name="modRegistry">an API for fetching metadata about loaded mods.</param> /// <param name="reflectionHelper">An API for accessing private game code.</param> /// <param name="translationHelper">An API for reading translations stored in the mod's <c>i18n</c> folder.</param> + /// <param name="contentPacks">The content packs loaded for this mod.</param> /// <exception cref="ArgumentNullException">An argument is null or empty.</exception> /// <exception cref="InvalidOperationException">The <paramref name="modDirectory"/> path does not exist on disk.</exception> - public ModHelper(string modID, string modDirectory, JsonHelper jsonHelper, IContentHelper contentHelper, ICommandHelper commandHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, ITranslationHelper translationHelper) + public ModHelper(string modID, string modDirectory, JsonHelper jsonHelper, IContentHelper contentHelper, ICommandHelper commandHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, ITranslationHelper translationHelper, IEnumerable<IContentPack> contentPacks) : base(modID) { // validate directory @@ -67,6 +73,7 @@ namespace StardewModdingAPI.Framework.ModHelpers this.ConsoleCommands = commandHelper ?? throw new ArgumentNullException(nameof(commandHelper)); this.Reflection = reflectionHelper ?? throw new ArgumentNullException(nameof(reflectionHelper)); this.Translation = translationHelper ?? throw new ArgumentNullException(nameof(translationHelper)); + this.ContentPacks = contentPacks.ToArray(); } /**** @@ -116,6 +123,14 @@ namespace StardewModdingAPI.Framework.ModHelpers this.JsonHelper.WriteJsonFile(path, model); } + /**** + ** Content packs + ****/ + /// <summary>Get all content packs loaded for this mod.</summary> + public IEnumerable<IContentPack> GetContentPacks() + { + return this.ContentPacks; + } /**** ** Disposal diff --git a/src/SMAPI/Framework/ModLoading/ModMetadata.cs b/src/SMAPI/Framework/ModLoading/ModMetadata.cs index 29bb6617..1a0f9994 100644 --- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs +++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs @@ -1,3 +1,4 @@ +using System; using StardewModdingAPI.Framework.ModData; namespace StardewModdingAPI.Framework.ModLoading @@ -26,12 +27,21 @@ namespace StardewModdingAPI.Framework.ModLoading /// <summary>The reason the metadata is invalid, if any.</summary> public string Error { get; private set; } - /// <summary>The mod instance (if it was loaded).</summary> + /// <summary>The mod instance (if loaded and <see cref="IsContentPack"/> is false).</summary> public IMod Mod { get; private set; } + /// <summary>The content pack instance (if loaded and <see cref="IsContentPack"/> is true).</summary> + public IContentPack ContentPack { get; private set; } + + /// <summary>Writes messages to the console and log file as this mod.</summary> + public IMonitor Monitor { get; private set; } + /// <summary>The mod-provided API (if any).</summary> public object Api { get; private set; } + /// <summary>Whether the mod is a content pack.</summary> + public bool IsContentPack => this.Manifest?.ContentPackFor != null; + /********* ** Public methods @@ -64,7 +74,24 @@ namespace StardewModdingAPI.Framework.ModLoading /// <param name="mod">The mod instance to set.</param> public IModMetadata SetMod(IMod mod) { + if (this.ContentPack != null) + throw new InvalidOperationException("A mod can't be both an assembly mod and content pack."); + this.Mod = mod; + this.Monitor = mod.Monitor; + return this; + } + + /// <summary>Set the mod instance.</summary> + /// <param name="contentPack">The contentPack instance to set.</param> + /// <param name="monitor">Writes messages to the console and log file.</param> + public IModMetadata SetMod(IContentPack contentPack, IMonitor monitor) + { + if (this.Mod != null) + throw new InvalidOperationException("A mod can't be both an assembly mod and content pack."); + + this.ContentPack = contentPack; + this.Monitor = monitor; return this; } diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index b46ee117..be73254d 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -30,18 +30,13 @@ namespace StardewModdingAPI.Framework.ModLoading string error = null; try { - // read manifest manifest = jsonHelper.ReadJsonFile<Manifest>(path); - - // validate if (manifest == null) { error = File.Exists(path) ? "its manifest is invalid." : "it doesn't have a manifest."; } - else if (string.IsNullOrWhiteSpace(manifest.EntryDll)) - error = "its manifest doesn't set an entry DLL."; } catch (SParseException ex) { @@ -85,7 +80,7 @@ namespace StardewModdingAPI.Framework.ModLoading if (mod.Status == ModMetadataStatus.Failed) continue; - // validate compatibility + // validate compatibility from internal data switch (mod.DataRecord?.Status) { case ModStatus.Obsolete: @@ -128,24 +123,52 @@ namespace StardewModdingAPI.Framework.ModLoading continue; } - // validate DLL value - if (string.IsNullOrWhiteSpace(mod.Manifest.EntryDll)) - { - mod.SetStatus(ModMetadataStatus.Failed, "its manifest has no EntryDLL field."); - continue; - } - if (mod.Manifest.EntryDll.Intersect(Path.GetInvalidFileNameChars()).Any()) + // validate DLL / content pack fields { - mod.SetStatus(ModMetadataStatus.Failed, $"its manifest has invalid filename '{mod.Manifest.EntryDll}' for the EntryDLL field."); - continue; - } + bool hasDll = !string.IsNullOrWhiteSpace(mod.Manifest.EntryDll); + bool isContentPack = mod.Manifest.ContentPackFor != null; - // validate DLL path - string assemblyPath = Path.Combine(mod.DirectoryPath, mod.Manifest.EntryDll); - if (!File.Exists(assemblyPath)) - { - mod.SetStatus(ModMetadataStatus.Failed, $"its DLL '{mod.Manifest.EntryDll}' doesn't exist."); - continue; + // validate field presence + if (!hasDll && !isContentPack) + { + mod.SetStatus(ModMetadataStatus.Failed, $"its manifest has no {nameof(IManifest.EntryDll)} or {nameof(IManifest.ContentPackFor)} field; must specify one."); + continue; + } + if (hasDll && isContentPack) + { + mod.SetStatus(ModMetadataStatus.Failed, $"its manifest sets both {nameof(IManifest.EntryDll)} and {nameof(IManifest.ContentPackFor)}, which are mutually exclusive."); + continue; + } + + // validate DLL + if (hasDll) + { + // invalid filename format + if (mod.Manifest.EntryDll.Intersect(Path.GetInvalidFileNameChars()).Any()) + { + mod.SetStatus(ModMetadataStatus.Failed, $"its manifest has invalid filename '{mod.Manifest.EntryDll}' for the EntryDLL field."); + continue; + } + + // invalid path + string assemblyPath = Path.Combine(mod.DirectoryPath, mod.Manifest.EntryDll); + if (!File.Exists(assemblyPath)) + { + mod.SetStatus(ModMetadataStatus.Failed, $"its DLL '{mod.Manifest.EntryDll}' doesn't exist."); + continue; + } + } + + // validate content pack + else + { + // invalid content pack ID + if (string.IsNullOrWhiteSpace(mod.Manifest.ContentPackFor.UniqueID)) + { + mod.SetStatus(ModMetadataStatus.Failed, $"its manifest declares {nameof(IManifest.ContentPackFor)} without its required {nameof(IManifestContentPackFor.UniqueID)} field."); + continue; + } + } } // validate required fields @@ -243,30 +266,17 @@ namespace StardewModdingAPI.Framework.ModLoading throw new InvalidModStateException($"Unknown dependency status '{states[mod]}'."); } - // no dependencies, mark sorted - if (mod.Manifest.Dependencies == null || !mod.Manifest.Dependencies.Any()) + // collect dependencies + ModDependency[] dependencies = this.GetDependenciesFrom(mod.Manifest, mods).ToArray(); + + // mark sorted if no dependencies + if (!dependencies.Any()) { sortedMods.Push(mod); return states[mod] = ModDependencyStatus.Sorted; } - // get dependencies - var dependencies = - ( - from entry in mod.Manifest.Dependencies - let dependencyMod = mods.FirstOrDefault(m => string.Equals(m.Manifest?.UniqueID, entry.UniqueID, StringComparison.InvariantCultureIgnoreCase)) - orderby entry.UniqueID - select new - { - ID = entry.UniqueID, - MinVersion = entry.MinimumVersion, - Mod = dependencyMod, - IsRequired = entry.IsRequired - } - ) - .ToArray(); - - // missing required dependencies, mark failed + // mark failed if missing dependencies { string[] failedModNames = ( from entry in dependencies @@ -371,5 +381,64 @@ namespace StardewModdingAPI.Framework.ModLoading yield return directory; } } + + /// <summary>Get the dependencies declared in a manifest.</summary> + /// <param name="manifest">The mod manifest.</param> + /// <param name="loadedMods">The loaded mods.</param> + private IEnumerable<ModDependency> GetDependenciesFrom(IManifest manifest, IModMetadata[] loadedMods) + { + IModMetadata FindMod(string id) => loadedMods.FirstOrDefault(m => string.Equals(m.Manifest?.UniqueID, id, StringComparison.InvariantCultureIgnoreCase)); + + // yield dependencies + if (manifest.Dependencies != null) + { + foreach (var entry in manifest.Dependencies) + yield return new ModDependency(entry.UniqueID, entry.MinimumVersion, FindMod(entry.UniqueID), entry.IsRequired); + } + + // yield content pack parent + if (manifest.ContentPackFor != null) + yield return new ModDependency(manifest.ContentPackFor.UniqueID, manifest.ContentPackFor.MinimumVersion, FindMod(manifest.ContentPackFor.UniqueID), isRequired: true); + } + + + /********* + ** Private models + *********/ + /// <summary>Represents a dependency from one mod to another.</summary> + private struct ModDependency + { + /********* + ** Accessors + *********/ + /// <summary>The unique ID of the required mod.</summary> + public string ID { get; } + + /// <summary>The minimum required version (if any).</summary> + public ISemanticVersion MinVersion { get; } + + /// <summary>Whether the mod shouldn't be loaded if the dependency isn't available.</summary> + public bool IsRequired { get; } + + /// <summary>The loaded mod that fulfills the dependency (if available).</summary> + public IModMetadata Mod { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="id">The unique ID of the required mod.</param> + /// <param name="minVersion">The minimum required version (if any).</param> + /// <param name="mod">The loaded mod that fulfills the dependency (if available).</param> + /// <param name="isRequired">Whether the mod shouldn't be loaded if the dependency isn't available.</param> + public ModDependency(string id, ISemanticVersion minVersion, IModMetadata mod, bool isRequired) + { + this.ID = id; + this.MinVersion = minVersion; + this.Mod = mod; + this.IsRequired = isRequired; + } + } } } diff --git a/src/SMAPI/Framework/ModRegistry.cs b/src/SMAPI/Framework/ModRegistry.cs index 453d2868..e7d4f89a 100644 --- a/src/SMAPI/Framework/ModRegistry.cs +++ b/src/SMAPI/Framework/ModRegistry.cs @@ -25,18 +25,27 @@ namespace StardewModdingAPI.Framework /********* ** Public methods *********/ - /// <summary>Register a mod as a possible source of deprecation warnings.</summary> + /// <summary>Register a mod.</summary> /// <param name="metadata">The mod metadata.</param> public void Add(IModMetadata metadata) { this.Mods.Add(metadata); - this.ModNamesByAssembly[metadata.Mod.GetType().Assembly.FullName] = metadata; + if (!metadata.IsContentPack) + this.ModNamesByAssembly[metadata.Mod.GetType().Assembly.FullName] = metadata; } /// <summary>Get metadata for all loaded mods.</summary> - public IEnumerable<IModMetadata> GetAll() + /// <param name="assemblyMods">Whether to include SMAPI mods.</param> + /// <param name="contentPacks">Whether to include content pack mods.</param> + public IEnumerable<IModMetadata> GetAll(bool assemblyMods = true, bool contentPacks = true) { - return this.Mods.Select(p => p); + IEnumerable<IModMetadata> query = this.Mods; + if (!assemblyMods) + query = query.Where(p => p.IsContentPack); + if (!contentPacks) + query = query.Where(p => !p.IsContentPack); + + return query; } /// <summary>Get metadata for a loaded mod.</summary> diff --git a/src/SMAPI/Framework/Models/Manifest.cs b/src/SMAPI/Framework/Models/Manifest.cs index f9762406..f5867cf3 100644 --- a/src/SMAPI/Framework/Models/Manifest.cs +++ b/src/SMAPI/Framework/Models/Manifest.cs @@ -20,16 +20,18 @@ namespace StardewModdingAPI.Framework.Models public string Author { get; set; } /// <summary>The mod version.</summary> - [JsonConverter(typeof(SemanticVersionConverter))] public ISemanticVersion Version { get; set; } /// <summary>The minimum SMAPI version required by this mod, if any.</summary> - [JsonConverter(typeof(SemanticVersionConverter))] public ISemanticVersion MinimumApiVersion { get; set; } - /// <summary>The name of the DLL in the directory that has the <see cref="IMod.Entry"/> method.</summary> + /// <summary>The name of the DLL in the directory that has the <see cref="IMod.Entry"/> method. Mutually exclusive with <see cref="ContentPackFor"/>.</summary> public string EntryDll { get; set; } + /// <summary>The mod which will read this as a content pack. Mutually exclusive with <see cref="IManifest.EntryDll"/>.</summary> + [JsonConverter(typeof(ManifestContentPackForConverter))] + public IManifestContentPackFor ContentPackFor { get; set; } + /// <summary>The other mods that must be loaded before this mod.</summary> [JsonConverter(typeof(ManifestDependencyArrayConverter))] public IManifestDependency[] Dependencies { get; set; } diff --git a/src/SMAPI/Framework/Models/ManifestContentPackFor.cs b/src/SMAPI/Framework/Models/ManifestContentPackFor.cs new file mode 100644 index 00000000..7836bbcc --- /dev/null +++ b/src/SMAPI/Framework/Models/ManifestContentPackFor.cs @@ -0,0 +1,15 @@ +namespace StardewModdingAPI.Framework.Models +{ + /// <summary>Indicates which mod can read the content pack represented by the containing manifest.</summary> + internal class ManifestContentPackFor : IManifestContentPackFor + { + /********* + ** Accessors + *********/ + /// <summary>The unique ID of the mod which can read this content pack.</summary> + public string UniqueID { get; set; } + + /// <summary>The minimum required version (if any).</summary> + public ISemanticVersion MinimumVersion { get; set; } + } +} diff --git a/src/SMAPI/Framework/Serialisation/JsonHelper.cs b/src/SMAPI/Framework/Serialisation/JsonHelper.cs index 2e2a666e..6cba343e 100644 --- a/src/SMAPI/Framework/Serialisation/JsonHelper.cs +++ b/src/SMAPI/Framework/Serialisation/JsonHelper.cs @@ -21,6 +21,9 @@ namespace StardewModdingAPI.Framework.Serialisation ObjectCreationHandling = ObjectCreationHandling.Replace, // avoid issue where default ICollection<T> values are duplicated each time the config is loaded Converters = new List<JsonConverter> { + // SMAPI types + new SemanticVersionConverter(), + // enums new StringEnumConverter<Buttons>(), new StringEnumConverter<Keys>(), diff --git a/src/SMAPI/Framework/Serialisation/SmapiConverters/ManifestContentPackForConverter.cs b/src/SMAPI/Framework/Serialisation/SmapiConverters/ManifestContentPackForConverter.cs new file mode 100644 index 00000000..af7558f6 --- /dev/null +++ b/src/SMAPI/Framework/Serialisation/SmapiConverters/ManifestContentPackForConverter.cs @@ -0,0 +1,50 @@ +using System; +using Newtonsoft.Json; +using StardewModdingAPI.Framework.Models; + +namespace StardewModdingAPI.Framework.Serialisation.SmapiConverters +{ + /// <summary>Handles deserialisation of <see cref="IManifestContentPackFor"/> arrays.</summary> + internal class ManifestContentPackForConverter : JsonConverter + { + /********* + ** Accessors + *********/ + /// <summary>Whether this converter can write JSON.</summary> + public override bool CanWrite => false; + + + /********* + ** Public methods + *********/ + /// <summary>Get whether this instance can convert the specified object type.</summary> + /// <param name="objectType">The object type.</param> + public override bool CanConvert(Type objectType) + { + return objectType == typeof(IManifestContentPackFor[]); + } + + + /********* + ** Protected methods + *********/ + /// <summary>Read the JSON representation of the object.</summary> + /// <param name="reader">The JSON reader.</param> + /// <param name="objectType">The object type.</param> + /// <param name="existingValue">The object being read.</param> + /// <param name="serializer">The calling serializer.</param> + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + return serializer.Deserialize<ManifestContentPackFor>(reader); + } + + /// <summary>Writes the JSON representation of the object.</summary> + /// <param name="writer">The JSON writer.</param> + /// <param name="value">The value.</param> + /// <param name="serializer">The calling serializer.</param> + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new InvalidOperationException("This converter does not write JSON."); + } + } +} |