mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
merge master
This commit is contained in:
commit
af52da06b0
30 changed files with 827 additions and 301 deletions
|
|
@ -1,4 +1,4 @@
|
|||
namespace Dalamud.Game.AddonEventManager;
|
||||
namespace Dalamud.Game.Addon;
|
||||
|
||||
/// <summary>
|
||||
/// Reimplementation of CursorType.
|
||||
|
|
|
|||
61
Dalamud/Game/AddonEventManager/AddonEventEntry.cs
Normal file
61
Dalamud/Game/AddonEventManager/AddonEventEntry.cs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
using System;
|
||||
|
||||
using Dalamud.Memory;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.Addon;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a registered event that a plugin registers with a native ui node.
|
||||
/// Contains all necessary information to track and clean up events automatically.
|
||||
/// </summary>
|
||||
internal unsafe class AddonEventEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of an invalid addon.
|
||||
/// </summary>
|
||||
public const string InvalidAddonName = "NullAddon";
|
||||
|
||||
private string? addonName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the addons AtkUnitBase.
|
||||
/// </summary>
|
||||
required public nint Addon { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the addon this args referrers to.
|
||||
/// </summary>
|
||||
public string AddonName => this.Addon == nint.Zero ? InvalidAddonName : this.addonName ??= MemoryHelper.ReadString((nint)((AtkUnitBase*)this.Addon)->Name, 0x20);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the event source.
|
||||
/// </summary>
|
||||
required public nint Node { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the handler that gets called when this event is triggered.
|
||||
/// </summary>
|
||||
required public IAddonEventManager.AddonEventHandler Handler { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique id for this event.
|
||||
/// </summary>
|
||||
required public uint ParamKey { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event type for this event.
|
||||
/// </summary>
|
||||
required public AddonEventType EventType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event handle for this event.
|
||||
/// </summary>
|
||||
required internal IAddonEventHandle Handle { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the formatted log string for this AddonEventEntry.
|
||||
/// </summary>
|
||||
internal string LogString => $"ParamKey: {this.ParamKey}, Addon: {this.AddonName}, Event: {this.EventType}, GUID: {this.Handle.EventGuid}";
|
||||
}
|
||||
21
Dalamud/Game/AddonEventManager/AddonEventHandle.cs
Normal file
21
Dalamud/Game/AddonEventManager/AddonEventHandle.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Addon;
|
||||
|
||||
/// <summary>
|
||||
/// Class that represents a addon event handle.
|
||||
/// </summary>
|
||||
public class AddonEventHandle : IAddonEventHandle
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public uint ParamKey { get; init; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string AddonName { get; init; } = "NullAddon";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AddonEventType EventType { get; init; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Guid EventGuid { get; init; }
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ using System.Runtime.InteropServices;
|
|||
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.AddonEventManager;
|
||||
namespace Dalamud.Game.Addon;
|
||||
|
||||
/// <summary>
|
||||
/// Event listener class for managing custom events.
|
||||
|
|
@ -40,6 +40,11 @@ internal unsafe class AddonEventListener : IDisposable
|
|||
/// <param name="unknown">Unknown Parameter.</param>
|
||||
public delegate void ReceiveEventDelegate(AtkEventListener* self, AtkEventType eventType, uint eventParam, AtkEvent* eventData, nint unknown);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of this listener.
|
||||
/// </summary>
|
||||
public nint Address => (nint)this.eventListener;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,30 +1,41 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.AddonEventManager;
|
||||
namespace Dalamud.Game.Addon;
|
||||
|
||||
/// <summary>
|
||||
/// Service provider for addon event management.
|
||||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal unsafe class AddonEventManager : IDisposable, IServiceType, IAddonEventManager
|
||||
internal unsafe class AddonEventManager : IDisposable, IServiceType
|
||||
{
|
||||
/// <summary>
|
||||
/// PluginName for Dalamud Internal use.
|
||||
/// </summary>
|
||||
public const string DalamudInternalKey = "Dalamud.Internal";
|
||||
|
||||
private static readonly ModuleLog Log = new("AddonEventManager");
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly AddonLifecycle addonLifecycle = Service<AddonLifecycle>.Get();
|
||||
|
||||
private readonly AddonLifecycleEventListener finalizeEventListener;
|
||||
|
||||
private readonly AddonEventManagerAddressResolver address;
|
||||
private readonly Hook<UpdateCursorDelegate> onUpdateCursor;
|
||||
|
||||
private readonly AddonEventListener eventListener;
|
||||
private readonly Dictionary<uint, IAddonEventManager.AddonEventHandler> eventHandlers;
|
||||
private readonly List<PluginEventController> pluginEventControllers;
|
||||
|
||||
private AddonCursorType? cursorOverride;
|
||||
|
||||
|
|
@ -34,64 +45,108 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType, IAddonEvent
|
|||
this.address = new AddonEventManagerAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.eventHandlers = new Dictionary<uint, IAddonEventManager.AddonEventHandler>();
|
||||
this.eventListener = new AddonEventListener(this.DalamudAddonEventHandler);
|
||||
this.pluginEventControllers = new List<PluginEventController>
|
||||
{
|
||||
new(DalamudInternalKey), // Create entry for Dalamud's Internal Use.
|
||||
};
|
||||
|
||||
this.cursorOverride = null;
|
||||
|
||||
this.onUpdateCursor = Hook<UpdateCursorDelegate>.FromAddress(this.address.UpdateCursor, this.UpdateCursorDetour);
|
||||
|
||||
this.finalizeEventListener = new AddonLifecycleEventListener(AddonEvent.PreFinalize, string.Empty, this.OnAddonFinalize);
|
||||
this.addonLifecycle.RegisterListener(this.finalizeEventListener);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
foreach (var pluginEventController in this.pluginEventControllers)
|
||||
{
|
||||
pluginEventController.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetCursor(AddonCursorType cursor) => this.cursorOverride = cursor;
|
||||
this.addonLifecycle.UnregisterListener(this.finalizeEventListener);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ResetCursor() => this.cursorOverride = null;
|
||||
/// <summary>
|
||||
/// Registers an event handler for the specified addon, node, and type.
|
||||
/// </summary>
|
||||
/// <param name="pluginId">Unique ID for this plugin.</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>
|
||||
/// <returns>IAddonEventHandle used to remove the event.</returns>
|
||||
internal IAddonEventHandle? AddEvent(string pluginId, IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
|
||||
{
|
||||
if (this.pluginEventControllers.FirstOrDefault(entry => entry.PluginId == pluginId) is { } eventController)
|
||||
{
|
||||
return eventController.AddEvent(atkUnitBase, atkResNode, eventType, eventHandler);
|
||||
}
|
||||
|
||||
Log.Verbose($"Unable to locate controller for {pluginId}. No event was added.");
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters an event handler with the specified event id and event type.
|
||||
/// </summary>
|
||||
/// <param name="pluginId">Unique ID for this plugin.</param>
|
||||
/// <param name="eventHandle">The Unique Id for this event.</param>
|
||||
internal void RemoveEvent(string pluginId, IAddonEventHandle eventHandle)
|
||||
{
|
||||
if (this.pluginEventControllers.FirstOrDefault(entry => entry.PluginId == pluginId) is { } eventController)
|
||||
{
|
||||
eventController.RemoveEvent(eventHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Verbose($"Unable to locate controller for {pluginId}. No event was removed.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force the game cursor to be the specified cursor.
|
||||
/// </summary>
|
||||
/// <param name="cursor">Which cursor to use.</param>
|
||||
internal void SetCursor(AddonCursorType cursor) => this.cursorOverride = cursor;
|
||||
|
||||
/// <summary>
|
||||
/// Un-forces the game cursor.
|
||||
/// </summary>
|
||||
internal void ResetCursor() => this.cursorOverride = null;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new managed event controller if one doesn't already exist for this pluginId.
|
||||
/// </summary>
|
||||
/// <param name="pluginId">Unique ID for this plugin.</param>
|
||||
internal void AddPluginEventController(string pluginId)
|
||||
{
|
||||
if (this.pluginEventControllers.All(entry => entry.PluginId != pluginId))
|
||||
{
|
||||
Log.Verbose($"Creating new PluginEventController for: {pluginId}");
|
||||
this.pluginEventControllers.Add(new PluginEventController(pluginId));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an existing managed event controller for the specified plugin.
|
||||
/// </summary>
|
||||
/// <param name="pluginId">Unique ID for this plugin.</param>
|
||||
internal void RemovePluginEventController(string pluginId)
|
||||
{
|
||||
if (this.pluginEventControllers.FirstOrDefault(entry => entry.PluginId == pluginId) is { } controller)
|
||||
{
|
||||
Log.Verbose($"Removing PluginEventController for: {pluginId}");
|
||||
this.pluginEventControllers.Remove(controller);
|
||||
controller.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[ServiceManager.CallWhenServicesReady]
|
||||
private void ContinueConstruction()
|
||||
|
|
@ -99,6 +154,22 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType, IAddonEvent
|
|||
this.onUpdateCursor.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When an addon finalizes, check it for any registered events, and unregister them.
|
||||
/// </summary>
|
||||
/// <param name="eventType">Event type that triggered this call.</param>
|
||||
/// <param name="addonInfo">Addon that triggered this call.</param>
|
||||
private void OnAddonFinalize(AddonEvent eventType, AddonArgs addonInfo)
|
||||
{
|
||||
// It shouldn't be possible for this event to be anything other than PreFinalize.
|
||||
if (eventType != AddonEvent.PreFinalize) return;
|
||||
|
||||
foreach (var pluginList in this.pluginEventControllers)
|
||||
{
|
||||
pluginList.RemoveForAddon(addonInfo.AddonName);
|
||||
}
|
||||
}
|
||||
|
||||
private nint UpdateCursorDetour(RaptureAtkModule* module)
|
||||
{
|
||||
try
|
||||
|
|
@ -123,22 +194,6 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType, IAddonEvent
|
|||
|
||||
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>
|
||||
|
|
@ -150,25 +205,24 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType, IAddonEvent
|
|||
#pragma warning disable SA1015
|
||||
[ResolveVia<IAddonEventManager>]
|
||||
#pragma warning restore SA1015
|
||||
internal unsafe class AddonEventManagerPluginScoped : IDisposable, IServiceType, IAddonEventManager
|
||||
internal class AddonEventManagerPluginScoped : IDisposable, IServiceType, IAddonEventManager
|
||||
{
|
||||
private static readonly ModuleLog Log = new("AddonEventManager");
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly AddonEventManager baseEventManager = Service<AddonEventManager>.Get();
|
||||
private readonly AddonEventManager eventManagerService = Service<AddonEventManager>.Get();
|
||||
|
||||
private readonly AddonEventListener eventListener;
|
||||
private readonly Dictionary<uint, IAddonEventManager.AddonEventHandler> eventHandlers;
|
||||
private readonly LocalPlugin plugin;
|
||||
|
||||
private bool isForcingCursor;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonEventManagerPluginScoped"/> class.
|
||||
/// </summary>
|
||||
public AddonEventManagerPluginScoped()
|
||||
/// <param name="plugin">Plugin info for the plugin that requested this service.</param>
|
||||
public AddonEventManagerPluginScoped(LocalPlugin plugin)
|
||||
{
|
||||
this.eventHandlers = new Dictionary<uint, IAddonEventManager.AddonEventHandler>();
|
||||
this.eventListener = new AddonEventListener(this.PluginAddonEventHandler);
|
||||
this.plugin = plugin;
|
||||
|
||||
this.eventManagerService.AddPluginEventController(plugin.Manifest.WorkingPluginId.ToString());
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -177,54 +231,26 @@ internal unsafe class AddonEventManagerPluginScoped : IDisposable, IServiceType,
|
|||
// if multiple plugins force cursors and dispose without un-forcing them then all forces will be cleared.
|
||||
if (this.isForcingCursor)
|
||||
{
|
||||
this.baseEventManager.ResetCursor();
|
||||
this.eventManagerService.ResetCursor();
|
||||
}
|
||||
|
||||
this.eventListener.Dispose();
|
||||
this.eventHandlers.Clear();
|
||||
this.eventManagerService.RemovePluginEventController(this.plugin.Manifest.WorkingPluginId.ToString());
|
||||
}
|
||||
|
||||
/// <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}");
|
||||
}
|
||||
}
|
||||
public IAddonEventHandle? AddEvent(IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
|
||||
=> this.eventManagerService.AddEvent(this.plugin.Manifest.WorkingPluginId.ToString(), atkUnitBase, atkResNode, eventType, eventHandler);
|
||||
|
||||
/// <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}");
|
||||
}
|
||||
}
|
||||
public void RemoveEvent(IAddonEventHandle eventHandle)
|
||||
=> this.eventManagerService.RemoveEvent(this.plugin.Manifest.WorkingPluginId.ToString(), eventHandle);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetCursor(AddonCursorType cursor)
|
||||
{
|
||||
this.isForcingCursor = true;
|
||||
|
||||
this.baseEventManager.SetCursor(cursor);
|
||||
this.eventManagerService.SetCursor(cursor);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -232,22 +258,6 @@ internal unsafe class AddonEventManagerPluginScoped : IDisposable, IServiceType,
|
|||
{
|
||||
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.");
|
||||
}
|
||||
}
|
||||
this.eventManagerService.ResetCursor();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Dalamud.Game.AddonEventManager;
|
||||
namespace Dalamud.Game.Addon;
|
||||
|
||||
/// <summary>
|
||||
/// AddonEventManager memory address resolver.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Dalamud.Game.AddonEventManager;
|
||||
namespace Dalamud.Game.Addon;
|
||||
|
||||
/// <summary>
|
||||
/// Reimplementation of AtkEventType.
|
||||
|
|
|
|||
29
Dalamud/Game/AddonEventManager/IAddonEventHandle.cs
Normal file
29
Dalamud/Game/AddonEventManager/IAddonEventHandle.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Addon;
|
||||
|
||||
/// <summary>
|
||||
/// Interface representing the data used for managing AddonEvents.
|
||||
/// </summary>
|
||||
public interface IAddonEventHandle
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the param key associated with this event.
|
||||
/// </summary>
|
||||
public uint ParamKey { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the addon that this event was attached to.
|
||||
/// </summary>
|
||||
public string AddonName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event type associated with this handle.
|
||||
/// </summary>
|
||||
public AddonEventType EventType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique ID for this handle.
|
||||
/// </summary>
|
||||
public Guid EventGuid { get; init; }
|
||||
}
|
||||
210
Dalamud/Game/AddonEventManager/PluginEventController.cs
Normal file
210
Dalamud/Game/AddonEventManager/PluginEventController.cs
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Memory;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.Addon;
|
||||
|
||||
/// <summary>
|
||||
/// Class to manage creating and cleaning up events per-plugin.
|
||||
/// </summary>
|
||||
internal unsafe class PluginEventController : IDisposable
|
||||
{
|
||||
private static readonly ModuleLog Log = new("AddonEventManager");
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PluginEventController"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pluginId">The Unique ID for this plugin.</param>
|
||||
public PluginEventController(string pluginId)
|
||||
{
|
||||
this.PluginId = pluginId;
|
||||
|
||||
this.EventListener = new AddonEventListener(this.PluginEventListHandler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique ID for this PluginEventList.
|
||||
/// </summary>
|
||||
public string PluginId { get; init; }
|
||||
|
||||
private AddonEventListener EventListener { get; init; }
|
||||
|
||||
private List<AddonEventEntry> Events { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a tracked event.
|
||||
/// </summary>
|
||||
/// <param name="atkUnitBase">The Parent addon for the event.</param>
|
||||
/// <param name="atkResNode">The Node for the event.</param>
|
||||
/// <param name="atkEventType">The Event Type.</param>
|
||||
/// <param name="handler">The delegate to call when invoking this event.</param>
|
||||
/// <returns>IAddonEventHandle used to remove the event.</returns>
|
||||
public IAddonEventHandle AddEvent(nint atkUnitBase, nint atkResNode, AddonEventType atkEventType, IAddonEventManager.AddonEventHandler handler)
|
||||
{
|
||||
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 = MemoryHelper.ReadStringNullTerminated((nint)addon->Name),
|
||||
ParamKey = eventId,
|
||||
EventType = atkEventType,
|
||||
EventGuid = eventGuid,
|
||||
};
|
||||
|
||||
var eventEntry = new AddonEventEntry
|
||||
{
|
||||
Addon = atkUnitBase,
|
||||
Handler = handler,
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a tracked event, also attempts to un-attach the event from native.
|
||||
/// </summary>
|
||||
/// <param name="handle">Unique ID of the event to remove.</param>
|
||||
public void RemoveEvent(IAddonEventHandle handle)
|
||||
{
|
||||
if (this.Events.FirstOrDefault(registeredEvent => registeredEvent.Handle == handle) is not { } targetEvent) return;
|
||||
|
||||
Log.Verbose($"Removing Event. {targetEvent.LogString}");
|
||||
this.TryRemoveEventFromNative(targetEvent);
|
||||
this.Events.Remove(targetEvent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all events attached to the specified addon.
|
||||
/// </summary>
|
||||
/// <param name="addonName">Addon name to remove events from.</param>
|
||||
public void RemoveForAddon(string addonName)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var registeredEvent in this.Events.ToList())
|
||||
{
|
||||
this.RemoveEvent(registeredEvent.Handle);
|
||||
}
|
||||
|
||||
this.EventListener.Dispose();
|
||||
}
|
||||
|
||||
private uint GetNextParamKey()
|
||||
{
|
||||
for (var i = 0u; i < uint.MaxValue; ++i)
|
||||
{
|
||||
if (this.Events.All(registeredEvent => registeredEvent.ParamKey != i)) return i;
|
||||
}
|
||||
|
||||
throw new OverflowException($"uint.MaxValue number of ParamKeys used for {this.PluginId}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to remove a tracked event from native UI.
|
||||
/// This method performs several safety checks to only remove events from a still active addon.
|
||||
/// If any of these checks fail, it likely means the native UI already cleaned up the event, and we don't have to worry about them.
|
||||
/// </summary>
|
||||
/// <param name="eventEntry">Event entry to remove.</param>
|
||||
private void TryRemoveEventFromNative(AddonEventEntry eventEntry)
|
||||
{
|
||||
// Is the eventEntry addon valid?
|
||||
if (eventEntry.AddonName is AddonEventEntry.InvalidAddonName) return;
|
||||
|
||||
// Is an addon with the same name active?
|
||||
var currentAddonPointer = Service<GameGui>.Get().GetAddonByName(eventEntry.AddonName);
|
||||
if (currentAddonPointer == nint.Zero) return;
|
||||
|
||||
// Is our stored addon pointer the same as the active addon pointer?
|
||||
if (currentAddonPointer != eventEntry.Addon) return;
|
||||
|
||||
// Does this addon contain the node this event is for? (by address)
|
||||
var atkUnitBase = (AtkUnitBase*)currentAddonPointer;
|
||||
var nodeFound = false;
|
||||
foreach (var index in Enumerable.Range(0, atkUnitBase->UldManager.NodeListCount))
|
||||
{
|
||||
var node = atkUnitBase->UldManager.NodeList[index];
|
||||
|
||||
// If this node matches our node, then we know our node is still valid.
|
||||
if (node is not null && (nint)node == eventEntry.Node)
|
||||
{
|
||||
nodeFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't find the node, we can't remove the event.
|
||||
if (!nodeFound) return;
|
||||
|
||||
// Does the node have a registered event matching the parameters we have?
|
||||
var atkResNode = (AtkResNode*)eventEntry.Node;
|
||||
var eventType = (AtkEventType)eventEntry.EventType;
|
||||
var currentEvent = atkResNode->AtkEventManager.Event;
|
||||
var eventFound = false;
|
||||
while (currentEvent is not null)
|
||||
{
|
||||
var paramKeyMatches = currentEvent->Param == eventEntry.ParamKey;
|
||||
var eventListenerAddressMatches = (nint)currentEvent->Listener == this.EventListener.Address;
|
||||
var eventTypeMatches = currentEvent->Type == 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);
|
||||
}
|
||||
|
||||
private void PluginEventListHandler(AtkEventListener* self, AtkEventType eventType, uint eventParam, AtkEvent* eventData, IntPtr unknown)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (eventData 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)eventData->Node, (nint)eventData->Target);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, "Exception in PluginEventList custom event invoke.");
|
||||
}
|
||||
}
|
||||
}
|
||||
46
Dalamud/Game/AddonLifecycle/AddonArgTypes/AddonArgs.cs
Normal file
46
Dalamud/Game/AddonLifecycle/AddonArgTypes/AddonArgs.cs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
using Dalamud.Memory;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.Addon;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for AddonLifecycle AddonArgTypes.
|
||||
/// </summary>
|
||||
public abstract unsafe class AddonArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Constant string representing the name of an addon that is invalid.
|
||||
/// </summary>
|
||||
public const string InvalidAddon = "NullAddon";
|
||||
|
||||
private string? addonName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the addon this args referrers to.
|
||||
/// </summary>
|
||||
public string AddonName => this.GetAddonName();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the addons AtkUnitBase.
|
||||
/// </summary>
|
||||
public nint Addon { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of these args.
|
||||
/// </summary>
|
||||
public abstract AddonArgsType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Helper method for ensuring the name of the addon is valid.
|
||||
/// </summary>
|
||||
/// <returns>The name of the addon for this object. <see cref="InvalidAddon"/> when invalid.</returns>
|
||||
private string GetAddonName()
|
||||
{
|
||||
if (this.Addon == nint.Zero) return InvalidAddon;
|
||||
|
||||
var addonPointer = (AtkUnitBase*)this.Addon;
|
||||
if (addonPointer->Name is null) return InvalidAddon;
|
||||
|
||||
return this.addonName ??= MemoryHelper.ReadString((nint)addonPointer->Name, 0x20);
|
||||
}
|
||||
}
|
||||
10
Dalamud/Game/AddonLifecycle/AddonArgTypes/AddonDrawArgs.cs
Normal file
10
Dalamud/Game/AddonLifecycle/AddonArgTypes/AddonDrawArgs.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
namespace Dalamud.Game.Addon;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for Finalize events.
|
||||
/// </summary>
|
||||
public class AddonDrawArgs : AddonArgs
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.Draw;
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
namespace Dalamud.Game.Addon;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for Finalize events.
|
||||
/// </summary>
|
||||
public class AddonFinalizeArgs : AddonArgs
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.Finalize;
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.Addon;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for Finalize events.
|
||||
/// </summary>
|
||||
public class AddonRefreshArgs : AddonArgs
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.Refresh;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of AtkValues.
|
||||
/// </summary>
|
||||
public uint AtkValueCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the AtkValue array.
|
||||
/// </summary>
|
||||
public nint AtkValues { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the AtkValues in the form of a span.
|
||||
/// </summary>
|
||||
public unsafe Span<AtkValue> AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount);
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
namespace Dalamud.Game.Addon;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for Finalize events.
|
||||
/// </summary>
|
||||
public class AddonRequestedUpdateArgs : AddonArgs
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.RequestedUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NumberArrayData** for this event.
|
||||
/// </summary>
|
||||
public nint NumberArrayData { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the StringArrayData** for this event.
|
||||
/// </summary>
|
||||
public nint StringArrayData { get; init; }
|
||||
}
|
||||
29
Dalamud/Game/AddonLifecycle/AddonArgTypes/AddonSetupArgs.cs
Normal file
29
Dalamud/Game/AddonLifecycle/AddonArgTypes/AddonSetupArgs.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.Addon;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for Setup events.
|
||||
/// </summary>
|
||||
public class AddonSetupArgs : AddonArgs
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.Setup;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of AtkValues.
|
||||
/// </summary>
|
||||
public uint AtkValueCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the AtkValue array.
|
||||
/// </summary>
|
||||
public nint AtkValues { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the AtkValues in the form of a span.
|
||||
/// </summary>
|
||||
public unsafe Span<AtkValue> AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount);
|
||||
}
|
||||
15
Dalamud/Game/AddonLifecycle/AddonArgTypes/AddonUpdateArgs.cs
Normal file
15
Dalamud/Game/AddonLifecycle/AddonArgTypes/AddonUpdateArgs.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
namespace Dalamud.Game.Addon;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for Finalize events.
|
||||
/// </summary>
|
||||
public class AddonUpdateArgs : AddonArgs
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.Update;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time since the last update.
|
||||
/// </summary>
|
||||
public float TimeDelta { get; init; }
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
using Dalamud.Memory;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.AddonLifecycle;
|
||||
|
||||
/// <summary>
|
||||
/// Addon argument data for use in event subscribers.
|
||||
/// </summary>
|
||||
public unsafe class AddonArgs
|
||||
{
|
||||
private string? addonName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the addon this args referrers to.
|
||||
/// </summary>
|
||||
public string AddonName => this.Addon == nint.Zero ? "NullAddon" : this.addonName ??= MemoryHelper.ReadString((nint)((AtkUnitBase*)this.Addon)->Name, 0x20);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the addons AtkUnitBase.
|
||||
/// </summary>
|
||||
required public nint Addon { get; init; }
|
||||
}
|
||||
37
Dalamud/Game/AddonLifecycle/AddonArgsType.cs
Normal file
37
Dalamud/Game/AddonLifecycle/AddonArgsType.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
namespace Dalamud.Game.Addon;
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration for available AddonLifecycle arg data.
|
||||
/// </summary>
|
||||
public enum AddonArgsType
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains argument data for Setup.
|
||||
/// </summary>
|
||||
Setup,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for Update.
|
||||
/// </summary>
|
||||
Update,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for Draw.
|
||||
/// </summary>
|
||||
Draw,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for Finalize.
|
||||
/// </summary>
|
||||
Finalize,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for RequestedUpdate.
|
||||
/// </summary>
|
||||
RequestedUpdate,
|
||||
|
||||
/// <summary>
|
||||
/// Contains argument data for Refresh.
|
||||
/// </summary>
|
||||
Refresh,
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Dalamud.Game.AddonLifecycle;
|
||||
namespace Dalamud.Game.Addon;
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration for available AddonLifecycle events.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ using Dalamud.Logging.Internal;
|
|||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.AddonLifecycle;
|
||||
namespace Dalamud.Game.Addon;
|
||||
|
||||
/// <summary>
|
||||
/// This class provides events for in-game addon lifecycles.
|
||||
|
|
@ -26,7 +26,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
|
|||
private readonly Framework framework = Service<Framework>.Get();
|
||||
|
||||
private readonly AddonLifecycleAddressResolver address;
|
||||
private readonly Hook<AddonSetupDelegate> onAddonSetupHook;
|
||||
private readonly CallHook<AddonSetupDelegate> onAddonSetupHook;
|
||||
private readonly Hook<AddonFinalizeDelegate> onAddonFinalizeHook;
|
||||
private readonly CallHook<AddonDrawDelegate> onAddonDrawHook;
|
||||
private readonly CallHook<AddonUpdateDelegate> onAddonUpdateHook;
|
||||
|
|
@ -45,7 +45,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
|
|||
|
||||
this.framework.Update += this.OnFrameworkUpdate;
|
||||
|
||||
this.onAddonSetupHook = Hook<AddonSetupDelegate>.FromAddress(this.address.AddonSetup, this.OnAddonSetup);
|
||||
this.onAddonSetupHook = new CallHook<AddonSetupDelegate>(this.address.AddonSetup, this.OnAddonSetup);
|
||||
this.onAddonFinalizeHook = Hook<AddonFinalizeDelegate>.FromAddress(this.address.AddonFinalize, this.OnAddonFinalize);
|
||||
this.onAddonDrawHook = new CallHook<AddonDrawDelegate>(this.address.AddonDraw, this.OnAddonDraw);
|
||||
this.onAddonUpdateHook = new CallHook<AddonUpdateDelegate>(this.address.AddonUpdate, this.OnAddonUpdate);
|
||||
|
|
@ -53,7 +53,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
|
|||
this.onAddonRequestedUpdateHook = new CallHook<AddonOnRequestedUpdateDelegate>(this.address.AddonOnRequestedUpdate, this.OnRequestedUpdate);
|
||||
}
|
||||
|
||||
private delegate nint AddonSetupDelegate(AtkUnitBase* addon);
|
||||
private delegate void AddonSetupDelegate(AtkUnitBase* addon, uint valueCount, AtkValue* values);
|
||||
|
||||
private delegate void AddonFinalizeDelegate(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase);
|
||||
|
||||
|
|
@ -136,36 +136,44 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
|
|||
}
|
||||
}
|
||||
|
||||
private nint OnAddonSetup(AtkUnitBase* addon)
|
||||
private void OnAddonSetup(AtkUnitBase* addon, uint valueCount, AtkValue* values)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PreSetup, new AddonArgs { Addon = (nint)addon });
|
||||
this.InvokeListeners(AddonEvent.PreSetup, new AddonSetupArgs
|
||||
{
|
||||
Addon = (nint)addon,
|
||||
AtkValueCount = valueCount,
|
||||
AtkValues = (nint)values,
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonSetup pre-setup invoke.");
|
||||
}
|
||||
|
||||
var result = this.onAddonSetupHook.Original(addon);
|
||||
addon->OnSetup(valueCount, values);
|
||||
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PostSetup, new AddonArgs { Addon = (nint)addon });
|
||||
this.InvokeListeners(AddonEvent.PostSetup, new AddonSetupArgs
|
||||
{
|
||||
Addon = (nint)addon,
|
||||
AtkValueCount = valueCount,
|
||||
AtkValues = (nint)values,
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonSetup post-setup invoke.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void OnAddonFinalize(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PreFinalize, new AddonArgs { Addon = (nint)atkUnitBase[0] });
|
||||
this.InvokeListeners(AddonEvent.PreFinalize, new AddonFinalizeArgs { Addon = (nint)atkUnitBase[0] });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
@ -179,7 +187,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
|
|||
{
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PreDraw, new AddonArgs { Addon = (nint)addon });
|
||||
this.InvokeListeners(AddonEvent.PreDraw, new AddonDrawArgs { Addon = (nint)addon });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
@ -190,7 +198,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
|
|||
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PostDraw, new AddonArgs { Addon = (nint)addon });
|
||||
this.InvokeListeners(AddonEvent.PostDraw, new AddonDrawArgs { Addon = (nint)addon });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
@ -202,7 +210,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
|
|||
{
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PreUpdate, new AddonArgs { Addon = (nint)addon });
|
||||
this.InvokeListeners(AddonEvent.PreUpdate, new AddonUpdateArgs { Addon = (nint)addon, TimeDelta = delta });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
@ -213,7 +221,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
|
|||
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PostUpdate, new AddonArgs { Addon = (nint)addon });
|
||||
this.InvokeListeners(AddonEvent.PostUpdate, new AddonUpdateArgs { Addon = (nint)addon, TimeDelta = delta });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
@ -225,7 +233,12 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
|
|||
{
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PreRefresh, new AddonArgs { Addon = (nint)addon });
|
||||
this.InvokeListeners(AddonEvent.PreRefresh, new AddonRefreshArgs
|
||||
{
|
||||
Addon = (nint)addon,
|
||||
AtkValueCount = valueCount,
|
||||
AtkValues = (nint)values,
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
@ -236,7 +249,12 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
|
|||
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PostRefresh, new AddonArgs { Addon = (nint)addon });
|
||||
this.InvokeListeners(AddonEvent.PostRefresh, new AddonRefreshArgs
|
||||
{
|
||||
Addon = (nint)addon,
|
||||
AtkValueCount = valueCount,
|
||||
AtkValues = (nint)values,
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
@ -250,7 +268,12 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
|
|||
{
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PreRequestedUpdate, new AddonArgs { Addon = (nint)addon });
|
||||
this.InvokeListeners(AddonEvent.PreRequestedUpdate, new AddonRequestedUpdateArgs
|
||||
{
|
||||
Addon = (nint)addon,
|
||||
NumberArrayData = (nint)numberArrayData,
|
||||
StringArrayData = (nint)stringArrayData,
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
@ -261,7 +284,12 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
|
|||
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PostRequestedUpdate, new AddonArgs { Addon = (nint)addon });
|
||||
this.InvokeListeners(AddonEvent.PostRequestedUpdate, new AddonRequestedUpdateArgs
|
||||
{
|
||||
Addon = (nint)addon,
|
||||
NumberArrayData = (nint)numberArrayData,
|
||||
StringArrayData = (nint)stringArrayData,
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Dalamud.Game.AddonLifecycle;
|
||||
namespace Dalamud.Game.Addon;
|
||||
|
||||
/// <summary>
|
||||
/// AddonLifecycleService memory address resolver.
|
||||
|
|
@ -41,7 +41,7 @@ internal class AddonLifecycleAddressResolver : BaseAddressResolver
|
|||
/// <param name="sig">The signature scanner to facilitate setup.</param>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
this.AddonSetup = sig.ScanText("E8 ?? ?? ?? ?? 8B 83 ?? ?? ?? ?? C1 E8 14");
|
||||
this.AddonSetup = sig.ScanText("FF 90 ?? ?? ?? ?? 48 8B 93 ?? ?? ?? ?? 80 8B");
|
||||
this.AddonFinalize = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 7C 24 ?? 41 8B C6");
|
||||
this.AddonDraw = sig.ScanText("FF 90 ?? ?? ?? ?? 83 EB 01 79 C1");
|
||||
this.AddonUpdate = sig.ScanText("FF 90 ?? ?? ?? ?? 40 88 AF");
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace Dalamud.Game.AddonLifecycle;
|
||||
namespace Dalamud.Game.Addon;
|
||||
|
||||
/// <summary>
|
||||
/// This class is a helper for tracking and invoking listener delegates.
|
||||
|
|
|
|||
|
|
@ -3,20 +3,16 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Game.AddonEventManager;
|
||||
using Dalamud.Game.Addon;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Memory;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Memory;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
using DalamudAddonEventManager = Dalamud.Game.AddonEventManager.AddonEventManager;
|
||||
|
||||
namespace Dalamud.Game.Gui.Dtr;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -27,9 +23,6 @@ namespace Dalamud.Game.Gui.Dtr;
|
|||
internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
||||
{
|
||||
private const uint BaseNodeId = 1000;
|
||||
private const uint MouseOverEventIdOffset = 10000;
|
||||
private const uint MouseOutEventIdOffset = 20000;
|
||||
private const uint MouseClickEventIdOffset = 30000;
|
||||
|
||||
private static readonly ModuleLog Log = new("DtrBar");
|
||||
|
||||
|
|
@ -43,23 +36,29 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudAddonEventManager uiEventManager = Service<DalamudAddonEventManager>.Get();
|
||||
private readonly AddonEventManager uiEventManager = Service<AddonEventManager>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly AddonLifecycle addonLifecycle = Service<AddonLifecycle>.Get();
|
||||
|
||||
private readonly AddonLifecycleEventListener dtrPostDrawListener;
|
||||
private readonly AddonLifecycleEventListener dtrPostRequestedUpdateListener;
|
||||
|
||||
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 readonly Dictionary<uint, List<IAddonEventHandle>> eventHandles = new();
|
||||
|
||||
private uint runningNodeIds = BaseNodeId;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private DtrBar(SigScanner sigScanner)
|
||||
private DtrBar()
|
||||
{
|
||||
this.address = new DtrBarAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
this.dtrPostDrawListener = new AddonLifecycleEventListener(AddonEvent.PostDraw, "_DTR", this.OnDtrPostDraw);
|
||||
this.dtrPostRequestedUpdateListener = new AddonLifecycleEventListener(AddonEvent.PostRequestedUpdate, "_DTR", this.OnAddonRequestedUpdateDetour);
|
||||
|
||||
this.onAddonDrawHook = Hook<AddonDrawDelegate>.FromAddress(this.address.AtkUnitBaseDraw, this.OnAddonDrawDetour);
|
||||
this.onAddonRequestedUpdateHook = Hook<AddonRequestedUpdateDelegate>.FromAddress(this.address.AddonRequestedUpdate, this.OnAddonRequestedUpdateDetour);
|
||||
this.addonLifecycle.RegisterListener(this.dtrPostDrawListener);
|
||||
this.addonLifecycle.RegisterListener(this.dtrPostRequestedUpdateListener);
|
||||
|
||||
this.framework.Update += this.Update;
|
||||
|
||||
|
|
@ -68,10 +67,6 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
this.configuration.QueueSave();
|
||||
}
|
||||
|
||||
private delegate void AddonDrawDelegate(AtkUnitBase* addon);
|
||||
|
||||
private delegate void AddonRequestedUpdateDelegate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DtrBarEntry Get(string title, SeString? text = null)
|
||||
{
|
||||
|
|
@ -102,8 +97,8 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
/// <inheritdoc/>
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
this.onAddonDrawHook.Dispose();
|
||||
this.onAddonRequestedUpdateHook.Dispose();
|
||||
this.addonLifecycle.UnregisterListener(this.dtrPostDrawListener);
|
||||
this.addonLifecycle.UnregisterListener(this.dtrPostRequestedUpdateListener);
|
||||
|
||||
foreach (var entry in this.entries)
|
||||
this.RemoveNode(entry.TextNode);
|
||||
|
|
@ -166,13 +161,6 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
});
|
||||
}
|
||||
|
||||
[ServiceManager.CallWhenServicesReady]
|
||||
private void ContinueConstruction()
|
||||
{
|
||||
this.onAddonDrawHook.Enable();
|
||||
this.onAddonRequestedUpdateHook.Enable();
|
||||
}
|
||||
|
||||
private AtkUnitBase* GetDtr() => (AtkUnitBase*)this.gameGui.GetAddonByName("_DTR").ToPointer();
|
||||
|
||||
private void Update(IFramework unused)
|
||||
|
|
@ -181,7 +169,7 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
this.HandleAddedNodes();
|
||||
|
||||
var dtr = this.GetDtr();
|
||||
if (dtr == null) return;
|
||||
if (dtr == null || dtr->RootNode == null || dtr->RootNode->ChildNode == null) return;
|
||||
|
||||
// The collision node on the DTR element is always the width of its content
|
||||
if (dtr->UldManager.NodeList == null) return;
|
||||
|
|
@ -232,7 +220,7 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
|
||||
if (this.configuration.DtrSwapDirection)
|
||||
{
|
||||
data.TextNode->AtkResNode.SetPositionFloat(runningXPos, 2);
|
||||
data.TextNode->AtkResNode.SetPositionFloat(runningXPos + this.configuration.DtrSpacing, 2);
|
||||
runningXPos += elementWidth;
|
||||
}
|
||||
else
|
||||
|
|
@ -241,6 +229,11 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
data.TextNode->AtkResNode.SetPositionFloat(runningXPos, 2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we want the node hidden, shift it up, to prevent collision conflicts
|
||||
data.TextNode->AtkResNode.SetY(-collisionNode->Height * dtr->RootNode->ScaleX);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -259,22 +252,16 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
}
|
||||
}
|
||||
|
||||
// This hooks all AtkUnitBase.Draw calls, then checks for our specific addon name.
|
||||
// AddonDtr doesn't implement it's own Draw method, would need to replace vtable entry to be more efficient.
|
||||
private void OnAddonDrawDetour(AtkUnitBase* addon)
|
||||
private void OnDtrPostDraw(AddonEvent eventType, AddonArgs addonInfo)
|
||||
{
|
||||
this.onAddonDrawHook!.Original(addon);
|
||||
|
||||
try
|
||||
{
|
||||
if (MemoryHelper.ReadString((nint)addon->Name, 0x20) is not "_DTR") return;
|
||||
var addon = (AtkUnitBase*)addonInfo.Addon;
|
||||
|
||||
this.UpdateNodePositions(addon);
|
||||
|
||||
if (!this.configuration.DtrSwapDirection)
|
||||
{
|
||||
var targetSize = (ushort)this.CalculateTotalSize();
|
||||
var sizeDelta = targetSize - addon->RootNode->Width;
|
||||
var sizeDelta = MathF.Round((targetSize - addon->RootNode->Width) * addon->RootNode->ScaleX);
|
||||
|
||||
if (addon->RootNode->Width != targetSize)
|
||||
{
|
||||
|
|
@ -286,11 +273,6 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonDraw.");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateNodePositions(AtkUnitBase* addon)
|
||||
{
|
||||
|
|
@ -315,19 +297,12 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
}
|
||||
}
|
||||
|
||||
private void OnAddonRequestedUpdateDetour(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
|
||||
private void OnAddonRequestedUpdateDetour(AddonEvent eventType, AddonArgs addonInfo)
|
||||
{
|
||||
this.onAddonRequestedUpdateHook.Original(addon, numberArrayData, stringArrayData);
|
||||
var addon = (AtkUnitBase*)addonInfo.Addon;
|
||||
|
||||
try
|
||||
{
|
||||
this.UpdateNodePositions(addon);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonRequestedUpdate.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if there are any Dalamud nodes in the DTR.
|
||||
|
|
@ -350,6 +325,11 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
private void RecreateNodes()
|
||||
{
|
||||
this.runningNodeIds = BaseNodeId;
|
||||
if (this.entries.Any())
|
||||
{
|
||||
this.eventHandles.Clear();
|
||||
}
|
||||
|
||||
foreach (var entry in this.entries)
|
||||
{
|
||||
entry.TextNode = this.MakeNode(++this.runningNodeIds);
|
||||
|
|
@ -384,9 +364,13 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
var dtr = this.GetDtr();
|
||||
if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false;
|
||||
|
||||
this.uiEventManager.AddEvent(node->AtkResNode.NodeID + MouseOverEventIdOffset, (nint)dtr, (nint)node, AddonEventType.MouseOver, this.DtrEventHandler);
|
||||
this.uiEventManager.AddEvent(node->AtkResNode.NodeID + MouseOutEventIdOffset, (nint)dtr, (nint)node, AddonEventType.MouseOut, this.DtrEventHandler);
|
||||
this.uiEventManager.AddEvent(node->AtkResNode.NodeID + MouseClickEventIdOffset, (nint)dtr, (nint)node, AddonEventType.MouseClick, this.DtrEventHandler);
|
||||
this.eventHandles.TryAdd(node->AtkResNode.NodeID, new List<IAddonEventHandle>());
|
||||
this.eventHandles[node->AtkResNode.NodeID].AddRange(new List<IAddonEventHandle>
|
||||
{
|
||||
this.uiEventManager.AddEvent(AddonEventManager.DalamudInternalKey, (nint)dtr, (nint)node, AddonEventType.MouseOver, this.DtrEventHandler),
|
||||
this.uiEventManager.AddEvent(AddonEventManager.DalamudInternalKey, (nint)dtr, (nint)node, AddonEventType.MouseOut, this.DtrEventHandler),
|
||||
this.uiEventManager.AddEvent(AddonEventManager.DalamudInternalKey, (nint)dtr, (nint)node, AddonEventType.MouseClick, this.DtrEventHandler),
|
||||
});
|
||||
|
||||
var lastChild = dtr->RootNode->ChildNode;
|
||||
while (lastChild->PrevSiblingNode != null) lastChild = lastChild->PrevSiblingNode;
|
||||
|
|
@ -404,14 +388,13 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
return true;
|
||||
}
|
||||
|
||||
private bool RemoveNode(AtkTextNode* node)
|
||||
private void RemoveNode(AtkTextNode* node)
|
||||
{
|
||||
var dtr = this.GetDtr();
|
||||
if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false;
|
||||
if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return;
|
||||
|
||||
this.uiEventManager.RemoveEvent(node->AtkResNode.NodeID + MouseOverEventIdOffset, (nint)node, AddonEventType.MouseOver);
|
||||
this.uiEventManager.RemoveEvent(node->AtkResNode.NodeID + MouseOutEventIdOffset, (nint)node, AddonEventType.MouseOut);
|
||||
this.uiEventManager.RemoveEvent(node->AtkResNode.NodeID + MouseClickEventIdOffset, (nint)node, AddonEventType.MouseClick);
|
||||
this.eventHandles[node->AtkResNode.NodeID].ForEach(handle => this.uiEventManager.RemoveEvent(AddonEventManager.DalamudInternalKey, handle));
|
||||
this.eventHandles[node->AtkResNode.NodeID].Clear();
|
||||
|
||||
var tmpPrevNode = node->AtkResNode.PrevSiblingNode;
|
||||
var tmpNextNode = node->AtkResNode.NextSiblingNode;
|
||||
|
|
@ -427,7 +410,6 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
|
|||
dtr->UldManager.UpdateDrawNodeList();
|
||||
dtr->UpdateCollisionNodeList(false);
|
||||
Log.Debug("Updated node draw list");
|
||||
return true;
|
||||
}
|
||||
|
||||
private AtkTextNode* MakeNode(uint nodeId)
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
namespace Dalamud.Game.Gui.Dtr;
|
||||
|
||||
/// <summary>
|
||||
/// DtrBar memory address resolver.
|
||||
/// </summary>
|
||||
internal class DtrBarAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the AtkUnitBaseDraw method.
|
||||
/// This is the base handler for all addons.
|
||||
/// We will use this here because _DTR does not have a overloaded handler, so we must use the base handler.
|
||||
/// </summary>
|
||||
public nint AtkUnitBaseDraw { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the DTRRequestUpdate method.
|
||||
/// </summary>
|
||||
public nint AddonRequestedUpdate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Scan for and setup any configured address pointers.
|
||||
/// </summary>
|
||||
/// <param name="scanner">The signature scanner to facilitate setup.</param>
|
||||
protected override void Setup64Bit(SigScanner scanner)
|
||||
{
|
||||
this.AtkUnitBaseDraw = scanner.ScanText("48 83 EC 28 F6 81 ?? ?? ?? ?? ?? 4C 8B C1");
|
||||
this.AddonRequestedUpdate = scanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B BA ?? ?? ?? ?? 48 8B F1 49 8B 98 ?? ?? ?? ?? 33 D2");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Dalamud.Interface;
|
||||
|
|
@ -267,4 +268,32 @@ public static class ColorHelpers
|
|||
/// <returns>The faded color.</returns>
|
||||
public static uint Fade(uint color, float amount)
|
||||
=> RgbaVector4ToUint(Fade(RgbaUintToVector4(color), amount));
|
||||
|
||||
/// <summary>
|
||||
/// Convert a KnownColor to a RGBA vector with values between 0.0f and 1.0f
|
||||
/// </summary>
|
||||
/// <param name="knownColor">Known Color to convert.</param>
|
||||
/// <returns>RGBA Vector with values between 0.0f and 1.0f.</returns>
|
||||
public static Vector4 Vector(this KnownColor knownColor)
|
||||
{
|
||||
var rgbColor = Color.FromKnownColor(knownColor);
|
||||
return new Vector4(rgbColor.R, rgbColor.G, rgbColor.B, rgbColor.A) / 255.0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes a Vector4 with RGBA 255 color values to values between 0.0f and 1.0f
|
||||
/// If values are out of RGBA 255 range, the original value is returned.
|
||||
/// </summary>
|
||||
/// <param name="color">The color vector to convert.</param>
|
||||
/// <returns>A vector with values between 0.0f and 1.0f.</returns>
|
||||
public static Vector4 NormalizeToUnitRange(this Vector4 color) => color switch
|
||||
{
|
||||
// If any components are out of range, return original value.
|
||||
{ W: > 255.0f or < 0.0f } or { X: > 255.0f or < 0.0f } or { Y: > 255.0f or < 0.0f } or { Z: > 255.0f or < 0.0f } => color,
|
||||
|
||||
// If all components are already unit range, return original value.
|
||||
{ W: >= 0.0f and <= 1.0f, X: >= 0.0f and <= 1.0f, Y: >= 0.0f and <= 1.0f, Z: >= 0.0f and <= 1.0f } => color,
|
||||
|
||||
_ => color / 255.0f,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Game.AddonLifecycle;
|
||||
using Dalamud.Game.Addon;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.Numerics;
|
|||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Logging.Internal;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using ImGuiNET;
|
||||
|
||||
|
|
@ -13,6 +14,8 @@ namespace Dalamud.Interface.Windowing;
|
|||
/// </summary>
|
||||
public abstract class Window
|
||||
{
|
||||
private static readonly ModuleLog Log = new("WindowSystem");
|
||||
|
||||
private static bool wasEscPressedLastFrame = false;
|
||||
|
||||
private bool internalLastIsOpen = false;
|
||||
|
|
@ -286,8 +289,15 @@ public abstract class Window
|
|||
if (this.ShowCloseButton ? ImGui.Begin(this.WindowName, ref this.internalIsOpen, this.Flags) : ImGui.Begin(this.WindowName, this.Flags))
|
||||
{
|
||||
// Draw the actual window contents
|
||||
try
|
||||
{
|
||||
this.Draw();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Error during Draw(): {this.WindowName}");
|
||||
}
|
||||
}
|
||||
|
||||
if (wasFocused)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Dalamud.Game.AddonEventManager;
|
||||
using Dalamud.Game.Addon;
|
||||
|
||||
namespace Dalamud.Plugin.Services;
|
||||
|
||||
|
|
@ -18,20 +18,18 @@ public interface IAddonEventManager
|
|||
/// <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);
|
||||
/// <returns>IAddonEventHandle used to remove the event. Null if no event was added.</returns>
|
||||
IAddonEventHandle? AddEvent(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);
|
||||
/// <param name="eventHandle">Unique handle identifying this event.</param>
|
||||
void RemoveEvent(IAddonEventHandle eventHandle);
|
||||
|
||||
/// <summary>
|
||||
/// Force the game cursor to be the specified cursor.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Game.AddonLifecycle;
|
||||
using Dalamud.Game.Addon;
|
||||
|
||||
namespace Dalamud.Plugin.Services;
|
||||
|
||||
|
|
@ -13,9 +13,9 @@ public interface IAddonLifecycle
|
|||
/// <summary>
|
||||
/// Delegate for receiving addon lifecycle event messages.
|
||||
/// </summary>
|
||||
/// <param name="eventType">The event type that triggered the message.</param>
|
||||
/// <param name="addonInfo">Information about what addon triggered the message.</param>
|
||||
public delegate void AddonEventDelegate(AddonEvent eventType, AddonArgs addonInfo);
|
||||
/// <param name="type">The event type that triggered the message.</param>
|
||||
/// <param name="args">Information about what addon triggered the message.</param>
|
||||
public delegate void AddonEventDelegate(AddonEvent type, AddonArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// Register a listener that will trigger on the specified event and any of the specified addons.
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<DalamudLibPath>$(appdata)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))'">
|
||||
<DalamudLibPath>$(DALAMUD_HOME)/</DalamudLibPath>
|
||||
<DalamudLibPath Condition="$([MSBuild]::IsOSPlatform('Windows'))">$(appdata)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
|
||||
<DalamudLibPath Condition="$([MSBuild]::IsOSPlatform('Linux'))">$(HOME)/.xlcore/dalamud/Hooks/dev/</DalamudLibPath>
|
||||
<DalamudLibPath Condition="$([MSBuild]::IsOSPlatform('OSX'))">$(HOME)/Library/Application Support/XIV on Mac/dalamud/Hooks/dev/</DalamudLibPath>
|
||||
<DalamudLibPath Condition="$(DALAMUD_HOME) != ''">$(DALAMUD_HOME)/</DalamudLibPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(DalamudLibPath)/targets/Dalamud.Plugin.targets"/>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue