mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
Add keeping track of seen players to player watcher.
This commit is contained in:
parent
906e057943
commit
743f83d12e
3 changed files with 340 additions and 333 deletions
|
|
@ -3,29 +3,28 @@ using System.Collections.Generic;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Penumbra.PlayerWatch
|
namespace Penumbra.PlayerWatch;
|
||||||
|
|
||||||
|
public delegate void PlayerChange( Character actor );
|
||||||
|
|
||||||
|
public interface IPlayerWatcherBase : IDisposable
|
||||||
{
|
{
|
||||||
public delegate void PlayerChange( Character actor );
|
public int Version { get; }
|
||||||
|
public bool Valid { get; }
|
||||||
|
}
|
||||||
|
|
||||||
public interface IPlayerWatcherBase : IDisposable
|
public interface IPlayerWatcher : IPlayerWatcherBase
|
||||||
{
|
{
|
||||||
public int Version { get; }
|
public event PlayerChange? PlayerChanged;
|
||||||
public bool Valid { get; }
|
public bool Active { get; }
|
||||||
}
|
|
||||||
|
|
||||||
public interface IPlayerWatcher : IPlayerWatcherBase
|
public void Enable();
|
||||||
{
|
public void Disable();
|
||||||
public event PlayerChange? PlayerChanged;
|
public void SetStatus( bool enabled );
|
||||||
public bool Active { get; }
|
|
||||||
|
|
||||||
public void Enable();
|
public void AddPlayerToWatch( string playerName );
|
||||||
public void Disable();
|
public void RemovePlayerFromWatch( string playerName );
|
||||||
public void SetStatus( bool enabled );
|
public CharacterEquipment UpdatePlayerWithoutEvent( Character actor );
|
||||||
|
|
||||||
public void AddPlayerToWatch( string playerName );
|
public IEnumerable< (string, (ulong, CharacterEquipment)[]) > WatchedPlayers();
|
||||||
public void RemovePlayerFromWatch( string playerName );
|
|
||||||
public CharacterEquipment UpdatePlayerWithoutEvent( Character actor );
|
|
||||||
|
|
||||||
public IEnumerable< (string, (uint, CharacterEquipment)[]) > WatchedPlayers();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -9,303 +9,311 @@ using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Penumbra.PlayerWatch
|
namespace Penumbra.PlayerWatch;
|
||||||
{
|
|
||||||
internal readonly struct WatchedPlayer
|
|
||||||
{
|
|
||||||
public readonly Dictionary< uint, CharacterEquipment > FoundActors;
|
|
||||||
public readonly HashSet< PlayerWatcher > RegisteredWatchers;
|
|
||||||
|
|
||||||
public WatchedPlayer( PlayerWatcher watcher )
|
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 + 48;
|
||||||
|
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 )
|
||||||
{
|
{
|
||||||
FoundActors = new Dictionary< uint, CharacterEquipment >(4);
|
EnablePlayerWatch();
|
||||||
RegisteredWatchers = new HashSet< PlayerWatcher >{ watcher };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class PlayerWatchBase : IDisposable
|
internal void UnregisterWatcher( PlayerWatcher watcher )
|
||||||
{
|
{
|
||||||
public const int GPosePlayerIdx = 201;
|
if( RegisteredWatchers.Remove( watcher ) )
|
||||||
public const int GPoseTableEnd = GPosePlayerIdx + 48;
|
|
||||||
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();
|
|
||||||
private int _frameTicker;
|
|
||||||
private bool _inGPose;
|
|
||||||
private bool _enabled;
|
|
||||||
private bool _cancel;
|
|
||||||
|
|
||||||
internal PlayerWatchBase( Framework framework, ClientState clientState, ObjectTable objects )
|
|
||||||
{
|
{
|
||||||
_framework = framework;
|
foreach( var (key, value) in Equip.ToArray() )
|
||||||
_clientState = clientState;
|
|
||||||
_objects = objects;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void RegisterWatcher( PlayerWatcher watcher )
|
|
||||||
{
|
|
||||||
RegisteredWatchers.Add( watcher );
|
|
||||||
if( watcher.Active )
|
|
||||||
{
|
{
|
||||||
EnablePlayerWatch();
|
if( value.RegisteredWatchers.Remove( watcher ) && value.RegisteredWatchers.Count == 0 )
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 );
|
||||||
{
|
|
||||||
Equip.Remove( key );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CheckActiveStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void CheckActiveStatus()
|
|
||||||
{
|
|
||||||
if( RegisteredWatchers.Any( w => w.Active ) )
|
|
||||||
{
|
|
||||||
EnablePlayerWatch();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DisablePlayerWatch();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static uint GetId( GameObject actor )
|
|
||||||
=> actor.ObjectId ^ actor.OwnerId;
|
|
||||||
|
|
||||||
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()
|
CheckActiveStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void CheckActiveStatus()
|
||||||
|
{
|
||||||
|
if( RegisteredWatchers.Any( w => w.Active ) )
|
||||||
{
|
{
|
||||||
if( !_enabled )
|
EnablePlayerWatch();
|
||||||
{
|
}
|
||||||
_enabled = true;
|
else
|
||||||
_framework.Update += OnFrameworkUpdate;
|
{
|
||||||
_clientState.TerritoryChanged += OnTerritoryChange;
|
DisablePlayerWatch();
|
||||||
_clientState.Logout += OnLogout;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void DisablePlayerWatch()
|
return equipment;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void AddPlayerToWatch( string playerName, PlayerWatcher watcher )
|
||||||
|
{
|
||||||
|
if( Equip.TryGetValue( playerName, out var items ) )
|
||||||
{
|
{
|
||||||
if( _enabled )
|
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 )
|
||||||
{
|
{
|
||||||
_enabled = false;
|
Equip.Remove( playerName );
|
||||||
_framework.Update -= OnFrameworkUpdate;
|
|
||||||
_clientState.TerritoryChanged -= OnTerritoryChange;
|
|
||||||
_clientState.Logout -= OnLogout;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
internal void EnablePlayerWatch()
|
||||||
=> DisablePlayerWatch();
|
{
|
||||||
|
if( !_enabled )
|
||||||
private void OnTerritoryChange( object? _1, ushort _2 )
|
|
||||||
=> Clear();
|
|
||||||
|
|
||||||
private void OnLogout( object? _1, object? _2 )
|
|
||||||
=> Clear();
|
|
||||||
|
|
||||||
internal void Clear()
|
|
||||||
{
|
{
|
||||||
PluginLog.Debug( "Clearing PlayerWatcher Store." );
|
_enabled = true;
|
||||||
_cancel = true;
|
_framework.Update += OnFrameworkUpdate;
|
||||||
foreach( var kvp in Equip )
|
_clientState.TerritoryChanged += OnTerritoryChange;
|
||||||
{
|
_clientState.Logout += OnLogout;
|
||||||
kvp.Value.FoundActors.Clear();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_frameTicker = 0;
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void TriggerEvents( IEnumerable< PlayerWatcher > watchers, Character player )
|
_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 ) )
|
||||||
{
|
{
|
||||||
PluginLog.Debug( "Triggering events for {PlayerName} at {Address}.", player.Name, player.Address );
|
watcher.Trigger( player );
|
||||||
foreach( var watcher in watchers.Where( w => w.Active ) )
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void TriggerGPose()
|
||||||
|
{
|
||||||
|
for( var i = GPosePlayerIdx; i < GPoseTableEnd; ++i )
|
||||||
|
{
|
||||||
|
var player = _objects[ i ];
|
||||||
|
if( player == null )
|
||||||
{
|
{
|
||||||
watcher.Trigger( player );
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( Equip.TryGetValue( player.Name.ToString(), out var watcher ) )
|
||||||
|
{
|
||||||
|
TriggerEvents( watcher.RegisteredWatchers, ( Character )player );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal void TriggerGPose()
|
private Character? CheckGPoseObject( GameObject player )
|
||||||
|
{
|
||||||
|
if( !_inGPose )
|
||||||
{
|
{
|
||||||
for( var i = GPosePlayerIdx; i < GPoseTableEnd; ++i )
|
return CharacterFactory.Convert( player );
|
||||||
{
|
|
||||||
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 )
|
for( var i = GPosePlayerIdx; i < GPoseTableEnd; ++i )
|
||||||
{
|
{
|
||||||
if( !_inGPose )
|
var a = _objects[ i ];
|
||||||
|
if( a == null )
|
||||||
{
|
{
|
||||||
return CharacterFactory.Convert( player );
|
return CharacterFactory.Convert( player );
|
||||||
}
|
}
|
||||||
|
|
||||||
for( var i = GPosePlayerIdx; i < GPoseTableEnd; ++i )
|
if( a.Name == player.Name )
|
||||||
{
|
{
|
||||||
var a = _objects[ i ];
|
return CharacterFactory.Convert( a );
|
||||||
if( a == null )
|
}
|
||||||
{
|
}
|
||||||
return CharacterFactory.Convert( player );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( a.Name == player.Name )
|
return CharacterFactory.Convert( player )!;
|
||||||
{
|
}
|
||||||
return CharacterFactory.Convert( a );
|
|
||||||
}
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
return CharacterFactory.Convert( player )!;
|
SeenActors.Clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++_frameTicker;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryGetPlayer( GameObject gameObject, out WatchedPlayer watch )
|
return _objects[ _frameTicker ];
|
||||||
{
|
}
|
||||||
watch = default;
|
|
||||||
var name = gameObject.Name.ToString();
|
|
||||||
return name.Length != 0 && Equip.TryGetValue( name, out watch );
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool InvalidObjectKind( ObjectKind kind )
|
private void OnFrameworkUpdate( object framework )
|
||||||
{
|
{
|
||||||
return kind switch
|
var newInGPose = _objects[ GPosePlayerIdx ] != null;
|
||||||
{
|
|
||||||
ObjectKind.BattleNpc => false,
|
|
||||||
ObjectKind.EventNpc => false,
|
|
||||||
ObjectKind.Player => false,
|
|
||||||
ObjectKind.Retainer => false,
|
|
||||||
_ => true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private GameObject? GetNextObject()
|
if( newInGPose != _inGPose )
|
||||||
{
|
{
|
||||||
if( _frameTicker == GPosePlayerIdx - 1 )
|
if( newInGPose )
|
||||||
{
|
{
|
||||||
_frameTicker = GPoseTableEnd;
|
TriggerGPose();
|
||||||
}
|
|
||||||
else if( _frameTicker == _objects.Length - 1 )
|
|
||||||
{
|
|
||||||
_frameTicker = 0;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
++_frameTicker;
|
Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
return _objects[ _frameTicker ];
|
_inGPose = newInGPose;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnFrameworkUpdate( object framework )
|
for( var i = 0; i < ObjectsPerFrame; ++i )
|
||||||
{
|
{
|
||||||
var newInGPose = _objects[ GPosePlayerIdx ] != null;
|
var actor = GetNextObject();
|
||||||
|
if( actor == null
|
||||||
if( newInGPose != _inGPose )
|
|| InvalidObjectKind( actor.ObjectKind )
|
||||||
|
|| !TryGetPlayer( actor, out var watch ) )
|
||||||
{
|
{
|
||||||
if( newInGPose )
|
continue;
|
||||||
{
|
|
||||||
TriggerGPose();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
_inGPose = newInGPose;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for( var i = 0; i < ObjectsPerFrame; ++i )
|
var character = CheckGPoseObject( actor );
|
||||||
|
if( _cancel )
|
||||||
{
|
{
|
||||||
var actor = GetNextObject();
|
_cancel = false;
|
||||||
if( actor == null
|
return;
|
||||||
|| 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 );
|
|
||||||
PluginLog.Verbose( "Comparing Gear for {PlayerName} ({Id}) at {Address}...", character.Name, id, character.Address);
|
|
||||||
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.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( character == null || character.ModelType() != 0 )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var id = GetId( character );
|
||||||
|
SeenActors.Add( id );
|
||||||
|
PluginLog.Verbose( "Comparing Gear for {PlayerName} ({Id}) at {Address}...", character.Name, id, character.Address );
|
||||||
|
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.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,97 +7,97 @@ using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Penumbra.PlayerWatch
|
namespace Penumbra.PlayerWatch;
|
||||||
|
|
||||||
|
public class PlayerWatcher : IPlayerWatcher
|
||||||
{
|
{
|
||||||
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 )
|
||||||
{
|
{
|
||||||
public int Version { get; } = 2;
|
_playerWatch ??= new PlayerWatchBase( framework, clientState, objects );
|
||||||
|
_playerWatch.RegisterWatcher( this );
|
||||||
|
}
|
||||||
|
|
||||||
private static PlayerWatchBase? _playerWatch;
|
public void Enable()
|
||||||
|
=> SetStatus( true );
|
||||||
|
|
||||||
public event PlayerChange? PlayerChanged;
|
public void Disable()
|
||||||
|
=> SetStatus( false );
|
||||||
|
|
||||||
public bool Active { get; set; } = true;
|
public void SetStatus( bool enabled )
|
||||||
|
{
|
||||||
|
Active = enabled && Valid;
|
||||||
|
_playerWatch?.CheckActiveStatus();
|
||||||
|
}
|
||||||
|
|
||||||
public bool Valid
|
internal void Trigger( Character actor )
|
||||||
=> _playerWatch != null;
|
=> PlayerChanged?.Invoke( actor );
|
||||||
|
|
||||||
internal PlayerWatcher( Framework framework, ClientState clientState, ObjectTable objects )
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if( _playerWatch == null )
|
||||||
{
|
{
|
||||||
_playerWatch ??= new PlayerWatchBase( framework, clientState, objects );
|
return;
|
||||||
_playerWatch.RegisterWatcher( this );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Enable()
|
Active = false;
|
||||||
=> SetStatus( true );
|
PlayerChanged = null;
|
||||||
|
_playerWatch.UnregisterWatcher( this );
|
||||||
public void Disable()
|
if( _playerWatch.RegisteredWatchers.Count == 0 )
|
||||||
=> SetStatus( false );
|
|
||||||
|
|
||||||
public void SetStatus( bool enabled )
|
|
||||||
{
|
{
|
||||||
Active = enabled && Valid;
|
_playerWatch.Dispose();
|
||||||
_playerWatch?.CheckActiveStatus();
|
_playerWatch = null;
|
||||||
}
|
|
||||||
|
|
||||||
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, (uint, 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
|
private void CheckValidity()
|
||||||
{
|
{
|
||||||
public static IPlayerWatcher Create( Framework framework, ClientState clientState, ObjectTable objects )
|
if( !Valid )
|
||||||
=> new PlayerWatcher( framework, clientState, objects );
|
{
|
||||||
|
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