diff --git a/Penumbra/Interop/ResourceTree/ResourceNode.cs b/Penumbra/Interop/ResourceTree/ResourceNode.cs index 6c3e1ebe..088527ca 100644 --- a/Penumbra/Interop/ResourceTree/ResourceNode.cs +++ b/Penumbra/Interop/ResourceTree/ResourceNode.cs @@ -1,4 +1,5 @@ using Penumbra.Api.Enums; +using Penumbra.Mods; using Penumbra.String; using Penumbra.String.Classes; using Penumbra.UI; @@ -16,6 +17,7 @@ public class ResourceNode : ICloneable public Utf8GamePath[] PossibleGamePaths; public FullPath FullPath; public string? ModName; + public readonly WeakReference Mod = new(null!); public string? ModRelativePath; public CiByteString AdditionalData; public readonly ulong Length; @@ -60,6 +62,7 @@ public class ResourceNode : ICloneable PossibleGamePaths = other.PossibleGamePaths; FullPath = other.FullPath; ModName = other.ModName; + Mod = other.Mod; ModRelativePath = other.ModRelativePath; AdditionalData = other.AdditionalData; Length = other.Length; diff --git a/Penumbra/Interop/ResourceTree/ResourceTreeFactory.cs b/Penumbra/Interop/ResourceTree/ResourceTreeFactory.cs index 9738148f..7e378f41 100644 --- a/Penumbra/Interop/ResourceTree/ResourceTreeFactory.cs +++ b/Penumbra/Interop/ResourceTree/ResourceTreeFactory.cs @@ -135,6 +135,7 @@ public class ResourceTreeFactory( if (node.FullPath.IsRooted && modManager.TryIdentifyPath(node.FullPath.FullName, out var mod, out var relativePath)) { node.ModName = mod.Name; + node.Mod.SetTarget(mod); node.ModRelativePath = relativePath; } } diff --git a/Penumbra/UI/AdvancedWindow/ResourceTreeViewer.cs b/Penumbra/UI/AdvancedWindow/ResourceTreeViewer.cs index 3aff2ac9..7bad64f9 100644 --- a/Penumbra/UI/AdvancedWindow/ResourceTreeViewer.cs +++ b/Penumbra/UI/AdvancedWindow/ResourceTreeViewer.cs @@ -3,55 +3,40 @@ using Dalamud.Interface.Utility; using ImGuiNET; using OtterGui.Raii; using OtterGui; +using OtterGui.Text; +using Penumbra.Api.Enums; using Penumbra.Interop.ResourceTree; +using Penumbra.Services; using Penumbra.UI.Classes; using Penumbra.String; namespace Penumbra.UI.AdvancedWindow; -public class ResourceTreeViewer +public class ResourceTreeViewer( + Configuration config, + ResourceTreeFactory treeFactory, + ChangedItemDrawer changedItemDrawer, + IncognitoService incognito, + int actionCapacity, + Action onRefresh, + Action drawActions, + CommunicatorService communicator) { private const ResourceTreeFactory.Flags ResourceTreeFactoryFlags = ResourceTreeFactory.Flags.RedactExternalPaths | ResourceTreeFactory.Flags.WithUiData | ResourceTreeFactory.Flags.WithOwnership; - private readonly Configuration _config; - private readonly ResourceTreeFactory _treeFactory; - private readonly ChangedItemDrawer _changedItemDrawer; - private readonly IncognitoService _incognito; - private readonly int _actionCapacity; - private readonly Action _onRefresh; - private readonly Action _drawActions; - private readonly HashSet _unfolded; + private readonly CommunicatorService _communicator = communicator; + private readonly HashSet _unfolded = []; - private readonly Dictionary _filterCache; + private readonly Dictionary _filterCache = []; - private TreeCategory _categoryFilter; - private ChangedItemIconFlag _typeFilter; - private string _nameFilter; - private string _nodeFilter; + private TreeCategory _categoryFilter = AllCategories; + private ChangedItemIconFlag _typeFilter = ChangedItemFlagExtensions.AllFlags; + private string _nameFilter = string.Empty; + private string _nodeFilter = string.Empty; private Task? _task; - public ResourceTreeViewer(Configuration config, ResourceTreeFactory treeFactory, ChangedItemDrawer changedItemDrawer, - IncognitoService incognito, int actionCapacity, Action onRefresh, Action drawActions) - { - _config = config; - _treeFactory = treeFactory; - _changedItemDrawer = changedItemDrawer; - _incognito = incognito; - _actionCapacity = actionCapacity; - _onRefresh = onRefresh; - _drawActions = drawActions; - _unfolded = []; - - _filterCache = []; - - _categoryFilter = AllCategories; - _typeFilter = ChangedItemFlagExtensions.AllFlags; - _nameFilter = string.Empty; - _nodeFilter = string.Empty; - } - public void Draw() { DrawControls(); @@ -74,7 +59,7 @@ public class ResourceTreeViewer } else if (_task.IsCompletedSuccessfully) { - var debugMode = _config.DebugMode; + var debugMode = config.DebugMode; foreach (var (tree, index) in _task.Result.WithIndex()) { var category = Classify(tree); @@ -83,7 +68,7 @@ public class ResourceTreeViewer using (var c = ImRaii.PushColor(ImGuiCol.Text, CategoryColor(category).Value())) { - var isOpen = ImGui.CollapsingHeader($"{(_incognito.IncognitoMode ? tree.AnonymizedName : tree.Name)}###{index}", + var isOpen = ImGui.CollapsingHeader($"{(incognito.IncognitoMode ? tree.AnonymizedName : tree.Name)}###{index}", index == 0 ? ImGuiTreeNodeFlags.DefaultOpen : 0); if (debugMode) { @@ -98,9 +83,9 @@ public class ResourceTreeViewer using var id = ImRaii.PushId(index); - ImGui.TextUnformatted($"Collection: {(_incognito.IncognitoMode ? tree.AnonymizedCollectionName : tree.CollectionName)}"); + ImGui.TextUnformatted($"Collection: {(incognito.IncognitoMode ? tree.AnonymizedCollectionName : tree.CollectionName)}"); - using var table = ImRaii.Table("##ResourceTree", _actionCapacity > 0 ? 4 : 3, + using var table = ImRaii.Table("##ResourceTree", actionCapacity > 0 ? 4 : 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); if (!table) continue; @@ -108,9 +93,9 @@ public class ResourceTreeViewer ImGui.TableSetupColumn(string.Empty, ImGuiTableColumnFlags.WidthStretch, 0.2f); ImGui.TableSetupColumn("Game Path", ImGuiTableColumnFlags.WidthStretch, 0.3f); ImGui.TableSetupColumn("Actual Path", ImGuiTableColumnFlags.WidthStretch, 0.5f); - if (_actionCapacity > 0) + if (actionCapacity > 0) ImGui.TableSetupColumn(string.Empty, ImGuiTableColumnFlags.WidthFixed, - (_actionCapacity - 1) * 3 * ImGuiHelpers.GlobalScale + _actionCapacity * ImGui.GetFrameHeight()); + (actionCapacity - 1) * 3 * ImGuiHelpers.GlobalScale + actionCapacity * ImGui.GetFrameHeight()); ImGui.TableHeadersRow(); DrawNodes(tree.Nodes, 0, unchecked(tree.DrawObjectAddress * 31), 0); @@ -150,7 +135,7 @@ public class ResourceTreeViewer ImGui.SetCursorPosY(ImGui.GetCursorPosY() - yOffset); using (ImRaii.Child("##typeFilter", new Vector2(ImGui.GetContentRegionAvail().X, ChangedItemDrawer.TypeFilterIconSize.Y))) { - filterChanged |= _changedItemDrawer.DrawTypeFilter(ref _typeFilter); + filterChanged |= changedItemDrawer.DrawTypeFilter(ref _typeFilter); } var fieldWidth = (ImGui.GetContentRegionAvail().X - checkSpacing * 2.0f - ImGui.GetFrameHeightWithSpacing()) / 2.0f; @@ -160,7 +145,7 @@ public class ResourceTreeViewer ImGui.SetNextItemWidth(fieldWidth); filterChanged |= ImGui.InputTextWithHint("##NodeFilter", "Filter by Item/Part Name or Path...", ref _nodeFilter, 128); ImGui.SameLine(0, checkSpacing); - _incognito.DrawToggle(ImGui.GetFrameHeightWithSpacing()); + incognito.DrawToggle(ImGui.GetFrameHeightWithSpacing()); if (filterChanged) _filterCache.Clear(); @@ -171,7 +156,7 @@ public class ResourceTreeViewer { try { - return _treeFactory.FromObjectTable(ResourceTreeFactoryFlags) + return treeFactory.FromObjectTable(ResourceTreeFactoryFlags) .Select(entry => entry.ResourceTree) .ToArray(); } @@ -179,16 +164,16 @@ public class ResourceTreeViewer { _filterCache.Clear(); _unfolded.Clear(); - _onRefresh(); + onRefresh(); } }); private void DrawNodes(IEnumerable resourceNodes, int level, nint pathHash, ChangedItemIconFlag parentFilterIconFlag) { - var debugMode = _config.DebugMode; + var debugMode = config.DebugMode; var frameHeight = ImGui.GetFrameHeight(); - var cellHeight = _actionCapacity > 0 ? frameHeight : 0.0f; + var cellHeight = actionCapacity > 0 ? frameHeight : 0.0f; foreach (var (resourceNode, index) in resourceNodes.WithIndex()) { @@ -231,7 +216,7 @@ public class ResourceTreeViewer ImGui.SameLine(0f, ImGui.GetStyle().ItemInnerSpacing.X); } - _changedItemDrawer.DrawCategoryIcon(resourceNode.IconFlag); + changedItemDrawer.DrawCategoryIcon(resourceNode.IconFlag); ImGui.SameLine(0f, ImGui.GetStyle().ItemInnerSpacing.X); ImGui.TableHeader(resourceNode.Name); if (ImGui.IsItemClicked() && unfoldable) @@ -270,14 +255,33 @@ public class ResourceTreeViewer ImGui.TableNextColumn(); if (resourceNode.FullPath.FullName.Length > 0) { - var uiFullPathStr = resourceNode.ModName != null && resourceNode.ModRelativePath != null - ? $"[{resourceNode.ModName}] {resourceNode.ModRelativePath}" - : resourceNode.FullPath.ToPath(); - ImGui.Selectable(uiFullPathStr, false, 0, new Vector2(ImGui.GetContentRegionAvail().X, cellHeight)); + var hasMod = resourceNode.Mod.TryGetTarget(out var mod); + if (resourceNode is { ModName: not null, ModRelativePath: not null }) + { + var modName = $"[{(hasMod ? mod!.Name : resourceNode.ModName)}]"; + var textPos = ImGui.GetCursorPosX() + ImUtf8.CalcTextSize(modName).X + ImGui.GetStyle().ItemInnerSpacing.X; + using var group = ImUtf8.Group(); + using (var color = ImRaii.PushColor(ImGuiCol.Text, (hasMod ? ColorId.NewMod : ColorId.DisabledMod).Value())) + { + ImUtf8.Selectable(modName, false, ImGuiSelectableFlags.AllowItemOverlap, new Vector2(ImGui.GetContentRegionAvail().X, cellHeight)); + } + + ImGui.SameLine(); + ImGui.SetCursorPosX(textPos); + ImUtf8.Text(resourceNode.ModRelativePath); + } + else + { + ImGui.Selectable(resourceNode.FullPath.ToPath(), false, ImGuiSelectableFlags.AllowItemOverlap, new Vector2(ImGui.GetContentRegionAvail().X, cellHeight)); + } + if (ImGui.IsItemClicked()) ImGui.SetClipboardText(resourceNode.FullPath.ToPath()); + if (hasMod && ImGui.IsItemClicked(ImGuiMouseButton.Right) && ImGui.GetIO().KeyCtrl) + _communicator.SelectTab.Invoke(TabType.Mods, mod); + ImGuiUtil.HoverTooltip( - $"{resourceNode.FullPath.ToPath()}\n\nClick to copy to clipboard.{GetAdditionalDataSuffix(resourceNode.AdditionalData)}"); + $"{resourceNode.FullPath.ToPath()}\n\nClick to copy to clipboard.{(hasMod ? "\nControl + Right-Click to jump to mod." : string.Empty)}{GetAdditionalDataSuffix(resourceNode.AdditionalData)}"); } else { @@ -289,12 +293,12 @@ public class ResourceTreeViewer mutedColor.Dispose(); - if (_actionCapacity > 0) + if (actionCapacity > 0) { ImGui.TableNextColumn(); using var spacing = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing with { X = 3 * ImGuiHelpers.GlobalScale }); - _drawActions(resourceNode, new Vector2(frameHeight)); + drawActions(resourceNode, new Vector2(frameHeight)); } if (unfolded) diff --git a/Penumbra/UI/AdvancedWindow/ResourceTreeViewerFactory.cs b/Penumbra/UI/AdvancedWindow/ResourceTreeViewerFactory.cs index ea64c0bf..10a4aea2 100644 --- a/Penumbra/UI/AdvancedWindow/ResourceTreeViewerFactory.cs +++ b/Penumbra/UI/AdvancedWindow/ResourceTreeViewerFactory.cs @@ -1,5 +1,6 @@ using OtterGui.Services; using Penumbra.Interop.ResourceTree; +using Penumbra.Services; namespace Penumbra.UI.AdvancedWindow; @@ -7,8 +8,9 @@ public class ResourceTreeViewerFactory( Configuration config, ResourceTreeFactory treeFactory, ChangedItemDrawer changedItemDrawer, - IncognitoService incognito) : IService + IncognitoService incognito, + CommunicatorService communicator) : IService { public ResourceTreeViewer Create(int actionCapacity, Action onRefresh, Action drawActions) - => new(config, treeFactory, changedItemDrawer, incognito, actionCapacity, onRefresh, drawActions); + => new(config, treeFactory, changedItemDrawer, incognito, actionCapacity, onRefresh, drawActions, communicator); } diff --git a/Penumbra/UI/Tabs/Debug/DebugTab.cs b/Penumbra/UI/Tabs/Debug/DebugTab.cs index fc735d04..46e427ed 100644 --- a/Penumbra/UI/Tabs/Debug/DebugTab.cs +++ b/Penumbra/UI/Tabs/Debug/DebugTab.cs @@ -461,7 +461,7 @@ public class DebugTab : Window, ITab, IUiService if (!ImGui.CollapsingHeader("Actors")) return; - using var table = Table("##actors", 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, + using var table = Table("##actors", 8, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, -Vector2.UnitX); if (!table) return; @@ -485,6 +485,9 @@ public class DebugTab : Window, ITab, IUiService ? $"{identifier.DataId} | {obj.AsObject->BaseId}" : identifier.DataId.ToString(); ImGuiUtil.DrawTableColumn(id); + ImGuiUtil.DrawTableColumn(obj.Address != nint.Zero ? $"0x{(*(nint*)obj.Address):X}" : "NULL"); + ImGuiUtil.DrawTableColumn(obj.Address != nint.Zero ? $"0x{obj.AsObject->EntityId:X}" : "NULL"); + ImGuiUtil.DrawTableColumn(obj.Address != nint.Zero ? obj.AsObject->IsCharacter() ? $"Character: {obj.AsCharacter->ObjectKind}" : "No Character" : "NULL"); } return; @@ -499,6 +502,9 @@ public class DebugTab : Window, ITab, IUiService ImGuiUtil.DrawTableColumn(string.Empty); ImGuiUtil.DrawTableColumn(_actors.ToString(id)); ImGuiUtil.DrawTableColumn(string.Empty); + ImGuiUtil.DrawTableColumn(string.Empty); + ImGuiUtil.DrawTableColumn(string.Empty); + ImGuiUtil.DrawTableColumn(string.Empty); } }