126 lines
3.4 KiB
C#
126 lines
3.4 KiB
C#
using SPTarkov.Server.Core.Models.Spt.Mod;
|
|
using SPTarkov.Server.Core.Utils.Cloners;
|
|
|
|
namespace SPTarkov.Server.Modding;
|
|
|
|
public class ModLoadOrder(ICloner cloner)
|
|
{
|
|
protected Dictionary<string, AbstractModMetadata> loadOrder = new();
|
|
protected Dictionary<string, AbstractModMetadata> mods = new();
|
|
protected Dictionary<string, AbstractModMetadata> modsAvailable = new();
|
|
|
|
public Dictionary<string, AbstractModMetadata> SetModList(Dictionary<string, AbstractModMetadata> mods)
|
|
{
|
|
this.mods = mods;
|
|
modsAvailable = cloner.Clone(this.mods);
|
|
loadOrder = new Dictionary<string, AbstractModMetadata>();
|
|
|
|
var visited = new HashSet<string>();
|
|
|
|
// invert loadBefore into loadAfter on specified mods
|
|
foreach (var (modName, modConfig) in modsAvailable)
|
|
{
|
|
if ((modConfig.LoadBefore ?? []).Count > 0)
|
|
{
|
|
InvertLoadBefore(modName);
|
|
}
|
|
}
|
|
|
|
foreach (var modName in modsAvailable.Keys)
|
|
{
|
|
GetLoadOrderRecursive(modName, visited);
|
|
}
|
|
|
|
return loadOrder;
|
|
}
|
|
|
|
public List<string> GetLoadOrder()
|
|
{
|
|
return [.. loadOrder.Keys];
|
|
}
|
|
|
|
public HashSet<string> GetModsOnLoadBefore(string mod)
|
|
{
|
|
if (!mods.TryGetValue(mod, out var config))
|
|
{
|
|
throw new Exception($"The mod {mod} does not exist!");
|
|
}
|
|
|
|
var loadBefore = new HashSet<string>(config.LoadBefore);
|
|
|
|
foreach (var loadBeforeMod in loadBefore)
|
|
{
|
|
if (!mods.ContainsKey(loadBeforeMod))
|
|
{
|
|
loadBefore.Remove(loadBeforeMod);
|
|
}
|
|
}
|
|
|
|
return loadBefore;
|
|
}
|
|
|
|
protected void InvertLoadBefore(string mod)
|
|
{
|
|
var loadBefore = GetModsOnLoadBefore(mod);
|
|
|
|
foreach (var loadBeforeMod in loadBefore)
|
|
{
|
|
var loadBeforeModConfig = modsAvailable[loadBeforeMod];
|
|
|
|
loadBeforeModConfig.LoadAfter ??= [];
|
|
loadBeforeModConfig.LoadAfter.Add(mod);
|
|
|
|
modsAvailable.Add(loadBeforeMod, loadBeforeModConfig);
|
|
}
|
|
}
|
|
|
|
protected void GetLoadOrderRecursive(string mod, HashSet<string> visited)
|
|
{
|
|
// Validate package
|
|
if (loadOrder.ContainsKey(mod))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (visited.Contains(mod))
|
|
{
|
|
// Additional info to help debug
|
|
throw new Exception($"Cyclic dependency detected for mod {mod}!");
|
|
}
|
|
|
|
// Check dependencies
|
|
if (!modsAvailable.TryGetValue(mod, out var config))
|
|
{
|
|
throw new Exception("modloader-error_parsing_mod_load_order");
|
|
}
|
|
|
|
config.LoadAfter ??= [];
|
|
config.ModDependencies ??= [];
|
|
|
|
var dependencies = new HashSet<string>(config.ModDependencies.Keys);
|
|
|
|
foreach (var modAfter in config.LoadAfter)
|
|
{
|
|
if (modsAvailable.TryGetValue(modAfter, out var value))
|
|
{
|
|
if (value?.LoadAfter?.Contains(mod) ?? false)
|
|
{
|
|
throw new Exception("modloader-load_order_conflict");
|
|
}
|
|
|
|
dependencies.Add(modAfter);
|
|
}
|
|
}
|
|
|
|
visited.Add(mod);
|
|
|
|
foreach (var nextMod in dependencies)
|
|
{
|
|
GetLoadOrderRecursive(nextMod, visited);
|
|
}
|
|
|
|
visited.Remove(mod);
|
|
loadOrder.Add(mod, config);
|
|
}
|
|
}
|