diff --git a/Dalamud/Game/AddonEventManager/AddonCursorType.cs b/Dalamud/Game/AddonEventManager/AddonCursorType.cs
new file mode 100644
index 000000000..8ba3a901b
--- /dev/null
+++ b/Dalamud/Game/AddonEventManager/AddonCursorType.cs
@@ -0,0 +1,97 @@
+namespace Dalamud.Game.AddonEventManager;
+
+///
+/// Reimplementation of CursorType.
+///
+public enum AddonCursorType
+{
+ ///
+ /// Arrow.
+ ///
+ Arrow,
+
+ ///
+ /// Boot.
+ ///
+ Boot,
+
+ ///
+ /// Search.
+ ///
+ Search,
+
+ ///
+ /// Chat Pointer.
+ ///
+ ChatPointer,
+
+ ///
+ /// Interact.
+ ///
+ Interact,
+
+ ///
+ /// Attack.
+ ///
+ Attack,
+
+ ///
+ /// Hand.
+ ///
+ Hand,
+
+ ///
+ /// Resizeable Left-Right.
+ ///
+ ResizeWE,
+
+ ///
+ /// Resizeable Up-Down.
+ ///
+ ResizeNS,
+
+ ///
+ /// Resizeable.
+ ///
+ ResizeNWSR,
+
+ ///
+ /// Resizeable 4-way.
+ ///
+ ResizeNESW,
+
+ ///
+ /// Clickable.
+ ///
+ Clickable,
+
+ ///
+ /// Text Input.
+ ///
+ TextInput,
+
+ ///
+ /// Text Click.
+ ///
+ TextClick,
+
+ ///
+ /// Grab.
+ ///
+ Grab,
+
+ ///
+ /// Chat Bubble.
+ ///
+ ChatBubble,
+
+ ///
+ /// No Access.
+ ///
+ NoAccess,
+
+ ///
+ /// Hidden.
+ ///
+ Hidden,
+}
diff --git a/Dalamud/Game/AddonEventManager/AddonEventManager.cs b/Dalamud/Game/AddonEventManager/AddonEventManager.cs
index 77111421f..ede59a9a9 100644
--- a/Dalamud/Game/AddonEventManager/AddonEventManager.cs
+++ b/Dalamud/Game/AddonEventManager/AddonEventManager.cs
@@ -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 onGlobalEventHook;
+ private readonly Hook onGlobalEventHook;
+ private readonly Hook onUpdateCursor;
private readonly Dictionary 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();
+ this.currentCursor = AddonCursorType.Arrow;
- this.onGlobalEventHook = Hook.FromAddress(this.address.GlobalEventHandler, this.GlobalEventHandler);
+ this.onGlobalEventHook = Hook.FromAddress(this.address.GlobalEventHandler, this.GlobalEventHandler);
+ this.onUpdateCursor = Hook.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);
///
public void Dispose()
{
this.onGlobalEventHook.Dispose();
+ this.onUpdateCursor.Dispose();
}
///
@@ -86,11 +96,31 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
///
/// Event id to unregister.
public void RemoveEvent(uint eventId) => this.eventHandlers.Remove(eventId);
+
+ ///
+ /// Sets the game cursor.
+ ///
+ /// Cursor type to set.
+ public void SetCursor(AddonCursorType cursor)
+ {
+ this.currentCursor = cursor;
+ this.cursorSet = true;
+ }
+
+ ///
+ /// Resets and un-forces custom cursor.
+ ///
+ 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);
+ }
}
///
@@ -137,6 +192,7 @@ internal unsafe class AddonEventManagerPluginScoped : IDisposable, IServiceType,
private readonly uint paramKeyStartRange;
private readonly List activeParamKeys;
+ private bool isForcingCursor;
///
/// Initializes a new instance of the 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();
+ }
}
///
@@ -204,4 +266,20 @@ internal unsafe class AddonEventManagerPluginScoped : IDisposable, IServiceType,
Log.Warning($"Attempted to unregister already unregistered eventId: {eventId}");
}
}
+
+ ///
+ public void SetCursor(AddonCursorType cursor)
+ {
+ this.isForcingCursor = true;
+
+ this.baseEventManager.SetCursor(cursor);
+ }
+
+ ///
+ public void ResetCursor()
+ {
+ this.isForcingCursor = false;
+
+ this.baseEventManager.ResetCursor();
+ }
}
diff --git a/Dalamud/Game/AddonEventManager/AddonEventManagerAddressResolver.cs b/Dalamud/Game/AddonEventManager/AddonEventManagerAddressResolver.cs
index 8dcf81580..5cfa51149 100644
--- a/Dalamud/Game/AddonEventManager/AddonEventManagerAddressResolver.cs
+++ b/Dalamud/Game/AddonEventManager/AddonEventManagerAddressResolver.cs
@@ -6,9 +6,14 @@
internal class AddonEventManagerAddressResolver : BaseAddressResolver
{
///
- /// Gets the address of the global atkevent handler
+ /// Gets the address of the global AtkEvent handler.
///
public nint GlobalEventHandler { get; private set; }
+
+ ///
+ /// Gets the address of the AtkModule UpdateCursor method.
+ ///
+ public nint UpdateCursor { get; private set; }
///
/// 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");
}
}
diff --git a/Dalamud/Plugin/Services/IAddonEventManager.cs b/Dalamud/Plugin/Services/IAddonEventManager.cs
index 8e2b1f67b..f052ed607 100644
--- a/Dalamud/Plugin/Services/IAddonEventManager.cs
+++ b/Dalamud/Plugin/Services/IAddonEventManager.cs
@@ -33,4 +33,15 @@ public interface IAddonEventManager
/// The node for this event.
/// The event type for this event.
void RemoveEvent(uint eventId, nint atkUnitBase, nint atkResNode, AddonEventType eventType);
+
+ ///
+ /// Force the game cursor to be the specified cursor.
+ ///
+ /// Which cursor to use.
+ void SetCursor(AddonCursorType cursor);
+
+ ///
+ /// Un-forces the game cursor.
+ ///
+ void ResetCursor();
}