refactor: rename to Macrology and clean up
This commit is contained in:
parent
9ba5cf3070
commit
f259780c0c
203
.editorconfig
203
.editorconfig
|
@ -1,203 +0,0 @@
|
|||
# Remove the line below if you want to inherit .editorconfig settings from higher directories
|
||||
root = true
|
||||
|
||||
# C# files
|
||||
[*.{cs,json}]
|
||||
|
||||
#### Core EditorConfig Options ####
|
||||
|
||||
# Indentation and spacing
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
tab_width = 4
|
||||
|
||||
# New line preferences
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
[*.cs]
|
||||
|
||||
#### .NET Coding Conventions ####
|
||||
|
||||
# Organize usings
|
||||
dotnet_separate_import_directive_groups = false
|
||||
dotnet_sort_system_directives_first = false
|
||||
file_header_template = unset
|
||||
|
||||
# this. and Me. preferences
|
||||
dotnet_style_qualification_for_event = true:silent
|
||||
dotnet_style_qualification_for_field = true:silent
|
||||
dotnet_style_qualification_for_method = true:silent
|
||||
dotnet_style_qualification_for_property = true:silent
|
||||
|
||||
# Language keywords vs BCL types preferences
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
|
||||
dotnet_style_predefined_type_for_member_access = true:silent
|
||||
|
||||
# Parentheses preferences
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
|
||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
|
||||
|
||||
# Modifier preferences
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
|
||||
|
||||
# Expression-level preferences
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||
dotnet_style_prefer_auto_properties = true:silent
|
||||
dotnet_style_prefer_compound_assignment = true:suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
||||
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
||||
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
||||
|
||||
# Field preferences
|
||||
dotnet_style_readonly_field = true:suggestion
|
||||
|
||||
# Parameter preferences
|
||||
dotnet_code_quality_unused_parameters = all:suggestion
|
||||
|
||||
#### C# Coding Conventions ####
|
||||
|
||||
# var preferences
|
||||
csharp_style_var_elsewhere = false:silent
|
||||
csharp_style_var_for_built_in_types = false:silent
|
||||
csharp_style_var_when_type_is_apparent = false:silent
|
||||
|
||||
# Expression-bodied members
|
||||
csharp_style_expression_bodied_accessors = true:silent
|
||||
csharp_style_expression_bodied_constructors = false:silent
|
||||
csharp_style_expression_bodied_indexers = true:silent
|
||||
csharp_style_expression_bodied_lambdas = true:silent
|
||||
csharp_style_expression_bodied_local_functions = false:silent
|
||||
csharp_style_expression_bodied_methods = false:silent
|
||||
csharp_style_expression_bodied_operators = false:silent
|
||||
csharp_style_expression_bodied_properties = true:silent
|
||||
|
||||
# Pattern matching preferences
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_prefer_switch_expression = true:suggestion
|
||||
|
||||
# Null-checking preferences
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
|
||||
# Modifier preferences
|
||||
csharp_prefer_static_local_function = true:suggestion
|
||||
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
|
||||
|
||||
# Code-block preferences
|
||||
csharp_prefer_braces = true:error
|
||||
csharp_prefer_simple_using_statement = true:suggestion
|
||||
|
||||
# Expression-level preferences
|
||||
csharp_prefer_simple_default_expression = true:suggestion
|
||||
csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
csharp_style_pattern_local_over_anonymous_function = true:suggestion
|
||||
csharp_style_prefer_index_operator = true:suggestion
|
||||
csharp_style_prefer_range_operator = true:suggestion
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
|
||||
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
||||
|
||||
# 'using' directive preferences
|
||||
csharp_using_directive_placement = outside_namespace:silent
|
||||
|
||||
#### C# Formatting Rules ####
|
||||
|
||||
# New line preferences
|
||||
csharp_new_line_before_catch = false
|
||||
csharp_new_line_before_else = false
|
||||
csharp_new_line_before_finally = false
|
||||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
csharp_new_line_before_members_in_object_initializers = true
|
||||
csharp_new_line_before_open_brace = none
|
||||
csharp_new_line_between_query_expression_clauses = true
|
||||
|
||||
# Indentation preferences
|
||||
csharp_indent_block_contents = true
|
||||
csharp_indent_braces = false
|
||||
csharp_indent_case_contents = true
|
||||
csharp_indent_case_contents_when_block = true
|
||||
csharp_indent_labels = one_less_than_current
|
||||
csharp_indent_switch_labels = true
|
||||
|
||||
# Space preferences
|
||||
csharp_space_after_cast = false
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
csharp_space_after_comma = true
|
||||
csharp_space_after_dot = false
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_after_semicolon_in_for_statement = true
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_space_around_declaration_statements = false
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
csharp_space_before_comma = false
|
||||
csharp_space_before_dot = false
|
||||
csharp_space_before_open_square_brackets = false
|
||||
csharp_space_before_semicolon_in_for_statement = false
|
||||
csharp_space_between_empty_square_brackets = false
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = false
|
||||
csharp_space_between_square_brackets = false
|
||||
|
||||
# Wrapping preferences
|
||||
csharp_preserve_single_line_blocks = true
|
||||
csharp_preserve_single_line_statements = true
|
||||
|
||||
#### Naming styles ####
|
||||
|
||||
# Naming rules
|
||||
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||
|
||||
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||
|
||||
# Symbol specifications
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.interface.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.types.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||
|
||||
# Naming styles
|
||||
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||
dotnet_naming_style.begins_with_i.required_suffix =
|
||||
dotnet_naming_style.begins_with_i.word_separator =
|
||||
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"Author": "ascclemens",
|
||||
"Name": "Custom Commands and Macro Macros",
|
||||
"Description": "Adds support for custom commands and better macros. /ccmm",
|
||||
"InternalName": "CCMM",
|
||||
"AssemblyVersion": "0.1.0",
|
||||
"RepoUrl": "https://sr.ht/~jkcclemens/CCMM",
|
||||
"ApplicableVersion": "any",
|
||||
"DalamudApiLevel": 1
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
using Dalamud.Plugin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace CCMM {
|
||||
public class Commands {
|
||||
private readonly CCMMPlugin plugin;
|
||||
|
||||
public static readonly IReadOnlyDictionary<string, string> COMMANDS = new Dictionary<string, string> {
|
||||
["/ccmm"] = "Open the CCMM interface",
|
||||
["/mmacro"] = "Execute a CCMM macro",
|
||||
["/mmcancel"] = "Cancel the first CCMM macro of a given type or all if \"all\" is passed",
|
||||
};
|
||||
|
||||
public Commands(CCMMPlugin plugin) {
|
||||
this.plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "CCMMPlugin cannot be null");
|
||||
}
|
||||
|
||||
public void OnCommand(string command, string args) {
|
||||
switch (command) {
|
||||
case "/ccmm":
|
||||
this.OnMainCommand();
|
||||
break;
|
||||
case "/mmacro":
|
||||
this.OnMacroCommand(args);
|
||||
break;
|
||||
case "/mmcancel":
|
||||
this.OnMacroCancelCommand(args);
|
||||
break;
|
||||
default:
|
||||
this.plugin.Interface.Framework.Gui.Chat.PrintError($"The command {command} was passed to CCMM, but there is no handler available.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMainCommand() {
|
||||
this.plugin.Ui.SettingsVisible = !this.plugin.Ui.SettingsVisible;
|
||||
}
|
||||
|
||||
private void OnMacroCommand(string args) {
|
||||
string first = args.Trim().Split(' ').FirstOrDefault() ?? "";
|
||||
if (!Guid.TryParse(first, out Guid id)) {
|
||||
this.plugin.Interface.Framework.Gui.Chat.PrintError("First argument must be the UUID of the macro to execute.");
|
||||
return;
|
||||
}
|
||||
Macro macro = this.plugin.Config.FindMacro(id);
|
||||
if (macro == null) {
|
||||
this.plugin.Interface.Framework.Gui.Chat.PrintError($"No macro with ID {id} found.");
|
||||
return;
|
||||
}
|
||||
this.plugin.MacroHandler.SpawnMacro(macro);
|
||||
}
|
||||
|
||||
private void OnMacroCancelCommand(string args) {
|
||||
string first = args.Trim().Split(' ').FirstOrDefault() ?? "";
|
||||
if (first == "all") {
|
||||
foreach (Guid running in this.plugin.MacroHandler.Running.Keys) {
|
||||
this.plugin.MacroHandler.CancelMacro(running);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!Guid.TryParse(first, out Guid id)) {
|
||||
this.plugin.Interface.Framework.Gui.Chat.PrintError("First argument must either be \"all\" or the UUID of the macro to cancel.");
|
||||
return;
|
||||
}
|
||||
Macro macro = this.plugin.Config.FindMacro(id);
|
||||
if (macro == null) {
|
||||
this.plugin.Interface.Framework.Gui.Chat.PrintError($"No macro with ID {id} found.");
|
||||
return;
|
||||
}
|
||||
KeyValuePair<Guid, Macro> entry = this.plugin.MacroHandler.Running.FirstOrDefault(e => e.Value.Id == id);
|
||||
if (entry.Value == null) {
|
||||
this.plugin.Interface.Framework.Gui.Chat.PrintError($"That macro is not running.");
|
||||
return;
|
||||
}
|
||||
this.plugin.MacroHandler.CancelMacro(entry.Key);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{09B0F618-89E6-4CEE-9835-A4686DE9B716}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>CCMM</RootNamespace>
|
||||
<AssemblyName>Custom Commands and Macro Macros</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Dalamud">
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\Dalamud.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ImGui.NET">
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\ImGui.NET.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ImGuiScene">
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\ImGuiScene.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json">
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Numerics" />
|
||||
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Threading.Channels, Version=4.0.2.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Threading.Channels.4.7.1\lib\net461\System.Threading.Channels.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\System.Threading.Tasks.Extensions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CCMMPlugin.cs" />
|
||||
<Compile Include="Commands.cs" />
|
||||
<Compile Include="Configuration.cs" />
|
||||
<Compile Include="GameFunctions.cs" />
|
||||
<Compile Include="MacroHandler.cs" />
|
||||
<Compile Include="PluginUI.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<None Include="CCMM.json" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
|
@ -1,62 +0,0 @@
|
|||
using Dalamud.Plugin;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace CCMM {
|
||||
public class GameFunctions {
|
||||
private readonly CCMMPlugin plugin;
|
||||
|
||||
private delegate IntPtr GetUIBaseDelegate();
|
||||
private delegate IntPtr GetUIModuleDelegate(IntPtr basePtr);
|
||||
private delegate void EasierProcessChatBoxDelegate(IntPtr uiModule, IntPtr message, IntPtr unused, byte a4);
|
||||
|
||||
private readonly GetUIModuleDelegate GetUIModule;
|
||||
private readonly EasierProcessChatBoxDelegate _EasierProcessChatBox;
|
||||
|
||||
private readonly IntPtr uiModulePtr;
|
||||
|
||||
public GameFunctions(CCMMPlugin plugin) {
|
||||
this.plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "Plugin cannot be null");
|
||||
|
||||
IntPtr getUIModulePtr = this.plugin.Interface.TargetModuleScanner.ScanText("E8 ?? ?? ?? ?? 48 83 7F ?? 00 48 8B F0");
|
||||
IntPtr easierProcessChatBoxPtr = this.plugin.Interface.TargetModuleScanner.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 45 84 C9");
|
||||
this.uiModulePtr = this.plugin.Interface.TargetModuleScanner.GetStaticAddressFromSig("48 8B 0D ?? ?? ?? ?? 48 8D 54 24 ?? 48 83 C1 10 E8 ?? ?? ?? ??");
|
||||
|
||||
if (getUIModulePtr == IntPtr.Zero || easierProcessChatBoxPtr == IntPtr.Zero || this.uiModulePtr == IntPtr.Zero) {
|
||||
PluginLog.Log($"getUIModulePtr: {getUIModulePtr.ToInt64():x}");
|
||||
PluginLog.Log($"easierProcessChatBoxPtr: {easierProcessChatBoxPtr.ToInt64():x}");
|
||||
PluginLog.Log($"this.uiModulePtr: {this.uiModulePtr.ToInt64():x}");
|
||||
throw new ApplicationException("Got null pointers for game signature(s)");
|
||||
}
|
||||
|
||||
this.GetUIModule = Marshal.GetDelegateForFunctionPointer<GetUIModuleDelegate>(getUIModulePtr);
|
||||
this._EasierProcessChatBox = Marshal.GetDelegateForFunctionPointer<EasierProcessChatBoxDelegate>(easierProcessChatBoxPtr);
|
||||
}
|
||||
|
||||
public void ProcessChatBox(string message) {
|
||||
IntPtr uiModule = this.GetUIModule(Marshal.ReadIntPtr(this.uiModulePtr));
|
||||
|
||||
if (uiModule == IntPtr.Zero) {
|
||||
throw new ApplicationException("uiModule was null");
|
||||
}
|
||||
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(message);
|
||||
|
||||
IntPtr mem1 = Marshal.AllocHGlobal(400);
|
||||
IntPtr mem2 = Marshal.AllocHGlobal(bytes.Length + 30);
|
||||
|
||||
Marshal.Copy(bytes, 0, mem2, bytes.Length);
|
||||
Marshal.WriteByte(mem2 + bytes.Length, 0);
|
||||
Marshal.WriteInt64(mem1, mem2.ToInt64());
|
||||
Marshal.WriteInt64(mem1 + 8, 64);
|
||||
Marshal.WriteInt64(mem1 + 8 + 8, bytes.Length + 1);
|
||||
Marshal.WriteInt64(mem1 + 8 + 8 + 8, 0);
|
||||
|
||||
this._EasierProcessChatBox(uiModule, mem1, IntPtr.Zero, 0);
|
||||
|
||||
Marshal.FreeHGlobal(mem1);
|
||||
Marshal.FreeHGlobal(mem2);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
using Dalamud.Game.Internal;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CCMM {
|
||||
public class MacroHandler {
|
||||
private bool ready = false;
|
||||
private readonly static Regex WAIT = new Regex(@"<wait\.(\d+(?:\.\d+)?)>", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private readonly CCMMPlugin plugin;
|
||||
private readonly Channel<string> commands = Channel.CreateUnbounded<string>();
|
||||
public ConcurrentDictionary<Guid, Macro> Running { get; } = new ConcurrentDictionary<Guid, Macro>();
|
||||
private readonly ConcurrentDictionary<Guid, bool> cancelled = new ConcurrentDictionary<Guid, bool>();
|
||||
private readonly ConcurrentDictionary<Guid, bool> paused = new ConcurrentDictionary<Guid, bool>();
|
||||
|
||||
public MacroHandler(CCMMPlugin plugin) {
|
||||
this.plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "CCMMPlugin cannot be null");
|
||||
this.ready = this.plugin.Interface.ClientState.LocalPlayer != null;
|
||||
}
|
||||
|
||||
private static string[] ExtractCommands(string macro) {
|
||||
return macro.Split('\n')
|
||||
.Where(line => !line.Trim().StartsWith("#"))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public Guid SpawnMacro(Macro macro) {
|
||||
if (!this.ready) {
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
string[] commands = ExtractCommands(macro.Contents);
|
||||
Guid id = Guid.NewGuid();
|
||||
if (commands.Length == 0) {
|
||||
// pretend we spawned a task, but actually don't
|
||||
return id;
|
||||
}
|
||||
this.Running.TryAdd(id, macro);
|
||||
Task.Run(async () => {
|
||||
int i = 0;
|
||||
do {
|
||||
if (this.cancelled.TryRemove(id, out bool cancel) && cancel) {
|
||||
break;
|
||||
}
|
||||
if (this.paused.TryGetValue(id, out bool paused) && paused) {
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
continue;
|
||||
}
|
||||
string command = commands[i];
|
||||
TimeSpan? wait = this.ExtractWait(ref command);
|
||||
if (command == "/loop") {
|
||||
i = -1;
|
||||
} else {
|
||||
await this.commands.Writer.WriteAsync(command);
|
||||
}
|
||||
if (wait != null) {
|
||||
await Task.Delay((TimeSpan)wait);
|
||||
}
|
||||
i += 1;
|
||||
} while (i < commands.Length);
|
||||
this.Running.TryRemove(id, out Macro _);
|
||||
});
|
||||
return id;
|
||||
}
|
||||
|
||||
public bool IsRunning(Guid id) {
|
||||
return this.Running.ContainsKey(id);
|
||||
}
|
||||
|
||||
public void CancelMacro(Guid id) {
|
||||
if (!this.IsRunning(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.cancelled.TryAdd(id, true);
|
||||
}
|
||||
|
||||
public void PauseMacro(Guid id) {
|
||||
this.paused.TryAdd(id, true);
|
||||
}
|
||||
|
||||
public void ResumeMacro(Guid id) {
|
||||
this.paused.TryRemove(id, out _);
|
||||
}
|
||||
|
||||
public bool IsPaused(Guid id) {
|
||||
this.paused.TryGetValue(id, out bool paused);
|
||||
return paused;
|
||||
}
|
||||
|
||||
public bool IsCancelled(Guid id) {
|
||||
this.cancelled.TryGetValue(id, out bool cancelled);
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "delegate")]
|
||||
public void OnFrameworkUpdate(Framework framework) {
|
||||
if (!this.commands.Reader.TryRead(out string command) || !this.ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.plugin.Functions.ProcessChatBox(command);
|
||||
}
|
||||
|
||||
private TimeSpan? ExtractWait(ref string command) {
|
||||
MatchCollection matches = WAIT.Matches(command);
|
||||
if (matches.Count == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Match match = matches[matches.Count - 1];
|
||||
string waitTime = match.Groups[1].Captures[0].Value;
|
||||
|
||||
if (double.TryParse(waitTime, out double seconds)) {
|
||||
command = WAIT.Replace(command, "");
|
||||
return TimeSpan.FromSeconds(seconds);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal void OnLogin(object sender, EventArgs args) {
|
||||
this.ready = true;
|
||||
}
|
||||
|
||||
internal void OnLogout(object sender, EventArgs args) {
|
||||
this.ready = false;
|
||||
|
||||
foreach (Guid id in this.Running.Keys) {
|
||||
this.CancelMacro(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,241 +0,0 @@
|
|||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace CCMM {
|
||||
public class PluginUI {
|
||||
private readonly CCMMPlugin plugin;
|
||||
private INode dragged = null;
|
||||
private Guid runningChoice = Guid.Empty;
|
||||
private bool showIdents = false;
|
||||
|
||||
private bool _settingsVisible = false;
|
||||
public bool SettingsVisible { get => this._settingsVisible; set => this._settingsVisible = value; }
|
||||
|
||||
public PluginUI(CCMMPlugin plugin) {
|
||||
this.plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "CCMMPlugin cannot be null");
|
||||
}
|
||||
|
||||
public void OpenSettings(object sender, EventArgs e) {
|
||||
this.SettingsVisible = true;
|
||||
}
|
||||
|
||||
public void Draw() {
|
||||
if (this.SettingsVisible) {
|
||||
this.DrawSettings();
|
||||
}
|
||||
}
|
||||
|
||||
private bool RemoveNode(List<INode> list, INode toRemove) {
|
||||
if (list.Remove(toRemove)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (INode node in list) {
|
||||
if (node.Children.Count > 0 && this.RemoveNode(node.Children, toRemove)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void DrawSettings() {
|
||||
// unset the cancel choice if no longer running
|
||||
if (this.runningChoice != Guid.Empty && !this.plugin.MacroHandler.IsRunning(this.runningChoice)) {
|
||||
this.runningChoice = Guid.Empty;
|
||||
}
|
||||
|
||||
if (ImGui.Begin(this.plugin.Name, ref this._settingsVisible)) {
|
||||
ImGui.Columns(2);
|
||||
|
||||
if (IconButton(FontAwesomeIcon.Plus)) {
|
||||
this.plugin.Config.Nodes.Add(new Macro("Untitled macro", ""));
|
||||
this.plugin.Config.Save();
|
||||
}
|
||||
Tooltip("Add macro");
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (IconButton(FontAwesomeIcon.FolderPlus)) {
|
||||
this.plugin.Config.Nodes.Add(new Folder("Untitled folder"));
|
||||
this.plugin.Config.Save();
|
||||
}
|
||||
Tooltip("Add folder");
|
||||
|
||||
List<INode> toRemove = new List<INode>();
|
||||
foreach (INode node in this.plugin.Config.Nodes) {
|
||||
toRemove.AddRange(this.DrawNode(node));
|
||||
}
|
||||
foreach (INode node in toRemove) {
|
||||
this.RemoveNode(this.plugin.Config.Nodes, node);
|
||||
}
|
||||
if (toRemove.Count != 0) {
|
||||
this.plugin.Config.Save();
|
||||
}
|
||||
|
||||
ImGui.NextColumn();
|
||||
|
||||
ImGui.Text("Running macros");
|
||||
ImGui.PushItemWidth(-1f);
|
||||
if (ImGui.ListBoxHeader("##running-macros", this.plugin.MacroHandler.Running.Count, 5)) {
|
||||
foreach (KeyValuePair<Guid, Macro> entry in this.plugin.MacroHandler.Running) {
|
||||
string name = $"{entry.Value.Name}";
|
||||
if (this.showIdents) {
|
||||
string ident = entry.Key.ToString();
|
||||
name += $" ({ident.Substring(ident.Length - 7)})";
|
||||
}
|
||||
if (this.plugin.MacroHandler.IsPaused(entry.Key)) {
|
||||
name += " (paused)";
|
||||
}
|
||||
bool cancalled = this.plugin.MacroHandler.IsCancelled(entry.Key);
|
||||
ImGuiSelectableFlags flags = cancalled ? ImGuiSelectableFlags.Disabled : ImGuiSelectableFlags.None;
|
||||
if (ImGui.Selectable($"{name}##{entry.Key}", this.runningChoice == entry.Key, flags)) {
|
||||
this.runningChoice = entry.Key;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.ListBoxFooter();
|
||||
}
|
||||
ImGui.PopItemWidth();
|
||||
|
||||
if (ImGui.Button("Cancel") && this.runningChoice != Guid.Empty) {
|
||||
this.plugin.MacroHandler.CancelMacro(this.runningChoice);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
bool paused = this.runningChoice != Guid.Empty && this.plugin.MacroHandler.IsPaused(this.runningChoice);
|
||||
if (ImGui.Button(paused ? "Resume" : "Pause") && this.runningChoice != Guid.Empty) {
|
||||
if (paused) {
|
||||
this.plugin.MacroHandler.ResumeMacro(this.runningChoice);
|
||||
} else {
|
||||
this.plugin.MacroHandler.PauseMacro(this.runningChoice);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
ImGui.Checkbox("Show unique identifiers", ref this.showIdents);
|
||||
|
||||
ImGui.Columns(1);
|
||||
|
||||
ImGui.End();
|
||||
}
|
||||
}
|
||||
|
||||
private List<INode> DrawNode(INode node) {
|
||||
List<INode> toRemove = new List<INode>();
|
||||
ImGui.PushID($"{node.Id}");
|
||||
bool open = ImGui.TreeNode($"{node.Id}", $"{node.Name}");
|
||||
|
||||
if (ImGui.BeginPopupContextItem()) {
|
||||
string name = node.Name;
|
||||
if (ImGui.InputText($"##{node.Id}-rename", ref name, (uint)this.plugin.Config.MaxLength, ImGuiInputTextFlags.AutoSelectAll)) {
|
||||
node.Name = name;
|
||||
this.plugin.Config.Save();
|
||||
}
|
||||
|
||||
if (ImGui.Button("Delete")) {
|
||||
toRemove.Add(node);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Copy UUID")) {
|
||||
ImGui.SetClipboardText($"{node.Id}");
|
||||
}
|
||||
|
||||
if (node is Macro macro) {
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Run##context")) {
|
||||
this.RunMacro(macro);
|
||||
}
|
||||
}
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
|
||||
if (ImGui.BeginDragDropSource()) {
|
||||
ImGui.Text(node.Name);
|
||||
this.dragged = node;
|
||||
ImGui.SetDragDropPayload("CCMM-GUID", IntPtr.Zero, 0);
|
||||
ImGui.EndDragDropSource();
|
||||
}
|
||||
|
||||
if (node is Folder dfolder && ImGui.BeginDragDropTarget()) {
|
||||
ImGuiPayloadPtr payloadPtr = ImGui.AcceptDragDropPayload("CCMM-GUID");
|
||||
bool nullPtr;
|
||||
unsafe {
|
||||
nullPtr = payloadPtr.NativePtr == null;
|
||||
}
|
||||
if (!nullPtr && payloadPtr.IsDelivery() && this.dragged != null) {
|
||||
dfolder.Children.Add(this.dragged.Duplicate());
|
||||
this.dragged.Id = Guid.NewGuid();
|
||||
toRemove.Add(this.dragged);
|
||||
|
||||
this.dragged = null;
|
||||
}
|
||||
|
||||
ImGui.EndDragDropTarget();
|
||||
}
|
||||
|
||||
ImGui.PopID();
|
||||
|
||||
if (open) {
|
||||
if (node is Macro macro) {
|
||||
this.DrawMacro(macro);
|
||||
} else if (node is Folder folder) {
|
||||
this.DrawFolder(folder);
|
||||
foreach (INode child in node.Children) {
|
||||
toRemove.AddRange(this.DrawNode(child));
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
|
||||
return toRemove;
|
||||
}
|
||||
|
||||
private void DrawMacro(Macro macro) {
|
||||
string contents = macro.Contents;
|
||||
ImGui.PushItemWidth(-1f);
|
||||
if (ImGui.InputTextMultiline($"##{macro.Id}-editor", ref contents, (uint)this.plugin.Config.MaxLength, new Vector2(0, 250))) {
|
||||
macro.Contents = contents;
|
||||
this.plugin.Config.Save();
|
||||
}
|
||||
ImGui.PopItemWidth();
|
||||
|
||||
if (ImGui.Button("Run")) {
|
||||
this.RunMacro(macro);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawFolder(Folder folder) {
|
||||
|
||||
}
|
||||
|
||||
private void RunMacro(Macro macro) {
|
||||
this.plugin.MacroHandler.SpawnMacro(macro);
|
||||
}
|
||||
|
||||
private static bool IconButton(FontAwesomeIcon icon) {
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
bool ret = ImGui.Button(icon.ToIconString());
|
||||
ImGui.PopFont();
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static void Tooltip(string text) {
|
||||
if (ImGui.IsItemHovered()) {
|
||||
ImGui.BeginTooltip();
|
||||
ImGui.TextUnformatted(text);
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Custom Commands and Macro Macros")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Custom Commands and Macro Macros")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2020")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("09b0f618-89e6-4cee-9835-a4686de9b716")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("0.1.0")]
|
||||
[assembly: AssemblyFileVersion("0.1.0")]
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.3" targetFramework="net48" />
|
||||
<package id="System.Threading.Channels" version="4.7.1" targetFramework="net48" />
|
||||
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net48" />
|
||||
</packages>
|
|
@ -3,11 +3,10 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
|||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30330.147
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Custom Commands and Macro Macros", "Custom Commands and Macro Macros\Custom Commands and Macro Macros.csproj", "{09B0F618-89E6-4CEE-9835-A4686DE9B716}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Macrology", "Macrology\Macrology.csproj", "{09B0F618-89E6-4CEE-9835-A4686DE9B716}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D9C29BDA-B9DA-4E1D-AAFA-55B898E33593}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
README.md = README.md
|
||||
EndProjectSection
|
||||
EndProject
|
90
Macrology/Commands.cs
Normal file
90
Macrology/Commands.cs
Normal file
|
@ -0,0 +1,90 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Macrology {
|
||||
public class Commands {
|
||||
private Macrology Plugin { get; }
|
||||
|
||||
public static readonly IReadOnlyDictionary<string, string> Descriptions = new Dictionary<string, string> {
|
||||
["/mmacros"] = "Open the Macrology interface",
|
||||
["/pmacrology"] = "Alias for /mmacros",
|
||||
["/macrology"] = "Alias for /mmacros",
|
||||
["/mmacro"] = "Execute a Macrology macro",
|
||||
["/mmcancel"] = "Cancel the first Macrology macro of a given type or all if \"all\" is passed",
|
||||
};
|
||||
|
||||
public Commands(Macrology plugin) {
|
||||
this.Plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "Macrology cannot be null");
|
||||
}
|
||||
|
||||
public void OnCommand(string command, string args) {
|
||||
switch (command) {
|
||||
case "/mmacros":
|
||||
case "/pmacrology":
|
||||
case "/macrology":
|
||||
this.OnMainCommand();
|
||||
break;
|
||||
case "/mmacro":
|
||||
this.OnMacroCommand(args);
|
||||
break;
|
||||
case "/mmcancel":
|
||||
this.OnMacroCancelCommand(args);
|
||||
break;
|
||||
default:
|
||||
this.Plugin.Interface.Framework.Gui.Chat.PrintError($"The command {command} was passed to Macrology, but there is no handler available.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMainCommand() {
|
||||
this.Plugin.Ui.SettingsVisible = !this.Plugin.Ui.SettingsVisible;
|
||||
}
|
||||
|
||||
private void OnMacroCommand(string args) {
|
||||
var first = args.Trim().Split(' ').FirstOrDefault() ?? "";
|
||||
if (!Guid.TryParse(first, out var id)) {
|
||||
this.Plugin.Interface.Framework.Gui.Chat.PrintError("First argument must be the UUID of the macro to execute.");
|
||||
return;
|
||||
}
|
||||
|
||||
var macro = this.Plugin.Config.FindMacro(id);
|
||||
if (macro == null) {
|
||||
this.Plugin.Interface.Framework.Gui.Chat.PrintError($"No macro with ID {id} found.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.Plugin.MacroHandler.SpawnMacro(macro);
|
||||
}
|
||||
|
||||
private void OnMacroCancelCommand(string args) {
|
||||
var first = args.Trim().Split(' ').FirstOrDefault() ?? "";
|
||||
if (first == "all") {
|
||||
foreach (var running in this.Plugin.MacroHandler.Running.Keys) {
|
||||
this.Plugin.MacroHandler.CancelMacro(running);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Guid.TryParse(first, out var id)) {
|
||||
this.Plugin.Interface.Framework.Gui.Chat.PrintError("First argument must either be \"all\" or the UUID of the macro to cancel.");
|
||||
return;
|
||||
}
|
||||
|
||||
var macro = this.Plugin.Config.FindMacro(id);
|
||||
if (macro == null) {
|
||||
this.Plugin.Interface.Framework.Gui.Chat.PrintError($"No macro with ID {id} found.");
|
||||
return;
|
||||
}
|
||||
|
||||
var entry = this.Plugin.MacroHandler.Running.FirstOrDefault(e => e.Value.Id == id);
|
||||
if (entry.Value == null) {
|
||||
this.Plugin.Interface.Framework.Gui.Chat.PrintError($"That macro is not running.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.Plugin.MacroHandler.CancelMacro(entry.Key);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,9 +7,9 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace CCMM {
|
||||
namespace Macrology {
|
||||
public class Configuration : IPluginConfiguration {
|
||||
private CCMMPlugin plugin;
|
||||
private Macrology Plugin { get; set; } = null!;
|
||||
|
||||
public int Version { get; set; } = 1;
|
||||
|
||||
|
@ -19,17 +19,17 @@ namespace CCMM {
|
|||
|
||||
public int MaxLength { get; set; } = 10_000;
|
||||
|
||||
internal void Initialise(CCMMPlugin plugin) {
|
||||
this.plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "CCMMPlugin cannot be null");
|
||||
internal void Initialise(Macrology plugin) {
|
||||
this.Plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "Macrology cannot be null");
|
||||
}
|
||||
|
||||
internal void Save() {
|
||||
string configPath = ConfigPath(plugin);
|
||||
string configText = JsonConvert.SerializeObject(this, Formatting.Indented);
|
||||
var configPath = ConfigPath(this.Plugin);
|
||||
var configText = JsonConvert.SerializeObject(this, Formatting.Indented);
|
||||
File.WriteAllText(configPath, configText);
|
||||
}
|
||||
|
||||
private static string ConfigPath(CCMMPlugin plugin) {
|
||||
private static string ConfigPath(Macrology plugin) {
|
||||
string[] paths = {
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"XIVLauncher",
|
||||
|
@ -39,20 +39,24 @@ namespace CCMM {
|
|||
return Path.Combine(paths);
|
||||
}
|
||||
|
||||
internal static Configuration Load(CCMMPlugin plugin) {
|
||||
string configPath = ConfigPath(plugin);
|
||||
if (File.Exists(configPath)) {
|
||||
internal static Configuration? Load(Macrology plugin) {
|
||||
var configPath = ConfigPath(plugin);
|
||||
|
||||
if (!File.Exists(configPath)) {
|
||||
return new Configuration();
|
||||
}
|
||||
|
||||
string configText;
|
||||
try {
|
||||
configText = File.ReadAllText(configPath);
|
||||
} catch (IOException e) {
|
||||
}
|
||||
catch (IOException e) {
|
||||
PluginLog.Log($"Could not read config at {configPath}: {e.Message}.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<Configuration>(configText);
|
||||
}
|
||||
return new Configuration();
|
||||
}
|
||||
|
||||
private static IEnumerable<T> Traverse<T>(T item, Func<T, IEnumerable<T>> childSelector) {
|
||||
var stack = new Stack<T>();
|
||||
|
@ -60,20 +64,14 @@ namespace CCMM {
|
|||
while (stack.Any()) {
|
||||
var next = stack.Pop();
|
||||
yield return next;
|
||||
foreach (var child in childSelector(next))
|
||||
foreach (var child in childSelector(next)) {
|
||||
stack.Push(child);
|
||||
}
|
||||
}
|
||||
|
||||
public Macro FindMacro(Guid id) {
|
||||
foreach (INode node in this.Nodes) {
|
||||
Macro macro = (Macro)Traverse(node, n => n.Children).FirstOrDefault(n => n.Id == id && n is Macro);
|
||||
if (macro != null) {
|
||||
return macro;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
public Macro? FindMacro(Guid id) {
|
||||
return this.Nodes.Select(node => (Macro?) Traverse(node, n => n.Children).FirstOrDefault(n => n.Id == id && n is Macro)).FirstOrDefault(macro => macro != null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,7 +88,7 @@ namespace CCMM {
|
|||
public string Name { get; set; }
|
||||
public List<INode> Children { get; private set; } = new List<INode>();
|
||||
|
||||
public Folder(string name, List<INode> children = null) {
|
||||
public Folder(string name, List<INode>? children = null) {
|
||||
this.Id = Guid.NewGuid();
|
||||
this.Name = name;
|
||||
if (children != null) {
|
||||
|
@ -139,18 +137,20 @@ namespace CCMM {
|
|||
public class NodeConverter : JsonConverter {
|
||||
public override bool CanWrite => false;
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanConvert(Type objectType) {
|
||||
return objectType == typeof(INode);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
|
||||
throw new InvalidOperationException("Use default serialization.");
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
|
||||
JArray jsonArray = JArray.Load(reader);
|
||||
List<INode> list = new List<INode>();
|
||||
foreach (JToken token in jsonArray) {
|
||||
JObject jsonObject = (JObject)token;
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) {
|
||||
var jsonArray = JArray.Load(reader);
|
||||
var list = new List<INode>();
|
||||
foreach (var token in jsonArray) {
|
||||
var jsonObject = (JObject) token;
|
||||
INode node;
|
||||
if (jsonObject.ContainsKey("Contents")) {
|
||||
node = new Macro(
|
||||
|
@ -158,15 +158,18 @@ namespace CCMM {
|
|||
jsonObject["Name"].ToObject<string>(),
|
||||
jsonObject["Contents"].ToObject<string>()
|
||||
);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
node = new Folder(
|
||||
jsonObject["Id"].ToObject<Guid>(),
|
||||
jsonObject["Name"].ToObject<string>(),
|
||||
(List<INode>)this.ReadJson(jsonObject["Children"].CreateReader(), typeof(List<INode>), null, serializer)
|
||||
(List<INode>) this.ReadJson(jsonObject["Children"].CreateReader(), typeof(List<INode>), null, serializer)
|
||||
);
|
||||
}
|
||||
|
||||
list.Add(node);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
14
Macrology/DalamudPackager.targets
Normal file
14
Macrology/DalamudPackager.targets
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project>
|
||||
<Target Name="PackagePlugin" AfterTargets="ILRepacker" Condition="'$(Configuration)' == 'Release'">
|
||||
<DalamudPackager
|
||||
ProjectDir="$(ProjectDir)"
|
||||
OutputPath="$(OutputPath)"
|
||||
AssemblyName="$(AssemblyName)"
|
||||
ManifestType="yaml"
|
||||
VersionComponents="3"
|
||||
MakeZip="true"
|
||||
Include="Macrology.dll;Macrology.pdb"/>
|
||||
</Target>
|
||||
</Project>
|
||||
|
86
Macrology/GameFunctions.cs
Normal file
86
Macrology/GameFunctions.cs
Normal file
|
@ -0,0 +1,86 @@
|
|||
using Dalamud.Plugin;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Macrology {
|
||||
public class GameFunctions {
|
||||
private Macrology Plugin { get; }
|
||||
|
||||
private delegate IntPtr GetUiModuleDelegate(IntPtr basePtr);
|
||||
|
||||
private delegate void EasierProcessChatBoxDelegate(IntPtr uiModule, IntPtr message, IntPtr unused, byte a4);
|
||||
|
||||
private readonly GetUiModuleDelegate _getUiModule;
|
||||
private readonly EasierProcessChatBoxDelegate _easierProcessChatBox;
|
||||
|
||||
private readonly IntPtr _uiModulePtr;
|
||||
|
||||
public GameFunctions(Macrology plugin) {
|
||||
this.Plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "Plugin cannot be null");
|
||||
|
||||
var getUiModulePtr = this.Plugin.Interface.TargetModuleScanner.ScanText("E8 ?? ?? ?? ?? 48 83 7F ?? 00 48 8B F0");
|
||||
var easierProcessChatBoxPtr = this.Plugin.Interface.TargetModuleScanner.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 45 84 C9");
|
||||
this._uiModulePtr = this.Plugin.Interface.TargetModuleScanner.GetStaticAddressFromSig("48 8B 0D ?? ?? ?? ?? 48 8D 54 24 ?? 48 83 C1 10 E8 ?? ?? ?? ??");
|
||||
|
||||
if (getUiModulePtr == IntPtr.Zero || easierProcessChatBoxPtr == IntPtr.Zero || this._uiModulePtr == IntPtr.Zero) {
|
||||
PluginLog.Log($"getUIModulePtr: {getUiModulePtr.ToInt64():x}");
|
||||
PluginLog.Log($"easierProcessChatBoxPtr: {easierProcessChatBoxPtr.ToInt64():x}");
|
||||
PluginLog.Log($"this.uiModulePtr: {this._uiModulePtr.ToInt64():x}");
|
||||
throw new ApplicationException("Got null pointers for game signature(s)");
|
||||
}
|
||||
|
||||
this._getUiModule = Marshal.GetDelegateForFunctionPointer<GetUiModuleDelegate>(getUiModulePtr);
|
||||
this._easierProcessChatBox = Marshal.GetDelegateForFunctionPointer<EasierProcessChatBoxDelegate>(easierProcessChatBoxPtr);
|
||||
}
|
||||
|
||||
public void ProcessChatBox(string message) {
|
||||
var uiModule = this._getUiModule(Marshal.ReadIntPtr(this._uiModulePtr));
|
||||
|
||||
if (uiModule == IntPtr.Zero) {
|
||||
throw new ApplicationException("uiModule was null");
|
||||
}
|
||||
|
||||
using var payload = new ChatPayload(message);
|
||||
var mem1 = Marshal.AllocHGlobal(400);
|
||||
Marshal.StructureToPtr(payload, mem1, false);
|
||||
|
||||
this._easierProcessChatBox(uiModule, mem1, IntPtr.Zero, 0);
|
||||
|
||||
Marshal.FreeHGlobal(mem1);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
[SuppressMessage("ReSharper", "PrivateFieldCanBeConvertedToLocalVariable")]
|
||||
internal readonly struct ChatPayload : IDisposable {
|
||||
[FieldOffset(0)]
|
||||
private readonly IntPtr textPtr;
|
||||
|
||||
[FieldOffset(16)]
|
||||
private readonly ulong textLen;
|
||||
|
||||
[FieldOffset(8)]
|
||||
private readonly ulong unk1;
|
||||
|
||||
[FieldOffset(24)]
|
||||
private readonly ulong unk2;
|
||||
|
||||
internal ChatPayload(string text) {
|
||||
var stringBytes = Encoding.UTF8.GetBytes(text);
|
||||
this.textPtr = Marshal.AllocHGlobal(stringBytes.Length + 30);
|
||||
Marshal.Copy(stringBytes, 0, this.textPtr, stringBytes.Length);
|
||||
Marshal.WriteByte(this.textPtr + stringBytes.Length, 0);
|
||||
|
||||
this.textLen = (ulong) (stringBytes.Length + 1);
|
||||
|
||||
this.unk1 = 64;
|
||||
this.unk2 = 0;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Marshal.FreeHGlobal(this.textPtr);
|
||||
}
|
||||
}
|
||||
}
|
17
Macrology/ILRepack.targets
Normal file
17
Macrology/ILRepack.targets
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Target Name="ILRepacker" AfterTargets="Build">
|
||||
<ItemGroup>
|
||||
<InputAssemblies Include="$(OutputPath)\*.dll"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ILRepack
|
||||
Parallel="true"
|
||||
Internalize="false"
|
||||
InputAssemblies="@(InputAssemblies)"
|
||||
TargetKind="Dll"
|
||||
TargetPlatformVersion="v4"
|
||||
LibraryPath="$(OutputPath)"
|
||||
OutputFile="$(OutputPath)\$(AssemblyName).dll"/>
|
||||
</Target>
|
||||
</Project>
|
178
Macrology/MacroHandler.cs
Normal file
178
Macrology/MacroHandler.cs
Normal file
|
@ -0,0 +1,178 @@
|
|||
using Dalamud.Game.Internal;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Macrology {
|
||||
public class MacroHandler {
|
||||
private bool _ready;
|
||||
private static readonly Regex Wait = new Regex(@"<wait\.(\d+(?:\.\d+)?)>", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static readonly string[] FastCommands = {
|
||||
"/ac",
|
||||
"/action",
|
||||
"/e",
|
||||
"/echo",
|
||||
};
|
||||
|
||||
private Macrology Plugin { get; }
|
||||
private readonly Channel<string> _commands = Channel.CreateUnbounded<string>();
|
||||
public ConcurrentDictionary<Guid, Macro> Running { get; } = new ConcurrentDictionary<Guid, Macro>();
|
||||
private readonly ConcurrentDictionary<Guid, bool> _cancelled = new ConcurrentDictionary<Guid, bool>();
|
||||
private readonly ConcurrentDictionary<Guid, bool> _paused = new ConcurrentDictionary<Guid, bool>();
|
||||
|
||||
public MacroHandler(Macrology plugin) {
|
||||
this.Plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "Macrology cannot be null");
|
||||
this._ready = this.Plugin.Interface.ClientState.LocalPlayer != null;
|
||||
}
|
||||
|
||||
private static string[] ExtractCommands(string macro) {
|
||||
return macro.Split('\n')
|
||||
.Where(line => line.Length > 0 && !line.StartsWith("#"))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public Guid SpawnMacro(Macro macro) {
|
||||
if (!this._ready) {
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
var commands = ExtractCommands(macro.Contents);
|
||||
var id = Guid.NewGuid();
|
||||
if (commands.Length == 0) {
|
||||
// pretend we spawned a task, but actually don't
|
||||
return id;
|
||||
}
|
||||
|
||||
this.Running.TryAdd(id, macro);
|
||||
Task.Run(async () => {
|
||||
// the default wait
|
||||
TimeSpan? defWait = null;
|
||||
// keep track of the line we're at in the macro
|
||||
var i = 0;
|
||||
do {
|
||||
// cancel if requested
|
||||
if (this._cancelled.TryRemove(id, out var cancel) && cancel) {
|
||||
break;
|
||||
}
|
||||
|
||||
// wait a second instead of executing if paused
|
||||
if (this._paused.TryGetValue(id, out var paused) && paused) {
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
continue;
|
||||
}
|
||||
|
||||
// get the line of the command
|
||||
var command = commands[i];
|
||||
// find the amount specified to wait, if any
|
||||
var wait = ExtractWait(ref command) ?? defWait;
|
||||
// go back to the beginning if the command is loop
|
||||
if (command.Trim() == "/loop") {
|
||||
i = 0;
|
||||
continue;
|
||||
}
|
||||
// set default wait
|
||||
if (command.Trim().StartsWith("/defaultwait ")) {
|
||||
var defWaitStr = command.Split(' ')[1];
|
||||
if (double.TryParse(defWaitStr, out var waitTime)) {
|
||||
defWait = TimeSpan.FromSeconds(waitTime);
|
||||
}
|
||||
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// send the command to the channel
|
||||
await this._commands.Writer.WriteAsync(command);
|
||||
|
||||
// wait a minimum amount of time (<wait.0> to bypass)
|
||||
if (FastCommands.Contains(command.Split(' ')[0])) {
|
||||
wait ??= TimeSpan.FromMilliseconds(10);
|
||||
} else {
|
||||
wait ??= TimeSpan.FromMilliseconds(100);
|
||||
}
|
||||
await Task.Delay((TimeSpan) wait);
|
||||
|
||||
// increment to next line
|
||||
i += 1;
|
||||
} while (i < commands.Length);
|
||||
|
||||
this.Running.TryRemove(id, out _);
|
||||
});
|
||||
return id;
|
||||
}
|
||||
|
||||
public bool IsRunning(Guid id) {
|
||||
return this.Running.ContainsKey(id);
|
||||
}
|
||||
|
||||
public void CancelMacro(Guid id) {
|
||||
if (!this.IsRunning(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._cancelled.TryAdd(id, true);
|
||||
}
|
||||
|
||||
public void PauseMacro(Guid id) {
|
||||
this._paused.TryAdd(id, true);
|
||||
}
|
||||
|
||||
public void ResumeMacro(Guid id) {
|
||||
this._paused.TryRemove(id, out _);
|
||||
}
|
||||
|
||||
public bool IsPaused(Guid id) {
|
||||
this._paused.TryGetValue(id, out var paused);
|
||||
return paused;
|
||||
}
|
||||
|
||||
public bool IsCancelled(Guid id) {
|
||||
this._cancelled.TryGetValue(id, out var cancelled);
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "delegate")]
|
||||
public void OnFrameworkUpdate(Framework framework) {
|
||||
// get a message to send, but discard it if we're not ready
|
||||
if (!this._commands.Reader.TryRead(out var command) || !this._ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
// send the message as if it were entered in the chat box
|
||||
this.Plugin.Functions.ProcessChatBox(command);
|
||||
}
|
||||
|
||||
private static TimeSpan? ExtractWait(ref string command) {
|
||||
var matches = Wait.Matches(command);
|
||||
if (matches.Count == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var match = matches[matches.Count - 1];
|
||||
var waitTime = match.Groups[1].Captures[0].Value;
|
||||
|
||||
if (!double.TryParse(waitTime, out var seconds)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
command = Wait.Replace(command, "");
|
||||
return TimeSpan.FromSeconds(seconds);
|
||||
}
|
||||
|
||||
internal void OnLogin(object sender, EventArgs args) {
|
||||
this._ready = true;
|
||||
}
|
||||
|
||||
internal void OnLogout(object sender, EventArgs args) {
|
||||
this._ready = false;
|
||||
|
||||
foreach (var id in this.Running.Keys) {
|
||||
this.CancelMacro(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +1,24 @@
|
|||
using Dalamud.Game.Command;
|
||||
using Dalamud.Plugin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CCMM {
|
||||
public class CCMMPlugin : IDalamudPlugin {
|
||||
private bool disposedValue;
|
||||
namespace Macrology {
|
||||
public class Macrology : IDalamudPlugin {
|
||||
private bool _disposedValue;
|
||||
|
||||
public string Name => "Custom Commands and Macro Macros";
|
||||
public string Name => "Macrology";
|
||||
|
||||
public DalamudPluginInterface Interface { get; private set; }
|
||||
public GameFunctions Functions { get; private set; }
|
||||
public PluginUI Ui { get; private set; }
|
||||
public MacroHandler MacroHandler { get; private set; }
|
||||
public Configuration Config { get; private set; }
|
||||
private Commands Commands { get; set; }
|
||||
public DalamudPluginInterface Interface { get; private set; } = null!;
|
||||
public GameFunctions Functions { get; private set; } = null!;
|
||||
public PluginUi Ui { get; private set; } = null!;
|
||||
public MacroHandler MacroHandler { get; private set; } = null!;
|
||||
public Configuration Config { get; private set; } = null!;
|
||||
private Commands Commands { get; set; } = null!;
|
||||
|
||||
public void Initialize(DalamudPluginInterface pluginInterface) {
|
||||
this.Interface = pluginInterface ?? throw new ArgumentNullException(nameof(pluginInterface), "DalamudPluginInterface cannot be null");
|
||||
this.Functions = new GameFunctions(this);
|
||||
this.Ui = new PluginUI(this);
|
||||
this.Ui = new PluginUi(this);
|
||||
this.MacroHandler = new MacroHandler(this);
|
||||
this.Config = Configuration.Load(this) ?? new Configuration();
|
||||
this.Config.Initialise(this);
|
||||
|
@ -30,7 +29,7 @@ namespace CCMM {
|
|||
this.Interface.Framework.OnUpdateEvent += this.MacroHandler.OnFrameworkUpdate;
|
||||
this.Interface.ClientState.OnLogin += this.MacroHandler.OnLogin;
|
||||
this.Interface.ClientState.OnLogout += this.MacroHandler.OnLogout;
|
||||
foreach (KeyValuePair<string, string> entry in Commands.COMMANDS) {
|
||||
foreach (var entry in Commands.Descriptions) {
|
||||
this.Interface.CommandManager.AddHandler(entry.Key, new CommandInfo(this.Commands.OnCommand) {
|
||||
HelpMessage = entry.Value,
|
||||
});
|
||||
|
@ -38,27 +37,27 @@ namespace CCMM {
|
|||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing) {
|
||||
if (!disposedValue) {
|
||||
if (this._disposedValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing) {
|
||||
this.Interface.UiBuilder.OnBuildUi -= this.Ui.Draw;
|
||||
this.Interface.UiBuilder.OnOpenConfigUi -= this.Ui.OpenSettings;
|
||||
this.Interface.Framework.OnUpdateEvent -= this.MacroHandler.OnFrameworkUpdate;
|
||||
this.Interface.ClientState.OnLogin -= this.MacroHandler.OnLogin;
|
||||
this.Interface.ClientState.OnLogout -= this.MacroHandler.OnLogout;
|
||||
foreach (string command in Commands.COMMANDS.Keys) {
|
||||
foreach (var command in Commands.Descriptions.Keys) {
|
||||
this.Interface.CommandManager.RemoveHandler(command);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
||||
// TODO: set large fields to null
|
||||
disposedValue = true;
|
||||
}
|
||||
this._disposedValue = true;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
34
Macrology/Macrology.csproj
Normal file
34
Macrology/Macrology.csproj
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<LangVersion>8</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyVersion>0.1.0</AssemblyVersion>
|
||||
<FileVersion>0.1.0</FileVersion>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Dalamud, Version=5.2.1.1, Culture=neutral, PublicKeyToken=null">
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\Dalamud.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="ImGui.NET, Version=1.72.0.0, Culture=neutral, PublicKeyToken=null">
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\ImGui.NET.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="ImGuiScene, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\ImGuiScene.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DalamudPackager" Version="1.0.0" />
|
||||
<PackageReference Include="ILRepack.Lib.MSBuild.Task" Version="2.0.18.2" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
8
Macrology/Macrology.yaml
Normal file
8
Macrology/Macrology.yaml
Normal file
|
@ -0,0 +1,8 @@
|
|||
author: ascclemens
|
||||
name: Macrology
|
||||
description: |-
|
||||
Adds a better macro system to the game.
|
||||
|
||||
Macrology allows for macros of infinite length, adds looping,
|
||||
allows comments, supports pausing, allows you to run multiple
|
||||
macros at once, and supports fractional waits.
|
248
Macrology/PluginUI.cs
Normal file
248
Macrology/PluginUI.cs
Normal file
|
@ -0,0 +1,248 @@
|
|||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Macrology {
|
||||
public class PluginUi {
|
||||
private Macrology Plugin { get; }
|
||||
private INode? Dragged { get; set; }
|
||||
private Guid RunningChoice { get; set; } = Guid.Empty;
|
||||
private bool _showIdents;
|
||||
|
||||
private bool _settingsVisible;
|
||||
|
||||
public bool SettingsVisible {
|
||||
get => this._settingsVisible;
|
||||
set => this._settingsVisible = value;
|
||||
}
|
||||
|
||||
public PluginUi(Macrology plugin) {
|
||||
this.Plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "Macrology cannot be null");
|
||||
}
|
||||
|
||||
public void OpenSettings(object sender, EventArgs e) {
|
||||
this.SettingsVisible = true;
|
||||
}
|
||||
|
||||
public void Draw() {
|
||||
if (this.SettingsVisible) {
|
||||
this.DrawSettings();
|
||||
}
|
||||
}
|
||||
|
||||
private bool RemoveNode(ICollection<INode> list, INode toRemove) {
|
||||
return list.Remove(toRemove) || list.Any(node => node.Children.Count > 0 && this.RemoveNode(node.Children, toRemove));
|
||||
}
|
||||
|
||||
private void DrawSettings() {
|
||||
// unset the cancel choice if no longer running
|
||||
if (this.RunningChoice != Guid.Empty && !this.Plugin.MacroHandler.IsRunning(this.RunningChoice)) {
|
||||
this.RunningChoice = Guid.Empty;
|
||||
}
|
||||
|
||||
if (!ImGui.Begin(this.Plugin.Name, ref this._settingsVisible)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.Columns(2);
|
||||
|
||||
if (IconButton(FontAwesomeIcon.Plus)) {
|
||||
this.Plugin.Config.Nodes.Add(new Macro("Untitled macro", ""));
|
||||
this.Plugin.Config.Save();
|
||||
}
|
||||
|
||||
Tooltip("Add macro");
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (IconButton(FontAwesomeIcon.FolderPlus)) {
|
||||
this.Plugin.Config.Nodes.Add(new Folder("Untitled folder"));
|
||||
this.Plugin.Config.Save();
|
||||
}
|
||||
|
||||
Tooltip("Add folder");
|
||||
|
||||
var toRemove = new List<INode>();
|
||||
foreach (var node in this.Plugin.Config.Nodes) {
|
||||
toRemove.AddRange(this.DrawNode(node));
|
||||
}
|
||||
|
||||
foreach (var node in toRemove) {
|
||||
this.RemoveNode(this.Plugin.Config.Nodes, node);
|
||||
}
|
||||
|
||||
if (toRemove.Count != 0) {
|
||||
this.Plugin.Config.Save();
|
||||
}
|
||||
|
||||
ImGui.NextColumn();
|
||||
|
||||
ImGui.Text("Running macros");
|
||||
ImGui.PushItemWidth(-1f);
|
||||
if (ImGui.ListBoxHeader("##running-macros", this.Plugin.MacroHandler.Running.Count, 5)) {
|
||||
foreach (var entry in this.Plugin.MacroHandler.Running) {
|
||||
var name = $"{entry.Value.Name}";
|
||||
if (this._showIdents) {
|
||||
var ident = entry.Key.ToString();
|
||||
name += $" ({ident.Substring(ident.Length - 7)})";
|
||||
}
|
||||
|
||||
if (this.Plugin.MacroHandler.IsPaused(entry.Key)) {
|
||||
name += " (paused)";
|
||||
}
|
||||
|
||||
var cancelled = this.Plugin.MacroHandler.IsCancelled(entry.Key);
|
||||
var flags = cancelled ? ImGuiSelectableFlags.Disabled : ImGuiSelectableFlags.None;
|
||||
if (ImGui.Selectable($"{name}##{entry.Key}", this.RunningChoice == entry.Key, flags)) {
|
||||
this.RunningChoice = entry.Key;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.ListBoxFooter();
|
||||
}
|
||||
|
||||
ImGui.PopItemWidth();
|
||||
|
||||
if (ImGui.Button("Cancel") && this.RunningChoice != Guid.Empty) {
|
||||
this.Plugin.MacroHandler.CancelMacro(this.RunningChoice);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
var paused = this.RunningChoice != Guid.Empty && this.Plugin.MacroHandler.IsPaused(this.RunningChoice);
|
||||
if (ImGui.Button(paused ? "Resume" : "Pause") && this.RunningChoice != Guid.Empty) {
|
||||
if (paused) {
|
||||
this.Plugin.MacroHandler.ResumeMacro(this.RunningChoice);
|
||||
}
|
||||
else {
|
||||
this.Plugin.MacroHandler.PauseMacro(this.RunningChoice);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
ImGui.Checkbox("Show unique identifiers", ref this._showIdents);
|
||||
|
||||
ImGui.Columns(1);
|
||||
|
||||
ImGui.End();
|
||||
}
|
||||
|
||||
private IEnumerable<INode> DrawNode(INode node) {
|
||||
var toRemove = new List<INode>();
|
||||
ImGui.PushID($"{node.Id}");
|
||||
var open = ImGui.TreeNode($"{node.Id}", $"{node.Name}");
|
||||
|
||||
if (ImGui.BeginPopupContextItem()) {
|
||||
var name = node.Name;
|
||||
if (ImGui.InputText($"##{node.Id}-rename", ref name, (uint) this.Plugin.Config.MaxLength, ImGuiInputTextFlags.AutoSelectAll)) {
|
||||
node.Name = name;
|
||||
this.Plugin.Config.Save();
|
||||
}
|
||||
|
||||
if (ImGui.Button("Delete")) {
|
||||
toRemove.Add(node);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Copy UUID")) {
|
||||
ImGui.SetClipboardText($"{node.Id}");
|
||||
}
|
||||
|
||||
if (node is Macro macro) {
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Run##context")) {
|
||||
this.RunMacro(macro);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
|
||||
if (ImGui.BeginDragDropSource()) {
|
||||
ImGui.Text(node.Name);
|
||||
this.Dragged = node;
|
||||
ImGui.SetDragDropPayload("MACROLOGY-GUID", IntPtr.Zero, 0);
|
||||
ImGui.EndDragDropSource();
|
||||
}
|
||||
|
||||
if (node is Folder dfolder && ImGui.BeginDragDropTarget()) {
|
||||
var payloadPtr = ImGui.AcceptDragDropPayload("MACROLOGY-GUID");
|
||||
bool nullPtr;
|
||||
unsafe {
|
||||
nullPtr = payloadPtr.NativePtr == null;
|
||||
}
|
||||
|
||||
if (!nullPtr && payloadPtr.IsDelivery() && this.Dragged != null) {
|
||||
dfolder.Children.Add(this.Dragged.Duplicate());
|
||||
this.Dragged.Id = Guid.NewGuid();
|
||||
toRemove.Add(this.Dragged);
|
||||
|
||||
this.Dragged = null;
|
||||
}
|
||||
|
||||
ImGui.EndDragDropTarget();
|
||||
}
|
||||
|
||||
ImGui.PopID();
|
||||
|
||||
if (open) {
|
||||
if (node is Macro macro) {
|
||||
this.DrawMacro(macro);
|
||||
}
|
||||
else if (node is Folder folder) {
|
||||
this.DrawFolder(folder);
|
||||
foreach (var child in node.Children) {
|
||||
toRemove.AddRange(this.DrawNode(child));
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
|
||||
return toRemove;
|
||||
}
|
||||
|
||||
private void DrawMacro(Macro macro) {
|
||||
var contents = macro.Contents;
|
||||
ImGui.PushItemWidth(-1f);
|
||||
if (ImGui.InputTextMultiline($"##{macro.Id}-editor", ref contents, (uint) this.Plugin.Config.MaxLength, new Vector2(0, 250))) {
|
||||
macro.Contents = contents;
|
||||
this.Plugin.Config.Save();
|
||||
}
|
||||
|
||||
ImGui.PopItemWidth();
|
||||
|
||||
if (ImGui.Button("Run")) {
|
||||
this.RunMacro(macro);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawFolder(Folder folder) {
|
||||
}
|
||||
|
||||
private void RunMacro(Macro macro) {
|
||||
this.Plugin.MacroHandler.SpawnMacro(macro);
|
||||
}
|
||||
|
||||
private static bool IconButton(FontAwesomeIcon icon) {
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
var ret = ImGui.Button(icon.ToIconString());
|
||||
ImGui.PopFont();
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static void Tooltip(string text) {
|
||||
if (ImGui.IsItemHovered()) {
|
||||
ImGui.BeginTooltip();
|
||||
ImGui.TextUnformatted(text);
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
README.md
15
README.md
|
@ -1,19 +1,13 @@
|
|||
# Custom Commands and Macro Macros
|
||||
# Macrology
|
||||
|
||||
**This plugin is still under heavy development and is not
|
||||
stable. Proceed with caution.**
|
||||
|
||||
## Description
|
||||
|
||||
This command allows you to create custom commands (not yet
|
||||
implemented) and unrestricted macros.
|
||||
This command allows you to create better and unrestricted macros.
|
||||
|
||||
**Custom commands** are commands that run a specific macro. For
|
||||
example, you could bind `/h` to `/tp Estate Hall (Free Company)`, or
|
||||
even multiple commands in sequence. In order to do this, you need...
|
||||
|
||||
**Macro macros** are like normal macros but different. Unlike normal
|
||||
macros, you can't assign them to hotbar buttons (yet?), but you can
|
||||
Unlike normal macros, you can't assign them to hotbar buttons (yet?), but you can
|
||||
execute them using commands (and custom commands), run multiple at the
|
||||
same time, pause them, make them loop, use `<wait.X>` with fractional
|
||||
seconds, and, of course, make them as long as you please (the *macro*
|
||||
|
@ -24,7 +18,7 @@ for easier access.
|
|||
|
||||
## Commands
|
||||
|
||||
- `/ccmm` - opens the main interface
|
||||
- `/mmacros` - opens the main interface
|
||||
- `/mmacro <uuid>` - executes the macro with the given UUID
|
||||
- `/mmcancel <all|uuid>` - either cancels all currently-running macros
|
||||
or cancels the first instance of the macro represented by the given
|
||||
|
@ -57,6 +51,5 @@ When using `<wait.#>` in a macro, you can use decimal points, like
|
|||
|
||||
## Known issues
|
||||
|
||||
- Custom commands are not implemented.
|
||||
- There is no way good way to reorder macros.
|
||||
- There is no way to take a macro out of a folder (i.e. put it back at the root).
|
Loading…
Reference in New Issue
Block a user