mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 18:27:24 +01:00
Add Chat Commands.
This commit is contained in:
parent
f4c47520cd
commit
5fa31d9917
2 changed files with 374 additions and 10 deletions
|
|
@ -137,6 +137,12 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
|||
public Actor Target
|
||||
=> _targets.Target?.Address ?? nint.Zero;
|
||||
|
||||
public Actor Focus
|
||||
=> _targets.FocusTarget?.Address ?? nint.Zero;
|
||||
|
||||
public Actor MouseOver
|
||||
=> _targets.MouseOverTarget?.Address ?? nint.Zero;
|
||||
|
||||
public (ActorIdentifier Identifier, ActorData Data) PlayerData
|
||||
{
|
||||
get
|
||||
|
|
@ -186,4 +192,22 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
|||
|
||||
public IEnumerable<ActorData> Values
|
||||
=> Identifiers.Values;
|
||||
|
||||
public bool GetName(string lowerName, out Actor actor)
|
||||
{
|
||||
(actor, var ret) = lowerName switch
|
||||
{
|
||||
"" => (Actor.Null, true),
|
||||
"<me>" => (Player, true),
|
||||
"self" => (Player, true),
|
||||
"<t>" => (Target, true),
|
||||
"target" => (Target, true),
|
||||
"<f>" => (Focus, true),
|
||||
"focus" => (Focus, true),
|
||||
"<mo>" => (MouseOver, true),
|
||||
"mouseover" => (MouseOver, true),
|
||||
_ => (Actor.Null, false),
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,54 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Glamourer.Automation;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Gui;
|
||||
using Glamourer.Gui.Tabs;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.Actors;
|
||||
|
||||
namespace Glamourer.Services;
|
||||
|
||||
public class CommandService : IDisposable
|
||||
{
|
||||
private const string HelpString = "[Copy|Apply|Save],[Name or PlaceHolder],<Name for Save>";
|
||||
private const string MainCommandString = "/glamourer";
|
||||
private const string ApplyCommandString = "/glamour";
|
||||
|
||||
private readonly CommandManager _commands;
|
||||
private readonly MainWindow _mainWindow;
|
||||
private readonly CommandManager _commands;
|
||||
private readonly MainWindow _mainWindow;
|
||||
private readonly ChatGui _chat;
|
||||
private readonly ActorService _actors;
|
||||
private readonly ObjectManager _objects;
|
||||
private readonly StateManager _stateManager;
|
||||
private readonly AutoDesignApplier _autoDesignApplier;
|
||||
private readonly DesignManager _designManager;
|
||||
private readonly DesignConverter _converter;
|
||||
|
||||
public CommandService(CommandManager commands, MainWindow mainWindow)
|
||||
public CommandService(CommandManager commands, MainWindow mainWindow, ChatGui chat, ActorService actors, ObjectManager objects,
|
||||
AutoDesignApplier autoDesignApplier, StateManager stateManager, DesignManager designManager, DesignConverter converter)
|
||||
{
|
||||
_commands = commands;
|
||||
_mainWindow = mainWindow;
|
||||
_commands = commands;
|
||||
_mainWindow = mainWindow;
|
||||
_chat = chat;
|
||||
_actors = actors;
|
||||
_objects = objects;
|
||||
_autoDesignApplier = autoDesignApplier;
|
||||
_stateManager = stateManager;
|
||||
_designManager = designManager;
|
||||
_converter = converter;
|
||||
|
||||
_commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." });
|
||||
_commands.AddHandler(ApplyCommandString, new CommandInfo(OnGlamour) { HelpMessage = $"Use Glamourer Functions: {HelpString}" });
|
||||
_commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." });
|
||||
_commands.AddHandler(ApplyCommandString,
|
||||
new CommandInfo(OnGlamour) { HelpMessage = "Use Glamourer Functions. Use with 'help' or '?' for extended help." });
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -33,5 +61,317 @@ public class CommandService : IDisposable
|
|||
=> _mainWindow.Toggle();
|
||||
|
||||
private void OnGlamour(string command, string arguments)
|
||||
{ }
|
||||
{
|
||||
var argumentList = arguments.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (argumentList.Length < 1)
|
||||
return;
|
||||
|
||||
var argument = argumentList.Length == 2 ? argumentList[1] : string.Empty;
|
||||
var _ = argumentList[0].ToLowerInvariant() switch
|
||||
{
|
||||
"apply" => Apply(argument),
|
||||
"reapply" => ReapplyState(argument),
|
||||
"revert" => Revert(argument),
|
||||
"reapplyautomation" => ReapplyAutomation(argument),
|
||||
//"automation" => SetAutomation(argument),
|
||||
"copy" => CopyState(argument),
|
||||
"save" => SaveState(argument),
|
||||
_ => PrintHelp(argumentList[0]),
|
||||
};
|
||||
}
|
||||
|
||||
private bool PrintHelp(string argument)
|
||||
{
|
||||
if (!string.Equals(argument, "help", StringComparison.OrdinalIgnoreCase) && argument != "?")
|
||||
_chat.Print(new SeStringBuilder().AddText("The given argument ").AddRed(argument, true)
|
||||
.AddText(" is not valid. Valid arguments are:").BuiltString);
|
||||
else
|
||||
_chat.Print("Valid arguments for /glamour are:");
|
||||
|
||||
_chat.Print(new SeStringBuilder().AddCommand("apply", "Applies a given design to a given character. Use without arguments for help.")
|
||||
.BuiltString);
|
||||
_chat.Print(new SeStringBuilder()
|
||||
.AddCommand("reapply", "Re-applies the current supposed state of a given character. Use without arguments for help.").BuiltString);
|
||||
_chat.Print(new SeStringBuilder().AddCommand("revert", "Reverts a given character to its game state. Use without arguments for help.")
|
||||
.BuiltString);
|
||||
_chat.Print(new SeStringBuilder().AddCommand("reapplyautomation",
|
||||
"Reverts a given character to its supposed state using automated designs. Use without arguments for help.").BuiltString);
|
||||
_chat.Print(new SeStringBuilder()
|
||||
.AddCommand("copy", "Copy the current state of a character to clipboard. Use without arguments for help.").BuiltString);
|
||||
_chat.Print(new SeStringBuilder()
|
||||
.AddCommand("save", "Save the current state of a character to a named design. Use without arguments for help.").BuiltString);
|
||||
//_chat.Print(new SeStringBuilder()
|
||||
// .AddCommand("automation", "Change the configuration for automated designs. Use without arguments for help.").BuiltString);
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: implement automation changes via chat.
|
||||
private bool SetAutomation(string arguments)
|
||||
=> true;
|
||||
|
||||
private bool ReapplyAutomation(string argument)
|
||||
{
|
||||
if (argument.Length == 0)
|
||||
{
|
||||
_chat.Print(new SeStringBuilder().AddText("Use with /glamour reapplyautomation ").AddGreen("[Character Identifier]").BuiltString);
|
||||
PlayerIdentifierHelp(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!IdentifierHandling(argument, out var identifier, false))
|
||||
return false;
|
||||
|
||||
_objects.Update();
|
||||
if (!_objects.TryGetValue(identifier, out var data))
|
||||
return true;
|
||||
|
||||
foreach (var actor in data.Objects)
|
||||
{
|
||||
if (_stateManager.GetOrCreate(identifier, actor, out var state))
|
||||
{
|
||||
_autoDesignApplier.ReapplyAutomation(actor, identifier, state);
|
||||
_stateManager.ReapplyState(actor);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool Revert(string argument)
|
||||
{
|
||||
if (argument.Length == 0)
|
||||
{
|
||||
_chat.Print(new SeStringBuilder().AddText("Use with /glamour revert ").AddGreen("[Character Identifier]").BuiltString);
|
||||
PlayerIdentifierHelp(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!IdentifierHandling(argument, out var identifier, false))
|
||||
return false;
|
||||
|
||||
if (_stateManager.TryGetValue(identifier, out var state))
|
||||
_stateManager.ResetState(state);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ReapplyState(string argument)
|
||||
{
|
||||
if (argument.Length == 0)
|
||||
{
|
||||
_chat.Print(new SeStringBuilder().AddText("Use with /glamour revert ").AddGreen("[Character Identifier]").BuiltString);
|
||||
PlayerIdentifierHelp(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!IdentifierHandling(argument, out var identifier, false))
|
||||
return false;
|
||||
|
||||
_objects.Update();
|
||||
if (!_objects.TryGetValue(identifier, out var data))
|
||||
return true;
|
||||
|
||||
foreach (var actor in data.Objects)
|
||||
_stateManager.ReapplyState(actor);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool Apply(string arguments)
|
||||
{
|
||||
var split = arguments.Split('|', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (split.Length != 2)
|
||||
{
|
||||
_chat.Print(new SeStringBuilder().AddText("Use with /glamour apply ").AddYellow("[Design Name or Identifier]").AddText(" | ")
|
||||
.AddGreen("[Character Identifier]").BuiltString);
|
||||
_chat.Print(new SeStringBuilder()
|
||||
.AddText(" 》 The design name must match up to case. If multiple designs of that name exist, the first one is chosen.")
|
||||
.BuiltString);
|
||||
_chat.Print(new SeStringBuilder()
|
||||
.AddText(
|
||||
" 》 If using the design identifier, you need to specify at least 4 characters for it, and the first one starting with the provided characters is chosen.")
|
||||
.BuiltString);
|
||||
PlayerIdentifierHelp(false);
|
||||
}
|
||||
|
||||
if (!GetDesign(split[0], out var design) || !IdentifierHandling(split[1], out var identifier, false))
|
||||
return false;
|
||||
|
||||
_objects.Update();
|
||||
if (!_objects.TryGetValue(identifier, out var actors))
|
||||
{
|
||||
if (_stateManager.TryGetValue(identifier, out var state))
|
||||
_stateManager.ApplyDesign(design, state, StateChanged.Source.Manual);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var actor in actors.Objects)
|
||||
{
|
||||
if (_stateManager.GetOrCreate(identifier, actor, out var state))
|
||||
_stateManager.ApplyDesign(design, state, StateChanged.Source.Manual);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool CopyState(string argument)
|
||||
{
|
||||
if (argument.Length == 0)
|
||||
{
|
||||
_chat.Print(new SeStringBuilder().AddText("Use with /glamour copy ").AddGreen("[Character Identifier]").BuiltString);
|
||||
PlayerIdentifierHelp(false);
|
||||
}
|
||||
|
||||
if (!IdentifierHandling(argument, out var identifier, false))
|
||||
return false;
|
||||
|
||||
string text;
|
||||
if (_stateManager.TryGetValue(identifier, out var state))
|
||||
{
|
||||
text = _converter.ShareBase64(state);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_objects.TryGetValue(identifier, out var data)
|
||||
|| !data.Valid
|
||||
|| !_stateManager.GetOrCreate(identifier, data.Objects[0], out state))
|
||||
{
|
||||
_chat.Print(new SeStringBuilder().AddText("Could not copy state to clipboard: The identified object ")
|
||||
.AddGreen(identifier.ToString(), true).AddText(" is not available and has no stored state.").BuiltString);
|
||||
return false;
|
||||
}
|
||||
|
||||
text = _converter.ShareBase64(state);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ImGui.SetClipboardText(text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_chat.Print("Could not copy state to clipboard: Failure to write to clipboard.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool SaveState(string arguments)
|
||||
{
|
||||
var split = arguments.Split('|', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (split.Length != 2)
|
||||
{
|
||||
_chat.Print(new SeStringBuilder().AddText("Use with /glamour save ").AddYellow("[New Design Name]").AddText(" | ")
|
||||
.AddGreen("[Character Identifier]").BuiltString);
|
||||
PlayerIdentifierHelp(false);
|
||||
}
|
||||
|
||||
if (!IdentifierHandling(split[1], out var identifier, false))
|
||||
return false;
|
||||
|
||||
if (!_stateManager.TryGetValue(identifier, out var state))
|
||||
{
|
||||
_objects.Update();
|
||||
if (!_objects.TryGetValue(identifier, out var data)
|
||||
|| !data.Valid
|
||||
|| !_stateManager.GetOrCreate(identifier, data.Objects[0], out state))
|
||||
{
|
||||
_chat.Print(new SeStringBuilder().AddText("Could not save state to design ").AddYellow(split[0], true)
|
||||
.AddText(": The identified object ")
|
||||
.AddGreen(identifier.ToString(), true).AddText(" is not available and has no stored state.").BuiltString);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var design = _converter.Convert(state, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant);
|
||||
_designManager.CreateClone(design, split[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool GetDesign(string argument, [NotNullWhen(true)] out Design? design)
|
||||
{
|
||||
design = null;
|
||||
if (argument.Length == 0)
|
||||
return false;
|
||||
|
||||
if (Guid.TryParse(argument, out var guid))
|
||||
{
|
||||
design = _designManager.Designs.FirstOrDefault(d => d.Identifier == 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)
|
||||
{
|
||||
_chat.Print(new SeStringBuilder().AddText("The token ").AddYellow(argument, true).AddText(" did not resolve to an existing design.")
|
||||
.BuiltString);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private unsafe bool IdentifierHandling(string argument, out ActorIdentifier identifier, bool allowAnyWorld)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_objects.GetName(argument.ToLowerInvariant(), out var obj))
|
||||
{
|
||||
identifier = _actors.AwaitedService.FromObject(obj.AsObject, out _, true, true, true);
|
||||
if (!identifier.IsValid)
|
||||
{
|
||||
_chat.Print(new SeStringBuilder().AddText("The placeholder ").AddGreen(argument)
|
||||
.AddText(" did not resolve to a game object with a valid identifier.").BuiltString);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
identifier = _actors.AwaitedService.FromUserString(argument);
|
||||
if (!allowAnyWorld
|
||||
&& identifier.Type is IdentifierType.Player or IdentifierType.Owned
|
||||
&& identifier.HomeWorld == ushort.MaxValue)
|
||||
{
|
||||
_chat.Print(new SeStringBuilder().AddText("The argument ").AddRed(argument, true)
|
||||
.AddText(" did not specify a world.").BuiltString);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (ActorManager.IdentifierParseError e)
|
||||
{
|
||||
_chat.Print(new SeStringBuilder().AddText("The argument ").AddRed(argument, true)
|
||||
.AddText($" could not be converted to an identifier. {e.Message}")
|
||||
.BuiltString);
|
||||
identifier = ActorIdentifier.Invalid;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayerIdentifierHelp(bool allowAnyWorld)
|
||||
{
|
||||
_chat.Print(new SeStringBuilder().AddText(" 》 Valid Character Identifiers have the form:").BuiltString);
|
||||
_chat.Print(new SeStringBuilder().AddText(" 》》》").AddGreen("<me>").AddText(" or ").AddGreen("<t>").AddText(" or ").AddGreen("<mo>")
|
||||
.AddText(" or ").AddGreen("<f>")
|
||||
.AddText(" as placeholders for your character, your target, your mouseover or your focus, if they exist.").BuiltString);
|
||||
_chat.Print(new SeStringBuilder().AddText(" 》》》").AddGreen("p").AddText(" | ").AddWhite("[Player Name]@[World Name]")
|
||||
.AddText(allowAnyWorld ? ", if no @ is provided, Any World is used." : ".")
|
||||
.BuiltString);
|
||||
_chat.Print(new SeStringBuilder().AddText(" 》》》").AddGreen("r").AddText(" | ").AddWhite("[Retainer Name]").AddText(".").BuiltString);
|
||||
_chat.Print(new SeStringBuilder().AddText(" 》》》").AddGreen("n").AddText(" | ").AddPurple("[NPC Type]").AddText(" : ")
|
||||
.AddRed("[NPC Name]").AddText(", where NPC Type can be ").AddInitialPurple("Mount").AddInitialPurple("Companion")
|
||||
.AddInitialPurple("Accessory").AddInitialPurple("Event NPC").AddText("or ").AddInitialPurple("Battle NPC", false).AddText(".")
|
||||
.BuiltString);
|
||||
_chat.Print(new SeStringBuilder().AddText(" 》》》 ").AddGreen("o").AddText(" | ").AddPurple("[NPC Type]")
|
||||
.AddText(" : ")
|
||||
.AddRed("[NPC Name]").AddText(" | ").AddWhite("[Player Name]@<World Name>").AddText(".").BuiltString);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue