Move PlayerWatcher to own assembly and make appropriate changes for reuse.

This commit is contained in:
Ottermandias 2021-07-26 15:53:41 +02:00
parent ea40d5bc9c
commit d99707f77e
9 changed files with 335 additions and 133 deletions

View file

@ -0,0 +1,28 @@
using System;
using Dalamud.Game.ClientState.Actors.Types;
using Penumbra.GameData.Structs;
namespace Penumbra.PlayerWatch
{
public delegate void ActorChange( Actor actor );
public interface IPlayerWatcherBase : IDisposable
{
public int Version { get; }
public bool Valid { get; }
}
public interface IPlayerWatcher : IPlayerWatcherBase
{
public event ActorChange? ActorChanged;
public bool Active { get; }
public void Enable();
public void Disable();
public void SetStatus( bool enabled );
public void AddPlayerToWatch( string name );
public void RemovePlayerFromWatch( string playerName );
public CharEquipment UpdateActorWithoutEvent( Actor actor );
}
}

View file

@ -0,0 +1,41 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<LangVersion>preview</LangVersion>
<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>
</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>
<ItemGroup>
<Reference Include="Dalamud">
<HintPath>$(DALAMUD_ROOT)\Dalamud.dll</HintPath>
<HintPath>..\libs\Dalamud.dll</HintPath>
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Penumbra.GameData\Penumbra.GameData.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,164 @@
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;
private const int ActorsPerFrame = 8;
private readonly DalamudPluginInterface _pi;
internal readonly HashSet< PlayerWatcher > RegisteredWatchers = new();
private readonly Dictionary< string, (CharEquipment, HashSet< PlayerWatcher >) > _equip = new();
private int _frameTicker;
private IntPtr _lastGPoseAddress = IntPtr.Zero;
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 CharEquipment UpdateActorWithoutEvent( Actor actor )
{
var equipment = new CharEquipment( 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 CharEquipment(), 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 );
}
}
private void OnFrameworkUpdate( object framework )
{
var actors = _pi.ClientState.Actors;
var gPoseActor = actors[ GPosePlayerActorIdx ];
if( gPoseActor == null )
{
if( _lastGPoseAddress != IntPtr.Zero && actors[ 0 ] != null && _equip.TryGetValue( actors[ 0 ].Name, out var player ) )
{
TriggerEvents( player.Item2, actors[ 0 ] );
}
_lastGPoseAddress = IntPtr.Zero;
}
else if( gPoseActor.Address != _lastGPoseAddress )
{
_lastGPoseAddress = gPoseActor.Address;
if( _equip.TryGetValue( gPoseActor.Name, out var gPose ) )
{
TriggerEvents( gPose.Item2, gPoseActor );
}
}
for( var i = 0; i < ActorsPerFrame; ++i )
{
_frameTicker = _frameTicker < actors.Length - 2
? _frameTicker + 2
: 0;
var actor = _frameTicker == 0 && gPoseActor != null ? gPoseActor : actors[ _frameTicker ];
if( actor == null
|| actor.ObjectKind != ObjectKind.Player
|| actor.Name == null
|| actor.Name.Length == 0 )
{
continue;
}
if( _equip.TryGetValue( actor.Name, out var equip ) && !equip.Item1.CompareAndUpdate( actor ) )
{
TriggerEvents( equip.Item2, actor );
}
}
}
}
}

View file

@ -0,0 +1,88 @@
using System;
using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Plugin;
using Penumbra.GameData.Structs;
namespace Penumbra.PlayerWatch
{
public class PlayerWatcher : IDisposable, IPlayerWatcher
{
public int Version { get; } = 1;
private static PlayerWatchBase? _playerWatch;
public event ActorChange? ActorChanged;
public bool Active { get; set; } = true;
public bool Valid
=> _playerWatch != null;
internal PlayerWatcher( DalamudPluginInterface pi )
{
_playerWatch ??= new PlayerWatchBase( pi );
_playerWatch.RegisterWatcher( this );
}
public void Enable()
=> Active = Valid;
public void Disable()
=> Active = false;
public void SetStatus( bool enabled )
=> Active = enabled && Valid;
internal void Trigger( Actor actor )
=> ActorChanged?.Invoke( actor );
public void Dispose()
{
if( _playerWatch == null )
{
return;
}
Active = false;
ActorChanged = 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 CharEquipment UpdateActorWithoutEvent( Actor actor )
{
CheckValidity();
return _playerWatch!.UpdateActorWithoutEvent( actor );
}
}
public static class PlayerWatchFactory
{
public static IPlayerWatcher Create( DalamudPluginInterface pi )
=> new PlayerWatcher( pi );
}
}

View file

@ -10,7 +10,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Penumbra.GameData", "Penumbra.GameData\Penumbra.GameData.csproj", "{EE551E87-FDB3-4612-B500-DC870C07C605}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.GameData", "Penumbra.GameData\Penumbra.GameData.csproj", "{EE551E87-FDB3-4612-B500-DC870C07C605}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.PlayerWatch", "Penumbra.PlayerWatch\Penumbra.PlayerWatch.csproj", "{01685BD8-8847-4B49-BF90-1683B4C76B0E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -26,6 +28,10 @@ Global
{EE551E87-FDB3-4612-B500-DC870C07C605}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EE551E87-FDB3-4612-B500-DC870C07C605}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EE551E87-FDB3-4612-B500-DC870C07C605}.Release|Any CPU.Build.0 = Release|Any CPU
{01685BD8-8847-4B49-BF90-1683B4C76B0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{01685BD8-8847-4B49-BF90-1683B4C76B0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{01685BD8-8847-4B49-BF90-1683B4C76B0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{01685BD8-8847-4B49-BF90-1683B4C76B0E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View file

@ -1,127 +0,0 @@
using System;
using System.Collections.Generic;
using Dalamud.Game.ClientState.Actors;
using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Plugin;
using Penumbra.GameData.Structs;
namespace Penumbra.Interop
{
public class PlayerWatcher : IDisposable
{
private const int ActorsPerFrame = 8;
private readonly DalamudPluginInterface _pi;
private readonly Dictionary< string, CharEquipment > _equip = new();
private int _frameTicker;
private IntPtr _lastGPoseAddress = IntPtr.Zero;
public PlayerWatcher( DalamudPluginInterface pi )
=> _pi = pi;
public delegate void ActorChange( Actor which );
public event ActorChange? ActorChanged;
public void AddPlayerToWatch( string playerName )
{
if( !_equip.ContainsKey( playerName ) )
{
_equip[ playerName ] = new CharEquipment();
}
}
public void RemovePlayerFromWatch( string playerName )
{
_equip.Remove( playerName );
}
public void SetActorWatch( bool on )
{
if( on )
{
EnableActorWatch();
}
else
{
DisableActorWatch();
}
}
public void EnableActorWatch()
{
_pi.Framework.OnUpdateEvent += OnFrameworkUpdate;
_pi.ClientState.TerritoryChanged += OnTerritoryChange;
_pi.ClientState.OnLogout += OnLogout;
}
public 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();
public void Clear()
{
foreach( var kvp in _equip )
{
kvp.Value.Clear();
}
_frameTicker = 0;
}
private void OnFrameworkUpdate( object framework )
{
var actors = _pi.ClientState.Actors;
var gPoseActor = actors[ ActorRefresher.GPosePlayerActorIdx ];
if( gPoseActor == null )
{
if( _lastGPoseAddress != IntPtr.Zero && actors[ 0 ] != null && _equip.ContainsKey( actors[ 0 ].Name ) )
{
ActorChanged?.Invoke( actors[ 0 ] );
}
_lastGPoseAddress = IntPtr.Zero;
}
else if( gPoseActor.Address != _lastGPoseAddress )
{
_lastGPoseAddress = gPoseActor.Address;
if( _equip.ContainsKey( gPoseActor.Name ) )
{
ActorChanged?.Invoke( gPoseActor );
}
}
for( var i = 0; i < ActorsPerFrame; ++i )
{
_frameTicker = _frameTicker < actors.Length - 2
? _frameTicker + 2
: 0;
var actor = _frameTicker == 0 && gPoseActor != null ? gPoseActor : actors[ _frameTicker ];
if( actor == null
|| actor.ObjectKind != ObjectKind.Player
|| actor.Name == null
|| actor.Name.Length == 0 )
{
continue;
}
if( _equip.TryGetValue( actor.Name, out var equip ) && !equip.CompareAndUpdate( actor ) )
{
ActorChanged?.Invoke( actor );
}
}
}
}
}

View file

@ -75,6 +75,7 @@
<ItemGroup>
<ProjectReference Include="..\Penumbra.GameData\Penumbra.GameData.csproj" />
<ProjectReference Include="..\Penumbra.PlayerWatch\Penumbra.PlayerWatch.csproj" />
</ItemGroup>
<ItemGroup>

View file

@ -6,6 +6,7 @@ using Penumbra.API;
using Penumbra.Interop;
using Penumbra.Meta.Files;
using Penumbra.Mods;
using Penumbra.PlayerWatch;
using Penumbra.UI;
using Penumbra.Util;
@ -30,7 +31,7 @@ namespace Penumbra
public SettingsInterface SettingsInterface { get; set; } = null!;
public MusicManager SoundShit { get; set; } = null!;
public ActorRefresher ActorRefresher { get; set; } = null!;
public PlayerWatcher PlayerWatcher { get; set; } = null!;
public IPlayerWatcher PlayerWatcher { get; set; } = null!;
private WebServer? _webServer;
@ -46,7 +47,7 @@ namespace Penumbra
SoundShit.DisableStreaming();
var gameUtils = Service< GameResourceManagement >.Set( PluginInterface );
PlayerWatcher = new PlayerWatcher( PluginInterface );
PlayerWatcher = PlayerWatchFactory.Create( PluginInterface );
Service< MetaDefaults >.Set( PluginInterface );
var modManager = Service< ModManager >.Set( this );
@ -78,7 +79,7 @@ namespace Penumbra
if( Configuration.EnableActorWatch && Configuration.IsEnabled )
{
PlayerWatcher.EnableActorWatch();
PlayerWatcher.Enable();
}
PlayerWatcher.ActorChanged += a =>

View file

@ -83,7 +83,7 @@ namespace Penumbra.UI
_base._plugin.ActorRefresher.RedrawAll( enabled ? Redraw.WithSettings : Redraw.WithoutSettings );
if( _config.EnableActorWatch )
{
_base._plugin.PlayerWatcher.SetActorWatch( enabled );
_base._plugin.PlayerWatcher.SetStatus( enabled );
}
}
}
@ -164,7 +164,7 @@ namespace Penumbra.UI
{
_config.EnableActorWatch = enabled;
_configChanged = true;
_base._plugin.PlayerWatcher.SetActorWatch( enabled );
_base._plugin.PlayerWatcher.SetStatus( enabled );
}
if( ImGui.IsItemHovered() )