Merge pull request #468 from daemitus/actors

This commit is contained in:
goaaats 2021-08-11 01:12:43 +02:00 committed by GitHub
commit 34cb2203a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 1718 additions and 1205 deletions

View file

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

View file

@ -1,149 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Game.ClientState.Actors.Types.NonPlayer;
using JetBrains.Annotations;
using Serilog;
namespace Dalamud.Game.ClientState.Actors
{
/// <summary>
/// This collection represents the currently spawned FFXIV actors.
/// </summary>
public sealed partial class ActorTable
{
private const int ActorTableLength = 424;
private readonly Dalamud dalamud;
private readonly ClientStateAddressResolver address;
/// <summary>
/// Initializes a new instance of the <see cref="ActorTable"/> class.
/// </summary>
/// <param name="dalamud">The <see cref="dalamud"/> instance.</param>
/// <param name="addressResolver">Client state address resolver.</param>
internal ActorTable(Dalamud dalamud, ClientStateAddressResolver addressResolver)
{
this.dalamud = dalamud;
this.address = addressResolver;
Log.Verbose($"Actor table address 0x{this.address.ActorTable.ToInt64():X}");
}
/// <summary>
/// Gets the amount of currently spawned actors.
/// </summary>
public int Length
{
get
{
var count = 0;
for (var i = 0; i < ActorTableLength; i++)
{
var ptr = this.GetActorAddress(i);
if (ptr != IntPtr.Zero)
{
count++;
}
}
return count;
}
}
/// <summary>
/// Get an actor at the specified spawn index.
/// </summary>
/// <param name="index">Spawn index.</param>
/// <returns>An <see cref="Actor"/> at the specified spawn index.</returns>
[CanBeNull]
public Actor this[int index]
{
get
{
var address = this.GetActorAddress(index);
return this.CreateActorReference(address);
}
}
/// <summary>
/// Gets the address of the actor at the specified index of the actor table.
/// </summary>
/// <param name="index">The index of the actor.</param>
/// <returns>The memory address of the actor.</returns>
public unsafe IntPtr GetActorAddress(int index)
{
if (index >= ActorTableLength)
return IntPtr.Zero;
return *(IntPtr*)(this.address.ActorTable + (8 * index));
}
/// <summary>
/// Create a reference to a FFXIV actor.
/// </summary>
/// <param name="address">The address of the actor in memory.</param>
/// <returns><see cref="Actor"/> object or inheritor containing requested data.</returns>
[CanBeNull]
public unsafe Actor CreateActorReference(IntPtr address)
{
if (this.dalamud.ClientState.LocalContentId == 0)
return null;
if (address == IntPtr.Zero)
return null;
var objKind = *(ObjectKind*)(address + ActorOffsets.ObjectKind);
return objKind switch
{
ObjectKind.Player => new PlayerCharacter(address, this.dalamud),
ObjectKind.BattleNpc => new BattleNpc(address, this.dalamud),
ObjectKind.EventObj => new EventObj(address, this.dalamud),
ObjectKind.Companion => new Npc(address, this.dalamud),
_ => new Actor(address, this.dalamud),
};
}
}
/// <summary>
/// This collection represents the currently spawned FFXIV actors.
/// </summary>
public sealed partial class ActorTable : IReadOnlyCollection<Actor>, ICollection
{
/// <inheritdoc/>
int IReadOnlyCollection<Actor>.Count => this.Length;
/// <inheritdoc/>
int ICollection.Count => this.Length;
/// <inheritdoc/>
bool ICollection.IsSynchronized => false;
/// <inheritdoc/>
object ICollection.SyncRoot => this;
/// <inheritdoc/>
public IEnumerator<Actor> GetEnumerator()
{
for (var i = 0; i < ActorTableLength; i++)
{
yield return this[i];
}
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
/// <inheritdoc/>
void ICollection.CopyTo(Array array, int index)
{
for (var i = 0; i < this.Length; i++)
{
array.SetValue(this[i], index);
index++;
}
}
}
}

View file

@ -1,125 +0,0 @@
using System;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Actors.Types;
using JetBrains.Annotations;
namespace Dalamud.Game.ClientState.Actors
{
/// <summary>
/// Get and set various kinds of targets for the player.
/// </summary>
public sealed class Targets
{
private readonly Dalamud dalamud;
private readonly ClientStateAddressResolver address;
/// <summary>
/// Initializes a new instance of the <see cref="Targets"/> class.
/// </summary>
/// <param name="dalamud">The Dalamud instance.</param>
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
internal Targets(Dalamud dalamud, ClientStateAddressResolver addressResolver)
{
this.dalamud = dalamud;
this.address = addressResolver;
}
/// <summary>
/// Gets the current target.
/// </summary>
[CanBeNull]
public Actor CurrentTarget => this.GetActorByOffset(TargetOffsets.CurrentTarget);
/// <summary>
/// Gets the mouseover target.
/// </summary>
[CanBeNull]
public Actor MouseOverTarget => this.GetActorByOffset(TargetOffsets.MouseOverTarget);
/// <summary>
/// Gets the focus target.
/// </summary>
[CanBeNull]
public Actor FocusTarget => this.GetActorByOffset(TargetOffsets.FocusTarget);
/// <summary>
/// Gets the previous target.
/// </summary>
[CanBeNull]
public Actor PreviousTarget => this.GetActorByOffset(TargetOffsets.PreviousTarget);
/// <summary>
/// Gets the soft target.
/// </summary>
[CanBeNull]
public Actor SoftTarget => this.GetActorByOffset(TargetOffsets.SoftTarget);
/// <summary>
/// Sets the current target.
/// </summary>
/// <param name="actor">Actor to target.</param>
public void SetCurrentTarget(Actor actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero, TargetOffsets.CurrentTarget);
/// <summary>
/// Sets the current target.
/// </summary>
/// <param name="actorAddress">Actor (address) to target.</param>
public void SetCurrentTarget(IntPtr actorAddress) => this.SetTarget(actorAddress, TargetOffsets.CurrentTarget);
/// <summary>
/// Sets the focus target.
/// </summary>
/// <param name="actor">Actor to focus.</param>
public void SetFocusTarget(Actor actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero, TargetOffsets.FocusTarget);
/// <summary>
/// Sets the focus target.
/// </summary>
/// <param name="actorAddress">Actor (address) to focus.</param>
public void SetFocusTarget(IntPtr actorAddress) => this.SetTarget(actorAddress, TargetOffsets.FocusTarget);
/// <summary>
/// Clears the current target.
/// </summary>
public void ClearCurrentTarget() => this.SetCurrentTarget(IntPtr.Zero);
/// <summary>
/// Clears the focus target.
/// </summary>
public void ClearFocusTarget() => this.SetFocusTarget(IntPtr.Zero);
private void SetTarget(IntPtr actorAddress, int offset)
{
if (this.address.TargetManager == IntPtr.Zero)
return;
Marshal.WriteIntPtr(this.address.TargetManager, offset, actorAddress);
}
[CanBeNull]
private Actor GetActorByOffset(int offset)
{
if (this.address.TargetManager == IntPtr.Zero)
return null;
var actorAddress = Marshal.ReadIntPtr(this.address.TargetManager + offset);
if (actorAddress == IntPtr.Zero)
return null;
return this.dalamud.ClientState.Actors.CreateActorReference(actorAddress);
}
}
/// <summary>
/// Memory offsets for the <see cref="Targets"/> type.
/// </summary>
public static class TargetOffsets
{
public const int CurrentTarget = 0x80;
public const int SoftTarget = 0x88;
public const int MouseOverTarget = 0xD0;
public const int FocusTarget = 0xF8;
public const int PreviousTarget = 0x110;
}
}

View file

@ -1,165 +0,0 @@
using System;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Structs;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Memory;
namespace Dalamud.Game.ClientState.Actors.Types
{
/// <summary>
/// This class represents a basic actor (GameObject) in FFXIV.
/// </summary>
public unsafe partial class Actor : IEquatable<Actor>
{
/// <summary>
/// Initializes a new instance of the <see cref="Actor"/> class.
/// </summary>
/// <param name="address">The address of this actor in memory.</param>
/// <param name="dalamud">A dalamud reference needed to access game data in Resolvers.</param>
internal Actor(IntPtr address, Dalamud dalamud)
{
this.Dalamud = dalamud;
this.Address = address;
}
/// <summary>
/// Gets the address of the actor in memory.
/// </summary>
public IntPtr Address { get; }
/// <summary>
/// Gets Dalamud itself.
/// </summary>
private protected Dalamud Dalamud { get; }
/// <summary>
/// This allows you to <c>if (actor) {...}</c> to check for validity.
/// </summary>
/// <param name="actor">The actor to check.</param>
/// <returns>True or false.</returns>
public static implicit operator bool(Actor actor) => IsValid(actor);
public static bool operator ==(Actor actor1, Actor actor2)
{
if (actor1 is null || actor2 is null)
return Equals(actor1, actor2);
return actor1.Equals(actor2);
}
public static bool operator !=(Actor actor1, Actor actor2) => !(actor1 == actor2);
/// <summary>
/// Gets a value indicating whether this actor is still valid in memory.
/// </summary>
/// <param name="actor">The actor to check.</param>
/// <returns>True or false.</returns>
public static bool IsValid(Actor actor)
{
if (actor == null)
return false;
if (actor.Dalamud.ClientState.LocalContentId == 0)
return false;
return true;
}
/// <summary>
/// Gets a value indicating whether this actor is still valid in memory.
/// </summary>
/// <returns>True or false.</returns>
public bool IsValid() => IsValid(this);
/// <inheritdoc/>
bool IEquatable<Actor>.Equals(Actor other) => this.ActorId == other?.ActorId;
/// <inheritdoc/>
public override bool Equals(object obj) => ((IEquatable<Actor>)this).Equals(obj as Actor);
/// <inheritdoc/>
public override int GetHashCode() => this.ActorId.GetHashCode();
}
/// <summary>
/// This class represents a basic actor (GameObject) in FFXIV.
/// </summary>
public unsafe partial class Actor
{
/// <summary>
/// Gets the displayname of this <see cref="Actor" />.
/// </summary>
public SeString Name => MemoryHelper.ReadSeString(this.Address + ActorOffsets.Name, 32);
/// <summary>
/// Gets the actor ID of this <see cref="Actor" />.
/// </summary>
public uint ActorId => *(uint*)(this.Address + ActorOffsets.ActorId);
/// <summary>
/// Gets the data ID for linking to other respective game data.
/// </summary>
public uint DataId => *(uint*)(this.Address + ActorOffsets.DataId);
/// <summary>
/// Gets the ID of this GameObject's owner.
/// </summary>
public uint OwnerId => *(uint*)(this.Address + ActorOffsets.OwnerId);
/// <summary>
/// Gets the entity kind of this <see cref="Actor" />.
/// See <see cref="ObjectKind">the ObjectKind enum</see> for possible values.
/// </summary>
public ObjectKind ObjectKind => *(ObjectKind*)(this.Address + ActorOffsets.ObjectKind);
/// <summary>
/// Gets the sub kind of this Actor.
/// </summary>
public byte SubKind => *(byte*)(this.Address + ActorOffsets.SubKind);
/// <summary>
/// Gets a value indicating whether the actor is friendly.
/// </summary>
public bool IsFriendly => *(int*)(this.Address + ActorOffsets.IsFriendly) > 0;
/// <summary>
/// Gets the X distance from the local player in yalms.
/// </summary>
public byte YalmDistanceX => *(byte*)(this.Address + ActorOffsets.YalmDistanceFromObjectX);
/// <summary>
/// Gets the target status.
/// </summary>
/// <remarks>
/// This is some kind of enum. It may be <see cref="StatusEffect"/>.
/// </remarks>
public byte TargetStatus => *(byte*)(this.Address + ActorOffsets.TargetStatus);
/// <summary>
/// Gets the Y distance from the local player in yalms.
/// </summary>
public byte YalmDistanceY => *(byte*)(this.Address + ActorOffsets.YalmDistanceFromObjectY);
/// <summary>
/// Gets the position of this <see cref="Actor" />.
/// </summary>
public Position3 Position => *(Position3*)(this.Address + ActorOffsets.Position);
/// <summary>
/// Gets the rotation of this <see cref="Actor" />.
/// This ranges from -pi to pi radians.
/// </summary>
public float Rotation => *(float*)(this.Address + ActorOffsets.Rotation);
/// <summary>
/// Gets the hitbox radius of this <see cref="Actor" />.
/// </summary>
public float HitboxRadius => *(float*)(this.Address + ActorOffsets.HitboxRadius);
/// <summary>
/// Gets the current target of the Actor.
/// </summary>
public virtual uint TargetActorID => 0;
}
}

View file

@ -1,61 +0,0 @@
using System.Diagnostics.CodeAnalysis;
namespace Dalamud.Game.ClientState.Actors.Types
{
/// <summary>
/// Memory offsets for the <see cref="Actor"/> type and all that inherit from it.
/// </summary>
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the offset usage instead.")]
public static class ActorOffsets
{
// GameObject(Actor)
// GameObject :: Character
// GameObject :: Character :: BattleChara
// GameObject :: Character :: Companion
public const int Name = 0x30;
public const int ActorId = 0x74;
public const int DataId = 0x80;
public const int OwnerId = 0x84;
public const int ObjectKind = 0x8C;
public const int SubKind = 0x8D;
public const int IsFriendly = 0x8E;
public const int YalmDistanceFromObjectX = 0x90;
public const int TargetStatus = 0x91;
public const int YalmDistanceFromObjectY = 0x92;
public const int Position = 0xA0;
public const int Rotation = 0xB0;
public const int HitboxRadius = 0xC0;
// End GameObject 0x1A0
public const int CurrentHp = 0x1C4;
public const int MaxHp = 0x1C8;
public const int CurrentMp = 0x1CC;
public const int MaxMp = 0x1D0;
public const int CurrentGp = 0x1D4;
public const int MaxGp = 0x1D6;
public const int CurrentCp = 0x1D8;
public const int MaxCp = 0x1DA;
public const int ClassJob = 0x1E2;
public const int Level = 0x1E3;
public const int PlayerCharacterTargetActorId = 0x230;
public const int Customize = 0x1898;
public const int CompanyTag = 0x18B2;
public const int BattleNpcTargetActorId = 0x18D8;
public const int NameId = 0x1940;
public const int CurrentWorld = 0x195C;
public const int HomeWorld = 0x195E;
public const int StatusFlags = 0x19A0;
// End Character 0x19B0
// End Companion 0x19C0
public const int UIStatusEffects = 0x19F8;
public const int IsCasting = 0x1B80;
public const int IsCasting2 = 0x1B82;
public const int CurrentCastSpellActionId = 0x1B84;
public const int CurrentCastTargetActorId = 0x1B90;
public const int CurrentCastTime = 0x1BB4;
public const int TotalCastTime = 0x1BB8;
// End BattleChara 0x2C00
}
}

View file

@ -1,124 +0,0 @@
using System;
using Dalamud.Game.ClientState.Resolvers;
using Dalamud.Game.ClientState.Structs;
using Dalamud.Memory;
namespace Dalamud.Game.ClientState.Actors.Types
{
/// <summary>
/// This class represents the base for non-static entities.
/// </summary>
public unsafe class Chara : Actor
{
/// <summary>
/// Initializes a new instance of the <see cref="Chara"/> class.
/// This represents a non-static entity.
/// </summary>
/// <param name="address">The address of this actor in memory.</param>
/// <param name="dalamud">A dalamud reference needed to access game data in Resolvers.</param>
internal Chara(IntPtr address, Dalamud dalamud)
: base(address, dalamud)
{
}
/// <summary>
/// Gets the current HP of this Chara.
/// </summary>
public uint CurrentHp => *(uint*)(this.Address + ActorOffsets.CurrentHp);
/// <summary>
/// Gets the maximum HP of this Chara.
/// </summary>
public uint MaxHp => *(uint*)(this.Address + ActorOffsets.MaxHp);
/// <summary>
/// Gets the current MP of this Chara.
/// </summary>
public uint CurrentMp => *(uint*)(this.Address + ActorOffsets.CurrentMp);
/// <summary>
/// Gets the maximum MP of this Chara.
/// </summary>
public uint MaxMp => *(uint*)(this.Address + ActorOffsets.MaxMp);
/// <summary>
/// Gets the current GP of this Chara.
/// </summary>
public uint CurrentGp => *(uint*)(this.Address + ActorOffsets.CurrentGp);
/// <summary>
/// Gets the maximum GP of this Chara.
/// </summary>
public uint MaxGp => *(uint*)(this.Address + ActorOffsets.MaxGp);
/// <summary>
/// Gets the current CP of this Chara.
/// </summary>
public uint CurrentCp => *(uint*)(this.Address + ActorOffsets.CurrentCp);
/// <summary>
/// Gets the maximum CP of this Chara.
/// </summary>
public uint MaxCp => *(uint*)(this.Address + ActorOffsets.MaxCp);
/// <summary>
/// Gets the ClassJob of this Chara.
/// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.ClassJob> ClassJob => new(*(byte*)(this.Address + ActorOffsets.ClassJob), this.Dalamud);
/// <summary>
/// Gets the level of this Chara.
/// </summary>
public byte Level => *(byte*)(this.Address + ActorOffsets.Level);
/// <summary>
/// Gets a byte array describing the visual appearance of this Chara.
/// Indexed by <see cref="CustomizeIndex"/>.
/// </summary>
public byte[] Customize => MemoryHelper.Read<byte>(this.Address + ActorOffsets.Customize, 28);
/// <summary>
/// Gets the status flags.
/// </summary>
public StatusFlags StatusFlags => *(StatusFlags*)(this.Address + ActorOffsets.StatusFlags);
/// <summary>
/// Gets the current status effects.
/// </summary>
/// <remarks>
/// This copies every time it is invoked, so make sure to only grab it once.
/// </remarks>
public StatusEffect[] StatusEffects => MemoryHelper.Read<StatusEffect>(this.Address + ActorOffsets.UIStatusEffects, 30, true);
/// <summary>
/// Gets a value indicating whether the actor is currently casting.
/// </summary>
public bool IsCasting => *(int*)(this.Address + ActorOffsets.IsCasting) > 0;
/// <summary>
/// Gets a value indicating whether the actor is currently casting (again?).
/// </summary>
public bool IsCasting2 => *(int*)(this.Address + ActorOffsets.IsCasting2) > 0;
/// <summary>
/// Gets the spell action ID currently being cast by the actor.
/// </summary>
public uint CurrentCastSpellActionId => *(uint*)(this.Address + ActorOffsets.CurrentCastSpellActionId);
/// <summary>
/// Gets the actor ID of the target currently being cast at by the actor.
/// </summary>
public uint CurrentCastTargetActorId => *(uint*)(this.Address + ActorOffsets.CurrentCastTargetActorId);
/// <summary>
/// Gets the current casting time of the spell being cast by the actor.
/// </summary>
public float CurrentCastTime => *(float*)(this.Address + ActorOffsets.CurrentCastTime);
/// <summary>
/// Gets the total casting time of the spell being cast by the actor.
/// </summary>
public float TotalCastTime => *(float*)(this.Address + ActorOffsets.TotalCastTime);
}
}

View file

@ -1,26 +0,0 @@
using System;
namespace Dalamud.Game.ClientState.Actors.Types.NonPlayer
{
/// <summary>
/// This class represents an EventObj.
/// </summary>
public unsafe class EventObj : Actor
{
/// <summary>
/// Initializes a new instance of the <see cref="EventObj"/> class.
/// Set up a new EventObj with the provided memory representation.
/// </summary>
/// <param name="address">The address of this actor in memory.</param>
/// <param name="dalamud">A dalamud reference needed to access game data in Resolvers.</param>
internal EventObj(IntPtr address, Dalamud dalamud)
: base(address, dalamud)
{
}
/// <summary>
/// Gets the event object ID of the linking to their respective game data.
/// </summary>
public uint EventObjectId => *(uint*)(this.Address + ActorOffsets.DataId);
}
}

View file

@ -1,54 +0,0 @@
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Memory;
namespace Dalamud.Game.ClientState.Actors.Types
{
/// <summary>
/// This class represents a party member.
/// </summary>
public class PartyMember
{
/// <summary>
/// Initializes a new instance of the <see cref="PartyMember"/> class.
/// </summary>
/// <param name="table">The ActorTable instance.</param>
/// <param name="rawData">The interop data struct.</param>
public PartyMember(ActorTable table, Structs.PartyMember rawData)
{
this.CharacterName = MemoryHelper.ReadSeString(rawData.namePtr);
this.Unknown = rawData.unknown;
this.Actor = null;
for (var i = 0; i < table.Length; i++)
{
if (table[i] != null && table[i].ActorId == rawData.actorId)
{
this.Actor = table[i];
break;
}
}
this.ObjectKind = rawData.objectKind;
}
/// <summary>
/// Gets the name of the character.
/// </summary>
public SeString CharacterName { get; }
/// <summary>
/// Gets something unknown.
/// </summary>
public long Unknown { get; }
/// <summary>
/// Gets the actor object that corresponds to this party member.
/// </summary>
public Actor Actor { get; }
/// <summary>
/// Gets the kind or type of actor.
/// </summary>
public ObjectKind ObjectKind { get; }
}
}

View file

@ -0,0 +1,188 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using JetBrains.Annotations;
using Serilog;
namespace Dalamud.Game.ClientState.Buddy
{
/// <summary>
/// This collection represents the buddies present in your squadron or trust party.
/// It does not include the local player.
/// </summary>
public sealed partial class BuddyList
{
private const uint InvalidObjectID = 0xE0000000;
private readonly Dalamud dalamud;
private readonly ClientStateAddressResolver address;
/// <summary>
/// Initializes a new instance of the <see cref="BuddyList"/> class.
/// </summary>
/// <param name="dalamud">The <see cref="dalamud"/> instance.</param>
/// <param name="addressResolver">Client state address resolver.</param>
internal BuddyList(Dalamud dalamud, ClientStateAddressResolver addressResolver)
{
this.dalamud = dalamud;
this.address = addressResolver;
Log.Verbose($"Buddy list address 0x{this.address.BuddyList.ToInt64():X}");
}
/// <summary>
/// Gets the amount of battle buddies the local player has.
/// </summary>
public int Length
{
get
{
var i = 0;
for (; i < 3; i++)
{
var addr = this.GetBattleBuddyMemberAddress(i);
var member = this.CreateBuddyMemberReference(addr);
if (member == null)
break;
}
return i;
}
}
/// <summary>
/// Gets a value indicating whether the local player's companion is present.
/// </summary>
public bool CompanionBuddyPresent => this.CompanionBuddy != null;
/// <summary>
/// Gets a value indicating whether the local player's pet is present.
/// </summary>
public bool PetBuddyPresent => this.PetBuddy != null;
/// <summary>
/// Gets the active companion buddy.
/// </summary>
[CanBeNull]
public BuddyMember CompanionBuddy
{
get
{
var addr = this.GetCompanionBuddyMemberAddress();
return this.CreateBuddyMemberReference(addr);
}
}
/// <summary>
/// Gets the active pet buddy.
/// </summary>
[CanBeNull]
public BuddyMember PetBuddy
{
get
{
var addr = this.GetPetBuddyMemberAddress();
return this.CreateBuddyMemberReference(addr);
}
}
/// <summary>
/// Gets the address of the buddy list.
/// </summary>
internal IntPtr BuddyListAddress => this.address.BuddyList;
private static int BuddyMemberSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy>();
private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy* BuddyListStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy*)this.BuddyListAddress;
/// <summary>
/// Gets a battle buddy at the specified spawn index.
/// </summary>
/// <param name="index">Spawn index.</param>
/// <returns>A <see cref="BuddyMember"/> at the specified spawn index.</returns>
[CanBeNull]
public BuddyMember this[int index]
{
get
{
var address = this.GetBattleBuddyMemberAddress(index);
return this.CreateBuddyMemberReference(address);
}
}
/// <summary>
/// Gets the address of the companion buddy.
/// </summary>
/// <returns>The memory address of the companion buddy.</returns>
public unsafe IntPtr GetCompanionBuddyMemberAddress()
{
return (IntPtr)(&this.BuddyListStruct->Companion);
}
/// <summary>
/// Gets the address of the pet buddy.
/// </summary>
/// <returns>The memory address of the pet buddy.</returns>
public unsafe IntPtr GetPetBuddyMemberAddress()
{
return (IntPtr)(&this.BuddyListStruct->Pet);
}
/// <summary>
/// Gets the address of the battle buddy at the specified index of the buddy list.
/// </summary>
/// <param name="index">The index of the battle buddy.</param>
/// <returns>The memory address of the battle buddy.</returns>
public unsafe IntPtr GetBattleBuddyMemberAddress(int index)
{
if (index < 0 || index >= 3)
return IntPtr.Zero;
return (IntPtr)(this.BuddyListStruct->BattleBuddies + (index * BuddyMemberSize));
}
/// <summary>
/// Create a reference to a buddy.
/// </summary>
/// <param name="address">The address of the buddy in memory.</param>
/// <returns><see cref="BuddyMember"/> object containing the requested data.</returns>
[CanBeNull]
public BuddyMember CreateBuddyMemberReference(IntPtr address)
{
if (this.dalamud.ClientState.LocalContentId == 0)
return null;
if (address == IntPtr.Zero)
return null;
var buddy = new BuddyMember(address, this.dalamud);
if (buddy.ObjectId == InvalidObjectID)
return null;
return buddy;
}
}
/// <summary>
/// This collection represents the buddies present in your squadron or trust party.
/// </summary>
public sealed partial class BuddyList : IReadOnlyCollection<BuddyMember>
{
/// <inheritdoc/>
int IReadOnlyCollection<BuddyMember>.Count => this.Length;
/// <inheritdoc/>
public IEnumerator<BuddyMember> GetEnumerator()
{
for (var i = 0; i < this.Length; i++)
{
yield return this[i];
}
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}
}

View file

@ -0,0 +1,78 @@
using System;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.ClientState.Resolvers;
using JetBrains.Annotations;
namespace Dalamud.Game.ClientState.Buddy
{
/// <summary>
/// This class represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
/// </summary>
public unsafe class BuddyMember
{
private Dalamud dalamud;
/// <summary>
/// Initializes a new instance of the <see cref="BuddyMember"/> class.
/// </summary>
/// <param name="address">Buddy address.</param>
/// <param name="dalamud">Dalamud instance.</param>
internal BuddyMember(IntPtr address, Dalamud dalamud)
{
this.dalamud = dalamud;
this.Address = address;
}
/// <summary>
/// Gets the address of the buddy in memory.
/// </summary>
public IntPtr Address { get; }
/// <summary>
/// Gets the object ID of this buddy.
/// </summary>
public uint ObjectId => this.Struct->ObjectID;
/// <summary>
/// Gets the actor associated with this buddy.
/// </summary>
/// <remarks>
/// This iterates the actor table, it should be used with care.
/// </remarks>
[CanBeNull]
public GameObject Actor => this.dalamud.ClientState.Objects.SearchByID(this.ObjectId);
/// <summary>
/// Gets the current health of this buddy.
/// </summary>
public uint CurrentHP => this.Struct->CurrentHealth;
/// <summary>
/// Gets the maximum health of this buddy.
/// </summary>
public uint MaxHP => this.Struct->MaxHealth;
/// <summary>
/// Gets the data ID of this buddy.
/// </summary>
public uint DataID => this.Struct->DataID;
/// <summary>
/// Gets the Mount data related to this buddy. It should only be used with companion buddies.
/// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.Mount> MountData => new(this.DataID, this.dalamud);
/// <summary>
/// Gets the Pet data related to this buddy. It should only be used with pet buddies.
/// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.Pet> PetData => new(this.DataID, this.dalamud);
/// <summary>
/// Gets the Trust data related to this buddy. It should only be used with battle buddies.
/// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.DawnGrowMember> TrustData => new(this.DataID, this.dalamud);
private FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember*)this.Address;
}
}

View file

@ -3,14 +3,15 @@ using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Actors;
using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Game.ClientState.Buddy;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Fates;
using Dalamud.Game.ClientState.GamePad;
using Dalamud.Game.ClientState.JobGauge;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.Internal;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Party;
using Dalamud.Hooking;
using JetBrains.Annotations;
using Serilog;
@ -45,12 +46,14 @@ namespace Dalamud.Game.ClientState
this.ClientLanguage = startInfo.Language;
this.Actors = new ActorTable(dalamud, this.address);
this.Objects = new ObjectTable(dalamud, this.address);
this.Fates = new FateTable(dalamud, this.address);
this.PartyList = new PartyList(dalamud, this.address);
this.BuddyList = new BuddyList(dalamud, this.address);
this.JobGauges = new JobGauges(this.address);
this.KeyState = new KeyState(this.address, scanner.Module.BaseAddress);
@ -102,7 +105,7 @@ namespace Dalamud.Game.ClientState
/// <summary>
/// Gets the table of all present actors.
/// </summary>
public ActorTable Actors { get; }
public ObjectTable Objects { get; }
/// <summary>
/// Gets the table of all present fates.
@ -124,6 +127,11 @@ namespace Dalamud.Game.ClientState
/// </summary>
public PartyList PartyList { get; }
/// <summary>
/// Gets the class facilitating buddy list data access.
/// </summary>
public BuddyList BuddyList { get; }
/// <summary>
/// Gets access to the keypress state of keyboard keys in game.
/// </summary>
@ -153,7 +161,7 @@ namespace Dalamud.Game.ClientState
/// Gets the local player character, if one is present.
/// </summary>
[CanBeNull]
public PlayerCharacter LocalPlayer => this.Actors[0] as PlayerCharacter;
public PlayerCharacter LocalPlayer => this.Objects[0] as PlayerCharacter;
/// <summary>
/// Gets the content ID of the local character.
@ -171,7 +179,6 @@ namespace Dalamud.Game.ClientState
public void Enable()
{
this.GamepadState.Enable();
this.PartyList.Enable();
this.setupTerritoryTypeHook.Enable();
}
@ -180,7 +187,6 @@ namespace Dalamud.Game.ClientState
/// </summary>
public void Dispose()
{
this.PartyList.Dispose();
this.setupTerritoryTypeHook.Dispose();
this.GamepadState.Dispose();

View file

@ -1,7 +1,4 @@
using System;
using System.Runtime.InteropServices;
using Dalamud.Game.Internal;
namespace Dalamud.Game.ClientState
{
@ -15,17 +12,25 @@ namespace Dalamud.Game.ClientState
/// <summary>
/// Gets the address of the actor table.
/// </summary>
public IntPtr ActorTable { get; private set; }
public IntPtr ObjectTable { get; private set; }
/// <summary>
/// Gets the address of the fate table pointer.
/// Gets the address of the buddy list.
/// </summary>
public IntPtr BuddyList { get; private set; }
/// <summary>
/// Gets the address of a pointer to the fate table.
/// </summary>
/// <remarks>
/// This is a static address to a pointer, not the address of the table itself.
/// </remarks>
public IntPtr FateTablePtr { get; private set; }
// public IntPtr ViewportActorTable { get; private set; }
/// <summary>
/// Gets the address of the Group Manager.
/// </summary>
public IntPtr GroupManager { get; private set; }
/// <summary>
/// Gets the address of the local content id.
@ -75,12 +80,16 @@ namespace Dalamud.Game.ClientState
// ViewportActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 85 ED", 0) + 0x148;
// SomeActorTableAccess = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 55 A0 48 8D 8E ?? ?? ?? ??");
this.ActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83");
this.ObjectTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83");
this.BuddyList = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 45 84 E4 75 1A F6 45 12 04");
this.FateTablePtr = sig.GetStaticAddressFromSig("48 8B 15 ?? ?? ?? ?? 48 8B F9 44 0F B7 41 ??");
this.GroupManager = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 80 B8 ?? ?? ?? ?? ?? 76 50");
this.LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 05 ?? ?? ?? ?? 48 39 07");
this.JobGaugeData = sig.GetStaticAddressFromSig("48 8B 0D ?? ?? ?? ?? 48 85 C9 74 43") + 0x10;
this.JobGaugeData = sig.GetStaticAddressFromSig("48 8B 0D ?? ?? ?? ?? 48 85 C9 74 43") + 0x8;
this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F9 66 89 91 ?? ?? ?? ??");
@ -88,11 +97,9 @@ namespace Dalamud.Game.ClientState
// so GetStaticAddressFromSig() can't be used. lea rcx, ds:1DB9F74h[rax*4]
this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4;
// PartyListUpdate = sig.ScanText("E8 ?? ?? ?? ?? 49 8B D7 4C 8D 86 ?? ?? ?? ??");
this.ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30");
this.TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB", 3);
this.TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB");
this.GamepadPoll = sig.ScanText("40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B");
}

View file

@ -1,4 +1,4 @@
namespace Dalamud.Game.ClientState.Actors.Types.NonPlayer
namespace Dalamud.Game.ClientState.Objects.Enums
{
/// <summary>
/// An Enum describing possible BattleNpc kinds.

View file

@ -1,4 +1,4 @@
namespace Dalamud.Game.ClientState.Actors.Types
namespace Dalamud.Game.ClientState.Objects.Enums
{
/// <summary>
/// This enum describes the indices of the Customize array.

View file

@ -1,4 +1,4 @@
namespace Dalamud.Game.ClientState.Actors.Types
namespace Dalamud.Game.ClientState.Objects.Enums
{
/// <summary>
/// Enum describing possible entity kinds.
@ -6,7 +6,7 @@ namespace Dalamud.Game.ClientState.Actors.Types
public enum ObjectKind : byte
{
/// <summary>
/// Invalid actor.
/// Invalid character.
/// </summary>
None = 0x00,

View file

@ -1,6 +1,6 @@
using System;
namespace Dalamud.Game.ClientState.Actors.Types
namespace Dalamud.Game.ClientState.Objects.Enums
{
/// <summary>
/// Enum describing possible status flags.
@ -14,42 +14,42 @@ namespace Dalamud.Game.ClientState.Actors.Types
None = 0,
/// <summary>
/// Hostile actor.
/// Hostile character.
/// </summary>
Hostile = 1,
/// <summary>
/// Actor in combat.
/// Character in combat.
/// </summary>
InCombat = 2,
/// <summary>
/// Actor weapon is out.
/// Character weapon is out.
/// </summary>
WeaponOut = 4,
/// <summary>
/// Actor offhand is out.
/// Character offhand is out.
/// </summary>
OffhandOut = 8,
/// <summary>
/// Actor is a party member.
/// Character is a party member.
/// </summary>
PartyMember = 16,
/// <summary>
/// Actor is a alliance member.
/// Character is a alliance member.
/// </summary>
AllianceMember = 32,
/// <summary>
/// Actor is in friend list.
/// Character is in friend list.
/// </summary>
Friend = 64,
/// <summary>
/// Actor is casting.
/// Character is casting.
/// </summary>
IsCasting = 128,
}

View file

@ -0,0 +1,146 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using JetBrains.Annotations;
using Serilog;
namespace Dalamud.Game.ClientState.Objects
{
/// <summary>
/// This collection represents the currently spawned FFXIV game objects.
/// </summary>
public sealed partial class ObjectTable
{
private const int ObjectTableLength = 424;
private readonly Dalamud dalamud;
private readonly ClientStateAddressResolver address;
/// <summary>
/// Initializes a new instance of the <see cref="ObjectTable"/> class.
/// </summary>
/// <param name="dalamud">The <see cref="dalamud"/> instance.</param>
/// <param name="addressResolver">Client state address resolver.</param>
internal ObjectTable(Dalamud dalamud, ClientStateAddressResolver addressResolver)
{
this.dalamud = dalamud;
this.address = addressResolver;
Log.Verbose($"Object table address 0x{this.address.ObjectTable.ToInt64():X}");
}
/// <summary>
/// Gets the address of the object table.
/// </summary>
public IntPtr Address => this.address.ObjectTable;
/// <summary>
/// Gets the length of the object table.
/// </summary>
public int Length => ObjectTableLength;
/// <summary>
/// Get an object at the specified spawn index.
/// </summary>
/// <param name="index">Spawn index.</param>
/// <returns>An <see cref="GameObject"/> at the specified spawn index.</returns>
[CanBeNull]
public GameObject this[int index]
{
get
{
var address = this.GetObjectAddress(index);
return this.CreateObjectReference(address);
}
}
/// <summary>
/// Search for a game object by their Object ID.
/// </summary>
/// <param name="objectID">Object ID to find.</param>
/// <returns>A game object or null.</returns>
[CanBeNull]
public GameObject SearchByID(uint objectID)
{
foreach (var obj in this)
{
if (obj == null)
continue;
if (obj.ObjectId == objectID)
return obj;
}
return null;
}
/// <summary>
/// Gets the address of the game object at the specified index of the object table.
/// </summary>
/// <param name="index">The index of the object.</param>
/// <returns>The memory address of the object.</returns>
public unsafe IntPtr GetObjectAddress(int index)
{
if (index < 0 || index >= ObjectTableLength)
return IntPtr.Zero;
return *(IntPtr*)(this.address.ObjectTable + (8 * index));
}
/// <summary>
/// Create a reference to an FFXIV game object.
/// </summary>
/// <param name="address">The address of the object in memory.</param>
/// <returns><see cref="GameObject"/> object or inheritor containing the requested data.</returns>
[CanBeNull]
public unsafe GameObject CreateObjectReference(IntPtr address)
{
if (this.dalamud.ClientState.LocalContentId == 0)
return null;
if (address == IntPtr.Zero)
return null;
var obj = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)address;
var objKind = (ObjectKind)obj->ObjectKind;
return objKind switch
{
ObjectKind.Player => new PlayerCharacter(address, this.dalamud),
ObjectKind.BattleNpc => new BattleNpc(address, this.dalamud),
ObjectKind.EventObj => new EventObj(address, this.dalamud),
ObjectKind.Companion => new Npc(address, this.dalamud),
_ => new GameObject(address, this.dalamud),
};
}
}
/// <summary>
/// This collection represents the currently spawned FFXIV game objects.
/// </summary>
public sealed partial class ObjectTable : IReadOnlyCollection<GameObject>
{
/// <inheritdoc/>
int IReadOnlyCollection<GameObject>.Count => this.Length;
/// <inheritdoc/>
public IEnumerator<GameObject> GetEnumerator()
{
for (var i = 0; i < ObjectTableLength; i++)
{
var obj = this[i];
if (obj == null)
continue;
yield return obj;
}
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}
}

View file

@ -1,11 +1,13 @@
using System;
namespace Dalamud.Game.ClientState.Actors.Types.NonPlayer
using Dalamud.Game.ClientState.Objects.Enums;
namespace Dalamud.Game.ClientState.Objects.Types
{
/// <summary>
/// This class represents a battle NPC.
/// </summary>
public unsafe class BattleNpc : Npc
public unsafe class BattleNpc : BattleChara
{
/// <summary>
/// Initializes a new instance of the <see cref="BattleNpc"/> class.
@ -21,11 +23,6 @@ namespace Dalamud.Game.ClientState.Actors.Types.NonPlayer
/// <summary>
/// Gets the BattleNpc <see cref="BattleNpcSubKind" /> of this BattleNpc.
/// </summary>
public BattleNpcSubKind BattleNpcKind => *(BattleNpcSubKind*)(this.Address + ActorOffsets.SubKind);
/// <summary>
/// Gets the target of the Battle NPC.
/// </summary>
public override uint TargetActorID => *(uint*)(this.Address + ActorOffsets.BattleNpcTargetActorId);
public BattleNpcSubKind BattleNpcKind => (BattleNpcSubKind)this.Struct->Character.GameObject.SubKind;
}
}

View file

@ -0,0 +1,23 @@
using System;
using Dalamud.Game.ClientState.Objects.Types;
namespace Dalamud.Game.ClientState.Objects.SubKinds
{
/// <summary>
/// This class represents an EventObj.
/// </summary>
public unsafe class EventObj : GameObject
{
/// <summary>
/// Initializes a new instance of the <see cref="EventObj"/> class.
/// Set up a new EventObj with the provided memory representation.
/// </summary>
/// <param name="address">The address of this event object in memory.</param>
/// <param name="dalamud">A dalamud reference.</param>
internal EventObj(IntPtr address, Dalamud dalamud)
: base(address, dalamud)
{
}
}
}

View file

@ -1,11 +1,13 @@
using System;
namespace Dalamud.Game.ClientState.Actors.Types.NonPlayer
using Dalamud.Game.ClientState.Objects.Types;
namespace Dalamud.Game.ClientState.Objects.SubKinds
{
/// <summary>
/// This class represents a NPC.
/// </summary>
public unsafe class Npc : Chara
public unsafe class Npc : Character
{
/// <summary>
/// Initializes a new instance of the <see cref="Npc"/> class.
@ -18,14 +20,9 @@ namespace Dalamud.Game.ClientState.Actors.Types.NonPlayer
{
}
/// <summary>
/// Gets the data ID of the NPC linking to their assoicated BNpcBase data.
/// </summary>
public uint BaseId => *(uint*)(this.Address + ActorOffsets.DataId);
/// <summary>
/// Gets the name ID of the NPC linking to their respective game data.
/// </summary>
public uint NameId => *(uint*)(this.Address + ActorOffsets.NameId);
public uint NameId => this.Struct->NameID;
}
}

View file

@ -1,15 +1,14 @@
using System;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.ClientState.Resolvers;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Memory;
namespace Dalamud.Game.ClientState.Actors.Types
namespace Dalamud.Game.ClientState.Objects.SubKinds
{
/// <summary>
/// This class represents a player character.
/// </summary>
public unsafe class PlayerCharacter : Chara
public unsafe class PlayerCharacter : BattleChara
{
/// <summary>
/// Initializes a new instance of the <see cref="PlayerCharacter"/> class.
@ -25,21 +24,16 @@ namespace Dalamud.Game.ClientState.Actors.Types
/// <summary>
/// Gets the current <see cref="ExcelResolver{T}">world</see> of the character.
/// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.World> CurrentWorld => new(*(ushort*)(this.Address + ActorOffsets.CurrentWorld), this.Dalamud);
public ExcelResolver<Lumina.Excel.GeneratedSheets.World> CurrentWorld => new(this.Struct->Character.CurrentWorld, this.Dalamud);
/// <summary>
/// Gets the home <see cref="ExcelResolver{T}">world</see> of the character.
/// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.World> HomeWorld => new(*(ushort*)(this.Address + ActorOffsets.HomeWorld), this.Dalamud);
/// <summary>
/// Gets the Free Company tag of this player.
/// </summary>
public SeString CompanyTag => MemoryHelper.ReadSeString(this.Address + ActorOffsets.CompanyTag, 6);
public ExcelResolver<Lumina.Excel.GeneratedSheets.World> HomeWorld => new(this.Struct->Character.HomeWorld, this.Dalamud);
/// <summary>
/// Gets the target actor ID of the PlayerCharacter.
/// </summary>
public override uint TargetActorID => *(uint*)(this.Address + ActorOffsets.PlayerCharacterTargetActorId);
public override uint TargetObjectId => this.Struct->Character.GameObject.TargetObjectID;
}
}

View file

@ -0,0 +1,169 @@
using System;
using Dalamud.Game.ClientState.Objects.Types;
using JetBrains.Annotations;
namespace Dalamud.Game.ClientState.Objects
{
/// <summary>
/// Get and set various kinds of targets for the player.
/// </summary>
public sealed unsafe class Targets
{
private readonly Dalamud dalamud;
private readonly ClientStateAddressResolver address;
/// <summary>
/// Initializes a new instance of the <see cref="Targets"/> class.
/// </summary>
/// <param name="dalamud">The Dalamud instance.</param>
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
internal Targets(Dalamud dalamud, ClientStateAddressResolver addressResolver)
{
this.dalamud = dalamud;
this.address = addressResolver;
}
/// <summary>
/// Gets the address of the target manager.
/// </summary>
public IntPtr Address => this.address.TargetManager;
/// <summary>
/// Gets or sets the current target.
/// </summary>
[CanBeNull]
public GameObject Target
{
get => this.dalamud.ClientState.Objects.CreateObjectReference((IntPtr)Struct->Target);
set => this.SetTarget(value);
}
/// <summary>
/// Gets or sets the mouseover target.
/// </summary>
[CanBeNull]
public GameObject MouseOverTarget
{
get => this.dalamud.ClientState.Objects.CreateObjectReference((IntPtr)Struct->MouseOverTarget);
set => this.SetMouseOverTarget(value);
}
/// <summary>
/// Gets or sets the focus target.
/// </summary>
[CanBeNull]
public GameObject FocusTarget
{
get => this.dalamud.ClientState.Objects.CreateObjectReference((IntPtr)Struct->FocusTarget);
set => this.SetFocusTarget(value);
}
/// <summary>
/// Gets or sets the previous target.
/// </summary>
[CanBeNull]
public GameObject PreviousTarget
{
get => this.dalamud.ClientState.Objects.CreateObjectReference((IntPtr)Struct->PreviousTarget);
set => this.SetPreviousTarget(value);
}
/// <summary>
/// Gets or sets the soft target.
/// </summary>
[CanBeNull]
public GameObject SoftTarget
{
get => this.dalamud.ClientState.Objects.CreateObjectReference((IntPtr)Struct->SoftTarget);
set => this.SetSoftTarget(value);
}
private FFXIVClientStructs.FFXIV.Client.Game.Control.TargetSystem* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Control.TargetSystem*)this.Address;
/// <summary>
/// Sets the current target.
/// </summary>
/// <param name="actor">Actor to target.</param>
public void SetTarget(GameObject actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero);
/// <summary>
/// Sets the mouseover target.
/// </summary>
/// <param name="actor">Actor to target.</param>
public void SetMouseOverTarget(GameObject actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero);
/// <summary>
/// Sets the focus target.
/// </summary>
/// <param name="actor">Actor to target.</param>
public void SetFocusTarget(GameObject actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero);
/// <summary>
/// Sets the previous target.
/// </summary>
/// <param name="actor">Actor to target.</param>
public void SetPreviousTarget(GameObject actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero);
/// <summary>
/// Sets the soft target.
/// </summary>
/// <param name="actor">Actor to target.</param>
public void SetSoftTarget(GameObject actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero);
/// <summary>
/// Sets the current target.
/// </summary>
/// <param name="actorAddress">Actor (address) to target.</param>
public void SetTarget(IntPtr actorAddress) => Struct->Target = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
/// <summary>
/// Sets the mouseover target.
/// </summary>
/// <param name="actorAddress">Actor (address) to target.</param>
public void SetMouseOverTarget(IntPtr actorAddress) => Struct->MouseOverTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
/// <summary>
/// Sets the focus target.
/// </summary>
/// <param name="actorAddress">Actor (address) to target.</param>
public void SetFocusTarget(IntPtr actorAddress) => Struct->FocusTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
/// <summary>
/// Sets the previous target.
/// </summary>
/// <param name="actorAddress">Actor (address) to target.</param>
public void SetPreviousTarget(IntPtr actorAddress) => Struct->PreviousTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
/// <summary>
/// Sets the soft target.
/// </summary>
/// <param name="actorAddress">Actor (address) to target.</param>
public void SetSoftTarget(IntPtr actorAddress) => Struct->SoftTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
/// <summary>
/// Clears the current target.
/// </summary>
public void ClearTarget() => this.SetTarget(IntPtr.Zero);
/// <summary>
/// Clears the mouseover target.
/// </summary>
public void ClearMouseOverTarget() => this.SetMouseOverTarget(IntPtr.Zero);
/// <summary>
/// Clears the focus target.
/// </summary>
public void ClearFocusTarget() => this.SetFocusTarget(IntPtr.Zero);
/// <summary>
/// Clears the previous target.
/// </summary>
public void ClearPreviousTarget() => this.SetPreviousTarget(IntPtr.Zero);
/// <summary>
/// Clears the soft target.
/// </summary>
public void ClearSoftTarget() => this.SetSoftTarget(IntPtr.Zero);
}
}

View file

@ -0,0 +1,68 @@
using System;
using Dalamud.Game.ClientState.Statuses;
namespace Dalamud.Game.ClientState.Objects.Types
{
/// <summary>
/// This class represents the battle characters.
/// </summary>
public unsafe class BattleChara : Character
{
/// <summary>
/// Initializes a new instance of the <see cref="BattleChara"/> class.
/// This represents a battle character.
/// </summary>
/// <param name="address">The address of this character in memory.</param>
/// <param name="dalamud">Dalamud itself.</param>
internal BattleChara(IntPtr address, Dalamud dalamud)
: base(address, dalamud)
{
}
/// <summary>
/// Gets the current status effects.
/// </summary>
public StatusList StatusList => new(&this.Struct->StatusManager, this.Dalamud);
/// <summary>
/// Gets a value indicating whether the chara is currently casting.
/// </summary>
public bool IsCasting => this.Struct->SpellCastInfo.IsCasting > 0;
/// <summary>
/// Gets a value indicating whether the cast is interruptible.
/// </summary>
public bool IsCastInterruptible => this.Struct->SpellCastInfo.Interruptible > 0;
/// <summary>
/// Gets the spell action type of the spell being cast by the actor.
/// </summary>
public byte CastActionType => (byte)this.Struct->SpellCastInfo.ActionType;
/// <summary>
/// Gets the spell action ID of the spell being cast by the actor.
/// </summary>
public uint CastActionId => this.Struct->SpellCastInfo.ActionID;
/// <summary>
/// Gets the object ID of the target currently being cast at by the chara.
/// </summary>
public uint CastTargetObjectId => this.Struct->SpellCastInfo.CastTargetID;
/// <summary>
/// Gets the current casting time of the spell being cast by the chara.
/// </summary>
public float CurrentCastTime => this.Struct->SpellCastInfo.CurrentCastTime;
/// <summary>
/// Gets the total casting time of the spell being cast by the chara.
/// </summary>
public float TotalCastTime => this.Struct->SpellCastInfo.TotalCastTime;
/// <summary>
/// Gets the underlying structure.
/// </summary>
private protected new FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)this.Address;
}
}

View file

@ -0,0 +1,103 @@
using System;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Resolvers;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Memory;
using JetBrains.Annotations;
namespace Dalamud.Game.ClientState.Objects.Types
{
/// <summary>
/// This class represents the base for non-static entities.
/// </summary>
public unsafe class Character : GameObject
{
/// <summary>
/// Initializes a new instance of the <see cref="Character"/> class.
/// This represents a non-static entity.
/// </summary>
/// <param name="address">The address of this character in memory.</param>
/// <param name="dalamud">Dalamud itself.</param>
internal Character(IntPtr address, Dalamud dalamud)
: base(address, dalamud)
{
}
/// <summary>
/// Gets the current HP of this Chara.
/// </summary>
public uint CurrentHp => this.Struct->Health;
/// <summary>
/// Gets the maximum HP of this Chara.
/// </summary>
public uint MaxHp => this.Struct->MaxHealth;
/// <summary>
/// Gets the current MP of this Chara.
/// </summary>
public uint CurrentMp => this.Struct->Mana;
/// <summary>
/// Gets the maximum MP of this Chara.
/// </summary>
public uint MaxMp => this.Struct->MaxMana;
/// <summary>
/// Gets the current GP of this Chara.
/// </summary>
public uint CurrentGp => this.Struct->GatheringPoints;
/// <summary>
/// Gets the maximum GP of this Chara.
/// </summary>
public uint MaxGp => this.Struct->MaxGatheringPoints;
/// <summary>
/// Gets the current CP of this Chara.
/// </summary>
public uint CurrentCp => this.Struct->CraftingPoints;
/// <summary>
/// Gets the maximum CP of this Chara.
/// </summary>
public uint MaxCp => this.Struct->MaxCraftingPoints;
/// <summary>
/// Gets the ClassJob of this Chara.
/// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.ClassJob> ClassJob => new(this.Struct->ClassJob, this.Dalamud);
/// <summary>
/// Gets the level of this Chara.
/// </summary>
public byte Level => this.Struct->Level;
/// <summary>
/// Gets a byte array describing the visual appearance of this Chara.
/// Indexed by <see cref="CustomizeIndex"/>.
/// </summary>
public byte[] Customize => MemoryHelper.Read<byte>((IntPtr)this.Struct->CustomizeData, 28);
/// <summary>
/// Gets the Free Company tag of this chara.
/// </summary>
public SeString CompanyTag => MemoryHelper.ReadSeString((IntPtr)this.Struct->FreeCompanyTag, 6);
/// <summary>
/// Gets the target object ID of the character.
/// </summary>
public override uint TargetObjectId => this.Struct->GameObject.TargetObjectID;
/// <summary>
/// Gets the status flags.
/// </summary>
public StatusFlags StatusFlags => (StatusFlags)this.Struct->StatusFlags;
/// <summary>
/// Gets the underlying structure.
/// </summary>
private protected new FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)this.Address;
}
}

View file

@ -0,0 +1,168 @@
using System;
using System.Numerics;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Memory;
using JetBrains.Annotations;
namespace Dalamud.Game.ClientState.Objects.Types
{
/// <summary>
/// This class represents a GameObject in FFXIV.
/// </summary>
public unsafe partial class GameObject : IEquatable<GameObject>
{
/// <summary>
/// Initializes a new instance of the <see cref="GameObject"/> class.
/// </summary>
/// <param name="address">The address of this game object in memory.</param>
/// <param name="dalamud">Dalamud itself.</param>
internal GameObject(IntPtr address, Dalamud dalamud)
{
this.Address = address;
this.Dalamud = dalamud;
}
/// <summary>
/// Gets the address of the game object in memory.
/// </summary>
public IntPtr Address { get; }
/// <summary>
/// Gets the Dalamud instance.
/// </summary>
private protected Dalamud Dalamud { get; }
/// <summary>
/// This allows you to <c>if (obj) {...}</c> to check for validity.
/// </summary>
/// <param name="gameObject">The actor to check.</param>
/// <returns>True or false.</returns>
public static implicit operator bool(GameObject? gameObject) => IsValid(gameObject);
public static bool operator ==(GameObject? gameObject1, GameObject? gameObject2)
{
// Using == results in a stack overflow.
if (gameObject1 is null || gameObject2 is null)
return Equals(gameObject1, gameObject2);
return gameObject1.Equals(gameObject2);
}
public static bool operator !=(GameObject? actor1, GameObject? actor2) => !(actor1 == actor2);
/// <summary>
/// Gets a value indicating whether this actor is still valid in memory.
/// </summary>
/// <param name="actor">The actor to check.</param>
/// <returns>True or false.</returns>
public static bool IsValid(GameObject? actor)
{
if (actor is null)
return false;
if (actor.Dalamud.ClientState.LocalContentId == 0)
return false;
return true;
}
/// <summary>
/// Gets a value indicating whether this actor is still valid in memory.
/// </summary>
/// <returns>True or false.</returns>
public bool IsValid() => IsValid(this);
/// <inheritdoc/>
bool IEquatable<GameObject>.Equals(GameObject other) => this.ObjectId == other?.ObjectId;
/// <inheritdoc/>
public override bool Equals(object obj) => ((IEquatable<GameObject>)this).Equals(obj as GameObject);
/// <inheritdoc/>
public override int GetHashCode() => this.ObjectId.GetHashCode();
}
/// <summary>
/// This class represents a basic actor (GameObject) in FFXIV.
/// </summary>
public unsafe partial class GameObject
{
/// <summary>
/// Gets the name of this <see cref="GameObject" />.
/// </summary>
public SeString Name => MemoryHelper.ReadSeString((IntPtr)this.Struct->Name, 32);
/// <summary>
/// Gets the object ID of this <see cref="GameObject" />.
/// </summary>
public uint ObjectId => this.Struct->ObjectID;
/// <summary>
/// Gets the data ID for linking to other respective game data.
/// </summary>
public uint DataId => this.Struct->DataID;
/// <summary>
/// Gets the ID of this GameObject's owner.
/// </summary>
public uint OwnerId => this.Struct->OwnerID;
/// <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 => (ObjectKind)this.Struct->ObjectKind;
/// <summary>
/// Gets the sub kind of this Actor.
/// </summary>
public byte SubKind => this.Struct->SubKind;
/// <summary>
/// Gets the X distance from the local player in yalms.
/// </summary>
public byte YalmDistanceX => this.Struct->YalmDistanceFromPlayerX;
/// <summary>
/// Gets the Y distance from the local player in yalms.
/// </summary>
public byte YalmDistanceZ => this.Struct->YalmDistanceFromPlayerZ;
/// <summary>
/// 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);
/// <summary>
/// Gets the rotation of this <see cref="GameObject" />.
/// This ranges from -pi to pi radians.
/// </summary>
public float Rotation => this.Struct->Rotation;
/// <summary>
/// Gets the hitbox radius of this <see cref="GameObject" />.
/// </summary>
public float HitboxRadius => this.Struct->HitboxRadius;
/// <summary>
/// Gets the current target of the game object.
/// </summary>
public virtual uint TargetObjectId => 0;
/// <summary>
/// Gets the target object of the game object.
/// </summary>
/// <remarks>
/// This iterates the actor table, it should be used with care.
/// </remarks>
[CanBeNull]
public virtual GameObject TargetObject => this.Dalamud.ClientState.Objects.SearchByID(this.TargetObjectId);
/// <summary>
/// Gets the underlying structure.
/// </summary>
private protected FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)this.Address;
}
}

View file

@ -0,0 +1,183 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using JetBrains.Annotations;
using Serilog;
namespace Dalamud.Game.ClientState.Party
{
/// <summary>
/// This collection represents the actors present in your party or alliance.
/// </summary>
public sealed unsafe partial class PartyList
{
private const int GroupLength = 8;
private const int AllianceLength = 20;
private readonly Dalamud dalamud;
private readonly ClientStateAddressResolver address;
/// <summary>
/// Initializes a new instance of the <see cref="PartyList"/> class.
/// </summary>
/// <param name="dalamud">The <see cref="dalamud"/> instance.</param>
/// <param name="addressResolver">Client state address resolver.</param>
internal PartyList(Dalamud dalamud, ClientStateAddressResolver addressResolver)
{
this.dalamud = dalamud;
this.address = addressResolver;
Log.Verbose($"Group manager address 0x{this.address.GroupManager.ToInt64():X}");
}
/// <summary>
/// Gets the amount of party members the local player has.
/// </summary>
public int Length => this.GroupManagerStruct->MemberCount;
/// <summary>
/// Gets the index of the party leader.
/// </summary>
public uint PartyLeaderIndex => this.GroupManagerStruct->PartyLeaderIndex;
/// <summary>
/// Gets a value indicating whether this group is an alliance.
/// </summary>
public bool IsAlliance => this.GroupManagerStruct->IsAlliance;
/// <summary>
/// Gets the address of the Group Manager.
/// </summary>
public IntPtr GroupManagerAddress => this.address.GroupManager;
/// <summary>
/// Gets the address of the party list within the group manager.
/// </summary>
public IntPtr GroupListAddress => (IntPtr)GroupManagerStruct->PartyMembers;
/// <summary>
/// Gets the address of the alliance member list within the group manager.
/// </summary>
public IntPtr AllianceListAddress => (IntPtr)this.GroupManagerStruct->AllianceMembers;
private static int PartyMemberSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember>();
private FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager* GroupManagerStruct => (FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager*)this.GroupManagerAddress;
/// <summary>
/// Get a party member at the specified spawn index.
/// </summary>
/// <param name="index">Spawn index.</param>
/// <returns>A <see cref="PartyMember"/> at the specified spawn index.</returns>
[CanBeNull]
public PartyMember this[int index]
{
get
{
// Normally using Length results in a recursion crash, however we know the party size via ptr.
if (index < 0 || index >= this.Length)
return null;
if (this.Length > GroupLength)
{
var addr = this.GetAllianceMemberAddress(index);
return this.CreateAllianceMemberReference(addr);
}
else
{
var addr = this.GetPartyMemberAddress(index);
return this.CreatePartyMemberReference(addr);
}
}
}
/// <summary>
/// Gets the address of the party member at the specified index of the party list.
/// </summary>
/// <param name="index">The index of the party member.</param>
/// <returns>The memory address of the party member.</returns>
public IntPtr GetPartyMemberAddress(int index)
{
if (index < 0 || index >= GroupLength)
return IntPtr.Zero;
return this.GroupListAddress + (index * PartyMemberSize);
}
/// <summary>
/// Create a reference to an FFXIV party member.
/// </summary>
/// <param name="address">The address of the party member in memory.</param>
/// <returns>The party member object containing the requested data.</returns>
[CanBeNull]
public PartyMember CreatePartyMemberReference(IntPtr address)
{
if (this.dalamud.ClientState.LocalContentId == 0)
return null;
if (address == IntPtr.Zero)
return null;
return new PartyMember(address, this.dalamud);
}
/// <summary>
/// Gets the address of the alliance member at the specified index of the alliance list.
/// </summary>
/// <param name="index">The index of the alliance member.</param>
/// <returns>The memory address of the alliance member.</returns>
public IntPtr GetAllianceMemberAddress(int index)
{
if (index < 0 || index >= AllianceLength)
return IntPtr.Zero;
return this.AllianceListAddress + (index * PartyMemberSize);
}
/// <summary>
/// Create a reference to an FFXIV alliance member.
/// </summary>
/// <param name="address">The address of the alliance member in memory.</param>
/// <returns>The party member object containing the requested data.</returns>
[CanBeNull]
public PartyMember CreateAllianceMemberReference(IntPtr address)
{
if (this.dalamud.ClientState.LocalContentId == 0)
return null;
if (address == IntPtr.Zero)
return null;
return new PartyMember(address, this.dalamud);
}
}
/// <summary>
/// This collection represents the party members present in your party or alliance.
/// </summary>
public sealed partial class PartyList : IReadOnlyCollection<PartyMember>
{
/// <inheritdoc/>
int IReadOnlyCollection<PartyMember>.Count => this.Length;
/// <inheritdoc/>
public IEnumerator<PartyMember> GetEnumerator()
{
// Normally using Length results in a recursion crash, however we know the party size via ptr.
for (var i = 0; i < this.Length; i++)
{
var member = this[i];
if (member == null)
break;
yield return member;
}
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}
}

View file

@ -0,0 +1,117 @@
using System;
using System.Numerics;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.ClientState.Resolvers;
using Dalamud.Game.ClientState.Statuses;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Memory;
using JetBrains.Annotations;
namespace Dalamud.Game.ClientState.Party
{
/// <summary>
/// This class represents a party member in the group manager.
/// </summary>
public unsafe class PartyMember
{
private Dalamud dalamud;
/// <summary>
/// Initializes a new instance of the <see cref="PartyMember"/> class.
/// </summary>
/// <param name="address">Address of the party member.</param>
/// <param name="dalamud">Dalamud itself.</param>
internal PartyMember(IntPtr address, Dalamud dalamud)
{
this.Address = address;
this.dalamud = dalamud;
}
/// <summary>
/// Gets the address of this party member in memory.
/// </summary>
public IntPtr Address { get; }
/// <summary>
/// Gets a list of buffs or debuffs applied to this party member.
/// </summary>
public StatusList Statuses => new(&this.Struct->StatusManager, this.dalamud);
/// <summary>
/// Gets the position of the party member.
/// </summary>
public Vector3 Position => new(this.Struct->X, this.Struct->Y, this.Struct->Z);
/// <summary>
/// Gets the content ID of the party member.
/// </summary>
public long ContentId => this.Struct->ContentID;
/// <summary>
/// Gets the actor ID of this party member.
/// </summary>
public uint ObjectId => this.Struct->ObjectID;
/// <summary>
/// Gets the actor associated with this buddy.
/// </summary>
/// <remarks>
/// This iterates the actor table, it should be used with care.
/// </remarks>
[CanBeNull]
public GameObject GameObject => this.dalamud.ClientState.Objects.SearchByID(this.ObjectId);
/// <summary>
/// Gets the current HP of this party member.
/// </summary>
public uint CurrentHP => this.Struct->CurrentHP;
/// <summary>
/// Gets the maximum HP of this party member.
/// </summary>
public uint MaxHP => this.Struct->MaxHP;
/// <summary>
/// Gets the current MP of this party member.
/// </summary>
public ushort CurrentMP => this.Struct->CurrentMP;
/// <summary>
/// Gets the maximum MP of this party member.
/// </summary>
public ushort MaxMP => this.Struct->MaxMP;
/// <summary>
/// Gets the territory this party member is located in.
/// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.TerritoryType> Territory => new(this.Struct->TerritoryType, this.dalamud);
/// <summary>
/// Gets the World this party member resides in.
/// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.World> World => new(this.Struct->HomeWorld, this.dalamud);
/// <summary>
/// Gets the displayname of this party member.
/// </summary>
public SeString Name => MemoryHelper.ReadSeString((IntPtr)Struct->Name, 0x40);
/// <summary>
/// Gets the sex of this party member.
/// </summary>
public byte Sex => this.Struct->Sex;
/// <summary>
/// Gets the classjob of this party member.
/// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.ClassJob> ClassJob => new(this.Struct->ClassJob, this.dalamud);
/// <summary>
/// Gets the level of this party member.
/// </summary>
public byte Level => this.Struct->Level;
private FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember*)this.Address;
}
}

View file

@ -1,139 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Actors.Types;
// using Dalamud.Hooking;
namespace Dalamud.Game.ClientState
{
/// <summary>
/// This class represents the members of your party.
/// </summary>
public sealed partial class PartyList
{
private readonly Dalamud dalamud;
private readonly ClientStateAddressResolver address;
// private bool isReady = false;
// private IntPtr partyListBegin;
// private Hook<PartyListUpdateDelegate> partyListUpdateHook;
/// <summary>
/// Initializes a new instance of the <see cref="PartyList"/> class.
/// </summary>
/// <param name="dalamud">The Dalamud instance.</param>
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
internal PartyList(Dalamud dalamud, ClientStateAddressResolver addressResolver)
{
this.address = addressResolver;
this.dalamud = dalamud;
// this.partyListUpdateHook = new Hook<PartyListUpdateDelegate>(Address.PartyListUpdate, new PartyListUpdateDelegate(PartyListUpdateDetour), this);
}
private delegate long PartyListUpdateDelegate(IntPtr structBegin, long param2, char param3);
/// <summary>
/// Gets the length of the PartyList.
/// </summary>
public int Length => 0; // !this.isReady ? 0 : Marshal.ReadByte(this.partyListBegin + 0xF0);
/// <summary>
/// Get the nth party member.
/// </summary>
/// <param name="index">Index of the party member.</param>
/// <returns>The party member.</returns>
public PartyMember this[int index]
{
get
{
return null;
// if (!this.isReady)
// return null;
// if (index >= this.Length)
// return null;
// var tblIndex = this.partyListBegin + (index * 24);
// var memberStruct = Marshal.PtrToStructure<Structs.PartyMember>(tblIndex);
// return new PartyMember(this.dalamud.ClientState.Actors, memberStruct);
}
}
/// <summary>
/// Enable this module.
/// </summary>
public void Enable()
{
// TODO Fix for 5.3
// this.partyListUpdateHook.Enable();
}
/// <summary>
/// Dispose of managed and unmanaged resources.
/// </summary>
public void Dispose()
{
// if (!this.isReady)
// this.partyListUpdateHook.Dispose();
// this.isReady = false;
}
// private long PartyListUpdateDetour(IntPtr structBegin, long param2, char param3)
// {
// var result = this.partyListUpdateHook.Original(structBegin, param2, param3);
// this.partyListBegin = structBegin + 0xB48;
// this.partyListUpdateHook.Dispose();
// this.isReady = true;
// return result;
// }
}
/// <summary>
/// Implements IReadOnlyCollection, IEnumerable.
/// </summary>
public sealed partial class PartyList : IReadOnlyCollection<PartyMember>
{
/// <inheritdoc/>
int IReadOnlyCollection<PartyMember>.Count => this.Length;
/// <inheritdoc/>
public IEnumerator<PartyMember> GetEnumerator()
{
for (var i = 0; i < this.Length; i++)
{
if (this[i] != null)
{
yield return this[i];
}
}
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}
/// <summary>
/// Implements ICollection.
/// </summary>
public sealed partial class PartyList : ICollection
{
/// <inheritdoc/>
public int Count => this.Length;
/// <inheritdoc/>
public object SyncRoot => this;
/// <inheritdoc/>
public bool IsSynchronized => false;
/// <inheritdoc/>
public void CopyTo(Array array, int index)
{
for (var i = 0; i < this.Length; i++)
{
array.SetValue(this[i], index);
index++;
}
}
}
}

View file

@ -0,0 +1,73 @@
using System;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.ClientState.Resolvers;
using JetBrains.Annotations;
namespace Dalamud.Game.ClientState.Statuses
{
/// <summary>
/// This class represents a status effect an actor is afflicted by.
/// </summary>
public unsafe class Status
{
private Dalamud dalamud;
/// <summary>
/// Initializes a new instance of the <see cref="Status"/> class.
/// </summary>
/// <param name="address">Status address.</param>
/// <param name="dalamud">Dalamud instance.</param>
internal Status(IntPtr address, Dalamud dalamud)
{
this.dalamud = dalamud;
this.Address = address;
}
/// <summary>
/// Gets the address of the status in memory.
/// </summary>
public IntPtr Address { get; }
/// <summary>
/// Gets the status ID of this status.
/// </summary>
public uint StatusID => this.Struct->StatusID;
/// <summary>
/// Gets the GameData associated with this status.
/// </summary>
public Lumina.Excel.GeneratedSheets.Status GameData => new ExcelResolver<Lumina.Excel.GeneratedSheets.Status>(this.Struct->StatusID, this.dalamud).GameData;
/// <summary>
/// Gets the parameter value of the status.
/// </summary>
public byte Param => this.Struct->Param;
/// <summary>
/// Gets the stack count of this status.
/// </summary>
public byte StackCount => this.Struct->StackCount;
/// <summary>
/// Gets the time remaining of this status.
/// </summary>
public float RemainingTime => this.Struct->RemainingTime;
/// <summary>
/// Gets the source ID of this status.
/// </summary>
public uint SourceID => this.Struct->SourceID;
/// <summary>
/// Gets the source actor associated with this status.
/// </summary>
/// <remarks>
/// This iterates the actor table, it should be used with care.
/// </remarks>
[CanBeNull]
public GameObject SourceActor => this.dalamud.ClientState.Objects.SearchByID(this.SourceID);
private FFXIVClientStructs.FFXIV.Client.Game.Status* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Status*)this.Address;
}
}

View file

@ -0,0 +1,161 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using JetBrains.Annotations;
namespace Dalamud.Game.ClientState.Statuses
{
/// <summary>
/// This collection represents the status effects an actor is afflicted by.
/// </summary>
public sealed unsafe partial class StatusList
{
private const int StatusListLength = 30;
private readonly Dalamud dalamud;
/// <summary>
/// Initializes a new instance of the <see cref="StatusList"/> class.
/// </summary>
/// <param name="address">Address of the status list.</param>
/// <param name="dalamud">The <see cref="dalamud"/> instance.</param>
internal StatusList(IntPtr address, Dalamud dalamud)
{
this.Address = address;
this.dalamud = dalamud;
}
/// <summary>
/// Initializes a new instance of the <see cref="StatusList"/> class.
/// </summary>
/// <param name="pointer">Pointer to the status list.</param>
/// <param name="dalamud">The <see cref="dalamud"/> instance.</param>
internal unsafe StatusList(void* pointer, Dalamud dalamud)
: this((IntPtr)pointer, dalamud)
{
}
/// <summary>
/// Gets the address of the status list in memory.
/// </summary>
public IntPtr Address { get; }
/// <summary>
/// Gets the amount of status effects the actor has.
/// </summary>
public int Length
{
get
{
var i = 0;
for (; i < StatusListLength; i++)
{
var status = this[i];
if (status == null || status.StatusID == 0)
break;
}
return i;
}
}
private static int StatusSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.Status>();
private FFXIVClientStructs.FFXIV.Client.Game.StatusManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.StatusManager*)this.Address;
/// <summary>
/// Get a status effect at the specified index.
/// </summary>
/// <param name="index">Status Index.</param>
/// <returns>The status at the specified index.</returns>
[CanBeNull]
public Status this[int index]
{
get
{
if (index < 0 || index > StatusListLength)
return null;
var addr = this.GetStatusAddress(index);
return this.CreateStatusReference(addr);
}
}
/// <summary>
/// Gets the address of the party member at the specified index of the party list.
/// </summary>
/// <param name="index">The index of the party member.</param>
/// <returns>The memory address of the party member.</returns>
public IntPtr GetStatusAddress(int index)
{
if (index < 0 || index >= StatusListLength)
return IntPtr.Zero;
return (IntPtr)(this.Struct->Status + (index * StatusSize));
}
/// <summary>
/// Create a reference to an FFXIV actor status.
/// </summary>
/// <param name="address">The address of the status effect in memory.</param>
/// <returns>The status object containing the requested data.</returns>
[CanBeNull]
public Status CreateStatusReference(IntPtr address)
{
if (this.dalamud.ClientState.LocalContentId == 0)
return null;
if (address == IntPtr.Zero)
return null;
return new Status(address, this.dalamud);
}
}
/// <summary>
/// This collection represents the status effects an actor is afflicted by.
/// </summary>
public sealed partial class StatusList : IReadOnlyCollection<Status>, ICollection
{
/// <inheritdoc/>
int IReadOnlyCollection<Status>.Count => this.Length;
/// <inheritdoc/>
int ICollection.Count => this.Length;
/// <inheritdoc/>
bool ICollection.IsSynchronized => false;
/// <inheritdoc/>
object ICollection.SyncRoot => this;
/// <inheritdoc/>
public IEnumerator<Status> GetEnumerator()
{
for (var i = 0; i < StatusListLength; i++)
{
var status = this[i];
if (status == null || status.StatusID == 0)
continue;
yield return status;
}
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
/// <inheritdoc/>
void ICollection.CopyTo(Array array, int index)
{
for (var i = 0; i < this.Length; i++)
{
array.SetValue(this[i], index);
index++;
}
}
}
}

View file

@ -1,26 +0,0 @@
using System;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Actors.Types;
namespace Dalamud.Game.ClientState.Structs
{
/// <summary>
/// This represents a native PartyMember class in memory.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct PartyMember
{
[FieldOffset(0x0)]
public IntPtr namePtr;
[FieldOffset(0x8)]
public long unknown;
[FieldOffset(0x10)]
public int actorId;
[FieldOffset(0x14)]
public ObjectKind objectKind;
}
}

View file

@ -279,23 +279,6 @@ namespace Dalamud.Game.Gui
return result;
}
/// <summary>
/// Converts in-world coordinates to screen coordinates (upper left corner origin).
/// </summary>
/// <param name="worldPos">Coordinates in the world.</param>
/// <param name="screenPos">Converted coordinates.</param>
/// <returns>True if worldPos corresponds to a position in front of the camera.</returns>
/// <remarks>
/// This overload requires a conversion to SharpDX vectors, however the penalty should be negligible.
/// </remarks>
public bool WorldToScreen(Position3 worldPos, out Vector2 screenPos)
{
// This overload is necessary due to Positon3 implicit operators.
var result = this.WorldToScreen((SharpDX.Vector3)worldPos, out var sharpScreenPos);
screenPos = sharpScreenPos.ToSystem();
return result;
}
/// <summary>
/// Converts screen coordinates to in-world coordinates via raycasting.
/// </summary>

View file

@ -1,51 +0,0 @@
using System.Runtime.InteropServices;
namespace Dalamud.Game
{
/// <summary>
/// A game native equivalent of a Vector3.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct Position3
{
/// <summary>
/// The X of (X,Z,Y).
/// </summary>
public float X;
/// <summary>
/// The Z of (X,Z,Y).
/// </summary>
public float Z;
/// <summary>
/// The Y of (X,Z,Y).
/// </summary>
public float Y;
/// <summary>
/// Initializes a new instance of the <see cref="Position3"/> struct.
/// </summary>
/// <param name="x">The X position.</param>
/// <param name="z">The Z position.</param>
/// <param name="y">The Y position.</param>
public Position3(float x, float z, float y)
{
this.X = x;
this.Z = z;
this.Y = y;
}
/// <summary>
/// Convert this Position3 to a System.Numerics.Vector3.
/// </summary>
/// <param name="pos">Position to convert.</param>
public static implicit operator System.Numerics.Vector3(Position3 pos) => new(pos.X, pos.Y, pos.Z);
/// <summary>
/// Convert this Position3 to a SharpDX.Vector3.
/// </summary>
/// <param name="pos">Position to convert.</param>
public static implicit operator SharpDX.Vector3(Position3 pos) => new(pos.X, pos.Z, pos.Y);
}
}

View file

@ -72,6 +72,12 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
}
}
/// <summary>
/// Gets the raw item ID of this payload.
/// </summary>
[JsonIgnore]
public uint ItemId => this.itemId;
/// <summary>
/// Gets the underlying Lumina Item represented by this payload.
/// </summary>

View file

@ -14,70 +14,3 @@ using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1515:Single-line comment should be preceded by blank line", Justification = "I like this better", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1127:Generic type constraints should be on their own line", Justification = "I like this better", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "We don't do those yet")]
// Extensions
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Group extensions with the relevant class", Scope = "type", Target = "~T:Dalamud.Interface.FontAwesomeExtensions")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Enum followed by an extension", Scope = "type", Target = "~T:Dalamud.Interface.FontAwesomeExtensions")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Group extensions with the relevant class", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.JobFlagsExt")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Enum followed by an extension", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.JobFlagsExt")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Group extensions with the relevant class", Scope = "type", Target = "~T:Dalamud.Game.Text.XivChatTypeExtensions")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Enum followed by an extension", Scope = "type", Target = "~T:Dalamud.Game.Text.XivChatTypeExtensions")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Group attributes with the relevant class", Scope = "type", Target = "~T:Dalamud.Game.Text.XivChatTypeInfoAttribute")]
// DalamudStartInfo.cs
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Interop", Scope = "type", Target = "~T:Dalamud.DalamudStartInfo")]
// PartyFinder
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Explicit struct layout", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.PartyFinder.Packet")]
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Explicit struct layout", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.PartyFinder.Listing")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "TODO", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.PartyFinder.Packet")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "TODO", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.PartyFinder.Listing")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "TODO", Scope = "type", Target = "~T:Dalamud.Game.ClientState.Structs.PartyMember")]
// <type>Offsets.cs
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204:Static elements should appear before instance elements", Justification = "Offset classes goto the end of file.", Scope = "type", Target = "~T:Dalamud.Game.ClientState.Actors.TargetOffsets")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Group offset classes with the relevant class.", Scope = "type", Target = "~T:Dalamud.Game.ClientState.Actors.TargetOffsets")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Group offset classes with the relevant class.", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.AddonOffsets")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the offset usage instead.", Scope = "type", Target = "~T:Dalamud.Game.ClientState.Actors.TargetOffsets")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the offset usage instead.", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.AddonOffsets")]
// Breaking api changes: these should be split into a PartyFinder subdirectory
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.SearchAreaFlags")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.JobFlags")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.ObjectiveFlags")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.ConditionFlags")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.DutyFinderSettingsFlags")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.LootRuleFlags")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.Category")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.DutyType")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.PartyFinderListingEventArgs")]
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.ClientState.Structs.PartyMember")]
[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "breaking api change", Scope = "member", Target = "~E:Dalamud.Game.Internal.Gui.PartyFinderGui.ReceiveListing")]
// Breaking api changes
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Internal.BaseAddressResolver.DebugScannedValues")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Internal.Gui.Addon.Addon.Address")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Internal.Gui.Addon.Addon.addonStruct")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Internal.Gui.GameGui.GetBaseUIObject")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Text.SeStringHandling.Payload.DataResolver")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.ClientState.Actors.Types.PartyMember")]
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Text.SeStringHandling.Payload.START_BYTE")]
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Text.SeStringHandling.Payload.END_BYTE")]
[assembly: SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Unused, but eventually, maybe.", Scope = "member", Target = "~F:Dalamud.Game.ClientState.PartyList.address")]
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204:Static elements should appear before instance elements", Justification = "breaking api change, move to util", Scope = "type", Target = "~T:Dalamud.Game.Text.EnumExtensions")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change, move to util", Scope = "type", Target = "~T:Dalamud.Game.Text.EnumExtensions")]
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Internal.Framework.StatsHistory")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Internal.Framework.StatsHistory")]
[assembly: SuppressMessage("Usage", "CA2211:Non-constant fields should not be visible", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Internal.Framework.StatsHistory")]
[assembly: SuppressMessage("Usage", "CA2208:Instantiate argument exceptions correctly", Justification = "Appears to be a bug, it is being used correctly", Scope = "member", Target = "~M:Dalamud.Data.DataManager.Initialize(System.String)")]
// I mostly didnt care to do these.
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Internal.Network.GameNetwork.OnNetworkMessage")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.Structures.MarketBoardCurrentOfferings")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.Structures.MarketBoardCurrentOfferings")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.Structures.MarketBoardHistory")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.Structures.MarketBoardHistory")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.Structures.MarketTaxRates")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.Structures.MarketTaxRates")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.MarketBoardItemRequest")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.MarketBoardItemRequest")]

View file

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Numerics;
@ -7,7 +8,6 @@ using System.Text;
using System.Threading;
using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.GamePad;
using Dalamud.Game.Internal.DXGI;
using Dalamud.Hooking;
@ -71,10 +71,10 @@ namespace Dalamud.Interface.Internal
this.address = sigResolver;
}
catch (Exception ex)
catch (KeyNotFoundException)
{
// The SigScanner method fails on wine/proton since DXGI is not a real DLL. We fall back to vtable to detect our Present function address.
Log.Debug(ex, "Could not get SwapChain address via sig method, falling back to vtable...");
Log.Debug("Could not get SwapChain address via sig method, falling back to vtable");
var vtableResolver = new SwapChainVtableResolver();
vtableResolver.Setup(scanner);
@ -93,7 +93,7 @@ namespace Dalamud.Interface.Internal
var fileName = new StringBuilder(255);
_ = NativeFunctions.GetModuleFileNameW(rtss, fileName, fileName.Capacity);
this.rtssPath = fileName.ToString();
Log.Verbose("RTSS at {0}", this.rtssPath);
Log.Verbose($"RTSS at {this.rtssPath}");
if (!NativeFunctions.FreeLibrary(rtss))
throw new Win32Exception();
@ -581,8 +581,7 @@ namespace Dalamud.Interface.Internal
this.dalamud.DalamudUi.ToggleGamepadModeNotifierWindow();
}
if (gamepadEnabled
&& (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0)
if (gamepadEnabled && (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0)
{
ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Activate] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.South);
ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Cancel] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.East);

View file

@ -5,13 +5,12 @@ using System.Linq;
using System.Numerics;
using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Game.ClientState.Actors.Types.NonPlayer;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.GamePad;
using Dalamud.Game.ClientState.JobGauge.Enums;
using Dalamud.Game.ClientState.JobGauge.Types;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Gui.Addons;
using Dalamud.Game.Gui.Toast;
using Dalamud.Game.Text;
@ -37,8 +36,8 @@ namespace Dalamud.Interface.Internal.Windows
private string serverOpString;
private DataKind currentKind;
private bool drawActors = false;
private float maxActorDrawDistance = 20;
private bool drawCharas = false;
private float maxCharaDrawDistance = 20;
private string inputSig = string.Empty;
private IntPtr sigResult = IntPtr.Zero;
@ -50,6 +49,7 @@ namespace Dalamud.Interface.Internal.Windows
private IntPtr findAgentInterfacePtr;
private bool resolveGameData = false;
private bool resolveObjects = false;
private UIDebug addonInspector = null;
@ -89,10 +89,11 @@ namespace Dalamud.Interface.Internal.Windows
{
Server_OpCode,
Address,
Actor_Table,
Object_Table,
Fate_Table,
Font_Test,
Party_List,
Buddy_List,
Plugin_IPC,
Condition,
Gauge,
@ -127,18 +128,23 @@ namespace Dalamud.Interface.Internal.Windows
if (string.IsNullOrEmpty(dataKind))
return;
if (dataKind == "ai")
dataKind = "Addon Inspector";
dataKind = dataKind switch
{
"ai" => "Addon Inspector",
"at" => "Object Table", // Actor Table
"ot" => "Object Table",
_ => dataKind,
};
dataKind = dataKind.Replace(" ", string.Empty).ToLower();
var dataKinds = Enum.GetValues(typeof(DataKind))
var matched = Enum.GetValues(typeof(DataKind))
.Cast<DataKind>()
.Where(k => nameof(k).Replace("_", string.Empty).ToLower() == dataKind)
.ToList();
.Where(k => Enum.GetName(typeof(DataKind), k).Replace("_", string.Empty).ToLower() == dataKind)
.FirstOrDefault();
if (dataKinds.Count > 0)
if (matched != default)
{
this.currentKind = dataKinds.First();
this.currentKind = matched;
}
else
{
@ -189,8 +195,8 @@ namespace Dalamud.Interface.Internal.Windows
this.DrawAddress();
break;
case DataKind.Actor_Table:
this.DrawActorTable();
case DataKind.Object_Table:
this.DrawObjectTable();
break;
case DataKind.Fate_Table:
@ -205,6 +211,10 @@ namespace Dalamud.Interface.Internal.Windows
this.DrawPartyList();
break;
case DataKind.Buddy_List:
this.DrawBuddyList();
break;
case DataKind.Plugin_IPC:
this.DrawPluginIPC();
break;
@ -309,24 +319,18 @@ namespace Dalamud.Interface.Internal.Windows
}
}
private void DrawActorTable()
private void DrawObjectTable()
{
var stateString = string.Empty;
// LocalPlayer is null in a number of situations (at least with the current visible-actors list)
// which would crash here.
if (this.dalamud.ClientState.Actors.Length == 0)
{
ImGui.TextUnformatted("Data not ready.");
}
else if (this.dalamud.ClientState.LocalPlayer == null)
if (this.dalamud.ClientState.LocalPlayer == null)
{
ImGui.TextUnformatted("LocalPlayer null.");
}
else
{
stateString += $"FrameworkBase: {this.dalamud.Framework.Address.BaseAddress.ToInt64():X}\n";
stateString += $"ActorTableLen: {this.dalamud.ClientState.Actors.Length}\n";
stateString += $"ObjectTableLen: {this.dalamud.ClientState.Objects.Length}\n";
stateString += $"LocalPlayerName: {this.dalamud.ClientState.LocalPlayer.Name}\n";
stateString += $"CurrentWorldName: {(this.resolveGameData ? this.dalamud.ClientState.LocalPlayer.CurrentWorld.GameData.Name : this.dalamud.ClientState.LocalPlayer.CurrentWorld.Id.ToString())}\n";
stateString += $"HomeWorldName: {(this.resolveGameData ? this.dalamud.ClientState.LocalPlayer.HomeWorld.GameData.Name : this.dalamud.ClientState.LocalPlayer.HomeWorld.Id.ToString())}\n";
@ -336,29 +340,29 @@ namespace Dalamud.Interface.Internal.Windows
ImGui.TextUnformatted(stateString);
ImGui.Checkbox("Draw actors on screen", ref this.drawActors);
ImGui.SliderFloat("Draw Distance", ref this.maxActorDrawDistance, 2f, 40f);
ImGui.Checkbox("Draw characters on screen", ref this.drawCharas);
ImGui.SliderFloat("Draw Distance", ref this.maxCharaDrawDistance, 2f, 40f);
for (var i = 0; i < this.dalamud.ClientState.Actors.Length; i++)
for (var i = 0; i < this.dalamud.ClientState.Objects.Length; i++)
{
var actor = this.dalamud.ClientState.Actors[i];
var obj = this.dalamud.ClientState.Objects[i];
if (actor == null)
if (obj == null)
continue;
this.PrintActor(actor, i.ToString());
this.PrintGameObject(obj, i.ToString());
if (this.drawActors && this.dalamud.Framework.Gui.WorldToScreen(actor.Position, out var screenCoords))
if (this.drawCharas && this.dalamud.Framework.Gui.WorldToScreen(obj.Position, out var screenCoords))
{
// So, while WorldToScreen will return false if the point is off of game client screen, to
// to avoid performance issues, we have to manually determine if creating a window would
// produce a new viewport, and skip rendering it if so
var actorText = $"{actor.Address.ToInt64():X}:{actor.ActorId:X}[{i}] - {actor.ObjectKind} - {actor.Name}";
var objectText = $"{obj.Address.ToInt64():X}:{obj.ObjectId:X}[{i}] - {obj.ObjectKind} - {obj.Name}";
var screenPos = ImGui.GetMainViewport().Pos;
var screenSize = ImGui.GetMainViewport().Size;
var windowSize = ImGui.CalcTextSize(actorText);
var windowSize = ImGui.CalcTextSize(objectText);
// Add some extra safety padding
windowSize.X += ImGui.GetStyle().WindowPadding.X + 10;
@ -368,12 +372,12 @@ namespace Dalamud.Interface.Internal.Windows
screenCoords.Y + windowSize.Y > screenPos.Y + screenSize.Y)
continue;
if (actor.YalmDistanceX > this.maxActorDrawDistance)
if (obj.YalmDistanceX > this.maxCharaDrawDistance)
continue;
ImGui.SetNextWindowPos(new Vector2(screenCoords.X, screenCoords.Y));
ImGui.SetNextWindowBgAlpha(Math.Max(1f - (actor.YalmDistanceX / this.maxActorDrawDistance), 0.2f));
ImGui.SetNextWindowBgAlpha(Math.Max(1f - (obj.YalmDistanceX / this.maxCharaDrawDistance), 0.2f));
if (ImGui.Begin(
$"Actor{i}##ActorWindow{i}",
ImGuiWindowFlags.NoDecoration |
@ -384,7 +388,7 @@ namespace Dalamud.Interface.Internal.Windows
ImGuiWindowFlags.NoDocking |
ImGuiWindowFlags.NoFocusOnAppearing |
ImGuiWindowFlags.NoNav))
ImGui.Text(actorText);
ImGui.Text(objectText);
ImGui.End();
}
}
@ -453,30 +457,118 @@ namespace Dalamud.Interface.Internal.Windows
private void DrawPartyList()
{
var partyString = string.Empty;
ImGui.Checkbox("Resolve Actors", ref this.resolveObjects);
if (this.dalamud.ClientState.PartyList.Length == 0)
ImGui.Text($"GroupManager: {this.dalamud.ClientState.PartyList.GroupManagerAddress.ToInt64():X}");
ImGui.Text($"GroupList: {this.dalamud.ClientState.PartyList.GroupListAddress.ToInt64():X}");
ImGui.Text($"AllianceList: {this.dalamud.ClientState.PartyList.AllianceListAddress.ToInt64():X}");
ImGui.Text($"{this.dalamud.ClientState.PartyList.Length} Members");
for (var i = 0; i < this.dalamud.ClientState.PartyList.Length; i++)
{
ImGui.TextUnformatted("Data not ready.");
}
else
{
partyString += $"{this.dalamud.ClientState.PartyList.Count} Members\n";
for (var i = 0; i < this.dalamud.ClientState.PartyList.Count; i++)
var member = this.dalamud.ClientState.PartyList[i];
if (member == null)
{
var member = this.dalamud.ClientState.PartyList[i];
if (member == null)
{
partyString +=
$"[{i}] was null\n";
continue;
}
partyString +=
$"[{i}] {member.CharacterName} - {member.ObjectKind} - {member.Actor.ActorId}\n";
ImGui.Text($"[{i}] was null");
continue;
}
ImGui.TextUnformatted(partyString);
ImGui.Text($"[{i}] {member.Address.ToInt64():X} - {member.Name} - {member.GameObject.ObjectId}");
if (this.resolveObjects)
{
var actor = member.GameObject;
if (actor == null)
{
ImGui.Text("Actor was null");
}
else
{
this.PrintGameObject(actor, "-");
}
}
}
}
private void DrawBuddyList()
{
ImGui.Checkbox("Resolve Actors", ref this.resolveObjects);
ImGui.Text($"BuddyList: {this.dalamud.ClientState.BuddyList.BuddyListAddress.ToInt64():X}");
{
var member = this.dalamud.ClientState.BuddyList.CompanionBuddy;
if (member == null)
{
ImGui.Text("[Companion] null");
}
else
{
ImGui.Text($"[Companion] {member.Address.ToInt64():X} - {member.ObjectId} - {member.DataID}");
if (this.resolveObjects)
{
var actor = member.Actor;
if (actor == null)
{
ImGui.Text("Actor was null");
}
else
{
this.PrintGameObject(actor, "-");
}
}
}
}
{
var member = this.dalamud.ClientState.BuddyList.PetBuddy;
if (member == null)
{
ImGui.Text("[Pet] null");
}
else
{
ImGui.Text($"[Pet] {member.Address.ToInt64():X} - {member.ObjectId} - {member.DataID}");
if (this.resolveObjects)
{
var actor = member.Actor;
if (actor == null)
{
ImGui.Text("Actor was null");
}
else
{
this.PrintGameObject(actor, "-");
}
}
}
}
{
var count = this.dalamud.ClientState.BuddyList.Length;
if (count == 0)
{
ImGui.Text("[BattleBuddy] None present");
}
else
{
for (var i = 0; i < count; i++)
{
var member = this.dalamud.ClientState.BuddyList[i];
ImGui.Text($"[BattleBuddy] [{i}] {member.Address.ToInt64():X} - {member.ObjectId} - {member.DataID}");
if (this.resolveObjects)
{
var actor = member.Actor;
if (actor == null)
{
ImGui.Text("Actor was null");
}
else
{
this.PrintGameObject(actor, "-");
}
}
}
}
}
}
@ -783,26 +875,26 @@ namespace Dalamud.Interface.Internal.Windows
{
var targetMgr = this.dalamud.ClientState.Targets;
if (targetMgr.CurrentTarget != null)
if (targetMgr.Target != null)
{
this.PrintActor(targetMgr.CurrentTarget, "CurrentTarget");
Util.ShowObject(targetMgr.CurrentTarget);
this.PrintGameObject(targetMgr.Target, "CurrentTarget");
Util.ShowObject(targetMgr.Target);
}
if (targetMgr.FocusTarget != null)
this.PrintActor(targetMgr.FocusTarget, "FocusTarget");
this.PrintGameObject(targetMgr.FocusTarget, "FocusTarget");
if (targetMgr.MouseOverTarget != null)
this.PrintActor(targetMgr.MouseOverTarget, "MouseOverTarget");
this.PrintGameObject(targetMgr.MouseOverTarget, "MouseOverTarget");
if (targetMgr.PreviousTarget != null)
this.PrintActor(targetMgr.PreviousTarget, "PreviousTarget");
this.PrintGameObject(targetMgr.PreviousTarget, "PreviousTarget");
if (targetMgr.SoftTarget != null)
this.PrintActor(targetMgr.SoftTarget, "SoftTarget");
this.PrintGameObject(targetMgr.SoftTarget, "SoftTarget");
if (ImGui.Button("Clear CT"))
targetMgr.ClearCurrentTarget();
targetMgr.ClearTarget();
if (ImGui.Button("Clear FT"))
targetMgr.ClearFocusTarget();
@ -812,7 +904,7 @@ namespace Dalamud.Interface.Internal.Windows
if (localPlayer != null)
{
if (ImGui.Button("Set CT"))
targetMgr.SetCurrentTarget(localPlayer);
targetMgr.SetTarget(localPlayer);
if (ImGui.Button("Set FT"))
targetMgr.SetFocusTarget(localPlayer);
@ -924,9 +1016,10 @@ namespace Dalamud.Interface.Internal.Windows
$"L3 {resolve(GamepadButtons.L3)} " +
$"R3 {resolve(GamepadButtons.R3)} ");
}
#if DEBUG
ImGui.Text($"GamepadInput 0x{this.dalamud.ClientState.GamepadState.GamepadInputAddress.ToInt64():X}");
#if DEBUG
if (ImGui.IsItemHovered())
ImGui.SetMouseCursor(ImGuiMouseCursor.Hand);
@ -969,15 +1062,15 @@ namespace Dalamud.Interface.Internal.Windows
}
}
private void PrintActor(Actor actor, string tag)
private void PrintGameObject(GameObject actor, string tag)
{
var actorString =
$"{actor.Address.ToInt64():X}:{actor.ActorId: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.TargetActorID:X}\n";
$"{actor.Address.ToInt64():X}:{actor.ObjectId: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.BaseId} NameId:{npc.NameId}\n";
actorString += $" DataId: {npc.DataId} NameId:{npc.NameId}\n";
if (actor is Chara chara)
if (actor is Character chara)
{
actorString +=
$" Level: {chara.Level} ClassJob: {(this.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";

View file

@ -1,4 +1,4 @@
using Dalamud.Utility;
using Dalamud.Utility;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
@ -18,12 +18,12 @@ namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
{
ImGui.Text("Checking actor table...");
if (this.index == dalamud.ClientState.Actors.Length - 1)
if (this.index == dalamud.ClientState.Objects.Length - 1)
{
return SelfTestStepResult.Pass;
}
var actor = dalamud.ClientState.Actors[this.index];
var actor = dalamud.ClientState.Objects[this.index];
this.index++;
if (actor == null)

View file

@ -1,5 +1,5 @@
using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Game.ClientState.Actors.Types.NonPlayer;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
@ -20,7 +20,7 @@ namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
switch (this.step)
{
case 0:
dalamud.ClientState.Targets.ClearCurrentTarget();
dalamud.ClientState.Targets.ClearTarget();
dalamud.ClientState.Targets.ClearFocusTarget();
this.step++;
@ -30,7 +30,7 @@ namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
case 1:
ImGui.Text("Target a player...");
var cTarget = dalamud.ClientState.Targets.CurrentTarget;
var cTarget = dalamud.ClientState.Targets.Target;
if (cTarget is PlayerCharacter)
{
this.step++;

View file

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Interface.Internal;
using ImGuiNET;

View file

@ -1,57 +0,0 @@
using System;
namespace Dalamud.Plugin.Internal.Types
{
/// <summary>
/// This represents the result of a an operation taken against a plugin.
/// Loading, unloading, installation, etc.
/// </summary>
internal enum PluginOperationResult
{
/// <summary>
/// The result is unknown. Should not be used.
/// </summary>
[Obsolete("Do not use this", error: true)]
Unknown,
/// <summary>
/// The result is pending. Take a seat and wait.
/// </summary>
Pending,
/// <summary>
/// The operation was successful.
/// </summary>
Success,
/// <summary>
/// During the plugin operation, an unexpected error occurred.
/// </summary>
UnknownError,
/// <summary>
/// The plugin state was invalid for the attempted operation.
/// </summary>
InvalidState,
/// <summary>
/// The plugin applicable version is not compativle with the currently running game.
/// </summary>
InvalidGameVersion,
/// <summary>
/// The plugin API level is not compatible with the currently running Dalamud.
/// </summary>
InvalidApiLevel,
/// <summary>
/// During loading, the current plugin was marked as disabled.
/// </summary>
InvalidStateDisabled,
/// <summary>
/// During loading, another plugin was detected with the same internal name.
/// </summary>
InvalidStateDuplicate,
}
}