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(); }