Skip to content

Commit

Permalink
Merge pull request #210 from anno-mods/devel/load_order_sort
Browse files Browse the repository at this point in the history
Load Order Display
  • Loading branch information
taubenangriff authored Apr 7, 2023
2 parents 57c8875 + 9824f06 commit 1c38df1
Show file tree
Hide file tree
Showing 18 changed files with 328 additions and 40 deletions.
1 change: 1 addition & 0 deletions ModManager/Utils/AppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
1 change: 1 addition & 0 deletions ModManager/ValueConverters/AttributeConverters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")),
};
}
Expand Down
2 changes: 1 addition & 1 deletion ModManager/Views/Components/AttributeStaticHelp.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")},
};

Expand Down
2 changes: 2 additions & 0 deletions ModManager/Views/Components/ModList.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@
<materialDesign:PackIcon Margin="0,0,5,0"
Kind="{Binding Path=. ,Converter={StaticResource AttribIconConverter}}"
Foreground="{Binding Path=. , Converter={StaticResource AttribColorConverter}}"
MinHeight="18"
MinWidth="18"
Padding="5"
ToolTipService.InitialShowDelay="0">
<materialDesign:PackIcon.ToolTip>
Expand Down
26 changes: 26 additions & 0 deletions ModManager/resources/texts.json
Original file line number Diff line number Diff line change
Expand Up @@ -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}'.",
Expand Down Expand Up @@ -1781,6 +1794,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.",
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Mod> Context { get; init; }

bool IAttribute.MultipleAllowed => true;

public GenericModContextAttribute()
{
Context = Enumerable.Empty<Mod>();
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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<Mod> 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
};
}
}
}
3 changes: 2 additions & 1 deletion ModManager_Classes/Models/Attributes/IAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ public enum AttributeType
ModContentInSubfolder,
IssueModRemoved,
IssueModAccess,
ModReplacedByIssue
ModReplacedByIssue,
CyclicDependency
}

public interface IAttribute
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Mod> 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
};
}
}
}
2 changes: 2 additions & 0 deletions ModManager_Classes/Models/Mod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ public bool IsRemoved
/// Category with default "NoCategory".
/// </summary>
public IText Category => Modinfo.Category;

public String ModID => Modinfo.ModID ?? FolderName;
#endregion

#region Optional Mod Manager info
Expand Down
99 changes: 89 additions & 10 deletions ModManager_Classes/Models/ModComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Mod>
{
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);
}
}

Expand Down Expand Up @@ -64,4 +72,75 @@ public int Compare(Mod? x, Mod? y)
return string.Compare(x.FolderName, y.FolderName);
}
}

public class ComparebyLoadOrder : IComparer<Mod>
{
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<Mod>
{
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;
}
}
}
2 changes: 2 additions & 0 deletions ModManager_Classes/Models/ModMetadata/Modinfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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"];
Expand Down
38 changes: 38 additions & 0 deletions ModManager_Classes/Validation/CyclicDependencyValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
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<Mod> changed, IReadOnlyCollection<Mod> all)
{
foreach (Mod x in all)
x.Attributes.RemoveAttributesByType(AttributeType.CyclicDependency);
foreach (Mod x in changed)
{
var cyclics = CyclicDependencies(x, all);
if (cyclics.Count() > 0)
{
x.Attributes.Add(CyclicDependencyAttributeFactory.Get(cyclics));
}
}
}

private IEnumerable<Mod> CyclicDependencies(Mod x, IReadOnlyCollection<Mod> others)
{
if (!x.IsActive)
return Enumerable.Empty<Mod>();

return others.Where(y =>
y.IsActive && (y.Modinfo?.LoadAfterIds?.Contains(x.ModID) ?? false)
&& (x.Modinfo?.LoadAfterIds?.Contains(y.ModID) ?? false));
}
}
}
3 changes: 2 additions & 1 deletion ModManager_Classes/Validation/ModCollectionHooks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion ModManager_Classes/Validation/ModCompatibilityValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ private static void ValidateSingle(Mod mod, IReadOnlyCollection<Mod> 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)
Expand Down
4 changes: 2 additions & 2 deletions tests/Imya.UnitTests/AttributeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Loading

0 comments on commit 1c38df1

Please sign in to comment.