diff --git a/Penumbra.PlayerWatch/CharacterEquipment.cs b/Penumbra.PlayerWatch/CharacterEquipment.cs deleted file mode 100644 index 79c7950..0000000 --- a/Penumbra.PlayerWatch/CharacterEquipment.cs +++ /dev/null @@ -1,145 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using Dalamud.Game.ClientState.Objects.Types; -using Penumbra.GameData.Structs; - -// Read the customization data regarding weapons and displayable equipment from an actor struct. -// Stores the data in a 56 bytes, i.e. 7 longs for easier comparison. -namespace Penumbra.PlayerWatch; - -[StructLayout(LayoutKind.Sequential, Pack = 1)] -public class CharacterEquipment -{ - public const int MainWeaponOffset = 0x6E0; - public const int OffWeaponOffset = 0x748; - public const int EquipmentOffset = 0x818; - public const int EquipmentSlots = 10; - public const int WeaponSlots = 2; - - public CharacterWeapon MainHand; - public CharacterWeapon OffHand; - public CharacterArmor Head; - public CharacterArmor Body; - public CharacterArmor Hands; - public CharacterArmor Legs; - public CharacterArmor Feet; - public CharacterArmor Ears; - public CharacterArmor Neck; - public CharacterArmor Wrists; - public CharacterArmor RFinger; - public CharacterArmor LFinger; - public ushort IsSet; // Also fills struct size to 56, a multiple of 8. - - public CharacterEquipment() - => Clear(); - - public CharacterEquipment(Character actor) - : this(actor.Address) - { } - - public override string ToString() - => IsSet == 0 - ? "(Not Set)" - : $"({MainHand}) | ({OffHand}) | ({Head}) | ({Body}) | ({Hands}) | ({Legs}) | " - + $"({Feet}) | ({Ears}) | ({Neck}) | ({Wrists}) | ({LFinger}) | ({RFinger})"; - - public bool Equal(Character rhs) - => CompareData(new CharacterEquipment(rhs)); - - public bool Equal(CharacterEquipment rhs) - => CompareData(rhs); - - public bool CompareAndUpdate(Character rhs) - => CompareAndOverwrite(new CharacterEquipment(rhs)); - - public bool CompareAndUpdate(CharacterEquipment rhs) - => CompareAndOverwrite(rhs); - - private unsafe CharacterEquipment(IntPtr actorAddress) - { - IsSet = 1; - var actorPtr = (byte*)actorAddress.ToPointer(); - fixed (CharacterWeapon* main = &MainHand, off = &OffHand) - { - Buffer.MemoryCopy(actorPtr + MainWeaponOffset, main, sizeof(CharacterWeapon), sizeof(CharacterWeapon)); - Buffer.MemoryCopy(actorPtr + OffWeaponOffset, off, sizeof(CharacterWeapon), sizeof(CharacterWeapon)); - } - - fixed (CharacterArmor* equipment = &Head) - { - Buffer.MemoryCopy(actorPtr + EquipmentOffset, equipment, EquipmentSlots * sizeof(CharacterArmor), - EquipmentSlots * sizeof(CharacterArmor)); - } - } - - public unsafe void Clear() - { - fixed (CharacterWeapon* main = &MainHand) - { - var structSizeEights = (2 + EquipmentSlots * sizeof(CharacterArmor) + WeaponSlots * sizeof(CharacterWeapon)) / 8; - for (ulong* ptr = (ulong*)main, end = ptr + structSizeEights; ptr != end; ++ptr) - *ptr = 0; - } - } - - private unsafe bool CompareAndOverwrite(CharacterEquipment rhs) - { - var structSizeEights = (2 + EquipmentSlots * sizeof(CharacterArmor) + WeaponSlots * sizeof(CharacterWeapon)) / 8; - var ret = true; - fixed (CharacterWeapon* data1 = &MainHand, data2 = &rhs.MainHand) - { - var ptr1 = (ulong*)data1; - var ptr2 = (ulong*)data2; - for (var end = ptr1 + structSizeEights; ptr1 != end; ++ptr1, ++ptr2) - { - if (*ptr1 != *ptr2) - { - *ptr1 = *ptr2; - ret = false; - } - } - } - - return ret; - } - - private unsafe bool CompareData(CharacterEquipment rhs) - { - var structSizeEights = (2 + EquipmentSlots * sizeof(CharacterArmor) + WeaponSlots * sizeof(CharacterWeapon)) / 8; - fixed (CharacterWeapon* data1 = &MainHand, data2 = &rhs.MainHand) - { - var ptr1 = (ulong*)data1; - var ptr2 = (ulong*)data2; - for (var end = ptr1 + structSizeEights; ptr1 != end; ++ptr1, ++ptr2) - { - if (*ptr1 != *ptr2) - return false; - } - } - - return true; - } - - public unsafe void WriteBytes(byte[] array, int offset = 0) - { - fixed (CharacterWeapon* data = &MainHand) - { - Marshal.Copy(new IntPtr(data), array, offset, 56); - } - } - - public byte[] ToBytes() - { - var ret = new byte[56]; - WriteBytes(ret); - return ret; - } - - public unsafe void FromBytes(byte[] array, int offset = 0) - { - fixed (CharacterWeapon* data = &MainHand) - { - Marshal.Copy(array, offset, new IntPtr(data), 56); - } - } -} diff --git a/Penumbra.PlayerWatch/CharacterFactory.cs b/Penumbra.PlayerWatch/CharacterFactory.cs deleted file mode 100644 index b88cb28..0000000 --- a/Penumbra.PlayerWatch/CharacterFactory.cs +++ /dev/null @@ -1,63 +0,0 @@ -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; - - 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.Retainer => 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/IPlayerWatcher.cs b/Penumbra.PlayerWatch/IPlayerWatcher.cs deleted file mode 100644 index bfdff17..0000000 --- a/Penumbra.PlayerWatch/IPlayerWatcher.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using Dalamud.Game.ClientState.Objects.Types; -using Penumbra.GameData.Structs; - -namespace Penumbra.PlayerWatch; - -public delegate void PlayerChange( Character actor ); - -public interface IPlayerWatcherBase : IDisposable -{ - public int Version { get; } - public bool Valid { get; } -} - -public interface IPlayerWatcher : IPlayerWatcherBase -{ - public event PlayerChange? PlayerChanged; - public bool Active { get; } - - public void Enable(); - public void Disable(); - public void SetStatus( bool enabled ); - - public void AddPlayerToWatch( string playerName ); - public void RemovePlayerFromWatch( string playerName ); - public CharacterEquipment UpdatePlayerWithoutEvent( Character actor ); - - public IEnumerable< (string, (ulong, CharacterEquipment)[]) > WatchedPlayers(); -} \ No newline at end of file diff --git a/Penumbra.PlayerWatch/Penumbra.PlayerWatch.csproj b/Penumbra.PlayerWatch/Penumbra.PlayerWatch.csproj deleted file mode 100644 index 1b64b68..0000000 --- a/Penumbra.PlayerWatch/Penumbra.PlayerWatch.csproj +++ /dev/null @@ -1,46 +0,0 @@ - - - net6.0-windows - preview - x64 - Penumbra.PlayerWatch - absolute gangstas - Penumbra - Copyright © 2020 - 1.0.0.0 - 1.0.0.0 - bin\$(Configuration)\ - true - enable - false - false - - - - full - DEBUG;TRACE - - - - pdbonly - - - - $(MSBuildWarningsAsMessages);MSB3277 - - - - $(AppData)\XIVLauncher\addon\Hooks\dev\ - - - - - $(DalamudLibPath)Dalamud.dll - False - - - - - - - \ No newline at end of file diff --git a/Penumbra.PlayerWatch/PlayerWatchBase.cs b/Penumbra.PlayerWatch/PlayerWatchBase.cs deleted file mode 100644 index 5b67048..0000000 --- a/Penumbra.PlayerWatch/PlayerWatchBase.cs +++ /dev/null @@ -1,323 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Dalamud.Game; -using Dalamud.Game.ClientState; -using Dalamud.Game.ClientState.Objects; -using Dalamud.Game.ClientState.Objects.Enums; -using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Logging; -using Penumbra.GameData.Structs; - -namespace Penumbra.PlayerWatch; - -internal readonly struct WatchedPlayer -{ - public readonly Dictionary< ulong, CharacterEquipment > FoundActors; - public readonly HashSet< PlayerWatcher > RegisteredWatchers; - - public WatchedPlayer( PlayerWatcher watcher ) - { - FoundActors = new Dictionary< ulong, CharacterEquipment >( 4 ); - RegisteredWatchers = new HashSet< PlayerWatcher > { watcher }; - } -} - -internal class PlayerWatchBase : IDisposable -{ - public const int GPosePlayerIdx = 201; - public const int GPoseTableEnd = GPosePlayerIdx + 40; - private const int ObjectsPerFrame = 32; - - private readonly Framework _framework; - private readonly ClientState _clientState; - private readonly ObjectTable _objects; - internal readonly HashSet< PlayerWatcher > RegisteredWatchers = new(); - internal readonly Dictionary< string, WatchedPlayer > Equip = new(); - internal HashSet< ulong > SeenActors; - private int _frameTicker; - private bool _inGPose; - private bool _enabled; - private bool _cancel; - - internal PlayerWatchBase( Framework framework, ClientState clientState, ObjectTable objects ) - { - _framework = framework; - _clientState = clientState; - _objects = objects; - SeenActors = new HashSet< ulong >( _objects.Length ); - } - - internal void RegisterWatcher( PlayerWatcher watcher ) - { - RegisteredWatchers.Add( watcher ); - if( watcher.Active ) - { - EnablePlayerWatch(); - } - } - - internal void UnregisterWatcher( PlayerWatcher watcher ) - { - if( RegisteredWatchers.Remove( watcher ) ) - { - foreach( var (key, value) in Equip.ToArray() ) - { - if( value.RegisteredWatchers.Remove( watcher ) && value.RegisteredWatchers.Count == 0 ) - { - Equip.Remove( key ); - } - } - } - - CheckActiveStatus(); - } - - internal void CheckActiveStatus() - { - if( RegisteredWatchers.Any( w => w.Active ) ) - { - EnablePlayerWatch(); - } - else - { - DisablePlayerWatch(); - } - } - - private static ulong GetId( GameObject actor ) - => actor.ObjectId | ( ( ulong )actor.OwnerId << 32 ); - - internal CharacterEquipment UpdatePlayerWithoutEvent( Character actor ) - { - var name = actor.Name.ToString(); - var equipment = new CharacterEquipment( actor ); - if( Equip.TryGetValue( name, out var watched ) ) - { - watched.FoundActors[ GetId( actor ) ] = equipment; - } - - return equipment; - } - - internal void AddPlayerToWatch( string playerName, PlayerWatcher watcher ) - { - if( Equip.TryGetValue( playerName, out var items ) ) - { - items.RegisteredWatchers.Add( watcher ); - } - else - { - Equip[ playerName ] = new WatchedPlayer( watcher ); - } - } - - public void RemovePlayerFromWatch( string playerName, PlayerWatcher watcher ) - { - if( Equip.TryGetValue( playerName, out var items ) ) - { - if( items.RegisteredWatchers.Remove( watcher ) && items.RegisteredWatchers.Count == 0 ) - { - Equip.Remove( playerName ); - } - } - } - - internal void EnablePlayerWatch() - { - if( !_enabled ) - { - _enabled = true; - _framework.Update += OnFrameworkUpdate; - _clientState.TerritoryChanged += OnTerritoryChange; - _clientState.Logout += OnLogout; - } - } - - internal void DisablePlayerWatch() - { - if( _enabled ) - { - _enabled = false; - _framework.Update -= OnFrameworkUpdate; - _clientState.TerritoryChanged -= OnTerritoryChange; - _clientState.Logout -= OnLogout; - } - } - - public void Dispose() - => DisablePlayerWatch(); - - private void OnTerritoryChange( object? _1, ushort _2 ) - => Clear(); - - private void OnLogout( object? _1, object? _2 ) - => Clear(); - - internal void Clear() - { - PluginLog.Debug( "Clearing PlayerWatcher Store." ); - _cancel = true; - foreach( var kvp in Equip ) - { - kvp.Value.FoundActors.Clear(); - } - - _frameTicker = 0; - } - - private static void TriggerEvents( IEnumerable< PlayerWatcher > watchers, Character player ) - { - PluginLog.Debug( "Triggering events for {PlayerName} at {Address}.", player.Name, player.Address ); - foreach( var watcher in watchers.Where( w => w.Active ) ) - { - watcher.Trigger( player ); - } - } - - internal void TriggerGPose() - { - for( var i = GPosePlayerIdx; i < GPoseTableEnd; ++i ) - { - var player = _objects[ i ]; - if( player == null ) - { - return; - } - - if( Equip.TryGetValue( player.Name.ToString(), out var watcher ) ) - { - TriggerEvents( watcher.RegisteredWatchers, ( Character )player ); - } - } - } - - private Character? CheckGPoseObject( GameObject player ) - { - if( !_inGPose ) - { - return CharacterFactory.Convert( player ); - } - - for( var i = GPosePlayerIdx; i < GPoseTableEnd; ++i ) - { - var a = _objects[ i ]; - if( a == null ) - { - return CharacterFactory.Convert( player ); - } - - if( a.Name == player.Name ) - { - return CharacterFactory.Convert( a ); - } - } - - return CharacterFactory.Convert( player )!; - } - - private bool TryGetPlayer( GameObject gameObject, out WatchedPlayer watch ) - { - watch = default; - var name = gameObject.Name.ToString(); - return name.Length != 0 && Equip.TryGetValue( name, out watch ); - } - - private static bool InvalidObjectKind( ObjectKind kind ) - { - return kind switch - { - ObjectKind.BattleNpc => false, - ObjectKind.EventNpc => false, - ObjectKind.Player => false, - ObjectKind.Retainer => false, - _ => true, - }; - } - - private GameObject? GetNextObject() - { - if( _frameTicker == GPosePlayerIdx - 1 ) - { - _frameTicker = GPoseTableEnd; - } - else if( _frameTicker == _objects.Length - 1 ) - { - _frameTicker = 0; - foreach( var (_, equip) in Equip.Values.SelectMany( d => d.FoundActors.Where( p => !SeenActors.Contains( p.Key ) ) ) ) - { - equip.Clear(); - } - - SeenActors.Clear(); - } - else - { - ++_frameTicker; - } - - return _objects[ _frameTicker ]; - } - - private void OnFrameworkUpdate( object framework ) - { - var newInGPose = _objects[ GPosePlayerIdx ] != null; - - if( newInGPose != _inGPose ) - { - if( newInGPose ) - { - TriggerGPose(); - } - else - { - Clear(); - } - - _inGPose = newInGPose; - } - - for( var i = 0; i < ObjectsPerFrame; ++i ) - { - var actor = GetNextObject(); - if( actor == null - || InvalidObjectKind( actor.ObjectKind ) - || !TryGetPlayer( actor, out var watch ) ) - { - continue; - } - - var character = CheckGPoseObject( actor ); - if( _cancel ) - { - _cancel = false; - return; - } - - if( character == null || character.ModelType() != 0 ) - { - continue; - } - - var id = GetId( character ); - SeenActors.Add( id ); - -#if DEBUG - PluginLog.Verbose( "Comparing Gear for {PlayerName:l} ({Id}) at 0x{Address:X}...", character.Name, id, character.Address.ToInt64() ); -#endif - - if( !watch.FoundActors.TryGetValue( id, out var equip ) ) - { - equip = new CharacterEquipment( character ); - watch.FoundActors[ id ] = equip; - TriggerEvents( watch.RegisteredWatchers, character ); - } - else if( !equip.CompareAndUpdate( character ) ) - { - TriggerEvents( watch.RegisteredWatchers, character ); - } - - break; // Only one comparison per frame. - } - } -} \ No newline at end of file diff --git a/Penumbra.PlayerWatch/PlayerWatcher.cs b/Penumbra.PlayerWatch/PlayerWatcher.cs deleted file mode 100644 index 7ae94f7..0000000 --- a/Penumbra.PlayerWatch/PlayerWatcher.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Dalamud.Game; -using Dalamud.Game.ClientState; -using Dalamud.Game.ClientState.Objects; -using Dalamud.Game.ClientState.Objects.Types; -using Penumbra.GameData.Structs; - -namespace Penumbra.PlayerWatch; - -public class PlayerWatcher : IPlayerWatcher -{ - public int Version - => 3; - - private static PlayerWatchBase? _playerWatch; - - public event PlayerChange? PlayerChanged; - - public bool Active { get; set; } = true; - - public bool Valid - => _playerWatch != null; - - internal PlayerWatcher( Framework framework, ClientState clientState, ObjectTable objects ) - { - _playerWatch ??= new PlayerWatchBase( framework, clientState, objects ); - _playerWatch.RegisterWatcher( this ); - } - - public void Enable() - => SetStatus( true ); - - public void Disable() - => SetStatus( false ); - - public void SetStatus( bool enabled ) - { - Active = enabled && Valid; - _playerWatch?.CheckActiveStatus(); - } - - internal void Trigger( Character actor ) - => PlayerChanged?.Invoke( actor ); - - public void Dispose() - { - if( _playerWatch == null ) - { - return; - } - - Active = false; - PlayerChanged = null; - _playerWatch.UnregisterWatcher( this ); - if( _playerWatch.RegisteredWatchers.Count == 0 ) - { - _playerWatch.Dispose(); - _playerWatch = null; - } - } - - private void CheckValidity() - { - if( !Valid ) - { - throw new Exception( $"PlayerWatch was already disposed." ); - } - } - - public void AddPlayerToWatch( string name ) - { - CheckValidity(); - _playerWatch!.AddPlayerToWatch( name, this ); - } - - public void RemovePlayerFromWatch( string playerName ) - { - CheckValidity(); - _playerWatch!.RemovePlayerFromWatch( playerName, this ); - } - - public CharacterEquipment UpdatePlayerWithoutEvent( Character actor ) - { - CheckValidity(); - return _playerWatch!.UpdatePlayerWithoutEvent( actor ); - } - - public IEnumerable< (string, (ulong, CharacterEquipment)[]) > WatchedPlayers() - { - CheckValidity(); - return _playerWatch!.Equip - .Where( kvp => kvp.Value.RegisteredWatchers.Contains( this ) ) - .Select( kvp => ( kvp.Key, kvp.Value.FoundActors.Select( kvp2 => ( kvp2.Key, kvp2.Value ) ).ToArray() ) ); - } -} - -public static class PlayerWatchFactory -{ - public static IPlayerWatcher Create( Framework framework, ClientState clientState, ObjectTable objects ) - => new PlayerWatcher( framework, clientState, objects ); -} \ No newline at end of file