From 95371739c4eb4baeb7e9bfe7d9ebafb93bb123e3 Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Sat, 27 Jul 2024 01:32:47 -0700 Subject: [PATCH] MacroMate, reworked macro copying, extra copying settings --- Craftimizer/Configuration.cs | 7 + Craftimizer/Plugin.cs | 2 + Craftimizer/Service.cs | 1 + Craftimizer/Utils/Ipc.cs | 69 ++++++++++ Craftimizer/Utils/MacroCopy.cs | 183 ++++++++++++++++++-------- Craftimizer/Windows/MacroClipboard.cs | 17 ++- Craftimizer/Windows/Settings.cs | 95 +++++++++---- 7 files changed, 290 insertions(+), 84 deletions(-) create mode 100644 Craftimizer/Utils/Ipc.cs diff --git a/Craftimizer/Configuration.cs b/Craftimizer/Configuration.cs index aacf279..1580d69 100644 --- a/Craftimizer/Configuration.cs +++ b/Craftimizer/Configuration.cs @@ -127,6 +127,7 @@ public enum CopyType OpenWindow, // useful for big macros CopyToMacro, // (add option for down or right) (max macro count; open copy-paste window if too much) CopyToClipboard, + CopyToMacroMate } public CopyType Type { get; set; } = CopyType.OpenWindow; @@ -137,6 +138,10 @@ public enum CopyType public int StartMacroIdx { get; set; } = 1; public int MaxMacroCount { get; set; } = 5; + // CopyToMacroMate + public string MacroMateName { get; set; } = "Craftimizer"; + public string MacroMateParent { get; set; } = string.Empty; + // Add /nextmacro [down] public bool UseNextMacro { get; set; } @@ -156,6 +161,8 @@ public enum CopyType // For SND; Cannot use CopyToMacro public bool CombineMacro { get; set; } + + public bool ShowCopiedMessage { get; set; } = true; } [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] diff --git a/Craftimizer/Plugin.cs b/Craftimizer/Plugin.cs index d0cf449..5226aab 100644 --- a/Craftimizer/Plugin.cs +++ b/Craftimizer/Plugin.cs @@ -32,6 +32,7 @@ public sealed class Plugin : IDalamudPlugin public Hooks Hooks { get; } public Chat Chat { get; } public CommunityMacros CommunityMacros { get; } + public Ipc Ipc { get; } public AttributeCommandManager AttributeCommandManager { get; } public Plugin(IDalamudPluginInterface pluginInterface) @@ -44,6 +45,7 @@ public Plugin(IDalamudPluginInterface pluginInterface) Hooks = new(); Chat = new(); CommunityMacros = new(); + Ipc = new(); AttributeCommandManager = new(); var assembly = Assembly.GetExecutingAssembly(); diff --git a/Craftimizer/Service.cs b/Craftimizer/Service.cs index 3e3fb93..d0519bc 100644 --- a/Craftimizer/Service.cs +++ b/Craftimizer/Service.cs @@ -32,6 +32,7 @@ public sealed class Service public static WindowSystem WindowSystem => Plugin.WindowSystem; public static Chat Chat => Plugin.Chat; public static CommunityMacros CommunityMacros => Plugin.CommunityMacros; + public static Ipc Ipc => Plugin.Ipc; #pragma warning restore CS8618 internal static void Initialize(Plugin plugin, IDalamudPluginInterface iface) diff --git a/Craftimizer/Utils/Ipc.cs b/Craftimizer/Utils/Ipc.cs new file mode 100644 index 0000000..3b605ef --- /dev/null +++ b/Craftimizer/Utils/Ipc.cs @@ -0,0 +1,69 @@ +using Craftimizer.Plugin; +using Dalamud.Plugin; +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using DotNext.Reflection; +using DotNext.Collections.Generic; + +namespace Craftimizer.Utils; + +public sealed class Ipc +{ + [AttributeUsage(AttributeTargets.Property)] + private sealed class IPCCallAttribute(string? name) : Attribute + { + public string? Name { get; } = name; + } + + public Ipc() + { + foreach (var prop in typeof(Ipc).GetProperties(BindingFlags.Instance | BindingFlags.Public)) + { + if (prop.GetCustomAttribute() is not { } attr) + continue; + + if (prop.GetMethod is not { } getMethod) + throw new InvalidOperationException("Property must have a getter"); + + if (getMethod.GetCustomAttribute() is null) + throw new InvalidOperationException("Property must have an auto getter"); + + var type = prop.PropertyType; + + if (!typeof(Delegate).IsAssignableFrom(type)) + throw new InvalidOperationException("Property type must be a delegate"); + + if (type.GetMethod("Invoke") is not { } typeMethod) + throw new InvalidOperationException("Delegate type has no Invoke"); + + var returnsVoid = typeMethod.ReturnType == typeof(void); + + var propSubscriber = typeof(IDalamudPluginInterface).GetMethod("GetIpcSubscriber", typeMethod.GetParameters().Length + 1, [typeof(string)]); + if (propSubscriber is null) + throw new InvalidOperationException("GetIpcSubscriber method not found"); + + var callGateSubscriber = propSubscriber.MakeGenericMethod([.. typeMethod.GetParameterTypes(), returnsVoid ? typeof(int) : typeMethod.ReturnType]).Invoke(Service.PluginInterface, [attr.Name ?? prop.Name]); + + if (callGateSubscriber is null) + throw new InvalidOperationException("CallGateSubscriber is null"); + + var invokeFunc = callGateSubscriber.GetType().GetMethod(returnsVoid ? "InvokeAction" : "InvokeFunc"); + if (invokeFunc is null) + throw new InvalidOperationException("Subscriber Invoke method not found"); + + prop.SetValue(this, Delegate.CreateDelegate(type, callGateSubscriber, invokeFunc)); + + Log.Debug($"Bound {prop.Name} IPC to {type}"); + } + } + + [IPCCall("MacroMate.IsAvailable")] + public Func MacroMateIsAvailable { get; private set; } = null!; + + [IPCCall("MacroMate.CreateOrUpdateMacro")] + public Func MacroMateCreateMacro { get; private set; } = null!; + + [IPCCall("MacroMate.ValidateGroupPath")] + public Func MacroMateValidateGroupPath { get; private set; } = null!; +} diff --git a/Craftimizer/Utils/MacroCopy.cs b/Craftimizer/Utils/MacroCopy.cs index 8cce6be..066b0b0 100644 --- a/Craftimizer/Utils/MacroCopy.cs +++ b/Craftimizer/Utils/MacroCopy.cs @@ -30,57 +30,72 @@ public static void Copy(IReadOnlyList actions) return; } - var config = Service.Configuration.MacroCopy; + var macros = GetMacros(actions, Service.Configuration.MacroCopy); + + switch (Service.Configuration.MacroCopy.Type) + { + case MacroCopyConfiguration.CopyType.OpenWindow: + Service.Plugin.OpenMacroClipboard(macros); + break; + case MacroCopyConfiguration.CopyType.CopyToMacro: + CopyToMacro(macros); + break; + case MacroCopyConfiguration.CopyType.CopyToClipboard: + CopyToClipboard(macros); + break; + case MacroCopyConfiguration.CopyType.CopyToMacroMate: + CopyToMacroMate(macros[0]); + break; + } + } + + private static List GetMacros(IReadOnlyList actions, MacroCopyConfiguration config) + { + var mustSplit = (config.Type == MacroCopyConfiguration.CopyType.CopyToMacro || !config.CombineMacro) && config.Type != MacroCopyConfiguration.CopyType.CopyToMacroMate; + var macros = new List(); - var s = new List(); + + var m = new List(); + for (var i = 0; i < actions.Count; ++i) { - if (config.UseMacroLock && s.Count == 0) - { - s.Add("/mlock"); - } + var a = actions[i]; + var isLast = i == actions.Count - 1; + var isSecondLast = i == actions.Count - 2; - s.Add(GetActionCommand(actions[i], config)); - - if (config.Type == MacroCopyConfiguration.CopyType.CopyToMacro || !config.CombineMacro) + if (config.UseMacroLock && m.Count == 0) + m.Add("/mlock"); + + m.Add(GetActionCommand(a, config)); + + if (mustSplit && !isLast) { - if (i != actions.Count - 1 && (i != actions.Count - 2 || config.ForceNotification)) + var endLine = GetEndCommand(macros.Count, false, config); + + if (endLine != null && m.Count == MacroSize - 1) { - if (s.Count == MacroSize - 1) - { - if (GetEndCommand(macros.Count, false, config) is { } endCommand) - s.Add(endCommand); - } - if (s.Count == MacroSize) - { - macros.Add(string.Join(Environment.NewLine, s)); - s.Clear(); - } + if (!isSecondLast || config.ForceNotification) + m.Add(endLine); } } - } - if (s.Count > 0) - { - if (s.Count < MacroSize || (config.Type != MacroCopyConfiguration.CopyType.CopyToMacro && config.CombineMacro)) + + if (mustSplit && m.Count == MacroSize) { - if (GetEndCommand(macros.Count, true, config) is { } endCommand) - s.Add(endCommand); + macros.Add(string.Join(Environment.NewLine, m)); + m.Clear(); } - macros.Add(string.Join(Environment.NewLine, s)); } - switch (config.Type) + if (m.Count != MacroSize && m.Count != 0) { - case MacroCopyConfiguration.CopyType.OpenWindow: - Service.Plugin.OpenMacroClipboard(macros); - break; - case MacroCopyConfiguration.CopyType.CopyToMacro: - CopyToMacro(macros, config); - break; - case MacroCopyConfiguration.CopyType.CopyToClipboard: - CopyToClipboard(macros); - break; + if (GetEndCommand(macros.Count, true, config) is { } endLine) + m.Add(endLine); } + + if (m.Count != 0) + macros.Add(string.Join(Environment.NewLine, m)); + + return macros; } private static string GetActionCommand(ActionType action, MacroCopyConfiguration config) @@ -124,22 +139,27 @@ private static string GetActionCommand(ActionType action, MacroCopyConfiguration return null; } - private static void CopyToMacro(List macros, MacroCopyConfiguration config) + private static void CopyToMacro(List macros) { + var config = Service.Configuration.MacroCopy; + int i, macroIdx; for ( i = 0, macroIdx = config.StartMacroIdx; i < macros.Count && i < config.MaxMacroCount && macroIdx < 100; i++, macroIdx += config.CopyDown ? 10 : 1) - SetMacro(macroIdx, config.SharedMacro, macros[i]); + SetMacro(macroIdx, config.SharedMacro, macros[i], i + 1); - Service.Plugin.DisplayNotification(new() + if (config.ShowCopiedMessage) { - Content = i > 1 ? "Copied macro to User Macros." : $"Copied {i} macros to User Macros.", - MinimizedText = i > 1 ? "Copied macro" : $"Copied {i} macros", - Title = "Macro Copied", - Type = NotificationType.Success - }); + Service.Plugin.DisplayNotification(new() + { + Content = i > 1 ? "Copied macro to User Macros." : $"Copied {i} macros to User Macros.", + MinimizedText = i > 1 ? "Copied macro" : $"Copied {i} macros", + Title = "Macro Copied", + Type = NotificationType.Success + }); + } if (i < macros.Count) { Service.Plugin.OpenMacroClipboard(macros); @@ -154,28 +174,85 @@ private static void CopyToMacro(List macros, MacroCopyConfiguration conf } } - private static unsafe void SetMacro(int idx, bool isShared, string macroText) + private static unsafe void SetMacro(int idx, bool isShared, string macroText, int macroIdx) { if (idx >= 100 || idx < 0) throw new ArgumentOutOfRangeException(nameof(idx), "Macro index must be between 0 and 99"); + var set = isShared ? 1u : 0u; + var module = RaptureMacroModule.Instance(); - var macro = module->GetMacro(isShared ? 1u : 0u, (uint)idx); + var macro = module->GetMacro(set, (uint)idx); + if (macro->IsEmpty()) + { + macro->Name.SetString($"Craftimizer Macro {macroIdx}"); + macro->SetIcon((uint)(macroIdx > 10 ? 66161 : (66161 + macroIdx))); + } var text = Utf8String.FromString(macroText.ReplaceLineEndings("\n")); module->ReplaceMacroLines(macro, text); text->Dtor(); IMemorySpace.Free(text); + + RaptureHotbarModule.Instance()->ReloadMacroSlots((byte)set, (byte)idx); } private static void CopyToClipboard(List macros) { ImGui.SetClipboardText(string.Join(Environment.NewLine + Environment.NewLine, macros)); - Service.Plugin.DisplayNotification(new() + if (Service.Configuration.MacroCopy.ShowCopiedMessage) + { + Service.Plugin.DisplayNotification(new() + { + Content = macros.Count == 1 ? "Copied macro to clipboard." : $"Copied {macros.Count} macros to clipboard.", + MinimizedText = macros.Count == 1 ? "Copied macro" : $"Copied {macros.Count} macros", + Title = "Macro Copied", + Type = NotificationType.Success + }); + } + } + + private static void CopyToMacroMate(string macro) + { + if (!Service.Ipc.MacroMateIsAvailable()) { - Content = macros.Count > 1 ? "Copied macro to clipboard." : $"Copied {macros.Count} macros to clipboard.", - MinimizedText = macros.Count > 1 ? "Copied macro" : $"Copied {macros.Count} macros", - Title = "Macro Copied", - Type = NotificationType.Success - }); + Service.Plugin.DisplayNotification(new() + { + Content = "Please check if it installed and enabled.", + MinimizedText = "Macro Mate is unavailable", + Title = "Macro Mate Unavailable", + Type = NotificationType.Error + }); + return; + } + + var parentPath = Service.Configuration.MacroCopy.MacroMateParent; + if (string.IsNullOrWhiteSpace(parentPath)) + parentPath = "/"; + + var (isValidParent, parentError) = Service.Ipc.MacroMateValidateGroupPath(parentPath); + if (!isValidParent) + { + Service.Plugin.DisplayNotification(new() + { + Content = parentError!, + MinimizedText = parentError, + Title = "Macro Mate Invalid Parent", + Type = NotificationType.Error + }); + return; + } + + Service.Ipc.MacroMateCreateMacro(Service.Configuration.MacroCopy.MacroMateName, macro, parentPath, null); + + if (Service.Configuration.MacroCopy.ShowCopiedMessage) + { + Service.Plugin.DisplayNotification(new() + { + Content = "Copied macro to Macro Mate.", + MinimizedText = "Copied macro", + Title = "Macro Copied", + Type = NotificationType.Success + }); + } } } diff --git a/Craftimizer/Windows/MacroClipboard.cs b/Craftimizer/Windows/MacroClipboard.cs index 0757e7d..0714e6e 100644 --- a/Craftimizer/Windows/MacroClipboard.cs +++ b/Craftimizer/Windows/MacroClipboard.cs @@ -39,7 +39,7 @@ public override void Draw() private void DrawMacro(int idx, string macro) { using var id = ImRaii.PushId(idx); - using var panel = ImRaii2.GroupPanel($"Macro {idx + 1}", -1, out var availWidth); + using var panel = ImRaii2.GroupPanel(Macros.Count == 1 ? "Macro" : $"Macro {idx + 1}", -1, out var availWidth); var cursor = ImGui.GetCursorPos(); @@ -56,13 +56,16 @@ private void DrawMacro(int idx, string macro) if (buttonClicked) { ImGui.SetClipboardText(macro); - Service.Plugin.DisplayNotification(new() + if (Service.Configuration.MacroCopy.ShowCopiedMessage) { - Content = $"Macro {idx + 1} copied to clipboard.", - MinimizedText = $"Copied macro {idx + 1}", - Title = "Macro Copied", - Type = NotificationType.Success - }); + Service.Plugin.DisplayNotification(new() + { + Content = Macros.Count == 1 ? "Copied macro to clipboard." : $"Copied macro {idx + 1} to clipboard.", + MinimizedText = Macros.Count == 1 ? "Copied macro" : $"Copied macro {idx + 1}", + Title = "Macro Copied", + Type = NotificationType.Success + }); + } } } if (buttonHovered) diff --git a/Craftimizer/Windows/Settings.cs b/Craftimizer/Windows/Settings.cs index b8a1ac0..7c14b3d 100644 --- a/Craftimizer/Windows/Settings.cs +++ b/Craftimizer/Windows/Settings.cs @@ -93,6 +93,22 @@ private static void DrawOption(string label, string tooltip, T value, T min, ImGuiUtils.TooltipWrapped(tooltip); } + private static void DrawOption(string label, string tooltip, string value, Action setter, ref bool isDirty) + { + ImGui.SetNextItemWidth(OptionWidth); + var text = value.ToString(); + if (ImGui.InputText(label, ref text, 255, ImGuiInputTextFlags.AutoSelectAll)) + { + if (value != text) + { + setter(text); + isDirty = true; + } + } + if (ImGui.IsItemHovered()) + ImGuiUtils.TooltipWrapped(tooltip); + } + private static void DrawOption(string label, string tooltip, Func getName, Func getTooltip, T value, Action setter, ref bool isDirty) where T : struct, Enum { ImGui.SetNextItemWidth(OptionWidth); @@ -147,6 +163,7 @@ private static string GetCopyTypeName(MacroCopyConfiguration.CopyType type) => MacroCopyConfiguration.CopyType.OpenWindow => "Open a Window", MacroCopyConfiguration.CopyType.CopyToMacro => "Copy to Macros", MacroCopyConfiguration.CopyType.CopyToClipboard => "Copy to Clipboard", + MacroCopyConfiguration.CopyType.CopyToMacroMate => "Copy to Macro Mate", _ => "Unknown", }; @@ -157,6 +174,7 @@ private static string GetCopyTypeTooltip(MacroCopyConfiguration.CopyType type) = "Copy, view, and choose at your own leisure.", MacroCopyConfiguration.CopyType.CopyToMacro => "Copy directly to the game's macro system.", MacroCopyConfiguration.CopyType.CopyToClipboard => "Copy to your clipboard. Macros are separated by a blank line.", + MacroCopyConfiguration.CopyType.CopyToMacroMate => "Copy directly to a Macro Mate macro. Requires the Macro Mate plugin.", _ => "Unknown" }; @@ -270,26 +288,55 @@ ref isDirty ref isDirty ); } + else if (Config.MacroCopy.Type == MacroCopyConfiguration.CopyType.CopyToMacroMate) + { + DrawOption( + "Macro Name", + "The name of the macro to be created or updated in Macro Mate.", + Config.MacroCopy.MacroMateName, + v => Config.MacroCopy.MacroMateName = v, + ref isDirty + ); + + DrawOption( + "Macro Parent", + "The name of the parent group of the new macro. Leave blank or \"/\" if there is none.", + Config.MacroCopy.MacroMateParent, + v => Config.MacroCopy.MacroMateParent = v, + ref isDirty + ); + } DrawOption( - "Use Macro Chain", - "Replaces the last step with /nextmacro to run the next macro " + - "automatically. Overrides the Intermediate Notification Sound.", - Config.MacroCopy.UseNextMacro, - v => Config.MacroCopy.UseNextMacro = v, + "Show Copied Message", + "Shows a notification in the bottom right when a macro is copied successfully.", + Config.MacroCopy.ShowCopiedMessage, + v => Config.MacroCopy.ShowCopiedMessage = v, ref isDirty ); - if (Config.MacroCopy.UseNextMacro && !Service.PluginInterface.InstalledPlugins.Any(p => p.IsLoaded && string.Equals(p.InternalName, "MacroChain", StringComparison.Ordinal))) + if (Config.MacroCopy.Type != MacroCopyConfiguration.CopyType.CopyToMacroMate) { - ImGui.SameLine(); - using (var color = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudOrange)) + DrawOption( + "Use Macro Chain", + "Replaces the last step with /nextmacro to run the next macro " + + "automatically. Overrides the Intermediate Notification Sound.", + Config.MacroCopy.UseNextMacro, + v => Config.MacroCopy.UseNextMacro = v, + ref isDirty + ); + + if (Config.MacroCopy.UseNextMacro && !Service.PluginInterface.InstalledPlugins.Any(p => p.IsLoaded && string.Equals(p.InternalName, "MacroChain", StringComparison.Ordinal))) { - using var font = ImRaii.PushFont(UiBuilder.IconFont); - ImGui.TextUnformatted(FontAwesomeIcon.ExclamationCircle.ToIconString()); + ImGui.SameLine(); + using (var color = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudOrange)) + { + using var font = ImRaii.PushFont(UiBuilder.IconFont); + ImGui.TextUnformatted(FontAwesomeIcon.ExclamationCircle.ToIconString()); + } + if (ImGui.IsItemHovered()) + ImGuiUtils.Tooltip("Macro Chain is not installed"); } - if (ImGui.IsItemHovered()) - ImGuiUtils.Tooltip("Macro Chain is not installed"); } DrawOption( @@ -311,8 +358,7 @@ ref isDirty if (Config.MacroCopy.AddNotification) { - var isForceUseful = Config.MacroCopy.Type == MacroCopyConfiguration.CopyType.CopyToMacro || !Config.MacroCopy.CombineMacro; - using (var d = ImRaii.Disabled(!isForceUseful)) + if ((Config.MacroCopy.Type == MacroCopyConfiguration.CopyType.CopyToMacro || !Config.MacroCopy.CombineMacro) && Config.MacroCopy.Type != MacroCopyConfiguration.CopyType.CopyToMacroMate) { DrawOption( "Force Notification", @@ -323,8 +369,6 @@ ref isDirty ref isDirty ); } - if (!isForceUseful && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) - ImGuiUtils.Tooltip("Only useful when Combine Macro is off"); DrawOption( "Add Notification Sound", @@ -336,7 +380,7 @@ ref isDirty if (Config.MacroCopy.AddNotificationSound) { - using (var d = ImRaii.Disabled(Config.MacroCopy.UseNextMacro)) + if (!Config.MacroCopy.UseNextMacro && Config.MacroCopy.Type != MacroCopyConfiguration.CopyType.CopyToMacroMate) { DrawOption( "Intermediate Notification Sound", @@ -379,13 +423,16 @@ ref isDirty ref isDirty ); - DrawOption( - "Combine Macro", - "Doesn't split the macro into smaller macros.", - Config.MacroCopy.CombineMacro, - v => Config.MacroCopy.CombineMacro = v, - ref isDirty - ); + if (Config.MacroCopy.Type != MacroCopyConfiguration.CopyType.CopyToMacroMate) + { + DrawOption( + "Combine Macro", + "Doesn't split the macro into smaller macros.", + Config.MacroCopy.CombineMacro, + v => Config.MacroCopy.CombineMacro = v, + ref isDirty + ); + } } }