Use ObjectManager, Actor and Model.

This commit is contained in:
Ottermandias 2024-03-19 22:52:20 +01:00
parent 5b9309a311
commit c8216b0acc
22 changed files with 240 additions and 325 deletions

View file

@ -1,9 +1,9 @@
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using OtterGui.Services;
using Penumbra.Collections;
using Penumbra.CrashHandler.Buffers;
using Penumbra.GameData;
using Penumbra.GameData.Interop;
using Penumbra.Interop.PathResolving;
using Penumbra.Interop.Structs;
using Penumbra.Services;
@ -16,10 +16,10 @@ public sealed unsafe class LoadCharacterVfx : FastHook<LoadCharacterVfx.Delegate
{
private readonly GameState _state;
private readonly CollectionResolver _collectionResolver;
private readonly IObjectTable _objects;
private readonly ObjectManager _objects;
private readonly CrashHandlerService _crashHandler;
public LoadCharacterVfx(HookManager hooks, GameState state, CollectionResolver collectionResolver, IObjectTable objects,
public LoadCharacterVfx(HookManager hooks, GameState state, CollectionResolver collectionResolver, ObjectManager objects,
CrashHandlerService crashHandler)
{
_state = state;
@ -39,19 +39,19 @@ public sealed unsafe class LoadCharacterVfx : FastHook<LoadCharacterVfx.Delegate
{
var obj = vfxParams->GameObjectType switch
{
0 => _objects.SearchById(vfxParams->GameObjectId),
0 => _objects.ById(vfxParams->GameObjectId),
2 => _objects[(int)vfxParams->GameObjectId],
4 => GetOwnedObject(vfxParams->GameObjectId),
_ => null,
_ => Actor.Null,
};
newData = obj != null
newData = obj.Valid
? _collectionResolver.IdentifyCollection((GameObject*)obj.Address, true)
: ResolveData.Invalid;
}
var last = _state.SetAnimationData(newData);
_crashHandler.LogAnimation(newData.AssociatedGameObject, newData.ModCollection, AnimationInvocationType.LoadCharacterVfx);
var ret = Task.Result.Original(vfxPath, vfxParams, unk1, unk2, unk3, unk4);
var ret = Task.Result.Original(vfxPath, vfxParams, unk1, unk2, unk3, unk4);
Penumbra.Log.Excessive(
$"[Load Character VFX] Invoked with {new ByteString(vfxPath)}, 0x{vfxParams->GameObjectId:X}, {vfxParams->TargetCount}, {unk1}, {unk2}, {unk3}, {unk4} -> 0x{ret:X}.");
_state.RestoreAnimationData(last);
@ -59,13 +59,11 @@ public sealed unsafe class LoadCharacterVfx : FastHook<LoadCharacterVfx.Delegate
}
/// <summary> Search an object by its id, then get its minion/mount/ornament. </summary>
private Dalamud.Game.ClientState.Objects.Types.GameObject? GetOwnedObject(uint id)
private Actor GetOwnedObject(uint id)
{
var owner = _objects.SearchById(id);
if (owner == null)
return null;
var idx = ((GameObject*)owner.Address)->ObjectIndex;
return _objects[idx + 1];
var owner = _objects.ById(id);
return !owner.Valid
? Actor.Null
: _objects[owner.Index.Index + 1];
}
}

View file

@ -3,8 +3,8 @@ using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using OtterGui.Services;
using Penumbra.Collections;
using Penumbra.CrashHandler;
using Penumbra.GameData;
using Penumbra.GameData.Interop;
using Penumbra.Interop.PathResolving;
using Penumbra.Services;
@ -19,11 +19,11 @@ public sealed unsafe class LoadTimelineResources : FastHook<LoadTimelineResource
private readonly GameState _state;
private readonly CollectionResolver _collectionResolver;
private readonly ICondition _conditions;
private readonly IObjectTable _objects;
private readonly ObjectManager _objects;
private readonly CrashHandlerService _crashHandler;
public LoadTimelineResources(HookManager hooks, GameState state, CollectionResolver collectionResolver, ICondition conditions,
IObjectTable objects, CrashHandlerService crashHandler)
ObjectManager objects, CrashHandlerService crashHandler)
{
_state = state;
_collectionResolver = collectionResolver;
@ -56,7 +56,7 @@ public sealed unsafe class LoadTimelineResources : FastHook<LoadTimelineResource
}
/// <summary> Use timelines vfuncs to obtain the associated game object. </summary>
public static ResolveData GetDataFromTimeline(IObjectTable objects, CollectionResolver resolver, nint timeline)
public static ResolveData GetDataFromTimeline(ObjectManager objects, CollectionResolver resolver, nint timeline)
{
try
{
@ -64,10 +64,10 @@ public sealed unsafe class LoadTimelineResources : FastHook<LoadTimelineResource
{
var getGameObjectIdx = ((delegate* unmanaged<nint, int>**)timeline)[0][Offsets.GetGameObjectIdxVfunc];
var idx = getGameObjectIdx(timeline);
if (idx >= 0 && idx < objects.Length)
if (idx >= 0 && idx < objects.Count)
{
var obj = (GameObject*)objects.GetObjectAddress(idx);
return obj != null ? resolver.IdentifyCollection(obj, true) : ResolveData.Invalid;
var obj = objects[idx];
return obj.Valid ? resolver.IdentifyCollection(obj.AsObject, true) : ResolveData.Invalid;
}
}
}

View file

@ -1,7 +1,7 @@
using Dalamud.Plugin.Services;
using OtterGui.Services;
using Penumbra.CrashHandler.Buffers;
using Penumbra.GameData;
using Penumbra.GameData.Interop;
using Penumbra.Interop.PathResolving;
using Penumbra.Interop.Structs;
using Penumbra.Services;
@ -13,10 +13,10 @@ public sealed unsafe class ScheduleClipUpdate : FastHook<ScheduleClipUpdate.Dele
{
private readonly GameState _state;
private readonly CollectionResolver _collectionResolver;
private readonly IObjectTable _objects;
private readonly ObjectManager _objects;
private readonly CrashHandlerService _crashHandler;
public ScheduleClipUpdate(HookManager hooks, GameState state, CollectionResolver collectionResolver, IObjectTable objects,
public ScheduleClipUpdate(HookManager hooks, GameState state, CollectionResolver collectionResolver, ObjectManager objects,
CrashHandlerService crashHandler)
{
_state = state;

View file

@ -1,8 +1,7 @@
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using OtterGui.Services;
using Penumbra.CrashHandler.Buffers;
using Penumbra.GameData;
using Penumbra.GameData.Interop;
using Penumbra.Interop.PathResolving;
using Penumbra.Services;
@ -13,10 +12,10 @@ public sealed unsafe class SomePapLoad : FastHook<SomePapLoad.Delegate>
{
private readonly GameState _state;
private readonly CollectionResolver _collectionResolver;
private readonly IObjectTable _objects;
private readonly ObjectManager _objects;
private readonly CrashHandlerService _crashHandler;
public SomePapLoad(HookManager hooks, GameState state, CollectionResolver collectionResolver, IObjectTable objects,
public SomePapLoad(HookManager hooks, GameState state, CollectionResolver collectionResolver, ObjectManager objects,
CrashHandlerService crashHandler)
{
_state = state;
@ -36,9 +35,9 @@ public sealed unsafe class SomePapLoad : FastHook<SomePapLoad.Delegate>
if (timelinePtr != nint.Zero)
{
var actorIdx = (int)(*(*(ulong**)timelinePtr + 1) >> 3);
if (actorIdx >= 0 && actorIdx < _objects.Length)
if (actorIdx >= 0 && actorIdx < _objects.Count)
{
var newData = _collectionResolver.IdentifyCollection((GameObject*)_objects.GetObjectAddress(actorIdx), true);
var newData = _collectionResolver.IdentifyCollection(_objects[actorIdx].AsObject, true);
var last = _state.SetAnimationData(newData);
_crashHandler.LogAnimation(newData.AssociatedGameObject, newData.ModCollection, AnimationInvocationType.PapLoad);
Task.Result.Original(a1, a2, a3, a4);

View file

@ -1,6 +1,7 @@
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using Penumbra.GameData.Files;
using Penumbra.GameData.Interop;
using Penumbra.Interop.SafeHandles;
namespace Penumbra.Interop.MaterialPreview;
@ -20,7 +21,7 @@ public sealed unsafe class LiveColorTablePreviewer : LiveMaterialPreviewerBase
public Half[] ColorTable { get; }
public LiveColorTablePreviewer(IObjectTable objects, IFramework framework, MaterialInfo materialInfo)
public LiveColorTablePreviewer(ObjectManager objects, IFramework framework, MaterialInfo materialInfo)
: base(objects, materialInfo)
{
_framework = framework;

View file

@ -1,5 +1,5 @@
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using Penumbra.GameData.Interop;
namespace Penumbra.Interop.MaterialPreview;
@ -11,7 +11,7 @@ public sealed unsafe class LiveMaterialPreviewer : LiveMaterialPreviewerBase
private readonly float[] _originalMaterialParameter;
private readonly uint[] _originalSamplerFlags;
public LiveMaterialPreviewer(IObjectTable objects, MaterialInfo materialInfo)
public LiveMaterialPreviewer(ObjectManager objects, MaterialInfo materialInfo)
: base(objects, materialInfo)
{
var mtrlHandle = Material->MaterialResourceHandle;

View file

@ -1,12 +1,13 @@
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Penumbra.GameData.Interop;
namespace Penumbra.Interop.MaterialPreview;
public abstract unsafe class LiveMaterialPreviewerBase : IDisposable
{
private readonly IObjectTable _objects;
private readonly ObjectManager _objects;
public readonly MaterialInfo MaterialInfo;
public readonly CharacterBase* DrawObject;
@ -14,7 +15,7 @@ public abstract unsafe class LiveMaterialPreviewerBase : IDisposable
protected bool Valid;
public LiveMaterialPreviewerBase(IObjectTable objects, MaterialInfo materialInfo)
public LiveMaterialPreviewerBase(ObjectManager objects, MaterialInfo materialInfo)
{
_objects = objects;

View file

@ -1,10 +1,11 @@
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Penumbra.GameData.Interop;
using Penumbra.GameData.Structs;
using Penumbra.Interop.ResourceTree;
using Penumbra.String;
using Model = Penumbra.GameData.Interop.Model;
namespace Penumbra.Interop.MaterialPreview;
@ -18,13 +19,13 @@ public enum DrawObjectType
public readonly record struct MaterialInfo(ObjectIndex ObjectIndex, DrawObjectType Type, int ModelSlot, int MaterialSlot)
{
public nint GetCharacter(IObjectTable objects)
=> objects.GetObjectAddress(ObjectIndex.Index);
public Actor GetCharacter(ObjectManager objects)
=> objects[ObjectIndex];
public nint GetDrawObject(nint address)
public nint GetDrawObject(Actor address)
=> GetDrawObject(Type, address);
public unsafe Material* GetDrawObjectMaterial(IObjectTable objects)
public unsafe Material* GetDrawObjectMaterial(ObjectManager objects)
=> GetDrawObjectMaterial((CharacterBase*)GetDrawObject(GetCharacter(objects)));
public unsafe Material* GetDrawObjectMaterial(CharacterBase* drawObject)
@ -60,13 +61,13 @@ public readonly record struct MaterialInfo(ObjectIndex ObjectIndex, DrawObjectTy
foreach (var type in Enum.GetValues<DrawObjectType>())
{
var drawObject = (CharacterBase*)GetDrawObject(type, objectPtr);
if (drawObject == null)
var drawObject = GetDrawObject(type, objectPtr);
if (!drawObject.Valid)
continue;
for (var i = 0; i < drawObject->SlotCount; ++i)
for (var i = 0; i < drawObject.AsCharacterBase->SlotCount; ++i)
{
var model = drawObject->Models[i];
var model = drawObject.AsCharacterBase->Models[i];
if (model == null)
continue;
@ -88,19 +89,18 @@ public readonly record struct MaterialInfo(ObjectIndex ObjectIndex, DrawObjectTy
return result;
}
private static unsafe nint GetDrawObject(DrawObjectType type, nint address)
private static unsafe Model GetDrawObject(DrawObjectType type, Actor address)
{
var gameObject = (Character*)address;
if (gameObject == null)
return nint.Zero;
if (!address.Valid)
return Model.Null;
return type switch
{
DrawObjectType.Character => (nint)gameObject->GameObject.GetDrawObject(),
DrawObjectType.Mainhand => (nint)gameObject->DrawData.Weapon(DrawDataContainer.WeaponSlot.MainHand).DrawObject,
DrawObjectType.Offhand => (nint)gameObject->DrawData.Weapon(DrawDataContainer.WeaponSlot.OffHand).DrawObject,
DrawObjectType.Vfx => (nint)gameObject->DrawData.Weapon(DrawDataContainer.WeaponSlot.Unk).DrawObject,
_ => nint.Zero,
DrawObjectType.Character => address.Model,
DrawObjectType.Mainhand => address.AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.MainHand).DrawObject,
DrawObjectType.Offhand => address.AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.OffHand).DrawObject,
DrawObjectType.Vfx => address.AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.Unk).DrawObject,
_ => Model.Null,
};
}
}

View file

@ -6,6 +6,7 @@ using Penumbra.Collections.Manager;
using Penumbra.GameData.Actors;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Interop;
using Penumbra.Util;
using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character;
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
@ -170,10 +171,10 @@ public sealed unsafe class CollectionResolver(
: null;
/// <summary> Check for the Yourself collection. </summary>
private ModCollection? CheckYourself(ActorIdentifier identifier, GameObject* actor)
private ModCollection? CheckYourself(ActorIdentifier identifier, Actor actor)
{
if (actor->ObjectIndex == 0
|| cutscenes.GetParentIndex(actor->ObjectIndex) == 0
if (actor.Index == 0
|| cutscenes.GetParentIndex(actor.Index.Index) == 0
|| identifier.Equals(actors.GetCurrentPlayer()))
return collectionManager.Active.ByType(CollectionType.Yourself);
@ -181,23 +182,23 @@ public sealed unsafe class CollectionResolver(
}
/// <summary> Check special collections given the actor. Returns notYetReady if the customize array is not filled. </summary>
private ModCollection? CollectionByAttributes(GameObject* actor, ref bool notYetReady)
private ModCollection? CollectionByAttributes(Actor actor, ref bool notYetReady)
{
if (!actor->IsCharacter())
if (!actor.IsCharacter)
return null;
// Only handle human models.
var character = (Character*)actor;
if (!IsModelHuman((uint)character->CharacterData.ModelCharaId))
if (!IsModelHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId))
return null;
if (character->DrawData.CustomizeData[0] == 0)
if (actor.Customize->Data[0] == 0)
{
notYetReady = true;
return null;
}
var bodyType = character->DrawData.CustomizeData[2];
var bodyType = actor.Customize->Data[2];
var collection = bodyType switch
{
3 => collectionManager.Active.ByType(CollectionType.NonPlayerElderly),
@ -207,9 +208,9 @@ public sealed unsafe class CollectionResolver(
if (collection != null)
return collection;
var race = (SubRace)character->DrawData.CustomizeData[4];
var gender = (Gender)(character->DrawData.CustomizeData[1] + 1);
var isNpc = actor->ObjectKind != (byte)ObjectKind.Player;
var race = (SubRace)actor.Customize->Data[4];
var gender = (Gender)(actor.Customize->Data[1] + 1);
var isNpc = !actor.IsPlayer;
var type = CollectionTypeExtensions.FromParts(race, gender, isNpc);
collection = collectionManager.Active.ByType(type);
@ -218,15 +219,14 @@ public sealed unsafe class CollectionResolver(
}
/// <summary> Get the collection applying to the owner if it is available. </summary>
private ModCollection? CheckOwnedCollection(ActorIdentifier identifier, GameObject* owner, ref bool notYetReady)
private ModCollection? CheckOwnedCollection(ActorIdentifier identifier, Actor owner, ref bool notYetReady)
{
if (identifier.Type != IdentifierType.Owned || !config.UseOwnerNameForCharacterCollection || owner == null)
if (identifier.Type != IdentifierType.Owned || !config.UseOwnerNameForCharacterCollection || !owner.Valid)
return null;
var id = actors.CreateIndividualUnchecked(IdentifierType.Player, identifier.PlayerName, identifier.HomeWorld.Id,
ObjectKind.None,
uint.MaxValue);
return CheckYourself(id, owner)
?? CollectionByAttributes(owner, ref notYetReady);
return CheckYourself(id, owner) ?? CollectionByAttributes(owner, ref notYetReady);
}
}

View file

@ -3,6 +3,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using OtterGui.Services;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Interop;
using Penumbra.Interop.Hooks.Objects;
using Penumbra.String;
@ -14,17 +15,17 @@ public sealed class CutsceneService : IService, IDisposable
public const int CutsceneEndIdx = (int)ScreenActor.CutsceneEnd;
public const int CutsceneSlots = CutsceneEndIdx - CutsceneStartIdx;
private readonly IObjectTable _objects;
private readonly ObjectManager _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]!));
.Where(i => _objects[i].Valid)
.Select(i => KeyValuePair.Create(i, this[i] ?? _objects.GetDalamudObject(i)!));
public unsafe CutsceneService(IObjectTable objects, CopyCharacter copyCharacter, CharacterDestructor characterDestructor,
public unsafe CutsceneService(ObjectManager objects, CopyCharacter copyCharacter, CharacterDestructor characterDestructor,
IClientState clientState)
{
_objects = objects;
@ -42,13 +43,13 @@ public sealed class CutsceneService : IService, IDisposable
/// Does not check for valid input index.
/// Returns null if no connected actor is set or the actor does not exist anymore.
/// </summary>
public Dalamud.Game.ClientState.Objects.Types.GameObject? this[int idx]
private Dalamud.Game.ClientState.Objects.Types.GameObject? this[int idx]
{
get
{
Debug.Assert(idx is >= CutsceneStartIdx and < CutsceneEndIdx);
idx = _copiedCharacters[idx - CutsceneStartIdx];
return idx < 0 ? null : _objects[idx];
return idx < 0 ? null : _objects.GetDalamudObject(idx);
}
}
@ -64,10 +65,10 @@ public sealed class CutsceneService : IService, IDisposable
if (parentIdx is < -1 or >= CutsceneEndIdx)
return false;
if (_objects.GetObjectAddress(copyIdx) == nint.Zero)
if (!_objects[copyIdx].Valid)
return false;
if (parentIdx != -1 && _objects.GetObjectAddress(parentIdx) == nint.Zero)
if (parentIdx != -1 && !_objects[parentIdx].Valid)
return false;
_copiedCharacters[copyIdx - CutsceneStartIdx] = (short)parentIdx;
@ -99,9 +100,9 @@ public sealed class CutsceneService : IService, IDisposable
{
// 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 address = _objects[i + CutsceneStartIdx];
if (address.IsPlayer)
address.AsCharacter->HomeWorld = character->HomeWorld;
_copiedCharacters[i] = -1;
}
@ -125,7 +126,7 @@ public sealed class CutsceneService : IService, IDisposable
/// <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()
private void RecoverGPoseActors()
{
Dictionary<ByteString, short>? actors = null;
@ -143,11 +144,11 @@ public sealed class CutsceneService : IService, IDisposable
bool TryGetName(int idx, out ByteString name)
{
name = ByteString.Empty;
var address = (GameObject*)_objects.GetObjectAddress(idx);
if (address == null)
var address = _objects[idx];
if (!address.Valid)
return false;
name = new ByteString(address->Name);
name = address.Utf8Name;
return !name.IsEmpty;
}

View file

@ -1,8 +1,7 @@
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using OtterGui.Services;
using Penumbra.GameData.Interop;
using Object = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.Object;
using Penumbra.GameData.Structs;
using Penumbra.Interop.Hooks.Objects;
@ -11,7 +10,7 @@ namespace Penumbra.Interop.PathResolving;
public sealed class DrawObjectState : IDisposable, IReadOnlyDictionary<nint, (nint, bool)>, IService
{
private readonly IObjectTable _objects;
private readonly ObjectManager _objects;
private readonly CreateCharacterBase _createCharacterBase;
private readonly WeaponReload _weaponReload;
private readonly CharacterBaseDestructor _characterBaseDestructor;
@ -22,7 +21,7 @@ public sealed class DrawObjectState : IDisposable, IReadOnlyDictionary<nint, (ni
public nint LastGameObject
=> _gameState.LastGameObject;
public unsafe DrawObjectState(IObjectTable objects, CreateCharacterBase createCharacterBase, WeaponReload weaponReload,
public unsafe DrawObjectState(ObjectManager objects, CreateCharacterBase createCharacterBase, WeaponReload weaponReload,
CharacterBaseDestructor characterBaseDestructor, GameState gameState)
{
_objects = objects;
@ -95,11 +94,11 @@ public sealed class DrawObjectState : IDisposable, IReadOnlyDictionary<nint, (ni
/// </summary>
private unsafe void InitializeDrawObjects()
{
for (var i = 0; i < _objects.Length; ++i)
for (var i = 0; i < _objects.Count; ++i)
{
var ptr = (GameObject*)_objects.GetObjectAddress(i);
if (ptr != null && ptr->IsCharacter() && ptr->DrawObject != null)
IterateDrawObjectTree(&ptr->DrawObject->Object, (nint)ptr, false, false);
var ptr = _objects[i];
if (ptr is { IsCharacter: true, Model.Valid: true })
IterateDrawObjectTree((Object*)ptr.Model.Address, ptr, false, false);
}
}

View file

@ -1,39 +1,26 @@
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Data;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Interop;
using Penumbra.Interop.PathResolving;
using Penumbra.String.Classes;
namespace Penumbra.Interop.ResourceTree;
public class ResourceTreeFactory
public class ResourceTreeFactory(
IDataManager gameData,
ObjectManager objects,
CollectionResolver resolver,
ObjectIdentification identifier,
Configuration config,
ActorManager actors,
PathState pathState)
{
private readonly IDataManager _gameData;
private readonly IObjectTable _objects;
private readonly CollectionResolver _collectionResolver;
private readonly ObjectIdentification _identifier;
private readonly Configuration _config;
private readonly ActorManager _actors;
private readonly PathState _pathState;
public ResourceTreeFactory(IDataManager gameData, IObjectTable objects, CollectionResolver resolver, ObjectIdentification identifier,
Configuration config, ActorManager actors, PathState pathState)
{
_gameData = gameData;
_objects = objects;
_collectionResolver = resolver;
_identifier = identifier;
_config = config;
_actors = actors;
_pathState = pathState;
}
private TreeBuildCache CreateTreeBuildCache()
=> new(_objects, _gameData, _actors);
=> new(objects, gameData, actors);
public IEnumerable<Dalamud.Game.ClientState.Objects.Types.Character> GetLocalPlayerRelatedCharacters()
{
@ -80,7 +67,7 @@ public class ResourceTreeFactory
if (drawObjStruct == null)
return null;
var collectionResolveData = _collectionResolver.IdentifyCollection(gameObjStruct, true);
var collectionResolveData = resolver.IdentifyCollection(gameObjStruct, true);
if (!collectionResolveData.Valid)
return null;
@ -89,9 +76,9 @@ public class ResourceTreeFactory
var networked = character.ObjectId != Dalamud.Game.ClientState.Objects.Types.GameObject.InvalidGameObjectId;
var tree = new ResourceTree(name, character.ObjectIndex, (nint)gameObjStruct, (nint)drawObjStruct, localPlayerRelated, related,
networked, collectionResolveData.ModCollection.Name);
var globalContext = new GlobalResolveContext(_identifier, collectionResolveData.ModCollection,
var globalContext = new GlobalResolveContext(identifier, collectionResolveData.ModCollection,
cache, (flags & Flags.WithUiData) != 0);
using (var _ = _pathState.EnterInternalResolve())
using (var _ = pathState.EnterInternalResolve())
{
tree.LoadResources(globalContext);
}
@ -103,56 +90,12 @@ public class ResourceTreeFactory
// ResolveGamePaths(tree, collectionResolveData.ModCollection);
if (globalContext.WithUiData)
ResolveUiData(tree);
FilterFullPaths(tree, (flags & Flags.RedactExternalPaths) != 0 ? _config.ModDirectory : null);
FilterFullPaths(tree, (flags & Flags.RedactExternalPaths) != 0 ? config.ModDirectory : null);
Cleanup(tree);
return tree;
}
private static void ResolveGamePaths(ResourceTree tree, ModCollection collection)
{
var forwardDictionary = new Dictionary<Utf8GamePath, FullPath?>();
var reverseDictionary = new Dictionary<string, HashSet<Utf8GamePath>>();
foreach (var node in tree.FlatNodes)
{
if (node.PossibleGamePaths.Length == 0 && !node.FullPath.InternalName.IsEmpty)
reverseDictionary.TryAdd(node.FullPath.ToPath(), null!);
else if (node.FullPath.InternalName.IsEmpty && node.PossibleGamePaths.Length == 1)
forwardDictionary.TryAdd(node.GamePath, null);
}
foreach (var key in forwardDictionary.Keys)
forwardDictionary[key] = collection.ResolvePath(key);
var reverseResolvedArray = collection.ReverseResolvePaths(reverseDictionary.Keys);
foreach (var (key, set) in reverseDictionary.Keys.Zip(reverseResolvedArray))
reverseDictionary[key] = set;
foreach (var node in tree.FlatNodes)
{
if (node.PossibleGamePaths.Length == 0 && !node.FullPath.InternalName.IsEmpty)
{
if (!reverseDictionary.TryGetValue(node.FullPath.ToPath(), out var resolvedSet))
continue;
if (resolvedSet.Count != 1)
{
Penumbra.Log.Debug(
$"Found {resolvedSet.Count} game paths while reverse-resolving {node.FullPath} in {collection.Name}:");
foreach (var gamePath in resolvedSet)
Penumbra.Log.Debug($"Game path: {gamePath}");
}
node.PossibleGamePaths = resolvedSet.ToArray();
}
else if (node.FullPath.InternalName.IsEmpty && node.PossibleGamePaths.Length == 1)
{
if (forwardDictionary.TryGetValue(node.GamePath, out var resolved))
node.FullPath = resolved ?? new FullPath(node.GamePath);
}
}
}
private static void ResolveUiData(ResourceTree tree)
{
foreach (var node in tree.FlatNodes)
@ -217,12 +160,12 @@ public class ResourceTreeFactory
private unsafe (string Name, bool PlayerRelated) GetCharacterName(Dalamud.Game.ClientState.Objects.Types.Character character,
TreeBuildCache cache)
{
var identifier = _actors.FromObject((GameObject*)character.Address, out var owner, true, false, false);
var identifier = actors.FromObject((GameObject*)character.Address, out var owner, true, false, false);
switch (identifier.Type)
{
case IdentifierType.Player: return (identifier.PlayerName.ToString(), true);
case IdentifierType.Owned:
var ownerChara = _objects.CreateObjectReference((nint)owner) as Dalamud.Game.ClientState.Objects.Types.Character;
var ownerChara = objects.Objects.CreateObjectReference(owner) as Dalamud.Game.ClientState.Objects.Types.Character;
if (ownerChara != null)
{
var ownerName = GetCharacterName(ownerChara, cache);

View file

@ -3,19 +3,19 @@ using Dalamud.Plugin.Services;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Files;
using Penumbra.GameData.Interop;
using Penumbra.GameData.Structs;
using Penumbra.String;
using Penumbra.String.Classes;
namespace Penumbra.Interop.ResourceTree;
internal readonly struct TreeBuildCache(IObjectTable objects, IDataManager dataManager, ActorManager actors)
internal readonly struct TreeBuildCache(ObjectManager objects, IDataManager dataManager, ActorManager actors)
{
private readonly Dictionary<FullPath, ShpkFile?> _shaderPackages = [];
public unsafe bool IsLocalPlayerRelated(Character character)
{
var player = objects[0];
var player = objects.GetDalamudObject(0);
if (player == null)
return false;
@ -31,30 +31,30 @@ internal readonly struct TreeBuildCache(IObjectTable objects, IDataManager dataM
}
public IEnumerable<Character> GetCharacters()
=> objects.OfType<Character>();
=> objects.Objects.OfType<Character>();
public IEnumerable<Character> GetLocalPlayerRelatedCharacters()
{
var player = objects[0];
var player = objects.GetDalamudObject(0);
if (player == null)
yield break;
yield return (Character)player;
var minion = objects[1];
var minion = objects.GetDalamudObject(1);
if (minion != null)
yield return (Character)minion;
var playerId = player.ObjectId;
for (var i = 2; i < ObjectIndex.CutsceneStart.Index; i += 2)
{
if (objects[i] is Character owned && owned.OwnerId == playerId)
if (objects.GetDalamudObject(i) is Character owned && owned.OwnerId == playerId)
yield return owned;
}
for (var i = ObjectIndex.CutsceneStart.Index; i < ObjectIndex.CharacterScreen.Index; ++i)
{
var character = objects[i] as Character;
var character = objects.GetDalamudObject((int) i) as Character;
if (character == null)
continue;
@ -62,34 +62,11 @@ internal readonly struct TreeBuildCache(IObjectTable objects, IDataManager dataM
if (parent < 0)
continue;
if (parent is 0 or 1 || objects[parent]?.OwnerId == playerId)
if (parent is 0 or 1 || objects.GetDalamudObject(parent)?.OwnerId == playerId)
yield return character;
}
}
private unsafe ByteString GetPlayerName(GameObject player)
{
var gameObject = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)player.Address;
return new ByteString(gameObject->Name);
}
private unsafe bool GetOwnedId(ByteString playerName, uint playerId, int idx, [NotNullWhen(true)] out Character? character)
{
character = objects[idx] as Character;
if (character == null)
return false;
var actorId = actors.FromObject(character, out var owner, true, true, true);
if (!actorId.IsValid)
return false;
if (owner != null && owner->OwnerID != playerId)
return false;
if (actorId.Type is not IdentifierType.Player || !actorId.PlayerName.Equals(playerName))
return false;
return true;
}
/// <summary> Try to read a shpk file from the given path and cache it on success. </summary>
public ShpkFile? ReadShaderPackage(FullPath path)
=> ReadFile(dataManager, path, _shaderPackages, bytes => new ShpkFile(bytes));

View file

@ -11,6 +11,7 @@ using Penumbra.Api.Enums;
using Penumbra.Communication;
using Penumbra.GameData;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Interop;
using Penumbra.Interop.Structs;
using Penumbra.Mods;
using Penumbra.Mods.Editor;
@ -57,7 +58,7 @@ public unsafe partial class RedrawService
// this will be in obj and true will be returned.
private bool FindCorrectActor(int idx, out GameObject? obj)
{
obj = _objects[idx];
obj = _objects.GetDalamudObject(idx);
if (!InGPose || obj == null || IsGPoseActor(idx))
return false;
@ -70,21 +71,21 @@ public unsafe partial class RedrawService
if (name == gPoseName)
{
obj = _objects[GPosePlayerIdx + i];
obj = _objects.GetDalamudObject(GPosePlayerIdx + i);
return true;
}
}
for (; _gPoseNameCounter < GPoseSlots; ++_gPoseNameCounter)
{
var gPoseName = _objects[GPosePlayerIdx + _gPoseNameCounter]?.Name.ToString();
var gPoseName = _objects.GetDalamudObject(GPosePlayerIdx + _gPoseNameCounter)?.Name.ToString();
_gPoseNames[_gPoseNameCounter] = gPoseName;
if (gPoseName == null)
break;
if (name == gPoseName)
{
obj = _objects[GPosePlayerIdx + _gPoseNameCounter];
obj = _objects.GetDalamudObject(GPosePlayerIdx + _gPoseNameCounter);
return true;
}
}
@ -111,7 +112,7 @@ public sealed unsafe partial class RedrawService : IDisposable
private const int FurnitureIdx = 1337;
private readonly IFramework _framework;
private readonly IObjectTable _objects;
private readonly ObjectManager _objects;
private readonly ITargetManager _targets;
private readonly ICondition _conditions;
private readonly IClientState _clientState;
@ -133,7 +134,7 @@ public sealed unsafe partial class RedrawService : IDisposable
public event GameObjectRedrawnDelegate? GameObjectRedrawn;
public RedrawService(IFramework framework, IObjectTable objects, ITargetManager targets, ICondition conditions, IClientState clientState,
public RedrawService(IFramework framework, ObjectManager objects, ITargetManager targets, ICondition conditions, IClientState clientState,
Configuration config, CommunicatorService communicator)
{
_framework = framework;
@ -170,7 +171,7 @@ public sealed unsafe partial class RedrawService : IDisposable
if (gPose)
DisableDraw(actor!);
if (actor is PlayerCharacter && _objects[tableIndex + 1] is { ObjectKind: ObjectKind.MountType or ObjectKind.Ornament } mountOrOrnament)
if (actor is PlayerCharacter && _objects.GetDalamudObject(tableIndex + 1) is { ObjectKind: ObjectKind.MountType or ObjectKind.Ornament } mountOrOrnament)
{
*ActorDrawState(mountOrOrnament) |= DrawState.Invisibility;
if (gPose)
@ -189,7 +190,7 @@ public sealed unsafe partial class RedrawService : IDisposable
if (gPose)
EnableDraw(actor!);
if (actor is PlayerCharacter && _objects[tableIndex + 1] is { ObjectKind: ObjectKind.MountType or ObjectKind.Ornament } mountOrOrnament)
if (actor is PlayerCharacter && _objects.GetDalamudObject(tableIndex + 1) is { ObjectKind: ObjectKind.MountType or ObjectKind.Ornament } mountOrOrnament)
{
*ActorDrawState(mountOrOrnament) &= ~DrawState.Invisibility;
if (gPose)
@ -212,7 +213,7 @@ public sealed unsafe partial class RedrawService : IDisposable
private void ReloadActorAfterGPose(GameObject? actor)
{
if (_objects[GPosePlayerIdx] != null)
if (_objects[GPosePlayerIdx].Valid)
{
ReloadActor(actor);
return;
@ -230,7 +231,7 @@ public sealed unsafe partial class RedrawService : IDisposable
if (_target < 0)
return;
var actor = _objects[_target];
var actor = _objects.GetDalamudObject(_target);
if (actor == null || _targets.Target != null)
return;
@ -316,12 +317,12 @@ public sealed unsafe partial class RedrawService : IDisposable
if (idx < 0)
{
var newIdx = ~idx;
WriteInvisible(_objects[newIdx]);
WriteInvisible(_objects.GetDalamudObject(newIdx));
_afterGPoseQueue[numKept++] = newIdx;
}
else
{
WriteVisible(_objects[idx]);
WriteVisible(_objects.GetDalamudObject(idx));
}
}
@ -357,8 +358,8 @@ public sealed unsafe partial class RedrawService : IDisposable
private GameObject? GetLocalPlayer()
{
var gPosePlayer = _objects[GPosePlayerIdx];
return gPosePlayer ?? _objects[0];
var gPosePlayer = _objects.GetDalamudObject(GPosePlayerIdx);
return gPosePlayer ?? _objects.GetDalamudObject(0);
}
public bool GetName(string lowerName, out GameObject? actor)
@ -379,7 +380,7 @@ public sealed unsafe partial class RedrawService : IDisposable
if (!ret && lowerName.Length > 1 && lowerName[0] == '#' && ushort.TryParse(lowerName[1..], out var objectIndex))
{
ret = true;
actor = _objects[objectIndex];
actor = _objects.GetDalamudObject((int) objectIndex);
}
return ret;
@ -387,8 +388,8 @@ public sealed unsafe partial class RedrawService : IDisposable
public void RedrawObject(int tableIndex, RedrawType settings)
{
if (tableIndex >= 0 && tableIndex < _objects.Length)
RedrawObject(_objects[tableIndex], settings);
if (tableIndex >= 0 && tableIndex < _objects.Count)
RedrawObject(_objects.GetDalamudObject(tableIndex), settings);
}
public void RedrawObject(string name, RedrawType settings)
@ -399,13 +400,13 @@ public sealed unsafe partial class RedrawService : IDisposable
else if (GetName(lowerName, out var target))
RedrawObject(target, settings);
else
foreach (var actor in _objects.Where(a => a.Name.ToString().ToLowerInvariant() == lowerName))
foreach (var actor in _objects.Objects.Where(a => a.Name.ToString().ToLowerInvariant() == lowerName))
RedrawObject(actor, settings);
}
public void RedrawAll(RedrawType settings)
{
foreach (var actor in _objects)
foreach (var actor in _objects.Objects)
RedrawObject(actor, settings);
}