From e617d0c1ea271a25009cd895302c54b188ed020e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 25 Jun 2021 16:08:29 +0200 Subject: [PATCH] Change actor redrawing to be frame based and allow for placeholders for redrawing. --- Penumbra/Game/ActorRefresher.cs | 266 ++++++++++++++++++++++++++++ Penumbra/Game/RefreshActors.cs | 61 ------- Penumbra/Plugin.cs | 11 +- Penumbra/UI/MenuTabs/TabSettings.cs | 2 +- 4 files changed, 275 insertions(+), 65 deletions(-) create mode 100644 Penumbra/Game/ActorRefresher.cs delete mode 100644 Penumbra/Game/RefreshActors.cs diff --git a/Penumbra/Game/ActorRefresher.cs b/Penumbra/Game/ActorRefresher.cs new file mode 100644 index 00000000..03049f79 --- /dev/null +++ b/Penumbra/Game/ActorRefresher.cs @@ -0,0 +1,266 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Threading.Tasks; +using Dalamud.Game.ClientState.Actors.Types; +using Dalamud.Plugin; +using Penumbra.Mods; + +namespace Penumbra.Game +{ + public enum Redraw + { + WithoutSettings, + WithSettings, + OnlyWithSettings, + Unload, + RedrawWithoutSettings, + RedrawWithSettings, + } + + public class ActorRefresher : IDisposable + { + private const int RenderModeOffset = 0x0104; + private const int ModelInvisibilityFlag = 0b10; + private const int UnloadAllRedrawDelay = 250; + private const int NpcActorId = -536870912; + + private readonly DalamudPluginInterface _pi; + private readonly ModManager _mods; + private readonly Queue< (int actorId, string name, Redraw s) > _actorIds = new(); + + private int _currentFrame = 0; + private bool _changedSettings = false; + private int _currentActorId = -1; + private string? _currentActorName = null; + + public ActorRefresher( DalamudPluginInterface pi, ModManager mods ) + { + _pi = pi; + _mods = mods; + } + + private void ChangeSettings() + => _changedSettings = true; + + private void RestoreSettings() + => _changedSettings = false; + + private static unsafe void WriteInvisible( IntPtr renderPtr ) + { + if( renderPtr != IntPtr.Zero ) + { + *( int* )renderPtr |= ModelInvisibilityFlag; + } + } + + private static unsafe void WriteVisible( IntPtr renderPtr ) + { + if( renderPtr != IntPtr.Zero ) + { + *( int* )renderPtr &= ~ModelInvisibilityFlag; + } + } + + private bool CheckActor( Actor actor ) + { + if( _currentActorId != actor.ActorId ) + { + return false; + } + + if( _currentActorId != NpcActorId ) + { + return true; + } + + return _currentActorName == actor.Name; + } + + private Actor? FindCurrentActor() + => _pi.ClientState.Actors.FirstOrDefault( CheckActor ); + + private void ChangeSettingsAndUndraw() + { + if( _actorIds.Count > 0 ) + { + var (id, name, s) = _actorIds.Dequeue(); + _currentActorName = name; + _currentActorId = id; + var actor = FindCurrentActor(); + if( actor == null ) + { + return; + } + + switch( s ) + { + case Redraw.Unload: + WriteInvisible( actor.Address + RenderModeOffset ); + _currentFrame = 0; + break; + case Redraw.RedrawWithSettings: + ChangeSettings(); + ++_currentFrame; + break; + case Redraw.RedrawWithoutSettings: + WriteVisible( actor.Address + RenderModeOffset ); + _currentFrame = 0; + break; + case Redraw.WithoutSettings: + WriteInvisible( actor.Address + RenderModeOffset ); + ++_currentFrame; + break; + case Redraw.WithSettings: + ChangeSettings(); + WriteInvisible( actor.Address + RenderModeOffset ); + ++_currentFrame; + break; + case Redraw.OnlyWithSettings: + ChangeSettings(); + if( !_changedSettings ) + { + return; + } + + WriteInvisible( actor.Address + RenderModeOffset ); + ++_currentFrame; + break; + default: throw new InvalidEnumArgumentException(); + } + } + else + { + _pi.Framework.OnUpdateEvent -= OnUpdateEvent; + } + } + + private void StartRedraw() + { + var actor = FindCurrentActor(); + if( actor == null ) + { + RevertSettings(); + return; + } + + WriteVisible( actor.Address + RenderModeOffset ); + _currentFrame = _changedSettings ? _currentFrame + 1 : 0; + } + + private void RevertSettings() + { + RestoreSettings(); + _currentFrame = 0; + } + + private void OnUpdateEvent( object framework ) + { + switch( _currentFrame ) + { + case 0: + ChangeSettingsAndUndraw(); + break; + case 1: + StartRedraw(); + break; + case 2: + RevertSettings(); + break; + default: + _currentFrame = 0; + break; + } + } + + private void RedrawActorIntern( int actorId, string actorName, Redraw settings ) + { + if( _actorIds.Contains( ( actorId, actorName, settings ) ) ) + { + return; + } + + _actorIds.Enqueue( ( actorId, actorName, settings ) ); + if( _actorIds.Count == 1 ) + { + _pi.Framework.OnUpdateEvent += OnUpdateEvent; + } + } + + public void RedrawActor( Actor? actor, Redraw settings = Redraw.WithSettings ) + { + if( actor != null ) + { + RedrawActorIntern( actor.ActorId, actor.Name, settings ); + } + } + + private Actor? GetName( string name ) + { + var lowerName = name.ToLowerInvariant(); + return lowerName switch + { + "" => null, + "" => _pi.ClientState.Actors[ 0 ], + "self" => _pi.ClientState.Actors[ 0 ], + "" => _pi.ClientState.Targets.CurrentTarget, + "target" => _pi.ClientState.Targets.CurrentTarget, + "" => _pi.ClientState.Targets.FocusTarget, + "focus" => _pi.ClientState.Targets.FocusTarget, + "" => _pi.ClientState.Targets.MouseOverTarget, + "mouseover" => _pi.ClientState.Targets.MouseOverTarget, + _ => _pi.ClientState.Actors.FirstOrDefault( + a => string.Equals( a.Name, lowerName, StringComparison.InvariantCultureIgnoreCase ) ), + }; + } + + public void RedrawActor( string name, Redraw settings = Redraw.WithSettings ) + => RedrawActor( GetName( name ), settings ); + + public void RedrawAll( Redraw settings = Redraw.WithSettings ) + { + foreach( var actor in _pi.ClientState.Actors ) + { + RedrawActor( actor, settings ); + } + } + + private void UnloadAll() + { + foreach( var a in _pi.ClientState.Actors ) + { + WriteInvisible( a.Address + RenderModeOffset ); + } + } + + private void RedrawAllWithoutSettings() + { + foreach( var a in _pi.ClientState.Actors ) + { + WriteVisible( a.Address + RenderModeOffset ); + } + } + + public async void UnloadAtOnceRedrawWithSettings() + { + UnloadAll(); + await Task.Delay( UnloadAllRedrawDelay ); + RedrawAll( Redraw.RedrawWithSettings ); + } + + public async void UnloadAtOnceRedrawWithoutSettings() + { + UnloadAll(); + await Task.Delay( UnloadAllRedrawDelay ); + RedrawAllWithoutSettings(); + } + + public void Dispose() + { + RevertSettings(); + _actorIds.Clear(); + _pi.Framework.OnUpdateEvent -= OnUpdateEvent; + } + } +} \ No newline at end of file diff --git a/Penumbra/Game/RefreshActors.cs b/Penumbra/Game/RefreshActors.cs deleted file mode 100644 index d3e70e95..00000000 --- a/Penumbra/Game/RefreshActors.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Linq; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using Dalamud.Game.ClientState.Actors; -using Dalamud.Game.ClientState.Actors.Types; - -namespace Penumbra.Game -{ - public static class RefreshActors - { - private const int RenderModeOffset = 0x0104; - private const int RenderTaskPlayerDelay = 75; - private const int RenderTaskOtherDelay = 25; - private const int ModelInvisibilityFlag = 0b10; - - private static async void Redraw( Actor actor ) - { - var ptr = actor.Address; - var renderModePtr = ptr + RenderModeOffset; - var renderStatus = Marshal.ReadInt32( renderModePtr ); - - async void DrawObject( int delay ) - { - Marshal.WriteInt32( renderModePtr, renderStatus | ModelInvisibilityFlag ); - await Task.Delay( delay ); - Marshal.WriteInt32( renderModePtr, renderStatus & ~ModelInvisibilityFlag ); - } - - if( actor.ObjectKind == ObjectKind.Player ) - { - DrawObject( RenderTaskPlayerDelay ); - await Task.Delay( RenderTaskPlayerDelay ); - } - else - { - DrawObject( RenderTaskOtherDelay ); - } - } - - public static void RedrawSpecific( ActorTable actors, string name ) - { - if( name?.Length == 0 ) - { - RedrawAll( actors ); - } - - foreach( var actor in actors.Where( a => a.Name == name ) ) - { - Redraw( actor ); - } - } - - public static void RedrawAll( ActorTable actors ) - { - foreach( var actor in actors ) - { - Redraw( actor ); - } - } - } -} \ No newline at end of file diff --git a/Penumbra/Plugin.cs b/Penumbra/Plugin.cs index 072cbf0d..7205ff89 100644 --- a/Penumbra/Plugin.cs +++ b/Penumbra/Plugin.cs @@ -1,4 +1,5 @@ using System.Linq; +using Dalamud.Game.ClientState.Actors.Types; using Dalamud.Game.Command; using Dalamud.Plugin; using EmbedIO; @@ -31,6 +32,7 @@ namespace Penumbra public ResourceLoader ResourceLoader { get; set; } = null!; public SettingsInterface SettingsInterface { get; set; } = null!; public MusicManager SoundShit { get; set; } = null!; + public ActorRefresher ActorRefresher { get; set; } = null!; private WebServer? _webServer; @@ -50,6 +52,8 @@ namespace Penumbra modManager.DiscoverMods(); + ActorRefresher = new ActorRefresher( PluginInterface, modManager ); + ResourceLoader = new ResourceLoader( this ); PluginInterface.CommandManager.AddHandler( CommandName, new CommandInfo( OnCommand ) @@ -98,6 +102,7 @@ namespace Penumbra public void Dispose() { + ActorRefresher.Dispose(); PluginInterface.UiBuilder.OnBuildUi -= SettingsInterface.Draw; PluginInterface.CommandManager.RemoveHandler( CommandName ); @@ -110,7 +115,7 @@ namespace Penumbra private void OnCommand( string command, string rawArgs ) { - var args = rawArgs.Split( ' ' ); + var args = rawArgs.Split( new[] { ' ' }, 2 ); if( args.Length > 0 && args[ 0 ].Length > 0 ) { switch( args[ 0 ] ) @@ -127,11 +132,11 @@ namespace Penumbra { if( args.Length > 1 ) { - RefreshActors.RedrawSpecific( PluginInterface.ClientState.Actors, string.Join( " ", args.Skip( 1 ) ) ); + ActorRefresher.RedrawActor( args[ 1 ] ); } else { - RefreshActors.RedrawAll( PluginInterface.ClientState.Actors ); + ActorRefresher.RedrawAll(); } break; diff --git a/Penumbra/UI/MenuTabs/TabSettings.cs b/Penumbra/UI/MenuTabs/TabSettings.cs index f778617c..df29fde4 100644 --- a/Penumbra/UI/MenuTabs/TabSettings.cs +++ b/Penumbra/UI/MenuTabs/TabSettings.cs @@ -68,7 +68,7 @@ namespace Penumbra.UI { _config.IsEnabled = enabled; _configChanged = true; - Game.RefreshActors.RedrawAll( _base._plugin!.PluginInterface!.ClientState.Actors ); + _base._plugin.ActorRefresher.RedrawAll( ); } }