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:
Exter-N 2023-09-17 22:25:31 +02:00 committed by Ottermandias
parent 2b4a01df06
commit d7205344eb
32 changed files with 826 additions and 80 deletions

View file

@ -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";
}
}
}