using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using CSStatus = FFXIVClientStructs.FFXIV.Client.Game.Status; namespace Dalamud.Game.ClientState.Statuses; /// /// This collection represents the status effects an actor is afflicted by. /// public sealed unsafe partial class StatusList { /// /// Initializes a new instance of the class. /// /// Address of the status list. internal StatusList(nint address) { this.Address = address; } /// /// Initializes a new instance of the class. /// /// Pointer to the status list. internal unsafe StatusList(void* pointer) : this((nint)pointer) { } /// /// Gets the address of the status list in memory. /// public nint Address { get; } /// /// Gets the amount of status effect slots the actor has. /// public int Length => Struct->NumValidStatuses; private static int StatusSize { get; } = Marshal.SizeOf(); private FFXIVClientStructs.FFXIV.Client.Game.StatusManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.StatusManager*)this.Address; /// /// Get a status effect at the specified index. /// /// Status Index. /// The status at the specified index. public IStatus? this[int index] { get { if (index < 0 || index > this.Length) return null; var addr = this.GetStatusAddress(index); return CreateStatusReference(addr); } } /// /// Create a reference to an FFXIV actor status list. /// /// The address of the status list in memory. /// The status object containing the requested data. public static StatusList? CreateStatusListReference(nint address) { if (address == IntPtr.Zero) return null; // 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 // here or somewhere else. var clientState = Service.Get(); if (clientState.LocalContentId == 0) return null; if (address == 0) return null; return new StatusList(address); } /// /// Create a reference to an FFXIV actor status. /// /// The address of the status effect in memory. /// The status object containing the requested data. public static IStatus? CreateStatusReference(nint address) { if (address == IntPtr.Zero) return null; if (address == 0) return null; return new Status((CSStatus*)address); } /// /// Gets the address of the status at the specific index in the status list. /// /// The index of the status. /// The memory address of the status. public nint GetStatusAddress(int index) { if (index < 0 || index >= this.Length) return 0; 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 { /// int IReadOnlyCollection.Count => this.Length; /// int ICollection.Count => this.Length; /// bool ICollection.IsSynchronized => false; /// object ICollection.SyncRoot => this; /// public IEnumerator GetEnumerator() { return new Enumerator(this); } /// IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); /// void ICollection.CopyTo(Array array, int index) { for (var i = 0; i < this.Length; i++) { array.SetValue(this[i], index); index++; } } private struct Enumerator(StatusList statusList) : IEnumerator { private int index = -1; public IStatus Current { get; private set; } object IEnumerator.Current => this.Current; public bool MoveNext() { while (++this.index < statusList.Length) { var status = statusList[this.index]; if (status != null && status.StatusId != 0) { this.Current = status; return true; } } this.Current = default; return false; } public void Reset() { this.index = -1; } public void Dispose() { } } }