Penumbra/Penumbra.PlayerWatch/PlayerWatchBase.cs

203 lines
No EOL
6 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.ClientState.Actors;
using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Plugin;
using Penumbra.GameData.Structs;
namespace Penumbra.PlayerWatch
{
internal class PlayerWatchBase : IDisposable
{
public const int GPosePlayerActorIdx = 201;
public const int GPoseActorEnd = GPosePlayerActorIdx + 48;
private const int ActorsPerFrame = 8;
private readonly DalamudPluginInterface _pi;
internal readonly HashSet< PlayerWatcher > RegisteredWatchers = new();
private readonly Dictionary< string, (ActorEquipment, HashSet< PlayerWatcher >) > _equip = new();
private int _frameTicker;
private bool _inGPose = false;
internal PlayerWatchBase( DalamudPluginInterface pi )
{
_pi = pi;
EnableActorWatch();
}
internal void RegisterWatcher( PlayerWatcher watcher )
{
RegisteredWatchers.Add( watcher );
}
internal void UnregisterWatcher( PlayerWatcher watcher )
{
if( RegisteredWatchers.Remove( watcher ) )
{
foreach( var items in _equip.Values )
{
items.Item2.Remove( watcher );
}
}
}
internal ActorEquipment UpdateActorWithoutEvent( Actor actor )
{
var equipment = new ActorEquipment( actor );
if( _equip.ContainsKey( actor.Name ) )
{
_equip[ actor.Name ] = ( equipment, _equip[ actor.Name ].Item2 );
}
return equipment;
}
internal void AddPlayerToWatch( string playerName, PlayerWatcher watcher )
{
if( _equip.TryGetValue( playerName, out var items ) )
{
items.Item2.Add( watcher );
}
else
{
_equip[ playerName ] = ( new ActorEquipment(), new HashSet< PlayerWatcher > { watcher } );
}
}
public void RemovePlayerFromWatch( string playerName, PlayerWatcher watcher )
{
if( _equip.TryGetValue( playerName, out var items ) )
{
items.Item2.Remove( watcher );
if( items.Item2.Count == 0 )
{
_equip.Remove( playerName );
}
}
}
internal void EnableActorWatch()
{
_pi.Framework.OnUpdateEvent += OnFrameworkUpdate;
_pi.ClientState.TerritoryChanged += OnTerritoryChange;
_pi.ClientState.OnLogout += OnLogout;
}
internal void DisableActorWatch()
{
_pi.Framework.OnUpdateEvent -= OnFrameworkUpdate;
_pi.ClientState.TerritoryChanged -= OnTerritoryChange;
_pi.ClientState.OnLogout -= OnLogout;
}
public void Dispose()
=> DisableActorWatch();
private void OnTerritoryChange( object _1, ushort _2 )
=> Clear();
private void OnLogout( object _1, object _2 )
=> Clear();
internal void Clear()
{
foreach( var kvp in _equip )
{
kvp.Value.Item1.Clear();
}
_frameTicker = 0;
}
private static void TriggerEvents( IEnumerable< PlayerWatcher > watchers, Actor actor )
{
foreach( var watcher in watchers.Where( w => w.Active ) )
{
watcher.Trigger( actor );
}
}
internal void TriggerGPose()
{
for( var i = GPosePlayerActorIdx; i < GPoseActorEnd; ++i )
{
var actor = _pi.ClientState.Actors[ i ];
if( actor == null )
{
return;
}
if( _equip.TryGetValue( actor.Name, out var watcher ) )
{
TriggerEvents( watcher.Item2, actor );
}
}
}
private Actor CheckGPoseActor( Actor actor )
{
if( !_inGPose )
{
return actor;
}
for( var i = GPosePlayerActorIdx; i < GPoseActorEnd; ++i )
{
var a = _pi.ClientState.Actors[ i ];
if( a == null )
{
return actor;
}
if( a.Name == actor.Name )
{
return a;
}
}
return actor;
}
private void OnFrameworkUpdate( object framework )
{
var actors = _pi.ClientState.Actors;
var newInGPose = actors[ GPosePlayerActorIdx ] != null;
if( newInGPose != _inGPose )
{
if( newInGPose )
{
TriggerGPose();
}
_inGPose = newInGPose;
}
for( var i = 0; i < ActorsPerFrame; ++i )
{
_frameTicker = _frameTicker < actors.Length - 2
? _frameTicker + 2
: 0;
var actor = actors[ _frameTicker ];
if( actor == null
|| actor.ObjectKind != ObjectKind.Player
|| actor.Name == null
|| actor.Name.Length == 0
|| !_equip.TryGetValue( actor.Name, out var equip ) )
{
continue;
}
actor = CheckGPoseActor( actor );
if( !equip.Item1.CompareAndUpdate( actor ) )
{
TriggerEvents( equip.Item2, actor );
}
}
}
}
}