diff --git a/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs b/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs index 89dd8b8b1..e0a5df06d 100644 --- a/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs +++ b/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs @@ -63,47 +63,37 @@ public interface IAetheryteEntry } /// -/// Class representing an aetheryte entry available to the game. +/// This struct represents an aetheryte entry available to the game. /// -internal sealed class AetheryteEntry : IAetheryteEntry +/// Data read from the Aetheryte List. +internal readonly struct AetheryteEntry(TeleportInfo data) : IAetheryteEntry { - private readonly TeleportInfo data; - - /// - /// Initializes a new instance of the class. - /// - /// Data read from the Aetheryte List. - internal AetheryteEntry(TeleportInfo data) - { - this.data = data; - } + /// + public uint AetheryteId => data.AetheryteId; /// - public uint AetheryteId => this.data.AetheryteId; + public uint TerritoryId => data.TerritoryId; /// - public uint TerritoryId => this.data.TerritoryId; + public byte SubIndex => data.SubIndex; /// - public byte SubIndex => this.data.SubIndex; + public byte Ward => data.Ward; /// - public byte Ward => this.data.Ward; + public byte Plot => data.Plot; /// - public byte Plot => this.data.Plot; + public uint GilCost => data.GilCost; /// - public uint GilCost => this.data.GilCost; + public bool IsFavourite => data.IsFavourite; /// - public bool IsFavourite => this.data.IsFavourite; + public bool IsSharedHouse => data.IsSharedHouse; /// - public bool IsSharedHouse => this.data.IsSharedHouse; - - /// - public bool IsApartment => this.data.IsApartment; + public bool IsApartment => data.IsApartment; /// public RowRef AetheryteData => LuminaUtils.CreateRef(this.AetheryteId); diff --git a/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs b/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs index a3d44d423..f72339ed2 100644 --- a/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs +++ b/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs @@ -87,10 +87,7 @@ internal sealed partial class AetheryteList /// public IEnumerator GetEnumerator() { - for (var i = 0; i < this.Length; i++) - { - yield return this[i]; - } + return new Enumerator(this); } /// @@ -98,4 +95,30 @@ internal sealed partial class AetheryteList { return this.GetEnumerator(); } + + private struct Enumerator(AetheryteList aetheryteList) : IEnumerator + { + private int index = 0; + + public IAetheryteEntry Current { get; private set; } + + object IEnumerator.Current => this.Current; + + public bool MoveNext() + { + if (this.index == aetheryteList.Length) return false; + this.Current = aetheryteList[this.index]; + this.index++; + return true; + } + + public void Reset() + { + this.index = 0; + } + + public void Dispose() + { + } + } } diff --git a/Dalamud/Game/ClientState/Buddy/BuddyList.cs b/Dalamud/Game/ClientState/Buddy/BuddyList.cs index 84cfd24a3..78809f8ba 100644 --- a/Dalamud/Game/ClientState/Buddy/BuddyList.cs +++ b/Dalamud/Game/ClientState/Buddy/BuddyList.cs @@ -8,6 +8,9 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.UI; +using CSBuddy = FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy; +using CSBuddyMember = FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember; + namespace Dalamud.Game.ClientState.Buddy; /// @@ -21,7 +24,7 @@ namespace Dalamud.Game.ClientState.Buddy; #pragma warning restore SA1015 internal sealed partial class BuddyList : IServiceType, IBuddyList { - private const uint InvalidObjectID = 0xE0000000; + private const uint InvalidEntityId = 0xE0000000; [ServiceManager.ServiceDependency] private readonly ClientState clientState = Service.Get(); @@ -69,7 +72,7 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList } } - private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy* BuddyListStruct => &UIState.Instance()->Buddy; + private unsafe CSBuddy* BuddyListStruct => &UIState.Instance()->Buddy; /// public IBuddyMember? this[int index] @@ -82,37 +85,37 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList } /// - public unsafe IntPtr GetCompanionBuddyMemberAddress() + public unsafe nint GetCompanionBuddyMemberAddress() { - return (IntPtr)this.BuddyListStruct->CompanionInfo.Companion; + return (nint)this.BuddyListStruct->CompanionInfo.Companion; } /// - public unsafe IntPtr GetPetBuddyMemberAddress() + public unsafe nint GetPetBuddyMemberAddress() { - return (IntPtr)this.BuddyListStruct->PetInfo.Pet; + return (nint)this.BuddyListStruct->PetInfo.Pet; } /// - public unsafe IntPtr GetBattleBuddyMemberAddress(int index) + public unsafe nint GetBattleBuddyMemberAddress(int index) { if (index < 0 || index >= 3) - return IntPtr.Zero; + return 0; - return (IntPtr)Unsafe.AsPointer(ref this.BuddyListStruct->BattleBuddies[index]); + return (nint)Unsafe.AsPointer(ref this.BuddyListStruct->BattleBuddies[index]); } /// - public IBuddyMember? CreateBuddyMemberReference(IntPtr address) + public unsafe IBuddyMember? CreateBuddyMemberReference(nint address) { + if (address == 0) + return null; + if (this.clientState.LocalContentId == 0) return null; - if (address == IntPtr.Zero) - return null; - - var buddy = new BuddyMember(address); - if (buddy.ObjectId == InvalidObjectID) + var buddy = new BuddyMember((CSBuddyMember*)address); + if (buddy.EntityId == InvalidEntityId) return null; return buddy; @@ -130,12 +133,35 @@ internal sealed partial class BuddyList /// public IEnumerator GetEnumerator() { - for (var i = 0; i < this.Length; i++) - { - yield return this[i]; - } + return new Enumerator(this); } /// IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + + private struct Enumerator(BuddyList buddyList) : IEnumerator + { + private int index = 0; + + public IBuddyMember Current { get; private set; } + + object IEnumerator.Current => this.Current; + + public bool MoveNext() + { + if (this.index == buddyList.Length) return false; + this.Current = buddyList[this.index]; + this.index++; + return true; + } + + public void Reset() + { + this.index = 0; + } + + public void Dispose() + { + } + } } diff --git a/Dalamud/Game/ClientState/Buddy/BuddyMember.cs b/Dalamud/Game/ClientState/Buddy/BuddyMember.cs index 393598d32..8018bafaf 100644 --- a/Dalamud/Game/ClientState/Buddy/BuddyMember.cs +++ b/Dalamud/Game/ClientState/Buddy/BuddyMember.cs @@ -1,20 +1,24 @@ +using System.Diagnostics.CodeAnalysis; + using Dalamud.Data; using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.Types; using Lumina.Excel; +using CSBuddyMember = FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember; + namespace Dalamud.Game.ClientState.Buddy; /// /// Interface representing represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties. /// -public interface IBuddyMember +public interface IBuddyMember : IEquatable { /// /// Gets the address of the buddy in memory. /// - IntPtr Address { get; } + nint Address { get; } /// /// Gets the object ID of this buddy. @@ -67,42 +71,34 @@ public interface IBuddyMember } /// -/// This class represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties. +/// This struct represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties. /// -internal unsafe class BuddyMember : IBuddyMember +/// A pointer to the BuddyMember. +internal readonly unsafe struct BuddyMember(CSBuddyMember* ptr) : IBuddyMember { [ServiceManager.ServiceDependency] private readonly ObjectTable objectTable = Service.Get(); - /// - /// Initializes a new instance of the class. - /// - /// Buddy address. - internal BuddyMember(IntPtr address) - { - this.Address = address; - } + /// + public nint Address => (nint)ptr; /// - public IntPtr Address { get; } + public uint ObjectId => this.EntityId; /// - public uint ObjectId => this.Struct->EntityId; + public uint EntityId => ptr->EntityId; /// - public uint EntityId => this.Struct->EntityId; + public IGameObject? GameObject => this.objectTable.SearchById(this.EntityId); /// - public IGameObject? GameObject => this.objectTable.SearchById(this.ObjectId); + public uint CurrentHP => ptr->CurrentHealth; /// - public uint CurrentHP => this.Struct->CurrentHealth; + public uint MaxHP => ptr->MaxHealth; /// - public uint MaxHP => this.Struct->MaxHealth; - - /// - public uint DataID => this.Struct->DataId; + public uint DataID => ptr->DataId; /// public RowRef MountData => LuminaUtils.CreateRef(this.DataID); @@ -113,5 +109,25 @@ internal unsafe class BuddyMember : IBuddyMember /// public RowRef TrustData => LuminaUtils.CreateRef(this.DataID); - private FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember*)this.Address; + public static bool operator ==(BuddyMember x, BuddyMember y) => x.Equals(y); + + public static bool operator !=(BuddyMember x, BuddyMember y) => !(x == y); + + /// + public bool Equals(IBuddyMember? other) + { + return this.EntityId == other.EntityId; + } + + /// + public override bool Equals([NotNullWhen(true)] object? obj) + { + return obj is BuddyMember fate && this.Equals(fate); + } + + /// + public override int GetHashCode() + { + return this.EntityId.GetHashCode(); + } } diff --git a/Dalamud/Game/ClientState/Fates/Fate.cs b/Dalamud/Game/ClientState/Fates/Fate.cs index 504b690c3..c40a8960e 100644 --- a/Dalamud/Game/ClientState/Fates/Fate.cs +++ b/Dalamud/Game/ClientState/Fates/Fate.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Numerics; using Dalamud.Data; @@ -6,10 +7,12 @@ using Dalamud.Memory; using Lumina.Excel; +using CSFateContext = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext; + namespace Dalamud.Game.ClientState.Fates; /// -/// Interface representing an fate entry that can be seen in the current area. +/// Interface representing a fate entry that can be seen in the current area. /// public interface IFate : IEquatable { @@ -111,133 +114,96 @@ public interface IFate : IEquatable /// /// Gets the address of this Fate in memory. /// - IntPtr Address { get; } + nint Address { get; } } /// -/// This class represents an FFXIV Fate. +/// This struct represents a Fate. /// -internal unsafe partial class Fate +/// A pointer to the FateContext. +internal readonly unsafe struct Fate(CSFateContext* ptr) : IFate { - /// - /// Initializes a new instance of the class. - /// - /// The address of this fate in memory. - internal Fate(IntPtr address) - { - this.Address = address; - } - /// - public IntPtr Address { get; } - - private FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext*)this.Address; - - public static bool operator ==(Fate fate1, Fate fate2) - { - if (fate1 is null || fate2 is null) - return Equals(fate1, fate2); - - return fate1.Equals(fate2); - } - - public static bool operator !=(Fate fate1, Fate fate2) => !(fate1 == fate2); - - /// - /// Gets a value indicating whether this Fate is still valid in memory. - /// - /// The fate to check. - /// True or false. - public static bool IsValid(Fate fate) - { - var clientState = Service.GetNullable(); - - if (fate == null || clientState == null) - return false; - - if (clientState.LocalContentId == 0) - return false; - - return true; - } - - /// - /// Gets a value indicating whether this actor is still valid in memory. - /// - /// True or false. - public bool IsValid() => IsValid(this); + public nint Address => (nint)ptr; /// - bool IEquatable.Equals(IFate other) => this.FateId == other?.FateId; - - /// - public override bool Equals(object obj) => ((IEquatable)this).Equals(obj as IFate); - - /// - public override int GetHashCode() => this.FateId.GetHashCode(); -} - -/// -/// This class represents an FFXIV Fate. -/// -internal unsafe partial class Fate : IFate -{ - /// - public ushort FateId => this.Struct->FateId; + public ushort FateId => ptr->FateId; /// public RowRef GameData => LuminaUtils.CreateRef(this.FateId); /// - public int StartTimeEpoch => this.Struct->StartTimeEpoch; + public int StartTimeEpoch => ptr->StartTimeEpoch; /// - public short Duration => this.Struct->Duration; + public short Duration => ptr->Duration; /// public long TimeRemaining => this.StartTimeEpoch + this.Duration - DateTimeOffset.Now.ToUnixTimeSeconds(); /// - public SeString Name => MemoryHelper.ReadSeString(&this.Struct->Name); + public SeString Name => MemoryHelper.ReadSeString(&ptr->Name); /// - public SeString Description => MemoryHelper.ReadSeString(&this.Struct->Description); + public SeString Description => MemoryHelper.ReadSeString(&ptr->Description); /// - public SeString Objective => MemoryHelper.ReadSeString(&this.Struct->Objective); + public SeString Objective => MemoryHelper.ReadSeString(&ptr->Objective); /// - public FateState State => (FateState)this.Struct->State; + public FateState State => (FateState)ptr->State; /// - public byte HandInCount => this.Struct->HandInCount; + public byte HandInCount => ptr->HandInCount; /// - public byte Progress => this.Struct->Progress; + public byte Progress => ptr->Progress; /// - public bool HasBonus => this.Struct->IsBonus; + public bool HasBonus => ptr->IsBonus; /// - public uint IconId => this.Struct->IconId; + public uint IconId => ptr->IconId; /// - public byte Level => this.Struct->Level; + public byte Level => ptr->Level; /// - public byte MaxLevel => this.Struct->MaxLevel; + public byte MaxLevel => ptr->MaxLevel; /// - public Vector3 Position => this.Struct->Location; + public Vector3 Position => ptr->Location; /// - public float Radius => this.Struct->Radius; + public float Radius => ptr->Radius; /// - public uint MapIconId => this.Struct->MapIconId; + public uint MapIconId => ptr->MapIconId; /// /// Gets the territory this is located in. /// - public RowRef TerritoryType => LuminaUtils.CreateRef(this.Struct->MapMarkers[0].MapMarkerData.TerritoryTypeId); + public RowRef TerritoryType => LuminaUtils.CreateRef(ptr->MapMarkers[0].MapMarkerData.TerritoryTypeId); + + public static bool operator ==(Fate x, Fate y) => x.Equals(y); + + public static bool operator !=(Fate x, Fate y) => !(x == y); + + /// + public bool Equals(IFate? other) + { + return this.FateId == other.FateId; + } + + /// + public override bool Equals([NotNullWhen(true)] object? obj) + { + return obj is Fate fate && this.Equals(fate); + } + + /// + public override int GetHashCode() + { + return this.FateId.GetHashCode(); + } } diff --git a/Dalamud/Game/ClientState/Fates/FateTable.cs b/Dalamud/Game/ClientState/Fates/FateTable.cs index 1bf557ad5..a6edf4a18 100644 --- a/Dalamud/Game/ClientState/Fates/FateTable.cs +++ b/Dalamud/Game/ClientState/Fates/FateTable.cs @@ -5,6 +5,7 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; +using CSFateContext = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext; using CSFateManager = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager; namespace Dalamud.Game.ClientState.Fates; @@ -25,7 +26,7 @@ internal sealed partial class FateTable : IServiceType, IFateTable } /// - public unsafe IntPtr Address => (nint)CSFateManager.Instance(); + public unsafe nint Address => (nint)CSFateManager.Instance(); /// public unsafe int Length @@ -72,30 +73,29 @@ internal sealed partial class FateTable : IServiceType, IFateTable } /// - public unsafe IntPtr GetFateAddress(int index) + public unsafe nint GetFateAddress(int index) { if (index >= this.Length) - return IntPtr.Zero; + return 0; var fateManager = CSFateManager.Instance(); if (fateManager == null) - return IntPtr.Zero; + return 0; - return (IntPtr)fateManager->Fates[index].Value; + return (nint)fateManager->Fates[index].Value; } /// - public IFate? CreateFateReference(IntPtr offset) + public unsafe IFate? CreateFateReference(IntPtr address) { - var clientState = Service.Get(); + if (address == 0) + return null; + var clientState = Service.Get(); if (clientState.LocalContentId == 0) return null; - if (offset == IntPtr.Zero) - return null; - - return new Fate(offset); + return new Fate((CSFateContext*)address); } } @@ -110,12 +110,35 @@ internal sealed partial class FateTable /// public IEnumerator GetEnumerator() { - for (var i = 0; i < this.Length; i++) - { - yield return this[i]; - } + return new Enumerator(this); } /// IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + + private struct Enumerator(FateTable fateTable) : IEnumerator + { + private int index = 0; + + public IFate Current { get; private set; } + + object IEnumerator.Current => this.Current; + + public bool MoveNext() + { + if (this.index == fateTable.Length) return false; + this.Current = fateTable[this.index]; + this.index++; + return true; + } + + public void Reset() + { + this.index = 0; + } + + public void Dispose() + { + } + } } diff --git a/Dalamud/Game/ClientState/Objects/ObjectTable.cs b/Dalamud/Game/ClientState/Objects/ObjectTable.cs index 84c1b5693..598daf518 100644 --- a/Dalamud/Game/ClientState/Objects/ObjectTable.cs +++ b/Dalamud/Game/ClientState/Objects/ObjectTable.cs @@ -12,8 +12,6 @@ using Dalamud.Utility; using FFXIVClientStructs.Interop; -using Microsoft.Extensions.ObjectPool; - using CSGameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject; using CSGameObjectManager = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObjectManager; @@ -34,8 +32,6 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable private readonly ClientState clientState; private readonly CachedEntry[] cachedObjectTable; - private readonly Enumerator?[] frameworkThreadEnumerators = new Enumerator?[4]; - [ServiceManager.ServiceConstructor] private unsafe ObjectTable(ClientState clientState) { @@ -47,9 +43,6 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable this.cachedObjectTable = new CachedEntry[objectTableLength]; for (var i = 0; i < this.cachedObjectTable.Length; i++) this.cachedObjectTable[i] = new(nativeObjectTable.GetPointer(i)); - - for (var i = 0; i < this.frameworkThreadEnumerators.Length; i++) - this.frameworkThreadEnumerators[i] = new(this, i); } /// @@ -239,30 +232,14 @@ internal sealed partial class ObjectTable public IEnumerator GetEnumerator() { ThreadSafety.AssertMainThread(); - - // If we're on the framework thread, see if there's an already allocated enumerator available for use. - foreach (ref var x in this.frameworkThreadEnumerators.AsSpan()) - { - if (x is not null) - { - var t = x; - x = null; - t.Reset(); - return t; - } - } - - // No reusable enumerator is available; allocate a new temporary one. - return new Enumerator(this, -1); + return new Enumerator(this); } /// IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); - private sealed class Enumerator(ObjectTable owner, int slotId) : IEnumerator, IResettable + private struct Enumerator(ObjectTable owner) : IEnumerator { - private ObjectTable? owner = owner; - private int index = -1; public IGameObject Current { get; private set; } = null!; @@ -274,7 +251,7 @@ internal sealed partial class ObjectTable if (this.index == objectTableLength) return false; - var cache = this.owner!.cachedObjectTable.AsSpan(); + var cache = owner.cachedObjectTable.AsSpan(); for (this.index++; this.index < objectTableLength; this.index++) { if (cache[this.index].Update() is { } ao) @@ -291,17 +268,6 @@ internal sealed partial class ObjectTable public void Dispose() { - if (this.owner is not { } o) - return; - - if (slotId != -1) - o.frameworkThreadEnumerators[slotId] = this; - } - - public bool TryReset() - { - this.Reset(); - return true; } } } diff --git a/Dalamud/Game/ClientState/Party/PartyList.cs b/Dalamud/Game/ClientState/Party/PartyList.cs index a016a8211..ec22932ab 100644 --- a/Dalamud/Game/ClientState/Party/PartyList.cs +++ b/Dalamud/Game/ClientState/Party/PartyList.cs @@ -8,6 +8,7 @@ using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; using CSGroupManager = FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager; +using CSPartyMember = FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember; namespace Dalamud.Game.ClientState.Party; @@ -42,20 +43,20 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList public bool IsAlliance => this.GroupManagerStruct->MainGroup.AllianceFlags > 0; /// - public unsafe IntPtr GroupManagerAddress => (nint)CSGroupManager.Instance(); + public unsafe nint GroupManagerAddress => (nint)CSGroupManager.Instance(); /// - public IntPtr GroupListAddress => (IntPtr)Unsafe.AsPointer(ref GroupManagerStruct->MainGroup.PartyMembers[0]); + public nint GroupListAddress => (nint)Unsafe.AsPointer(ref GroupManagerStruct->MainGroup.PartyMembers[0]); /// - public IntPtr AllianceListAddress => (IntPtr)Unsafe.AsPointer(ref this.GroupManagerStruct->MainGroup.AllianceMembers[0]); + public nint AllianceListAddress => (nint)Unsafe.AsPointer(ref this.GroupManagerStruct->MainGroup.AllianceMembers[0]); /// public long PartyId => this.GroupManagerStruct->MainGroup.PartyId; - private static int PartyMemberSize { get; } = Marshal.SizeOf(); + private static int PartyMemberSize { get; } = Marshal.SizeOf(); - private FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager* GroupManagerStruct => (FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager*)this.GroupManagerAddress; + private CSGroupManager* GroupManagerStruct => (CSGroupManager*)this.GroupManagerAddress; /// public IPartyMember? this[int index] @@ -80,45 +81,45 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList } /// - public IntPtr GetPartyMemberAddress(int index) + public nint GetPartyMemberAddress(int index) { if (index < 0 || index >= GroupLength) - return IntPtr.Zero; + return 0; return this.GroupListAddress + (index * PartyMemberSize); } /// - public IPartyMember? CreatePartyMemberReference(IntPtr address) + public IPartyMember? CreatePartyMemberReference(nint address) { if (this.clientState.LocalContentId == 0) return null; - if (address == IntPtr.Zero) + if (address == 0) return null; - return new PartyMember(address); + return new PartyMember((CSPartyMember*)address); } /// - public IntPtr GetAllianceMemberAddress(int index) + public nint GetAllianceMemberAddress(int index) { if (index < 0 || index >= AllianceLength) - return IntPtr.Zero; + return 0; return this.AllianceListAddress + (index * PartyMemberSize); } /// - public IPartyMember? CreateAllianceMemberReference(IntPtr address) + public IPartyMember? CreateAllianceMemberReference(nint address) { if (this.clientState.LocalContentId == 0) return null; - if (address == IntPtr.Zero) + if (address == 0) return null; - return new PartyMember(address); + return new PartyMember((CSPartyMember*)address); } } @@ -133,18 +134,44 @@ internal sealed partial class PartyList /// public IEnumerator 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; - } + return new Enumerator(this); } /// IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + + private struct Enumerator(PartyList partyList) : IEnumerator + { + private int index = 0; + + public IPartyMember Current { get; private set; } + + object IEnumerator.Current => this.Current; + + public bool MoveNext() + { + if (this.index == partyList.Length) return false; + + for (; this.index < partyList.Length; this.index++) + { + var partyMember = partyList[this.index]; + if (partyMember != null) + { + this.Current = partyMember; + return true; + } + } + + return false; + } + + public void Reset() + { + this.index = 0; + } + + public void Dispose() + { + } + } } diff --git a/Dalamud/Game/ClientState/Party/PartyMember.cs b/Dalamud/Game/ClientState/Party/PartyMember.cs index 4c738d866..c9980d9f2 100644 --- a/Dalamud/Game/ClientState/Party/PartyMember.cs +++ b/Dalamud/Game/ClientState/Party/PartyMember.cs @@ -1,26 +1,27 @@ +using System.Diagnostics.CodeAnalysis; using System.Numerics; -using System.Runtime.CompilerServices; using Dalamud.Data; using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Statuses; using Dalamud.Game.Text.SeStringHandling; -using Dalamud.Memory; using Lumina.Excel; +using CSPartyMember = FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember; + namespace Dalamud.Game.ClientState.Party; /// /// Interface representing a party member. /// -public interface IPartyMember +public interface IPartyMember : IEquatable { /// /// Gets the address of this party member in memory. /// - IntPtr Address { get; } + nint Address { get; } /// /// Gets a list of buffs or debuffs applied to this party member. @@ -108,69 +109,81 @@ public interface IPartyMember } /// -/// This class represents a party member in the group manager. +/// This struct represents a party member in the group manager. /// -internal unsafe class PartyMember : IPartyMember +/// A pointer to the PartyMember. +internal unsafe readonly struct PartyMember(CSPartyMember* ptr) : IPartyMember { - /// - /// Initializes a new instance of the class. - /// - /// Address of the party member. - internal PartyMember(IntPtr address) - { - this.Address = address; - } + /// + public nint Address => (nint)ptr; /// - public IntPtr Address { get; } + public StatusList Statuses => new(&ptr->StatusManager); /// - public StatusList Statuses => new(&this.Struct->StatusManager); + public Vector3 Position => ptr->Position; /// - public Vector3 Position => this.Struct->Position; + public long ContentId => (long)ptr->ContentId; /// - public long ContentId => (long)this.Struct->ContentId; + public uint ObjectId => ptr->EntityId; /// - public uint ObjectId => this.Struct->EntityId; - - /// - public uint EntityId => this.Struct->EntityId; + public uint EntityId => ptr->EntityId; /// public IGameObject? GameObject => Service.Get().SearchById(this.EntityId); /// - public uint CurrentHP => this.Struct->CurrentHP; + public uint CurrentHP => ptr->CurrentHP; /// - public uint MaxHP => this.Struct->MaxHP; + public uint MaxHP => ptr->MaxHP; /// - public ushort CurrentMP => this.Struct->CurrentMP; + public ushort CurrentMP => ptr->CurrentMP; /// - public ushort MaxMP => this.Struct->MaxMP; + public ushort MaxMP => ptr->MaxMP; /// - public RowRef Territory => LuminaUtils.CreateRef(this.Struct->TerritoryType); + public RowRef Territory => LuminaUtils.CreateRef(ptr->TerritoryType); /// - public RowRef World => LuminaUtils.CreateRef(this.Struct->HomeWorld); + public RowRef World => LuminaUtils.CreateRef(ptr->HomeWorld); /// - public SeString Name => SeString.Parse(this.Struct->Name); + public SeString Name => SeString.Parse(ptr->Name); /// - public byte Sex => this.Struct->Sex; + public byte Sex => ptr->Sex; /// - public RowRef ClassJob => LuminaUtils.CreateRef(this.Struct->ClassJob); + public RowRef ClassJob => LuminaUtils.CreateRef(ptr->ClassJob); /// - public byte Level => this.Struct->Level; + public byte Level => ptr->Level; - private FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember*)this.Address; + public static bool operator ==(PartyMember x, PartyMember y) => x.Equals(y); + + public static bool operator !=(PartyMember x, PartyMember y) => !(x == y); + + /// + public bool Equals(IPartyMember? other) + { + return this.EntityId == other.EntityId; + } + + /// + public override bool Equals([NotNullWhen(true)] object? obj) + { + return obj is PartyMember fate && this.Equals(fate); + } + + /// + public override int GetHashCode() + { + return this.EntityId.GetHashCode(); + } } diff --git a/Dalamud/Game/ClientState/Statuses/Status.cs b/Dalamud/Game/ClientState/Statuses/Status.cs index 2775f8f9b..160b15de5 100644 --- a/Dalamud/Game/ClientState/Statuses/Status.cs +++ b/Dalamud/Game/ClientState/Statuses/Status.cs @@ -1,61 +1,49 @@ +using System.Diagnostics.CodeAnalysis; + using Dalamud.Data; using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.Types; using Lumina.Excel; +using CSStatus = FFXIVClientStructs.FFXIV.Client.Game.Status; + namespace Dalamud.Game.ClientState.Statuses; /// -/// This class represents a status effect an actor is afflicted by. +/// Interface representing a status. /// -public unsafe class Status +public interface IStatus : IEquatable { - /// - /// Initializes a new instance of the class. - /// - /// Status address. - internal Status(IntPtr address) - { - this.Address = address; - } - /// /// Gets the address of the status in memory. /// - public IntPtr Address { get; } + nint Address { get; } /// /// Gets the status ID of this status. /// - public uint StatusId => this.Struct->StatusId; + uint StatusId { get; } /// /// Gets the GameData associated with this status. /// - public RowRef GameData => LuminaUtils.CreateRef(this.Struct->StatusId); + RowRef GameData { get; } /// /// Gets the parameter value of the status. /// - public ushort Param => this.Struct->Param; - - /// - /// Gets the stack count of this status. - /// Only valid if this is a non-food status. - /// - [Obsolete($"Replaced with {nameof(Param)}", true)] - public byte StackCount => (byte)this.Struct->Param; + ushort Param { get; } /// /// Gets the time remaining of this status. /// - public float RemainingTime => this.Struct->RemainingTime; + float RemainingTime { get; } /// /// Gets the source ID of this status. /// - public uint SourceId => this.Struct->SourceObject.ObjectId; + uint SourceId { get; } /// /// Gets the source actor associated with this status. @@ -63,7 +51,55 @@ public unsafe class Status /// /// This iterates the actor table, it should be used with care. /// + IGameObject? SourceObject { get; } +} + +/// +/// This struct represents a status effect an actor is afflicted by. +/// +/// A pointer to the Status. +internal unsafe readonly struct Status(CSStatus* ptr) : IStatus +{ + /// + public nint Address => (nint)ptr; + + /// + public uint StatusId => ptr->StatusId; + + /// + public RowRef GameData => LuminaUtils.CreateRef(ptr->StatusId); + + /// + public ushort Param => ptr->Param; + + /// + public float RemainingTime => ptr->RemainingTime; + + /// + public uint SourceId => ptr->SourceObject.ObjectId; + + /// public IGameObject? SourceObject => Service.Get().SearchById(this.SourceId); - private FFXIVClientStructs.FFXIV.Client.Game.Status* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Status*)this.Address; + public static bool operator ==(Status x, Status y) => x.Equals(y); + + public static bool operator !=(Status x, Status y) => !(x == y); + + /// + public bool Equals(IStatus? other) + { + return this.StatusId == other.StatusId && this.SourceId == other.SourceId && this.Param == other.Param && this.RemainingTime == other.RemainingTime; + } + + /// + public override bool Equals([NotNullWhen(true)] object? obj) + { + return obj is Status fate && this.Equals(fate); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(this.StatusId, this.SourceId, this.Param, this.RemainingTime); + } } diff --git a/Dalamud/Game/ClientState/Statuses/StatusList.cs b/Dalamud/Game/ClientState/Statuses/StatusList.cs index a38e45ea3..50d242d33 100644 --- a/Dalamud/Game/ClientState/Statuses/StatusList.cs +++ b/Dalamud/Game/ClientState/Statuses/StatusList.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using CSStatus = FFXIVClientStructs.FFXIV.Client.Game.Status; + namespace Dalamud.Game.ClientState.Statuses; /// @@ -14,7 +16,7 @@ public sealed unsafe partial class StatusList /// Initializes a new instance of the class. /// /// Address of the status list. - internal StatusList(IntPtr address) + internal StatusList(nint address) { this.Address = address; } @@ -24,14 +26,14 @@ public sealed unsafe partial class StatusList /// /// Pointer to the status list. internal unsafe StatusList(void* pointer) - : this((IntPtr)pointer) + : this((nint)pointer) { } /// /// Gets the address of the status list in memory. /// - public IntPtr Address { get; } + public nint Address { get; } /// /// Gets the amount of status effect slots the actor has. @@ -47,7 +49,7 @@ public sealed unsafe partial class StatusList /// /// Status Index. /// The status at the specified index. - public Status? this[int index] + public IStatus? this[int index] { get { @@ -64,7 +66,7 @@ public sealed unsafe partial class StatusList /// /// The address of the status list in memory. /// The status object containing the requested data. - public static StatusList? CreateStatusListReference(IntPtr address) + public static StatusList? CreateStatusListReference(nint address) { // The use case for CreateStatusListReference and CreateStatusReference to be static is so // fake status lists can be generated. Since they aren't exposed as services, it's either @@ -74,7 +76,7 @@ public sealed unsafe partial class StatusList if (clientState.LocalContentId == 0) return null; - if (address == IntPtr.Zero) + if (address == 0) return null; return new StatusList(address); @@ -85,17 +87,17 @@ public sealed unsafe partial class StatusList /// /// The address of the status effect in memory. /// The status object containing the requested data. - public static Status? CreateStatusReference(IntPtr address) + public static IStatus? CreateStatusReference(nint address) { var clientState = Service.Get(); if (clientState.LocalContentId == 0) return null; - if (address == IntPtr.Zero) + if (address == 0) return null; - return new Status(address); + return new Status((CSStatus*)address); } /// @@ -103,22 +105,22 @@ public sealed unsafe partial class StatusList /// /// The index of the status. /// The memory address of the status. - public IntPtr GetStatusAddress(int index) + public nint GetStatusAddress(int index) { if (index < 0 || index >= this.Length) - return IntPtr.Zero; + return 0; - return (IntPtr)Unsafe.AsPointer(ref this.Struct->Status[index]); + return (nint)Unsafe.AsPointer(ref this.Struct->Status[index]); } } /// /// This collection represents the status effects an actor is afflicted by. /// -public sealed partial class StatusList : IReadOnlyCollection, ICollection +public sealed partial class StatusList : IReadOnlyCollection, ICollection { /// - int IReadOnlyCollection.Count => this.Length; + int IReadOnlyCollection.Count => this.Length; /// int ICollection.Count => this.Length; @@ -130,17 +132,9 @@ public sealed partial class StatusList : IReadOnlyCollection, ICollectio object ICollection.SyncRoot => this; /// - public IEnumerator GetEnumerator() + public IEnumerator GetEnumerator() { - for (var i = 0; i < this.Length; i++) - { - var status = this[i]; - - if (status == null || status.StatusId == 0) - continue; - - yield return status; - } + return new Enumerator(this); } /// @@ -155,4 +149,39 @@ public sealed partial class StatusList : IReadOnlyCollection, ICollectio index++; } } + + private struct Enumerator(StatusList statusList) : IEnumerator + { + private int index = 0; + + public IStatus Current { get; private set; } + + object IEnumerator.Current => this.Current; + + public bool MoveNext() + { + if (this.index == statusList.Length) return false; + + for (; this.index < statusList.Length; this.index++) + { + var status = statusList[this.index]; + if (status != null && status.StatusId != 0) + { + this.Current = status; + return true; + } + } + + return false; + } + + public void Reset() + { + this.index = 0; + } + + public void Dispose() + { + } + } } diff --git a/Dalamud/Game/ClientState/Structs/StatusEffect.cs b/Dalamud/Game/ClientState/Structs/StatusEffect.cs deleted file mode 100644 index 2a60a7d3b..000000000 --- a/Dalamud/Game/ClientState/Structs/StatusEffect.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Dalamud.Game.ClientState.Structs; - -/// -/// Native memory representation of a FFXIV status effect. -/// -[StructLayout(LayoutKind.Sequential)] -public struct StatusEffect -{ - /// - /// The effect ID. - /// - public short EffectId; - - /// - /// How many stacks are present. - /// - public byte StackCount; - - /// - /// Additional parameters. - /// - public byte Param; - - /// - /// The duration remaining. - /// - public float Duration; - - /// - /// The ID of the actor that caused this effect. - /// - public int OwnerId; -}