Skip to content

Commit

Permalink
Allow vanilla mugshots to be double-clicked.
Browse files Browse the repository at this point in the history
Uses a separate method to revert to vanilla, which is called when the mugshot is marked as being base-game related.

Also fixes the bug where double-clicking on a mugshot without a facegen wouldn't work. This was caused by the FaceGen Redirect fixes a while back. Allowing these faces to be chosen as long as there is a corresponding plugin (even if there is no facegen) should hopefully fix the bug without breaking the FG override logic.

Fixes #122
  • Loading branch information
focustense committed Oct 3, 2021
1 parent e92144f commit cba1e8d
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 29 deletions.
17 changes: 8 additions & 9 deletions Focus.Apps.EasyNpc/Profiles/LineupBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool> disposed = new();
private readonly IFileSystem fs;
private readonly MugshotFile genericFemaleFile;
Expand Down Expand Up @@ -68,7 +63,11 @@ public async IAsyncEnumerable<Mugshot> Build(INpcBasicInfo npc, IEnumerable<stri
var modNamesFromPlugins = new HashSet<string>(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
Expand All @@ -82,7 +81,7 @@ public async IAsyncEnumerable<Mugshot> Build(INpcBasicInfo npc, IEnumerable<stri
var mugshotFile = GetMugshotFile(mugshotFiles, pluginGroup.ModKey, npc.IsFemale, modSynonyms);
if (!string.IsNullOrEmpty(mugshotFile.TargetModName))
includedMugshotModNames.Add(mugshotFile.TargetModName);
if (!pluginGroup.ModKey.IsEmpty() && pluginGroup.ModKey != BaseGameMod)
if (!pluginGroup.ModKey.IsEmpty() && pluginGroup.ModKey != Placeholders.BaseGameMod)
modNamesFromPlugins.Add(pluginGroup.ModKey.Name);
yield return CreateMugshotModel(
mugshotFile, pluginGroup.ModKey, pluginGroup.Components, pluginGroup.Plugins);
Expand Down Expand Up @@ -184,8 +183,8 @@ private MugshotFile GetPlaceholderFile(string assetsDirectory, bool female)
{
if (modKey is null)
return null;
if (modKey == BaseGameMod)
return BaseGameMod;
if (modKey == Placeholders.BaseGameMod)
return Placeholders.BaseGameMod;
return modRepository.FindByKey(modKey);
}
}
Expand Down
1 change: 1 addition & 0 deletions Focus.Apps.EasyNpc/Profiles/MugshotViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace Focus.Apps.EasyNpc.Profiles
public class MugshotViewModel
{
public IReadOnlyList<string> 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; }
Expand Down
14 changes: 12 additions & 2 deletions Focus.Apps.EasyNpc/Profiles/Npc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}

Expand Down
46 changes: 29 additions & 17 deletions Focus.Apps.EasyNpc/Profiles/NpcViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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];
Expand Down
17 changes: 17 additions & 0 deletions Focus.Apps.EasyNpc/Profiles/Placeholders.cs
Original file line number Diff line number Diff line change
@@ -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 },
};
}
}
2 changes: 1 addition & 1 deletion Focus.Apps.EasyNpc/Profiles/ProfilePage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit cba1e8d

Please sign in to comment.