summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2022-11-11 01:29:30 -0500
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2022-11-11 01:29:30 -0500
commit133aeab3fccb3acdfaff8b128cbfafda03b7c8fc (patch)
tree4ab4fe6e9f0a77eff90f03665987f1afc58489e6
parenteaacfd04b8d526d9d190c864231f5f365e19a7da (diff)
parentdbf7750f3e27cf7c50e2f06005fd14da95627dc3 (diff)
downloadSMAPI-133aeab3fccb3acdfaff8b128cbfafda03b7c8fc.tar.gz
SMAPI-133aeab3fccb3acdfaff8b128cbfafda03b7c8fc.tar.bz2
SMAPI-133aeab3fccb3acdfaff8b128cbfafda03b7c8fc.zip
Merge pull request #882 from Shockah/mod-load-order
Add options to override mod load order # Conflicts: # src/SMAPI/Framework/Models/SConfig.cs
-rw-r--r--src/SMAPI/Framework/ModLoading/ModResolver.cs30
-rw-r--r--src/SMAPI/Framework/Models/SConfig.cs18
-rw-r--r--src/SMAPI/Framework/SCore.cs23
-rw-r--r--src/SMAPI/SMAPI.config.json14
4 files changed, 78 insertions, 7 deletions
diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs
index 9db9db99..96975e05 100644
--- a/src/SMAPI/Framework/ModLoading/ModResolver.cs
+++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs
@@ -165,10 +165,32 @@ namespace StardewModdingAPI.Framework.ModLoading
}
}
+ /// <summary>Apply preliminary overrides to the load order based on the SMAPI configuration.</summary>
+ /// <param name="mods">The mods to process.</param>
+ /// <param name="modIdsToLoadEarly">The mod IDs SMAPI should load before any other mods (except those needed to load them).</param>
+ /// <param name="modIdsToLoadLate">The mod IDs SMAPI should load after any other mods.</param>
+ public IModMetadata[] ApplyLoadOrderOverrides(IModMetadata[] mods, HashSet<string> modIdsToLoadEarly, HashSet<string> modIdsToLoadLate)
+ {
+ if (!modIdsToLoadEarly.Any() && !modIdsToLoadLate.Any())
+ return mods;
+
+ return mods
+ .OrderBy(mod =>
+ {
+ string id = mod.Manifest.UniqueID;
+ if (modIdsToLoadEarly.Contains(id))
+ return -1;
+ if (modIdsToLoadLate.Contains(id))
+ return 1;
+ return 0;
+ })
+ .ToArray();
+ }
+
/// <summary>Sort the given mods by the order they should be loaded.</summary>
/// <param name="mods">The mods to process.</param>
/// <param name="modDatabase">Handles access to SMAPI's internal mod metadata list.</param>
- public IEnumerable<IModMetadata> ProcessDependencies(IEnumerable<IModMetadata> mods, ModDatabase modDatabase)
+ public IEnumerable<IModMetadata> ProcessDependencies(IReadOnlyList<IModMetadata> mods, ModDatabase modDatabase)
{
// initialize metadata
mods = mods.ToArray();
@@ -184,7 +206,7 @@ namespace StardewModdingAPI.Framework.ModLoading
// sort mods
foreach (IModMetadata mod in mods)
- this.ProcessDependencies(mods.ToArray(), modDatabase, mod, states, sortedMods, new List<IModMetadata>());
+ this.ProcessDependencies(mods, modDatabase, mod, states, sortedMods, new List<IModMetadata>());
return sortedMods.Reverse();
}
@@ -201,7 +223,7 @@ namespace StardewModdingAPI.Framework.ModLoading
/// <param name="sortedMods">The list in which to save mods sorted by dependency order.</param>
/// <param name="currentChain">The current change of mod dependencies.</param>
/// <returns>Returns the mod dependency status.</returns>
- private ModDependencyStatus ProcessDependencies(IModMetadata[] mods, ModDatabase modDatabase, IModMetadata mod, IDictionary<IModMetadata, ModDependencyStatus> states, Stack<IModMetadata> sortedMods, ICollection<IModMetadata> currentChain)
+ private ModDependencyStatus ProcessDependencies(IReadOnlyList<IModMetadata> mods, ModDatabase modDatabase, IModMetadata mod, IDictionary<IModMetadata, ModDependencyStatus> states, Stack<IModMetadata> sortedMods, ICollection<IModMetadata> currentChain)
{
// check if already visited
switch (states[mod])
@@ -332,7 +354,7 @@ namespace StardewModdingAPI.Framework.ModLoading
/// <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)
+ private IEnumerable<ModDependency> GetDependenciesFrom(IManifest manifest, IReadOnlyList<IModMetadata> loadedMods)
{
IModMetadata? FindMod(string id) => loadedMods.FirstOrDefault(m => m.HasID(id));
diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs
index 825ebb44..87970e6c 100644
--- a/src/SMAPI/Framework/Models/SConfig.cs
+++ b/src/SMAPI/Framework/Models/SConfig.cs
@@ -86,6 +86,12 @@ namespace StardewModdingAPI.Framework.Models
/// <summary>The mod IDs SMAPI should ignore when performing update checks or validating update keys.</summary>
public HashSet<string> SuppressUpdateChecks { get; set; }
+ /// <summary>The mod IDs SMAPI should load before any other mods (except those needed to load them).</summary>
+ public HashSet<string> ModsToLoadEarly { get; set; }
+
+ /// <summary>The mod IDs SMAPI should load after any other mods.</summary>
+ public HashSet<string> ModsToLoadLate { get; set; }
+
/********
** Public methods
@@ -105,7 +111,9 @@ namespace StardewModdingAPI.Framework.Models
/// <param name="consoleColors"><inheritdoc cref="ConsoleColors" path="/summary" /></param>
/// <param name="suppressHarmonyDebugMode"><inheritdoc cref="SuppressHarmonyDebugMode" path="/summary" /></param>
/// <param name="suppressUpdateChecks"><inheritdoc cref="SuppressUpdateChecks" path="/summary" /></param>
- public SConfig(bool developerMode, bool? checkForUpdates, bool? listenForConsoleInput, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, ColorSchemeConfig consoleColors, bool? suppressHarmonyDebugMode, string[]? suppressUpdateChecks)
+ /// <param name="modsToLoadEarly"><inheritdoc cref="ModsToLoadEarly" path="/summary" /></param>
+ /// <param name="modsToLoadLate"><inheritdoc cref="ModsToLoadLate" path="/summary" /></param>
+ public SConfig(bool developerMode, bool? checkForUpdates, bool? listenForConsoleInput, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, ColorSchemeConfig consoleColors, bool? suppressHarmonyDebugMode, string[]? suppressUpdateChecks, string[]? modsToLoadEarly, string[]? modsToLoadLate)
{
this.DeveloperMode = developerMode;
this.CheckForUpdates = checkForUpdates ?? (bool)SConfig.DefaultValues[nameof(this.CheckForUpdates)];
@@ -121,6 +129,8 @@ namespace StardewModdingAPI.Framework.Models
this.ConsoleColors = consoleColors;
this.SuppressHarmonyDebugMode = suppressHarmonyDebugMode ?? (bool)SConfig.DefaultValues[nameof(this.SuppressHarmonyDebugMode)];
this.SuppressUpdateChecks = new HashSet<string>(suppressUpdateChecks ?? Array.Empty<string>(), StringComparer.OrdinalIgnoreCase);
+ this.ModsToLoadEarly = new HashSet<string>(modsToLoadEarly ?? Array.Empty<string>(), StringComparer.OrdinalIgnoreCase);
+ this.ModsToLoadLate = new HashSet<string>(modsToLoadLate ?? Array.Empty<string>(), StringComparer.OrdinalIgnoreCase);
}
/// <summary>Override the value of <see cref="DeveloperMode"/>.</summary>
@@ -142,6 +152,12 @@ namespace StardewModdingAPI.Framework.Models
custom[name] = value;
}
+ if (this.ModsToLoadEarly.Any())
+ custom[nameof(this.ModsToLoadEarly)] = $"[{string.Join(", ", this.ModsToLoadEarly)}]";
+
+ if (this.ModsToLoadLate.Any())
+ custom[nameof(this.ModsToLoadLate)] = $"[{string.Join(", ", this.ModsToLoadLate)}]";
+
if (!this.SuppressUpdateChecks.SetEquals(SConfig.DefaultSuppressUpdateChecks))
custom[nameof(this.SuppressUpdateChecks)] = $"[{string.Join(", ", this.SuppressUpdateChecks)}]";
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index 7bb54653..8e21e7dc 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -423,8 +423,29 @@ namespace StardewModdingAPI.Framework
this.Monitor.Log($" Skipped {mod.GetRelativePathWithRoot()} (folder name starts with a dot).");
mods = mods.Where(p => !p.IsIgnored).ToArray();
- // load mods
+ // validate manifests
resolver.ValidateManifests(mods, Constants.ApiVersion, toolkit.GetUpdateUrl, getFileLookup: this.GetFileLookup);
+
+ // apply load order customizations
+ if (this.Settings.ModsToLoadEarly.Any() || this.Settings.ModsToLoadLate.Any())
+ {
+ HashSet<string> installedIds = new HashSet<string>(mods.Select(p => p.Manifest.UniqueID), StringComparer.OrdinalIgnoreCase);
+
+ string[] missingEarlyMods = this.Settings.ModsToLoadEarly.Where(id => !installedIds.Contains(id)).OrderBy(p => p, StringComparer.OrdinalIgnoreCase).ToArray();
+ string[] missingLateMods = this.Settings.ModsToLoadLate.Where(id => !installedIds.Contains(id)).OrderBy(p => p, StringComparer.OrdinalIgnoreCase).ToArray();
+ string[] duplicateMods = this.Settings.ModsToLoadLate.Where(id => this.Settings.ModsToLoadEarly.Contains(id)).OrderBy(p => p, StringComparer.OrdinalIgnoreCase).ToArray();
+
+ if (missingEarlyMods.Any())
+ this.Monitor.Log($" The 'smapi-internal/config.json' file lists mod IDs in {nameof(this.Settings.ModsToLoadEarly)} which aren't installed: '{string.Join("', '", missingEarlyMods)}'.", LogLevel.Warn);
+ if (missingLateMods.Any())
+ this.Monitor.Log($" The 'smapi-internal/config.json' file lists mod IDs in {nameof(this.Settings.ModsToLoadLate)} which aren't installed: '{string.Join("', '", missingLateMods)}'.", LogLevel.Warn);
+ if (duplicateMods.Any())
+ this.Monitor.Log($" The 'smapi-internal/config.json' file lists mod IDs which are in both {nameof(this.Settings.ModsToLoadEarly)} and {nameof(this.Settings.ModsToLoadLate)}: '{string.Join("', '", duplicateMods)}'. These will be loaded early.", LogLevel.Warn);
+
+ mods = resolver.ApplyLoadOrderOverrides(mods, this.Settings.ModsToLoadEarly, this.Settings.ModsToLoadLate);
+ }
+
+ // load mods
mods = resolver.ProcessDependencies(mods, modDatabase).ToArray();
this.LoadMods(mods, this.Toolkit.JsonHelper, this.ContentCore, modDatabase);
diff --git a/src/SMAPI/SMAPI.config.json b/src/SMAPI/SMAPI.config.json
index 52f25fdd..0d00db4d 100644
--- a/src/SMAPI/SMAPI.config.json
+++ b/src/SMAPI/SMAPI.config.json
@@ -147,5 +147,17 @@ copy all the settings, or you may cause bugs due to overridden changes in future
"SMAPI.ConsoleCommands",
"SMAPI.ErrorHandler",
"SMAPI.SaveBackup"
- ]
+ ],
+
+ /**
+ * The mod IDs SMAPI should load before any other mods (except those needed to load them)
+ * or after any other mods.
+ *
+ * This lets you manually fix the load order if needed, but this is a last resort — SMAPI
+ * automatically adjusts the load order based on mods' dependencies, so needing to manually
+ * edit the order is usually a problem with one or both mods' metadata that can be reported to
+ * the mod author.
+ */
+ "ModsToLoadEarly": [],
+ "ModsToLoadLate": []
}