merge master

This commit is contained in:
goat 2023-09-23 11:22:50 +02:00
commit af52da06b0
No known key found for this signature in database
GPG key ID: 49E2AA8C6A76498B
30 changed files with 827 additions and 301 deletions

View file

@ -3,20 +3,16 @@ using System.Collections.Generic;
using System.Linq;
using Dalamud.Configuration.Internal;
using Dalamud.Game.AddonEventManager;
using Dalamud.Game.Addon;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Hooking;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Memory;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Graphics;
using FFXIVClientStructs.FFXIV.Client.System.Memory;
using FFXIVClientStructs.FFXIV.Component.GUI;
using DalamudAddonEventManager = Dalamud.Game.AddonEventManager.AddonEventManager;
namespace Dalamud.Game.Gui.Dtr;
/// <summary>
@ -27,9 +23,6 @@ namespace Dalamud.Game.Gui.Dtr;
internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
{
private const uint BaseNodeId = 1000;
private const uint MouseOverEventIdOffset = 10000;
private const uint MouseOutEventIdOffset = 20000;
private const uint MouseClickEventIdOffset = 30000;
private static readonly ModuleLog Log = new("DtrBar");
@ -43,23 +36,29 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
[ServiceManager.ServiceDependency]
private readonly DalamudAddonEventManager uiEventManager = Service<DalamudAddonEventManager>.Get();
private readonly AddonEventManager uiEventManager = Service<AddonEventManager>.Get();
private readonly DtrBarAddressResolver address;
[ServiceManager.ServiceDependency]
private readonly AddonLifecycle addonLifecycle = Service<AddonLifecycle>.Get();
private readonly AddonLifecycleEventListener dtrPostDrawListener;
private readonly AddonLifecycleEventListener dtrPostRequestedUpdateListener;
private readonly ConcurrentBag<DtrBarEntry> newEntries = new();
private readonly List<DtrBarEntry> entries = new();
private readonly Hook<AddonDrawDelegate> onAddonDrawHook;
private readonly Hook<AddonRequestedUpdateDelegate> onAddonRequestedUpdateHook;
private readonly Dictionary<uint, List<IAddonEventHandle>> eventHandles = new();
private uint runningNodeIds = BaseNodeId;
[ServiceManager.ServiceConstructor]
private DtrBar(SigScanner sigScanner)
private DtrBar()
{
this.address = new DtrBarAddressResolver();
this.address.Setup(sigScanner);
this.dtrPostDrawListener = new AddonLifecycleEventListener(AddonEvent.PostDraw, "_DTR", this.OnDtrPostDraw);
this.dtrPostRequestedUpdateListener = new AddonLifecycleEventListener(AddonEvent.PostRequestedUpdate, "_DTR", this.OnAddonRequestedUpdateDetour);
this.onAddonDrawHook = Hook<AddonDrawDelegate>.FromAddress(this.address.AtkUnitBaseDraw, this.OnAddonDrawDetour);
this.onAddonRequestedUpdateHook = Hook<AddonRequestedUpdateDelegate>.FromAddress(this.address.AddonRequestedUpdate, this.OnAddonRequestedUpdateDetour);
this.addonLifecycle.RegisterListener(this.dtrPostDrawListener);
this.addonLifecycle.RegisterListener(this.dtrPostRequestedUpdateListener);
this.framework.Update += this.Update;
@ -68,10 +67,6 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
this.configuration.QueueSave();
}
private delegate void AddonDrawDelegate(AtkUnitBase* addon);
private delegate void AddonRequestedUpdateDelegate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData);
/// <inheritdoc/>
public DtrBarEntry Get(string title, SeString? text = null)
{
@ -102,8 +97,8 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
/// <inheritdoc/>
void IDisposable.Dispose()
{
this.onAddonDrawHook.Dispose();
this.onAddonRequestedUpdateHook.Dispose();
this.addonLifecycle.UnregisterListener(this.dtrPostDrawListener);
this.addonLifecycle.UnregisterListener(this.dtrPostRequestedUpdateListener);
foreach (var entry in this.entries)
this.RemoveNode(entry.TextNode);
@ -165,13 +160,6 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
return xPos.CompareTo(yPos);
});
}
[ServiceManager.CallWhenServicesReady]
private void ContinueConstruction()
{
this.onAddonDrawHook.Enable();
this.onAddonRequestedUpdateHook.Enable();
}
private AtkUnitBase* GetDtr() => (AtkUnitBase*)this.gameGui.GetAddonByName("_DTR").ToPointer();
@ -181,7 +169,7 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
this.HandleAddedNodes();
var dtr = this.GetDtr();
if (dtr == null) return;
if (dtr == null || dtr->RootNode == null || dtr->RootNode->ChildNode == null) return;
// The collision node on the DTR element is always the width of its content
if (dtr->UldManager.NodeList == null) return;
@ -232,7 +220,7 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
if (this.configuration.DtrSwapDirection)
{
data.TextNode->AtkResNode.SetPositionFloat(runningXPos, 2);
data.TextNode->AtkResNode.SetPositionFloat(runningXPos + this.configuration.DtrSpacing, 2);
runningXPos += elementWidth;
}
else
@ -241,6 +229,11 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
data.TextNode->AtkResNode.SetPositionFloat(runningXPos, 2);
}
}
else
{
// If we want the node hidden, shift it up, to prevent collision conflicts
data.TextNode->AtkResNode.SetY(-collisionNode->Height * dtr->RootNode->ScaleX);
}
}
}
@ -258,37 +251,26 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
this.ApplySort();
}
}
// This hooks all AtkUnitBase.Draw calls, then checks for our specific addon name.
// AddonDtr doesn't implement it's own Draw method, would need to replace vtable entry to be more efficient.
private void OnAddonDrawDetour(AtkUnitBase* addon)
private void OnDtrPostDraw(AddonEvent eventType, AddonArgs addonInfo)
{
this.onAddonDrawHook!.Original(addon);
var addon = (AtkUnitBase*)addonInfo.Addon;
try
{
if (MemoryHelper.ReadString((nint)addon->Name, 0x20) is not "_DTR") return;
this.UpdateNodePositions(addon);
this.UpdateNodePositions(addon);
if (!this.configuration.DtrSwapDirection)
{
var targetSize = (ushort)this.CalculateTotalSize();
var sizeDelta = targetSize - addon->RootNode->Width;
if (addon->RootNode->Width != targetSize)
{
addon->RootNode->SetWidth(targetSize);
addon->SetX((short)(addon->GetX() - sizeDelta));
// force a RequestedUpdate immediately to force the game to right-justify it immediately.
addon->OnUpdate(AtkStage.GetSingleton()->GetNumberArrayData(), AtkStage.GetSingleton()->GetStringArrayData());
}
}
}
catch (Exception e)
if (!this.configuration.DtrSwapDirection)
{
Log.Error(e, "Exception in OnAddonDraw.");
var targetSize = (ushort)this.CalculateTotalSize();
var sizeDelta = MathF.Round((targetSize - addon->RootNode->Width) * addon->RootNode->ScaleX);
if (addon->RootNode->Width != targetSize)
{
addon->RootNode->SetWidth(targetSize);
addon->SetX((short)(addon->GetX() - sizeDelta));
// force a RequestedUpdate immediately to force the game to right-justify it immediately.
addon->OnUpdate(AtkStage.GetSingleton()->GetNumberArrayData(), AtkStage.GetSingleton()->GetStringArrayData());
}
}
}
@ -315,18 +297,11 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
}
}
private void OnAddonRequestedUpdateDetour(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
private void OnAddonRequestedUpdateDetour(AddonEvent eventType, AddonArgs addonInfo)
{
this.onAddonRequestedUpdateHook.Original(addon, numberArrayData, stringArrayData);
try
{
this.UpdateNodePositions(addon);
}
catch (Exception e)
{
Log.Error(e, "Exception in OnAddonRequestedUpdate.");
}
var addon = (AtkUnitBase*)addonInfo.Addon;
this.UpdateNodePositions(addon);
}
/// <summary>
@ -350,6 +325,11 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
private void RecreateNodes()
{
this.runningNodeIds = BaseNodeId;
if (this.entries.Any())
{
this.eventHandles.Clear();
}
foreach (var entry in this.entries)
{
entry.TextNode = this.MakeNode(++this.runningNodeIds);
@ -384,10 +364,14 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
var dtr = this.GetDtr();
if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false;
this.uiEventManager.AddEvent(node->AtkResNode.NodeID + MouseOverEventIdOffset, (nint)dtr, (nint)node, AddonEventType.MouseOver, this.DtrEventHandler);
this.uiEventManager.AddEvent(node->AtkResNode.NodeID + MouseOutEventIdOffset, (nint)dtr, (nint)node, AddonEventType.MouseOut, this.DtrEventHandler);
this.uiEventManager.AddEvent(node->AtkResNode.NodeID + MouseClickEventIdOffset, (nint)dtr, (nint)node, AddonEventType.MouseClick, this.DtrEventHandler);
this.eventHandles.TryAdd(node->AtkResNode.NodeID, new List<IAddonEventHandle>());
this.eventHandles[node->AtkResNode.NodeID].AddRange(new List<IAddonEventHandle>
{
this.uiEventManager.AddEvent(AddonEventManager.DalamudInternalKey, (nint)dtr, (nint)node, AddonEventType.MouseOver, this.DtrEventHandler),
this.uiEventManager.AddEvent(AddonEventManager.DalamudInternalKey, (nint)dtr, (nint)node, AddonEventType.MouseOut, this.DtrEventHandler),
this.uiEventManager.AddEvent(AddonEventManager.DalamudInternalKey, (nint)dtr, (nint)node, AddonEventType.MouseClick, this.DtrEventHandler),
});
var lastChild = dtr->RootNode->ChildNode;
while (lastChild->PrevSiblingNode != null) lastChild = lastChild->PrevSiblingNode;
Log.Debug($"Found last sibling: {(ulong)lastChild:X}");
@ -404,14 +388,13 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
return true;
}
private bool RemoveNode(AtkTextNode* node)
private void RemoveNode(AtkTextNode* node)
{
var dtr = this.GetDtr();
if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false;
if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return;
this.uiEventManager.RemoveEvent(node->AtkResNode.NodeID + MouseOverEventIdOffset, (nint)node, AddonEventType.MouseOver);
this.uiEventManager.RemoveEvent(node->AtkResNode.NodeID + MouseOutEventIdOffset, (nint)node, AddonEventType.MouseOut);
this.uiEventManager.RemoveEvent(node->AtkResNode.NodeID + MouseClickEventIdOffset, (nint)node, AddonEventType.MouseClick);
this.eventHandles[node->AtkResNode.NodeID].ForEach(handle => this.uiEventManager.RemoveEvent(AddonEventManager.DalamudInternalKey, handle));
this.eventHandles[node->AtkResNode.NodeID].Clear();
var tmpPrevNode = node->AtkResNode.PrevSiblingNode;
var tmpNextNode = node->AtkResNode.NextSiblingNode;
@ -427,7 +410,6 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
dtr->UldManager.UpdateDrawNodeList();
dtr->UpdateCollisionNodeList(false);
Log.Debug("Updated node draw list");
return true;
}
private AtkTextNode* MakeNode(uint nodeId)

View file

@ -1,29 +0,0 @@
namespace Dalamud.Game.Gui.Dtr;
/// <summary>
/// DtrBar memory address resolver.
/// </summary>
internal class DtrBarAddressResolver : BaseAddressResolver
{
/// <summary>
/// Gets the address of the AtkUnitBaseDraw method.
/// This is the base handler for all addons.
/// We will use this here because _DTR does not have a overloaded handler, so we must use the base handler.
/// </summary>
public nint AtkUnitBaseDraw { get; private set; }
/// <summary>
/// Gets the address of the DTRRequestUpdate method.
/// </summary>
public nint AddonRequestedUpdate { get; private set; }
/// <summary>
/// Scan for and setup any configured address pointers.
/// </summary>
/// <param name="scanner">The signature scanner to facilitate setup.</param>
protected override void Setup64Bit(SigScanner scanner)
{
this.AtkUnitBaseDraw = scanner.ScanText("48 83 EC 28 F6 81 ?? ?? ?? ?? ?? 4C 8B C1");
this.AddonRequestedUpdate = scanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B BA ?? ?? ?? ?? 48 8B F1 49 8B 98 ?? ?? ?? ?? 33 D2");
}
}