From 08d198ab5ce6a1d2690df6be1064aba992d27240 Mon Sep 17 00:00:00 2001 From: oorzkws <65210810+oorzkws@users.noreply.github.com> Date: Thu, 6 Apr 2023 05:57:03 -0600 Subject: [PATCH 1/6] update .gitignore, csproj Fixes the build warnings for other OS --- .gitignore | 3 ++- BigPlayerDebuffs.csproj | 2 +- packages.lock.json | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index ace3a58..10292e8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ packages/ bin/ obj/ Publish/ -Publish \ No newline at end of file +Publish +*.user diff --git a/BigPlayerDebuffs.csproj b/BigPlayerDebuffs.csproj index d03dabf..9531df5 100644 --- a/BigPlayerDebuffs.csproj +++ b/BigPlayerDebuffs.csproj @@ -10,7 +10,7 @@ - net7.0 + net7.0-windows x64 enable latest diff --git a/packages.lock.json b/packages.lock.json index ce366a3..2426061 100644 --- a/packages.lock.json +++ b/packages.lock.json @@ -1,7 +1,7 @@ { "version": 1, "dependencies": { - "net7.0": { + "net7.0-windows7.0": { "DalamudPackager": { "type": "Direct", "requested": "[2.1.10, )", From 6a6f4e0dcb8c822543346fdf2fdee986f537e7fd Mon Sep 17 00:00:00 2001 From: oorzkws <65210810+oorzkws@users.noreply.github.com> Date: Thu, 6 Apr 2023 05:58:28 -0600 Subject: [PATCH 2/6] Config update - focus target support, cleaner UI --- BigPlayerDebuffsConfig.cs | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/BigPlayerDebuffsConfig.cs b/BigPlayerDebuffsConfig.cs index 8d65a8e..b979f31 100644 --- a/BigPlayerDebuffsConfig.cs +++ b/BigPlayerDebuffsConfig.cs @@ -2,10 +2,7 @@ using Dalamud.Plugin; using ImGuiNET; using System; -using System.Collections.Generic; -using System.Diagnostics; using System.Numerics; -using Dalamud.Game.Text; namespace BigPlayerDebuffs { @@ -27,8 +24,11 @@ public class BigPlayerDebuffsConfig : IPluginConfiguration { public int Version { get; set; } + // What does the 'b' mean? public float bScale = 1.4f; - + public float fScale = 1.25f; + public bool includeMainTarget = true; + public bool includeFocusTarget = true; public void Init(BigPlayerDebuffs plugin, DalamudPluginInterface pluginInterface) { this.plugin = plugin; @@ -47,17 +47,36 @@ public bool DrawConfigUI() { var modified = false; - ImGui.SetNextWindowSize(new Vector2(500 * scale, 350), ImGuiCond.FirstUseEver); - ImGui.SetNextWindowSizeConstraints(new Vector2(500 * scale, 350), new Vector2(560 * scale, 650)); - ImGui.Begin($"{plugin.Name} Config", ref drawConfig, ImGuiWindowFlags.NoCollapse); - - modified |= ImGui.SliderFloat("Own Buff/Debuff Scale", ref bScale, 1.0F, 2.0F); - + ImGui.SetNextWindowSize(new Vector2(550, 120) * scale, ImGuiCond.FirstUseEver); + ImGui.SetNextWindowSizeConstraints(new Vector2(550, 110), new Vector2(1100, 650) * scale); + ImGui.Begin($"{plugin.Name} Configuration", ref drawConfig, ImGuiWindowFlags.NoCollapse); + // Target UI + ImGui.BeginGroup(); + modified |= ImGui.Checkbox("##Enable scaling in Target UI",ref includeMainTarget); + if(ImGui.IsItemHovered()) + ImGui.SetTooltip("Enable scaling in target UI"); + ImGui.SameLine(); + ImGui.BeginDisabled(!includeMainTarget); + modified |= ImGui.SliderFloat("Scale in Target UI", ref bScale, 1.0F, 2.0F, "%.2f"); + ImGui.EndDisabled(); + ImGui.EndGroup(); + // Focus target + ImGui.BeginGroup(); + modified |= ImGui.Checkbox("##Enable scaling in Focus Target UI",ref includeFocusTarget); + if(ImGui.IsItemHovered()) + ImGui.SetTooltip("Enable scaling in focus target UI"); + ImGui.SameLine(); + ImGui.BeginDisabled(!includeFocusTarget); + modified |= ImGui.SliderFloat("Scale in Focus Target UI", ref fScale, 1.0F, 2.0F, "%.2f"); + ImGui.EndDisabled(); + ImGui.EndGroup(); + ImGui.Text("Hint: Ctrl+Click a slider to input a number directly"); ImGui.End(); if (modified) { + plugin.ResetTargetStatus(); Save(); } From 5e5fab059ddc1244d006ff51241c7738c6ec853c Mon Sep 17 00:00:00 2001 From: oorzkws <65210810+oorzkws@users.noreply.github.com> Date: Thu, 6 Apr 2023 05:59:47 -0600 Subject: [PATCH 3/6] Main flow rework, introduces Focus Target support I might have accidentally spent a few hours on this. The pre-existing compiler warnings are still there and yours to solve how you see fit. Resolves rgd87/BigPlayerDebuffs#4 on merge. --- BigPlayerDebuffs.cs | 341 ++++++++++++++++++++++---------------------- 1 file changed, 174 insertions(+), 167 deletions(-) diff --git a/BigPlayerDebuffs.cs b/BigPlayerDebuffs.cs index ca5dc1e..e048e7e 100644 --- a/BigPlayerDebuffs.cs +++ b/BigPlayerDebuffs.cs @@ -1,20 +1,18 @@ using System; +using System.Collections.Generic; using System.Linq; using Dalamud.Plugin; using System.Reflection; using FFXIVClientStructs.Attributes; using FFXIVClientStructs.FFXIV.Component.GUI; - -using Dalamud.Hooking; -using Dalamud.Logging; using Dalamud.Game; using Dalamud.Game.Gui; using Dalamud.Game.ClientState; -using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.Command; using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Data; +using Dalamud.Logging; namespace BigPlayerDebuffs { @@ -69,7 +67,8 @@ public class BigPlayerDebuffs: IDalamudPlugin { internal Common common; int curSecondRowOffset = 41; - int curDebuffs = -1; + int targetDebuffs = -1; + private int fTargetDebuffs = -1; public BigPlayerDebuffs( DalamudPluginInterface pluginInterface, @@ -124,7 +123,7 @@ TargetManager targets public void InvalidateState() { - curDebuffs = -1; + targetDebuffs = -1; curSecondRowOffset = -1; UpdateTargetStatus(); } @@ -144,26 +143,6 @@ public void Dispose() { RemoveCommands(); } - //public void Initialize(DalamudPluginInterface pluginInterface) { - // this.PluginInterface = pluginInterface; - // this.PluginConfig = (BigPlayerDebuffsConfig) pluginInterface.GetPluginConfig() ?? new BigPlayerDebuffsConfig(); - // this.PluginConfig.Init(this, pluginInterface); - - // this.common = new Common(pluginInterface); - - - // PluginInterface.UiBuilder.OnOpenConfigUi += (sender, args) => { - // this.drawConfigWindow = true; - // }; - - // PluginInterface.UiBuilder.OnBuildUi += this.BuildUI; - - - // PluginInterface.Framework.OnUpdateEvent += FrameworkOnUpdate; - - // SetupCommands(); - //} - private void FrameworkOnUpdate(Framework framework) { #if DEBUG @@ -180,164 +159,192 @@ private void FrameworkOnUpdate(Framework framework) #endif } - private unsafe void UpdateTargetStatus() - { - - //var targetGameObject = TargetManager.Target; - if (TargetManager.Target is BattleChara target) - { - - var playerAuras = 0; + private enum ChildEnumMode { + NextNext, + ChildNext, + PrevPrev, + ChildPrev, + ChildPrevPrev + }; + + private enum ChildEnumOrder { + ZeroForward, + MaxBackward + } - //PluginLog.Log($"StatusEffects.Length {target.StatusEffects.Length}"); // Always 30 + private unsafe struct UiElement { + public readonly AtkUnitBase* Element; + public readonly string Name; + public readonly int StatusListIndex; + public readonly ChildEnumMode EnumMode; + public readonly ChildEnumOrder EnumOrder; + + public UiElement(string name, int statusListIndex) { + Name = name; + Element = Common.GetUnitBase(name); + StatusListIndex = statusListIndex; + EnumMode = Name == "_TargetInfoBuffDebuff" ? ChildEnumMode.ChildPrev : ChildEnumMode.NextNext; + EnumOrder = Name == "_TargetInfoBuffDebuff" ? ChildEnumOrder.MaxBackward : ChildEnumOrder.ZeroForward; + } - var localPlayerId = ClientState.LocalPlayer?.ObjectId; - for (var i = 0; i < 30; i++) - { - if (target.StatusList[i].SourceId == localPlayerId) playerAuras++; + public bool Valid() => Element is not null + && Element->UldManager.NodeList is not null + && Element->UldManager.NodeList[StatusListIndex] is not null; + + public AtkResNode* StatusList => Element->UldManager.NodeList[StatusListIndex]; + + public AtkResNode*[] Children { + get { + if (!Valid()) + return new AtkResNode*[0]; + var children = new AtkResNode*[StatusList->ChildCount]; + + // Separate debuff does it a bit differently :\ + var child = EnumMode switch { + ChildEnumMode.NextNext => StatusList->NextSiblingNode, + ChildEnumMode.ChildNext => StatusList->ChildNode, + ChildEnumMode.PrevPrev => StatusList->PrevSiblingNode, + ChildEnumMode.ChildPrev => StatusList->ChildNode, + ChildEnumMode.ChildPrevPrev => StatusList->ChildNode->PrevSiblingNode, + _ => throw new ArgumentOutOfRangeException(nameof(ChildEnumMode), $"Unexpected enum value: {EnumMode}") + }; + + // No children? No problem + if (child is null || (int) child == 0) + return new AtkResNode*[0]; + // Reverse for MaxBackward + var i = EnumOrder == ChildEnumOrder.MaxBackward ? children.Length - 1 : 0; + + // soundness (index out of range) + // will error if the game lies to us about ChildCount + while (child is not null) { + var newIndex = EnumOrder == ChildEnumOrder.MaxBackward ? i-- : i++; + children[newIndex] = child; + + child = EnumMode switch { + ChildEnumMode.NextNext => child->NextSiblingNode, + ChildEnumMode.ChildNext => child->NextSiblingNode, + ChildEnumMode.PrevPrev => child->PrevSiblingNode, + ChildEnumMode.ChildPrev => child->PrevSiblingNode, + ChildEnumMode.ChildPrevPrev => child->PrevSiblingNode, + _ => throw new ArgumentOutOfRangeException(nameof(ChildEnumMode), $"Unexpected enum value: {EnumMode}") + }; + } + + // Note: The re-sorting we do here lets us avoid annoyances when iterating later + // because we no longer have to care what nuisances affect accessing the target + return children; } + } + } - //PluginLog.Log($"Player Auras old:{this.curDebuffs} new: {playerAuras}"); - - if (this.curDebuffs != playerAuras) { - - //PluginLog.Log($"Updating..."); - - var playerScale = this.PluginConfig.bScale; - - var targetInfoUnitBase = Common.GetUnitBase("_TargetInfo", 1); - if (targetInfoUnitBase == null) return; - if (targetInfoUnitBase->UldManager.NodeList == null || targetInfoUnitBase->UldManager.NodeListCount < 53) return; - - var targetInfoStatusUnitBase = Common.GetUnitBase("_TargetInfoBuffDebuff", 1); - if (targetInfoStatusUnitBase == null) return; - if (targetInfoStatusUnitBase->UldManager.NodeList == null || targetInfoStatusUnitBase->UldManager.NodeListCount < 32) return; - - this.curDebuffs = playerAuras; - - var adjustOffsetY = -(int)(41 * (playerScale-1.0f)/4.5); - - var xIncrement = (int)((playerScale - 1.0f) * 25); + private readonly Dictionary targetElements = new(){ + {1, "_TargetInfoBuffDebuff"}, + {2, "_TargetInfo"}, + {3, "_FocusTargetInfo"} + }; - // Split Target Frame + private unsafe void UpdateTargetStatus() { - var growingOffsetX = 0; - for (var i = 0; i < 15; i++) - { - var node = targetInfoStatusUnitBase->UldManager.NodeList[31 - i]; - node->X = i * 25 + growingOffsetX; + var localPlayerId = ClientState.LocalPlayer?.ObjectId; + var playerAuras = 0; + if (TargetManager.Target is BattleChara target) { + playerAuras = target.StatusList.Count(s => s.SourceId == localPlayerId); + } - if (i < playerAuras) - { - node->ScaleX = playerScale; - node->ScaleY = playerScale; - node->Y = adjustOffsetY; - growingOffsetX += xIncrement; - } - else - { - node->ScaleX = 1.0f; - node->ScaleY = 1.0f; - node->Y = 0; - } - node->Flags_2 |= 0x1; // 0x1 flag i'm guessing recalculates only for this node - } + var focusAuras = 0; + if (TargetManager.FocusTarget is BattleChara focusTarget) { + focusAuras = focusTarget.StatusList.Count(s => s.SourceId == localPlayerId); + } + + //PluginLog.Log($"StatusEffects.Length {target.StatusEffects.Length}"); // Always 30 + //PluginLog.Log($"Player Auras old:{this.curDebuffs} new: {playerAuras}"); + // Hasn't changed since last tick + if (targetDebuffs == playerAuras && fTargetDebuffs == focusAuras) { + return; + } - // Merged Target Frame - - growingOffsetX = 0; - for (var i = 0; i < 15; i++) - { - var node = targetInfoUnitBase->UldManager.NodeList[32 - i]; - node->X = i * 25 + growingOffsetX; - - if (i < playerAuras) - { - node->ScaleX = playerScale; - node->ScaleY = playerScale; - node->Y = adjustOffsetY; - growingOffsetX += xIncrement; - } - else - { - node->ScaleX = 1.0f; - node->ScaleY = 1.0f; - node->Y = 0; - } - node->Flags_2 |= 0x1; + //PluginLog.Log($"Updating..."); + foreach (var element in targetElements) { + var playerScale = PluginConfig.bScale; + var targetAuras = playerAuras; + switch (element.Value) { + case "_TargetInfoBuffDebuff" when PluginConfig.includeMainTarget: + break; + case "_TargetInfo" when PluginConfig.includeMainTarget: + break; + case "_FocusTargetInfo" when PluginConfig.includeFocusTarget: + playerScale = PluginConfig.fScale; + targetAuras = focusAuras; + break; + default: + continue; + } + var uiElement = new UiElement(element.Value, element.Key); + var children = uiElement.Children; + // Poor man's IEnumerable, but that's life with unsafe + for (var childIndex = 0; childIndex < children.Length; childIndex++) { + var child = children[childIndex]; + var scalar = childIndex < targetAuras ? playerScale : 1.0f; + child->ScaleX = scalar; + child->ScaleY = scalar; + child->X = childIndex % 15 * child->Width; + child->Y = childIndex < 15 ? 0 : child->Height; + if (childIndex < targetAuras) { + child->ScaleX = playerScale; + child->ScaleY = playerScale; } - - /////////////////// - - var newSecondRowOffset = (playerAuras > 0) ? (int)(playerScale*41) : 41; - - if (newSecondRowOffset != this.curSecondRowOffset) - { - // Split Target Frame Second Row - for (var i = 16; i >= 2; i--) - { - targetInfoStatusUnitBase->UldManager.NodeList[i]->Y = newSecondRowOffset; - targetInfoStatusUnitBase->UldManager.NodeList[i]->Flags_2 |= 0x1; - } - // Merged Target Frame Second Row - for (var i = 17; i >= 3; i--) - { - targetInfoUnitBase->UldManager.NodeList[i]->Y = newSecondRowOffset; - targetInfoUnitBase->UldManager.NodeList[i]->Flags_2 |= 0x1; - } - this.curSecondRowOffset = newSecondRowOffset; + switch (childIndex) { + // For simplicity's sake, we're going to assume no player has >14 debuffs out at once + case < 15 when targetAuras > 0: + // Add the difference between an unscaled and a scaled icon + child->X += (child->Width * playerScale - child->Width) * MathF.Min(childIndex, targetAuras); + // We bump the Y offset a bit for our changed icons + if (childIndex < targetAuras) { + child->Y = (child->Height * playerScale - child->Height) / -(child->Height / 2 ); + } + else { + child->Y = 0; + } + break; + case > 14 when targetAuras > 0: + child->Y = child->Height * playerScale; + break; + default: + child->Y = childIndex > 14 ? child->Height * playerScale : 0; + break; } - - // Setting 0x4 flag on the root element to recalculate the scales down the tree - targetInfoStatusUnitBase->UldManager.NodeList[1]->Flags_2 |= 0x4; - targetInfoStatusUnitBase->UldManager.NodeList[1]->Flags_2 |= 0x1; - targetInfoUnitBase->UldManager.NodeList[2]->Flags_2 |= 0x4; - targetInfoUnitBase->UldManager.NodeList[2]->Flags_2 |= 0x1; - + // Set update flag + child->Flags_2 |= 0x1; + // Onto the next one + child = child->NextSiblingNode; } + uiElement.StatusList->Flags_2 |= 0x4; + uiElement.StatusList->Flags_2 |= 0x1; } - } - private unsafe void ResetTargetStatus() + public unsafe void ResetTargetStatus() { - var targetInfoUnitBase = Common.GetUnitBase("_TargetInfo", 1); - if (targetInfoUnitBase == null) return; - if (targetInfoUnitBase->UldManager.NodeList == null || targetInfoUnitBase->UldManager.NodeListCount < 53) return; - - var targetInfoStatusUnitBase = Common.GetUnitBase("_TargetInfoBuffDebuff", 1); - if (targetInfoStatusUnitBase == null) return; - if (targetInfoStatusUnitBase->UldManager.NodeList == null || targetInfoStatusUnitBase->UldManager.NodeListCount < 32) return; + foreach (var element in targetElements) { + var uiElement = new UiElement(element.Value, element.Key); + var children = uiElement.Children; + // Poor man's IEnumerable, but that's life with unsafe + for(var childIndex = 0; childIndex < children.Length; childIndex++) { + var child = children[childIndex]; + child->ScaleX = 1.0f; + child->ScaleY = 1.0f; + child->X = childIndex % 15 * child->Width; + child->Y = childIndex < 15 ? 0 : child->Height; + // Set update flag + child->Flags_2 |= 0x1; + // Onto the next one + child = child->NextSiblingNode; + } - for (var i = 0; i < 15; i++) - { - var node = targetInfoStatusUnitBase->UldManager.NodeList[31 - i]; - node->ScaleX = 1.0f; - node->ScaleY = 1.0f; - node->X = i * 25; - node->Y = 0; - node->Flags_2 |= 0x1; - - node = targetInfoUnitBase->UldManager.NodeList[32 - i]; - node->ScaleX = 1.0f; - node->ScaleY = 1.0f; - node->X = i * 25; - node->Y = 0; - node->Flags_2 |= 0x1; + uiElement.StatusList->Flags_2 |= 0x4; + uiElement.StatusList->Flags_2 |= 0x1; } - for (var i = 17; i >= 2; i--) - { - targetInfoStatusUnitBase->UldManager.NodeList[i]->Y = 41; - targetInfoStatusUnitBase->UldManager.NodeList[i]->Flags_2 |= 0x1; - } - for (var i = 18; i >= 3; i--) - { - targetInfoUnitBase->UldManager.NodeList[i]->Y = 41; - targetInfoUnitBase->UldManager.NodeList[i]->Flags_2 |= 0x1; - } - - targetInfoStatusUnitBase->UldManager.NodeList[1]->Flags_2 |= 0x4; - targetInfoUnitBase->UldManager.NodeList[2]->Flags_2 |= 0x4; } From b88270e338132375791b19d8b5a3216a9c715f5f Mon Sep 17 00:00:00 2001 From: oorzkws <65210810+oorzkws@users.noreply.github.com> Date: Sat, 8 Apr 2023 13:49:01 -0600 Subject: [PATCH 4/6] big refactor fixed warnings, solved the focus target issue using a disgusting hack - check the text color for white (this seems to still be valid for buffs/debuffs without durations) --- BigPlayerDebuffs.cs | 589 +++++++++++++++++--------------------- BigPlayerDebuffs.csproj | 110 +++---- BigPlayerDebuffsConfig.cs | 128 ++++----- 3 files changed, 381 insertions(+), 446 deletions(-) diff --git a/BigPlayerDebuffs.cs b/BigPlayerDebuffs.cs index e048e7e..4f666a7 100644 --- a/BigPlayerDebuffs.cs +++ b/BigPlayerDebuffs.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Generic; using System.Linq; using Dalamud.Plugin; -using System.Reflection; -using FFXIVClientStructs.Attributes; using FFXIVClientStructs.FFXIV.Component.GUI; using Dalamud.Game; using Dalamud.Game.Gui; @@ -11,366 +8,310 @@ using Dalamud.Game.Command; using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Data; -using Dalamud.Logging; - -namespace BigPlayerDebuffs -{ - internal unsafe class Common { - public static DalamudPluginInterface PluginInterface { get; private set; } - public static GameGui GameGui { get; private set; } - - public Common(DalamudPluginInterface pluginInterface, GameGui gameGui) - { - PluginInterface = pluginInterface; - GameGui = gameGui; - } - public static AtkUnitBase* GetUnitBase(string name, int index = 1) - { - return (AtkUnitBase*)GameGui.GetAddonByName(name, index); - } - public static T* GetUnitBase(string name = null, int index = 1) where T : unmanaged - { - if (string.IsNullOrEmpty(name)) - { - var attr = (Addon)typeof(T).GetCustomAttribute(typeof(Addon)); - if (attr != null) - { - name = attr.AddonIdentifiers.FirstOrDefault(); - } - } +namespace BigPlayerDebuffs; - if (string.IsNullOrEmpty(name)) return null; +internal enum ChildEnumMode { + NextNext, + ChildNext, + PrevPrev, + ChildPrev, + ChildPrevPrev +}; - return (T*)GameGui.GetAddonByName(name, index); - } - } +internal enum ChildEnumOrder { + ZeroForward, + MaxBackward +} - public class BigPlayerDebuffs: IDalamudPlugin { - public string Name => "BigPlayerDebuffs"; - - public static DalamudPluginInterface PluginInterface { get; private set; } - public static ClientState ClientState { get; private set; } - public static TargetManager TargetManager{ get; private set; } - public static Framework Framework { get; private set; } - public static GameGui GameGui { get; private set; } - public static CommandManager CommandManager { get; private set; } - public static ObjectTable Objects { get; private set; } - public static SigScanner SigScanner { get; private set; } - public static DataManager DataManager { get; private set; } - - public BigPlayerDebuffsConfig PluginConfig { get; private set; } - - private bool drawConfigWindow; - - internal Common common; - - int curSecondRowOffset = 41; - int targetDebuffs = -1; - private int fTargetDebuffs = -1; - - public BigPlayerDebuffs( - DalamudPluginInterface pluginInterface, - ClientState clientState, - CommandManager commandManager, - Framework framework, - ObjectTable objects, - GameGui gameGui, - SigScanner sigScanner, - DataManager dataManager, - TargetManager targets - ) - { - PluginInterface = pluginInterface; - ClientState = clientState; - Framework = framework; - CommandManager = commandManager; - Objects = objects; - SigScanner = sigScanner; - DataManager = dataManager; - TargetManager = targets; - - this.common = new Common(pluginInterface, gameGui); - - this.PluginConfig = (BigPlayerDebuffsConfig)pluginInterface.GetPluginConfig() ?? new BigPlayerDebuffsConfig(); - this.PluginConfig.Init(this, pluginInterface); - - // Upgrade if config is too old - //try - //{ - // Config = PluginInterface.GetPluginConfig() as Configuration ?? new Configuration(); - //} - //catch (Exception e) - //{ - // PluginLog.LogError("Error loading config", e); - // Config = new Configuration(); - // Config.Save(); - //} - //if (Config.Version == 0) - //{ - // PluginLog.Log("Old config version found"); - // Config = new Configuration(); - // Config.Save(); - //} - - PluginInterface.UiBuilder.Draw += this.BuildUI; - PluginInterface.UiBuilder.OpenConfigUi += this.OnOpenConfig; - Framework.Update += FrameworkOnUpdate; - SetupCommands(); +internal unsafe class UiElement { + public readonly string Name; - } + private AtkUnitBase* element; + private readonly int childListPos; + private readonly ChildEnumMode enumMode; + private readonly ChildEnumOrder enumOrder; + private readonly GameGui gui; - public void InvalidateState() - { - targetDebuffs = -1; - curSecondRowOffset = -1; - UpdateTargetStatus(); - } + public void Refresh() { + element = (AtkUnitBase*) gui.GetAddonByName(Name); + } + + public UiElement(string name, int childListIndex, ChildEnumMode mode, ChildEnumOrder order, GameGui gameGui) { + Name = name; + childListPos = childListIndex; + enumMode = mode; + enumOrder = order; + gui = gameGui; + Refresh(); + } + private bool Valid() => element is not null + && element->UldManager.NodeList is not null + && element->UldManager.NodeList[childListPos] is not null; - public void Dispose() { - PluginInterface.UiBuilder.Draw -= this.BuildUI; - PluginInterface.UiBuilder.OpenConfigUi -= OnOpenConfig; + public AtkResNode* StatusList => element->UldManager.NodeList[childListPos]; - Framework.Update -= FrameworkOnUpdate; + public AtkResNode*[] Children { + get { + if (!Valid()) { + return new AtkResNode*[0]; + } - ResetTargetStatus(); + var children = new AtkResNode*[StatusList->ChildCount]; + + // Separate debuff does it a bit differently :\ + var child = enumMode switch { + ChildEnumMode.NextNext => StatusList->NextSiblingNode, + ChildEnumMode.ChildNext => StatusList->ChildNode, + ChildEnumMode.PrevPrev => StatusList->PrevSiblingNode, + ChildEnumMode.ChildPrev => StatusList->ChildNode, + ChildEnumMode.ChildPrevPrev => StatusList->ChildNode->PrevSiblingNode, + _ => throw new ArgumentOutOfRangeException(nameof(ChildEnumMode), + $"Unexpected enum value: {enumMode}") + }; + + // No children? No problem + if (child is null || (int) child == 0) { + return new AtkResNode*[0]; + } - PluginInterface = null; - //Config = null; + // Reverse for MaxBackward + var i = enumOrder == ChildEnumOrder.MaxBackward ? children.Length - 1 : 0; + + // soundness (index out of range) + // will error if the game lies to us about ChildCount + while (child is not null) { + var newIndex = enumOrder == ChildEnumOrder.MaxBackward ? i-- : i++; + children[newIndex] = child; + + child = enumMode switch { + ChildEnumMode.NextNext => child->NextSiblingNode, + ChildEnumMode.ChildNext => child->NextSiblingNode, + ChildEnumMode.PrevPrev => child->PrevSiblingNode, + ChildEnumMode.ChildPrev => child->PrevSiblingNode, + ChildEnumMode.ChildPrevPrev => child->PrevSiblingNode, + _ => throw new ArgumentOutOfRangeException(nameof(ChildEnumMode), + $"Unexpected enum value: {enumMode}") + }; + } - RemoveCommands(); + // Note: The re-sorting we do here lets us avoid annoyances when iterating later + // because we no longer have to care what nuisances affect accessing the target + return children; } + } +} + +// ReSharper disable once ClassNeverInstantiated.Global +public class BigPlayerDebuffs : IDalamudPlugin { + public string Name => "BigPlayerDebuffs"; + + private readonly DalamudPluginInterface pluginInterface; + private readonly ClientState client; + private readonly TargetManager targets; + private readonly Framework framework; + private readonly CommandManager commands; + + private readonly BigPlayerDebuffsConfig pluginConfig; + + private readonly UiElement[] uiElements; + + private bool drawConfigWindow; + + private int targetDebuffs = -1; + private int fTargetDebuffs = -1; + + public BigPlayerDebuffs( + DalamudPluginInterface dalamudPluginInterface, + ClientState clientState, + CommandManager commandManager, + Framework dalamudFramework, + GameGui gameGui, + TargetManager targetManager + ) { + pluginInterface = dalamudPluginInterface; + client = clientState; + commands = commandManager; + framework = dalamudFramework; + targets = targetManager; + + pluginConfig = pluginInterface.GetPluginConfig() as BigPlayerDebuffsConfig ?? new BigPlayerDebuffsConfig(); + pluginConfig.Init(this, pluginInterface); + + // We have to supply .gui since unsafe classes are static + uiElements = new[] { + new UiElement("_TargetInfoBuffDebuff", 1, ChildEnumMode.ChildPrev, ChildEnumOrder.MaxBackward, gameGui), + new UiElement("_TargetInfo", 2, ChildEnumMode.NextNext, ChildEnumOrder.ZeroForward, gameGui), + new UiElement("_FocusTargetInfo", 3, ChildEnumMode.NextNext, ChildEnumOrder.ZeroForward, gameGui) + }; + + // Wire up + pluginInterface.UiBuilder.Draw += BuildUi; + pluginInterface.UiBuilder.OpenConfigUi += OnOpenConfig; + framework.Update += FrameworkOnUpdate; + SetupCommands(); + } + + public void InvalidateState() { + targetDebuffs = -1; + UpdateTargetStatus(); + } - private void FrameworkOnUpdate(Framework framework) - { + public void Dispose() { + // Remove hooks + pluginInterface.UiBuilder.Draw -= BuildUi; + pluginInterface.UiBuilder.OpenConfigUi -= OnOpenConfig; + framework.Update -= FrameworkOnUpdate; + RemoveCommands(); + // Reset changes + ResetTargetStatus(); + } + + private void FrameworkOnUpdate(Framework _) { #if DEBUG - try - { - UpdateTargetStatus(); - } - catch (Exception ex) - { - PluginLog.Error(ex.ToString()); - } + try { + UpdateTargetStatus(); + } + catch (Exception ex) { + PluginLog.Error(ex.ToString()); + } #else UpdateTargetStatus(); #endif - } + } - private enum ChildEnumMode { - NextNext, - ChildNext, - PrevPrev, - ChildPrev, - ChildPrevPrev - }; - private enum ChildEnumOrder { - ZeroForward, - MaxBackward + private unsafe void UpdateTargetStatus() { + var localPlayerId = client.LocalPlayer?.ObjectId; + var playerAuras = 0; + // The actual width and height of the tokens don't matter, they're always 25 apart. + // e.g. the aspected benefit icon is 24px wide, but the second slot is still at [25, 41] + const int slotWidth = 25; + const int slotHeight = 41; + if (targets.Target is BattleChara target) { + playerAuras = target.StatusList.Count(s => s.SourceId == localPlayerId); + var x = (FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*) target.Address; + x->StatusManager.GetStatusIndex(0x0, target.ObjectId); } - private unsafe struct UiElement { - public readonly AtkUnitBase* Element; - public readonly string Name; - public readonly int StatusListIndex; - public readonly ChildEnumMode EnumMode; - public readonly ChildEnumOrder EnumOrder; - - public UiElement(string name, int statusListIndex) { - Name = name; - Element = Common.GetUnitBase(name); - StatusListIndex = statusListIndex; - EnumMode = Name == "_TargetInfoBuffDebuff" ? ChildEnumMode.ChildPrev : ChildEnumMode.NextNext; - EnumOrder = Name == "_TargetInfoBuffDebuff" ? ChildEnumOrder.MaxBackward : ChildEnumOrder.ZeroForward; - } - - public bool Valid() => Element is not null - && Element->UldManager.NodeList is not null - && Element->UldManager.NodeList[StatusListIndex] is not null; - - public AtkResNode* StatusList => Element->UldManager.NodeList[StatusListIndex]; - - public AtkResNode*[] Children { - get { - if (!Valid()) - return new AtkResNode*[0]; - var children = new AtkResNode*[StatusList->ChildCount]; - - // Separate debuff does it a bit differently :\ - var child = EnumMode switch { - ChildEnumMode.NextNext => StatusList->NextSiblingNode, - ChildEnumMode.ChildNext => StatusList->ChildNode, - ChildEnumMode.PrevPrev => StatusList->PrevSiblingNode, - ChildEnumMode.ChildPrev => StatusList->ChildNode, - ChildEnumMode.ChildPrevPrev => StatusList->ChildNode->PrevSiblingNode, - _ => throw new ArgumentOutOfRangeException(nameof(ChildEnumMode), $"Unexpected enum value: {EnumMode}") - }; - - // No children? No problem - if (child is null || (int) child == 0) - return new AtkResNode*[0]; - // Reverse for MaxBackward - var i = EnumOrder == ChildEnumOrder.MaxBackward ? children.Length - 1 : 0; - - // soundness (index out of range) - // will error if the game lies to us about ChildCount - while (child is not null) { - var newIndex = EnumOrder == ChildEnumOrder.MaxBackward ? i-- : i++; - children[newIndex] = child; - - child = EnumMode switch { - ChildEnumMode.NextNext => child->NextSiblingNode, - ChildEnumMode.ChildNext => child->NextSiblingNode, - ChildEnumMode.PrevPrev => child->PrevSiblingNode, - ChildEnumMode.ChildPrev => child->PrevSiblingNode, - ChildEnumMode.ChildPrevPrev => child->PrevSiblingNode, - _ => throw new ArgumentOutOfRangeException(nameof(ChildEnumMode), $"Unexpected enum value: {EnumMode}") - }; - } - - // Note: The re-sorting we do here lets us avoid annoyances when iterating later - // because we no longer have to care what nuisances affect accessing the target - return children; - } - } + var focusAuras = 0; + if (targets.FocusTarget is BattleChara focusTarget) { + focusAuras = focusTarget.StatusList.Count(s => s.SourceId == localPlayerId); } - private readonly Dictionary targetElements = new(){ - {1, "_TargetInfoBuffDebuff"}, - {2, "_TargetInfo"}, - {3, "_FocusTargetInfo"} - }; - - private unsafe void UpdateTargetStatus() { - - var localPlayerId = ClientState.LocalPlayer?.ObjectId; - var playerAuras = 0; - if (TargetManager.Target is BattleChara target) { - playerAuras = target.StatusList.Count(s => s.SourceId == localPlayerId); - } + //PluginLog.Log($"StatusEffects.Length {target.StatusEffects.Length}"); // Always 30 + //PluginLog.Log($"Player Auras old:{this.curDebuffs} new: {playerAuras}"); + // Hasn't changed since last tick + if (targetDebuffs == playerAuras && fTargetDebuffs == focusAuras) { + return; + } - var focusAuras = 0; - if (TargetManager.FocusTarget is BattleChara focusTarget) { - focusAuras = focusTarget.StatusList.Count(s => s.SourceId == localPlayerId); - } - - //PluginLog.Log($"StatusEffects.Length {target.StatusEffects.Length}"); // Always 30 - //PluginLog.Log($"Player Auras old:{this.curDebuffs} new: {playerAuras}"); - // Hasn't changed since last tick - if (targetDebuffs == playerAuras && fTargetDebuffs == focusAuras) { - return; + // Update our counters + targetDebuffs = playerAuras; + fTargetDebuffs = focusAuras; + + + //PluginLog.Log($"Updating..."); + foreach (var element in uiElements) { + element.Refresh(); + var targetAuras = playerAuras; + var playerScale = pluginConfig.bScale; + switch (element.Name) { + case "_TargetInfoBuffDebuff" when pluginConfig.IncludeMainTarget: + break; + case "_TargetInfo" when pluginConfig.IncludeMainTarget: + break; + case "_FocusTargetInfo" when pluginConfig.IncludeFocusTarget: + targetAuras = focusAuras; + playerScale = pluginConfig.FocusScale; + break; + default: + continue; } - //PluginLog.Log($"Updating..."); - foreach (var element in targetElements) { - var playerScale = PluginConfig.bScale; - var targetAuras = playerAuras; - switch (element.Value) { - case "_TargetInfoBuffDebuff" when PluginConfig.includeMainTarget: - break; - case "_TargetInfo" when PluginConfig.includeMainTarget: - break; - case "_FocusTargetInfo" when PluginConfig.includeFocusTarget: - playerScale = PluginConfig.fScale; - targetAuras = focusAuras; - break; - default: - continue; - } - var uiElement = new UiElement(element.Value, element.Key); - var children = uiElement.Children; - // Poor man's IEnumerable, but that's life with unsafe - for (var childIndex = 0; childIndex < children.Length; childIndex++) { - var child = children[childIndex]; - var scalar = childIndex < targetAuras ? playerScale : 1.0f; - child->ScaleX = scalar; - child->ScaleY = scalar; - child->X = childIndex % 15 * child->Width; - child->Y = childIndex < 15 ? 0 : child->Height; - if (childIndex < targetAuras) { - child->ScaleX = playerScale; - child->ScaleY = playerScale; + var children = element.Children; + var xOffsets = new[] {0f, 0f}; + // Poor man's IEnumerable, but that's life with unsafe + for (var childIndex = 0; childIndex < children.Length; childIndex++) { + var child = children[childIndex]; + var textComponent = (AtkTextNode*) child->GetComponent()->GetTextNodeById(2); + var row = childIndex < 15 ? 0 : 1; + ref var xOffset = ref xOffsets[row]; + // TODO: find a check that isn't a lazy hack + var isOwnStatus = textComponent->TextColor is not {R: 255, G: 255, B: 255, A: 255}; + var scalar = isOwnStatus ? playerScale : 1.0f; + child->ScaleX = scalar; + child->ScaleY = scalar; + // Add in our running shift value + child->X = childIndex % 15 * slotWidth + xOffset; + child->Y = row == 0 ? 0 : slotHeight; + + // We actually have work to do? + if (targetAuras > 0) { + // If we're on the second row, factor in if the first row has shifted + if (row > 0 && xOffset > 0f) { + child->Y *= playerScale; } - switch (childIndex) { - // For simplicity's sake, we're going to assume no player has >14 debuffs out at once - case < 15 when targetAuras > 0: - // Add the difference between an unscaled and a scaled icon - child->X += (child->Width * playerScale - child->Width) * MathF.Min(childIndex, targetAuras); - // We bump the Y offset a bit for our changed icons - if (childIndex < targetAuras) { - child->Y = (child->Height * playerScale - child->Height) / -(child->Height / 2 ); - } - else { - child->Y = 0; - } - break; - case > 14 when targetAuras > 0: - child->Y = child->Height * playerScale; - break; - default: - child->Y = childIndex > 14 ? child->Height * playerScale : 0; - break; + // We bump the Y offset a bit for our changed icons + if (isOwnStatus) { + // Add the difference between an unscaled and a scaled icon to our running total + xOffset += slotWidth * playerScale - slotWidth; + // Y pos gets shifted slightly to match the top part of the unscaled icons + child->Y = (slotHeight * playerScale - slotHeight) / -(slotHeight / 2); } - // Set update flag - child->Flags_2 |= 0x1; - // Onto the next one - child = child->NextSiblingNode; } - uiElement.StatusList->Flags_2 |= 0x4; - uiElement.StatusList->Flags_2 |= 0x1; + + // Set update flag + child->Flags_2 |= 0x1; + // We could step this out but then we have to add null checks and stuff + element.StatusList->Flags_2 |= 0x4; + element.StatusList->Flags_2 |= 0x1; } } + } - public unsafe void ResetTargetStatus() - { - foreach (var element in targetElements) { - var uiElement = new UiElement(element.Value, element.Key); - var children = uiElement.Children; - // Poor man's IEnumerable, but that's life with unsafe - for(var childIndex = 0; childIndex < children.Length; childIndex++) { - var child = children[childIndex]; - child->ScaleX = 1.0f; - child->ScaleY = 1.0f; - child->X = childIndex % 15 * child->Width; - child->Y = childIndex < 15 ? 0 : child->Height; - // Set update flag - child->Flags_2 |= 0x1; - // Onto the next one - child = child->NextSiblingNode; - } - - uiElement.StatusList->Flags_2 |= 0x4; - uiElement.StatusList->Flags_2 |= 0x1; + public unsafe void ResetTargetStatus() { + foreach (var element in uiElements) { + element.Refresh(); + var children = element.Children; + // Poor man's IEnumerable, but that's life with unsafe + for (var childIndex = 0; childIndex < children.Length; childIndex++) { + var child = children[childIndex]; + child->ScaleX = 1.0f; + child->ScaleY = 1.0f; + child->X = childIndex % 15 * child->Width; + child->Y = childIndex < 15 ? 0 : child->Height; + // Set update flag + child->Flags_2 |= 0x1; } - } + element.StatusList->Flags_2 |= 0x4; + element.StatusList->Flags_2 |= 0x1; + } + } - public void SetupCommands() { - CommandManager.AddHandler("/bigplayerdebuffs", new Dalamud.Game.Command.CommandInfo(OnConfigCommandHandler) { - HelpMessage = $"Open config window for {this.Name}", - ShowInHelp = true - }); - } + private void SetupCommands() { + commands.AddHandler("/bigplayerdebuffs", new CommandInfo(OnConfigCommandHandler) { + HelpMessage = $"Open config window for {Name}", + ShowInHelp = true + }); + } - private void OnOpenConfig() - { - drawConfigWindow = true; - } + private void OnOpenConfig() { + drawConfigWindow = true; + } - public void OnConfigCommandHandler(string command, string args) { - drawConfigWindow = !drawConfigWindow; - } + private void OnConfigCommandHandler(string command, string args) { + drawConfigWindow = !drawConfigWindow; + } - public void RemoveCommands() { - CommandManager.RemoveHandler("/bigplayerdebuffs"); - } + private void RemoveCommands() { + commands.RemoveHandler("/bigplayerdebuffs"); + } - private void BuildUI() { - drawConfigWindow = drawConfigWindow && PluginConfig.DrawConfigUI(); - } + private void BuildUi() { + drawConfigWindow = drawConfigWindow && pluginConfig.DrawConfigUi(); } -} +} \ No newline at end of file diff --git a/BigPlayerDebuffs.csproj b/BigPlayerDebuffs.csproj index 9531df5..10142dc 100644 --- a/BigPlayerDebuffs.csproj +++ b/BigPlayerDebuffs.csproj @@ -1,63 +1,63 @@ - - - - 1.1.0.8 - Scales up buffs/debuffs applied by you on Target Status frames - - https://github.com/rgd87/BigPlayerDebuffs - + + + + 1.1.0.8 + Scales up buffs/debuffs applied by you on Target Status frames + + https://github.com/rgd87/BigPlayerDebuffs + - - net7.0-windows - x64 - enable - latest - true - false - false - true - + + net7.0-windows + x64 + enable + latest + true + false + false + true + - - $(appdata)\XIVLauncher\addon\Hooks\dev\ - + + $(appdata)\XIVLauncher\addon\Hooks\dev\ + - - x64 - + + x64 + - - - - $(DalamudLibPath)FFXIVClientStructs.dll - false - - - $(DalamudLibPath)Newtonsoft.Json.dll - false - - - $(DalamudLibPath)Dalamud.dll - false - - - $(DalamudLibPath)ImGui.NET.dll - false - - - $(DalamudLibPath)ImGuiScene.dll - false - - - $(DalamudLibPath)Lumina.dll - false - - - $(DalamudLibPath)Lumina.Excel.dll - false - - + + + + $(DalamudLibPath)FFXIVClientStructs.dll + false + + + $(DalamudLibPath)Newtonsoft.Json.dll + false + + + $(DalamudLibPath)Dalamud.dll + false + + + $(DalamudLibPath)ImGui.NET.dll + false + + + $(DalamudLibPath)ImGuiScene.dll + false + + + $(DalamudLibPath)Lumina.dll + false + + + $(DalamudLibPath)Lumina.Excel.dll + false + + diff --git a/BigPlayerDebuffsConfig.cs b/BigPlayerDebuffsConfig.cs index b979f31..b30f3f5 100644 --- a/BigPlayerDebuffsConfig.cs +++ b/BigPlayerDebuffsConfig.cs @@ -4,83 +4,77 @@ using System; using System.Numerics; -namespace BigPlayerDebuffs -{ - public class RouletteConfig { - public bool Enabled; - public bool Tank; - public bool Healer; - public bool DPS; - } +namespace BigPlayerDebuffs; + +public class BigPlayerDebuffsConfig : IPluginConfiguration { + [NonSerialized] private DalamudPluginInterface pluginInterface = null!; + + [NonSerialized] private BigPlayerDebuffs plugin = null!; - public class BigPlayerDebuffsConfig : IPluginConfiguration { - [NonSerialized] - private DalamudPluginInterface pluginInterface; + //[NonSerialized] private bool showWebhookWindow; - [NonSerialized] - private BigPlayerDebuffs plugin; + public int Version { get; set; } - //[NonSerialized] private bool showWebhookWindow; + // What does the 'b' mean? Should be named something like TargetScale but config compatibility /shrug + // ReSharper disable once InconsistentNaming + public float bScale = 1.4f; + public float FocusScale = 1.25f; + public bool IncludeMainTarget = true; + public bool IncludeFocusTarget = true; - public int Version { get; set; } + public void Init(BigPlayerDebuffs ownPlugin, DalamudPluginInterface dalamudPluginInterface) { + plugin = ownPlugin; + pluginInterface = dalamudPluginInterface; + } + + private void Save() { + pluginInterface.SavePluginConfig(this); + plugin.InvalidateState(); + } - // What does the 'b' mean? - public float bScale = 1.4f; - public float fScale = 1.25f; - public bool includeMainTarget = true; - public bool includeFocusTarget = true; + public bool DrawConfigUi() { + var drawConfig = true; - public void Init(BigPlayerDebuffs plugin, DalamudPluginInterface pluginInterface) { - this.plugin = plugin; - this.pluginInterface = pluginInterface; + var scale = ImGui.GetIO().FontGlobalScale; + + var modified = false; + + ImGui.SetNextWindowSize(new Vector2(550, 120) * scale, ImGuiCond.FirstUseEver); + ImGui.SetNextWindowSizeConstraints(new Vector2(550, 110), new Vector2(1100, 650) * scale); + ImGui.Begin($"{plugin.Name} Configuration", ref drawConfig, ImGuiWindowFlags.NoCollapse); + // Target UI + ImGui.BeginGroup(); + modified |= ImGui.Checkbox("##Enable scaling in Target UI", ref IncludeMainTarget); + if (ImGui.IsItemHovered()) { + ImGui.SetTooltip("Enable scaling in target UI"); } - public void Save() { - pluginInterface.SavePluginConfig(this); - plugin.InvalidateState(); + ImGui.SameLine(); + ImGui.BeginDisabled(!IncludeMainTarget); + modified |= ImGui.SliderFloat("Scale in Target UI", ref bScale, 1.0F, 2.0F, "%.2f"); + ImGui.EndDisabled(); + ImGui.EndGroup(); + // Focus target + ImGui.BeginGroup(); + modified |= ImGui.Checkbox("##Enable scaling in Focus Target UI", ref IncludeFocusTarget); + if (ImGui.IsItemHovered()) { + ImGui.SetTooltip("Enable scaling in focus target UI"); } - public bool DrawConfigUI() { - var drawConfig = true; - - var scale = ImGui.GetIO().FontGlobalScale; - - var modified = false; - - ImGui.SetNextWindowSize(new Vector2(550, 120) * scale, ImGuiCond.FirstUseEver); - ImGui.SetNextWindowSizeConstraints(new Vector2(550, 110), new Vector2(1100, 650) * scale); - ImGui.Begin($"{plugin.Name} Configuration", ref drawConfig, ImGuiWindowFlags.NoCollapse); - // Target UI - ImGui.BeginGroup(); - modified |= ImGui.Checkbox("##Enable scaling in Target UI",ref includeMainTarget); - if(ImGui.IsItemHovered()) - ImGui.SetTooltip("Enable scaling in target UI"); - ImGui.SameLine(); - ImGui.BeginDisabled(!includeMainTarget); - modified |= ImGui.SliderFloat("Scale in Target UI", ref bScale, 1.0F, 2.0F, "%.2f"); - ImGui.EndDisabled(); - ImGui.EndGroup(); - // Focus target - ImGui.BeginGroup(); - modified |= ImGui.Checkbox("##Enable scaling in Focus Target UI",ref includeFocusTarget); - if(ImGui.IsItemHovered()) - ImGui.SetTooltip("Enable scaling in focus target UI"); - ImGui.SameLine(); - ImGui.BeginDisabled(!includeFocusTarget); - modified |= ImGui.SliderFloat("Scale in Focus Target UI", ref fScale, 1.0F, 2.0F, "%.2f"); - ImGui.EndDisabled(); - ImGui.EndGroup(); - ImGui.Text("Hint: Ctrl+Click a slider to input a number directly"); - ImGui.End(); - - - if (modified) - { - plugin.ResetTargetStatus(); - Save(); - } - - return drawConfig; + ImGui.SameLine(); + ImGui.BeginDisabled(!IncludeFocusTarget); + modified |= ImGui.SliderFloat("Scale in Focus Target UI", ref FocusScale, 1.0F, 2.0F, "%.2f"); + ImGui.EndDisabled(); + ImGui.EndGroup(); + ImGui.Text("Hint: Ctrl+Click a slider to input a number directly"); + ImGui.End(); + + + if (modified) { + plugin.ResetTargetStatus(); + Save(); } + + return drawConfig; } } \ No newline at end of file From a752d0da2dc2655cdff0847228a640340db10fda Mon Sep 17 00:00:00 2001 From: oorzkws <65210810+oorzkws@users.noreply.github.com> Date: Sat, 8 Apr 2023 14:10:25 -0600 Subject: [PATCH 5/6] Simplify logic Technically uses more CPU cycles but the target logic seems to update on a very slightly differently timed loop than the UI, causing weird issues --- BigPlayerDebuffs.cs | 73 ++++++++------------------------------- BigPlayerDebuffsConfig.cs | 1 - 2 files changed, 14 insertions(+), 60 deletions(-) diff --git a/BigPlayerDebuffs.cs b/BigPlayerDebuffs.cs index 4f666a7..e4bc457 100644 --- a/BigPlayerDebuffs.cs +++ b/BigPlayerDebuffs.cs @@ -1,13 +1,9 @@ using System; -using System.Linq; using Dalamud.Plugin; using FFXIVClientStructs.FFXIV.Component.GUI; using Dalamud.Game; using Dalamud.Game.Gui; -using Dalamud.Game.ClientState; using Dalamud.Game.Command; -using Dalamud.Game.ClientState.Objects; -using Dalamud.Game.ClientState.Objects.Types; namespace BigPlayerDebuffs; @@ -108,8 +104,6 @@ public class BigPlayerDebuffs : IDalamudPlugin { public string Name => "BigPlayerDebuffs"; private readonly DalamudPluginInterface pluginInterface; - private readonly ClientState client; - private readonly TargetManager targets; private readonly Framework framework; private readonly CommandManager commands; @@ -119,22 +113,15 @@ public class BigPlayerDebuffs : IDalamudPlugin { private bool drawConfigWindow; - private int targetDebuffs = -1; - private int fTargetDebuffs = -1; - public BigPlayerDebuffs( DalamudPluginInterface dalamudPluginInterface, - ClientState clientState, CommandManager commandManager, Framework dalamudFramework, - GameGui gameGui, - TargetManager targetManager + GameGui gameGui ) { pluginInterface = dalamudPluginInterface; - client = clientState; commands = commandManager; framework = dalamudFramework; - targets = targetManager; pluginConfig = pluginInterface.GetPluginConfig() as BigPlayerDebuffsConfig ?? new BigPlayerDebuffsConfig(); pluginConfig.Init(this, pluginInterface); @@ -153,11 +140,6 @@ TargetManager targetManager SetupCommands(); } - public void InvalidateState() { - targetDebuffs = -1; - UpdateTargetStatus(); - } - public void Dispose() { // Remove hooks pluginInterface.UiBuilder.Draw -= BuildUi; @@ -183,39 +165,15 @@ private void FrameworkOnUpdate(Framework _) { private unsafe void UpdateTargetStatus() { - var localPlayerId = client.LocalPlayer?.ObjectId; - var playerAuras = 0; // The actual width and height of the tokens don't matter, they're always 25 apart. // e.g. the aspected benefit icon is 24px wide, but the second slot is still at [25, 41] const int slotWidth = 25; const int slotHeight = 41; - if (targets.Target is BattleChara target) { - playerAuras = target.StatusList.Count(s => s.SourceId == localPlayerId); - var x = (FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*) target.Address; - x->StatusManager.GetStatusIndex(0x0, target.ObjectId); - } - - var focusAuras = 0; - if (targets.FocusTarget is BattleChara focusTarget) { - focusAuras = focusTarget.StatusList.Count(s => s.SourceId == localPlayerId); - } - - //PluginLog.Log($"StatusEffects.Length {target.StatusEffects.Length}"); // Always 30 - //PluginLog.Log($"Player Auras old:{this.curDebuffs} new: {playerAuras}"); - // Hasn't changed since last tick - if (targetDebuffs == playerAuras && fTargetDebuffs == focusAuras) { - return; - } - - // Update our counters - targetDebuffs = playerAuras; - fTargetDebuffs = focusAuras; //PluginLog.Log($"Updating..."); foreach (var element in uiElements) { element.Refresh(); - var targetAuras = playerAuras; var playerScale = pluginConfig.bScale; switch (element.Name) { case "_TargetInfoBuffDebuff" when pluginConfig.IncludeMainTarget: @@ -223,7 +181,6 @@ private unsafe void UpdateTargetStatus() { case "_TargetInfo" when pluginConfig.IncludeMainTarget: break; case "_FocusTargetInfo" when pluginConfig.IncludeFocusTarget: - targetAuras = focusAuras; playerScale = pluginConfig.FocusScale; break; default: @@ -243,23 +200,21 @@ private unsafe void UpdateTargetStatus() { var scalar = isOwnStatus ? playerScale : 1.0f; child->ScaleX = scalar; child->ScaleY = scalar; - // Add in our running shift value - child->X = childIndex % 15 * slotWidth + xOffset; + child->X = childIndex % 15 * slotWidth; child->Y = row == 0 ? 0 : slotHeight; - // We actually have work to do? - if (targetAuras > 0) { - // If we're on the second row, factor in if the first row has shifted - if (row > 0 && xOffset > 0f) { - child->Y *= playerScale; - } - // We bump the Y offset a bit for our changed icons - if (isOwnStatus) { - // Add the difference between an unscaled and a scaled icon to our running total - xOffset += slotWidth * playerScale - slotWidth; - // Y pos gets shifted slightly to match the top part of the unscaled icons - child->Y = (slotHeight * playerScale - slotHeight) / -(slotHeight / 2); - } + // Add in our running shift value + child->X += xOffset; + // If we're on the second row, factor in if the first row has shifted + if (row > 0 && xOffset > 0f) { + child->Y *= playerScale; + } + // We bump the Y offset a bit for our changed icons + if (isOwnStatus) { + // Add the difference between an unscaled and a scaled icon to our running total + xOffset += slotWidth * playerScale - slotWidth; + // Y pos gets shifted slightly to match the top part of the unscaled icons + child->Y = (slotHeight * playerScale - slotHeight) / -(slotHeight / 2); } // Set update flag diff --git a/BigPlayerDebuffsConfig.cs b/BigPlayerDebuffsConfig.cs index b30f3f5..6eae4eb 100644 --- a/BigPlayerDebuffsConfig.cs +++ b/BigPlayerDebuffsConfig.cs @@ -29,7 +29,6 @@ public void Init(BigPlayerDebuffs ownPlugin, DalamudPluginInterface dalamudPlugi private void Save() { pluginInterface.SavePluginConfig(this); - plugin.InvalidateState(); } public bool DrawConfigUi() { From 7228042e1a2a1612fb177c38f6534e42dcc177a6 Mon Sep 17 00:00:00 2001 From: oorzkws <65210810+oorzkws@users.noreply.github.com> Date: Fri, 14 Apr 2023 01:34:50 -0600 Subject: [PATCH 6/6] Add support for own buff/debuff bar Some annoyances because the ResNode doesn't have a ChildCount --- BigPlayerDebuffs.cs | 61 ++++++++++++++++++++++++++++++++------- BigPlayerDebuffsConfig.cs | 31 +++++++++++++++++--- 2 files changed, 77 insertions(+), 15 deletions(-) diff --git a/BigPlayerDebuffs.cs b/BigPlayerDebuffs.cs index e4bc457..3761ba3 100644 --- a/BigPlayerDebuffs.cs +++ b/BigPlayerDebuffs.cs @@ -4,6 +4,9 @@ using Dalamud.Game; using Dalamud.Game.Gui; using Dalamud.Game.Command; +#if DEBUG +using Dalamud.Logging; +#endif namespace BigPlayerDebuffs; @@ -20,8 +23,15 @@ internal enum ChildEnumOrder { MaxBackward } +internal enum ElementType { + Status, + FocusStatus, + TargetStatus +} + internal unsafe class UiElement { public readonly string Name; + public readonly ElementType Type; private AtkUnitBase* element; private readonly int childListPos; @@ -33,8 +43,9 @@ public void Refresh() { element = (AtkUnitBase*) gui.GetAddonByName(Name); } - public UiElement(string name, int childListIndex, ChildEnumMode mode, ChildEnumOrder order, GameGui gameGui) { + public UiElement(string name, ElementType type, int childListIndex, ChildEnumMode mode, ChildEnumOrder order, GameGui gameGui) { Name = name; + Type = type; childListPos = childListIndex; enumMode = mode; enumOrder = order; @@ -44,7 +55,8 @@ public UiElement(string name, int childListIndex, ChildEnumMode mode, ChildEnumO private bool Valid() => element is not null && element->UldManager.NodeList is not null - && element->UldManager.NodeList[childListPos] is not null; + && element->UldManager.NodeList[childListPos] is not null + && element->IsVisible; public AtkResNode* StatusList => element->UldManager.NodeList[childListPos]; @@ -53,8 +65,20 @@ public AtkResNode*[] Children { if (!Valid()) { return new AtkResNode*[0]; } - + var children = new AtkResNode*[StatusList->ChildCount]; + // TODO: Find a better method for determining child count that applies to both situations + if (children.Length == 0) { + // If the nodelist doesn't have a child count, we assume the res node we were given as our starting point + // is the last node before we begin seeing IconText Component Nodes + var totalCount = element->UldManager.NodeListCount - childListPos - 1; + if (totalCount < 0) { + return new AtkResNode*[0]; + } + else { + children = new AtkResNode*[totalCount]; + } + } // Separate debuff does it a bit differently :\ var child = enumMode switch { @@ -79,7 +103,16 @@ public AtkResNode*[] Children { // will error if the game lies to us about ChildCount while (child is not null) { var newIndex = enumOrder == ChildEnumOrder.MaxBackward ? i-- : i++; + #if DEBUG + try { + children[newIndex] = child; + } + catch (IndexOutOfRangeException e) { + PluginLog.Warning($"Index {i} outside of array with length {children.Length-1} for {Name}"); + } + #else children[newIndex] = child; + #endif child = enumMode switch { ChildEnumMode.NextNext => child->NextSiblingNode, @@ -128,9 +161,14 @@ GameGui gameGui // We have to supply .gui since unsafe classes are static uiElements = new[] { - new UiElement("_TargetInfoBuffDebuff", 1, ChildEnumMode.ChildPrev, ChildEnumOrder.MaxBackward, gameGui), - new UiElement("_TargetInfo", 2, ChildEnumMode.NextNext, ChildEnumOrder.ZeroForward, gameGui), - new UiElement("_FocusTargetInfo", 3, ChildEnumMode.NextNext, ChildEnumOrder.ZeroForward, gameGui) + new UiElement("_TargetInfoBuffDebuff", ElementType.TargetStatus, 1, ChildEnumMode.ChildPrev, ChildEnumOrder.MaxBackward, gameGui), + new UiElement("_TargetInfo", ElementType.TargetStatus, 2, ChildEnumMode.NextNext, ChildEnumOrder.ZeroForward, gameGui), + new UiElement("_FocusTargetInfo", ElementType.FocusStatus, 3, ChildEnumMode.NextNext, ChildEnumOrder.ZeroForward, gameGui), + new UiElement("_Status", ElementType.Status, 0, ChildEnumMode.ChildPrev, ChildEnumOrder.MaxBackward, gameGui), + new UiElement("_StatusCustom0", ElementType.Status, 4, ChildEnumMode.PrevPrev, ChildEnumOrder.MaxBackward, gameGui), + new UiElement("_StatusCustom1", ElementType.Status, 4, ChildEnumMode.PrevPrev, ChildEnumOrder.MaxBackward, gameGui), + new UiElement("_StatusCustom2", ElementType.Status, 4, ChildEnumMode.PrevPrev, ChildEnumOrder.MaxBackward, gameGui), + new UiElement("_StatusCustom3", ElementType.Status, 3, ChildEnumMode.PrevPrev, ChildEnumOrder.MaxBackward, gameGui) }; // Wire up @@ -175,14 +213,15 @@ private unsafe void UpdateTargetStatus() { foreach (var element in uiElements) { element.Refresh(); var playerScale = pluginConfig.bScale; - switch (element.Name) { - case "_TargetInfoBuffDebuff" when pluginConfig.IncludeMainTarget: - break; - case "_TargetInfo" when pluginConfig.IncludeMainTarget: + switch (element.Type) { + case ElementType.TargetStatus when pluginConfig.IncludeMainTarget: break; - case "_FocusTargetInfo" when pluginConfig.IncludeFocusTarget: + case ElementType.FocusStatus when pluginConfig.IncludeFocusTarget: playerScale = pluginConfig.FocusScale; break; + case ElementType.Status when pluginConfig.IncludeBuffBar: + playerScale = pluginConfig.BarScale; + break; default: continue; } diff --git a/BigPlayerDebuffsConfig.cs b/BigPlayerDebuffsConfig.cs index 6eae4eb..d8a4477 100644 --- a/BigPlayerDebuffsConfig.cs +++ b/BigPlayerDebuffsConfig.cs @@ -19,8 +19,10 @@ public class BigPlayerDebuffsConfig : IPluginConfiguration { // ReSharper disable once InconsistentNaming public float bScale = 1.4f; public float FocusScale = 1.25f; + public float BarScale = 1.25f; public bool IncludeMainTarget = true; public bool IncludeFocusTarget = true; + public bool IncludeBuffBar = false; public void Init(BigPlayerDebuffs ownPlugin, DalamudPluginInterface dalamudPluginInterface) { plugin = ownPlugin; @@ -41,30 +43,51 @@ public bool DrawConfigUi() { ImGui.SetNextWindowSize(new Vector2(550, 120) * scale, ImGuiCond.FirstUseEver); ImGui.SetNextWindowSizeConstraints(new Vector2(550, 110), new Vector2(1100, 650) * scale); ImGui.Begin($"{plugin.Name} Configuration", ref drawConfig, ImGuiWindowFlags.NoCollapse); - // Target UI + + #region Target + ImGui.BeginGroup(); modified |= ImGui.Checkbox("##Enable scaling in Target UI", ref IncludeMainTarget); if (ImGui.IsItemHovered()) { ImGui.SetTooltip("Enable scaling in target UI"); } - ImGui.SameLine(); ImGui.BeginDisabled(!IncludeMainTarget); modified |= ImGui.SliderFloat("Scale in Target UI", ref bScale, 1.0F, 2.0F, "%.2f"); ImGui.EndDisabled(); ImGui.EndGroup(); - // Focus target + + #endregion + #region FocusTarget + ImGui.BeginGroup(); modified |= ImGui.Checkbox("##Enable scaling in Focus Target UI", ref IncludeFocusTarget); if (ImGui.IsItemHovered()) { ImGui.SetTooltip("Enable scaling in focus target UI"); } - ImGui.SameLine(); ImGui.BeginDisabled(!IncludeFocusTarget); modified |= ImGui.SliderFloat("Scale in Focus Target UI", ref FocusScale, 1.0F, 2.0F, "%.2f"); ImGui.EndDisabled(); ImGui.EndGroup(); + + #endregion + #region BuffBar + + ImGui.BeginGroup(); + modified |= ImGui.Checkbox("##Enable scaling in own buff/debuff display", ref IncludeBuffBar); + if (ImGui.IsItemHovered()) { + ImGui.SetTooltip("Enable scaling in own buff/debuff display"); + } + ImGui.SameLine(); + ImGui.BeginDisabled(!IncludeBuffBar); + modified |= ImGui.SliderFloat("Scale in self buff bar", ref BarScale, 1.0F, 2.0F, "%.2f"); + ImGui.EndDisabled(); + ImGui.EndGroup(); + + #endregion + + // Hint and end ImGui.Text("Hint: Ctrl+Click a slider to input a number directly"); ImGui.End();