diff --git a/AetherBags/AddonLifecycles/InventoryLifecycles.cs b/AetherBags/AddonLifecycles/InventoryLifecycles.cs index 7c0abdc..04f3352 100644 --- a/AetherBags/AddonLifecycles/InventoryLifecycles.cs +++ b/AetherBags/AddonLifecycles/InventoryLifecycles.cs @@ -20,6 +20,17 @@ public class InventoryLifecycles : IDisposable Services.Logger.Verbose("InventoryLifecycles initialized"); } + /* + values[0] = OpenType + values[1] = OpenTitleId + values[2] = tab index + values[3] = InventoryAddonId | (OpenerAddonId << 16) + values[4] = focus + values[5] = title + values[6] = upper title + values[7] = can use Saddlebags (Agent InventoryBuddy IsActivatable) + */ + private unsafe void PreRefreshHandler(AddonEvent type, AddonArgs args) { if (args is not AddonRefreshArgs refreshArgs) diff --git a/AetherBags/Inventory/InventoryContextState.cs b/AetherBags/Inventory/InventoryContextState.cs new file mode 100644 index 0000000..f81a6ef --- /dev/null +++ b/AetherBags/Inventory/InventoryContextState.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Client.UI.Arrays; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; + +namespace AetherBags.Inventory; + +public static unsafe class InventoryContextState +{ + private static readonly HashSet<(int page, int slot)> EligibleSlots = new(); + private static readonly HashSet<(InventoryType container, int slot)> BlockedSlots = new(); + private static readonly Dictionary VisualLocationMap = new(); + private static uint _lastContextId; + + public static void RefreshMaps() + { + EligibleSlots. Clear(); + VisualLocationMap. Clear(); + + var sorter = ItemOrderModule.Instance()->InventorySorter; + if (sorter == null) return; + + var agentInventory = AgentInventory.Instance(); + bool hasContext = agentInventory != null && agentInventory->OpenTitleId != 0; + _lastContextId = hasContext ? agentInventory->OpenTitleId : 0; + + var invArray = hasContext ? InventoryNumberArray.Instance() : null; + + int itemsPerPage = sorter->ItemsPerPage; + + for (int displayIdx = 0; displayIdx < 140; displayIdx++) + { + var entry = sorter->Items[displayIdx]. Value; + if (entry == null) continue; + + int realPage = entry->Page; + int realSlot = entry->Slot; + + int visualPage = displayIdx / itemsPerPage; + int visualSlot = displayIdx % itemsPerPage; + int visualContainerId = 48 + visualPage; + + VisualLocationMap[new InventoryMappedLocation(realPage, realSlot)] = new InventoryMappedLocation(visualContainerId, visualSlot); + + if (hasContext && invArray != null) + { + var itemData = invArray->Items[displayIdx]; + if (itemData. IconId == 0) continue; + + bool eligible = itemData.ItemFlags.MirageFlag == 0; + if (eligible) + { + EligibleSlots.Add((realPage, realSlot)); + } + } + } + } + + public static void RefreshBlockedSlots() + { + BlockedSlots.Clear(); + + var inventoryManager = InventoryManager.Instance(); + if (inventoryManager == null) return; + + var blockedContainer = inventoryManager->GetInventoryContainer(InventoryType.BlockedItems); + if (blockedContainer == null) return; + + for (int i = 0; i < blockedContainer->Size; i++) + { + ref var item = ref blockedContainer->Items[i]; + if (item.ItemId == 0) continue; + + BlockedSlots.Add((item.Container, item.Slot)); + } + } + + public static bool IsEligible(int page, int slot) + => EligibleSlots.Contains((page, slot)); + + public static bool IsSlotBlocked(InventoryType container, int slot) + => BlockedSlots.Contains((container, slot)); + + public static bool HasActiveContext + => _lastContextId != 0; + + public static InventoryMappedLocation GetVisualLocation(int page, int slot) + => VisualLocationMap.TryGetValue(new InventoryMappedLocation(page, slot), out var result) ? result : new InventoryMappedLocation(48 + page, slot); +} \ No newline at end of file diff --git a/AetherBags/Inventory/InventoryLocation.cs b/AetherBags/Inventory/InventoryLocation.cs index 13b36d1..35ab722 100644 --- a/AetherBags/Inventory/InventoryLocation.cs +++ b/AetherBags/Inventory/InventoryLocation.cs @@ -8,5 +8,14 @@ public readonly record struct InventoryLocation(InventoryType Container, ushort public bool IsValid => Container != 0; + public override string ToString() => $"{Container}@{Slot}"; +} + +public readonly record struct InventoryMappedLocation(int Container, int Slot) +{ + public static readonly InventoryMappedLocation Invalid = new(0, 0); + + public bool IsValid => Container != 0; + public override string ToString() => $"{Container}@{Slot}"; } \ No newline at end of file diff --git a/AetherBags/Inventory/InventoryState.cs b/AetherBags/Inventory/InventoryState.cs index d4e279a..1fc3982 100644 --- a/AetherBags/Inventory/InventoryState.cs +++ b/AetherBags/Inventory/InventoryState.cs @@ -52,6 +52,8 @@ public static unsafe class InventoryState InventoryScanner.ScanBags(inventoryManager, stackMode, AggByKey); CategoryBucketManager.ResetBuckets(BucketsByKey); InventoryScanner.BuildItemInfos(AggByKey, ItemInfoByKey); + InventoryContextState.RefreshMaps(); + InventoryContextState.RefreshBlockedSlots(); if (userCategoriesEnabled && userCategories.Count > 0) { diff --git a/AetherBags/Inventory/ItemInfo.cs b/AetherBags/Inventory/ItemInfo.cs index 0047377..1893dde 100644 --- a/AetherBags/Inventory/ItemInfo.cs +++ b/AetherBags/Inventory/ItemInfo.cs @@ -59,6 +59,35 @@ public sealed class ItemInfo : IEquatable private string Description => _description ??= Row.Description.ToString(); + public InventoryMappedLocation VisualLocation => + IsMainInventory ? InventoryContextState.GetVisualLocation(InventoryPage, Item.Slot) + : new InventoryMappedLocation((int)Item.Container.AgentItemContainerId, Item. Slot); + + + public int InventoryPage => Item.Container switch + { + InventoryType.Inventory1 => 0, + InventoryType.Inventory2 => 1, + InventoryType.Inventory3 => 2, + InventoryType.Inventory4 => 3, + _ => -1 + }; + + public bool IsSlotBlocked => InventoryContextState.IsSlotBlocked(Item.Container, Item.Slot); + + public bool IsEligibleForContext + { + get + { + if (!InventoryContextState.HasActiveContext) + return true; + + return IsMainInventory && InventoryContextState.IsEligible(InventoryPage, Item.Slot); + } + } + + public bool IsMainInventory => InventoryPage >= 0; + public bool IsRegexMatch(string searchTerms) { if (string.IsNullOrEmpty(searchTerms)) diff --git a/AetherBags/Nodes/Inventory/InventoryCategoryNode.cs b/AetherBags/Nodes/Inventory/InventoryCategoryNode.cs index a30c507..3606974 100644 --- a/AetherBags/Nodes/Inventory/InventoryCategoryNode.cs +++ b/AetherBags/Nodes/Inventory/InventoryCategoryNode.cs @@ -6,6 +6,7 @@ using AetherBags.Inventory; using AetherBags.Nodes.Layout; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; using FFXIVClientStructs.FFXIV.Component.GUI; using KamiToolKit.Classes; using KamiToolKit.Nodes; @@ -213,19 +214,21 @@ public class InventoryCategoryNode : SimpleComponentNode private unsafe InventoryDragDropNode CreateInventoryDragDropNode(ItemInfo data) { InventoryItem item = data.Item; + InventoryMappedLocation location = data.VisualLocation; return new InventoryDragDropNode { Size = new Vector2(42, 46), + Alpha = data.IsEligibleForContext || data.IsSlotBlocked ? 1.0f : 0.4f, IsVisible = true, IconId = item.IconId, AcceptedType = DragDropType.Item, - IsDraggable = true, + IsDraggable = !data.IsSlotBlocked, Payload = new DragDropPayload { - Type = DragDropType.Inventory_Item, - Int1 = (int)item.Container, - Int2 = item.Slot, + Type = DragDropType.Item, + Int1 = location.Container, + Int2 = location.Slot, }, IsClickable = true, OnEnd = _ => System.AddonInventoryWindow.ManualInventoryRefresh(), @@ -248,7 +251,6 @@ public class InventoryCategoryNode : SimpleComponentNode private void OnPayloadAccepted(DragDropNode _, DragDropPayload payload, ItemInfo targetItemInfo) { - Services.Logger.Debug($"[OnPayload] Received payload of type {payload.Type}, Int1={payload.Int1}, Int2={payload.Int2}, RefIndex={payload.ReferenceIndex}, Text={payload.Text}"); if (!payload.IsValidInventoryPayload) return; @@ -260,10 +262,16 @@ public class InventoryCategoryNode : SimpleComponentNode return; } - InventoryLocation targetLocation = new InventoryLocation(targetItemInfo.Item.Container, (ushort)targetItemInfo.Item.Slot); + InventoryLocation targetLocation = new InventoryLocation( + targetItemInfo.Item.Container, + (ushort)targetItemInfo.Item.Slot + ); - Services.Logger.Debug($"[OnPayload] Moving {sourceLocation.ToString()} -> {targetLocation.ToString()}"); + Services.Logger.Debug($"[OnPayload] Moving {sourceLocation} -> {targetLocation}"); - InventoryMoveHelper.MoveItem(sourceLocation.Container, sourceLocation.Slot, targetLocation.Container, targetLocation.Slot); + InventoryMoveHelper.MoveItem( + sourceLocation.Container, sourceLocation.Slot, + targetLocation.Container, targetLocation.Slot + ); } } \ No newline at end of file