Merge pull request #2458 from Haselnussbomber/struct-enumerators

[API14] Use struct enumerators/types
This commit is contained in:
goat 2025-11-17 18:24:07 +01:00 committed by GitHub
commit ba0cf4c990
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 424 additions and 344 deletions

View file

@ -63,47 +63,37 @@ public interface IAetheryteEntry
} }
/// <summary> /// <summary>
/// Class representing an aetheryte entry available to the game. /// This struct represents an aetheryte entry available to the game.
/// </summary>
internal sealed class AetheryteEntry : IAetheryteEntry
{
private readonly TeleportInfo data;
/// <summary>
/// Initializes a new instance of the <see cref="AetheryteEntry"/> class.
/// </summary> /// </summary>
/// <param name="data">Data read from the Aetheryte List.</param> /// <param name="data">Data read from the Aetheryte List.</param>
internal AetheryteEntry(TeleportInfo data) internal readonly struct AetheryteEntry(TeleportInfo data) : IAetheryteEntry
{ {
this.data = data; /// <inheritdoc />
} public uint AetheryteId => data.AetheryteId;
/// <inheritdoc /> /// <inheritdoc />
public uint AetheryteId => this.data.AetheryteId; public uint TerritoryId => data.TerritoryId;
/// <inheritdoc /> /// <inheritdoc />
public uint TerritoryId => this.data.TerritoryId; public byte SubIndex => data.SubIndex;
/// <inheritdoc /> /// <inheritdoc />
public byte SubIndex => this.data.SubIndex; public byte Ward => data.Ward;
/// <inheritdoc /> /// <inheritdoc />
public byte Ward => this.data.Ward; public byte Plot => data.Plot;
/// <inheritdoc /> /// <inheritdoc />
public byte Plot => this.data.Plot; public uint GilCost => data.GilCost;
/// <inheritdoc /> /// <inheritdoc />
public uint GilCost => this.data.GilCost; public bool IsFavourite => data.IsFavourite;
/// <inheritdoc /> /// <inheritdoc />
public bool IsFavourite => this.data.IsFavourite; public bool IsSharedHouse => data.IsSharedHouse;
/// <inheritdoc /> /// <inheritdoc />
public bool IsSharedHouse => this.data.IsSharedHouse; public bool IsApartment => data.IsApartment;
/// <inheritdoc />
public bool IsApartment => this.data.IsApartment;
/// <inheritdoc /> /// <inheritdoc />
public RowRef<Lumina.Excel.Sheets.Aetheryte> AetheryteData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Aetheryte>(this.AetheryteId); public RowRef<Lumina.Excel.Sheets.Aetheryte> AetheryteData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Aetheryte>(this.AetheryteId);

View file

@ -87,10 +87,7 @@ internal sealed partial class AetheryteList
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerator<IAetheryteEntry> GetEnumerator() public IEnumerator<IAetheryteEntry> GetEnumerator()
{ {
for (var i = 0; i < this.Length; i++) return new Enumerator(this);
{
yield return this[i];
}
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -98,4 +95,30 @@ internal sealed partial class AetheryteList
{ {
return this.GetEnumerator(); return this.GetEnumerator();
} }
private struct Enumerator(AetheryteList aetheryteList) : IEnumerator<IAetheryteEntry>
{
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()
{
}
}
} }

View file

@ -8,6 +8,9 @@ using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.UI; 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; namespace Dalamud.Game.ClientState.Buddy;
/// <summary> /// <summary>
@ -21,7 +24,7 @@ namespace Dalamud.Game.ClientState.Buddy;
#pragma warning restore SA1015 #pragma warning restore SA1015
internal sealed partial class BuddyList : IServiceType, IBuddyList internal sealed partial class BuddyList : IServiceType, IBuddyList
{ {
private const uint InvalidObjectID = 0xE0000000; private const uint InvalidEntityId = 0xE0000000;
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly ClientState clientState = Service<ClientState>.Get(); private readonly ClientState clientState = Service<ClientState>.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;
/// <inheritdoc/> /// <inheritdoc/>
public IBuddyMember? this[int index] public IBuddyMember? this[int index]
@ -82,37 +85,37 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList
} }
/// <inheritdoc/> /// <inheritdoc/>
public unsafe IntPtr GetCompanionBuddyMemberAddress() public unsafe nint GetCompanionBuddyMemberAddress()
{ {
return (IntPtr)this.BuddyListStruct->CompanionInfo.Companion; return (nint)this.BuddyListStruct->CompanionInfo.Companion;
} }
/// <inheritdoc/> /// <inheritdoc/>
public unsafe IntPtr GetPetBuddyMemberAddress() public unsafe nint GetPetBuddyMemberAddress()
{ {
return (IntPtr)this.BuddyListStruct->PetInfo.Pet; return (nint)this.BuddyListStruct->PetInfo.Pet;
} }
/// <inheritdoc/> /// <inheritdoc/>
public unsafe IntPtr GetBattleBuddyMemberAddress(int index) public unsafe nint GetBattleBuddyMemberAddress(int index)
{ {
if (index < 0 || index >= 3) 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]);
} }
/// <inheritdoc/> /// <inheritdoc/>
public IBuddyMember? CreateBuddyMemberReference(IntPtr address) public unsafe IBuddyMember? CreateBuddyMemberReference(nint address)
{ {
if (address == 0)
return null;
if (this.clientState.LocalContentId == 0) if (this.clientState.LocalContentId == 0)
return null; return null;
if (address == IntPtr.Zero) var buddy = new BuddyMember((CSBuddyMember*)address);
return null; if (buddy.EntityId == InvalidEntityId)
var buddy = new BuddyMember(address);
if (buddy.ObjectId == InvalidObjectID)
return null; return null;
return buddy; return buddy;
@ -130,12 +133,35 @@ internal sealed partial class BuddyList
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerator<IBuddyMember> GetEnumerator() public IEnumerator<IBuddyMember> GetEnumerator()
{ {
for (var i = 0; i < this.Length; i++) return new Enumerator(this);
{
yield return this[i];
}
} }
/// <inheritdoc/> /// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
private struct Enumerator(BuddyList buddyList) : IEnumerator<IBuddyMember>
{
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()
{
}
}
} }

View file

@ -1,20 +1,24 @@
using System.Diagnostics.CodeAnalysis;
using Dalamud.Data; using Dalamud.Data;
using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Objects.Types;
using Lumina.Excel; using Lumina.Excel;
using CSBuddyMember = FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember;
namespace Dalamud.Game.ClientState.Buddy; namespace Dalamud.Game.ClientState.Buddy;
/// <summary> /// <summary>
/// Interface representing represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties. /// Interface representing represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
/// </summary> /// </summary>
public interface IBuddyMember public interface IBuddyMember : IEquatable<IBuddyMember>
{ {
/// <summary> /// <summary>
/// Gets the address of the buddy in memory. /// Gets the address of the buddy in memory.
/// </summary> /// </summary>
IntPtr Address { get; } nint Address { get; }
/// <summary> /// <summary>
/// Gets the object ID of this buddy. /// Gets the object ID of this buddy.
@ -67,42 +71,34 @@ public interface IBuddyMember
} }
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
internal unsafe class BuddyMember : IBuddyMember /// <param name="ptr">A pointer to the BuddyMember.</param>
internal readonly unsafe struct BuddyMember(CSBuddyMember* ptr) : IBuddyMember
{ {
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly ObjectTable objectTable = Service<ObjectTable>.Get(); private readonly ObjectTable objectTable = Service<ObjectTable>.Get();
/// <summary> /// <inheritdoc />
/// Initializes a new instance of the <see cref="BuddyMember"/> class. public nint Address => (nint)ptr;
/// </summary>
/// <param name="address">Buddy address.</param>
internal BuddyMember(IntPtr address)
{
this.Address = address;
}
/// <inheritdoc /> /// <inheritdoc />
public IntPtr Address { get; } public uint ObjectId => this.EntityId;
/// <inheritdoc /> /// <inheritdoc />
public uint ObjectId => this.Struct->EntityId; public uint EntityId => ptr->EntityId;
/// <inheritdoc /> /// <inheritdoc />
public uint EntityId => this.Struct->EntityId; public IGameObject? GameObject => this.objectTable.SearchById(this.EntityId);
/// <inheritdoc /> /// <inheritdoc />
public IGameObject? GameObject => this.objectTable.SearchById(this.ObjectId); public uint CurrentHP => ptr->CurrentHealth;
/// <inheritdoc /> /// <inheritdoc />
public uint CurrentHP => this.Struct->CurrentHealth; public uint MaxHP => ptr->MaxHealth;
/// <inheritdoc /> /// <inheritdoc />
public uint MaxHP => this.Struct->MaxHealth; public uint DataID => ptr->DataId;
/// <inheritdoc />
public uint DataID => this.Struct->DataId;
/// <inheritdoc /> /// <inheritdoc />
public RowRef<Lumina.Excel.Sheets.Mount> MountData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Mount>(this.DataID); public RowRef<Lumina.Excel.Sheets.Mount> MountData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Mount>(this.DataID);
@ -113,5 +109,25 @@ internal unsafe class BuddyMember : IBuddyMember
/// <inheritdoc /> /// <inheritdoc />
public RowRef<Lumina.Excel.Sheets.DawnGrowMember> TrustData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.DawnGrowMember>(this.DataID); public RowRef<Lumina.Excel.Sheets.DawnGrowMember> TrustData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.DawnGrowMember>(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);
/// <inheritdoc/>
public bool Equals(IBuddyMember? other)
{
return this.EntityId == other.EntityId;
}
/// <inheritdoc/>
public override bool Equals([NotNullWhen(true)] object? obj)
{
return obj is BuddyMember fate && this.Equals(fate);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.EntityId.GetHashCode();
}
} }

View file

@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Numerics; using System.Numerics;
using Dalamud.Data; using Dalamud.Data;
@ -6,10 +7,12 @@ using Dalamud.Memory;
using Lumina.Excel; using Lumina.Excel;
using CSFateContext = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext;
namespace Dalamud.Game.ClientState.Fates; namespace Dalamud.Game.ClientState.Fates;
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
public interface IFate : IEquatable<IFate> public interface IFate : IEquatable<IFate>
{ {
@ -111,133 +114,96 @@ public interface IFate : IEquatable<IFate>
/// <summary> /// <summary>
/// Gets the address of this Fate in memory. /// Gets the address of this Fate in memory.
/// </summary> /// </summary>
IntPtr Address { get; } nint Address { get; }
} }
/// <summary> /// <summary>
/// This class represents an FFXIV Fate. /// This struct represents a Fate.
/// </summary> /// </summary>
internal unsafe partial class Fate /// <param name="ptr">A pointer to the FateContext.</param>
{ internal readonly unsafe struct Fate(CSFateContext* ptr) : IFate
/// <summary>
/// Initializes a new instance of the <see cref="Fate"/> class.
/// </summary>
/// <param name="address">The address of this fate in memory.</param>
internal Fate(IntPtr address)
{
this.Address = address;
}
/// <inheritdoc />
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);
/// <summary>
/// Gets a value indicating whether this Fate is still valid in memory.
/// </summary>
/// <param name="fate">The fate to check.</param>
/// <returns>True or false.</returns>
public static bool IsValid(Fate fate)
{
var clientState = Service<ClientState>.GetNullable();
if (fate == null || clientState == null)
return false;
if (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<IFate>.Equals(IFate other) => this.FateId == other?.FateId;
/// <inheritdoc/>
public override bool Equals(object obj) => ((IEquatable<IFate>)this).Equals(obj as IFate);
/// <inheritdoc/>
public override int GetHashCode() => this.FateId.GetHashCode();
}
/// <summary>
/// This class represents an FFXIV Fate.
/// </summary>
internal unsafe partial class Fate : IFate
{ {
/// <inheritdoc /> /// <inheritdoc />
public ushort FateId => this.Struct->FateId; public nint Address => (nint)ptr;
/// <inheritdoc/>
public ushort FateId => ptr->FateId;
/// <inheritdoc/> /// <inheritdoc/>
public RowRef<Lumina.Excel.Sheets.Fate> GameData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Fate>(this.FateId); public RowRef<Lumina.Excel.Sheets.Fate> GameData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Fate>(this.FateId);
/// <inheritdoc/> /// <inheritdoc/>
public int StartTimeEpoch => this.Struct->StartTimeEpoch; public int StartTimeEpoch => ptr->StartTimeEpoch;
/// <inheritdoc/> /// <inheritdoc/>
public short Duration => this.Struct->Duration; public short Duration => ptr->Duration;
/// <inheritdoc/> /// <inheritdoc/>
public long TimeRemaining => this.StartTimeEpoch + this.Duration - DateTimeOffset.Now.ToUnixTimeSeconds(); public long TimeRemaining => this.StartTimeEpoch + this.Duration - DateTimeOffset.Now.ToUnixTimeSeconds();
/// <inheritdoc/> /// <inheritdoc/>
public SeString Name => MemoryHelper.ReadSeString(&this.Struct->Name); public SeString Name => MemoryHelper.ReadSeString(&ptr->Name);
/// <inheritdoc/> /// <inheritdoc/>
public SeString Description => MemoryHelper.ReadSeString(&this.Struct->Description); public SeString Description => MemoryHelper.ReadSeString(&ptr->Description);
/// <inheritdoc/> /// <inheritdoc/>
public SeString Objective => MemoryHelper.ReadSeString(&this.Struct->Objective); public SeString Objective => MemoryHelper.ReadSeString(&ptr->Objective);
/// <inheritdoc/> /// <inheritdoc/>
public FateState State => (FateState)this.Struct->State; public FateState State => (FateState)ptr->State;
/// <inheritdoc/> /// <inheritdoc/>
public byte HandInCount => this.Struct->HandInCount; public byte HandInCount => ptr->HandInCount;
/// <inheritdoc/> /// <inheritdoc/>
public byte Progress => this.Struct->Progress; public byte Progress => ptr->Progress;
/// <inheritdoc/> /// <inheritdoc/>
public bool HasBonus => this.Struct->IsBonus; public bool HasBonus => ptr->IsBonus;
/// <inheritdoc/> /// <inheritdoc/>
public uint IconId => this.Struct->IconId; public uint IconId => ptr->IconId;
/// <inheritdoc/> /// <inheritdoc/>
public byte Level => this.Struct->Level; public byte Level => ptr->Level;
/// <inheritdoc/> /// <inheritdoc/>
public byte MaxLevel => this.Struct->MaxLevel; public byte MaxLevel => ptr->MaxLevel;
/// <inheritdoc/> /// <inheritdoc/>
public Vector3 Position => this.Struct->Location; public Vector3 Position => ptr->Location;
/// <inheritdoc/> /// <inheritdoc/>
public float Radius => this.Struct->Radius; public float Radius => ptr->Radius;
/// <inheritdoc/> /// <inheritdoc/>
public uint MapIconId => this.Struct->MapIconId; public uint MapIconId => ptr->MapIconId;
/// <summary> /// <summary>
/// Gets the territory this <see cref="Fate"/> is located in. /// Gets the territory this <see cref="Fate"/> is located in.
/// </summary> /// </summary>
public RowRef<Lumina.Excel.Sheets.TerritoryType> TerritoryType => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(this.Struct->MapMarkers[0].MapMarkerData.TerritoryTypeId); public RowRef<Lumina.Excel.Sheets.TerritoryType> TerritoryType => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(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);
/// <inheritdoc/>
public bool Equals(IFate? other)
{
return this.FateId == other.FateId;
}
/// <inheritdoc/>
public override bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Fate fate && this.Equals(fate);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.FateId.GetHashCode();
}
} }

View file

@ -5,6 +5,7 @@ using Dalamud.IoC;
using Dalamud.IoC.Internal; using Dalamud.IoC.Internal;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using CSFateContext = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext;
using CSFateManager = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager; using CSFateManager = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager;
namespace Dalamud.Game.ClientState.Fates; namespace Dalamud.Game.ClientState.Fates;
@ -25,7 +26,7 @@ internal sealed partial class FateTable : IServiceType, IFateTable
} }
/// <inheritdoc/> /// <inheritdoc/>
public unsafe IntPtr Address => (nint)CSFateManager.Instance(); public unsafe nint Address => (nint)CSFateManager.Instance();
/// <inheritdoc/> /// <inheritdoc/>
public unsafe int Length public unsafe int Length
@ -72,30 +73,29 @@ internal sealed partial class FateTable : IServiceType, IFateTable
} }
/// <inheritdoc/> /// <inheritdoc/>
public unsafe IntPtr GetFateAddress(int index) public unsafe nint GetFateAddress(int index)
{ {
if (index >= this.Length) if (index >= this.Length)
return IntPtr.Zero; return 0;
var fateManager = CSFateManager.Instance(); var fateManager = CSFateManager.Instance();
if (fateManager == null) if (fateManager == null)
return IntPtr.Zero; return 0;
return (IntPtr)fateManager->Fates[index].Value; return (nint)fateManager->Fates[index].Value;
} }
/// <inheritdoc/> /// <inheritdoc/>
public IFate? CreateFateReference(IntPtr offset) public unsafe IFate? CreateFateReference(IntPtr address)
{ {
var clientState = Service<ClientState>.Get(); if (address == 0)
return null;
var clientState = Service<ClientState>.Get();
if (clientState.LocalContentId == 0) if (clientState.LocalContentId == 0)
return null; return null;
if (offset == IntPtr.Zero) return new Fate((CSFateContext*)address);
return null;
return new Fate(offset);
} }
} }
@ -110,12 +110,35 @@ internal sealed partial class FateTable
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerator<IFate> GetEnumerator() public IEnumerator<IFate> GetEnumerator()
{ {
for (var i = 0; i < this.Length; i++) return new Enumerator(this);
{
yield return this[i];
}
} }
/// <inheritdoc/> /// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
private struct Enumerator(FateTable fateTable) : IEnumerator<IFate>
{
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()
{
}
}
} }

View file

@ -12,8 +12,6 @@ using Dalamud.Utility;
using FFXIVClientStructs.Interop; using FFXIVClientStructs.Interop;
using Microsoft.Extensions.ObjectPool;
using CSGameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject; using CSGameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
using CSGameObjectManager = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObjectManager; 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 ClientState clientState;
private readonly CachedEntry[] cachedObjectTable; private readonly CachedEntry[] cachedObjectTable;
private readonly Enumerator?[] frameworkThreadEnumerators = new Enumerator?[4];
[ServiceManager.ServiceConstructor] [ServiceManager.ServiceConstructor]
private unsafe ObjectTable(ClientState clientState) private unsafe ObjectTable(ClientState clientState)
{ {
@ -47,9 +43,6 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
this.cachedObjectTable = new CachedEntry[objectTableLength]; this.cachedObjectTable = new CachedEntry[objectTableLength];
for (var i = 0; i < this.cachedObjectTable.Length; i++) for (var i = 0; i < this.cachedObjectTable.Length; i++)
this.cachedObjectTable[i] = new(nativeObjectTable.GetPointer(i)); this.cachedObjectTable[i] = new(nativeObjectTable.GetPointer(i));
for (var i = 0; i < this.frameworkThreadEnumerators.Length; i++)
this.frameworkThreadEnumerators[i] = new(this, i);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -239,30 +232,14 @@ internal sealed partial class ObjectTable
public IEnumerator<IGameObject> GetEnumerator() public IEnumerator<IGameObject> GetEnumerator()
{ {
ThreadSafety.AssertMainThread(); ThreadSafety.AssertMainThread();
return new Enumerator(this);
// 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);
} }
/// <inheritdoc/> /// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
private sealed class Enumerator(ObjectTable owner, int slotId) : IEnumerator<IGameObject>, IResettable private struct Enumerator(ObjectTable owner) : IEnumerator<IGameObject>
{ {
private ObjectTable? owner = owner;
private int index = -1; private int index = -1;
public IGameObject Current { get; private set; } = null!; public IGameObject Current { get; private set; } = null!;
@ -274,7 +251,7 @@ internal sealed partial class ObjectTable
if (this.index == objectTableLength) if (this.index == objectTableLength)
return false; return false;
var cache = this.owner!.cachedObjectTable.AsSpan(); var cache = owner.cachedObjectTable.AsSpan();
for (this.index++; this.index < objectTableLength; this.index++) for (this.index++; this.index < objectTableLength; this.index++)
{ {
if (cache[this.index].Update() is { } ao) if (cache[this.index].Update() is { } ao)
@ -291,17 +268,6 @@ internal sealed partial class ObjectTable
public void Dispose() public void Dispose()
{ {
if (this.owner is not { } o)
return;
if (slotId != -1)
o.frameworkThreadEnumerators[slotId] = this;
}
public bool TryReset()
{
this.Reset();
return true;
} }
} }
} }

View file

@ -8,6 +8,7 @@ using Dalamud.IoC.Internal;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using CSGroupManager = FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager; using CSGroupManager = FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager;
using CSPartyMember = FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember;
namespace Dalamud.Game.ClientState.Party; 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 bool IsAlliance => this.GroupManagerStruct->MainGroup.AllianceFlags > 0;
/// <inheritdoc/> /// <inheritdoc/>
public unsafe IntPtr GroupManagerAddress => (nint)CSGroupManager.Instance(); public unsafe nint GroupManagerAddress => (nint)CSGroupManager.Instance();
/// <inheritdoc/> /// <inheritdoc/>
public IntPtr GroupListAddress => (IntPtr)Unsafe.AsPointer(ref GroupManagerStruct->MainGroup.PartyMembers[0]); public nint GroupListAddress => (nint)Unsafe.AsPointer(ref GroupManagerStruct->MainGroup.PartyMembers[0]);
/// <inheritdoc/> /// <inheritdoc/>
public IntPtr AllianceListAddress => (IntPtr)Unsafe.AsPointer(ref this.GroupManagerStruct->MainGroup.AllianceMembers[0]); public nint AllianceListAddress => (nint)Unsafe.AsPointer(ref this.GroupManagerStruct->MainGroup.AllianceMembers[0]);
/// <inheritdoc/> /// <inheritdoc/>
public long PartyId => this.GroupManagerStruct->MainGroup.PartyId; public long PartyId => this.GroupManagerStruct->MainGroup.PartyId;
private static int PartyMemberSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember>(); private static int PartyMemberSize { get; } = Marshal.SizeOf<CSPartyMember>();
private FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager* GroupManagerStruct => (FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager*)this.GroupManagerAddress; private CSGroupManager* GroupManagerStruct => (CSGroupManager*)this.GroupManagerAddress;
/// <inheritdoc/> /// <inheritdoc/>
public IPartyMember? this[int index] public IPartyMember? this[int index]
@ -80,45 +81,45 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList
} }
/// <inheritdoc/> /// <inheritdoc/>
public IntPtr GetPartyMemberAddress(int index) public nint GetPartyMemberAddress(int index)
{ {
if (index < 0 || index >= GroupLength) if (index < 0 || index >= GroupLength)
return IntPtr.Zero; return 0;
return this.GroupListAddress + (index * PartyMemberSize); return this.GroupListAddress + (index * PartyMemberSize);
} }
/// <inheritdoc/> /// <inheritdoc/>
public IPartyMember? CreatePartyMemberReference(IntPtr address) public IPartyMember? CreatePartyMemberReference(nint address)
{ {
if (this.clientState.LocalContentId == 0) if (this.clientState.LocalContentId == 0)
return null; return null;
if (address == IntPtr.Zero) if (address == 0)
return null; return null;
return new PartyMember(address); return new PartyMember((CSPartyMember*)address);
} }
/// <inheritdoc/> /// <inheritdoc/>
public IntPtr GetAllianceMemberAddress(int index) public nint GetAllianceMemberAddress(int index)
{ {
if (index < 0 || index >= AllianceLength) if (index < 0 || index >= AllianceLength)
return IntPtr.Zero; return 0;
return this.AllianceListAddress + (index * PartyMemberSize); return this.AllianceListAddress + (index * PartyMemberSize);
} }
/// <inheritdoc/> /// <inheritdoc/>
public IPartyMember? CreateAllianceMemberReference(IntPtr address) public IPartyMember? CreateAllianceMemberReference(nint address)
{ {
if (this.clientState.LocalContentId == 0) if (this.clientState.LocalContentId == 0)
return null; return null;
if (address == IntPtr.Zero) if (address == 0)
return null; return null;
return new PartyMember(address); return new PartyMember((CSPartyMember*)address);
} }
} }
@ -133,18 +134,44 @@ internal sealed partial class PartyList
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerator<IPartyMember> GetEnumerator() public IEnumerator<IPartyMember> GetEnumerator()
{ {
// Normally using Length results in a recursion crash, however we know the party size via ptr. return new Enumerator(this);
for (var i = 0; i < this.Length; i++)
{
var member = this[i];
if (member == null)
break;
yield return member;
}
} }
/// <inheritdoc/> /// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
private struct Enumerator(PartyList partyList) : IEnumerator<IPartyMember>
{
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()
{
}
}
} }

View file

@ -1,26 +1,27 @@
using System.Diagnostics.CodeAnalysis;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using Dalamud.Data; using Dalamud.Data;
using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.ClientState.Statuses; using Dalamud.Game.ClientState.Statuses;
using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Memory;
using Lumina.Excel; using Lumina.Excel;
using CSPartyMember = FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember;
namespace Dalamud.Game.ClientState.Party; namespace Dalamud.Game.ClientState.Party;
/// <summary> /// <summary>
/// Interface representing a party member. /// Interface representing a party member.
/// </summary> /// </summary>
public interface IPartyMember public interface IPartyMember : IEquatable<IPartyMember>
{ {
/// <summary> /// <summary>
/// Gets the address of this party member in memory. /// Gets the address of this party member in memory.
/// </summary> /// </summary>
IntPtr Address { get; } nint Address { get; }
/// <summary> /// <summary>
/// Gets a list of buffs or debuffs applied to this party member. /// Gets a list of buffs or debuffs applied to this party member.
@ -108,69 +109,81 @@ public interface IPartyMember
} }
/// <summary> /// <summary>
/// This class represents a party member in the group manager. /// This struct represents a party member in the group manager.
/// </summary> /// </summary>
internal unsafe class PartyMember : IPartyMember /// <param name="ptr">A pointer to the PartyMember.</param>
internal unsafe readonly struct PartyMember(CSPartyMember* ptr) : IPartyMember
{ {
/// <summary> /// <inheritdoc/>
/// Initializes a new instance of the <see cref="PartyMember"/> class. public nint Address => (nint)ptr;
/// </summary>
/// <param name="address">Address of the party member.</param>
internal PartyMember(IntPtr address)
{
this.Address = address;
}
/// <inheritdoc/> /// <inheritdoc/>
public IntPtr Address { get; } public StatusList Statuses => new(&ptr->StatusManager);
/// <inheritdoc/> /// <inheritdoc/>
public StatusList Statuses => new(&this.Struct->StatusManager); public Vector3 Position => ptr->Position;
/// <inheritdoc/> /// <inheritdoc/>
public Vector3 Position => this.Struct->Position; public long ContentId => (long)ptr->ContentId;
/// <inheritdoc/> /// <inheritdoc/>
public long ContentId => (long)this.Struct->ContentId; public uint ObjectId => ptr->EntityId;
/// <inheritdoc/> /// <inheritdoc/>
public uint ObjectId => this.Struct->EntityId; public uint EntityId => ptr->EntityId;
/// <inheritdoc/>
public uint EntityId => this.Struct->EntityId;
/// <inheritdoc/> /// <inheritdoc/>
public IGameObject? GameObject => Service<ObjectTable>.Get().SearchById(this.EntityId); public IGameObject? GameObject => Service<ObjectTable>.Get().SearchById(this.EntityId);
/// <inheritdoc/> /// <inheritdoc/>
public uint CurrentHP => this.Struct->CurrentHP; public uint CurrentHP => ptr->CurrentHP;
/// <inheritdoc/> /// <inheritdoc/>
public uint MaxHP => this.Struct->MaxHP; public uint MaxHP => ptr->MaxHP;
/// <inheritdoc/> /// <inheritdoc/>
public ushort CurrentMP => this.Struct->CurrentMP; public ushort CurrentMP => ptr->CurrentMP;
/// <inheritdoc/> /// <inheritdoc/>
public ushort MaxMP => this.Struct->MaxMP; public ushort MaxMP => ptr->MaxMP;
/// <inheritdoc/> /// <inheritdoc/>
public RowRef<Lumina.Excel.Sheets.TerritoryType> Territory => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(this.Struct->TerritoryType); public RowRef<Lumina.Excel.Sheets.TerritoryType> Territory => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(ptr->TerritoryType);
/// <inheritdoc/> /// <inheritdoc/>
public RowRef<Lumina.Excel.Sheets.World> World => LuminaUtils.CreateRef<Lumina.Excel.Sheets.World>(this.Struct->HomeWorld); public RowRef<Lumina.Excel.Sheets.World> World => LuminaUtils.CreateRef<Lumina.Excel.Sheets.World>(ptr->HomeWorld);
/// <inheritdoc/> /// <inheritdoc/>
public SeString Name => SeString.Parse(this.Struct->Name); public SeString Name => SeString.Parse(ptr->Name);
/// <inheritdoc/> /// <inheritdoc/>
public byte Sex => this.Struct->Sex; public byte Sex => ptr->Sex;
/// <inheritdoc/> /// <inheritdoc/>
public RowRef<Lumina.Excel.Sheets.ClassJob> ClassJob => LuminaUtils.CreateRef<Lumina.Excel.Sheets.ClassJob>(this.Struct->ClassJob); public RowRef<Lumina.Excel.Sheets.ClassJob> ClassJob => LuminaUtils.CreateRef<Lumina.Excel.Sheets.ClassJob>(ptr->ClassJob);
/// <inheritdoc/> /// <inheritdoc/>
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);
/// <inheritdoc/>
public bool Equals(IPartyMember? other)
{
return this.EntityId == other.EntityId;
}
/// <inheritdoc/>
public override bool Equals([NotNullWhen(true)] object? obj)
{
return obj is PartyMember fate && this.Equals(fate);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.EntityId.GetHashCode();
}
} }

View file

@ -1,61 +1,49 @@
using System.Diagnostics.CodeAnalysis;
using Dalamud.Data; using Dalamud.Data;
using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Objects.Types;
using Lumina.Excel; using Lumina.Excel;
using CSStatus = FFXIVClientStructs.FFXIV.Client.Game.Status;
namespace Dalamud.Game.ClientState.Statuses; namespace Dalamud.Game.ClientState.Statuses;
/// <summary> /// <summary>
/// This class represents a status effect an actor is afflicted by. /// Interface representing a status.
/// </summary> /// </summary>
public unsafe class Status public interface IStatus : IEquatable<IStatus>
{ {
/// <summary>
/// Initializes a new instance of the <see cref="Status"/> class.
/// </summary>
/// <param name="address">Status address.</param>
internal Status(IntPtr address)
{
this.Address = address;
}
/// <summary> /// <summary>
/// Gets the address of the status in memory. /// Gets the address of the status in memory.
/// </summary> /// </summary>
public IntPtr Address { get; } nint Address { get; }
/// <summary> /// <summary>
/// Gets the status ID of this status. /// Gets the status ID of this status.
/// </summary> /// </summary>
public uint StatusId => this.Struct->StatusId; uint StatusId { get; }
/// <summary> /// <summary>
/// Gets the GameData associated with this status. /// Gets the GameData associated with this status.
/// </summary> /// </summary>
public RowRef<Lumina.Excel.Sheets.Status> GameData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Status>(this.Struct->StatusId); RowRef<Lumina.Excel.Sheets.Status> GameData { get; }
/// <summary> /// <summary>
/// Gets the parameter value of the status. /// Gets the parameter value of the status.
/// </summary> /// </summary>
public ushort Param => this.Struct->Param; ushort Param { get; }
/// <summary>
/// Gets the stack count of this status.
/// Only valid if this is a non-food status.
/// </summary>
[Obsolete($"Replaced with {nameof(Param)}", true)]
public byte StackCount => (byte)this.Struct->Param;
/// <summary> /// <summary>
/// Gets the time remaining of this status. /// Gets the time remaining of this status.
/// </summary> /// </summary>
public float RemainingTime => this.Struct->RemainingTime; float RemainingTime { get; }
/// <summary> /// <summary>
/// Gets the source ID of this status. /// Gets the source ID of this status.
/// </summary> /// </summary>
public uint SourceId => this.Struct->SourceObject.ObjectId; uint SourceId { get; }
/// <summary> /// <summary>
/// Gets the source actor associated with this status. /// Gets the source actor associated with this status.
@ -63,7 +51,55 @@ public unsafe class Status
/// <remarks> /// <remarks>
/// This iterates the actor table, it should be used with care. /// This iterates the actor table, it should be used with care.
/// </remarks> /// </remarks>
IGameObject? SourceObject { get; }
}
/// <summary>
/// This struct represents a status effect an actor is afflicted by.
/// </summary>
/// <param name="ptr">A pointer to the Status.</param>
internal unsafe readonly struct Status(CSStatus* ptr) : IStatus
{
/// <inheritdoc/>
public nint Address => (nint)ptr;
/// <inheritdoc/>
public uint StatusId => ptr->StatusId;
/// <inheritdoc/>
public RowRef<Lumina.Excel.Sheets.Status> GameData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Status>(ptr->StatusId);
/// <inheritdoc/>
public ushort Param => ptr->Param;
/// <inheritdoc/>
public float RemainingTime => ptr->RemainingTime;
/// <inheritdoc/>
public uint SourceId => ptr->SourceObject.ObjectId;
/// <inheritdoc/>
public IGameObject? SourceObject => Service<ObjectTable>.Get().SearchById(this.SourceId); public IGameObject? SourceObject => Service<ObjectTable>.Get().SearchById(this.SourceId);
private FFXIVClientStructs.FFXIV.Client.Game.Status* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Status*)this.Address; public static bool operator ==(Status x, Status y) => x.Equals(y);
public static bool operator !=(Status x, Status y) => !(x == y);
/// <inheritdoc/>
public bool Equals(IStatus? other)
{
return this.StatusId == other.StatusId && this.SourceId == other.SourceId && this.Param == other.Param && this.RemainingTime == other.RemainingTime;
}
/// <inheritdoc/>
public override bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Status fate && this.Equals(fate);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return HashCode.Combine(this.StatusId, this.SourceId, this.Param, this.RemainingTime);
}
} }

View file

@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using CSStatus = FFXIVClientStructs.FFXIV.Client.Game.Status;
namespace Dalamud.Game.ClientState.Statuses; namespace Dalamud.Game.ClientState.Statuses;
/// <summary> /// <summary>
@ -14,7 +16,7 @@ public sealed unsafe partial class StatusList
/// Initializes a new instance of the <see cref="StatusList"/> class. /// Initializes a new instance of the <see cref="StatusList"/> class.
/// </summary> /// </summary>
/// <param name="address">Address of the status list.</param> /// <param name="address">Address of the status list.</param>
internal StatusList(IntPtr address) internal StatusList(nint address)
{ {
this.Address = address; this.Address = address;
} }
@ -24,14 +26,14 @@ public sealed unsafe partial class StatusList
/// </summary> /// </summary>
/// <param name="pointer">Pointer to the status list.</param> /// <param name="pointer">Pointer to the status list.</param>
internal unsafe StatusList(void* pointer) internal unsafe StatusList(void* pointer)
: this((IntPtr)pointer) : this((nint)pointer)
{ {
} }
/// <summary> /// <summary>
/// Gets the address of the status list in memory. /// Gets the address of the status list in memory.
/// </summary> /// </summary>
public IntPtr Address { get; } public nint Address { get; }
/// <summary> /// <summary>
/// Gets the amount of status effect slots the actor has. /// Gets the amount of status effect slots the actor has.
@ -47,7 +49,7 @@ public sealed unsafe partial class StatusList
/// </summary> /// </summary>
/// <param name="index">Status Index.</param> /// <param name="index">Status Index.</param>
/// <returns>The status at the specified index.</returns> /// <returns>The status at the specified index.</returns>
public Status? this[int index] public IStatus? this[int index]
{ {
get get
{ {
@ -64,7 +66,7 @@ public sealed unsafe partial class StatusList
/// </summary> /// </summary>
/// <param name="address">The address of the status list in memory.</param> /// <param name="address">The address of the status list in memory.</param>
/// <returns>The status object containing the requested data.</returns> /// <returns>The status object containing the requested data.</returns>
public static StatusList? CreateStatusListReference(IntPtr address) public static StatusList? CreateStatusListReference(nint address)
{ {
// The use case for CreateStatusListReference and CreateStatusReference to be static is so // 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 // 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) if (clientState.LocalContentId == 0)
return null; return null;
if (address == IntPtr.Zero) if (address == 0)
return null; return null;
return new StatusList(address); return new StatusList(address);
@ -85,17 +87,17 @@ public sealed unsafe partial class StatusList
/// </summary> /// </summary>
/// <param name="address">The address of the status effect in memory.</param> /// <param name="address">The address of the status effect in memory.</param>
/// <returns>The status object containing the requested data.</returns> /// <returns>The status object containing the requested data.</returns>
public static Status? CreateStatusReference(IntPtr address) public static IStatus? CreateStatusReference(nint address)
{ {
var clientState = Service<ClientState>.Get(); var clientState = Service<ClientState>.Get();
if (clientState.LocalContentId == 0) if (clientState.LocalContentId == 0)
return null; return null;
if (address == IntPtr.Zero) if (address == 0)
return null; return null;
return new Status(address); return new Status((CSStatus*)address);
} }
/// <summary> /// <summary>
@ -103,22 +105,22 @@ public sealed unsafe partial class StatusList
/// </summary> /// </summary>
/// <param name="index">The index of the status.</param> /// <param name="index">The index of the status.</param>
/// <returns>The memory address of the status.</returns> /// <returns>The memory address of the status.</returns>
public IntPtr GetStatusAddress(int index) public nint GetStatusAddress(int index)
{ {
if (index < 0 || index >= this.Length) 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]);
} }
} }
/// <summary> /// <summary>
/// This collection represents the status effects an actor is afflicted by. /// This collection represents the status effects an actor is afflicted by.
/// </summary> /// </summary>
public sealed partial class StatusList : IReadOnlyCollection<Status>, ICollection public sealed partial class StatusList : IReadOnlyCollection<IStatus>, ICollection
{ {
/// <inheritdoc/> /// <inheritdoc/>
int IReadOnlyCollection<Status>.Count => this.Length; int IReadOnlyCollection<IStatus>.Count => this.Length;
/// <inheritdoc/> /// <inheritdoc/>
int ICollection.Count => this.Length; int ICollection.Count => this.Length;
@ -130,17 +132,9 @@ public sealed partial class StatusList : IReadOnlyCollection<Status>, ICollectio
object ICollection.SyncRoot => this; object ICollection.SyncRoot => this;
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerator<Status> GetEnumerator() public IEnumerator<IStatus> GetEnumerator()
{ {
for (var i = 0; i < this.Length; i++) return new Enumerator(this);
{
var status = this[i];
if (status == null || status.StatusId == 0)
continue;
yield return status;
}
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -155,4 +149,39 @@ public sealed partial class StatusList : IReadOnlyCollection<Status>, ICollectio
index++; index++;
} }
} }
private struct Enumerator(StatusList statusList) : IEnumerator<IStatus>
{
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()
{
}
}
} }

View file

@ -1,35 +0,0 @@
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs;
/// <summary>
/// Native memory representation of a FFXIV status effect.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct StatusEffect
{
/// <summary>
/// The effect ID.
/// </summary>
public short EffectId;
/// <summary>
/// How many stacks are present.
/// </summary>
public byte StackCount;
/// <summary>
/// Additional parameters.
/// </summary>
public byte Param;
/// <summary>
/// The duration remaining.
/// </summary>
public float Duration;
/// <summary>
/// The ID of the actor that caused this effect.
/// </summary>
public int OwnerId;
}