using SPTarkov.Server.Core.Models.Spt.Mod; using SPTarkov.Server.Core.Utils.Cloners; namespace SPTarkov.Server.Modding; public class ModLoadOrder(ICloner cloner) { protected Dictionary loadOrder = new(); protected Dictionary mods = new(); protected Dictionary modsAvailable = new(); public Dictionary SetModList(Dictionary mods) { this.mods = mods; modsAvailable = cloner.Clone(this.mods); loadOrder = new Dictionary(); var visited = new HashSet(); // 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 GetLoadOrder() { return [.. loadOrder.Keys]; } public HashSet GetModsOnLoadBefore(string mod) { if (!mods.TryGetValue(mod, out var config)) { throw new Exception($"The mod {mod} does not exist!"); } var loadBefore = new HashSet(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 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(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); } }