refactor: begin update to HUD Manager
This commit is contained in:
parent
252e93b78c
commit
cb727afc72
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
|
|
|
@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 16
|
# Visual Studio Version 16
|
||||||
VisualStudioVersion = 16.0.30320.27
|
VisualStudioVersion = 16.0.30320.27
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HudSwap", "HudSwap\HudSwap.csproj", "{CBF13A29-A5A4-4FA2-8A4F-CFBB5FB83F90}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HUD Manager", "HUD Manager\HUD Manager.csproj", "{CBF13A29-A5A4-4FA2-8A4F-CFBB5FB83F90}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
33
HUD Manager/Configuration/Config.cs
Normal file
33
HUD Manager/Configuration/Config.cs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Dalamud.Configuration;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
|
||||||
|
namespace HUD_Manager.Configuration {
|
||||||
|
[Serializable]
|
||||||
|
public class Config : IPluginConfiguration {
|
||||||
|
public int Version { get; set; } = 2;
|
||||||
|
|
||||||
|
private DalamudPluginInterface Interface { get; set; } = null!;
|
||||||
|
|
||||||
|
public bool FirstRun { get; set; } = true;
|
||||||
|
public bool UnderstandsRisks { get; set; }
|
||||||
|
|
||||||
|
public bool ImportPositions { get; set; }
|
||||||
|
public bool SwapsEnabled { get; set; }
|
||||||
|
|
||||||
|
public HudSlot StagingSlot { get; set; } = HudSlot.Four;
|
||||||
|
|
||||||
|
public Dictionary<Guid, SavedLayout> Layouts { get; } = new();
|
||||||
|
|
||||||
|
public List<HudConditionMatch> HudConditionMatches { get; } = new();
|
||||||
|
|
||||||
|
public void Initialize(DalamudPluginInterface pluginInterface) {
|
||||||
|
this.Interface = pluginInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save() {
|
||||||
|
this.Interface.SavePluginConfig(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
HUD Manager/Configuration/ConfigV1.cs
Executable file
20
HUD Manager/Configuration/ConfigV1.cs
Executable file
|
@ -0,0 +1,20 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace HUD_Manager.Configuration {
|
||||||
|
[Serializable]
|
||||||
|
public class ConfigV1 {
|
||||||
|
public bool FirstRun { get; set; }
|
||||||
|
public bool UnderstandsRisks { get; set; }
|
||||||
|
public bool SwapsEnabled { get; set; }
|
||||||
|
public HudSlot StagingSlot { get; set; }
|
||||||
|
public Dictionary<Guid, ConfigV1Layout> Layouts2 { get; set; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class ConfigV1Layout {
|
||||||
|
public string Name { get; set; } = null!;
|
||||||
|
public Dictionary<string, Vector2<short>> Positions { get; set; } = null!;
|
||||||
|
public byte[] Hud { get; set; } = null!;
|
||||||
|
}
|
||||||
|
}
|
74
HUD Manager/Configuration/Migrator.cs
Executable file
74
HUD Manager/Configuration/Migrator.cs
Executable file
|
@ -0,0 +1,74 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using HUD_Manager.Structs;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace HUD_Manager.Configuration {
|
||||||
|
public static class Migrator {
|
||||||
|
private static Config Migrate(ConfigV1 old) {
|
||||||
|
var config = new Config {
|
||||||
|
FirstRun = old.FirstRun,
|
||||||
|
StagingSlot = old.StagingSlot,
|
||||||
|
SwapsEnabled = old.SwapsEnabled,
|
||||||
|
UnderstandsRisks = old.UnderstandsRisks,
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var entry in old.Layouts2) {
|
||||||
|
Layout layout;
|
||||||
|
unsafe {
|
||||||
|
fixed (byte* ptr = entry.Value.Hud) {
|
||||||
|
layout = Marshal.PtrToStructure<Layout>((IntPtr) ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var saved = new SavedLayout(entry.Value.Name, layout, entry.Value.Positions);
|
||||||
|
config.Layouts[entry.Key] = saved;
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string PluginConfig(string? pluginName = null) {
|
||||||
|
pluginName ??= Assembly.GetAssembly(typeof(Plugin)).GetName().Name;
|
||||||
|
return Path.Combine(new[] {
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||||
|
"XIVLauncher",
|
||||||
|
"pluginConfigs",
|
||||||
|
$"{pluginName}.json",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Config LoadConfig(Plugin plugin) {
|
||||||
|
var managerPath = PluginConfig();
|
||||||
|
|
||||||
|
if (File.Exists(managerPath)) {
|
||||||
|
goto DefaultConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hudSwapPath = PluginConfig("HudSwap");
|
||||||
|
|
||||||
|
if (File.Exists(hudSwapPath)) {
|
||||||
|
var oldText = File.ReadAllText(hudSwapPath);
|
||||||
|
var config = JsonConvert.DeserializeObject<JObject>(oldText);
|
||||||
|
uint version = 1;
|
||||||
|
if (config.TryGetValue("Version", out var token)) {
|
||||||
|
version = token.Value<uint>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version == 1) {
|
||||||
|
var v1 = config.ToObject<ConfigV1>(new JsonSerializer {
|
||||||
|
TypeNameHandling = TypeNameHandling.None,
|
||||||
|
});
|
||||||
|
|
||||||
|
return Migrate(v1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultConfig:
|
||||||
|
return plugin.Interface.GetPluginConfig() as Config ?? new Config();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
HUD Manager/Configuration/SavedLayout.cs
Normal file
40
HUD Manager/Configuration/SavedLayout.cs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using HUD_Manager.Structs;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace HUD_Manager.Configuration {
|
||||||
|
[Serializable]
|
||||||
|
public class SavedLayout {
|
||||||
|
public Dictionary<ElementKind, Element> Elements { get; }
|
||||||
|
public Dictionary<string, Vector2<short>> Positions { get; private set; }
|
||||||
|
|
||||||
|
public string Name { get; private set; }
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
public SavedLayout(string name, Dictionary<ElementKind, Element> elements, Dictionary<string, Vector2<short>> positions) {
|
||||||
|
this.Name = name;
|
||||||
|
this.Elements = elements;
|
||||||
|
this.Positions = positions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SavedLayout(string name, Layout hud, Dictionary<string, Vector2<short>> positions) {
|
||||||
|
this.Name = name;
|
||||||
|
this.Elements = hud.ToDictionary();
|
||||||
|
this.Positions = positions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Layout ToLayout() {
|
||||||
|
var elements = this.Elements.Values.ToList();
|
||||||
|
|
||||||
|
while (elements.Count < 81) {
|
||||||
|
elements.Add(new Element());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Layout {
|
||||||
|
elements = elements.ToArray(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
HUD Manager/DalamudPackager.targets
Executable file
10
HUD Manager/DalamudPackager.targets
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
<Project>
|
||||||
|
<Target Name="PackagePlugin" AfterTargets="Build" Condition="'$(Configuration)' == 'Release'">
|
||||||
|
<DalamudPackager
|
||||||
|
ProjectDir="$(ProjectDir)"
|
||||||
|
OutputPath="$(OutputPath)"
|
||||||
|
AssemblyName="$(AssemblyName)"
|
||||||
|
VersionComponents="4"
|
||||||
|
MakeZip="true"/>
|
||||||
|
</Target>
|
||||||
|
</Project>
|
60
HUD Manager/GameFunctions.cs
Normal file
60
HUD Manager/GameFunctions.cs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace HUD_Manager {
|
||||||
|
public class GameFunctions {
|
||||||
|
private delegate IntPtr GetUiBaseDelegate();
|
||||||
|
private delegate IntPtr GetUiWindowDelegate(IntPtr uiBase, string uiName, int index);
|
||||||
|
private delegate void MoveWindowDelegate(IntPtr windowBase, short x, short y);
|
||||||
|
|
||||||
|
private readonly GetUiBaseDelegate _getUiBase;
|
||||||
|
private readonly GetUiWindowDelegate _getUiWindow;
|
||||||
|
private readonly MoveWindowDelegate _moveWindow;
|
||||||
|
|
||||||
|
private Plugin Plugin { get; }
|
||||||
|
|
||||||
|
public GameFunctions(Plugin plugin) {
|
||||||
|
this.Plugin = plugin;
|
||||||
|
|
||||||
|
var getUiBasePtr = this.Plugin.Interface.TargetModuleScanner.ScanText("E8 ?? ?? ?? ?? 41 b8 01 00 00 00 48 8d 15 ?? ?? ?? ?? 48 8b 48 20 e8 ?? ?? ?? ?? 48 8b cf");
|
||||||
|
var getUiWindowPtr = this.Plugin.Interface.TargetModuleScanner.ScanText("e8 ?? ?? ?? ?? 48 8b cf 48 89 87 ?? ?? 00 00 e8 ?? ?? ?? ?? 41 b8 01 00 00 00");
|
||||||
|
var moveWindowPtr = this.Plugin.Interface.TargetModuleScanner.ScanText("E8 ?? ?? ?? ?? 48 83 BB ?? ?? ?? ?? 00 74 ?? 48 8B 8B ?? ?? ?? ?? 48 85 C9 74 ?? E8 ?? ?? ?? ??");
|
||||||
|
|
||||||
|
this._getUiBase = Marshal.GetDelegateForFunctionPointer<GetUiBaseDelegate>(getUiBasePtr);
|
||||||
|
this._getUiWindow = Marshal.GetDelegateForFunctionPointer<GetUiWindowDelegate>(getUiWindowPtr);
|
||||||
|
this._moveWindow = Marshal.GetDelegateForFunctionPointer<MoveWindowDelegate>(moveWindowPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntPtr GetUiBase() {
|
||||||
|
return this._getUiBase.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntPtr GetUiWindow(string uiName, int index) {
|
||||||
|
var uiBase = this.GetUiBase();
|
||||||
|
var offset = Marshal.ReadIntPtr(uiBase, 0x20);
|
||||||
|
|
||||||
|
return this._getUiWindow.Invoke(offset, uiName, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MoveWindow(string uiName, short x, short y) {
|
||||||
|
var windowBase = this.GetUiWindow(uiName, 1);
|
||||||
|
if (windowBase == IntPtr.Zero) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._moveWindow.Invoke(windowBase, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2<short>? GetWindowPosition(string uiName) {
|
||||||
|
var windowBase = this.GetUiWindow(uiName, 1);
|
||||||
|
if (windowBase == IntPtr.Zero) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var x = Marshal.ReadInt16(windowBase + 0x1bc);
|
||||||
|
var y = Marshal.ReadInt16(windowBase + 0x1bc + 2);
|
||||||
|
|
||||||
|
return new Vector2<short>(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
HUD Manager/HUD Manager.csproj
Executable file
39
HUD Manager/HUD Manager.csproj
Executable file
|
@ -0,0 +1,39 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<TargetFramework>net48</TargetFramework>
|
||||||
|
<Version>2.0.0.1</Version>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Dalamud, Version=5.2.3.3, Culture=neutral, PublicKeyToken=null">
|
||||||
|
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\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\dev\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\dev\ImGuiScene.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Lumina, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null">
|
||||||
|
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Lumina.Excel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
|
||||||
|
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
|
||||||
|
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Newtonsoft.Json.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="DalamudPackager" Version="1.2.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
10
HUD Manager/HUD Manager.yaml
Normal file
10
HUD Manager/HUD Manager.yaml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
author: ascclemens
|
||||||
|
name: HUD Manager
|
||||||
|
description: |
|
||||||
|
A plugin to manage your HUD.
|
||||||
|
|
||||||
|
- Save infinite HUD layouts
|
||||||
|
- Swap between HUD layouts based on conditions
|
||||||
|
- Edit HUD elements precisely without /hudlayout
|
||||||
|
- Export and import layouts
|
||||||
|
repo_url: https://git.sr.ht/~jkcclemens/HUDManager
|
145
HUD Manager/Hud.cs
Normal file
145
HUD Manager/Hud.cs
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using HUD_Manager.Structs;
|
||||||
|
|
||||||
|
namespace HUD_Manager {
|
||||||
|
public class Hud {
|
||||||
|
// Updated 5.45
|
||||||
|
// This is 81 elements * 36 bytes each. Each element is 32 bytes in ADDON.DAT,
|
||||||
|
// but they're 36 bytes when loaded into memory.
|
||||||
|
private const int LayoutSize = 0xb64;
|
||||||
|
// Updated 5.4
|
||||||
|
private const int SlotOffset = 0x59e8;
|
||||||
|
|
||||||
|
private delegate IntPtr GetFilePointerDelegate(byte index);
|
||||||
|
|
||||||
|
private delegate uint SetHudLayoutDelegate(IntPtr filePtr, uint hudLayout, byte unk0, byte unk1);
|
||||||
|
|
||||||
|
private readonly GetFilePointerDelegate? _getFilePointer;
|
||||||
|
private readonly SetHudLayoutDelegate? _setHudLayout;
|
||||||
|
|
||||||
|
private Plugin Plugin { get; }
|
||||||
|
|
||||||
|
public Hud(Plugin plugin) {
|
||||||
|
this.Plugin = plugin;
|
||||||
|
var getFilePointerPtr = this.Plugin.Interface.TargetModuleScanner.ScanText("E8 ?? ?? ?? ?? 48 85 C0 74 14 83 7B 44 00");
|
||||||
|
var setHudLayoutPtr = this.Plugin.Interface.TargetModuleScanner.ScanText("E8 ?? ?? ?? ?? 33 C0 EB 15");
|
||||||
|
if (getFilePointerPtr != IntPtr.Zero) {
|
||||||
|
this._getFilePointer = Marshal.GetDelegateForFunctionPointer<GetFilePointerDelegate>(getFilePointerPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setHudLayoutPtr != IntPtr.Zero) {
|
||||||
|
this._setHudLayout = Marshal.GetDelegateForFunctionPointer<SetHudLayoutDelegate>(setHudLayoutPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntPtr GetFilePointer(byte index) {
|
||||||
|
return this._getFilePointer?.Invoke(index) ?? IntPtr.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SelectSlot(HudSlot slot, bool force = false) {
|
||||||
|
if (this._setHudLayout == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var file = this.GetFilePointer(0);
|
||||||
|
// change the current slot so the game lets us pick one that's currently in use
|
||||||
|
if (!force) {
|
||||||
|
goto Return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentSlotPtr = this.GetDataPointer() + SlotOffset;
|
||||||
|
// read the current slot
|
||||||
|
var currentSlot = (uint) Marshal.ReadInt32(currentSlotPtr);
|
||||||
|
// change it to a different slot
|
||||||
|
if (currentSlot == (uint) slot) {
|
||||||
|
if (currentSlot < 3) {
|
||||||
|
currentSlot += 1;
|
||||||
|
} else {
|
||||||
|
currentSlot = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// back up this different slot
|
||||||
|
var backup = this.ReadLayout((HudSlot) currentSlot);
|
||||||
|
// change the current slot in memory
|
||||||
|
Marshal.WriteInt32(currentSlotPtr, (int) currentSlot);
|
||||||
|
// ask the game to change slot to our desired slot
|
||||||
|
// for some reason, this overwrites the current slot, so this is why we back up
|
||||||
|
this._setHudLayout.Invoke(file, (uint) slot, 0, 1);
|
||||||
|
// restore the backup
|
||||||
|
this.WriteLayout((HudSlot) currentSlot, backup);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Return:
|
||||||
|
this._setHudLayout.Invoke(file, (uint) slot, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntPtr GetDataPointer() {
|
||||||
|
var dataPtr = this.GetFilePointer(0) + 0x50;
|
||||||
|
return Marshal.ReadIntPtr(dataPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal IntPtr GetLayoutPointer(HudSlot slot) {
|
||||||
|
var slotNum = (int) slot;
|
||||||
|
return this.GetDataPointer() + 0x2c58 + slotNum * LayoutSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HudSlot GetActiveHudSlot() {
|
||||||
|
var slotVal = Marshal.ReadInt32(this.GetDataPointer() + SlotOffset);
|
||||||
|
|
||||||
|
if (!Enum.IsDefined(typeof(HudSlot), slotVal)) {
|
||||||
|
throw new IOException($"invalid hud slot in FFXIV memory of ${slotVal}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (HudSlot) slotVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Layout ReadLayout(HudSlot slot) {
|
||||||
|
var slotPtr = this.GetLayoutPointer(slot);
|
||||||
|
return Marshal.PtrToStructure<Layout>(slotPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteLayout(HudSlot slot, Layout layout) {
|
||||||
|
var slotPtr = this.GetLayoutPointer(slot);
|
||||||
|
|
||||||
|
var dict = layout.ToDictionary();
|
||||||
|
|
||||||
|
// update existing elements with saved data instead of wholesale overwriting
|
||||||
|
var slotLayout = this.ReadLayout(slot);
|
||||||
|
for (var i = 0; i < slotLayout.elements.Length; i++) {
|
||||||
|
if (dict.TryGetValue(slotLayout.elements[i].id, out var element)) {
|
||||||
|
slotLayout.elements[i] = element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Marshal.StructureToPtr(slotLayout, slotPtr, false);
|
||||||
|
|
||||||
|
// copy directly over
|
||||||
|
// Marshal.StructureToPtr(layout, slotPtr, false);
|
||||||
|
|
||||||
|
var currentSlot = this.GetActiveHudSlot();
|
||||||
|
if (currentSlot == slot) {
|
||||||
|
this.SelectSlot(currentSlot, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum HudSlot {
|
||||||
|
One = 0,
|
||||||
|
Two = 1,
|
||||||
|
Three = 2,
|
||||||
|
Four = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Vector2<T> {
|
||||||
|
public T X { get; }
|
||||||
|
public T Y { get; }
|
||||||
|
|
||||||
|
public Vector2(T x, T y) {
|
||||||
|
this.X = x;
|
||||||
|
this.Y = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
78
HUD Manager/Plugin.cs
Normal file
78
HUD Manager/Plugin.cs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Game.Command;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using HUD_Manager.Configuration;
|
||||||
|
|
||||||
|
namespace HUD_Manager {
|
||||||
|
public class Plugin : IDalamudPlugin {
|
||||||
|
public string Name => "HUD Manager";
|
||||||
|
|
||||||
|
private PluginUi Ui { get; set; } = null!;
|
||||||
|
private Swapper Swapper { get; set; } = null!;
|
||||||
|
|
||||||
|
public DalamudPluginInterface Interface { get; private set; } = null!;
|
||||||
|
public Hud Hud { get; private set; } = null!;
|
||||||
|
public Statuses Statuses { get; private set; } = null!;
|
||||||
|
public GameFunctions GameFunctions { get; private set; } = null!;
|
||||||
|
public Config Config { get; private set; } = null!;
|
||||||
|
|
||||||
|
public void Initialize(DalamudPluginInterface pluginInterface) {
|
||||||
|
this.Interface = pluginInterface;
|
||||||
|
|
||||||
|
this.Config = Migrator.LoadConfig(this);
|
||||||
|
this.Config.Initialize(this.Interface);
|
||||||
|
this.Config.Save();
|
||||||
|
|
||||||
|
this.Ui = new PluginUi(this);
|
||||||
|
this.Hud = new Hud(this);
|
||||||
|
this.Statuses = new Statuses(this);
|
||||||
|
this.GameFunctions = new GameFunctions(this);
|
||||||
|
this.Swapper = new Swapper(this);
|
||||||
|
|
||||||
|
if (this.Config.FirstRun) {
|
||||||
|
this.Config.FirstRun = false;
|
||||||
|
if (this.Config.Layouts.Count == 0) {
|
||||||
|
foreach (HudSlot slot in Enum.GetValues(typeof(HudSlot))) {
|
||||||
|
this.Ui.ImportSlot($"Auto-import {(int) slot + 1}", slot, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Interface.UiBuilder.OnBuildUi += this.Ui.Draw;
|
||||||
|
this.Interface.UiBuilder.OnOpenConfigUi += this.Ui.ConfigUi;
|
||||||
|
this.Interface.Framework.OnUpdateEvent += this.Swapper.OnFrameworkUpdate;
|
||||||
|
|
||||||
|
this.Interface.CommandManager.AddHandler("/hud", new CommandInfo(this.OnCommand) {
|
||||||
|
HelpMessage = "Open the HUD Manager settings or swap to layout name",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() {
|
||||||
|
this.Interface.UiBuilder.OnBuildUi -= this.Ui.Draw;
|
||||||
|
this.Interface.UiBuilder.OnOpenConfigUi -= this.Ui.ConfigUi;
|
||||||
|
this.Interface.Framework.OnUpdateEvent -= this.Swapper.OnFrameworkUpdate;
|
||||||
|
this.Interface.CommandManager.RemoveHandler("/hud");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCommand(string command, string args) {
|
||||||
|
if (string.IsNullOrWhiteSpace(args)) {
|
||||||
|
this.Ui.SettingsVisible = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entry = this.Config.Layouts.FirstOrDefault(e => e.Value.Name == args);
|
||||||
|
if (entry.Equals(default(KeyValuePair<Guid, SavedLayout>))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var layout = entry.Value;
|
||||||
|
|
||||||
|
this.Hud.WriteLayout(this.Config.StagingSlot, layout.ToLayout());
|
||||||
|
this.Hud.SelectSlot(this.Config.StagingSlot, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
639
HUD Manager/PluginUi.cs
Normal file
639
HUD Manager/PluginUi.cs
Normal file
|
@ -0,0 +1,639 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using HUD_Manager.Configuration;
|
||||||
|
using HUD_Manager.Structs;
|
||||||
|
using ImGuiNET;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Action = System.Action;
|
||||||
|
|
||||||
|
// TODO: Zone swaps?
|
||||||
|
|
||||||
|
namespace HUD_Manager {
|
||||||
|
public class PluginUi {
|
||||||
|
private static readonly string[] SavedWindows = {
|
||||||
|
"AreaMap",
|
||||||
|
"ChatLog",
|
||||||
|
"ChatLogPanel_0",
|
||||||
|
"ChatLogPanel_1",
|
||||||
|
"ChatLogPanel_2",
|
||||||
|
"ChatLogPanel_3",
|
||||||
|
};
|
||||||
|
|
||||||
|
private Plugin Plugin { get; }
|
||||||
|
|
||||||
|
private bool _settingsVisible;
|
||||||
|
|
||||||
|
public bool SettingsVisible {
|
||||||
|
get => this._settingsVisible;
|
||||||
|
set => this._settingsVisible = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _importName = "";
|
||||||
|
private string _renameName = "";
|
||||||
|
private Guid _selectedLayoutId = Guid.Empty;
|
||||||
|
|
||||||
|
private SavedLayout? SelectedSavedLayout => this._selectedLayoutId == Guid.Empty ? null : this.Plugin.Config.Layouts[this._selectedLayoutId];
|
||||||
|
|
||||||
|
private int _editingConditionIndex = -1;
|
||||||
|
private HudConditionMatch? _editingCondition;
|
||||||
|
private bool _scrollToAdd;
|
||||||
|
|
||||||
|
public PluginUi(Plugin plugin) {
|
||||||
|
this.Plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConfigUi(object sender, EventArgs args) {
|
||||||
|
this.SettingsVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawSettings() {
|
||||||
|
void DrawHudSlotButtons(string idSuffix, Action<HudSlot> hudSlotAction, Action clipboardAction) {
|
||||||
|
var slotButtonSize = new Vector2(40, 0);
|
||||||
|
foreach (HudSlot slot in Enum.GetValues(typeof(HudSlot))) {
|
||||||
|
// Surround the button with parentheses if this is the current slot
|
||||||
|
var slotText = slot == this.Plugin.Hud.GetActiveHudSlot() ? $"({(int) slot + 1})" : ((int) slot + 1).ToString();
|
||||||
|
var buttonName = $"{slotText}##${idSuffix}";
|
||||||
|
if (ImGui.Button(buttonName, slotButtonSize)) {
|
||||||
|
PluginLog.Log("Importing outer");
|
||||||
|
hudSlotAction(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (ImGui.Button($"Clipboard##{idSuffix}")) {
|
||||||
|
clipboardAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.SettingsVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SetNextWindowSize(new Vector2(500, 475), ImGuiCond.FirstUseEver);
|
||||||
|
|
||||||
|
if (!ImGui.Begin(this.Plugin.Name, ref this._settingsVisible)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.BeginTabBar("##hudmanager-tabs")) {
|
||||||
|
if (!this.Plugin.Config.UnderstandsRisks) {
|
||||||
|
if (ImGui.BeginTabItem("About")) {
|
||||||
|
ImGui.TextColored(new Vector4(1f, 0f, 0f, 1f), "Read this first");
|
||||||
|
ImGui.Separator();
|
||||||
|
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 20f);
|
||||||
|
ImGui.Text("HUD Manager will use the configured staging slot as its own slot to make changes to. This means the staging slot will be overwritten whenever any swap happens.");
|
||||||
|
ImGui.Spacing();
|
||||||
|
ImGui.Text("Any HUD layout changes you make while HUD Manager is enabled may potentially be lost, no matter what slot. If you want to make changes to your HUD layout, TURN OFF HUD Manager first.");
|
||||||
|
ImGui.Spacing();
|
||||||
|
ImGui.Text("When editing or making a new layout, to be completely safe, turn off swaps, set up your layout, import the layout into HUD Manager, then turn on swaps.");
|
||||||
|
ImGui.Spacing();
|
||||||
|
ImGui.Text("If you are a new user, HUD Manager auto-imported your existing layouts on startup.");
|
||||||
|
ImGui.Spacing();
|
||||||
|
ImGui.Text("Finally, HUD Manager is beta software. Back up your character data before using this plugin. You may lose some to all of your HUD layouts while testing this plugin.");
|
||||||
|
ImGui.Separator();
|
||||||
|
ImGui.Text("If you have read all of the above and are okay with continuing, check the box below to enable HUD Manager. You only need to do this once.");
|
||||||
|
ImGui.PopTextWrapPos();
|
||||||
|
var understandsRisks = this.Plugin.Config.UnderstandsRisks;
|
||||||
|
if (ImGui.Checkbox("I understand", ref understandsRisks)) {
|
||||||
|
this.Plugin.Config.UnderstandsRisks = understandsRisks;
|
||||||
|
this.Plugin.Config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTabBar();
|
||||||
|
ImGui.End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.BeginTabItem("Layouts")) {
|
||||||
|
ImGui.Text("Saved layouts");
|
||||||
|
if (this.Plugin.Config.Layouts.Count == 0) {
|
||||||
|
ImGui.Text("None saved!");
|
||||||
|
} else {
|
||||||
|
ImGui.PushItemWidth(-1);
|
||||||
|
if (ImGui.ListBoxHeader("##saved-layouts")) {
|
||||||
|
foreach (var entry in this.Plugin.Config.Layouts) {
|
||||||
|
if (!ImGui.Selectable($"{entry.Value.Name}##{entry.Key}", this._selectedLayoutId == entry.Key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._selectedLayoutId = entry.Key;
|
||||||
|
this._renameName = entry.Value.Name;
|
||||||
|
this._importName = this._renameName;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.ListBoxFooter();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PopItemWidth();
|
||||||
|
|
||||||
|
ImGui.PushItemWidth(200);
|
||||||
|
ImGui.InputText("##rename-input", ref this._renameName, 100);
|
||||||
|
ImGui.PopItemWidth();
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button("Rename") && this._renameName.Length != 0 && this.SelectedSavedLayout != null) {
|
||||||
|
var layout = this.Plugin.Config.Layouts[this._selectedLayoutId];
|
||||||
|
var newLayout = new SavedLayout(this._renameName, layout.ToLayout(), layout.Positions);
|
||||||
|
this.Plugin.Config.Layouts[this._selectedLayoutId] = newLayout;
|
||||||
|
this.Plugin.Config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
const int layoutActionButtonWidth = 30;
|
||||||
|
// `layoutActionButtonWidth` must be multiplied by however many action buttons there are here
|
||||||
|
ImGui.SameLine(ImGui.GetWindowContentRegionWidth() - layoutActionButtonWidth * 1);
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString(), new Vector2(layoutActionButtonWidth, 0)) &&
|
||||||
|
this.SelectedSavedLayout != null) {
|
||||||
|
this.Plugin.Config.Layouts.Remove(this._selectedLayoutId);
|
||||||
|
this.Plugin.Config.HudConditionMatches.RemoveAll(m => m.LayoutId == this._selectedLayoutId);
|
||||||
|
this._selectedLayoutId = Guid.Empty;
|
||||||
|
this._renameName = "";
|
||||||
|
this.Plugin.Config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PopFont();
|
||||||
|
|
||||||
|
ImGui.Text("Copy to...");
|
||||||
|
DrawHudSlotButtons("copy", slot => {
|
||||||
|
if (this.SelectedSavedLayout == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Plugin.Hud.WriteLayout(slot, this.SelectedSavedLayout.ToLayout());
|
||||||
|
}, () => {
|
||||||
|
if (this.SelectedSavedLayout == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = JsonConvert.SerializeObject(this.SelectedSavedLayout);
|
||||||
|
ImGui.SetClipboardText(json);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
ImGui.Text("Import");
|
||||||
|
|
||||||
|
ImGui.InputText("Imported layout name", ref this._importName, 100);
|
||||||
|
|
||||||
|
var importPositions = this.Plugin.Config.ImportPositions;
|
||||||
|
if (ImGui.Checkbox("Import window positions", ref importPositions)) {
|
||||||
|
this.Plugin.Config.ImportPositions = importPositions;
|
||||||
|
this.Plugin.Config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
HelpMarker("If this is checked, the position of the chat box and the map will be saved with the imported layout.");
|
||||||
|
|
||||||
|
var isOverwriting = this.Plugin.Config.Layouts.Values.Any(layout => layout.Name == this._importName);
|
||||||
|
ImGui.Text((isOverwriting ? "Overwrite" : "Import") + " from...");
|
||||||
|
|
||||||
|
DrawHudSlotButtons("import", slot => {
|
||||||
|
PluginLog.Log("Importing inner");
|
||||||
|
this.ImportSlot(this._importName, slot);
|
||||||
|
this._importName = "";
|
||||||
|
}, () => {
|
||||||
|
SavedLayout? shared = null;
|
||||||
|
try {
|
||||||
|
shared = JsonConvert.DeserializeObject<SavedLayout>(ImGui.GetClipboardText());
|
||||||
|
} catch (Exception) {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shared == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Import(this._importName, shared.ToLayout(), shared.Positions);
|
||||||
|
this._importName = "";
|
||||||
|
});
|
||||||
|
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.BeginTabItem("Swaps")) {
|
||||||
|
var enabled = this.Plugin.Config.SwapsEnabled;
|
||||||
|
if (ImGui.Checkbox("Enable swaps", ref enabled)) {
|
||||||
|
this.Plugin.Config.SwapsEnabled = enabled;
|
||||||
|
this.Plugin.Config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Text("Note: Disable swaps when editing your HUD.");
|
||||||
|
|
||||||
|
ImGui.Spacing();
|
||||||
|
var staging = ((int) this.Plugin.Config.StagingSlot + 1).ToString();
|
||||||
|
if (ImGui.BeginCombo("Staging slot", staging)) {
|
||||||
|
foreach (HudSlot slot in Enum.GetValues(typeof(HudSlot))) {
|
||||||
|
if (!ImGui.Selectable(((int) slot + 1).ToString())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Plugin.Config.StagingSlot = slot;
|
||||||
|
this.Plugin.Config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndCombo();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
HelpMarker("The staging slot is the HUD layout slot that will be used as your HUD layout. All changes will be written to this slot when swaps are enabled.");
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
if (this.Plugin.Config.Layouts.Count == 0) {
|
||||||
|
ImGui.Text("Create at least one layout to begin setting up swaps.");
|
||||||
|
} else {
|
||||||
|
ImGui.Text("Add swap conditions below.\nThe first condition that is satisfied will be the layout that is used.");
|
||||||
|
ImGui.Separator();
|
||||||
|
this.DrawConditionsTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
if (ImGui.BeginTabItem("Debug")) {
|
||||||
|
ImGui.TextUnformatted("Print layout pointer address");
|
||||||
|
|
||||||
|
if (ImGui.Button("1")) {
|
||||||
|
var ptr = this.Plugin.Hud.GetLayoutPointer(HudSlot.One);
|
||||||
|
this.Plugin.Interface.Framework.Gui.Chat.Print($"{ptr.ToInt64():x}");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (ImGui.Button("2")) {
|
||||||
|
var ptr = this.Plugin.Hud.GetLayoutPointer(HudSlot.Two);
|
||||||
|
this.Plugin.Interface.Framework.Gui.Chat.Print($"{ptr.ToInt64():x}");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (ImGui.Button("3")) {
|
||||||
|
var ptr = this.Plugin.Hud.GetLayoutPointer(HudSlot.Three);
|
||||||
|
this.Plugin.Interface.Framework.Gui.Chat.Print($"{ptr.ToInt64():x}");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (ImGui.Button("4")) {
|
||||||
|
var ptr = this.Plugin.Hud.GetLayoutPointer(HudSlot.Four);
|
||||||
|
this.Plugin.Interface.Framework.Gui.Chat.Print($"{ptr.ToInt64():x}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.Button("Save layout")) {
|
||||||
|
var ptr = this.Plugin.Hud.GetLayoutPointer(HudSlot.One);
|
||||||
|
var layout = Marshal.PtrToStructure<Layout>(ptr);
|
||||||
|
this.PreviousLayout = layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (ImGui.Button("Find difference")) {
|
||||||
|
var ptr = this.Plugin.Hud.GetLayoutPointer(HudSlot.One);
|
||||||
|
var layout = Marshal.PtrToStructure<Layout>(ptr);
|
||||||
|
|
||||||
|
foreach (var prevElem in this.PreviousLayout.elements) {
|
||||||
|
var currElem = layout.elements.FirstOrDefault(el => el.id == prevElem.id);
|
||||||
|
if (currElem.visibility == prevElem.visibility && !(Math.Abs(currElem.x - prevElem.x) > .01)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginLog.Log(currElem.id.ToString());
|
||||||
|
this.Plugin.Interface.Framework.Gui.Chat.Print(currElem.id.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.BeginChild("ui-elements", new Vector2(0, 0))) {
|
||||||
|
var ptr = this.Plugin.Hud.GetLayoutPointer(this.Plugin.Hud.GetActiveHudSlot());
|
||||||
|
var layout = Marshal.PtrToStructure<Layout>(ptr);
|
||||||
|
|
||||||
|
var changed = false;
|
||||||
|
|
||||||
|
var elements = (ElementKind[]) Enum.GetValues(typeof(ElementKind));
|
||||||
|
foreach (var kind in elements.OrderBy(el => el.ToString())) {
|
||||||
|
for (var i = 0; i < layout.elements.Length; i++) {
|
||||||
|
if (layout.elements[i].id != kind) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TextUnformatted(kind.ToString());
|
||||||
|
|
||||||
|
var x = layout.elements[i].x;
|
||||||
|
if (ImGui.DragFloat($"X##{kind}", ref x)) {
|
||||||
|
layout.elements[i].x = x;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var y = layout.elements[i].y;
|
||||||
|
if (ImGui.DragFloat($"Y##{kind}", ref y)) {
|
||||||
|
layout.elements[i].y = y;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var visible = layout.elements[i].visibility == Visibility.Visible;
|
||||||
|
if (ImGui.Checkbox($"Visible##{kind}", ref visible)) {
|
||||||
|
layout.elements[i].visibility = visible ? Visibility.Visible : Visibility.Hidden;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var scale = layout.elements[i].scale;
|
||||||
|
if (ImGui.DragFloat($"Scale##{kind}", ref scale)) {
|
||||||
|
layout.elements[i].scale = scale;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var opacity = (int) layout.elements[i].opacity;
|
||||||
|
if (ImGui.DragInt($"Opacity##{kind}", ref opacity, 1, 1, 255)) {
|
||||||
|
layout.elements[i].opacity = (byte) opacity;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
Marshal.StructureToPtr(layout, ptr, false);
|
||||||
|
this.Plugin.Hud.SelectSlot(this.Plugin.Hud.GetActiveHudSlot(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ImGui.EndTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Layout PreviousLayout { get; set; }
|
||||||
|
|
||||||
|
private void DrawConditionsTable() {
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
var height = ImGui.GetContentRegionAvail().Y - ImGui.CalcTextSize(FontAwesomeIcon.Plus.ToIconString()).Y - ImGui.GetStyle().ItemSpacing.Y - ImGui.GetStyle().ItemInnerSpacing.Y * 2;
|
||||||
|
ImGui.PopFont();
|
||||||
|
if (!ImGui.BeginChild("##conditions-table", new Vector2(-1, height))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Columns(4);
|
||||||
|
|
||||||
|
var conditions = new List<HudConditionMatch>(this.Plugin.Config.HudConditionMatches);
|
||||||
|
if (this._editingConditionIndex == conditions.Count) {
|
||||||
|
conditions.Add(new HudConditionMatch());
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Text("Job");
|
||||||
|
ImGui.NextColumn();
|
||||||
|
|
||||||
|
ImGui.Text("State");
|
||||||
|
ImGui.NextColumn();
|
||||||
|
|
||||||
|
ImGui.Text("Layout");
|
||||||
|
ImGui.NextColumn();
|
||||||
|
|
||||||
|
ImGui.Text("Options");
|
||||||
|
ImGui.NextColumn();
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
var addCondition = false;
|
||||||
|
var actionedItemIndex = -1;
|
||||||
|
var action = 0; // 0 for delete, otherwise move.
|
||||||
|
foreach (var item in conditions.Select((cond, i) => new {cond, i})) {
|
||||||
|
if (this._editingConditionIndex == item.i) {
|
||||||
|
this._editingCondition ??= new HudConditionMatch();
|
||||||
|
ImGui.PushItemWidth(-1);
|
||||||
|
if (ImGui.BeginCombo("##condition-edit-job", this._editingCondition.ClassJob ?? "Any")) {
|
||||||
|
if (ImGui.Selectable("Any##condition-edit-job")) {
|
||||||
|
this._editingCondition.ClassJob = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var job in this.Plugin.Interface.Data.GetExcelSheet<ClassJob>().Skip(1)) {
|
||||||
|
if (ImGui.Selectable($"{job.Abbreviation}##condition-edit-job")) {
|
||||||
|
this._editingCondition.ClassJob = job.Abbreviation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndCombo();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PopItemWidth();
|
||||||
|
ImGui.NextColumn();
|
||||||
|
|
||||||
|
ImGui.PushItemWidth(-1);
|
||||||
|
if (ImGui.BeginCombo("##condition-edit-status", this._editingCondition.Status?.Name() ?? "Any")) {
|
||||||
|
if (ImGui.Selectable("Any##condition-edit-status")) {
|
||||||
|
this._editingCondition.Status = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Status status in Enum.GetValues(typeof(Status))) {
|
||||||
|
if (ImGui.Selectable($"{status.Name()}##condition-edit-status")) {
|
||||||
|
this._editingCondition.Status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndCombo();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PopItemWidth();
|
||||||
|
ImGui.NextColumn();
|
||||||
|
|
||||||
|
ImGui.PushItemWidth(-1);
|
||||||
|
var comboPreview = this._editingCondition.LayoutId == Guid.Empty ? string.Empty : this.Plugin.Config.Layouts[this._editingCondition.LayoutId].Name;
|
||||||
|
if (ImGui.BeginCombo("##condition-edit-layout", comboPreview)) {
|
||||||
|
foreach (var layout in this.Plugin.Config.Layouts) {
|
||||||
|
if (ImGui.Selectable($"{layout.Value.Name}##condition-edit-layout-{layout.Key}")) {
|
||||||
|
this._editingCondition.LayoutId = layout.Key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndCombo();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PopItemWidth();
|
||||||
|
ImGui.NextColumn();
|
||||||
|
|
||||||
|
if (this._editingCondition.LayoutId != Guid.Empty) {
|
||||||
|
if (IconButton(FontAwesomeIcon.Check, "condition-edit")) {
|
||||||
|
addCondition = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IconButton(FontAwesomeIcon.Times, "condition-stop")) {
|
||||||
|
this._editingConditionIndex = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._scrollToAdd) {
|
||||||
|
this._scrollToAdd = false;
|
||||||
|
ImGui.SetScrollHereY();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ImGui.Text(item.cond.ClassJob ?? string.Empty);
|
||||||
|
ImGui.NextColumn();
|
||||||
|
|
||||||
|
ImGui.Text(item.cond.Status?.Name() ?? string.Empty);
|
||||||
|
ImGui.NextColumn();
|
||||||
|
|
||||||
|
this.Plugin.Config.Layouts.TryGetValue(item.cond.LayoutId, out var condLayout);
|
||||||
|
ImGui.Text(condLayout?.Name ?? string.Empty);
|
||||||
|
ImGui.NextColumn();
|
||||||
|
|
||||||
|
if (IconButton(FontAwesomeIcon.PencilAlt, $"{item.i}")) {
|
||||||
|
this._editingConditionIndex = item.i;
|
||||||
|
this._editingCondition = item.cond;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (IconButton(FontAwesomeIcon.Trash, $"{item.i}")) {
|
||||||
|
actionedItemIndex = item.i;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (IconButton(FontAwesomeIcon.ArrowUp, $"{item.i}")) {
|
||||||
|
actionedItemIndex = item.i;
|
||||||
|
action = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (IconButton(FontAwesomeIcon.ArrowDown, $"{item.i}")) {
|
||||||
|
actionedItemIndex = item.i;
|
||||||
|
action = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.NextColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Columns(1);
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
ImGui.EndChild();
|
||||||
|
|
||||||
|
if (IconButton(FontAwesomeIcon.Plus, "condition")) {
|
||||||
|
this._editingConditionIndex = this.Plugin.Config.HudConditionMatches.Count;
|
||||||
|
this._editingCondition = new HudConditionMatch();
|
||||||
|
this._scrollToAdd = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var recalculate = false;
|
||||||
|
|
||||||
|
if (addCondition) {
|
||||||
|
recalculate = true;
|
||||||
|
if (this._editingConditionIndex == this.Plugin.Config.HudConditionMatches.Count && this._editingCondition != null) {
|
||||||
|
this.Plugin.Config.HudConditionMatches.Add(this._editingCondition);
|
||||||
|
} else if (this._editingCondition != null) {
|
||||||
|
this.Plugin.Config.HudConditionMatches.RemoveAt(this._editingConditionIndex);
|
||||||
|
this.Plugin.Config.HudConditionMatches.Insert(this._editingConditionIndex, this._editingCondition);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Plugin.Config.Save();
|
||||||
|
this._editingConditionIndex = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionedItemIndex >= 0) {
|
||||||
|
recalculate = true;
|
||||||
|
if (action == 0) {
|
||||||
|
this.Plugin.Config.HudConditionMatches.RemoveAt(actionedItemIndex);
|
||||||
|
} else {
|
||||||
|
if (actionedItemIndex + action >= 0 && actionedItemIndex + action < this.Plugin.Config.HudConditionMatches.Count) {
|
||||||
|
// Move the condition.
|
||||||
|
var c = this.Plugin.Config.HudConditionMatches[actionedItemIndex];
|
||||||
|
this.Plugin.Config.HudConditionMatches.RemoveAt(actionedItemIndex);
|
||||||
|
this.Plugin.Config.HudConditionMatches.Insert(actionedItemIndex + action, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Plugin.Config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!recalculate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var player = this.Plugin.Interface.ClientState.LocalPlayer;
|
||||||
|
if (player == null || !this.Plugin.Config.SwapsEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Plugin.Statuses.Update(player);
|
||||||
|
this.Plugin.Statuses.SetHudLayout(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IconButton(FontAwesomeIcon icon, string append = "") {
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
var button = ImGui.Button($"{icon.ToIconString()}##{append}");
|
||||||
|
ImGui.PopFont();
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void HelpMarker(string text) {
|
||||||
|
ImGui.TextDisabled("(?)");
|
||||||
|
if (!ImGui.IsItemHovered()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.BeginTooltip();
|
||||||
|
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 20f);
|
||||||
|
ImGui.TextUnformatted(text);
|
||||||
|
ImGui.PopTextWrapPos();
|
||||||
|
ImGui.EndTooltip();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw() {
|
||||||
|
this.DrawSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<string, Vector2<short>> GetPositions() {
|
||||||
|
var positions = new Dictionary<string, Vector2<short>>();
|
||||||
|
|
||||||
|
foreach (var name in SavedWindows) {
|
||||||
|
var pos = this.Plugin.GameFunctions.GetWindowPosition(name);
|
||||||
|
if (pos != null) {
|
||||||
|
positions[name] = pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return positions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ImportSlot(string name, HudSlot slot, bool save = true) {
|
||||||
|
var positions = this.Plugin.Config.ImportPositions
|
||||||
|
? this.GetPositions()
|
||||||
|
: new Dictionary<string, Vector2<short>>();
|
||||||
|
|
||||||
|
this.Import(name, this.Plugin.Hud.ReadLayout(slot), positions, save);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Import(string name, Layout layout, Dictionary<string, Vector2<short>> positions, bool save = true) {
|
||||||
|
var guid = this.Plugin.Config.Layouts.FirstOrDefault(kv => kv.Value.Name == name).Key;
|
||||||
|
guid = guid != default ? guid : Guid.NewGuid();
|
||||||
|
|
||||||
|
this.Plugin.Config.Layouts[guid] = new SavedLayout(name, layout, positions);
|
||||||
|
if (save) {
|
||||||
|
this.Plugin.Config.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,64 +1,62 @@
|
||||||
using Dalamud.Game.ClientState;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Dalamud.Game.ClientState;
|
||||||
using Dalamud.Game.ClientState.Actors.Types;
|
using Dalamud.Game.ClientState.Actors.Types;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Converters;
|
||||||
|
|
||||||
// TODO: Zone swaps?
|
// TODO: Zone swaps?
|
||||||
|
|
||||||
namespace HudSwap {
|
namespace HUD_Manager {
|
||||||
public class Statuses {
|
public class Statuses {
|
||||||
private readonly HudSwapPlugin plugin;
|
private Plugin Plugin { get; }
|
||||||
private readonly DalamudPluginInterface pi;
|
|
||||||
|
|
||||||
private readonly Dictionary<Status, bool> condition = new Dictionary<Status, bool>();
|
private readonly Dictionary<Status, bool> _condition = new();
|
||||||
private ClassJob job;
|
private ClassJob? _job;
|
||||||
|
|
||||||
internal static byte GetStatus(DalamudPluginInterface pi, Actor actor) {
|
internal static byte GetStatus(DalamudPluginInterface pi, Actor actor) {
|
||||||
IntPtr statusPtr = pi.TargetModuleScanner.ResolveRelativeAddress(actor.Address, 0x1980);
|
var statusPtr = pi.TargetModuleScanner.ResolveRelativeAddress(actor.Address, 0x1980);
|
||||||
return Marshal.ReadByte(statusPtr);
|
return Marshal.ReadByte(statusPtr);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Statuses(HudSwapPlugin plugin, DalamudPluginInterface pi) {
|
public Statuses(Plugin plugin) {
|
||||||
this.plugin = plugin;
|
this.Plugin = plugin;
|
||||||
this.pi = pi;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Update(PlayerCharacter player) {
|
public bool Update(PlayerCharacter? player) {
|
||||||
if (player == null) {
|
if (player == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool anyChanged = false;
|
var anyChanged = false;
|
||||||
|
|
||||||
ClassJob currentJob = this.pi.Data.GetExcelSheet<ClassJob>().GetRow(player.ClassJob.Id);
|
var currentJob = this.Plugin.Interface.Data.GetExcelSheet<ClassJob>().GetRow(player.ClassJob.Id);
|
||||||
if (this.job != null && this.job != currentJob) {
|
if (this._job != null && this._job != currentJob) {
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
}
|
}
|
||||||
this.job = currentJob;
|
this._job = currentJob;
|
||||||
|
|
||||||
foreach (Status status in Enum.GetValues(typeof(Status))) {
|
foreach (Status status in Enum.GetValues(typeof(Status))) {
|
||||||
var old = this.condition.ContainsKey(status) && this.condition[status];
|
var old = this._condition.ContainsKey(status) && this._condition[status];
|
||||||
this.condition[status] = status.Active(player, this.pi);
|
this._condition[status] = status.Active(player, this.Plugin.Interface);
|
||||||
anyChanged |= old != this.condition[status];
|
anyChanged |= old != this._condition[status];
|
||||||
}
|
}
|
||||||
|
|
||||||
return anyChanged;
|
return anyChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Guid CalculateCurrentHud() {
|
private Guid CalculateCurrentHud() {
|
||||||
PlayerCharacter player = this.pi.ClientState.LocalPlayer;
|
var player = this.Plugin.Interface.ClientState.LocalPlayer;
|
||||||
if (player == null) {
|
if (player == null) {
|
||||||
return Guid.Empty;
|
return Guid.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var match in this.plugin.Config.HudConditionMatches) {
|
foreach (var match in this.Plugin.Config.HudConditionMatches) {
|
||||||
if ((!match.Status.HasValue || this.condition[match.Status.Value]) &&
|
if ((!match.Status.HasValue || this._condition[match.Status.Value]) &&
|
||||||
(match.ClassJob == null || this.job.Abbreviation == match.ClassJob)) {
|
(match.ClassJob == null || this._job?.Abbreviation == match.ClassJob)) {
|
||||||
return match.LayoutId;
|
return match.LayoutId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,23 +64,23 @@ namespace HudSwap {
|
||||||
return Guid.Empty;
|
return Guid.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetHudLayout(PlayerCharacter player, bool update = false) {
|
public void SetHudLayout(PlayerCharacter? player, bool update = false) {
|
||||||
if (update && player != null) {
|
if (update && player != null) {
|
||||||
this.Update(player);
|
this.Update(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
Guid layoutId = this.CalculateCurrentHud();
|
var layoutId = this.CalculateCurrentHud();
|
||||||
if (layoutId == Guid.Empty) {
|
if (layoutId == Guid.Empty) {
|
||||||
return; // FIXME: do something better
|
return; // FIXME: do something better
|
||||||
}
|
}
|
||||||
if (!this.plugin.Config.Layouts2.TryGetValue(layoutId, out Layout layout)) {
|
if (!this.Plugin.Config.Layouts.TryGetValue(layoutId, out var layout)) {
|
||||||
return; // FIXME: do something better
|
return; // FIXME: do something better
|
||||||
}
|
}
|
||||||
this.plugin.Hud.WriteLayout(this.plugin.Config.StagingSlot, layout.Hud);
|
this.Plugin.Hud.WriteLayout(this.Plugin.Config.StagingSlot, layout.ToLayout());
|
||||||
this.plugin.Hud.SelectSlot(this.plugin.Config.StagingSlot, true);
|
this.Plugin.Hud.SelectSlot(this.Plugin.Config.StagingSlot, true);
|
||||||
|
|
||||||
foreach (KeyValuePair<string, Vector2<short>> entry in layout.Positions) {
|
foreach (var entry in layout.Positions) {
|
||||||
this.plugin.GameFunctions.MoveWindow(entry.Key, entry.Value.X, entry.Value.Y);
|
this.Plugin.GameFunctions.MoveWindow(entry.Key, entry.Value.X, entry.Value.Y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,7 +90,7 @@ namespace HudSwap {
|
||||||
/// Values stored here should be the abbreviation of the class/job name (all caps).
|
/// Values stored here should be the abbreviation of the class/job name (all caps).
|
||||||
/// We do this because using <see cref="ClassJob"/> results in circular dependency errors when serializing.
|
/// We do this because using <see cref="ClassJob"/> results in circular dependency errors when serializing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClassJob { get; set; }
|
public string? ClassJob { get; set; }
|
||||||
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public Status? Status { get; set; }
|
public Status? Status { get; set; }
|
||||||
|
@ -141,7 +139,7 @@ namespace HudSwap {
|
||||||
throw new ArgumentNullException(nameof(pi), "DalamudPluginInterface cannot be null");
|
throw new ArgumentNullException(nameof(pi), "DalamudPluginInterface cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
ConditionFlag flag = (ConditionFlag)status;
|
var flag = (ConditionFlag)status;
|
||||||
if (flag != ConditionFlag.None) {
|
if (flag != ConditionFlag.None) {
|
||||||
return pi.ClientState.Condition[flag];
|
return pi.ClientState.Condition[flag];
|
||||||
}
|
}
|
29
HUD Manager/Structs/Element.cs
Executable file
29
HUD Manager/Structs/Element.cs
Executable file
|
@ -0,0 +1,29 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace HUD_Manager.Structs {
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct Element {
|
||||||
|
// [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||||
|
// public byte[] unknown0;
|
||||||
|
|
||||||
|
public ElementKind id;
|
||||||
|
|
||||||
|
public float x;
|
||||||
|
|
||||||
|
public float y;
|
||||||
|
|
||||||
|
public float scale;
|
||||||
|
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 9)]
|
||||||
|
public byte[] unknown4;
|
||||||
|
|
||||||
|
public Visibility visibility;
|
||||||
|
|
||||||
|
public byte unknown6;
|
||||||
|
|
||||||
|
public byte opacity;
|
||||||
|
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||||
|
public byte[] unknown8;
|
||||||
|
}
|
||||||
|
}
|
79
HUD Manager/Structs/ElementKind.cs
Executable file
79
HUD Manager/Structs/ElementKind.cs
Executable file
|
@ -0,0 +1,79 @@
|
||||||
|
namespace HUD_Manager.Structs {
|
||||||
|
public enum ElementKind : uint {
|
||||||
|
FocusTargetBar = 3264409695,
|
||||||
|
StatusInfoEnfeeblements = 511728259,
|
||||||
|
StatusInfoEnhancements = 524431540,
|
||||||
|
StatusInfoOther = 482796762,
|
||||||
|
TargetInfoHp = 3172107127,
|
||||||
|
TargetInfoProgressBar = 3411321583,
|
||||||
|
TargetInfoStatus = 124737899,
|
||||||
|
PartyList = 1027756089,
|
||||||
|
EnemyList = 3099420293,
|
||||||
|
ScenarioGuide = 2297324375,
|
||||||
|
ExperienceBar = 568671438,
|
||||||
|
PetHotbar = 3637610751,
|
||||||
|
Hotbar10 = 4117249958,
|
||||||
|
Hotbar9 = 4104803729,
|
||||||
|
Hotbar8 = 4294316716,
|
||||||
|
Hotbar7 = 4264851611,
|
||||||
|
Hotbar6 = 4235380418,
|
||||||
|
Hotbar5 = 4256214261,
|
||||||
|
Hotbar4 = 4177508976,
|
||||||
|
Hotbar3 = 4181577799,
|
||||||
|
Hotbar2 = 4219170334,
|
||||||
|
Hotbar1 = 3297588741,
|
||||||
|
CrossHotbar = 3129075921,
|
||||||
|
ProgressBar = 3971127313,
|
||||||
|
Minimap = 1901658651,
|
||||||
|
BloodGauge = 4031678328, // DRK
|
||||||
|
DarksideGauge = 4052544847, // DRK
|
||||||
|
OathGauge = 4022009408, // PLD
|
||||||
|
BeastGauge = 2136801802, // WAR
|
||||||
|
PowderGauge = 2931998943, // GNB
|
||||||
|
ArcanaGauge = 2509863090, // AST
|
||||||
|
AetherflowGaugeSch = 3403503819, // SCH
|
||||||
|
FaerieGauge = 2712183943, // SCH
|
||||||
|
HealingGauge = 2050434994, // WHM
|
||||||
|
DragonGauge = 3130538176, // DRG
|
||||||
|
ChakraGauge = 1939064324, // MNK
|
||||||
|
HutonGauge = 1895405704, // NIN
|
||||||
|
NinkiGauge = 1899754175, // NIN
|
||||||
|
SenGauge = 3983830498, // SAM
|
||||||
|
KenkiGauge = 3971352533, // SAM
|
||||||
|
SongGauge = 2121561139, // BRD
|
||||||
|
HeatGauge = 2557790060, // MCH
|
||||||
|
StepGauge = 2431309076, // DNC
|
||||||
|
FourfoldFeathers = 2435366691, // DNC
|
||||||
|
ElementalGauge = 3702264410, // BLM
|
||||||
|
BalanceGauge = 4010433280, // RDM
|
||||||
|
TranceGauge = 976301837, // SMN
|
||||||
|
AetherflowGaugeSmn = 1005798714, // SMN
|
||||||
|
ItemHelp = 1120659295,
|
||||||
|
ActionHelp = 1180822218,
|
||||||
|
Gil = 1125522082,
|
||||||
|
InventoryGrid = 471196175,
|
||||||
|
MainMenu = 2331597424,
|
||||||
|
Notices = 3743511396,
|
||||||
|
ParameterBar = 2552153246,
|
||||||
|
LimitGauge = 3349103882,
|
||||||
|
DutyList = 2727411922,
|
||||||
|
ServerInfo = 3450378102,
|
||||||
|
AllianceList1 = 692286106,
|
||||||
|
AllianceList2 = 721800387,
|
||||||
|
NewGamePlusGuide = 3660166250,
|
||||||
|
TargetBar = 2436811133,
|
||||||
|
StatusEffects = 1247188502,
|
||||||
|
DutyGauge = 2168013717,
|
||||||
|
DutyAction = 1421395594,
|
||||||
|
CompressedAether = 3300326260,
|
||||||
|
RivalWingsMercenaryInfo = 2045148168,
|
||||||
|
RivalWingsTeamInfo = 1180574470,
|
||||||
|
RivalWingsStationInfo = 2702651974,
|
||||||
|
RivalWingsAllianceList = 3869061330,
|
||||||
|
RivalWingsGauges = 273150177,
|
||||||
|
TheFeastEnemyInfo = 912936203,
|
||||||
|
TheFeastAllyInfo = 933766972,
|
||||||
|
TheFeastScore = 3622852831,
|
||||||
|
BattleHighGauge = 884971695,
|
||||||
|
}
|
||||||
|
}
|
24
HUD Manager/Structs/Layout.cs
Executable file
24
HUD Manager/Structs/Layout.cs
Executable file
|
@ -0,0 +1,24 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace HUD_Manager.Structs {
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct Layout {
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 81)]
|
||||||
|
public Element[] elements;
|
||||||
|
|
||||||
|
public Dictionary<ElementKind, Element> ToDictionary() {
|
||||||
|
// NOTE: not using ToDictionary here because duplicate keys are possible with old broken layouts
|
||||||
|
var dict = new Dictionary<ElementKind, Element>();
|
||||||
|
foreach (var elem in this.elements) {
|
||||||
|
if (elem.id == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
dict[elem.id] = elem;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
HUD Manager/Structs/Visibility.cs
Executable file
6
HUD Manager/Structs/Visibility.cs
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace HUD_Manager.Structs {
|
||||||
|
public enum Visibility : byte {
|
||||||
|
Hidden = 1,
|
||||||
|
Visible = 3,
|
||||||
|
}
|
||||||
|
}
|
31
HUD Manager/Swapper.cs
Normal file
31
HUD Manager/Swapper.cs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
using System;
|
||||||
|
using Dalamud.Game.Internal;
|
||||||
|
|
||||||
|
namespace HUD_Manager {
|
||||||
|
public class Swapper {
|
||||||
|
private Plugin Plugin { get; }
|
||||||
|
|
||||||
|
public Swapper(Plugin plugin) {
|
||||||
|
this.Plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnFrameworkUpdate(Framework framework) {
|
||||||
|
if (framework == null) {
|
||||||
|
throw new ArgumentNullException(nameof(framework), "Framework cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.Plugin.Config.SwapsEnabled || !this.Plugin.Config.UnderstandsRisks) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var player = this.Plugin.Interface.ClientState.LocalPlayer;
|
||||||
|
if (player == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.Plugin.Statuses.Update(player)) {
|
||||||
|
this.Plugin.Statuses.SetHudLayout(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,69 +0,0 @@
|
||||||
using Dalamud.Plugin;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace HudSwap {
|
|
||||||
public class GameFunctions {
|
|
||||||
private delegate IntPtr GetUIBaseDelegate();
|
|
||||||
private delegate IntPtr GetUIWindowDelegate(IntPtr uiBase, string uiName, int index);
|
|
||||||
private delegate void MoveWindowDelegate(IntPtr windowBase, short x, short y);
|
|
||||||
|
|
||||||
private readonly GetUIBaseDelegate getUIBase;
|
|
||||||
private readonly GetUIWindowDelegate getUIWindow;
|
|
||||||
private readonly MoveWindowDelegate moveWindow;
|
|
||||||
|
|
||||||
public GameFunctions(DalamudPluginInterface pi) {
|
|
||||||
if (pi == null) {
|
|
||||||
throw new ArgumentNullException(nameof(pi), "DalamudPluginInterface cannot be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
IntPtr getUIBasePtr = pi.TargetModuleScanner.ScanText("E8 ?? ?? ?? ?? 41 b8 01 00 00 00 48 8d 15 ?? ?? ?? ?? 48 8b 48 20 e8 ?? ?? ?? ?? 48 8b cf");
|
|
||||||
IntPtr getUIWindowPtr = pi.TargetModuleScanner.ScanText("e8 ?? ?? ?? ?? 48 8b cf 48 89 87 ?? ?? 00 00 e8 ?? ?? ?? ?? 41 b8 01 00 00 00");
|
|
||||||
IntPtr moveWindowPtr = pi.TargetModuleScanner.ScanText("E8 ?? ?? ?? ?? 48 83 BB ?? ?? ?? ?? 00 74 ?? 48 8B 8B ?? ?? ?? ?? 48 85 C9 74 ?? E8 ?? ?? ?? ??");
|
|
||||||
|
|
||||||
if (getUIBasePtr == IntPtr.Zero || getUIWindowPtr == IntPtr.Zero || moveWindowPtr == IntPtr.Zero) {
|
|
||||||
throw new ApplicationException("could not get game functions");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.getUIBase = Marshal.GetDelegateForFunctionPointer<GetUIBaseDelegate>(getUIBasePtr);
|
|
||||||
this.getUIWindow = Marshal.GetDelegateForFunctionPointer<GetUIWindowDelegate>(getUIWindowPtr);
|
|
||||||
this.moveWindow = Marshal.GetDelegateForFunctionPointer<MoveWindowDelegate>(moveWindowPtr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr GetUIBase() {
|
|
||||||
return this.getUIBase.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr GetUIWindow(string uiName, int index) {
|
|
||||||
IntPtr uiBase = this.GetUIBase();
|
|
||||||
IntPtr offset = Marshal.ReadIntPtr(uiBase, 0x20);
|
|
||||||
|
|
||||||
return this.getUIWindow.Invoke(offset, uiName, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void MoveWindow(string uiName, short x, short y) {
|
|
||||||
IntPtr windowBase = this.GetUIWindow(uiName, 1);
|
|
||||||
if (windowBase == IntPtr.Zero) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.moveWindow.Invoke(windowBase, x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Vector2<short> GetWindowPosition(string uiName) {
|
|
||||||
IntPtr windowBase = this.GetUIWindow(uiName, 1);
|
|
||||||
if (windowBase == IntPtr.Zero) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
short x = Marshal.ReadInt16(windowBase + 0x1bc);
|
|
||||||
short y = Marshal.ReadInt16(windowBase + 0x1bc + 2);
|
|
||||||
|
|
||||||
return new Vector2<short>(x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
// This file is used by Code Analysis to maintain SuppressMessage
|
|
||||||
// attributes that are applied to this project.
|
|
||||||
// Project-level suppressions either have no target or are given
|
|
||||||
// a specific target and scoped to a namespace, type, member, etc.
|
|
||||||
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
|
|
||||||
[assembly: SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Will be done eventually", Scope = "module")]
|
|
||||||
[assembly: SuppressMessage("Globalization", "CA1305", Scope = "module")]
|
|
||||||
[assembly: SuppressMessage("Globalization", "CA1304", Scope = "module")]
|
|
||||||
[assembly: SuppressMessage("Globalization", "CA1724", Scope = "module")]
|
|
194
HudSwap/HUD.cs
194
HudSwap/HUD.cs
|
@ -1,194 +0,0 @@
|
||||||
using Dalamud.Plugin;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace HudSwap {
|
|
||||||
public class HUD {
|
|
||||||
private const int LAYOUT_SIZE = 0xb40; // 5.4
|
|
||||||
private const int SLOT_OFFSET = 0x59e8; // 5.4
|
|
||||||
|
|
||||||
private delegate IntPtr GetFilePointerDelegate(byte index);
|
|
||||||
|
|
||||||
private delegate uint SetHudLayoutDelegate(IntPtr filePtr, uint hudLayout, byte unk0, byte unk1);
|
|
||||||
|
|
||||||
private readonly GetFilePointerDelegate _getFilePointer;
|
|
||||||
private readonly SetHudLayoutDelegate _setHudLayout;
|
|
||||||
|
|
||||||
private readonly DalamudPluginInterface pi;
|
|
||||||
|
|
||||||
public HUD(DalamudPluginInterface pi) {
|
|
||||||
this.pi = pi ?? throw new ArgumentNullException(nameof(pi), "DalamudPluginInterface cannot be null");
|
|
||||||
IntPtr getFilePointerPtr = this.pi.TargetModuleScanner.ScanText("E8 ?? ?? ?? ?? 48 85 C0 74 14 83 7B 44 00");
|
|
||||||
IntPtr setHudLayoutPtr = this.pi.TargetModuleScanner.ScanText("E8 ?? ?? ?? ?? 33 C0 EB 15");
|
|
||||||
if (getFilePointerPtr != IntPtr.Zero) {
|
|
||||||
this._getFilePointer = Marshal.GetDelegateForFunctionPointer<GetFilePointerDelegate>(getFilePointerPtr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setHudLayoutPtr != IntPtr.Zero) {
|
|
||||||
this._setHudLayout = Marshal.GetDelegateForFunctionPointer<SetHudLayoutDelegate>(setHudLayoutPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr GetFilePointer(byte index) {
|
|
||||||
return this._getFilePointer.Invoke(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
public uint SelectSlot(HudSlot slot, bool force = false) {
|
|
||||||
IntPtr file = this.GetFilePointer(0);
|
|
||||||
// change the current slot so the game lets us pick one that's currently in use
|
|
||||||
if (!force) {
|
|
||||||
goto Return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IntPtr currentSlotPtr = this.GetDataPointer() + SLOT_OFFSET;
|
|
||||||
// read the current slot
|
|
||||||
uint currentSlot = (uint)Marshal.ReadInt32(currentSlotPtr);
|
|
||||||
// change it to a different slot
|
|
||||||
if (currentSlot == (uint)slot) {
|
|
||||||
if (currentSlot < 3) {
|
|
||||||
currentSlot += 1;
|
|
||||||
} else {
|
|
||||||
currentSlot = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// back up this different slot
|
|
||||||
byte[] backup = this.ReadLayout((HudSlot)currentSlot);
|
|
||||||
// change the current slot in memory
|
|
||||||
Marshal.WriteInt32(currentSlotPtr, (int)currentSlot);
|
|
||||||
// ask the game to change slot to our desired slot
|
|
||||||
// for some reason, this overwrites the current slot, so this is why we back up
|
|
||||||
uint res = this._setHudLayout.Invoke(file, (uint)slot, 0, 1);
|
|
||||||
// restore the backup
|
|
||||||
this.WriteLayout((HudSlot)currentSlot, backup);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
Return:
|
|
||||||
return this._setHudLayout.Invoke(file, (uint)slot, 0, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr GetDataPointer() {
|
|
||||||
IntPtr dataPtr = this.GetFilePointer(0) + 0x50;
|
|
||||||
return Marshal.ReadIntPtr(dataPtr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr GetLayoutPointer(HudSlot slot) {
|
|
||||||
int slotNum = (int)slot;
|
|
||||||
return this.GetDataPointer() + 0x2c58 + (slotNum * LAYOUT_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HudSlot GetActiveHudSlot() {
|
|
||||||
int slotVal = Marshal.ReadInt32(this.GetDataPointer() + SLOT_OFFSET);
|
|
||||||
|
|
||||||
if (!Enum.IsDefined(typeof(HudSlot), slotVal)) {
|
|
||||||
throw new IOException($"invalid hud slot in FFXIV memory of ${slotVal}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (HudSlot)slotVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] ReadLayout(HudSlot slot) {
|
|
||||||
IntPtr slotPtr = this.GetLayoutPointer(slot);
|
|
||||||
byte[] bytes = new byte[LAYOUT_SIZE];
|
|
||||||
Marshal.Copy(slotPtr, bytes, 0, LAYOUT_SIZE);
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteLayout(HudSlot slot, byte[] layout) {
|
|
||||||
if (layout == null) {
|
|
||||||
throw new ArgumentNullException(nameof(layout), "layout cannot be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (layout.Length != LAYOUT_SIZE) {
|
|
||||||
throw new ArgumentException($"layout must be {LAYOUT_SIZE} bytes", nameof(layout));
|
|
||||||
}
|
|
||||||
|
|
||||||
IntPtr slotPtr = this.GetLayoutPointer(slot);
|
|
||||||
Marshal.Copy(layout, 0, slotPtr, LAYOUT_SIZE);
|
|
||||||
|
|
||||||
var currentSlot = this.GetActiveHudSlot();
|
|
||||||
if (currentSlot == slot) {
|
|
||||||
this.SelectSlot(currentSlot, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteLayout(byte[] layout) => WriteLayout(this.GetActiveHudSlot(), layout);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum HudSlot {
|
|
||||||
One = 0,
|
|
||||||
Two = 1,
|
|
||||||
Three = 2,
|
|
||||||
Four = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Vector2<T> {
|
|
||||||
public T X { get; private set; }
|
|
||||||
public T Y { get; private set; }
|
|
||||||
|
|
||||||
public Vector2(T x, T y) {
|
|
||||||
this.X = x;
|
|
||||||
this.Y = y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
public class SharedLayout {
|
|
||||||
[JsonProperty]
|
|
||||||
private readonly byte[] compressed;
|
|
||||||
|
|
||||||
[NonSerialized]
|
|
||||||
private byte[] uncompressed = null;
|
|
||||||
|
|
||||||
public Dictionary<string, Vector2<short>> Positions { get; private set; }
|
|
||||||
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "nah")]
|
|
||||||
public byte[] Layout() {
|
|
||||||
if (this.uncompressed != null) {
|
|
||||||
return this.uncompressed;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
using (MemoryStream compressed = new MemoryStream(this.compressed)) {
|
|
||||||
using (GZipStream gzip = new GZipStream(compressed, CompressionMode.Decompress)) {
|
|
||||||
using (MemoryStream uncompressed = new MemoryStream()) {
|
|
||||||
gzip.CopyTo(uncompressed);
|
|
||||||
this.uncompressed = uncompressed.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.uncompressed;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonConstructor]
|
|
||||||
private SharedLayout() {
|
|
||||||
// For JSON
|
|
||||||
}
|
|
||||||
|
|
||||||
public SharedLayout(Layout layout) {
|
|
||||||
if (layout == null) {
|
|
||||||
throw new ArgumentNullException(nameof(layout), "Layout cannot be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
using (MemoryStream compressed = new MemoryStream()) {
|
|
||||||
using (GZipStream gzip = new GZipStream(compressed, CompressionLevel.Optimal)) {
|
|
||||||
using (MemoryStream uncompressed = new MemoryStream(layout.Hud)) {
|
|
||||||
uncompressed.CopyTo(gzip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.compressed = compressed.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.Positions = layout.Positions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,110 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<Import Project="..\packages\Microsoft.CodeAnalysis.FxCopAnalyzers.2.9.6\build\Microsoft.CodeAnalysis.FxCopAnalyzers.props" Condition="Exists('..\packages\Microsoft.CodeAnalysis.FxCopAnalyzers.2.9.6\build\Microsoft.CodeAnalysis.FxCopAnalyzers.props')" />
|
|
||||||
<Import Project="..\packages\Microsoft.NetFramework.Analyzers.2.9.6\build\Microsoft.NetFramework.Analyzers.props" Condition="Exists('..\packages\Microsoft.NetFramework.Analyzers.2.9.6\build\Microsoft.NetFramework.Analyzers.props')" />
|
|
||||||
<Import Project="..\packages\Microsoft.NetCore.Analyzers.2.9.6\build\Microsoft.NetCore.Analyzers.props" Condition="Exists('..\packages\Microsoft.NetCore.Analyzers.2.9.6\build\Microsoft.NetCore.Analyzers.props')" />
|
|
||||||
<Import Project="..\packages\Microsoft.CodeQuality.Analyzers.2.9.6\build\Microsoft.CodeQuality.Analyzers.props" Condition="Exists('..\packages\Microsoft.CodeQuality.Analyzers.2.9.6\build\Microsoft.CodeQuality.Analyzers.props')" />
|
|
||||||
<Import Project="..\packages\Microsoft.CodeAnalysis.VersionCheckAnalyzer.2.9.6\build\Microsoft.CodeAnalysis.VersionCheckAnalyzer.props" Condition="Exists('..\packages\Microsoft.CodeAnalysis.VersionCheckAnalyzer.2.9.6\build\Microsoft.CodeAnalysis.VersionCheckAnalyzer.props')" />
|
|
||||||
<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>{CBF13A29-A5A4-4FA2-8A4F-CFBB5FB83F90}</ProjectGuid>
|
|
||||||
<OutputType>Library</OutputType>
|
|
||||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
|
||||||
<RootNamespace>HudSwap</RootNamespace>
|
|
||||||
<AssemblyName>HudSwap</AssemblyName>
|
|
||||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
|
||||||
<FileAlignment>512</FileAlignment>
|
|
||||||
<Deterministic>true</Deterministic>
|
|
||||||
<NuGetPackageImportStamp>
|
|
||||||
</NuGetPackageImportStamp>
|
|
||||||
</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>
|
|
||||||
</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>
|
|
||||||
</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="Lumina, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\Lumina.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Lumina.Excel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
|
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\Lumina.Excel.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\Newtonsoft.Json.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="System" />
|
|
||||||
<Reference Include="System.Core" />
|
|
||||||
<Reference Include="System.Numerics" />
|
|
||||||
<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="GameFunctions.cs" />
|
|
||||||
<Compile Include="Layout.cs" />
|
|
||||||
<Compile Include="PluginConfig.cs" />
|
|
||||||
<Compile Include="GlobalSuppressions.cs" />
|
|
||||||
<Compile Include="HUD.cs" />
|
|
||||||
<Compile Include="Plugin.cs" />
|
|
||||||
<Compile Include="PluginUI.cs" />
|
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
|
||||||
<Compile Include="Statuses.cs" />
|
|
||||||
<Compile Include="Swapper.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<None Include="HudSwap.json" />
|
|
||||||
<None Include="packages.config" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Analyzer Include="..\packages\Microsoft.CodeAnalysis.VersionCheckAnalyzer.2.9.6\analyzers\dotnet\cs\Microsoft.CodeAnalysis.VersionCheckAnalyzer.resources.dll" />
|
|
||||||
<Analyzer Include="..\packages\Microsoft.CodeAnalysis.VersionCheckAnalyzer.2.9.6\analyzers\dotnet\Microsoft.CodeAnalysis.VersionCheckAnalyzer.dll" />
|
|
||||||
<Analyzer Include="..\packages\Microsoft.CodeQuality.Analyzers.2.9.6\analyzers\dotnet\cs\Humanizer.dll" />
|
|
||||||
<Analyzer Include="..\packages\Microsoft.CodeQuality.Analyzers.2.9.6\analyzers\dotnet\cs\Microsoft.CodeQuality.Analyzers.dll" />
|
|
||||||
<Analyzer Include="..\packages\Microsoft.CodeQuality.Analyzers.2.9.6\analyzers\dotnet\cs\Microsoft.CodeQuality.CSharp.Analyzers.dll" />
|
|
||||||
<Analyzer Include="..\packages\Microsoft.NetCore.Analyzers.2.9.6\analyzers\dotnet\cs\Microsoft.NetCore.Analyzers.dll" />
|
|
||||||
<Analyzer Include="..\packages\Microsoft.NetCore.Analyzers.2.9.6\analyzers\dotnet\cs\Microsoft.NetCore.CSharp.Analyzers.dll" />
|
|
||||||
<Analyzer Include="..\packages\Microsoft.NetFramework.Analyzers.2.9.6\analyzers\dotnet\cs\Microsoft.NetFramework.Analyzers.dll" />
|
|
||||||
<Analyzer Include="..\packages\Microsoft.NetFramework.Analyzers.2.9.6\analyzers\dotnet\cs\Microsoft.NetFramework.CSharp.Analyzers.dll" />
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
|
||||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
|
||||||
<PropertyGroup>
|
|
||||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
|
||||||
</PropertyGroup>
|
|
||||||
<Error Condition="!Exists('..\packages\Microsoft.CodeAnalysis.VersionCheckAnalyzer.2.9.6\build\Microsoft.CodeAnalysis.VersionCheckAnalyzer.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.CodeAnalysis.VersionCheckAnalyzer.2.9.6\build\Microsoft.CodeAnalysis.VersionCheckAnalyzer.props'))" />
|
|
||||||
<Error Condition="!Exists('..\packages\Microsoft.CodeQuality.Analyzers.2.9.6\build\Microsoft.CodeQuality.Analyzers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.CodeQuality.Analyzers.2.9.6\build\Microsoft.CodeQuality.Analyzers.props'))" />
|
|
||||||
<Error Condition="!Exists('..\packages\Microsoft.NetCore.Analyzers.2.9.6\build\Microsoft.NetCore.Analyzers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.NetCore.Analyzers.2.9.6\build\Microsoft.NetCore.Analyzers.props'))" />
|
|
||||||
<Error Condition="!Exists('..\packages\Microsoft.NetFramework.Analyzers.2.9.6\build\Microsoft.NetFramework.Analyzers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.NetFramework.Analyzers.2.9.6\build\Microsoft.NetFramework.Analyzers.props'))" />
|
|
||||||
<Error Condition="!Exists('..\packages\Microsoft.CodeAnalysis.FxCopAnalyzers.2.9.6\build\Microsoft.CodeAnalysis.FxCopAnalyzers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.CodeAnalysis.FxCopAnalyzers.2.9.6\build\Microsoft.CodeAnalysis.FxCopAnalyzers.props'))" />
|
|
||||||
</Target>
|
|
||||||
</Project>
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"Author": "ascclemens",
|
|
||||||
"Name": "HudSwap",
|
|
||||||
"Description": "Automatically changes your HUD layout for you. /phudswap",
|
|
||||||
"InternalName": "HudSwap",
|
|
||||||
"AssemblyVersion": "1.5.1",
|
|
||||||
"RepoUrl": "https://sr.ht/~jkcclemens/HudSwap",
|
|
||||||
"ApplicableVersion": "any",
|
|
||||||
"DalamudApiLevel": 2
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace HudSwap {
|
|
||||||
[Serializable]
|
|
||||||
public class Layout {
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays")]
|
|
||||||
public byte[] Hud { get; private set; }
|
|
||||||
public Dictionary<string, Vector2<short>> Positions { get; private set; }
|
|
||||||
|
|
||||||
public string Name { get; private set; }
|
|
||||||
|
|
||||||
public Layout(string name, byte[] hud, Dictionary<string, Vector2<short>> positions) {
|
|
||||||
this.Name = name;
|
|
||||||
this.Hud = hud;
|
|
||||||
this.Positions = positions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,89 +0,0 @@
|
||||||
using Dalamud.Game.Command;
|
|
||||||
using Dalamud.Plugin;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace HudSwap {
|
|
||||||
public class HudSwapPlugin : IDalamudPlugin {
|
|
||||||
public string Name => "HudSwap";
|
|
||||||
|
|
||||||
private DalamudPluginInterface pi;
|
|
||||||
private PluginUI ui;
|
|
||||||
private Swapper swapper;
|
|
||||||
|
|
||||||
public HUD Hud { get; private set; }
|
|
||||||
public Statuses Statuses { get; private set; }
|
|
||||||
public GameFunctions GameFunctions { get; private set; }
|
|
||||||
public PluginConfig Config { get; private set; }
|
|
||||||
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "nah")]
|
|
||||||
public void Initialize(DalamudPluginInterface pluginInterface) {
|
|
||||||
this.pi = pluginInterface ?? throw new ArgumentNullException(nameof(pluginInterface), "DalamudPluginInterface cannot be null");
|
|
||||||
try {
|
|
||||||
this.Config = this.pi.GetPluginConfig() as PluginConfig ?? new PluginConfig();
|
|
||||||
} catch (Exception) {
|
|
||||||
this.pi.UiBuilder.OnBuildUi += PluginUI.ConfigError;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.Config.Initialize(this.pi);
|
|
||||||
|
|
||||||
this.ui = new PluginUI(this, this.pi);
|
|
||||||
this.Hud = new HUD(this.pi);
|
|
||||||
this.Statuses = new Statuses(this, this.pi);
|
|
||||||
this.GameFunctions = new GameFunctions(this.pi);
|
|
||||||
|
|
||||||
this.swapper = new Swapper(this, this.pi);
|
|
||||||
|
|
||||||
if (this.Config.FirstRun) {
|
|
||||||
this.Config.FirstRun = false;
|
|
||||||
if (this.Config.Layouts2.Count == 0) {
|
|
||||||
foreach (HudSlot slot in Enum.GetValues(typeof(HudSlot))) {
|
|
||||||
this.ui.ImportSlot($"Auto-import {(int)slot + 1}", slot, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.Config.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pi.UiBuilder.OnBuildUi += this.ui.Draw;
|
|
||||||
this.pi.UiBuilder.OnOpenConfigUi += this.ui.ConfigUI;
|
|
||||||
this.pi.Framework.OnUpdateEvent += this.swapper.OnFrameworkUpdate;
|
|
||||||
|
|
||||||
this.pi.CommandManager.AddHandler("/phudswap", new CommandInfo(OnSettingsCommand) {
|
|
||||||
HelpMessage = "Open the HudSwap settings"
|
|
||||||
});
|
|
||||||
this.pi.CommandManager.AddHandler("/phud", new CommandInfo(OnSwapCommand) {
|
|
||||||
HelpMessage = "/phud <name> - Swap to HUD layout called <name>"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void Dispose(bool all) {
|
|
||||||
this.pi.UiBuilder.OnBuildUi -= this.ui.Draw;
|
|
||||||
this.pi.UiBuilder.OnOpenConfigUi -= this.ui.ConfigUI;
|
|
||||||
this.pi.Framework.OnUpdateEvent -= this.swapper.OnFrameworkUpdate;
|
|
||||||
this.pi.CommandManager.RemoveHandler("/phudswap");
|
|
||||||
this.pi.CommandManager.RemoveHandler("/phud");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
this.Dispose(true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSettingsCommand(string command, string args) {
|
|
||||||
this.ui.SettingsVisible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSwapCommand(string command, string args) {
|
|
||||||
KeyValuePair<Guid, Layout> entry = this.Config.Layouts2.FirstOrDefault(e => e.Value.Name == args);
|
|
||||||
if (entry.Equals(default(KeyValuePair<Guid, Layout>))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Layout layout = entry.Value;
|
|
||||||
|
|
||||||
this.Hud.WriteLayout(this.Config.StagingSlot, layout.Hud);
|
|
||||||
this.Hud.SelectSlot(this.Config.StagingSlot, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,144 +0,0 @@
|
||||||
using Dalamud.Configuration;
|
|
||||||
using Dalamud.Plugin;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
|
|
||||||
namespace HudSwap {
|
|
||||||
[Serializable]
|
|
||||||
public class PluginConfig : IPluginConfiguration {
|
|
||||||
public int Version { get; set; } = 1;
|
|
||||||
|
|
||||||
[NonSerialized]
|
|
||||||
private DalamudPluginInterface pi;
|
|
||||||
|
|
||||||
public bool FirstRun { get; set; } = true;
|
|
||||||
public bool UnderstandsRisks { get; set; } = false;
|
|
||||||
|
|
||||||
public bool ImportPositions { get; set; } = false;
|
|
||||||
public bool SwapsEnabled { get; set; } = false;
|
|
||||||
|
|
||||||
public HudSlot StagingSlot { get; set; } = HudSlot.Four;
|
|
||||||
|
|
||||||
[Obsolete("Superceded by HudConditionMatches")]
|
|
||||||
public Guid DefaultLayout { get; set; } = Guid.Empty;
|
|
||||||
|
|
||||||
#pragma warning disable CA1051 // Do not declare visible instance fields
|
|
||||||
[Obsolete("Individual layout fields are deprecated; use StatusLayouts instead")]
|
|
||||||
public Guid combatLayout = Guid.Empty;
|
|
||||||
[Obsolete("Individual layout fields are deprecated; use StatusLayouts instead")]
|
|
||||||
public Guid weaponDrawnLayout = Guid.Empty;
|
|
||||||
[Obsolete("Individual layout fields are deprecated; use StatusLayouts instead")]
|
|
||||||
public Guid instanceLayout = Guid.Empty;
|
|
||||||
[Obsolete("Individual layout fields are deprecated; use StatusLayouts instead")]
|
|
||||||
public Guid craftingLayout = Guid.Empty;
|
|
||||||
[Obsolete("Individual layout fields are deprecated; use StatusLayouts instead")]
|
|
||||||
public Guid gatheringLayout = Guid.Empty;
|
|
||||||
[Obsolete("Individual layout fields are deprecated; use StatusLayouts instead")]
|
|
||||||
public Guid fishingLayout = Guid.Empty;
|
|
||||||
[Obsolete("Individual layout fields are deprecated; use StatusLayouts instead")]
|
|
||||||
public Guid roleplayingLayout = Guid.Empty;
|
|
||||||
#pragma warning restore CA1051 // Do not declare visible instance fields
|
|
||||||
|
|
||||||
[Obsolete("Superceded by HudConditionMatches")]
|
|
||||||
public Dictionary<Status, Guid> StatusLayouts { get; } = new Dictionary<Status, Guid>();
|
|
||||||
[Obsolete("Superceded by HudConditionMatches")]
|
|
||||||
public Dictionary<string, Guid> JobLayouts { get; } = new Dictionary<string, Guid>();
|
|
||||||
[Obsolete("Superceded by HudConditionMatches")]
|
|
||||||
public bool HighPriorityJobs { get; set; } = false;
|
|
||||||
[Obsolete("Superceded by HudConditionMatches")]
|
|
||||||
public bool JobsCombatOnly { get; set; } = false;
|
|
||||||
|
|
||||||
[Obsolete("Use Layouts2 instead")]
|
|
||||||
public Dictionary<Guid, Tuple<string, byte[]>> Layouts { get; } = new Dictionary<Guid, Tuple<string, byte[]>>();
|
|
||||||
public Dictionary<Guid, Layout> Layouts2 { get; } = new Dictionary<Guid, Layout>();
|
|
||||||
|
|
||||||
public List<HudConditionMatch> HudConditionMatches { get; } = new List<HudConditionMatch>();
|
|
||||||
|
|
||||||
public void Initialize(DalamudPluginInterface pluginInterface) {
|
|
||||||
this.pi = pluginInterface;
|
|
||||||
this.Migrate();
|
|
||||||
this.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Save() {
|
|
||||||
this.pi.SavePluginConfig(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Migrate() {
|
|
||||||
#pragma warning disable 618
|
|
||||||
if (this.combatLayout != Guid.Empty) {
|
|
||||||
this.StatusLayouts[Status.InCombat] = this.combatLayout;
|
|
||||||
this.combatLayout = Guid.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.weaponDrawnLayout != Guid.Empty) {
|
|
||||||
this.StatusLayouts[Status.WeaponDrawn] = this.weaponDrawnLayout;
|
|
||||||
this.weaponDrawnLayout = Guid.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.instanceLayout != Guid.Empty) {
|
|
||||||
this.StatusLayouts[Status.InInstance] = this.instanceLayout;
|
|
||||||
this.instanceLayout = Guid.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.craftingLayout != Guid.Empty) {
|
|
||||||
this.StatusLayouts[Status.Crafting] = this.craftingLayout;
|
|
||||||
this.craftingLayout = Guid.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.gatheringLayout != Guid.Empty) {
|
|
||||||
this.StatusLayouts[Status.Gathering] = this.gatheringLayout;
|
|
||||||
this.gatheringLayout = Guid.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.fishingLayout != Guid.Empty) {
|
|
||||||
this.StatusLayouts[Status.Fishing] = this.fishingLayout;
|
|
||||||
this.fishingLayout = Guid.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.roleplayingLayout != Guid.Empty) {
|
|
||||||
this.StatusLayouts[Status.Roleplaying] = this.roleplayingLayout;
|
|
||||||
this.roleplayingLayout = Guid.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.Layouts.Count != 0) {
|
|
||||||
foreach (KeyValuePair<Guid, Tuple<string, byte[]>> entry in this.Layouts) {
|
|
||||||
Layout layout = new Layout(entry.Value.Item1, entry.Value.Item2, new Dictionary<string, Vector2<short>>());
|
|
||||||
this.Layouts2.Add(entry.Key, layout);
|
|
||||||
}
|
|
||||||
this.Layouts.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.JobLayouts.Count != 0) {
|
|
||||||
foreach (var jobLayout in this.JobLayouts) {
|
|
||||||
this.HudConditionMatches.Add(new HudConditionMatch() {
|
|
||||||
ClassJob = jobLayout.Key,
|
|
||||||
Status = JobsCombatOnly ? Status.InCombat : default,
|
|
||||||
LayoutId = jobLayout.Value
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.JobLayouts.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.StatusLayouts.Count != 0) {
|
|
||||||
foreach (var statusLayout in this.StatusLayouts) {
|
|
||||||
var match = new HudConditionMatch() {
|
|
||||||
Status = statusLayout.Key,
|
|
||||||
LayoutId = statusLayout.Value
|
|
||||||
};
|
|
||||||
if (this.HighPriorityJobs) {
|
|
||||||
this.HudConditionMatches.Add(match);
|
|
||||||
} else {
|
|
||||||
this.HudConditionMatches.Insert(0, match);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.StatusLayouts.Clear();
|
|
||||||
}
|
|
||||||
#pragma warning restore 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,509 +0,0 @@
|
||||||
using Dalamud.Game.ClientState.Actors.Types;
|
|
||||||
using Dalamud.Interface;
|
|
||||||
using Dalamud.Plugin;
|
|
||||||
using ImGuiNET;
|
|
||||||
using Lumina.Excel.GeneratedSheets;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
// TODO: Zone swaps?
|
|
||||||
|
|
||||||
namespace HudSwap {
|
|
||||||
public class PluginUI {
|
|
||||||
private static readonly string[] SAVED_WINDOWS = {
|
|
||||||
"AreaMap",
|
|
||||||
"ChatLog",
|
|
||||||
"ChatLogPanel_0",
|
|
||||||
"ChatLogPanel_1",
|
|
||||||
"ChatLogPanel_2",
|
|
||||||
"ChatLogPanel_3",
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly HudSwapPlugin plugin;
|
|
||||||
private readonly DalamudPluginInterface pi;
|
|
||||||
|
|
||||||
private bool _settingsVisible = false;
|
|
||||||
public bool SettingsVisible { get => this._settingsVisible; set => this._settingsVisible = value; }
|
|
||||||
|
|
||||||
public PluginUI(HudSwapPlugin plugin, DalamudPluginInterface pi) {
|
|
||||||
this.plugin = plugin;
|
|
||||||
this.pi = pi;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ConfigUI(object sender, EventArgs args) {
|
|
||||||
this.SettingsVisible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string importName = "";
|
|
||||||
private string renameName = "";
|
|
||||||
private Guid selectedLayoutId = Guid.Empty;
|
|
||||||
|
|
||||||
private Layout selectedLayout =>
|
|
||||||
selectedLayoutId == Guid.Empty ? null : this.plugin.Config.Layouts2[this.selectedLayoutId];
|
|
||||||
|
|
||||||
private int editingConditionIndex = -1;
|
|
||||||
private HudConditionMatch editingCondition;
|
|
||||||
private bool scrollToAdd = false;
|
|
||||||
|
|
||||||
private static bool configErrorOpen = true;
|
|
||||||
public static void ConfigError() {
|
|
||||||
if (ImGui.Begin("HudSwap error", ref configErrorOpen)) {
|
|
||||||
ImGui.Text("Could not load HudSwap configuration.");
|
|
||||||
ImGui.Spacing();
|
|
||||||
ImGui.Text("If you are updating from a previous version, please\ndelete your configuration file and restart the game.");
|
|
||||||
|
|
||||||
ImGui.End();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DrawSettings() {
|
|
||||||
void DrawHudSlotButtons(string idSuffix, System.Action<HudSlot> hudSlotAction, System.Action clipboardAction) {
|
|
||||||
Vector2 slotButtonSize = new Vector2(40, 0);
|
|
||||||
foreach (HudSlot slot in Enum.GetValues(typeof(HudSlot))) {
|
|
||||||
// Surround the button with parentheses if this is the current slot
|
|
||||||
string slotText = slot == this.plugin.Hud.GetActiveHudSlot() ? $"({(int)slot + 1})" : ((int)slot + 1).ToString();
|
|
||||||
string buttonName = $"{slotText}##${idSuffix}";
|
|
||||||
if (ImGui.Button(buttonName, slotButtonSize)) {
|
|
||||||
PluginLog.Log("Importing outer");
|
|
||||||
hudSlotAction(slot);
|
|
||||||
}
|
|
||||||
ImGui.SameLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
|
|
||||||
if (ImGui.Button($"Clipboard##{idSuffix}")) {
|
|
||||||
clipboardAction();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.SettingsVisible) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SetNextWindowSize(new Vector2(500, 450), ImGuiCond.FirstUseEver);
|
|
||||||
|
|
||||||
if (!ImGui.Begin(this.plugin.Name, ref this._settingsVisible)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.BeginTabBar("##hudswap-tabs")) {
|
|
||||||
if (!this.plugin.Config.UnderstandsRisks) {
|
|
||||||
if (ImGui.BeginTabItem("About")) {
|
|
||||||
ImGui.TextColored(new Vector4(1f, 0f, 0f, 1f), "Read this first");
|
|
||||||
ImGui.Separator();
|
|
||||||
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 20f);
|
|
||||||
ImGui.Text("HudSwap will use the configured staging slot as its own slot to make changes to. This means the staging slot will be overwritten whenever any swap happens.");
|
|
||||||
ImGui.Spacing();
|
|
||||||
ImGui.Text("Any HUD layout changes you make while HudSwap is enabled may potentially be lost, no matter what slot. If you want to make changes to your HUD layout, TURN OFF HudSwap first.");
|
|
||||||
ImGui.Spacing();
|
|
||||||
ImGui.Text("When editing or making a new layout, to be completely safe, turn off swaps, set up your layout, import the layout into HudSwap, then turn on swaps.");
|
|
||||||
ImGui.Spacing();
|
|
||||||
ImGui.Text("If you are a new user, HudSwap auto-imported your existing layouts on startup.");
|
|
||||||
ImGui.Spacing();
|
|
||||||
ImGui.Text("Finally, HudSwap is beta software. Back up your character data before using this plugin. You may lose some to all of your HUD layouts while testing this plugin.");
|
|
||||||
ImGui.Separator();
|
|
||||||
ImGui.Text("If you have read all of the above and are okay with continuing, check the box below to enable HudSwap. You only need to do this once.");
|
|
||||||
ImGui.PopTextWrapPos();
|
|
||||||
bool understandsRisks = this.plugin.Config.UnderstandsRisks;
|
|
||||||
if (ImGui.Checkbox("I understand", ref understandsRisks)) {
|
|
||||||
this.plugin.Config.UnderstandsRisks = understandsRisks;
|
|
||||||
this.plugin.Config.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndTabBar();
|
|
||||||
ImGui.End();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.BeginTabItem("Layouts")) {
|
|
||||||
ImGui.Text("Saved layouts");
|
|
||||||
if (this.plugin.Config.Layouts2.Count == 0) {
|
|
||||||
ImGui.Text("None saved!");
|
|
||||||
} else {
|
|
||||||
ImGui.PushItemWidth(-1);
|
|
||||||
if (ImGui.ListBoxHeader("##saved-layouts")) {
|
|
||||||
foreach (KeyValuePair<Guid, Layout> entry in this.plugin.Config.Layouts2) {
|
|
||||||
if (ImGui.Selectable($"{entry.Value.Name}##{entry.Key}", this.selectedLayoutId == entry.Key)) {
|
|
||||||
this.selectedLayoutId = entry.Key;
|
|
||||||
this.renameName = entry.Value.Name;
|
|
||||||
this.importName = this.renameName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui.ListBoxFooter();
|
|
||||||
}
|
|
||||||
ImGui.PopItemWidth();
|
|
||||||
|
|
||||||
ImGui.PushItemWidth(200);
|
|
||||||
ImGui.InputText("##rename-input", ref this.renameName, 100);
|
|
||||||
ImGui.PopItemWidth();
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (ImGui.Button("Rename") && this.renameName.Length != 0 && this.selectedLayout != null) {
|
|
||||||
Layout layout = this.plugin.Config.Layouts2[this.selectedLayoutId];
|
|
||||||
Layout newLayout = new Layout(this.renameName, layout.Hud, layout.Positions);
|
|
||||||
this.plugin.Config.Layouts2[this.selectedLayoutId] = newLayout;
|
|
||||||
this.plugin.Config.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
const int layoutActionButtonWidth = 30;
|
|
||||||
// `layoutActionButtonWidth` must be multiplied by however many action buttons there are here
|
|
||||||
ImGui.SameLine(ImGui.GetWindowContentRegionWidth() - layoutActionButtonWidth * 1);
|
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
|
||||||
if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString(), new Vector2(layoutActionButtonWidth, 0)) &&
|
|
||||||
this.selectedLayout != null) {
|
|
||||||
this.plugin.Config.Layouts2.Remove(this.selectedLayoutId);
|
|
||||||
this.plugin.Config.HudConditionMatches.RemoveAll(m => m.LayoutId == this.selectedLayoutId);
|
|
||||||
this.selectedLayoutId = Guid.Empty;
|
|
||||||
this.renameName = "";
|
|
||||||
this.plugin.Config.Save();
|
|
||||||
}
|
|
||||||
ImGui.PopFont();
|
|
||||||
|
|
||||||
ImGui.Text("Copy to...");
|
|
||||||
DrawHudSlotButtons("copy", slot => {
|
|
||||||
if (this.selectedLayout == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.plugin.Hud.WriteLayout(slot, this.selectedLayout.Hud);
|
|
||||||
}, () => {
|
|
||||||
if (this.selectedLayout == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SharedLayout shared = new SharedLayout(selectedLayout);
|
|
||||||
string json = JsonConvert.SerializeObject(shared);
|
|
||||||
ImGui.SetClipboardText(json);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.Separator();
|
|
||||||
|
|
||||||
ImGui.Text("Import");
|
|
||||||
|
|
||||||
ImGui.InputText("Imported layout name", ref this.importName, 100);
|
|
||||||
|
|
||||||
bool importPositions = this.plugin.Config.ImportPositions;
|
|
||||||
if (ImGui.Checkbox("Import window positions", ref importPositions)) {
|
|
||||||
this.plugin.Config.ImportPositions = importPositions;
|
|
||||||
this.plugin.Config.Save();
|
|
||||||
}
|
|
||||||
ImGui.SameLine();
|
|
||||||
HelpMarker("If this is checked, the position of the chat box and the map will be saved with the imported layout.");
|
|
||||||
|
|
||||||
bool isOverwriting =
|
|
||||||
this.plugin.Config.Layouts2.Values.Any(layout => layout.Name == this.importName);
|
|
||||||
ImGui.Text((isOverwriting ? "Overwrite" : "Import") + " from...");
|
|
||||||
|
|
||||||
DrawHudSlotButtons("import", slot => {
|
|
||||||
PluginLog.Log("Importing inner");
|
|
||||||
this.ImportSlot(this.importName, slot);
|
|
||||||
this.importName = "";
|
|
||||||
}, () => {
|
|
||||||
SharedLayout shared = null;
|
|
||||||
try {
|
|
||||||
shared = (SharedLayout)JsonConvert.DeserializeObject(ImGui.GetClipboardText(), typeof(SharedLayout));
|
|
||||||
#pragma warning disable CA1031 // Do not catch general exception types
|
|
||||||
} catch (Exception) {
|
|
||||||
#pragma warning restore CA1031 // Do not catch general exception types
|
|
||||||
}
|
|
||||||
if (shared != null) {
|
|
||||||
byte[] layout = shared.Layout();
|
|
||||||
if (layout != null) {
|
|
||||||
this.Import(this.importName, layout, shared.Positions);
|
|
||||||
this.importName = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.BeginTabItem("Swaps")) {
|
|
||||||
bool enabled = this.plugin.Config.SwapsEnabled;
|
|
||||||
if (ImGui.Checkbox("Enable swaps", ref enabled)) {
|
|
||||||
this.plugin.Config.SwapsEnabled = enabled;
|
|
||||||
this.plugin.Config.Save();
|
|
||||||
}
|
|
||||||
ImGui.Text("Note: Disable swaps when editing your HUD.");
|
|
||||||
|
|
||||||
ImGui.Spacing();
|
|
||||||
string staging = ((int)this.plugin.Config.StagingSlot + 1).ToString();
|
|
||||||
if (ImGui.BeginCombo("Staging slot", staging)) {
|
|
||||||
foreach (HudSlot slot in Enum.GetValues(typeof(HudSlot))) {
|
|
||||||
if (ImGui.Selectable(((int)slot + 1).ToString())) {
|
|
||||||
this.plugin.Config.StagingSlot = slot;
|
|
||||||
this.plugin.Config.Save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui.EndCombo();
|
|
||||||
}
|
|
||||||
ImGui.SameLine();
|
|
||||||
HelpMarker("The staging slot is the HUD layout slot that will be used as your HUD layout. All changes will be written to this slot when swaps are enabled.");
|
|
||||||
|
|
||||||
ImGui.Separator();
|
|
||||||
|
|
||||||
if (this.plugin.Config.Layouts2.Count == 0) {
|
|
||||||
ImGui.Text("Create at least one layout to begin setting up swaps.");
|
|
||||||
} else {
|
|
||||||
ImGui.Text("Add swap conditions below.\nThe first condition that is satisfied will be the layout that is used.");
|
|
||||||
ImGui.Separator();
|
|
||||||
this.DrawConditionsTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndTabBar();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.End();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawConditionsTable() {
|
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
|
||||||
float height = ImGui.GetContentRegionAvail().Y - ImGui.CalcTextSize(FontAwesomeIcon.Plus.ToIconString()).Y - ImGui.GetStyle().ItemSpacing.Y - ImGui.GetStyle().ItemInnerSpacing.Y * 2;
|
|
||||||
ImGui.PopFont();
|
|
||||||
if (!ImGui.BeginChild("##conditions-table", new Vector2(-1, height))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.Columns(4);
|
|
||||||
|
|
||||||
var conditions = new List<HudConditionMatch>(this.plugin.Config.HudConditionMatches);
|
|
||||||
if (this.editingConditionIndex == conditions.Count) {
|
|
||||||
conditions.Add(new HudConditionMatch());
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.Text("Job");
|
|
||||||
ImGui.NextColumn();
|
|
||||||
|
|
||||||
ImGui.Text("State");
|
|
||||||
ImGui.NextColumn();
|
|
||||||
|
|
||||||
ImGui.Text("Layout");
|
|
||||||
ImGui.NextColumn();
|
|
||||||
|
|
||||||
ImGui.Text("Options");
|
|
||||||
ImGui.NextColumn();
|
|
||||||
|
|
||||||
ImGui.Separator();
|
|
||||||
|
|
||||||
bool addCondition = false;
|
|
||||||
int actionedItemIndex = -1;
|
|
||||||
int action = 0; // 0 for delete, otherwise move.
|
|
||||||
foreach (var item in conditions.Select((cond, i) => new { cond, i })) {
|
|
||||||
if (this.editingConditionIndex == item.i) {
|
|
||||||
ImGui.PushItemWidth(-1);
|
|
||||||
if (ImGui.BeginCombo("##condition-edit-job", this.editingCondition.ClassJob ?? "Any")) {
|
|
||||||
if (ImGui.Selectable("Any##condition-edit-job")) {
|
|
||||||
this.editingCondition.ClassJob = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (ClassJob job in this.pi.Data.GetExcelSheet<ClassJob>().Skip(1)) {
|
|
||||||
if (ImGui.Selectable($"{job.Abbreviation}##condition-edit-job")) {
|
|
||||||
this.editingCondition.ClassJob = job.Abbreviation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndCombo();
|
|
||||||
}
|
|
||||||
ImGui.PopItemWidth();
|
|
||||||
ImGui.NextColumn();
|
|
||||||
|
|
||||||
ImGui.PushItemWidth(-1);
|
|
||||||
if (ImGui.BeginCombo("##condition-edit-status", this.editingCondition.Status?.Name() ?? "Any")) {
|
|
||||||
if (ImGui.Selectable("Any##condition-edit-status")) {
|
|
||||||
this.editingCondition.Status = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (Status status in Enum.GetValues(typeof(Status))) {
|
|
||||||
if (ImGui.Selectable($"{status.Name()}##condition-edit-status")) {
|
|
||||||
this.editingCondition.Status = status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndCombo();
|
|
||||||
}
|
|
||||||
ImGui.PopItemWidth();
|
|
||||||
ImGui.NextColumn();
|
|
||||||
|
|
||||||
ImGui.PushItemWidth(-1);
|
|
||||||
string comboPreview = this.editingCondition.LayoutId == Guid.Empty ? string.Empty : this.plugin.Config.Layouts2[this.editingCondition.LayoutId].Name;
|
|
||||||
if (ImGui.BeginCombo("##condition-edit-layout", comboPreview)) {
|
|
||||||
foreach (var layout in this.plugin.Config.Layouts2) {
|
|
||||||
if (ImGui.Selectable($"{layout.Value.Name}##condition-edit-layout-{layout.Key}")) {
|
|
||||||
this.editingCondition.LayoutId = layout.Key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndCombo();
|
|
||||||
}
|
|
||||||
ImGui.PopItemWidth();
|
|
||||||
ImGui.NextColumn();
|
|
||||||
|
|
||||||
if (this.editingCondition.LayoutId != Guid.Empty) {
|
|
||||||
if (IconButton(FontAwesomeIcon.Check, "condition-edit")) {
|
|
||||||
addCondition = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IconButton(FontAwesomeIcon.Times, "condition-stop")) {
|
|
||||||
this.editingConditionIndex = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.scrollToAdd) {
|
|
||||||
this.scrollToAdd = false;
|
|
||||||
ImGui.SetScrollHereY();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ImGui.Text(item.cond.ClassJob ?? string.Empty);
|
|
||||||
ImGui.NextColumn();
|
|
||||||
|
|
||||||
ImGui.Text(item.cond.Status?.Name() ?? string.Empty);
|
|
||||||
ImGui.NextColumn();
|
|
||||||
|
|
||||||
this.plugin.Config.Layouts2.TryGetValue(item.cond.LayoutId, out Layout condLayout);
|
|
||||||
ImGui.Text(condLayout?.Name ?? string.Empty);
|
|
||||||
ImGui.NextColumn();
|
|
||||||
|
|
||||||
if (IconButton(FontAwesomeIcon.PencilAlt, $"{item.i}")) {
|
|
||||||
this.editingConditionIndex = item.i;
|
|
||||||
this.editingCondition = item.cond;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (IconButton(FontAwesomeIcon.Trash, $"{item.i}")) {
|
|
||||||
actionedItemIndex = item.i;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (IconButton(FontAwesomeIcon.ArrowUp, $"{item.i}")) {
|
|
||||||
actionedItemIndex = item.i;
|
|
||||||
action = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (IconButton(FontAwesomeIcon.ArrowDown, $"{item.i}")) {
|
|
||||||
actionedItemIndex = item.i;
|
|
||||||
action = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.NextColumn();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.Columns(1);
|
|
||||||
|
|
||||||
ImGui.Separator();
|
|
||||||
|
|
||||||
ImGui.EndChild();
|
|
||||||
|
|
||||||
if (IconButton(FontAwesomeIcon.Plus, "condition")) {
|
|
||||||
this.editingConditionIndex = this.plugin.Config.HudConditionMatches.Count;
|
|
||||||
this.editingCondition = new HudConditionMatch();
|
|
||||||
this.scrollToAdd = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool recalculate = false;
|
|
||||||
|
|
||||||
if (addCondition) {
|
|
||||||
recalculate = true;
|
|
||||||
if (this.editingConditionIndex == this.plugin.Config.HudConditionMatches.Count) {
|
|
||||||
this.plugin.Config.HudConditionMatches.Add(this.editingCondition);
|
|
||||||
} else {
|
|
||||||
this.plugin.Config.HudConditionMatches.RemoveAt(this.editingConditionIndex);
|
|
||||||
this.plugin.Config.HudConditionMatches.Insert(this.editingConditionIndex, this.editingCondition);
|
|
||||||
}
|
|
||||||
this.plugin.Config.Save();
|
|
||||||
this.editingConditionIndex = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actionedItemIndex >= 0) {
|
|
||||||
recalculate = true;
|
|
||||||
if (action == 0) {
|
|
||||||
this.plugin.Config.HudConditionMatches.RemoveAt(actionedItemIndex);
|
|
||||||
} else {
|
|
||||||
if (actionedItemIndex + action >= 0 && actionedItemIndex + action < this.plugin.Config.HudConditionMatches.Count) {
|
|
||||||
// Move the condition.
|
|
||||||
var c = this.plugin.Config.HudConditionMatches[actionedItemIndex];
|
|
||||||
this.plugin.Config.HudConditionMatches.RemoveAt(actionedItemIndex);
|
|
||||||
this.plugin.Config.HudConditionMatches.Insert(actionedItemIndex + action, c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.plugin.Config.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recalculate) {
|
|
||||||
PlayerCharacter player = this.pi.ClientState.LocalPlayer;
|
|
||||||
if (player != null && this.plugin.Config.SwapsEnabled) {
|
|
||||||
this.plugin.Statuses.Update(player);
|
|
||||||
this.plugin.Statuses.SetHudLayout(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IconButton(FontAwesomeIcon icon, string append = "") {
|
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
|
||||||
bool button = ImGui.Button($"{icon.ToIconString()}##{append}");
|
|
||||||
ImGui.PopFont();
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void HelpMarker(string text) {
|
|
||||||
ImGui.TextDisabled("(?)");
|
|
||||||
if (ImGui.IsItemHovered()) {
|
|
||||||
ImGui.BeginTooltip();
|
|
||||||
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 20f);
|
|
||||||
ImGui.TextUnformatted(text);
|
|
||||||
ImGui.PopTextWrapPos();
|
|
||||||
ImGui.EndTooltip();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Draw() {
|
|
||||||
this.DrawSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Dictionary<string, Vector2<short>> GetPositions() {
|
|
||||||
Dictionary<string, Vector2<short>> positions = new Dictionary<string, Vector2<short>>();
|
|
||||||
|
|
||||||
foreach (string name in SAVED_WINDOWS) {
|
|
||||||
Vector2<short> pos = this.plugin.GameFunctions.GetWindowPosition(name);
|
|
||||||
if (pos != null) {
|
|
||||||
positions[name] = pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return positions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ImportSlot(string name, HudSlot slot, bool save = true) {
|
|
||||||
Dictionary<string, Vector2<short>> positions;
|
|
||||||
if (this.plugin.Config.ImportPositions) {
|
|
||||||
positions = this.GetPositions();
|
|
||||||
} else {
|
|
||||||
positions = new Dictionary<string, Vector2<short>>();
|
|
||||||
}
|
|
||||||
this.Import(name, this.plugin.Hud.ReadLayout(slot), positions, save);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Import(string name, byte[] layout, Dictionary<string, Vector2<short>> positions, bool save = true) {
|
|
||||||
Guid guid = this.plugin.Config.Layouts2.FirstOrDefault(kv => kv.Value.Name == name).Key;
|
|
||||||
guid = guid != default ? guid : Guid.NewGuid();
|
|
||||||
|
|
||||||
this.plugin.Config.Layouts2[Guid.NewGuid()] = new Layout(name, layout, positions);
|
|
||||||
if (save) {
|
|
||||||
this.plugin.Config.Save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,497 +0,0 @@
|
||||||
using Dalamud.Game.ClientState.Actors.Types;
|
|
||||||
using Dalamud.Interface;
|
|
||||||
using Dalamud.Plugin;
|
|
||||||
using ImGuiNET;
|
|
||||||
using Lumina.Excel.GeneratedSheets;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
// TODO: Zone swaps?
|
|
||||||
|
|
||||||
namespace HudSwap {
|
|
||||||
public class PluginUI {
|
|
||||||
private static readonly string[] SAVED_WINDOWS = {
|
|
||||||
"AreaMap",
|
|
||||||
"ChatLog",
|
|
||||||
"ChatLogPanel_0",
|
|
||||||
"ChatLogPanel_1",
|
|
||||||
"ChatLogPanel_2",
|
|
||||||
"ChatLogPanel_3",
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly HudSwapPlugin plugin;
|
|
||||||
private readonly DalamudPluginInterface pi;
|
|
||||||
|
|
||||||
private bool _settingsVisible = false;
|
|
||||||
public bool SettingsVisible { get => this._settingsVisible; set => this._settingsVisible = value; }
|
|
||||||
|
|
||||||
public PluginUI(HudSwapPlugin plugin, DalamudPluginInterface pi) {
|
|
||||||
this.plugin = plugin;
|
|
||||||
this.pi = pi;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ConfigUI(object sender, EventArgs args) {
|
|
||||||
this.SettingsVisible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string importName = "";
|
|
||||||
private string renameName = "";
|
|
||||||
private Guid selectedLayoutId = Guid.Empty;
|
|
||||||
|
|
||||||
private Layout selectedLayout =>
|
|
||||||
selectedLayoutId == Guid.Empty ? null : this.plugin.Config.Layouts2[this.selectedLayoutId];
|
|
||||||
|
|
||||||
private int editingConditionIndex = -1;
|
|
||||||
private HudConditionMatch editingCondition;
|
|
||||||
private bool scrollToAdd = false;
|
|
||||||
|
|
||||||
private static bool configErrorOpen = true;
|
|
||||||
public static void ConfigError() {
|
|
||||||
if (ImGui.Begin("HudSwap error", ref configErrorOpen)) {
|
|
||||||
ImGui.Text("Could not load HudSwap configuration.");
|
|
||||||
ImGui.Spacing();
|
|
||||||
ImGui.Text("If you are updating from a previous version, please\ndelete your configuration file and restart the game.");
|
|
||||||
|
|
||||||
ImGui.End();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DrawSettings() {
|
|
||||||
if (!this.SettingsVisible) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SetNextWindowSize(new Vector2(500, 450), ImGuiCond.FirstUseEver);
|
|
||||||
|
|
||||||
if (!ImGui.Begin(this.plugin.Name, ref this._settingsVisible)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.BeginTabBar("##hudswap-tabs")) {
|
|
||||||
if (!this.plugin.Config.UnderstandsRisks) {
|
|
||||||
if (ImGui.BeginTabItem("About")) {
|
|
||||||
ImGui.TextColored(new Vector4(1f, 0f, 0f, 1f), "Read this first");
|
|
||||||
ImGui.Separator();
|
|
||||||
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 20f);
|
|
||||||
ImGui.Text("HudSwap will use the configured staging slot as its own slot to make changes to. This means the staging slot will be overwritten whenever any swap happens.");
|
|
||||||
ImGui.Spacing();
|
|
||||||
ImGui.Text("Any HUD layout changes you make while HudSwap is enabled may potentially be lost, no matter what slot. If you want to make changes to your HUD layout, TURN OFF HudSwap first.");
|
|
||||||
ImGui.Spacing();
|
|
||||||
ImGui.Text("When editing or making a new layout, to be completely safe, turn off swaps, set up your layout, import the layout into HudSwap, then turn on swaps.");
|
|
||||||
ImGui.Spacing();
|
|
||||||
ImGui.Text("If you are a new user, HudSwap auto-imported your existing layouts on startup.");
|
|
||||||
ImGui.Spacing();
|
|
||||||
ImGui.Text("Finally, HudSwap is beta software. Back up your character data before using this plugin. You may lose some to all of your HUD layouts while testing this plugin.");
|
|
||||||
ImGui.Separator();
|
|
||||||
ImGui.Text("If you have read all of the above and are okay with continuing, check the box below to enable HudSwap. You only need to do this once.");
|
|
||||||
ImGui.PopTextWrapPos();
|
|
||||||
bool understandsRisks = this.plugin.Config.UnderstandsRisks;
|
|
||||||
if (ImGui.Checkbox("I understand", ref understandsRisks)) {
|
|
||||||
this.plugin.Config.UnderstandsRisks = understandsRisks;
|
|
||||||
this.plugin.Config.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndTabBar();
|
|
||||||
ImGui.End();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.BeginTabItem("Layouts")) {
|
|
||||||
ImGui.Text("Saved layouts");
|
|
||||||
if (this.plugin.Config.Layouts2.Count == 0) {
|
|
||||||
ImGui.Text("None saved!");
|
|
||||||
} else {
|
|
||||||
ImGui.PushItemWidth(-1);
|
|
||||||
if (ImGui.ListBoxHeader("##saved-layouts")) {
|
|
||||||
foreach (KeyValuePair<Guid, Layout> entry in this.plugin.Config.Layouts2) {
|
|
||||||
if (ImGui.Selectable($"{entry.Value.Name}##{entry.Key}", this.selectedLayoutId == entry.Key)) {
|
|
||||||
this.selectedLayoutId = entry.Key;
|
|
||||||
this.renameName = entry.Value.Name;
|
|
||||||
this.importName = this.renameName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui.ListBoxFooter();
|
|
||||||
}
|
|
||||||
ImGui.PopItemWidth();
|
|
||||||
|
|
||||||
ImGui.PushItemWidth(200);
|
|
||||||
ImGui.InputText("##rename-input", ref this.renameName, 100);
|
|
||||||
ImGui.PopItemWidth();
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (ImGui.Button("Rename") && this.renameName.Length != 0 && this.selectedLayoutId != null) {
|
|
||||||
Layout layout = this.plugin.Config.Layouts2[this.selectedLayoutId];
|
|
||||||
Layout newLayout = new Layout(this.renameName, layout.Hud, layout.Positions);
|
|
||||||
this.plugin.Config.Layouts2[this.selectedLayoutId] = newLayout;
|
|
||||||
this.plugin.Config.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
const int deleteButtonWidth = 30;
|
|
||||||
ImGui.SameLine(ImGui.GetWindowContentRegionWidth() - deleteButtonWidth);
|
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
|
||||||
if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString(), new Vector2(deleteButtonWidth, 0)) &&
|
|
||||||
this.selectedLayoutId != null) {
|
|
||||||
this.plugin.Config.Layouts2.Remove(this.selectedLayoutId);
|
|
||||||
this.plugin.Config.HudConditionMatches.RemoveAll(m => m.LayoutId == this.selectedLayoutId);
|
|
||||||
this.selectedLayoutId = Guid.Empty;
|
|
||||||
this.renameName = "";
|
|
||||||
this.plugin.Config.Save();
|
|
||||||
}
|
|
||||||
ImGui.PopFont();
|
|
||||||
|
|
||||||
ImGui.Text("Copy to...");
|
|
||||||
Vector2 slotButtonSize = new Vector2(40, 0);
|
|
||||||
foreach (HudSlot slot in Enum.GetValues(typeof(HudSlot))) {
|
|
||||||
// Surround the button with parentheses if this is the current slot
|
|
||||||
string slotText = slot == this.plugin.Hud.GetActiveHudSlot() ? $"({(int)slot + 1})" : ((int)slot + 1).ToString();
|
|
||||||
string buttonName = $"{slotText}##copy";
|
|
||||||
if (ImGui.Button(buttonName, slotButtonSize) && this.selectedLayout != null) {
|
|
||||||
this.plugin.Hud.WriteLayout(slot, this.selectedLayout.Hud);
|
|
||||||
}
|
|
||||||
ImGui.SameLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
|
|
||||||
if (ImGui.Button("Clipboard") && this.selectedLayoutId != null) {
|
|
||||||
if (this.plugin.Config.Layouts2.TryGetValue(this.selectedLayoutId, out Layout layout)) {
|
|
||||||
SharedLayout shared = new SharedLayout(layout);
|
|
||||||
string json = JsonConvert.SerializeObject(shared);
|
|
||||||
ImGui.SetClipboardText(json);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.Separator();
|
|
||||||
|
|
||||||
ImGui.Text("Import");
|
|
||||||
|
|
||||||
ImGui.InputText("Imported layout name", ref this.importName, 100);
|
|
||||||
|
|
||||||
bool importPositions = this.plugin.Config.ImportPositions;
|
|
||||||
if (ImGui.Checkbox("Import window positions", ref importPositions)) {
|
|
||||||
this.plugin.Config.ImportPositions = importPositions;
|
|
||||||
this.plugin.Config.Save();
|
|
||||||
}
|
|
||||||
ImGui.SameLine();
|
|
||||||
HelpMarker("If this is checked, the position of the chat box and the map will be saved with the imported layout.");
|
|
||||||
|
|
||||||
foreach (HudSlot slot in Enum.GetValues(typeof(HudSlot))) {
|
|
||||||
string buttonName = $"{(int)slot + 1}##import";
|
|
||||||
if (ImGui.Button(buttonName) && this.importName.Length != 0) {
|
|
||||||
this.ImportSlot(this.importName, slot);
|
|
||||||
this.importName = "";
|
|
||||||
}
|
|
||||||
ImGui.SameLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.Button("Clipboard") && this.importName.Length != 0) {
|
|
||||||
SharedLayout shared = null;
|
|
||||||
try {
|
|
||||||
shared = (SharedLayout)JsonConvert.DeserializeObject(ImGui.GetClipboardText(), typeof(SharedLayout));
|
|
||||||
#pragma warning disable CA1031 // Do not catch general exception types
|
|
||||||
} catch (Exception) {
|
|
||||||
#pragma warning restore CA1031 // Do not catch general exception types
|
|
||||||
}
|
|
||||||
if (shared != null) {
|
|
||||||
byte[] layout = shared.Layout();
|
|
||||||
if (layout != null) {
|
|
||||||
this.Import(this.importName, layout, shared.Positions);
|
|
||||||
this.importName = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.BeginTabItem("Swaps")) {
|
|
||||||
bool enabled = this.plugin.Config.SwapsEnabled;
|
|
||||||
if (ImGui.Checkbox("Enable swaps", ref enabled)) {
|
|
||||||
this.plugin.Config.SwapsEnabled = enabled;
|
|
||||||
this.plugin.Config.Save();
|
|
||||||
}
|
|
||||||
ImGui.Text("Note: Disable swaps when editing your HUD.");
|
|
||||||
|
|
||||||
ImGui.Spacing();
|
|
||||||
string staging = ((int)this.plugin.Config.StagingSlot + 1).ToString();
|
|
||||||
if (ImGui.BeginCombo("Staging slot", staging)) {
|
|
||||||
foreach (HudSlot slot in Enum.GetValues(typeof(HudSlot))) {
|
|
||||||
if (ImGui.Selectable(((int)slot + 1).ToString())) {
|
|
||||||
this.plugin.Config.StagingSlot = slot;
|
|
||||||
this.plugin.Config.Save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui.EndCombo();
|
|
||||||
}
|
|
||||||
ImGui.SameLine();
|
|
||||||
HelpMarker("The staging slot is the HUD layout slot that will be used as your HUD layout. All changes will be written to this slot when swaps are enabled.");
|
|
||||||
|
|
||||||
ImGui.Separator();
|
|
||||||
|
|
||||||
if (this.plugin.Config.Layouts2.Count == 0) {
|
|
||||||
ImGui.Text("Create at least one layout to begin setting up swaps.");
|
|
||||||
} else {
|
|
||||||
ImGui.Text("Add swap conditions below.\nThe first condition that is satisfied will be the layout that is used.");
|
|
||||||
ImGui.Separator();
|
|
||||||
this.DrawConditionsTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndTabBar();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.End();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawConditionsTable() {
|
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
|
||||||
float height = ImGui.GetContentRegionAvail().Y - ImGui.CalcTextSize(FontAwesomeIcon.Plus.ToIconString()).Y - ImGui.GetStyle().ItemSpacing.Y - ImGui.GetStyle().ItemInnerSpacing.Y * 2;
|
|
||||||
ImGui.PopFont();
|
|
||||||
if (!ImGui.BeginChild("##conditions-table", new Vector2(-1, height))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.Columns(4);
|
|
||||||
|
|
||||||
var conditions = new List<HudConditionMatch>(this.plugin.Config.HudConditionMatches);
|
|
||||||
if (this.editingConditionIndex == conditions.Count) {
|
|
||||||
conditions.Add(new HudConditionMatch());
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.Text("Job");
|
|
||||||
ImGui.NextColumn();
|
|
||||||
|
|
||||||
ImGui.Text("State");
|
|
||||||
ImGui.NextColumn();
|
|
||||||
|
|
||||||
ImGui.Text("Layout");
|
|
||||||
ImGui.NextColumn();
|
|
||||||
|
|
||||||
ImGui.Text("Options");
|
|
||||||
ImGui.NextColumn();
|
|
||||||
|
|
||||||
ImGui.Separator();
|
|
||||||
|
|
||||||
bool addCondition = false;
|
|
||||||
int actionedItemIndex = -1;
|
|
||||||
int action = 0; // 0 for delete, otherwise move.
|
|
||||||
foreach (var item in conditions.Select((cond, i) => new { cond, i })) {
|
|
||||||
if (this.editingConditionIndex == item.i) {
|
|
||||||
ImGui.PushItemWidth(-1);
|
|
||||||
if (ImGui.BeginCombo("##condition-edit-job", this.editingCondition.ClassJob ?? "Any")) {
|
|
||||||
if (ImGui.Selectable("Any##condition-edit-job")) {
|
|
||||||
this.editingCondition.ClassJob = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (ClassJob job in this.pi.Data.GetExcelSheet<ClassJob>().Skip(1)) {
|
|
||||||
if (ImGui.Selectable($"{job.Abbreviation}##condition-edit-job")) {
|
|
||||||
this.editingCondition.ClassJob = job.Abbreviation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndCombo();
|
|
||||||
}
|
|
||||||
ImGui.PopItemWidth();
|
|
||||||
ImGui.NextColumn();
|
|
||||||
|
|
||||||
ImGui.PushItemWidth(-1);
|
|
||||||
if (ImGui.BeginCombo("##condition-edit-status", this.editingCondition.Status?.Name() ?? "Any")) {
|
|
||||||
if (ImGui.Selectable("Any##condition-edit-status")) {
|
|
||||||
this.editingCondition.Status = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (Status status in Enum.GetValues(typeof(Status))) {
|
|
||||||
if (ImGui.Selectable($"{status.Name()}##condition-edit-status")) {
|
|
||||||
this.editingCondition.Status = status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndCombo();
|
|
||||||
}
|
|
||||||
ImGui.PopItemWidth();
|
|
||||||
ImGui.NextColumn();
|
|
||||||
|
|
||||||
ImGui.PushItemWidth(-1);
|
|
||||||
string comboPreview = this.editingCondition.LayoutId == Guid.Empty ? string.Empty : this.plugin.Config.Layouts2[this.editingCondition.LayoutId].Name;
|
|
||||||
if (ImGui.BeginCombo("##condition-edit-layout", comboPreview)) {
|
|
||||||
foreach (var layout in this.plugin.Config.Layouts2) {
|
|
||||||
if (ImGui.Selectable($"{layout.Value.Name}##condition-edit-layout-{layout.Key}")) {
|
|
||||||
this.editingCondition.LayoutId = layout.Key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndCombo();
|
|
||||||
}
|
|
||||||
ImGui.PopItemWidth();
|
|
||||||
ImGui.NextColumn();
|
|
||||||
|
|
||||||
if (this.editingCondition.LayoutId != Guid.Empty) {
|
|
||||||
if (IconButton(FontAwesomeIcon.Check, "condition-edit")) {
|
|
||||||
addCondition = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IconButton(FontAwesomeIcon.Times, "condition-stop")) {
|
|
||||||
this.editingConditionIndex = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.scrollToAdd) {
|
|
||||||
this.scrollToAdd = false;
|
|
||||||
ImGui.SetScrollHereY();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ImGui.Text(item.cond.ClassJob ?? string.Empty);
|
|
||||||
ImGui.NextColumn();
|
|
||||||
|
|
||||||
ImGui.Text(item.cond.Status?.Name() ?? string.Empty);
|
|
||||||
ImGui.NextColumn();
|
|
||||||
|
|
||||||
this.plugin.Config.Layouts2.TryGetValue(item.cond.LayoutId, out Layout condLayout);
|
|
||||||
ImGui.Text(condLayout?.Name ?? string.Empty);
|
|
||||||
ImGui.NextColumn();
|
|
||||||
|
|
||||||
if (IconButton(FontAwesomeIcon.PencilAlt, $"{item.i}")) {
|
|
||||||
this.editingConditionIndex = item.i;
|
|
||||||
this.editingCondition = item.cond;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (IconButton(FontAwesomeIcon.Trash, $"{item.i}")) {
|
|
||||||
actionedItemIndex = item.i;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (IconButton(FontAwesomeIcon.ArrowUp, $"{item.i}")) {
|
|
||||||
actionedItemIndex = item.i;
|
|
||||||
action = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (IconButton(FontAwesomeIcon.ArrowDown, $"{item.i}")) {
|
|
||||||
actionedItemIndex = item.i;
|
|
||||||
action = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.NextColumn();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.Columns(1);
|
|
||||||
|
|
||||||
ImGui.Separator();
|
|
||||||
|
|
||||||
ImGui.EndChild();
|
|
||||||
|
|
||||||
if (IconButton(FontAwesomeIcon.Plus, "condition")) {
|
|
||||||
this.editingConditionIndex = this.plugin.Config.HudConditionMatches.Count;
|
|
||||||
this.editingCondition = new HudConditionMatch();
|
|
||||||
this.scrollToAdd = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool recalculate = false;
|
|
||||||
|
|
||||||
if (addCondition) {
|
|
||||||
recalculate = true;
|
|
||||||
if (this.editingConditionIndex == this.plugin.Config.HudConditionMatches.Count) {
|
|
||||||
this.plugin.Config.HudConditionMatches.Add(this.editingCondition);
|
|
||||||
} else {
|
|
||||||
this.plugin.Config.HudConditionMatches.RemoveAt(this.editingConditionIndex);
|
|
||||||
this.plugin.Config.HudConditionMatches.Insert(this.editingConditionIndex, this.editingCondition);
|
|
||||||
}
|
|
||||||
this.plugin.Config.Save();
|
|
||||||
this.editingConditionIndex = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actionedItemIndex >= 0) {
|
|
||||||
recalculate = true;
|
|
||||||
if (action == 0) {
|
|
||||||
this.plugin.Config.HudConditionMatches.RemoveAt(actionedItemIndex);
|
|
||||||
} else {
|
|
||||||
if (actionedItemIndex + action >= 0 && actionedItemIndex + action < this.plugin.Config.HudConditionMatches.Count) {
|
|
||||||
// Move the condition.
|
|
||||||
var c = this.plugin.Config.HudConditionMatches[actionedItemIndex];
|
|
||||||
this.plugin.Config.HudConditionMatches.RemoveAt(actionedItemIndex);
|
|
||||||
this.plugin.Config.HudConditionMatches.Insert(actionedItemIndex + action, c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.plugin.Config.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recalculate) {
|
|
||||||
PlayerCharacter player = this.pi.ClientState.LocalPlayer;
|
|
||||||
if (player != null && this.plugin.Config.SwapsEnabled) {
|
|
||||||
this.plugin.Statuses.Update(player);
|
|
||||||
this.plugin.Statuses.SetHudLayout(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IconButton(FontAwesomeIcon icon, string append = "") {
|
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
|
||||||
bool button = ImGui.Button($"{icon.ToIconString()}##{append}");
|
|
||||||
ImGui.PopFont();
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void HelpMarker(string text) {
|
|
||||||
ImGui.TextDisabled("(?)");
|
|
||||||
if (ImGui.IsItemHovered()) {
|
|
||||||
ImGui.BeginTooltip();
|
|
||||||
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 20f);
|
|
||||||
ImGui.TextUnformatted(text);
|
|
||||||
ImGui.PopTextWrapPos();
|
|
||||||
ImGui.EndTooltip();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Draw() {
|
|
||||||
this.DrawSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Dictionary<string, Vector2<short>> GetPositions() {
|
|
||||||
Dictionary<string, Vector2<short>> positions = new Dictionary<string, Vector2<short>>();
|
|
||||||
|
|
||||||
foreach (string name in SAVED_WINDOWS) {
|
|
||||||
Vector2<short> pos = this.plugin.GameFunctions.GetWindowPosition(name);
|
|
||||||
if (pos != null) {
|
|
||||||
positions[name] = pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return positions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ImportSlot(string name, HudSlot slot, bool save = true) {
|
|
||||||
Dictionary<string, Vector2<short>> positions;
|
|
||||||
if (this.plugin.Config.ImportPositions) {
|
|
||||||
positions = this.GetPositions();
|
|
||||||
} else {
|
|
||||||
positions = new Dictionary<string, Vector2<short>>();
|
|
||||||
}
|
|
||||||
this.Import(name, this.plugin.Hud.ReadLayout(slot), positions, save);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Import(string name, byte[] layout, Dictionary<string, Vector2<short>> positions, bool save = true) {
|
|
||||||
Guid guid = this.plugin.Config.Layouts2.FirstOrDefault(kv => kv.Value.Name == name).Key;
|
|
||||||
if (guid == default) {
|
|
||||||
guid = new Guid();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.plugin.Config.Layouts2[Guid.NewGuid()] = new Layout(name, layout, positions);
|
|
||||||
if (save) {
|
|
||||||
this.plugin.Config.Save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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("HudSwap")]
|
|
||||||
[assembly: AssemblyDescription("")]
|
|
||||||
[assembly: AssemblyConfiguration("")]
|
|
||||||
[assembly: AssemblyCompany("")]
|
|
||||||
[assembly: AssemblyProduct("HudSwap")]
|
|
||||||
[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("cbf13a29-a5a4-4fa2-8a4f-cfbb5fb83f90")]
|
|
||||||
|
|
||||||
// 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("1.5.1")]
|
|
||||||
[assembly: AssemblyFileVersion("1.5.1")]
|
|
|
@ -1,35 +0,0 @@
|
||||||
using Dalamud.Game.ClientState.Actors.Types;
|
|
||||||
using Dalamud.Game.Internal;
|
|
||||||
using Dalamud.Plugin;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace HudSwap {
|
|
||||||
public class Swapper {
|
|
||||||
private readonly HudSwapPlugin plugin;
|
|
||||||
private readonly DalamudPluginInterface pi;
|
|
||||||
|
|
||||||
public Swapper(HudSwapPlugin plugin, DalamudPluginInterface pi) {
|
|
||||||
this.plugin = plugin;
|
|
||||||
this.pi = pi;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnFrameworkUpdate(Framework framework) {
|
|
||||||
if (framework == null) {
|
|
||||||
throw new ArgumentNullException(nameof(framework), "Framework cannot be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.plugin.Config.SwapsEnabled || !this.plugin.Config.UnderstandsRisks) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlayerCharacter player = this.pi.ClientState.LocalPlayer;
|
|
||||||
if (player == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.plugin.Statuses.Update(player)) {
|
|
||||||
this.plugin.Statuses.SetHudLayout(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<packages>
|
|
||||||
<package id="Microsoft.CodeAnalysis.FxCopAnalyzers" version="2.9.6" targetFramework="net48" developmentDependency="true" />
|
|
||||||
<package id="Microsoft.CodeAnalysis.VersionCheckAnalyzer" version="2.9.6" targetFramework="net48" developmentDependency="true" />
|
|
||||||
<package id="Microsoft.CodeQuality.Analyzers" version="2.9.6" targetFramework="net48" developmentDependency="true" />
|
|
||||||
<package id="Microsoft.NetCore.Analyzers" version="2.9.6" targetFramework="net48" developmentDependency="true" />
|
|
||||||
<package id="Microsoft.NetFramework.Analyzers" version="2.9.6" targetFramework="net48" developmentDependency="true" />
|
|
||||||
</packages>
|
|
Loading…
Reference in New Issue
Block a user