mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
ResourceTree improvements + IPC
- Moves ResourceType enum out of GameData as discussed on Discord ; - Adds new color coding for local player and non-networked objects on On-Screen ; - Adds ResourceTree-related IPC ; - Fixes #342.
This commit is contained in:
parent
2b4a01df06
commit
d7205344eb
32 changed files with 826 additions and 80 deletions
|
|
@ -15,6 +15,8 @@ using Penumbra.Mods.Manager;
|
|||
using Penumbra.Services;
|
||||
using Penumbra.UI;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Penumbra.Api;
|
||||
|
||||
|
|
@ -35,6 +37,7 @@ public class IpcTester : IDisposable
|
|||
private readonly ModSettings _modSettings;
|
||||
private readonly Editing _editing;
|
||||
private readonly Temporary _temporary;
|
||||
private readonly ResourceTree _resourceTree;
|
||||
|
||||
public IpcTester(Configuration config, DalamudServices dalamud, PenumbraIpcProviders ipcProviders, ModManager modManager,
|
||||
CollectionManager collections, TempModManager tempMods, TempCollectionManager tempCollections, SaveService saveService)
|
||||
|
|
@ -52,6 +55,7 @@ public class IpcTester : IDisposable
|
|||
_modSettings = new ModSettings(dalamud.PluginInterface);
|
||||
_editing = new Editing(dalamud.PluginInterface);
|
||||
_temporary = new Temporary(dalamud.PluginInterface, modManager, collections, tempMods, tempCollections, saveService, config);
|
||||
_resourceTree = new ResourceTree(dalamud.PluginInterface, dalamud.Objects);
|
||||
UnsubscribeEvents();
|
||||
}
|
||||
|
||||
|
|
@ -75,6 +79,7 @@ public class IpcTester : IDisposable
|
|||
_temporary.Draw();
|
||||
_temporary.DrawCollections();
|
||||
_temporary.DrawMods();
|
||||
_resourceTree.Draw();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
@ -1397,4 +1402,222 @@ public class IpcTester : IDisposable
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ResourceTree
|
||||
{
|
||||
private readonly DalamudPluginInterface _pi;
|
||||
private readonly IObjectTable _objects;
|
||||
|
||||
private string _gameObjectIndices = "0";
|
||||
private bool _mergeSameCollection = false;
|
||||
private ResourceType _type = ResourceType.Mtrl;
|
||||
private bool _withUIData = false;
|
||||
|
||||
private (string, IReadOnlyDictionary<string, string[]>?)[]? _lastGameObjectResourcePaths;
|
||||
private (string, IReadOnlyDictionary<string, string[]>?)[]? _lastPlayerResourcePaths;
|
||||
private (string, IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>?)[]? _lastGameObjectResourcesOfType;
|
||||
private (string, IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>?)[]? _lastPlayerResourcesOfType;
|
||||
|
||||
public ResourceTree(DalamudPluginInterface pi, IObjectTable objects)
|
||||
{
|
||||
_pi = pi;
|
||||
_objects = objects;
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var _ = ImRaii.TreeNode("Resource Tree");
|
||||
if (!_)
|
||||
return;
|
||||
|
||||
ImGui.InputText("GameObject indices", ref _gameObjectIndices, 511);
|
||||
ImGui.Checkbox("Merge entries that use the same collection", ref _mergeSameCollection);
|
||||
ImGuiUtil.GenericEnumCombo("Resource type", ImGui.CalcItemWidth(), _type, out _type, Enum.GetValues<ResourceType>());
|
||||
ImGui.Checkbox("Also get names and icons", ref _withUIData);
|
||||
|
||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
DrawIntro(Ipc.GetGameObjectResourcePaths.Label, "Get GameObject resource paths");
|
||||
if (ImGui.Button("Get##GameObjectResourcePaths"))
|
||||
{
|
||||
var gameObjects = GetSelectedGameObjects();
|
||||
var resourcePaths = Ipc.GetGameObjectResourcePaths.Subscriber(_pi).Invoke(gameObjects, _mergeSameCollection);
|
||||
|
||||
_lastGameObjectResourcePaths = gameObjects
|
||||
.Select(GameObjectToString)
|
||||
.Zip(resourcePaths)
|
||||
.ToArray();
|
||||
|
||||
ImGui.OpenPopup(nameof(Ipc.GetGameObjectResourcePaths));
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.GetPlayerResourcePaths.Label, "Get local player resource paths");
|
||||
if (ImGui.Button("Get##PlayerResourcePaths"))
|
||||
{
|
||||
_lastPlayerResourcePaths = Ipc.GetPlayerResourcePaths.Subscriber(_pi).Invoke(_mergeSameCollection)
|
||||
.Select(pair => (GameObjectToString(pair.Key), (IReadOnlyDictionary<string, string[]>?)pair.Value))
|
||||
.ToArray();
|
||||
|
||||
ImGui.OpenPopup(nameof(Ipc.GetPlayerResourcePaths));
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.GetGameObjectResourcesOfType.Label, "Get GameObject resources of type");
|
||||
if (ImGui.Button("Get##GameObjectResourcesOfType"))
|
||||
{
|
||||
var gameObjects = GetSelectedGameObjects();
|
||||
var resourcesOfType = Ipc.GetGameObjectResourcesOfType.Subscriber(_pi).Invoke(gameObjects, _type, _withUIData);
|
||||
|
||||
_lastGameObjectResourcesOfType = gameObjects
|
||||
.Select(GameObjectToString)
|
||||
.Zip(resourcesOfType)
|
||||
.ToArray();
|
||||
|
||||
ImGui.OpenPopup(nameof(Ipc.GetGameObjectResourcesOfType));
|
||||
}
|
||||
|
||||
DrawIntro(Ipc.GetPlayerResourcesOfType.Label, "Get local player resources of type");
|
||||
if (ImGui.Button("Get##PlayerResourcesOfType"))
|
||||
{
|
||||
_lastPlayerResourcesOfType = Ipc.GetPlayerResourcesOfType.Subscriber(_pi).Invoke(_type, _withUIData)
|
||||
.Select(pair => (GameObjectToString(pair.Key), (IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>?)pair.Value))
|
||||
.ToArray();
|
||||
|
||||
ImGui.OpenPopup(nameof(Ipc.GetPlayerResourcesOfType));
|
||||
}
|
||||
|
||||
DrawPopup(nameof(Ipc.GetGameObjectResourcePaths), ref _lastGameObjectResourcePaths, DrawResourcePaths);
|
||||
DrawPopup(nameof(Ipc.GetPlayerResourcePaths), ref _lastPlayerResourcePaths, DrawResourcePaths);
|
||||
|
||||
DrawPopup(nameof(Ipc.GetGameObjectResourcesOfType), ref _lastGameObjectResourcesOfType, DrawResourcesOfType);
|
||||
DrawPopup(nameof(Ipc.GetPlayerResourcesOfType), ref _lastPlayerResourcesOfType, DrawResourcesOfType);
|
||||
}
|
||||
|
||||
private static void DrawPopup<T>(string popupId, ref T? result, Action<T> drawResult) where T : class
|
||||
{
|
||||
ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(1000, 500));
|
||||
using var popup = ImRaii.Popup(popupId);
|
||||
if (!popup)
|
||||
{
|
||||
result = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
ImGui.CloseCurrentPopup();
|
||||
return;
|
||||
}
|
||||
|
||||
drawResult(result);
|
||||
|
||||
if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
|
||||
{
|
||||
result = null;
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawWithHeaders<T>((string, T?)[] result, Action<T> drawItem) where T : class
|
||||
{
|
||||
var firstSeen = new Dictionary<T, string>();
|
||||
foreach (var (label, item) in result)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
ImRaii.TreeNode($"{label}: null", ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (firstSeen.TryGetValue(item, out var firstLabel))
|
||||
{
|
||||
ImRaii.TreeNode($"{label}: same as {firstLabel}", ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
firstSeen.Add(item, label);
|
||||
|
||||
using var header = ImRaii.TreeNode(label);
|
||||
if (!header)
|
||||
continue;
|
||||
|
||||
drawItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawResourcePaths((string, IReadOnlyDictionary<string, string[]>?)[] result)
|
||||
{
|
||||
DrawWithHeaders(result, paths =>
|
||||
{
|
||||
using var table = ImRaii.Table(string.Empty, 2, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
ImGui.TableSetupColumn("Actual Path", ImGuiTableColumnFlags.WidthStretch, 0.6f);
|
||||
ImGui.TableSetupColumn("Game Paths", ImGuiTableColumnFlags.WidthStretch, 0.4f);
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
foreach (var (actualPath, gamePaths) in paths)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(actualPath);
|
||||
ImGui.TableNextColumn();
|
||||
foreach (var gamePath in gamePaths)
|
||||
ImGui.TextUnformatted(gamePath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void DrawResourcesOfType((string, IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>?)[] result)
|
||||
{
|
||||
DrawWithHeaders(result, resources =>
|
||||
{
|
||||
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("Icon & Name", ImGuiTableColumnFlags.WidthStretch, 0.3f);
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
foreach (var (resourceHandle, (actualPath, name, icon)) in resources)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
TextUnformattedMono($"0x{resourceHandle:X}");
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(actualPath);
|
||||
if (_withUIData)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
TextUnformattedMono(icon.ToString());
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(name);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void TextUnformattedMono(string text)
|
||||
{
|
||||
using var _ = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
ImGui.TextUnformatted(text);
|
||||
}
|
||||
|
||||
private ushort[] GetSelectedGameObjects()
|
||||
=> _gameObjectIndices.Split(',')
|
||||
.SelectWhere(index => (ushort.TryParse(index.Trim(), out var i), i))
|
||||
.ToArray();
|
||||
|
||||
private unsafe string GameObjectToString(ushort gameObjectIndex)
|
||||
{
|
||||
var gameObject = _objects[gameObjectIndex];
|
||||
|
||||
return gameObject != null
|
||||
? $"[{gameObjectIndex}] {gameObject.Name} ({gameObject.ObjectKind})"
|
||||
: $"[{gameObjectIndex}] null";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,13 +23,15 @@ using Penumbra.Import.Textures;
|
|||
using Penumbra.Interop.Services;
|
||||
using Penumbra.UI;
|
||||
using TextureType = Penumbra.Api.Enums.TextureType;
|
||||
using Penumbra.Interop.ResourceTree;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Penumbra.Api;
|
||||
|
||||
public class PenumbraApi : IDisposable, IPenumbraApi
|
||||
{
|
||||
public (int, int) ApiVersion
|
||||
=> (4, 21);
|
||||
=> (4, 22);
|
||||
|
||||
public event Action<string>? PreSettingsPanelDraw
|
||||
{
|
||||
|
|
@ -104,31 +106,33 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
private ModFileSystem _modFileSystem;
|
||||
private ConfigWindow _configWindow;
|
||||
private TextureManager _textureManager;
|
||||
private ResourceTreeFactory _resourceTreeFactory;
|
||||
|
||||
public unsafe PenumbraApi(CommunicatorService communicator, ModManager modManager, ResourceLoader resourceLoader,
|
||||
Configuration config, CollectionManager collectionManager, DalamudServices dalamud, TempCollectionManager tempCollections,
|
||||
TempModManager tempMods, ActorService actors, CollectionResolver collectionResolver, CutsceneService cutsceneService,
|
||||
ModImportManager modImportManager, CollectionEditor collectionEditor, RedrawService redrawService, ModFileSystem modFileSystem,
|
||||
ConfigWindow configWindow, TextureManager textureManager)
|
||||
ConfigWindow configWindow, TextureManager textureManager, ResourceTreeFactory resourceTreeFactory)
|
||||
{
|
||||
_communicator = communicator;
|
||||
_modManager = modManager;
|
||||
_resourceLoader = resourceLoader;
|
||||
_config = config;
|
||||
_collectionManager = collectionManager;
|
||||
_dalamud = dalamud;
|
||||
_tempCollections = tempCollections;
|
||||
_tempMods = tempMods;
|
||||
_actors = actors;
|
||||
_collectionResolver = collectionResolver;
|
||||
_cutsceneService = cutsceneService;
|
||||
_modImportManager = modImportManager;
|
||||
_collectionEditor = collectionEditor;
|
||||
_redrawService = redrawService;
|
||||
_modFileSystem = modFileSystem;
|
||||
_configWindow = configWindow;
|
||||
_textureManager = textureManager;
|
||||
_lumina = _dalamud.GameData.GameData;
|
||||
_communicator = communicator;
|
||||
_modManager = modManager;
|
||||
_resourceLoader = resourceLoader;
|
||||
_config = config;
|
||||
_collectionManager = collectionManager;
|
||||
_dalamud = dalamud;
|
||||
_tempCollections = tempCollections;
|
||||
_tempMods = tempMods;
|
||||
_actors = actors;
|
||||
_collectionResolver = collectionResolver;
|
||||
_cutsceneService = cutsceneService;
|
||||
_modImportManager = modImportManager;
|
||||
_collectionEditor = collectionEditor;
|
||||
_redrawService = redrawService;
|
||||
_modFileSystem = modFileSystem;
|
||||
_configWindow = configWindow;
|
||||
_textureManager = textureManager;
|
||||
_resourceTreeFactory = resourceTreeFactory;
|
||||
_lumina = _dalamud.GameData.GameData;
|
||||
|
||||
_resourceLoader.ResourceLoaded += OnResourceLoaded;
|
||||
_communicator.ModPathChanged.Subscribe(ModPathChangeSubscriber, ModPathChanged.Priority.Api);
|
||||
|
|
@ -145,24 +149,25 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
_communicator.ModPathChanged.Unsubscribe(ModPathChangeSubscriber);
|
||||
_communicator.ModSettingChanged.Unsubscribe(OnModSettingChange);
|
||||
_communicator.CreatedCharacterBase.Unsubscribe(OnCreatedCharacterBase);
|
||||
_lumina = null;
|
||||
_communicator = null!;
|
||||
_modManager = null!;
|
||||
_resourceLoader = null!;
|
||||
_config = null!;
|
||||
_collectionManager = null!;
|
||||
_dalamud = null!;
|
||||
_tempCollections = null!;
|
||||
_tempMods = null!;
|
||||
_actors = null!;
|
||||
_collectionResolver = null!;
|
||||
_cutsceneService = null!;
|
||||
_modImportManager = null!;
|
||||
_collectionEditor = null!;
|
||||
_redrawService = null!;
|
||||
_modFileSystem = null!;
|
||||
_configWindow = null!;
|
||||
_textureManager = null!;
|
||||
_lumina = null;
|
||||
_communicator = null!;
|
||||
_modManager = null!;
|
||||
_resourceLoader = null!;
|
||||
_config = null!;
|
||||
_collectionManager = null!;
|
||||
_dalamud = null!;
|
||||
_tempCollections = null!;
|
||||
_tempMods = null!;
|
||||
_actors = null!;
|
||||
_collectionResolver = null!;
|
||||
_cutsceneService = null!;
|
||||
_modImportManager = null!;
|
||||
_collectionEditor = null!;
|
||||
_redrawService = null!;
|
||||
_modFileSystem = null!;
|
||||
_configWindow = null!;
|
||||
_textureManager = null!;
|
||||
_resourceTreeFactory = null!;
|
||||
}
|
||||
|
||||
public event ChangedItemClick? ChangedItemClicked
|
||||
|
|
@ -1011,6 +1016,40 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
};
|
||||
// @formatter:on
|
||||
|
||||
public IReadOnlyDictionary<string, string[]>?[] GetGameObjectResourcePaths(ushort[] gameObjects, bool mergeSameCollection)
|
||||
{
|
||||
var characters = gameObjects.Select(index => _dalamud.Objects[index]).OfType<Character>();
|
||||
var resourceTrees = _resourceTreeFactory.FromCharacters(characters, false, false);
|
||||
var pathDictionaries = ResourceTreeApiHelper.GetResourcePathDictionaries(resourceTrees, mergeSameCollection);
|
||||
|
||||
return Array.ConvertAll(gameObjects, obj => pathDictionaries.TryGetValue(obj, out var pathDict) ? pathDict : null);
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<ushort, IReadOnlyDictionary<string, string[]>> GetPlayerResourcePaths(bool mergeSameCollection)
|
||||
{
|
||||
var resourceTrees = _resourceTreeFactory.FromObjectTable(true, false, false);
|
||||
var pathDictionaries = ResourceTreeApiHelper.GetResourcePathDictionaries(resourceTrees, mergeSameCollection);
|
||||
|
||||
return pathDictionaries.AsReadOnly();
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>?[] GetGameObjectResourcesOfType(ushort[] gameObjects, ResourceType type, bool withUIData)
|
||||
{
|
||||
var characters = gameObjects.Select(index => _dalamud.Objects[index]).OfType<Character>();
|
||||
var resourceTrees = _resourceTreeFactory.FromCharacters(characters, withUIData, false);
|
||||
var resDictionaries = ResourceTreeApiHelper.GetResourcesOfType(resourceTrees, type);
|
||||
|
||||
return Array.ConvertAll(gameObjects, obj => resDictionaries.TryGetValue(obj, out var resDict) ? resDict : null);
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<ushort, IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>> GetPlayerResourcesOfType(ResourceType type, bool withUIData)
|
||||
{
|
||||
var resourceTrees = _resourceTreeFactory.FromObjectTable(true, withUIData, false);
|
||||
var resDictionaries = ResourceTreeApiHelper.GetResourcesOfType(resourceTrees, type);
|
||||
|
||||
return resDictionaries.AsReadOnly();
|
||||
}
|
||||
|
||||
|
||||
// TODO: cleanup when incrementing API
|
||||
public string GetMetaManipulations(string characterName)
|
||||
|
|
|
|||
|
|
@ -118,6 +118,12 @@ public class PenumbraIpcProviders : IDisposable
|
|||
internal readonly FuncProvider<string, int, PenumbraApiEc> RemoveTemporaryModAll;
|
||||
internal readonly FuncProvider<string, string, int, PenumbraApiEc> RemoveTemporaryMod;
|
||||
|
||||
// Resource Tree
|
||||
internal readonly FuncProvider<ushort[], bool, IReadOnlyDictionary<string, string[]>?[]> GetGameObjectResourcePaths;
|
||||
internal readonly FuncProvider<bool, IReadOnlyDictionary<ushort, IReadOnlyDictionary<string, string[]>>> GetPlayerResourcePaths;
|
||||
internal readonly FuncProvider<ushort[], ResourceType, bool, IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>?[]> GetGameObjectResourcesOfType;
|
||||
internal readonly FuncProvider<ResourceType, bool, IReadOnlyDictionary<ushort, IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>>> GetPlayerResourcesOfType;
|
||||
|
||||
public PenumbraIpcProviders(DalamudServices dalamud, IPenumbraApi api, ModManager modManager, CollectionManager collections,
|
||||
TempModManager tempMods, TempCollectionManager tempCollections, SaveService saveService, Configuration config)
|
||||
{
|
||||
|
|
@ -236,6 +242,12 @@ public class PenumbraIpcProviders : IDisposable
|
|||
RemoveTemporaryModAll = Ipc.RemoveTemporaryModAll.Provider(pi, Api.RemoveTemporaryModAll);
|
||||
RemoveTemporaryMod = Ipc.RemoveTemporaryMod.Provider(pi, Api.RemoveTemporaryMod);
|
||||
|
||||
// ResourceTree
|
||||
GetGameObjectResourcePaths = Ipc.GetGameObjectResourcePaths.Provider(pi, Api.GetGameObjectResourcePaths);
|
||||
GetPlayerResourcePaths = Ipc.GetPlayerResourcePaths.Provider(pi, Api.GetPlayerResourcePaths);
|
||||
GetGameObjectResourcesOfType = Ipc.GetGameObjectResourcesOfType.Provider(pi, Api.GetGameObjectResourcesOfType);
|
||||
GetPlayerResourcesOfType = Ipc.GetPlayerResourcesOfType.Provider(pi, Api.GetPlayerResourcesOfType);
|
||||
|
||||
Tester = new IpcTester(config, dalamud, this, modManager, collections, tempMods, tempCollections, saveService);
|
||||
|
||||
Initialized.Invoke();
|
||||
|
|
@ -345,6 +357,12 @@ public class PenumbraIpcProviders : IDisposable
|
|||
ConvertTextureFile.Dispose();
|
||||
ConvertTextureData.Dispose();
|
||||
|
||||
// Resource Tree
|
||||
GetGameObjectResourcePaths.Dispose();
|
||||
GetPlayerResourcePaths.Dispose();
|
||||
GetGameObjectResourcesOfType.Dispose();
|
||||
GetPlayerResourcesOfType.Dispose();
|
||||
|
||||
Disposed.Invoke();
|
||||
Disposed.Dispose();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using OtterGui.Classes;
|
|||
using OtterGui.Filesystem;
|
||||
using OtterGui.Widgets;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Import.Structs;
|
||||
using Penumbra.Interop.Services;
|
||||
using Penumbra.Mods;
|
||||
|
|
|
|||
260
Penumbra/Enums/ResourceType.cs
Normal file
260
Penumbra/Enums/ResourceType.cs
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.String;
|
||||
using Penumbra.String.Functions;
|
||||
|
||||
namespace Penumbra.Enums;
|
||||
|
||||
[Flags]
|
||||
public enum ResourceTypeFlag : ulong
|
||||
{
|
||||
Aet = 0x0000_0000_0000_0001,
|
||||
Amb = 0x0000_0000_0000_0002,
|
||||
Atch = 0x0000_0000_0000_0004,
|
||||
Atex = 0x0000_0000_0000_0008,
|
||||
Avfx = 0x0000_0000_0000_0010,
|
||||
Awt = 0x0000_0000_0000_0020,
|
||||
Cmp = 0x0000_0000_0000_0040,
|
||||
Dic = 0x0000_0000_0000_0080,
|
||||
Eid = 0x0000_0000_0000_0100,
|
||||
Envb = 0x0000_0000_0000_0200,
|
||||
Eqdp = 0x0000_0000_0000_0400,
|
||||
Eqp = 0x0000_0000_0000_0800,
|
||||
Essb = 0x0000_0000_0000_1000,
|
||||
Est = 0x0000_0000_0000_2000,
|
||||
Evp = 0x0000_0000_0000_4000,
|
||||
Exd = 0x0000_0000_0000_8000,
|
||||
Exh = 0x0000_0000_0001_0000,
|
||||
Exl = 0x0000_0000_0002_0000,
|
||||
Fdt = 0x0000_0000_0004_0000,
|
||||
Gfd = 0x0000_0000_0008_0000,
|
||||
Ggd = 0x0000_0000_0010_0000,
|
||||
Gmp = 0x0000_0000_0020_0000,
|
||||
Gzd = 0x0000_0000_0040_0000,
|
||||
Imc = 0x0000_0000_0080_0000,
|
||||
Lcb = 0x0000_0000_0100_0000,
|
||||
Lgb = 0x0000_0000_0200_0000,
|
||||
Luab = 0x0000_0000_0400_0000,
|
||||
Lvb = 0x0000_0000_0800_0000,
|
||||
Mdl = 0x0000_0000_1000_0000,
|
||||
Mlt = 0x0000_0000_2000_0000,
|
||||
Mtrl = 0x0000_0000_4000_0000,
|
||||
Obsb = 0x0000_0000_8000_0000,
|
||||
Pap = 0x0000_0001_0000_0000,
|
||||
Pbd = 0x0000_0002_0000_0000,
|
||||
Pcb = 0x0000_0004_0000_0000,
|
||||
Phyb = 0x0000_0008_0000_0000,
|
||||
Plt = 0x0000_0010_0000_0000,
|
||||
Scd = 0x0000_0020_0000_0000,
|
||||
Sgb = 0x0000_0040_0000_0000,
|
||||
Shcd = 0x0000_0080_0000_0000,
|
||||
Shpk = 0x0000_0100_0000_0000,
|
||||
Sklb = 0x0000_0200_0000_0000,
|
||||
Skp = 0x0000_0400_0000_0000,
|
||||
Stm = 0x0000_0800_0000_0000,
|
||||
Svb = 0x0000_1000_0000_0000,
|
||||
Tera = 0x0000_2000_0000_0000,
|
||||
Tex = 0x0000_4000_0000_0000,
|
||||
Tmb = 0x0000_8000_0000_0000,
|
||||
Ugd = 0x0001_0000_0000_0000,
|
||||
Uld = 0x0002_0000_0000_0000,
|
||||
Waoe = 0x0004_0000_0000_0000,
|
||||
Wtd = 0x0008_0000_0000_0000,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ResourceCategoryFlag : ushort
|
||||
{
|
||||
Common = 0x0001,
|
||||
BgCommon = 0x0002,
|
||||
Bg = 0x0004,
|
||||
Cut = 0x0008,
|
||||
Chara = 0x0010,
|
||||
Shader = 0x0020,
|
||||
Ui = 0x0040,
|
||||
Sound = 0x0080,
|
||||
Vfx = 0x0100,
|
||||
UiScript = 0x0200,
|
||||
Exd = 0x0400,
|
||||
GameScript = 0x0800,
|
||||
Music = 0x1000,
|
||||
SqpackTest = 0x2000,
|
||||
}
|
||||
|
||||
public static class ResourceExtensions
|
||||
{
|
||||
public static readonly ResourceTypeFlag AllResourceTypes = Enum.GetValues<ResourceTypeFlag>().Aggregate((v, f) => v | f);
|
||||
public static readonly ResourceCategoryFlag AllResourceCategories = Enum.GetValues<ResourceCategoryFlag>().Aggregate((v, f) => v | f);
|
||||
|
||||
public static ResourceTypeFlag ToFlag(this ResourceType type)
|
||||
=> type switch
|
||||
{
|
||||
ResourceType.Aet => ResourceTypeFlag.Aet,
|
||||
ResourceType.Amb => ResourceTypeFlag.Amb,
|
||||
ResourceType.Atch => ResourceTypeFlag.Atch,
|
||||
ResourceType.Atex => ResourceTypeFlag.Atex,
|
||||
ResourceType.Avfx => ResourceTypeFlag.Avfx,
|
||||
ResourceType.Awt => ResourceTypeFlag.Awt,
|
||||
ResourceType.Cmp => ResourceTypeFlag.Cmp,
|
||||
ResourceType.Dic => ResourceTypeFlag.Dic,
|
||||
ResourceType.Eid => ResourceTypeFlag.Eid,
|
||||
ResourceType.Envb => ResourceTypeFlag.Envb,
|
||||
ResourceType.Eqdp => ResourceTypeFlag.Eqdp,
|
||||
ResourceType.Eqp => ResourceTypeFlag.Eqp,
|
||||
ResourceType.Essb => ResourceTypeFlag.Essb,
|
||||
ResourceType.Est => ResourceTypeFlag.Est,
|
||||
ResourceType.Evp => ResourceTypeFlag.Evp,
|
||||
ResourceType.Exd => ResourceTypeFlag.Exd,
|
||||
ResourceType.Exh => ResourceTypeFlag.Exh,
|
||||
ResourceType.Exl => ResourceTypeFlag.Exl,
|
||||
ResourceType.Fdt => ResourceTypeFlag.Fdt,
|
||||
ResourceType.Gfd => ResourceTypeFlag.Gfd,
|
||||
ResourceType.Ggd => ResourceTypeFlag.Ggd,
|
||||
ResourceType.Gmp => ResourceTypeFlag.Gmp,
|
||||
ResourceType.Gzd => ResourceTypeFlag.Gzd,
|
||||
ResourceType.Imc => ResourceTypeFlag.Imc,
|
||||
ResourceType.Lcb => ResourceTypeFlag.Lcb,
|
||||
ResourceType.Lgb => ResourceTypeFlag.Lgb,
|
||||
ResourceType.Luab => ResourceTypeFlag.Luab,
|
||||
ResourceType.Lvb => ResourceTypeFlag.Lvb,
|
||||
ResourceType.Mdl => ResourceTypeFlag.Mdl,
|
||||
ResourceType.Mlt => ResourceTypeFlag.Mlt,
|
||||
ResourceType.Mtrl => ResourceTypeFlag.Mtrl,
|
||||
ResourceType.Obsb => ResourceTypeFlag.Obsb,
|
||||
ResourceType.Pap => ResourceTypeFlag.Pap,
|
||||
ResourceType.Pbd => ResourceTypeFlag.Pbd,
|
||||
ResourceType.Pcb => ResourceTypeFlag.Pcb,
|
||||
ResourceType.Phyb => ResourceTypeFlag.Phyb,
|
||||
ResourceType.Plt => ResourceTypeFlag.Plt,
|
||||
ResourceType.Scd => ResourceTypeFlag.Scd,
|
||||
ResourceType.Sgb => ResourceTypeFlag.Sgb,
|
||||
ResourceType.Shcd => ResourceTypeFlag.Shcd,
|
||||
ResourceType.Shpk => ResourceTypeFlag.Shpk,
|
||||
ResourceType.Sklb => ResourceTypeFlag.Sklb,
|
||||
ResourceType.Skp => ResourceTypeFlag.Skp,
|
||||
ResourceType.Stm => ResourceTypeFlag.Stm,
|
||||
ResourceType.Svb => ResourceTypeFlag.Svb,
|
||||
ResourceType.Tera => ResourceTypeFlag.Tera,
|
||||
ResourceType.Tex => ResourceTypeFlag.Tex,
|
||||
ResourceType.Tmb => ResourceTypeFlag.Tmb,
|
||||
ResourceType.Ugd => ResourceTypeFlag.Ugd,
|
||||
ResourceType.Uld => ResourceTypeFlag.Uld,
|
||||
ResourceType.Waoe => ResourceTypeFlag.Waoe,
|
||||
ResourceType.Wtd => ResourceTypeFlag.Wtd,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
public static bool FitsFlag(this ResourceType type, ResourceTypeFlag flags)
|
||||
=> (type.ToFlag() & flags) != 0;
|
||||
|
||||
public static ResourceCategoryFlag ToFlag(this ResourceCategory type)
|
||||
=> type switch
|
||||
{
|
||||
ResourceCategory.Common => ResourceCategoryFlag.Common,
|
||||
ResourceCategory.BgCommon => ResourceCategoryFlag.BgCommon,
|
||||
ResourceCategory.Bg => ResourceCategoryFlag.Bg,
|
||||
ResourceCategory.Cut => ResourceCategoryFlag.Cut,
|
||||
ResourceCategory.Chara => ResourceCategoryFlag.Chara,
|
||||
ResourceCategory.Shader => ResourceCategoryFlag.Shader,
|
||||
ResourceCategory.Ui => ResourceCategoryFlag.Ui,
|
||||
ResourceCategory.Sound => ResourceCategoryFlag.Sound,
|
||||
ResourceCategory.Vfx => ResourceCategoryFlag.Vfx,
|
||||
ResourceCategory.UiScript => ResourceCategoryFlag.UiScript,
|
||||
ResourceCategory.Exd => ResourceCategoryFlag.Exd,
|
||||
ResourceCategory.GameScript => ResourceCategoryFlag.GameScript,
|
||||
ResourceCategory.Music => ResourceCategoryFlag.Music,
|
||||
ResourceCategory.SqpackTest => ResourceCategoryFlag.SqpackTest,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
public static bool FitsFlag(this ResourceCategory type, ResourceCategoryFlag flags)
|
||||
=> (type.ToFlag() & flags) != 0;
|
||||
|
||||
public static ResourceType FromBytes(byte a1, byte a2, byte a3)
|
||||
=> (ResourceType)(((uint)ByteStringFunctions.AsciiToLower(a1) << 16)
|
||||
| ((uint)ByteStringFunctions.AsciiToLower(a2) << 8)
|
||||
| ByteStringFunctions.AsciiToLower(a3));
|
||||
|
||||
public static ResourceType FromBytes(byte a1, byte a2, byte a3, byte a4)
|
||||
=> (ResourceType)(((uint)ByteStringFunctions.AsciiToLower(a1) << 24)
|
||||
| ((uint)ByteStringFunctions.AsciiToLower(a2) << 16)
|
||||
| ((uint)ByteStringFunctions.AsciiToLower(a3) << 8)
|
||||
| ByteStringFunctions.AsciiToLower(a4));
|
||||
|
||||
public static ResourceType FromBytes(char a1, char a2, char a3)
|
||||
=> FromBytes((byte)a1, (byte)a2, (byte)a3);
|
||||
|
||||
public static ResourceType FromBytes(char a1, char a2, char a3, char a4)
|
||||
=> FromBytes((byte)a1, (byte)a2, (byte)a3, (byte)a4);
|
||||
|
||||
public static ResourceType Type(string path)
|
||||
{
|
||||
var ext = Path.GetExtension(path.AsSpan());
|
||||
ext = ext.Length == 0 ? path.AsSpan() : ext[1..];
|
||||
|
||||
return ext.Length switch
|
||||
{
|
||||
0 => 0,
|
||||
1 => (ResourceType)ext[^1],
|
||||
2 => FromBytes('\0', ext[^2], ext[^1]),
|
||||
3 => FromBytes(ext[^3], ext[^2], ext[^1]),
|
||||
_ => FromBytes(ext[^4], ext[^3], ext[^2], ext[^1]),
|
||||
};
|
||||
}
|
||||
|
||||
public static ResourceType Type(ByteString path)
|
||||
{
|
||||
var extIdx = path.LastIndexOf((byte)'.');
|
||||
var ext = extIdx == -1 ? path : extIdx == path.Length - 1 ? ByteString.Empty : path.Substring(extIdx + 1);
|
||||
|
||||
return ext.Length switch
|
||||
{
|
||||
0 => 0,
|
||||
1 => (ResourceType)ext[^1],
|
||||
2 => FromBytes(0, ext[^2], ext[^1]),
|
||||
3 => FromBytes(ext[^3], ext[^2], ext[^1]),
|
||||
_ => FromBytes(ext[^4], ext[^3], ext[^2], ext[^1]),
|
||||
};
|
||||
}
|
||||
|
||||
public static ResourceCategory Category(ByteString path)
|
||||
{
|
||||
if (path.Length < 3)
|
||||
return ResourceCategory.Debug;
|
||||
|
||||
return ByteStringFunctions.AsciiToUpper(path[0]) switch
|
||||
{
|
||||
(byte)'C' => ByteStringFunctions.AsciiToUpper(path[1]) switch
|
||||
{
|
||||
(byte)'O' => ResourceCategory.Common,
|
||||
(byte)'U' => ResourceCategory.Cut,
|
||||
(byte)'H' => ResourceCategory.Chara,
|
||||
_ => ResourceCategory.Debug,
|
||||
},
|
||||
(byte)'B' => ByteStringFunctions.AsciiToUpper(path[2]) switch
|
||||
{
|
||||
(byte)'C' => ResourceCategory.BgCommon,
|
||||
(byte)'/' => ResourceCategory.Bg,
|
||||
_ => ResourceCategory.Debug,
|
||||
},
|
||||
(byte)'S' => ByteStringFunctions.AsciiToUpper(path[1]) switch
|
||||
{
|
||||
(byte)'H' => ResourceCategory.Shader,
|
||||
(byte)'O' => ResourceCategory.Sound,
|
||||
(byte)'Q' => ResourceCategory.SqpackTest,
|
||||
_ => ResourceCategory.Debug,
|
||||
},
|
||||
(byte)'U' => ByteStringFunctions.AsciiToUpper(path[2]) switch
|
||||
{
|
||||
(byte)'/' => ResourceCategory.Ui,
|
||||
(byte)'S' => ResourceCategory.UiScript,
|
||||
_ => ResourceCategory.Debug,
|
||||
},
|
||||
(byte)'V' => ResourceCategory.Vfx,
|
||||
(byte)'E' => ResourceCategory.Exd,
|
||||
(byte)'G' => ResourceCategory.GameScript,
|
||||
(byte)'M' => ResourceCategory.Music,
|
||||
_ => ResourceCategory.Debug,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,8 @@ using Dalamud.Plugin.Services;
|
|||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.String;
|
||||
using Penumbra.String.Classes;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using Dalamud.Utility.Signatures;
|
|||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.ResourceLoading;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
|||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.ResourceLoading;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.String;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
using Dalamud.Hooking;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.ResourceLoading;
|
||||
using Penumbra.Interop.Services;
|
||||
using Penumbra.Interop.Structs;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.String;
|
||||
using Penumbra.String.Classes;
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
|||
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||
using FFXIVClientStructs.Interop;
|
||||
using FFXIVClientStructs.STD;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Penumbra.Interop.ResourceLoading;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
using Dalamud.Hooking;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.String;
|
||||
using Penumbra.String.Classes;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
using Dalamud.Hooking;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
namespace Penumbra.Interop.ResourceLoading;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||
using OtterGui;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.String.Classes;
|
||||
using ChangedItemIcon = Penumbra.UI.ChangedItemDrawer.ChangedItemIcon;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,9 +12,12 @@ namespace Penumbra.Interop.ResourceTree;
|
|||
public class ResourceTree
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly int GameObjectIndex;
|
||||
public readonly nint GameObjectAddress;
|
||||
public readonly nint DrawObjectAddress;
|
||||
public readonly bool LocalPlayerRelated;
|
||||
public readonly bool PlayerRelated;
|
||||
public readonly bool Networked;
|
||||
public readonly string CollectionName;
|
||||
public readonly List<ResourceNode> Nodes;
|
||||
public readonly HashSet<ResourceNode> FlatNodes;
|
||||
|
|
@ -23,15 +26,18 @@ public class ResourceTree
|
|||
public CustomizeData CustomizeData;
|
||||
public GenderRace RaceCode;
|
||||
|
||||
public ResourceTree(string name, nint gameObjectAddress, nint drawObjectAddress, bool playerRelated, string collectionName)
|
||||
public ResourceTree(string name, int gameObjectIndex, nint gameObjectAddress, nint drawObjectAddress, bool localPlayerRelated, bool playerRelated, bool networked, string collectionName)
|
||||
{
|
||||
Name = name;
|
||||
GameObjectAddress = gameObjectAddress;
|
||||
DrawObjectAddress = drawObjectAddress;
|
||||
PlayerRelated = playerRelated;
|
||||
CollectionName = collectionName;
|
||||
Nodes = new List<ResourceNode>();
|
||||
FlatNodes = new HashSet<ResourceNode>();
|
||||
Name = name;
|
||||
GameObjectIndex = gameObjectIndex;
|
||||
GameObjectAddress = gameObjectAddress;
|
||||
DrawObjectAddress = drawObjectAddress;
|
||||
LocalPlayerRelated = localPlayerRelated;
|
||||
Networked = networked;
|
||||
PlayerRelated = playerRelated;
|
||||
CollectionName = collectionName;
|
||||
Nodes = new List<ResourceNode>();
|
||||
FlatNodes = new HashSet<ResourceNode>();
|
||||
}
|
||||
|
||||
internal unsafe void LoadResources(GlobalResolveContext globalContext)
|
||||
|
|
@ -64,7 +70,7 @@ public class ResourceTree
|
|||
|
||||
AddSkeleton(Nodes, globalContext.CreateContext(EquipSlot.Unknown, default), model->Skeleton);
|
||||
|
||||
if (character->GameObject.GetObjectKind() == (byte)ObjectKind.Pc)
|
||||
if (model->GetModelType() == CharacterBase.ModelType.Human)
|
||||
AddHumanResources(globalContext, (HumanExt*)model);
|
||||
}
|
||||
|
||||
|
|
|
|||
104
Penumbra/Interop/ResourceTree/ResourceTreeApiHelper.cs
Normal file
104
Penumbra/Interop/ResourceTree/ResourceTreeApiHelper.cs
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.UI;
|
||||
|
||||
namespace Penumbra.Interop.ResourceTree;
|
||||
|
||||
internal static class ResourceTreeApiHelper
|
||||
{
|
||||
public static Dictionary<ushort, IReadOnlyDictionary<string, string[]>> GetResourcePathDictionaries(IEnumerable<(Character, ResourceTree)> resourceTrees,
|
||||
bool mergeSameCollection)
|
||||
=> mergeSameCollection ? GetResourcePathDictionariesMerged(resourceTrees) : GetResourcePathDictionariesUnmerged(resourceTrees);
|
||||
|
||||
private static Dictionary<ushort, IReadOnlyDictionary<string, string[]>> GetResourcePathDictionariesMerged(IEnumerable<(Character, ResourceTree)> resourceTrees)
|
||||
{
|
||||
var collections = new Dictionary<ushort, string>(4);
|
||||
var pathDictionaries = new Dictionary<string, Dictionary<string, HashSet<string>>>(4);
|
||||
|
||||
foreach (var (gameObject, resourceTree) in resourceTrees)
|
||||
{
|
||||
if (collections.ContainsKey(gameObject.ObjectIndex))
|
||||
continue;
|
||||
|
||||
collections.Add(gameObject.ObjectIndex, resourceTree.CollectionName);
|
||||
if (!pathDictionaries.TryGetValue(resourceTree.CollectionName, out var pathDictionary))
|
||||
{
|
||||
pathDictionary = new();
|
||||
pathDictionaries.Add(resourceTree.CollectionName, pathDictionary);
|
||||
}
|
||||
|
||||
CollectResourcePaths(pathDictionary, resourceTree);
|
||||
}
|
||||
|
||||
var pathRODictionaries = pathDictionaries.ToDictionary(pair => pair.Key,
|
||||
pair => (IReadOnlyDictionary<string, string[]>)pair.Value.ToDictionary(pair => pair.Key, pair => pair.Value.ToArray()).AsReadOnly());
|
||||
|
||||
return collections.ToDictionary(pair => pair.Key, pair => pathRODictionaries[pair.Value]);
|
||||
}
|
||||
|
||||
private static Dictionary<ushort, IReadOnlyDictionary<string, string[]>> GetResourcePathDictionariesUnmerged(IEnumerable<(Character, ResourceTree)> resourceTrees)
|
||||
{
|
||||
var pathDictionaries = new Dictionary<ushort, Dictionary<string, HashSet<string>>>(4);
|
||||
|
||||
foreach (var (gameObject, resourceTree) in resourceTrees)
|
||||
{
|
||||
if (pathDictionaries.ContainsKey(gameObject.ObjectIndex))
|
||||
continue;
|
||||
|
||||
var pathDictionary = new Dictionary<string, HashSet<string>>();
|
||||
pathDictionaries.Add(gameObject.ObjectIndex, pathDictionary);
|
||||
|
||||
CollectResourcePaths(pathDictionary, resourceTree);
|
||||
}
|
||||
|
||||
return pathDictionaries.ToDictionary(pair => pair.Key,
|
||||
pair => (IReadOnlyDictionary<string, string[]>)pair.Value.ToDictionary(pair => pair.Key, pair => pair.Value.ToArray()).AsReadOnly());
|
||||
}
|
||||
|
||||
private static void CollectResourcePaths(Dictionary<string, HashSet<string>> pathDictionary, ResourceTree resourceTree)
|
||||
{
|
||||
foreach (var node in resourceTree.FlatNodes)
|
||||
{
|
||||
if (node.PossibleGamePaths.Length == 0)
|
||||
continue;
|
||||
|
||||
var fullPath = node.FullPath.ToPath();
|
||||
if (!pathDictionary.TryGetValue(fullPath, out var gamePaths))
|
||||
{
|
||||
gamePaths = new();
|
||||
pathDictionary.Add(fullPath, gamePaths);
|
||||
}
|
||||
|
||||
foreach (var gamePath in node.PossibleGamePaths)
|
||||
gamePaths.Add(gamePath.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public static Dictionary<ushort, IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>> GetResourcesOfType(IEnumerable<(Character, ResourceTree)> resourceTrees,
|
||||
ResourceType type)
|
||||
{
|
||||
var resDictionaries = new Dictionary<ushort, Dictionary<nint, (string, string, ChangedItemIcon)>>(4);
|
||||
foreach (var (gameObject, resourceTree) in resourceTrees)
|
||||
{
|
||||
if (resDictionaries.ContainsKey(gameObject.ObjectIndex))
|
||||
continue;
|
||||
|
||||
var resDictionary = new Dictionary<nint, (string, string, ChangedItemIcon)>();
|
||||
resDictionaries.Add(gameObject.ObjectIndex, resDictionary);
|
||||
|
||||
foreach (var node in resourceTree.FlatNodes)
|
||||
{
|
||||
if (node.Type != type)
|
||||
continue;
|
||||
if (resDictionary.ContainsKey(node.ResourceHandle))
|
||||
continue;
|
||||
|
||||
var fullPath = node.FullPath.ToPath();
|
||||
resDictionary.Add(node.ResourceHandle, (fullPath, node.Name ?? string.Empty, ChangedItemDrawer.ToApiIcon(node.Icon)));
|
||||
}
|
||||
}
|
||||
|
||||
return resDictionaries.ToDictionary(pair => pair.Key,
|
||||
pair => (IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>)pair.Value.AsReadOnly());
|
||||
}
|
||||
}
|
||||
|
|
@ -27,21 +27,35 @@ public class ResourceTreeFactory
|
|||
_actors = actors;
|
||||
}
|
||||
|
||||
public ResourceTree[] FromObjectTable(bool withNames = true, bool redactExternalPaths = true)
|
||||
{
|
||||
var cache = new TreeBuildCache(_objects, _gameData);
|
||||
private TreeBuildCache CreateTreeBuildCache()
|
||||
=> new(_objects, _gameData, _actors);
|
||||
|
||||
return cache.Characters
|
||||
.Select(c => FromCharacter(c, cache, withNames, redactExternalPaths))
|
||||
.OfType<ResourceTree>()
|
||||
.ToArray();
|
||||
public IEnumerable<Dalamud.Game.ClientState.Objects.Types.Character> GetLocalPlayerRelatedCharacters()
|
||||
{
|
||||
var cache = CreateTreeBuildCache();
|
||||
|
||||
return cache.Characters.Where(cache.IsLocalPlayerRelated);
|
||||
}
|
||||
|
||||
public IEnumerable<(Dalamud.Game.ClientState.Objects.Types.Character Character, ResourceTree ResourceTree)> FromObjectTable(
|
||||
bool localPlayerRelatedOnly = false, bool withUIData = true, bool redactExternalPaths = true)
|
||||
{
|
||||
var cache = CreateTreeBuildCache();
|
||||
var characters = localPlayerRelatedOnly ? cache.Characters.Where(cache.IsLocalPlayerRelated) : cache.Characters;
|
||||
|
||||
foreach (var character in characters)
|
||||
{
|
||||
var tree = FromCharacter(character, cache, withUIData, redactExternalPaths);
|
||||
if (tree != null)
|
||||
yield return (character, tree);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<(Dalamud.Game.ClientState.Objects.Types.Character Character, ResourceTree ResourceTree)> FromCharacters(
|
||||
IEnumerable<Dalamud.Game.ClientState.Objects.Types.Character> characters,
|
||||
bool withUIData = true, bool redactExternalPaths = true)
|
||||
{
|
||||
var cache = new TreeBuildCache(_objects, _gameData);
|
||||
var cache = CreateTreeBuildCache();
|
||||
foreach (var character in characters)
|
||||
{
|
||||
var tree = FromCharacter(character, cache, withUIData, redactExternalPaths);
|
||||
|
|
@ -52,7 +66,7 @@ public class ResourceTreeFactory
|
|||
|
||||
public ResourceTree? FromCharacter(Dalamud.Game.ClientState.Objects.Types.Character character, bool withUIData = true,
|
||||
bool redactExternalPaths = true)
|
||||
=> FromCharacter(character, new TreeBuildCache(_objects, _gameData), withUIData, redactExternalPaths);
|
||||
=> FromCharacter(character, CreateTreeBuildCache(), withUIData, redactExternalPaths);
|
||||
|
||||
private unsafe ResourceTree? FromCharacter(Dalamud.Game.ClientState.Objects.Types.Character character, TreeBuildCache cache,
|
||||
bool withUIData = true, bool redactExternalPaths = true)
|
||||
|
|
@ -69,8 +83,10 @@ public class ResourceTreeFactory
|
|||
if (!collectionResolveData.Valid)
|
||||
return null;
|
||||
|
||||
var (name, related) = GetCharacterName(character, cache);
|
||||
var tree = new ResourceTree(name, (nint)gameObjStruct, (nint)drawObjStruct, related, collectionResolveData.ModCollection.Name);
|
||||
var localPlayerRelated = cache.IsLocalPlayerRelated(character);
|
||||
var (name, related) = GetCharacterName(character, cache);
|
||||
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(_config, _identifier.AwaitedService, cache, collectionResolveData.ModCollection,
|
||||
((Character*)gameObjStruct)->CharacterData.ModelCharaId, withUIData, redactExternalPaths);
|
||||
tree.LoadResources(globalContext);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.Interop.Services;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
namespace Penumbra.Interop.ResourceTree;
|
||||
|
|
@ -8,18 +10,38 @@ namespace Penumbra.Interop.ResourceTree;
|
|||
internal class TreeBuildCache
|
||||
{
|
||||
private readonly IDataManager _dataManager;
|
||||
private readonly ActorService _actors;
|
||||
private readonly Dictionary<FullPath, ShpkFile?> _shaderPackages = new();
|
||||
private readonly uint _localPlayerId;
|
||||
public readonly List<Character> Characters;
|
||||
public readonly Dictionary<uint, Character> CharactersById;
|
||||
|
||||
public TreeBuildCache(IObjectTable objects, IDataManager dataManager)
|
||||
public TreeBuildCache(IObjectTable objects, IDataManager dataManager, ActorService actors)
|
||||
{
|
||||
_dataManager = dataManager;
|
||||
Characters = objects.Where(c => c is Character ch && ch.IsValid()).Cast<Character>().ToList();
|
||||
_dataManager = dataManager;
|
||||
_actors = actors;
|
||||
Characters = objects.OfType<Character>().Where(ch => ch.IsValid()).ToList();
|
||||
CharactersById = Characters
|
||||
.Where(c => c.ObjectId != GameObject.InvalidGameObjectId)
|
||||
.GroupBy(c => c.ObjectId)
|
||||
.ToDictionary(c => c.Key, c => c.First());
|
||||
_localPlayerId = Characters.Count > 0 && Characters[0].ObjectIndex == 0 ? Characters[0].ObjectId : GameObject.InvalidGameObjectId;
|
||||
}
|
||||
|
||||
public unsafe bool IsLocalPlayerRelated(Character character)
|
||||
{
|
||||
if (_localPlayerId == GameObject.InvalidGameObjectId)
|
||||
return false;
|
||||
|
||||
// Index 0 is the local player, index 1 is the mount/minion/accessory.
|
||||
if (character.ObjectIndex < 2 || character.ObjectIndex == RedrawService.GPosePlayerIdx)
|
||||
return true;
|
||||
|
||||
if (!_actors.AwaitedService.FromObject(character, out var owner, true, false, false).IsValid)
|
||||
return false;
|
||||
|
||||
// Check for SMN/SCH pet, chocobo and other owned NPCs.
|
||||
return owner != null && owner->ObjectID == _localPlayerId;
|
||||
}
|
||||
|
||||
/// <summary> Try to read a shpk file from the given path and cache it on success. </summary>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.ResourceLoading;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.String;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Files;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.RegularExpressions;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Files;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Meta;
|
||||
using static Penumbra.Mods.ItemSwap.ItemSwap;
|
||||
using Penumbra.Services;
|
||||
|
|
|
|||
|
|
@ -90,6 +90,10 @@
|
|||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Enums\" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="GetGitHash" BeforeTargets="GetAssemblyVersion" Returns="InformationalVersion">
|
||||
<Exec Command="git rev-parse --short HEAD" ConsoleToMSBuild="true" StandardOutputImportance="low">
|
||||
<Output TaskParameter="ConsoleOutput" PropertyName="GitCommitHash" />
|
||||
|
|
|
|||
|
|
@ -40,8 +40,6 @@ public class ResourceTreeViewer
|
|||
if (!child)
|
||||
return;
|
||||
|
||||
var textColorNonPlayer = ImGui.GetColorU32(ImGuiCol.Text);
|
||||
var textColorPlayer = (textColorNonPlayer & 0xFF000000u) | ((textColorNonPlayer & 0x00FEFEFE) >> 1) | 0x8000u; // Half green
|
||||
if (!_task.IsCompleted)
|
||||
{
|
||||
ImGui.NewLine();
|
||||
|
|
@ -55,17 +53,30 @@ public class ResourceTreeViewer
|
|||
}
|
||||
else if (_task.IsCompletedSuccessfully)
|
||||
{
|
||||
var debugMode = _config.DebugMode;
|
||||
foreach (var (tree, index) in _task.Result.WithIndex())
|
||||
{
|
||||
using (var c = ImRaii.PushColor(ImGuiCol.Text, tree.PlayerRelated ? textColorPlayer : textColorNonPlayer))
|
||||
var headerColorId =
|
||||
tree.LocalPlayerRelated ? ColorId.ResTreeLocalPlayer :
|
||||
tree.PlayerRelated ? ColorId.ResTreePlayer :
|
||||
tree.Networked ? ColorId.ResTreeNetworked :
|
||||
ColorId.ResTreeNonNetworked;
|
||||
using (var c = ImRaii.PushColor(ImGuiCol.Text, headerColorId.Value()))
|
||||
{
|
||||
if (!ImGui.CollapsingHeader($"{tree.Name}##{index}", index == 0 ? ImGuiTreeNodeFlags.DefaultOpen : 0))
|
||||
var isOpen = ImGui.CollapsingHeader($"{tree.Name}##{index}", index == 0 ? ImGuiTreeNodeFlags.DefaultOpen : 0);
|
||||
if (debugMode)
|
||||
{
|
||||
using var _ = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
ImGuiUtil.HoverTooltip(
|
||||
$"Object Index: {tree.GameObjectIndex}\nObject Address: 0x{tree.GameObjectAddress:X16}\nDraw Object Address: 0x{tree.DrawObjectAddress:X16}");
|
||||
}
|
||||
if (!isOpen)
|
||||
continue;
|
||||
}
|
||||
|
||||
using var id = ImRaii.PushId(index);
|
||||
|
||||
ImGui.Text($"Collection: {tree.CollectionName}");
|
||||
ImGui.TextUnformatted($"Collection: {tree.CollectionName}");
|
||||
|
||||
using var table = ImRaii.Table("##ResourceTree", _actionCapacity > 0 ? 4 : 3,
|
||||
ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
|
||||
|
|
@ -90,7 +101,9 @@ public class ResourceTreeViewer
|
|||
{
|
||||
try
|
||||
{
|
||||
return _treeFactory.FromObjectTable();
|
||||
return _treeFactory.FromObjectTable()
|
||||
.Select(entry => entry.ResourceTree)
|
||||
.ToArray();
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ using Penumbra.GameData.Enums;
|
|||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.UI.Classes;
|
||||
using ApiChangedItemIcon = Penumbra.Api.Enums.ChangedItemIcon;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
|
||||
|
|
@ -311,6 +312,29 @@ public class ChangedItemDrawer : IDisposable
|
|||
_ => "Other",
|
||||
};
|
||||
|
||||
internal static ApiChangedItemIcon ToApiIcon(ChangedItemIcon icon)
|
||||
=> icon switch
|
||||
{
|
||||
ChangedItemIcon.Head => ApiChangedItemIcon.Head,
|
||||
ChangedItemIcon.Body => ApiChangedItemIcon.Body,
|
||||
ChangedItemIcon.Hands => ApiChangedItemIcon.Hands,
|
||||
ChangedItemIcon.Legs => ApiChangedItemIcon.Legs,
|
||||
ChangedItemIcon.Feet => ApiChangedItemIcon.Feet,
|
||||
ChangedItemIcon.Ears => ApiChangedItemIcon.Ears,
|
||||
ChangedItemIcon.Neck => ApiChangedItemIcon.Neck,
|
||||
ChangedItemIcon.Wrists => ApiChangedItemIcon.Wrists,
|
||||
ChangedItemIcon.Finger => ApiChangedItemIcon.Finger,
|
||||
ChangedItemIcon.Monster => ApiChangedItemIcon.Monster,
|
||||
ChangedItemIcon.Demihuman => ApiChangedItemIcon.Demihuman,
|
||||
ChangedItemIcon.Customization => ApiChangedItemIcon.Customization,
|
||||
ChangedItemIcon.Action => ApiChangedItemIcon.Action,
|
||||
ChangedItemIcon.Emote => ApiChangedItemIcon.Emote,
|
||||
ChangedItemIcon.Mainhand => ApiChangedItemIcon.Mainhand,
|
||||
ChangedItemIcon.Offhand => ApiChangedItemIcon.Offhand,
|
||||
ChangedItemIcon.Unknown => ApiChangedItemIcon.Unknown,
|
||||
_ => ApiChangedItemIcon.None,
|
||||
};
|
||||
|
||||
/// <summary> Apply Changed Item Counters to the Name if necessary. </summary>
|
||||
private static string ChangedItemName(string name, object? data)
|
||||
=> data is int counter ? $"{counter} Files Manipulating {name}s" : name;
|
||||
|
|
|
|||
|
|
@ -24,10 +24,16 @@ public enum ColorId
|
|||
NoAssignment,
|
||||
SelectorPriority,
|
||||
InGameHighlight,
|
||||
ResTreeLocalPlayer,
|
||||
ResTreePlayer,
|
||||
ResTreeNetworked,
|
||||
ResTreeNonNetworked,
|
||||
}
|
||||
|
||||
public static class Colors
|
||||
{
|
||||
// These are written as 0xAABBGGRR.
|
||||
|
||||
public const uint PressEnterWarningBg = 0xFF202080;
|
||||
public const uint RegexWarningBorder = 0xFF0000B0;
|
||||
public const uint MetaInfoText = 0xAAFFFFFF;
|
||||
|
|
@ -64,6 +70,10 @@ public static class Colors
|
|||
ColorId.NoAssignment => ( 0x00000000, "Unassigned Collection Assignment", "A collection assignment that is not configured to any collection and thus just has no specific treatment."),
|
||||
ColorId.SelectorPriority => ( 0xFF808080, "Mod Selector Priority", "The priority displayed for non-zero priority mods in the mod selector."),
|
||||
ColorId.InGameHighlight => ( 0xFFEBCF89, "In-Game Highlight", "An in-game element that has been highlighted for ease of editing."),
|
||||
ColorId.ResTreeLocalPlayer => ( 0xFFFFE0A0, "On-Screen: You", "You and what you own (mount, minion, accessory, pets and so on), in the On-Screen tab." ),
|
||||
ColorId.ResTreePlayer => ( 0xFFC0FFC0, "On-Screen: Other Players", "Other players and what they own, in the On-Screen tab." ),
|
||||
ColorId.ResTreeNetworked => ( 0xFFFFFFFF, "On-Screen: Non-Players (Networked)", "Non-player entities handled by the game server, in the On-Screen tab." ),
|
||||
ColorId.ResTreeNonNetworked => ( 0xFFC0C0FF, "On-Screen: Non-Players (Local)", "Non-player entities handled locally, in the On-Screen tab." ),
|
||||
_ => throw new ArgumentOutOfRangeException( nameof( color ), color, null ),
|
||||
// @formatter:on
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using OtterGui.Classes;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Enums;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.String;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
|||
using ImGuiNET;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Widgets;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.ResourceLoading;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Services;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using OtterGui;
|
|||
using OtterGui.Classes;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Table;
|
||||
using Penumbra.Enums;
|
||||
using Penumbra.String;
|
||||
|
||||
namespace Penumbra.UI.ResourceWatcher;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue