using System.Buffers.Binary; using System.Numerics; using System.Text; using Dalamud.Bindings.ImGui; using Dalamud.Data; using Dalamud.Game.Inventory; using Dalamud.Interface.Textures; using Dalamud.Interface.Textures.Internal; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.Game; using Lumina.Excel.Sheets; namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// /// Widget for displaying inventory data. /// internal class InventoryWidget : IDataWindowWidget { private DataManager dataManager; private TextureManager textureManager; private GameInventoryType? selectedInventoryType = GameInventoryType.Inventory1; /// public string[]? CommandShortcuts { get; init; } = ["inv", "inventory"]; /// public string DisplayName { get; init; } = "Inventory"; /// public bool Ready { get; set; } /// public void Load() { this.Ready = true; } /// public void Draw() { this.dataManager ??= Service.Get(); this.textureManager ??= Service.Get(); this.DrawInventoryTypeList(); if (this.selectedInventoryType == null) return; ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); this.DrawInventoryType(this.selectedInventoryType.Value); } private static string StripSoftHypen(string input) { return input.Replace("\u00AD", string.Empty); } private unsafe void DrawInventoryTypeList() { using var table = ImRaii.Table("InventoryTypeTable"u8, 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders | ImGuiTableFlags.ScrollY | ImGuiTableFlags.NoSavedSettings, new Vector2(300, -1)); if (!table) return; ImGui.TableSetupColumn("Type"u8); ImGui.TableSetupColumn("Size"u8, ImGuiTableColumnFlags.WidthFixed, 40); ImGui.TableSetupScrollFreeze(2, 1); ImGui.TableHeadersRow(); foreach (var inventoryType in Enum.GetValues()) { var items = GameInventoryItem.GetReadOnlySpanOfInventory(inventoryType); using var itemDisabled = ImRaii.Disabled(items.IsEmpty); ImGui.TableNextRow(); ImGui.TableNextColumn(); // Type if (ImGui.Selectable(inventoryType.ToString(), this.selectedInventoryType == inventoryType, ImGuiSelectableFlags.SpanAllColumns)) { this.selectedInventoryType = inventoryType; } using (var contextMenu = ImRaii.ContextPopupItem($"##InventoryContext{inventoryType}")) { if (contextMenu) { if (ImGui.MenuItem("Copy Name"u8)) { ImGui.SetClipboardText(inventoryType.ToString()); } if (ImGui.MenuItem("Copy Address"u8)) { var container = InventoryManager.Instance()->GetInventoryContainer((InventoryType)inventoryType); ImGui.SetClipboardText($"0x{(nint)container:X}"); } } } ImGui.TableNextColumn(); // Size ImGui.Text(items.Length.ToString()); } } private unsafe void DrawInventoryType(GameInventoryType inventoryType) { var items = GameInventoryItem.GetReadOnlySpanOfInventory(inventoryType); if (items.IsEmpty) { ImGui.Text($"{inventoryType} is empty."); return; } using var itemTable = ImRaii.Table("InventoryItemTable"u8, 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders | ImGuiTableFlags.ScrollY | ImGuiTableFlags.NoSavedSettings); if (!itemTable) return; ImGui.TableSetupColumn("Slot"u8, ImGuiTableColumnFlags.WidthFixed, 40); ImGui.TableSetupColumn("ItemId"u8, ImGuiTableColumnFlags.WidthFixed, 70); ImGui.TableSetupColumn("Quantity"u8, ImGuiTableColumnFlags.WidthFixed, 70); ImGui.TableSetupColumn("Item"u8); ImGui.TableSetupScrollFreeze(0, 1); ImGui.TableHeadersRow(); for (var slotIndex = 0; slotIndex < items.Length; slotIndex++) { var item = items[slotIndex]; using var disableditem = ImRaii.Disabled(item.ItemId == 0); ImGui.TableNextRow(); ImGui.TableNextColumn(); // Slot ImGui.Text(slotIndex.ToString()); ImGui.TableNextColumn(); // ItemId ImGuiHelpers.ClickToCopyText(item.ItemId.ToString()); ImGui.TableNextColumn(); // Quantity ImGuiHelpers.ClickToCopyText(item.Quantity.ToString()); ImGui.TableNextColumn(); // Item if (item.ItemId != 0 && item.Quantity != 0) { var itemName = ItemUtil.GetItemName(item.ItemId).ExtractText(); var iconId = this.GetItemIconId(item.ItemId); if (this.textureManager.Shared.TryGetFromGameIcon(new GameIconLookup(iconId, item.IsHq), out var tex) && tex.TryGetWrap(out var texture, out _)) { ImGui.Image(texture.Handle, new Vector2(ImGui.GetTextLineHeight())); if (ImGui.IsItemHovered()) { ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); ImGui.BeginTooltip(); ImGui.Text("Click to copy IconId"u8); ImGui.Text($"ID: {iconId} – Size: {texture.Width}x{texture.Height}"); ImGui.Image(texture.Handle, new(texture.Width, texture.Height)); ImGui.EndTooltip(); } if (ImGui.IsItemClicked()) ImGui.SetClipboardText(iconId.ToString()); } ImGui.SameLine(); using var itemNameColor = ImRaii.PushColor(ImGuiCol.Text, this.GetItemRarityColor(item.ItemId)); using var node = ImRaii.TreeNode($"{itemName}###{inventoryType}_{slotIndex}", ImGuiTreeNodeFlags.SpanAvailWidth); itemNameColor.Dispose(); using (var contextMenu = ImRaii.ContextPopupItem($"{inventoryType}_{slotIndex}_ContextMenu")) { if (contextMenu) { if (ImGui.MenuItem("Copy Name"u8)) { ImGui.SetClipboardText(itemName); } } } if (!node) continue; using var itemInfoTable = ImRaii.Table($"{inventoryType}_{slotIndex}_Table", 2, ImGuiTableFlags.BordersInner | ImGuiTableFlags.NoSavedSettings); if (!itemInfoTable) continue; ImGui.TableSetupColumn("Name"u8, ImGuiTableColumnFlags.WidthFixed, 150); ImGui.TableSetupColumn("Value"u8); // ImGui.TableHeadersRow(); static void AddKeyValueRow(string fieldName, string value) { ImGui.TableNextRow(); ImGui.TableNextColumn(); ImGui.Text(fieldName); ImGui.TableNextColumn(); ImGuiHelpers.ClickToCopyText(value); } static void AddValueValueRow(string value1, string value2) { ImGui.TableNextRow(); ImGui.TableNextColumn(); ImGuiHelpers.ClickToCopyText(value1); ImGui.TableNextColumn(); ImGuiHelpers.ClickToCopyText(value2); } AddKeyValueRow("ItemId", item.ItemId.ToString()); AddKeyValueRow("Quantity", item.Quantity.ToString()); AddKeyValueRow("GlamourId", item.GlamourId.ToString()); if (!ItemUtil.IsEventItem(item.ItemId)) { AddKeyValueRow(item.IsCollectable ? "Collectability" : "Spiritbond", item.SpiritbondOrCollectability.ToString()); if (item.CrafterContentId != 0) AddKeyValueRow("CrafterContentId", item.CrafterContentId.ToString()); } var flagsBuilder = new StringBuilder(); if (item.IsHq) { flagsBuilder.Append("IsHq"); } if (item.IsCompanyCrestApplied) { if (flagsBuilder.Length != 0) flagsBuilder.Append(", "); flagsBuilder.Append("IsCompanyCrestApplied"); } if (item.IsRelic) { if (flagsBuilder.Length != 0) flagsBuilder.Append(", "); flagsBuilder.Append("IsRelic"); } if (item.IsCollectable) { if (flagsBuilder.Length != 0) flagsBuilder.Append(", "); flagsBuilder.Append("IsCollectable"); } if (flagsBuilder.Length == 0) flagsBuilder.Append("None"); AddKeyValueRow("Flags", flagsBuilder.ToString()); if (ItemUtil.IsNormalItem(item.ItemId) && this.dataManager.Excel.GetSheet().TryGetRow(item.ItemId, out var itemRow)) { if (itemRow.DyeCount > 0 && item.Stains.Length > 0) { ImGui.TableNextRow(); ImGui.TableNextColumn(); ImGui.Text("Stains"u8); ImGui.TableNextColumn(); using var stainTable = ImRaii.Table($"{inventoryType}_{slotIndex}_StainTable", 2, ImGuiTableFlags.BordersInner | ImGuiTableFlags.NoSavedSettings); if (!stainTable) continue; ImGui.TableSetupColumn("Stain Id"u8, ImGuiTableColumnFlags.WidthFixed, 80); ImGui.TableSetupColumn("Name"u8); ImGui.TableHeadersRow(); for (var i = 0; i < itemRow.DyeCount; i++) { var stainId = item.Stains[i]; AddValueValueRow(stainId.ToString(), this.GetStainName(stainId)); } } if (itemRow.MateriaSlotCount > 0 && item.Materia.Length > 0) { ImGui.TableNextRow(); ImGui.TableNextColumn(); ImGui.Text("Materia"u8); ImGui.TableNextColumn(); using var materiaTable = ImRaii.Table($"{inventoryType}_{slotIndex}_MateriaTable", 2, ImGuiTableFlags.BordersInner | ImGuiTableFlags.NoSavedSettings); if (!materiaTable) continue; ImGui.TableSetupColumn("Materia Id"u8, ImGuiTableColumnFlags.WidthFixed, 80); ImGui.TableSetupColumn("MateriaGrade Id"u8); ImGui.TableHeadersRow(); for (var i = 0; i < Math.Min(itemRow.MateriaSlotCount, item.Materia.Length); i++) { AddValueValueRow(item.Materia[i].ToString(), item.MateriaGrade[i].ToString()); } } } } } } private string GetStainName(uint stainId) { return this.dataManager.Excel.GetSheet().TryGetRow(stainId, out var stainRow) ? StripSoftHypen(stainRow.Name.ExtractText()) : $"Stain#{stainId}"; } private uint GetItemRarityColor(uint itemId, bool isEdgeColor = false) { if (ItemUtil.IsEventItem(itemId)) return isEdgeColor ? 0xFF000000 : 0xFFFFFFFF; if (!this.dataManager.Excel.GetSheet().TryGetRow(ItemUtil.GetBaseId(itemId).ItemId, out var item)) return isEdgeColor ? 0xFF000000 : 0xFFFFFFFF; var rowId = ItemUtil.GetItemRarityColorType(item.RowId, isEdgeColor); return this.dataManager.Excel.GetSheet().TryGetRow(rowId, out var color) ? BinaryPrimitives.ReverseEndianness(color.Dark) | 0xFF000000 : 0xFFFFFFFF; } private uint GetItemIconId(uint itemId) { // EventItem if (ItemUtil.IsEventItem(itemId)) return this.dataManager.Excel.GetSheet().TryGetRow(itemId, out var eventItem) ? eventItem.Icon : 0u; // HighQuality if (ItemUtil.IsHighQuality(itemId)) itemId -= 1_000_000; // Collectible if (ItemUtil.IsCollectible(itemId)) itemId -= 500_000; return this.dataManager.Excel.GetSheet().TryGetRow(itemId, out var item) ? item.Icon : 0u; } }