Merge pull request #882 from Shockah/mod-load-order
Add options to override mod load order # Conflicts: # src/SMAPI/Framework/Models/SConfig.cs
This commit is contained in:
commit
133aeab3fc
|
@ -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>
|
/// <summary>Sort the given mods by the order they should be loaded.</summary>
|
||||||
/// <param name="mods">The mods to process.</param>
|
/// <param name="mods">The mods to process.</param>
|
||||||
/// <param name="modDatabase">Handles access to SMAPI's internal mod metadata list.</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
|
// initialize metadata
|
||||||
mods = mods.ToArray();
|
mods = mods.ToArray();
|
||||||
|
@ -184,7 +206,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
||||||
|
|
||||||
// sort mods
|
// sort mods
|
||||||
foreach (IModMetadata mod in 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();
|
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="sortedMods">The list in which to save mods sorted by dependency order.</param>
|
||||||
/// <param name="currentChain">The current change of mod dependencies.</param>
|
/// <param name="currentChain">The current change of mod dependencies.</param>
|
||||||
/// <returns>Returns the mod dependency status.</returns>
|
/// <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
|
// check if already visited
|
||||||
switch (states[mod])
|
switch (states[mod])
|
||||||
|
@ -332,7 +354,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
||||||
/// <summary>Get the dependencies declared in a manifest.</summary>
|
/// <summary>Get the dependencies declared in a manifest.</summary>
|
||||||
/// <param name="manifest">The mod manifest.</param>
|
/// <param name="manifest">The mod manifest.</param>
|
||||||
/// <param name="loadedMods">The loaded mods.</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));
|
IModMetadata? FindMod(string id) => loadedMods.FirstOrDefault(m => m.HasID(id));
|
||||||
|
|
||||||
|
|
|
@ -86,6 +86,12 @@ namespace StardewModdingAPI.Framework.Models
|
||||||
/// <summary>The mod IDs SMAPI should ignore when performing update checks or validating update keys.</summary>
|
/// <summary>The mod IDs SMAPI should ignore when performing update checks or validating update keys.</summary>
|
||||||
public HashSet<string> SuppressUpdateChecks { get; set; }
|
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
|
** Public methods
|
||||||
|
@ -105,7 +111,9 @@ namespace StardewModdingAPI.Framework.Models
|
||||||
/// <param name="consoleColors"><inheritdoc cref="ConsoleColors" path="/summary" /></param>
|
/// <param name="consoleColors"><inheritdoc cref="ConsoleColors" path="/summary" /></param>
|
||||||
/// <param name="suppressHarmonyDebugMode"><inheritdoc cref="SuppressHarmonyDebugMode" path="/summary" /></param>
|
/// <param name="suppressHarmonyDebugMode"><inheritdoc cref="SuppressHarmonyDebugMode" path="/summary" /></param>
|
||||||
/// <param name="suppressUpdateChecks"><inheritdoc cref="SuppressUpdateChecks" 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.DeveloperMode = developerMode;
|
||||||
this.CheckForUpdates = checkForUpdates ?? (bool)SConfig.DefaultValues[nameof(this.CheckForUpdates)];
|
this.CheckForUpdates = checkForUpdates ?? (bool)SConfig.DefaultValues[nameof(this.CheckForUpdates)];
|
||||||
|
@ -121,6 +129,8 @@ namespace StardewModdingAPI.Framework.Models
|
||||||
this.ConsoleColors = consoleColors;
|
this.ConsoleColors = consoleColors;
|
||||||
this.SuppressHarmonyDebugMode = suppressHarmonyDebugMode ?? (bool)SConfig.DefaultValues[nameof(this.SuppressHarmonyDebugMode)];
|
this.SuppressHarmonyDebugMode = suppressHarmonyDebugMode ?? (bool)SConfig.DefaultValues[nameof(this.SuppressHarmonyDebugMode)];
|
||||||
this.SuppressUpdateChecks = new HashSet<string>(suppressUpdateChecks ?? Array.Empty<string>(), StringComparer.OrdinalIgnoreCase);
|
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>
|
/// <summary>Override the value of <see cref="DeveloperMode"/>.</summary>
|
||||||
|
@ -142,6 +152,12 @@ namespace StardewModdingAPI.Framework.Models
|
||||||
custom[name] = value;
|
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))
|
if (!this.SuppressUpdateChecks.SetEquals(SConfig.DefaultSuppressUpdateChecks))
|
||||||
custom[nameof(this.SuppressUpdateChecks)] = $"[{string.Join(", ", this.SuppressUpdateChecks)}]";
|
custom[nameof(this.SuppressUpdateChecks)] = $"[{string.Join(", ", this.SuppressUpdateChecks)}]";
|
||||||
|
|
||||||
|
|
|
@ -423,8 +423,29 @@ namespace StardewModdingAPI.Framework
|
||||||
this.Monitor.Log($" Skipped {mod.GetRelativePathWithRoot()} (folder name starts with a dot).");
|
this.Monitor.Log($" Skipped {mod.GetRelativePathWithRoot()} (folder name starts with a dot).");
|
||||||
mods = mods.Where(p => !p.IsIgnored).ToArray();
|
mods = mods.Where(p => !p.IsIgnored).ToArray();
|
||||||
|
|
||||||
// load mods
|
// validate manifests
|
||||||
resolver.ValidateManifests(mods, Constants.ApiVersion, toolkit.GetUpdateUrl, getFileLookup: this.GetFileLookup);
|
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();
|
mods = resolver.ProcessDependencies(mods, modDatabase).ToArray();
|
||||||
this.LoadMods(mods, this.Toolkit.JsonHelper, this.ContentCore, modDatabase);
|
this.LoadMods(mods, this.Toolkit.JsonHelper, this.ContentCore, modDatabase);
|
||||||
|
|
||||||
|
|
|
@ -147,5 +147,17 @@ copy all the settings, or you may cause bugs due to overridden changes in future
|
||||||
"SMAPI.ConsoleCommands",
|
"SMAPI.ConsoleCommands",
|
||||||
"SMAPI.ErrorHandler",
|
"SMAPI.ErrorHandler",
|
||||||
"SMAPI.SaveBackup"
|
"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": []
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue