Allow player watcher to watch NPCs with human model.

This commit is contained in:
Ottermandias 2021-10-10 15:00:25 +02:00
parent 6a024ba5d1
commit ba7dc6fda7
2 changed files with 98 additions and 13 deletions

View file

@ -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;
}
}

View file

@ -15,7 +15,7 @@ namespace Penumbra.PlayerWatch
{ {
public const int GPosePlayerIdx = 201; public const int GPosePlayerIdx = 201;
public const int GPoseTableEnd = GPosePlayerIdx + 48; public const int GPoseTableEnd = GPosePlayerIdx + 48;
private const int ObjectsPerFrame = 8; private const int ObjectsPerFrame = 32;
private readonly Framework _framework; private readonly Framework _framework;
private readonly ClientState _clientState; private readonly ClientState _clientState;
@ -172,11 +172,11 @@ namespace Penumbra.PlayerWatch
} }
} }
private Character CheckGPoseObject( GameObject player ) private Character? CheckGPoseObject( GameObject player )
{ {
if( !_inGPose ) if( !_inGPose )
{ {
return ( Character )player; return CharacterFactory.Convert( player );
} }
for( var i = GPosePlayerIdx; i < GPoseTableEnd; ++i ) for( var i = GPosePlayerIdx; i < GPoseTableEnd; ++i )
@ -184,16 +184,16 @@ namespace Penumbra.PlayerWatch
var a = _objects[ i ]; var a = _objects[ i ];
if( a == null ) if( a == null )
{ {
return ( Character )player; return CharacterFactory.Convert( player);
} }
if( a.Name == player.Name ) 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 ) 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 ); 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 ) private void OnFrameworkUpdate( object framework )
{ {
var newInGPose = _objects[ GPosePlayerIdx ] != null; var newInGPose = _objects[ GPosePlayerIdx ] != null;
@ -223,26 +246,26 @@ namespace Penumbra.PlayerWatch
for( var i = 0; i < ObjectsPerFrame; ++i ) for( var i = 0; i < ObjectsPerFrame; ++i )
{ {
_frameTicker = _frameTicker < GPosePlayerIdx - 2 var actor = GetNextObject();
? _frameTicker + 2
: 0;
var actor = _objects[ _frameTicker ];
if( actor == null if( actor == null
|| actor.ObjectKind != ObjectKind.Player || InvalidObjectKind(actor.ObjectKind)
|| !TryGetPlayer( actor, out var equip ) ) || !TryGetPlayer( actor, out var equip ) )
{ {
continue; continue;
} }
var character = CheckGPoseObject( actor ); var character = CheckGPoseObject( actor );
if( _cancel ) if( _cancel )
{ {
_cancel = false; _cancel = false;
return; return;
} }
if( character == null || character.ModelType() != 0 )
{
continue;
}
PluginLog.Verbose( "Comparing Gear for {PlayerName} at {Address}...", character.Name, character.Address ); PluginLog.Verbose( "Comparing Gear for {PlayerName} at {Address}...", character.Name, character.Address );
if( !equip.Item1.CompareAndUpdate( character ) ) if( !equip.Item1.CompareAndUpdate( character ) )
{ {