Add interfaces to non public/sealed classes referenced in public interfaces (#1808)

* Add interfaces to non public/sealed classes referenced in public interfaces

* Fixed inheritdocs + made most classes internal

* Add missing properties to IFate and Fate, fix documentation

---------

Co-authored-by: goat <16760685+goaaats@users.noreply.github.com>
This commit is contained in:
Blair 2024-06-29 07:05:34 +10:00 committed by GitHub
parent 3994f528b8
commit 7947b896ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
55 changed files with 1466 additions and 584 deletions

View file

@ -73,7 +73,7 @@ namespace Dalamud.CorePlugin
Log.Information($"CorePlugin : DefaultFontHandle.ImFontChanged called {fc}"); Log.Information($"CorePlugin : DefaultFontHandle.ImFontChanged called {fc}");
}; };
Service<CommandManager>.Get().AddHandler("/coreplug", new(this.OnCommand) { HelpMessage = "Access the plugin." }); Service<CommandManager>.Get().AddHandler("/coreplug", new CommandInfo(this.OnCommand) { HelpMessage = "Access the plugin." });
log.Information("CorePlugin ctor!"); log.Information("CorePlugin ctor!");
} }

View file

@ -4,9 +4,9 @@ using FFXIVClientStructs.FFXIV.Client.Game.UI;
namespace Dalamud.Game.ClientState.Aetherytes; namespace Dalamud.Game.ClientState.Aetherytes;
/// <summary> /// <summary>
/// This class represents an entry in the Aetheryte list. /// Class representing an aetheryte entry available to the game.
/// </summary> /// </summary>
public sealed class AetheryteEntry internal sealed class AetheryteEntry : IAetheryteEntry
{ {
private readonly TeleportInfo data; private readonly TeleportInfo data;
@ -19,53 +19,90 @@ public sealed class AetheryteEntry
this.data = data; this.data = data;
} }
/// <inheritdoc />
public uint AetheryteId => this.data.AetheryteId;
/// <inheritdoc />
public uint TerritoryId => this.data.TerritoryId;
/// <inheritdoc />
public byte SubIndex => this.data.SubIndex;
/// <inheritdoc />
public byte Ward => this.data.Ward;
/// <inheritdoc />
public byte Plot => this.data.Plot;
/// <inheritdoc />
public uint GilCost => this.data.GilCost;
/// <inheritdoc />
public bool IsFavourite => this.data.IsFavourite != 0;
/// <inheritdoc />
public bool IsSharedHouse => this.data.IsSharedHouse;
/// <inheritdoc />
public bool IsApartment => this.data.IsApartment;
/// <inheritdoc />
public ExcelResolver<Lumina.Excel.GeneratedSheets.Aetheryte> AetheryteData => new(this.AetheryteId);
}
/// <summary>
/// Interface representing an aetheryte entry available to the game.
/// </summary>
public interface IAetheryteEntry
{
/// <summary> /// <summary>
/// Gets the Aetheryte ID. /// Gets the Aetheryte ID.
/// </summary> /// </summary>
public uint AetheryteId => this.data.AetheryteId; uint AetheryteId { get; }
/// <summary> /// <summary>
/// Gets the Territory ID. /// Gets the Territory ID.
/// </summary> /// </summary>
public uint TerritoryId => this.data.TerritoryId; uint TerritoryId { get; }
/// <summary> /// <summary>
/// Gets the SubIndex used when there can be multiple Aetherytes with the same ID (Private/Shared Estates etc.). /// Gets the SubIndex used when there can be multiple Aetherytes with the same ID (Private/Shared Estates etc.).
/// </summary> /// </summary>
public byte SubIndex => this.data.SubIndex; byte SubIndex { get; }
/// <summary> /// <summary>
/// Gets the Ward. Zero if not a Shared Estate. /// Gets the Ward. Zero if not a Shared Estate.
/// </summary> /// </summary>
public byte Ward => this.data.Ward; byte Ward { get; }
/// <summary> /// <summary>
/// Gets the Plot. Zero if not a Shared Estate. /// Gets the Plot. Zero if not a Shared Estate.
/// </summary> /// </summary>
public byte Plot => this.data.Plot; byte Plot { get; }
/// <summary> /// <summary>
/// Gets the Cost in Gil to Teleport to this location. /// Gets the Cost in Gil to Teleport to this location.
/// </summary> /// </summary>
public uint GilCost => this.data.GilCost; uint GilCost { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether the LocalPlayer has set this Aetheryte as Favorite or not. /// Gets a value indicating whether the LocalPlayer has set this Aetheryte as Favorite or not.
/// </summary> /// </summary>
public bool IsFavourite => this.data.IsFavourite != 0; bool IsFavourite { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether this Aetheryte is a Shared Estate or not. /// Gets a value indicating whether this Aetheryte is a Shared Estate or not.
/// </summary> /// </summary>
public bool IsSharedHouse => this.data.IsSharedHouse; bool IsSharedHouse { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether this Aetheryte is an Apartment or not. /// Gets a value indicating whether this Aetheryte is an Apartment or not.
/// </summary> /// </summary>
public bool IsApartment => this.data.IsApartment; bool IsApartment { get; }
/// <summary> /// <summary>
/// Gets the Aetheryte data related to this aetheryte. /// Gets the Aetheryte data related to this aetheryte.
/// </summary> /// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.Aetheryte> AetheryteData => new(this.AetheryteId); ExcelResolver<Lumina.Excel.GeneratedSheets.Aetheryte> AetheryteData { get; }
} }

View file

@ -49,7 +49,7 @@ internal sealed unsafe partial class AetheryteList : IServiceType, IAetheryteLis
} }
/// <inheritdoc/> /// <inheritdoc/>
public AetheryteEntry? this[int index] public IAetheryteEntry? this[int index]
{ {
get get
{ {
@ -84,7 +84,7 @@ internal sealed partial class AetheryteList
public int Count => this.Length; public int Count => this.Length;
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerator<AetheryteEntry> GetEnumerator() public IEnumerator<IAetheryteEntry> GetEnumerator()
{ {
for (var i = 0; i < this.Length; i++) for (var i = 0; i < this.Length; i++)
{ {

View file

@ -56,7 +56,7 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList
} }
/// <inheritdoc/> /// <inheritdoc/>
public BuddyMember? CompanionBuddy public IBuddyMember? CompanionBuddy
{ {
get get
{ {
@ -66,7 +66,7 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList
} }
/// <inheritdoc/> /// <inheritdoc/>
public BuddyMember? PetBuddy public IBuddyMember? PetBuddy
{ {
get get
{ {
@ -85,7 +85,7 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList
private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy* BuddyListStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy*)this.BuddyListAddress; private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy* BuddyListStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy*)this.BuddyListAddress;
/// <inheritdoc/> /// <inheritdoc/>
public BuddyMember? this[int index] public IBuddyMember? this[int index]
{ {
get get
{ {
@ -116,7 +116,7 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList
} }
/// <inheritdoc/> /// <inheritdoc/>
public BuddyMember? CreateBuddyMemberReference(IntPtr address) public IBuddyMember? CreateBuddyMemberReference(IntPtr address)
{ {
if (this.clientState.LocalContentId == 0) if (this.clientState.LocalContentId == 0)
return null; return null;
@ -138,10 +138,10 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList
internal sealed partial class BuddyList internal sealed partial class BuddyList
{ {
/// <inheritdoc/> /// <inheritdoc/>
int IReadOnlyCollection<BuddyMember>.Count => this.Length; int IReadOnlyCollection<IBuddyMember>.Count => this.Length;
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerator<BuddyMember> GetEnumerator() public IEnumerator<IBuddyMember> GetEnumerator()
{ {
for (var i = 0; i < this.Length; i++) for (var i = 0; i < this.Length; i++)
{ {

View file

@ -7,7 +7,7 @@ namespace Dalamud.Game.ClientState.Buddy;
/// <summary> /// <summary>
/// This class represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties. /// This class represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
/// </summary> /// </summary>
public unsafe class BuddyMember internal unsafe class BuddyMember : IBuddyMember
{ {
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly ObjectTable objectTable = Service<ObjectTable>.Get(); private readonly ObjectTable objectTable = Service<ObjectTable>.Get();
@ -21,15 +21,50 @@ public unsafe class BuddyMember
this.Address = address; this.Address = address;
} }
/// <inheritdoc />
public IntPtr Address { get; }
/// <inheritdoc />
public uint ObjectId => this.Struct->EntityId;
/// <inheritdoc />
public IGameObject? GameObject => this.objectTable.SearchById(this.ObjectId);
/// <inheritdoc />
public uint CurrentHP => this.Struct->CurrentHealth;
/// <inheritdoc />
public uint MaxHP => this.Struct->MaxHealth;
/// <inheritdoc />
public uint DataID => this.Struct->DataId;
/// <inheritdoc />
public ExcelResolver<Lumina.Excel.GeneratedSheets.Mount> MountData => new(this.DataID);
/// <inheritdoc />
public ExcelResolver<Lumina.Excel.GeneratedSheets.Pet> PetData => new(this.DataID);
/// <inheritdoc />
public ExcelResolver<Lumina.Excel.GeneratedSheets.DawnGrowMember> TrustData => new(this.DataID);
private FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember*)this.Address;
}
/// <summary>
/// Interface representing represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
/// </summary>
public interface IBuddyMember
{
/// <summary> /// <summary>
/// Gets the address of the buddy in memory. /// Gets the address of the buddy in memory.
/// </summary> /// </summary>
public IntPtr Address { get; } IntPtr Address { get; }
/// <summary> /// <summary>
/// Gets the object ID of this buddy. /// Gets the object ID of this buddy.
/// </summary> /// </summary>
public uint ObjectId => this.Struct->EntityId; unsafe uint ObjectId { get; }
/// <summary> /// <summary>
/// Gets the actor associated with this buddy. /// Gets the actor associated with this buddy.
@ -37,37 +72,35 @@ public unsafe class BuddyMember
/// <remarks> /// <remarks>
/// This iterates the actor table, it should be used with care. /// This iterates the actor table, it should be used with care.
/// </remarks> /// </remarks>
public GameObject? GameObject => this.objectTable.SearchById(this.ObjectId); IGameObject? GameObject { get; }
/// <summary> /// <summary>
/// Gets the current health of this buddy. /// Gets the current health of this buddy.
/// </summary> /// </summary>
public uint CurrentHP => this.Struct->CurrentHealth; unsafe uint CurrentHP { get; }
/// <summary> /// <summary>
/// Gets the maximum health of this buddy. /// Gets the maximum health of this buddy.
/// </summary> /// </summary>
public uint MaxHP => this.Struct->MaxHealth; unsafe uint MaxHP { get; }
/// <summary> /// <summary>
/// Gets the data ID of this buddy. /// Gets the data ID of this buddy.
/// </summary> /// </summary>
public uint DataID => this.Struct->DataId; unsafe uint DataID { get; }
/// <summary> /// <summary>
/// Gets the Mount data related to this buddy. It should only be used with companion buddies. /// Gets the Mount data related to this buddy. It should only be used with companion buddies.
/// </summary> /// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.Mount> MountData => new(this.DataID); ExcelResolver<Lumina.Excel.GeneratedSheets.Mount> MountData { get; }
/// <summary> /// <summary>
/// Gets the Pet data related to this buddy. It should only be used with pet buddies. /// Gets the Pet data related to this buddy. It should only be used with pet buddies.
/// </summary> /// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.Pet> PetData => new(this.DataID); ExcelResolver<Lumina.Excel.GeneratedSheets.Pet> PetData { get; }
/// <summary> /// <summary>
/// Gets the Trust data related to this buddy. It should only be used with battle buddies. /// Gets the Trust data related to this buddy. It should only be used with battle buddies.
/// </summary> /// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.DawnGrowMember> TrustData => new(this.DataID); ExcelResolver<Lumina.Excel.GeneratedSheets.DawnGrowMember> TrustData { get; }
private FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember*)this.Address;
} }

View file

@ -103,7 +103,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
} }
/// <inheritdoc/> /// <inheritdoc/>
public PlayerCharacter? LocalPlayer => Service<ObjectTable>.GetNullable()?[0] as PlayerCharacter; public IPlayerCharacter? LocalPlayer => Service<ObjectTable>.GetNullable()?[0] as IPlayerCharacter;
/// <inheritdoc/> /// <inheritdoc/>
public ulong LocalContentId => (ulong)Marshal.ReadInt64(this.address.LocalContentId); public ulong LocalContentId => (ulong)Marshal.ReadInt64(this.address.LocalContentId);
@ -275,7 +275,7 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat
public uint MapId => this.clientStateService.MapId; public uint MapId => this.clientStateService.MapId;
/// <inheritdoc/> /// <inheritdoc/>
public PlayerCharacter? LocalPlayer => this.clientStateService.LocalPlayer; public IPlayerCharacter? LocalPlayer => this.clientStateService.LocalPlayer;
/// <inheritdoc/> /// <inheritdoc/>
public ulong LocalContentId => this.clientStateService.LocalContentId; public ulong LocalContentId => this.clientStateService.LocalContentId;

View file

@ -10,7 +10,7 @@ namespace Dalamud.Game.ClientState.Fates;
/// <summary> /// <summary>
/// This class represents an FFXIV Fate. /// This class represents an FFXIV Fate.
/// </summary> /// </summary>
public unsafe partial class Fate : IEquatable<Fate> internal unsafe partial class Fate
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Fate"/> class. /// Initializes a new instance of the <see cref="Fate"/> class.
@ -21,9 +21,7 @@ public unsafe partial class Fate : IEquatable<Fate>
this.Address = address; this.Address = address;
} }
/// <summary> /// <inheritdoc />
/// Gets the address of this Fate in memory.
/// </summary>
public IntPtr Address { get; } public IntPtr Address { get; }
private FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext*)this.Address; private FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext*)this.Address;
@ -63,10 +61,10 @@ public unsafe partial class Fate : IEquatable<Fate>
public bool IsValid() => IsValid(this); public bool IsValid() => IsValid(this);
/// <inheritdoc/> /// <inheritdoc/>
bool IEquatable<Fate>.Equals(Fate other) => this.FateId == other?.FateId; bool IEquatable<IFate>.Equals(IFate other) => this.FateId == other?.FateId;
/// <inheritdoc/> /// <inheritdoc/>
public override bool Equals(object obj) => ((IEquatable<Fate>)this).Equals(obj as Fate); public override bool Equals(object obj) => ((IEquatable<IFate>)this).Equals(obj as IFate);
/// <inheritdoc/> /// <inheritdoc/>
public override int GetHashCode() => this.FateId.GetHashCode(); public override int GetHashCode() => this.FateId.GetHashCode();
@ -75,60 +73,171 @@ public unsafe partial class Fate : IEquatable<Fate>
/// <summary> /// <summary>
/// This class represents an FFXIV Fate. /// This class represents an FFXIV Fate.
/// </summary> /// </summary>
public unsafe partial class Fate internal unsafe partial class Fate : IFate
{ {
/// <summary> /// <inheritdoc/>
/// Gets the Fate ID of this <see cref="Fate" />.
/// </summary>
public ushort FateId => this.Struct->FateId; public ushort FateId => this.Struct->FateId;
/// <summary> /// <inheritdoc/>
/// Gets game data linked to this Fate.
/// </summary>
public Lumina.Excel.GeneratedSheets.Fate GameData => Service<DataManager>.Get().GetExcelSheet<Lumina.Excel.GeneratedSheets.Fate>().GetRow(this.FateId); public Lumina.Excel.GeneratedSheets.Fate GameData => Service<DataManager>.Get().GetExcelSheet<Lumina.Excel.GeneratedSheets.Fate>().GetRow(this.FateId);
/// <summary> /// <inheritdoc/>
/// Gets the time this <see cref="Fate"/> started.
/// </summary>
public int StartTimeEpoch => this.Struct->StartTimeEpoch; public int StartTimeEpoch => this.Struct->StartTimeEpoch;
/// <summary> /// <inheritdoc/>
/// Gets how long this <see cref="Fate"/> will run.
/// </summary>
public short Duration => this.Struct->Duration; public short Duration => this.Struct->Duration;
/// <summary> /// <inheritdoc/>
/// Gets the remaining time in seconds for this <see cref="Fate"/>.
/// </summary>
public long TimeRemaining => this.StartTimeEpoch + this.Duration - DateTimeOffset.Now.ToUnixTimeSeconds(); public long TimeRemaining => this.StartTimeEpoch + this.Duration - DateTimeOffset.Now.ToUnixTimeSeconds();
/// <summary> /// <inheritdoc/>
/// Gets the displayname of this <see cref="Fate" />.
/// </summary>
public SeString Name => MemoryHelper.ReadSeString(&this.Struct->Name); public SeString Name => MemoryHelper.ReadSeString(&this.Struct->Name);
/// <summary> /// <inheritdoc/>
/// Gets the state of this <see cref="Fate"/> (Running, Ended, Failed, Preparation, WaitingForEnd). public SeString Description => MemoryHelper.ReadSeString(&this.Struct->Description);
/// </summary>
/// <inheritdoc/>
public SeString Objective => MemoryHelper.ReadSeString(&this.Struct->Objective);
/// <inheritdoc/>
public FateState State => (FateState)this.Struct->State; public FateState State => (FateState)this.Struct->State;
/// <summary> /// <inheritdoc/>
/// Gets the progress amount of this <see cref="Fate"/>. public byte HandInCount => this.Struct->HandInCount;
/// </summary>
/// <inheritdoc/>
public byte Progress => this.Struct->Progress; public byte Progress => this.Struct->Progress;
/// <summary> /// <inheritdoc/>
/// Gets the level of this <see cref="Fate"/>. public bool HasExpBonus => this.Struct->IsExpBonus;
/// </summary>
/// <inheritdoc/>
public uint IconId => this.Struct->IconId;
/// <inheritdoc/>
public byte Level => this.Struct->Level; public byte Level => this.Struct->Level;
/// <summary> /// <inheritdoc/>
/// Gets the position of this <see cref="Fate"/>. public byte MaxLevel => this.Struct->MaxLevel;
/// </summary>
/// <inheritdoc/>
public Vector3 Position => this.Struct->Location; public Vector3 Position => this.Struct->Location;
/// <inheritdoc/>
public float Radius => this.Struct->Radius;
/// <inheritdoc/>
public uint MapIconId => this.Struct->MapIconId;
/// <summary> /// <summary>
/// Gets the territory this <see cref="Fate"/> is located in. /// Gets the territory this <see cref="Fate"/> is located in.
/// </summary> /// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.TerritoryType> TerritoryType => new(this.Struct->TerritoryId); public ExcelResolver<Lumina.Excel.GeneratedSheets.TerritoryType> TerritoryType => new(this.Struct->TerritoryId);
} }
/// <summary>
/// Interface representing an fate entry that can be seen in the current area.
/// </summary>
public interface IFate : IEquatable<IFate>
{
/// <summary>
/// Gets the Fate ID of this <see cref="Fate" />.
/// </summary>
ushort FateId { get; }
/// <summary>
/// Gets game data linked to this Fate.
/// </summary>
Lumina.Excel.GeneratedSheets.Fate GameData { get; }
/// <summary>
/// Gets the time this <see cref="Fate"/> started.
/// </summary>
int StartTimeEpoch { get; }
/// <summary>
/// Gets how long this <see cref="Fate"/> will run.
/// </summary>
short Duration { get; }
/// <summary>
/// Gets the remaining time in seconds for this <see cref="Fate"/>.
/// </summary>
long TimeRemaining { get; }
/// <summary>
/// Gets the displayname of this <see cref="Fate" />.
/// </summary>
SeString Name { get; }
/// <summary>
/// Gets the description of this <see cref="Fate" />.
/// </summary>
SeString Description { get; }
/// <summary>
/// Gets the objective of this <see cref="Fate" />.
/// </summary>
SeString Objective { get; }
/// <summary>
/// Gets the state of this <see cref="Fate"/> (Running, Ended, Failed, Preparation, WaitingForEnd).
/// </summary>
FateState State { get; }
/// <summary>
/// Gets the hand in count of this <see cref="Fate"/>.
/// </summary>
byte HandInCount { get; }
/// <summary>
/// Gets the progress amount of this <see cref="Fate"/>.
/// </summary>
byte Progress { get; }
/// <summary>
/// Gets a value indicating whether or not this <see cref="Fate"/> has a EXP bonus.
/// </summary>
bool HasExpBonus { get; }
/// <summary>
/// Gets the icon id of this <see cref="Fate"/>.
/// </summary>
uint IconId { get; }
/// <summary>
/// Gets the level of this <see cref="Fate"/>.
/// </summary>
byte Level { get; }
/// <summary>
/// Gets the max level level of this <see cref="Fate"/>.
/// </summary>
byte MaxLevel { get; }
/// <summary>
/// Gets the position of this <see cref="Fate"/>.
/// </summary>
Vector3 Position { get; }
/// <summary>
/// Gets the radius of this <see cref="Fate"/>.
/// </summary>
float Radius { get; }
/// <summary>
/// Gets the map icon id of this <see cref="Fate"/>.
/// </summary>
uint MapIconId { get; }
/// <summary>
/// Gets the territory this <see cref="Fate"/> is located in.
/// </summary>
ExcelResolver<Lumina.Excel.GeneratedSheets.TerritoryType> TerritoryType { get; }
/// <summary>
/// Gets the address of this Fate in memory.
/// </summary>
IntPtr Address { get; }
}

View file

@ -69,7 +69,7 @@ internal sealed partial class FateTable : IServiceType, IFateTable
private unsafe FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager*)this.FateTableAddress; private unsafe FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager*)this.FateTableAddress;
/// <inheritdoc/> /// <inheritdoc/>
public Fate? this[int index] public IFate? this[int index]
{ {
get get
{ {
@ -78,6 +78,20 @@ internal sealed partial class FateTable : IServiceType, IFateTable
} }
} }
/// <inheritdoc/>
public bool IsValid(IFate fate)
{
var clientState = Service<ClientState>.GetNullable();
if (fate == null || clientState == null)
return false;
if (clientState.LocalContentId == 0)
return false;
return true;
}
/// <inheritdoc/> /// <inheritdoc/>
public unsafe IntPtr GetFateAddress(int index) public unsafe IntPtr GetFateAddress(int index)
{ {
@ -92,7 +106,7 @@ internal sealed partial class FateTable : IServiceType, IFateTable
} }
/// <inheritdoc/> /// <inheritdoc/>
public Fate? CreateFateReference(IntPtr offset) public IFate? CreateFateReference(IntPtr offset)
{ {
var clientState = Service<ClientState>.Get(); var clientState = Service<ClientState>.Get();
@ -112,10 +126,10 @@ internal sealed partial class FateTable : IServiceType, IFateTable
internal sealed partial class FateTable internal sealed partial class FateTable
{ {
/// <inheritdoc/> /// <inheritdoc/>
int IReadOnlyCollection<Fate>.Count => this.Length; int IReadOnlyCollection<IFate>.Count => this.Length;
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerator<Fate> GetEnumerator() public IEnumerator<IFate> GetEnumerator()
{ {
for (var i = 0; i < this.Length; i++) for (var i = 0; i < this.Length; i++)
{ {

View file

@ -71,7 +71,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
public int Length => ObjectTableLength; public int Length => ObjectTableLength;
/// <inheritdoc/> /// <inheritdoc/>
public GameObject? this[int index] public IGameObject? this[int index]
{ {
get get
{ {
@ -82,7 +82,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
} }
/// <inheritdoc/> /// <inheritdoc/>
public GameObject? SearchById(ulong gameObjectId) public IGameObject? SearchById(ulong gameObjectId)
{ {
_ = this.WarnMultithreadedUsage(); _ = this.WarnMultithreadedUsage();
@ -107,7 +107,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
} }
/// <inheritdoc/> /// <inheritdoc/>
public unsafe GameObject? CreateObjectReference(nint address) public unsafe IGameObject? CreateObjectReference(nint address)
{ {
_ = this.WarnMultithreadedUsage(); _ = this.WarnMultithreadedUsage();
@ -216,7 +216,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
internal sealed partial class ObjectTable internal sealed partial class ObjectTable
{ {
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerator<GameObject> GetEnumerator() public IEnumerator<IGameObject> GetEnumerator()
{ {
// If something's trying to enumerate outside the framework thread, we use the ObjectPool. // If something's trying to enumerate outside the framework thread, we use the ObjectPool.
if (this.WarnMultithreadedUsage()) if (this.WarnMultithreadedUsage())
@ -246,7 +246,7 @@ internal sealed partial class ObjectTable
/// <inheritdoc/> /// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
private sealed class Enumerator : IEnumerator<GameObject>, IResettable private sealed class Enumerator : IEnumerator<IGameObject>, IResettable
{ {
private readonly int slotId; private readonly int slotId;
private ObjectTable? owner; private ObjectTable? owner;
@ -261,7 +261,7 @@ internal sealed partial class ObjectTable
this.slotId = slotId; this.slotId = slotId;
} }
public GameObject Current { get; private set; } = null!; public IGameObject Current { get; private set; } = null!;
object IEnumerator.Current => this.Current; object IEnumerator.Current => this.Current;

View file

@ -5,7 +5,7 @@ namespace Dalamud.Game.ClientState.Objects.Types;
/// <summary> /// <summary>
/// This class represents a battle NPC. /// This class represents a battle NPC.
/// </summary> /// </summary>
public unsafe class BattleNpc : BattleChara internal unsafe class BattleNpc : BattleChara, IBattleNpc
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BattleNpc"/> class. /// Initializes a new instance of the <see cref="BattleNpc"/> class.
@ -25,3 +25,14 @@ public unsafe class BattleNpc : BattleChara
/// <inheritdoc/> /// <inheritdoc/>
public override ulong TargetObjectId => this.Struct->Character.TargetId; public override ulong TargetObjectId => this.Struct->Character.TargetId;
} }
/// <summary>
/// A interface that represents a battle NPC.
/// </summary>
internal interface IBattleNpc
{
/// <summary>
/// Gets the BattleNpc <see cref="BattleNpcSubKind" /> of this BattleNpc.
/// </summary>
BattleNpcSubKind BattleNpcKind { get; }
}

View file

@ -5,7 +5,7 @@ namespace Dalamud.Game.ClientState.Objects.SubKinds;
/// <summary> /// <summary>
/// This class represents an EventObj. /// This class represents an EventObj.
/// </summary> /// </summary>
public unsafe class EventObj : GameObject internal unsafe class EventObj : GameObject
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="EventObj"/> class. /// Initializes a new instance of the <see cref="EventObj"/> class.

View file

@ -5,7 +5,7 @@ namespace Dalamud.Game.ClientState.Objects.SubKinds;
/// <summary> /// <summary>
/// This class represents a NPC. /// This class represents a NPC.
/// </summary> /// </summary>
public unsafe class Npc : Character internal unsafe class Npc : Character
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Npc"/> class. /// Initializes a new instance of the <see cref="Npc"/> class.

View file

@ -1,12 +1,19 @@
using System.Numerics;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.ClientState.Resolvers; using Dalamud.Game.ClientState.Resolvers;
using Dalamud.Game.ClientState.Statuses;
using Dalamud.Game.Text.SeStringHandling;
using Lumina.Excel.GeneratedSheets;
namespace Dalamud.Game.ClientState.Objects.SubKinds; namespace Dalamud.Game.ClientState.Objects.SubKinds;
/// <summary> /// <summary>
/// This class represents a player character. /// This class represents a player character.
/// </summary> /// </summary>
public unsafe class PlayerCharacter : BattleChara internal unsafe class PlayerCharacter : BattleChara, IPlayerCharacter
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PlayerCharacter"/> class. /// Initializes a new instance of the <see cref="PlayerCharacter"/> class.
@ -18,14 +25,10 @@ public unsafe class PlayerCharacter : BattleChara
{ {
} }
/// <summary> /// <inheritdoc/>
/// Gets the current <see cref="ExcelResolver{T}">world</see> of the character.
/// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.World> CurrentWorld => new(this.Struct->CurrentWorld); public ExcelResolver<Lumina.Excel.GeneratedSheets.World> CurrentWorld => new(this.Struct->CurrentWorld);
/// <summary> /// <inheritdoc/>
/// Gets the home <see cref="ExcelResolver{T}">world</see> of the character.
/// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.World> HomeWorld => new(this.Struct->HomeWorld); public ExcelResolver<Lumina.Excel.GeneratedSheets.World> HomeWorld => new(this.Struct->HomeWorld);
/// <summary> /// <summary>
@ -33,3 +36,19 @@ public unsafe class PlayerCharacter : BattleChara
/// </summary> /// </summary>
public override ulong TargetObjectId => this.Struct->LookAt.Controller.Params[0].TargetParam.TargetId; public override ulong TargetObjectId => this.Struct->LookAt.Controller.Params[0].TargetParam.TargetId;
} }
/// <summary>
/// Interface representing a player character.
/// </summary>
public interface IPlayerCharacter : IBattleChara
{
/// <summary>
/// Gets the current <see cref="ExcelResolver{T}">world</see> of the character.
/// </summary>
unsafe ExcelResolver<Lumina.Excel.GeneratedSheets.World> CurrentWorld { get; }
/// <summary>
/// Gets the home <see cref="ExcelResolver{T}">world</see> of the character.
/// </summary>
unsafe ExcelResolver<Lumina.Excel.GeneratedSheets.World> HomeWorld { get; }
}

View file

@ -27,49 +27,49 @@ internal sealed unsafe class TargetManager : IServiceType, ITargetManager
} }
/// <inheritdoc/> /// <inheritdoc/>
public GameObject? Target public IGameObject? Target
{ {
get => this.objectTable.CreateObjectReference((IntPtr)Struct->Target); get => this.objectTable.CreateObjectReference((IntPtr)Struct->Target);
set => Struct->Target = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address; set => Struct->Target = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
} }
/// <inheritdoc/> /// <inheritdoc/>
public GameObject? MouseOverTarget public IGameObject? MouseOverTarget
{ {
get => this.objectTable.CreateObjectReference((IntPtr)Struct->MouseOverTarget); get => this.objectTable.CreateObjectReference((IntPtr)Struct->MouseOverTarget);
set => Struct->MouseOverTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address; set => Struct->MouseOverTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
} }
/// <inheritdoc/> /// <inheritdoc/>
public GameObject? FocusTarget public IGameObject? FocusTarget
{ {
get => this.objectTable.CreateObjectReference((IntPtr)Struct->FocusTarget); get => this.objectTable.CreateObjectReference((IntPtr)Struct->FocusTarget);
set => Struct->FocusTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address; set => Struct->FocusTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
} }
/// <inheritdoc/> /// <inheritdoc/>
public GameObject? PreviousTarget public IGameObject? PreviousTarget
{ {
get => this.objectTable.CreateObjectReference((IntPtr)Struct->PreviousTarget); get => this.objectTable.CreateObjectReference((IntPtr)Struct->PreviousTarget);
set => Struct->PreviousTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address; set => Struct->PreviousTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
} }
/// <inheritdoc/> /// <inheritdoc/>
public GameObject? SoftTarget public IGameObject? SoftTarget
{ {
get => this.objectTable.CreateObjectReference((IntPtr)Struct->SoftTarget); get => this.objectTable.CreateObjectReference((IntPtr)Struct->SoftTarget);
set => Struct->SoftTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address; set => Struct->SoftTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
} }
/// <inheritdoc/> /// <inheritdoc/>
public GameObject? GPoseTarget public IGameObject? GPoseTarget
{ {
get => this.objectTable.CreateObjectReference((IntPtr)Struct->GPoseTarget); get => this.objectTable.CreateObjectReference((IntPtr)Struct->GPoseTarget);
set => Struct->GPoseTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address; set => Struct->GPoseTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
} }
/// <inheritdoc/> /// <inheritdoc/>
public GameObject? MouseOverNameplateTarget public IGameObject? MouseOverNameplateTarget
{ {
get => this.objectTable.CreateObjectReference((IntPtr)Struct->MouseOverNameplateTarget); get => this.objectTable.CreateObjectReference((IntPtr)Struct->MouseOverNameplateTarget);
set => Struct->MouseOverNameplateTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address; set => Struct->MouseOverNameplateTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;

View file

@ -6,7 +6,7 @@ namespace Dalamud.Game.ClientState.Objects.Types;
/// <summary> /// <summary>
/// This class represents the battle characters. /// This class represents the battle characters.
/// </summary> /// </summary>
public unsafe class BattleChara : Character internal unsafe class BattleChara : Character, IBattleChara
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BattleChara"/> class. /// Initializes a new instance of the <see cref="BattleChara"/> class.
@ -18,57 +18,32 @@ public unsafe class BattleChara : Character
{ {
} }
/// <summary> /// <inheritdoc/>
/// Gets the current status effects.
/// </summary>
public StatusList StatusList => new(this.Struct->GetStatusManager()); public StatusList StatusList => new(this.Struct->GetStatusManager());
/// <summary> /// <inheritdoc/>
/// Gets a value indicating whether the chara is currently casting.
/// </summary>
public bool IsCasting => this.Struct->GetCastInfo()->IsCasting > 0; public bool IsCasting => this.Struct->GetCastInfo()->IsCasting > 0;
/// <summary> /// <inheritdoc/>
/// Gets a value indicating whether the cast is interruptible.
/// </summary>
public bool IsCastInterruptible => this.Struct->GetCastInfo()->Interruptible > 0; public bool IsCastInterruptible => this.Struct->GetCastInfo()->Interruptible > 0;
/// <summary> /// <inheritdoc/>
/// Gets the spell action type of the spell being cast by the actor.
/// </summary>
public byte CastActionType => (byte)this.Struct->GetCastInfo()->ActionType; public byte CastActionType => (byte)this.Struct->GetCastInfo()->ActionType;
/// <summary> /// <inheritdoc/>
/// Gets the spell action ID of the spell being cast by the actor.
/// </summary>
public uint CastActionId => this.Struct->GetCastInfo()->ActionId; public uint CastActionId => this.Struct->GetCastInfo()->ActionId;
/// <summary> /// <inheritdoc/>
/// Gets the object ID of the target currently being cast at by the chara.
/// </summary>
public ulong CastTargetObjectId => this.Struct->GetCastInfo()->TargetId; public ulong CastTargetObjectId => this.Struct->GetCastInfo()->TargetId;
/// <summary> /// <inheritdoc/>
/// Gets the current casting time of the spell being cast by the chara.
/// </summary>
public float CurrentCastTime => this.Struct->GetCastInfo()->CurrentCastTime; public float CurrentCastTime => this.Struct->GetCastInfo()->CurrentCastTime;
/// <summary> /// <inheritdoc/>
/// Gets the total casting time of the spell being cast by the chara.
/// </summary>
/// <remarks>
/// This can only be a portion of the total cast for some actions.
/// Use AdjustedTotalCastTime if you always need the total cast time.
/// </remarks>
[Api10ToDo("Rename so it is not confused with AdjustedTotalCastTime")] [Api10ToDo("Rename so it is not confused with AdjustedTotalCastTime")]
public float TotalCastTime => this.Struct->GetCastInfo()->TotalCastTime; public float TotalCastTime => this.Struct->GetCastInfo()->TotalCastTime;
/// <summary> /// <inheritdoc/>
/// Gets the <see cref="TotalCastTime"/> plus any adjustments from the game, such as Action offset 2B. Used for display purposes.
/// </summary>
/// <remarks>
/// This is the actual total cast time for all actions.
/// </remarks>
[Api10ToDo("Rename so it is not confused with TotalCastTime")] [Api10ToDo("Rename so it is not confused with TotalCastTime")]
public float AdjustedTotalCastTime => this.Struct->GetCastInfo()->AdjustedTotalCastTime; public float AdjustedTotalCastTime => this.Struct->GetCastInfo()->AdjustedTotalCastTime;
@ -77,3 +52,61 @@ public unsafe class BattleChara : Character
/// </summary> /// </summary>
protected internal new FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)this.Address; protected internal new FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)this.Address;
} }
/// <summary>
/// Interface representing a battle character.
/// </summary>
public interface IBattleChara : ICharacter
{
/// <summary>
/// Gets the current status effects.
/// </summary>
public StatusList StatusList { get; }
/// <summary>
/// Gets a value indicating whether the chara is currently casting.
/// </summary>
public bool IsCasting { get; }
/// <summary>
/// Gets a value indicating whether the cast is interruptible.
/// </summary>
public bool IsCastInterruptible { get; }
/// <summary>
/// Gets the spell action type of the spell being cast by the actor.
/// </summary>
public byte CastActionType { get; }
/// <summary>
/// Gets the spell action ID of the spell being cast by the actor.
/// </summary>
public uint CastActionId { get; }
/// <summary>
/// Gets the object ID of the target currently being cast at by the chara.
/// </summary>
public ulong CastTargetObjectId { get; }
/// <summary>
/// Gets the current casting time of the spell being cast by the chara.
/// </summary>
public float CurrentCastTime { get; }
/// <summary>
/// Gets the total casting time of the spell being cast by the chara.
/// </summary>
/// <remarks>
/// This can only be a portion of the total cast for some actions.
/// Use AdjustedTotalCastTime if you always need the total cast time.
/// </remarks>
public float TotalCastTime { get; }
/// <summary>
/// Gets the <see cref="TotalCastTime"/> plus any adjustments from the game, such as Action offset 2B. Used for display purposes.
/// </summary>
/// <remarks>
/// This is the actual total cast time for all actions.
/// </remarks>
public float AdjustedTotalCastTime { get; }
}

View file

@ -11,7 +11,7 @@ namespace Dalamud.Game.ClientState.Objects.Types;
/// <summary> /// <summary>
/// This class represents the base for non-static entities. /// This class represents the base for non-static entities.
/// </summary> /// </summary>
public unsafe class Character : GameObject internal unsafe class Character : GameObject, ICharacter
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Character"/> class. /// Initializes a new instance of the <see cref="Character"/> class.
@ -23,70 +23,43 @@ public unsafe class Character : GameObject
{ {
} }
/// <summary> /// <inheritdoc/>
/// Gets the current HP of this Chara.
/// </summary>
public uint CurrentHp => this.Struct->CharacterData.Health; public uint CurrentHp => this.Struct->CharacterData.Health;
/// <summary> /// <inheritdoc/>
/// Gets the maximum HP of this Chara.
/// </summary>
public uint MaxHp => this.Struct->CharacterData.MaxHealth; public uint MaxHp => this.Struct->CharacterData.MaxHealth;
/// <summary> /// <inheritdoc/>
/// Gets the current MP of this Chara.
/// </summary>
public uint CurrentMp => this.Struct->CharacterData.Mana; public uint CurrentMp => this.Struct->CharacterData.Mana;
/// <summary> /// <inheritdoc/>
/// Gets the maximum MP of this Chara.
/// </summary>
public uint MaxMp => this.Struct->CharacterData.MaxMana; public uint MaxMp => this.Struct->CharacterData.MaxMana;
/// <summary> /// <inheritdoc/>
/// Gets the current GP of this Chara.
/// </summary>
public uint CurrentGp => this.Struct->CharacterData.GatheringPoints; public uint CurrentGp => this.Struct->CharacterData.GatheringPoints;
/// <summary> /// <inheritdoc/>
/// Gets the maximum GP of this Chara.
/// </summary>
public uint MaxGp => this.Struct->CharacterData.MaxGatheringPoints; public uint MaxGp => this.Struct->CharacterData.MaxGatheringPoints;
/// <summary> /// <inheritdoc/>
/// Gets the current CP of this Chara.
/// </summary>
public uint CurrentCp => this.Struct->CharacterData.CraftingPoints; public uint CurrentCp => this.Struct->CharacterData.CraftingPoints;
/// <summary> /// <inheritdoc/>
/// Gets the maximum CP of this Chara.
/// </summary>
public uint MaxCp => this.Struct->CharacterData.MaxCraftingPoints; public uint MaxCp => this.Struct->CharacterData.MaxCraftingPoints;
/// <summary> /// <inheritdoc/>
/// Gets the shield percentage of this Chara.
/// </summary>
public byte ShieldPercentage => this.Struct->CharacterData.ShieldValue; public byte ShieldPercentage => this.Struct->CharacterData.ShieldValue;
/// <summary> /// <inheritdoc/>
/// Gets the ClassJob of this Chara.
/// </summary>
public ExcelResolver<ClassJob> ClassJob => new(this.Struct->CharacterData.ClassJob); public ExcelResolver<ClassJob> ClassJob => new(this.Struct->CharacterData.ClassJob);
/// <summary> /// <inheritdoc/>
/// Gets the level of this Chara.
/// </summary>
public byte Level => this.Struct->CharacterData.Level; public byte Level => this.Struct->CharacterData.Level;
/// <summary> /// <inheritdoc/>
/// Gets a byte array describing the visual appearance of this Chara.
/// Indexed by <see cref="CustomizeIndex"/>.
/// </summary>
public byte[] Customize => this.Struct->DrawData.CustomizeData.Data.ToArray(); public byte[] Customize => this.Struct->DrawData.CustomizeData.Data.ToArray();
/// <summary> /// <inheritdoc/>
/// Gets the Free Company tag of this chara.
/// </summary>
public SeString CompanyTag => MemoryHelper.ReadSeString((nint)Unsafe.AsPointer(ref this.Struct->FreeCompanyTag[0]), 6); public SeString CompanyTag => MemoryHelper.ReadSeString((nint)Unsafe.AsPointer(ref this.Struct->FreeCompanyTag[0]), 6);
/// <summary> /// <summary>
@ -94,14 +67,10 @@ public unsafe class Character : GameObject
/// </summary> /// </summary>
public override ulong TargetObjectId => this.Struct->TargetId; public override ulong TargetObjectId => this.Struct->TargetId;
/// <summary> /// <inheritdoc/>
/// Gets the name ID of the character.
/// </summary>
public uint NameId => this.Struct->NameId; public uint NameId => this.Struct->NameId;
/// <summary> /// <inheritdoc/>
/// Gets the current online status of the character.
/// </summary>
public ExcelResolver<OnlineStatus> OnlineStatus => new(this.Struct->CharacterData.OnlineStatus); public ExcelResolver<OnlineStatus> OnlineStatus => new(this.Struct->CharacterData.OnlineStatus);
/// <summary> /// <summary>
@ -123,3 +92,90 @@ public unsafe class Character : GameObject
protected internal new FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Struct => protected internal new FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Struct =>
(FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)this.Address; (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)this.Address;
} }
/// <summary>
/// Interface representing a character.
/// </summary>
public interface ICharacter : IGameObject
{
/// <summary>
/// Gets the current HP of this Chara.
/// </summary>
public uint CurrentHp { get; }
/// <summary>
/// Gets the maximum HP of this Chara.
/// </summary>
public uint MaxHp { get; }
/// <summary>
/// Gets the current MP of this Chara.
/// </summary>
public uint CurrentMp { get; }
/// <summary>
/// Gets the maximum MP of this Chara.
/// </summary>
public uint MaxMp { get; }
/// <summary>
/// Gets the current GP of this Chara.
/// </summary>
public uint CurrentGp { get; }
/// <summary>
/// Gets the maximum GP of this Chara.
/// </summary>
public uint MaxGp { get; }
/// <summary>
/// Gets the current CP of this Chara.
/// </summary>
public uint CurrentCp { get; }
/// <summary>
/// Gets the maximum CP of this Chara.
/// </summary>
public uint MaxCp { get; }
/// <summary>
/// Gets the shield percentage of this Chara.
/// </summary>
public byte ShieldPercentage { get; }
/// <summary>
/// Gets the ClassJob of this Chara.
/// </summary>
public ExcelResolver<ClassJob> ClassJob { get; }
/// <summary>
/// Gets the level of this Chara.
/// </summary>
public byte Level { get; }
/// <summary>
/// Gets a byte array describing the visual appearance of this Chara.
/// Indexed by <see cref="CustomizeIndex"/>.
/// </summary>
public byte[] Customize { get; }
/// <summary>
/// Gets the Free Company tag of this chara.
/// </summary>
public SeString CompanyTag { get; }
/// <summary>
/// Gets the name ID of the character.
/// </summary>
public uint NameId { get; }
/// <summary>
/// Gets the current online status of the character.
/// </summary>
public ExcelResolver<OnlineStatus> OnlineStatus { get; }
/// <summary>
/// Gets the status flags.
/// </summary>
public StatusFlags StatusFlags { get; }
}

View file

@ -10,7 +10,7 @@ namespace Dalamud.Game.ClientState.Objects.Types;
/// <summary> /// <summary>
/// This class represents a GameObject in FFXIV. /// This class represents a GameObject in FFXIV.
/// </summary> /// </summary>
public unsafe partial class GameObject : IEquatable<GameObject> internal partial class GameObject
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="GameObject"/> class. /// Initializes a new instance of the <see cref="GameObject"/> class.
@ -54,7 +54,7 @@ public unsafe partial class GameObject : IEquatable<GameObject>
/// </summary> /// </summary>
/// <param name="actor">The actor to check.</param> /// <param name="actor">The actor to check.</param>
/// <returns>True or false.</returns> /// <returns>True or false.</returns>
public static bool IsValid(GameObject? actor) public static bool IsValid(IGameObject? actor)
{ {
var clientState = Service<ClientState>.GetNullable(); var clientState = Service<ClientState>.GetNullable();
@ -74,10 +74,10 @@ public unsafe partial class GameObject : IEquatable<GameObject>
public bool IsValid() => IsValid(this); public bool IsValid() => IsValid(this);
/// <inheritdoc/> /// <inheritdoc/>
bool IEquatable<GameObject>.Equals(GameObject other) => this.GameObjectId == other?.GameObjectId; bool IEquatable<IGameObject>.Equals(IGameObject other) => this.GameObjectId == other?.GameObjectId;
/// <inheritdoc/> /// <inheritdoc/>
public override bool Equals(object obj) => ((IEquatable<GameObject>)this).Equals(obj as GameObject); public override bool Equals(object obj) => ((IEquatable<IGameObject>)this).Equals(obj as IGameObject);
/// <inheritdoc/> /// <inheritdoc/>
public override int GetHashCode() => this.GameObjectId.GetHashCode(); public override int GetHashCode() => this.GameObjectId.GetHashCode();
@ -86,103 +86,59 @@ public unsafe partial class GameObject : IEquatable<GameObject>
/// <summary> /// <summary>
/// This class represents a basic actor (GameObject) in FFXIV. /// This class represents a basic actor (GameObject) in FFXIV.
/// </summary> /// </summary>
public unsafe partial class GameObject internal unsafe partial class GameObject : IGameObject
{ {
/// <summary> /// <inheritdoc/>
/// Gets the name of this <see cref="GameObject" />.
/// </summary>
public SeString Name => MemoryHelper.ReadSeString((nint)Unsafe.AsPointer(ref this.Struct->Name[0]), 64); public SeString Name => MemoryHelper.ReadSeString((nint)Unsafe.AsPointer(ref this.Struct->Name[0]), 64);
/// <summary> /// <inheritdoc/>
/// Gets the GameObjectID for this GameObject. The Game Object ID is a globally unique identifier that points to
/// this specific object. This ID is used to reference specific objects on the local client (e.g. for targeting).
///
/// Not to be confused with <see cref="EntityId"/>.
/// </summary>
public ulong GameObjectId => this.Struct->GetGameObjectId(); public ulong GameObjectId => this.Struct->GetGameObjectId();
/// <summary> /// <inheritdoc/>
/// Gets the Entity ID for this GameObject. Entity IDs are assigned to networked GameObjects.
///
/// A value of <c>0xE000_0000</c> indicates that this entity is not networked and has specific interactivity rules.
/// </summary>
public uint EntityId => this.Struct->EntityId; public uint EntityId => this.Struct->EntityId;
/// <summary> /// <inheritdoc/>
/// Gets the data ID for linking to other respective game data.
/// </summary>
public uint DataId => this.Struct->BaseId; public uint DataId => this.Struct->BaseId;
/// <summary> /// <inheritdoc/>
/// Gets the ID of this GameObject's owner.
/// </summary>
public uint OwnerId => this.Struct->OwnerId; public uint OwnerId => this.Struct->OwnerId;
/// <summary> /// <inheritdoc/>
/// Gets the index of this object in the object table.
/// </summary>
public ushort ObjectIndex => this.Struct->ObjectIndex; public ushort ObjectIndex => this.Struct->ObjectIndex;
/// <summary> /// <inheritdoc/>
/// Gets the entity kind of this <see cref="GameObject" />.
/// See <see cref="ObjectKind">the ObjectKind enum</see> for possible values.
/// </summary>
public ObjectKind ObjectKind => (ObjectKind)this.Struct->ObjectKind; public ObjectKind ObjectKind => (ObjectKind)this.Struct->ObjectKind;
/// <summary> /// <inheritdoc/>
/// Gets the sub kind of this Actor.
/// </summary>
public byte SubKind => this.Struct->SubKind; public byte SubKind => this.Struct->SubKind;
/// <summary> /// <inheritdoc/>
/// Gets the X distance from the local player in yalms.
/// </summary>
public byte YalmDistanceX => this.Struct->YalmDistanceFromPlayerX; public byte YalmDistanceX => this.Struct->YalmDistanceFromPlayerX;
/// <summary> /// <inheritdoc/>
/// Gets the Y distance from the local player in yalms.
/// </summary>
public byte YalmDistanceZ => this.Struct->YalmDistanceFromPlayerZ; public byte YalmDistanceZ => this.Struct->YalmDistanceFromPlayerZ;
/// <summary> /// <inheritdoc/>
/// Gets a value indicating whether the object is dead or alive.
/// </summary>
public bool IsDead => this.Struct->IsDead(); public bool IsDead => this.Struct->IsDead();
/// <summary> /// <inheritdoc/>
/// Gets a value indicating whether the object is targetable.
/// </summary>
public bool IsTargetable => this.Struct->GetIsTargetable(); public bool IsTargetable => this.Struct->GetIsTargetable();
/// <summary> /// <inheritdoc/>
/// Gets the position of this <see cref="GameObject" />.
/// </summary>
public Vector3 Position => new(this.Struct->Position.X, this.Struct->Position.Y, this.Struct->Position.Z); public Vector3 Position => new(this.Struct->Position.X, this.Struct->Position.Y, this.Struct->Position.Z);
/// <summary> /// <inheritdoc/>
/// Gets the rotation of this <see cref="GameObject" />.
/// This ranges from -pi to pi radians.
/// </summary>
public float Rotation => this.Struct->Rotation; public float Rotation => this.Struct->Rotation;
/// <summary> /// <inheritdoc/>
/// Gets the hitbox radius of this <see cref="GameObject" />.
/// </summary>
public float HitboxRadius => this.Struct->HitboxRadius; public float HitboxRadius => this.Struct->HitboxRadius;
/// <summary> /// <inheritdoc/>
/// Gets the current target of the game object.
/// </summary>
public virtual ulong TargetObjectId => 0; public virtual ulong TargetObjectId => 0;
/// <summary> /// <inheritdoc/>
/// Gets the target object of the game object.
/// </summary>
/// <remarks>
/// This iterates the actor table, it should be used with care.
/// </remarks>
// TODO: Fix for non-networked GameObjects // TODO: Fix for non-networked GameObjects
public virtual GameObject? TargetObject => Service<ObjectTable>.Get().SearchById(this.TargetObjectId); public virtual IGameObject? TargetObject => Service<ObjectTable>.Get().SearchById(this.TargetObjectId);
/// <summary> /// <summary>
/// Gets the underlying structure. /// Gets the underlying structure.
@ -192,3 +148,116 @@ public unsafe partial class GameObject
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString() => $"{this.GameObjectId:X}({this.Name.TextValue} - {this.ObjectKind}) at {this.Address:X}"; public override string ToString() => $"{this.GameObjectId:X}({this.Name.TextValue} - {this.ObjectKind}) at {this.Address:X}";
} }
/// <summary>
/// Interface representing a game object.
/// </summary>
public interface IGameObject : IEquatable<IGameObject>
{
/// <summary>
/// Gets the name of this <see cref="GameObject" />.
/// </summary>
public SeString Name { get; }
/// <summary>
/// Gets the GameObjectID for this GameObject. The Game Object ID is a globally unique identifier that points to
/// this specific object. This ID is used to reference specific objects on the local client (e.g. for targeting).
///
/// Not to be confused with <see cref="EntityId"/>.
/// </summary>
public ulong GameObjectId { get; }
/// <summary>
/// Gets the Entity ID for this GameObject. Entity IDs are assigned to networked GameObjects.
///
/// A value of <c>0xE000_0000</c> indicates that this entity is not networked and has specific interactivity rules.
/// </summary>
public uint EntityId { get; }
/// <summary>
/// Gets the data ID for linking to other respective game data.
/// </summary>
public uint DataId { get; }
/// <summary>
/// Gets the ID of this GameObject's owner.
/// </summary>
public uint OwnerId { get; }
/// <summary>
/// Gets the index of this object in the object table.
/// </summary>
public ushort ObjectIndex { get; }
/// <summary>
/// Gets the entity kind of this <see cref="GameObject" />.
/// See <see cref="ObjectKind">the ObjectKind enum</see> for possible values.
/// </summary>
public ObjectKind ObjectKind { get; }
/// <summary>
/// Gets the sub kind of this Actor.
/// </summary>
public byte SubKind { get; }
/// <summary>
/// Gets the X distance from the local player in yalms.
/// </summary>
public byte YalmDistanceX { get; }
/// <summary>
/// Gets the Y distance from the local player in yalms.
/// </summary>
public byte YalmDistanceZ { get; }
/// <summary>
/// Gets a value indicating whether the object is dead or alive.
/// </summary>
public bool IsDead { get; }
/// <summary>
/// Gets a value indicating whether the object is targetable.
/// </summary>
public bool IsTargetable { get; }
/// <summary>
/// Gets the position of this <see cref="GameObject" />.
/// </summary>
public Vector3 Position { get; }
/// <summary>
/// Gets the rotation of this <see cref="GameObject" />.
/// This ranges from -pi to pi radians.
/// </summary>
public float Rotation { get; }
/// <summary>
/// Gets the hitbox radius of this <see cref="GameObject" />.
/// </summary>
public float HitboxRadius { get; }
/// <summary>
/// Gets the current target of the game object.
/// </summary>
public ulong TargetObjectId { get; }
/// <summary>
/// Gets the target object of the game object.
/// </summary>
/// <remarks>
/// This iterates the actor table, it should be used with care.
/// </remarks>
// TODO: Fix for non-networked GameObjects
public IGameObject? TargetObject { get; }
/// <summary>
/// Gets the address of the game object in memory.
/// </summary>
public IntPtr Address { get; }
/// <summary>
/// Gets a value indicating whether this actor is still valid in memory.
/// </summary>
/// <returns>True or false.</returns>
public bool IsValid();
}

View file

@ -63,7 +63,7 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList
private FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager* GroupManagerStruct => (FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager*)this.GroupManagerAddress; private FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager* GroupManagerStruct => (FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager*)this.GroupManagerAddress;
/// <inheritdoc/> /// <inheritdoc/>
public PartyMember? this[int index] public IPartyMember? this[int index]
{ {
get get
{ {
@ -94,7 +94,7 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList
} }
/// <inheritdoc/> /// <inheritdoc/>
public PartyMember? CreatePartyMemberReference(IntPtr address) public IPartyMember? CreatePartyMemberReference(IntPtr address)
{ {
if (this.clientState.LocalContentId == 0) if (this.clientState.LocalContentId == 0)
return null; return null;
@ -115,7 +115,7 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList
} }
/// <inheritdoc/> /// <inheritdoc/>
public PartyMember? CreateAllianceMemberReference(IntPtr address) public IPartyMember? CreateAllianceMemberReference(IntPtr address)
{ {
if (this.clientState.LocalContentId == 0) if (this.clientState.LocalContentId == 0)
return null; return null;
@ -133,10 +133,10 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList
internal sealed partial class PartyList internal sealed partial class PartyList
{ {
/// <inheritdoc/> /// <inheritdoc/>
int IReadOnlyCollection<PartyMember>.Count => this.Length; int IReadOnlyCollection<IPartyMember>.Count => this.Length;
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerator<PartyMember> GetEnumerator() public IEnumerator<IPartyMember> GetEnumerator()
{ {
// Normally using Length results in a recursion crash, however we know the party size via ptr. // Normally using Length results in a recursion crash, however we know the party size via ptr.
for (var i = 0; i < this.Length; i++) for (var i = 0; i < this.Length; i++)

View file

@ -13,7 +13,7 @@ namespace Dalamud.Game.ClientState.Party;
/// <summary> /// <summary>
/// This class represents a party member in the group manager. /// This class represents a party member in the group manager.
/// </summary> /// </summary>
public unsafe class PartyMember public unsafe class PartyMember : IPartyMember
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PartyMember"/> class. /// Initializes a new instance of the <see cref="PartyMember"/> class.
@ -55,7 +55,7 @@ public unsafe class PartyMember
/// <remarks> /// <remarks>
/// This iterates the actor table, it should be used with care. /// This iterates the actor table, it should be used with care.
/// </remarks> /// </remarks>
public GameObject? GameObject => Service<ObjectTable>.Get().SearchById(this.ObjectId); public IGameObject? GameObject => Service<ObjectTable>.Get().SearchById(this.ObjectId);
/// <summary> /// <summary>
/// Gets the current HP of this party member. /// Gets the current HP of this party member.
@ -109,3 +109,92 @@ public unsafe class PartyMember
private FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember*)this.Address; private FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember*)this.Address;
} }
/// <summary>
/// Interface representing a party member.
/// </summary>
public interface IPartyMember
{
/// <summary>
/// Gets the address of this party member in memory.
/// </summary>
IntPtr Address { get; }
/// <summary>
/// Gets a list of buffs or debuffs applied to this party member.
/// </summary>
StatusList Statuses { get; }
/// <summary>
/// Gets the position of the party member.
/// </summary>
Vector3 Position { get; }
/// <summary>
/// Gets the content ID of the party member.
/// </summary>
long ContentId { get; }
/// <summary>
/// Gets the actor ID of this party member.
/// </summary>
uint ObjectId { get; }
/// <summary>
/// Gets the actor associated with this buddy.
/// </summary>
/// <remarks>
/// This iterates the actor table, it should be used with care.
/// </remarks>
IGameObject? GameObject { get; }
/// <summary>
/// Gets the current HP of this party member.
/// </summary>
uint CurrentHP { get; }
/// <summary>
/// Gets the maximum HP of this party member.
/// </summary>
uint MaxHP { get; }
/// <summary>
/// Gets the current MP of this party member.
/// </summary>
ushort CurrentMP { get; }
/// <summary>
/// Gets the maximum MP of this party member.
/// </summary>
ushort MaxMP { get; }
/// <summary>
/// Gets the territory this party member is located in.
/// </summary>
ExcelResolver<Lumina.Excel.GeneratedSheets.TerritoryType> Territory { get; }
/// <summary>
/// Gets the World this party member resides in.
/// </summary>
ExcelResolver<Lumina.Excel.GeneratedSheets.World> World { get; }
/// <summary>
/// Gets the displayname of this party member.
/// </summary>
SeString Name { get; }
/// <summary>
/// Gets the sex of this party member.
/// </summary>
byte Sex { get; }
/// <summary>
/// Gets the classjob of this party member.
/// </summary>
ExcelResolver<Lumina.Excel.GeneratedSheets.ClassJob> ClassJob { get; }
/// <summary>
/// Gets the level of this party member.
/// </summary>
byte Level { get; }
}

View file

@ -59,7 +59,7 @@ public unsafe class Status
/// <remarks> /// <remarks>
/// This iterates the actor table, it should be used with care. /// This iterates the actor table, it should be used with care.
/// </remarks> /// </remarks>
public GameObject? SourceObject => Service<ObjectTable>.Get().SearchById(this.SourceId); public IGameObject? SourceObject => Service<ObjectTable>.Get().SearchById(this.SourceId);
private FFXIVClientStructs.FFXIV.Client.Game.Status* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Status*)this.Address; private FFXIVClientStructs.FFXIV.Client.Game.Status* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Status*)this.Address;
} }

View file

@ -3,42 +3,52 @@ namespace Dalamud.Game.Command;
/// <summary> /// <summary>
/// This class describes a registered command. /// This class describes a registered command.
/// </summary> /// </summary>
public sealed class CommandInfo internal sealed class CommandInfo : ICommandInfo
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CommandInfo"/> class. /// Initializes a new instance of the <see cref="CommandInfo"/> class.
/// Create a new CommandInfo with the provided handler. /// Create a new CommandInfo with the provided handler.
/// </summary> /// </summary>
/// <param name="handler">The method to call when the command is run.</param> /// <param name="handler">The method to call when the command is run.</param>
public CommandInfo(HandlerDelegate handler) public CommandInfo(ICommandInfo.HandlerDelegate handler)
{ {
this.Handler = handler; this.Handler = handler;
} }
/// <inheritdoc/>
public ICommandInfo.HandlerDelegate Handler { get; }
/// <inheritdoc/>
public string HelpMessage { get; set; } = string.Empty;
/// <inheritdoc/>
public bool ShowInHelp { get; set; } = true;
}
/// <summary>
/// Interface representing a registered command.
/// </summary>
public interface ICommandInfo
{
/// <summary> /// <summary>
/// The function to be executed when the command is dispatched. /// The function to be executed when the command is dispatched.
/// </summary> /// </summary>
/// <param name="command">The command itself.</param> /// <param name="command">The command itself.</param>
/// <param name="arguments">The arguments supplied to the command, ready for parsing.</param> /// <param name="arguments">The arguments supplied to the command, ready for parsing.</param>
public delegate void HandlerDelegate(string command, string arguments); public delegate void HandlerDelegate(string command, string arguments);
/// <summary> /// <summary>
/// Gets a <see cref="HandlerDelegate"/> which will be called when the command is dispatched. /// Gets a <see cref="HandlerDelegate"/> which will be called when the command is dispatched.
/// </summary> /// </summary>
public HandlerDelegate Handler { get; } HandlerDelegate Handler { get; }
/// <summary> /// <summary>
/// Gets or sets the help message for this command. /// Gets or sets the help message for this command.
/// </summary> /// </summary>
public string HelpMessage { get; set; } = string.Empty; string HelpMessage { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether if this command should be shown in the help output. /// Gets or sets a value indicating whether if this command should be shown in the help output.
/// </summary> /// </summary>
public bool ShowInHelp { get; set; } = true; bool ShowInHelp { get; set; }
/// <summary>
/// Gets or sets the name of the assembly responsible for this command.
/// </summary>
internal string LoaderAssemblyName { get; set; } = string.Empty;
} }

View file

@ -1,6 +1,7 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Dalamud.Console; using Dalamud.Console;
@ -23,7 +24,8 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag
{ {
private static readonly ModuleLog Log = new("Command"); private static readonly ModuleLog Log = new("Command");
private readonly ConcurrentDictionary<string, CommandInfo> commandMap = new(); private readonly ConcurrentDictionary<string, ICommandInfo> commandMap = new();
private readonly ConcurrentDictionary<(string, ICommandInfo), string> commandAssemblyNameMap = new();
private readonly Regex commandRegexEn = new(@"^The command (?<command>.+) does not exist\.$", RegexOptions.Compiled); private readonly Regex commandRegexEn = new(@"^The command (?<command>.+) does not exist\.$", RegexOptions.Compiled);
private readonly Regex commandRegexJp = new(@"^そのコマンドはありません。: (?<command>.+)$", RegexOptions.Compiled); private readonly Regex commandRegexJp = new(@"^そのコマンドはありません。: (?<command>.+)$", RegexOptions.Compiled);
private readonly Regex commandRegexDe = new(@"^„(?<command>.+)“ existiert nicht als Textkommando\.$", RegexOptions.Compiled); private readonly Regex commandRegexDe = new(@"^„(?<command>.+)“ existiert nicht als Textkommando\.$", RegexOptions.Compiled);
@ -54,7 +56,7 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag
} }
/// <inheritdoc/> /// <inheritdoc/>
public ReadOnlyDictionary<string, CommandInfo> Commands => new(this.commandMap); public ReadOnlyDictionary<string, ICommandInfo> Commands => new(this.commandMap);
/// <inheritdoc/> /// <inheritdoc/>
public bool ProcessCommand(string content) public bool ProcessCommand(string content)
@ -100,7 +102,7 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag
} }
/// <inheritdoc/> /// <inheritdoc/>
public void DispatchCommand(string command, string argument, CommandInfo info) public void DispatchCommand(string command, string argument, ICommandInfo info)
{ {
try try
{ {
@ -111,9 +113,31 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag
Log.Error(ex, "Error while dispatching command {CommandName} (Argument: {Argument})", command, argument); Log.Error(ex, "Error while dispatching command {CommandName} (Argument: {Argument})", command, argument);
} }
} }
/// <inheritdoc/>
public bool AddHandler(string command, ICommandInfo info, string loaderAssemblyName = "")
{
if (info == null)
throw new ArgumentNullException(nameof(info), "Command handler is null.");
if (!this.commandMap.TryAdd(command, info))
{
Log.Error("Command {CommandName} is already registered.", command);
return false;
}
if (!this.commandAssemblyNameMap.TryAdd((command, info), loaderAssemblyName))
{
this.commandMap.Remove(command, out _);
Log.Error("Command {CommandName} is already registered in the assembly name map.", command);
return false;
}
return true;
}
/// <inheritdoc/> /// <inheritdoc/>
public bool AddHandler(string command, CommandInfo info) public bool AddHandler(string command, ICommandInfo info)
{ {
if (info == null) if (info == null)
throw new ArgumentNullException(nameof(info), "Command handler is null."); throw new ArgumentNullException(nameof(info), "Command handler is null.");
@ -133,6 +157,32 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag
return this.commandMap.Remove(command, out _); return this.commandMap.Remove(command, out _);
} }
/// <summary>
/// Returns the assembly name from which the command was added or blank if added internally.
/// </summary>
/// <param name="command">The command.</param>
/// <param name="commandInfo">A ICommandInfo object.</param>
/// <returns>The name of the assembly.</returns>
public string GetHandlerAssemblyName(string command, ICommandInfo commandInfo)
{
if (this.commandAssemblyNameMap.TryGetValue((command, commandInfo), out var assemblyName))
{
return assemblyName;
}
return string.Empty;
}
/// <summary>
/// Returns a list of commands given a specified assembly name.
/// </summary>
/// <param name="assemblyName">The name of the assembly.</param>
/// <returns>A list of commands and their associated activation string.</returns>
public List<KeyValuePair<(string, ICommandInfo), string>> GetHandlersByAssemblyName(string assemblyName)
{
return this.commandAssemblyNameMap.Where(c => c.Value == assemblyName).ToList();
}
/// <inheritdoc/> /// <inheritdoc/>
void IInternalDisposableService.DisposeService() void IInternalDisposableService.DisposeService()
{ {
@ -199,7 +249,7 @@ internal class CommandManagerPluginScoped : IInternalDisposableService, ICommand
} }
/// <inheritdoc/> /// <inheritdoc/>
public ReadOnlyDictionary<string, CommandInfo> Commands => this.commandManagerService.Commands; public ReadOnlyDictionary<string, ICommandInfo> Commands => this.commandManagerService.Commands;
/// <inheritdoc/> /// <inheritdoc/>
void IInternalDisposableService.DisposeService() void IInternalDisposableService.DisposeService()
@ -217,16 +267,15 @@ internal class CommandManagerPluginScoped : IInternalDisposableService, ICommand
=> this.commandManagerService.ProcessCommand(content); => this.commandManagerService.ProcessCommand(content);
/// <inheritdoc/> /// <inheritdoc/>
public void DispatchCommand(string command, string argument, CommandInfo info) public void DispatchCommand(string command, string argument, ICommandInfo info)
=> this.commandManagerService.DispatchCommand(command, argument, info); => this.commandManagerService.DispatchCommand(command, argument, info);
/// <inheritdoc/> /// <inheritdoc/>
public bool AddHandler(string command, CommandInfo info) public bool AddHandler(string command, ICommandInfo info)
{ {
if (!this.pluginRegisteredCommands.Contains(command)) if (!this.pluginRegisteredCommands.Contains(command))
{ {
info.LoaderAssemblyName = this.pluginInfo.InternalName; if (this.commandManagerService.AddHandler(command, info, this.pluginInfo.InternalName))
if (this.commandManagerService.AddHandler(command, info))
{ {
this.pluginRegisteredCommands.Add(command); this.pluginRegisteredCommands.Add(command);
return true; return true;

View file

@ -54,7 +54,7 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
/// <inheritdoc/> /// <inheritdoc/>
public event IContextMenu.OnMenuOpenedDelegate? OnMenuOpened; public event IContextMenu.OnMenuOpenedDelegate? OnMenuOpened;
private Dictionary<ContextMenuType, List<MenuItem>> MenuItems { get; } = new(); private Dictionary<ContextMenuType, List<IMenuItem>> MenuItems { get; } = new();
private object MenuItemsLock { get; } = new(); private object MenuItemsLock { get; } = new();
@ -62,7 +62,7 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
private ContextMenuType? SelectedMenuType { get; set; } private ContextMenuType? SelectedMenuType { get; set; }
private List<MenuItem>? SelectedItems { get; set; } private List<IMenuItem>? SelectedItems { get; set; }
private HashSet<nint> SelectedEventInterfaces { get; } = new(); private HashSet<nint> SelectedEventInterfaces { get; } = new();
@ -72,7 +72,7 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
// 0 -> inf: selected items // 0 -> inf: selected items
private List<int> MenuCallbackIds { get; } = new(); private List<int> MenuCallbackIds { get; } = new();
private IReadOnlyList<MenuItem>? SubmenuItems { get; set; } private IReadOnlyList<IMenuItem>? SubmenuItems { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
void IInternalDisposableService.DisposeService() void IInternalDisposableService.DisposeService()
@ -90,7 +90,7 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
} }
/// <inheritdoc/> /// <inheritdoc/>
public void AddMenuItem(ContextMenuType menuType, MenuItem item) public void AddMenuItem(ContextMenuType menuType, IMenuItem item)
{ {
lock (this.MenuItemsLock) lock (this.MenuItemsLock)
{ {
@ -101,7 +101,7 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
} }
/// <inheritdoc/> /// <inheritdoc/>
public bool RemoveMenuItem(ContextMenuType menuType, MenuItem item) public bool RemoveMenuItem(ContextMenuType menuType, IMenuItem item)
{ {
lock (this.MenuItemsLock) lock (this.MenuItemsLock)
{ {
@ -167,7 +167,7 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
return values; return values;
} }
private void SetupGenericMenu(int headerCount, int sizeHeaderIdx, int returnHeaderIdx, int submenuHeaderIdx, IReadOnlyList<MenuItem> items, ref int valueCount, ref AtkValue* values) private void SetupGenericMenu(int headerCount, int sizeHeaderIdx, int returnHeaderIdx, int submenuHeaderIdx, IReadOnlyList<IMenuItem> items, ref int valueCount, ref AtkValue* values)
{ {
var itemsWithIdx = items.Select((item, idx) => (item, idx)).OrderBy(i => i.item.Priority).ToArray(); var itemsWithIdx = items.Select((item, idx) => (item, idx)).OrderBy(i => i.item.Priority).ToArray();
var prefixItems = itemsWithIdx.Where(i => i.item.Priority < 0).ToArray(); var prefixItems = itemsWithIdx.Where(i => i.item.Priority < 0).ToArray();
@ -215,7 +215,7 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
returnMask <<= prefixMenuSize; returnMask <<= prefixMenuSize;
submenuMask <<= prefixMenuSize; submenuMask <<= prefixMenuSize;
void FillData(Span<AtkValue> disabledData, Span<AtkValue> nameData, int i, MenuItem item, int idx) void FillData(Span<AtkValue> disabledData, Span<AtkValue> nameData, int i, IMenuItem item, int idx)
{ {
this.MenuCallbackIds.Add(idx); this.MenuCallbackIds.Add(idx);
@ -231,7 +231,7 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
submenuMask |= 1u << i; submenuMask |= 1u << i;
nameData[i].ChangeType(ValueType.String); nameData[i].ChangeType(ValueType.String);
nameData[i].SetManagedString(item.PrefixedName.Encode().NullTerminate()); nameData[i].SetManagedString(this.GetPrefixedName(item).Encode().NullTerminate());
} }
for (var i = 0; i < prefixMenuSize; ++i) for (var i = 0; i < prefixMenuSize; ++i)
@ -253,8 +253,19 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
offsetData[sizeHeaderIdx].UInt += (uint)items.Count; offsetData[sizeHeaderIdx].UInt += (uint)items.Count;
} }
/// <summary>
/// Gets the name with the given prefix.
/// </summary>
internal SeString GetPrefixedName(IMenuItem menuItem) =>
menuItem.Prefix is { } prefix
? new SeStringBuilder()
.AddUiForeground($"{prefix.ToIconString()} ", menuItem.PrefixColor)
.Append(menuItem.Name)
.Build()
: menuItem.Name;
private void SetupContextMenu(IReadOnlyList<MenuItem> items, ref int valueCount, ref AtkValue* values) private void SetupContextMenu(IReadOnlyList<IMenuItem> items, ref int valueCount, ref AtkValue* values)
{ {
// 0: UInt = Item Count // 0: UInt = Item Count
// 1: UInt = 0 (probably window name, just unused) // 1: UInt = 0 (probably window name, just unused)
@ -268,8 +279,8 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
{ {
if (!item.Prefix.HasValue) if (!item.Prefix.HasValue)
{ {
item.Prefix = MenuItem.DalamudDefaultPrefix; item.Prefix = IMenuItem.DalamudDefaultPrefix;
item.PrefixColor = MenuItem.DalamudDefaultPrefixColor; item.PrefixColor = IMenuItem.DalamudDefaultPrefixColor;
if (!item.UseDefaultPrefix) if (!item.UseDefaultPrefix)
{ {
@ -281,7 +292,7 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
this.SetupGenericMenu(7, 0, 2, 3, items, ref valueCount, ref values); this.SetupGenericMenu(7, 0, 2, 3, items, ref valueCount, ref values);
} }
private void SetupContextSubMenu(IReadOnlyList<MenuItem> items, ref int valueCount, ref AtkValue* values) private void SetupContextSubMenu(IReadOnlyList<IMenuItem> items, ref int valueCount, ref AtkValue* values)
{ {
// 0: UInt = ContextItemCount // 0: UInt = ContextItemCount
// 1: skipped? // 1: skipped?
@ -376,7 +387,7 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
return ret; return ret;
} }
private List<MenuItem> FixupMenuList(List<MenuItem> items, int nativeMenuSize) private List<IMenuItem> FixupMenuList(List<IMenuItem> items, int nativeMenuSize)
{ {
// The in game menu actually supports 32 items, but the last item can't have a visible submenu arrow. // The in game menu actually supports 32 items, but the last item can't have a visible submenu arrow.
// As such, we'll only work with 31 items. // As such, we'll only work with 31 items.
@ -401,7 +412,7 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
return items; return items;
} }
private void OpenSubmenu(SeString name, IReadOnlyList<MenuItem> submenuItems, int posX, int posY) private void OpenSubmenu(SeString name, IReadOnlyList<IMenuItem> submenuItems, int posX, int posY)
{ {
if (submenuItems.Count == 0) if (submenuItems.Count == 0)
throw new ArgumentException("Submenu must not be empty", nameof(submenuItems)); throw new ArgumentException("Submenu must not be empty", nameof(submenuItems));
@ -513,7 +524,7 @@ internal class ContextMenuPluginScoped : IInternalDisposableService, IContextMen
/// <inheritdoc/> /// <inheritdoc/>
public event IContextMenu.OnMenuOpenedDelegate? OnMenuOpened; public event IContextMenu.OnMenuOpenedDelegate? OnMenuOpened;
private Dictionary<ContextMenuType, List<MenuItem>> MenuItems { get; } = new(); private Dictionary<ContextMenuType, List<IMenuItem>> MenuItems { get; } = new();
private object MenuItemsLock { get; } = new(); private object MenuItemsLock { get; } = new();
@ -535,7 +546,7 @@ internal class ContextMenuPluginScoped : IInternalDisposableService, IContextMen
} }
/// <inheritdoc/> /// <inheritdoc/>
public void AddMenuItem(ContextMenuType menuType, MenuItem item) public void AddMenuItem(ContextMenuType menuType, IMenuItem item)
{ {
lock (this.MenuItemsLock) lock (this.MenuItemsLock)
{ {
@ -548,7 +559,7 @@ internal class ContextMenuPluginScoped : IInternalDisposableService, IContextMen
} }
/// <inheritdoc/> /// <inheritdoc/>
public bool RemoveMenuItem(ContextMenuType menuType, MenuItem item) public bool RemoveMenuItem(ContextMenuType menuType, IMenuItem item)
{ {
lock (this.MenuItemsLock) lock (this.MenuItemsLock)
{ {
@ -559,6 +570,6 @@ internal class ContextMenuPluginScoped : IInternalDisposableService, IContextMen
return this.parentService.RemoveMenuItem(menuType, item); return this.parentService.RemoveMenuItem(menuType, item);
} }
private void OnMenuOpenedForward(MenuOpenedArgs args) => private void OnMenuOpenedForward(IMenuOpenedArgs args) =>
this.OnMenuOpened?.Invoke(args); this.OnMenuOpened?.Invoke(args);
} }

View file

@ -11,7 +11,7 @@ namespace Dalamud.Game.Gui.ContextMenu;
/// <summary> /// <summary>
/// Base class for <see cref="IContextMenu"/> menu args. /// Base class for <see cref="IContextMenu"/> menu args.
/// </summary> /// </summary>
public abstract unsafe class MenuArgs internal abstract unsafe class MenuArgs : IMenuArgs
{ {
private IReadOnlySet<nint>? eventInterfaces; private IReadOnlySet<nint>? eventInterfaces;
@ -37,6 +37,51 @@ public abstract unsafe class MenuArgs
}; };
} }
/// <inheritdoc/>
public string? AddonName { get; }
/// <inheritdoc/>
public nint AddonPtr { get; }
/// <inheritdoc/>
public nint AgentPtr { get; }
/// <inheritdoc/>
public ContextMenuType MenuType { get; }
/// <inheritdoc/>
public MenuTarget Target { get; }
/// <inheritdoc/>
public IReadOnlySet<nint> EventInterfaces
{
get
{
if (this.MenuType is ContextMenuType.Default)
{
return this.eventInterfaces ?? new HashSet<nint>();
}
else
{
throw new InvalidOperationException("Not a default context menu");
}
}
}
}
/// <summary>
/// Interface representing a context menus args.
/// </summary>
public interface IMenuArgs
{
/// <summary>
/// Gets a list of AtkEventInterface pointers associated with the context menu.
/// Only available with <see cref="ContextMenuType.Default"/>.
/// Almost always an agent pointer. You can use this to find out what type of context menu it is.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when the context menu is not a <see cref="ContextMenuType.Default"/>.</exception>
public IReadOnlySet<nint> EventInterfaces { get; }
/// <summary> /// <summary>
/// Gets the name of the addon that opened the context menu. /// Gets the name of the addon that opened the context menu.
/// </summary> /// </summary>
@ -63,25 +108,4 @@ public abstract unsafe class MenuArgs
/// <see cref="ContextMenuType.Inventory"/> signifies a <see cref="MenuTargetInventory"/>. /// <see cref="ContextMenuType.Inventory"/> signifies a <see cref="MenuTargetInventory"/>.
/// </summary> /// </summary>
public MenuTarget Target { get; } public MenuTarget Target { get; }
/// <summary>
/// Gets a list of AtkEventInterface pointers associated with the context menu.
/// Only available with <see cref="ContextMenuType.Default"/>.
/// Almost always an agent pointer. You can use this to find out what type of context menu it is.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when the context menu is not a <see cref="ContextMenuType.Default"/>.</exception>
public IReadOnlySet<nint> EventInterfaces
{
get
{
if (this.MenuType is ContextMenuType.Default)
{
return this.eventInterfaces ?? new HashSet<nint>();
}
else
{
throw new InvalidOperationException("Not a default context menu");
}
}
}
} }

View file

@ -8,32 +8,15 @@ namespace Dalamud.Game.Gui.ContextMenu;
/// <summary> /// <summary>
/// A menu item that can be added to a context menu. /// A menu item that can be added to a context menu.
/// </summary> /// </summary>
public sealed record MenuItem public sealed record MenuItem : IMenuItem
{ {
/// <summary> /// <inheritdoc/>
/// The default prefix used if no specific preset is specified.
/// </summary>
public const SeIconChar DalamudDefaultPrefix = SeIconChar.BoxedLetterD;
/// <summary>
/// The default prefix color used if no specific preset is specified.
/// </summary>
public const ushort DalamudDefaultPrefixColor = 539;
/// <summary>
/// Gets or sets the display name of the menu item.
/// </summary>
public SeString Name { get; set; } = SeString.Empty; public SeString Name { get; set; } = SeString.Empty;
/// <summary> /// <inheritdoc/>
/// Gets or sets the prefix attached to the beginning of <see cref="Name"/>.
/// </summary>
public SeIconChar? Prefix { get; set; } public SeIconChar? Prefix { get; set; }
/// <summary> /// <inheritdoc/>
/// Sets the character to prefix the <see cref="Name"/> with. Will be converted into a fancy boxed letter icon. Must be an uppercase letter.
/// </summary>
/// <exception cref="ArgumentException"><paramref name="value"/> must be an uppercase letter.</exception>
public char? PrefixChar public char? PrefixChar
{ {
set set
@ -52,55 +35,97 @@ public sealed record MenuItem
} }
} }
/// <inheritdoc/>
public ushort PrefixColor { get; set; }
/// <inheritdoc/>
public bool UseDefaultPrefix { get; set; }
/// <inheritdoc/>
public Action<IMenuItemClickedArgs>? OnClicked { get; set; }
/// <inheritdoc/>
public int Priority { get; set; }
/// <inheritdoc/>
public bool IsEnabled { get; set; } = true;
/// <inheritdoc/>
public bool IsSubmenu { get; set; }
/// <inheritdoc/>
public bool IsReturn { get; set; }
}
/// <summary>
/// Interface representing a menu item to be added to a context menu.
/// </summary>
public interface IMenuItem
{
/// <summary>
/// The default prefix used if no specific preset is specified.
/// </summary>
public const SeIconChar DalamudDefaultPrefix = SeIconChar.BoxedLetterD;
/// <summary>
/// The default prefix color used if no specific preset is specified.
/// </summary>
public const ushort DalamudDefaultPrefixColor = 539;
/// <summary>
/// Gets or sets the display name of the menu item.
/// </summary>
SeString Name { get; set; }
/// <summary>
/// Gets or sets the prefix attached to the beginning of <see cref="Name"/>.
/// </summary>
SeIconChar? Prefix { get; set; }
/// <summary>
/// Sets the character to prefix the <see cref="Name"/> with. Will be converted into a fancy boxed letter icon. Must be an uppercase letter.
/// </summary>
/// <exception cref="ArgumentException"><paramref name="value"/> must be an uppercase letter.</exception>
char? PrefixChar { set; }
/// <summary> /// <summary>
/// Gets or sets the color of the <see cref="Prefix"/>. Specifies a <see cref="UIColor"/> row id. /// Gets or sets the color of the <see cref="Prefix"/>. Specifies a <see cref="UIColor"/> row id.
/// </summary> /// </summary>
public ushort PrefixColor { get; set; } ushort PrefixColor { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the dev wishes to intentionally use the default prefix symbol and color. /// Gets or sets a value indicating whether the dev wishes to intentionally use the default prefix symbol and color.
/// </summary> /// </summary>
public bool UseDefaultPrefix { get; set; } bool UseDefaultPrefix { get; set; }
/// <summary> /// <summary>
/// Gets or sets the callback to be invoked when the menu item is clicked. /// Gets or sets the callback to be invoked when the menu item is clicked.
/// </summary> /// </summary>
public Action<MenuItemClickedArgs>? OnClicked { get; set; } Action<IMenuItemClickedArgs>? OnClicked { get; set; }
/// <summary> /// <summary>
/// Gets or sets the priority (or order) with which the menu item should be displayed in descending order. /// Gets or sets the priority (or order) with which the menu item should be displayed in descending order.
/// Priorities below 0 will be displayed above the native menu items. /// Priorities below 0 will be displayed above the native menu items.
/// Other priorities will be displayed below the native menu items. /// Other priorities will be displayed below the native menu items.
/// </summary> /// </summary>
public int Priority { get; set; } int Priority { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the menu item is enabled. /// Gets or sets a value indicating whether the menu item is enabled.
/// Disabled items will be faded and cannot be clicked on. /// Disabled items will be faded and cannot be clicked on.
/// </summary> /// </summary>
public bool IsEnabled { get; set; } = true; bool IsEnabled { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the menu item is a submenu. /// Gets or sets a value indicating whether the menu item is a submenu.
/// This value is purely visual. Submenu items will have an arrow to its right. /// This value is purely visual. Submenu items will have an arrow to its right.
/// </summary> /// </summary>
public bool IsSubmenu { get; set; } bool IsSubmenu { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the menu item is a return item. /// Gets or sets a value indicating whether the menu item is a return item.
/// This value is purely visual. Return items will have a back arrow to its left. /// This value is purely visual. Return items will have a back arrow to its left.
/// If both <see cref="IsSubmenu"/> and <see cref="IsReturn"/> are true, the return arrow will take precedence. /// If both <see cref="IsSubmenu"/> and <see cref="IsReturn"/> are true, the return arrow will take precedence.
/// </summary> /// </summary>
public bool IsReturn { get; set; } bool IsReturn { get; set; }
/// <summary>
/// Gets the name with the given prefix.
/// </summary>
internal SeString PrefixedName =>
this.Prefix is { } prefix
? new SeStringBuilder()
.AddUiForeground($"{prefix.ToIconString()} ", this.PrefixColor)
.Append(this.Name)
.Build()
: this.Name;
} }

View file

@ -10,7 +10,7 @@ namespace Dalamud.Game.Gui.ContextMenu;
/// <summary> /// <summary>
/// Callback args used when a menu item is clicked. /// Callback args used when a menu item is clicked.
/// </summary> /// </summary>
public sealed unsafe class MenuItemClickedArgs : MenuArgs internal sealed unsafe class MenuItemClickedArgs : MenuArgs, IMenuItemClickedArgs
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="MenuItemClickedArgs"/> class. /// Initializes a new instance of the <see cref="MenuItemClickedArgs"/> class.
@ -20,26 +20,73 @@ public sealed unsafe class MenuItemClickedArgs : MenuArgs
/// <param name="agent">Agent associated with the context menu.</param> /// <param name="agent">Agent associated with the context menu.</param>
/// <param name="type">The type of context menu.</param> /// <param name="type">The type of context menu.</param>
/// <param name="eventInterfaces">List of AtkEventInterfaces associated with the context menu.</param> /// <param name="eventInterfaces">List of AtkEventInterfaces associated with the context menu.</param>
internal MenuItemClickedArgs(Action<SeString?, IReadOnlyList<MenuItem>> openSubmenu, AtkUnitBase* addon, AgentInterface* agent, ContextMenuType type, IReadOnlySet<nint> eventInterfaces) internal MenuItemClickedArgs(Action<SeString?, IReadOnlyList<IMenuItem>> openSubmenu, AtkUnitBase* addon, AgentInterface* agent, ContextMenuType type, IReadOnlySet<nint> eventInterfaces)
: base(addon, agent, type, eventInterfaces) : base(addon, agent, type, eventInterfaces)
{ {
this.OnOpenSubmenu = openSubmenu; this.OnOpenSubmenu = openSubmenu;
} }
private Action<SeString?, IReadOnlyList<MenuItem>> OnOpenSubmenu { get; } private Action<SeString?, IReadOnlyList<IMenuItem>> OnOpenSubmenu { get; }
/// <inheritdoc/>
public void OpenSubmenu(SeString name, IReadOnlyList<IMenuItem> items) =>
this.OnOpenSubmenu(name, items);
/// <inheritdoc/>
public void OpenSubmenu(IReadOnlyList<IMenuItem> items) =>
this.OnOpenSubmenu(null, items);
}
/// <summary>
/// An interface representing the callback args used when a menu item is clicked.
/// </summary>
public interface IMenuItemClickedArgs
{
/// <summary> /// <summary>
/// Opens a submenu with the given name and items. /// Opens a submenu with the given name and items.
/// </summary> /// </summary>
/// <param name="name">The name of the submenu, displayed at the top.</param> /// <param name="name">The name of the submenu, displayed at the top.</param>
/// <param name="items">The items to display in the submenu.</param> /// <param name="items">The items to display in the submenu.</param>
public void OpenSubmenu(SeString name, IReadOnlyList<MenuItem> items) => void OpenSubmenu(SeString name, IReadOnlyList<IMenuItem> items);
this.OnOpenSubmenu(name, items);
/// <summary> /// <summary>
/// Opens a submenu with the given items. /// Opens a submenu with the given items.
/// </summary> /// </summary>
/// <param name="items">The items to display in the submenu.</param> /// <param name="items">The items to display in the submenu.</param>
public void OpenSubmenu(IReadOnlyList<MenuItem> items) => void OpenSubmenu(IReadOnlyList<IMenuItem> items);
this.OnOpenSubmenu(null, items);
/// <summary>
/// Gets a list of AtkEventInterface pointers associated with the context menu.
/// Only available with <see cref="ContextMenuType.Default"/>.
/// Almost always an agent pointer. You can use this to find out what type of context menu it is.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when the context menu is not a <see cref="ContextMenuType.Default"/>.</exception>
IReadOnlySet<nint> EventInterfaces { get; }
/// <summary>
/// Gets the name of the addon that opened the context menu.
/// </summary>
string? AddonName { get; }
/// <summary>
/// Gets the memory pointer of the addon that opened the context menu.
/// </summary>
nint AddonPtr { get; }
/// <summary>
/// Gets the memory pointer of the agent that opened the context menu.
/// </summary>
nint AgentPtr { get; }
/// <summary>
/// Gets the type of the context menu.
/// </summary>
ContextMenuType MenuType { get; }
/// <summary>
/// Gets the target info of the context menu. The actual type depends on <see cref="MenuType"/>.
/// <see cref="ContextMenuType.Default"/> signifies a <see cref="MenuTargetDefault"/>.
/// <see cref="ContextMenuType.Inventory"/> signifies a <see cref="MenuTargetInventory"/>.
/// </summary>
MenuTarget Target { get; }
} }

View file

@ -8,7 +8,7 @@ namespace Dalamud.Game.Gui.ContextMenu;
/// <summary> /// <summary>
/// Callback args used when a menu item is opened. /// Callback args used when a menu item is opened.
/// </summary> /// </summary>
public sealed unsafe class MenuOpenedArgs : MenuArgs internal sealed unsafe class MenuOpenedArgs : MenuArgs, IMenuOpenedArgs
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="MenuOpenedArgs"/> class. /// Initializes a new instance of the <see cref="MenuOpenedArgs"/> class.
@ -26,10 +26,19 @@ public sealed unsafe class MenuOpenedArgs : MenuArgs
private Action<MenuItem> OnAddMenuItem { get; } private Action<MenuItem> OnAddMenuItem { get; }
/// <inheritdoc/>
public void AddMenuItem(MenuItem item) =>
this.OnAddMenuItem(item);
}
/// <summary>
/// An interface representing the callback args used when a menu item is opened.
/// </summary>
public interface IMenuOpenedArgs : IMenuArgs
{
/// <summary> /// <summary>
/// Adds a custom menu item to the context menu. /// Adds a custom menu item to the context menu.
/// </summary> /// </summary>
/// <param name="item">The menu item to add.</param> /// <param name="item">The menu item to add.</param>
public void AddMenuItem(MenuItem item) => void AddMenuItem(MenuItem item);
this.OnAddMenuItem(item);
} }

View file

@ -36,7 +36,7 @@ public sealed unsafe class MenuTargetDefault : MenuTarget
/// <summary> /// <summary>
/// Gets the target object. /// Gets the target object.
/// </summary> /// </summary>
public GameObject? TargetObject => Service<ObjectTable>.Get().SearchById(this.TargetObjectId); public IGameObject? TargetObject => Service<ObjectTable>.Get().SearchById(this.TargetObjectId);
/// <summary> /// <summary>
/// Gets the content id of the target. /// Gets the content id of the target.

View file

@ -77,7 +77,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
public IReadOnlyList<IReadOnlyDtrBarEntry> Entries => this.entries; public IReadOnlyList<IReadOnlyDtrBarEntry> Entries => this.entries;
/// <inheritdoc/> /// <inheritdoc/>
public DtrBarEntry Get(string title, SeString? text = null) public IDtrBarEntry Get(string title, SeString? text = null)
{ {
if (this.entries.Any(x => x.Title == title) || this.newEntries.Any(x => x.Title == title)) if (this.entries.Any(x => x.Title == title) || this.newEntries.Any(x => x.Title == title))
throw new ArgumentException("An entry with the same title already exists."); throw new ArgumentException("An entry with the same title already exists.");
@ -501,7 +501,7 @@ internal class DtrBarPluginScoped : IInternalDisposableService, IDtrBar
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly DtrBar dtrBarService = Service<DtrBar>.Get(); private readonly DtrBar dtrBarService = Service<DtrBar>.Get();
private readonly Dictionary<string, DtrBarEntry> pluginEntries = new(); private readonly Dictionary<string, IDtrBarEntry> pluginEntries = new();
/// <inheritdoc/> /// <inheritdoc/>
public IReadOnlyList<IReadOnlyDtrBarEntry> Entries => this.dtrBarService.Entries; public IReadOnlyList<IReadOnlyDtrBarEntry> Entries => this.dtrBarService.Entries;
@ -518,7 +518,7 @@ internal class DtrBarPluginScoped : IInternalDisposableService, IDtrBar
} }
/// <inheritdoc/> /// <inheritdoc/>
public DtrBarEntry Get(string title, SeString? text = null) public IDtrBarEntry Get(string title, SeString? text = null)
{ {
// If we already have a known entry for this plugin, return it. // If we already have a known entry for this plugin, return it.
if (this.pluginEntries.TryGetValue(title, out var existingEntry)) return existingEntry; if (this.pluginEntries.TryGetValue(title, out var existingEntry)) return existingEntry;

View file

@ -154,5 +154,5 @@ internal class PartyFinderGuiPluginScoped : IInternalDisposableService, IPartyFi
this.ReceiveListing = null; this.ReceiveListing = null;
} }
private void ReceiveListingForward(PartyFinderListing listing, PartyFinderListingEventArgs args) => this.ReceiveListing?.Invoke(listing, args); private void ReceiveListingForward(IPartyFinderListing listing, IPartyFinderListingEventArgs args) => this.ReceiveListing?.Invoke(listing, args);
} }

View file

@ -12,7 +12,7 @@ namespace Dalamud.Game.Gui.PartyFinder.Types;
/// <summary> /// <summary>
/// A single listing in party finder. /// A single listing in party finder.
/// </summary> /// </summary>
public class PartyFinderListing internal class PartyFinderListing : IPartyFinderListing
{ {
private readonly byte objective; private readonly byte objective;
private readonly byte conditions; private readonly byte conditions;
@ -63,171 +63,267 @@ public class PartyFinderListing
.ToArray(); .ToArray();
} }
/// <summary> /// <inheritdoc/>
/// Gets the ID assigned to this listing by the game's server.
/// </summary>
public uint Id { get; } public uint Id { get; }
/// <summary> /// <inheritdoc/>
/// Gets the lower bits of the player's content ID.
/// </summary>
public uint ContentIdLower { get; } public uint ContentIdLower { get; }
/// <summary> /// <inheritdoc/>
/// Gets the name of the player hosting this listing.
/// </summary>
public SeString Name { get; } public SeString Name { get; }
/// <summary> /// <inheritdoc/>
/// Gets the description of this listing as set by the host. May be multiple lines.
/// </summary>
public SeString Description { get; } public SeString Description { get; }
/// <summary> /// <inheritdoc/>
/// Gets the world that this listing was created on.
/// </summary>
public Lazy<World> World { get; } public Lazy<World> World { get; }
/// <summary> /// <inheritdoc/>
/// Gets the home world of the listing's host.
/// </summary>
public Lazy<World> HomeWorld { get; } public Lazy<World> HomeWorld { get; }
/// <summary> /// <inheritdoc/>
/// Gets the current world of the listing's host.
/// </summary>
public Lazy<World> CurrentWorld { get; } public Lazy<World> CurrentWorld { get; }
/// <summary> /// <inheritdoc/>
/// Gets the Party Finder category this listing is listed under.
/// </summary>
public DutyCategory Category { get; } public DutyCategory Category { get; }
/// <summary> /// <inheritdoc/>
/// Gets the row ID of the duty this listing is for. May be 0 for non-duty listings.
/// </summary>
public ushort RawDuty { get; } public ushort RawDuty { get; }
/// <summary> /// <inheritdoc/>
/// Gets the duty this listing is for. May be null for non-duty listings.
/// </summary>
public Lazy<ContentFinderCondition> Duty { get; } public Lazy<ContentFinderCondition> Duty { get; }
/// <summary> /// <inheritdoc/>
/// Gets the type of duty this listing is for.
/// </summary>
public DutyType DutyType { get; } public DutyType DutyType { get; }
/// <summary> /// <inheritdoc/>
/// Gets a value indicating whether if this listing is beginner-friendly. Shown with a sprout icon in-game.
/// </summary>
public bool BeginnersWelcome { get; } public bool BeginnersWelcome { get; }
/// <summary> /// <inheritdoc/>
/// Gets how many seconds this listing will continue to be available for. It may end before this time if the party
/// fills or the host ends it early.
/// </summary>
public ushort SecondsRemaining { get; } public ushort SecondsRemaining { get; }
/// <summary> /// <inheritdoc/>
/// Gets the minimum item level required to join this listing.
/// </summary>
public ushort MinimumItemLevel { get; } public ushort MinimumItemLevel { get; }
/// <summary> /// <inheritdoc/>
/// Gets the number of parties this listing is recruiting for.
/// </summary>
public byte Parties { get; } public byte Parties { get; }
/// <summary> /// <inheritdoc/>
/// Gets the number of player slots this listing is recruiting for.
/// </summary>
public byte SlotsAvailable { get; } public byte SlotsAvailable { get; }
/// <summary> /// <inheritdoc/>
/// Gets the time at which the server this listings is on last restarted for a patch/hotfix.
/// Probably.
/// </summary>
public uint LastPatchHotfixTimestamp { get; } public uint LastPatchHotfixTimestamp { get; }
/// <summary> /// <inheritdoc/>
/// Gets a list of player slots that the Party Finder is accepting.
/// </summary>
public IReadOnlyCollection<PartyFinderSlot> Slots => this.slots; public IReadOnlyCollection<PartyFinderSlot> Slots => this.slots;
/// <summary> /// <inheritdoc/>
/// Gets the objective of this listing.
/// </summary>
public ObjectiveFlags Objective => (ObjectiveFlags)this.objective; public ObjectiveFlags Objective => (ObjectiveFlags)this.objective;
/// <summary> /// <inheritdoc/>
/// Gets the conditions of this listing.
/// </summary>
public ConditionFlags Conditions => (ConditionFlags)this.conditions; public ConditionFlags Conditions => (ConditionFlags)this.conditions;
/// <summary> /// <inheritdoc/>
/// Gets the Duty Finder settings that will be used for this listing.
/// </summary>
public DutyFinderSettingsFlags DutyFinderSettings => (DutyFinderSettingsFlags)this.dutyFinderSettings; public DutyFinderSettingsFlags DutyFinderSettings => (DutyFinderSettingsFlags)this.dutyFinderSettings;
/// <summary> /// <inheritdoc/>
/// Gets the loot rules that will be used for this listing.
/// </summary>
public LootRuleFlags LootRules => (LootRuleFlags)this.lootRules; public LootRuleFlags LootRules => (LootRuleFlags)this.lootRules;
/// <summary> /// <inheritdoc/>
/// Gets where this listing is searching. Note that this is also used for denoting alliance raid listings and one
/// player per job.
/// </summary>
public SearchAreaFlags SearchArea => (SearchAreaFlags)this.searchArea; public SearchAreaFlags SearchArea => (SearchAreaFlags)this.searchArea;
/// <summary> /// <inheritdoc/>
/// Gets a list of the class/job IDs that are currently present in the party.
/// </summary>
public IReadOnlyCollection<byte> RawJobsPresent => this.jobsPresent; public IReadOnlyCollection<byte> RawJobsPresent => this.jobsPresent;
/// <summary> /// <inheritdoc/>
/// Gets a list of the classes/jobs that are currently present in the party.
/// </summary>
public IReadOnlyCollection<Lazy<ClassJob>> JobsPresent { get; } public IReadOnlyCollection<Lazy<ClassJob>> JobsPresent { get; }
#region Indexers #region Indexers
/// <summary> /// <inheritdoc/>
/// Check if the given flag is present.
/// </summary>
/// <param name="flag">The flag to check for.</param>
/// <returns>A value indicating whether the flag is present.</returns>
public bool this[ObjectiveFlags flag] => this.objective == 0 || (this.objective & (uint)flag) > 0; public bool this[ObjectiveFlags flag] => this.objective == 0 || (this.objective & (uint)flag) > 0;
/// <summary> /// <inheritdoc/>
/// Check if the given flag is present.
/// </summary>
/// <param name="flag">The flag to check for.</param>
/// <returns>A value indicating whether the flag is present.</returns>
public bool this[ConditionFlags flag] => this.conditions == 0 || (this.conditions & (uint)flag) > 0; public bool this[ConditionFlags flag] => this.conditions == 0 || (this.conditions & (uint)flag) > 0;
/// <summary> /// <inheritdoc/>
/// Check if the given flag is present.
/// </summary>
/// <param name="flag">The flag to check for.</param>
/// <returns>A value indicating whether the flag is present.</returns>
public bool this[DutyFinderSettingsFlags flag] => this.dutyFinderSettings == 0 || (this.dutyFinderSettings & (uint)flag) > 0; public bool this[DutyFinderSettingsFlags flag] => this.dutyFinderSettings == 0 || (this.dutyFinderSettings & (uint)flag) > 0;
/// <summary> /// <inheritdoc/>
/// Check if the given flag is present.
/// </summary>
/// <param name="flag">The flag to check for.</param>
/// <returns>A value indicating whether the flag is present.</returns>
public bool this[LootRuleFlags flag] => this.lootRules == 0 || (this.lootRules & (uint)flag) > 0; public bool this[LootRuleFlags flag] => this.lootRules == 0 || (this.lootRules & (uint)flag) > 0;
/// <summary> /// <inheritdoc/>
/// Check if the given flag is present.
/// </summary>
/// <param name="flag">The flag to check for.</param>
/// <returns>A value indicating whether the flag is present.</returns>
public bool this[SearchAreaFlags flag] => this.searchArea == 0 || (this.searchArea & (uint)flag) > 0; public bool this[SearchAreaFlags flag] => this.searchArea == 0 || (this.searchArea & (uint)flag) > 0;
#endregion #endregion
} }
/// <summary>
/// A interface representing a single listing in party finder.
/// </summary>
public interface IPartyFinderListing
{
/// <summary>
/// Gets the objective of this listing.
/// </summary>
ObjectiveFlags Objective { get; }
/// <summary>
/// Gets the conditions of this listing.
/// </summary>
ConditionFlags Conditions { get; }
/// <summary>
/// Gets the Duty Finder settings that will be used for this listing.
/// </summary>
DutyFinderSettingsFlags DutyFinderSettings { get; }
/// <summary>
/// Gets the loot rules that will be used for this listing.
/// </summary>
LootRuleFlags LootRules { get; }
/// <summary>
/// Gets where this listing is searching. Note that this is also used for denoting alliance raid listings and one
/// player per job.
/// </summary>
SearchAreaFlags SearchArea { get; }
/// <summary>
/// Gets a list of player slots that the Party Finder is accepting.
/// </summary>
IReadOnlyCollection<PartyFinderSlot> Slots { get; }
/// <summary>
/// Gets a list of the classes/jobs that are currently present in the party.
/// </summary>
IReadOnlyCollection<Lazy<ClassJob>> JobsPresent { get; }
/// <summary>
/// Gets the ID assigned to this listing by the game's server.
/// </summary>
uint Id { get; }
/// <summary>
/// Gets the lower bits of the player's content ID.
/// </summary>
uint ContentIdLower { get; }
/// <summary>
/// Gets the name of the player hosting this listing.
/// </summary>
SeString Name { get; }
/// <summary>
/// Gets the description of this listing as set by the host. May be multiple lines.
/// </summary>
SeString Description { get; }
/// <summary>
/// Gets the world that this listing was created on.
/// </summary>
Lazy<World> World { get; }
/// <summary>
/// Gets the home world of the listing's host.
/// </summary>
Lazy<World> HomeWorld { get; }
/// <summary>
/// Gets the current world of the listing's host.
/// </summary>
Lazy<World> CurrentWorld { get; }
/// <summary>
/// Gets the Party Finder category this listing is listed under.
/// </summary>
DutyCategory Category { get; }
/// <summary>
/// Gets the row ID of the duty this listing is for. May be 0 for non-duty listings.
/// </summary>
ushort RawDuty { get; }
/// <summary>
/// Gets the duty this listing is for. May be null for non-duty listings.
/// </summary>
Lazy<ContentFinderCondition> Duty { get; }
/// <summary>
/// Gets the type of duty this listing is for.
/// </summary>
DutyType DutyType { get; }
/// <summary>
/// Gets a value indicating whether if this listing is beginner-friendly. Shown with a sprout icon in-game.
/// </summary>
bool BeginnersWelcome { get; }
/// <summary>
/// Gets how many seconds this listing will continue to be available for. It may end before this time if the party
/// fills or the host ends it early.
/// </summary>
ushort SecondsRemaining { get; }
/// <summary>
/// Gets the minimum item level required to join this listing.
/// </summary>
ushort MinimumItemLevel { get; }
/// <summary>
/// Gets the number of parties this listing is recruiting for.
/// </summary>
byte Parties { get; }
/// <summary>
/// Gets the number of player slots this listing is recruiting for.
/// </summary>
byte SlotsAvailable { get; }
/// <summary>
/// Gets the time at which the server this listings is on last restarted for a patch/hotfix.
/// Probably.
/// </summary>
uint LastPatchHotfixTimestamp { get; }
/// <summary>
/// Gets a list of the class/job IDs that are currently present in the party.
/// </summary>
IReadOnlyCollection<byte> RawJobsPresent { get; }
/// <summary>
/// Check if the given flag is present.
/// </summary>
/// <param name="flag">The flag to check for.</param>
/// <returns>A value indicating whether the flag is present.</returns>
bool this[ObjectiveFlags flag] { get; }
/// <summary>
/// Check if the given flag is present.
/// </summary>
/// <param name="flag">The flag to check for.</param>
/// <returns>A value indicating whether the flag is present.</returns>
bool this[ConditionFlags flag] { get; }
/// <summary>
/// Check if the given flag is present.
/// </summary>
/// <param name="flag">The flag to check for.</param>
/// <returns>A value indicating whether the flag is present.</returns>
bool this[DutyFinderSettingsFlags flag] { get; }
/// <summary>
/// Check if the given flag is present.
/// </summary>
/// <param name="flag">The flag to check for.</param>
/// <returns>A value indicating whether the flag is present.</returns>
bool this[LootRuleFlags flag] { get; }
/// <summary>
/// Check if the given flag is present.
/// </summary>
/// <param name="flag">The flag to check for.</param>
/// <returns>A value indicating whether the flag is present.</returns>
bool this[SearchAreaFlags flag] { get; }
}

View file

@ -3,7 +3,7 @@ namespace Dalamud.Game.Gui.PartyFinder.Types;
/// <summary> /// <summary>
/// This class represents additional arguments passed by the game. /// This class represents additional arguments passed by the game.
/// </summary> /// </summary>
public class PartyFinderListingEventArgs internal class PartyFinderListingEventArgs : IPartyFinderListingEventArgs
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PartyFinderListingEventArgs"/> class. /// Initializes a new instance of the <see cref="PartyFinderListingEventArgs"/> class.
@ -14,13 +14,25 @@ public class PartyFinderListingEventArgs
this.BatchNumber = batchNumber; this.BatchNumber = batchNumber;
} }
/// <inheritdoc/>
public int BatchNumber { get; }
/// <inheritdoc/>
public bool Visible { get; set; } = true;
}
/// <summary>
/// A interface representing additional arguments passed by the game.
/// </summary>
public interface IPartyFinderListingEventArgs
{
/// <summary> /// <summary>
/// Gets the batch number. /// Gets the batch number.
/// </summary> /// </summary>
public int BatchNumber { get; } int BatchNumber { get; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the listing is visible. /// Gets or sets a value indicating whether the listing is visible.
/// </summary> /// </summary>
public bool Visible { get; set; } = true; bool Visible { get; set; }
} }

View file

@ -56,8 +56,8 @@ internal class CommandWidget : IDataWindowWidget
? commands.OrderBy(kv => kv.Key).ToArray() ? commands.OrderBy(kv => kv.Key).ToArray()
: commands.OrderByDescending(kv => kv.Key).ToArray(), : commands.OrderByDescending(kv => kv.Key).ToArray(),
1 => sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending 1 => sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending
? commands.OrderBy(kv => kv.Value.LoaderAssemblyName).ToArray() ? commands.OrderBy(kv => commandManager.GetHandlerAssemblyName(kv.Key, kv.Value)).ToArray()
: commands.OrderByDescending(kv => kv.Value.LoaderAssemblyName).ToArray(), : commands.OrderByDescending(kv => commandManager.GetHandlerAssemblyName(kv.Key, kv.Value)).ToArray(),
_ => commands, _ => commands,
}; };
} }
@ -70,7 +70,7 @@ internal class CommandWidget : IDataWindowWidget
ImGui.Text(command.Key); ImGui.Text(command.Key);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.Text(command.Value.LoaderAssemblyName); ImGui.Text(commandManager.GetHandlerAssemblyName(command.Key, command.Value));
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.TextWrapped(command.Value.HelpMessage); ImGui.TextWrapped(command.Value.HelpMessage);

View file

@ -10,9 +10,9 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
/// </summary> /// </summary>
internal class DtrBarWidget : IDataWindowWidget internal class DtrBarWidget : IDataWindowWidget
{ {
private DtrBarEntry? dtrTest1; private IDtrBarEntry? dtrTest1;
private DtrBarEntry? dtrTest2; private IDtrBarEntry? dtrTest2;
private DtrBarEntry? dtrTest3; private IDtrBarEntry? dtrTest3;
/// <inheritdoc/> /// <inheritdoc/>
public string[]? CommandShortcuts { get; init; } = { "dtr", "dtrbar" }; public string[]? CommandShortcuts { get; init; } = { "dtr", "dtrbar" };
@ -51,7 +51,7 @@ internal class DtrBarWidget : IDataWindowWidget
} }
} }
private void DrawDtrTestEntry(ref DtrBarEntry? entry, string title) private void DrawDtrTestEntry(ref IDtrBarEntry? entry, string title)
{ {
var dtrBar = Service<DtrBar>.Get(); var dtrBar = Service<DtrBar>.Get();

View file

@ -2597,7 +2597,7 @@ internal class PluginInstallerWindow : Window, IDisposable
var commands = commandManager.Commands var commands = commandManager.Commands
.Where(cInfo => .Where(cInfo =>
cInfo.Value is { ShowInHelp: true } && cInfo.Value is { ShowInHelp: true } &&
cInfo.Value.LoaderAssemblyName == plugin.Manifest.InternalName) commandManager.GetHandlerAssemblyName(cInfo.Key, cInfo.Value) == plugin.Manifest.InternalName)
.ToArray(); .ToArray();
if (commands.Any()) if (commands.Any())

View file

@ -123,7 +123,7 @@ internal class ContextMenuAgingStep : IAgingStep
this.targetCharacter = null; this.targetCharacter = null;
} }
private void OnMenuOpened(MenuOpenedArgs args) private void OnMenuOpened(IMenuOpenedArgs args)
{ {
this.LogMenuOpened(args); this.LogMenuOpened(args);
@ -139,11 +139,11 @@ internal class ContextMenuAgingStep : IAgingStep
PrefixColor = 56, PrefixColor = 56,
Priority = -1, Priority = -1,
IsSubmenu = true, IsSubmenu = true,
OnClicked = (MenuItemClickedArgs a) => OnClicked = (IMenuItemClickedArgs a) =>
{ {
SeString name; SeString name;
uint count; uint count;
var targetItem = (a.Target as MenuTargetInventory).TargetItem; var targetItem = (a.Target as MenuTargetInventory)!.TargetItem;
if (targetItem is { } item) if (targetItem is { } item)
{ {
name = (this.itemSheet.GetRow(item.ItemId)?.Name.ToDalamudString() ?? $"Unknown ({item.ItemId})") + (item.IsHq ? $" {SeIconChar.HighQuality.ToIconString()}" : string.Empty); name = (this.itemSheet.GetRow(item.ItemId)?.Name.ToDalamudString() ?? $"Unknown ({item.ItemId})") + (item.IsHq ? $" {SeIconChar.HighQuality.ToIconString()}" : string.Empty);
@ -186,7 +186,7 @@ internal class ContextMenuAgingStep : IAgingStep
} }
} }
private void LogMenuOpened(MenuOpenedArgs args) private void LogMenuOpened(IMenuOpenedArgs args)
{ {
Log.Verbose($"Got {args.MenuType} context menu with addon 0x{args.AddonPtr:X8} ({args.AddonName}) and agent 0x{args.AgentPtr:X8}"); Log.Verbose($"Got {args.MenuType} context menu with addon 0x{args.AddonPtr:X8} ({args.AddonName}) and agent 0x{args.AgentPtr:X8}");
if (args.Target is MenuTargetDefault targetDefault) if (args.Target is MenuTargetDefault targetDefault)

View file

@ -51,7 +51,7 @@ internal class PartyFinderAgingStep : IAgingStep
} }
} }
private void PartyFinderOnReceiveListing(PartyFinderListing listing, PartyFinderListingEventArgs args) private void PartyFinderOnReceiveListing(IPartyFinderListing listing, IPartyFinderListingEventArgs args)
{ {
this.hasPassed = true; this.hasPassed = true;
} }

View file

@ -147,7 +147,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
return; return;
var scale = ImGui.GetIO().FontGlobalScale; var scale = ImGui.GetIO().FontGlobalScale;
var entries = this.titleScreenMenu.Entries; var entries = this.titleScreenMenu.PluginEntries;
var hovered = ImGui.IsWindowHovered( var hovered = ImGui.IsWindowHovered(
ImGuiHoveredFlags.RootAndChildWindows | ImGuiHoveredFlags.RootAndChildWindows |
@ -283,7 +283,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
} }
private bool DrawEntry( private bool DrawEntry(
TitleScreenMenuEntry entry, bool inhibitFadeout, bool showText, bool isFirst, bool overrideAlpha, bool interactable) ITitleScreenMenuEntry entry, bool inhibitFadeout, bool showText, bool isFirst, bool overrideAlpha, bool interactable)
{ {
using var fontScopeDispose = this.myFontHandle.Value.Push(); using var fontScopeDispose = this.myFontHandle.Value.Push();

View file

@ -36,7 +36,7 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu
internal event Action? EntryListChange; internal event Action? EntryListChange;
/// <inheritdoc/> /// <inheritdoc/>
public IReadOnlyList<TitleScreenMenuEntry> Entries public IReadOnlyList<IReadOnlyTitleScreenMenuEntry> Entries
{ {
get get
{ {
@ -50,8 +50,32 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu
} }
} }
/// <inheritdoc/> /// <summary>
public TitleScreenMenuEntry AddEntry(string text, IDalamudTextureWrap texture, Action onTriggered) /// Gets the list of entries in the title screen menu.
/// </summary>
public IReadOnlyList<ITitleScreenMenuEntry> PluginEntries
{
get
{
lock (this.entries)
{
if (!this.entries.Any())
return Array.Empty<TitleScreenMenuEntry>();
return this.entriesView ??= this.entries.OrderByDescending(x => x.IsInternal).ToArray();
}
}
}
/// <summary>
/// Adds a new entry to the title screen menu.
/// </summary>
/// <param name="text">The text to show.</param>
/// <param name="texture">The texture to show.</param>
/// <param name="onTriggered">The action to execute when the option is selected.</param>
/// <returns>A <see cref="TitleScreenMenu"/> object that can be used to manage the entry.</returns>
/// <exception cref="ArgumentException">Thrown when the texture provided does not match the required resolution(64x64).</exception>
public ITitleScreenMenuEntry AddPluginEntry(string text, IDalamudTextureWrap texture, Action onTriggered)
{ {
if (texture.Height != TextureSize || texture.Width != TextureSize) if (texture.Height != TextureSize || texture.Width != TextureSize)
{ {
@ -78,7 +102,27 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu
} }
/// <inheritdoc/> /// <inheritdoc/>
public TitleScreenMenuEntry AddEntry(ulong priority, string text, IDalamudTextureWrap texture, Action onTriggered) public IReadOnlyTitleScreenMenuEntry AddEntry(string text, IDalamudTextureWrap texture, Action onTriggered)
{
return this.AddPluginEntry(text, texture, onTriggered);
}
/// <inheritdoc/>
public IReadOnlyTitleScreenMenuEntry AddEntry(ulong priority, string text, IDalamudTextureWrap texture, Action onTriggered)
{
return this.AddPluginEntry(priority, text, texture, onTriggered);
}
/// <summary>
/// Adds a new entry to the title screen menu.
/// </summary>
/// <param name="priority">Priority of the entry.</param>
/// <param name="text">The text to show.</param>
/// <param name="texture">The texture to show.</param>
/// <param name="onTriggered">The action to execute when the option is selected.</param>
/// <returns>A <see cref="TitleScreenMenu"/> object that can be used to manage the entry.</returns>
/// <exception cref="ArgumentException">Thrown when the texture provided does not match the required resolution(64x64).</exception>
public ITitleScreenMenuEntry AddPluginEntry(ulong priority, string text, IDalamudTextureWrap texture, Action onTriggered)
{ {
if (texture.Height != TextureSize || texture.Width != TextureSize) if (texture.Height != TextureSize || texture.Width != TextureSize)
{ {
@ -101,11 +145,11 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu
} }
/// <inheritdoc/> /// <inheritdoc/>
public void RemoveEntry(TitleScreenMenuEntry entry) public void RemoveEntry(IReadOnlyTitleScreenMenuEntry entry)
{ {
lock (this.entries) lock (this.entries)
{ {
this.entries.Remove(entry); this.entries.RemoveAll(pluginEntry => pluginEntry == entry);
this.entriesView = null; this.entriesView = null;
} }
@ -196,10 +240,10 @@ internal class TitleScreenMenuPluginScoped : IInternalDisposableService, ITitleS
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly TitleScreenMenu titleScreenMenuService = Service<TitleScreenMenu>.Get(); private readonly TitleScreenMenu titleScreenMenuService = Service<TitleScreenMenu>.Get();
private readonly List<TitleScreenMenuEntry> pluginEntries = new(); private readonly List<IReadOnlyTitleScreenMenuEntry> pluginEntries = new();
/// <inheritdoc/> /// <inheritdoc/>
public IReadOnlyList<TitleScreenMenuEntry>? Entries => this.titleScreenMenuService.Entries; public IReadOnlyList<IReadOnlyTitleScreenMenuEntry>? Entries => this.titleScreenMenuService.Entries;
/// <inheritdoc/> /// <inheritdoc/>
void IInternalDisposableService.DisposeService() void IInternalDisposableService.DisposeService()
@ -211,25 +255,25 @@ internal class TitleScreenMenuPluginScoped : IInternalDisposableService, ITitleS
} }
/// <inheritdoc/> /// <inheritdoc/>
public TitleScreenMenuEntry AddEntry(string text, IDalamudTextureWrap texture, Action onTriggered) public IReadOnlyTitleScreenMenuEntry AddEntry(string text, IDalamudTextureWrap texture, Action onTriggered)
{ {
var entry = this.titleScreenMenuService.AddEntry(text, texture, onTriggered); var entry = this.titleScreenMenuService.AddPluginEntry(text, texture, onTriggered);
this.pluginEntries.Add(entry); this.pluginEntries.Add(entry);
return entry; return entry;
} }
/// <inheritdoc/> /// <inheritdoc/>
public TitleScreenMenuEntry AddEntry(ulong priority, string text, IDalamudTextureWrap texture, Action onTriggered) public IReadOnlyTitleScreenMenuEntry AddEntry(ulong priority, string text, IDalamudTextureWrap texture, Action onTriggered)
{ {
var entry = this.titleScreenMenuService.AddEntry(priority, text, texture, onTriggered); var entry = this.titleScreenMenuService.AddPluginEntry(priority, text, texture, onTriggered);
this.pluginEntries.Add(entry); this.pluginEntries.Add(entry);
return entry; return entry;
} }
/// <inheritdoc/> /// <inheritdoc/>
public void RemoveEntry(TitleScreenMenuEntry entry) public void RemoveEntry(IReadOnlyTitleScreenMenuEntry entry)
{ {
this.pluginEntries.Remove(entry); this.pluginEntries.Remove(entry);
this.titleScreenMenuService.RemoveEntry(entry); this.titleScreenMenuService.RemoveEntry(entry);

View file

@ -11,7 +11,7 @@ namespace Dalamud.Interface;
/// <summary> /// <summary>
/// Class representing an entry in the title screen menu. /// Class representing an entry in the title screen menu.
/// </summary> /// </summary>
public class TitleScreenMenuEntry : IComparable<TitleScreenMenuEntry> public class TitleScreenMenuEntry : ITitleScreenMenuEntry
{ {
private readonly Action onTriggered; private readonly Action onTriggered;
@ -40,40 +40,26 @@ public class TitleScreenMenuEntry : IComparable<TitleScreenMenuEntry>
this.ShowConditionKeys = (showConditionKeys ?? Array.Empty<VirtualKey>()).ToImmutableSortedSet(); this.ShowConditionKeys = (showConditionKeys ?? Array.Empty<VirtualKey>()).ToImmutableSortedSet();
} }
/// <summary> /// <inheritdoc/>
/// Gets the priority of this entry.
/// </summary>
public ulong Priority { get; init; } public ulong Priority { get; init; }
/// <summary> /// <inheritdoc/>
/// Gets or sets the name of this entry.
/// </summary>
public string Name { get; set; } public string Name { get; set; }
/// <summary> /// <inheritdoc/>
/// Gets or sets the texture of this entry.
/// </summary>
public IDalamudTextureWrap Texture { get; set; } public IDalamudTextureWrap Texture { get; set; }
/// <summary> /// <inheritdoc/>
/// Gets or sets a value indicating whether or not this entry is internal. public bool IsInternal { get; set; }
/// </summary>
internal bool IsInternal { get; set; }
/// <summary> /// <inheritdoc/>
/// Gets the calling assembly of this entry. public Assembly? CallingAssembly { get; init; }
/// </summary>
internal Assembly? CallingAssembly { get; init; }
/// <summary> /// <inheritdoc/>
/// Gets the internal ID of this entry. public Guid Id { get; init; } = Guid.NewGuid();
/// </summary>
internal Guid Id { get; init; } = Guid.NewGuid();
/// <summary> /// <inheritdoc/>
/// Gets the keys that have to be pressed to show the menu. public IReadOnlySet<VirtualKey> ShowConditionKeys { get; init; }
/// </summary>
internal IReadOnlySet<VirtualKey> ShowConditionKeys { get; init; }
/// <inheritdoc/> /// <inheritdoc/>
public int CompareTo(TitleScreenMenuEntry? other) public int CompareTo(TitleScreenMenuEntry? other)
@ -105,14 +91,72 @@ public class TitleScreenMenuEntry : IComparable<TitleScreenMenuEntry>
/// Determines the displaying condition of this menu entry is met. /// Determines the displaying condition of this menu entry is met.
/// </summary> /// </summary>
/// <returns>True if met.</returns> /// <returns>True if met.</returns>
internal bool IsShowConditionSatisfied() => public bool IsShowConditionSatisfied() =>
this.ShowConditionKeys.All(x => Service<KeyState>.GetNullable()?[x] is true); this.ShowConditionKeys.All(x => Service<KeyState>.GetNullable()?[x] is true);
/// <summary> /// <summary>
/// Trigger the action associated with this entry. /// Trigger the action associated with this entry.
/// </summary> /// </summary>
internal void Trigger() public void Trigger()
{ {
this.onTriggered(); this.onTriggered();
} }
} }
/// <summary>
/// A interface representing an entry in the title screen menu.
/// </summary>
public interface ITitleScreenMenuEntry : IReadOnlyTitleScreenMenuEntry, IComparable<TitleScreenMenuEntry>
{
/// <summary>
/// Gets or sets a value indicating whether or not this entry is internal.
/// </summary>
bool IsInternal { get; set; }
/// <summary>
/// Gets the calling assembly of this entry.
/// </summary>
Assembly? CallingAssembly { get; init; }
/// <summary>
/// Gets the internal ID of this entry.
/// </summary>
Guid Id { get; init; }
/// <summary>
/// Gets the keys that have to be pressed to show the menu.
/// </summary>
IReadOnlySet<VirtualKey> ShowConditionKeys { get; init; }
/// <summary>
/// Determines the displaying condition of this menu entry is met.
/// </summary>
/// <returns>True if met.</returns>
bool IsShowConditionSatisfied();
/// <summary>
/// Trigger the action associated with this entry.
/// </summary>
void Trigger();
}
/// <summary>
/// A interface representing a read only entry in the title screen menu.
/// </summary>
public interface IReadOnlyTitleScreenMenuEntry
{
/// <summary>
/// Gets the priority of this entry.
/// </summary>
ulong Priority { get; }
/// <summary>
/// Gets the name of this entry.
/// </summary>
string Name { get; }
/// <summary>
/// Gets the texture of this entry.
/// </summary>
IDalamudTextureWrap Texture { get; }
}

View file

@ -71,10 +71,11 @@ internal static class PluginValidator
problems.Add(new NoMainUiProblem()); problems.Add(new NoMainUiProblem());
var cmdManager = Service<CommandManager>.Get(); var cmdManager = Service<CommandManager>.Get();
foreach (var cmd in cmdManager.Commands.Where(x => x.Value.LoaderAssemblyName == plugin.InternalName && x.Value.ShowInHelp))
foreach (var cmd in cmdManager.GetHandlersByAssemblyName(plugin.InternalName).Where(c => c.Key.Item2.ShowInHelp))
{ {
if (string.IsNullOrEmpty(cmd.Value.HelpMessage)) if (string.IsNullOrEmpty(cmd.Key.Item2.HelpMessage))
problems.Add(new CommandWithoutHelpTextProblem(cmd.Key)); problems.Add(new CommandWithoutHelpTextProblem(cmd.Value));
} }
if (plugin.Manifest.Tags == null || plugin.Manifest.Tags.Count == 0) if (plugin.Manifest.Tags == null || plugin.Manifest.Tags.Count == 0)

View file

@ -7,7 +7,7 @@ namespace Dalamud.Plugin.Services;
/// <summary> /// <summary>
/// This collection represents the list of available Aetherytes in the Teleport window. /// This collection represents the list of available Aetherytes in the Teleport window.
/// </summary> /// </summary>
public interface IAetheryteList : IReadOnlyCollection<AetheryteEntry> public interface IAetheryteList : IReadOnlyCollection<IAetheryteEntry>
{ {
/// <summary> /// <summary>
/// Gets the amount of Aetherytes the local player has unlocked. /// Gets the amount of Aetherytes the local player has unlocked.
@ -19,5 +19,5 @@ public interface IAetheryteList : IReadOnlyCollection<AetheryteEntry>
/// </summary> /// </summary>
/// <param name="index">Index.</param> /// <param name="index">Index.</param>
/// <returns>A <see cref="AetheryteEntry"/> at the specified index.</returns> /// <returns>A <see cref="AetheryteEntry"/> at the specified index.</returns>
public AetheryteEntry? this[int index] { get; } public IAetheryteEntry? this[int index] { get; }
} }

View file

@ -8,7 +8,7 @@ namespace Dalamud.Plugin.Services;
/// This collection represents the buddies present in your squadron or trust party. /// This collection represents the buddies present in your squadron or trust party.
/// It does not include the local player. /// It does not include the local player.
/// </summary> /// </summary>
public interface IBuddyList : IReadOnlyCollection<BuddyMember> public interface IBuddyList : IReadOnlyCollection<IBuddyMember>
{ {
/// <summary> /// <summary>
/// Gets the amount of battle buddies the local player has. /// Gets the amount of battle buddies the local player has.
@ -18,19 +18,19 @@ public interface IBuddyList : IReadOnlyCollection<BuddyMember>
/// <summary> /// <summary>
/// Gets the active companion buddy. /// Gets the active companion buddy.
/// </summary> /// </summary>
public BuddyMember? CompanionBuddy { get; } public IBuddyMember? CompanionBuddy { get; }
/// <summary> /// <summary>
/// Gets the active pet buddy. /// Gets the active pet buddy.
/// </summary> /// </summary>
public BuddyMember? PetBuddy { get; } public IBuddyMember? PetBuddy { get; }
/// <summary> /// <summary>
/// Gets a battle buddy at the specified spawn index. /// Gets a battle buddy at the specified spawn index.
/// </summary> /// </summary>
/// <param name="index">Spawn index.</param> /// <param name="index">Spawn index.</param>
/// <returns>A <see cref="BuddyMember"/> at the specified spawn index.</returns> /// <returns>A <see cref="BuddyMember"/> at the specified spawn index.</returns>
public BuddyMember? this[int index] { get; } public IBuddyMember? this[int index] { get; }
/// <summary> /// <summary>
/// Gets the address of the companion buddy. /// Gets the address of the companion buddy.
@ -56,5 +56,5 @@ public interface IBuddyList : IReadOnlyCollection<BuddyMember>
/// </summary> /// </summary>
/// <param name="address">The address of the buddy in memory.</param> /// <param name="address">The address of the buddy in memory.</param>
/// <returns><see cref="BuddyMember"/> object containing the requested data.</returns> /// <returns><see cref="BuddyMember"/> object containing the requested data.</returns>
public BuddyMember? CreateBuddyMemberReference(nint address); public IBuddyMember? CreateBuddyMemberReference(nint address);
} }

View file

@ -56,7 +56,7 @@ public interface IClientState
/// <summary> /// <summary>
/// Gets the local player character, if one is present. /// Gets the local player character, if one is present.
/// </summary> /// </summary>
public PlayerCharacter? LocalPlayer { get; } public IPlayerCharacter? LocalPlayer { get; }
/// <summary> /// <summary>
/// Gets the content ID of the local character. /// Gets the content ID of the local character.

View file

@ -12,7 +12,7 @@ public interface ICommandManager
/// <summary> /// <summary>
/// Gets a read-only list of all registered commands. /// Gets a read-only list of all registered commands.
/// </summary> /// </summary>
public ReadOnlyDictionary<string, CommandInfo> Commands { get; } public ReadOnlyDictionary<string, ICommandInfo> Commands { get; }
/// <summary> /// <summary>
/// Process a command in full. /// Process a command in full.
@ -27,7 +27,7 @@ public interface ICommandManager
/// <param name="command">The command to dispatch.</param> /// <param name="command">The command to dispatch.</param>
/// <param name="argument">The provided arguments.</param> /// <param name="argument">The provided arguments.</param>
/// <param name="info">A <see cref="CommandInfo"/> object describing this command.</param> /// <param name="info">A <see cref="CommandInfo"/> object describing this command.</param>
public void DispatchCommand(string command, string argument, CommandInfo info); public void DispatchCommand(string command, string argument, ICommandInfo info);
/// <summary> /// <summary>
/// Add a command handler, which you can use to add your own custom commands to the in-game chat. /// Add a command handler, which you can use to add your own custom commands to the in-game chat.
@ -35,7 +35,7 @@ public interface ICommandManager
/// <param name="command">The command to register.</param> /// <param name="command">The command to register.</param>
/// <param name="info">A <see cref="CommandInfo"/> object describing the command.</param> /// <param name="info">A <see cref="CommandInfo"/> object describing the command.</param>
/// <returns>If adding was successful.</returns> /// <returns>If adding was successful.</returns>
public bool AddHandler(string command, CommandInfo info); public bool AddHandler(string command, ICommandInfo info);
/// <summary> /// <summary>
/// Remove a command from the command handlers. /// Remove a command from the command handlers.

View file

@ -11,7 +11,7 @@ public interface IContextMenu
/// A delegate type used for the <see cref="OnMenuOpened"/> event. /// A delegate type used for the <see cref="OnMenuOpened"/> event.
/// </summary> /// </summary>
/// <param name="args">Information about the currently opening menu.</param> /// <param name="args">Information about the currently opening menu.</param>
public delegate void OnMenuOpenedDelegate(MenuOpenedArgs args); public delegate void OnMenuOpenedDelegate(IMenuOpenedArgs args);
/// <summary> /// <summary>
/// Event that gets fired whenever any context menu is opened. /// Event that gets fired whenever any context menu is opened.
@ -25,7 +25,7 @@ public interface IContextMenu
/// <param name="menuType">The type of context menu to add the item to.</param> /// <param name="menuType">The type of context menu to add the item to.</param>
/// <param name="item">The item to add.</param> /// <param name="item">The item to add.</param>
/// <remarks>Used to add a context menu entry to <em>all</em> context menus.</remarks> /// <remarks>Used to add a context menu entry to <em>all</em> context menus.</remarks>
void AddMenuItem(ContextMenuType menuType, MenuItem item); void AddMenuItem(ContextMenuType menuType, IMenuItem item);
/// <summary> /// <summary>
/// Removes a menu item from a context menu. /// Removes a menu item from a context menu.
@ -34,5 +34,5 @@ public interface IContextMenu
/// <param name="item">The item to add.</param> /// <param name="item">The item to add.</param>
/// <remarks>Used to remove a context menu entry from <em>all</em> context menus.</remarks> /// <remarks>Used to remove a context menu entry from <em>all</em> context menus.</remarks>
/// <returns><see langword="true"/> if the item was removed, <see langword="false"/> if it was not found.</returns> /// <returns><see langword="true"/> if the item was removed, <see langword="false"/> if it was not found.</returns>
bool RemoveMenuItem(ContextMenuType menuType, MenuItem item); bool RemoveMenuItem(ContextMenuType menuType, IMenuItem item);
} }

View file

@ -24,8 +24,7 @@ public interface IDtrBar
/// <param name="text">The text the entry shows.</param> /// <param name="text">The text the entry shows.</param>
/// <returns>The entry object used to update, hide and remove the entry.</returns> /// <returns>The entry object used to update, hide and remove the entry.</returns>
/// <exception cref="ArgumentException">Thrown when an entry with the specified title exists.</exception> /// <exception cref="ArgumentException">Thrown when an entry with the specified title exists.</exception>
[Api10ToDo("Return IDtrBarEntry instead of DtrBarEntry")] public IDtrBarEntry Get(string title, SeString? text = null);
public DtrBarEntry Get(string title, SeString? text = null);
/// <summary> /// <summary>
/// Removes a DTR bar entry from the system. /// Removes a DTR bar entry from the system.

View file

@ -7,7 +7,7 @@ namespace Dalamud.Plugin.Services;
/// <summary> /// <summary>
/// This collection represents the currently available Fate events. /// This collection represents the currently available Fate events.
/// </summary> /// </summary>
public interface IFateTable : IReadOnlyCollection<Fate> public interface IFateTable : IReadOnlyCollection<IFate>
{ {
/// <summary> /// <summary>
/// Gets the address of the Fate table. /// Gets the address of the Fate table.
@ -18,13 +18,20 @@ public interface IFateTable : IReadOnlyCollection<Fate>
/// Gets the amount of currently active Fates. /// Gets the amount of currently active Fates.
/// </summary> /// </summary>
public int Length { get; } public int Length { get; }
/// <summary>
/// Gets a value indicating whether this Fate is still valid in memory.
/// </summary>
/// <param name="fate">The fate to check.</param>
/// <returns>True or false.</returns>
public bool IsValid(IFate fate);
/// <summary> /// <summary>
/// Get an actor at the specified spawn index. /// Get an actor at the specified spawn index.
/// </summary> /// </summary>
/// <param name="index">Spawn index.</param> /// <param name="index">Spawn index.</param>
/// <returns>A <see cref="Fate"/> at the specified spawn index.</returns> /// <returns>A <see cref="Fate"/> at the specified spawn index.</returns>
public Fate? this[int index] { get; } public IFate? this[int index] { get; }
/// <summary> /// <summary>
/// Gets the address of the Fate at the specified index of the fate table. /// Gets the address of the Fate at the specified index of the fate table.
@ -38,5 +45,5 @@ public interface IFateTable : IReadOnlyCollection<Fate>
/// </summary> /// </summary>
/// <param name="offset">The offset of the actor in memory.</param> /// <param name="offset">The offset of the actor in memory.</param>
/// <returns><see cref="Fate"/> object containing requested data.</returns> /// <returns><see cref="Fate"/> object containing requested data.</returns>
public Fate? CreateFateReference(nint offset); public IFate? CreateFateReference(nint offset);
} }

View file

@ -7,7 +7,7 @@ namespace Dalamud.Plugin.Services;
/// <summary> /// <summary>
/// This collection represents the currently spawned FFXIV game objects. /// This collection represents the currently spawned FFXIV game objects.
/// </summary> /// </summary>
public interface IObjectTable : IEnumerable<GameObject> public interface IObjectTable : IEnumerable<IGameObject>
{ {
/// <summary> /// <summary>
/// Gets the address of the object table. /// Gets the address of the object table.
@ -24,14 +24,14 @@ public interface IObjectTable : IEnumerable<GameObject>
/// </summary> /// </summary>
/// <param name="index">Spawn index.</param> /// <param name="index">Spawn index.</param>
/// <returns>An <see cref="GameObject"/> at the specified spawn index.</returns> /// <returns>An <see cref="GameObject"/> at the specified spawn index.</returns>
public GameObject? this[int index] { get; } public IGameObject? this[int index] { get; }
/// <summary> /// <summary>
/// Search for a game object by their Object ID. /// Search for a game object by their Object ID.
/// </summary> /// </summary>
/// <param name="gameObjectId">Object ID to find.</param> /// <param name="gameObjectId">Object ID to find.</param>
/// <returns>A game object or null.</returns> /// <returns>A game object or null.</returns>
public GameObject? SearchById(ulong gameObjectId); public IGameObject? SearchById(ulong gameObjectId);
/// <summary> /// <summary>
/// Gets the address of the game object at the specified index of the object table. /// Gets the address of the game object at the specified index of the object table.
@ -45,5 +45,5 @@ public interface IObjectTable : IEnumerable<GameObject>
/// </summary> /// </summary>
/// <param name="address">The address of the object in memory.</param> /// <param name="address">The address of the object in memory.</param>
/// <returns><see cref="GameObject"/> object or inheritor containing the requested data.</returns> /// <returns><see cref="GameObject"/> object or inheritor containing the requested data.</returns>
public GameObject? CreateObjectReference(nint address); public IGameObject? CreateObjectReference(nint address);
} }

View file

@ -13,7 +13,7 @@ public interface IPartyFinderGui
/// </summary> /// </summary>
/// <param name="listing">The listings received.</param> /// <param name="listing">The listings received.</param>
/// <param name="args">Additional arguments passed by the game.</param> /// <param name="args">Additional arguments passed by the game.</param>
public delegate void PartyFinderListingEventDelegate(PartyFinderListing listing, PartyFinderListingEventArgs args); public delegate void PartyFinderListingEventDelegate(IPartyFinderListing listing, IPartyFinderListingEventArgs args);
/// <summary> /// <summary>
/// Event fired each time the game receives an individual Party Finder listing. /// Event fired each time the game receives an individual Party Finder listing.

View file

@ -7,7 +7,7 @@ namespace Dalamud.Plugin.Services;
/// <summary> /// <summary>
/// This collection represents the actors present in your party or alliance. /// This collection represents the actors present in your party or alliance.
/// </summary> /// </summary>
public interface IPartyList : IReadOnlyCollection<PartyMember> public interface IPartyList : IReadOnlyCollection<IPartyMember>
{ {
/// <summary> /// <summary>
/// Gets the amount of party members the local player has. /// Gets the amount of party members the local player has.
@ -49,7 +49,7 @@ public interface IPartyList : IReadOnlyCollection<PartyMember>
/// </summary> /// </summary>
/// <param name="index">Spawn index.</param> /// <param name="index">Spawn index.</param>
/// <returns>A <see cref="PartyMember"/> at the specified spawn index.</returns> /// <returns>A <see cref="PartyMember"/> at the specified spawn index.</returns>
public PartyMember? this[int index] { get; } public IPartyMember? this[int index] { get; }
/// <summary> /// <summary>
/// Gets the address of the party member at the specified index of the party list. /// Gets the address of the party member at the specified index of the party list.
@ -63,7 +63,7 @@ public interface IPartyList : IReadOnlyCollection<PartyMember>
/// </summary> /// </summary>
/// <param name="address">The address of the party member in memory.</param> /// <param name="address">The address of the party member in memory.</param>
/// <returns>The party member object containing the requested data.</returns> /// <returns>The party member object containing the requested data.</returns>
public PartyMember? CreatePartyMemberReference(nint address); public IPartyMember? CreatePartyMemberReference(nint address);
/// <summary> /// <summary>
/// Gets the address of the alliance member at the specified index of the alliance list. /// Gets the address of the alliance member at the specified index of the alliance list.
@ -77,5 +77,5 @@ public interface IPartyList : IReadOnlyCollection<PartyMember>
/// </summary> /// </summary>
/// <param name="address">The address of the alliance member in memory.</param> /// <param name="address">The address of the alliance member in memory.</param>
/// <returns>The party member object containing the requested data.</returns> /// <returns>The party member object containing the requested data.</returns>
public PartyMember? CreateAllianceMemberReference(nint address); public IPartyMember? CreateAllianceMemberReference(nint address);
} }

View file

@ -11,41 +11,41 @@ public interface ITargetManager
/// Gets or sets the current target. /// Gets or sets the current target.
/// Set to null to clear the target. /// Set to null to clear the target.
/// </summary> /// </summary>
public GameObject? Target { get; set; } public IGameObject? Target { get; set; }
/// <summary> /// <summary>
/// Gets or sets the mouseover target. /// Gets or sets the mouseover target.
/// Set to null to clear the target. /// Set to null to clear the target.
/// </summary> /// </summary>
public GameObject? MouseOverTarget { get; set; } public IGameObject? MouseOverTarget { get; set; }
/// <summary> /// <summary>
/// Gets or sets the focus target. /// Gets or sets the focus target.
/// Set to null to clear the target. /// Set to null to clear the target.
/// </summary> /// </summary>
public GameObject? FocusTarget { get; set; } public IGameObject? FocusTarget { get; set; }
/// <summary> /// <summary>
/// Gets or sets the previous target. /// Gets or sets the previous target.
/// Set to null to clear the target. /// Set to null to clear the target.
/// </summary> /// </summary>
public GameObject? PreviousTarget { get; set; } public IGameObject? PreviousTarget { get; set; }
/// <summary> /// <summary>
/// Gets or sets the soft target. /// Gets or sets the soft target.
/// Set to null to clear the target. /// Set to null to clear the target.
/// </summary> /// </summary>
public GameObject? SoftTarget { get; set; } public IGameObject? SoftTarget { get; set; }
/// <summary> /// <summary>
/// Gets or sets the gpose target. /// Gets or sets the gpose target.
/// Set to null to clear the target. /// Set to null to clear the target.
/// </summary> /// </summary>
public GameObject? GPoseTarget { get; set; } public IGameObject? GPoseTarget { get; set; }
/// <summary> /// <summary>
/// Gets or sets the mouseover nameplate target. /// Gets or sets the mouseover nameplate target.
/// Set to null to clear the target. /// Set to null to clear the target.
/// </summary> /// </summary>
public GameObject? MouseOverNameplateTarget { get; set; } public IGameObject? MouseOverNameplateTarget { get; set; }
} }

View file

@ -11,9 +11,9 @@ namespace Dalamud.Plugin.Services;
public interface ITitleScreenMenu public interface ITitleScreenMenu
{ {
/// <summary> /// <summary>
/// Gets the list of entries in the title screen menu. /// Gets the list of read only entries in the title screen menu.
/// </summary> /// </summary>
public IReadOnlyList<TitleScreenMenuEntry> Entries { get; } public IReadOnlyList<IReadOnlyTitleScreenMenuEntry> Entries { get; }
/// <summary> /// <summary>
/// Adds a new entry to the title screen menu. /// Adds a new entry to the title screen menu.
@ -21,9 +21,9 @@ public interface ITitleScreenMenu
/// <param name="text">The text to show.</param> /// <param name="text">The text to show.</param>
/// <param name="texture">The texture to show.</param> /// <param name="texture">The texture to show.</param>
/// <param name="onTriggered">The action to execute when the option is selected.</param> /// <param name="onTriggered">The action to execute when the option is selected.</param>
/// <returns>A <see cref="TitleScreenMenu"/> object that can be used to manage the entry.</returns> /// <returns>A <see cref="IReadOnlyTitleScreenMenuEntry"/> object that can be reference the entry.</returns>
/// <exception cref="ArgumentException">Thrown when the texture provided does not match the required resolution(64x64).</exception> /// <exception cref="ArgumentException">Thrown when the texture provided does not match the required resolution(64x64).</exception>
public TitleScreenMenuEntry AddEntry(string text, IDalamudTextureWrap texture, Action onTriggered); public IReadOnlyTitleScreenMenuEntry AddEntry(string text, IDalamudTextureWrap texture, Action onTriggered);
/// <summary> /// <summary>
/// Adds a new entry to the title screen menu. /// Adds a new entry to the title screen menu.
@ -32,13 +32,13 @@ public interface ITitleScreenMenu
/// <param name="text">The text to show.</param> /// <param name="text">The text to show.</param>
/// <param name="texture">The texture to show.</param> /// <param name="texture">The texture to show.</param>
/// <param name="onTriggered">The action to execute when the option is selected.</param> /// <param name="onTriggered">The action to execute when the option is selected.</param>
/// <returns>A <see cref="TitleScreenMenu"/> object that can be used to manage the entry.</returns> /// <returns>A <see cref="IReadOnlyTitleScreenMenuEntry"/> object that can be used to reference the entry.</returns>
/// <exception cref="ArgumentException">Thrown when the texture provided does not match the required resolution(64x64).</exception> /// <exception cref="ArgumentException">Thrown when the texture provided does not match the required resolution(64x64).</exception>
public TitleScreenMenuEntry AddEntry(ulong priority, string text, IDalamudTextureWrap texture, Action onTriggered); public IReadOnlyTitleScreenMenuEntry AddEntry(ulong priority, string text, IDalamudTextureWrap texture, Action onTriggered);
/// <summary> /// <summary>
/// Remove an entry from the title screen menu. /// Remove an entry from the title screen menu.
/// </summary> /// </summary>
/// <param name="entry">The entry to remove.</param> /// <param name="entry">The entry to remove.</param>
public void RemoveEntry(TitleScreenMenuEntry entry); public void RemoveEntry(IReadOnlyTitleScreenMenuEntry entry);
} }

View file

@ -142,7 +142,7 @@ public static class MapUtil
/// <param name="go">The GameObject to get the position for.</param> /// <param name="go">The GameObject to get the position for.</param>
/// <param name="correctZOffset">Whether to "correct" a Z offset to sane values for maps that don't have one.</param> /// <param name="correctZOffset">Whether to "correct" a Z offset to sane values for maps that don't have one.</param>
/// <returns>A Vector3 that represents the X (east/west), Y (north/south), and Z (height) position of this object.</returns> /// <returns>A Vector3 that represents the X (east/west), Y (north/south), and Z (height) position of this object.</returns>
public static unsafe Vector3 GetMapCoordinates(this GameObject go, bool correctZOffset = false) public static unsafe Vector3 GetMapCoordinates(this IGameObject go, bool correctZOffset = false)
{ {
var agentMap = AgentMap.Instance(); var agentMap = AgentMap.Instance();

View file

@ -270,7 +270,7 @@ public static class Util
/// </summary> /// </summary>
/// <param name="go">The GameObject to show.</param> /// <param name="go">The GameObject to show.</param>
/// <param name="autoExpand">Whether or not the struct should start as expanded.</param> /// <param name="autoExpand">Whether or not the struct should start as expanded.</param>
public static unsafe void ShowGameObjectStruct(GameObject go, bool autoExpand = true) public static unsafe void ShowGameObjectStruct(IGameObject go, bool autoExpand = true)
{ {
switch (go) switch (go)
{ {
@ -280,8 +280,8 @@ public static class Util
case Character chara: case Character chara:
ShowStruct(chara.Struct, autoExpand); ShowStruct(chara.Struct, autoExpand);
break; break;
default: case GameObject gameObject:
ShowStruct(go.Struct, autoExpand); ShowStruct(gameObject.Struct, autoExpand);
break; break;
} }
} }
@ -739,6 +739,40 @@ public static class Util
// ignore // ignore
} }
} }
/// <summary>
/// Print formatted IGameObject Information to ImGui.
/// </summary>
/// <param name="actor">IGameObject to Display.</param>
/// <param name="tag">Display Tag.</param>
/// <param name="resolveGameData">If the IGameObjects data should be resolved.</param>
internal static void PrintGameObject(IGameObject actor, string tag, bool resolveGameData)
{
var actorString =
$"{actor.Address.ToInt64():X}:{actor.GameObjectId:X}[{tag}] - {actor.ObjectKind} - {actor.Name} - X{actor.Position.X} Y{actor.Position.Y} Z{actor.Position.Z} D{actor.YalmDistanceX} R{actor.Rotation} - Target: {actor.TargetObjectId:X}\n";
if (actor is Npc npc)
actorString += $" DataId: {npc.DataId} NameId:{npc.NameId}\n";
if (actor is ICharacter chara)
{
actorString +=
$" Level: {chara.Level} ClassJob: {(resolveGameData ? chara.ClassJob.GameData?.Name : chara.ClassJob.Id.ToString())} CHP: {chara.CurrentHp} MHP: {chara.MaxHp} CMP: {chara.CurrentMp} MMP: {chara.MaxMp}\n Customize: {BitConverter.ToString(chara.Customize).Replace("-", " ")} StatusFlags: {chara.StatusFlags}\n";
}
if (actor is IPlayerCharacter pc)
{
actorString +=
$" HomeWorld: {(resolveGameData ? pc.HomeWorld.GameData?.Name : pc.HomeWorld.Id.ToString())} CurrentWorld: {(resolveGameData ? pc.CurrentWorld.GameData?.Name : pc.CurrentWorld.Id.ToString())} FC: {pc.CompanyTag}\n";
}
ImGui.TextUnformatted(actorString);
ImGui.SameLine();
if (ImGui.Button($"C##{actor.Address.ToInt64()}"))
{
ImGui.SetClipboardText(actor.Address.ToInt64().ToString("X"));
}
}
/// <summary> /// <summary>
/// Print formatted GameObject Information to ImGui. /// Print formatted GameObject Information to ImGui.