MacroMate, reworked macro copying, extra copying settings

This commit is contained in:
Asriel Camora
2024-07-27 01:32:47 -07:00
parent f2d03730ce
commit 95371739c4
7 changed files with 293 additions and 87 deletions
+7
View File
@@ -127,6 +127,7 @@ public class MacroCopyConfiguration
OpenWindow, // useful for big macros OpenWindow, // useful for big macros
CopyToMacro, // (add option for down or right) (max macro count; open copy-paste window if too much) CopyToMacro, // (add option for down or right) (max macro count; open copy-paste window if too much)
CopyToClipboard, CopyToClipboard,
CopyToMacroMate
} }
public CopyType Type { get; set; } = CopyType.OpenWindow; public CopyType Type { get; set; } = CopyType.OpenWindow;
@@ -137,6 +138,10 @@ public class MacroCopyConfiguration
public int StartMacroIdx { get; set; } = 1; public int StartMacroIdx { get; set; } = 1;
public int MaxMacroCount { get; set; } = 5; public int MaxMacroCount { get; set; } = 5;
// CopyToMacroMate
public string MacroMateName { get; set; } = "Craftimizer";
public string MacroMateParent { get; set; } = string.Empty;
// Add /nextmacro [down] // Add /nextmacro [down]
public bool UseNextMacro { get; set; } public bool UseNextMacro { get; set; }
@@ -156,6 +161,8 @@ public class MacroCopyConfiguration
// For SND; Cannot use CopyToMacro // For SND; Cannot use CopyToMacro
public bool CombineMacro { get; set; } public bool CombineMacro { get; set; }
public bool ShowCopiedMessage { get; set; } = true;
} }
[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
+2
View File
@@ -32,6 +32,7 @@ public sealed class Plugin : IDalamudPlugin
public Hooks Hooks { get; } public Hooks Hooks { get; }
public Chat Chat { get; } public Chat Chat { get; }
public CommunityMacros CommunityMacros { get; } public CommunityMacros CommunityMacros { get; }
public Ipc Ipc { get; }
public AttributeCommandManager AttributeCommandManager { get; } public AttributeCommandManager AttributeCommandManager { get; }
public Plugin(IDalamudPluginInterface pluginInterface) public Plugin(IDalamudPluginInterface pluginInterface)
@@ -44,6 +45,7 @@ public sealed class Plugin : IDalamudPlugin
Hooks = new(); Hooks = new();
Chat = new(); Chat = new();
CommunityMacros = new(); CommunityMacros = new();
Ipc = new();
AttributeCommandManager = new(); AttributeCommandManager = new();
var assembly = Assembly.GetExecutingAssembly(); var assembly = Assembly.GetExecutingAssembly();
+1
View File
@@ -32,6 +32,7 @@ public sealed class Service
public static WindowSystem WindowSystem => Plugin.WindowSystem; public static WindowSystem WindowSystem => Plugin.WindowSystem;
public static Chat Chat => Plugin.Chat; public static Chat Chat => Plugin.Chat;
public static CommunityMacros CommunityMacros => Plugin.CommunityMacros; public static CommunityMacros CommunityMacros => Plugin.CommunityMacros;
public static Ipc Ipc => Plugin.Ipc;
#pragma warning restore CS8618 #pragma warning restore CS8618
internal static void Initialize(Plugin plugin, IDalamudPluginInterface iface) internal static void Initialize(Plugin plugin, IDalamudPluginInterface iface)
+69
View File
@@ -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<IPCCallAttribute>() is not { } attr)
continue;
if (prop.GetMethod is not { } getMethod)
throw new InvalidOperationException("Property must have a getter");
if (getMethod.GetCustomAttribute<CompilerGeneratedAttribute>() 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<bool> MacroMateIsAvailable { get; private set; } = null!;
[IPCCall("MacroMate.CreateOrUpdateMacro")]
public Func<string, string, string?, uint?, bool> MacroMateCreateMacro { get; private set; } = null!;
[IPCCall("MacroMate.ValidateGroupPath")]
public Func<string, (bool, string?)> MacroMateValidateGroupPath { get; private set; } = null!;
}
+123 -46
View File
@@ -30,59 +30,74 @@ public static class MacroCopy
return; return;
} }
var config = Service.Configuration.MacroCopy; var macros = GetMacros(actions, Service.Configuration.MacroCopy);
var macros = new List<string>();
var s = new List<string>();
for (var i = 0; i < actions.Count; ++i)
{
if (config.UseMacroLock && s.Count == 0)
{
s.Add("/mlock");
}
s.Add(GetActionCommand(actions[i], config)); switch (Service.Configuration.MacroCopy.Type)
if (config.Type == MacroCopyConfiguration.CopyType.CopyToMacro || !config.CombineMacro)
{
if (i != actions.Count - 1 && (i != actions.Count - 2 || config.ForceNotification))
{
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 (s.Count > 0)
{
if (s.Count < MacroSize || (config.Type != MacroCopyConfiguration.CopyType.CopyToMacro && config.CombineMacro))
{
if (GetEndCommand(macros.Count, true, config) is { } endCommand)
s.Add(endCommand);
}
macros.Add(string.Join(Environment.NewLine, s));
}
switch (config.Type)
{ {
case MacroCopyConfiguration.CopyType.OpenWindow: case MacroCopyConfiguration.CopyType.OpenWindow:
Service.Plugin.OpenMacroClipboard(macros); Service.Plugin.OpenMacroClipboard(macros);
break; break;
case MacroCopyConfiguration.CopyType.CopyToMacro: case MacroCopyConfiguration.CopyType.CopyToMacro:
CopyToMacro(macros, config); CopyToMacro(macros);
break; break;
case MacroCopyConfiguration.CopyType.CopyToClipboard: case MacroCopyConfiguration.CopyType.CopyToClipboard:
CopyToClipboard(macros); CopyToClipboard(macros);
break; break;
case MacroCopyConfiguration.CopyType.CopyToMacroMate:
CopyToMacroMate(macros[0]);
break;
} }
} }
private static List<string> GetMacros(IReadOnlyList<ActionType> actions, MacroCopyConfiguration config)
{
var mustSplit = (config.Type == MacroCopyConfiguration.CopyType.CopyToMacro || !config.CombineMacro) && config.Type != MacroCopyConfiguration.CopyType.CopyToMacroMate;
var macros = new List<string>();
var m = new List<string>();
for (var i = 0; i < actions.Count; ++i)
{
var a = actions[i];
var isLast = i == actions.Count - 1;
var isSecondLast = i == actions.Count - 2;
if (config.UseMacroLock && m.Count == 0)
m.Add("/mlock");
m.Add(GetActionCommand(a, config));
if (mustSplit && !isLast)
{
var endLine = GetEndCommand(macros.Count, false, config);
if (endLine != null && m.Count == MacroSize - 1)
{
if (!isSecondLast || config.ForceNotification)
m.Add(endLine);
}
}
if (mustSplit && m.Count == MacroSize)
{
macros.Add(string.Join(Environment.NewLine, m));
m.Clear();
}
}
if (m.Count != MacroSize && m.Count != 0)
{
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) private static string GetActionCommand(ActionType action, MacroCopyConfiguration config)
{ {
var actionBase = action.Base(); var actionBase = action.Base();
@@ -124,15 +139,19 @@ public static class MacroCopy
return null; return null;
} }
private static void CopyToMacro(List<string> macros, MacroCopyConfiguration config) private static void CopyToMacro(List<string> macros)
{ {
var config = Service.Configuration.MacroCopy;
int i, macroIdx; int i, macroIdx;
for ( for (
i = 0, macroIdx = config.StartMacroIdx; i = 0, macroIdx = config.StartMacroIdx;
i < macros.Count && i < config.MaxMacroCount && macroIdx < 100; i < macros.Count && i < config.MaxMacroCount && macroIdx < 100;
i++, macroIdx += config.CopyDown ? 10 : 1) i++, macroIdx += config.CopyDown ? 10 : 1)
SetMacro(macroIdx, config.SharedMacro, macros[i]); SetMacro(macroIdx, config.SharedMacro, macros[i], i + 1);
if (config.ShowCopiedMessage)
{
Service.Plugin.DisplayNotification(new() Service.Plugin.DisplayNotification(new()
{ {
Content = i > 1 ? "Copied macro to User Macros." : $"Copied {i} macros to User Macros.", Content = i > 1 ? "Copied macro to User Macros." : $"Copied {i} macros to User Macros.",
@@ -140,6 +159,7 @@ public static class MacroCopy
Title = "Macro Copied", Title = "Macro Copied",
Type = NotificationType.Success Type = NotificationType.Success
}); });
}
if (i < macros.Count) if (i < macros.Count)
{ {
Service.Plugin.OpenMacroClipboard(macros); Service.Plugin.OpenMacroClipboard(macros);
@@ -154,28 +174,85 @@ public static class MacroCopy
} }
} }
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) if (idx >= 100 || idx < 0)
throw new ArgumentOutOfRangeException(nameof(idx), "Macro index must be between 0 and 99"); throw new ArgumentOutOfRangeException(nameof(idx), "Macro index must be between 0 and 99");
var set = isShared ? 1u : 0u;
var module = RaptureMacroModule.Instance(); 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")); var text = Utf8String.FromString(macroText.ReplaceLineEndings("\n"));
module->ReplaceMacroLines(macro, text); module->ReplaceMacroLines(macro, text);
text->Dtor(); text->Dtor();
IMemorySpace.Free(text); IMemorySpace.Free(text);
RaptureHotbarModule.Instance()->ReloadMacroSlots((byte)set, (byte)idx);
} }
private static void CopyToClipboard(List<string> macros) private static void CopyToClipboard(List<string> macros)
{ {
ImGui.SetClipboardText(string.Join(Environment.NewLine + Environment.NewLine, macros)); ImGui.SetClipboardText(string.Join(Environment.NewLine + Environment.NewLine, macros));
if (Service.Configuration.MacroCopy.ShowCopiedMessage)
{
Service.Plugin.DisplayNotification(new() Service.Plugin.DisplayNotification(new()
{ {
Content = macros.Count > 1 ? "Copied macro to clipboard." : $"Copied {macros.Count} macros to clipboard.", Content = macros.Count == 1 ? "Copied macro to clipboard." : $"Copied {macros.Count} macros to clipboard.",
MinimizedText = macros.Count > 1 ? "Copied macro" : $"Copied {macros.Count} macros", MinimizedText = macros.Count == 1 ? "Copied macro" : $"Copied {macros.Count} macros",
Title = "Macro Copied", Title = "Macro Copied",
Type = NotificationType.Success Type = NotificationType.Success
}); });
} }
} }
private static void CopyToMacroMate(string macro)
{
if (!Service.Ipc.MacroMateIsAvailable())
{
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
});
}
}
}
+6 -3
View File
@@ -39,7 +39,7 @@ public sealed class MacroClipboard : Window, IDisposable
private void DrawMacro(int idx, string macro) private void DrawMacro(int idx, string macro)
{ {
using var id = ImRaii.PushId(idx); 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(); var cursor = ImGui.GetCursorPos();
@@ -56,15 +56,18 @@ public sealed class MacroClipboard : Window, IDisposable
if (buttonClicked) if (buttonClicked)
{ {
ImGui.SetClipboardText(macro); ImGui.SetClipboardText(macro);
if (Service.Configuration.MacroCopy.ShowCopiedMessage)
{
Service.Plugin.DisplayNotification(new() Service.Plugin.DisplayNotification(new()
{ {
Content = $"Macro {idx + 1} copied to clipboard.", Content = Macros.Count == 1 ? "Copied macro to clipboard." : $"Copied macro {idx + 1} to clipboard.",
MinimizedText = $"Copied macro {idx + 1}", MinimizedText = Macros.Count == 1 ? "Copied macro" : $"Copied macro {idx + 1}",
Title = "Macro Copied", Title = "Macro Copied",
Type = NotificationType.Success Type = NotificationType.Success
}); });
} }
} }
}
if (buttonHovered) if (buttonHovered)
ImGuiUtils.Tooltip("Copy to Clipboard"); ImGuiUtils.Tooltip("Copy to Clipboard");
+52 -5
View File
@@ -93,6 +93,22 @@ public sealed class Settings : Window, IDisposable
ImGuiUtils.TooltipWrapped(tooltip); ImGuiUtils.TooltipWrapped(tooltip);
} }
private static void DrawOption(string label, string tooltip, string value, Action<string> 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<T>(string label, string tooltip, Func<T, string> getName, Func<T, string> getTooltip, T value, Action<T> setter, ref bool isDirty) where T : struct, Enum private static void DrawOption<T>(string label, string tooltip, Func<T, string> getName, Func<T, string> getTooltip, T value, Action<T> setter, ref bool isDirty) where T : struct, Enum
{ {
ImGui.SetNextItemWidth(OptionWidth); ImGui.SetNextItemWidth(OptionWidth);
@@ -147,6 +163,7 @@ public sealed class Settings : Window, IDisposable
MacroCopyConfiguration.CopyType.OpenWindow => "Open a Window", MacroCopyConfiguration.CopyType.OpenWindow => "Open a Window",
MacroCopyConfiguration.CopyType.CopyToMacro => "Copy to Macros", MacroCopyConfiguration.CopyType.CopyToMacro => "Copy to Macros",
MacroCopyConfiguration.CopyType.CopyToClipboard => "Copy to Clipboard", MacroCopyConfiguration.CopyType.CopyToClipboard => "Copy to Clipboard",
MacroCopyConfiguration.CopyType.CopyToMacroMate => "Copy to Macro Mate",
_ => "Unknown", _ => "Unknown",
}; };
@@ -157,6 +174,7 @@ public sealed class Settings : Window, IDisposable
"Copy, view, and choose at your own leisure.", "Copy, view, and choose at your own leisure.",
MacroCopyConfiguration.CopyType.CopyToMacro => "Copy directly to the game's macro system.", 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.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" _ => "Unknown"
}; };
@@ -270,7 +288,35 @@ public sealed class Settings : Window, IDisposable
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(
"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.Type != MacroCopyConfiguration.CopyType.CopyToMacroMate)
{
DrawOption( DrawOption(
"Use Macro Chain", "Use Macro Chain",
"Replaces the last step with /nextmacro to run the next macro " + "Replaces the last step with /nextmacro to run the next macro " +
@@ -291,6 +337,7 @@ public sealed class Settings : Window, IDisposable
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
ImGuiUtils.Tooltip("Macro Chain is not installed"); ImGuiUtils.Tooltip("Macro Chain is not installed");
} }
}
DrawOption( DrawOption(
"Add Macro Lock", "Add Macro Lock",
@@ -311,8 +358,7 @@ public sealed class Settings : Window, IDisposable
if (Config.MacroCopy.AddNotification) if (Config.MacroCopy.AddNotification)
{ {
var isForceUseful = Config.MacroCopy.Type == MacroCopyConfiguration.CopyType.CopyToMacro || !Config.MacroCopy.CombineMacro; if ((Config.MacroCopy.Type == MacroCopyConfiguration.CopyType.CopyToMacro || !Config.MacroCopy.CombineMacro) && Config.MacroCopy.Type != MacroCopyConfiguration.CopyType.CopyToMacroMate)
using (var d = ImRaii.Disabled(!isForceUseful))
{ {
DrawOption( DrawOption(
"Force Notification", "Force Notification",
@@ -323,8 +369,6 @@ public sealed class Settings : Window, IDisposable
ref isDirty ref isDirty
); );
} }
if (!isForceUseful && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
ImGuiUtils.Tooltip("Only useful when Combine Macro is off");
DrawOption( DrawOption(
"Add Notification Sound", "Add Notification Sound",
@@ -336,7 +380,7 @@ public sealed class Settings : Window, IDisposable
if (Config.MacroCopy.AddNotificationSound) if (Config.MacroCopy.AddNotificationSound)
{ {
using (var d = ImRaii.Disabled(Config.MacroCopy.UseNextMacro)) if (!Config.MacroCopy.UseNextMacro && Config.MacroCopy.Type != MacroCopyConfiguration.CopyType.CopyToMacroMate)
{ {
DrawOption( DrawOption(
"Intermediate Notification Sound", "Intermediate Notification Sound",
@@ -379,6 +423,8 @@ public sealed class Settings : Window, IDisposable
ref isDirty ref isDirty
); );
if (Config.MacroCopy.Type != MacroCopyConfiguration.CopyType.CopyToMacroMate)
{
DrawOption( DrawOption(
"Combine Macro", "Combine Macro",
"Doesn't split the macro into smaller macros.", "Doesn't split the macro into smaller macros.",
@@ -388,6 +434,7 @@ public sealed class Settings : Window, IDisposable
); );
} }
} }
}
if (isDirty) if (isDirty)
Config.Save(); Config.Save();