diff --git a/AetherBags.sln.DotSettings.user b/AetherBags.sln.DotSettings.user
index f39de2b..cf7e7c4 100644
--- a/AetherBags.sln.DotSettings.user
+++ b/AetherBags.sln.DotSettings.user
@@ -2,5 +2,7 @@
ForceIncluded
ForceIncluded
ForceIncluded
+ ForceIncluded
ForceIncluded
+ ForceIncluded
ForceIncluded
\ No newline at end of file
diff --git a/AetherBags/Extensions/InventoryTypeExtensions.cs b/AetherBags/Extensions/InventoryTypeExtensions.cs
index 4c28d8f..0b8ff14 100644
--- a/AetherBags/Extensions/InventoryTypeExtensions.cs
+++ b/AetherBags/Extensions/InventoryTypeExtensions.cs
@@ -17,6 +17,11 @@ public static unsafe class InventoryTypeExtensions
InventoryType.Inventory2 => 49,
InventoryType.Inventory3 => 50,
InventoryType.Inventory4 => 51,
+ InventoryType.RetainerPage1 => 52,
+ InventoryType.RetainerPage2 => 53,
+ InventoryType.RetainerPage3 => 54,
+ InventoryType.RetainerPage4 => 55,
+ InventoryType.RetainerPage5 => 56,
InventoryType.ArmoryMainHand => 57,
InventoryType.ArmoryHead => 58,
InventoryType.ArmoryBody => 59,
@@ -45,6 +50,11 @@ public static unsafe class InventoryTypeExtensions
49 => InventoryType.Inventory2,
50 => InventoryType.Inventory3,
51 => InventoryType.Inventory4,
+ 52 => InventoryType.RetainerPage1,
+ 53 => InventoryType.RetainerPage2,
+ 54 => InventoryType.RetainerPage3,
+ 55 => InventoryType.RetainerPage4,
+ 56 => InventoryType.RetainerPage5,
57 => InventoryType.ArmoryMainHand,
58 => InventoryType.ArmoryHead,
59 => InventoryType.ArmoryBody,
@@ -85,6 +95,13 @@ public static unsafe class InventoryTypeExtensions
InventoryType.SaddleBag2 => ItemOrderModule.Instance()->SaddleBagSorter,
InventoryType.PremiumSaddleBag1 => ItemOrderModule.Instance()->PremiumSaddleBagSorter,
InventoryType.PremiumSaddleBag2 => ItemOrderModule.Instance()->PremiumSaddleBagSorter,
+ InventoryType.RetainerPage1 => ItemOrderModule.Instance()->GetActiveRetainerSorter(),
+ InventoryType.RetainerPage2 => ItemOrderModule.Instance()->GetActiveRetainerSorter(),
+ InventoryType.RetainerPage3 => ItemOrderModule.Instance()->GetActiveRetainerSorter(),
+ InventoryType.RetainerPage4 => ItemOrderModule.Instance()->GetActiveRetainerSorter(),
+ InventoryType.RetainerPage5 => ItemOrderModule.Instance()->GetActiveRetainerSorter(),
+ InventoryType.RetainerPage6 => ItemOrderModule.Instance()->GetActiveRetainerSorter(),
+ InventoryType.RetainerPage7 => ItemOrderModule.Instance()->GetActiveRetainerSorter(),
_ => null,
};
@@ -94,6 +111,12 @@ public static unsafe class InventoryTypeExtensions
InventoryType.Inventory4 => inventoryType.GetInventorySorter->ItemsPerPage * 3,
InventoryType.SaddleBag2 => inventoryType.GetInventorySorter->ItemsPerPage,
InventoryType.PremiumSaddleBag2 => inventoryType.GetInventorySorter->ItemsPerPage,
+ InventoryType.RetainerPage2 => inventoryType.GetInventorySorter->ItemsPerPage,
+ InventoryType.RetainerPage3 => inventoryType.GetInventorySorter->ItemsPerPage * 2,
+ InventoryType.RetainerPage4 => inventoryType.GetInventorySorter->ItemsPerPage * 3,
+ InventoryType.RetainerPage5 => inventoryType.GetInventorySorter->ItemsPerPage * 4,
+ InventoryType.RetainerPage6 => inventoryType.GetInventorySorter->ItemsPerPage * 5,
+ InventoryType.RetainerPage7 => inventoryType.GetInventorySorter->ItemsPerPage * 6,
_ => 0,
};
@@ -123,11 +146,21 @@ public static unsafe class InventoryTypeExtensions
InventoryType.ArmoryRings or
InventoryType.ArmorySoulCrystal;
+ public bool IsRetainer => inventoryType is
+ InventoryType.RetainerPage1 or
+ InventoryType.RetainerPage2 or
+ InventoryType.RetainerPage3 or
+ InventoryType.RetainerPage4 or
+ InventoryType.RetainerPage5 or
+ InventoryType.RetainerPage6 or
+ InventoryType.RetainerPage7;
+
public int ContainerGroup => inventoryType switch
{
_ when inventoryType.IsMainInventory => 1,
_ when inventoryType.IsSaddleBag => 2,
_ when inventoryType.IsArmory => 3,
+ _ when inventoryType.IsRetainer => 4,
_ => 0,
};
@@ -157,9 +190,10 @@ public static unsafe class InventoryTypeExtensions
InventoryType baseType = inventoryType switch
{
_ when inventoryType.IsMainInventory => InventoryType.Inventory1,
- _ when inventoryType.IsSaddleBag => inventoryType is InventoryType. SaddleBag1 or InventoryType.SaddleBag2
- ? InventoryType. SaddleBag1
+ _ when inventoryType.IsSaddleBag => inventoryType is InventoryType.SaddleBag1 or InventoryType.SaddleBag2
+ ? InventoryType.SaddleBag1
: InventoryType.PremiumSaddleBag1,
+ _ when inventoryType.IsRetainer => InventoryType.RetainerPage1,
_ => inventoryType,
};
@@ -168,5 +202,43 @@ public static unsafe class InventoryTypeExtensions
return (realContainer, realSlot);
}
+
+ public int GetVisualSlotFromReal(int realSlot)
+ {
+ var sorter = inventoryType.GetInventorySorter;
+ if (sorter == null)
+ return realSlot;
+
+ int startIndex = inventoryType.GetInventoryStartIndex;
+ long itemCount = sorter->Items.LongCount;
+
+ // Search through the sorter to find which visual index maps to this real slot
+ for (int visualIdx = 0; visualIdx < itemCount; visualIdx++)
+ {
+ var entry = sorter->Items[visualIdx]. Value;
+ if (entry == null) continue;
+
+ // Calculate what container this entry belongs to
+ InventoryType baseType = inventoryType switch
+ {
+ _ when inventoryType.IsMainInventory => InventoryType. Inventory1,
+ _ when inventoryType.IsSaddleBag => inventoryType is InventoryType.SaddleBag1 or InventoryType.SaddleBag2
+ ? InventoryType. SaddleBag1
+ : InventoryType.PremiumSaddleBag1,
+ _ when inventoryType.IsRetainer => InventoryType.RetainerPage1,
+ _ => inventoryType,
+ };
+
+ InventoryType entryContainer = baseType + entry->Page;
+
+ if (entryContainer == inventoryType && entry->Slot == realSlot)
+ {
+ // Found it! Return visual index relative to the container's start
+ return visualIdx - startIndex;
+ }
+ }
+
+ return realSlot; // Fallback
+ }
}
}
\ No newline at end of file
diff --git a/AetherBags/Helpers/InventoryMoveHelper.cs b/AetherBags/Helpers/InventoryMoveHelper.cs
index ad15f7c..18ef92a 100644
--- a/AetherBags/Helpers/InventoryMoveHelper.cs
+++ b/AetherBags/Helpers/InventoryMoveHelper.cs
@@ -10,6 +10,10 @@ public static unsafe class InventoryMoveHelper
{
public static void MoveItem(InventoryType sourceContainer, ushort sourceSlot, InventoryType destContainer, ushort destSlot)
{
+ Services.Logger.Debug($"[MoveItem] {sourceContainer}@{sourceSlot} -> {destContainer}@{destSlot}");
+ InventoryManager.Instance()->MoveItemSlot(sourceContainer, sourceSlot, destContainer, destSlot, true);
+ System.AddonInventoryWindow.ManualInventoryRefresh();
+ return;
bool isCrossContainerMove = ! sourceContainer.IsSameContainerGroup(destContainer);
if (isCrossContainerMove)
diff --git a/AetherBags/Hooks/InventoryHook.cs b/AetherBags/Hooks/InventoryHook.cs
index 5e821fc..dd2fa48 100644
--- a/AetherBags/Hooks/InventoryHook.cs
+++ b/AetherBags/Hooks/InventoryHook.cs
@@ -46,7 +46,7 @@ public sealed unsafe class InventoryHooks : IDisposable
InventoryItem* sourceItem = InventoryManager.Instance()->GetInventorySlot(srcType, srcSlot);
InventoryItem* destItem = InventoryManager.Instance()->GetInventorySlot(dstType, dstSlot);
- Services.Logger.Debug($"[MoveItemSlot] Moving {srcType}@{srcSlot} ID:{sourceItem->ItemId} -> {dstType}@{dstSlot} ID:{destItem->ItemId} Unk: {unk}");
+ Services.Logger.Debug($"[MoveItemSlot Hook] Moving {srcType}@{srcSlot} ID:{sourceItem->ItemId} -> {dstType}@{dstSlot} ID:{destItem->ItemId} Unk: {unk}");
return _moveItemSlotHook!.Original(manager, srcType, srcSlot, dstType, dstSlot, unk);
}
diff --git a/AetherBags/Nodes/Inventory/InventoryCategoryNode.cs b/AetherBags/Nodes/Inventory/InventoryCategoryNode.cs
index f8fbd36..af2a4f6 100644
--- a/AetherBags/Nodes/Inventory/InventoryCategoryNode.cs
+++ b/AetherBags/Nodes/Inventory/InventoryCategoryNode.cs
@@ -248,6 +248,7 @@ public class InventoryCategoryNode : SimpleComponentNode
if (payload.Type != DragDropType.Item && payload.Type != DragDropType.Inventory_Item)
return;
+ Services.Logger.Debug($"[OnPayload] Received payload of type {payload.Type}, Int1={payload.Int1}, Int2={payload.Int2}, RefIndex={payload.ReferenceIndex}, Text={payload.Text}");
var (sourceContainer, sourceSlot) = ResolveSourceFromPayload(payload);
if (sourceContainer == 0)
@@ -272,21 +273,35 @@ public class InventoryCategoryNode : SimpleComponentNode
}
int containerId = payload.Int1;
- int slotIndex = payload.Int2;
+ int uiSlot = 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)
+ // Retainers have special handling: UI has 5 tabs × 35 slots, data has 7 pages × 25 slots
+ if (sourceContainer. IsRetainer)
{
- var (realContainer, realSlot) = sourceContainer.GetRealItemLocation(slotIndex);
+ // Container IDs 52-56 = UI tabs 0-4
+ int uiTabIndex = containerId - 52;
+
+ // Convert to global data index
+ int globalDataIndex = (uiTabIndex * 35) + uiSlot;
+
+ // Calculate data page and slot
+ int dataPage = globalDataIndex / 25;
+ int dataSlot = globalDataIndex % 25;
+
+ InventoryType dataContainer = InventoryType.RetainerPage1 + (uint)dataPage;
+
+ // Now resolve through sorter for the actual storage location
+ var (realContainer, realSlot) = dataContainer.GetRealItemLocation(dataSlot);
return (realContainer, realSlot);
}
- // For other containers (saddlebags, armory, etc.), use the slot directly
- return (sourceContainer, (ushort)slotIndex);
+ // For non-retainers, use the standard resolution
+ var (realContainerOther, realSlotOther) = sourceContainer.GetRealItemLocation(uiSlot);
+ return (realContainerOther, realSlotOther);
}
}
\ No newline at end of file