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(),
["Equipment"] = SerializeEquipment(),
["Customize"] = SerializeCustomize(),
["Parameters"] = SerializeParameters(),
["Parameters"] = SerializeParameters(),
["Mods"] = SerializeMods(),
};
return ret;

View file

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

View file

@ -148,7 +148,8 @@ public unsafe class PenumbraService : IDisposable
public void OpenModPage(Mod mod)
{
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
@ -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.
/// If it is disabled, ignore all other settings.
/// </summary>
public string SetMod(Mod mod, ModSettings settings)
public string SetMod(Mod mod, ModSettings settings, string? collection = null)
{
if (!Available)
return "Penumbra is not available.";
@ -166,12 +167,13 @@ public unsafe class PenumbraService : IDisposable
var sb = new StringBuilder();
try
{
var collection = _currentCollection.Invoke(ApiCollectionType.Current);
var ec = _setMod.Invoke(collection, mod.DirectoryName, mod.Name, settings.Enabled);
if (ec is 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.");
collection ??= _currentCollection.Invoke(ApiCollectionType.Current);
var ec = _setMod.Invoke(collection, mod.DirectoryName, mod.Name, settings.Enabled);
switch (ec)
{
case PenumbraApiEc.ModMissing: return $"The mod {mod.Name} [{mod.DirectoryName}] could not be found.";
case PenumbraApiEc.CollectionMissing: return $"The collection {collection} could not be found.";
}
if (!settings.Enabled)
return string.Empty;
@ -216,13 +218,23 @@ public unsafe class PenumbraService : IDisposable
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>
public Actor GameObjectFromDrawObject(Model drawObject)
=> Available ? _drawObjectInfo.Invoke(drawObject.Address).Item1 : Actor.Null;
/// <summary> Obtain the parent of a cutscene actor if it is known. </summary>
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>
public void RedrawObject(Actor actor, RedrawType settings)

View file

@ -10,6 +10,8 @@ using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Gui;
using Glamourer.Interop;
using Glamourer.Interop.Penumbra;
using Glamourer.Interop.Structs;
using Glamourer.State;
using ImGuiNET;
using OtterGui;
@ -36,10 +38,11 @@ public class CommandService : IDisposable
private readonly DesignConverter _converter;
private readonly DesignFileSystem _designFileSystem;
private readonly Configuration _config;
private readonly PenumbraService _penumbra;
public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorManager actors, ObjectManager objects,
AutoDesignApplier autoDesignApplier, StateManager stateManager, DesignManager designManager, DesignConverter converter,
DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config)
DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config, PenumbraService penumbra)
{
_commands = commands;
_mainWindow = mainWindow;
@ -53,6 +56,7 @@ public class CommandService : IDisposable
_designFileSystem = designFileSystem;
_autoDesignManager = autoDesignManager;
_config = config;
_penumbra = penumbra;
_commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." });
_commands.AddHandler(ApplyCommandString,
@ -368,11 +372,14 @@ public class CommandService : IDisposable
private bool Apply(string arguments)
{
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]")
.AddText(" | ")
.AddGreen("[Character Identifier]").BuiltString);
.AddGreen("[Character Identifier]")
.AddText("; ")
.AddBlue("<Apply Mods>")
.BuiltString);
_chat.Print(new SeStringBuilder()
.AddText(
" 》 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);
_chat.Print(new SeStringBuilder()
.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);
}
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;
_objects.Update();
@ -405,7 +429,10 @@ public class CommandService : IDisposable
foreach (var actor in actors.Objects)
{
if (_stateManager.GetOrCreate(actor.GetIdentifier(_actors), actor, out var state))
{
ApplyModSettings(design, actor, applyMods);
_stateManager.ApplyDesign(design, state, StateChanged.Source.Manual);
}
}
}
}
@ -413,6 +440,29 @@ public class CommandService : IDisposable
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)
{
if (argument.Length == 0)
@ -501,7 +551,8 @@ public class CommandService : IDisposable
&& _stateManager.GetOrCreate(identifier, data.Objects[0], out state)))
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);
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.")
.BuiltString);
return false;
}
private unsafe bool IdentifierHandling(string argument, out ActorIdentifier[] identifiers, bool allowAnyWorld, bool allowIndex)