diff --git a/Penumbra/Api/IPenumbraApi.cs b/Penumbra/Api/IPenumbraApi.cs index a9288829..69b7479e 100644 --- a/Penumbra/Api/IPenumbraApi.cs +++ b/Penumbra/Api/IPenumbraApi.cs @@ -26,6 +26,8 @@ public delegate void ModSettingChanged( ModSettingChange type, string collection public delegate void CreatingCharacterBaseDelegate( IntPtr gameObject, ModCollection collection, IntPtr modelId, IntPtr customize, IntPtr equipData ); +public delegate void CreatedCharacterBaseDelegate( IntPtr gameObject, ModCollection collection, IntPtr drawObject ); + public enum PenumbraApiEc { Success = 0, @@ -73,6 +75,10 @@ public interface IPenumbraApi : IPenumbraApiBase // before the Draw Object is actually created, so customize and equipdata can be manipulated beforehand. public event CreatingCharacterBaseDelegate? CreatingCharacterBase; + // Triggered after a character base was created if a corresponding gameObject could be found, + // so you can apply flag changes after finishing. + public event CreatedCharacterBaseDelegate? CreatedCharacterBase; + // Queue redrawing of all actors of the given name with the given RedrawType. public void RedrawObject( string name, RedrawType setting ); diff --git a/Penumbra/Api/IpcTester.cs b/Penumbra/Api/IpcTester.cs index 3b3390ee..c251eb35 100644 --- a/Penumbra/Api/IpcTester.cs +++ b/Penumbra/Api/IpcTester.cs @@ -31,7 +31,8 @@ public class IpcTester : IDisposable private readonly ICallGateSubscriber< string, bool, object? > _modDirectoryChanged; private readonly ICallGateSubscriber< IntPtr, int, object? > _redrawn; private readonly ICallGateSubscriber< ModSettingChange, string, string, bool, object? > _settingChanged; - private readonly ICallGateSubscriber< IntPtr, string, IntPtr, IntPtr, IntPtr, object? > _characterBaseCreated; + private readonly ICallGateSubscriber< IntPtr, string, IntPtr, IntPtr, IntPtr, object? > _characterBaseCreating; + private readonly ICallGateSubscriber< IntPtr, string, IntPtr, object? > _characterBaseCreated; private readonly List< DateTimeOffset > _initializedList = new(); private readonly List< DateTimeOffset > _disposedList = new(); @@ -47,15 +48,17 @@ public class IpcTester : IDisposable _postSettingsDraw = _pi.GetIpcSubscriber< string, object? >( PenumbraIpc.LabelProviderPostSettingsDraw ); _settingChanged = _pi.GetIpcSubscriber< ModSettingChange, string, string, bool, object? >( PenumbraIpc.LabelProviderModSettingChanged ); _modDirectoryChanged = _pi.GetIpcSubscriber< string, bool, object? >( PenumbraIpc.LabelProviderModDirectoryChanged ); - _characterBaseCreated = + _characterBaseCreating = _pi.GetIpcSubscriber< IntPtr, string, IntPtr, IntPtr, IntPtr, object? >( PenumbraIpc.LabelProviderCreatingCharacterBase ); + _characterBaseCreated = _pi.GetIpcSubscriber< IntPtr, string, IntPtr, object? >( PenumbraIpc.LabelProviderCreatedCharacterBase ); _initialized.Subscribe( AddInitialized ); _disposed.Subscribe( AddDisposed ); _redrawn.Subscribe( SetLastRedrawn ); _preSettingsDraw.Subscribe( UpdateLastDrawnMod ); _postSettingsDraw.Subscribe( UpdateLastDrawnMod ); _settingChanged.Subscribe( UpdateLastModSetting ); - _characterBaseCreated.Subscribe( UpdateLastCreated ); + _characterBaseCreating.Subscribe( UpdateLastCreated ); + _characterBaseCreated.Subscribe( UpdateLastCreated2 ); _modDirectoryChanged.Subscribe( UpdateModDirectoryChanged ); } @@ -69,7 +72,8 @@ public class IpcTester : IDisposable _preSettingsDraw.Unsubscribe( UpdateLastDrawnMod ); _postSettingsDraw.Unsubscribe( UpdateLastDrawnMod ); _settingChanged.Unsubscribe( UpdateLastModSetting ); - _characterBaseCreated.Unsubscribe( UpdateLastCreated ); + _characterBaseCreating.Unsubscribe( UpdateLastCreated ); + _characterBaseCreated.Unsubscribe( UpdateLastCreated2 ); _modDirectoryChanged.Unsubscribe( UpdateModDirectoryChanged ); } @@ -218,6 +222,7 @@ public class IpcTester : IDisposable private IntPtr _currentDrawObject = IntPtr.Zero; private int _currentCutsceneActor = 0; private string _lastCreatedGameObjectName = string.Empty; + private IntPtr _lastCreatedDrawObject = IntPtr.Zero; private DateTimeOffset _lastCreatedGameObjectTime = DateTimeOffset.MaxValue; private unsafe void UpdateLastCreated( IntPtr gameObject, string _, IntPtr _2, IntPtr _3, IntPtr _4 ) @@ -225,6 +230,15 @@ public class IpcTester : IDisposable var obj = ( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* )gameObject; _lastCreatedGameObjectName = new Utf8String( obj->GetName() ).ToString(); _lastCreatedGameObjectTime = DateTimeOffset.Now; + _lastCreatedDrawObject = IntPtr.Zero; + } + + private unsafe void UpdateLastCreated2( IntPtr gameObject, string _, IntPtr drawObject ) + { + var obj = ( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* )gameObject; + _lastCreatedGameObjectName = new Utf8String( obj->GetName() ).ToString(); + _lastCreatedGameObjectTime = DateTimeOffset.Now; + _lastCreatedDrawObject = drawObject; } private void DrawResolve() @@ -318,7 +332,9 @@ public class IpcTester : IDisposable DrawIntro( PenumbraIpc.LabelProviderCreatingCharacterBase, "Last Drawobject created" ); if( _lastCreatedGameObjectTime < DateTimeOffset.Now ) { - ImGui.TextUnformatted( $"for <{_lastCreatedGameObjectName}> at {_lastCreatedGameObjectTime}" ); + ImGui.TextUnformatted( _lastCreatedDrawObject != IntPtr.Zero + ? $"0x{_lastCreatedDrawObject:X} for <{_lastCreatedGameObjectName}> at {_lastCreatedGameObjectTime}" + : $"NULL for <{_lastCreatedGameObjectName}> at {_lastCreatedGameObjectTime}" ); } } diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index 9e938322..fb708bbe 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -45,6 +45,12 @@ public class PenumbraApi : IDisposable, IPenumbraApi remove => PathResolver.DrawObjectState.CreatingCharacterBase -= value; } + public event CreatedCharacterBaseDelegate? CreatedCharacterBase + { + add => PathResolver.DrawObjectState.CreatedCharacterBase += value; + remove => PathResolver.DrawObjectState.CreatedCharacterBase -= value; + } + public bool Valid => _penumbra != null; diff --git a/Penumbra/Api/PenumbraIpc.cs b/Penumbra/Api/PenumbraIpc.cs index 98b3b7f9..34172c92 100644 --- a/Penumbra/Api/PenumbraIpc.cs +++ b/Penumbra/Api/PenumbraIpc.cs @@ -282,6 +282,7 @@ public partial class PenumbraIpc public const string LabelProviderReverseResolvePath = "Penumbra.ReverseResolvePath"; public const string LabelProviderReverseResolvePlayerPath = "Penumbra.ReverseResolvePlayerPath"; public const string LabelProviderCreatingCharacterBase = "Penumbra.CreatingCharacterBase"; + public const string LabelProviderCreatedCharacterBase = "Penumbra.CreatedCharacterBase"; internal ICallGateProvider< string, string >? ProviderResolveDefault; internal ICallGateProvider< string, string, string >? ProviderResolveCharacter; @@ -291,6 +292,7 @@ public partial class PenumbraIpc internal ICallGateProvider< string, string, string[] >? ProviderReverseResolvePath; internal ICallGateProvider< string, string[] >? ProviderReverseResolvePathPlayer; internal ICallGateProvider< IntPtr, string, IntPtr, IntPtr, IntPtr, object? >? ProviderCreatingCharacterBase; + internal ICallGateProvider< IntPtr, string, IntPtr, object? >? ProviderCreatedCharacterBase; private void InitializeResolveProviders( DalamudPluginInterface pi ) { @@ -374,6 +376,17 @@ public partial class PenumbraIpc { PluginLog.Error( $"Error registering IPC provider for {LabelProviderCreatingCharacterBase}:\n{e}" ); } + + try + { + ProviderCreatedCharacterBase = + pi.GetIpcProvider< IntPtr, string, IntPtr, object? >( LabelProviderCreatedCharacterBase ); + Api.CreatedCharacterBase += CreatedCharacterBaseEvent; + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderCreatedCharacterBase}:\n{e}" ); + } } private void DisposeResolveProviders() @@ -385,12 +398,18 @@ public partial class PenumbraIpc ProviderReverseResolvePath?.UnregisterFunc(); ProviderReverseResolvePathPlayer?.UnregisterFunc(); Api.CreatingCharacterBase -= CreatingCharacterBaseEvent; + Api.CreatedCharacterBase -= CreatedCharacterBaseEvent; } private void CreatingCharacterBaseEvent( IntPtr gameObject, ModCollection collection, IntPtr modelId, IntPtr customize, IntPtr equipData ) { ProviderCreatingCharacterBase?.SendMessage( gameObject, collection.Name, modelId, customize, equipData ); } + + private void CreatedCharacterBaseEvent( IntPtr gameObject, ModCollection collection, IntPtr drawObject ) + { + ProviderCreatedCharacterBase?.SendMessage( gameObject, collection.Name, drawObject ); + } } public partial class PenumbraIpc diff --git a/Penumbra/Collections/ConflictCache.cs b/Penumbra/Collections/ConflictCache.cs deleted file mode 100644 index e1bf9f42..00000000 --- a/Penumbra/Collections/ConflictCache.cs +++ /dev/null @@ -1,148 +0,0 @@ -using System; -using System.Collections.Generic; -using OtterGui.Classes; -using Penumbra.GameData.ByteString; -using Penumbra.Meta.Manipulations; - -namespace Penumbra.Collections; - -public struct ConflictCache -{ - // A conflict stores all data about a mod conflict. - public readonly struct Conflict : IComparable< Conflict > - { - public readonly object Data; - public readonly int Mod1; - public readonly int Mod2; - public readonly bool Mod1Priority; - public readonly bool Solved; - - public Conflict( int modIdx1, int modIdx2, bool priority, bool solved, object data ) - { - Mod1 = modIdx1; - Mod2 = modIdx2; - Data = data; - Mod1Priority = priority; - Solved = solved; - } - - // Order: Mod1 -> Mod1 overwritten -> Mod2 -> File > MetaManipulation - public int CompareTo( Conflict other ) - { - var idxComp = Mod1.CompareTo( other.Mod1 ); - if( idxComp != 0 ) - { - return idxComp; - } - - if( Mod1Priority != other.Mod1Priority ) - { - return Mod1Priority ? 1 : -1; - } - - idxComp = Mod2.CompareTo( other.Mod2 ); - if( idxComp != 0 ) - { - return idxComp; - } - - return Data switch - { - Utf8GamePath p when other.Data is Utf8GamePath q => p.CompareTo( q ), - Utf8GamePath => -1, - MetaManipulation m when other.Data is MetaManipulation n => m.CompareTo( n ), - MetaManipulation => 1, - _ => 0, - }; - } - - public override string ToString() - => ( Mod1Priority, Solved ) switch - { - (true, true) => $"{Penumbra.ModManager[ Mod1 ].Name} > {Penumbra.ModManager[ Mod2 ].Name} ({Data})", - (true, false) => $"{Penumbra.ModManager[ Mod1 ].Name} >= {Penumbra.ModManager[ Mod2 ].Name} ({Data})", - (false, true) => $"{Penumbra.ModManager[ Mod1 ].Name} < {Penumbra.ModManager[ Mod2 ].Name} ({Data})", - (false, false) => $"{Penumbra.ModManager[ Mod1 ].Name} <= {Penumbra.ModManager[ Mod2 ].Name} ({Data})", - }; - } - - private readonly List< Conflict > _conflicts = new(); - private bool _isSorted = true; - - public ConflictCache() - { } - - public IReadOnlyList< Conflict > Conflicts - { - get - { - Sort(); - return _conflicts; - } - } - - // Find all mod conflicts concerning the specified mod (in both directions). - public SubList< Conflict > ModConflicts( int modIdx ) - { - Sort(); - var start = _conflicts.FindIndex( c => c.Mod1 == modIdx ); - if( start < 0 ) - { - return SubList< Conflict >.Empty; - } - - var end = _conflicts.FindIndex( start, c => c.Mod1 != modIdx ); - return new SubList< Conflict >( _conflicts, start, end - start ); - } - - private void Sort() - { - if( !_isSorted ) - { - _conflicts?.Sort(); - _isSorted = true; - } - } - - // Add both directions for the mod. - // On same priority, it is assumed that mod1 is the earlier one. - // Also update older conflicts to refer to the highest-prioritized conflict. - private void AddConflict( int modIdx1, int modIdx2, int priority1, int priority2, object data ) - { - var solved = priority1 != priority2; - var priority = priority1 >= priority2; - var prioritizedMod = priority ? modIdx1 : modIdx2; - _conflicts.Add( new Conflict( modIdx1, modIdx2, priority, solved, data ) ); - _conflicts.Add( new Conflict( modIdx2, modIdx1, !priority, solved, data ) ); - for( var i = 0; i < _conflicts.Count; ++i ) - { - var c = _conflicts[ i ]; - if( data.Equals( c.Data ) ) - { - _conflicts[ i ] = c.Mod1Priority - ? new Conflict( prioritizedMod, c.Mod2, true, c.Solved || solved, data ) - : new Conflict( c.Mod1, prioritizedMod, false, c.Solved || solved, data ); - } - } - - _isSorted = false; - } - - public void AddConflict( int modIdx1, int modIdx2, int priority1, int priority2, Utf8GamePath gamePath ) - => AddConflict( modIdx1, modIdx2, priority1, priority2, ( object )gamePath ); - - public void AddConflict( int modIdx1, int modIdx2, int priority1, int priority2, MetaManipulation manipulation ) - => AddConflict( modIdx1, modIdx2, priority1, priority2, ( object )manipulation ); - - public void ClearConflicts() - => _conflicts?.Clear(); - - public void ClearFileConflicts() - => _conflicts?.RemoveAll( m => m.Data is Utf8GamePath ); - - public void ClearMetaConflicts() - => _conflicts?.RemoveAll( m => m.Data is MetaManipulation ); - - public void ClearConflictsWithMod( int modIdx ) - => _conflicts?.RemoveAll( m => m.Mod1 == modIdx || m.Mod2 == modIdx ); -} \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs b/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs index d51ffadd..ae0fc049 100644 --- a/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs +++ b/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs @@ -18,6 +18,7 @@ public unsafe partial class PathResolver public class DrawObjectState { public static event CreatingCharacterBaseDelegate? CreatingCharacterBase; + public static event CreatedCharacterBaseDelegate? CreatedCharacterBase; public IEnumerable< KeyValuePair< IntPtr, (ModCollection, int) > > DrawObjects => _drawObjectToObject; @@ -147,6 +148,7 @@ public unsafe partial class PathResolver if( LastGameObject != null ) { _drawObjectToObject[ ret ] = ( _lastCreatedCollection!, LastGameObject->ObjectIndex ); + CreatedCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!, ret ); } return ret;