using System; using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; using Serilog; namespace Dalamud.Game.ClientState.Party; /// /// This collection represents the actors present in your party or alliance. /// [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 internal sealed unsafe partial class PartyList : IServiceType, IPartyList { private const int GroupLength = 8; private const int AllianceLength = 20; [ServiceManager.ServiceDependency] private readonly ClientState clientState = Service.Get(); private readonly ClientStateAddressResolver address; [ServiceManager.ServiceConstructor] private PartyList() { this.address = this.clientState.AddressResolver; Log.Verbose($"Group manager address 0x{this.address.GroupManager.ToInt64():X}"); } /// public int Length => this.GroupManagerStruct->MemberCount; /// public uint PartyLeaderIndex => this.GroupManagerStruct->PartyLeaderIndex; /// public bool IsAlliance => this.GroupManagerStruct->AllianceFlags > 0; /// public IntPtr GroupManagerAddress => this.address.GroupManager; /// public IntPtr GroupListAddress => (IntPtr)GroupManagerStruct->PartyMembers; /// public IntPtr AllianceListAddress => (IntPtr)this.GroupManagerStruct->AllianceMembers; /// public long PartyId => this.GroupManagerStruct->PartyId; private static int PartyMemberSize { get; } = Marshal.SizeOf(); private FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager* GroupManagerStruct => (FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager*)this.GroupManagerAddress; /// 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); } } } /// public IntPtr GetPartyMemberAddress(int index) { if (index < 0 || index >= GroupLength) return IntPtr.Zero; return this.GroupListAddress + (index * PartyMemberSize); } /// public PartyMember? CreatePartyMemberReference(IntPtr address) { if (this.clientState.LocalContentId == 0) return null; if (address == IntPtr.Zero) return null; return new PartyMember(address); } /// public IntPtr GetAllianceMemberAddress(int index) { if (index < 0 || index >= AllianceLength) return IntPtr.Zero; return this.AllianceListAddress + (index * PartyMemberSize); } /// public PartyMember? CreateAllianceMemberReference(IntPtr address) { if (this.clientState.LocalContentId == 0) return null; if (address == IntPtr.Zero) return null; return new PartyMember(address); } } /// /// This collection represents the party members present in your party or alliance. /// internal sealed partial class PartyList { /// int IReadOnlyCollection.Count => this.Length; /// 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; } } /// IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); }