mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-02-07 08:24:37 +01:00
Merge pull request #2571 from MidoriKami/AgentLifecycle
Add IAgentLifecycle
This commit is contained in:
commit
39e60f27f2
11 changed files with 1100 additions and 0 deletions
39
Dalamud/Game/Agent/AgentArgTypes/AgentArgs.cs
Normal file
39
Dalamud/Game/Agent/AgentArgTypes/AgentArgs.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
using Dalamud.Game.NativeWrapper;
|
||||
|
||||
namespace Dalamud.Game.Agent.AgentArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for AgentLifecycle AgentArgTypes.
|
||||
/// </summary>
|
||||
public unsafe class AgentArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentArgs"/> class.
|
||||
/// </summary>
|
||||
internal AgentArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the Agents AgentInterface*.
|
||||
/// </summary>
|
||||
public AgentInterfacePtr Agent { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the agent id.
|
||||
/// </summary>
|
||||
public AgentId AgentId { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of these args.
|
||||
/// </summary>
|
||||
public virtual AgentArgsType Type => AgentArgsType.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the typed pointer to the Agents AgentInterface*.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">AgentInterface.</typeparam>
|
||||
/// <returns>Typed pointer to contained Agents AgentInterface.</returns>
|
||||
public T* GetAgentPointer<T>() where T : unmanaged
|
||||
=> (T*)this.Agent.Address;
|
||||
}
|
||||
22
Dalamud/Game/Agent/AgentArgTypes/AgentClassJobChangeArgs.cs
Normal file
22
Dalamud/Game/Agent/AgentArgTypes/AgentClassJobChangeArgs.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
namespace Dalamud.Game.Agent.AgentArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Agent argument data for game events.
|
||||
/// </summary>
|
||||
public class AgentClassJobChangeArgs : AgentArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentClassJobChangeArgs"/> class.
|
||||
/// </summary>
|
||||
internal AgentClassJobChangeArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AgentArgsType Type => AgentArgsType.ClassJobChange;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating what the new ClassJob is.
|
||||
/// </summary>
|
||||
public byte ClassJobId { get; set; }
|
||||
}
|
||||
22
Dalamud/Game/Agent/AgentArgTypes/AgentGameEventArgs.cs
Normal file
22
Dalamud/Game/Agent/AgentArgTypes/AgentGameEventArgs.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
namespace Dalamud.Game.Agent.AgentArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Agent argument data for game events.
|
||||
/// </summary>
|
||||
public class AgentGameEventArgs : AgentArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentGameEventArgs"/> class.
|
||||
/// </summary>
|
||||
internal AgentGameEventArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AgentArgsType Type => AgentArgsType.GameEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value representing which gameEvent was triggered.
|
||||
/// </summary>
|
||||
public int GameEvent { get; set; }
|
||||
}
|
||||
27
Dalamud/Game/Agent/AgentArgTypes/AgentLevelChangeArgs.cs
Normal file
27
Dalamud/Game/Agent/AgentArgTypes/AgentLevelChangeArgs.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
namespace Dalamud.Game.Agent.AgentArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Agent argument data for game events.
|
||||
/// </summary>
|
||||
public class AgentLevelChangeArgs : AgentArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentLevelChangeArgs"/> class.
|
||||
/// </summary>
|
||||
internal AgentLevelChangeArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AgentArgsType Type => AgentArgsType.LevelChange;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating which ClassJob was switched to.
|
||||
/// </summary>
|
||||
public byte ClassJobId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating what the new level is.
|
||||
/// </summary>
|
||||
public ushort Level { get; set; }
|
||||
}
|
||||
37
Dalamud/Game/Agent/AgentArgTypes/AgentReceiveEventArgs.cs
Normal file
37
Dalamud/Game/Agent/AgentArgTypes/AgentReceiveEventArgs.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
namespace Dalamud.Game.Agent.AgentArgTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Agent argument data for ReceiveEvent events.
|
||||
/// </summary>
|
||||
public class AgentReceiveEventArgs : AgentArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentReceiveEventArgs"/> class.
|
||||
/// </summary>
|
||||
internal AgentReceiveEventArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AgentArgsType Type => AgentArgsType.ReceiveEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the AtkValue return value for this event message.
|
||||
/// </summary>
|
||||
public nint ReturnValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the AtkValue array for this event message.
|
||||
/// </summary>
|
||||
public nint AtkValues { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the AtkValue count for this event message.
|
||||
/// </summary>
|
||||
public uint ValueCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the event kind for this event message.
|
||||
/// </summary>
|
||||
public ulong EventKind { get; set; }
|
||||
}
|
||||
32
Dalamud/Game/Agent/AgentArgsType.cs
Normal file
32
Dalamud/Game/Agent/AgentArgsType.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
namespace Dalamud.Game.Agent;
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration for available AgentLifecycle arg data.
|
||||
/// </summary>
|
||||
public enum AgentArgsType
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic arg type that contains no meaningful data.
|
||||
/// </summary>
|
||||
Generic,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for ReceiveEvent.
|
||||
/// </summary>
|
||||
ReceiveEvent,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for GameEvent.
|
||||
/// </summary>
|
||||
GameEvent,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for LevelChange.
|
||||
/// </summary>
|
||||
LevelChange,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for ClassJobChange.
|
||||
/// </summary>
|
||||
ClassJobChange,
|
||||
}
|
||||
87
Dalamud/Game/Agent/AgentEvent.cs
Normal file
87
Dalamud/Game/Agent/AgentEvent.cs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
namespace Dalamud.Game.Agent;
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration for available AgentLifecycle events.
|
||||
/// </summary>
|
||||
public enum AgentEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// An event that is fired before the agent processes its Receive Event Function.
|
||||
/// </summary>
|
||||
PreReceiveEvent,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after the agent has processed its Receive Event Function.
|
||||
/// </summary>
|
||||
PostReceiveEvent,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before the agent processes its Filtered Receive Event Function.
|
||||
/// </summary>
|
||||
PreReceiveFilteredEvent,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after the agent has processed its Filtered Receive Event Function.
|
||||
/// </summary>
|
||||
PostReceiveFilteredEvent,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before the agent processes its Show Function.
|
||||
/// </summary>
|
||||
PreShow,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after the agent has processed its Show Function.
|
||||
/// </summary>
|
||||
PostShow,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before the agent processes its Hide Function.
|
||||
/// </summary>
|
||||
PreHide,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after the agent has processed its Hide Function.
|
||||
/// </summary>
|
||||
PostHide,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before the agent processes its Update Function.
|
||||
/// </summary>
|
||||
PreUpdate,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after the agent has processed its Update Function.
|
||||
/// </summary>
|
||||
PostUpdate,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before the agent processes its Game Event Function.
|
||||
/// </summary>
|
||||
PreGameEvent,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after the agent has processed its Game Event Function.
|
||||
/// </summary>
|
||||
PostGameEvent,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before the agent processes its Game Event Function.
|
||||
/// </summary>
|
||||
PreLevelChange,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after the agent has processed its Level Change Function.
|
||||
/// </summary>
|
||||
PostLevelChange,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired before the agent processes its ClassJob Change Function.
|
||||
/// </summary>
|
||||
PreClassJobChange,
|
||||
|
||||
/// <summary>
|
||||
/// An event that is fired after the agent has processed its ClassJob Change Function.
|
||||
/// </summary>
|
||||
PostClassJobChange,
|
||||
}
|
||||
315
Dalamud/Game/Agent/AgentLifecycle.cs
Normal file
315
Dalamud/Game/Agent/AgentLifecycle.cs
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Dalamud.Game.Agent.AgentArgTypes;
|
||||
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.Client.UI.Agent;
|
||||
using FFXIVClientStructs.Interop;
|
||||
|
||||
namespace Dalamud.Game.Agent;
|
||||
|
||||
/// <summary>
|
||||
/// This class provides events for in-game agent lifecycles.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal unsafe class AgentLifecycle : IInternalDisposableService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a list of all allocated agent virtual tables.
|
||||
/// </summary>
|
||||
public static readonly List<AgentVirtualTable> AllocatedTables = [];
|
||||
|
||||
private static readonly ModuleLog Log = new("AgentLifecycle");
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly Framework framework = Service<Framework>.Get();
|
||||
|
||||
private Hook<AgentModule.Delegates.Ctor>? onInitializeAgentsHook;
|
||||
private bool isInvokingListeners;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private AgentLifecycle()
|
||||
{
|
||||
var agentModuleInstance = AgentModule.Instance();
|
||||
|
||||
// Hook is only used to determine appropriate timing for replacing Agent Virtual Tables
|
||||
// If the agent module is already initialized, then we can replace the tables safely.
|
||||
if (agentModuleInstance is null)
|
||||
{
|
||||
this.onInitializeAgentsHook = Hook<AgentModule.Delegates.Ctor>.FromAddress((nint)AgentModule.MemberFunctionPointers.Ctor, this.OnAgentModuleInitialize);
|
||||
this.onInitializeAgentsHook.Enable();
|
||||
}
|
||||
else
|
||||
{
|
||||
// For safety because this might be injected async, we will make sure we are on the main thread first.
|
||||
this.framework.RunOnFrameworkThread(() => this.ReplaceVirtualTables(agentModuleInstance));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all AgentLifecycle Event Listeners.
|
||||
/// </summary> <br/>
|
||||
/// Mapping is: EventType -> ListenerList
|
||||
internal Dictionary<AgentEvent, Dictionary<AgentId, HashSet<AgentLifecycleEventListener>>> EventListeners { get; } = [];
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.onInitializeAgentsHook?.Dispose();
|
||||
this.onInitializeAgentsHook = null;
|
||||
|
||||
AllocatedTables.ForEach(entry => entry.Dispose());
|
||||
AllocatedTables.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a listener for the target event and agent.
|
||||
/// </summary>
|
||||
/// <param name="listener">The listener to register.</param>
|
||||
internal void RegisterListener(AgentLifecycleEventListener listener)
|
||||
{
|
||||
this.framework.RunOnTick(() =>
|
||||
{
|
||||
if (!this.EventListeners.ContainsKey(listener.EventType))
|
||||
{
|
||||
if (!this.EventListeners.TryAdd(listener.EventType, []))
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: uint.MaxValue is a valid agent id, as that will trigger on any agent for this event type
|
||||
if (!this.EventListeners[listener.EventType].ContainsKey(listener.AgentId))
|
||||
{
|
||||
if (!this.EventListeners[listener.EventType].TryAdd(listener.AgentId, []))
|
||||
return;
|
||||
}
|
||||
|
||||
this.EventListeners[listener.EventType][listener.AgentId].Add(listener);
|
||||
},
|
||||
delayTicks: this.isInvokingListeners ? 1 : 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters the listener from events.
|
||||
/// </summary>
|
||||
/// <param name="listener">The listener to unregister.</param>
|
||||
internal void UnregisterListener(AgentLifecycleEventListener listener)
|
||||
{
|
||||
this.framework.RunOnTick(() =>
|
||||
{
|
||||
if (this.EventListeners.TryGetValue(listener.EventType, out var agentListeners))
|
||||
{
|
||||
if (agentListeners.TryGetValue(listener.AgentId, out var agentListener))
|
||||
{
|
||||
agentListener.Remove(listener);
|
||||
}
|
||||
}
|
||||
},
|
||||
delayTicks: this.isInvokingListeners ? 1 : 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoke listeners for the specified event type.
|
||||
/// </summary>
|
||||
/// <param name="eventType">Event Type.</param>
|
||||
/// <param name="args">AgentARgs.</param>
|
||||
/// <param name="blame">What to blame on errors.</param>
|
||||
internal void InvokeListenersSafely(AgentEvent eventType, AgentArgs args, [CallerMemberName] string blame = "")
|
||||
{
|
||||
this.isInvokingListeners = true;
|
||||
|
||||
// Early return if we don't have any listeners of this type
|
||||
if (!this.EventListeners.TryGetValue(eventType, out var agentListeners)) return;
|
||||
|
||||
// Handle listeners for this event type that don't care which agent is triggering it
|
||||
if (agentListeners.TryGetValue((AgentId)uint.MaxValue, out var globalListeners))
|
||||
{
|
||||
foreach (var listener in globalListeners)
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.FunctionDelegate.Invoke(eventType, args);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"Exception in {blame} during {eventType} invoke, for global agent event listener.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle listeners that are listening for this agent and event type specifically
|
||||
if (agentListeners.TryGetValue(args.AgentId, out var agentListener))
|
||||
{
|
||||
foreach (var listener in agentListener)
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.FunctionDelegate.Invoke(eventType, args);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"Exception in {blame} during {eventType} invoke, for specific agent {args.AgentId}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.isInvokingListeners = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a virtual table address to the original virtual table address.
|
||||
/// </summary>
|
||||
/// <param name="tableAddress">The modified address to resolve.</param>
|
||||
/// <returns>The original address.</returns>
|
||||
internal AgentInterface.AgentInterfaceVirtualTable* GetOriginalVirtualTable(AgentInterface.AgentInterfaceVirtualTable* tableAddress)
|
||||
{
|
||||
var matchedTable = AllocatedTables.FirstOrDefault(table => table.ModifiedVirtualTable == tableAddress);
|
||||
if (matchedTable == null) return null;
|
||||
|
||||
return matchedTable.OriginalVirtualTable;
|
||||
}
|
||||
|
||||
private void OnAgentModuleInitialize(AgentModule* thisPtr, UIModule* uiModule)
|
||||
{
|
||||
this.onInitializeAgentsHook!.Original(thisPtr, uiModule);
|
||||
|
||||
try
|
||||
{
|
||||
this.ReplaceVirtualTables(thisPtr);
|
||||
|
||||
// We don't need this hook anymore, it did its job!
|
||||
this.onInitializeAgentsHook!.Dispose();
|
||||
this.onInitializeAgentsHook = null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in AgentLifecycle during AgentModule Ctor.");
|
||||
}
|
||||
}
|
||||
|
||||
private void ReplaceVirtualTables(AgentModule* agentModule)
|
||||
{
|
||||
foreach (uint index in Enumerable.Range(0, agentModule->Agents.Length))
|
||||
{
|
||||
try
|
||||
{
|
||||
var agentPointer = agentModule->Agents.GetPointer((int)index);
|
||||
|
||||
if (agentPointer is null)
|
||||
{
|
||||
Log.Warning("Null Agent Found?");
|
||||
continue;
|
||||
}
|
||||
|
||||
// AgentVirtualTable class handles creating the virtual table, and overriding each of the tracked virtual functions
|
||||
AllocatedTables.Add(new AgentVirtualTable(agentPointer->Value, (AgentId)index, this));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in AgentLifecycle during ReplaceVirtualTables.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin-scoped version of a AgentLifecycle service.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IAgentLifecycle>]
|
||||
#pragma warning restore SA1015
|
||||
internal class AgentLifecyclePluginScoped : IInternalDisposableService, IAgentLifecycle
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly AgentLifecycle agentLifecycleService = Service<AgentLifecycle>.Get();
|
||||
|
||||
private readonly List<AgentLifecycleEventListener> eventListeners = [];
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
foreach (var listener in this.eventListeners)
|
||||
{
|
||||
this.agentLifecycleService.UnregisterListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RegisterListener(AgentEvent eventType, IEnumerable<AgentId> agentIds, IAgentLifecycle.AgentEventDelegate handler)
|
||||
{
|
||||
foreach (var agentId in agentIds)
|
||||
{
|
||||
this.RegisterListener(eventType, agentId, handler);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RegisterListener(AgentEvent eventType, AgentId agentId, IAgentLifecycle.AgentEventDelegate handler)
|
||||
{
|
||||
var listener = new AgentLifecycleEventListener(eventType, agentId, handler);
|
||||
this.eventListeners.Add(listener);
|
||||
this.agentLifecycleService.RegisterListener(listener);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RegisterListener(AgentEvent eventType, IAgentLifecycle.AgentEventDelegate handler)
|
||||
{
|
||||
this.RegisterListener(eventType, (AgentId)uint.MaxValue, handler);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UnregisterListener(AgentEvent eventType, IEnumerable<AgentId> agentIds, IAgentLifecycle.AgentEventDelegate? handler = null)
|
||||
{
|
||||
foreach (var agentId in agentIds)
|
||||
{
|
||||
this.UnregisterListener(eventType, agentId, handler);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UnregisterListener(AgentEvent eventType, AgentId agentId, IAgentLifecycle.AgentEventDelegate? handler = null)
|
||||
{
|
||||
this.eventListeners.RemoveAll(entry =>
|
||||
{
|
||||
if (entry.EventType != eventType) return false;
|
||||
if (entry.AgentId != agentId) return false;
|
||||
if (handler is not null && entry.FunctionDelegate != handler) return false;
|
||||
|
||||
this.agentLifecycleService.UnregisterListener(entry);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UnregisterListener(AgentEvent eventType, IAgentLifecycle.AgentEventDelegate? handler = null)
|
||||
{
|
||||
this.UnregisterListener(eventType, (AgentId)uint.MaxValue, handler);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UnregisterListener(params IAgentLifecycle.AgentEventDelegate[] handlers)
|
||||
{
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
this.eventListeners.RemoveAll(entry =>
|
||||
{
|
||||
if (entry.FunctionDelegate != handler) return false;
|
||||
|
||||
this.agentLifecycleService.UnregisterListener(entry);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe nint GetOriginalVirtualTable(nint virtualTableAddress)
|
||||
=> (nint)this.agentLifecycleService.GetOriginalVirtualTable((AgentInterface.AgentInterfaceVirtualTable*)virtualTableAddress);
|
||||
}
|
||||
38
Dalamud/Game/Agent/AgentLifecycleEventListener.cs
Normal file
38
Dalamud/Game/Agent/AgentLifecycleEventListener.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace Dalamud.Game.Agent;
|
||||
|
||||
/// <summary>
|
||||
/// This class is a helper for tracking and invoking listener delegates.
|
||||
/// </summary>
|
||||
public class AgentLifecycleEventListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentLifecycleEventListener"/> class.
|
||||
/// </summary>
|
||||
/// <param name="eventType">Event type to listen for.</param>
|
||||
/// <param name="agentId">Agent id to listen for.</param>
|
||||
/// <param name="functionDelegate">Delegate to invoke.</param>
|
||||
internal AgentLifecycleEventListener(AgentEvent eventType, AgentId agentId, IAgentLifecycle.AgentEventDelegate functionDelegate)
|
||||
{
|
||||
this.EventType = eventType;
|
||||
this.AgentId = agentId;
|
||||
this.FunctionDelegate = functionDelegate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the agentId of the agent this listener is looking for.
|
||||
/// uint.MaxValue if it wants to be called for any agent.
|
||||
/// </summary>
|
||||
public AgentId AgentId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event type this listener is looking for.
|
||||
/// </summary>
|
||||
public AgentEvent EventType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the delegate this listener invokes.
|
||||
/// </summary>
|
||||
public IAgentLifecycle.AgentEventDelegate FunctionDelegate { get; init; }
|
||||
}
|
||||
393
Dalamud/Game/Agent/AgentVirtualTable.cs
Normal file
393
Dalamud/Game/Agent/AgentVirtualTable.cs
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
using Dalamud.Game.Agent.AgentArgTypes;
|
||||
using Dalamud.Logging.Internal;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Memory;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.Agent;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a class that holds references to an agents original and modified virtual table entries.
|
||||
/// </summary>
|
||||
internal unsafe class AgentVirtualTable : IDisposable
|
||||
{
|
||||
// This need to be at minimum the largest virtual table size of all agents
|
||||
// Copying extra entries is not problematic, and is considered safe.
|
||||
private const int VirtualTableEntryCount = 60;
|
||||
|
||||
private const bool EnableLogging = true;
|
||||
|
||||
private static readonly ModuleLog Log = new("AgentVT");
|
||||
|
||||
private readonly AgentLifecycle lifecycleService;
|
||||
|
||||
private readonly AgentId agentId;
|
||||
|
||||
// Each agent gets its own set of args that are used to mutate the original call when used in pre-calls
|
||||
private readonly AgentReceiveEventArgs receiveEventArgs = new();
|
||||
private readonly AgentReceiveEventArgs filteredReceiveEventArgs = new();
|
||||
private readonly AgentArgs showArgs = new();
|
||||
private readonly AgentArgs hideArgs = new();
|
||||
private readonly AgentArgs updateArgs = new();
|
||||
private readonly AgentGameEventArgs gameEventArgs = new();
|
||||
private readonly AgentLevelChangeArgs levelChangeArgs = new();
|
||||
private readonly AgentClassJobChangeArgs classJobChangeArgs = new();
|
||||
|
||||
private readonly AgentInterface* agentInterface;
|
||||
|
||||
// Pinned Function Delegates, as these functions get assigned to an unmanaged virtual table,
|
||||
// the CLR needs to know they are in use, or it will invalidate them causing random crashing.
|
||||
private readonly AgentInterface.Delegates.ReceiveEvent receiveEventFunction;
|
||||
private readonly AgentInterface.Delegates.ReceiveEvent2 filteredReceiveEventFunction;
|
||||
private readonly AgentInterface.Delegates.Show showFunction;
|
||||
private readonly AgentInterface.Delegates.Hide hideFunction;
|
||||
private readonly AgentInterface.Delegates.Update updateFunction;
|
||||
private readonly AgentInterface.Delegates.OnGameEvent gameEventFunction;
|
||||
private readonly AgentInterface.Delegates.OnLevelChange levelChangeFunction;
|
||||
private readonly AgentInterface.Delegates.OnClassJobChange classJobChangeFunction;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentVirtualTable"/> class.
|
||||
/// </summary>
|
||||
/// <param name="agent">AgentInterface* for the agent to replace the table of.</param>
|
||||
/// <param name="agentId">Agent ID.</param>
|
||||
/// <param name="lifecycleService">Reference to AgentLifecycle service to callback and invoke listeners.</param>
|
||||
internal AgentVirtualTable(AgentInterface* agent, AgentId agentId, AgentLifecycle lifecycleService)
|
||||
{
|
||||
Log.Debug($"Initializing AgentVirtualTable for {agentId}, Address: {(nint)agent:X}");
|
||||
|
||||
this.agentInterface = agent;
|
||||
this.agentId = agentId;
|
||||
this.lifecycleService = lifecycleService;
|
||||
|
||||
// Save original virtual table
|
||||
this.OriginalVirtualTable = agent->VirtualTable;
|
||||
|
||||
// Create copy of original table
|
||||
// Note this will copy any derived/overriden functions that this specific agent has.
|
||||
// Note: currently there are 16 virtual functions, but there's no harm in copying more for when they add new virtual functions to the game
|
||||
this.ModifiedVirtualTable = (AgentInterface.AgentInterfaceVirtualTable*)IMemorySpace.GetUISpace()->Malloc(0x8 * VirtualTableEntryCount, 8);
|
||||
NativeMemory.Copy(agent->VirtualTable, this.ModifiedVirtualTable, 0x8 * VirtualTableEntryCount);
|
||||
|
||||
// Overwrite the agents existing virtual table with our own
|
||||
agent->VirtualTable = this.ModifiedVirtualTable;
|
||||
|
||||
// Pin each of our listener functions
|
||||
this.receiveEventFunction = this.OnAgentReceiveEvent;
|
||||
this.filteredReceiveEventFunction = this.OnAgentFilteredReceiveEvent;
|
||||
this.showFunction = this.OnAgentShow;
|
||||
this.hideFunction = this.OnAgentHide;
|
||||
this.updateFunction = this.OnAgentUpdate;
|
||||
this.gameEventFunction = this.OnAgentGameEvent;
|
||||
this.levelChangeFunction = this.OnAgentLevelChange;
|
||||
this.classJobChangeFunction = this.OnClassJobChange;
|
||||
|
||||
// Overwrite specific virtual table entries
|
||||
this.ModifiedVirtualTable->ReceiveEvent = (delegate* unmanaged<AgentInterface*, AtkValue*, AtkValue*, uint, ulong, AtkValue*>)Marshal.GetFunctionPointerForDelegate(this.receiveEventFunction);
|
||||
this.ModifiedVirtualTable->ReceiveEvent2 = (delegate* unmanaged<AgentInterface*, AtkValue*, AtkValue*, uint, ulong, AtkValue*>)Marshal.GetFunctionPointerForDelegate(this.filteredReceiveEventFunction);
|
||||
this.ModifiedVirtualTable->Show = (delegate* unmanaged<AgentInterface*, void>)Marshal.GetFunctionPointerForDelegate(this.showFunction);
|
||||
this.ModifiedVirtualTable->Hide = (delegate* unmanaged<AgentInterface*, void>)Marshal.GetFunctionPointerForDelegate(this.hideFunction);
|
||||
this.ModifiedVirtualTable->Update = (delegate* unmanaged<AgentInterface*, uint, void>)Marshal.GetFunctionPointerForDelegate(this.updateFunction);
|
||||
this.ModifiedVirtualTable->OnGameEvent = (delegate* unmanaged<AgentInterface*, AgentInterface.GameEvent, void>)Marshal.GetFunctionPointerForDelegate(this.gameEventFunction);
|
||||
this.ModifiedVirtualTable->OnLevelChange = (delegate* unmanaged<AgentInterface*, byte, ushort, void>)Marshal.GetFunctionPointerForDelegate(this.levelChangeFunction);
|
||||
this.ModifiedVirtualTable->OnClassJobChange = (delegate* unmanaged<AgentInterface*, byte, void>)Marshal.GetFunctionPointerForDelegate(this.classJobChangeFunction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the original virtual table address for this agent.
|
||||
/// </summary>
|
||||
internal AgentInterface.AgentInterfaceVirtualTable* OriginalVirtualTable { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the modified virtual address for this agent.
|
||||
/// </summary>
|
||||
internal AgentInterface.AgentInterfaceVirtualTable* ModifiedVirtualTable { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
// Ensure restoration is done atomically.
|
||||
Interlocked.Exchange(ref *(nint*)&this.agentInterface->VirtualTable, (nint)this.OriginalVirtualTable);
|
||||
IMemorySpace.Free(this.ModifiedVirtualTable, 0x8 * VirtualTableEntryCount);
|
||||
}
|
||||
|
||||
private AtkValue* OnAgentReceiveEvent(AgentInterface* thisPtr, AtkValue* returnValue, AtkValue* values, uint valueCount, ulong eventKind)
|
||||
{
|
||||
AtkValue* result = null;
|
||||
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.receiveEventArgs.Agent = thisPtr;
|
||||
this.receiveEventArgs.AgentId = this.agentId;
|
||||
this.receiveEventArgs.ReturnValue = (nint)returnValue;
|
||||
this.receiveEventArgs.AtkValues = (nint)values;
|
||||
this.receiveEventArgs.ValueCount = valueCount;
|
||||
this.receiveEventArgs.EventKind = eventKind;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreReceiveEvent, this.receiveEventArgs);
|
||||
|
||||
returnValue = (AtkValue*)this.receiveEventArgs.ReturnValue;
|
||||
values = (AtkValue*)this.receiveEventArgs.AtkValues;
|
||||
valueCount = this.receiveEventArgs.ValueCount;
|
||||
eventKind = this.receiveEventArgs.EventKind;
|
||||
|
||||
try
|
||||
{
|
||||
result = this.OriginalVirtualTable->ReceiveEvent(thisPtr, returnValue, values, valueCount, eventKind);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Agent ReceiveEvent. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostReceiveEvent, this.receiveEventArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentReceiveEvent.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private AtkValue* OnAgentFilteredReceiveEvent(AgentInterface* thisPtr, AtkValue* returnValue, AtkValue* values, uint valueCount, ulong eventKind)
|
||||
{
|
||||
AtkValue* result = null;
|
||||
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.filteredReceiveEventArgs.Agent = thisPtr;
|
||||
this.filteredReceiveEventArgs.AgentId = this.agentId;
|
||||
this.filteredReceiveEventArgs.ReturnValue = (nint)returnValue;
|
||||
this.filteredReceiveEventArgs.AtkValues = (nint)values;
|
||||
this.filteredReceiveEventArgs.ValueCount = valueCount;
|
||||
this.filteredReceiveEventArgs.EventKind = eventKind;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreReceiveFilteredEvent, this.filteredReceiveEventArgs);
|
||||
|
||||
returnValue = (AtkValue*)this.filteredReceiveEventArgs.ReturnValue;
|
||||
values = (AtkValue*)this.filteredReceiveEventArgs.AtkValues;
|
||||
valueCount = this.filteredReceiveEventArgs.ValueCount;
|
||||
eventKind = this.filteredReceiveEventArgs.EventKind;
|
||||
|
||||
try
|
||||
{
|
||||
result = this.OriginalVirtualTable->ReceiveEvent2(thisPtr, returnValue, values, valueCount, eventKind);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Agent FilteredReceiveEvent. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostReceiveFilteredEvent, this.filteredReceiveEventArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentFilteredReceiveEvent.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void OnAgentShow(AgentInterface* thisPtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.showArgs.Agent = thisPtr;
|
||||
this.showArgs.AgentId = this.agentId;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreShow, this.showArgs);
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->Show(thisPtr);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon Show. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostShow, this.showArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentShow.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAgentHide(AgentInterface* thisPtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.hideArgs.Agent = thisPtr;
|
||||
this.hideArgs.AgentId = this.agentId;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreHide, this.hideArgs);
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->Hide(thisPtr);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon Hide. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostHide, this.hideArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentHide.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAgentUpdate(AgentInterface* thisPtr, uint frameCount)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.updateArgs.Agent = thisPtr;
|
||||
this.updateArgs.AgentId = this.agentId;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreUpdate, this.updateArgs);
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->Update(thisPtr, frameCount);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon Update. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostUpdate, this.updateArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentUpdate.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAgentGameEvent(AgentInterface* thisPtr, AgentInterface.GameEvent gameEvent)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.gameEventArgs.Agent = thisPtr;
|
||||
this.gameEventArgs.AgentId = this.agentId;
|
||||
this.gameEventArgs.GameEvent = (int)gameEvent;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreGameEvent, this.gameEventArgs);
|
||||
|
||||
gameEvent = (AgentInterface.GameEvent)this.gameEventArgs.GameEvent;
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->OnGameEvent(thisPtr, gameEvent);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon OnGameEvent. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostGameEvent, this.gameEventArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentGameEvent.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAgentLevelChange(AgentInterface* thisPtr, byte classJobId, ushort level)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.levelChangeArgs.Agent = thisPtr;
|
||||
this.levelChangeArgs.AgentId = this.agentId;
|
||||
this.levelChangeArgs.ClassJobId = classJobId;
|
||||
this.levelChangeArgs.Level = level;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreLevelChange, this.levelChangeArgs);
|
||||
|
||||
classJobId = this.levelChangeArgs.ClassJobId;
|
||||
level = this.levelChangeArgs.Level;
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->OnLevelChange(thisPtr, classJobId, level);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon OnLevelChange. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostLevelChange, this.levelChangeArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentLevelChange.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnClassJobChange(AgentInterface* thisPtr, byte classJobId)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogEvent(EnableLogging);
|
||||
|
||||
this.classJobChangeArgs.Agent = thisPtr;
|
||||
this.classJobChangeArgs.AgentId = this.agentId;
|
||||
this.classJobChangeArgs.ClassJobId = classJobId;
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreClassJobChange, this.classJobChangeArgs);
|
||||
|
||||
classJobId = this.classJobChangeArgs.ClassJobId;
|
||||
|
||||
try
|
||||
{
|
||||
this.OriginalVirtualTable->OnClassJobChange(thisPtr, classJobId);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original Addon OnClassJobChange. This may be a bug in the game or another plugin hooking this method.");
|
||||
}
|
||||
|
||||
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostClassJobChange, this.classJobChangeArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception from Dalamud when attempting to process OnClassJobChange.");
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private void LogEvent(bool loggingEnabled, [CallerMemberName] string caller = "")
|
||||
{
|
||||
if (loggingEnabled)
|
||||
{
|
||||
// Manually disable the really spammy log events, you can comment this out if you need to debug them.
|
||||
if (caller is "OnAgentUpdate" || this.agentId is AgentId.PadMouseMode)
|
||||
return;
|
||||
|
||||
Log.Debug($"[{caller}]: {this.agentId}");
|
||||
}
|
||||
}
|
||||
}
|
||||
88
Dalamud/Plugin/Services/IAgentLifecycle.cs
Normal file
88
Dalamud/Plugin/Services/IAgentLifecycle.cs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Game.Agent;
|
||||
using Dalamud.Game.Agent.AgentArgTypes;
|
||||
|
||||
namespace Dalamud.Plugin.Services;
|
||||
|
||||
/// <summary>
|
||||
/// This class provides events for in-game agent lifecycles.
|
||||
/// </summary>
|
||||
public interface IAgentLifecycle : IDalamudService
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate for receiving agent lifecycle event messages.
|
||||
/// </summary>
|
||||
/// <param name="type">The event type that triggered the message.</param>
|
||||
/// <param name="args">Information about what agent triggered the message.</param>
|
||||
public delegate void AgentEventDelegate(AgentEvent type, AgentArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// Register a listener that will trigger on the specified event and any of the specified agent.
|
||||
/// </summary>
|
||||
/// <param name="eventType">Event type to trigger on.</param>
|
||||
/// <param name="agentIds">Agent IDs that will trigger the handler to be invoked.</param>
|
||||
/// <param name="handler">The handler to invoke.</param>
|
||||
void RegisterListener(AgentEvent eventType, IEnumerable<AgentId> agentIds, AgentEventDelegate handler);
|
||||
|
||||
/// <summary>
|
||||
/// Register a listener that will trigger on the specified event only for the specified agent.
|
||||
/// </summary>
|
||||
/// <param name="eventType">Event type to trigger on.</param>
|
||||
/// <param name="agentId">The agent ID that will trigger the handler to be invoked.</param>
|
||||
/// <param name="handler">The handler to invoke.</param>
|
||||
void RegisterListener(AgentEvent eventType, AgentId agentId, AgentEventDelegate handler);
|
||||
|
||||
/// <summary>
|
||||
/// Register a listener that will trigger on the specified event for any agent.
|
||||
/// </summary>
|
||||
/// <param name="eventType">Event type to trigger on.</param>
|
||||
/// <param name="handler">The handler to invoke.</param>
|
||||
void RegisterListener(AgentEvent eventType, AgentEventDelegate handler);
|
||||
|
||||
/// <summary>
|
||||
/// Unregister listener from specified event type and specified agent IDs.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If a specific handler is not provided, all handlers for the event type and agent IDs will be unregistered.
|
||||
/// </remarks>
|
||||
/// <param name="eventType">Event type to deregister.</param>
|
||||
/// <param name="agentIds">Agent IDs to deregister.</param>
|
||||
/// <param name="handler">Optional specific handler to remove.</param>
|
||||
void UnregisterListener(AgentEvent eventType, IEnumerable<AgentId> agentIds, [Optional] AgentEventDelegate handler);
|
||||
|
||||
/// <summary>
|
||||
/// Unregister all listeners for the specified event type and agent ID.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If a specific handler is not provided, all handlers for the event type and agents will be unregistered.
|
||||
/// </remarks>
|
||||
/// <param name="eventType">Event type to deregister.</param>
|
||||
/// <param name="agentId">Agent id to deregister.</param>
|
||||
/// <param name="handler">Optional specific handler to remove.</param>
|
||||
void UnregisterListener(AgentEvent eventType, AgentId agentId, [Optional] AgentEventDelegate handler);
|
||||
|
||||
/// <summary>
|
||||
/// Unregister an event type handler.<br/>This will only remove a handler that is added via <see cref="RegisterListener(AgentEvent, AgentEventDelegate)"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If a specific handler is not provided, all handlers for the event type and agents will be unregistered.
|
||||
/// </remarks>
|
||||
/// <param name="eventType">Event type to deregister.</param>
|
||||
/// <param name="handler">Optional specific handler to remove.</param>
|
||||
void UnregisterListener(AgentEvent eventType, [Optional] AgentEventDelegate handler);
|
||||
|
||||
/// <summary>
|
||||
/// Unregister all events that use the specified handlers.
|
||||
/// </summary>
|
||||
/// <param name="handlers">Handlers to remove.</param>
|
||||
void UnregisterListener(params AgentEventDelegate[] handlers);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves an agents virtual table address back to the original unmodified table address.
|
||||
/// </summary>
|
||||
/// <param name="virtualTableAddress">The address of a modified agents virtual table.</param>
|
||||
/// <returns>The address of the agents original virtual table.</returns>
|
||||
nint GetOriginalVirtualTable(nint virtualTableAddress);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue