From d99707f77ef9736e7474e540dd72faa583d584fd Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 26 Jul 2021 15:53:41 +0200 Subject: [PATCH] Move PlayerWatcher to own assembly and make appropriate changes for reuse. --- Penumbra.PlayerWatch/IPlayerWatcher.cs | 28 +++ .../Penumbra.PlayerWatch.csproj | 41 +++++ Penumbra.PlayerWatch/PlayerWatchBase.cs | 164 ++++++++++++++++++ Penumbra.PlayerWatch/PlayerWatcher.cs | 88 ++++++++++ Penumbra.sln | 8 +- Penumbra/Interop/PlayerWatcher.cs | 127 -------------- Penumbra/Penumbra.csproj | 1 + Penumbra/Plugin.cs | 7 +- Penumbra/UI/MenuTabs/TabSettings.cs | 4 +- 9 files changed, 335 insertions(+), 133 deletions(-) create mode 100644 Penumbra.PlayerWatch/IPlayerWatcher.cs create mode 100644 Penumbra.PlayerWatch/Penumbra.PlayerWatch.csproj create mode 100644 Penumbra.PlayerWatch/PlayerWatchBase.cs create mode 100644 Penumbra.PlayerWatch/PlayerWatcher.cs delete mode 100644 Penumbra/Interop/PlayerWatcher.cs diff --git a/Penumbra.PlayerWatch/IPlayerWatcher.cs b/Penumbra.PlayerWatch/IPlayerWatcher.cs new file mode 100644 index 00000000..280ee0a9 --- /dev/null +++ b/Penumbra.PlayerWatch/IPlayerWatcher.cs @@ -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 ); + } +} \ No newline at end of file diff --git a/Penumbra.PlayerWatch/Penumbra.PlayerWatch.csproj b/Penumbra.PlayerWatch/Penumbra.PlayerWatch.csproj new file mode 100644 index 00000000..b3b90ead --- /dev/null +++ b/Penumbra.PlayerWatch/Penumbra.PlayerWatch.csproj @@ -0,0 +1,41 @@ + + + net472 + preview + Penumbra.PlayerWatch + absolute gangstas + Penumbra + Copyright © 2020 + 1.0.0.0 + 1.0.0.0 + bin\$(Configuration)\ + true + enable + + + + full + DEBUG;TRACE + + + + pdbonly + + + + $(MSBuildWarningsAsMessages);MSB3277 + + + + + $(DALAMUD_ROOT)\Dalamud.dll + ..\libs\Dalamud.dll + $(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll + False + + + + + + + \ No newline at end of file diff --git a/Penumbra.PlayerWatch/PlayerWatchBase.cs b/Penumbra.PlayerWatch/PlayerWatchBase.cs new file mode 100644 index 00000000..39022154 --- /dev/null +++ b/Penumbra.PlayerWatch/PlayerWatchBase.cs @@ -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 ); + } + } + } + } +} \ No newline at end of file diff --git a/Penumbra.PlayerWatch/PlayerWatcher.cs b/Penumbra.PlayerWatch/PlayerWatcher.cs new file mode 100644 index 00000000..e9644dd3 --- /dev/null +++ b/Penumbra.PlayerWatch/PlayerWatcher.cs @@ -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 ); + } +} \ No newline at end of file diff --git a/Penumbra.sln b/Penumbra.sln index 587b4ab3..58df4bf5 100644 --- a/Penumbra.sln +++ b/Penumbra.sln @@ -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 diff --git a/Penumbra/Interop/PlayerWatcher.cs b/Penumbra/Interop/PlayerWatcher.cs deleted file mode 100644 index 56eafaa5..00000000 --- a/Penumbra/Interop/PlayerWatcher.cs +++ /dev/null @@ -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 ); - } - } - } - } -} \ No newline at end of file diff --git a/Penumbra/Penumbra.csproj b/Penumbra/Penumbra.csproj index 822385de..3ea18e2a 100644 --- a/Penumbra/Penumbra.csproj +++ b/Penumbra/Penumbra.csproj @@ -75,6 +75,7 @@ + diff --git a/Penumbra/Plugin.cs b/Penumbra/Plugin.cs index f3a32f18..2ef25d7c 100644 --- a/Penumbra/Plugin.cs +++ b/Penumbra/Plugin.cs @@ -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 => diff --git a/Penumbra/UI/MenuTabs/TabSettings.cs b/Penumbra/UI/MenuTabs/TabSettings.cs index 373d1d3f..cf26c3c2 100644 --- a/Penumbra/UI/MenuTabs/TabSettings.cs +++ b/Penumbra/UI/MenuTabs/TabSettings.cs @@ -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() )