From a5c0d192ff8c45a731eea291066d1af88afbb630 Mon Sep 17 00:00:00 2001 From: taubenangriff <51975164+taubenangriff@users.noreply.github.com> Date: Fri, 7 Apr 2023 15:58:46 +0200 Subject: [PATCH 01/10] add ModID to the mod class --- ModManager_Classes/Models/Mod.cs | 2 ++ ModManager_Classes/Models/ModMetadata/Modinfo.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ModManager_Classes/Models/Mod.cs b/ModManager_Classes/Models/Mod.cs index 548cfc04..2811bea7 100644 --- a/ModManager_Classes/Models/Mod.cs +++ b/ModManager_Classes/Models/Mod.cs @@ -69,6 +69,8 @@ public bool IsRemoved /// Category with default "NoCategory". /// public IText Category => Modinfo.Category; + + public String ModID => Modinfo.ModID ?? FolderName; #endregion #region Optional Mod Manager info diff --git a/ModManager_Classes/Models/ModMetadata/Modinfo.cs b/ModManager_Classes/Models/ModMetadata/Modinfo.cs index 8b5e65b4..bc7c18c1 100644 --- a/ModManager_Classes/Models/ModMetadata/Modinfo.cs +++ b/ModManager_Classes/Models/ModMetadata/Modinfo.cs @@ -19,6 +19,7 @@ public Modinfo() { } public string? CreatorName { get; set; } public string? CreatorContact { get; set; } public string? Image { get; set; } + public string[]? LoadAfterIds { get; set; } public LocalizedModinfo GetLocalized(string name) => new (name, this); } @@ -39,6 +40,7 @@ public LocalizedModinfo(string name, Modinfo? modinfo) CreatorName = modinfo?.CreatorName; CreatorContact = modinfo?.CreatorContact; Image = modinfo?.Image; + LoadAfterIds = modinfo?.LoadAfterIds; // localize Category = (modinfo?.Category is not null) ? TextManager.CreateLocalizedText(modinfo.Category) : TextManager.Instance["MODLIST_NOCATEGORY"]; From 50b488c9c76918e4c54fd3679322dde288a0ea74 Mon Sep 17 00:00:00 2001 From: taubenangriff <51975164+taubenangriff@users.noreply.github.com> Date: Fri, 7 Apr 2023 15:58:58 +0200 Subject: [PATCH 02/10] implement a Comparer by loadorder --- ModManager_Classes/Models/ModComparer.cs | 99 +++++++++++++++++++++--- 1 file changed, 89 insertions(+), 10 deletions(-) diff --git a/ModManager_Classes/Models/ModComparer.cs b/ModManager_Classes/Models/ModComparer.cs index 2ff5c2ea..19a5c7d5 100644 --- a/ModManager_Classes/Models/ModComparer.cs +++ b/ModManager_Classes/Models/ModComparer.cs @@ -16,17 +16,25 @@ public int Compare(Mod? x, Mod? y) if (x is null) return -1; if (y is null) return 1; - int active = y.IsActive.CompareTo(x.IsActive); - if (active != 0) - return active; - int category = string.Compare(x.Modinfo?.Category.Text, y.Modinfo?.Category.Text); - if (category != 0) - return category; - int name = string.Compare(x.Modinfo?.ModName.Text, y.Modinfo?.ModName.Text); - if (name != 0) - return name; + var byActive = CompareByActive.Default.Compare(x, y); + if (byActive != 0) + return byActive; - return 0; + return CompareByCategoryName.Default.Compare(x, y); + } + } + + public class CompareByActive : IComparer + { + public readonly static CompareByActive Default = new(); + + public int Compare(Mod? x, Mod? y) + { + if (y is null && x is null) return 0; + if (x is null) return -1; + if (y is null) return 1; + + return y.IsActive.CompareTo(x.IsActive); } } @@ -64,4 +72,75 @@ public int Compare(Mod? x, Mod? y) return string.Compare(x.FolderName, y.FolderName); } } + + public class ComparebyLoadOrder : IComparer + { + public readonly static ComparebyLoadOrder Default = new(); + + public int Compare(Mod? x, Mod? y) + { + //ignore inactive + var byActive = CompareByActive.Default.Compare(x, y); + if (byActive != 0) + return byActive; + + var catX = GetCategory(x!); + var catY = GetCategory(y!); + + //same category + if (catX == catY) + { + if (catX != Category.NoLoadAfter) + CompareByLoadAfterID.Default.Compare(x, y); + return CompareByCategoryName.Default.Compare(x, y); + } + + //different categories + + //x is wildcard dependant: x comes last. Double wildcard is excluded by same category + if (IsWildcardDependant(x)) + return 1; + else if (IsWildcardDependant(y)) + return -1; + return CompareByLoadAfterID.Default.Compare(x, y); + } + + private enum Category { LoadAfterNoWildcard, NoLoadAfter, Wildcard } + private Category GetCategory(Mod x) + { + if (x.Modinfo.LoadAfterIds is null) + return Category.NoLoadAfter; + if (IsWildcardDependant(x)) + return Category.Wildcard; + else return Category.LoadAfterNoWildcard; + } + + private bool IsWildcardDependant(Mod x) + { + return x.Modinfo?.LoadAfterIds?.Contains("*") ?? false; + } + + } + + public class CompareByLoadAfterID : IComparer + { + public readonly static CompareByLoadAfterID Default = new(); + + public int Compare(Mod? x, Mod? y) + { + if (y is null && x is null) return 0; + if (x is null) return -1; + if (y is null) return 1; + + var xDy = x?.Modinfo?.LoadAfterIds?.Contains(y?.ModID) ?? false; + var yDx = y?.Modinfo?.LoadAfterIds?.Contains(x?.ModID) ?? false; + if (xDy && yDx) + return 0; + if (xDy) + return 1; + if (yDx) + return -1; + return 0; + } + } } From f17ef48177b48a8feea149eed50552a37c095050 Mon Sep 17 00:00:00 2001 From: taubenangriff <51975164+taubenangriff@users.noreply.github.com> Date: Fri, 7 Apr 2023 15:59:21 +0200 Subject: [PATCH 03/10] add Sort By Load Order to Settings --- ModManager/Utils/AppSettings.cs | 1 + ModManager/resources/texts.json | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/ModManager/Utils/AppSettings.cs b/ModManager/Utils/AppSettings.cs index 642f9c38..5f07651d 100644 --- a/ModManager/Utils/AppSettings.cs +++ b/ModManager/Utils/AppSettings.cs @@ -238,6 +238,7 @@ public AppSettings() Sortings.Add(new SortSetting(CompareByActiveCategoryName.Default, TextManager["SORTING_DEFAULT"], "Default")); Sortings.Add(new SortSetting(CompareByCategoryName.Default, TextManager["SORTING_ACTIVE_AGNOSTIC"], "ActiveAgnostic")); Sortings.Add(new SortSetting(CompareByFolder.Default, TextManager["SORTING_BYFOLDER"], "Folder")); + Sortings.Add(new SortSetting(ComparebyLoadOrder.Default, TextManager["SORTING_LOADORDER"], "LoadOrder")); RateLimitChanged += x => InstallationManager.Instance.DownloadConfig.MaximumBytesPerSecond = x; diff --git a/ModManager/resources/texts.json b/ModManager/resources/texts.json index f3708c3d..07580827 100644 --- a/ModManager/resources/texts.json +++ b/ModManager/resources/texts.json @@ -1781,6 +1781,19 @@ "Spanish": null, "Taiwanese": null }, + "SORTING_LOADORDER": { + "Chinese": null, + "English": "By Load Order", + "French": null, + "German": "Nach Ladereihenfolge", + "Italian": null, + "Japanese": null, + "Korean": null, + "Polish": null, + "Russian": null, + "Spanish": null, + "Taiwanese": null + }, "INSTALLATION_NOINSTALLS": { "Chinese": null, "English": "No installs are running currently.", From c3b0607286ab26140304d29586a6bf9b7960e849 Mon Sep 17 00:00:00 2001 From: taubenangriff <51975164+taubenangriff@users.noreply.github.com> Date: Fri, 7 Apr 2023 15:59:30 +0200 Subject: [PATCH 04/10] Add tests for load order sort --- tests/Imya.UnitTests/SortOrderTests.cs | 93 ++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 tests/Imya.UnitTests/SortOrderTests.cs diff --git a/tests/Imya.UnitTests/SortOrderTests.cs b/tests/Imya.UnitTests/SortOrderTests.cs new file mode 100644 index 00000000..7fc8d2e1 --- /dev/null +++ b/tests/Imya.UnitTests/SortOrderTests.cs @@ -0,0 +1,93 @@ +using Imya.Models; +using Imya.Models.Attributes; +using Imya.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Imya.UnitTests +{ + public class SortOrderTests + { + [Fact] + public void LoadOrder_NormalDependency() + { + DirectoryEx.EnsureDeleted("mods"); + + //Mod1 + const string mod1 = "mods\\mod1"; + Directory.CreateDirectory(mod1); + File.WriteAllText($"{mod1}\\modinfo.json", "{\"ModID\": \"Mod1\", \"Version\": \"1\", \"LoadAfterIds\": [\"Mod2\"]}"); + + //Mod2: Last in folder + const string mod2 = "mods\\zmod2"; + Directory.CreateDirectory(mod2); + File.WriteAllText($"{mod2}\\modinfo.json", "{\"ModID\": \"Mod2\", \"Version\": \"1\"}"); + + var target = new ModCollection("mods"); + target.LoadModsAsync().Wait(); + + var sorted = target.Mods.ToList().OrderBy(x => x, ComparebyLoadOrder.Default).ToArray(); + + Assert.True(sorted[0].ModID == "Mod2"); + Assert.True(sorted[1].ModID == "Mod1"); + } + + [Fact] + public void LoadOrder_WildcardDependency() + { + DirectoryEx.EnsureDeleted("mods"); + + //Mod1 + const string mod1 = "mods\\mod1"; + Directory.CreateDirectory(mod1); + File.WriteAllText($"{mod1}\\modinfo.json", "{\"ModID\": \"Mod1\", \"Version\": \"1\", \"LoadAfterIds\": [\"*\"]}"); + + //Mod2: Last in folder + const string mod2 = "mods\\zmod2"; + Directory.CreateDirectory(mod2); + File.WriteAllText($"{mod2}\\modinfo.json", "{\"ModID\": \"Mod2\", \"Version\": \"1\"}"); + + var target = new ModCollection("mods"); + target.LoadModsAsync().Wait(); + + var sorted = target.Mods.ToList().OrderBy(x => x, ComparebyLoadOrder.Default).ToArray(); + + Assert.True(sorted[0].ModID == "Mod2"); + Assert.True(sorted[1].ModID == "Mod1"); + } + + [Fact] + public void LoadOrder_ThreeMods() + { + DirectoryEx.EnsureDeleted("mods"); + + //Mod1 + const string mod1 = "mods\\mod1"; + Directory.CreateDirectory(mod1); + File.WriteAllText($"{mod1}\\modinfo.json", "{\"ModID\": \"Mod1\", \"Version\": \"1\", \"LoadAfterIds\": [\"*\"]}"); + + //Mod2: Last in folder + const string mod2 = "mods\\zmod2"; + Directory.CreateDirectory(mod2); + File.WriteAllText($"{mod2}\\modinfo.json", "{\"ModID\": \"Mod2\", \"Version\": \"1\"}"); + + const string mod3 = "mods\\mod3"; + Directory.CreateDirectory(mod3); + File.WriteAllText($"{mod3}\\modinfo.json", "{\"ModID\": \"Mod3\", \"Version\": \"1\", \"LoadAfterIds\": [\"Mod2\"]}"); + + var target = new ModCollection("mods"); + target.LoadModsAsync().Wait(); + + var sorted = target.Mods.ToList().OrderBy(x => x, ComparebyLoadOrder.Default).ToArray(); + + Assert.True(sorted[0].ModID == "Mod2"); + Assert.True(sorted[1].ModID == "Mod3"); + Assert.True(sorted[2].ModID == "Mod1"); + } + } +} From 5ef2c68cdc05e260bee09eef0e83e8c28d3655b4 Mon Sep 17 00:00:00 2001 From: taubenangriff <51975164+taubenangriff@users.noreply.github.com> Date: Fri, 7 Apr 2023 16:49:25 +0200 Subject: [PATCH 05/10] Refactor Compability issue attribute into a reusable generic attribute --- .../Components/AttributeStaticHelp.xaml.cs | 2 +- .../GenericModContextAttribute.cs | 18 ++++++++++++++ .../ModCompabilityIssueAttribute.cs | 24 ------------------- .../ModCompabilityAttributeFactory.cs | 24 +++++++++++++++++++ .../Validation/ModCompatibilityValidator.cs | 2 +- tests/Imya.UnitTests/AttributeTests.cs | 4 ++-- 6 files changed, 46 insertions(+), 28 deletions(-) create mode 100644 ModManager_Classes/Models/Attributes/ConcreteAttributes/GenericModContextAttribute.cs delete mode 100644 ModManager_Classes/Models/Attributes/ConcreteAttributes/ModCompabilityIssueAttribute.cs create mode 100644 ModManager_Classes/Models/Attributes/ModCompabilityAttributeFactory.cs diff --git a/ModManager/Views/Components/AttributeStaticHelp.xaml.cs b/ModManager/Views/Components/AttributeStaticHelp.xaml.cs index eddee504..01396483 100644 --- a/ModManager/Views/Components/AttributeStaticHelp.xaml.cs +++ b/ModManager/Views/Components/AttributeStaticHelp.xaml.cs @@ -43,7 +43,7 @@ public AttributeStaticHelp() new() { Attribute = ModStatusAttributeFactory.Get(ModStatus.Obsolete), Text = TextManager.GetText("ATTRIBUTE_STATICHELP_OBSOLETEMOD") }, new() { Attribute = TweakedAttributeFactory.Get(), Text = TextManager.GetText("ATTRIBUTE_STATICHELP_TWEAKEDMOD") }, new() { Attribute = MissingModinfoAttributeFactory.Get(), Text = TextManager.GetText("ATTRIBUTE_STATICHELP_NOMODINFO") }, - new() { Attribute = new ModCompabilityIssueAttribute(), Text = TextManager.GetText("ATTRIBUTE_STATICHELP_COMPABILITY")}, + new() { Attribute = new GenericModContextAttribute(), Text = TextManager.GetText("ATTRIBUTE_STATICHELP_COMPABILITY")}, new() { Attribute = new ModDependencyIssueAttribute(), Text = TextManager.GetText("ATTRIBUTE_STATICHELP_DEPENDENCY")}, }; diff --git a/ModManager_Classes/Models/Attributes/ConcreteAttributes/GenericModContextAttribute.cs b/ModManager_Classes/Models/Attributes/ConcreteAttributes/GenericModContextAttribute.cs new file mode 100644 index 00000000..036b9188 --- /dev/null +++ b/ModManager_Classes/Models/Attributes/ConcreteAttributes/GenericModContextAttribute.cs @@ -0,0 +1,18 @@ +using Imya.Utils; + +namespace Imya.Models.Attributes +{ + public class GenericModContextAttribute : IAttribute + { + public AttributeType AttributeType { get; init; } + public IText Description { get; init; } + public IEnumerable Context { get; init; } + + bool IAttribute.MultipleAllowed => true; + + public GenericModContextAttribute() + { + Context = Enumerable.Empty(); + } + } +} diff --git a/ModManager_Classes/Models/Attributes/ConcreteAttributes/ModCompabilityIssueAttribute.cs b/ModManager_Classes/Models/Attributes/ConcreteAttributes/ModCompabilityIssueAttribute.cs deleted file mode 100644 index eecdf527..00000000 --- a/ModManager_Classes/Models/Attributes/ConcreteAttributes/ModCompabilityIssueAttribute.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Imya.Utils; - -namespace Imya.Models.Attributes -{ - public class ModCompabilityIssueAttribute : IAttribute - { - public AttributeType AttributeType { get; } = AttributeType.ModCompabilityIssue; - public IText Description { get => new SimpleText(String.Format(TextManager.Instance.GetText("ATTRIBUTE_COMPABILITYERROR").Text, String.Join(',', CompabilityIssues.Select(x => $"[{x.Category}] {x.Name}")))); } - - bool IAttribute.MultipleAllowed => true; - - public IEnumerable CompabilityIssues { get; } - - public ModCompabilityIssueAttribute(IEnumerable issues) - { - CompabilityIssues = issues; - } - - public ModCompabilityIssueAttribute() - { - CompabilityIssues = Enumerable.Empty(); - } - } -} diff --git a/ModManager_Classes/Models/Attributes/ModCompabilityAttributeFactory.cs b/ModManager_Classes/Models/Attributes/ModCompabilityAttributeFactory.cs new file mode 100644 index 00000000..36b81d60 --- /dev/null +++ b/ModManager_Classes/Models/Attributes/ModCompabilityAttributeFactory.cs @@ -0,0 +1,24 @@ +using Imya.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Models.Attributes +{ + public class ModCompabilityAttributeFactory + { + public static IAttribute Get(IEnumerable context) + { + return new GenericModContextAttribute() + { + AttributeType = AttributeType.ModCompabilityIssue, + Description = new SimpleText( + String.Format(TextManager.Instance.GetText("ATTRIBUTE_COMPABILITYERROR").Text, + String.Join(',', context.Select(x => $"[{x.Category}] {x.Name}")))), + Context = context + }; + } + } +} diff --git a/ModManager_Classes/Validation/ModCompatibilityValidator.cs b/ModManager_Classes/Validation/ModCompatibilityValidator.cs index 29acd442..8345bceb 100644 --- a/ModManager_Classes/Validation/ModCompatibilityValidator.cs +++ b/ModManager_Classes/Validation/ModCompatibilityValidator.cs @@ -29,7 +29,7 @@ private static void ValidateSingle(Mod mod, IReadOnlyCollection collection) var incompatibles = GetIncompatibleMods(mod.Modinfo, collection); if (incompatibles.Any()) - mod.Attributes.AddAttribute(new ModCompabilityIssueAttribute(incompatibles)); + mod.Attributes.AddAttribute(ModCompabilityAttributeFactory.Get(incompatibles)); Mod? newReplacementMod = HasBeenDeprecated(mod.Modinfo, collection) ?? IsNewestOfID(mod, collection); if (newReplacementMod is not null && newReplacementMod != mod) diff --git a/tests/Imya.UnitTests/AttributeTests.cs b/tests/Imya.UnitTests/AttributeTests.cs index a5272324..67e4048b 100644 --- a/tests/Imya.UnitTests/AttributeTests.cs +++ b/tests/Imya.UnitTests/AttributeTests.cs @@ -10,8 +10,8 @@ public void AllowMultiple() { AttributeCollection attributes = new(); - attributes.AddAttribute(new ModCompabilityIssueAttribute()); - attributes.AddAttribute(new ModCompabilityIssueAttribute()); + attributes.AddAttribute(new GenericModContextAttribute()); + attributes.AddAttribute(new GenericModContextAttribute()); Assert.Equal(2, attributes.Count); } From a313e0b92d2ee092fbfb43a987a8c85dd058dc35 Mon Sep 17 00:00:00 2001 From: taubenangriff <51975164+taubenangriff@users.noreply.github.com> Date: Fri, 7 Apr 2023 16:49:42 +0200 Subject: [PATCH 06/10] Add cyclic dependency attribute --- .../ValueConverters/AttributeConverters.cs | 1 + .../CyclicDependencyAttributeFactory.cs | 24 +++++++++++++++++++ .../Models/Attributes/IAttribute.cs | 3 ++- 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 ModManager_Classes/Models/Attributes/CyclicDependencyAttributeFactory.cs diff --git a/ModManager/ValueConverters/AttributeConverters.cs b/ModManager/ValueConverters/AttributeConverters.cs index 4a9505c5..0c55986f 100644 --- a/ModManager/ValueConverters/AttributeConverters.cs +++ b/ModManager/ValueConverters/AttributeConverters.cs @@ -24,6 +24,7 @@ public static (string, SolidColorBrush) AttributeToIcon(AttributeType type, ModS AttributeType.ModContentInSubfolder => ("AlertBox", FindResourceBrush("ErrorColorBrush")), AttributeType.IssueModRemoved => ("TrashCanOutline", FindResourceBrush("ErrorColorBrush")), AttributeType.IssueModAccess => ("FolderAlertOutline", FindResourceBrush("ErrorColorBrush")), + AttributeType.CyclicDependency => ("CircleArrows", FindResourceBrush("ErrorColorBrush")), _ => ("InformationOutline", FindResourceBrush("TextColorBrush")), }; } diff --git a/ModManager_Classes/Models/Attributes/CyclicDependencyAttributeFactory.cs b/ModManager_Classes/Models/Attributes/CyclicDependencyAttributeFactory.cs new file mode 100644 index 00000000..f1cea32c --- /dev/null +++ b/ModManager_Classes/Models/Attributes/CyclicDependencyAttributeFactory.cs @@ -0,0 +1,24 @@ +using Imya.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Models.Attributes +{ + public class CyclicDependencyAttributeFactory + { + public static IAttribute Get(IEnumerable context) + { + return new GenericModContextAttribute() + { + AttributeType = AttributeType.CyclicDependency, + Description = new SimpleText( + String.Format(TextManager.Instance.GetText("ATTRIBUTE_CYCLIC_DEPENDENCY").Text, + String.Join(',', context.Select(x => $"[{x.Category}] {x.Name}")))), + Context = context + }; + } + } +} diff --git a/ModManager_Classes/Models/Attributes/IAttribute.cs b/ModManager_Classes/Models/Attributes/IAttribute.cs index c5530eb7..84f99218 100644 --- a/ModManager_Classes/Models/Attributes/IAttribute.cs +++ b/ModManager_Classes/Models/Attributes/IAttribute.cs @@ -16,7 +16,8 @@ public enum AttributeType ModContentInSubfolder, IssueModRemoved, IssueModAccess, - ModReplacedByIssue + ModReplacedByIssue, + CyclicDependency } public interface IAttribute From a71d6781486b73070d9ce834657ac25cd1e0252e Mon Sep 17 00:00:00 2001 From: taubenangriff <51975164+taubenangriff@users.noreply.github.com> Date: Fri, 7 Apr 2023 16:50:01 +0200 Subject: [PATCH 07/10] validate cyclic dependencies --- .../Validation/CyclicDependencyValidator.cs | 33 +++++++++++++++++++ .../Validation/ModCollectionHooks.cs | 3 +- 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 ModManager_Classes/Validation/CyclicDependencyValidator.cs diff --git a/ModManager_Classes/Validation/CyclicDependencyValidator.cs b/ModManager_Classes/Validation/CyclicDependencyValidator.cs new file mode 100644 index 00000000..a2f2b4f2 --- /dev/null +++ b/ModManager_Classes/Validation/CyclicDependencyValidator.cs @@ -0,0 +1,33 @@ +using Imya.Models; +using Imya.Models.Attributes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Validation +{ + internal class CyclicDependencyValidator : IModValidator + { + public void Validate(IEnumerable changed, IReadOnlyCollection all) + { + foreach (Mod x in changed) + { + var cyclics = CyclicDependencies(x, all); + if (cyclics.Count() > 0) + { + x.Attributes.Add(CyclicDependencyAttributeFactory.Get(cyclics)); + } + } + } + + private IEnumerable CyclicDependencies(Mod x, IReadOnlyCollection others) + { + return others.Where(y => + (y.Modinfo?.LoadAfterIds?.Contains(x.ModID) ?? false) + && (x.Modinfo?.LoadAfterIds?.Contains(y.ModID) ?? false)); + } + } +} diff --git a/ModManager_Classes/Validation/ModCollectionHooks.cs b/ModManager_Classes/Validation/ModCollectionHooks.cs index 49a9fd4e..c0d8e00e 100644 --- a/ModManager_Classes/Validation/ModCollectionHooks.cs +++ b/ModManager_Classes/Validation/ModCollectionHooks.cs @@ -12,7 +12,8 @@ public class ModCollectionHooks private readonly IModValidator[] validators = new IModValidator[] { new ModContentValidator(), - new ModCompatibilityValidator() + new ModCompatibilityValidator(), + new CyclicDependencyValidator() }; public ModCollectionHooks(ModCollection mods) From f840cb2c428db852dcd1d925223d10679404b6b2 Mon Sep 17 00:00:00 2001 From: taubenangriff <51975164+taubenangriff@users.noreply.github.com> Date: Fri, 7 Apr 2023 16:50:07 +0200 Subject: [PATCH 08/10] add texts --- ModManager/resources/texts.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ModManager/resources/texts.json b/ModManager/resources/texts.json index 07580827..4be79bb3 100644 --- a/ModManager/resources/texts.json +++ b/ModManager/resources/texts.json @@ -1378,6 +1378,19 @@ "Spanish": null, "Taiwanese": null }, + "ATTRIBUTE_CYCLIC_DEPENDENCY": { + "Chinese": null, + "English": "Cyclic dependency in loading order with: {0}. This may cause issues ingame!", + "French": null, + "German": "Zyklische Abhängigkeit in der Ladeordnung: {0}. Dies könnte im Spiel Probleme verursachen!", + "Italian": null, + "Japanese": null, + "Korean": null, + "Polish": null, + "Russian": null, + "Spanish": null, + "Taiwanese": null + }, "ATTRIBUTE_REPLACEDBY": { "Chinese": null, "English": "This mod is old.\nIt has been replaced by '{0}'.", From eb12a9933b8b00ec62d337070c6cc8b456fabccf Mon Sep 17 00:00:00 2001 From: taubenangriff <51975164+taubenangriff@users.noreply.github.com> Date: Fri, 7 Apr 2023 16:50:17 +0200 Subject: [PATCH 09/10] improve visibility of attribute icons --- ModManager/Views/Components/ModList.xaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ModManager/Views/Components/ModList.xaml b/ModManager/Views/Components/ModList.xaml index 05fc9987..533f4c96 100644 --- a/ModManager/Views/Components/ModList.xaml +++ b/ModManager/Views/Components/ModList.xaml @@ -132,6 +132,8 @@ From 9824f06e8209d38701a94bc9bc1af1f1137a0ab5 Mon Sep 17 00:00:00 2001 From: taubenangriff <51975164+taubenangriff@users.noreply.github.com> Date: Fri, 7 Apr 2023 17:05:44 +0200 Subject: [PATCH 10/10] Only validate active mods for cyclics --- ModManager_Classes/Validation/CyclicDependencyValidator.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ModManager_Classes/Validation/CyclicDependencyValidator.cs b/ModManager_Classes/Validation/CyclicDependencyValidator.cs index a2f2b4f2..df137b58 100644 --- a/ModManager_Classes/Validation/CyclicDependencyValidator.cs +++ b/ModManager_Classes/Validation/CyclicDependencyValidator.cs @@ -13,6 +13,8 @@ internal class CyclicDependencyValidator : IModValidator { public void Validate(IEnumerable changed, IReadOnlyCollection all) { + foreach (Mod x in all) + x.Attributes.RemoveAttributesByType(AttributeType.CyclicDependency); foreach (Mod x in changed) { var cyclics = CyclicDependencies(x, all); @@ -25,8 +27,11 @@ public void Validate(IEnumerable changed, IReadOnlyCollection all) private IEnumerable CyclicDependencies(Mod x, IReadOnlyCollection others) { + if (!x.IsActive) + return Enumerable.Empty(); + return others.Where(y => - (y.Modinfo?.LoadAfterIds?.Contains(x.ModID) ?? false) + y.IsActive && (y.Modinfo?.LoadAfterIds?.Contains(x.ModID) ?? false) && (x.Modinfo?.LoadAfterIds?.Contains(y.ModID) ?? false)); } }