refactor: begin update to HUD Manager

This commit is contained in:
Anna 2021-03-08 10:03:11 -05:00
parent 2454811d49
commit 507812d111
Signed by: anna
GPG Key ID: 0B391D8F06FCD9E0
32 changed files with 1351 additions and 1970 deletions

View File

@ -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

View File

@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30320.27
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
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View 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);
}
}
}

View 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!;
}
}

View 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();
}
}
}

View 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(),
};
}
}
}

View 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>

View 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
View 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>

View 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
View 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
View 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
View 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();
}
}
}
}

View File

@ -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.Plugin;
using Lumina.Excel.GeneratedSheets;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
// TODO: Zone swaps?
namespace HudSwap {
namespace HUD_Manager {
public class Statuses {
private readonly HudSwapPlugin plugin;
private readonly DalamudPluginInterface pi;
private Plugin Plugin { get; }
private readonly Dictionary<Status, bool> condition = new Dictionary<Status, bool>();
private ClassJob job;
private readonly Dictionary<Status, bool> _condition = new();
private ClassJob? _job;
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);
}
public Statuses(HudSwapPlugin plugin, DalamudPluginInterface pi) {
this.plugin = plugin;
this.pi = pi;
public Statuses(Plugin plugin) {
this.Plugin = plugin;
}
public bool Update(PlayerCharacter player) {
public bool Update(PlayerCharacter? player) {
if (player == null) {
return false;
}
bool anyChanged = false;
var anyChanged = false;
ClassJob currentJob = this.pi.Data.GetExcelSheet<ClassJob>().GetRow(player.ClassJob.Id);
if (this.job != null && this.job != currentJob) {
var currentJob = this.Plugin.Interface.Data.GetExcelSheet<ClassJob>().GetRow(player.ClassJob.Id);
if (this._job != null && this._job != currentJob) {
anyChanged = true;
}
this.job = currentJob;
this._job = currentJob;
foreach (Status status in Enum.GetValues(typeof(Status))) {
var old = this.condition.ContainsKey(status) && this.condition[status];
this.condition[status] = status.Active(player, this.pi);
anyChanged |= old != this.condition[status];
var old = this._condition.ContainsKey(status) && this._condition[status];
this._condition[status] = status.Active(player, this.Plugin.Interface);
anyChanged |= old != this._condition[status];
}
return anyChanged;
}
public Guid CalculateCurrentHud() {
PlayerCharacter player = this.pi.ClientState.LocalPlayer;
private Guid CalculateCurrentHud() {
var player = this.Plugin.Interface.ClientState.LocalPlayer;
if (player == null) {
return Guid.Empty;
}
foreach (var match in this.plugin.Config.HudConditionMatches) {
if ((!match.Status.HasValue || this.condition[match.Status.Value]) &&
(match.ClassJob == null || this.job.Abbreviation == match.ClassJob)) {
foreach (var match in this.Plugin.Config.HudConditionMatches) {
if ((!match.Status.HasValue || this._condition[match.Status.Value]) &&
(match.ClassJob == null || this._job?.Abbreviation == match.ClassJob)) {
return match.LayoutId;
}
}
@ -66,23 +64,23 @@ namespace HudSwap {
return Guid.Empty;
}
public void SetHudLayout(PlayerCharacter player, bool update = false) {
public void SetHudLayout(PlayerCharacter? player, bool update = false) {
if (update && player != null) {
this.Update(player);
}
Guid layoutId = this.CalculateCurrentHud();
var layoutId = this.CalculateCurrentHud();
if (layoutId == Guid.Empty) {
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
}
this.plugin.Hud.WriteLayout(this.plugin.Config.StagingSlot, layout.Hud);
this.plugin.Hud.SelectSlot(this.plugin.Config.StagingSlot, true);
this.Plugin.Hud.WriteLayout(this.Plugin.Config.StagingSlot, layout.ToLayout());
this.Plugin.Hud.SelectSlot(this.Plugin.Config.StagingSlot, true);
foreach (KeyValuePair<string, Vector2<short>> entry in layout.Positions) {
this.plugin.GameFunctions.MoveWindow(entry.Key, entry.Value.X, entry.Value.Y);
foreach (var entry in layout.Positions) {
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).
/// We do this because using <see cref="ClassJob"/> results in circular dependency errors when serializing.
/// </summary>
public string ClassJob { get; set; }
public string? ClassJob { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public Status? Status { get; set; }
@ -141,7 +139,7 @@ namespace HudSwap {
throw new ArgumentNullException(nameof(pi), "DalamudPluginInterface cannot be null");
}
ConditionFlag flag = (ConditionFlag)status;
var flag = (ConditionFlag)status;
if (flag != ConditionFlag.None) {
return pi.ClientState.Condition[flag];
}

29
HUD Manager/Structs/Element.cs Executable file
View 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;
}
}

View 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
View 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;
}
}
}

View File

@ -0,0 +1,6 @@
namespace HUD_Manager.Structs {
public enum Visibility : byte {
Hidden = 1,
Visible = 3,
}
}

31
HUD Manager/Swapper.cs Normal file
View 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);
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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")]

View File

@ -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;
}
}
}

View File

@ -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>

View File

@ -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
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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
}
}
}

View File

@ -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();
}
}
}
}

View File

@ -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();
}
}
}
}

View File

@ -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")]

View File

@ -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);
}
}
}
}

View File

@ -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>