From 67bd9953295e18f7fc1ba8d244cd3b32a65a7999 Mon Sep 17 00:00:00 2001 From: Zeffuro Date: Wed, 24 Dec 2025 21:11:32 +0100 Subject: [PATCH] Fix move item --- AetherBags.sln.DotSettings.user | 1 + .../Extensions/InventoryTypeExtensions.cs | 76 ++++++++- AetherBags/Helpers/InventoryMoveHelper.cs | 52 ++++++ .../Nodes/Inventory/InventoryCategoryNode.cs | 152 +++++++----------- 4 files changed, 185 insertions(+), 96 deletions(-) create mode 100644 AetherBags/Helpers/InventoryMoveHelper.cs diff --git a/AetherBags.sln.DotSettings.user b/AetherBags.sln.DotSettings.user index 4930879..f39de2b 100644 --- a/AetherBags.sln.DotSettings.user +++ b/AetherBags.sln.DotSettings.user @@ -1,5 +1,6 @@  ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded \ No newline at end of file diff --git a/AetherBags/Extensions/InventoryTypeExtensions.cs b/AetherBags/Extensions/InventoryTypeExtensions.cs index a703d96..628462b 100644 --- a/AetherBags/Extensions/InventoryTypeExtensions.cs +++ b/AetherBags/Extensions/InventoryTypeExtensions.cs @@ -85,14 +85,88 @@ public static unsafe class InventoryTypeExtensions InventoryType.SaddleBag2 => ItemOrderModule.Instance()->SaddleBagSorter, InventoryType.PremiumSaddleBag1 => ItemOrderModule.Instance()->PremiumSaddleBagSorter, InventoryType.PremiumSaddleBag2 => ItemOrderModule.Instance()->PremiumSaddleBagSorter, - _ => throw new Exception($"Type Not Implemented: {inventoryType}"), + _ => null, }; public int GetInventoryStartIndex => inventoryType switch { InventoryType.Inventory2 => inventoryType.GetInventorySorter->ItemsPerPage, InventoryType.Inventory3 => inventoryType.GetInventorySorter->ItemsPerPage * 2, InventoryType.Inventory4 => inventoryType.GetInventorySorter->ItemsPerPage * 3, + InventoryType.SaddleBag2 => inventoryType.GetInventorySorter->ItemsPerPage, + InventoryType.PremiumSaddleBag2 => inventoryType.GetInventorySorter->ItemsPerPage, _ => 0, }; + + public bool IsMainInventory => inventoryType is + InventoryType.Inventory1 or + InventoryType.Inventory2 or + InventoryType.Inventory3 or + InventoryType.Inventory4; + + public bool IsSaddleBag => inventoryType is + InventoryType.SaddleBag1 or + InventoryType.SaddleBag2 or + InventoryType.PremiumSaddleBag1 or + InventoryType.PremiumSaddleBag2; + + public bool IsArmory => inventoryType is + InventoryType.ArmoryMainHand or + InventoryType.ArmoryHead or + InventoryType.ArmoryBody or + InventoryType.ArmoryHands or + InventoryType.ArmoryLegs or + InventoryType.ArmoryFeets or + InventoryType.ArmoryOffHand or + InventoryType.ArmoryEar or + InventoryType.ArmoryNeck or + InventoryType.ArmoryWrist or + InventoryType.ArmoryRings or + InventoryType.ArmorySoulCrystal; + + public int ContainerGroup => inventoryType switch + { + _ when inventoryType.IsMainInventory => 1, + _ when inventoryType.IsSaddleBag => 2, + _ when inventoryType.IsArmory => 3, + _ => 0, + }; + + public bool IsSameContainerGroup(InventoryType other) + => inventoryType.ContainerGroup == other.ContainerGroup; + + /// + /// Resolves the real container and slot for this inventory type using ItemOrderModule. + /// For sorted inventories, the visual slot differs from the actual storage slot. + /// + public (InventoryType Container, ushort Slot) GetRealItemLocation(int visualSlot) + { + var sorter = inventoryType.GetInventorySorter; + if (sorter == null) + return (inventoryType, (ushort)visualSlot); + + int startIndex = inventoryType.GetInventoryStartIndex; + int sorterIndex = startIndex + visualSlot; + + if (sorterIndex < 0 || sorterIndex >= sorter->Items.LongCount) + return (inventoryType, (ushort)visualSlot); + + var entry = sorter->Items[sorterIndex].Value; + if (entry == null) + return (inventoryType, (ushort)visualSlot); + + InventoryType baseType = inventoryType switch + { + _ when inventoryType.IsMainInventory => InventoryType.Inventory1, + _ when inventoryType.IsSaddleBag => inventoryType is InventoryType. SaddleBag1 or InventoryType.SaddleBag2 + ? InventoryType. SaddleBag1 + : InventoryType.PremiumSaddleBag1, + _ => inventoryType, + }; + + InventoryType realContainer = baseType + entry->Page; + ushort realSlot = entry->Slot; + + return (realContainer, realSlot); + } } } \ No newline at end of file diff --git a/AetherBags/Helpers/InventoryMoveHelper.cs b/AetherBags/Helpers/InventoryMoveHelper.cs new file mode 100644 index 0000000..ad15f7c --- /dev/null +++ b/AetherBags/Helpers/InventoryMoveHelper.cs @@ -0,0 +1,52 @@ +using AetherBags. Extensions; +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Component. GUI; +using ValueType = FFXIVClientStructs. FFXIV. Component.GUI.ValueType; + +namespace AetherBags. Helpers; + +public static unsafe class InventoryMoveHelper +{ + public static void MoveItem(InventoryType sourceContainer, ushort sourceSlot, InventoryType destContainer, ushort destSlot) + { + bool isCrossContainerMove = ! sourceContainer.IsSameContainerGroup(destContainer); + + if (isCrossContainerMove) + { + MoveItemViaAgent(sourceContainer, sourceSlot, destContainer, destSlot); + } + else + { + InventoryManager.Instance()->MoveItemSlot(sourceContainer, sourceSlot, destContainer, destSlot, true); + } + } + + private static void MoveItemViaAgent(InventoryType sourceInventory, ushort sourceSlot, InventoryType destInventory, ushort destSlot) + { + uint sourceContainerId = sourceInventory.AgentItemContainerId; + uint destContainerId = destInventory.AgentItemContainerId; + + if (sourceContainerId == 0 || destContainerId == 0) + { + Services.Logger.Warning($"[MoveItemViaAgent] Invalid container IDs: src={sourceContainerId}, dst={destContainerId}"); + return; + } + + Services.Logger.Debug($"[MoveItemViaAgent] {sourceContainerId}:{sourceSlot} -> {destContainerId}:{destSlot}"); + + var atkValues = stackalloc AtkValue[4]; + for (var i = 0; i < 4; i++) + atkValues[i]. Type = ValueType.UInt; + + atkValues[0].SetUInt(sourceContainerId); + atkValues[1].SetUInt(sourceSlot); + atkValues[2].SetUInt(destContainerId); + atkValues[3].SetUInt(destSlot); + + var retVal = stackalloc AtkValue[1]; + + RaptureAtkModule* atkModule = RaptureAtkModule.Instance(); + atkModule->HandleItemMove(retVal, atkValues, 4); + } +} \ No newline at end of file diff --git a/AetherBags/Nodes/Inventory/InventoryCategoryNode.cs b/AetherBags/Nodes/Inventory/InventoryCategoryNode.cs index be9c151..3702545 100644 --- a/AetherBags/Nodes/Inventory/InventoryCategoryNode.cs +++ b/AetherBags/Nodes/Inventory/InventoryCategoryNode.cs @@ -1,16 +1,16 @@ using System; using System.Numerics; +using AetherBags.Extensions; +using AetherBags.Helpers; 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; + // TODO: Switch back to CS version when Dalamud Updated using DragDropFixedNode = AetherBags.Nodes.DragDropNode; -using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType; namespace AetherBags.Nodes.Inventory; @@ -23,14 +23,11 @@ public class InventoryCategoryNode : SimpleComponentNode private const float HeaderHeight = 16; private const float MinWidth = 40; - private float? _fixedWidth; - + private float? _fixedWidth; private int _hoverRefs; private bool _headerSuppressed; private bool _headerExpanded; - private float _baseHeaderWidth = 96f; - private string _fullHeaderText = string.Empty; public event Action? HeaderHoverChanged; @@ -77,7 +74,6 @@ public class InventoryCategoryNode : SimpleComponentNode _categoryNameTextNode.String = _fullHeaderText; _categoryNameTextNode.TextColor = value.Category.Color; - _categoryNameTextNode.TooltipString = value.Category.Description; UpdateItemGrid(); @@ -96,7 +92,7 @@ public class InventoryCategoryNode : SimpleComponentNode } } - public float? FixedWidth + public float? FixedWidth { get => _fixedWidth; set @@ -114,7 +110,6 @@ public class InventoryCategoryNode : SimpleComponentNode _headerExpanded = true; ApplyHeaderVisualStateAndSize(); - HeaderHoverChanged?.Invoke(this, true); } @@ -127,7 +122,6 @@ public class InventoryCategoryNode : SimpleComponentNode _headerExpanded = false; ApplyHeaderVisualStateAndSize(); - HeaderHoverChanged?.Invoke(this, false); } @@ -140,12 +134,11 @@ public class InventoryCategoryNode : SimpleComponentNode private void ApplyHeaderVisualStateAndSize() { - _categoryNameTextNode.IsVisible = !_headerSuppressed; + _categoryNameTextNode.IsVisible = ! _headerSuppressed; if (_headerSuppressed) return; var flags = _categoryNameTextNode.TextFlags; - flags &= ~(TextFlags.WordWrap | TextFlags.MultiLine); if (_headerExpanded) @@ -153,7 +146,7 @@ public class InventoryCategoryNode : SimpleComponentNode flags &= ~(TextFlags.OverflowHidden | TextFlags.Ellipsis); _categoryNameTextNode.TextFlags = flags; - if (!string.IsNullOrEmpty(_fullHeaderText)) + if (! string.IsNullOrEmpty(_fullHeaderText)) _categoryNameTextNode.String = _fullHeaderText; Vector2 drawSize = _categoryNameTextNode.GetTextDrawSize(); @@ -167,7 +160,7 @@ public class InventoryCategoryNode : SimpleComponentNode if (!string.IsNullOrEmpty(_fullHeaderText)) _categoryNameTextNode.String = _fullHeaderText; - flags |= (TextFlags.OverflowHidden | TextFlags.Ellipsis); + flags |= TextFlags.OverflowHidden | TextFlags.Ellipsis; _categoryNameTextNode.TextFlags = flags; } } @@ -179,58 +172,30 @@ public class InventoryCategoryNode : SimpleComponentNode if (itemCount == 0) { float width = _fixedWidth ?? MinWidth; - Size = new Vector2(width, HeaderHeight); - _baseHeaderWidth = width; - _itemGridNode.Position = new Vector2(0, HeaderHeight); _itemGridNode.Size = new Vector2(width, 0); - ApplyHeaderVisualStateAndSize(); return; } - int itemsPerLine = _itemGridNode.ItemsPerLine; - if (itemsPerLine < 1) itemsPerLine = 1; - + int itemsPerLine = Math.Max(1, _itemGridNode.ItemsPerLine); int rows = (itemCount + itemsPerLine - 1) / itemsPerLine; int actualColumns = Math.Min(itemCount, itemsPerLine); - float cellW, cellH; - if (_itemGridNode.Nodes.Count > 0) - { - var firstChild = _itemGridNode.Nodes[0]; - cellW = firstChild.Width; - cellH = firstChild.Height; - } - else - { - cellW = FallbackItemSize; - cellH = FallbackItemSize; - } + float cellW = _itemGridNode.Nodes.Count > 0 ? _itemGridNode.Nodes[0].Width : FallbackItemSize; + float cellH = _itemGridNode.Nodes.Count > 0 ? _itemGridNode.Nodes[0].Height : FallbackItemSize; float hPad = _itemGridNode.HorizontalPadding; float vPad = _itemGridNode.VerticalPadding; - float calculatedWidth; - if (_fixedWidth.HasValue) - { - calculatedWidth = _fixedWidth.Value; - } - else - { - calculatedWidth = actualColumns * cellW + (actualColumns - 1) * hPad; - if (calculatedWidth < MinWidth) calculatedWidth = MinWidth; - } - + float calculatedWidth = _fixedWidth ?? Math.Max(MinWidth, actualColumns * cellW + (actualColumns - 1) * hPad); float height = HeaderHeight + rows * cellH + (rows - 1) * vPad; Size = new Vector2(calculatedWidth, height); - _itemGridNode.Position = new Vector2(0, HeaderHeight); _itemGridNode.Size = new Vector2(calculatedWidth, height - HeaderHeight); - _baseHeaderWidth = calculatedWidth; ApplyHeaderVisualStateAndSize(); @@ -248,7 +213,7 @@ public class InventoryCategoryNode : SimpleComponentNode { InventoryItem item = data.Item; - var node = new InventoryDragDropNode + return new InventoryDragDropNode { Size = new Vector2(42, 46), IsVisible = true, @@ -258,14 +223,11 @@ public class InventoryCategoryNode : SimpleComponentNode Payload = new DragDropPayload { Type = DragDropType.Inventory_Item, - Int1 = (int)item.GetInventoryType(), + Int1 = (int)item.Container, Int2 = item.Slot, }, IsClickable = true, - OnEnd = _ => - { - System.AddonInventoryWindow.ManualInventoryRefresh(); - }, + OnEnd = _ => System.AddonInventoryWindow.ManualInventoryRefresh(), OnPayloadAccepted = (n, p) => OnPayloadAccepted(n, p, data), OnRollOver = n => { @@ -277,54 +239,54 @@ public class InventoryCategoryNode : SimpleComponentNode EndHeaderHover(); n.HideTooltip(); }, - ItemInfo = data }; - - return node; } - private unsafe void OnPayloadAccepted(DragDropNode node, DragDropPayload payload, ItemInfo itemInfo) + private void OnPayloadAccepted(DragDropNode node, DragDropPayload payload, ItemInfo targetItemInfo) { - if (payload.Type != DragDropType.Item) return; - InventoryItem item = itemInfo.Item; - Services.Logger.Debug($"Inventory DragDropNode Payload Accepted: {payload.Type} Int1: {payload.Int1} Int2: {payload.Int2} ReferenceIndex: {payload.ReferenceIndex}"); - InventoryType inventoryType = InventoryType.GetInventoryTypeFromContainerId(payload.Int1); - ushort sourceSlot = (ushort)payload.Int2; - ItemOrderModuleSorterItemEntry* itemEntry = item.GetItemOrderData(); - Services.Logger.Debug($"{item.Slot} vs {item.GetSlot()}: entry: {itemEntry->Slot}"); - Services.Logger.Info($"[OnPayload] Moving {inventoryType}@{sourceSlot} -> {item.Container}@{item.Slot} -> {item.Name.ExtractText()}"); - InventoryManager.Instance()->MoveItemSlot(inventoryType, sourceSlot, item.Container, item.GetSlot(), true); + if (payload.Type != DragDropType.Item && payload.Type != DragDropType.Inventory_Item) + return; + var (sourceContainer, sourceSlot) = ResolveSourceFromPayload(payload); - // System.AddonInventoryWindow.ManualInventoryRefresh(); - - // Should work for swapping item but need a fake empty slot to put new items in probably. - // Services.Logger.Debug($"Moving Item from {inventoryType} Slot {sourceSlot} to {itemInfo.Item.Container} Slot {itemInfo.Item.GetSlot()}"); - //MoveItem(inventoryType, sourceSlot, itemInfo.Item.Container, itemInfo.Item.GetSlot()); - } - - // Possibly still use this - private unsafe void MoveItem(InventoryType sourceInventory, uint sourceSlot, InventoryType destinationInventory, uint destinationSlot) - { - var sourceContainerId = sourceInventory.AgentItemContainerId; - var destinationContainerId = destinationInventory.AgentItemContainerId; - - if (sourceContainerId != 0 && destinationContainerId != 0) { - var atkValues = stackalloc AtkValue[4]; - for (var i = 0; i < 4; i++) atkValues[i].Type = ValueType.UInt; - - atkValues[0].UInt = sourceContainerId; - atkValues[1].UInt = sourceSlot; - atkValues[2].UInt = destinationContainerId; - atkValues[3].UInt = destinationSlot; - - var retVal = stackalloc AtkValue[1]; - - RaptureAtkModule* atkModule = RaptureAtkModule.Instance(); - // (RaptureAtkModule* a1, void* outValue, AtkValue* atkValues); - // (AtkValue* returnValue, AtkValue* values, uint valueCount) - atkModule->HandleItemMove(retVal, atkValues, 4); + if (sourceContainer == 0) + { + Services.Logger.Warning($"[OnPayload] Could not resolve source from payload"); + return; } + + InventoryType targetContainer = targetItemInfo.Item.Container; + ushort targetSlot = (ushort)targetItemInfo.Item.Slot; + + Services.Logger.Info($"[OnPayload] Moving {sourceContainer}@{sourceSlot} -> {targetContainer}@{targetSlot}"); + + InventoryMoveHelper.MoveItem(sourceContainer, sourceSlot, targetContainer, targetSlot); } -} + + private static (InventoryType Container, ushort Slot) ResolveSourceFromPayload(DragDropPayload payload) + { + if (payload.Type == DragDropType.Inventory_Item) + { + return ((InventoryType)payload.Int1, (ushort)payload.Int2); + } + + int containerId = payload.Int1; + int slotIndex = payload.Int2; + + InventoryType sourceContainer = InventoryType.GetInventoryTypeFromContainerId(containerId); + + if (sourceContainer == 0) + return (0, 0); + + // For main inventory, resolve the real slot via ItemOrderModule + if (sourceContainer.IsMainInventory) + { + var (realContainer, realSlot) = sourceContainer.GetRealItemLocation(slotIndex); + return (realContainer, realSlot); + } + + // For other containers (saddlebags, armory, etc.), use the slot directly + return (sourceContainer, (ushort)slotIndex); + } +} \ No newline at end of file