Merge remote-tracking branch 'origin/master' into dev/pie-lover

This commit is contained in:
Shawrkie Williams
2025-12-27 06:26:47 -05:00
14 changed files with 391 additions and 85 deletions
+4
View File
@@ -1,6 +1,10 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAgentInventory_002Ecs_002Fl_003AC_0021_003FUsers_003FJeffro_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F9e458ae8a124476099a99b081d71ce27826848_003F26_003F0a847424_003FAgentInventory_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAgentSatisfactionSupply_002Ecs_002Fl_003AC_0021_003FUsers_003FJeffro_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb2cd0663609440e590f52980cafc1ba3822648_003F28_003Ffa48b62e_003FAgentSatisfactionSupply_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAgentSatisfactionSupply_002Ecs_002Fl_003AC_0021_003FUsers_003FJeffro_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb2cd0663609440e590f52980cafc1ba3822648_003F28_003Ffa48b62e_003FAgentSatisfactionSupply_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAgentUpdateFlag_002Ecs_002Fl_003AC_0021_003FUsers_003FJeffro_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F7097e6bfd2288e8ff8dacc8d1e21863898453e58b9546b9752e0c0a5bed4dc_003FAgentUpdateFlag_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAtkValue_002Ecs_002Fl_003AC_0021_003FUsers_003FJeffro_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F1b966bf9f0d5b3eb39a7ee3ff6ab5c83f5bea8a841eafd7c8a1e55532d2d952_003FAtkValue_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAtkValue_002Ecs_002Fl_003AC_0021_003FUsers_003FJeffro_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F1b966bf9f0d5b3eb39a7ee3ff6ab5c83f5bea8a841eafd7c8a1e55532d2d952_003FAtkValue_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACurrencyManager_002Ecs_002Fl_003AC_0021_003FUsers_003FJeffro_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe66db7cd515142b9bbfb1b4e18f82ace825448_003Ffc_003F78df30c7_003FCurrencyManager_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACurrencyManager_002Ecs_002Fl_003AC_0021_003FUsers_003FJeffro_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe66db7cd515142b9bbfb1b4e18f82ace825448_003Ffc_003F78df30c7_003FCurrencyManager_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AInventoryItem_002Ecs_002Fl_003AC_0021_003FUsers_003FJeffro_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F9e458ae8a124476099a99b081d71ce27826848_003F6f_003Ffa56b446_003FInventoryItem_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AInventoryManager_002Ecs_002Fl_003AC_0021_003FUsers_003FJeffro_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F7d322ffbe41aca452171c1858ac4d72a967922191dfb8ada66667df5fd58b_003FInventoryManager_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AInventoryManager_002Ecs_002Fl_003AC_0021_003FUsers_003FJeffro_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F7d322ffbe41aca452171c1858ac4d72a967922191dfb8ada66667df5fd58b_003FInventoryManager_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AItemOrderModule_002Ecs_002Fl_003AC_0021_003FUsers_003FJeffro_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F77e1e4eb417120eb1e45bbb81bf6a70e47ea3c097bdf493584be7f333fa_003FItemOrderModule_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AItem_002Eg_002Ecs_002Fl_003AC_0021_003FUsers_003FJeffro_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F8ec7cc8a18dbb6a6f3c21f8adcb4e2661dc7979_003FItem_002Eg_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AItem_002Eg_002Ecs_002Fl_003AC_0021_003FUsers_003FJeffro_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F8ec7cc8a18dbb6a6f3c21f8adcb4e2661dc7979_003FItem_002Eg_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
+12 -1
View File
@@ -9,6 +9,7 @@ using AetherBags.Nodes.Inventory;
using AetherBags.Nodes.Layout; using AetherBags.Nodes.Layout;
using Dalamud.Game.Addon.Lifecycle; using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Game.Gui;
using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit; using KamiToolKit;
@@ -22,6 +23,7 @@ public class AddonInventoryWindow : NativeAddon
private readonly InventoryCategoryHoverCoordinator _hoverCoordinator = new(); private readonly InventoryCategoryHoverCoordinator _hoverCoordinator = new();
private readonly HashSet<InventoryCategoryNode> _hoverSubscribed = new(); private readonly HashSet<InventoryCategoryNode> _hoverSubscribed = new();
private InventoryNotificationNode _notificationNode = null!;
private WrappingGridNode<InventoryCategoryNode> _categoriesNode = null!; private WrappingGridNode<InventoryCategoryNode> _categoriesNode = null!;
private TextInputWithHintNode _searchInputNode = null!; private TextInputWithHintNode _searchInputNode = null!;
private CircleButtonNode _settingsButtonNode = null!; private CircleButtonNode _settingsButtonNode = null!;
@@ -69,6 +71,13 @@ public class AddonInventoryWindow : NativeAddon
float x = headerX + (headerW - size.X) * 0.5f; float x = headerX + (headerW - size.X) * 0.5f;
float y = headerY + (headerH - size.Y) * 0.5f; float y = headerY + (headerH - size.Y) * 0.5f;
_notificationNode = new InventoryNotificationNode
{
Position = new Vector2(WindowNode!.X - 4f, WindowNode!.Y - 32f),
Size = new Vector2(headerW, 28f),
};
_notificationNode.AttachNode(this);
_searchInputNode = new TextInputWithHintNode _searchInputNode = new TextInputWithHintNode
{ {
Position = new Vector2(x, y), Position = new Vector2(x, y),
@@ -105,7 +114,6 @@ public class AddonInventoryWindow : NativeAddon
base.OnSetup(addon); base.OnSetup(addon);
} }
protected override unsafe void OnUpdate(AtkUnitBase* addon) protected override unsafe void OnUpdate(AtkUnitBase* addon)
{ {
if (_refreshQueued) if (_refreshQueued)
@@ -117,6 +125,9 @@ public class AddonInventoryWindow : NativeAddon
RefreshCategoriesCore(doAutosize); RefreshCategoriesCore(doAutosize);
} }
InventoryNotificationType currentNotificationType = (InventoryNotificationType) AgentInventory.Instance()->OpenTitleId;
if(currentNotificationType != _notificationNode.NotificationType) _notificationNode.NotificationType = currentNotificationType;
base.OnUpdate(addon); base.OnUpdate(addon);
} }
@@ -1,4 +1,6 @@
using AetherBags.Interop; using AetherBags.Interop;
using AetherBags.Inventory;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes; using KamiToolKit.Classes;
using Lumina.Text.ReadOnly; using Lumina.Text.ReadOnly;
@@ -6,7 +8,7 @@ using Lumina.Text;
namespace AetherBags.Extensions; namespace AetherBags.Extensions;
// TODO: Remove this when CS is merged into Dalamud. // TODO: Remove FixedInterface when CS is merged into Dalamud.
public static unsafe class DragDropPayloadExtensions public static unsafe class DragDropPayloadExtensions
{ {
public static DragDropPayload FromFixedInterface(AtkDragDropInterface* dragDropInterface) public static DragDropPayload FromFixedInterface(AtkDragDropInterface* dragDropInterface)
@@ -54,4 +56,59 @@ public static unsafe class DragDropPayloadExtensions
} }
} }
} }
extension(DragDropPayload payload)
{
public bool IsValidInventoryPayload =>
payload.Type is DragDropType.Inventory_Item
or DragDropType.Inventory_Crystal
or DragDropType.RemoteInventory_Item
or DragDropType.Item;
public InventoryLocation InventoryLocation
{
get
{
if (!payload.IsValidInventoryPayload) return default;
if (payload.Type == DragDropType.Inventory_Item)
{
return new InventoryLocation((InventoryType)payload.Int1, (ushort)payload.Int2);
}
int containerId = payload.Int1;
int uiSlot = payload.Int2;
InventoryType sourceContainer = InventoryType.GetInventoryTypeFromContainerId(containerId);
if (sourceContainer == 0)
return new InventoryLocation(0, 0);
// Retainers have special handling: UI has 5 tabs × 35 slots, data has 7 pages × 25 slots
if (sourceContainer.IsRetainer)
{
// 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 new InventoryLocation(realContainer, realSlot);
}
// For non-retainers, use the standard resolution
var (container, slot) = sourceContainer.GetRealItemLocation(uiSlot);
return new InventoryLocation(container, slot);
}
}
}
} }
@@ -1,4 +1,5 @@
using System; using System;
using AetherBags.Inventory;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI.Misc; using FFXIVClientStructs.FFXIV.Client.UI.Misc;
@@ -17,6 +18,12 @@ public static unsafe class InventoryTypeExtensions
InventoryType.Inventory2 => 49, InventoryType.Inventory2 => 49,
InventoryType.Inventory3 => 50, InventoryType.Inventory3 => 50,
InventoryType.Inventory4 => 51, InventoryType.Inventory4 => 51,
// It's possible that these are actually UI IDs
InventoryType.RetainerPage1 => 52,
InventoryType.RetainerPage2 => 53,
InventoryType.RetainerPage3 => 54,
InventoryType.RetainerPage4 => 55,
InventoryType.RetainerPage5 => 56,
InventoryType.ArmoryMainHand => 57, InventoryType.ArmoryMainHand => 57,
InventoryType.ArmoryHead => 58, InventoryType.ArmoryHead => 58,
InventoryType.ArmoryBody => 59, InventoryType.ArmoryBody => 59,
@@ -45,6 +52,11 @@ public static unsafe class InventoryTypeExtensions
49 => InventoryType.Inventory2, 49 => InventoryType.Inventory2,
50 => InventoryType.Inventory3, 50 => InventoryType.Inventory3,
51 => InventoryType.Inventory4, 51 => InventoryType.Inventory4,
52 => InventoryType.RetainerPage1,
53 => InventoryType.RetainerPage2,
54 => InventoryType.RetainerPage3,
55 => InventoryType.RetainerPage4,
56 => InventoryType.RetainerPage5,
57 => InventoryType.ArmoryMainHand, 57 => InventoryType.ArmoryMainHand,
58 => InventoryType.ArmoryHead, 58 => InventoryType.ArmoryHead,
59 => InventoryType.ArmoryBody, 59 => InventoryType.ArmoryBody,
@@ -85,6 +97,13 @@ public static unsafe class InventoryTypeExtensions
InventoryType.SaddleBag2 => ItemOrderModule.Instance()->SaddleBagSorter, InventoryType.SaddleBag2 => ItemOrderModule.Instance()->SaddleBagSorter,
InventoryType.PremiumSaddleBag1 => ItemOrderModule.Instance()->PremiumSaddleBagSorter, InventoryType.PremiumSaddleBag1 => ItemOrderModule.Instance()->PremiumSaddleBagSorter,
InventoryType.PremiumSaddleBag2 => 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, _ => null,
}; };
@@ -94,6 +113,12 @@ public static unsafe class InventoryTypeExtensions
InventoryType.Inventory4 => inventoryType.GetInventorySorter->ItemsPerPage * 3, InventoryType.Inventory4 => inventoryType.GetInventorySorter->ItemsPerPage * 3,
InventoryType.SaddleBag2 => inventoryType.GetInventorySorter->ItemsPerPage, InventoryType.SaddleBag2 => inventoryType.GetInventorySorter->ItemsPerPage,
InventoryType.PremiumSaddleBag2 => 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, _ => 0,
}; };
@@ -123,11 +148,21 @@ public static unsafe class InventoryTypeExtensions
InventoryType.ArmoryRings or InventoryType.ArmoryRings or
InventoryType.ArmorySoulCrystal; 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 public int ContainerGroup => inventoryType switch
{ {
_ when inventoryType.IsMainInventory => 1, _ when inventoryType.IsMainInventory => 1,
_ when inventoryType.IsSaddleBag => 2, _ when inventoryType.IsSaddleBag => 2,
_ when inventoryType.IsArmory => 3, _ when inventoryType.IsArmory => 3,
_ when inventoryType.IsRetainer => 4,
_ => 0, _ => 0,
}; };
@@ -138,35 +173,36 @@ public static unsafe class InventoryTypeExtensions
/// Resolves the real container and slot for this inventory type using ItemOrderModule. /// Resolves the real container and slot for this inventory type using ItemOrderModule.
/// For sorted inventories, the visual slot differs from the actual storage slot. /// For sorted inventories, the visual slot differs from the actual storage slot.
/// </summary> /// </summary>
public (InventoryType Container, ushort Slot) GetRealItemLocation(int visualSlot) public InventoryLocation GetRealItemLocation(int visualSlot)
{ {
var sorter = inventoryType.GetInventorySorter; var sorter = inventoryType.GetInventorySorter;
if (sorter == null) if (sorter == null)
return (inventoryType, (ushort)visualSlot); return new InventoryLocation(inventoryType, (ushort)visualSlot);
int startIndex = inventoryType.GetInventoryStartIndex; int startIndex = inventoryType.GetInventoryStartIndex;
int sorterIndex = startIndex + visualSlot; int sorterIndex = startIndex + visualSlot;
if (sorterIndex < 0 || sorterIndex >= sorter->Items.LongCount) if (sorterIndex < 0 || sorterIndex >= sorter->Items.LongCount)
return (inventoryType, (ushort)visualSlot); return new InventoryLocation(inventoryType, (ushort)visualSlot);
var entry = sorter->Items[sorterIndex].Value; var entry = sorter->Items[sorterIndex].Value;
if (entry == null) if (entry == null)
return (inventoryType, (ushort)visualSlot); return new InventoryLocation(inventoryType, (ushort)visualSlot);
InventoryType baseType = inventoryType switch InventoryType baseType = inventoryType switch
{ {
_ when inventoryType.IsMainInventory => InventoryType.Inventory1, _ when inventoryType.IsMainInventory => InventoryType.Inventory1,
_ when inventoryType.IsSaddleBag => inventoryType is InventoryType. SaddleBag1 or InventoryType.SaddleBag2 _ when inventoryType.IsSaddleBag => inventoryType is InventoryType.SaddleBag1 or InventoryType.SaddleBag2
? InventoryType. SaddleBag1 ? InventoryType.SaddleBag1
: InventoryType.PremiumSaddleBag1, : InventoryType.PremiumSaddleBag1,
_ when inventoryType.IsRetainer => InventoryType.RetainerPage1,
_ => inventoryType, _ => inventoryType,
}; };
InventoryType realContainer = baseType + entry->Page; InventoryType realContainer = baseType + entry->Page;
ushort realSlot = entry->Slot; ushort realSlot = entry->Slot;
return (realContainer, realSlot); return new InventoryLocation(realContainer, realSlot);
} }
} }
} }
+7 -10
View File
@@ -8,20 +8,16 @@ namespace AetherBags. Helpers;
public static unsafe class InventoryMoveHelper public static unsafe class InventoryMoveHelper
{ {
// Requires the visual UI slots instead of actual slots.
public static void MoveItem(InventoryType sourceContainer, ushort sourceSlot, InventoryType destContainer, ushort destSlot) public static void MoveItem(InventoryType sourceContainer, ushort sourceSlot, InventoryType destContainer, ushort destSlot)
{ {
bool isCrossContainerMove = ! sourceContainer.IsSameContainerGroup(destContainer); Services.Logger.Debug($"[MoveItem] {sourceContainer}@{sourceSlot} -> {destContainer}@{destSlot}");
InventoryManager.Instance()->MoveItemSlot(sourceContainer, sourceSlot, destContainer, destSlot, true);
if (isCrossContainerMove) Services.Framework.DelayTicks(2);
{ Services.Framework.RunOnFrameworkThread(System.AddonInventoryWindow.ManualInventoryRefresh);
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) private static void MoveItemViaAgent(InventoryType sourceInventory, ushort sourceSlot, InventoryType destInventory, ushort destSlot)
{ {
uint sourceContainerId = sourceInventory.AgentItemContainerId; uint sourceContainerId = sourceInventory.AgentItemContainerId;
@@ -49,4 +45,5 @@ public static unsafe class InventoryMoveHelper
RaptureAtkModule* atkModule = RaptureAtkModule.Instance(); RaptureAtkModule* atkModule = RaptureAtkModule.Instance();
atkModule->HandleItemMove(retVal, atkValues, 4); atkModule->HandleItemMove(retVal, atkValues, 4);
} }
*/
} }
+1 -1
View File
@@ -46,7 +46,7 @@ public sealed unsafe class InventoryHooks : IDisposable
InventoryItem* sourceItem = InventoryManager.Instance()->GetInventorySlot(srcType, srcSlot); InventoryItem* sourceItem = InventoryManager.Instance()->GetInventorySlot(srcType, srcSlot);
InventoryItem* destItem = InventoryManager.Instance()->GetInventorySlot(dstType, dstSlot); 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); return _moveItemSlotHook!.Original(manager, srcType, srcSlot, dstType, dstSlot, unk);
} }
+12
View File
@@ -0,0 +1,12 @@
using FFXIVClientStructs.FFXIV.Client.Game;
namespace AetherBags.Inventory;
public readonly record struct InventoryLocation(InventoryType Container, ushort Slot)
{
public static readonly InventoryLocation Invalid = new(0, 0);
public bool IsValid => Container != 0;
public override string ToString() => $"{Container}@{Slot}";
}
@@ -0,0 +1,99 @@
using System.Collections.Generic;
using Lumina.Excel;
using Lumina.Excel.Sheets;
using Lumina.Text.ReadOnly;
namespace AetherBags.Inventory;
public class InventoryNotificationState
{
private readonly Dictionary<InventoryNotificationType, InventoryNotificationInfo> notificationCache;
public InventoryNotificationState()
{
var addonSheet = Services.DataManager.GetExcelSheet<Addon>();
notificationCache = new Dictionary<InventoryNotificationType, InventoryNotificationInfo>
{
{ InventoryNotificationType.Sell, new InventoryNotificationInfo(addonSheet.GetRow(530).Text, addonSheet.GetRow(3576).Text) },
{ InventoryNotificationType.Trade, new InventoryNotificationInfo(addonSheet.GetRow(531).Text, addonSheet.GetRow(3572).Text) },
{ InventoryNotificationType.Letters, new InventoryNotificationInfo(addonSheet.GetRow(549).Text, addonSheet.GetRow(3575).Text) },
{ InventoryNotificationType.Retainer, new InventoryNotificationInfo(addonSheet.GetRow(532).Text, addonSheet.GetRow(3573).Text) },
{ InventoryNotificationType.RetainerEquip, new InventoryNotificationInfo(addonSheet.GetRow(778).Text, addonSheet.GetRow(3585).Text) },
{ InventoryNotificationType.Equip, new InventoryNotificationInfo(addonSheet.GetRow(538).Text, addonSheet.GetRow(3577).Text) },
{ InventoryNotificationType.Armory, new InventoryNotificationInfo(addonSheet.GetRow(775).Text, addonSheet.GetRow(3578).Text) },
{ InventoryNotificationType.Markets, new InventoryNotificationInfo(addonSheet.GetRow(548).Text, addonSheet.GetRow(3574).Text) },
{ InventoryNotificationType.Trade2, new InventoryNotificationInfo(addonSheet.GetRow(531).Text, addonSheet.GetRow(3572).Text) },
{ InventoryNotificationType.CompanyChest, new InventoryNotificationInfo(addonSheet.GetRow(776).Text, addonSheet.GetRow(3579).Text) },
{ InventoryNotificationType.Exterior, new InventoryNotificationInfo(addonSheet.GetRow(3583).Text, addonSheet.GetRow(3581).Text) },
{ InventoryNotificationType.Interior, new InventoryNotificationInfo(addonSheet.GetRow(3584).Text, addonSheet.GetRow(3582).Text) },
{ InventoryNotificationType.Layout, new InventoryNotificationInfo(addonSheet.GetRow(6237).Text, addonSheet.GetRow(3580).Text) },
{ InventoryNotificationType.Plant, new InventoryNotificationInfo(addonSheet.GetRow(6416).Text, addonSheet.GetRow(6418).Text) },
{ InventoryNotificationType.Fertilize, new InventoryNotificationInfo(addonSheet.GetRow(6417).Text, addonSheet.GetRow(6419).Text) },
{ InventoryNotificationType.Transmutation, new InventoryNotificationInfo(addonSheet.GetRow(3911).Text, addonSheet.GetRow(3901).Text) },
{ InventoryNotificationType.Reward, new InventoryNotificationInfo(addonSheet.GetRow(6503).Text, addonSheet.GetRow(6502).Text) },
{ InventoryNotificationType.Feed, new InventoryNotificationInfo(addonSheet.GetRow(6519).Text, addonSheet.GetRow(6518).Text) },
{ InventoryNotificationType.Charge, new InventoryNotificationInfo(addonSheet.GetRow(8638).Text, addonSheet.GetRow(8637).Text) },
{ InventoryNotificationType.Convert, new InventoryNotificationInfo(addonSheet.GetRow(8647).Text, addonSheet.GetRow(8646).Text) },
{ InventoryNotificationType.Covering, new InventoryNotificationInfo(addonSheet.GetRow(9029).Text, addonSheet.GetRow(9028).Text) },
{ InventoryNotificationType.Feed2, new InventoryNotificationInfo(addonSheet.GetRow(9041).Text, addonSheet.GetRow(9040).Text) },
{ InventoryNotificationType.Manual, new InventoryNotificationInfo(addonSheet.GetRow(9044).Text, addonSheet.GetRow(9043).Text) },
{ InventoryNotificationType.Chocobo, new InventoryNotificationInfo(addonSheet.GetRow(9073).Text, addonSheet.GetRow(9072).Text) },
{ InventoryNotificationType.Outfit, new InventoryNotificationInfo(addonSheet.GetRow(6578).Text, addonSheet.GetRow(6579).Text) },
{ InventoryNotificationType.Outfit2, new InventoryNotificationInfo(addonSheet.GetRow(6578).Text, addonSheet.GetRow(6579).Text) },
{ InventoryNotificationType.Plant2, new InventoryNotificationInfo(addonSheet.GetRow(6416).Text, addonSheet.GetRow(6418).Text) },
{ InventoryNotificationType.Aquarium, new InventoryNotificationInfo(addonSheet.GetRow(6808).Text, addonSheet.GetRow(6807).Text) },
{ InventoryNotificationType.SaddleBag, new InventoryNotificationInfo(addonSheet.GetRow(891).Text, addonSheet.GetRow(892).Text) },
{ InventoryNotificationType.Donate, new InventoryNotificationInfo(addonSheet.GetRow(11595).Text, addonSheet.GetRow(11596).Text) },
{ InventoryNotificationType.Trade3, new InventoryNotificationInfo(addonSheet.GetRow(531).Text, addonSheet.GetRow(3572).Text) },
{ InventoryNotificationType.Trade4, new InventoryNotificationInfo(addonSheet.GetRow(531).Text, addonSheet.GetRow(3572).Text) },
{ InventoryNotificationType.Exterior2, new InventoryNotificationInfo(addonSheet.GetRow(3583).Text, addonSheet.GetRow(3581).Text) },
{ InventoryNotificationType.Interior2, new InventoryNotificationInfo(addonSheet.GetRow(6237).Text, addonSheet.GetRow(3580).Text) },
};
}
public InventoryNotificationInfo? GetNotificationInfo(uint openTitleId)
{
return notificationCache.GetValueOrDefault((InventoryNotificationType)openTitleId);
}
public record InventoryNotificationInfo(ReadOnlySeString Title, ReadOnlySeString Message);
}
public enum InventoryNotificationType : uint
{
None = 0,
Sell = 1,
Trade = 2,
Letters = 3,
Retainer = 4,
RetainerEquip = 5,
Equip = 6,
Armory = 7,
Markets = 8,
Trade2 = 9,
CompanyChest = 10,
Exterior = 11,
Interior = 12,
Layout = 13,
Plant = 14,
Fertilize = 15,
Transmutation = 16,
Reward = 17,
Feed = 18,
Charge = 19,
Convert = 20,
Covering = 21,
Feed2 = 22,
Manual = 23,
Chocobo = 24,
Outfit = 25,
Outfit2 = 26,
Plant2 = 27,
Aquarium = 28,
SaddleBag = 29,
Donate = 30,
Trade3 = 31,
Trade4 = 32,
Exterior2 = 33,
Interior2 = 34
}
-22
View File
@@ -151,28 +151,6 @@
} }
} }
public override ReadOnlySeString? Tooltip {
get;
set {
field = value;
switch (value) {
case { IsEmpty: false } when !TooltipRegistered:
AddEvent(AtkEventType.DragDropRollOver, ShowTooltip);
AddEvent(AtkEventType.DragDropRollOut, HideTooltip);
TooltipRegistered = true;
break;
case null when TooltipRegistered:
RemoveEvent(AtkEventType.DragDropRollOver, ShowTooltip);
RemoveEvent(AtkEventType.DragDropRollOut, HideTooltip);
TooltipRegistered = false;
break;
}
}
}
private void DragDropInsertHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) { private void DragDropInsertHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
atkEvent->SetEventIsHandled(); atkEvent->SetEventIsHandled();
@@ -20,7 +20,7 @@ public class TextInputWithHintNode : SimpleComponentNode {
TexturePath = "ui/uld/CircleButtons.tex", TexturePath = "ui/uld/CircleButtons.tex",
TextureCoordinates = new Vector2(112.0f, 84.0f), TextureCoordinates = new Vector2(112.0f, 84.0f),
TextureSize = new Vector2(28.0f, 28.0f), TextureSize = new Vector2(28.0f, 28.0f),
Tooltip = new SeStringBuilder() TextTooltip = new SeStringBuilder()
.Append("Supports Regex Search") .Append("Supports Regex Search")
.AppendNewLine() .AppendNewLine()
.Append("Start input with '$' to search by description") .Append("Start input with '$' to search by description")
@@ -5,6 +5,7 @@ using AetherBags.Helpers;
using AetherBags.Inventory; using AetherBags.Inventory;
using AetherBags.Nodes.Layout; using AetherBags.Nodes.Layout;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes; using KamiToolKit.Classes;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
@@ -74,7 +75,7 @@ public class InventoryCategoryNode : SimpleComponentNode
_categoryNameTextNode.String = _fullHeaderText; _categoryNameTextNode.String = _fullHeaderText;
_categoryNameTextNode.TextColor = value.Category.Color; _categoryNameTextNode.TextColor = value.Category.Color;
_categoryNameTextNode.TooltipString = value.Category.Description; _categoryNameTextNode.TextTooltip = value.Category.Description;
UpdateItemGrid(); UpdateItemGrid();
RecalculateSize(); RecalculateSize();
@@ -209,7 +210,7 @@ public class InventoryCategoryNode : SimpleComponentNode
CreateInventoryDragDropNode); CreateInventoryDragDropNode);
} }
private InventoryDragDropNode CreateInventoryDragDropNode(ItemInfo data) private unsafe InventoryDragDropNode CreateInventoryDragDropNode(ItemInfo data)
{ {
InventoryItem item = data.Item; InventoryItem item = data.Item;
@@ -228,65 +229,41 @@ public class InventoryCategoryNode : SimpleComponentNode
}, },
IsClickable = true, IsClickable = true,
OnEnd = _ => System.AddonInventoryWindow.ManualInventoryRefresh(), OnEnd = _ => System.AddonInventoryWindow.ManualInventoryRefresh(),
OnPayloadAccepted = (n, p) => OnPayloadAccepted(n, p, data), OnPayloadAccepted = (node, payload) => OnPayloadAccepted(node, payload, data),
OnRollOver = n => OnRollOver = node =>
{ {
BeginHeaderHover(); BeginHeaderHover();
n.ShowInventoryItemTooltip(item.Container, item.Slot); node.ShowInventoryItemTooltip(item.Container, item.Slot);
}, },
OnRollOut = n => OnRollOut = node =>
{ {
EndHeaderHover(); EndHeaderHover();
n.HideTooltip();
ushort addonId = RaptureAtkUnitManager.Instance()->GetAddonByNode(node)->Id;
AtkStage.Instance()->TooltipManager.HideTooltip(addonId);
}, },
ItemInfo = data ItemInfo = data
}; };
} }
private void OnPayloadAccepted(DragDropNode node, DragDropPayload payload, ItemInfo targetItemInfo) private void OnPayloadAccepted(DragDropNode _, DragDropPayload payload, ItemInfo targetItemInfo)
{ {
if (payload.Type != DragDropType.Item && payload.Type != DragDropType.Inventory_Item) 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; return;
var (sourceContainer, sourceSlot) = ResolveSourceFromPayload(payload); InventoryLocation sourceLocation = payload.InventoryLocation;
if (sourceContainer == 0) if (!sourceLocation.IsValid)
{ {
Services.Logger.Warning($"[OnPayload] Could not resolve source from payload"); Services.Logger.Warning($"[OnPayload] Could not resolve source from payload");
return; return;
} }
InventoryType targetContainer = targetItemInfo.Item.Container; InventoryLocation targetLocation = new InventoryLocation(targetItemInfo.Item.Container, (ushort)targetItemInfo.Item.Slot);
ushort targetSlot = (ushort)targetItemInfo.Item.Slot;
Services.Logger.Debug($"[OnPayload] Moving {sourceContainer}@{sourceSlot} -> {targetContainer}@{targetSlot}"); Services.Logger.Debug($"[OnPayload] Moving {sourceLocation.ToString()} -> {targetLocation.ToString()}");
InventoryMoveHelper.MoveItem(sourceContainer, sourceSlot, targetContainer, targetSlot); InventoryMoveHelper.MoveItem(sourceLocation.Container, sourceLocation.Slot, targetLocation.Container, targetLocation.Slot);
}
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);
} }
} }
@@ -0,0 +1,134 @@
using System.Collections.Generic;
using System.Numerics;
using AetherBags.Inventory;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit;
using KamiToolKit.Classes;
using KamiToolKit.Classes.Timelines;
using KamiToolKit.Nodes;
using Lumina.Excel;
using Lumina.Excel.Sheets;
using Lumina.Text;
using Lumina.Text.ReadOnly;
namespace AetherBags.Nodes.Inventory;
public sealed class InventoryNotificationNode : SimpleComponentNode
{
private readonly SimpleNineGridNode glowNode;
private readonly TextNode titleTextNode;
private readonly TextNode messageTextNode;
private static readonly InventoryNotificationState NotificationState = new();
public InventoryNotificationNode()
{
AddTimeline(ParentLabels);
glowNode = new SimpleNineGridNode {
TexturePath = "ui/uld/Inventory.tex",
TextureSize = new Vector2(56.0f, 56.0f),
TextureCoordinates = new Vector2(88.0f, 0.0f),
TopOffset = 10,
BottomOffset = 10,
LeftOffset = 26,
RightOffset = 26,
};
glowNode.AttachNode(this);
glowNode.AddTimeline(GlowKeyFrames);
titleTextNode = new TextNode
{
Position = new Vector2(0, 10f),
FontType = FontType.MiedingerMed,
FontSize = 18,
TextColor = ColorHelper.GetColor(50),
TextOutlineColor = ColorHelper.GetColor(37),
TextFlags = TextFlags.Edge,
AlignmentType = AlignmentType.Center,
};
titleTextNode.AttachNode(this);
titleTextNode.AddTimeline(TextKeyFrames);
messageTextNode = new TextNode
{
Position = new Vector2(0, -10f),
FontType = FontType.Axis,
FontSize = 14,
TextColor = ColorHelper.GetColor(50),
TextOutlineColor = ColorHelper.GetColor(37),
TextFlags = TextFlags.Edge,
AlignmentType = AlignmentType.Center,
};
messageTextNode.AttachNode(this);
messageTextNode.AddTimeline(TextKeyFrames);
Timeline?.PlayAnimation(17);
}
protected override void OnSizeChanged()
{
base.OnSizeChanged();
glowNode.Size = Size with { Y = 40 };
titleTextNode.Size = Size with { Y = 20 };
messageTextNode.Size = Size with { Y = 16 };
}
public InventoryNotificationType NotificationType
{
get;
set
{
field = value;
if (value == InventoryNotificationType.None)
{
titleTextNode.String = string.Empty;
messageTextNode.String = string.Empty;
Timeline?.PlayAnimation(17); // Hide
}
else
{
var info = NotificationState.GetNotificationInfo((uint)value);
if (info != null)
{
titleTextNode.SeString = info.Title;
messageTextNode.SeString = info.Message;
Timeline?.PlayAnimation(101); // Show
}
}
}
} = InventoryNotificationType.None;
// Future Zeff, this always goes on a parent
private Timeline ParentLabels => new TimelineBuilder()
.BeginFrameSet(1, 59)
.AddLabel(1, 17, AtkTimelineJumpBehavior.PlayOnce, 0)
.AddLabel(10, 101, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(25, 102, AtkTimelineJumpBehavior.Start, 0)
.AddLabel(59, 0, AtkTimelineJumpBehavior.LoopForever, 102)
.EndFrameSet()
.Build();
// Future Zeff, this always goes on a child
private Timeline GlowKeyFrames => new TimelineBuilder().BeginFrameSet(15, 59)
.AddFrame(10, scale: new Vector2(1.4f, 1.0f), alpha: 0, addColor: new Vector3(128, 128, 128))
.AddFrame(15, scale: new Vector2(1.0f, 1.0f), alpha: 255, addColor: new Vector3(128, 128, 128))
.AddFrame(21, scale: new Vector2(1.0f, 1.0f), alpha: 255, addColor: new Vector3(0, 0, 0))
.AddFrame(40, scale: new Vector2(1.0f, 1.0f), alpha: 255, addColor: new Vector3(0, 0, 0))
.AddFrame(46, scale: new Vector2(1.0f, 1.0f), alpha: 255, addColor: new Vector3(10, 10, 10))
.AddFrame(59, scale: new Vector2(1.0f, 1.0f), alpha: 255, addColor: new Vector3(0, 0, 0))
.EndFrameSet()
.Build();
// Future Zeff, this always goes on a child
private Timeline TextKeyFrames => new TimelineBuilder().BeginFrameSet(15, 59)
.AddFrame(15, alpha: 0, addColor: new Vector3(128, 128, 128))
.AddFrame(18, alpha: 255, addColor: new Vector3(64, 64, 64))
.AddFrame(25, alpha: 255, addColor: new Vector3(0, 0, 0))
.AddFrame(40, alpha: 255, addColor: new Vector3(0, 0, 0))
.AddFrame(46, alpha: 255, addColor: new Vector3(64, 64, 64))
.AddFrame(59, alpha: 255, addColor: new Vector3(0, 0, 0))
.EndFrameSet()
.Build();
}
+1
View File
@@ -13,6 +13,7 @@ public class Services
[PluginService] public static IDataManager DataManager { get; set; } = null!; [PluginService] public static IDataManager DataManager { get; set; } = null!;
[PluginService] public static IDalamudPluginInterface PluginInterface { get; private set; } = null!; [PluginService] public static IDalamudPluginInterface PluginInterface { get; private set; } = null!;
[PluginService] public static IFramework Framework { get; private set; } = null!; [PluginService] public static IFramework Framework { get; private set; } = null!;
[PluginService] public static IGameGui GameGui { get; private set; } = null!;
[PluginService] public static IKeyState KeyState { get; private set; } = null!; [PluginService] public static IKeyState KeyState { get; private set; } = null!;
[PluginService] public static IPluginLog Logger { get; private set; } = null!; [PluginService] public static IPluginLog Logger { get; private set; } = null!;
[PluginService] public static INotificationManager NotificationManager { get; private set; } = null!; [PluginService] public static INotificationManager NotificationManager { get; private set; } = null!;