From 363b22061318f69ff20463cd29e351ef6e927094 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 22 Jan 2024 16:53:13 +0100 Subject: [PATCH] Some improvements of the cutscene service. --- .../Interop/PathResolving/CutsceneService.cs | 74 +++++++++++++++++-- 1 file changed, 69 insertions(+), 5 deletions(-) diff --git a/Penumbra/Interop/PathResolving/CutsceneService.cs b/Penumbra/Interop/PathResolving/CutsceneService.cs index 2eeefbd8..85944d74 100644 --- a/Penumbra/Interop/PathResolving/CutsceneService.cs +++ b/Penumbra/Interop/PathResolving/CutsceneService.cs @@ -1,8 +1,10 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Object; using OtterGui.Services; using Penumbra.GameData.Enums; using Penumbra.Interop.Hooks.Objects; +using Penumbra.String; namespace Penumbra.Interop.PathResolving; @@ -22,15 +24,19 @@ public sealed class CutsceneService : IService, IDisposable .Where(i => _objects[i] != null) .Select(i => KeyValuePair.Create(i, this[i] ?? _objects[i]!)); - public unsafe CutsceneService(IObjectTable objects, CopyCharacter copyCharacter, CharacterDestructor characterDestructor) + public unsafe CutsceneService(IObjectTable objects, CopyCharacter copyCharacter, CharacterDestructor characterDestructor, + IClientState clientState) { _objects = objects; _copyCharacter = copyCharacter; _characterDestructor = characterDestructor; _copyCharacter.Subscribe(OnCharacterCopy, CopyCharacter.Priority.CutsceneService); _characterDestructor.Subscribe(OnCharacterDestructor, CharacterDestructor.Priority.CutsceneService); + if (clientState.IsGPosing) + RecoverGPoseActors(); } + /// /// Get the related actor to a cutscene actor. /// Does not check for valid input index. @@ -66,11 +72,28 @@ public sealed class CutsceneService : IService, IDisposable private unsafe void OnCharacterDestructor(Character* character) { - if (character->GameObject.ObjectIndex is < CutsceneStartIdx or >= CutsceneEndIdx) - return; + if (character->GameObject.ObjectIndex < CutsceneStartIdx) + { + // Remove all associations for now non-existing actor. + for (var i = 0; i < _copiedCharacters.Length; ++i) + { + if (_copiedCharacters[i] == character->GameObject.ObjectIndex) + { + // A hack to deal with GPose actors leaving and thus losing the link, we just set the home world instead. + // I do not think this breaks anything? + var address = (GameObject*)_objects.GetObjectAddress(i + CutsceneStartIdx); + if (address != null && address->GetObjectKind() is (byte)ObjectKind.Pc) + ((Character*)address)->HomeWorld = character->HomeWorld; - var idx = character->GameObject.ObjectIndex - CutsceneStartIdx; - _copiedCharacters[idx] = -1; + _copiedCharacters[i] = -1; + } + } + } + else if (character->GameObject.ObjectIndex < CutsceneEndIdx) + { + var idx = character->GameObject.ObjectIndex - CutsceneStartIdx; + _copiedCharacters[idx] = -1; + } } private unsafe void OnCharacterCopy(Character* target, Character* source) @@ -81,4 +104,45 @@ public sealed class CutsceneService : IService, IDisposable var idx = target->GameObject.ObjectIndex - CutsceneStartIdx; _copiedCharacters[idx] = (short)(source != null ? source->GameObject.ObjectIndex : -1); } + + /// Try to recover GPose actors on reloads into a running game. + /// This is not 100% accurate due to world IDs, minions etc., but will be mostly sane. + private unsafe void RecoverGPoseActors() + { + Dictionary? actors = null; + + for (var i = CutsceneStartIdx; i < CutsceneEndIdx; ++i) + { + if (!TryGetName(i, out var name)) + continue; + + if ((actors ??= CreateActors()).TryGetValue(name, out var idx)) + _copiedCharacters[i - CutsceneStartIdx] = idx; + } + + return; + + bool TryGetName(int idx, out ByteString name) + { + name = ByteString.Empty; + var address = (GameObject*)_objects.GetObjectAddress(idx); + if (address == null) + return false; + + name = new ByteString(address->Name); + return !name.IsEmpty; + } + + Dictionary CreateActors() + { + var ret = new Dictionary(); + for (short i = 0; i < CutsceneStartIdx; ++i) + { + if (TryGetName(i, out var name)) + ret.TryAdd(name, i); + } + + return ret; + } + } }