From fc12b41f335af96c6d52aafdea0cafdf87a10cea Mon Sep 17 00:00:00 2001 From: Zeffuro Date: Fri, 26 Dec 2025 09:02:06 +0100 Subject: [PATCH] Add Retainers, need to refactor more --- AetherBags.sln.DotSettings.user | 2 + .../Extensions/InventoryTypeExtensions.cs | 76 ++++++++++++++++++- AetherBags/Helpers/InventoryMoveHelper.cs | 4 + AetherBags/Hooks/InventoryHook.cs | 2 +- .../Nodes/Inventory/InventoryCategoryNode.cs | 27 +++++-- 5 files changed, 102 insertions(+), 9 deletions(-) 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