Add selection designs to chat commands.

This commit is contained in:
Ottermandias 2024-12-31 13:20:36 +01:00
parent 70cf21cf57
commit 71e80740f6
2 changed files with 202 additions and 101 deletions

View file

@ -6,6 +6,7 @@ using Glamourer.Designs;
using Glamourer.Designs.Special; using Glamourer.Designs.Special;
using Glamourer.GameData; using Glamourer.GameData;
using Glamourer.Gui; using Glamourer.Gui;
using Glamourer.Gui.Tabs.DesignTab;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Glamourer.State; using Glamourer.State;
using ImGuiNET; using ImGuiNET;
@ -22,31 +23,30 @@ namespace Glamourer.Services;
public class CommandService : IDisposable, IApiService public class CommandService : IDisposable, IApiService
{ {
private const string RandomString = "random";
private const string MainCommandString = "/glamourer"; private const string MainCommandString = "/glamourer";
private const string ApplyCommandString = "/glamour"; private const string ApplyCommandString = "/glamour";
private readonly ICommandManager _commands; private readonly ICommandManager _commands;
private readonly MainWindow _mainWindow; private readonly MainWindow _mainWindow;
private readonly IChatGui _chat; private readonly IChatGui _chat;
private readonly ActorManager _actors; private readonly ActorManager _actors;
private readonly ObjectManager _objects; private readonly ObjectManager _objects;
private readonly StateManager _stateManager; private readonly StateManager _stateManager;
private readonly AutoDesignApplier _autoDesignApplier; private readonly AutoDesignApplier _autoDesignApplier;
private readonly AutoDesignManager _autoDesignManager; private readonly AutoDesignManager _autoDesignManager;
private readonly DesignManager _designManager; private readonly Configuration _config;
private readonly DesignConverter _converter; private readonly ModSettingApplier _modApplier;
private readonly DesignFileSystem _designFileSystem; private readonly ItemManager _items;
private readonly Configuration _config; private readonly CustomizeService _customizeService;
private readonly ModSettingApplier _modApplier; private readonly DesignManager _designManager;
private readonly ItemManager _items; private readonly DesignConverter _converter;
private readonly RandomDesignGenerator _randomDesign; private readonly DesignResolver _resolver;
private readonly CustomizeService _customizeService;
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, ModSettingApplier modApplier, DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config, ModSettingApplier modApplier,
ItemManager items, RandomDesignGenerator randomDesign, CustomizeService customizeService) ItemManager items, RandomDesignGenerator randomDesign, CustomizeService customizeService, DesignFileSystemSelector designSelector,
QuickDesignCombo quickDesignCombo, DesignResolver resolver)
{ {
_commands = commands; _commands = commands;
_mainWindow = mainWindow; _mainWindow = mainWindow;
@ -57,13 +57,12 @@ public class CommandService : IDisposable, IApiService
_stateManager = stateManager; _stateManager = stateManager;
_designManager = designManager; _designManager = designManager;
_converter = converter; _converter = converter;
_designFileSystem = designFileSystem;
_autoDesignManager = autoDesignManager; _autoDesignManager = autoDesignManager;
_config = config; _config = config;
_modApplier = modApplier; _modApplier = modApplier;
_items = items; _items = items;
_randomDesign = randomDesign;
_customizeService = customizeService; _customizeService = customizeService;
_resolver = resolver;
_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,
@ -611,7 +610,7 @@ public class CommandService : IDisposable, IApiService
if (split.Length is not 2) if (split.Length is not 2)
{ {
_chat.Print(new SeStringBuilder().AddText("Use with /glamour apply ") _chat.Print(new SeStringBuilder().AddText("Use with /glamour apply ")
.AddYellow("[Design Name, Path or Identifier, Random, or Clipboard]") .AddYellow("[Design Name, Path or Identifier, Quick, Selection, Random, or Clipboard]")
.AddText(" | ") .AddText(" | ")
.AddGreen("[Character Identifier]") .AddGreen("[Character Identifier]")
.AddText("; ") .AddText("; ")
@ -628,6 +627,10 @@ public class CommandService : IDisposable, IApiService
_chat.Print(new SeStringBuilder() _chat.Print(new SeStringBuilder()
.AddText(" 》 The design path is the folder path in the selector, with '/' as separators. It is also case-insensitive.") .AddText(" 》 The design path is the folder path in the selector, with '/' as separators. It is also case-insensitive.")
.BuiltString); .BuiltString);
_chat.Print(new SeStringBuilder()
.AddText(" 》 Quick will use the design currently selected in the Quick Design Bar, if any.").BuiltString);
_chat.Print(new SeStringBuilder()
.AddText(" 》 Selection will use the design currently selected in the main interfaces Designs tab, if any.").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() _chat.Print(new SeStringBuilder()
@ -656,7 +659,7 @@ public class CommandService : IDisposable, IApiService
"y" => true, "y" => true,
_ => false, _ => false,
}; };
if (!GetDesign(split[0], out var design, true) || !IdentifierHandling(split2[0], out var identifiers, false, true)) if (!_resolver.GetDesign(split[0], out var design, true) || !IdentifierHandling(split2[0], out var identifiers, false, true))
return false; return false;
_objects.Update(); _objects.Update();
@ -688,7 +691,7 @@ public class CommandService : IDisposable, IApiService
if (!applyMods || design is not Design d) if (!applyMods || design is not Design d)
return; return;
var (messages, appliedMods, collection, name, overridden) = _modApplier.ApplyModSettings(d.AssociatedMods, actor); var (messages, appliedMods, _, name, overridden) = _modApplier.ApplyModSettings(d.AssociatedMods, actor);
foreach (var message in messages) foreach (var message in messages)
Glamourer.Messager.Chat.Print($"Error applying mod settings: {message}"); Glamourer.Messager.Chat.Print($"Error applying mod settings: {message}");
@ -717,7 +720,7 @@ public class CommandService : IDisposable, IApiService
return false; return false;
} }
if (!GetDesign(argument, out var designBase, false) || designBase is not Design d) if (!_resolver.GetDesign(argument, out var designBase, false) || designBase is not Design d)
return false; return false;
_designManager.Delete(d); _designManager.Delete(d);
@ -796,81 +799,6 @@ public class CommandService : IDisposable, IApiService
return false; return false;
} }
private bool GetDesign(string argument, [NotNullWhen(true)] out DesignBase? design, bool allowSpecial)
{
design = null;
if (argument.Length == 0)
return false;
if (allowSpecial)
{
if (string.Equals("clipboard", argument, StringComparison.OrdinalIgnoreCase))
{
try
{
var clipboardText = ImGui.GetClipboardText();
if (clipboardText.Length > 0)
design = _converter.FromBase64(clipboardText, true, true, out _);
}
catch
{
// ignored
}
if (design != null)
return true;
_chat.Print(new SeStringBuilder().AddText("Your current clipboard did not contain a valid design string.").BuiltString);
return false;
}
if (argument.StartsWith(RandomString, StringComparison.OrdinalIgnoreCase))
{
try
{
if (argument.Length == RandomString.Length)
design = _randomDesign.Design();
else if (argument[RandomString.Length] == ':')
design = _randomDesign.Design(argument[(RandomString.Length + 1)..]);
if (design == null)
{
_chat.Print(new SeStringBuilder().AddText("No design matched your restrictions.").BuiltString);
return false;
}
_chat.Print($"Chose random design {((Design)design).Name}.");
}
catch (Exception ex)
{
_chat.Print(new SeStringBuilder().AddText($"Error in the restriction string: {ex.Message}").BuiltString);
return false;
}
return true;
}
}
if (Guid.TryParse(argument, out var guid))
{
design = _designManager.Designs.ByIdentifier(guid);
}
else
{
var lower = argument.ToLowerInvariant();
design = _designManager.Designs.FirstOrDefault(d
=> d.Name.Lower == lower || lower.Length > 3 && d.Identifier.ToString().StartsWith(lower));
if (design == null && _designFileSystem.Find(lower, out var child) && child is DesignFileSystem.Leaf leaf)
design = leaf.Value;
}
if (design != null)
return true;
_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) private unsafe bool IdentifierHandling(string argument, out ActorIdentifier[] identifiers, bool allowAnyWorld, bool allowIndex)
{ {
try try
@ -882,7 +810,7 @@ public class CommandService : IDisposable, IApiService
{ {
_chat.Print(new SeStringBuilder().AddText("The placeholder ").AddGreen(argument) _chat.Print(new SeStringBuilder().AddText("The placeholder ").AddGreen(argument)
.AddText(" did not resolve to a game object with a valid identifier.").BuiltString); .AddText(" did not resolve to a game object with a valid identifier.").BuiltString);
identifiers = Array.Empty<ActorIdentifier>(); identifiers = [];
return false; return false;
} }
@ -913,7 +841,7 @@ public class CommandService : IDisposable, IApiService
_chat.Print(new SeStringBuilder().AddText("The argument ").AddRed(argument, true) _chat.Print(new SeStringBuilder().AddText("The argument ").AddRed(argument, true)
.AddText($" could not be converted to an identifier. {e.Message}") .AddText($" could not be converted to an identifier. {e.Message}")
.BuiltString); .BuiltString);
identifiers = Array.Empty<ActorIdentifier>(); identifiers = [];
return false; return false;
} }
} }

View file

@ -0,0 +1,173 @@
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin.Services;
using Glamourer.Designs;
using Glamourer.Designs.Special;
using Glamourer.Gui;
using Glamourer.Gui.Tabs.DesignTab;
using ImGuiNET;
using OtterGui.Services;
using OtterGui.Classes;
namespace Glamourer.Services;
public class DesignResolver(
DesignFileSystemSelector designSelector,
QuickDesignCombo quickDesignCombo,
DesignConverter converter,
DesignManager manager,
DesignFileSystem designFileSystem,
RandomDesignGenerator randomDesign,
IChatGui chat) : IService
{
private const string RandomString = "random";
public bool GetDesign(string argument, [NotNullWhen(true)] out DesignBase? design, bool allowSpecial)
{
if (GetDesign(argument, out design, out var error, out var message, allowSpecial))
{
if (message != null)
chat.Print(message);
return true;
}
if (error != null)
chat.Print(error);
return false;
}
public bool GetDesign(string argument, [NotNullWhen(true)] out DesignBase? design, out SeString? error, out SeString? message,
bool allowSpecial)
{
design = null;
error = null;
message = null;
if (argument.Length == 0)
return false;
if (allowSpecial)
{
if (string.Equals("selection", argument, StringComparison.OrdinalIgnoreCase))
return GetSelectedDesign(ref design, ref error);
if (string.Equals("quick", argument, StringComparison.OrdinalIgnoreCase))
return GetQuickDesign(ref design, ref error);
if (string.Equals("clipboard", argument, StringComparison.OrdinalIgnoreCase))
return GetClipboardDesign(ref design, ref error);
if (argument.StartsWith(RandomString, StringComparison.OrdinalIgnoreCase))
return GetRandomDesign(argument, ref design, ref error, ref message);
}
return GetStandardDesign(argument, ref design, ref error);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool GetSelectedDesign(ref DesignBase? design, ref SeString? error)
{
design = designSelector.Selected;
if (design != null)
return true;
error = "You do not have selected any design in the Designs Tab.";
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool GetQuickDesign(ref DesignBase? design, ref SeString? error)
{
design = quickDesignCombo.Design as Design;
if (design != null)
return true;
error = "You do not have selected any design in the Quick Design Bar.";
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool GetClipboardDesign(ref DesignBase? design, ref SeString? error)
{
try
{
var clipboardText = ImGui.GetClipboardText();
if (clipboardText.Length > 0)
design = converter.FromBase64(clipboardText, true, true, out _);
}
catch
{
// ignored
}
if (design != null)
return true;
error = "Your current clipboard did not contain a valid design string.";
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool GetRandomDesign(string argument, ref DesignBase? design, ref SeString? error, ref SeString? message)
{
try
{
if (argument.Length == RandomString.Length)
design = randomDesign.Design();
else if (argument[RandomString.Length] == ':')
design = randomDesign.Design(argument[(RandomString.Length + 1)..]);
if (design == null)
{
error = "No design matched your restrictions.";
return false;
}
message = $"Chose random design {((Design)design).Name}.";
}
catch (Exception ex)
{
error = $"Error in the restriction string: {ex.Message}";
return false;
}
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool GetStandardDesign(string argument, ref DesignBase? design, ref SeString? error)
{
// As Guid
if (Guid.TryParse(argument, out var guid))
{
design = manager.Designs.ByIdentifier(guid);
}
else
{
var lower = argument.ToLowerInvariant();
// Search for design by name and partial identifier.
design = manager.Designs.FirstOrDefault(MatchNameAndIdentifier(lower));
// Search for design by path, if nothing was found.
if (design == null && designFileSystem.Find(lower, out var child) && child is DesignFileSystem.Leaf leaf)
design = leaf.Value;
}
if (design != null)
return true;
error = new SeStringBuilder().AddText("The token ").AddYellow(argument, true).AddText(" did not resolve to an existing design.")
.BuiltString;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Func<Design, bool> MatchNameAndIdentifier(string lower)
{
// Check for names and identifiers, prefer names
if (lower.Length > 3)
return d => d.Name.Lower == lower || d.Identifier.ToString().StartsWith(lower);
// Check only for names.
return d => d.Name.Lower == lower;
}
}