Add option to apply mod associations with /glamour apply.

This commit is contained in:
Ottermandias 2024-01-08 23:25:51 +01:00
parent 1a0a0f681f
commit d62d7e352f
4 changed files with 82 additions and 20 deletions

View file

@ -66,7 +66,7 @@ public sealed class Design : DesignBase, ISavable
["WriteProtected"] = WriteProtected(), ["WriteProtected"] = WriteProtected(),
["Equipment"] = SerializeEquipment(), ["Equipment"] = SerializeEquipment(),
["Customize"] = SerializeCustomize(), ["Customize"] = SerializeCustomize(),
["Parameters"] = SerializeParameters(), ["Parameters"] = SerializeParameters(),
["Mods"] = SerializeMods(), ["Mods"] = SerializeMods(),
}; };
return ret; return ret;

View file

@ -279,10 +279,10 @@ public class DesignBase
{ {
var ret = new JObject var ret = new JObject
{ {
["FileVersion"] = FileVersion, ["FileVersion"] = FileVersion,
["Equipment"] = SerializeEquipment(), ["Equipment"] = SerializeEquipment(),
["Customize"] = SerializeCustomize(), ["Customize"] = SerializeCustomize(),
["Parameters"] = SerializeParameters(), ["Parameters"] = SerializeParameters(),
}; };
return ret; return ret;
} }

View file

@ -148,7 +148,8 @@ public unsafe class PenumbraService : IDisposable
public void OpenModPage(Mod mod) public void OpenModPage(Mod mod)
{ {
if (_openModPage.Invoke(TabType.Mods, mod.DirectoryName, mod.Name) == PenumbraApiEc.ModMissing) if (_openModPage.Invoke(TabType.Mods, mod.DirectoryName, mod.Name) == PenumbraApiEc.ModMissing)
Glamourer.Messager.NotificationMessage($"Could not open the mod {mod.Name}, no fitting mod was found in your Penumbra install.", NotificationType.Info, false); Glamourer.Messager.NotificationMessage($"Could not open the mod {mod.Name}, no fitting mod was found in your Penumbra install.",
NotificationType.Info, false);
} }
public string CurrentCollection public string CurrentCollection
@ -158,7 +159,7 @@ public unsafe class PenumbraService : IDisposable
/// Try to set all mod settings as desired. Only sets when the mod should be enabled. /// Try to set all mod settings as desired. Only sets when the mod should be enabled.
/// If it is disabled, ignore all other settings. /// If it is disabled, ignore all other settings.
/// </summary> /// </summary>
public string SetMod(Mod mod, ModSettings settings) public string SetMod(Mod mod, ModSettings settings, string? collection = null)
{ {
if (!Available) if (!Available)
return "Penumbra is not available."; return "Penumbra is not available.";
@ -166,12 +167,13 @@ public unsafe class PenumbraService : IDisposable
var sb = new StringBuilder(); var sb = new StringBuilder();
try try
{ {
var collection = _currentCollection.Invoke(ApiCollectionType.Current); collection ??= _currentCollection.Invoke(ApiCollectionType.Current);
var ec = _setMod.Invoke(collection, mod.DirectoryName, mod.Name, settings.Enabled); var ec = _setMod.Invoke(collection, mod.DirectoryName, mod.Name, settings.Enabled);
if (ec is PenumbraApiEc.ModMissing) switch (ec)
return $"The mod {mod.Name} [{mod.DirectoryName}] could not be found."; {
case PenumbraApiEc.ModMissing: return $"The mod {mod.Name} [{mod.DirectoryName}] could not be found.";
Debug.Assert(ec is not PenumbraApiEc.CollectionMissing, "Missing collection should not be possible."); case PenumbraApiEc.CollectionMissing: return $"The collection {collection} could not be found.";
}
if (!settings.Enabled) if (!settings.Enabled)
return string.Empty; return string.Empty;
@ -216,13 +218,23 @@ public unsafe class PenumbraService : IDisposable
return valid ? name : string.Empty; return valid ? name : string.Empty;
} }
/// <summary> Obtain the name of the collection currently assigned to the given actor. </summary>
public string GetActorCollection(Actor actor)
{
if (!Available)
return string.Empty;
var (valid, _, name) = _objectCollection.Invoke(actor.Index.Index);
return valid ? name : string.Empty;
}
/// <summary> Obtain the game object corresponding to a draw object. </summary> /// <summary> Obtain the game object corresponding to a draw object. </summary>
public Actor GameObjectFromDrawObject(Model drawObject) public Actor GameObjectFromDrawObject(Model drawObject)
=> Available ? _drawObjectInfo.Invoke(drawObject.Address).Item1 : Actor.Null; => Available ? _drawObjectInfo.Invoke(drawObject.Address).Item1 : Actor.Null;
/// <summary> Obtain the parent of a cutscene actor if it is known. </summary> /// <summary> Obtain the parent of a cutscene actor if it is known. </summary>
public short CutsceneParent(ushort idx) public short CutsceneParent(ushort idx)
=> (short) (Available ? _cutsceneParent.Invoke(idx) : -1); => (short)(Available ? _cutsceneParent.Invoke(idx) : -1);
/// <summary> Try to redraw the given actor. </summary> /// <summary> Try to redraw the given actor. </summary>
public void RedrawObject(Actor actor, RedrawType settings) public void RedrawObject(Actor actor, RedrawType settings)

View file

@ -10,6 +10,8 @@ using Glamourer.Events;
using Glamourer.GameData; using Glamourer.GameData;
using Glamourer.Gui; using Glamourer.Gui;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.Interop.Penumbra;
using Glamourer.Interop.Structs;
using Glamourer.State; using Glamourer.State;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
@ -36,10 +38,11 @@ public class CommandService : IDisposable
private readonly DesignConverter _converter; private readonly DesignConverter _converter;
private readonly DesignFileSystem _designFileSystem; private readonly DesignFileSystem _designFileSystem;
private readonly Configuration _config; private readonly Configuration _config;
private readonly PenumbraService _penumbra;
public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorManager actors, ObjectManager objects, public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorManager actors, ObjectManager objects,
AutoDesignApplier autoDesignApplier, StateManager stateManager, DesignManager designManager, DesignConverter converter, AutoDesignApplier autoDesignApplier, StateManager stateManager, DesignManager designManager, DesignConverter converter,
DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config) DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config, PenumbraService penumbra)
{ {
_commands = commands; _commands = commands;
_mainWindow = mainWindow; _mainWindow = mainWindow;
@ -53,6 +56,7 @@ public class CommandService : IDisposable
_designFileSystem = designFileSystem; _designFileSystem = designFileSystem;
_autoDesignManager = autoDesignManager; _autoDesignManager = autoDesignManager;
_config = config; _config = config;
_penumbra = penumbra;
_commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." }); _commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." });
_commands.AddHandler(ApplyCommandString, _commands.AddHandler(ApplyCommandString,
@ -368,11 +372,14 @@ public class CommandService : IDisposable
private bool Apply(string arguments) private bool Apply(string arguments)
{ {
var split = arguments.Split('|', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); var split = arguments.Split('|', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (split.Length != 2) if (split.Length is not 2)
{ {
_chat.Print(new SeStringBuilder().AddText("Use with /glamour apply ").AddYellow("[Design Name, Path or Identifier, or Clipboard]") _chat.Print(new SeStringBuilder().AddText("Use with /glamour apply ").AddYellow("[Design Name, Path or Identifier, or Clipboard]")
.AddText(" | ") .AddText(" | ")
.AddGreen("[Character Identifier]").BuiltString); .AddGreen("[Character Identifier]")
.AddText("; ")
.AddBlue("<Apply Mods>")
.BuiltString);
_chat.Print(new SeStringBuilder() _chat.Print(new SeStringBuilder()
.AddText( .AddText(
" 》 The design name is case-insensitive. If multiple designs of that name up to case exist, the first one is chosen.") " 》 The design name is case-insensitive. If multiple designs of that name up to case exist, the first one is chosen.")
@ -386,10 +393,27 @@ public class CommandService : IDisposable
.BuiltString); .BuiltString);
_chat.Print(new SeStringBuilder() _chat.Print(new SeStringBuilder()
.AddText(" 》 Clipboard as a single word will try to apply a design string currently in your clipboard.").BuiltString); .AddText(" 》 Clipboard as a single word will try to apply a design string currently in your clipboard.").BuiltString);
_chat.Print(new SeStringBuilder()
.AddText(" 》 ").AddBlue("<Enable Mods>").AddText(" is optional and can be omitted (together with the ;), ").AddBlue("true")
.AddText(" or ").AddBlue("false").AddText(".").BuiltString);
_chat.Print(new SeStringBuilder().AddText("If ").AddBlue("true")
.AddText(", it will try to apply mod associations to the collection assigned to the identified character.").BuiltString);
PlayerIdentifierHelp(false, true); PlayerIdentifierHelp(false, true);
} }
if (!GetDesign(split[0], out var design, true) || !IdentifierHandling(split[1], out var identifiers, false, true)) var split2 = split[1].Split(';', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
var applyMods = split2.Length == 2
&& split2[1].ToLowerInvariant() switch
{
"true" => true,
"1" => true,
"t" => true,
"yes" => true,
"y" => true,
_ => false,
};
if (!GetDesign(split[0], out var design, true) || !IdentifierHandling(split2[0], out var identifiers, false, true))
return false; return false;
_objects.Update(); _objects.Update();
@ -405,7 +429,10 @@ public class CommandService : IDisposable
foreach (var actor in actors.Objects) foreach (var actor in actors.Objects)
{ {
if (_stateManager.GetOrCreate(actor.GetIdentifier(_actors), actor, out var state)) if (_stateManager.GetOrCreate(actor.GetIdentifier(_actors), actor, out var state))
{
ApplyModSettings(design, actor, applyMods);
_stateManager.ApplyDesign(design, state, StateChanged.Source.Manual); _stateManager.ApplyDesign(design, state, StateChanged.Source.Manual);
}
} }
} }
} }
@ -413,6 +440,29 @@ public class CommandService : IDisposable
return true; return true;
} }
private void ApplyModSettings(DesignBase design, Actor actor, bool applyMods)
{
if (!applyMods || design is not Design d)
return;
var collection = _penumbra.GetActorCollection(actor);
if (collection.Length <= 0)
return;
var appliedMods = 0;
foreach (var (mod, setting) in d.AssociatedMods)
{
var message = _penumbra.SetMod(mod, setting, collection);
if (message.Length > 0)
Glamourer.Messager.Chat.Print($"Error applying mod settings: {message}");
else
++appliedMods;
}
if (appliedMods > 0)
Glamourer.Messager.Chat.Print($"Applied {appliedMods} mod settings to {collection}.");
}
private bool Delete(string argument) private bool Delete(string argument)
{ {
if (argument.Length == 0) if (argument.Length == 0)
@ -501,7 +551,8 @@ public class CommandService : IDisposable
&& _stateManager.GetOrCreate(identifier, data.Objects[0], out state))) && _stateManager.GetOrCreate(identifier, data.Objects[0], out state)))
continue; continue;
var design = _converter.Convert(state, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All, CustomizeParameterExtensions.All); var design = _converter.Convert(state, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All,
CustomizeParameterExtensions.All);
_designManager.CreateClone(design, split[0], true); _designManager.CreateClone(design, split[0], true);
return true; return true;
} }
@ -556,7 +607,6 @@ public class CommandService : IDisposable
_chat.Print(new SeStringBuilder().AddText("The token ").AddYellow(argument, true).AddText(" did not resolve to an existing design.") _chat.Print(new SeStringBuilder().AddText("The token ").AddYellow(argument, true).AddText(" did not resolve to an existing design.")
.BuiltString); .BuiltString);
return false; return false;
} }
private unsafe bool IdentifierHandling(string argument, out ActorIdentifier[] identifiers, bool allowAnyWorld, bool allowIndex) private unsafe bool IdentifierHandling(string argument, out ActorIdentifier[] identifiers, bool allowAnyWorld, bool allowIndex)