Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions xcom2-launcher/xcom2-launcher/Classes/Mod/ModEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ public class ModEntry
/// </summary>
public List<long> IgnoredDependencies { get; set; } = new List<long>();

/// <summary>
/// Workshop ids that this installed mod claims to also satisfy. Bridges dependency
/// references when the user has installed a Workshop mod under a custom folder
/// (no publishedfileid) or has installed a beta whose Workshop id differs from the
/// stable id another mod requires.
/// </summary>
public List<long> WorkshopIdAliases { get; set; } = new List<long>();

/// <summary>
/// Contains the tags that were downloaded from steam.
/// </summary>
Expand Down
76 changes: 70 additions & 6 deletions xcom2-launcher/xcom2-launcher/Classes/Mod/ModList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,62 @@ public void UpdatedModDependencyState(ModEntry mod)
{
var requiredMods = GetRequiredMods(mod, true, true);
var allRequiredModsAvailable = requiredMods.All(m => m.WorkshopID != 0 && m.isActive && !m.State.HasFlag(ModState.NotInstalled) && !m.State.HasFlag(ModState.NotLoaded));

if (allRequiredModsAvailable)
mod.RemoveState(ModState.MissingDependencies);
else
else
mod.AddState(ModState.MissingDependencies);
}

/// <summary>
/// Adds <paramref name="workshopId"/> to <see cref="ModEntry.IgnoredDependencies"/> on
/// every installed mod whose <see cref="ModEntry.Dependencies"/> contains it, then
/// re-evaluates the missing-dependency state for each affected mod.
/// </summary>
/// <returns>The mods whose IgnoredDependencies was modified (empty if no dependents,
/// or every dependent already ignored it).</returns>
public List<ModEntry> IgnoreDependencyEverywhere(long workshopId)
{
var affected = new List<ModEntry>();
if (workshopId <= 0) return affected;

foreach (var mod in All)
{
if (!mod.Dependencies.Contains(workshopId)) continue;
if (mod.IgnoredDependencies.Contains(workshopId)) continue;

mod.IgnoredDependencies.Add(workshopId);
UpdatedModDependencyState(mod);
affected.Add(mod);
}

Log.Info($"Bulk-ignored workshop id {workshopId} on {affected.Count} mods.");
return affected;
}

/// <summary>
/// Removes <paramref name="workshopId"/> from <see cref="ModEntry.IgnoredDependencies"/>
/// on every installed mod that currently ignores it, then re-evaluates state.
/// </summary>
/// <returns>The mods whose IgnoredDependencies was modified.</returns>
public List<ModEntry> UnignoreDependencyEverywhere(long workshopId)
{
var affected = new List<ModEntry>();
if (workshopId <= 0) return affected;

foreach (var mod in All)
{
if (mod.IgnoredDependencies.Remove(workshopId))
{
UpdatedModDependencyState(mod);
affected.Add(mod);
}
}

Log.Info($"Bulk-unignored workshop id {workshopId} on {affected.Count} mods.");
return affected;
}

public List<ModEntry> ImportMods(List<string> modPaths)
{
Log.Info("Checking mod directories for new mods");
Expand Down Expand Up @@ -601,10 +650,13 @@ private async Task<List<ModEntry>> LoadNotInstalledDependencies(List<long> requi

foreach (var requiredModId in requiredModIds)
{
var result = All.FirstOrDefault(m => m.WorkshopID == requiredModId);
// Exact-WorkshopID match wins over an alias match if both exist.
var result = All
.OrderBy(m => m.WorkshopID == requiredModId ? 0 : 1)
.FirstOrDefault(m => m.WorkshopID == requiredModId || m.WorkshopIdAliases.Contains(requiredModId));
if (result != null)
{
// dependency is already installed
// dependency is already installed (or aliased to a local mod)
continue;
}

Expand Down Expand Up @@ -676,7 +728,11 @@ public List<ModEntry> GetDependentMods(ModEntry mod, bool compareModId = true)
return result;
}

return All.Where(m => m.Dependencies.Contains(mod.WorkshopID)).ToList();
// A mod is a dependent if it lists this mod's WorkshopID OR any of its declared aliases.
return All.Where(m =>
m.Dependencies.Contains(mod.WorkshopID) ||
(mod.WorkshopIdAliases.Count > 0 && mod.WorkshopIdAliases.Any(a => m.Dependencies.Contains(a)))
).ToList();
}

/// <summary>
Expand All @@ -703,10 +759,18 @@ public List<ModEntry> GetRequiredMods(ModEntry mod, bool substituteDuplicates =
foreach (var id in dependencies)
{
// Check if required mod is already installed and use it if available.
var result = installedMods.FirstOrDefault(m => m.WorkshopID == id);
// Exact WorkshopID match wins over an alias match (a real Workshop install
// beats a user-declared alias for the same id every time).
var result = installedMods
.OrderBy(m => m.WorkshopID == id ? 0 : 1)
.FirstOrDefault(m => m.WorkshopID == id || m.WorkshopIdAliases.Contains(id));

if (result != null)
{
if (result.WorkshopID != id)
{
Log.Info($"Resolved workshop dep {id} via alias on '{result.Name}' (WorkshopID {result.WorkshopID}).");
}
// If the required mod is installed but disabled a duplicate, use the primary duplicate
if (substituteDuplicates && result.State.HasFlag(ModState.DuplicateDisabled))
{
Expand Down
10 changes: 9 additions & 1 deletion xcom2-launcher/xcom2-launcher/Classes/Mod/ModListFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ class ModListFilter : TextMatchFilter
{
private readonly List<ModState> _States;
private readonly bool _ShowHiddenMods;
private readonly bool _ShowIgnoredDepsOnly;

public ModListFilter(ObjectListView olv, string text, List<ModState> states, bool showHiddenMods) : base(olv, text)
public ModListFilter(ObjectListView olv, string text, List<ModState> states, bool showHiddenMods, bool showIgnoredDepsOnly = false) : base(olv, text)
{
_States = states;
_ShowHiddenMods = showHiddenMods;
_ShowIgnoredDepsOnly = showIgnoredDepsOnly;
}

public override bool Filter(object modelObject)
Expand Down Expand Up @@ -53,6 +55,12 @@ public override bool Filter(object modelObject)
filterMatch |= mod.isHidden;
}

if (_ShowIgnoredDepsOnly)
{
isAdditionalFilterActive = true;
filterMatch |= mod.IgnoredDependencies != null && mod.IgnoredDependencies.Count > 0;
}

if (!isAdditionalFilterActive)
{
return textMatch;
Expand Down
47 changes: 35 additions & 12 deletions xcom2-launcher/xcom2-launcher/Forms/MainForm.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions xcom2-launcher/xcom2-launcher/Forms/MainForm.Events.cs
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,7 @@ private void bClearStateFilter_Click(object sender, EventArgs e)
cFilterDuplicate.Checked = false;
cFilterHidden.Checked = false;
cFilterMissingDependency.Checked = false;
cFilterIgnoredDependencies.Checked = false;
cFilterNew.Checked = false;
cFilterNotInstalled.Checked = false;
cFilterNotLoaded.Checked = false;
Expand Down
8 changes: 7 additions & 1 deletion xcom2-launcher/xcom2-launcher/Forms/MainForm.ModList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,12 @@ private void RefreshModelFilter()
stateFlags.Add(ModState.MissingDependencies);
}

modlist_ListObjectListView.ModelFilter = new ModListFilter(modlist_ListObjectListView, modlist_FilterCueTextBox.Text, stateFlags, cFilterHidden.Checked);
modlist_ListObjectListView.ModelFilter = new ModListFilter(
modlist_ListObjectListView,
modlist_FilterCueTextBox.Text,
stateFlags,
cFilterHidden.Checked,
cFilterIgnoredDependencies.Checked);
}

/// <summary>
Expand Down Expand Up @@ -1342,6 +1347,7 @@ void ProcessModListItemCheckChanged(ModEntry modChecked)
}

UpdateStateFilterLabels();
RefreshModelFilter();
UpdateLabels();
UpdateDependencyInformation(ModList.SelectedObject);
}
Expand Down
Loading