From 588ad40dc597953767a99a125e10ef88258a7631 Mon Sep 17 00:00:00 2001 From: Jackson <9527380+Jaksuhn@users.noreply.github.com> Date: Fri, 17 Nov 2023 10:21:21 +0000 Subject: [PATCH] added hold and release commands --- .../Grammar/Commands/CallbackCommand.cs | 13 +-- .../Grammar/Commands/HoldCommand.cs | 81 +++++++++++++++++++ .../Grammar/Commands/ReleaseCommand.cs | 81 +++++++++++++++++++ SomethingNeedDoing/Grammar/MacroParser.cs | 2 + SomethingNeedDoing/Interface/HelpWindow.cs | 59 +++++++++++++- SomethingNeedDoing/Misc/Keyboard.cs | 44 ++++++++++ 6 files changed, 271 insertions(+), 9 deletions(-) create mode 100644 SomethingNeedDoing/Grammar/Commands/HoldCommand.cs create mode 100644 SomethingNeedDoing/Grammar/Commands/ReleaseCommand.cs diff --git a/SomethingNeedDoing/Grammar/Commands/CallbackCommand.cs b/SomethingNeedDoing/Grammar/Commands/CallbackCommand.cs index f974783f..7de9f752 100644 --- a/SomethingNeedDoing/Grammar/Commands/CallbackCommand.cs +++ b/SomethingNeedDoing/Grammar/Commands/CallbackCommand.cs @@ -15,7 +15,7 @@ //internal class CallbackCommand : MacroCommand //{ // private static readonly Regex Regex = new(@"^/callback\s+(?.*?)\s*$", RegexOptions.Compiled); -// private AtkUnitBase addon; +// private AtkUnitBase* addon; // private bool updateState; // private List valueArgs = new(); @@ -37,7 +37,7 @@ // public static CallbackCommand Parse(List args) // { - + // var valueArgs = new List(); // var current = ""; @@ -81,14 +81,15 @@ // Svc.Log.Error("Error: Unclosed quotes."); // } -// return new CallbackCommand(text); +// return new CallbackCommand(args); // } -// /// // public async override Task Execute(ActiveMacro macro, CancellationToken token) // { -// Callback.Fire(IsAddonReady,) - +// unsafe +// { +// Callback.Fire(addon, updateState, valueArgs.ToArray()); +// } // await this.PerformWait(token); // } //} diff --git a/SomethingNeedDoing/Grammar/Commands/HoldCommand.cs b/SomethingNeedDoing/Grammar/Commands/HoldCommand.cs new file mode 100644 index 00000000..8df80152 --- /dev/null +++ b/SomethingNeedDoing/Grammar/Commands/HoldCommand.cs @@ -0,0 +1,81 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; + +using Dalamud.Game.ClientState.Keys; +using Dalamud.Logging; +using SomethingNeedDoing.Exceptions; +using SomethingNeedDoing.Grammar.Modifiers; +using SomethingNeedDoing.Misc; + +namespace SomethingNeedDoing.Grammar.Commands; + +/// +/// The /send command. +/// +internal class HoldCommand : MacroCommand +{ + private static readonly Regex Regex = new(@"^/hold\s+(?.*?)\s*$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private readonly VirtualKey[] vkCodes; + + /// + /// Initializes a new instance of the class. + /// + /// Original text. + /// VirtualKey codes. + /// Wait value. + private HoldCommand(string text, VirtualKey[] vkCodes, WaitModifier wait) + : base(text, wait) + { + this.vkCodes = vkCodes; + } + + /// + /// Parse the text as a command. + /// + /// Text to parse. + /// A parsed command. + public static HoldCommand Parse(string text) + { + _ = WaitModifier.TryParse(ref text, out var waitModifier); + + var match = Regex.Match(text); + if (!match.Success) + throw new MacroSyntaxError(text); + + var nameValue = ExtractAndUnquote(match, "name"); + var vkCodes = nameValue.Split("+") + .Select(name => + { + if (!Enum.TryParse(name, true, out var vkCode)) + throw new MacroCommandError("Invalid virtual key"); + + return vkCode; + }) + .ToArray(); + + return new HoldCommand(text, vkCodes, waitModifier); + } + + /// + public async override Task Execute(ActiveMacro macro, CancellationToken token) + { + Service.Log.Debug($"Executing: {this.Text}"); + + if (this.vkCodes.Length == 1) + { + Keyboard.Hold(this.vkCodes[0]); + } + else + { + var key = this.vkCodes.Last(); + var mods = this.vkCodes.SkipLast(1); + Keyboard.Hold(key, mods); + } + + await this.PerformWait(token); + } +} diff --git a/SomethingNeedDoing/Grammar/Commands/ReleaseCommand.cs b/SomethingNeedDoing/Grammar/Commands/ReleaseCommand.cs new file mode 100644 index 00000000..d2871c8e --- /dev/null +++ b/SomethingNeedDoing/Grammar/Commands/ReleaseCommand.cs @@ -0,0 +1,81 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; + +using Dalamud.Game.ClientState.Keys; +using Dalamud.Logging; +using SomethingNeedDoing.Exceptions; +using SomethingNeedDoing.Grammar.Modifiers; +using SomethingNeedDoing.Misc; + +namespace SomethingNeedDoing.Grammar.Commands; + +/// +/// The /send command. +/// +internal class ReleaseCommand : MacroCommand +{ + private static readonly Regex Regex = new(@"^/release\s+(?.*?)\s*$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private readonly VirtualKey[] vkCodes; + + /// + /// Initializes a new instance of the class. + /// + /// Original text. + /// VirtualKey codes. + /// Wait value. + private ReleaseCommand(string text, VirtualKey[] vkCodes, WaitModifier wait) + : base(text, wait) + { + this.vkCodes = vkCodes; + } + + /// + /// Parse the text as a command. + /// + /// Text to parse. + /// A parsed command. + public static ReleaseCommand Parse(string text) + { + _ = WaitModifier.TryParse(ref text, out var waitModifier); + + var match = Regex.Match(text); + if (!match.Success) + throw new MacroSyntaxError(text); + + var nameValue = ExtractAndUnquote(match, "name"); + var vkCodes = nameValue.Split("+") + .Select(name => + { + if (!Enum.TryParse(name, true, out var vkCode)) + throw new MacroCommandError("Invalid virtual key"); + + return vkCode; + }) + .ToArray(); + + return new ReleaseCommand(text, vkCodes, waitModifier); + } + + /// + public async override Task Execute(ActiveMacro macro, CancellationToken token) + { + Service.Log.Debug($"Executing: {this.Text}"); + + if (this.vkCodes.Length == 1) + { + Keyboard.Release(this.vkCodes[0]); + } + else + { + var key = this.vkCodes.Last(); + var mods = this.vkCodes.SkipLast(1); + Keyboard.Release(key, mods); + } + + await this.PerformWait(token); + } +} diff --git a/SomethingNeedDoing/Grammar/MacroParser.cs b/SomethingNeedDoing/Grammar/MacroParser.cs index 016dcfb4..9c42465d 100644 --- a/SomethingNeedDoing/Grammar/MacroParser.cs +++ b/SomethingNeedDoing/Grammar/MacroParser.cs @@ -69,6 +69,8 @@ public static MacroCommand ParseLine(string line) "/requirestats" => RequireStatsCommand.Parse(line), "/runmacro" => RunMacroCommand.Parse(line), "/send" => SendCommand.Parse(line), + "/hold" => HoldCommand.Parse(line), + "/release" => ReleaseCommand.Parse(line), "/target" when Service.Configuration.UseSNDTargeting => TargetCommand.Parse(line), "/waitaddon" => WaitAddonCommand.Parse(line), "/wait" => WaitCommand.Parse(line), diff --git a/SomethingNeedDoing/Interface/HelpWindow.cs b/SomethingNeedDoing/Interface/HelpWindow.cs index 894fcb53..f46cdc55 100644 --- a/SomethingNeedDoing/Interface/HelpWindow.cs +++ b/SomethingNeedDoing/Interface/HelpWindow.cs @@ -135,6 +135,26 @@ private readonly (string Name, string? Alias, string Description, string[] Modif "/send NUMPAD0", "/send CONTROL+MENU+SHIFT+NUMPAD0", }), + ( + "hold", null, + "Send an arbitrary keystroke, to be held down, with optional modifiers. Keys are pressed in the same order as the command.", + new[] { "wait" }, + new[] + { + "/send MULTIPLY", + "/send NUMPAD0", + "/send CONTROL+MENU+SHIFT+NUMPAD0", + }), + ( + "release", null, + "Send an arbitrary keystroke, to be released, with optional modifiers. Keys are pressed in the same order as the command.", + new[] { "wait" }, + new[] + { + "/send MULTIPLY", + "/send NUMPAD0", + "/send CONTROL+MENU+SHIFT+NUMPAD0", + }), ( "target", null, "Target anyone and anything that can be selected.", @@ -296,6 +316,12 @@ static void DisplayChangelog(string date, string changes, bool separator = true) ImGui.PushFont(UiBuilder.MonoFont); + DisplayChangelog( + "2023-11-17", + "- Added /hold\n" + + "- Added /release.\n" + + "- Updated help documentation for lua commands.\n"); + DisplayChangelog( "2023-11-15", "- Added GetClassJobId()\n"); @@ -850,11 +876,38 @@ bool HasStatus(string name) // id: status effect id(s). bool HasStatusId(uint id, ...) -IsAddonVisible(string addonName) -IsAddonReady(string addonName) +bool IsAddonVisible(string addonName) +bool IsAddonReady(string addonName) // Can fetch nested nodes -GetNodeText(string addonName, int nodeNumber, ...) +string GetNodeText(string addonName, int nodeNumber, ...) + +string GetSelectStringText(int index) +string GetSelectIconStringText(int index) + +bool GetCharacterCondition(int flagID, bool hasCondition = true) + +bool IsInZone(int zoneID) +int GetZoneID() + +string GetCharacterName(bool includeWorld = false) + +int GetItemCount(int itemID, bool includeHQ = true) + +bool DeliverooIsTurnInRunning() + +uint GetProgressIncrease(uint actionID) +uint GetQualityIncrease(uint actionID) + +void LeaveDuty() + +bool IsLocalPlayerNull() +bool IsPlayerDead() +bool IsPlayerCasting() + +uint GetGil() + +uint GetClassJobId() ".Trim(); ImGui.TextWrapped(text); diff --git a/SomethingNeedDoing/Misc/Keyboard.cs b/SomethingNeedDoing/Misc/Keyboard.cs index b5be7285..5c9e0ad1 100644 --- a/SomethingNeedDoing/Misc/Keyboard.cs +++ b/SomethingNeedDoing/Misc/Keyboard.cs @@ -56,6 +56,50 @@ public static void Send(VirtualKey key, IEnumerable? mods) } } + public static void Hold(VirtualKey key) => Hold(key, null); + + public static void Hold(VirtualKey key, IEnumerable? mods) + { + const int WM_KEYDOWN = 0x100; + + if (key != 0) + { + //var hWnd = handle ??= Process.GetCurrentProcess().MainWindowHandle; + if (WindowFunctions.TryFindGameWindow(out var hWnd)) + { + if (mods != null) + { + foreach (var mod in mods) + _ = SendMessage(hWnd, WM_KEYDOWN, (IntPtr)mod, IntPtr.Zero); + } + + _ = SendMessage(hWnd, WM_KEYDOWN, (IntPtr)key, IntPtr.Zero); + } + } + } + + public static void Release (VirtualKey key) => Release(key, null); + + public static void Release(VirtualKey key, IEnumerable? mods) + { + const int WM_KEYUP = 0x101; + + if (key != 0) + { + //var hWnd = handle ??= Process.GetCurrentProcess().MainWindowHandle; + if (WindowFunctions.TryFindGameWindow(out var hWnd)) + { + _ = SendMessage(hWnd, WM_KEYUP, (IntPtr)key, IntPtr.Zero); + + if (mods != null) + { + foreach (var mod in mods) + _ = SendMessage(hWnd, WM_KEYUP, (IntPtr)mod, IntPtr.Zero); + } + } + } + } + [DllImport("user32.dll")] private static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam); }