diff --git a/Penumbra.PlayerWatch/CharacterFactory.cs b/Penumbra.PlayerWatch/CharacterFactory.cs new file mode 100644 index 00000000..806efafe --- /dev/null +++ b/Penumbra.PlayerWatch/CharacterFactory.cs @@ -0,0 +1,62 @@ +using System; +using System.Reflection; +using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Game.ClientState.Objects.SubKinds; +using Dalamud.Game.ClientState.Objects.Types; + +namespace Penumbra.PlayerWatch +{ + public static class CharacterFactory + { + private static ConstructorInfo? _characterConstructor = null; + + private static void Initialize() + { + _characterConstructor ??= typeof( Character ).GetConstructor( BindingFlags.NonPublic | BindingFlags.Instance, null, new[] + { + typeof( IntPtr ), + }, null )!; + } + + private static Character Character( IntPtr address ) + { + Initialize(); + return ( Character )_characterConstructor?.Invoke( new object[] + { + address, + } )!; + } + + public static Character? Convert( GameObject? actor ) + { + if( actor == null ) + { + return null; + } + + return actor switch + { + PlayerCharacter p => p, + BattleChara b => b, + _ => actor.ObjectKind switch + { + ObjectKind.BattleNpc => Character( actor.Address ), + ObjectKind.Companion => Character( actor.Address ), + ObjectKind.EventNpc => Character( actor.Address ), + _ => null, + }, + }; + } + } + + public static class GameObjectExtensions + { + private const int ModelTypeOffset = 0x01B4; + + public static unsafe int ModelType( this GameObject actor ) + => *( int* )( actor.Address + ModelTypeOffset ); + + public static unsafe void SetModelType( this GameObject actor, int value ) + => *( int* )( actor.Address + ModelTypeOffset ) = value; + } +} \ No newline at end of file diff --git a/Penumbra.PlayerWatch/PlayerWatchBase.cs b/Penumbra.PlayerWatch/PlayerWatchBase.cs index ddb0a3f7..6c203151 100644 --- a/Penumbra.PlayerWatch/PlayerWatchBase.cs +++ b/Penumbra.PlayerWatch/PlayerWatchBase.cs @@ -15,7 +15,7 @@ namespace Penumbra.PlayerWatch { public const int GPosePlayerIdx = 201; public const int GPoseTableEnd = GPosePlayerIdx + 48; - private const int ObjectsPerFrame = 8; + private const int ObjectsPerFrame = 32; private readonly Framework _framework; private readonly ClientState _clientState; @@ -172,11 +172,11 @@ namespace Penumbra.PlayerWatch } } - private Character CheckGPoseObject( GameObject player ) + private Character? CheckGPoseObject( GameObject player ) { if( !_inGPose ) { - return ( Character )player; + return CharacterFactory.Convert( player ); } for( var i = GPosePlayerIdx; i < GPoseTableEnd; ++i ) @@ -184,16 +184,16 @@ namespace Penumbra.PlayerWatch var a = _objects[ i ]; if( a == null ) { - return ( Character )player; + return CharacterFactory.Convert( player); } if( a.Name == player.Name ) { - return ( Character )a; + return CharacterFactory.Convert( a ); } } - return ( Character )player; + return CharacterFactory.Convert(player)!; } private bool TryGetPlayer( GameObject gameObject, out (CharacterEquipment, HashSet< PlayerWatcher >) equip ) @@ -203,6 +203,29 @@ namespace Penumbra.PlayerWatch return name.Length != 0 && Equip.TryGetValue( name, out equip ); } + private static bool InvalidObjectKind( ObjectKind kind ) + { + return kind switch + { + ObjectKind.BattleNpc => false, + ObjectKind.EventNpc => false, + ObjectKind.Player => false, + _ => true, + }; + } + + private GameObject? GetNextObject() + { + if( _frameTicker == GPosePlayerIdx - 1 ) + _frameTicker = GPoseTableEnd; + else if( _frameTicker == _objects.Length - 1 ) + _frameTicker = 0; + else + ++_frameTicker; + + return _objects[ _frameTicker ]; + } + private void OnFrameworkUpdate( object framework ) { var newInGPose = _objects[ GPosePlayerIdx ] != null; @@ -223,26 +246,26 @@ namespace Penumbra.PlayerWatch for( var i = 0; i < ObjectsPerFrame; ++i ) { - _frameTicker = _frameTicker < GPosePlayerIdx - 2 - ? _frameTicker + 2 - : 0; - - var actor = _objects[ _frameTicker ]; + var actor = GetNextObject(); if( actor == null - || actor.ObjectKind != ObjectKind.Player + || InvalidObjectKind(actor.ObjectKind) || !TryGetPlayer( actor, out var equip ) ) { continue; } var character = CheckGPoseObject( actor ); - if( _cancel ) { _cancel = false; return; } + if( character == null || character.ModelType() != 0 ) + { + continue; + } + PluginLog.Verbose( "Comparing Gear for {PlayerName} at {Address}...", character.Name, character.Address ); if( !equip.Item1.CompareAndUpdate( character ) ) {