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

@ -1 +1 @@
Subproject commit 1df06807650a79813791effaa01fb7c4710b3dab
Subproject commit d2a1406bc32f715c0687613f02e3f74caf7ceea9

@ -1 +1 @@
Subproject commit d53db6a358cedecd3ef18f62f12a07deff4b61ee
Subproject commit a1262e242ca33bb0e9e4f080d294d9160b5e54eb

View file

@ -1,3 +1,4 @@
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Dalamud.Plugin;
@ -18,6 +19,7 @@ using Penumbra.Collections.Manager;
using Dalamud.Plugin.Services;
using Penumbra.GameData.Structs;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Interop;
namespace Penumbra.Api;
@ -40,7 +42,7 @@ public class IpcTester : IDisposable
private readonly Temporary _temporary;
private readonly ResourceTree _resourceTree;
public IpcTester(Configuration config, DalamudPluginInterface pi, IObjectTable objects, IClientState clientState,
public IpcTester(Configuration config, DalamudPluginInterface pi, ObjectManager objects, IClientState clientState,
PenumbraIpcProviders ipcProviders, ModManager modManager, CollectionManager collections, TempModManager tempMods,
TempCollectionManager tempCollections, SaveService saveService)
{
@ -280,16 +282,16 @@ public class IpcTester : IDisposable
{
ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
using var popup = ImRaii.Popup("Config Popup");
if (popup)
{
using (var font = ImRaii.PushFont(UiBuilder.MonoFont))
{
ImGuiUtil.TextWrapped(_currentConfiguration);
}
if (!popup)
return;
if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
ImGui.CloseCurrentPopup();
using (ImRaii.PushFont(UiBuilder.MonoFont))
{
ImGuiUtil.TextWrapped(_currentConfiguration);
}
if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
ImGui.CloseCurrentPopup();
}
private void UpdateModDirectoryChanged(string path, bool valid)
@ -304,15 +306,15 @@ public class IpcTester : IDisposable
public readonly EventSubscriber<ChangedItemType, uint> Tooltip;
public readonly EventSubscriber<MouseButton, ChangedItemType, uint> Click;
private string _lastDrawnMod = string.Empty;
private DateTimeOffset _lastDrawnModTime = DateTimeOffset.MinValue;
private bool _subscribedToTooltip = false;
private bool _subscribedToClick = false;
private string _lastClicked = string.Empty;
private string _lastHovered = string.Empty;
private TabType _selectTab = TabType.None;
private string _modName = string.Empty;
private PenumbraApiEc _ec = PenumbraApiEc.Success;
private string _lastDrawnMod = string.Empty;
private DateTimeOffset _lastDrawnModTime = DateTimeOffset.MinValue;
private bool _subscribedToTooltip;
private bool _subscribedToClick;
private string _lastClicked = string.Empty;
private string _lastHovered = string.Empty;
private TabType _selectTab = TabType.None;
private string _modName = string.Empty;
private PenumbraApiEc _ec = PenumbraApiEc.Success;
public Ui(DalamudPluginInterface pi)
{
@ -401,14 +403,14 @@ public class IpcTester : IDisposable
{
private readonly DalamudPluginInterface _pi;
private readonly IClientState _clientState;
private readonly IObjectTable _objects;
private readonly ObjectManager _objects;
public readonly EventSubscriber<IntPtr, int> Redrawn;
private string _redrawName = string.Empty;
private int _redrawIndex = 0;
private string _redrawName = string.Empty;
private int _redrawIndex;
private string _lastRedrawnString = "None";
public Redrawing(DalamudPluginInterface pi, IObjectTable objects, IClientState clientState)
public Redrawing(DalamudPluginInterface pi, ObjectManager objects, IClientState clientState)
{
_pi = pi;
_objects = objects;
@ -440,8 +442,8 @@ public class IpcTester : IDisposable
DrawIntro(Ipc.RedrawObjectByIndex.Label, "Redraw by Index");
var tmp = _redrawIndex;
ImGui.SetNextItemWidth(100 * UiHelpers.Scale);
if (ImGui.DragInt("##redrawIndex", ref tmp, 0.1f, 0, _objects.Length))
_redrawIndex = Math.Clamp(tmp, 0, _objects.Length);
if (ImGui.DragInt("##redrawIndex", ref tmp, 0.1f, 0, _objects.Count))
_redrawIndex = Math.Clamp(tmp, 0, _objects.Count);
ImGui.SameLine();
if (ImGui.Button("Redraw##Index"))
@ -458,12 +460,12 @@ public class IpcTester : IDisposable
private void SetLastRedrawn(IntPtr address, int index)
{
if (index < 0
|| index > _objects.Length
|| index > _objects.Count
|| address == IntPtr.Zero
|| _objects[index]?.Address != address)
|| _objects[index].Address != address)
_lastRedrawnString = "Invalid";
_lastRedrawnString = $"{_objects[index]!.Name} (0x{address:X}, {index})";
_lastRedrawnString = $"{_objects[index].Utf8Name} (0x{address:X}, {index})";
}
}
@ -588,8 +590,8 @@ public class IpcTester : IDisposable
private string _currentResolvePath = string.Empty;
private string _currentResolveCharacter = string.Empty;
private string _currentReversePath = string.Empty;
private int _currentReverseIdx = 0;
private Task<(string[], string[][])> _task = Task.FromException<(string[], string[][])>(new Exception());
private int _currentReverseIdx;
private Task<(string[], string[][])> _task = Task.FromException<(string[], string[][])>(new Exception());
public Resolve(DalamudPluginInterface pi)
=> _pi = pi;
@ -696,8 +698,6 @@ public class IpcTester : IDisposable
return text;
}
;
DrawIntro(Ipc.ResolvePlayerPaths.Label, "Resolved Paths (Player)");
if (forwardArray.Length > 0 || reverseArray.Length > 0)
{
@ -721,18 +721,18 @@ public class IpcTester : IDisposable
{
private readonly DalamudPluginInterface _pi;
private int _objectIdx = 0;
private int _objectIdx;
private string _collectionName = string.Empty;
private bool _allowCreation = true;
private bool _allowDeletion = true;
private ApiCollectionType _type = ApiCollectionType.Current;
private string _characterCollectionName = string.Empty;
private IList<string> _collections = new List<string>();
private IList<string> _collections = [];
private string _changedItemCollection = string.Empty;
private IReadOnlyDictionary<string, object?> _changedItems = new Dictionary<string, object?>();
private PenumbraApiEc _returnCode = PenumbraApiEc.Success;
private string? _oldCollection = null;
private string? _oldCollection;
public Collections(DalamudPluginInterface pi)
=> _pi = pi;
@ -845,8 +845,8 @@ public class IpcTester : IDisposable
{
private readonly DalamudPluginInterface _pi;
private string _characterName = string.Empty;
private int _gameObjectIndex = 0;
private string _characterName = string.Empty;
private int _gameObjectIndex;
public Meta(DalamudPluginInterface pi)
=> _pi = pi;
@ -1040,11 +1040,11 @@ public class IpcTester : IDisposable
private string _settingsModName = string.Empty;
private string _settingsCollection = string.Empty;
private bool _settingsAllowInheritance = true;
private bool _settingsInherit = false;
private bool _settingsEnabled = false;
private int _settingsPriority = 0;
private bool _settingsInherit;
private bool _settingsEnabled;
private int _settingsPriority;
private IDictionary<string, (IList<string>, GroupType)>? _availableSettings;
private IDictionary<string, IList<string>>? _currentSettings = null;
private IDictionary<string, IList<string>>? _currentSettings;
public ModSettings(DalamudPluginInterface pi)
{
@ -1287,7 +1287,7 @@ public class IpcTester : IDisposable
private string _tempFilePath = "test/success.mtrl";
private string _tempManipulation = string.Empty;
private PenumbraApiEc _lastTempError;
private int _tempActorIndex = 0;
private int _tempActorIndex;
private bool _forceOverwrite;
public void Draw()
@ -1441,15 +1441,13 @@ public class IpcTester : IDisposable
}
}
private class ResourceTree
private class ResourceTree(DalamudPluginInterface pi, ObjectManager objects)
{
private readonly DalamudPluginInterface _pi;
private readonly IObjectTable _objects;
private readonly Stopwatch _stopwatch = new();
private readonly Stopwatch _stopwatch = new();
private string _gameObjectIndices = "0";
private ResourceType _type = ResourceType.Mtrl;
private bool _withUIData = false;
private bool _withUiData;
private (string, IReadOnlyDictionary<string, string[]>?)[]? _lastGameObjectResourcePaths;
private (string, IReadOnlyDictionary<string, string[]>?)[]? _lastPlayerResourcePaths;
@ -1459,12 +1457,6 @@ public class IpcTester : IDisposable
private (string, Ipc.ResourceTree)[]? _lastPlayerResourceTrees;
private TimeSpan _lastCallDuration;
public ResourceTree(DalamudPluginInterface pi, IObjectTable objects)
{
_pi = pi;
_objects = objects;
}
public void Draw()
{
using var _ = ImRaii.TreeNode("Resource Tree");
@ -1473,7 +1465,7 @@ public class IpcTester : IDisposable
ImGui.InputText("GameObject indices", ref _gameObjectIndices, 511);
ImGuiUtil.GenericEnumCombo("Resource type", ImGui.CalcItemWidth(), _type, out _type, Enum.GetValues<ResourceType>());
ImGui.Checkbox("Also get names and icons", ref _withUIData);
ImGui.Checkbox("Also get names and icons", ref _withUiData);
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
if (!table)
@ -1483,7 +1475,7 @@ public class IpcTester : IDisposable
if (ImGui.Button("Get##GameObjectResourcePaths"))
{
var gameObjects = GetSelectedGameObjects();
var subscriber = Ipc.GetGameObjectResourcePaths.Subscriber(_pi);
var subscriber = Ipc.GetGameObjectResourcePaths.Subscriber(pi);
_stopwatch.Restart();
var resourcePaths = subscriber.Invoke(gameObjects);
@ -1499,7 +1491,7 @@ public class IpcTester : IDisposable
DrawIntro(Ipc.GetPlayerResourcePaths.Label, "Get local player resource paths");
if (ImGui.Button("Get##PlayerResourcePaths"))
{
var subscriber = Ipc.GetPlayerResourcePaths.Subscriber(_pi);
var subscriber = Ipc.GetPlayerResourcePaths.Subscriber(pi);
_stopwatch.Restart();
var resourcePaths = subscriber.Invoke();
@ -1515,9 +1507,9 @@ public class IpcTester : IDisposable
if (ImGui.Button("Get##GameObjectResourcesOfType"))
{
var gameObjects = GetSelectedGameObjects();
var subscriber = Ipc.GetGameObjectResourcesOfType.Subscriber(_pi);
var subscriber = Ipc.GetGameObjectResourcesOfType.Subscriber(pi);
_stopwatch.Restart();
var resourcesOfType = subscriber.Invoke(_type, _withUIData, gameObjects);
var resourcesOfType = subscriber.Invoke(_type, _withUiData, gameObjects);
_lastCallDuration = _stopwatch.Elapsed;
_lastGameObjectResourcesOfType = gameObjects
@ -1531,9 +1523,9 @@ public class IpcTester : IDisposable
DrawIntro(Ipc.GetPlayerResourcesOfType.Label, "Get local player resources of type");
if (ImGui.Button("Get##PlayerResourcesOfType"))
{
var subscriber = Ipc.GetPlayerResourcesOfType.Subscriber(_pi);
var subscriber = Ipc.GetPlayerResourcesOfType.Subscriber(pi);
_stopwatch.Restart();
var resourcesOfType = subscriber.Invoke(_type, _withUIData);
var resourcesOfType = subscriber.Invoke(_type, _withUiData);
_lastCallDuration = _stopwatch.Elapsed;
_lastPlayerResourcesOfType = resourcesOfType
@ -1547,9 +1539,9 @@ public class IpcTester : IDisposable
if (ImGui.Button("Get##GameObjectResourceTrees"))
{
var gameObjects = GetSelectedGameObjects();
var subscriber = Ipc.GetGameObjectResourceTrees.Subscriber(_pi);
var subscriber = Ipc.GetGameObjectResourceTrees.Subscriber(pi);
_stopwatch.Restart();
var trees = subscriber.Invoke(_withUIData, gameObjects);
var trees = subscriber.Invoke(_withUiData, gameObjects);
_lastCallDuration = _stopwatch.Elapsed;
_lastGameObjectResourceTrees = gameObjects
@ -1563,9 +1555,9 @@ public class IpcTester : IDisposable
DrawIntro(Ipc.GetPlayerResourceTrees.Label, "Get local player resource trees");
if (ImGui.Button("Get##PlayerResourceTrees"))
{
var subscriber = Ipc.GetPlayerResourceTrees.Subscriber(_pi);
var subscriber = Ipc.GetPlayerResourceTrees.Subscriber(pi);
_stopwatch.Restart();
var trees = subscriber.Invoke(_withUIData);
var trees = subscriber.Invoke(_withUiData);
_lastCallDuration = _stopwatch.Elapsed;
_lastPlayerResourceTrees = trees
@ -1666,13 +1658,13 @@ public class IpcTester : IDisposable
{
DrawWithHeaders(result, resources =>
{
using var table = ImRaii.Table(string.Empty, _withUIData ? 3 : 2, ImGuiTableFlags.SizingFixedFit);
using var table = ImRaii.Table(string.Empty, _withUiData ? 3 : 2, ImGuiTableFlags.SizingFixedFit);
if (!table)
return;
ImGui.TableSetupColumn("Resource Handle", ImGuiTableColumnFlags.WidthStretch, 0.15f);
ImGui.TableSetupColumn("Actual Path", ImGuiTableColumnFlags.WidthStretch, _withUIData ? 0.55f : 0.85f);
if (_withUIData)
ImGui.TableSetupColumn("Actual Path", ImGuiTableColumnFlags.WidthStretch, _withUiData ? 0.55f : 0.85f);
if (_withUiData)
ImGui.TableSetupColumn("Icon & Name", ImGuiTableColumnFlags.WidthStretch, 0.3f);
ImGui.TableHeadersRow();
@ -1682,7 +1674,7 @@ public class IpcTester : IDisposable
TextUnformattedMono($"0x{resourceHandle:X}");
ImGui.TableNextColumn();
ImGui.TextUnformatted(actualPath);
if (_withUIData)
if (_withUiData)
{
ImGui.TableNextColumn();
TextUnformattedMono(icon.ToString());
@ -1699,11 +1691,11 @@ public class IpcTester : IDisposable
{
ImGui.TextUnformatted($"Name: {tree.Name}\nRaceCode: {(GenderRace)tree.RaceCode}");
using var table = ImRaii.Table(string.Empty, _withUIData ? 7 : 5, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.Resizable);
using var table = ImRaii.Table(string.Empty, _withUiData ? 7 : 5, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.Resizable);
if (!table)
return;
if (_withUIData)
if (_withUiData)
{
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch, 0.5f);
ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthStretch, 0.1f);
@ -1726,11 +1718,11 @@ public class IpcTester : IDisposable
ImGui.TableNextColumn();
var hasChildren = node.Children.Any();
using var treeNode = ImRaii.TreeNode(
$"{(_withUIData ? node.Name ?? "Unknown" : node.Type)}##{node.ObjectAddress:X8}",
$"{(_withUiData ? node.Name ?? "Unknown" : node.Type)}##{node.ObjectAddress:X8}",
hasChildren
? ImGuiTreeNodeFlags.SpanFullWidth
: ImGuiTreeNodeFlags.SpanFullWidth | ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.NoTreePushOnOpen);
if (_withUIData)
if (_withUiData)
{
ImGui.TableNextColumn();
TextUnformattedMono(node.Type.ToString());
@ -1770,10 +1762,10 @@ public class IpcTester : IDisposable
private unsafe string GameObjectToString(ObjectIndex gameObjectIndex)
{
var gameObject = _objects[gameObjectIndex.Index];
var gameObject = objects[gameObjectIndex];
return gameObject != null
? $"[{gameObjectIndex}] {gameObject.Name} ({gameObject.ObjectKind})"
return gameObject.Valid
? $"[{gameObjectIndex}] {gameObject.Utf8Name} ({(ObjectKind)gameObject.AsObject->ObjectKind})"
: $"[{gameObjectIndex}] null";
}
}

View file

@ -20,6 +20,7 @@ using Penumbra.String.Classes;
using Penumbra.Services;
using Penumbra.Collections.Manager;
using Penumbra.Communication;
using Penumbra.GameData.Interop;
using Penumbra.Import.Textures;
using Penumbra.Interop.Services;
using Penumbra.UI;
@ -93,7 +94,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
private IDataManager _gameData;
private IFramework _framework;
private IObjectTable _objects;
private ObjectManager _objects;
private ModManager _modManager;
private ResourceLoader _resourceLoader;
private Configuration _config;
@ -111,7 +112,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
private TextureManager _textureManager;
private ResourceTreeFactory _resourceTreeFactory;
public unsafe PenumbraApi(CommunicatorService communicator, IDataManager gameData, IFramework framework, IObjectTable objects,
public unsafe PenumbraApi(CommunicatorService communicator, IDataManager gameData, IFramework framework, ObjectManager objects,
ModManager modManager, ResourceLoader resourceLoader, Configuration config, CollectionManager collectionManager,
TempCollectionManager tempCollections, TempModManager tempMods, ActorManager actors, CollectionResolver collectionResolver,
CutsceneService cutsceneService, ModImportManager modImportManager, CollectionEditor collectionEditor, RedrawService redrawService,
@ -928,10 +929,10 @@ public class PenumbraApi : IDisposable, IPenumbraApi
{
CheckInitialized();
if (actorIndex < 0 || actorIndex >= _objects.Length)
if (actorIndex < 0 || actorIndex >= _objects.Count)
return PenumbraApiEc.InvalidArgument;
var identifier = _actors.FromObject(_objects[actorIndex], false, false, true);
var identifier = _actors.FromObject(_objects[actorIndex], out _, false, false, true);
if (!identifier.IsValid)
return PenumbraApiEc.InvalidArgument;
@ -1076,11 +1077,11 @@ public class PenumbraApi : IDisposable, IPenumbraApi
public IReadOnlyDictionary<string, string[]>?[] GetGameObjectResourcePaths(ushort[] gameObjects)
{
var characters = gameObjects.Select(index => _objects[index]).OfType<Character>();
var characters = gameObjects.Select(index => _objects.GetDalamudObject((int) index)).OfType<Character>();
var resourceTrees = _resourceTreeFactory.FromCharacters(characters, 0);
var pathDictionaries = ResourceTreeApiHelper.GetResourcePathDictionaries(resourceTrees);
return Array.ConvertAll(gameObjects, obj => pathDictionaries.TryGetValue(obj, out var pathDict) ? pathDict : null);
return Array.ConvertAll(gameObjects, obj => pathDictionaries.GetValueOrDefault(obj));
}
public IReadOnlyDictionary<ushort, IReadOnlyDictionary<string, string[]>> GetPlayerResourcePaths()
@ -1091,39 +1092,39 @@ public class PenumbraApi : IDisposable, IPenumbraApi
return pathDictionaries.AsReadOnly();
}
public IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>?[] GetGameObjectResourcesOfType(ResourceType type, bool withUIData,
public IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>?[] GetGameObjectResourcesOfType(ResourceType type, bool withUiData,
params ushort[] gameObjects)
{
var characters = gameObjects.Select(index => _objects[index]).OfType<Character>();
var resourceTrees = _resourceTreeFactory.FromCharacters(characters, withUIData ? ResourceTreeFactory.Flags.WithUiData : 0);
var characters = gameObjects.Select(index => _objects.GetDalamudObject((int)index)).OfType<Character>();
var resourceTrees = _resourceTreeFactory.FromCharacters(characters, withUiData ? ResourceTreeFactory.Flags.WithUiData : 0);
var resDictionaries = ResourceTreeApiHelper.GetResourcesOfType(resourceTrees, type);
return Array.ConvertAll(gameObjects, obj => resDictionaries.TryGetValue(obj, out var resDict) ? resDict : null);
return Array.ConvertAll(gameObjects, obj => resDictionaries.GetValueOrDefault(obj));
}
public IReadOnlyDictionary<ushort, IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>> GetPlayerResourcesOfType(ResourceType type,
bool withUIData)
bool withUiData)
{
var resourceTrees = _resourceTreeFactory.FromObjectTable(ResourceTreeFactory.Flags.LocalPlayerRelatedOnly
| (withUIData ? ResourceTreeFactory.Flags.WithUiData : 0));
| (withUiData ? ResourceTreeFactory.Flags.WithUiData : 0));
var resDictionaries = ResourceTreeApiHelper.GetResourcesOfType(resourceTrees, type);
return resDictionaries.AsReadOnly();
}
public Ipc.ResourceTree?[] GetGameObjectResourceTrees(bool withUIData, params ushort[] gameObjects)
public Ipc.ResourceTree?[] GetGameObjectResourceTrees(bool withUiData, params ushort[] gameObjects)
{
var characters = gameObjects.Select(index => _objects[index]).OfType<Character>();
var resourceTrees = _resourceTreeFactory.FromCharacters(characters, withUIData ? ResourceTreeFactory.Flags.WithUiData : 0);
var characters = gameObjects.Select(index => _objects.GetDalamudObject((int)index)).OfType<Character>();
var resourceTrees = _resourceTreeFactory.FromCharacters(characters, withUiData ? ResourceTreeFactory.Flags.WithUiData : 0);
var resDictionary = ResourceTreeApiHelper.EncapsulateResourceTrees(resourceTrees);
return Array.ConvertAll(gameObjects, obj => resDictionary.TryGetValue(obj, out var nodes) ? nodes : null);
return Array.ConvertAll(gameObjects, obj => resDictionary.GetValueOrDefault(obj));
}
public IReadOnlyDictionary<ushort, Ipc.ResourceTree> GetPlayerResourceTrees(bool withUIData)
public IReadOnlyDictionary<ushort, Ipc.ResourceTree> GetPlayerResourceTrees(bool withUiData)
{
var resourceTrees = _resourceTreeFactory.FromObjectTable(ResourceTreeFactory.Flags.LocalPlayerRelatedOnly
| (withUIData ? ResourceTreeFactory.Flags.WithUiData : 0));
| (withUiData ? ResourceTreeFactory.Flags.WithUiData : 0));
var resDictionary = ResourceTreeApiHelper.EncapsulateResourceTrees(resourceTrees);
return resDictionary.AsReadOnly();
@ -1165,11 +1166,11 @@ public class PenumbraApi : IDisposable, IPenumbraApi
private unsafe bool AssociatedCollection(int gameObjectIdx, out ModCollection collection)
{
collection = _collectionManager.Active.Default;
if (gameObjectIdx < 0 || gameObjectIdx >= _objects.Length)
if (gameObjectIdx < 0 || gameObjectIdx >= _objects.Count)
return false;
var ptr = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)_objects.GetObjectAddress(gameObjectIdx);
var data = _collectionResolver.IdentifyCollection(ptr, false);
var ptr = _objects[gameObjectIdx];
var data = _collectionResolver.IdentifyCollection(ptr.AsObject, false);
if (data.Valid)
collection = data.ModCollection;
@ -1179,10 +1180,10 @@ public class PenumbraApi : IDisposable, IPenumbraApi
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private unsafe ActorIdentifier AssociatedIdentifier(int gameObjectIdx)
{
if (gameObjectIdx < 0 || gameObjectIdx >= _objects.Length)
if (gameObjectIdx < 0 || gameObjectIdx >= _objects.Count)
return ActorIdentifier.Invalid;
var ptr = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)_objects.GetObjectAddress(gameObjectIdx);
var ptr = _objects[gameObjectIdx];
return _actors.FromObject(ptr, out _, false, true, true);
}

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);
}

View file

@ -12,6 +12,7 @@ using Penumbra.Collections.Manager;
using Penumbra.Communication;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Files;
using Penumbra.GameData.Interop;
using Penumbra.Import.Models;
using Penumbra.Import.Textures;
using Penumbra.Interop.Hooks.Objects;
@ -46,7 +47,7 @@ public partial class ModEditWindow : Window, IDisposable
private readonly IDragDropManager _dragDropManager;
private readonly IDataManager _gameData;
private readonly IFramework _framework;
private readonly IObjectTable _objects;
private readonly ObjectManager _objects;
private readonly CharacterBaseDestructor _characterBaseDestructor;
private Vector2 _iconSize = Vector2.Zero;
@ -446,7 +447,7 @@ public partial class ModEditWindow : Window, IDisposable
DrawOptionSelectHeader();
var setsEqual = !_editor!.SwapEditor.Changes;
var setsEqual = !_editor.SwapEditor.Changes;
var tt = setsEqual ? "No changes staged." : "Apply the currently staged changes to the option.";
ImGui.NewLine();
if (ImGuiUtil.DrawDisabledButton("Apply Changes", Vector2.Zero, tt, setsEqual))
@ -577,7 +578,7 @@ public partial class ModEditWindow : Window, IDisposable
Configuration config, ModEditor editor, ResourceTreeFactory resourceTreeFactory, MetaFileManager metaFileManager,
StainService stainService, ActiveCollections activeCollections, ModMergeTab modMergeTab,
CommunicatorService communicator, TextureManager textures, ModelManager models, IDragDropManager dragDropManager,
ChangedItemDrawer changedItemDrawer, IObjectTable objects, IFramework framework, CharacterBaseDestructor characterBaseDestructor)
ChangedItemDrawer changedItemDrawer, ObjectManager objects, IFramework framework, CharacterBaseDestructor characterBaseDestructor)
: base(WindowBaseLabel)
{
_performance = performance;

View file

@ -71,8 +71,7 @@ public sealed class PredefinedTagManager : ISavable, IReadOnlyList<string>
foreach (var (tag, data) in tags)
_predefinedTags.TryAdd(tag, data);
break;
default:
throw new Exception($"Invalid version {version}.");
default: throw new Exception($"Invalid version {version}.");
}
}
catch (Exception ex)

View file

@ -21,6 +21,7 @@ using Penumbra.Collections.Manager;
using Penumbra.GameData.Actors;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Files;
using Penumbra.GameData.Interop;
using Penumbra.Import.Structs;
using Penumbra.Import.Textures;
using Penumbra.Interop.PathResolving;
@ -90,12 +91,12 @@ public class DebugTab : Window, ITab
private readonly RedrawService _redraws;
private readonly DictEmote _emotes;
private readonly Diagnostics _diagnostics;
private readonly IObjectTable _objects;
private readonly ObjectManager _objects;
private readonly IClientState _clientState;
private readonly IpcTester _ipcTester;
private readonly CrashHandlerPanel _crashHandlerPanel;
public DebugTab(PerformanceTracker performance, Configuration config, CollectionManager collectionManager, IObjectTable objects,
public DebugTab(PerformanceTracker performance, Configuration config, CollectionManager collectionManager, ObjectManager objects,
IClientState clientState,
ValidityChecker validityChecker, ModManager modManager, HttpApi httpApi, ActorManager actors, StainService stains,
CharacterUtility characterUtility, ResidentResourceManager residentResources,
@ -430,7 +431,7 @@ public class DebugTab : Window, ITab
DrawSpecial("Current Card", _actors.GetCardPlayer());
DrawSpecial("Current Glamour", _actors.GetGlamourPlayer());
foreach (var obj in _objects)
foreach (var obj in _objects.Objects)
{
ImGuiUtil.DrawTableColumn($"{((GameObject*)obj.Address)->ObjectIndex}");
ImGuiUtil.DrawTableColumn($"0x{obj.Address:X}");

View file

@ -14,6 +14,7 @@ using Penumbra.Mods.Manager;
using Penumbra.UI.ModsTab;
using ModFileSystemSelector = Penumbra.UI.ModsTab.ModFileSystemSelector;
using Penumbra.Collections.Manager;
using Penumbra.GameData.Interop;
namespace Penumbra.UI.Tabs;
@ -28,7 +29,7 @@ public class ModsTab(
IClientState clientState,
CollectionSelectHeader collectionHeader,
ITargetManager targets,
IObjectTable objectTable)
ObjectManager objects)
: ITab
{
private readonly ActiveCollections _activeCollections = collectionManager.Active;
@ -128,7 +129,7 @@ public class ModsTab(
using var disabled = ImRaii.Disabled(clientState.LocalPlayer == null);
ImGui.SameLine();
var buttonWidth = frameHeight with { X = ImGui.GetContentRegionAvail().X / 5 };
var tt = objectTable.GetObjectAddress(0) == nint.Zero
var tt = !objects[0].Valid
? "\nCan only be used when you are logged in and your character is available."
: string.Empty;
DrawButton(buttonWidth, "All", string.Empty, tt);