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();
}