diff --git a/Focus.Apps.EasyNpc/Profiles/LineupBuilder.cs b/Focus.Apps.EasyNpc/Profiles/LineupBuilder.cs index e564031..6ac54af 100644 --- a/Focus.Apps.EasyNpc/Profiles/LineupBuilder.cs +++ b/Focus.Apps.EasyNpc/Profiles/LineupBuilder.cs @@ -18,11 +18,6 @@ public interface ILineupBuilder public class LineupBuilder : ILineupBuilder, IDisposable { - // Placeholders for tracking base game content. - private static readonly ModComponentInfo BaseGameComponent = new( - new ModLocatorKey(string.Empty, "Vanilla"), string.Empty, "Vanilla", string.Empty); - private static readonly ModInfo BaseGameMod = new(string.Empty, "Vanilla"); - private readonly Subject disposed = new(); private readonly IFileSystem fs; private readonly MugshotFile genericFemaleFile; @@ -68,7 +63,11 @@ public async IAsyncEnumerable Build(INpcBasicInfo npc, IEnumerable(StringComparer.CurrentCultureIgnoreCase); var pluginGroups = affectingPlugins .SelectMany(p => loadOrderGraph.IsImplicit(p) ? - new[] { new { Plugin = p, ModComponent = BaseGameComponent, ModKey = BaseGameMod as IModLocatorKey } } : + new[] { new { + Plugin = p, + ModComponent = Placeholders.BaseGameComponent, + ModKey = Placeholders.BaseGameMod as IModLocatorKey + } } : modRepository.SearchForFiles(p, false).Select(x => new { Plugin = p, x.ModComponent, x.ModKey })) .GroupBy(x => x.ModKey, ModLocatorKeyComparer.Default) .Select(g => new @@ -82,7 +81,7 @@ public async IAsyncEnumerable Build(INpcBasicInfo npc, IEnumerable InstalledPlugins => mugshot.InstalledPlugins; + public bool IsBaseGame => mugshot.InstalledMod == Placeholders.BaseGameMod; public bool IsDisabledByErrors { get; private init; } public bool IsFocused { get; set; } public bool IsHighlighted { get; set; } diff --git a/Focus.Apps.EasyNpc/Profiles/Npc.cs b/Focus.Apps.EasyNpc/Profiles/Npc.cs index 359d76d..1ed7e5a 100644 --- a/Focus.Apps.EasyNpc/Profiles/Npc.cs +++ b/Focus.Apps.EasyNpc/Profiles/Npc.cs @@ -129,6 +129,14 @@ public bool IsFacePlugin(string pluginName) return FaceOption.PluginName.Equals(pluginName, StringComparison.CurrentCultureIgnoreCase); } + public ChangeResult RevertToBaseGame() + { + var option = Options + .Where(x => x.IsBaseGame) + .LastOrDefault(); + return option is not null ? SetFaceOption(option.PluginName) : ChangeResult.Invalid; + } + public ChangeResult SetDefaultOption(string pluginName, bool asFallback = false) { var option = FindOption(pluginName); @@ -152,12 +160,14 @@ public ChangeResult SetFaceMod(string modName) { var mod = ModLocatorKey.TryParse(modName, out var key) ? modRepository.FindByKey(key) : modRepository.GetByName(modName); - if (mod is null || !modRepository.ContainsFile(mod, FileStructure.GetFaceMeshFileName(this), true)) + if (mod is null) + return ChangeResult.Invalid; + var bestOption = Options.LastOrDefault(x => modRepository.ContainsFile(mod, x.PluginName, false)); + if (bestOption is null && !modRepository.ContainsFile(mod, FileStructure.GetFaceMeshFileName(this), true)) return ChangeResult.Invalid; if ((FaceGenOverride is not null && FaceGenOverride.IncludesName(modName)) || modRepository.ContainsFile(mod, FaceOption.PluginName, false)) return ChangeResult.Redundant; - var bestOption = Options.LastOrDefault(x => modRepository.ContainsFile(mod, x.PluginName, false)); return bestOption is not null ? SetFaceOption(bestOption.PluginName) : SetFaceGenOverride(mod); } diff --git a/Focus.Apps.EasyNpc/Profiles/NpcViewModel.cs b/Focus.Apps.EasyNpc/Profiles/NpcViewModel.cs index 1e39f31..1d02afc 100644 --- a/Focus.Apps.EasyNpc/Profiles/NpcViewModel.cs +++ b/Focus.Apps.EasyNpc/Profiles/NpcViewModel.cs @@ -63,25 +63,14 @@ public bool TrySetDefaultPlugin(string pluginName, [MaybeNullWhen(false)] out Np return success; } + public bool TrySetFaceMod(MugshotViewModel mugshot, [MaybeNullWhen(false)] out NpcOptionViewModel option) + { + return TrySetFaceMod(mugshot.ModName, mugshot.IsBaseGame, out option); + } + public bool TrySetFaceMod(string modName, [MaybeNullWhen(false)] out NpcOptionViewModel option) { - var success = npc.SetFaceMod(modName) == Npc.ChangeResult.OK; - if (success) - { - // If the selected mod ends up being an override (no option/plugin), the "option" parameter will be the - // previously-selected option, and the FaceOption won't change. This is the expected behavior. There is - // no guarantee that the output option always corresponds to the mod name. - option = GetOption(npc.FaceOption.PluginName); - if (FaceOption != option) - FaceOption = option; - else - // Change handler won't run if they're the same, need to explicitly update mugshot states. - UpdateMugshotAssignments(); - FaceModNames = npc.GetFaceModNames().ToHashSet(StringComparer.CurrentCultureIgnoreCase); - } - else - option = null; - return success; + return TrySetFaceMod(modName, false, out option); } public bool TrySetFacePlugin(string pluginName, [MaybeNullWhen(false)] out NpcOptionViewModel option) @@ -180,6 +169,29 @@ private void Option_PropertyChanged(object? sender, PropertyChangedEventArgs e) } } + private bool TrySetFaceMod( + string modName, bool isBaseGame, [MaybeNullWhen(false)] out NpcOptionViewModel option) + { + var result = isBaseGame ? npc.RevertToBaseGame() : npc.SetFaceMod(modName); + var success = result == Npc.ChangeResult.OK; + if (success) + { + // If the selected mod ends up being an override (no option/plugin), the "option" parameter will be the + // previously-selected option, and the FaceOption won't change. This is the expected behavior. There is + // no guarantee that the output option always corresponds to the mod name. + option = GetOption(npc.FaceOption.PluginName); + if (FaceOption != option) + FaceOption = option; + else + // Change handler won't run if they're the same, need to explicitly update mugshot states. + UpdateMugshotAssignments(); + FaceModNames = npc.GetFaceModNames().ToHashSet(StringComparer.CurrentCultureIgnoreCase); + } + else + option = null; + return success; + } + private void UpdateHighlights(string? pluginName) { var pluginNames = pluginName is not null ? new[] { pluginName } : new string[0]; diff --git a/Focus.Apps.EasyNpc/Profiles/Placeholders.cs b/Focus.Apps.EasyNpc/Profiles/Placeholders.cs new file mode 100644 index 0000000..b7d5df7 --- /dev/null +++ b/Focus.Apps.EasyNpc/Profiles/Placeholders.cs @@ -0,0 +1,17 @@ +using Focus.ModManagers; + +namespace Focus.Apps.EasyNpc.Profiles +{ + public static class Placeholders + { + private static readonly string BaseGameLabel = "Vanilla"; + + public static readonly ModComponentInfo BaseGameComponent = new( + new ModLocatorKey(string.Empty, BaseGameLabel), string.Empty, BaseGameLabel, string.Empty); + + public static readonly ModInfo BaseGameMod = new(string.Empty, BaseGameLabel) + { + Components = new[] { BaseGameComponent }, + }; + } +} diff --git a/Focus.Apps.EasyNpc/Profiles/ProfilePage.xaml.cs b/Focus.Apps.EasyNpc/Profiles/ProfilePage.xaml.cs index 2ba927b..b20a534 100644 --- a/Focus.Apps.EasyNpc/Profiles/ProfilePage.xaml.cs +++ b/Focus.Apps.EasyNpc/Profiles/ProfilePage.xaml.cs @@ -25,7 +25,7 @@ private void MugshotListViewItem_MouseDoubleClick(object sender, MouseButtonEven if (e.ChangedButton != MouseButton.Left) return; if ((sender as FrameworkElement)?.DataContext is MugshotViewModel mugshot) - Model.SelectedNpc?.TrySetFaceMod(mugshot.ModName, out _); + Model.SelectedNpc?.TrySetFaceMod(mugshot, out _); } private void SaveProfile_Click(object sender, RoutedEventArgs e)