diff --git a/docs/release-notes.md b/docs/release-notes.md index dc0f584d..d31b95e5 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -6,6 +6,7 @@ * For players: * Added friendly log message for save file-not-found errors. * Updated for the 'Force Off' gamepad mode added in Stardew Valley 1.4.1. + * The 'skipped mods' list now shows broken dependencies first, so it's easier to see which ones to fix first. * Fixed compatibility with Linux Mint 18 (thanks to techge!) and Arch Linux. * Fixed compatibility with Linux systems which have libhybris-utils installed. * Fixes for the bundled Console Commands mod: diff --git a/src/SMAPI/Framework/IModMetadata.cs b/src/SMAPI/Framework/IModMetadata.cs index 6ee7df69..37927482 100644 --- a/src/SMAPI/Framework/IModMetadata.cs +++ b/src/SMAPI/Framework/IModMetadata.cs @@ -105,6 +105,10 @@ namespace StardewModdingAPI.Framework /// Only return valid update keys. IEnumerable GetUpdateKeys(bool validOnly = true); + /// Get the mod IDs that must be installed to load this mod. + /// Whether to include optional dependencies. + IEnumerable GetRequiredModIds(bool includeOptional = false); + /// Whether the mod has at least one valid update key set. bool HasValidUpdateKeys(); diff --git a/src/SMAPI/Framework/ModLoading/ModMetadata.cs b/src/SMAPI/Framework/ModLoading/ModMetadata.cs index 7f788d17..0e90362e 100644 --- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs +++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs @@ -188,6 +188,27 @@ namespace StardewModdingAPI.Framework.ModLoading } } + /// Get the mod IDs that must be installed to load this mod. + /// Whether to include optional dependencies. + public IEnumerable GetRequiredModIds(bool includeOptional = false) + { + HashSet required = new HashSet(StringComparer.InvariantCultureIgnoreCase); + + // yield dependencies + if (this.Manifest?.Dependencies != null) + { + foreach (var entry in this.Manifest?.Dependencies) + { + if ((entry.IsRequired || includeOptional) && required.Add(entry.UniqueID)) + yield return entry.UniqueID; + } + } + + // yield content pack parent + if (this.Manifest?.ContentPackFor?.UniqueID != null && required.Add(this.Manifest.ContentPackFor.UniqueID)) + yield return this.Manifest.ContentPackFor.UniqueID; + } + /// Whether the mod has at least one valid update key set. public bool HasValidUpdateKeys() { diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index c9c9d14a..a89c14d7 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1046,26 +1046,48 @@ namespace StardewModdingAPI.Framework // log skipped mods if (skippedMods.Any()) { + // get logging logic + HashSet logged = new HashSet(); + void LogSkippedMod(IModMetadata mod, string errorReason, string errorDetails) + { + string message = $" - {mod.DisplayName}{(mod.Manifest?.Version != null ? " " + mod.Manifest.Version.ToString() : "")} because {errorReason}"; + + if (logged.Add($"{message}|{errorDetails}")) + { + this.Monitor.Log(message, LogLevel.Error); + if (errorDetails != null) + this.Monitor.Log($" ({errorDetails})", LogLevel.Trace); + } + } + + // find skipped dependencies + KeyValuePair>[] skippedDependencies; + { + HashSet skippedDependencyIds = new HashSet(StringComparer.InvariantCultureIgnoreCase); + HashSet skippedModIds = new HashSet(from mod in skippedMods where mod.Key.HasID() select mod.Key.Manifest.UniqueID, StringComparer.InvariantCultureIgnoreCase); + foreach (IModMetadata mod in skippedMods.Keys) + { + foreach (string requiredId in skippedModIds.Intersect(mod.GetRequiredModIds())) + skippedDependencyIds.Add(requiredId); + } + skippedDependencies = skippedMods.Where(p => p.Key.HasID() && skippedDependencyIds.Contains(p.Key.Manifest.UniqueID)).ToArray(); + } + + // log skipped mods this.Monitor.Log(" Skipped mods", LogLevel.Error); this.Monitor.Log(" " + "".PadRight(50, '-'), LogLevel.Error); this.Monitor.Log(" These mods could not be added to your game.", LogLevel.Error); this.Monitor.Newline(); - HashSet logged = new HashSet(); - foreach (var pair in skippedMods.OrderBy(p => p.Key.DisplayName)) + if (skippedDependencies.Any()) { - IModMetadata mod = pair.Key; - string errorReason = pair.Value.Item1; - string errorDetails = pair.Value.Item2; - string message = $" - {mod.DisplayName}{(mod.Manifest?.Version != null ? " " + mod.Manifest.Version.ToString() : "")} because {errorReason}"; - - if (!logged.Add($"{message}|{errorDetails}")) - continue; // skip duplicate messages (e.g. if multiple copies of the mod are installed) - - this.Monitor.Log(message, LogLevel.Error); - if (errorDetails != null) - this.Monitor.Log($" ({errorDetails})", LogLevel.Trace); + foreach (var pair in skippedDependencies.OrderBy(p => p.Key.DisplayName)) + LogSkippedMod(pair.Key, pair.Value.Item1, pair.Value.Item2); + this.Monitor.Newline(); } + + foreach (var pair in skippedMods.OrderBy(p => p.Key.DisplayName)) + LogSkippedMod(pair.Key, pair.Value.Item1, pair.Value.Item2); this.Monitor.Newline(); }