diff --git a/Dalamud/Game/Addon/Events/AddonEventData.cs b/Dalamud/Game/Addon/Events/AddonEventData.cs
new file mode 100644
index 000000000..3a5c05660
--- /dev/null
+++ b/Dalamud/Game/Addon/Events/AddonEventData.cs
@@ -0,0 +1,46 @@
+namespace Dalamud.Game.Addon.Events;
+
+///
+/// Object representing data that is relevant in handling native events.
+///
+public class AddonEventData
+{
+ ///
+ /// Gets the AtkEventType for this event.
+ ///
+ public AddonEventType AtkEventType { get; internal set; }
+
+ ///
+ /// Gets the param field for this event.
+ ///
+ public uint Param { get; internal set; }
+
+ ///
+ /// Gets the pointer to the AtkEvent object for this event.
+ ///
+ /// Note: This is not a pointer to the AtkEventData object.
+ /// Warning: AtkEvent->Node has been modified to be the AtkUnitBase*, and AtkEvent->Target has been modified to be the AtkResNode* that triggered this event.
+ public nint AtkEventPointer { get; internal set; }
+
+ ///
+ /// Gets the pointer to the AtkEventData object for this event.
+ ///
+ /// This field will contain relevant data such as left vs right click, scroll up vs scroll down.
+ public nint AtkEventDataPointer { get; internal set; }
+
+ ///
+ /// Gets the pointer to the AtkUnitBase that is handling this event.
+ ///
+ public nint AddonPointer { get; internal set; }
+
+ ///
+ /// Gets the pointer to the AtkResNode that triggered this event.
+ ///
+ public nint NodeTargetPointer { get; internal set; }
+
+ ///
+ /// Gets or sets a pointer to the AtkEventListener responsible for handling this event.
+ /// Note: As the event listener is dalamud allocated, there's no reason to expose this field.
+ ///
+ internal nint AtkEventListener { get; set; }
+}
diff --git a/Dalamud/Game/Addon/Events/AddonEventEntry.cs b/Dalamud/Game/Addon/Events/AddonEventEntry.cs
index 8b5808087..50b9c7ec4 100644
--- a/Dalamud/Game/Addon/Events/AddonEventEntry.cs
+++ b/Dalamud/Game/Addon/Events/AddonEventEntry.cs
@@ -1,5 +1,6 @@
-using Dalamud.Memory;
-using Dalamud.Plugin.Services;
+using Dalamud.Plugin.Services;
+using Dalamud.Utility;
+
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace Dalamud.Game.Addon.Events;
@@ -14,9 +15,9 @@ internal unsafe class AddonEventEntry
/// Name of an invalid addon.
///
public const string InvalidAddonName = "NullAddon";
-
+
private string? addonName;
-
+
///
/// Gets the pointer to the addons AtkUnitBase.
///
@@ -35,18 +36,25 @@ internal unsafe class AddonEventEntry
///
/// Gets the handler that gets called when this event is triggered.
///
- public required IAddonEventManager.AddonEventHandler Handler { get; init; }
-
+ [Obsolete("Use AddonEventDelegate Delegate instead")]
+ public IAddonEventManager.AddonEventHandler Handler { get; init; }
+
+ ///
+ /// Gets the delegate that gets called when this event is triggered.
+ ///
+ [Api13ToDo("Make this field required")]
+ public IAddonEventManager.AddonEventDelegate Delegate { get; init; }
+
///
/// Gets the unique id for this event.
///
public required uint ParamKey { get; init; }
-
+
///
/// Gets the event type for this event.
///
public required AddonEventType EventType { get; init; }
-
+
///
/// Gets the event handle for this event.
///
diff --git a/Dalamud/Game/Addon/Events/AddonEventManager.cs b/Dalamud/Game/Addon/Events/AddonEventManager.cs
index a7241dd58..0990c1f5f 100644
--- a/Dalamud/Game/Addon/Events/AddonEventManager.cs
+++ b/Dalamud/Game/Addon/Events/AddonEventManager.cs
@@ -24,21 +24,21 @@ internal unsafe class AddonEventManager : IInternalDisposableService
/// PluginName for Dalamud Internal use.
///
public static readonly Guid DalamudInternalKey = Guid.NewGuid();
-
+
private static readonly ModuleLog Log = new("AddonEventManager");
-
+
[ServiceManager.ServiceDependency]
private readonly AddonLifecycle addonLifecycle = Service.Get();
private readonly AddonLifecycleEventListener finalizeEventListener;
-
+
private readonly AddonEventManagerAddressResolver address;
private readonly Hook onUpdateCursor;
private readonly ConcurrentDictionary pluginEventControllers;
-
+
private AddonCursorType? cursorOverride;
-
+
[ServiceManager.ServiceConstructor]
private AddonEventManager(TargetSigScanner sigScanner)
{
@@ -47,7 +47,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService
this.pluginEventControllers = new ConcurrentDictionary();
this.pluginEventControllers.TryAdd(DalamudInternalKey, new PluginEventController());
-
+
this.cursorOverride = null;
this.onUpdateCursor = Hook.FromAddress(this.address.UpdateCursor, this.UpdateCursorDetour);
@@ -69,7 +69,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService
{
pluginEventController.Dispose();
}
-
+
this.addonLifecycle.UnregisterListener(this.finalizeEventListener);
}
@@ -92,7 +92,30 @@ internal unsafe class AddonEventManager : IInternalDisposableService
{
Log.Verbose($"Unable to locate controller for {pluginId}. No event was added.");
}
-
+
+ return null;
+ }
+
+ ///
+ /// Registers an event handler for the specified addon, node, and type.
+ ///
+ /// Unique ID for this plugin.
+ /// The parent addon for this event.
+ /// The node that will trigger this event.
+ /// The event type for this event.
+ /// The delegate to call when event is triggered.
+ /// IAddonEventHandle used to remove the event.
+ internal IAddonEventHandle? AddEvent(Guid pluginId, nint atkUnitBase, nint atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventDelegate eventDelegate)
+ {
+ if (this.pluginEventControllers.TryGetValue(pluginId, out var controller))
+ {
+ return controller.AddEvent(atkUnitBase, atkResNode, eventType, eventDelegate);
+ }
+ else
+ {
+ Log.Verbose($"Unable to locate controller for {pluginId}. No event was added.");
+ }
+
return null;
}
@@ -112,7 +135,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService
Log.Verbose($"Unable to locate controller for {pluginId}. No event was removed.");
}
}
-
+
///
/// Force the game cursor to be the specified cursor.
///
@@ -167,21 +190,21 @@ internal unsafe class AddonEventManager : IInternalDisposableService
pluginList.Value.RemoveForAddon(addonInfo.AddonName);
}
}
-
+
private nint UpdateCursorDetour(RaptureAtkModule* module)
{
try
{
var atkStage = AtkStage.Instance();
-
+
if (this.cursorOverride is not null && atkStage is not null)
{
var cursor = (AddonCursorType)atkStage->AtkCursor.Type;
- if (cursor != this.cursorOverride)
+ if (cursor != this.cursorOverride)
{
AtkStage.Instance()->AtkCursor.SetCursorType((AtkCursor.CursorType)this.cursorOverride, 1);
}
-
+
return nint.Zero;
}
}
@@ -218,7 +241,7 @@ internal class AddonEventManagerPluginScoped : IInternalDisposableService, IAddo
public AddonEventManagerPluginScoped(LocalPlugin plugin)
{
this.plugin = plugin;
-
+
this.eventManagerService.AddPluginEventController(plugin.EffectiveWorkingPluginId);
}
@@ -236,28 +259,32 @@ internal class AddonEventManagerPluginScoped : IInternalDisposableService, IAddo
this.eventManagerService.RemovePluginEventController(this.plugin.EffectiveWorkingPluginId);
}).Wait();
}
-
+
///
- public IAddonEventHandle? AddEvent(IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
+ public IAddonEventHandle? AddEvent(IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
=> this.eventManagerService.AddEvent(this.plugin.EffectiveWorkingPluginId, atkUnitBase, atkResNode, eventType, eventHandler);
+ ///
+ public IAddonEventHandle? AddEvent(nint atkUnitBase, nint atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventDelegate eventDelegate)
+ => this.eventManagerService.AddEvent(this.plugin.EffectiveWorkingPluginId, atkUnitBase, atkResNode, eventType, eventDelegate);
+
///
public void RemoveEvent(IAddonEventHandle eventHandle)
=> this.eventManagerService.RemoveEvent(this.plugin.EffectiveWorkingPluginId, eventHandle);
-
+
///
public void SetCursor(AddonCursorType cursor)
{
this.isForcingCursor = true;
-
+
this.eventManagerService.SetCursor(cursor);
}
-
+
///
public void ResetCursor()
{
this.isForcingCursor = false;
-
+
this.eventManagerService.ResetCursor();
}
}
diff --git a/Dalamud/Game/Addon/Events/PluginEventController.cs b/Dalamud/Game/Addon/Events/PluginEventController.cs
index f32c7ad8f..0b1491e77 100644
--- a/Dalamud/Game/Addon/Events/PluginEventController.cs
+++ b/Dalamud/Game/Addon/Events/PluginEventController.cs
@@ -4,6 +4,7 @@ using System.Linq;
using Dalamud.Game.Gui;
using Dalamud.Logging.Internal;
using Dalamud.Plugin.Services;
+using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Component.GUI;
@@ -25,7 +26,7 @@ internal unsafe class PluginEventController : IDisposable
}
private AddonEventListener EventListener { get; init; }
-
+
private List Events { get; } = new();
///
@@ -36,6 +37,7 @@ internal unsafe class PluginEventController : IDisposable
/// The Event Type.
/// The delegate to call when invoking this event.
/// IAddonEventHandle used to remove the event.
+ [Obsolete("Use AddEvent that uses AddonEventDelegate instead of AddonEventHandler")]
public IAddonEventHandle AddEvent(nint atkUnitBase, nint atkResNode, AddonEventType atkEventType, IAddonEventManager.AddonEventHandler handler)
{
var node = (AtkResNode*)atkResNode;
@@ -43,7 +45,7 @@ internal unsafe class PluginEventController : IDisposable
var eventType = (AtkEventType)atkEventType;
var eventId = this.GetNextParamKey();
var eventGuid = Guid.NewGuid();
-
+
var eventHandle = new AddonEventHandle
{
AddonName = addon->NameString,
@@ -51,11 +53,54 @@ internal unsafe class PluginEventController : IDisposable
EventType = atkEventType,
EventGuid = eventGuid,
};
-
+
var eventEntry = new AddonEventEntry
{
Addon = atkUnitBase,
Handler = handler,
+ Delegate = null,
+ Node = atkResNode,
+ EventType = atkEventType,
+ ParamKey = eventId,
+ Handle = eventHandle,
+ };
+
+ Log.Verbose($"Adding Event. {eventEntry.LogString}");
+ this.EventListener.RegisterEvent(addon, node, eventType, eventId);
+ this.Events.Add(eventEntry);
+
+ return eventHandle;
+ }
+
+ ///
+ /// Adds a tracked event.
+ ///
+ /// The Parent addon for the event.
+ /// The Node for the event.
+ /// The Event Type.
+ /// The delegate to call when invoking this event.
+ /// IAddonEventHandle used to remove the event.
+ public IAddonEventHandle AddEvent(nint atkUnitBase, nint atkResNode, AddonEventType atkEventType, IAddonEventManager.AddonEventDelegate eventDelegate)
+ {
+ var node = (AtkResNode*)atkResNode;
+ var addon = (AtkUnitBase*)atkUnitBase;
+ var eventType = (AtkEventType)atkEventType;
+ var eventId = this.GetNextParamKey();
+ var eventGuid = Guid.NewGuid();
+
+ var eventHandle = new AddonEventHandle
+ {
+ AddonName = addon->NameString,
+ ParamKey = eventId,
+ EventType = atkEventType,
+ EventGuid = eventGuid,
+ };
+
+ var eventEntry = new AddonEventEntry
+ {
+ Addon = atkUnitBase,
+ Delegate = eventDelegate,
+ Handler = null,
Node = atkResNode,
EventType = atkEventType,
ParamKey = eventId,
@@ -91,14 +136,14 @@ internal unsafe class PluginEventController : IDisposable
if (this.Events.Where(entry => entry.AddonName == addonName).ToList() is { Count: not 0 } events)
{
Log.Verbose($"Addon: {addonName} is Finalizing, removing {events.Count} events.");
-
+
foreach (var registeredEvent in events)
{
this.RemoveEvent(registeredEvent.Handle);
}
}
}
-
+
///
public void Dispose()
{
@@ -106,7 +151,7 @@ internal unsafe class PluginEventController : IDisposable
{
this.RemoveEvent(registeredEvent.Handle);
}
-
+
this.EventListener.Dispose();
}
@@ -119,7 +164,7 @@ internal unsafe class PluginEventController : IDisposable
throw new OverflowException($"uint.MaxValue number of ParamKeys used for this event controller.");
}
-
+
///
/// Attempts to remove a tracked event from native UI.
/// This method performs several safety checks to only remove events from a still active addon.
@@ -153,7 +198,7 @@ internal unsafe class PluginEventController : IDisposable
break;
}
}
-
+
// If we didn't find the node, we can't remove the event.
if (!nodeFound) return;
@@ -167,33 +212,45 @@ internal unsafe class PluginEventController : IDisposable
var paramKeyMatches = currentEvent->Param == eventEntry.ParamKey;
var eventListenerAddressMatches = (nint)currentEvent->Listener == this.EventListener.Address;
var eventTypeMatches = currentEvent->State.EventType == eventType;
-
+
if (paramKeyMatches && eventListenerAddressMatches && eventTypeMatches)
{
eventFound = true;
break;
}
-
+
// Move to the next event.
currentEvent = currentEvent->NextEvent;
}
-
+
// If we didn't find the event, we can't remove the event.
if (!eventFound) return;
// We have a valid addon, valid node, valid event, and valid key.
this.EventListener.UnregisterEvent(atkResNode, eventType, eventEntry.ParamKey);
}
-
+
+ [Api13ToDo("Remove invoke from eventInfo.Handler, and remove nullability from eventInfo.Delegate?.Invoke")]
private void PluginEventListHandler(AtkEventListener* self, AtkEventType eventType, uint eventParam, AtkEvent* eventPtr, AtkEventData* eventDataPtr)
{
try
{
if (eventPtr is null) return;
if (this.Events.FirstOrDefault(handler => handler.ParamKey == eventParam) is not { } eventInfo) return;
-
+
// We stored the AtkUnitBase* in EventData->Node, and EventData->Target contains the node that triggered the event.
- eventInfo.Handler.Invoke((AddonEventType)eventType, (nint)eventPtr->Node, (nint)eventPtr->Target);
+ eventInfo.Handler?.Invoke((AddonEventType)eventType, (nint)eventPtr->Node, (nint)eventPtr->Target);
+
+ eventInfo.Delegate?.Invoke((AddonEventType)eventType, new AddonEventData
+ {
+ AddonPointer = (nint)eventPtr->Node,
+ NodeTargetPointer = (nint)eventPtr->Target,
+ AtkEventDataPointer = (nint)eventDataPtr,
+ AtkEventListener = (nint)self,
+ AtkEventType = (AddonEventType)eventType,
+ Param = eventParam,
+ AtkEventPointer = (nint)eventPtr,
+ });
}
catch (Exception exception)
{
diff --git a/Dalamud/Plugin/Services/IAddonEventManager.cs b/Dalamud/Plugin/Services/IAddonEventManager.cs
index c6ec5a941..e534eafb4 100644
--- a/Dalamud/Plugin/Services/IAddonEventManager.cs
+++ b/Dalamud/Plugin/Services/IAddonEventManager.cs
@@ -13,8 +13,16 @@ public interface IAddonEventManager
/// Event type for this event handler.
/// The parent addon for this event handler.
/// The specific node that will trigger this event handler.
+ [Obsolete("Use AddonEventDelegate instead")]
public delegate void AddonEventHandler(AddonEventType atkEventType, nint atkUnitBase, nint atkResNode);
+ ///
+ /// Delegate to be called when an event is received.
+ ///
+ /// The AtkEventType that triggered this event.
+ /// The event data object for use in handling this event.
+ public delegate void AddonEventDelegate(AddonEventType atkEventType, AddonEventData data);
+
///
/// Registers an event handler for the specified addon, node, and type.
///
@@ -23,8 +31,19 @@ public interface IAddonEventManager
/// The event type for this event.
/// The handler to call when event is triggered.
/// IAddonEventHandle used to remove the event. Null if no event was added.
+ [Obsolete("Use AddEvent with AddonEventDelegate instead of AddonEventHandler")]
IAddonEventHandle? AddEvent(nint atkUnitBase, nint atkResNode, AddonEventType eventType, AddonEventHandler eventHandler);
+ ///
+ /// Registers an event handler for the specified addon, node, and type.
+ ///
+ /// The parent addon for this event.
+ /// The node that will trigger this event.
+ /// The event type for this event.
+ /// The handler to call when event is triggered.
+ /// IAddonEventHandle used to remove the event. Null if no event was added.
+ IAddonEventHandle? AddEvent(nint atkUnitBase, nint atkResNode, AddonEventType eventType, AddonEventDelegate eventDelegate);
+
///
/// Unregisters an event handler with the specified event id and event type.
///