From 1d935def5865ee2c8460d1a5600b0b4bd36adf57 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 10 Jun 2022 14:37:05 +0200 Subject: [PATCH] Fix object reloading in GPose, also add index-based redraw to API/IPC. --- Penumbra/Api/IPenumbraApi.cs | 3 + Penumbra/Api/PenumbraApi.cs | 6 ++ Penumbra/Api/PenumbraIpc.cs | 13 +++ Penumbra/Api/RedrawController.cs | 14 ++- Penumbra/Interop/ObjectReloader.cs | 139 +++++++++++++++++++++++------ 5 files changed, 145 insertions(+), 30 deletions(-) diff --git a/Penumbra/Api/IPenumbraApi.cs b/Penumbra/Api/IPenumbraApi.cs index 14c3b8ea..9714b497 100644 --- a/Penumbra/Api/IPenumbraApi.cs +++ b/Penumbra/Api/IPenumbraApi.cs @@ -34,6 +34,9 @@ public interface IPenumbraApi : IPenumbraApiBase // Queue redrawing of all actors of the given name with the given RedrawType. public void RedrawObject( string name, RedrawType setting ); + // Queue redrawing of the actor with the given object table index, if it exists, with the given RedrawType. + public void RedrawObject( int tableIndex, RedrawType setting ); + // Queue redrawing of the specific actor with the given RedrawType. Should only be used when the actor is sure to be valid. public void RedrawObject( GameObject gameObject, RedrawType setting ); diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index 9238607c..082e9642 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -71,6 +71,12 @@ public class PenumbraApi : IDisposable, IPenumbraApi } } + public void RedrawObject( int tableIndex, RedrawType setting ) + { + CheckInitialized(); + _penumbra!.ObjectReloader.RedrawObject( tableIndex, setting ); + } + public void RedrawObject( string name, RedrawType setting ) { CheckInitialized(); diff --git a/Penumbra/Api/PenumbraIpc.cs b/Penumbra/Api/PenumbraIpc.cs index d19d13b8..d8dc6db0 100644 --- a/Penumbra/Api/PenumbraIpc.cs +++ b/Penumbra/Api/PenumbraIpc.cs @@ -112,10 +112,12 @@ public partial class PenumbraIpc public partial class PenumbraIpc { public const string LabelProviderRedrawName = "Penumbra.RedrawObjectByName"; + public const string LabelProviderRedrawIndex = "Penumbra.RedrawObjectByIndex"; public const string LabelProviderRedrawObject = "Penumbra.RedrawObject"; public const string LabelProviderRedrawAll = "Penumbra.RedrawAll"; internal ICallGateProvider< string, int, object >? ProviderRedrawName; + internal ICallGateProvider< int, int, object >? ProviderRedrawIndex; internal ICallGateProvider< GameObject, int, object >? ProviderRedrawObject; internal ICallGateProvider< int, object >? ProviderRedrawAll; @@ -142,6 +144,16 @@ public partial class PenumbraIpc PluginLog.Error( $"Error registering IPC provider for {LabelProviderRedrawName}:\n{e}" ); } + try + { + ProviderRedrawIndex = pi.GetIpcProvider( LabelProviderRedrawIndex ); + ProviderRedrawIndex.RegisterAction( ( idx, i ) => Api.RedrawObject( idx, CheckRedrawType( i ) ) ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderRedrawName}:\n{e}" ); + } + try { ProviderRedrawObject = pi.GetIpcProvider< GameObject, int, object >( LabelProviderRedrawObject ); @@ -166,6 +178,7 @@ public partial class PenumbraIpc private void DisposeRedrawProviders() { ProviderRedrawName?.UnregisterAction(); + ProviderRedrawIndex?.UnregisterAction(); ProviderRedrawObject?.UnregisterAction(); ProviderRedrawAll?.UnregisterAction(); } diff --git a/Penumbra/Api/RedrawController.cs b/Penumbra/Api/RedrawController.cs index f00f1871..df470a1a 100644 --- a/Penumbra/Api/RedrawController.cs +++ b/Penumbra/Api/RedrawController.cs @@ -17,7 +17,18 @@ public class RedrawController : WebApiController public async Task Redraw() { var data = await HttpContext.GetRequestDataAsync< RedrawData >(); - _penumbra.Api.RedrawObject( data.Name, data.Type ); + if( data.ObjectTableIndex >= 0 ) + { + _penumbra.Api.RedrawObject( data.ObjectTableIndex, data.Type ); + } + else if( data.Name.Length > 0 ) + { + _penumbra.Api.RedrawObject( data.Name, data.Type ); + } + else + { + _penumbra.Api.RedrawAll( data.Type ); + } } [Route( HttpVerbs.Post, "/redrawAll" )] @@ -30,5 +41,6 @@ public class RedrawController : WebApiController { public string Name { get; set; } = string.Empty; public RedrawType Type { get; set; } = RedrawType.Redraw; + public int ObjectTableIndex { get; set; } = -1; } } \ No newline at end of file diff --git a/Penumbra/Interop/ObjectReloader.cs b/Penumbra/Interop/ObjectReloader.cs index bf89f061..45c64e03 100644 --- a/Penumbra/Interop/ObjectReloader.cs +++ b/Penumbra/Interop/ObjectReloader.cs @@ -8,12 +8,100 @@ using Penumbra.Interop.Structs; namespace Penumbra.Interop; -public sealed unsafe class ObjectReloader : IDisposable +public unsafe partial class ObjectReloader { public const int GPosePlayerIdx = 201; public const int GPoseSlots = 42; public const int GPoseEndIdx = GPosePlayerIdx + GPoseSlots; + private readonly string?[] _gPoseNames = new string?[GPoseSlots]; + private int _gPoseNameCounter = 0; + private bool _inGPose = false; + + // VFuncs that disable and enable draw, used only for GPose actors. + private static void DisableDraw( GameObject actor ) + => ( ( delegate* unmanaged< IntPtr, void >** )actor.Address )[ 0 ][ 17 ]( actor.Address ); + + private static void EnableDraw( GameObject actor ) + => ( ( delegate* unmanaged< IntPtr, void >** )actor.Address )[ 0 ][ 16 ]( actor.Address ); + + + // Check whether we currently are in GPose. + // Also clear the name list. + private void SetGPose() + { + _inGPose = Dalamud.Objects[ GPosePlayerIdx ] != null; + _gPoseNameCounter = 0; + } + + private static bool IsGPoseActor( int idx ) + => idx is >= GPosePlayerIdx and < GPoseEndIdx; + + // Return whether an object has to be replaced by a GPose object. + // If the object does not exist, is already a GPose actor + // or no actor of the same name is found in the GPose actor list, + // obj will be the object itself (or null) and false will be returned. + // If we are in GPose and a game object with the same name as the original actor is found, + // this will be in obj and true will be returned. + private bool FindCorrectActor( int idx, out GameObject? obj ) + { + obj = Dalamud.Objects[ idx ]; + if( !_inGPose || obj == null || IsGPoseActor( idx ) ) + { + return false; + } + + var name = obj.Name.ToString(); + for( var i = 0; i < _gPoseNameCounter; ++i ) + { + var gPoseName = _gPoseNames[ i ]; + if( gPoseName == null ) + { + break; + } + + if( name == gPoseName ) + { + obj = Dalamud.Objects[ GPosePlayerIdx + i ]; + return true; + } + } + + for( ; _gPoseNameCounter < GPoseSlots; ++_gPoseNameCounter ) + { + var gPoseName = Dalamud.Objects[ GPosePlayerIdx + _gPoseNameCounter ]?.Name.ToString(); + _gPoseNames[ _gPoseNameCounter ] = gPoseName; + if( gPoseName == null ) + { + break; + } + + if( name == gPoseName ) + { + obj = Dalamud.Objects[ GPosePlayerIdx + _gPoseNameCounter ]; + return true; + } + } + + return obj; + } + + // Do not ever redraw any of the five UI Window actors. + private static bool BadRedrawIndices( GameObject? actor, out int tableIndex ) + { + if( actor == null ) + { + tableIndex = -1; + return true; + } + + tableIndex = ObjectTableIndex( actor ); + return tableIndex is >= 240 and < 245; + } +} + +public sealed unsafe partial class ObjectReloader : IDisposable +{ private readonly List< int > _queue = new(100); private readonly List< int > _afterGPoseQueue = new(GPoseSlots); private int _target = -1; @@ -27,27 +115,9 @@ public sealed unsafe class ObjectReloader : IDisposable public static DrawState* ActorDrawState( GameObject actor ) => ( DrawState* )( actor.Address + 0x0104 ); - private static void DisableDraw( GameObject actor ) - => ( ( delegate* unmanaged< IntPtr, void >** )actor.Address )[ 0 ][ 17 ]( actor.Address ); - - private static void EnableDraw( GameObject actor ) - => ( ( delegate* unmanaged< IntPtr, void >** )actor.Address )[ 0 ][ 16 ]( actor.Address ); - private static int ObjectTableIndex( GameObject actor ) => ( ( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* )actor.Address )->ObjectIndex; - private static bool BadRedrawIndices( GameObject? actor, out int tableIndex ) - { - if( actor == null ) - { - tableIndex = -1; - return true; - } - - tableIndex = ObjectTableIndex( actor ); - return tableIndex is >= 240 and < 245; - } - private static void WriteInvisible( GameObject? actor ) { if( BadRedrawIndices( actor, out var tableIndex ) ) @@ -57,7 +127,7 @@ public sealed unsafe class ObjectReloader : IDisposable *ActorDrawState( actor! ) |= DrawState.Invisibility; - if( tableIndex is >= GPosePlayerIdx and < GPoseEndIdx ) + if( IsGPoseActor( tableIndex ) ) { DisableDraw( actor! ); } @@ -72,7 +142,7 @@ public sealed unsafe class ObjectReloader : IDisposable *ActorDrawState( actor! ) &= ~DrawState.Invisibility; - if( tableIndex is >= GPosePlayerIdx and < GPoseEndIdx ) + if( IsGPoseActor( tableIndex ) ) { EnableDraw( actor! ); } @@ -136,15 +206,22 @@ public sealed unsafe class ObjectReloader : IDisposable for( var i = 0; i < _queue.Count; ++i ) { var idx = _queue[ i ]; - if( idx < 0 ) + if( FindCorrectActor( idx < 0 ? ~idx : idx, out var obj ) ) { - var newIdx = ~idx; - WriteInvisible( Dalamud.Objects[ newIdx ] ); - _queue[ numKept++ ] = newIdx; + _afterGPoseQueue.Add( idx < 0 ? idx : ~idx ); } - else + + if( obj != null ) { - WriteVisible( Dalamud.Objects[ idx ] ); + if( idx < 0 ) + { + WriteInvisible( obj ); + _queue[ numKept++ ] = ObjectTableIndex( obj ); + } + else + { + WriteVisible( obj ); + } } } @@ -153,7 +230,7 @@ public sealed unsafe class ObjectReloader : IDisposable private void HandleAfterGPose() { - if( _afterGPoseQueue.Count == 0 || Dalamud.Objects[ GPosePlayerIdx ] != null ) + if( _afterGPoseQueue.Count == 0 || _inGPose ) { return; } @@ -174,7 +251,7 @@ public sealed unsafe class ObjectReloader : IDisposable } } - _afterGPoseQueue.RemoveRange( numKept, _queue.Count - numKept ); + _afterGPoseQueue.RemoveRange( numKept, _afterGPoseQueue.Count - numKept ); } private void OnUpdateEvent( object framework ) @@ -186,6 +263,7 @@ public sealed unsafe class ObjectReloader : IDisposable return; } + SetGPose(); HandleRedraw(); HandleAfterGPose(); HandleTarget(); @@ -229,6 +307,9 @@ public sealed unsafe class ObjectReloader : IDisposable return ret; } + public void RedrawObject( int tableIndex, RedrawType settings ) + => RedrawObject( Dalamud.Objects[tableIndex], settings ); + public void RedrawObject( string name, RedrawType settings ) { var lowerName = name.ToLowerInvariant();