diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs index 16db84292..08bbeb938 100644 --- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs +++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs @@ -153,23 +153,11 @@ internal sealed class DalamudConfiguration : IInternalDisposableService /// public float GlobalUiScale { get; set; } = 1.0f; - /// - /// Gets or sets a value indicating whether to use AXIS fonts from the game. - /// - [Obsolete($"See {nameof(DefaultFontSpec)}")] - public bool UseAxisFontsFromGame { get; set; } = true; - /// /// Gets or sets the default font spec. /// public IFontSpec? DefaultFontSpec { get; set; } - /// - /// Gets or sets the gamma value to apply for Dalamud fonts. Do not use. - /// - [Obsolete("It happens that nobody touched this setting", true)] - public float FontGammaLevel { get; set; } = 1.4f; - /// Gets or sets the opacity of the IME state indicator. /// 0 will hide the state indicator. 1 will make the state indicator fully visible. Values outside the /// range will be clamped to [0, 1]. diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 0875054f2..7c451d97f 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -6,7 +6,7 @@ XIV Launcher addon framework - 12.0.1.5 + 13.0.0.0 $(DalamudVersion) $(DalamudVersion) $(DalamudVersion) diff --git a/Dalamud/Game/Addon/Events/AddonEventEntry.cs b/Dalamud/Game/Addon/Events/AddonEventEntry.cs index 50b9c7ec4..30d0465dc 100644 --- a/Dalamud/Game/Addon/Events/AddonEventEntry.cs +++ b/Dalamud/Game/Addon/Events/AddonEventEntry.cs @@ -1,4 +1,4 @@ -using Dalamud.Plugin.Services; +using Dalamud.Plugin.Services; using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Component.GUI; @@ -33,17 +33,10 @@ internal unsafe class AddonEventEntry /// public required nint Node { get; init; } - /// - /// Gets the handler that gets called when this event is triggered. - /// - [Obsolete("Use AddonEventDelegate Delegate instead")] - public IAddonEventManager.AddonEventHandler Handler { get; init; } - /// /// Gets the delegate that gets called when this event is triggered. /// - [Api13ToDo("Make this field required")] - public IAddonEventManager.AddonEventDelegate Delegate { get; init; } + public required IAddonEventManager.AddonEventDelegate Delegate { get; init; } /// /// Gets the unique id for this event. diff --git a/Dalamud/Game/Addon/Events/AddonEventManager.cs b/Dalamud/Game/Addon/Events/AddonEventManager.cs index 0990c1f5f..945197e2b 100644 --- a/Dalamud/Game/Addon/Events/AddonEventManager.cs +++ b/Dalamud/Game/Addon/Events/AddonEventManager.cs @@ -73,29 +73,6 @@ internal unsafe class AddonEventManager : IInternalDisposableService this.addonLifecycle.UnregisterListener(this.finalizeEventListener); } - /// - /// Registers an event handler for the specified addon, node, and type. - /// - /// Unique ID for this plugin. - /// The parent addon for this event. - /// The node that will trigger this event. - /// The event type for this event. - /// The handler to call when event is triggered. - /// IAddonEventHandle used to remove the event. - internal IAddonEventHandle? AddEvent(Guid pluginId, IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler) - { - if (this.pluginEventControllers.TryGetValue(pluginId, out var controller)) - { - return controller.AddEvent(atkUnitBase, atkResNode, eventType, eventHandler); - } - else - { - Log.Verbose($"Unable to locate controller for {pluginId}. No event was added."); - } - - return null; - } - /// /// Registers an event handler for the specified addon, node, and type. /// @@ -260,10 +237,6 @@ internal class AddonEventManagerPluginScoped : IInternalDisposableService, IAddo }).Wait(); } - /// - public IAddonEventHandle? AddEvent(IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler) - => this.eventManagerService.AddEvent(this.plugin.EffectiveWorkingPluginId, atkUnitBase, atkResNode, eventType, eventHandler); - /// public IAddonEventHandle? AddEvent(nint atkUnitBase, nint atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventDelegate eventDelegate) => this.eventManagerService.AddEvent(this.plugin.EffectiveWorkingPluginId, atkUnitBase, atkResNode, eventType, eventDelegate); diff --git a/Dalamud/Game/Addon/Events/AddonEventType.cs b/Dalamud/Game/Addon/Events/AddonEventType.cs index cd04152ca..2c88c797b 100644 --- a/Dalamud/Game/Addon/Events/AddonEventType.cs +++ b/Dalamud/Game/Addon/Events/AddonEventType.cs @@ -121,12 +121,6 @@ public enum AddonEventType : byte /// ListItemClick = 35, - /// - /// AtkComponentList Toggle. - /// - [Obsolete("Use ListItemClick")] - ListItemToggle = 35, - /// /// AtkComponentList Double Click. /// diff --git a/Dalamud/Game/Addon/Events/AddonEventData.cs b/Dalamud/Game/Addon/Events/EventDataTypes/AddonEventData.cs similarity index 59% rename from Dalamud/Game/Addon/Events/AddonEventData.cs rename to Dalamud/Game/Addon/Events/EventDataTypes/AddonEventData.cs index 3a5c05660..423bf5eb9 100644 --- a/Dalamud/Game/Addon/Events/AddonEventData.cs +++ b/Dalamud/Game/Addon/Events/EventDataTypes/AddonEventData.cs @@ -1,10 +1,32 @@ -namespace Dalamud.Game.Addon.Events; +namespace Dalamud.Game.Addon.Events.EventDataTypes; /// /// Object representing data that is relevant in handling native events. /// public class AddonEventData { + /// + /// Initializes a new instance of the class. + /// + internal AddonEventData() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Other event data to copy. + internal AddonEventData(AddonEventData eventData) + { + this.AtkEventType = eventData.AtkEventType; + this.Param = eventData.Param; + this.AtkEventPointer = eventData.AtkEventPointer; + this.AtkEventDataPointer = eventData.AtkEventDataPointer; + this.AddonPointer = eventData.AddonPointer; + this.NodeTargetPointer = eventData.NodeTargetPointer; + this.AtkEventListener = eventData.AtkEventListener; + } + /// /// Gets the AtkEventType for this event. /// @@ -18,8 +40,8 @@ public class AddonEventData /// /// Gets the pointer to the AtkEvent object for this event. /// - /// Note: This is not a pointer to the AtkEventData object.

- /// Warning: AtkEvent->Node has been modified to be the AtkUnitBase*, and AtkEvent->Target has been modified to be the AtkResNode* that triggered this event. + /// Note: This is not a pointer to the AtkEventData object.

+ /// Warning: AtkEvent->Node has been modified to be the AtkUnitBase*, and AtkEvent->Target has been modified to be the AtkResNode* that triggered this event.
public nint AtkEventPointer { get; internal set; } /// diff --git a/Dalamud/Game/Addon/Events/EventDataTypes/AddonMouseEventData.cs b/Dalamud/Game/Addon/Events/EventDataTypes/AddonMouseEventData.cs new file mode 100644 index 000000000..27d56c287 --- /dev/null +++ b/Dalamud/Game/Addon/Events/EventDataTypes/AddonMouseEventData.cs @@ -0,0 +1,82 @@ +using System.Numerics; + +using FFXIVClientStructs.FFXIV.Component.GUI; + +using AtkMouseData = FFXIVClientStructs.FFXIV.Component.GUI.AtkEventData.AtkMouseData; +using ModifierFlag = FFXIVClientStructs.FFXIV.Component.GUI.AtkEventData.AtkMouseData.ModifierFlag; + +namespace Dalamud.Game.Addon.Events.EventDataTypes; + +/// +public unsafe class AddonMouseEventData : AddonEventData +{ + /// + /// Initializes a new instance of the class. + /// + internal AddonMouseEventData() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Other event data to copy. + internal AddonMouseEventData(AddonEventData eventData) + : base(eventData) + { + } + + /// + /// Gets a value indicating whether the event was a Left Mouse Click. + /// + public bool IsLeftClick => this.MouseData.ButtonId is 0; + + /// + /// Gets a value indicating whether the event was a Right Mouse Click. + /// + public bool IsRightClick => this.MouseData.ButtonId is 1; + + /// + /// Gets a value indicating whether there are any modifiers set such as alt, control, shift, or dragging. + /// + public bool IsNoModifier => this.MouseData.Modifier is 0; + + /// + /// Gets a value indicating whether alt was being held when this event triggered. + /// + public bool IsAltHeld => this.MouseData.Modifier.HasFlag(ModifierFlag.Alt); + + /// + /// Gets a value indicating whether control was being held when this event triggered. + /// + public bool IsControlHeld => this.MouseData.Modifier.HasFlag(ModifierFlag.Ctrl); + + /// + /// Gets a value indicating whether shift was being held when this event triggered. + /// + public bool IsShiftHeld => this.MouseData.Modifier.HasFlag(ModifierFlag.Shift); + + /// + /// Gets a value indicating whether this event is a mouse drag or not. + /// + public bool IsDragging => this.MouseData.Modifier.HasFlag(ModifierFlag.Dragging); + + /// + /// Gets a value indicating whether the event was a scroll up. + /// + public bool IsScrollUp => this.MouseData.WheelDirection is 1; + + /// + /// Gets a value indicating whether the event was a scroll down. + /// + public bool IsScrollDown => this.MouseData.WheelDirection is -1; + + /// + /// Gets the position of the mouse when this event was triggered. + /// + public Vector2 Position => new(this.MouseData.PosX, this.MouseData.PosY); + + private AtkEventData* AtkEventData => (AtkEventData*)this.AtkEventDataPointer; + + private AtkMouseData MouseData => this.AtkEventData->MouseData; +} diff --git a/Dalamud/Game/Addon/Events/PluginEventController.cs b/Dalamud/Game/Addon/Events/PluginEventController.cs index 0b1491e77..afaee9966 100644 --- a/Dalamud/Game/Addon/Events/PluginEventController.cs +++ b/Dalamud/Game/Addon/Events/PluginEventController.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; +using Dalamud.Game.Addon.Events.EventDataTypes; using Dalamud.Game.Gui; using Dalamud.Logging.Internal; using Dalamud.Plugin.Services; @@ -29,49 +30,6 @@ internal unsafe class PluginEventController : IDisposable private List Events { get; } = new(); - /// - /// Adds a tracked event. - /// - /// The Parent addon for the event. - /// The Node for the event. - /// The Event Type. - /// The delegate to call when invoking this event. - /// IAddonEventHandle used to remove the event. - [Obsolete("Use AddEvent that uses AddonEventDelegate instead of AddonEventHandler")] - 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 = addon->NameString, - ParamKey = eventId, - EventType = atkEventType, - EventGuid = eventGuid, - }; - - var eventEntry = new AddonEventEntry - { - Addon = atkUnitBase, - Handler = handler, - Delegate = null, - 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; - } - /// /// Adds a tracked event. /// @@ -100,7 +58,6 @@ internal unsafe class PluginEventController : IDisposable { Addon = atkUnitBase, Delegate = eventDelegate, - Handler = null, Node = atkResNode, EventType = atkEventType, ParamKey = eventId, @@ -184,7 +141,7 @@ internal unsafe class PluginEventController : IDisposable if (currentAddonPointer != eventEntry.Addon) return; // Make sure the addon is not unloaded - var atkUnitBase = (AtkUnitBase*)currentAddonPointer; + var atkUnitBase = currentAddonPointer.Struct; if (atkUnitBase->UldManager.LoadedState == AtkLoadState.Unloaded) return; // Does this addon contain the node this event is for? (by address) @@ -230,7 +187,6 @@ internal unsafe class PluginEventController : IDisposable this.EventListener.UnregisterEvent(atkResNode, eventType, eventEntry.ParamKey); } - [Api13ToDo("Remove invoke from eventInfo.Handler, and remove nullability from eventInfo.Delegate?.Invoke")] private void PluginEventListHandler(AtkEventListener* self, AtkEventType eventType, uint eventParam, AtkEvent* eventPtr, AtkEventData* eventDataPtr) { try @@ -238,10 +194,7 @@ internal unsafe class PluginEventController : IDisposable if (eventPtr 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)eventPtr->Node, (nint)eventPtr->Target); - - eventInfo.Delegate?.Invoke((AddonEventType)eventType, new AddonEventData + eventInfo.Delegate.Invoke((AddonEventType)eventType, new AddonEventData { AddonPointer = (nint)eventPtr->Node, NodeTargetPointer = (nint)eventPtr->Target, diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonArgs.cs index 36083337e..8a97f5cc2 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonArgs.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonArgs.cs @@ -1,8 +1,4 @@ -using System.Runtime.CompilerServices; - -using Dalamud.Memory; - -using FFXIVClientStructs.FFXIV.Component.GUI; +using Dalamud.Game.NativeWrapper; namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes; @@ -17,7 +13,6 @@ public abstract unsafe class AddonArgs public const string InvalidAddon = "NullAddon"; private string? addonName; - private IntPtr addon; /// /// Gets the name of the addon this args referrers to. @@ -27,10 +22,10 @@ public abstract unsafe class AddonArgs /// /// Gets the pointer to the addons AtkUnitBase. /// - public nint Addon + public AtkUnitBasePtr Addon { - get => this.AddonInternal; - init => this.AddonInternal = value; + get; + internal set; } /// @@ -38,22 +33,6 @@ public abstract unsafe class AddonArgs /// public abstract AddonArgsType Type { get; } - /// - /// Gets or sets the pointer to the addons AtkUnitBase. - /// - internal nint AddonInternal - { - get => this.addon; - set - { - this.addon = value; - - // Note: always clear addonName on updating the addon being pointed. - // Same address may point to a different addon. - this.addonName = null; - } - } - /// /// Checks if addon name matches the given span of char. /// @@ -61,19 +40,27 @@ public abstract unsafe class AddonArgs /// Whether it is the case. internal bool IsAddon(ReadOnlySpan name) { - if (this.Addon == nint.Zero) return false; - if (name.Length is 0 or > 0x20) + if (this.Addon.IsNull) return false; - var addonPointer = (AtkUnitBase*)this.Addon; - if (addonPointer->Name[0] == 0) return false; + if (name.Length is 0 or > 32) + return false; - // note: might want to rewrite this to just compare to NameString - return MemoryHelper.EqualsZeroTerminatedString( - name, - (nint)Unsafe.AsPointer(ref addonPointer->Name[0]), - null, - 0x20); + var addonName = this.Addon.Name; + + if (string.IsNullOrEmpty(addonName)) + return false; + + return name == addonName; + } + + /// + /// Clears this AddonArgs values. + /// + internal virtual void Clear() + { + this.addonName = null; + this.Addon = 0; } /// @@ -82,11 +69,13 @@ public abstract unsafe class AddonArgs /// The name of the addon for this object. when invalid. private string GetAddonName() { - if (this.Addon == nint.Zero) return InvalidAddon; + if (this.Addon.IsNull) return InvalidAddon; - var addonPointer = (AtkUnitBase*)this.Addon; - if (addonPointer->Name[0] == 0) return InvalidAddon; + var name = this.Addon.Name; - return this.addonName ??= addonPointer->NameString; + if (string.IsNullOrEmpty(name)) + return InvalidAddon; + + return this.addonName ??= name; } } diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonReceiveEventArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonReceiveEventArgs.cs index a557b0cb3..980fe4f2f 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonReceiveEventArgs.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonReceiveEventArgs.cs @@ -41,4 +41,14 @@ public class AddonReceiveEventArgs : AddonArgs, ICloneable /// object ICloneable.Clone() => this.Clone(); + + /// + internal override void Clear() + { + base.Clear(); + this.AtkEventType = default; + this.EventParam = default; + this.AtkEvent = default; + this.Data = default; + } } diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRefreshArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRefreshArgs.cs index 6e1b11ead..d28631c3c 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRefreshArgs.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRefreshArgs.cs @@ -38,4 +38,12 @@ public class AddonRefreshArgs : AddonArgs, ICloneable /// object ICloneable.Clone() => this.Clone(); + + /// + internal override void Clear() + { + base.Clear(); + this.AtkValueCount = default; + this.AtkValues = default; + } } diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRequestedUpdateArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRequestedUpdateArgs.cs index 26357abb0..e87a980fd 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRequestedUpdateArgs.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRequestedUpdateArgs.cs @@ -1,4 +1,4 @@ -namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes; +namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes; /// /// Addon argument data for OnRequestedUpdate events. @@ -31,4 +31,12 @@ public class AddonRequestedUpdateArgs : AddonArgs, ICloneable /// object ICloneable.Clone() => this.Clone(); + + /// + internal override void Clear() + { + base.Clear(); + this.NumberArrayData = default; + this.StringArrayData = default; + } } diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonSetupArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonSetupArgs.cs index 19c93ce25..0dd9ecee2 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonSetupArgs.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonSetupArgs.cs @@ -1,4 +1,4 @@ -using FFXIVClientStructs.FFXIV.Component.GUI; +using FFXIVClientStructs.FFXIV.Component.GUI; namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes; @@ -38,4 +38,12 @@ public class AddonSetupArgs : AddonArgs, ICloneable /// object ICloneable.Clone() => this.Clone(); + + /// + internal override void Clear() + { + base.Clear(); + this.AtkValueCount = default; + this.AtkValues = default; + } } diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonUpdateArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonUpdateArgs.cs index cc34a7531..a263f6ae4 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonUpdateArgs.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonUpdateArgs.cs @@ -35,4 +35,11 @@ public class AddonUpdateArgs : AddonArgs, ICloneable /// object ICloneable.Clone() => this.Clone(); + + /// + internal override void Clear() + { + base.Clear(); + this.TimeDeltaInternal = default; + } } diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs index b9179edde..b44ab8764 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs @@ -238,7 +238,8 @@ internal unsafe class AddonLifecycle : IInternalDisposableService } using var returner = this.argsPool.Rent(out AddonSetupArgs arg); - arg.AddonInternal = (nint)addon; + arg.Clear(); + arg.Addon = (nint)addon; arg.AtkValueCount = valueCount; arg.AtkValues = (nint)values; this.InvokeListenersSafely(AddonEvent.PreSetup, arg); @@ -270,7 +271,8 @@ internal unsafe class AddonLifecycle : IInternalDisposableService } using var returner = this.argsPool.Rent(out AddonFinalizeArgs arg); - arg.AddonInternal = (nint)atkUnitBase[0]; + arg.Clear(); + arg.Addon = (nint)atkUnitBase[0]; this.InvokeListenersSafely(AddonEvent.PreFinalize, arg); try @@ -286,7 +288,8 @@ internal unsafe class AddonLifecycle : IInternalDisposableService private void OnAddonDraw(AtkUnitBase* addon) { using var returner = this.argsPool.Rent(out AddonDrawArgs arg); - arg.AddonInternal = (nint)addon; + arg.Clear(); + arg.Addon = (nint)addon; this.InvokeListenersSafely(AddonEvent.PreDraw, arg); try @@ -304,7 +307,8 @@ internal unsafe class AddonLifecycle : IInternalDisposableService private void OnAddonUpdate(AtkUnitBase* addon, float delta) { using var returner = this.argsPool.Rent(out AddonUpdateArgs arg); - arg.AddonInternal = (nint)addon; + arg.Clear(); + arg.Addon = (nint)addon; arg.TimeDeltaInternal = delta; this.InvokeListenersSafely(AddonEvent.PreUpdate, arg); @@ -325,7 +329,8 @@ internal unsafe class AddonLifecycle : IInternalDisposableService var result = false; using var returner = this.argsPool.Rent(out AddonRefreshArgs arg); - arg.AddonInternal = (nint)addon; + arg.Clear(); + arg.Addon = (nint)addon; arg.AtkValueCount = valueCount; arg.AtkValues = (nint)values; this.InvokeListenersSafely(AddonEvent.PreRefresh, arg); @@ -348,7 +353,8 @@ internal unsafe class AddonLifecycle : IInternalDisposableService private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData) { using var returner = this.argsPool.Rent(out AddonRequestedUpdateArgs arg); - arg.AddonInternal = (nint)addon; + arg.Clear(); + arg.Addon = (nint)addon; arg.NumberArrayData = (nint)numberArrayData; arg.StringArrayData = (nint)stringArrayData; this.InvokeListenersSafely(AddonEvent.PreRequestedUpdate, arg); diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleReceiveEventListener.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleReceiveEventListener.cs index a66440b25..0d2bcc7f2 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleReceiveEventListener.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleReceiveEventListener.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using Dalamud.Hooking; @@ -86,7 +86,8 @@ internal unsafe class AddonLifecycleReceiveEventListener : IDisposable } using var returner = this.argsPool.Rent(out AddonReceiveEventArgs arg); - arg.AddonInternal = (nint)addon; + arg.Clear(); + arg.Addon = (nint)addon; arg.AtkEventType = (byte)eventType; arg.EventParam = eventParam; arg.AtkEvent = (IntPtr)atkEvent; diff --git a/Dalamud/Game/ChatHandlers.cs b/Dalamud/Game/ChatHandlers.cs index 61cd88a05..c57dd70b8 100644 --- a/Dalamud/Game/ChatHandlers.cs +++ b/Dalamud/Game/ChatHandlers.cs @@ -119,8 +119,6 @@ internal partial class ChatHandlers : IServiceType if (string.IsNullOrEmpty(this.configuration.LastVersion) || !Util.AssemblyVersion.StartsWith(this.configuration.LastVersion)) { var linkPayload = chatGui.AddChatLinkHandler( - "dalamud", - 8459324, (_, _) => dalamudInterface.OpenPluginInstallerTo(PluginInstallerOpenKind.Changelogs)); var updateMessage = new SeStringBuilder() diff --git a/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs b/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs index ef6649d7d..df4620f93 100644 --- a/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs +++ b/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs @@ -188,12 +188,6 @@ public enum ConditionFlag /// ExecutingCraftingAction = 40, - /// - /// Unable to execute command while crafting. - /// - [Obsolete("Renamed to ExecutingCraftingAction.")] - Crafting40 = 40, - /// /// Unable to execute command while preparing to craft. /// @@ -205,12 +199,6 @@ public enum ConditionFlag /// Includes fishing. ExecutingGatheringAction = 42, - /// - /// Unable to execute command while gathering. - /// - [Obsolete("Renamed to ExecutingGatheringAction.")] - Gathering42 = 42, - /// /// Unable to execute command while fishing. /// @@ -235,12 +223,6 @@ public enum ConditionFlag /// Jumping = 48, - /// - /// Unable to execute command while auto-run is active. - /// - [Obsolete("To avoid confusion, renamed to UsingChocoboTaxi.")] - AutorunActive = 49, - /// /// Unable to execute command while auto-run is active. /// @@ -282,12 +264,6 @@ public enum ConditionFlag /// BoundByDuty56 = 56, - /// - /// Unable to execute command at this time. - /// - [Obsolete("Renamed to MountOrOrnamentTransition.")] - Unknown57 = 57, - /// /// Unable to execute command at this time. /// @@ -461,12 +437,6 @@ public enum ConditionFlag /// RolePlaying = 90, - /// - /// Unable to execute command while bound by duty. - /// - [Obsolete("Use InDutyQueue")] - BoundToDuty97 = 91, - /// /// Unable to execute command while bound by duty. /// Specifically triggered when you are in a queue for a duty but not inside a duty. @@ -483,12 +453,6 @@ public enum ConditionFlag /// WaitingToVisitOtherWorld = 93, - /// - /// Unable to execute command while using a parasol. - /// - [Obsolete("Renamed to UsingFashionAccessory.")] - UsingParasol = 94, - /// /// Unable to execute command while using a fashion accessory. /// diff --git a/Dalamud/Game/ClientState/Fates/Fate.cs b/Dalamud/Game/ClientState/Fates/Fate.cs index 2da2dde9d..504b690c3 100644 --- a/Dalamud/Game/ClientState/Fates/Fate.cs +++ b/Dalamud/Game/ClientState/Fates/Fate.cs @@ -3,7 +3,6 @@ using System.Numerics; using Dalamud.Data; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Memory; -using Dalamud.Utility; using Lumina.Excel; @@ -69,12 +68,6 @@ public interface IFate : IEquatable /// byte Progress { get; } - /// - /// Gets a value indicating whether this has a EXP bonus. - /// - [Obsolete($"Use {nameof(HasBonus)} instead")] - bool HasExpBonus { get; } - /// /// Gets a value indicating whether this has a bonus. /// @@ -222,10 +215,6 @@ internal unsafe partial class Fate : IFate /// public byte Progress => this.Struct->Progress; - /// - [Api13ToDo("Remove")] - public bool HasExpBonus => this.HasBonus; - /// public bool HasBonus => this.Struct->IsBonus; diff --git a/Dalamud/Game/Gui/ChatGui.cs b/Dalamud/Game/Gui/ChatGui.cs index 022968c7e..6de4e3c3f 100644 --- a/Dalamud/Game/Gui/ChatGui.cs +++ b/Dalamud/Game/Gui/ChatGui.cs @@ -12,6 +12,7 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; using Dalamud.Memory; +using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Services; using Dalamud.Utility; @@ -40,7 +41,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui private static readonly ModuleLog Log = new("ChatGui"); private readonly Queue chatQueue = new(); - private readonly Dictionary<(string PluginName, uint CommandId), Action> dalamudLinkHandlers = new(); + private readonly Dictionary<(string PluginName, Guid CommandId), Action> dalamudLinkHandlers = new(); private readonly Hook printMessageHook; private readonly Hook inventoryItemCopyHook; @@ -49,7 +50,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui [ServiceManager.ServiceDependency] private readonly DalamudConfiguration configuration = Service.Get(); - private ImmutableDictionary<(string PluginName, uint CommandId), Action>? dalamudLinkHandlersCopy; + private ImmutableDictionary<(string PluginName, Guid CommandId), Action>? dalamudLinkHandlersCopy; [ServiceManager.ServiceConstructor] private ChatGui() @@ -85,7 +86,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui public byte LastLinkedItemFlags { get; private set; } /// - public IReadOnlyDictionary<(string PluginName, uint CommandId), Action> RegisteredLinkHandlers + public IReadOnlyDictionary<(string PluginName, Guid CommandId), Action> RegisteredLinkHandlers { get { @@ -161,6 +162,28 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui #endregion + #region Chat Links + + /// + public DalamudLinkPayload AddChatLinkHandler(Action commandAction) + { + return this.AddChatLinkHandler("Dalamud", commandAction); + } + + /// + public void RemoveChatLinkHandler(Guid commandId) + { + this.RemoveChatLinkHandler("Dalamud", commandId); + } + + /// + public void RemoveChatLinkHandler() + { + this.RemoveChatLinkHandler("Dalamud"); + } + + #endregion + /// /// Process a chat queue. /// @@ -217,12 +240,11 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui /// Create a link handler. /// /// The name of the plugin handling the link. - /// The ID of the command to run. /// The command action itself. /// A payload for handling. - [Api13ToDo("Plugins should not specify their own command IDs here. We should assign them ourselves.")] - internal DalamudLinkPayload AddChatLinkHandler(string pluginName, uint commandId, Action commandAction) + internal DalamudLinkPayload AddChatLinkHandler(string pluginName, Action commandAction) { + var commandId = Guid.CreateVersion7(); var payload = new DalamudLinkPayload { Plugin = pluginName, CommandId = commandId }; lock (this.dalamudLinkHandlers) { @@ -255,7 +277,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui /// /// The name of the plugin handling the link. /// The ID of the command to be removed. - internal void RemoveChatLinkHandler(string pluginName, uint commandId) + internal void RemoveChatLinkHandler(string pluginName, Guid commandId) { lock (this.dalamudLinkHandlers) { @@ -478,11 +500,15 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui [ServiceManager.ServiceDependency] private readonly ChatGui chatGuiService = Service.Get(); + private readonly LocalPlugin plugin; + /// /// Initializes a new instance of the class. /// - internal ChatGuiPluginScoped() + /// The plugin. + internal ChatGuiPluginScoped(LocalPlugin plugin) { + this.plugin = plugin; this.chatGuiService.ChatMessage += this.OnMessageForward; this.chatGuiService.CheckMessageHandled += this.OnCheckMessageForward; this.chatGuiService.ChatMessageHandled += this.OnMessageHandledForward; @@ -508,7 +534,7 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui public byte LastLinkedItemFlags => this.chatGuiService.LastLinkedItemFlags; /// - public IReadOnlyDictionary<(string PluginName, uint CommandId), Action> RegisteredLinkHandlers => this.chatGuiService.RegisteredLinkHandlers; + public IReadOnlyDictionary<(string PluginName, Guid CommandId), Action> RegisteredLinkHandlers => this.chatGuiService.RegisteredLinkHandlers; /// void IInternalDisposableService.DisposeService() @@ -524,6 +550,18 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui this.ChatMessageUnhandled = null; } + /// + public DalamudLinkPayload AddChatLinkHandler(Action commandAction) + => this.chatGuiService.AddChatLinkHandler(this.plugin.InternalName, commandAction); + + /// + public void RemoveChatLinkHandler(Guid commandId) + => this.chatGuiService.RemoveChatLinkHandler(this.plugin.InternalName, commandId); + + /// + public void RemoveChatLinkHandler() + => this.chatGuiService.RemoveChatLinkHandler(this.plugin.InternalName); + /// public void Print(XivChatEntry chat) => this.chatGuiService.Print(chat); diff --git a/Dalamud/Game/Gui/Dtr/DtrBar.cs b/Dalamud/Game/Gui/Dtr/DtrBar.cs index 6f3f9a8dd..8a8a4787a 100644 --- a/Dalamud/Game/Gui/Dtr/DtrBar.cs +++ b/Dalamud/Game/Gui/Dtr/DtrBar.cs @@ -5,6 +5,7 @@ using System.Threading; using Dalamud.Configuration.Internal; using Dalamud.Game.Addon.Events; +using Dalamud.Game.Addon.Events.EventDataTypes; using Dalamud.Game.Addon.Lifecycle; using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using Dalamud.Game.Text.SeStringHandling; @@ -330,7 +331,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar this.entriesReadOnlyCopy = null; } - private AtkUnitBase* GetDtr() => (AtkUnitBase*)this.gameGui.GetAddonByName("_DTR").ToPointer(); + private AtkUnitBase* GetDtr() => this.gameGui.GetAddonByName("_DTR").Struct; private void Update(IFramework unused) { @@ -427,7 +428,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar private void FixCollision(AddonEvent eventType, AddonArgs addonInfo) { - var addon = (AtkUnitBase*)addonInfo.Addon; + var addon = addonInfo.Addon.Struct; if (addon->RootNode is null || addon->UldManager.NodeList is null) return; float minX = addon->RootNode->Width; @@ -596,24 +597,13 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar newTextNode->TextColor = new ByteColor { R = 255, G = 255, B = 255, A = 255 }; newTextNode->EdgeColor = new ByteColor { R = 142, G = 106, B = 12, A = 255 }; - // ICreatable was restored, this may be necessary if AtkUldManager.CreateAtkTextNode(); is used instead of Create - // // Memory is filled with random data after being created, zero out some things to avoid issues. - // newTextNode->UnkPtr_1 = null; - // newTextNode->SelectStart = 0; - // newTextNode->SelectEnd = 0; - // newTextNode->FontCacheHandle = 0; - // newTextNode->CharSpacing = 0; - // newTextNode->BackgroundColor = new ByteColor { R = 0, G = 0, B = 0, A = 0 }; - // newTextNode->TextId = 0; - // newTextNode->SheetType = 0; - return newTextNode; } - private void DtrEventHandler(AddonEventType atkEventType, IntPtr atkUnitBase, IntPtr atkResNode) + private void DtrEventHandler(AddonEventType atkEventType, AddonEventData eventData) { - var addon = (AtkUnitBase*)atkUnitBase; - var node = (AtkResNode*)atkResNode; + var addon = (AtkUnitBase*)eventData.AddonPointer; + var node = (AtkResNode*)eventData.NodeTargetPointer; DtrBarEntry? dtrBarEntry = null; this.entriesLock.EnterReadLock(); @@ -652,7 +642,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar break; case AddonEventType.MouseClick: - dtrBarEntry.OnClick.Invoke(); + dtrBarEntry.OnClick?.Invoke(new AddonMouseEventData(eventData)); break; } } diff --git a/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs b/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs index 26708eb4c..54847705d 100644 --- a/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs +++ b/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs @@ -1,4 +1,5 @@ using Dalamud.Configuration.Internal; +using Dalamud.Game.Addon.Events.EventDataTypes; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Plugin.Internal.Types; using Dalamud.Utility; @@ -42,12 +43,6 @@ public interface IReadOnlyDtrBarEntry /// Gets a value indicating whether the user has hidden this entry from view through the Dalamud settings. /// public bool UserHidden { get; } - - /// - /// Triggers the click action of this entry. - /// - /// True, if a click action was registered and executed. - public bool TriggerClickAction(); } /// @@ -71,9 +66,9 @@ public interface IDtrBarEntry : IReadOnlyDtrBarEntry public new bool Shown { get; set; } /// - /// Gets or sets a action to be invoked when the user clicks on the dtr entry. + /// Gets or sets an action to be invoked when the user clicks on the dtr entry. /// - public Action? OnClick { get; set; } + public Action? OnClick { get; set; } /// /// Remove this entry from the bar. @@ -122,10 +117,8 @@ internal sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry /// public SeString? Tooltip { get; set; } - /// - /// Gets or sets a action to be invoked when the user clicks on the dtr entry. - /// - public Action? OnClick { get; set; } + /// + public Action? OnClick { get; set; } /// public bool HasClickAction => this.OnClick != null; @@ -145,7 +138,7 @@ internal sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry } /// - [Api13ToDo("Maybe make this config scoped to internalname?")] + [Api13ToDo("Maybe make this config scoped to internal name?")] public bool UserHidden => this.configuration.DtrIgnore?.Contains(this.Title) ?? false; /// @@ -178,16 +171,6 @@ internal sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry /// internal LocalPlugin? OwnerPlugin { get; set; } - /// - public bool TriggerClickAction() - { - if (this.OnClick == null) - return false; - - this.OnClick.Invoke(); - return true; - } - /// /// Remove this entry from the bar. /// You will need to re-acquire it from DtrBar to reuse it. diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs index a1e44918a..5c0770385 100644 --- a/Dalamud/Game/Gui/GameGui.cs +++ b/Dalamud/Game/Gui/GameGui.cs @@ -1,6 +1,7 @@ using System.Runtime.InteropServices; using Dalamud.Bindings.ImGui; +using Dalamud.Game.NativeWrapper; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Hooking; using Dalamud.Interface.Utility; @@ -165,79 +166,59 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui } /// - public IntPtr GetUIModule() + public UIModulePtr GetUIModule() { - var framework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance(); - if (framework == null) - return IntPtr.Zero; - - var uiModule = framework->GetUIModule(); - if (uiModule == null) - return IntPtr.Zero; - - return (IntPtr)uiModule; + return (nint)UIModule.Instance(); } /// - public IntPtr GetAddonByName(string name, int index = 1) + public AtkUnitBasePtr GetAddonByName(string name, int index = 1) { - var atkStage = AtkStage.Instance(); - if (atkStage == null) - return IntPtr.Zero; + var unitManager = RaptureAtkUnitManager.Instance(); + if (unitManager == null) + return 0; - var unitMgr = atkStage->RaptureAtkUnitManager; - if (unitMgr == null) - return IntPtr.Zero; - - var addon = unitMgr->GetAddonByName(name, index); - if (addon == null) - return IntPtr.Zero; - - return (IntPtr)addon; + return (nint)unitManager->GetAddonByName(name, index); } /// - public IntPtr FindAgentInterface(string addonName) + public AgentInterfacePtr GetAgentById(int id) + { + var agentModule = AgentModule.Instance(); + if (agentModule == null || id < 0 || id >= agentModule->Agents.Length) + return 0; + + return (nint)agentModule->Agents[id].Value; + } + + /// + public AgentInterfacePtr FindAgentInterface(string addonName) { var addon = this.GetAddonByName(addonName); return this.FindAgentInterface(addon); } /// - public IntPtr FindAgentInterface(void* addon) => this.FindAgentInterface((IntPtr)addon); - - /// - public IntPtr FindAgentInterface(IntPtr addonPtr) + public AgentInterfacePtr FindAgentInterface(AtkUnitBasePtr addon) { - if (addonPtr == IntPtr.Zero) - return IntPtr.Zero; + if (addon.IsNull) + return 0; - var uiModule = (UIModule*)this.GetUIModule(); - if (uiModule == null) - return IntPtr.Zero; - - var agentModule = uiModule->GetAgentModule(); + var agentModule = AgentModule.Instance(); if (agentModule == null) - return IntPtr.Zero; - - var addon = (AtkUnitBase*)addonPtr; - var addonId = addon->ParentId == 0 ? addon->Id : addon->ParentId; + return 0; + var addonId = addon.ParentId == 0 ? addon.Id : addon.ParentId; if (addonId == 0) - return IntPtr.Zero; + return 0; - var index = 0; - while (true) + foreach (AgentInterface* agent in agentModule->Agents) { - var agent = agentModule->GetAgentByInternalId((AgentId)index++); - if (agent == uiModule || agent == null) - break; - - if (agent->AddonId == addonId) - return new IntPtr(agent); + if (agent != null && agent->AddonId == addonId) + return (nint)agent; } - return IntPtr.Zero; + return 0; } /// @@ -452,25 +433,25 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui => this.gameGuiService.ScreenToWorld(screenPos, out worldPos, rayDistance); /// - public IntPtr GetUIModule() + public UIModulePtr GetUIModule() => this.gameGuiService.GetUIModule(); /// - public IntPtr GetAddonByName(string name, int index = 1) + public AtkUnitBasePtr GetAddonByName(string name, int index = 1) => this.gameGuiService.GetAddonByName(name, index); /// - public IntPtr FindAgentInterface(string addonName) + public AgentInterfacePtr GetAgentById(int id) + => this.gameGuiService.GetAgentById(id); + + /// + public AgentInterfacePtr FindAgentInterface(string addonName) => this.gameGuiService.FindAgentInterface(addonName); /// - public unsafe IntPtr FindAgentInterface(void* addon) + public AgentInterfacePtr FindAgentInterface(AtkUnitBasePtr addon) => this.gameGuiService.FindAgentInterface(addon); - /// - public IntPtr FindAgentInterface(IntPtr addonPtr) - => this.gameGuiService.FindAgentInterface(addonPtr); - private void UiHideToggledForward(object sender, bool toggled) => this.UiHideToggled?.Invoke(sender, toggled); private void HoveredItemForward(object sender, ulong itemId) => this.HoveredItemChanged?.Invoke(sender, itemId); diff --git a/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs b/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs index cd84b996b..7f83f180c 100644 --- a/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs +++ b/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs @@ -72,7 +72,7 @@ internal sealed class NamePlateGui : IInternalDisposableService, INamePlateGui /// public unsafe void RequestRedraw() { - var addon = (AddonNamePlate*)this.gameGui.GetAddonByName("NamePlate"); + var addon = (AddonNamePlate*)(nint)this.gameGui.GetAddonByName("NamePlate"); if (addon != null) { addon->DoFullUpdate = 1; diff --git a/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs b/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs index 475892205..5d6130cc1 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs @@ -127,7 +127,7 @@ public enum JobFlags : ulong RedMage = 1ul << 24, /// - /// Blue mage (BLM). + /// Blue mage (BLU). /// BlueMage = 1ul << 25, diff --git a/Dalamud/Game/Internal/Completion.cs b/Dalamud/Game/Internal/Completion.cs deleted file mode 100644 index 01c9c99c5..000000000 --- a/Dalamud/Game/Internal/Completion.cs +++ /dev/null @@ -1,322 +0,0 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; - -using Dalamud.Game.Command; -using Dalamud.Game.Text.SeStringHandling; -using Dalamud.Hooking; -using Dalamud.Plugin.Services; -using Dalamud.Utility; - -using FFXIVClientStructs.FFXIV.Client.System.String; -using FFXIVClientStructs.FFXIV.Client.UI; -using FFXIVClientStructs.FFXIV.Component.Completion; -using FFXIVClientStructs.FFXIV.Component.GUI; - -namespace Dalamud.Game.Internal; - -/// -/// This class adds dalamud and plugin commands to the chat box's autocompletion. -/// -[ServiceManager.EarlyLoadedService] -internal sealed unsafe class Completion : IInternalDisposableService -{ - // 0xFF is a magic group number that causes CompletionModule's internals to treat entries - // as raw strings instead of as lookups into an EXD sheet - private const int GroupNumber = 0xFF; - - [ServiceManager.ServiceDependency] - private readonly CommandManager commandManager = Service.Get(); - - [ServiceManager.ServiceDependency] - private readonly Framework framework = Service.Get(); - - private readonly Dictionary cachedCommands = []; - private readonly ConcurrentQueue addedCommands = []; - - private EntryStrings? dalamudCategory; - - private Hook? getSelection; - - // This is marked volatile since we set and check it from different threads. Instead of using a synchronization - // primitive, a volatile is sufficient since the absolute worst case is that we delay one extra frame to reset - // the list, which is fine - private volatile bool needsClear; - private bool disposed; - private nint wantedVtblPtr; - - /// - /// Initializes a new instance of the class. - /// - [ServiceManager.ServiceConstructor] - internal Completion() - { - this.commandManager.CommandAdded += this.OnCommandAdded; - this.commandManager.CommandRemoved += this.OnCommandRemoved; - - this.framework.Update += this.OnUpdate; - } - - /// Finalizes an instance of the class. - ~Completion() => this.Dispose(false); - - /// - void IInternalDisposableService.DisposeService() => this.Dispose(true); - - private static AtkUnitBase* FindOwningAddon(AtkComponentTextInput* component) - { - if (component == null) return null; - - var node = (AtkResNode*)component->OwnerNode; - if (node == null) return null; - - while (node->ParentNode != null) - node = node->ParentNode; - - foreach (var addon in RaptureAtkUnitManager.Instance()->AllLoadedUnitsList.Entries) - { - if (addon.Value->RootNode == node) - return addon; - } - - return null; - } - - private AtkComponentTextInput* GetActiveTextInput() - { - var mod = RaptureAtkModule.Instance(); - if (mod == null) return null; - - var basePtr = mod->TextInput.TargetTextInputEventInterface; - if (basePtr == null) return null; - - // Once CS has an implementation for multiple inheritance, we can remove this sig from dalamud - // as well as the nasty pointer arithmetic below. But for now, we need to do this manually. - // The AtkTextInputEventInterface* is the secondary base class for AtkComponentTextInput* - // so the pointer is sizeof(AtkComponentInputBase) into the object. We verify that we're looking - // at the object we think we are by confirming the pointed-to vtbl matches the known secondary vtbl for - // AtkComponentTextInput, and if it does, we can shift the pointer back to get the start of our text input - if (this.wantedVtblPtr == 0) - { - this.wantedVtblPtr = - Service.Get().GetStaticAddressFromSig( - "48 89 01 48 8D 05 ?? ?? ?? ?? 48 89 81 ?? ?? ?? ?? 48 8D 05 ?? ?? ?? ?? 48 89 81 ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 8B 48 68", - 4); - } - - var vtblPtr = *(nint*)basePtr; - if (vtblPtr != this.wantedVtblPtr) return null; - - // This needs to be updated if the layout/base order of AtkComponentTextInput changes - return (AtkComponentTextInput*)((AtkComponentInputBase*)basePtr - 1); - } - - private bool AllowCompletion(string cmd) - { - // this is one of our commands, let's see if we should allow this to be completed - var component = this.GetActiveTextInput(); - - // ContainingAddon or ContainingAddon2 aren't always populated, but they - // seem to be in any case where this is actually a completable AtkComponentTextInput - // In the worst case, we can walk the AtkNode tree- but let's try the easy pointers first - var addon = component->ContainingAddon; - if (addon == null) addon = component->ContainingAddon2; - if (addon == null) addon = FindOwningAddon(component); - - if (addon == null || addon->NameString != "ChatLog") - { - // we don't know what addon is completing, or we know it isn't ChatLog - // either way, we should just reject this completion - return false; - } - - // We're in ChatLog, so check if this is the start of the text input - // AtkComponentTextInput->UnkText1 is the evaluated version of the current text - // so if the command starts with that, then either it's empty or a prefix completion. - // In either case, we're happy to allow completion. - return cmd.StartsWith(component->UnkText1.StringPtr.ExtractText()); - } - - private void Dispose(bool disposing) - { - if (this.disposed) - return; - - if (disposing) - { - this.getSelection?.Disable(); - this.getSelection?.Dispose(); - this.framework.Update -= this.OnUpdate; - this.commandManager.CommandAdded -= this.OnCommandAdded; - this.commandManager.CommandRemoved -= this.OnCommandRemoved; - - this.dalamudCategory?.Dispose(); - this.ClearCachedCommands(); - } - - this.disposed = true; - } - - private void OnCommandAdded(object? sender, CommandManager.CommandEventArgs e) - { - if (e.CommandInfo.ShowInHelp) - this.addedCommands.Enqueue(e.Command); - } - - private void OnCommandRemoved(object? sender, CommandManager.CommandEventArgs e) => this.needsClear = true; - - private void OnUpdate(IFramework fw) - { - var atkModule = RaptureAtkModule.Instance(); - if (atkModule == null) return; - - var textInput = &atkModule->TextInput; - - if (textInput->CompletionModule == null) return; - - // Before we change _anything_ we need to check the state of the UI- if the completion list is open - // changes to the underlying data are extremely unsafe, so we'll just wait until the next frame - // worst case, someone tries to complete a command that _just_ got unloaded so it won't do anything - // but that's the same as making a typo, really - if (textInput->CompletionDepth > 0) return; - - // Create the category for Dalamud commands. - // This needs to be done here, since we cannot create Utf8Strings before the game - // has initialized (no allocator set up yet). - this.dalamudCategory ??= new EntryStrings("【Dalamud】"); - - this.LoadCommands(textInput->CompletionModule); - } - - private CategoryData* EnsureCategoryData(CompletionModule* module) - { - if (module == null) return null; - - if (this.getSelection == null) - { - this.getSelection = Hook.FromAddress( - (IntPtr)module->VirtualTable->GetSelection, - this.GetSelectionDetour); - this.getSelection.Enable(); - } - - for (var i = 0; i < module->CategoryNames.Count; i++) - { - if (module->CategoryNames[i].AsReadOnlySeStringSpan().ContainsText("【Dalamud】"u8)) - { - return module->CategoryData[i]; - } - } - - // Create the category since we don't have one - var categoryData = (CategoryData*)Memory.MemoryHelper.GameAllocateDefault((ulong)sizeof(CategoryData)); - categoryData->Ctor(GroupNumber, 0xFF); - module->AddCategoryData(GroupNumber, this.dalamudCategory!.Display->StringPtr, - this.dalamudCategory.Match->StringPtr, categoryData); - - return categoryData; - } - - private void ClearCachedCommands() - { - if (this.cachedCommands.Count == 0) - return; - - foreach (var entry in this.cachedCommands.Values) - { - entry.Dispose(); - } - - this.cachedCommands.Clear(); - } - - private void LoadCommands(CompletionModule* completionModule) - { - if (completionModule == null) return; - if (completionModule->CategoryNames.Count == 0) return; // We want this data populated first - - if (this.needsClear && this.cachedCommands.Count > 0) - { - this.needsClear = false; - completionModule->ClearCompletionData(); - this.ClearCachedCommands(); - return; - } - - var catData = this.EnsureCategoryData(completionModule); - if (catData == null) return; - - if (catData->CompletionData.Count == 0) - { - var inputCommands = this.commandManager.Commands.Where(pair => pair.Value.ShowInHelp); - foreach (var (cmd, _) in inputCommands) - AddEntry(cmd); - catData->SortEntries(); - - return; - } - - var needsSort = false; - while (this.addedCommands.TryDequeue(out var cmd)) - { - needsSort = true; - AddEntry(cmd); - } - - if (needsSort) - catData->SortEntries(); - - return; - - void AddEntry(string cmd) - { - if (this.cachedCommands.ContainsKey(cmd)) return; - - var cmdStr = new EntryStrings(cmd); - this.cachedCommands.Add(cmd, cmdStr); - completionModule->AddCompletionEntry( - GroupNumber, - 0xFF, - cmdStr.Display->StringPtr, - cmdStr.Match->StringPtr, - 0xFF); - } - } - - private int GetSelectionDetour(CompletionModule* thisPtr, CategoryData.CompletionDataStruct* dataStructs, int index, Utf8String* outputString, Utf8String* outputDisplayString) - { - var ret = this.getSelection!.Original.Invoke(thisPtr, dataStructs, index, outputString, outputDisplayString); - if (ret != -2 || outputString == null) return ret; - - // -2 means it was a plain text final selection, so it might be ours - // Unfortunately, the code that uses this string mangles the color macro for some reason... - // We'll just strip those out since we don't need the color in the chatbox - var txt = outputString->StringPtr.ExtractText(); - if (!this.cachedCommands.ContainsKey(txt)) - return ret; - - if (!this.AllowCompletion(txt)) - { - outputString->Clear(); - if (outputDisplayString != null) outputDisplayString->Clear(); - return ret; - } - - outputString->SetString(txt + " "); - return ret; - } - - private class EntryStrings(string command) : IDisposable - { - public Utf8String* Display { get; } = - Utf8String.FromSequence(new SeStringBuilder().AddUiForeground(command, 539).BuiltString.EncodeWithNullTerminator()); - - public Utf8String* Match { get; } = Utf8String.FromString(command); - - public void Dispose() - { - this.Display->Dtor(true); - this.Match->Dtor(true); - } - } -} diff --git a/Dalamud/Game/Internal/DalamudCompletion.cs b/Dalamud/Game/Internal/DalamudCompletion.cs new file mode 100644 index 000000000..ec5652b3c --- /dev/null +++ b/Dalamud/Game/Internal/DalamudCompletion.cs @@ -0,0 +1,279 @@ +using System.Collections.Generic; +using System.Linq; + +using Dalamud.Game.Command; +using Dalamud.Hooking; +using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Client.System.Memory; +using FFXIVClientStructs.FFXIV.Client.System.String; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Component.Completion; +using FFXIVClientStructs.FFXIV.Component.GUI; + +using Lumina.Text; + +namespace Dalamud.Game.Internal; + +/// +/// This class adds Dalamud and plugin commands to the chat box's autocompletion. +/// +[ServiceManager.EarlyLoadedService] +internal sealed unsafe class DalamudCompletion : IInternalDisposableService +{ + // 0xFF is a magic group number that causes CompletionModule's internals to treat entries + // as raw strings instead of as lookups into an EXD sheet + private const int GroupNumber = 0xFF; + + [ServiceManager.ServiceDependency] + private readonly CommandManager commandManager = Service.Get(); + + [ServiceManager.ServiceDependency] + private readonly Framework framework = Service.Get(); + + private readonly Dictionary cachedCommands = []; + + private EntryStrings? dalamudCategory; + + private Hook openSuggestionsHook; + private Hook? getSelectionHook; + + /// + /// Initializes a new instance of the class. + /// + [ServiceManager.ServiceConstructor] + internal DalamudCompletion() + { + this.framework.RunOnTick(this.Setup); + } + + /// + void IInternalDisposableService.DisposeService() + { + this.openSuggestionsHook?.Disable(); + this.openSuggestionsHook?.Dispose(); + + this.getSelectionHook?.Disable(); + this.getSelectionHook?.Dispose(); + + this.dalamudCategory?.Dispose(); + + this.ClearCachedCommands(); + } + + private void Setup() + { + var uiModule = UIModule.Instance(); + if (uiModule == null || uiModule->FrameCount == 0) + { + this.framework.RunOnTick(this.Setup); + return; + } + + this.dalamudCategory = new EntryStrings("【Dalamud】"); + + this.openSuggestionsHook = Hook.FromAddress( + (nint)AtkTextInput.MemberFunctionPointers.OpenCompletion, + this.OpenSuggestionsDetour); + + this.getSelectionHook = Hook.FromAddress( + (nint)uiModule->CompletionModule.VirtualTable->GetSelection, + this.GetSelectionDetour); + + this.openSuggestionsHook.Enable(); + this.getSelectionHook.Enable(); + } + + private void OpenSuggestionsDetour(AtkTextInput* thisPtr) + { + this.UpdateCompletionData(); + this.openSuggestionsHook!.Original(thisPtr); + } + + private int GetSelectionDetour(CompletionModule* thisPtr, CategoryData.CompletionDataStruct* dataStructs, int index, Utf8String* outputString, Utf8String* outputDisplayString) + { + var ret = this.getSelectionHook!.Original.Invoke(thisPtr, dataStructs, index, outputString, outputDisplayString); + this.HandleInsert(ret, outputString, outputDisplayString); + return ret; + } + + private void UpdateCompletionData() + { + if (!this.TryGetActiveTextInput(out var component, out var addon)) + { + if (this.HasDalamudCategory()) + this.ResetCompletionData(); + + return; + } + + var uiModule = UIModule.Instance(); + if (uiModule == null) + return; + + this.ResetCompletionData(); + this.ClearCachedCommands(); + + var currentText = component->UnkText1.StringPtr.ExtractText(); + + var commands = this.commandManager.Commands + .Where(kv => kv.Value.ShowInHelp && (currentText.Length == 0 || kv.Key.StartsWith(currentText))) + .OrderBy(kv => kv.Key); + + if (!commands.Any()) + return; + + var categoryData = (CategoryData*)IMemorySpace.GetDefaultSpace()->Malloc((ulong)sizeof(CategoryData), 0x08); + categoryData->Ctor(GroupNumber, 0xFF); + + uiModule->CompletionModule.AddCategoryData( + GroupNumber, + this.dalamudCategory!.Display->StringPtr, + this.dalamudCategory.Match->StringPtr, categoryData); + + foreach (var (cmd, info) in commands) + { + if (!this.cachedCommands.TryGetValue(cmd, out var entryString)) + this.cachedCommands.Add(cmd, entryString = new EntryStrings(cmd)); + + uiModule->CompletionModule.AddCompletionEntry( + GroupNumber, + 0xFF, + entryString.Display->StringPtr, + entryString.Match->StringPtr, + 0xFF); + } + + categoryData->SortEntries(); + } + + private void HandleInsert(int ret, Utf8String* outputString, Utf8String* outputDisplayString) + { + // -2 means it was a plain text final selection, so it might be ours. + if (ret != -2 || outputString == null) + return; + + // Strip out color payloads that we added to the string. + var txt = outputString->StringPtr.ExtractText(); + if (!this.cachedCommands.ContainsKey(txt)) + return; + + if (!this.TryGetActiveTextInput(out _, out _)) + { + outputString->Clear(); + + if (outputDisplayString != null) + outputDisplayString->Clear(); + + return; + } + + outputString->SetString(txt + ' '); + } + + private bool TryGetActiveTextInput(out AtkComponentTextInput* component, out AtkUnitBase* addon) + { + component = null; + addon = null; + + var raptureAtkModule = RaptureAtkModule.Instance(); + if (raptureAtkModule == null) + return false; + + var textInputEventInterface = raptureAtkModule->TextInput.TargetTextInputEventInterface; + if (textInputEventInterface == null) + return false; + + var ownerNode = textInputEventInterface->GetOwnerNode(); + if (ownerNode == null || ownerNode->GetNodeType() != NodeType.Component) + return false; + + var componentNode = (AtkComponentNode*)ownerNode; + var componentBase = componentNode->Component; + if (componentBase == null || componentBase->GetComponentType() != ComponentType.TextInput) + return false; + + component = (AtkComponentTextInput*)componentBase; + + addon = component->ContainingAddon; + + if (addon == null) + addon = component->ContainingAddon2; + + if (addon == null) + addon = RaptureAtkUnitManager.Instance()->GetAddonByNode((AtkResNode*)component->OwnerNode); + + return addon != null && addon->NameString == "ChatLog"; + } + + private bool HasDalamudCategory() + { + var uiModule = UIModule.Instance(); + if (uiModule == null) + return false; + + for (var i = 0; i < uiModule->CompletionModule.CategoryNames.Count; i++) + { + if (uiModule->CompletionModule.CategoryNames[i].AsReadOnlySeStringSpan().ContainsText("【Dalamud】"u8)) + { + return true; + } + } + + return false; + } + + private void ResetCompletionData() + { + var uiModule = UIModule.Instance(); + if (uiModule == null) + return; + + uiModule->CompletionModule.ClearCompletionData(); + + // This happens in UIModule.Update. Just repeat it to fill CompletionData back up with defaults. + uiModule->CompletionModule.Update( + &uiModule->CompletionSheetName, + &uiModule->CompletionOpenIconMacro, + &uiModule->CompletionCloseIconMacro, + 0); + } + + private void ClearCachedCommands() + { + foreach (var entry in this.cachedCommands.Values) + { + entry.Dispose(); + } + + this.cachedCommands.Clear(); + } + + private class EntryStrings : IDisposable + { + public EntryStrings(string command) + { + var rssb = SeStringBuilder.SharedPool.Get(); + + this.Display = Utf8String.FromSequence(rssb + .PushColorType(539) + .Append(command) + .PopColorType() + .GetViewAsSpan()); + + SeStringBuilder.SharedPool.Return(rssb); + + this.Match = Utf8String.FromString(command); + } + + public Utf8String* Display { get; } + + public Utf8String* Match { get; } + + public void Dispose() + { + this.Display->Dtor(true); + this.Match->Dtor(true); + } + } +} diff --git a/Dalamud/Game/Inventory/GameInventoryItem.cs b/Dalamud/Game/Inventory/GameInventoryItem.cs index 085200742..e5363d92a 100644 --- a/Dalamud/Game/Inventory/GameInventoryItem.cs +++ b/Dalamud/Game/Inventory/GameInventoryItem.cs @@ -1,7 +1,9 @@ +using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Dalamud.Data; +using Dalamud.Game.Inventory.Records; using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.Game; @@ -160,6 +162,25 @@ public unsafe struct GameInventoryItem : IEquatable } } + public IReadOnlyList MateriaEntries + { + get + { + if (ItemUtil.IsEventItem(this.BaseItemId) || this.IsMateriaUsedForDate) + return []; + + var result = new List(); + for (byte i = 0; i < this.InternalItem.GetMateriaCount(); i++) + { + var entry = new MateriaEntry(this.InternalItem.GetMateriaId(i), this.InternalItem.GetMateriaGrade(i)); + if (entry.IsValid()) + result.Add(entry); + } + + return result; + } + } + /// /// Gets the address of native inventory item in the game.
/// Can be 0 if this instance of does not point to a valid set of container type and slot.
diff --git a/Dalamud/Game/Inventory/Records/MateriaEntry.cs b/Dalamud/Game/Inventory/Records/MateriaEntry.cs new file mode 100644 index 000000000..4c7528123 --- /dev/null +++ b/Dalamud/Game/Inventory/Records/MateriaEntry.cs @@ -0,0 +1,42 @@ +using Dalamud.Data; + +using Lumina.Excel; +using Lumina.Excel.Sheets; + +namespace Dalamud.Game.Inventory.Records; + +/// +/// A record to hold easy information about a given piece of Materia. +/// +public record MateriaEntry +{ + /// + /// Initializes a new instance of the class. + /// + /// The ID of the materia. + /// The grade of the materia. + public MateriaEntry(ushort typeId, byte gradeValue) + { + this.Type = LuminaUtils.CreateRef(typeId); + this.Grade = LuminaUtils.CreateRef(gradeValue); + } + + /// + /// Gets the Lumina row for this Materia. + /// + public RowRef Type { get; } + + /// + /// Gets the Lumina row for this Materia's grade. + /// + public RowRef Grade { get; } + + /// + /// Checks if this MateriaEntry is valid. + /// + /// True if valid, false otherwise. + internal bool IsValid() + { + return this.Type.IsValid && this.Grade.IsValid; + } +} diff --git a/Dalamud/Game/NativeWrapper/AgentInterfacePtr.cs b/Dalamud/Game/NativeWrapper/AgentInterfacePtr.cs new file mode 100644 index 000000000..b5a6375a9 --- /dev/null +++ b/Dalamud/Game/NativeWrapper/AgentInterfacePtr.cs @@ -0,0 +1,96 @@ +using System.Runtime.InteropServices; + +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; + +namespace Dalamud.Game.NativeWrapper; + +/// +/// A readonly wrapper for AgentInterface. +/// +/// The address to the AgentInterface. +[StructLayout(LayoutKind.Explicit, Size = 0x08)] +public readonly unsafe struct AgentInterfacePtr(nint address) : IEquatable +{ + /// + /// The address to the AgentInterface. + /// + [FieldOffset(0x00)] + public readonly nint Address = address; + + /// + /// Gets a value indicating whether the underlying pointer is a nullptr. + /// + public readonly bool IsNull => this.Address == 0; + + /// + /// Gets a value indicating whether the agents addon is visible. + /// + public readonly AtkUnitBasePtr Addon + { + get + { + if (this.IsNull) + return 0; + + var raptureAtkUnitManager = RaptureAtkUnitManager.Instance(); + if (raptureAtkUnitManager == null) + return 0; + + return (nint)raptureAtkUnitManager->GetAddonById(this.AddonId); + } + } + + /// + /// Gets a value indicating whether the agent is active. + /// + public readonly ushort AddonId => (ushort)(this.IsNull ? 0 : this.Struct->GetAddonId()); + + /// + /// Gets a value indicating whether the agent is active. + /// + public readonly bool IsAgentActive => !this.IsNull && this.Struct->IsAgentActive(); + + /// + /// Gets a value indicating whether the agents addon is ready. + /// + public readonly bool IsAddonReady => !this.IsNull && this.Struct->IsAddonReady(); + + /// + /// Gets a value indicating whether the agents addon is visible. + /// + public readonly bool IsAddonShown => !this.IsNull && this.Struct->IsAddonShown(); + + /// + /// Gets the AgentInterface*. + /// + /// Internal use only. + internal readonly AgentInterface* Struct => (AgentInterface*)this.Address; + + public static implicit operator nint(AgentInterfacePtr wrapper) => wrapper.Address; + + public static implicit operator AgentInterfacePtr(nint address) => new(address); + + public static implicit operator AgentInterfacePtr(void* ptr) => new((nint)ptr); + + public static bool operator ==(AgentInterfacePtr left, AgentInterfacePtr right) => left.Address == right.Address; + + public static bool operator !=(AgentInterfacePtr left, AgentInterfacePtr right) => left.Address != right.Address; + + /// + /// Focuses the AtkUnitBase. + /// + /// true when the addon was focused, false otherwise. + public readonly bool FocusAddon() => this.IsNull && this.Struct->FocusAddon(); + + /// Determines whether the specified AgentInterfacePtr is equal to the current AgentInterfacePtr. + /// The AgentInterfacePtr to compare with the current AgentInterfacePtr. + /// true if the specified AgentInterfacePtr is equal to the current AgentInterfacePtr; otherwise, false. + public readonly bool Equals(AgentInterfacePtr other) => this.Address == other.Address; + + /// + public override readonly bool Equals(object obj) => obj is AgentInterfacePtr wrapper && this.Equals(wrapper); + + /// + public override readonly int GetHashCode() => ((nuint)this.Address).GetHashCode(); +} diff --git a/Dalamud/Game/NativeWrapper/AtkUnitBasePtr.cs b/Dalamud/Game/NativeWrapper/AtkUnitBasePtr.cs new file mode 100644 index 000000000..d4ebf4d3b --- /dev/null +++ b/Dalamud/Game/NativeWrapper/AtkUnitBasePtr.cs @@ -0,0 +1,171 @@ +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.InteropServices; + +using FFXIVClientStructs.FFXIV.Component.GUI; +using FFXIVClientStructs.Interop; + +namespace Dalamud.Game.NativeWrapper; + +/// +/// A readonly wrapper for AtkUnitBase. +/// +/// The address to the AtkUnitBase. +[StructLayout(LayoutKind.Explicit, Size = 0x08)] +public readonly unsafe struct AtkUnitBasePtr(nint address) : IEquatable +{ + /// + /// The address to the AtkUnitBase. + /// + [FieldOffset(0x00)] + public readonly nint Address = address; + + /// + /// Gets a value indicating whether the underlying pointer is a nullptr. + /// + public readonly bool IsNull => this.Address == 0; + + /// + /// Gets a value indicating whether the OnSetup function has been called. + /// + public readonly bool IsReady => !this.IsNull && this.Struct->IsReady; + + /// + /// Gets a value indicating whether the AtkUnitBase is visible. + /// + public readonly bool IsVisible => !this.IsNull && this.Struct->IsVisible; + + /// + /// Gets the name. + /// + public readonly string Name => this.IsNull ? string.Empty : this.Struct->NameString; + + /// + /// Gets the id. + /// + public readonly ushort Id => this.IsNull ? (ushort)0 : this.Struct->Id; + + /// + /// Gets the parent id. + /// + public readonly ushort ParentId => this.IsNull ? (ushort)0 : this.Struct->ParentId; + + /// + /// Gets the host id. + /// + public readonly ushort HostId => this.IsNull ? (ushort)0 : this.Struct->HostId; + + /// + /// Gets the scale. + /// + public readonly float Scale => this.IsNull ? 0f : this.Struct->Scale; + + /// + /// Gets the x-position. + /// + public readonly short X => this.IsNull ? (short)0 : this.Struct->X; + + /// + /// Gets the y-position. + /// + public readonly short Y => this.IsNull ? (short)0 : this.Struct->Y; + + /// + /// Gets the width. + /// + public readonly float Width => this.IsNull ? 0f : this.Struct->GetScaledWidth(false); + + /// + /// Gets the height. + /// + public readonly float Height => this.IsNull ? 0f : this.Struct->GetScaledHeight(false); + + /// + /// Gets the scaled width. + /// + public readonly float ScaledWidth => this.IsNull ? 0f : this.Struct->GetScaledWidth(true); + + /// + /// Gets the scaled height. + /// + public readonly float ScaledHeight => this.IsNull ? 0f : this.Struct->GetScaledHeight(true); + + /// + /// Gets the position. + /// + public readonly Vector2 Position => new(this.X, this.Y); + + /// + /// Gets the size. + /// + public readonly Vector2 Size => new(this.Width, this.Height); + + /// + /// Gets the scaled size. + /// + public readonly Vector2 ScaledSize => new(this.ScaledWidth, this.ScaledHeight); + + /// + /// Gets the number of entries. + /// + public readonly int AtkValuesCount => this.Struct->AtkValuesCount; + + /// + /// Gets an enumerable collection of of the addons current AtkValues. + /// + /// + /// An of corresponding to the addons AtkValues. + /// + public IEnumerable AtkValues + { + get + { + for (var i = 0; i < this.AtkValuesCount; i++) + { + AtkValuePtr ptr; + unsafe + { + ptr = new AtkValuePtr((nint)this.Struct->AtkValuesSpan.GetPointer(i)); + } + + yield return ptr; + } + } + } + + /// + /// Gets the AtkUnitBase*. + /// + /// Internal use only. + internal readonly AtkUnitBase* Struct => (AtkUnitBase*)this.Address; + + public static implicit operator nint(AtkUnitBasePtr wrapper) => wrapper.Address; + + public static implicit operator AtkUnitBasePtr(nint address) => new(address); + + public static implicit operator AtkUnitBasePtr(void* ptr) => new((nint)ptr); + + public static bool operator ==(AtkUnitBasePtr left, AtkUnitBasePtr right) => left.Address == right.Address; + + public static bool operator !=(AtkUnitBasePtr left, AtkUnitBasePtr right) => left.Address != right.Address; + + /// + /// Focuses the AtkUnitBase. + /// + public readonly void Focus() + { + if (!this.IsNull) + this.Struct->Focus(); + } + + /// Determines whether the specified AtkUnitBasePtr is equal to the current AtkUnitBasePtr. + /// The AtkUnitBasePtr to compare with the current AtkUnitBasePtr. + /// true if the specified AtkUnitBasePtr is equal to the current AtkUnitBasePtr; otherwise, false. + public readonly bool Equals(AtkUnitBasePtr other) => this.Address == other.Address; + + /// + public override readonly bool Equals(object obj) => obj is AtkUnitBasePtr wrapper && this.Equals(wrapper); + + /// + public override readonly int GetHashCode() => ((nuint)this.Address).GetHashCode(); +} diff --git a/Dalamud/Game/NativeWrapper/AtkValuePtr.cs b/Dalamud/Game/NativeWrapper/AtkValuePtr.cs new file mode 100644 index 000000000..a47483a66 --- /dev/null +++ b/Dalamud/Game/NativeWrapper/AtkValuePtr.cs @@ -0,0 +1,113 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Component.GUI; + +namespace Dalamud.Game.NativeWrapper; + +/// +/// A readonly wrapper for AtkValue. +/// +/// The address to the AtkValue. +[StructLayout(LayoutKind.Explicit, Size = 0x08)] +public readonly unsafe struct AtkValuePtr(nint address) : IEquatable +{ + /// + /// The address to the AtkValue. + /// + [FieldOffset(0x00)] + public readonly nint Address = address; + + /// + /// Gets a value indicating whether the underlying pointer is a nullptr. + /// + public readonly bool IsNull => this.Address == 0; + + /// + /// Gets the value type. + /// + public readonly AtkValueType ValueType => (AtkValueType)this.Struct->Type; + + /// + /// Gets the AtkValue*. + /// + /// Internal use only. + internal readonly AtkValue* Struct => (AtkValue*)this.Address; + + public static implicit operator nint(AtkValuePtr wrapper) => wrapper.Address; + + public static implicit operator AtkValuePtr(nint address) => new(address); + + public static implicit operator AtkValuePtr(void* ptr) => new((nint)ptr); + + public static bool operator ==(AtkValuePtr left, AtkValuePtr right) => left.Address == right.Address; + + public static bool operator !=(AtkValuePtr left, AtkValuePtr right) => left.Address != right.Address; + + /// + /// Gets the value of the underlying as a boxed object, based on its . + /// + /// + /// The boxed value represented by this , or null if the value is null or undefined. + /// + /// + /// Thrown if the value type is not currently handled by this implementation. + /// + public unsafe object? GetValue() + { + if (this.Struct == null) + return null; + + return this.ValueType switch + { + AtkValueType.Undefined or AtkValueType.Null => null, + AtkValueType.Bool => this.Struct->Bool, + AtkValueType.Int => this.Struct->Int, + AtkValueType.Int64 => this.Struct->Int64, + AtkValueType.UInt => this.Struct->UInt, + AtkValueType.UInt64 => this.Struct->UInt64, + AtkValueType.Float => this.Struct->Float, + AtkValueType.String or AtkValueType.String8 or AtkValueType.ManagedString => this.Struct->String.HasValue ? this.Struct->String.AsReadOnlySeString() : default, + AtkValueType.WideString => this.Struct->WideString == null ? string.Empty : new string(this.Struct->WideString), + AtkValueType.Pointer => (nint)this.Struct->Pointer, + _ => throw new NotImplementedException($"AtkValueType {this.ValueType} is currently not supported"), + }; + } + + /// + /// Attempts to retrieve the value as a strongly-typed object if the underlying type matches. + /// + /// The expected value type to extract. + /// + /// When this method returns true, contains the extracted value of type . + /// Otherwise, contains the default value of type . + /// + /// + /// true if the value was successfully extracted and matched ; otherwise, false. + /// + public unsafe bool TryGet([NotNullWhen(true)] out T? result) where T : struct + { + object? value = this.GetValue(); + if (value is T typed) + { + result = typed; + return true; + } + + result = default; + return false; + } + + /// Determines whether the specified AtkValuePtr is equal to the current AtkValuePtr. + /// The AtkValuePtr to compare with the current AtkValuePtr. + /// true if the specified AtkValuePtr is equal to the current AtkValuePtr; otherwise, false. + public readonly bool Equals(AtkValuePtr other) => this.Address == other.Address || this.Struct->EqualTo(other.Struct); + + /// + public override readonly bool Equals(object obj) => obj is AtkValuePtr wrapper && this.Equals(wrapper); + + /// + public override readonly int GetHashCode() => ((nuint)this.Address).GetHashCode(); +} diff --git a/Dalamud/Game/NativeWrapper/AtkValueType.cs b/Dalamud/Game/NativeWrapper/AtkValueType.cs new file mode 100644 index 000000000..ef169e102 --- /dev/null +++ b/Dalamud/Game/NativeWrapper/AtkValueType.cs @@ -0,0 +1,87 @@ +namespace Dalamud.Game.NativeWrapper; + +/// +/// Represents the data type of the AtkValue. +/// +public enum AtkValueType +{ + /// + /// The value is undefined or invalid. + /// + Undefined = 0, + + /// + /// The value is null. + /// + Null = 0x1, + + /// + /// The value is a boolean. + /// + Bool = 0x2, + + /// + /// The value is a 32-bit signed integer. + /// + Int = 0x3, + + /// + /// The value is a 64-bit signed integer. + /// + Int64 = 0x4, + + /// + /// The value is a 32-bit unsigned integer. + /// + UInt = 0x5, + + /// + /// The value is a 64-bit unsigned integer. + /// + UInt64 = 0x6, + + /// + /// The value is a 32-bit floating-point number. + /// + Float = 0x7, + + /// + /// The value points to a null-terminated 8-bit character string (ASCII or UTF-8). + /// + String = 0x8, + + /// + /// The value points to a null-terminated 16-bit character string (UTF-16 / wide string). + /// + WideString = 0x9, + + /// + /// The value points to a constant null-terminated 8-bit character string (const char*). + /// + String8 = 0xA, + + /// + /// The value is a vector. + /// + Vector = 0xB, + + /// + /// The value is a pointer. + /// + Pointer = 0xC, + + /// + /// The value is pointing to an array of AtkValue entries. + /// + AtkValues = 0xD, + + /// + /// The value is a managed string. See . + /// + ManagedString = 0x28, + + /// + /// The value is a managed vector. See . + /// + ManagedVector = 0x2B, +} diff --git a/Dalamud/Game/NativeWrapper/UIModulePtr.cs b/Dalamud/Game/NativeWrapper/UIModulePtr.cs new file mode 100644 index 000000000..f6b841610 --- /dev/null +++ b/Dalamud/Game/NativeWrapper/UIModulePtr.cs @@ -0,0 +1,51 @@ +using System.Runtime.InteropServices; + +using FFXIVClientStructs.FFXIV.Client.UI; + +namespace Dalamud.Game.NativeWrapper; + +/// +/// A readonly wrapper for UIModule. +/// +/// The address to the UIModule. +[StructLayout(LayoutKind.Explicit, Size = 0x08)] +public readonly unsafe struct UIModulePtr(nint address) : IEquatable +{ + /// + /// The address to the UIModule. + /// + [FieldOffset(0x00)] + public readonly nint Address = address; + + /// + /// Gets a value indicating whether the underlying pointer is a nullptr. + /// + public readonly bool IsNull => this.Address == 0; + + /// + /// Gets the UIModule*. + /// + /// Internal use only. + internal readonly UIModule* Struct => (UIModule*)this.Address; + + public static implicit operator nint(UIModulePtr wrapper) => wrapper.Address; + + public static implicit operator UIModulePtr(nint address) => new(address); + + public static implicit operator UIModulePtr(void* ptr) => new((nint)ptr); + + public static bool operator ==(UIModulePtr left, UIModulePtr right) => left.Address == right.Address; + + public static bool operator !=(UIModulePtr left, UIModulePtr right) => left.Address != right.Address; + + /// Determines whether the specified UIModulePtr is equal to the current UIModulePtr. + /// The UIModulePtr to compare with the current UIModulePtr. + /// true if the specified UIModulePtr is equal to the current UIModulePtr; otherwise, false. + public readonly bool Equals(UIModulePtr other) => this.Address == other.Address; + + /// + public override readonly bool Equals(object obj) => obj is UIModulePtr wrapper && this.Equals(wrapper); + + /// + public override readonly int GetHashCode() => ((nuint)this.Address).GetHashCode(); +} diff --git a/Dalamud/Game/Network/GameNetwork.cs b/Dalamud/Game/Network/GameNetwork.cs index 5c21add0f..be464ef34 100644 --- a/Dalamud/Game/Network/GameNetwork.cs +++ b/Dalamud/Game/Network/GameNetwork.cs @@ -17,7 +17,7 @@ namespace Dalamud.Game.Network; /// This class handles interacting with game network events. ///
[ServiceManager.EarlyLoadedService] -internal sealed unsafe class GameNetwork : IInternalDisposableService, IGameNetwork +internal sealed unsafe class GameNetwork : IInternalDisposableService { private readonly GameNetworkAddressResolver address; private readonly Hook processZonePacketDownHook; @@ -51,11 +51,23 @@ internal sealed unsafe class GameNetwork : IInternalDisposableService, IGameNetw this.processZonePacketUpHook.Enable(); } + /// + /// The delegate type of a network message event. + /// + /// The pointer to the raw data. + /// The operation ID code. + /// The source actor ID. + /// The taret actor ID. + /// The direction of the packed. + public delegate void OnNetworkMessageDelegate(nint dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction); + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate byte ProcessZonePacketUpDelegate(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4); - /// - public event IGameNetwork.OnNetworkMessageDelegate? NetworkMessage; + /// + /// Event that is called when a network message is sent/received. + /// + public event OnNetworkMessageDelegate? NetworkMessage; /// void IInternalDisposableService.DisposeService() @@ -136,39 +148,3 @@ internal sealed unsafe class GameNetwork : IInternalDisposableService, IGameNetw return this.processZonePacketUpHook.Original(a1, dataPtr, a3, a4); } } - -/// -/// Plugin-scoped version of a AddonLifecycle service. -/// -[PluginInterface] -[ServiceManager.ScopedService] -#pragma warning disable SA1015 -[ResolveVia] -#pragma warning restore SA1015 -internal class GameNetworkPluginScoped : IInternalDisposableService, IGameNetwork -{ - [ServiceManager.ServiceDependency] - private readonly GameNetwork gameNetworkService = Service.Get(); - - /// - /// Initializes a new instance of the class. - /// - internal GameNetworkPluginScoped() - { - this.gameNetworkService.NetworkMessage += this.NetworkMessageForward; - } - - /// - public event IGameNetwork.OnNetworkMessageDelegate? NetworkMessage; - - /// - void IInternalDisposableService.DisposeService() - { - this.gameNetworkService.NetworkMessage -= this.NetworkMessageForward; - - this.NetworkMessage = null; - } - - private void NetworkMessageForward(nint dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction) - => this.NetworkMessage?.Invoke(dataPtr, opCode, sourceActorId, targetActorId, direction); -} diff --git a/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs b/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs index f851e7686..262e7ad21 100644 --- a/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs +++ b/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs @@ -3,7 +3,6 @@ using Dalamud.Utility; using Lumina.Extensions; -using ItemKind = Dalamud.Game.Text.SeStringHandling.Payloads.ItemPayload.ItemKind; using LSheets = Lumina.Excel.Sheets; namespace Dalamud.Game.Text.Evaluator.Internal; diff --git a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs index 57040701c..624b62253 100644 --- a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs +++ b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs @@ -40,8 +40,6 @@ using StatusSheet = Lumina.Excel.Sheets.Status; namespace Dalamud.Game.Text.Evaluator; -#pragma warning disable SeStringEvaluator - /// /// Evaluator for SeStrings. /// diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs index 8b020b111..ee06172b4 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs @@ -16,7 +16,7 @@ public class DalamudLinkPayload : Payload public override PayloadType Type => PayloadType.DalamudLink; /// Gets the plugin command ID to be linked. - public uint CommandId { get; internal set; } + public Guid CommandId { get; internal set; } /// Gets an optional extra integer value 1. public int Extra1 { get; internal set; } @@ -40,7 +40,7 @@ public class DalamudLinkPayload : Payload var ssb = Lumina.Text.SeStringBuilder.SharedPool.Get(); var res = ssb.BeginMacro(MacroCode.Link) .AppendIntExpression((int)EmbeddedInfoType.DalamudLink - 1) - .AppendUIntExpression(this.CommandId) + .AppendStringExpression(this.CommandId.ToString()) .AppendIntExpression(this.Extra1) .AppendIntExpression(this.Extra2) .BeginStringExpression() @@ -72,15 +72,15 @@ public class DalamudLinkPayload : Payload if (!pluginExpression.TryGetString(out var pluginString)) return; - if (!commandIdExpression.TryGetUInt(out var commandId)) + if (!commandIdExpression.TryGetString(out var commandId)) return; this.Plugin = pluginString.ExtractText(); - this.CommandId = commandId; + this.CommandId = Guid.Parse(commandId.ExtractText()); } else { - if (!commandIdExpression.TryGetUInt(out var commandId)) + if (!commandIdExpression.TryGetString(out var commandId)) return; if (!extra1Expression.TryGetInt(out var extra1)) @@ -102,7 +102,7 @@ public class DalamudLinkPayload : Payload return; } - this.CommandId = commandId; + this.CommandId = Guid.Parse(commandId.ExtractText()); this.Extra1 = extra1; this.Extra2 = extra2; this.Plugin = extraData[0]; diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs index 25cdf7f9f..0c1f75a1d 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs @@ -72,33 +72,6 @@ public class ItemPayload : Payload { } - /// - /// Kinds of items that can be fetched from this payload. - /// - [Api13ToDo("Move this out of ItemPayload. It's used in other classes too.")] - public enum ItemKind : uint - { - /// - /// Normal items. - /// - Normal, - - /// - /// Collectible Items. - /// - Collectible = 500_000, - - /// - /// High-Quality items. - /// - Hq = 1_000_000, - - /// - /// Event/Key items. - /// - EventItem = 2_000_000, - } - /// public override PayloadType Type => PayloadType.Item; diff --git a/Dalamud/Game/Text/SeStringHandling/SeString.cs b/Dalamud/Game/Text/SeStringHandling/SeString.cs index b7618305a..a30ad9bbe 100644 --- a/Dalamud/Game/Text/SeStringHandling/SeString.cs +++ b/Dalamud/Game/Text/SeStringHandling/SeString.cs @@ -181,7 +181,7 @@ public class SeString /// An optional name override to display, instead of the actual item name. /// An SeString containing all the payloads necessary to display an item link in the chat log. public static SeString CreateItemLink(uint itemId, bool isHq, string? displayNameOverride = null) => - CreateItemLink(itemId, isHq ? ItemPayload.ItemKind.Hq : ItemPayload.ItemKind.Normal, displayNameOverride); + CreateItemLink(itemId, isHq ? ItemKind.Hq : ItemKind.Normal, displayNameOverride); /// /// Creates an SeString representing an entire Payload chain that can be used to link an item in the chat log. @@ -190,7 +190,7 @@ public class SeString /// The kind of item to link. /// An optional name override to display, instead of the actual item name. /// An SeString containing all the payloads necessary to display an item link in the chat log. - public static SeString CreateItemLink(uint itemId, ItemPayload.ItemKind kind = ItemPayload.ItemKind.Normal, string? displayNameOverride = null) + public static SeString CreateItemLink(uint itemId, ItemKind kind = ItemKind.Normal, string? displayNameOverride = null) { var clientState = Service.Get(); var seStringEvaluator = Service.Get(); diff --git a/Dalamud/Game/Text/SeStringHandling/SeStringBuilder.cs b/Dalamud/Game/Text/SeStringHandling/SeStringBuilder.cs index d5080e6e8..ae673e516 100644 --- a/Dalamud/Game/Text/SeStringHandling/SeStringBuilder.cs +++ b/Dalamud/Game/Text/SeStringHandling/SeStringBuilder.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Dalamud.Game.Text.SeStringHandling.Payloads; +using Dalamud.Utility; namespace Dalamud.Game.Text.SeStringHandling; @@ -126,7 +127,7 @@ public class SeStringBuilder /// Kind of item to encode. /// Override for the item's name. /// The current builder. - public SeStringBuilder AddItemLink(uint itemId, ItemPayload.ItemKind kind = ItemPayload.ItemKind.Normal, string? itemNameOverride = null) => + public SeStringBuilder AddItemLink(uint itemId, ItemKind kind = ItemKind.Normal, string? itemNameOverride = null) => this.Append(SeString.CreateItemLink(itemId, kind, itemNameOverride)); /// diff --git a/Dalamud/Interface/Internal/UiDebug.cs b/Dalamud/Interface/Internal/UiDebug.cs index 11858e49d..4b7219fbd 100644 --- a/Dalamud/Interface/Internal/UiDebug.cs +++ b/Dalamud/Interface/Internal/UiDebug.cs @@ -100,8 +100,8 @@ internal unsafe class UiDebug } ImGui.Separator(); - ImGuiHelpers.ClickToCopyText($"Address: {(ulong)atkUnitBase:X}", $"{(ulong)atkUnitBase:X}"); - ImGuiHelpers.ClickToCopyText($"Agent: {(ulong)agent:X}", $"{(ulong)agent:X}"); + ImGuiHelpers.ClickToCopyText($"Address: {(nint)atkUnitBase:X}", $"{(nint)atkUnitBase:X}"); + ImGuiHelpers.ClickToCopyText($"Agent: {(nint)agent:X}", $"{(nint)agent:X}"); ImGui.Separator(); ImGui.Text($"Position: [ {atkUnitBase->X} , {atkUnitBase->Y} ]"); diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.cs index f377f8890..a7094457b 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.cs @@ -80,7 +80,7 @@ public unsafe partial class AddonTree : IDisposable { var ptr = GameGui.GetAddonByName(name); - if ((AtkUnitBase*)ptr != null) + if (!ptr.IsNull) { if (AddonTrees.TryGetValue(name, out var tree)) { @@ -151,7 +151,7 @@ public unsafe partial class AddonTree : IDisposable var uldManager = addon->UldManager; PrintFieldValuePair("Address", $"{(nint)addon:X}"); - PrintFieldValuePair("Agent", $"{GameGui.FindAgentInterface(addon):X}"); + PrintFieldValuePair("Agent", $"{(nint)GameGui.FindAgentInterface(addon):X}"); PrintFieldValuePairs( ("X", $"{addon->X}"), @@ -233,7 +233,7 @@ public unsafe partial class AddonTree : IDisposable /// true if the addon is found. private bool ValidateAddon(out AtkUnitBase* addon) { - addon = (AtkUnitBase*)GameGui.GetAddonByName(this.AddonName); + addon = GameGui.GetAddonByName(this.AddonName).Struct; if (addon == null || (nint)addon != this.InitialPtr) { this.Dispose(); diff --git a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs index d72c3dd41..c9850274b 100644 --- a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs +++ b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs @@ -49,7 +49,7 @@ internal unsafe partial class UiDebug2 /// Gets the base address for all unit lists. /// /// The address, if found. - internal static AtkUnitList* GetUnitListBaseAddr() => &((UIModule*)GameGui.GetUIModule())->GetRaptureAtkModule()->RaptureAtkUnitManager.AtkUnitManager.DepthLayerOneList; + internal static AtkUnitList* GetUnitListBaseAddr() => &RaptureAtkUnitManager.Instance()->DepthLayerOneList; private void DrawSidebar() { diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonWidget.cs index 8b63ab14a..3530dc725 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonWidget.cs @@ -1,7 +1,6 @@ -using Dalamud.Bindings.ImGui; -using Dalamud.Game.Gui; +using Dalamud.Bindings.ImGui; using Dalamud.Memory; -using Dalamud.Utility; +using Dalamud.Game.NativeWrapper; namespace Dalamud.Interface.Internal.Windows.Data.Widgets; @@ -12,7 +11,7 @@ internal unsafe class AddonWidget : IDataWindowWidget { private string inputAddonName = string.Empty; private int inputAddonIndex; - private nint findAgentInterfacePtr; + private AgentInterfacePtr agentInterfacePtr; /// public string DisplayName { get; init; } = "Addon"; @@ -40,30 +39,27 @@ internal unsafe class AddonWidget : IDataWindowWidget if (this.inputAddonName.IsNullOrEmpty()) return; - var address = gameGui.GetAddonByName(this.inputAddonName, this.inputAddonIndex); - - if (address == nint.Zero) + var addon = gameGui.GetAddonByName(this.inputAddonName, this.inputAddonIndex); + if (addon.IsNull) { ImGui.Text("Null"); return; } - var addon = (FFXIVClientStructs.FFXIV.Component.GUI.AtkUnitBase*)address; - var name = addon->NameString; - ImGui.TextUnformatted($"{name} - {Util.DescribeAddress(address)}\n v:{addon->IsVisible} x:{addon->X} y:{addon->Y} s:{addon->Scale}, w:{addon->RootNode->Width}, h:{addon->RootNode->Height}"); + ImGui.TextUnformatted($"{addon.Name} - {Util.DescribeAddress(addon)}\n v:{addon.IsVisible} x:{addon.X} y:{addon.Y} s:{addon.Scale}, w:{addon.Width}, h:{addon.Height}"); if (ImGui.Button("Find Agent")) { - this.findAgentInterfacePtr = gameGui.FindAgentInterface(address); + this.agentInterfacePtr = gameGui.FindAgentInterface(addon); } - if (this.findAgentInterfacePtr != nint.Zero) + if (!this.agentInterfacePtr.IsNull) { - ImGui.TextUnformatted($"Agent: {Util.DescribeAddress(this.findAgentInterfacePtr)}"); + ImGui.TextUnformatted($"Agent: {Util.DescribeAddress(this.agentInterfacePtr)}"); ImGui.SameLine(); if (ImGui.Button("C")) - ImGui.SetClipboardText(this.findAgentInterfacePtr.ToInt64().ToString("X")); + ImGui.SetClipboardText(this.agentInterfacePtr.Address.ToString("X")); } } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ItemPayloadSelfTestStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ItemPayloadSelfTestStep.cs index 7d16585d4..0244f3c5e 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ItemPayloadSelfTestStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ItemPayloadSelfTestStep.cs @@ -1,7 +1,7 @@ -using Dalamud.Bindings.ImGui; +using Dalamud.Bindings.ImGui; using Dalamud.Game.Gui; using Dalamud.Game.Text.SeStringHandling; -using Dalamud.Game.Text.SeStringHandling.Payloads; +using Dalamud.Utility; namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; @@ -58,7 +58,7 @@ internal class ItemPayloadSelfTestStep : ISelfTestStep this.currentSubStep++; break; case SubStep.PrintHqItem: - toPrint = SeString.CreateItemLink(hqItemId, ItemPayload.ItemKind.Hq); + toPrint = SeString.CreateItemLink(hqItemId, ItemKind.Hq); this.currentSubStep++; break; case SubStep.HoverHqItem: @@ -68,7 +68,7 @@ internal class ItemPayloadSelfTestStep : ISelfTestStep this.currentSubStep++; break; case SubStep.PrintCollectable: - toPrint = SeString.CreateItemLink(collectableItemId, ItemPayload.ItemKind.Collectible); + toPrint = SeString.CreateItemLink(collectableItemId, ItemKind.Collectible); this.currentSubStep++; break; case SubStep.HoverCollectable: @@ -78,7 +78,7 @@ internal class ItemPayloadSelfTestStep : ISelfTestStep this.currentSubStep++; break; case SubStep.PrintEventItem: - toPrint = SeString.CreateItemLink(eventItemId, ItemPayload.ItemKind.EventItem); + toPrint = SeString.CreateItemLink(eventItemId, ItemKind.EventItem); this.currentSubStep++; break; case SubStep.HoverEventItem: diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/Steps/MarketBoardSelfTestStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/MarketBoardSelfTestStep.cs index ac4a2a958..7f27de613 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/Steps/MarketBoardSelfTestStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/MarketBoardSelfTestStep.cs @@ -59,7 +59,7 @@ internal class MarketBoardSelfTestStep : ISelfTestStep ImGui.Text($"Quantity: {this.historyListing.Quantity.ToString()}"); ImGui.Text($"Buyer: {this.historyListing.BuyerName}"); ImGui.Text($"Sale Price: {this.historyListing.SalePrice.ToString()}"); - ImGui.Text($"Purchase Time: {this.historyListing.PurchaseTime.ToString(CultureInfo.InvariantCulture)}"); + ImGui.Text($"Purchase Time: {this.historyListing.PurchaseTime.ToLocalTime().ToString(CultureInfo.InvariantCulture)}"); ImGui.Separator(); if (ImGui.Button("Looks Correct / Skip")) { diff --git a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs index 028291375..cedd260a6 100644 --- a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs +++ b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs @@ -473,7 +473,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable { if (args is not AddonDrawArgs drawArgs) return; - var addon = (AtkUnitBase*)drawArgs.Addon; + var addon = drawArgs.Addon.Struct; var textNode = addon->GetTextNodeById(3); // look and feel init. should be harmless to set. diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs index c37434baf..6ae810dec 100644 --- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs +++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs @@ -1,4 +1,4 @@ -using System.Buffers; +using System.Buffers; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -116,19 +116,11 @@ internal sealed partial class FontAtlasFactory public IFontSpec DefaultFontSpec => this.DefaultFontSpecOverride ?? Service.Get().DefaultFontSpec -#pragma warning disable CS0618 // Type or member is obsolete - ?? (Service.Get().UseAxisFontsFromGame -#pragma warning restore CS0618 // Type or member is obsolete - ? new() - { - FontId = new GameFontAndFamilyId(GameFontFamily.Axis), - SizePx = InterfaceManager.DefaultFontSizePx, - } - : new SingleFontSpec - { - FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansJpMedium), - SizePx = InterfaceManager.DefaultFontSizePx + 1, - }); + ?? new SingleFontSpec() + { + FontId = new GameFontAndFamilyId(GameFontFamily.Axis), + SizePx = InterfaceManager.DefaultFontSizePx, + }; /// /// Gets the service instance of . diff --git a/Dalamud/Plugin/ActivePluginsChangedEventArgs.cs b/Dalamud/Plugin/ActivePluginsChangedEventArgs.cs new file mode 100644 index 000000000..0c857be79 --- /dev/null +++ b/Dalamud/Plugin/ActivePluginsChangedEventArgs.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; + +namespace Dalamud.Plugin; + +/// +/// Contains data about changes to the list of active plugins. +/// +public class ActivePluginsChangedEventArgs : EventArgs +{ + /// + /// Initializes a new instance of the class + /// with the specified parameters. + /// + /// The kind of change that triggered the event. + /// The internal names of the plugins affected by the change. + internal ActivePluginsChangedEventArgs(PluginListInvalidationKind kind, IEnumerable affectedInternalNames) + { + this.Kind = kind; + this.AffectedInternalNames = affectedInternalNames; + } + + /// + /// Gets the invalidation kind that caused this event to be fired. + /// + public PluginListInvalidationKind Kind { get; } + + /// + /// Gets the InternalNames of affected plugins. + /// + public IEnumerable AffectedInternalNames { get; } +} diff --git a/Dalamud/Plugin/DalamudPluginInterface.cs b/Dalamud/Plugin/DalamudPluginInterface.cs index e1c6b7830..25c998a42 100644 --- a/Dalamud/Plugin/DalamudPluginInterface.cs +++ b/Dalamud/Plugin/DalamudPluginInterface.cs @@ -13,8 +13,6 @@ using Dalamud.Data; using Dalamud.Game.Gui; using Dalamud.Game.Text; using Dalamud.Game.Text.Sanitizer; -using Dalamud.Game.Text.SeStringHandling; -using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Interface; using Dalamud.Interface.Internal; using Dalamud.Interface.Internal.Windows.PluginInstaller; @@ -427,39 +425,6 @@ internal sealed class DalamudPluginInterface : IDalamudPluginInterface, IDisposa #endregion - #region Chat Links - - // TODO API9: Move to chatgui, don't allow passing own commandId - - /// - /// Register a chat link handler. - /// - /// The ID of the command. - /// The action to be executed. - /// Returns an SeString payload for the link. - public DalamudLinkPayload AddChatLinkHandler(uint commandId, Action commandAction) - { - return Service.Get().AddChatLinkHandler(this.plugin.InternalName, commandId, commandAction); - } - - /// - /// Remove a chat link handler. - /// - /// The ID of the command. - public void RemoveChatLinkHandler(uint commandId) - { - Service.Get().RemoveChatLinkHandler(this.plugin.InternalName, commandId); - } - - /// - /// Removes all chat link handlers registered by the plugin. - /// - public void RemoveChatLinkHandler() - { - Service.Get().RemoveChatLinkHandler(this.plugin.InternalName); - } - #endregion - #region Dependency Injection /// @@ -523,15 +488,14 @@ internal sealed class DalamudPluginInterface : IDalamudPluginInterface, IDisposa /// /// Dispatch the active plugins changed event. /// - /// What action caused this event to be fired. - /// If this plugin was affected by the change. - internal void NotifyActivePluginsChanged(PluginListInvalidationKind kind, bool affectedThisPlugin) + /// The event arguments containing information about the change. + internal void NotifyActivePluginsChanged(ActivePluginsChangedEventArgs args) { foreach (var action in Delegate.EnumerateInvocationList(this.ActivePluginsChanged)) { try { - action(kind, affectedThisPlugin); + action(args); } catch (Exception ex) { diff --git a/Dalamud/Plugin/IDalamudPluginInterface.cs b/Dalamud/Plugin/IDalamudPluginInterface.cs index 5b7c3836e..1a1a47403 100644 --- a/Dalamud/Plugin/IDalamudPluginInterface.cs +++ b/Dalamud/Plugin/IDalamudPluginInterface.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading.Tasks; @@ -32,9 +32,8 @@ public interface IDalamudPluginInterface /// /// Delegate for events that listen to changes to the list of active plugins. /// - /// What action caused this event to be fired. - /// If this plugin was affected by the change. - public delegate void ActivePluginsChangedDelegate(PluginListInvalidationKind kind, bool affectedThisPlugin); + /// The event arguments containing information about the change. + public delegate void ActivePluginsChangedDelegate(ActivePluginsChangedEventArgs args); /// /// Event that gets fired when loc is changed @@ -281,25 +280,6 @@ public interface IDalamudPluginInterface /// directory with path of AppData/XIVLauncher/pluginConfig/PluginInternalName/loc. string GetPluginLocDirectory(); - /// - /// Register a chat link handler. - /// - /// The ID of the command. - /// The action to be executed. - /// Returns an SeString payload for the link. - DalamudLinkPayload AddChatLinkHandler(uint commandId, Action commandAction); - - /// - /// Remove a chat link handler. - /// - /// The ID of the command. - void RemoveChatLinkHandler(uint commandId); - - /// - /// Removes all chat link handlers registered by the plugin. - /// - void RemoveChatLinkHandler(); - /// /// Create a new object of the provided type using its default constructor, then inject objects and properties. /// diff --git a/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs b/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs index a35928b8a..169864bdf 100644 --- a/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs +++ b/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs @@ -100,8 +100,6 @@ internal class AutoUpdateManager : IServiceType this.openInstallerWindowLinkTask = Service.GetAsync().ContinueWith( chatGuiTask => chatGuiTask.Result.AddChatLinkHandler( - "Dalamud", - 1001, (_, _) => { Service.GetNullable()?.OpenPluginInstallerTo(PluginInstallerOpenKind.InstalledPlugins); diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index bfb1b3430..a4aa3919b 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -125,8 +125,6 @@ internal class PluginManager : IInternalDisposableService this.openInstallerWindowPluginChangelogsLink = Service.GetAsync().ContinueWith( chatGuiTask => chatGuiTask.Result.AddChatLinkHandler( - "Dalamud", - 1003, (_, _) => { Service.GetNullable()?.OpenPluginInstallerTo( @@ -1294,6 +1292,23 @@ internal class PluginManager : IInternalDisposableService /// The calling plugin, or null. public LocalPlugin? FindCallingPlugin() => this.FindCallingPlugin(new StackTrace()); + /// + /// Notifies all plugins that the active plugins list changed. + /// + /// The invalidation kind. + /// The affected plugins. + public void NotifyPluginsForStateChange(PluginListInvalidationKind kind, IEnumerable affectedInternalNames) + { + foreach (var installedPlugin in this.installedPluginsList) + { + if (!installedPlugin.IsLoaded || installedPlugin.DalamudInterface == null) + continue; + + installedPlugin.DalamudInterface.NotifyActivePluginsChanged( + new ActivePluginsChangedEventArgs(kind, affectedInternalNames)); + } + } + /// /// Resolves the services that a plugin may have a dependency on.
/// This is required, as the lifetime of a plugin cannot be longer than PluginManager, @@ -1823,20 +1838,6 @@ internal class PluginManager : IInternalDisposableService this.OnInstalledPluginsChanged?.InvokeSafely(); } - private void NotifyPluginsForStateChange(PluginListInvalidationKind kind, IEnumerable affectedInternalNames) - { - foreach (var installedPlugin in this.installedPluginsList) - { - if (!installedPlugin.IsLoaded || installedPlugin.DalamudInterface == null) - continue; - - installedPlugin.DalamudInterface.NotifyActivePluginsChanged( - kind, - // ReSharper disable once PossibleMultipleEnumeration - affectedInternalNames.Contains(installedPlugin.Manifest.InternalName)); - } - } - private void LoadAndStartLoadSyncPlugins() { try diff --git a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs index 4b2b62669..70b1db872 100644 --- a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs +++ b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs @@ -395,6 +395,9 @@ internal class LocalPlugin : IAsyncDisposable this.dalamudInterface); this.State = PluginState.Loaded; Log.Information("Finished loading {PluginName}", this.InternalName); + + var manager = Service.Get(); + manager.NotifyPluginsForStateChange(PluginListInvalidationKind.Loaded, [this.manifest.InternalName]); } catch (Exception ex) { @@ -470,6 +473,9 @@ internal class LocalPlugin : IAsyncDisposable this.State = PluginState.Unloaded; Log.Information("Finished unloading {PluginName}", this.InternalName); + + var manager = Service.Get(); + manager.NotifyPluginsForStateChange(PluginListInvalidationKind.Unloaded, [this.manifest.InternalName]); } catch (Exception ex) { diff --git a/Dalamud/Plugin/PluginListInvalidationKind.cs b/Dalamud/Plugin/PluginListInvalidationKind.cs index 4e7782703..588ae60d7 100644 --- a/Dalamud/Plugin/PluginListInvalidationKind.cs +++ b/Dalamud/Plugin/PluginListInvalidationKind.cs @@ -1,10 +1,20 @@ -namespace Dalamud.Plugin; +namespace Dalamud.Plugin; /// /// Causes for a change to the plugin list. /// public enum PluginListInvalidationKind { + /// + /// A plugin was loaded. + /// + Loaded, + + /// + /// A plugin was unloaded. + /// + Unloaded, + /// /// An installer-initiated update reloaded plugins. /// diff --git a/Dalamud/Plugin/Services/IAddonEventManager.cs b/Dalamud/Plugin/Services/IAddonEventManager.cs index e534eafb4..c6499e4e2 100644 --- a/Dalamud/Plugin/Services/IAddonEventManager.cs +++ b/Dalamud/Plugin/Services/IAddonEventManager.cs @@ -1,4 +1,5 @@ using Dalamud.Game.Addon.Events; +using Dalamud.Game.Addon.Events.EventDataTypes; namespace Dalamud.Plugin.Services; @@ -7,15 +8,6 @@ namespace Dalamud.Plugin.Services; ///
public interface IAddonEventManager { - /// - /// Delegate to be called when an event is received. - /// - /// Event type for this event handler. - /// The parent addon for this event handler. - /// The specific node that will trigger this event handler. - [Obsolete("Use AddonEventDelegate instead")] - public delegate void AddonEventHandler(AddonEventType atkEventType, nint atkUnitBase, nint atkResNode); - /// /// Delegate to be called when an event is received. /// @@ -23,17 +15,6 @@ public interface IAddonEventManager /// The event data object for use in handling this event. public delegate void AddonEventDelegate(AddonEventType atkEventType, AddonEventData data); - /// - /// Registers an event handler for the specified addon, node, and type. - /// - /// The parent addon for this event. - /// The node that will trigger this event. - /// The event type for this event. - /// The handler to call when event is triggered. - /// IAddonEventHandle used to remove the event. Null if no event was added. - [Obsolete("Use AddEvent with AddonEventDelegate instead of AddonEventHandler")] - IAddonEventHandle? AddEvent(nint atkUnitBase, nint atkResNode, AddonEventType eventType, AddonEventHandler eventHandler); - /// /// Registers an event handler for the specified addon, node, and type. /// diff --git a/Dalamud/Plugin/Services/IChatGui.cs b/Dalamud/Plugin/Services/IChatGui.cs index 3f221b3bb..ab595dc3f 100644 --- a/Dalamud/Plugin/Services/IChatGui.cs +++ b/Dalamud/Plugin/Services/IChatGui.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Dalamud.Game.Gui; using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Game.Text.SeStringHandling.Payloads; namespace Dalamud.Plugin.Services; @@ -82,7 +83,25 @@ public interface IChatGui /// /// Gets the dictionary of Dalamud Link Handlers. /// - public IReadOnlyDictionary<(string PluginName, uint CommandId), Action> RegisteredLinkHandlers { get; } + public IReadOnlyDictionary<(string PluginName, Guid CommandId), Action> RegisteredLinkHandlers { get; } + + /// + /// Register a chat link handler. + /// + /// The action to be executed. + /// Returns an SeString payload for the link. + public DalamudLinkPayload AddChatLinkHandler(Action commandAction); + + /// + /// Remove a chat link handler. + /// + /// The ID of the command. + public void RemoveChatLinkHandler(Guid commandId); + + /// + /// Removes all chat link handlers registered by the plugin. + /// + public void RemoveChatLinkHandler(); /// /// Queue a chat message. Dalamud will send queued messages on the next framework event. diff --git a/Dalamud/Plugin/Services/IGameGui.cs b/Dalamud/Plugin/Services/IGameGui.cs index 0e2da7874..773ba61b4 100644 --- a/Dalamud/Plugin/Services/IGameGui.cs +++ b/Dalamud/Plugin/Services/IGameGui.cs @@ -1,6 +1,7 @@ -using System.Numerics; +using System.Numerics; using Dalamud.Game.Gui; +using Dalamud.Game.NativeWrapper; using Dalamud.Game.Text.SeStringHandling.Payloads; namespace Dalamud.Plugin.Services; @@ -75,37 +76,37 @@ public unsafe interface IGameGui public bool ScreenToWorld(Vector2 screenPos, out Vector3 worldPos, float rayDistance = 100000.0f); /// - /// Gets a pointer to the game's UI module. + /// Gets a pointer to the game's UIModule instance. /// - /// IntPtr pointing to UI module. - public nint GetUIModule(); + /// A pointer wrapper to UIModule. + public UIModulePtr GetUIModule(); /// /// Gets the pointer to the Addon with the given name and index. /// /// Name of addon to find. /// Index of addon to find (1-indexed). - /// nint.Zero if unable to find UI, otherwise nint pointing to the start of the addon. - public nint GetAddonByName(string name, int index = 1); + /// A pointer wrapper to the addon. + public AtkUnitBasePtr GetAddonByName(string name, int index = 1); + + /// + /// Find the agent associated with an addon, if possible. + /// + /// The agent id. + /// A pointer wrapper to the agent interface. + public AgentInterfacePtr GetAgentById(int id); /// /// Find the agent associated with an addon, if possible. /// /// The addon name. - /// A pointer to the agent interface. - public nint FindAgentInterface(string addonName); + /// A pointer wrapper to the agent interface. + public AgentInterfacePtr FindAgentInterface(string addonName); /// /// Find the agent associated with an addon, if possible. /// /// The addon address. - /// A pointer to the agent interface. - public nint FindAgentInterface(void* addon); - - /// - /// Find the agent associated with an addon, if possible. - /// - /// The addon address. - /// A pointer to the agent interface. - public IntPtr FindAgentInterface(IntPtr addonPtr); + /// A pointer wrapper to the agent interface. + public AgentInterfacePtr FindAgentInterface(AtkUnitBasePtr addon); } diff --git a/Dalamud/Plugin/Services/IGameNetwork.cs b/Dalamud/Plugin/Services/IGameNetwork.cs index eed79b4af..969176da7 100644 --- a/Dalamud/Plugin/Services/IGameNetwork.cs +++ b/Dalamud/Plugin/Services/IGameNetwork.cs @@ -5,6 +5,7 @@ namespace Dalamud.Plugin.Services; /// /// This class handles interacting with game network events. /// +[Obsolete("Will be removed in a future release. Use packet handler hooks instead.", true)] public interface IGameNetwork { // TODO(v9): we shouldn't be passing pointers to the actual data here diff --git a/Dalamud/Plugin/Services/ISeStringEvaluator.cs b/Dalamud/Plugin/Services/ISeStringEvaluator.cs index 846dcd53e..65932652e 100644 --- a/Dalamud/Plugin/Services/ISeStringEvaluator.cs +++ b/Dalamud/Plugin/Services/ISeStringEvaluator.cs @@ -1,5 +1,3 @@ -using System.Diagnostics.CodeAnalysis; - using Dalamud.Game; using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.Text.Evaluator; @@ -11,7 +9,6 @@ namespace Dalamud.Plugin.Services; /// /// Defines a service for retrieving localized text for various in-game entities. /// -[Experimental("SeStringEvaluator")] public interface ISeStringEvaluator { /// diff --git a/Dalamud/Utility/ItemUtil.cs b/Dalamud/Utility/ItemUtil.cs index 0b37a6abb..5f718bcee 100644 --- a/Dalamud/Utility/ItemUtil.cs +++ b/Dalamud/Utility/ItemUtil.cs @@ -7,10 +7,34 @@ using Lumina.Excel.Sheets; using Lumina.Text; using Lumina.Text.ReadOnly; -using static Dalamud.Game.Text.SeStringHandling.Payloads.ItemPayload; - namespace Dalamud.Utility; +/// +/// Kinds of items that can be fetched from this payload. +/// +public enum ItemKind : uint +{ + /// + /// Normal items. + /// + Normal, + + /// + /// Collectible Items. + /// + Collectible = 500_000, + + /// + /// High-Quality items. + /// + Hq = 1_000_000, + + /// + /// Event/Key items. + /// + EventItem = 2_000_000, +} + /// /// Utilities related to Items. /// diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index 498faaec2..7c625e6de 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -624,37 +624,6 @@ public static partial class Util Win32_PInvoke.FlashWindowEx(flashInfo); } - /// - /// Overwrite text in a file by first writing it to a temporary file, and then - /// moving that file to the path specified. - /// - /// The path of the file to write to. - /// The text to write. - [Api13ToDo("Remove.")] - [Obsolete("Replaced with FilesystemUtil.WriteAllTextSafe()")] - public static void WriteAllTextSafe(string path, string text) => FilesystemUtil.WriteAllTextSafe(path, text); - - /// - /// Overwrite text in a file by first writing it to a temporary file, and then - /// moving that file to the path specified. - /// - /// The path of the file to write to. - /// The text to write. - /// Encoding to use. - [Api13ToDo("Remove.")] - [Obsolete("Replaced with FilesystemUtil.WriteAllTextSafe()")] - public static void WriteAllTextSafe(string path, string text, Encoding encoding) => FilesystemUtil.WriteAllTextSafe(path, text, encoding); - - /// - /// Overwrite data in a file by first writing it to a temporary file, and then - /// moving that file to the path specified. - /// - /// The path of the file to write to. - /// The data to write. - [Api13ToDo("Remove.")] - [Obsolete("Replaced with FilesystemUtil.WriteAllBytesSafe()")] - public static void WriteAllBytesSafe(string path, byte[] bytes) => FilesystemUtil.WriteAllBytesSafe(path, bytes); - /// Gets a temporary file name, for use as the sourceFileName in /// . /// The target file. diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 6a987dceb..294254960 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 6a987dceb781f707d50b7351f6f727b05956343e +Subproject commit 2942549605a0b1c7dfb274afabfe7db0332415bc