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