Skip to content

Commit

Permalink
Add filtered (invalid only) NPC reset.
Browse files Browse the repository at this point in the history
Does exactly what's described in the issue and limits the reset to NPCs that are pointing to unreachable plugins, if the corresponding option is checked.

Also adjusts the build checker to only pay attention to NPCs that are actually in the current profile, otherwise it's inconsistent with the reset and with the build itself. Sometimes NPCs can be filtered out of the profile, even if they have invalid selections, due to no longer having any overrides. These are ignored in the output, so there is no reason to report errors on them.

Fixes #18.
  • Loading branch information
focustense committed Jun 29, 2021
1 parent ba49f9d commit 0e6b108
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 11 deletions.
12 changes: 6 additions & 6 deletions Focus.Apps.EasyNpc/Build/BuildChecker.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Focus.Apps.EasyNpc.Configuration;
using Focus.Apps.EasyNpc.GameData.Files;
using Focus.Apps.EasyNpc.GameData.Records;
using Focus.Apps.EasyNpc.Profile;
using System;
using System.Collections.Generic;
Expand All @@ -12,18 +11,19 @@ namespace Focus.Apps.EasyNpc.Build
public class BuildChecker<TKey>
where TKey : struct
{
private readonly IDictionary<Tuple<string, string>, INpc<TKey>> allNpcs;
private readonly ArchiveFileMap archiveFileMap;
private readonly IArchiveProvider archiveProvider;
private readonly IReadOnlyList<string> loadOrder;
private readonly IModPluginMapFactory modPluginMapFactory;
private readonly IDictionary<Tuple<string, string>, NpcConfiguration<TKey>> npcConfigs;
private readonly IReadOnlyProfileEventLog profileEventLog;

public BuildChecker(
IReadOnlyList<string> loadOrder, IEnumerable<INpc<TKey>> allNpcs, IModPluginMapFactory modPluginMapFactory,
IArchiveProvider archiveProvider, IReadOnlyProfileEventLog profileEventLog)
IReadOnlyList<string> loadOrder, IEnumerable<NpcConfiguration<TKey>> npcConfigs,
IModPluginMapFactory modPluginMapFactory, IArchiveProvider archiveProvider,
IReadOnlyProfileEventLog profileEventLog)
{
this.allNpcs = allNpcs.ToDictionary(x => Tuple.Create(x.BasePluginName, x.LocalFormIdHex));
this.npcConfigs = npcConfigs.ToDictionary(x => Tuple.Create(x.BasePluginName, x.LocalFormIdHex));
this.loadOrder = loadOrder;
this.modPluginMapFactory = modPluginMapFactory;
this.archiveProvider = archiveProvider;
Expand Down Expand Up @@ -77,7 +77,7 @@ private IEnumerable<BuildWarning> CheckMissingPlugins(IEnumerable<ProfileEvent>
return events
.MostRecentByNpc()
.WithMissingPlugins(loadOrder.ToHashSet(StringComparer.OrdinalIgnoreCase))
.Select(x => allNpcs.TryGetValue(Tuple.Create(x.BasePluginName, x.LocalFormIdHex), out var npc) ?
.Select(x => npcConfigs.TryGetValue(Tuple.Create(x.BasePluginName, x.LocalFormIdHex), out var npc) ?
new
{
npc.EditorId,
Expand Down
5 changes: 3 additions & 2 deletions Focus.Apps.EasyNpc/Main/MainViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,12 @@ public MainViewModel(bool isFirstLaunch, bool debugMode)
Profile = new ProfileViewModel<TKey>(
Loader.Npcs, Loader.ModPluginMapFactory, Loader.LoadedPluginNames, Loader.LoadedMasterNames,
profileEventLog);
Maintenance = new MaintenanceViewModel<TKey>(Profile.GetAllNpcConfigurations(), profileEventLog);
var npcConfigs = Profile.GetAllNpcConfigurations();
Maintenance = new MaintenanceViewModel<TKey>(npcConfigs, profileEventLog, Loader.LoadedPluginNames);
var wigResolver = new SimpleWigResolver<TKey>(Loader.Hairs);
var faceGenEditor = new NiflyFaceGenEditor(Logger);
var buildChecker = new BuildChecker<TKey>(
Loader.LoadedPluginNames, Loader.Npcs, Loader.ModPluginMapFactory, gameDataEditor.ArchiveProvider,
Loader.LoadedPluginNames, npcConfigs, Loader.ModPluginMapFactory, gameDataEditor.ArchiveProvider,
profileEventLog);
Build = new BuildViewModel<TKey>(
gameDataEditor.ArchiveProvider, buildChecker, gameDataEditor.MergedPluginBuilder,
Expand Down
6 changes: 6 additions & 0 deletions Focus.Apps.EasyNpc/Maintenance/MaintenancePage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@
<Button x:Name="ResetFacesButton" Margin="8,0,0,0" IsEnabled="{Binding CanResetNpcs}" Click="ResetFacesButton_Click">Reset Face Selections</Button>
<fa:SvgAwesome Icon="Solid_Cog" Height="24" Margin="8,0,0,0" Spin="True" Visibility="{Binding IsResettingNpcs, Converter={StaticResource BoolToVisibility}}" d:Visibility="Collapsed"/>
</ui:SimpleStackPanel>
<CheckBox Margin="0,8,0,0" IsChecked="{Binding OnlyResetInvalid}" IsEnabled="{Binding CanResetNpcs}" Content="Only reset references to missing plugins">
<CheckBox.ToolTip>
If checked, clicking either of the buttons above will not touch any NPC unless their current settings reference a plugin that is invalid, i.e. no longer present in the load order.
This is useful if you've removed a large mod or overhaul and need to fix several records that were pointing to it.
</CheckBox.ToolTip>
</CheckBox>
</ui:SimpleStackPanel>
</GroupBox>
</Border>
Expand Down
28 changes: 25 additions & 3 deletions Focus.Apps.EasyNpc/Maintenance/MaintenanceViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,21 @@ public class MaintenanceViewModel<TKey> : INotifyPropertyChanged
public bool IsTrimmingAutoSave { get; private set; }
public int LogFileCount { get; private set; }
public decimal LogFileSizeMb { get; private set; }
public bool OnlyResetInvalid { get; set; }

private readonly IReadOnlySet<string> loadedPlugins;
private readonly IReadOnlyList<NpcConfiguration<TKey>> npcConfigs;
private readonly IReadOnlySet<Tuple<string, string>> npcKeys;
private readonly ProfileEventLog profileEventLog;

public MaintenanceViewModel(IEnumerable<NpcConfiguration<TKey>> npcConfigs, ProfileEventLog profileEventLog)
public MaintenanceViewModel(
IEnumerable<NpcConfiguration<TKey>> npcConfigs, ProfileEventLog profileEventLog,
IEnumerable<string> loadedPlugins)
{
this.npcConfigs = npcConfigs.ToList().AsReadOnly();
npcKeys = npcConfigs.Select(x => Tuple.Create(x.BasePluginName, x.LocalFormIdHex)).ToHashSet();
this.profileEventLog = profileEventLog;
this.loadedPlugins = loadedPlugins.ToHashSet(StringComparer.OrdinalIgnoreCase);
}

public void DeleteOldLogFiles()
Expand Down Expand Up @@ -69,6 +74,7 @@ public void ResetNpcDefaults()
IsResettingNpcs = true;
try
{
var resetPredicate = GetResetPredicate(NpcProfileField.DefaultPlugin);
// This is only going to work on configurations that are actually loaded, i.e. for NPCs that are present
// in the current load order AND have at least one override. It seems somehow unintuitive that this
// won't clean up all the garbage from previous runs, but on the other hand, that's how the autosave
Expand All @@ -78,7 +84,8 @@ public void ResetNpcDefaults()
// Resetting is distinct from trimming; if someone has made major changes to their load order and wants
// to ensure that their profile/autosave is absolutely squeaky clean, they should trim, THEN reset.
foreach (var npcConfig in npcConfigs)
npcConfig.Reset(defaults: true, faces: false);
if (resetPredicate(npcConfig))
npcConfig.Reset(defaults: true, faces: false);
}
finally
{
Expand All @@ -91,9 +98,11 @@ public void ResetNpcFaces()
IsResettingNpcs = true;
try
{
var resetPredicate = GetResetPredicate(NpcProfileField.FacePlugin);
// Refer to caveats in ResetNpcDefaults.
foreach (var npcConfig in npcConfigs)
npcConfig.Reset(defaults: false, faces: true);
if (resetPredicate(npcConfig))
npcConfig.Reset(defaults: false, faces: true);
}
finally
{
Expand All @@ -118,6 +127,19 @@ public void TrimAutoSave()
}
}

private Predicate<NpcConfiguration<TKey>> GetResetPredicate(NpcProfileField field)
{
if (!OnlyResetInvalid)
return npc => true;
var filteredNpcs = profileEventLog
.MostRecentByNpc()
.Where(e => e.Field == field)
.WithMissingPlugins(loadedPlugins)
.Select(e => Tuple.Create(e.BasePluginName, e.LocalFormIdHex))
.ToHashSet();
return npc => filteredNpcs.Contains(Tuple.Create(npc.BasePluginName, npc.LocalFormIdHex));
}

private void RefreshLogStats()
{
var logFileNames = Directory.GetFiles(ProgramData.DirectoryPath, "Log_*.txt")
Expand Down

0 comments on commit 0e6b108

Please sign in to comment.