diff options
Diffstat (limited to 'src/StardewModdingAPI')
32 files changed, 2103 insertions, 1294 deletions
diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 1860795d..5e4759e9 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -33,7 +33,7 @@ namespace StardewModdingAPI ** Public ****/ /// <summary>SMAPI's current semantic version.</summary> - public static ISemanticVersion ApiVersion { get; } = new SemanticVersion(1, 12, 0); + public static ISemanticVersion ApiVersion { get; } = new SemanticVersion(1, 13, 0); /// <summary>The minimum supported version of Stardew Valley.</summary> public static ISemanticVersion MinimumGameVersion { get; } = new SemanticVersion("1.2.26"); @@ -71,6 +71,12 @@ namespace StardewModdingAPI /// <summary>The file path to the log where the latest output should be saved.</summary> internal static string DefaultLogPath => Path.Combine(Constants.LogDir, "SMAPI-latest.txt"); + /// <summary>A copy of the log leading up to the previous fatal crash, if any.</summary> + internal static string FatalCrashLog => Path.Combine(Constants.LogDir, "SMAPI-crash.txt"); + + /// <summary>The file path which stores a fatal crash message for the next run.</summary> + internal static string FatalCrashMarker => Path.Combine(Constants.ExecutionPath, "StardewModdingAPI.crash.marker"); + /// <summary>The full path to the folder containing mods.</summary> internal static string ModPath { get; } = Path.Combine(Constants.ExecutionPath, "Mods"); diff --git a/src/StardewModdingAPI/Context.cs b/src/StardewModdingAPI/Context.cs index 2da14eed..6bc5ae56 100644 --- a/src/StardewModdingAPI/Context.cs +++ b/src/StardewModdingAPI/Context.cs @@ -4,18 +4,27 @@ using StardewValley.Menus; namespace StardewModdingAPI { /// <summary>Provides information about the current game state.</summary> - internal static class Context + public static class Context { /********* ** Accessors *********/ + /**** + ** Public + ****/ + /// <summary>Whether the player has loaded a save and the world has finished initialising.</summary> + public static bool IsWorldReady { get; internal set; } + + /**** + ** Internal + ****/ /// <summary>Whether a player save has been loaded.</summary> - public static bool IsSaveLoaded => Game1.hasLoadedGame && !string.IsNullOrEmpty(Game1.player.name); + internal static bool IsSaveLoaded => Game1.hasLoadedGame && !string.IsNullOrEmpty(Game1.player.name); /// <summary>Whether the game is currently writing to the save file.</summary> - public static bool IsSaving => SaveGame.IsProcessing && (Game1.activeClickableMenu is SaveGameMenu || Game1.activeClickableMenu is ShippingMenu); // IsProcessing is never set to false on Linux/Mac + internal static bool IsSaving => Game1.activeClickableMenu is SaveGameMenu || Game1.activeClickableMenu is ShippingMenu; // saving is performed by SaveGameMenu, but it's wrapped by ShippingMenu on days when the player shipping something /// <summary>Whether the game is currently running the draw loop.</summary> - public static bool IsInDrawLoop { get; set; } + internal static bool IsInDrawLoop { get; set; } } } diff --git a/src/StardewModdingAPI/Events/ContentEvents.cs b/src/StardewModdingAPI/Events/ContentEvents.cs index 5b4146c5..0dcd2cc6 100644 --- a/src/StardewModdingAPI/Events/ContentEvents.cs +++ b/src/StardewModdingAPI/Events/ContentEvents.cs @@ -27,7 +27,12 @@ namespace StardewModdingAPI.Events public static event EventHandler<EventArgsValueChanged<string>> AfterLocaleChanged; /// <summary>Raised when an XNB file is being read into the cache. Mods can change the data here before it's cached.</summary> - internal static event EventHandler<IContentEventHelper> AfterAssetLoaded; +#if EXPERIMENTAL + public +#else + internal +#endif + static event EventHandler<IContentEventHelper> AfterAssetLoaded; /********* diff --git a/src/StardewModdingAPI/Events/ControlEvents.cs b/src/StardewModdingAPI/Events/ControlEvents.cs index 790bf193..80d0f547 100644 --- a/src/StardewModdingAPI/Events/ControlEvents.cs +++ b/src/StardewModdingAPI/Events/ControlEvents.cs @@ -77,40 +77,36 @@ namespace StardewModdingAPI.Events /// <summary>Raise a <see cref="ControllerButtonPressed"/> event.</summary> /// <param name="monitor">Encapsulates monitoring and logging.</param> - /// <param name="playerIndex">The player who pressed the button.</param> /// <param name="button">The controller button that was pressed.</param> - internal static void InvokeButtonPressed(IMonitor monitor, PlayerIndex playerIndex, Buttons button) + internal static void InvokeButtonPressed(IMonitor monitor, Buttons button) { - monitor.SafelyRaiseGenericEvent($"{nameof(ControlEvents)}.{nameof(ControlEvents.ControllerButtonPressed)}", ControlEvents.ControllerButtonPressed?.GetInvocationList(), null, new EventArgsControllerButtonPressed(playerIndex, button)); + monitor.SafelyRaiseGenericEvent($"{nameof(ControlEvents)}.{nameof(ControlEvents.ControllerButtonPressed)}", ControlEvents.ControllerButtonPressed?.GetInvocationList(), null, new EventArgsControllerButtonPressed(PlayerIndex.One, button)); } /// <summary>Raise a <see cref="ControllerButtonReleased"/> event.</summary> /// <param name="monitor">Encapsulates monitoring and logging.</param> - /// <param name="playerIndex">The player who released the button.</param> /// <param name="button">The controller button that was released.</param> - internal static void InvokeButtonReleased(IMonitor monitor, PlayerIndex playerIndex, Buttons button) + internal static void InvokeButtonReleased(IMonitor monitor, Buttons button) { - monitor.SafelyRaiseGenericEvent($"{nameof(ControlEvents)}.{nameof(ControlEvents.ControllerButtonReleased)}", ControlEvents.ControllerButtonReleased?.GetInvocationList(), null, new EventArgsControllerButtonReleased(playerIndex, button)); + monitor.SafelyRaiseGenericEvent($"{nameof(ControlEvents)}.{nameof(ControlEvents.ControllerButtonReleased)}", ControlEvents.ControllerButtonReleased?.GetInvocationList(), null, new EventArgsControllerButtonReleased(PlayerIndex.One, button)); } /// <summary>Raise a <see cref="ControllerTriggerPressed"/> event.</summary> /// <param name="monitor">Encapsulates monitoring and logging.</param> - /// <param name="playerIndex">The player who pressed the trigger button.</param> /// <param name="button">The trigger button that was pressed.</param> /// <param name="value">The current trigger value.</param> - internal static void InvokeTriggerPressed(IMonitor monitor, PlayerIndex playerIndex, Buttons button, float value) + internal static void InvokeTriggerPressed(IMonitor monitor, Buttons button, float value) { - monitor.SafelyRaiseGenericEvent($"{nameof(ControlEvents)}.{nameof(ControlEvents.ControllerTriggerPressed)}", ControlEvents.ControllerTriggerPressed?.GetInvocationList(), null, new EventArgsControllerTriggerPressed(playerIndex, button, value)); + monitor.SafelyRaiseGenericEvent($"{nameof(ControlEvents)}.{nameof(ControlEvents.ControllerTriggerPressed)}", ControlEvents.ControllerTriggerPressed?.GetInvocationList(), null, new EventArgsControllerTriggerPressed(PlayerIndex.One, button, value)); } /// <summary>Raise a <see cref="ControllerTriggerReleased"/> event.</summary> /// <param name="monitor">Encapsulates monitoring and logging.</param> - /// <param name="playerIndex">The player who pressed the trigger button.</param> /// <param name="button">The trigger button that was pressed.</param> /// <param name="value">The current trigger value.</param> - internal static void InvokeTriggerReleased(IMonitor monitor, PlayerIndex playerIndex, Buttons button, float value) + internal static void InvokeTriggerReleased(IMonitor monitor, Buttons button, float value) { - monitor.SafelyRaiseGenericEvent($"{nameof(ControlEvents)}.{nameof(ControlEvents.ControllerTriggerReleased)}", ControlEvents.ControllerTriggerReleased?.GetInvocationList(), null, new EventArgsControllerTriggerReleased(playerIndex, button, value)); + monitor.SafelyRaiseGenericEvent($"{nameof(ControlEvents)}.{nameof(ControlEvents.ControllerTriggerReleased)}", ControlEvents.ControllerTriggerReleased?.GetInvocationList(), null, new EventArgsControllerTriggerReleased(PlayerIndex.One, button, value)); } } } diff --git a/src/StardewModdingAPI/Events/GameEvents.cs b/src/StardewModdingAPI/Events/GameEvents.cs index 029ec1f9..4f9ce7a7 100644 --- a/src/StardewModdingAPI/Events/GameEvents.cs +++ b/src/StardewModdingAPI/Events/GameEvents.cs @@ -19,6 +19,9 @@ namespace StardewModdingAPI.Events /// <summary>Raised during launch after configuring XNA or MonoGame. The game window hasn't been opened by this point. Called after <see cref="Microsoft.Xna.Framework.Game.Initialize"/>.</summary> internal static event EventHandler InitializeInternal; + /// <summary>Raised during launch after configuring Stardew Valley, loading it into memory, and opening the game window. The window is still blank by this point.</summary> + internal static event EventHandler GameLoadedInternal; + /// <summary>Raised during launch after configuring XNA or MonoGame. The game window hasn't been opened by this point. Called after <see cref="Microsoft.Xna.Framework.Game.Initialize"/>.</summary> [Obsolete("The " + nameof(Mod) + "." + nameof(Mod.Entry) + " method is now called after the " + nameof(GameEvents.Initialize) + " event, so any contained logic can be done directly in " + nameof(Mod.Entry) + ".")] public static event EventHandler Initialize; @@ -28,9 +31,11 @@ namespace StardewModdingAPI.Events public static event EventHandler LoadContent; /// <summary>Raised during launch after configuring Stardew Valley, loading it into memory, and opening the game window. The window is still blank by this point.</summary> + [Obsolete("The " + nameof(Mod) + "." + nameof(Mod.Entry) + " method is now called after the game loads, so any contained logic can be done directly in " + nameof(Mod.Entry) + ".")] public static event EventHandler GameLoaded; /// <summary>Raised during the first game update tick.</summary> + [Obsolete("The " + nameof(Mod) + "." + nameof(Mod.Entry) + " method is now called after the game loads, so any contained logic can be done directly in " + nameof(Mod.Entry) + ".")] public static event EventHandler FirstUpdateTick; /// <summary>Raised when the game updates its state (≈60 times per second).</summary> @@ -99,7 +104,32 @@ namespace StardewModdingAPI.Events /// <param name="monitor">Encapsulates monitoring and logging.</param> internal static void InvokeGameLoaded(IMonitor monitor) { - monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.GameLoaded)}", GameEvents.GameLoaded?.GetInvocationList()); + // notify SMAPI + monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.GameLoadedInternal)}", GameEvents.GameLoadedInternal?.GetInvocationList()); + + // notify mods + if (GameEvents.GameLoaded == null) + return; + + string name = $"{nameof(GameEvents)}.{nameof(GameEvents.GameLoaded)}"; + Delegate[] handlers = GameEvents.GameLoaded.GetInvocationList(); + + GameEvents.DeprecationManager.WarnForEvent(handlers, name, "1.12", DeprecationLevel.Info); + monitor.SafelyRaisePlainEvent(name, handlers); + } + + /// <summary>Raise a <see cref="FirstUpdateTick"/> event.</summary> + /// <param name="monitor">Encapsulates monitoring and logging.</param> + internal static void InvokeFirstUpdateTick(IMonitor monitor) + { + if (GameEvents.FirstUpdateTick == null) + return; + + string name = $"{nameof(GameEvents)}.{nameof(GameEvents.FirstUpdateTick)}"; + Delegate[] handlers = GameEvents.FirstUpdateTick.GetInvocationList(); + + GameEvents.DeprecationManager.WarnForEvent(handlers, name, "1.12", DeprecationLevel.Info); + monitor.SafelyRaisePlainEvent(name, handlers); } /// <summary>Raise an <see cref="UpdateTick"/> event.</summary> @@ -150,12 +180,5 @@ namespace StardewModdingAPI.Events { monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.OneSecondTick)}", GameEvents.OneSecondTick?.GetInvocationList()); } - - /// <summary>Raise a <see cref="FirstUpdateTick"/> event.</summary> - /// <param name="monitor">Encapsulates monitoring and logging.</param> - internal static void InvokeFirstUpdateTick(IMonitor monitor) - { - monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.FirstUpdateTick)}", GameEvents.FirstUpdateTick?.GetInvocationList()); - } } } diff --git a/src/StardewModdingAPI/Framework/Countdown.cs b/src/StardewModdingAPI/Framework/Countdown.cs new file mode 100644 index 00000000..25ca2546 --- /dev/null +++ b/src/StardewModdingAPI/Framework/Countdown.cs @@ -0,0 +1,44 @@ +namespace StardewModdingAPI.Framework +{ + /// <summary>Counts down from a baseline value.</summary> + internal class Countdown + { + /********* + ** Accessors + *********/ + /// <summary>The initial value from which to count down.</summary> + public int Initial { get; } + + /// <summary>The current value.</summary> + public int Current { get; private set; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="initial">The initial value from which to count down.</param> + public Countdown(int initial) + { + this.Initial = initial; + this.Current = initial; + } + + /// <summary>Reduce the current value by one.</summary> + /// <returns>Returns whether the value was decremented (i.e. wasn't already zero).</returns> + public bool Decrement() + { + if (this.Current <= 0) + return false; + + this.Current--; + return true; + } + + /// <summary>Restart the countdown.</summary> + public void Reset() + { + this.Current = this.Initial; + } + } +} diff --git a/src/StardewModdingAPI/Framework/InternalExtensions.cs b/src/StardewModdingAPI/Framework/InternalExtensions.cs index 5199c72d..cadf6598 100644 --- a/src/StardewModdingAPI/Framework/InternalExtensions.cs +++ b/src/StardewModdingAPI/Framework/InternalExtensions.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using Microsoft.Xna.Framework.Graphics; +using StardewValley; namespace StardewModdingAPI.Framework { @@ -128,5 +130,25 @@ namespace StardewModdingAPI.Framework deprecationManager.Warn(modName, nounPhrase, version, severity); } } + + /**** + ** Sprite batch + ****/ + /// <summary>Get whether the sprite batch is between a begin and end pair.</summary> + /// <param name="spriteBatch">The sprite batch to check.</param> + /// <param name="reflection">The reflection helper with which to access private fields.</param> + public static bool IsOpen(this SpriteBatch spriteBatch, IReflectionHelper reflection) + { + // get field name + const string fieldName = +#if SMAPI_FOR_WINDOWS + "inBeginEndPair"; +#else + "_beginCalled"; +#endif + + // get result + return reflection.GetPrivateValue<bool>(Game1.spriteBatch, fieldName); + } } } diff --git a/src/StardewModdingAPI/Framework/ModHelper.cs b/src/StardewModdingAPI/Framework/ModHelper.cs index 09297a65..7810148c 100644 --- a/src/StardewModdingAPI/Framework/ModHelper.cs +++ b/src/StardewModdingAPI/Framework/ModHelper.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.Serialisation; namespace StardewModdingAPI.Framework @@ -25,7 +24,7 @@ namespace StardewModdingAPI.Framework public IContentHelper Content { get; } /// <summary>Simplifies access to private game code.</summary> - public IReflectionHelper Reflection { get; } = new ReflectionHelper(); + public IReflectionHelper Reflection { get; } /// <summary>Metadata about loaded mods.</summary> public IModRegistry ModRegistry { get; } @@ -44,9 +43,10 @@ namespace StardewModdingAPI.Framework /// <param name="modRegistry">Metadata about loaded mods.</param> /// <param name="commandManager">Manages console commands.</param> /// <param name="contentManager">The content manager which loads content assets.</param> + /// <param name="reflection">Simplifies access to private game code.</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(IManifest manifest, string modDirectory, JsonHelper jsonHelper, IModRegistry modRegistry, CommandManager commandManager, SContentManager contentManager) + public ModHelper(IManifest manifest, string modDirectory, JsonHelper jsonHelper, IModRegistry modRegistry, CommandManager commandManager, SContentManager contentManager, IReflectionHelper reflection) { // validate if (string.IsNullOrWhiteSpace(modDirectory)) @@ -64,6 +64,7 @@ namespace StardewModdingAPI.Framework this.Content = new ContentHelper(contentManager, modDirectory, manifest.Name); this.ModRegistry = modRegistry; this.ConsoleCommands = new CommandHelper(manifest.Name, commandManager); + this.Reflection = reflection; } /**** diff --git a/src/StardewModdingAPI/Framework/AssemblyDefinitionResolver.cs b/src/StardewModdingAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs index b4e69fcd..4378798c 100644 --- a/src/StardewModdingAPI/Framework/AssemblyDefinitionResolver.cs +++ b/src/StardewModdingAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using Mono.Cecil; -namespace StardewModdingAPI.Framework +namespace StardewModdingAPI.Framework.ModLoading { /// <summary>A minimal assembly definition resolver which resolves references to known assemblies.</summary> internal class AssemblyDefinitionResolver : DefaultAssemblyResolver diff --git a/src/StardewModdingAPI/Framework/AssemblyLoader.cs b/src/StardewModdingAPI/Framework/ModLoading/AssemblyLoader.cs index 2c9973c1..42bd7bfb 100644 --- a/src/StardewModdingAPI/Framework/AssemblyLoader.cs +++ b/src/StardewModdingAPI/Framework/ModLoading/AssemblyLoader.cs @@ -7,7 +7,7 @@ using Mono.Cecil; using Mono.Cecil.Cil; using StardewModdingAPI.AssemblyRewriters; -namespace StardewModdingAPI.Framework +namespace StardewModdingAPI.Framework.ModLoading { /// <summary>Preprocesses and loads mod assemblies.</summary> internal class AssemblyLoader diff --git a/src/StardewModdingAPI/Framework/AssemblyParseResult.cs b/src/StardewModdingAPI/Framework/ModLoading/AssemblyParseResult.cs index bff976aa..69c99afe 100644 --- a/src/StardewModdingAPI/Framework/AssemblyParseResult.cs +++ b/src/StardewModdingAPI/Framework/ModLoading/AssemblyParseResult.cs @@ -1,7 +1,7 @@ using System.IO; using Mono.Cecil; -namespace StardewModdingAPI.Framework +namespace StardewModdingAPI.Framework.ModLoading { /// <summary>Metadata about a parsed assembly definition.</summary> internal class AssemblyParseResult diff --git a/src/StardewModdingAPI/Framework/ModLoading/IModMetadata.cs b/src/StardewModdingAPI/Framework/ModLoading/IModMetadata.cs new file mode 100644 index 00000000..3771ffdd --- /dev/null +++ b/src/StardewModdingAPI/Framework/ModLoading/IModMetadata.cs @@ -0,0 +1,39 @@ +using StardewModdingAPI.Framework.Models; + +namespace StardewModdingAPI.Framework.ModLoading +{ + /// <summary>Metadata for a mod.</summary> + internal interface IModMetadata + { + /********* + ** Accessors + *********/ + /// <summary>The mod's display name.</summary> + string DisplayName { get; } + + /// <summary>The mod's full directory path.</summary> + string DirectoryPath { get; } + + /// <summary>The mod manifest.</summary> + IManifest Manifest { get; } + + /// <summary>Optional metadata about a mod version that SMAPI should assume is compatible or broken, regardless of whether it detects incompatible code.</summary> + ModCompatibility Compatibility { get; } + + /// <summary>The metadata resolution status.</summary> + ModMetadataStatus Status { get; } + + /// <summary>The reason the metadata is invalid, if any.</summary> + string Error { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Set the mod status.</summary> + /// <param name="status">The metadata resolution status.</param> + /// <param name="error">The reason the metadata is invalid, if any.</param> + /// <returns>Return the instance for chaining.</returns> + IModMetadata SetStatus(ModMetadataStatus status, string error = null); + } +} diff --git a/src/StardewModdingAPI/Framework/ModLoading/InvalidModStateException.cs b/src/StardewModdingAPI/Framework/ModLoading/InvalidModStateException.cs new file mode 100644 index 00000000..ab11272a --- /dev/null +++ b/src/StardewModdingAPI/Framework/ModLoading/InvalidModStateException.cs @@ -0,0 +1,14 @@ +using System; + +namespace StardewModdingAPI.Framework.ModLoading +{ + /// <summary>An exception which indicates that something went seriously wrong while loading mods, and SMAPI should abort outright.</summary> + public class InvalidModStateException : Exception + { + /// <summary>Construct an instance.</summary> + /// <param name="message">The error message.</param> + /// <param name="ex">The underlying exception, if any.</param> + public InvalidModStateException(string message, Exception ex = null) + : base(message, ex) { } + } +} diff --git a/src/StardewModdingAPI/Framework/ModLoading/ModDependencyStatus.cs b/src/StardewModdingAPI/Framework/ModLoading/ModDependencyStatus.cs new file mode 100644 index 00000000..0774b487 --- /dev/null +++ b/src/StardewModdingAPI/Framework/ModLoading/ModDependencyStatus.cs @@ -0,0 +1,18 @@ +namespace StardewModdingAPI.Framework.ModLoading +{ + /// <summary>The status of a given mod in the dependency-sorting algorithm.</summary> + internal enum ModDependencyStatus + { + /// <summary>The mod hasn't been visited yet.</summary> + Queued, + + /// <summary>The mod is currently being analysed as part of a dependency chain.</summary> + Checking, + + /// <summary>The mod has already been sorted.</summary> + Sorted, + + /// <summary>The mod couldn't be sorted due to a metadata issue (e.g. missing dependencies).</summary> + Failed + } +} diff --git a/src/StardewModdingAPI/Framework/ModLoading/ModMetadata.cs b/src/StardewModdingAPI/Framework/ModLoading/ModMetadata.cs new file mode 100644 index 00000000..7b25e090 --- /dev/null +++ b/src/StardewModdingAPI/Framework/ModLoading/ModMetadata.cs @@ -0,0 +1,57 @@ +using StardewModdingAPI.Framework.Models; + +namespace StardewModdingAPI.Framework.ModLoading +{ + /// <summary>Metadata for a mod.</summary> + internal class ModMetadata : IModMetadata + { + /********* + ** Accessors + *********/ + /// <summary>The mod's display name.</summary> + public string DisplayName { get; } + + /// <summary>The mod's full directory path.</summary> + public string DirectoryPath { get; } + + /// <summary>The mod manifest.</summary> + public IManifest Manifest { get; } + + /// <summary>Optional metadata about a mod version that SMAPI should assume is compatible or broken, regardless of whether it detects incompatible code.</summary> + public ModCompatibility Compatibility { get; } + + /// <summary>The metadata resolution status.</summary> + public ModMetadataStatus Status { get; private set; } + + /// <summary>The reason the metadata is invalid, if any.</summary> + public string Error { get; private set; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="displayName">The mod's display name.</param> + /// <param name="directoryPath">The mod's full directory path.</param> + /// <param name="manifest">The mod manifest.</param> + /// <param name="compatibility">Optional metadata about a mod version that SMAPI should assume is compatible or broken, regardless of whether it detects incompatible code.</param> + public ModMetadata(string displayName, string directoryPath, IManifest manifest, ModCompatibility compatibility) + { + this.DisplayName = displayName; + this.DirectoryPath = directoryPath; + this.Manifest = manifest; + this.Compatibility = compatibility; + } + + /// <summary>Set the mod status.</summary> + /// <param name="status">The metadata resolution status.</param> + /// <param name="error">The reason the metadata is invalid, if any.</param> + /// <returns>Return the instance for chaining.</returns> + public IModMetadata SetStatus(ModMetadataStatus status, string error = null) + { + this.Status = status; + this.Error = error; + return this; + } + } +} diff --git a/src/StardewModdingAPI/Framework/ModLoading/ModMetadataStatus.cs b/src/StardewModdingAPI/Framework/ModLoading/ModMetadataStatus.cs new file mode 100644 index 00000000..1b2b0b55 --- /dev/null +++ b/src/StardewModdingAPI/Framework/ModLoading/ModMetadataStatus.cs @@ -0,0 +1,12 @@ +namespace StardewModdingAPI.Framework.ModLoading +{ + /// <summary>Indicates the status of a mod's metadata resolution.</summary> + internal enum ModMetadataStatus + { + /// <summary>The mod has been found, but hasn't been processed yet.</summary> + Found, + + /// <summary>The mod cannot be loaded.</summary> + Failed + } +}
\ No newline at end of file diff --git a/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs b/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs new file mode 100644 index 00000000..2c68a639 --- /dev/null +++ b/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs @@ -0,0 +1,291 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using StardewModdingAPI.Framework.Models; +using StardewModdingAPI.Framework.Serialisation; + +namespace StardewModdingAPI.Framework.ModLoading +{ + /// <summary>Finds and processes mod metadata.</summary> + internal class ModResolver + { + /********* + ** Public methods + *********/ + /// <summary>Get manifest metadata for each folder in the given root path.</summary> + /// <param name="rootPath">The root path to search for mods.</param> + /// <param name="jsonHelper">The JSON helper with which to read manifests.</param> + /// <param name="compatibilityRecords">Metadata about mods that SMAPI should assume is compatible or broken, regardless of whether it detects incompatible code.</param> + /// <returns>Returns the manifests by relative folder.</returns> + public IEnumerable<IModMetadata> ReadManifests(string rootPath, JsonHelper jsonHelper, IEnumerable<ModCompatibility> compatibilityRecords) + { + compatibilityRecords = compatibilityRecords.ToArray(); + foreach (DirectoryInfo modDir in this.GetModFolders(rootPath)) |
