diff --git a/Penumbra.GameData/Actors/ActorManager.Data.cs b/Penumbra.GameData/Actors/ActorManager.Data.cs index 235ba0a3..0a030b28 100644 --- a/Penumbra.GameData/Actors/ActorManager.Data.cs +++ b/Penumbra.GameData/Actors/ActorManager.Data.cs @@ -14,6 +14,7 @@ using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Group; using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Component.GUI; using Lumina.Excel.GeneratedSheets; using Lumina.Text; using Penumbra.GameData.Data; @@ -227,7 +228,7 @@ public sealed partial class ActorManager : IDisposable private unsafe bool SearchPlayerCustomize(Character* character, int idx, out ActorIdentifier id) { var other = (Character*)_objects.GetObjectAddress(idx); - if (other == null || !CustomizeData.Equals((CustomizeData*)character->CustomizeData, (CustomizeData*)other->CustomizeData)) + if (other == null || !CustomizeData.ScreenActorEquals((CustomizeData*)character->CustomizeData, (CustomizeData*)other->CustomizeData)) { id = ActorIdentifier.Invalid; return false; @@ -246,14 +247,23 @@ public sealed partial class ActorManager : IDisposable private unsafe ActorIdentifier SearchPlayersCustomize(Character* gameObject) { + static bool Compare(Character* a, Character* b) + { + var data1 = (CustomizeData*)a->CustomizeData; + var data2 = (CustomizeData*)b->CustomizeData; + var equals = CustomizeData.ScreenActorEquals(data1, data2); + return equals; + } + for (var i = 0; i < (int)ScreenActor.CutsceneStart; i += 2) { var obj = (GameObject*)_objects.GetObjectAddress(i); if (obj != null && obj->ObjectKind is (byte)ObjectKind.Player - && CustomizeData.Equals((CustomizeData*)gameObject->CustomizeData, (CustomizeData*)((Character*)obj)->CustomizeData)) + && Compare(gameObject, (Character*)obj)) return FromObject(obj, out _, false, true); } + return ActorIdentifier.Invalid; } @@ -281,15 +291,18 @@ public sealed partial class ActorManager : IDisposable public unsafe bool ResolvePvPBannerPlayer(ScreenActor type, out ActorIdentifier id) { id = ActorIdentifier.Invalid; - var addon = _gameGui.GetAddonByName("PvPMKSIntroduction"); - if (addon == IntPtr.Zero) + if (!_clientState.IsPvPExcludingDen) + return false; + + var addon = (AtkUnitBase*)_gameGui.GetAddonByName("PvPMap"); + if (addon == null || addon->IsVisible) return false; var obj = (Character*)_objects.GetObjectAddress((int)type); if (obj == null) return false; - var identifier = type switch + id = type switch { ScreenActor.CharacterScreen => SearchPlayersCustomize(obj), ScreenActor.ExamineScreen => SearchPlayersCustomize(obj), diff --git a/Penumbra.GameData/Structs/CustomizeData.cs b/Penumbra.GameData/Structs/CustomizeData.cs index 1524ae11..c60ee746 100644 --- a/Penumbra.GameData/Structs/CustomizeData.cs +++ b/Penumbra.GameData/Structs/CustomizeData.cs @@ -1,8 +1,11 @@ using System; +using System.Runtime.InteropServices; +using System.Text; using Penumbra.String.Functions; namespace Penumbra.GameData.Structs; - + +[StructLayout(LayoutKind.Sequential, Size = Size)] public unsafe struct CustomizeData : IEquatable< CustomizeData > { public const int Size = 26; @@ -40,12 +43,18 @@ public unsafe struct CustomizeData : IEquatable< CustomizeData > } } - public static bool Equals( CustomizeData* lhs, CustomizeData* rhs ) - => MemoryUtility.MemCmpUnchecked( lhs, rhs, Size ) == 0; - public override bool Equals( object? obj ) => obj is CustomizeData other && Equals( other ); + public static bool Equals(CustomizeData* lhs, CustomizeData* rhs) + => MemoryUtility.MemCmpUnchecked(lhs, rhs, Size) == 0; + + /// Compare Gender and then only from Height onwards, because all screen actors are set to Height 50, + /// the Race is implicitly included in the subrace (after height), + /// and the body type is irrelevant for players.> + public static bool ScreenActorEquals(CustomizeData* lhs, CustomizeData* rhs) + => lhs->Data[1] == rhs->Data[1] && MemoryUtility.MemCmpUnchecked(lhs->Data + 4, rhs->Data + 4, Size - 4) == 0; + public override int GetHashCode() { fixed( byte* ptr = Data ) @@ -65,6 +74,17 @@ public unsafe struct CustomizeData : IEquatable< CustomizeData > } } + public string WriteBytes() + { + var sb = new StringBuilder(Size * 3); + for (var i = 0; i < Size - 1; ++i) + { + sb.Append($"{Data[i]:X2} "); + } + sb.Append($"{Data[Size - 1]:X2}"); + return sb.ToString(); + } + public bool LoadBase64( string base64 ) { var buffer = stackalloc byte[Size]; diff --git a/Penumbra/Collections/IndividualCollections.Access.cs b/Penumbra/Collections/IndividualCollections.Access.cs index 73e06fef..2e421807 100644 --- a/Penumbra/Collections/IndividualCollections.Access.cs +++ b/Penumbra/Collections/IndividualCollections.Access.cs @@ -72,51 +72,73 @@ public sealed partial class IndividualCollections : IReadOnlyList< (string Displ return false; } case IdentifierType.Npc: return _individuals.TryGetValue( identifier, out collection ); - case IdentifierType.Special: return CheckWorlds( ConvertSpecialIdentifier( identifier ), out collection ); + case IdentifierType.Special: return CheckWorlds( ConvertSpecialIdentifier( identifier ).Item1, out collection ); } collection = null; return false; } - public ActorIdentifier ConvertSpecialIdentifier( ActorIdentifier identifier ) + public enum SpecialResult + { + PartyBanner, + PvPBanner, + Mahjong, + CharacterScreen, + FittingRoom, + DyePreview, + Portrait, + Inspect, + Card, + Glamour, + Invalid, + } + + public (ActorIdentifier, SpecialResult) ConvertSpecialIdentifier( ActorIdentifier identifier ) { if( identifier.Type != IdentifierType.Special ) { - return identifier; + return ( identifier, SpecialResult.Invalid ); } - if( _actorManager.ResolvePartyBannerPlayer( identifier.Special, out var id ) - || _actorManager.ResolvePvPBannerPlayer( identifier.Special, out id ) - || _actorManager.ResolveMahjongPlayer( identifier.Special, out id ) ) + if( _actorManager.ResolvePartyBannerPlayer( identifier.Special, out var id ) ) { - return identifier; + return ( id, SpecialResult.PartyBanner ); + } + + if( _actorManager.ResolvePvPBannerPlayer( identifier.Special, out id ) ) + { + return ( id, SpecialResult.PvPBanner ); + } + + if( _actorManager.ResolveMahjongPlayer( identifier.Special, out id ) ) + { + return ( id, SpecialResult.Mahjong ); } switch( identifier.Special ) { - case ScreenActor.CharacterScreen when Penumbra.Config.UseCharacterCollectionInMainWindow: - case ScreenActor.FittingRoom when Penumbra.Config.UseCharacterCollectionInTryOn: - case ScreenActor.DyePreview when Penumbra.Config.UseCharacterCollectionInTryOn: - case ScreenActor.Portrait when Penumbra.Config.UseCharacterCollectionsInCards: - return _actorManager.GetCurrentPlayer(); + case ScreenActor.CharacterScreen when Penumbra.Config.UseCharacterCollectionInMainWindow: return ( _actorManager.GetCurrentPlayer(), SpecialResult.CharacterScreen ); + case ScreenActor.FittingRoom when Penumbra.Config.UseCharacterCollectionInTryOn: return ( _actorManager.GetCurrentPlayer(), SpecialResult.FittingRoom ); + case ScreenActor.DyePreview when Penumbra.Config.UseCharacterCollectionInTryOn: return ( _actorManager.GetCurrentPlayer(), SpecialResult.DyePreview ); + case ScreenActor.Portrait when Penumbra.Config.UseCharacterCollectionsInCards: return ( _actorManager.GetCurrentPlayer(), SpecialResult.Portrait ); case ScreenActor.ExamineScreen: { identifier = _actorManager.GetInspectPlayer(); if( identifier.IsValid ) { - return Penumbra.Config.UseCharacterCollectionInInspect ? identifier : ActorIdentifier.Invalid; + return ( Penumbra.Config.UseCharacterCollectionInInspect ? identifier : ActorIdentifier.Invalid, SpecialResult.Inspect ); } identifier = _actorManager.GetCardPlayer(); if( identifier.IsValid ) { - return Penumbra.Config.UseCharacterCollectionInInspect ? identifier : ActorIdentifier.Invalid; + return ( Penumbra.Config.UseCharacterCollectionInInspect ? identifier : ActorIdentifier.Invalid, SpecialResult.Card ); } - return Penumbra.Config.UseCharacterCollectionInTryOn ? _actorManager.GetGlamourPlayer() : ActorIdentifier.Invalid; + return ( Penumbra.Config.UseCharacterCollectionInTryOn ? _actorManager.GetGlamourPlayer() : ActorIdentifier.Invalid, SpecialResult.Glamour ); } - default: return identifier; + default: return ( identifier, SpecialResult.Invalid ); } } diff --git a/Penumbra/Interop/Resolver/PathResolver.Identification.cs b/Penumbra/Interop/Resolver/PathResolver.Identification.cs index abf85749..9ddb9941 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Identification.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Identification.cs @@ -10,7 +10,6 @@ using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.Util; using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character; -using CustomizeData = Penumbra.GameData.Structs.CustomizeData; using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject; using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind; @@ -18,48 +17,6 @@ namespace Penumbra.Interop.Resolver; public unsafe partial class PathResolver { - private static ResolveData IdentifyMahjong( GameObject* gameObject ) - { - static bool SearchPlayer( Character* character, int idx, out ActorIdentifier id ) - { - var other = ( Character* )Dalamud.Objects.GetObjectAddress( idx ); - if( other == null || !CustomizeData.Equals( ( CustomizeData* )character->CustomizeData, ( CustomizeData* )other->CustomizeData ) ) - { - id = ActorIdentifier.Invalid; - return false; - } - - id = Penumbra.Actors.FromObject( &other->GameObject, out _, false, true ); - return true; - } - - static ActorIdentifier SearchPlayers( Character* gameObject, int idx1, int idx2, int idx3 ) - => SearchPlayer( gameObject, idx1, out var id ) || SearchPlayer( gameObject, idx2, out id ) || SearchPlayer( gameObject, idx3, out id ) - ? id - : ActorIdentifier.Invalid; - - var identifier = gameObject->ObjectIndex switch - { - 0 => Penumbra.Actors.GetCurrentPlayer(), - 2 => Penumbra.Actors.FromObject( gameObject, out _, false, true ), - 4 => Penumbra.Actors.FromObject( gameObject, out _, false, true ), - 6 => Penumbra.Actors.FromObject( gameObject, out _, false, true ), - 240 => Penumbra.Actors.GetCurrentPlayer(), - 241 => SearchPlayers( ( Character* )gameObject, 2, 4, 6 ), - 242 => SearchPlayers( ( Character* )gameObject, 4, 2, 6 ), - 243 => SearchPlayers( ( Character* )gameObject, 6, 2, 4 ), - _ => ActorIdentifier.Invalid, - }; - - var collection = ( identifier.IsValid ? CollectionByIdentifier( identifier ) : null ) - ?? CheckYourself( identifier, gameObject ) - ?? CollectionByAttributes( gameObject ) - ?? Penumbra.CollectionManager.Default; - - return IdentifiedCache.Set( collection, identifier, gameObject ); - } - - // Identify the correct collection for a GameObject by index and name. public static ResolveData IdentifyCollection( GameObject* gameObject, bool useCache ) { @@ -89,7 +46,7 @@ public unsafe partial class PathResolver } // Aesthetician. The relevant actor is yourself, so use player collection when possible. - if( Dalamud.GameGui.GetAddonByName( "ScreenLog", 1 ) == IntPtr.Zero ) + if( Dalamud.GameGui.GetAddonByName( "ScreenLog" ) == IntPtr.Zero ) { var player = Penumbra.Actors.GetCurrentPlayer(); var collection2 = ( player.IsValid ? CollectionByIdentifier( player ) : null ) @@ -102,12 +59,11 @@ public unsafe partial class PathResolver var identifier = Penumbra.Actors.FromObject( gameObject, out var owner, true, false ); if( identifier.Type is IdentifierType.Special ) { - if( Penumbra.Config.UseNoModsInInspect && identifier.Special == ScreenActor.ExamineScreen ) + ( identifier, var type ) = Penumbra.CollectionManager.Individuals.ConvertSpecialIdentifier( identifier ); + if( Penumbra.Config.UseNoModsInInspect && type == IndividualCollections.SpecialResult.Inspect ) { return IdentifiedCache.Set( ModCollection.Empty, identifier, gameObject ); } - - identifier = Penumbra.CollectionManager.Individuals.ConvertSpecialIdentifier( identifier ); } var collection = CollectionByIdentifier( identifier )