Skip to content

Commit

Permalink
Updated to latest Penumbra.GameData, updated ObjectManager
Browse files Browse the repository at this point in the history
  • Loading branch information
RisaDev committed Feb 3, 2024
1 parent 24aaa30 commit dc7fb73
Show file tree
Hide file tree
Showing 16 changed files with 313 additions and 246 deletions.
16 changes: 9 additions & 7 deletions CustomizePlus.GameData/Extensions/ActorIdentifierExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using Dalamud.Game.ClientState.Objects.Enums;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
using PenumbraExtensions = Penumbra.GameData.Actors.ActorIdentifierExtensions;

namespace CustomizePlus.GameData.Extensions;

Expand All @@ -19,10 +21,10 @@ public static string ToNameWithoutOwnerName(this ActorIdentifier identifier)
if (identifier.Type != IdentifierType.Owned)
return identifier.ToName();

if (ActorIdentifier.Manager == null)
if (PenumbraExtensions.Manager == null)
throw new Exception("ActorIdentifier.Manager is not initialized");

return ActorIdentifier.Manager.Data.ToName(identifier.Kind, identifier.DataId);
return PenumbraExtensions.Manager.Data.ToName(identifier.Kind, identifier.DataId);
}

/// <summary>
Expand Down Expand Up @@ -93,7 +95,7 @@ public static ActorIdentifier GetTrueActorForSpecialType(this ActorIdentifier id
if (identifier.Type != IdentifierType.Special)
return ActorIdentifier.Invalid;

if (ActorIdentifier.Manager == null)
if (PenumbraExtensions.Manager == null)
throw new Exception("ActorIdentifier.Manager is not initialized");

switch (identifier.Special)
Expand All @@ -103,12 +105,12 @@ public static ActorIdentifier GetTrueActorForSpecialType(this ActorIdentifier id
case ScreenActor.FittingRoom:
case ScreenActor.DyePreview:
case ScreenActor.Portrait:
return ActorIdentifier.Manager.GetCurrentPlayer();
return PenumbraExtensions.Manager.GetCurrentPlayer();
case ScreenActor.ExamineScreen:
var examineIdentifier = ActorIdentifier.Manager.GetInspectPlayer();
var examineIdentifier = PenumbraExtensions.Manager.GetInspectPlayer();

if (!examineIdentifier.IsValid)
examineIdentifier = ActorIdentifier.Manager.GetGlamourPlayer(); //returns ActorIdentifier.Invalid if player is invalid
examineIdentifier = PenumbraExtensions.Manager.GetGlamourPlayer(); //returns ActorIdentifier.Invalid if player is invalid

if (!examineIdentifier.IsValid)
return ActorIdentifier.Invalid;
Expand All @@ -117,7 +119,7 @@ public static ActorIdentifier GetTrueActorForSpecialType(this ActorIdentifier id
case ScreenActor.Card6:
case ScreenActor.Card7:
case ScreenActor.Card8:
return ActorIdentifier.Manager.GetCardPlayer();
return PenumbraExtensions.Manager.GetCardPlayer();
}

return ActorIdentifier.Invalid;
Expand Down
48 changes: 48 additions & 0 deletions CustomizePlus.GameData/Hooks/Objects/CharacterDestructor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Dalamud.Hooking;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using OtterGui.Classes;
using OtterGui.Services;
using Penumbra.GameData;

namespace CustomizePlus.GameData.Hooks.Objects;
public sealed unsafe class CharacterDestructor : EventWrapperPtr<Character, CharacterDestructor.Priority>, IHookService
{
public enum Priority
{
/// <seealso cref="PathResolving.CutsceneService"/>
CutsceneService = 0,

/// <seealso cref="PathResolving.IdentifiedCollectionCache"/>
IdentifiedCollectionCache = 0,
}

public CharacterDestructor(HookManager hooks)
: base("Character Destructor")
=> _task = hooks.CreateHook<Delegate>(Name, Sigs.CharacterDestructor, Detour, true);

private readonly Task<Hook<Delegate>> _task;

public nint Address
=> _task.Result.Address;

public void Enable()
=> _task.Result.Enable();

public void Disable()
=> _task.Result.Disable();

public Task Awaiter
=> _task;

public bool Finished
=> _task.IsCompletedSuccessfully;

private delegate void Delegate(Character* character);

private void Detour(Character* character)
{
//Penumbra.Log.Verbose($"[{Name}] Triggered with 0x{(nint)character:X}.");
Invoke(character);
_task.Result.Original(character);
}
}
46 changes: 46 additions & 0 deletions CustomizePlus.GameData/Hooks/Objects/CopyCharacter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Dalamud.Hooking;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using OtterGui.Classes;
using OtterGui.Services;

namespace CustomizePlus.GameData.Hooks.Objects;
public sealed unsafe class CopyCharacter : EventWrapperPtr<Character, Character, CopyCharacter.Priority>, IHookService
{
public enum Priority
{
/// <seealso cref="PathResolving.CutsceneService"/>
CutsceneService = 0,
}

public CopyCharacter(HookManager hooks)
: base("Copy Character")
=> _task = hooks.CreateHook<Delegate>(Name, Address, Detour, true);

private readonly Task<Hook<Delegate>> _task;

public nint Address
=> (nint)CharacterSetup.MemberFunctionPointers.CopyFromCharacter;

public void Enable()
=> _task.Result.Enable();

public void Disable()
=> _task.Result.Disable();

public Task Awaiter
=> _task;

public bool Finished
=> _task.IsCompletedSuccessfully;

private delegate ulong Delegate(CharacterSetup* target, Character* source, uint unk);

private ulong Detour(CharacterSetup* target, Character* source, uint unk)
{
// TODO: update when CS updated.
var character = ((Character**)target)[1];
//Penumbra.Log.Verbose($"[{Name}] Triggered with target: 0x{(nint)target:X}, source : 0x{(nint)source:X} unk: {unk}.");
Invoke(character, source);
return _task.Result.Original(target, source, unk);
}
}
122 changes: 103 additions & 19 deletions CustomizePlus.GameData/Services/CutsceneService.cs
Original file line number Diff line number Diff line change
@@ -1,38 +1,43 @@
using Dalamud.Plugin.Services;
using CustomizePlus.GameData.Hooks.Objects;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using Penumbra.GameData.Actors;
using System;
using System.Collections.Generic;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using OtterGui.Services;
using Penumbra.GameData.Enums;
using Penumbra.String;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CustomizePlus.GameData.Services;

public class CutsceneService : IDisposable
public class CutsceneService : IService, IDisposable
{
public const int CutsceneStartIdx = (int)ScreenActor.CutsceneStart;
public const int CutsceneEndIdx = (int)ScreenActor.CutsceneEnd;
public const int CutsceneSlots = CutsceneEndIdx - CutsceneStartIdx;

private readonly GameEventManager _events;
private readonly IObjectTable _objects;
private readonly CopyCharacter _copyCharacter;
private readonly CharacterDestructor _characterDestructor;
private readonly short[] _copiedCharacters = Enumerable.Repeat((short)-1, CutsceneSlots).ToArray();

public IEnumerable<KeyValuePair<int, Dalamud.Game.ClientState.Objects.Types.GameObject>> Actors
=> Enumerable.Range(CutsceneStartIdx, CutsceneSlots)
.Where(i => _objects[i] != null)
.Select(i => KeyValuePair.Create(i, this[i] ?? _objects[i]!));

public unsafe CutsceneService(IObjectTable objects, GameEventManager events)
public unsafe CutsceneService(IObjectTable objects, CopyCharacter copyCharacter, CharacterDestructor characterDestructor,
IClientState clientState)
{
_objects = objects;
_events = events;
_events.CopyCharacter += OnCharacterCopy;
_events.CharacterDestructor += OnCharacterDestructor;
_copyCharacter = copyCharacter;
_characterDestructor = characterDestructor;
_copyCharacter.Subscribe(OnCharacterCopy, CopyCharacter.Priority.CutsceneService);
_characterDestructor.Subscribe(OnCharacterDestructor, CharacterDestructor.Priority.CutsceneService);
if (clientState.IsGPosing)
RecoverGPoseActors();
}


/// <summary>
/// Get the related actor to a cutscene actor.
/// Does not check for valid input index.
Expand All @@ -50,6 +55,27 @@ public Dalamud.Game.ClientState.Objects.Types.GameObject? this[int idx]

/// <summary> Return the currently set index of a parent or -1 if none is set or the index is invalid. </summary>
public int GetParentIndex(int idx)
=> GetParentIndex((ushort)idx);

public bool SetParentIndex(int copyIdx, int parentIdx)
{
if (copyIdx is < CutsceneStartIdx or >= CutsceneEndIdx)
return false;

if (parentIdx is < -1 or >= CutsceneEndIdx)
return false;

if (_objects.GetObjectAddress(copyIdx) == nint.Zero)
return false;

if (parentIdx != -1 && _objects.GetObjectAddress(parentIdx) == nint.Zero)
return false;

_copiedCharacters[copyIdx - CutsceneStartIdx] = (short)parentIdx;
return true;
}

public short GetParentIndex(ushort idx)
{
if (idx is >= CutsceneStartIdx and < CutsceneEndIdx)
return _copiedCharacters[idx - CutsceneStartIdx];
Expand All @@ -59,17 +85,34 @@ public int GetParentIndex(int idx)

public unsafe void Dispose()
{
_events.CopyCharacter -= OnCharacterCopy;
_events.CharacterDestructor -= OnCharacterDestructor;
_copyCharacter.Unsubscribe(OnCharacterCopy);
_characterDestructor.Unsubscribe(OnCharacterDestructor);
}

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)
Expand All @@ -80,4 +123,45 @@ private unsafe void OnCharacterCopy(Character* target, Character* source)
var idx = target->GameObject.ObjectIndex - CutsceneStartIdx;
_copiedCharacters[idx] = (short)(source != null ? source->GameObject.ObjectIndex : -1);
}

/// <summary> Try to recover GPose actors on reloads into a running game. </summary>
/// <remarks> This is not 100% accurate due to world IDs, minions etc., but will be mostly sane. </remarks>
private unsafe void RecoverGPoseActors()
{
Dictionary<ByteString, short>? 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<ByteString, short> CreateActors()
{
var ret = new Dictionary<ByteString, short>();
for (short i = 0; i < CutsceneStartIdx; ++i)
{
if (TryGetName(i, out var name))
ret.TryAdd(name, i);
}

return ret;
}
}
}
Loading

0 comments on commit dc7fb73

Please sign in to comment.