mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Merge pull request #1369 from MidoriKami/DTRBarEnhancements
This commit is contained in:
commit
0f74c8e62c
10 changed files with 952 additions and 38 deletions
97
Dalamud/Game/AddonEventManager/AddonCursorType.cs
Normal file
97
Dalamud/Game/AddonEventManager/AddonCursorType.cs
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
namespace Dalamud.Game.AddonEventManager;
|
||||
|
||||
/// <summary>
|
||||
/// Reimplementation of CursorType.
|
||||
/// </summary>
|
||||
public enum AddonCursorType
|
||||
{
|
||||
/// <summary>
|
||||
/// Arrow.
|
||||
/// </summary>
|
||||
Arrow,
|
||||
|
||||
/// <summary>
|
||||
/// Boot.
|
||||
/// </summary>
|
||||
Boot,
|
||||
|
||||
/// <summary>
|
||||
/// Search.
|
||||
/// </summary>
|
||||
Search,
|
||||
|
||||
/// <summary>
|
||||
/// Chat Pointer.
|
||||
/// </summary>
|
||||
ChatPointer,
|
||||
|
||||
/// <summary>
|
||||
/// Interact.
|
||||
/// </summary>
|
||||
Interact,
|
||||
|
||||
/// <summary>
|
||||
/// Attack.
|
||||
/// </summary>
|
||||
Attack,
|
||||
|
||||
/// <summary>
|
||||
/// Hand.
|
||||
/// </summary>
|
||||
Hand,
|
||||
|
||||
/// <summary>
|
||||
/// Resizeable Left-Right.
|
||||
/// </summary>
|
||||
ResizeWE,
|
||||
|
||||
/// <summary>
|
||||
/// Resizeable Up-Down.
|
||||
/// </summary>
|
||||
ResizeNS,
|
||||
|
||||
/// <summary>
|
||||
/// Resizeable.
|
||||
/// </summary>
|
||||
ResizeNWSR,
|
||||
|
||||
/// <summary>
|
||||
/// Resizeable 4-way.
|
||||
/// </summary>
|
||||
ResizeNESW,
|
||||
|
||||
/// <summary>
|
||||
/// Clickable.
|
||||
/// </summary>
|
||||
Clickable,
|
||||
|
||||
/// <summary>
|
||||
/// Text Input.
|
||||
/// </summary>
|
||||
TextInput,
|
||||
|
||||
/// <summary>
|
||||
/// Text Click.
|
||||
/// </summary>
|
||||
TextClick,
|
||||
|
||||
/// <summary>
|
||||
/// Grab.
|
||||
/// </summary>
|
||||
Grab,
|
||||
|
||||
/// <summary>
|
||||
/// Chat Bubble.
|
||||
/// </summary>
|
||||
ChatBubble,
|
||||
|
||||
/// <summary>
|
||||
/// No Access.
|
||||
/// </summary>
|
||||
NoAccess,
|
||||
|
||||
/// <summary>
|
||||
/// Hidden.
|
||||
/// </summary>
|
||||
Hidden,
|
||||
}
|
||||
87
Dalamud/Game/AddonEventManager/AddonEventListener.cs
Normal file
87
Dalamud/Game/AddonEventManager/AddonEventListener.cs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.AddonEventManager;
|
||||
|
||||
/// <summary>
|
||||
/// Event listener class for managing custom events.
|
||||
/// </summary>
|
||||
// Custom event handler tech provided by Pohky, implemented by MidoriKami
|
||||
internal unsafe class AddonEventListener : IDisposable
|
||||
{
|
||||
private ReceiveEventDelegate? receiveEventDelegate;
|
||||
|
||||
private AtkEventListener* eventListener;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonEventListener"/> class.
|
||||
/// </summary>
|
||||
/// <param name="eventHandler">The managed handler to send events to.</param>
|
||||
public AddonEventListener(ReceiveEventDelegate eventHandler)
|
||||
{
|
||||
this.receiveEventDelegate = eventHandler;
|
||||
|
||||
this.eventListener = (AtkEventListener*)Marshal.AllocHGlobal(sizeof(AtkEventListener));
|
||||
this.eventListener->vtbl = (void*)Marshal.AllocHGlobal(sizeof(void*) * 3);
|
||||
this.eventListener->vfunc[0] = (delegate* unmanaged<void>)&NullSub;
|
||||
this.eventListener->vfunc[1] = (delegate* unmanaged<void>)&NullSub;
|
||||
this.eventListener->vfunc[2] = (void*)Marshal.GetFunctionPointerForDelegate(this.receiveEventDelegate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for receiving custom events.
|
||||
/// </summary>
|
||||
/// <param name="self">Pointer to the event listener.</param>
|
||||
/// <param name="eventType">Event type.</param>
|
||||
/// <param name="eventParam">Unique Id for this event.</param>
|
||||
/// <param name="eventData">Event Data.</param>
|
||||
/// <param name="unknown">Unknown Parameter.</param>
|
||||
public delegate void ReceiveEventDelegate(AtkEventListener* self, AtkEventType eventType, uint eventParam, AtkEvent* eventData, nint unknown);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (this.eventListener is null) return;
|
||||
|
||||
Marshal.FreeHGlobal((nint)this.eventListener->vtbl);
|
||||
Marshal.FreeHGlobal((nint)this.eventListener);
|
||||
|
||||
this.eventListener = null;
|
||||
this.receiveEventDelegate = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register an event to this event handler.
|
||||
/// </summary>
|
||||
/// <param name="addon">Addon that triggers this event.</param>
|
||||
/// <param name="node">Node to attach event to.</param>
|
||||
/// <param name="eventType">Event type to trigger this event.</param>
|
||||
/// <param name="param">Unique id for this event.</param>
|
||||
public void RegisterEvent(AtkUnitBase* addon, AtkResNode* node, AtkEventType eventType, uint param)
|
||||
{
|
||||
if (node is null) return;
|
||||
|
||||
node->AddEvent(eventType, param, this.eventListener, (AtkResNode*)addon, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregister an event from this event handler.
|
||||
/// </summary>
|
||||
/// <param name="node">Node to remove the event from.</param>
|
||||
/// <param name="eventType">Event type that this event is for.</param>
|
||||
/// <param name="param">Unique id for this event.</param>
|
||||
public void UnregisterEvent(AtkResNode* node, AtkEventType eventType, uint param)
|
||||
{
|
||||
if (node is null) return;
|
||||
|
||||
node->RemoveEvent(eventType, param, this.eventListener, false);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void NullSub()
|
||||
{
|
||||
/* do nothing */
|
||||
}
|
||||
}
|
||||
253
Dalamud/Game/AddonEventManager/AddonEventManager.cs
Normal file
253
Dalamud/Game/AddonEventManager/AddonEventManager.cs
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.AddonEventManager;
|
||||
|
||||
/// <summary>
|
||||
/// Service provider for addon event management.
|
||||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal unsafe class AddonEventManager : IDisposable, IServiceType, IAddonEventManager
|
||||
{
|
||||
private static readonly ModuleLog Log = new("AddonEventManager");
|
||||
|
||||
private readonly AddonEventManagerAddressResolver address;
|
||||
private readonly Hook<UpdateCursorDelegate> onUpdateCursor;
|
||||
|
||||
private readonly AddonEventListener eventListener;
|
||||
private readonly Dictionary<uint, IAddonEventManager.AddonEventHandler> eventHandlers;
|
||||
|
||||
private AddonCursorType? cursorOverride;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private AddonEventManager(SigScanner sigScanner)
|
||||
{
|
||||
this.address = new AddonEventManagerAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.eventHandlers = new Dictionary<uint, IAddonEventManager.AddonEventHandler>();
|
||||
this.eventListener = new AddonEventListener(this.DalamudAddonEventHandler);
|
||||
|
||||
this.cursorOverride = null;
|
||||
|
||||
this.onUpdateCursor = Hook<UpdateCursorDelegate>.FromAddress(this.address.UpdateCursor, this.UpdateCursorDetour);
|
||||
}
|
||||
|
||||
private delegate nint UpdateCursorDelegate(RaptureAtkModule* module);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void AddEvent(uint eventId, IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
|
||||
{
|
||||
if (!this.eventHandlers.ContainsKey(eventId))
|
||||
{
|
||||
var type = (AtkEventType)eventType;
|
||||
var node = (AtkResNode*)atkResNode;
|
||||
var addon = (AtkUnitBase*)atkUnitBase;
|
||||
|
||||
this.eventHandlers.Add(eventId, eventHandler);
|
||||
this.eventListener.RegisterEvent(addon, node, type, eventId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning($"Attempted to register already registered eventId: {eventId}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RemoveEvent(uint eventId, IntPtr atkResNode, AddonEventType eventType)
|
||||
{
|
||||
if (this.eventHandlers.ContainsKey(eventId))
|
||||
{
|
||||
var type = (AtkEventType)eventType;
|
||||
var node = (AtkResNode*)atkResNode;
|
||||
|
||||
this.eventListener.UnregisterEvent(node, type, eventId);
|
||||
this.eventHandlers.Remove(eventId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning($"Attempted to unregister already unregistered eventId: {eventId}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.onUpdateCursor.Dispose();
|
||||
this.eventListener.Dispose();
|
||||
this.eventHandlers.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetCursor(AddonCursorType cursor) => this.cursorOverride = cursor;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ResetCursor() => this.cursorOverride = null;
|
||||
|
||||
[ServiceManager.CallWhenServicesReady]
|
||||
private void ContinueConstruction()
|
||||
{
|
||||
this.onUpdateCursor.Enable();
|
||||
}
|
||||
|
||||
private nint UpdateCursorDetour(RaptureAtkModule* module)
|
||||
{
|
||||
try
|
||||
{
|
||||
var atkStage = AtkStage.GetSingleton();
|
||||
|
||||
if (this.cursorOverride is not null && atkStage is not null)
|
||||
{
|
||||
var cursor = (AddonCursorType)atkStage->AtkCursor.Type;
|
||||
if (cursor != this.cursorOverride)
|
||||
{
|
||||
AtkStage.GetSingleton()->AtkCursor.SetCursorType((AtkCursor.CursorType)this.cursorOverride, 1);
|
||||
}
|
||||
|
||||
return nint.Zero;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in UpdateCursorDetour.");
|
||||
}
|
||||
|
||||
return this.onUpdateCursor!.Original(module);
|
||||
}
|
||||
|
||||
private void DalamudAddonEventHandler(AtkEventListener* self, AtkEventType eventType, uint eventParam, AtkEvent* eventData, IntPtr unknown)
|
||||
{
|
||||
if (this.eventHandlers.TryGetValue(eventParam, out var handler) && eventData is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// We passed the AtkUnitBase into the EventData.Node field from our AddonEventHandler
|
||||
handler?.Invoke((AddonEventType)eventType, (nint)eventData->Node, (nint)eventData->Target);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, "Exception in DalamudAddonEventHandler custom event invoke.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin-scoped version of a AddonEventManager service.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IAddonEventManager>]
|
||||
#pragma warning restore SA1015
|
||||
internal unsafe class AddonEventManagerPluginScoped : IDisposable, IServiceType, IAddonEventManager
|
||||
{
|
||||
private static readonly ModuleLog Log = new("AddonEventManager");
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly AddonEventManager baseEventManager = Service<AddonEventManager>.Get();
|
||||
|
||||
private readonly AddonEventListener eventListener;
|
||||
private readonly Dictionary<uint, IAddonEventManager.AddonEventHandler> eventHandlers;
|
||||
|
||||
private bool isForcingCursor;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonEventManagerPluginScoped"/> class.
|
||||
/// </summary>
|
||||
public AddonEventManagerPluginScoped()
|
||||
{
|
||||
this.eventHandlers = new Dictionary<uint, IAddonEventManager.AddonEventHandler>();
|
||||
this.eventListener = new AddonEventListener(this.PluginAddonEventHandler);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
// if multiple plugins force cursors and dispose without un-forcing them then all forces will be cleared.
|
||||
if (this.isForcingCursor)
|
||||
{
|
||||
this.baseEventManager.ResetCursor();
|
||||
}
|
||||
|
||||
this.eventListener.Dispose();
|
||||
this.eventHandlers.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void AddEvent(uint eventId, IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
|
||||
{
|
||||
if (!this.eventHandlers.ContainsKey(eventId))
|
||||
{
|
||||
var type = (AtkEventType)eventType;
|
||||
var node = (AtkResNode*)atkResNode;
|
||||
var addon = (AtkUnitBase*)atkUnitBase;
|
||||
|
||||
this.eventHandlers.Add(eventId, eventHandler);
|
||||
this.eventListener.RegisterEvent(addon, node, type, eventId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning($"Attempted to register already registered eventId: {eventId}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RemoveEvent(uint eventId, IntPtr atkResNode, AddonEventType eventType)
|
||||
{
|
||||
if (this.eventHandlers.ContainsKey(eventId))
|
||||
{
|
||||
var type = (AtkEventType)eventType;
|
||||
var node = (AtkResNode*)atkResNode;
|
||||
|
||||
this.eventListener.UnregisterEvent(node, type, eventId);
|
||||
this.eventHandlers.Remove(eventId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning($"Attempted to unregister already unregistered eventId: {eventId}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetCursor(AddonCursorType cursor)
|
||||
{
|
||||
this.isForcingCursor = true;
|
||||
|
||||
this.baseEventManager.SetCursor(cursor);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ResetCursor()
|
||||
{
|
||||
this.isForcingCursor = false;
|
||||
|
||||
this.baseEventManager.ResetCursor();
|
||||
}
|
||||
|
||||
private void PluginAddonEventHandler(AtkEventListener* self, AtkEventType eventType, uint eventParam, AtkEvent* eventData, IntPtr unknown)
|
||||
{
|
||||
if (this.eventHandlers.TryGetValue(eventParam, out var handler) && eventData is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// We passed the AtkUnitBase into the EventData.Node field from our AddonEventHandler
|
||||
handler?.Invoke((AddonEventType)eventType, (nint)eventData->Node, (nint)eventData->Target);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, "Exception in PluginAddonEventHandler custom event invoke.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
namespace Dalamud.Game.AddonEventManager;
|
||||
|
||||
/// <summary>
|
||||
/// AddonEventManager memory address resolver.
|
||||
/// </summary>
|
||||
internal class AddonEventManagerAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the AtkModule UpdateCursor method.
|
||||
/// </summary>
|
||||
public nint UpdateCursor { 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.UpdateCursor = scanner.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 20 4C 8B F1 E8 ?? ?? ?? ?? 49 8B CE");
|
||||
}
|
||||
}
|
||||
132
Dalamud/Game/AddonEventManager/AddonEventType.cs
Normal file
132
Dalamud/Game/AddonEventManager/AddonEventType.cs
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
namespace Dalamud.Game.AddonEventManager;
|
||||
|
||||
/// <summary>
|
||||
/// Reimplementation of AtkEventType.
|
||||
/// </summary>
|
||||
public enum AddonEventType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Mouse Down.
|
||||
/// </summary>
|
||||
MouseDown = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Mouse Up.
|
||||
/// </summary>
|
||||
MouseUp = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Mouse Move.
|
||||
/// </summary>
|
||||
MouseMove = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Mouse Over.
|
||||
/// </summary>
|
||||
MouseOver = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Mouse Out.
|
||||
/// </summary>
|
||||
MouseOut = 7,
|
||||
|
||||
/// <summary>
|
||||
/// Mouse Click.
|
||||
/// </summary>
|
||||
MouseClick = 9,
|
||||
|
||||
/// <summary>
|
||||
/// Input Received.
|
||||
/// </summary>
|
||||
InputReceived = 12,
|
||||
|
||||
/// <summary>
|
||||
/// Focus Start.
|
||||
/// </summary>
|
||||
FocusStart = 18,
|
||||
|
||||
/// <summary>
|
||||
/// Focus Stop.
|
||||
/// </summary>
|
||||
FocusStop = 19,
|
||||
|
||||
/// <summary>
|
||||
/// Button Press, sent on MouseDown on Button.
|
||||
/// </summary>
|
||||
ButtonPress = 23,
|
||||
|
||||
/// <summary>
|
||||
/// Button Release, sent on MouseUp and MouseOut.
|
||||
/// </summary>
|
||||
ButtonRelease = 24,
|
||||
|
||||
/// <summary>
|
||||
/// Button Click, sent on MouseUp and MouseClick on button.
|
||||
/// </summary>
|
||||
ButtonClick = 25,
|
||||
|
||||
/// <summary>
|
||||
/// List Item RollOver.
|
||||
/// </summary>
|
||||
ListItemRollOver = 33,
|
||||
|
||||
/// <summary>
|
||||
/// List Item Roll Out.
|
||||
/// </summary>
|
||||
ListItemRollOut = 34,
|
||||
|
||||
/// <summary>
|
||||
/// List Item Toggle.
|
||||
/// </summary>
|
||||
ListItemToggle = 35,
|
||||
|
||||
/// <summary>
|
||||
/// Drag Drop Roll Over.
|
||||
/// </summary>
|
||||
DragDropRollOver = 52,
|
||||
|
||||
/// <summary>
|
||||
/// Drag Drop Roll Out.
|
||||
/// </summary>
|
||||
DragDropRollOut = 53,
|
||||
|
||||
/// <summary>
|
||||
/// Drag Drop Unknown.
|
||||
/// </summary>
|
||||
DragDropUnk54 = 54,
|
||||
|
||||
/// <summary>
|
||||
/// Drag Drop Unknown.
|
||||
/// </summary>
|
||||
DragDropUnk55 = 55,
|
||||
|
||||
/// <summary>
|
||||
/// Icon Text Roll Over.
|
||||
/// </summary>
|
||||
IconTextRollOver = 56,
|
||||
|
||||
/// <summary>
|
||||
/// Icon Text Roll Out.
|
||||
/// </summary>
|
||||
IconTextRollOut = 57,
|
||||
|
||||
/// <summary>
|
||||
/// Icon Text Click.
|
||||
/// </summary>
|
||||
IconTextClick = 58,
|
||||
|
||||
/// <summary>
|
||||
/// Window Roll Over.
|
||||
/// </summary>
|
||||
WindowRollOver = 67,
|
||||
|
||||
/// <summary>
|
||||
/// Window Roll Out.
|
||||
/// </summary>
|
||||
WindowRollOut = 68,
|
||||
|
||||
/// <summary>
|
||||
/// Window Change Scale.
|
||||
/// </summary>
|
||||
WindowChangeScale = 69,
|
||||
}
|
||||
|
|
@ -1,15 +1,22 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Game.AddonEventManager;
|
||||
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 Serilog;
|
||||
|
||||
using DalamudAddonEventManager = Dalamud.Game.AddonEventManager.AddonEventManager;
|
||||
|
||||
namespace Dalamud.Game.Gui.Dtr;
|
||||
|
||||
|
|
@ -19,13 +26,15 @@ namespace Dalamud.Game.Gui.Dtr;
|
|||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IDtrBar>]
|
||||
#pragma warning restore SA1015
|
||||
public 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");
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly Framework framework = Service<Framework>.Get();
|
||||
|
||||
|
|
@ -35,12 +44,25 @@ public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
private List<DtrBarEntry> entries = new();
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudAddonEventManager uiEventManager = Service<DalamudAddonEventManager>.Get();
|
||||
|
||||
private readonly DtrBarAddressResolver address;
|
||||
private readonly ConcurrentBag<DtrBarEntry> newEntries = new();
|
||||
private readonly List<DtrBarEntry> entries = new();
|
||||
private readonly Hook<AddonDrawDelegate> onAddonDrawHook;
|
||||
private readonly Hook<AddonRequestedUpdateDelegate> onAddonRequestedUpdateHook;
|
||||
private uint runningNodeIds = BaseNodeId;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private DtrBar()
|
||||
private DtrBar(SigScanner sigScanner)
|
||||
{
|
||||
this.address = new DtrBarAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.onAddonDrawHook = Hook<AddonDrawDelegate>.FromAddress(this.address.AtkUnitBaseDraw, this.OnAddonDrawDetour);
|
||||
this.onAddonRequestedUpdateHook = Hook<AddonRequestedUpdateDelegate>.FromAddress(this.address.AddonRequestedUpdate, this.OnAddonRequestedUpdateDetour);
|
||||
|
||||
this.framework.Update += this.Update;
|
||||
|
||||
this.configuration.DtrOrder ??= new List<string>();
|
||||
|
|
@ -48,28 +70,43 @@ public 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)
|
||||
{
|
||||
if (this.entries.Any(x => x.Title == title))
|
||||
if (this.entries.Any(x => x.Title == title) || this.newEntries.Any(x => x.Title == title))
|
||||
throw new ArgumentException("An entry with the same title already exists.");
|
||||
|
||||
var node = this.MakeNode(++this.runningNodeIds);
|
||||
var entry = new DtrBarEntry(title, node);
|
||||
var entry = new DtrBarEntry(title, null);
|
||||
entry.Text = text;
|
||||
|
||||
// Add the entry to the end of the order list, if it's not there already.
|
||||
if (!this.configuration.DtrOrder!.Contains(title))
|
||||
this.configuration.DtrOrder!.Add(title);
|
||||
this.entries.Add(entry);
|
||||
this.ApplySort();
|
||||
|
||||
this.newEntries.Add(entry);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Remove(string title)
|
||||
{
|
||||
if (this.entries.FirstOrDefault(entry => entry.Title == title) is { } dtrBarEntry)
|
||||
{
|
||||
dtrBarEntry.Remove();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
this.onAddonDrawHook.Dispose();
|
||||
this.onAddonRequestedUpdateHook.Dispose();
|
||||
|
||||
foreach (var entry in this.entries)
|
||||
this.RemoveNode(entry.TextNode);
|
||||
|
||||
|
|
@ -130,12 +167,20 @@ public 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();
|
||||
|
||||
private void Update(Framework unused)
|
||||
{
|
||||
this.HandleRemovedNodes();
|
||||
this.HandleAddedNodes();
|
||||
|
||||
var dtr = this.GetDtr();
|
||||
if (dtr == null) return;
|
||||
|
|
@ -148,7 +193,7 @@ public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
if (!this.CheckForDalamudNodes())
|
||||
this.RecreateNodes();
|
||||
|
||||
var collisionNode = dtr->UldManager.NodeList[1];
|
||||
var collisionNode = dtr->GetNodeById(17);
|
||||
if (collisionNode == null) return;
|
||||
|
||||
// If we are drawing backwards, we should start from the right side of the collision node. That is,
|
||||
|
|
@ -157,28 +202,24 @@ public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
? collisionNode->X + collisionNode->Width
|
||||
: collisionNode->X;
|
||||
|
||||
for (var i = 0; i < this.entries.Count; i++)
|
||||
foreach (var data in this.entries)
|
||||
{
|
||||
var data = this.entries[i];
|
||||
var isHide = this.configuration.DtrIgnore!.Any(x => x == data.Title) || !data.Shown;
|
||||
|
||||
if (data.Dirty && data.Added && data.Text != null && data.TextNode != null)
|
||||
if (data is { Dirty: true, Added: true, Text: not null, TextNode: not null })
|
||||
{
|
||||
var node = data.TextNode;
|
||||
node->SetText(data.Text?.Encode());
|
||||
node->SetText(data.Text.Encode());
|
||||
ushort w = 0, h = 0;
|
||||
|
||||
if (isHide)
|
||||
if (!isHide)
|
||||
{
|
||||
node->AtkResNode.ToggleVisibility(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
node->AtkResNode.ToggleVisibility(true);
|
||||
node->GetTextDrawSize(&w, &h, node->NodeText.StringPtr);
|
||||
node->AtkResNode.SetWidth(w);
|
||||
}
|
||||
|
||||
node->AtkResNode.ToggleVisibility(!isHide);
|
||||
|
||||
data.Dirty = false;
|
||||
}
|
||||
|
||||
|
|
@ -202,8 +243,91 @@ public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
data.TextNode->AtkResNode.SetPositionFloat(runningXPos, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.entries[i] = data;
|
||||
private void HandleAddedNodes()
|
||||
{
|
||||
if (this.newEntries.Any())
|
||||
{
|
||||
foreach (var newEntry in this.newEntries)
|
||||
{
|
||||
newEntry.TextNode = this.MakeNode(++this.runningNodeIds);
|
||||
this.entries.Add(newEntry);
|
||||
}
|
||||
|
||||
this.newEntries.Clear();
|
||||
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)
|
||||
{
|
||||
this.onAddonDrawHook!.Original(addon);
|
||||
|
||||
try
|
||||
{
|
||||
if (MemoryHelper.ReadString((nint)addon->Name, 0x20) is not "_DTR") return;
|
||||
|
||||
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)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonDraw.");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateNodePositions(AtkUnitBase* addon)
|
||||
{
|
||||
// If we grow to the right, we need to left-justify the original elements.
|
||||
// else if we grow to the left, the game right-justifies it for us.
|
||||
if (this.configuration.DtrSwapDirection)
|
||||
{
|
||||
var targetSize = (ushort)this.CalculateTotalSize();
|
||||
addon->RootNode->SetWidth(targetSize);
|
||||
var sizeOffset = addon->GetNodeById(17)->GetX();
|
||||
|
||||
var node = addon->RootNode->ChildNode;
|
||||
while (node is not null)
|
||||
{
|
||||
if (node->NodeID < 1000 && node->IsVisible)
|
||||
{
|
||||
node->SetX(node->GetX() - sizeOffset);
|
||||
}
|
||||
|
||||
node = node->PrevSiblingNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddonRequestedUpdateDetour(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
|
||||
{
|
||||
this.onAddonRequestedUpdateHook.Original(addon, numberArrayData, stringArrayData);
|
||||
|
||||
try
|
||||
{
|
||||
this.UpdateNodePositions(addon);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonRequestedUpdate.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -235,11 +359,37 @@ public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
}
|
||||
}
|
||||
|
||||
// Calculates the total width the dtr bar should be
|
||||
private float CalculateTotalSize()
|
||||
{
|
||||
var addon = this.GetDtr();
|
||||
if (addon is null || addon->RootNode is null || addon->UldManager.NodeList is null) return 0;
|
||||
|
||||
var totalSize = 0.0f;
|
||||
|
||||
foreach (var index in Enumerable.Range(0, addon->UldManager.NodeListCount))
|
||||
{
|
||||
var node = addon->UldManager.NodeList[index];
|
||||
|
||||
// Node 17 is the default CollisionNode that fits over the existing elements
|
||||
if (node->NodeID is 17) totalSize += node->Width;
|
||||
|
||||
// Node > 1000, are our custom nodes
|
||||
if (node->NodeID is > 1000 && node->IsVisible) totalSize += node->Width + this.configuration.DtrSpacing;
|
||||
}
|
||||
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
private bool AddNode(AtkTextNode* node)
|
||||
{
|
||||
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);
|
||||
|
||||
var lastChild = dtr->RootNode->ChildNode;
|
||||
while (lastChild->PrevSiblingNode != null) lastChild = lastChild->PrevSiblingNode;
|
||||
Log.Debug($"Found last sibling: {(ulong)lastChild:X}");
|
||||
|
|
@ -251,6 +401,7 @@ public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
Log.Debug("Set last sibling of DTR and updated child count");
|
||||
|
||||
dtr->UldManager.UpdateDrawNodeList();
|
||||
dtr->UpdateCollisionNodeList(false);
|
||||
Log.Debug("Updated node draw list");
|
||||
return true;
|
||||
}
|
||||
|
|
@ -260,6 +411,10 @@ public 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.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);
|
||||
|
||||
var tmpPrevNode = node->AtkResNode.PrevSiblingNode;
|
||||
var tmpNextNode = node->AtkResNode.NextSiblingNode;
|
||||
|
||||
|
|
@ -272,25 +427,23 @@ public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
dtr->RootNode->ChildCount = (ushort)(dtr->RootNode->ChildCount - 1);
|
||||
Log.Debug("Set last sibling of DTR and updated child count");
|
||||
dtr->UldManager.UpdateDrawNodeList();
|
||||
dtr->UpdateCollisionNodeList(false);
|
||||
Log.Debug("Updated node draw list");
|
||||
return true;
|
||||
}
|
||||
|
||||
private AtkTextNode* MakeNode(uint nodeId)
|
||||
{
|
||||
var newTextNode = (AtkTextNode*)IMemorySpace.GetUISpace()->Malloc((ulong)sizeof(AtkTextNode), 8);
|
||||
var newTextNode = IMemorySpace.GetUISpace()->Create<AtkTextNode>();
|
||||
if (newTextNode == null)
|
||||
{
|
||||
Log.Debug("Failed to allocate memory for text node");
|
||||
Log.Debug("Failed to allocate memory for AtkTextNode");
|
||||
return null;
|
||||
}
|
||||
|
||||
IMemorySpace.Memset(newTextNode, 0, (ulong)sizeof(AtkTextNode));
|
||||
newTextNode->Ctor();
|
||||
|
||||
newTextNode->AtkResNode.NodeID = nodeId;
|
||||
newTextNode->AtkResNode.Type = NodeType.Text;
|
||||
newTextNode->AtkResNode.NodeFlags = NodeFlags.AnchorLeft | NodeFlags.AnchorTop;
|
||||
newTextNode->AtkResNode.NodeFlags = NodeFlags.AnchorLeft | NodeFlags.AnchorTop | NodeFlags.Enabled | NodeFlags.RespondToMouse | NodeFlags.HasCollision | NodeFlags.EmitsEvents;
|
||||
newTextNode->AtkResNode.DrawFlags = 12;
|
||||
newTextNode->AtkResNode.SetWidth(22);
|
||||
newTextNode->AtkResNode.SetHeight(22);
|
||||
|
|
@ -304,16 +457,96 @@ public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
|
||||
newTextNode->SetText(" ");
|
||||
|
||||
newTextNode->TextColor.R = 255;
|
||||
newTextNode->TextColor.G = 255;
|
||||
newTextNode->TextColor.B = 255;
|
||||
newTextNode->TextColor.A = 255;
|
||||
|
||||
newTextNode->EdgeColor.R = 142;
|
||||
newTextNode->EdgeColor.G = 106;
|
||||
newTextNode->EdgeColor.B = 12;
|
||||
newTextNode->EdgeColor.A = 255;
|
||||
newTextNode->TextColor = new ByteColor { R = 255, G = 255, B = 255, A = 255 };
|
||||
newTextNode->EdgeColor = new ByteColor { R = 142, G = 106, B = 12, A = 255 };
|
||||
|
||||
return newTextNode;
|
||||
}
|
||||
|
||||
private void DtrEventHandler(AddonEventType atkEventType, IntPtr atkUnitBase, IntPtr atkResNode)
|
||||
{
|
||||
var addon = (AtkUnitBase*)atkUnitBase;
|
||||
var node = (AtkResNode*)atkResNode;
|
||||
|
||||
if (this.entries.FirstOrDefault(entry => entry.TextNode == node) is not { } dtrBarEntry) return;
|
||||
|
||||
if (dtrBarEntry is { Tooltip: not null })
|
||||
{
|
||||
switch (atkEventType)
|
||||
{
|
||||
case AddonEventType.MouseOver:
|
||||
AtkStage.GetSingleton()->TooltipManager.ShowTooltip(addon->ID, node, dtrBarEntry.Tooltip.Encode());
|
||||
break;
|
||||
|
||||
case AddonEventType.MouseOut:
|
||||
AtkStage.GetSingleton()->TooltipManager.HideTooltip(addon->ID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dtrBarEntry is { OnClick: not null })
|
||||
{
|
||||
switch (atkEventType)
|
||||
{
|
||||
case AddonEventType.MouseOver:
|
||||
this.uiEventManager.SetCursor(AddonCursorType.Clickable);
|
||||
break;
|
||||
|
||||
case AddonEventType.MouseOut:
|
||||
this.uiEventManager.ResetCursor();
|
||||
break;
|
||||
|
||||
case AddonEventType.MouseClick:
|
||||
dtrBarEntry.OnClick.Invoke();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin-scoped version of a AddonEventManager service.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IDtrBar>]
|
||||
#pragma warning restore SA1015
|
||||
internal class DtrBarPluginScoped : IDisposable, IServiceType, IDtrBar
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DtrBar dtrBarService = Service<DtrBar>.Get();
|
||||
|
||||
private readonly Dictionary<string, DtrBarEntry> pluginEntries = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var entry in this.pluginEntries)
|
||||
{
|
||||
entry.Value.Remove();
|
||||
}
|
||||
|
||||
this.pluginEntries.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DtrBarEntry Get(string title, SeString? text = null)
|
||||
{
|
||||
// If we already have a known entry for this plugin, return it.
|
||||
if (this.pluginEntries.TryGetValue(title, out var existingEntry)) return existingEntry;
|
||||
|
||||
return this.pluginEntries[title] = this.dtrBarService.Get(title, text);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Remove(string title)
|
||||
{
|
||||
if (this.pluginEntries.TryGetValue(title, out var existingEntry))
|
||||
{
|
||||
existingEntry.Remove();
|
||||
this.pluginEntries.Remove(title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
29
Dalamud/Game/Gui/Dtr/DtrBarAddressResolver.cs
Normal file
29
Dalamud/Game/Gui/Dtr/DtrBarAddressResolver.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
namespace Dalamud.Game.Gui.Dtr;
|
||||
|
||||
/// <summary>
|
||||
/// DtrBar memory address resolver.
|
||||
/// </summary>
|
||||
public 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");
|
||||
}
|
||||
}
|
||||
|
|
@ -41,6 +41,16 @@ public sealed unsafe class DtrBarEntry : IDisposable
|
|||
this.Dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a tooltip to be shown when the user mouses over the dtr entry.
|
||||
/// </summary>
|
||||
public SeString? Tooltip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a action to be invoked when the user clicks on the dtr entry.
|
||||
/// </summary>
|
||||
public Action? OnClick { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this entry is visible.
|
||||
|
|
|
|||
46
Dalamud/Plugin/Services/IAddonEventManager.cs
Normal file
46
Dalamud/Plugin/Services/IAddonEventManager.cs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
using Dalamud.Game.AddonEventManager;
|
||||
|
||||
namespace Dalamud.Plugin.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service provider for addon event management.
|
||||
/// </summary>
|
||||
public interface IAddonEventManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate to be called when an event is received.
|
||||
/// </summary>
|
||||
/// <param name="atkEventType">Event type for this event handler.</param>
|
||||
/// <param name="atkUnitBase">The parent addon for this event handler.</param>
|
||||
/// <param name="atkResNode">The specific node that will trigger this event handler.</param>
|
||||
public delegate void AddonEventHandler(AddonEventType atkEventType, nint atkUnitBase, nint atkResNode);
|
||||
|
||||
/// <summary>
|
||||
/// Registers an event handler for the specified addon, node, and type.
|
||||
/// </summary>
|
||||
/// <param name="eventId">Unique Id for this event, maximum 0x10000.</param>
|
||||
/// <param name="atkUnitBase">The parent addon for this event.</param>
|
||||
/// <param name="atkResNode">The node that will trigger this event.</param>
|
||||
/// <param name="eventType">The event type for this event.</param>
|
||||
/// <param name="eventHandler">The handler to call when event is triggered.</param>
|
||||
void AddEvent(uint eventId, nint atkUnitBase, nint atkResNode, AddonEventType eventType, AddonEventHandler eventHandler);
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters an event handler with the specified event id and event type.
|
||||
/// </summary>
|
||||
/// <param name="eventId">The Unique Id for this event.</param>
|
||||
/// <param name="atkResNode">The node for this event.</param>
|
||||
/// <param name="eventType">The event type for this event.</param>
|
||||
void RemoveEvent(uint eventId, nint atkResNode, AddonEventType eventType);
|
||||
|
||||
/// <summary>
|
||||
/// Force the game cursor to be the specified cursor.
|
||||
/// </summary>
|
||||
/// <param name="cursor">Which cursor to use.</param>
|
||||
void SetCursor(AddonCursorType cursor);
|
||||
|
||||
/// <summary>
|
||||
/// Un-forces the game cursor.
|
||||
/// </summary>
|
||||
void ResetCursor();
|
||||
}
|
||||
|
|
@ -19,4 +19,10 @@ public interface IDtrBar
|
|||
/// <returns>The entry object used to update, hide and remove the entry.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when an entry with the specified title exists.</exception>
|
||||
public DtrBarEntry Get(string title, SeString? text = null);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a DTR bar entry from the system.
|
||||
/// </summary>
|
||||
/// <param name="title">Title of the entry to remove.</param>
|
||||
public void Remove(string title);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue