[AddonEventManager] Add Cursor Control

This commit is contained in:
MidoriKami 2023-09-02 00:06:54 -07:00
parent 7ac37a579b
commit ad06b5f054
4 changed files with 196 additions and 4 deletions

View file

@ -0,0 +1,97 @@
namespace Dalamud.Game.AddonEventManager;
/// <summary>
/// Reimplementation of CursorType.
/// </summary>
public enum AddonCursorType
{
/// <summary>
/// Arrow.
/// </summary>
Arrow,
/// <summary>
/// Boot.
/// </summary>
Boot,
/// <summary>
/// Search.
/// </summary>
Search,
/// <summary>
/// Chat Pointer.
/// </summary>
ChatPointer,
/// <summary>
/// Interact.
/// </summary>
Interact,
/// <summary>
/// Attack.
/// </summary>
Attack,
/// <summary>
/// Hand.
/// </summary>
Hand,
/// <summary>
/// Resizeable Left-Right.
/// </summary>
ResizeWE,
/// <summary>
/// Resizeable Up-Down.
/// </summary>
ResizeNS,
/// <summary>
/// Resizeable.
/// </summary>
ResizeNWSR,
/// <summary>
/// Resizeable 4-way.
/// </summary>
ResizeNESW,
/// <summary>
/// Clickable.
/// </summary>
Clickable,
/// <summary>
/// Text Input.
/// </summary>
TextInput,
/// <summary>
/// Text Click.
/// </summary>
TextClick,
/// <summary>
/// Grab.
/// </summary>
Grab,
/// <summary>
/// Chat Bubble.
/// </summary>
ChatBubble,
/// <summary>
/// No Access.
/// </summary>
NoAccess,
/// <summary>
/// Hidden.
/// </summary>
Hidden,
}

View file

@ -6,6 +6,7 @@ using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace Dalamud.Game.AddonEventManager;
@ -32,9 +33,13 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
private static readonly ModuleLog Log = new("AddonEventManager");
private readonly AddonEventManagerAddressResolver address;
private readonly Hook<GlobalEventHandlerDetour> onGlobalEventHook;
private readonly Hook<GlobalEventHandlerDelegate> onGlobalEventHook;
private readonly Hook<UpdateCursorDelegate> onUpdateCursor;
private readonly Dictionary<uint, IAddonEventManager.AddonEventHandler> eventHandlers;
private AddonCursorType currentCursor;
private bool cursorSet;
private uint currentPluginParamStart = ParamKeyStart;
[ServiceManager.ServiceConstructor]
@ -44,16 +49,21 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
this.address.Setup(sigScanner);
this.eventHandlers = new Dictionary<uint, IAddonEventManager.AddonEventHandler>();
this.currentCursor = AddonCursorType.Arrow;
this.onGlobalEventHook = Hook<GlobalEventHandlerDetour>.FromAddress(this.address.GlobalEventHandler, this.GlobalEventHandler);
this.onGlobalEventHook = Hook<GlobalEventHandlerDelegate>.FromAddress(this.address.GlobalEventHandler, this.GlobalEventHandler);
this.onUpdateCursor = Hook<UpdateCursorDelegate>.FromAddress(this.address.UpdateCursor, this.UpdateCursorDetour);
}
private delegate nint GlobalEventHandlerDetour(AtkUnitBase* atkUnitBase, AtkEventType eventType, uint eventParam, AtkResNode** eventData, nint unknown);
private delegate nint GlobalEventHandlerDelegate(AtkUnitBase* atkUnitBase, AtkEventType eventType, uint eventParam, AtkResNode** eventData, nint unknown);
private delegate nint UpdateCursorDelegate(RaptureAtkModule* module);
/// <inheritdoc/>
public void Dispose()
{
this.onGlobalEventHook.Dispose();
this.onUpdateCursor.Dispose();
}
/// <summary>
@ -86,11 +96,31 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
/// </summary>
/// <param name="eventId">Event id to unregister.</param>
public void RemoveEvent(uint eventId) => this.eventHandlers.Remove(eventId);
/// <summary>
/// Sets the game cursor.
/// </summary>
/// <param name="cursor">Cursor type to set.</param>
public void SetCursor(AddonCursorType cursor)
{
this.currentCursor = cursor;
this.cursorSet = true;
}
/// <summary>
/// Resets and un-forces custom cursor.
/// </summary>
public void ResetCursor()
{
this.currentCursor = AddonCursorType.Arrow;
this.cursorSet = false;
}
[ServiceManager.CallWhenServicesReady]
private void ContinueConstruction()
{
this.onGlobalEventHook.Enable();
this.onUpdateCursor.Enable();
}
private nint GlobalEventHandler(AtkUnitBase* atkUnitBase, AtkEventType eventType, uint eventParam, AtkResNode** eventData, nint unknown)
@ -117,6 +147,31 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
return this.onGlobalEventHook!.Original(atkUnitBase, eventType, eventParam, eventData, unknown);
}
private nint UpdateCursorDetour(RaptureAtkModule* module)
{
try
{
var atkStage = AtkStage.GetSingleton();
if (this.cursorSet && atkStage is not null)
{
var cursor = (AddonCursorType)atkStage->AtkCursor.Type;
if (cursor != this.currentCursor)
{
AtkStage.GetSingleton()->AtkCursor.SetCursorType((AtkCursor.CursorType)this.currentCursor, 1);
}
return nint.Zero;
}
}
catch (Exception e)
{
Log.Error(e, "Exception in UpdateCursorDetour.");
}
return this.onUpdateCursor!.Original(module);
}
}
/// <summary>
@ -137,6 +192,7 @@ internal unsafe class AddonEventManagerPluginScoped : IDisposable, IServiceType,
private readonly uint paramKeyStartRange;
private readonly List<uint> activeParamKeys;
private bool isForcingCursor;
/// <summary>
/// Initializes a new instance of the <see cref="AddonEventManagerPluginScoped"/> class.
@ -154,6 +210,12 @@ internal unsafe class AddonEventManagerPluginScoped : IDisposable, IServiceType,
{
this.baseEventManager.RemoveEvent(activeKey);
}
// if multiple plugins force cursors and dispose without un-forcing them then all forces will be cleared.
if (this.isForcingCursor)
{
this.baseEventManager.ResetCursor();
}
}
/// <inheritdoc/>
@ -204,4 +266,20 @@ internal unsafe class AddonEventManagerPluginScoped : IDisposable, IServiceType,
Log.Warning($"Attempted to unregister already unregistered eventId: {eventId}");
}
}
/// <inheritdoc/>
public void SetCursor(AddonCursorType cursor)
{
this.isForcingCursor = true;
this.baseEventManager.SetCursor(cursor);
}
/// <inheritdoc/>
public void ResetCursor()
{
this.isForcingCursor = false;
this.baseEventManager.ResetCursor();
}
}

View file

@ -6,9 +6,14 @@
internal class AddonEventManagerAddressResolver : BaseAddressResolver
{
/// <summary>
/// Gets the address of the global atkevent handler
/// Gets the address of the global AtkEvent handler.
/// </summary>
public nint GlobalEventHandler { get; private set; }
/// <summary>
/// Gets the address of the AtkModule UpdateCursor method.
/// </summary>
public nint UpdateCursor { get; private set; }
/// <summary>
/// Scan for and setup any configured address pointers.
@ -17,5 +22,6 @@ internal class AddonEventManagerAddressResolver : BaseAddressResolver
protected override void Setup64Bit(SigScanner scanner)
{
this.GlobalEventHandler = scanner.ScanText("48 89 5C 24 ?? 48 89 7C 24 ?? 55 41 56 41 57 48 8B EC 48 83 EC 50 44 0F B7 F2");
this.UpdateCursor = scanner.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 20 4C 8B F1 E8 ?? ?? ?? ?? 49 8B CE");
}
}

View file

@ -33,4 +33,15 @@ public interface IAddonEventManager
/// <param name="atkResNode">The node for this event.</param>
/// <param name="eventType">The event type for this event.</param>
void RemoveEvent(uint eventId, nint atkUnitBase, nint atkResNode, AddonEventType eventType);
/// <summary>
/// Force the game cursor to be the specified cursor.
/// </summary>
/// <param name="cursor">Which cursor to use.</param>
void SetCursor(AddonCursorType cursor);
/// <summary>
/// Un-forces the game cursor.
/// </summary>
void ResetCursor();
}