diff --git a/Penumbra/Interop/Hooks/Objects/CharacterDestructor.cs b/Penumbra/Interop/Hooks/Objects/CharacterDestructor.cs
index ffe2f72d..55b392ba 100644
--- a/Penumbra/Interop/Hooks/Objects/CharacterDestructor.cs
+++ b/Penumbra/Interop/Hooks/Objects/CharacterDestructor.cs
@@ -15,6 +15,9 @@ public sealed unsafe class CharacterDestructor : EventWrapperPtr
IdentifiedCollectionCache = 0,
+
+ ///
+ DrawObjectState = 0,
}
public CharacterDestructor(HookManager hooks)
diff --git a/Penumbra/Interop/PathResolving/DrawObjectState.cs b/Penumbra/Interop/PathResolving/DrawObjectState.cs
index 5e413fe2..28a0dd8d 100644
--- a/Penumbra/Interop/PathResolving/DrawObjectState.cs
+++ b/Penumbra/Interop/PathResolving/DrawObjectState.cs
@@ -15,6 +15,7 @@ public sealed class DrawObjectState : IDisposable, IReadOnlyDictionary _drawObjectToGameObject = [];
@@ -23,21 +24,24 @@ public sealed class DrawObjectState : IDisposable, IReadOnlyDictionary _gameState.LastGameObject;
public unsafe DrawObjectState(ObjectManager objects, CreateCharacterBase createCharacterBase, WeaponReload weaponReload,
- CharacterBaseDestructor characterBaseDestructor, GameState gameState, IFramework framework)
+ CharacterBaseDestructor characterBaseDestructor, GameState gameState, IFramework framework, CharacterDestructor characterDestructor)
{
_objects = objects;
_createCharacterBase = createCharacterBase;
_weaponReload = weaponReload;
_characterBaseDestructor = characterBaseDestructor;
_gameState = gameState;
+ _characterDestructor = characterDestructor;
framework.RunOnFrameworkThread(InitializeDrawObjects);
_weaponReload.Subscribe(OnWeaponReloading, WeaponReload.Priority.DrawObjectState);
_weaponReload.Subscribe(OnWeaponReloaded, WeaponReload.PostEvent.Priority.DrawObjectState);
_createCharacterBase.Subscribe(OnCharacterBaseCreated, CreateCharacterBase.PostEvent.Priority.DrawObjectState);
_characterBaseDestructor.Subscribe(OnCharacterBaseDestructor, CharacterBaseDestructor.Priority.DrawObjectState);
+ _characterDestructor.Subscribe(OnCharacterDestructor, CharacterDestructor.Priority.DrawObjectState);
}
+
public bool ContainsKey(nint key)
=> _drawObjectToGameObject.ContainsKey(key);
@@ -68,6 +72,36 @@ public sealed class DrawObjectState : IDisposable, IReadOnlyDictionary
+ /// Seems like sometimes the draw object of a game object is destroyed in frames after the original game object is already destroyed.
+ /// So protect against outdated game object pointers in the dictionary.
+ ///
+ private unsafe void OnCharacterDestructor(Character* a)
+ {
+ if (a is null)
+ return;
+
+ var character = (nint)a;
+ var delete = stackalloc nint[5];
+ var current = 0;
+ foreach (var (drawObject, (gameObject, _)) in _drawObjectToGameObject)
+ {
+ if (gameObject != character)
+ continue;
+
+ delete[current++] = drawObject;
+ if (current is 4)
+ break;
+ }
+
+ for (var ptr = delete; *ptr != nint.Zero; ++ptr)
+ {
+ _drawObjectToGameObject.Remove(*ptr, out var pair);
+ Penumbra.Log.Excessive($"[DrawObjectState] Removed draw object 0x{*ptr:X} -> 0x{(nint)a:X} (actual: 0x{pair.GameObject:X}, {pair.IsChild}).");
+ }
}
private unsafe void OnWeaponReloading(DrawDataContainer* _, Character* character, CharacterWeapon* _2)
diff --git a/Penumbra/UI/Changelog.cs b/Penumbra/UI/Changelog.cs
index 32abeb41..5e1612eb 100644
--- a/Penumbra/UI/Changelog.cs
+++ b/Penumbra/UI/Changelog.cs
@@ -90,7 +90,6 @@ public class PenumbraChangelog : IUiService
.RegisterEntry("The EQP entry previously named Unknown 4 was renamed to 'Hide Glove Cuffs'.")
.RegisterEntry("Fixed the changed item identification for EST changes.")
.RegisterEntry("Fixed clipping issues in the changed items panel when no grouping was active.");
-
private static void Add1_3_5_0(Changelog log)