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