refactor: rename to Macrology and clean up
This commit is contained in:
parent
a5e8c6a00e
commit
0554a87442
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
|
# Visual Studio Version 16
|
||||||
VisualStudioVersion = 16.0.30330.147
|
VisualStudioVersion = 16.0.30330.147
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
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
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D9C29BDA-B9DA-4E1D-AAFA-55B898E33593}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D9C29BDA-B9DA-4E1D-AAFA-55B898E33593}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
.editorconfig = .editorconfig
|
|
||||||
README.md = README.md
|
README.md = README.md
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
|
@ -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.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace CCMM {
|
namespace Macrology {
|
||||||
public class Configuration : IPluginConfiguration {
|
public class Configuration : IPluginConfiguration {
|
||||||
private CCMMPlugin plugin;
|
private Macrology Plugin { get; set; } = null!;
|
||||||
|
|
||||||
public int Version { get; set; } = 1;
|
public int Version { get; set; } = 1;
|
||||||
|
|
||||||
|
@ -19,17 +19,17 @@ namespace CCMM {
|
||||||
|
|
||||||
public int MaxLength { get; set; } = 10_000;
|
public int MaxLength { get; set; } = 10_000;
|
||||||
|
|
||||||
internal void Initialise(CCMMPlugin plugin) {
|
internal void Initialise(Macrology plugin) {
|
||||||
this.plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "CCMMPlugin cannot be null");
|
this.Plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "Macrology cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Save() {
|
internal void Save() {
|
||||||
string configPath = ConfigPath(plugin);
|
var configPath = ConfigPath(this.Plugin);
|
||||||
string configText = JsonConvert.SerializeObject(this, Formatting.Indented);
|
var configText = JsonConvert.SerializeObject(this, Formatting.Indented);
|
||||||
File.WriteAllText(configPath, configText);
|
File.WriteAllText(configPath, configText);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ConfigPath(CCMMPlugin plugin) {
|
private static string ConfigPath(Macrology plugin) {
|
||||||
string[] paths = {
|
string[] paths = {
|
||||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||||
"XIVLauncher",
|
"XIVLauncher",
|
||||||
|
@ -39,19 +39,23 @@ namespace CCMM {
|
||||||
return Path.Combine(paths);
|
return Path.Combine(paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Configuration Load(CCMMPlugin plugin) {
|
internal static Configuration? Load(Macrology plugin) {
|
||||||
string configPath = ConfigPath(plugin);
|
var configPath = ConfigPath(plugin);
|
||||||
if (File.Exists(configPath)) {
|
|
||||||
string configText;
|
if (!File.Exists(configPath)) {
|
||||||
try {
|
return new Configuration();
|
||||||
configText = File.ReadAllText(configPath);
|
|
||||||
} catch (IOException e) {
|
|
||||||
PluginLog.Log($"Could not read config at {configPath}: {e.Message}.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return JsonConvert.DeserializeObject<Configuration>(configText);
|
|
||||||
}
|
}
|
||||||
return new Configuration();
|
|
||||||
|
string configText;
|
||||||
|
try {
|
||||||
|
configText = File.ReadAllText(configPath);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
PluginLog.Log($"Could not read config at {configPath}: {e.Message}.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonConvert.DeserializeObject<Configuration>(configText);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<T> Traverse<T>(T item, Func<T, IEnumerable<T>> childSelector) {
|
private static IEnumerable<T> Traverse<T>(T item, Func<T, IEnumerable<T>> childSelector) {
|
||||||
|
@ -60,20 +64,14 @@ namespace CCMM {
|
||||||
while (stack.Any()) {
|
while (stack.Any()) {
|
||||||
var next = stack.Pop();
|
var next = stack.Pop();
|
||||||
yield return next;
|
yield return next;
|
||||||
foreach (var child in childSelector(next))
|
foreach (var child in childSelector(next)) {
|
||||||
stack.Push(child);
|
stack.Push(child);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Macro FindMacro(Guid id) {
|
public Macro? FindMacro(Guid id) {
|
||||||
foreach (INode node in this.Nodes) {
|
return this.Nodes.Select(node => (Macro?) Traverse(node, n => n.Children).FirstOrDefault(n => n.Id == id && n is Macro)).FirstOrDefault(macro => macro != null);
|
||||||
Macro macro = (Macro)Traverse(node, n => n.Children).FirstOrDefault(n => n.Id == id && n is Macro);
|
|
||||||
if (macro != null) {
|
|
||||||
return macro;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +88,7 @@ namespace CCMM {
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public List<INode> Children { get; private set; } = new List<INode>();
|
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.Id = Guid.NewGuid();
|
||||||
this.Name = name;
|
this.Name = name;
|
||||||
if (children != null) {
|
if (children != null) {
|
||||||
|
@ -139,18 +137,20 @@ namespace CCMM {
|
||||||
public class NodeConverter : JsonConverter {
|
public class NodeConverter : JsonConverter {
|
||||||
public override bool CanWrite => false;
|
public override bool CanWrite => false;
|
||||||
public override bool CanRead => true;
|
public override bool CanRead => true;
|
||||||
|
|
||||||
public override bool CanConvert(Type objectType) {
|
public override bool CanConvert(Type objectType) {
|
||||||
return objectType == typeof(INode);
|
return objectType == typeof(INode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
|
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
|
||||||
throw new InvalidOperationException("Use default serialization.");
|
throw new InvalidOperationException("Use default serialization.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
|
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) {
|
||||||
JArray jsonArray = JArray.Load(reader);
|
var jsonArray = JArray.Load(reader);
|
||||||
List<INode> list = new List<INode>();
|
var list = new List<INode>();
|
||||||
foreach (JToken token in jsonArray) {
|
foreach (var token in jsonArray) {
|
||||||
JObject jsonObject = (JObject)token;
|
var jsonObject = (JObject) token;
|
||||||
INode node;
|
INode node;
|
||||||
if (jsonObject.ContainsKey("Contents")) {
|
if (jsonObject.ContainsKey("Contents")) {
|
||||||
node = new Macro(
|
node = new Macro(
|
||||||
|
@ -158,15 +158,18 @@ namespace CCMM {
|
||||||
jsonObject["Name"].ToObject<string>(),
|
jsonObject["Name"].ToObject<string>(),
|
||||||
jsonObject["Contents"].ToObject<string>()
|
jsonObject["Contents"].ToObject<string>()
|
||||||
);
|
);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
node = new Folder(
|
node = new Folder(
|
||||||
jsonObject["Id"].ToObject<Guid>(),
|
jsonObject["Id"].ToObject<Guid>(),
|
||||||
jsonObject["Name"].ToObject<string>(),
|
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);
|
list.Add(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
|
@ -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.Game.Command;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace CCMM {
|
namespace Macrology {
|
||||||
public class CCMMPlugin : IDalamudPlugin {
|
public class Macrology : IDalamudPlugin {
|
||||||
private bool disposedValue;
|
private bool _disposedValue;
|
||||||
|
|
||||||
public string Name => "Custom Commands and Macro Macros";
|
public string Name => "Macrology";
|
||||||
|
|
||||||
public DalamudPluginInterface Interface { get; private set; }
|
public DalamudPluginInterface Interface { get; private set; } = null!;
|
||||||
public GameFunctions Functions { get; private set; }
|
public GameFunctions Functions { get; private set; } = null!;
|
||||||
public PluginUI Ui { get; private set; }
|
public PluginUi Ui { get; private set; } = null!;
|
||||||
public MacroHandler MacroHandler { get; private set; }
|
public MacroHandler MacroHandler { get; private set; } = null!;
|
||||||
public Configuration Config { get; private set; }
|
public Configuration Config { get; private set; } = null!;
|
||||||
private Commands Commands { get; set; }
|
private Commands Commands { get; set; } = null!;
|
||||||
|
|
||||||
public void Initialize(DalamudPluginInterface pluginInterface) {
|
public void Initialize(DalamudPluginInterface pluginInterface) {
|
||||||
this.Interface = pluginInterface ?? throw new ArgumentNullException(nameof(pluginInterface), "DalamudPluginInterface cannot be null");
|
this.Interface = pluginInterface ?? throw new ArgumentNullException(nameof(pluginInterface), "DalamudPluginInterface cannot be null");
|
||||||
this.Functions = new GameFunctions(this);
|
this.Functions = new GameFunctions(this);
|
||||||
this.Ui = new PluginUI(this);
|
this.Ui = new PluginUi(this);
|
||||||
this.MacroHandler = new MacroHandler(this);
|
this.MacroHandler = new MacroHandler(this);
|
||||||
this.Config = Configuration.Load(this) ?? new Configuration();
|
this.Config = Configuration.Load(this) ?? new Configuration();
|
||||||
this.Config.Initialise(this);
|
this.Config.Initialise(this);
|
||||||
|
@ -30,7 +29,7 @@ namespace CCMM {
|
||||||
this.Interface.Framework.OnUpdateEvent += this.MacroHandler.OnFrameworkUpdate;
|
this.Interface.Framework.OnUpdateEvent += this.MacroHandler.OnFrameworkUpdate;
|
||||||
this.Interface.ClientState.OnLogin += this.MacroHandler.OnLogin;
|
this.Interface.ClientState.OnLogin += this.MacroHandler.OnLogin;
|
||||||
this.Interface.ClientState.OnLogout += this.MacroHandler.OnLogout;
|
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) {
|
this.Interface.CommandManager.AddHandler(entry.Key, new CommandInfo(this.Commands.OnCommand) {
|
||||||
HelpMessage = entry.Value,
|
HelpMessage = entry.Value,
|
||||||
});
|
});
|
||||||
|
@ -38,27 +37,27 @@ namespace CCMM {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing) {
|
protected virtual void Dispose(bool disposing) {
|
||||||
if (!disposedValue) {
|
if (this._disposedValue) {
|
||||||
if (disposing) {
|
return;
|
||||||
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) {
|
|
||||||
this.Interface.CommandManager.RemoveHandler(command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
|
||||||
// TODO: set large fields to null
|
|
||||||
disposedValue = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 (var command in Commands.Descriptions.Keys) {
|
||||||
|
this.Interface.CommandManager.RemoveHandler(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._disposedValue = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
Dispose(disposing: true);
|
this.Dispose(true);
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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>
|
|
@ -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.
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
README.md
17
README.md
|
@ -1,19 +1,13 @@
|
||||||
# Custom Commands and Macro Macros
|
# Macrology
|
||||||
|
|
||||||
**This plugin is still under heavy development and is not
|
**This plugin is still under heavy development and is not
|
||||||
stable. Proceed with caution.**
|
stable. Proceed with caution.**
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
This command allows you to create custom commands (not yet
|
This command allows you to create better and unrestricted macros.
|
||||||
implemented) and unrestricted macros.
|
|
||||||
|
|
||||||
**Custom commands** are commands that run a specific macro. For
|
Unlike normal macros, you can't assign them to hotbar buttons (yet?), but you can
|
||||||
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
|
|
||||||
execute them using commands (and custom commands), run multiple at the
|
execute them using commands (and custom commands), run multiple at the
|
||||||
same time, pause them, make them loop, use `<wait.X>` with fractional
|
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*
|
seconds, and, of course, make them as long as you please (the *macro*
|
||||||
|
@ -24,7 +18,7 @@ for easier access.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
- `/ccmm` - opens the main interface
|
- `/mmacros` - opens the main interface
|
||||||
- `/mmacro <uuid>` - executes the macro with the given UUID
|
- `/mmacro <uuid>` - executes the macro with the given UUID
|
||||||
- `/mmcancel <all|uuid>` - either cancels all currently-running macros
|
- `/mmcancel <all|uuid>` - either cancels all currently-running macros
|
||||||
or cancels the first instance of the macro represented by the given
|
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
|
## Known issues
|
||||||
|
|
||||||
- Custom commands are not implemented.
|
|
||||||
- There is no way good way to reorder macros.
|
- 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).
|
- 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