mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 18:27:24 +01:00
Remove PlayerWatch.
This commit is contained in:
parent
d70bc675f2
commit
e107870fbf
6 changed files with 0 additions and 710 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<AssemblyTitle>Penumbra.PlayerWatch</AssemblyTitle>
|
||||
<Company>absolute gangstas</Company>
|
||||
<Product>Penumbra</Product>
|
||||
<Copyright>Copyright © 2020</Copyright>
|
||||
<FileVersion>1.0.0.0</FileVersion>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Nullable>enable</Nullable>
|
||||
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugType>full</DebugType>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<DalamudLibPath>$(AppData)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Dalamud">
|
||||
<HintPath>$(DalamudLibPath)Dalamud.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 );
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue