From 2f7977d144c5a136d0866defb46c929280ebcb7f Mon Sep 17 00:00:00 2001 From: Zeffuro Date: Mon, 29 Dec 2025 09:02:57 +0100 Subject: [PATCH 1/4] Cleanup --- AetherBags/Inventory/InventoryContextState.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/AetherBags/Inventory/InventoryContextState.cs b/AetherBags/Inventory/InventoryContextState.cs index f81a6ef..88c8a6b 100644 --- a/AetherBags/Inventory/InventoryContextState.cs +++ b/AetherBags/Inventory/InventoryContextState.cs @@ -15,8 +15,8 @@ public static unsafe class InventoryContextState public static void RefreshMaps() { - EligibleSlots. Clear(); - VisualLocationMap. Clear(); + EligibleSlots.Clear(); + VisualLocationMap.Clear(); var sorter = ItemOrderModule.Instance()->InventorySorter; if (sorter == null) return; @@ -31,7 +31,7 @@ public static unsafe class InventoryContextState for (int displayIdx = 0; displayIdx < 140; displayIdx++) { - var entry = sorter->Items[displayIdx]. Value; + var entry = sorter->Items[displayIdx].Value; if (entry == null) continue; int realPage = entry->Page; @@ -46,7 +46,7 @@ public static unsafe class InventoryContextState if (hasContext && invArray != null) { var itemData = invArray->Items[displayIdx]; - if (itemData. IconId == 0) continue; + if (itemData.IconId == 0) continue; bool eligible = itemData.ItemFlags.MirageFlag == 0; if (eligible) From c3b61e80456d7766ca7c69674da8959c9ef9a951 Mon Sep 17 00:00:00 2001 From: Zeffuro Date: Mon, 29 Dec 2025 09:28:45 +0100 Subject: [PATCH 2/4] Implement CommandHandler --- AetherBags/Addons/AddonInventoryWindow.cs | 10 ++ AetherBags/Commands/CommandHandler.cs | 134 ++++++++++++++++++++++ AetherBags/Plugin.cs | 39 +------ AetherBags/Services.cs | 1 + 4 files changed, 150 insertions(+), 34 deletions(-) create mode 100644 AetherBags/Commands/CommandHandler.cs diff --git a/AetherBags/Addons/AddonInventoryWindow.cs b/AetherBags/Addons/AddonInventoryWindow.cs index b56d771..93eeb92 100644 --- a/AetherBags/Addons/AddonInventoryWindow.cs +++ b/AetherBags/Addons/AddonInventoryWindow.cs @@ -297,6 +297,16 @@ public class AddonInventoryWindow : NativeAddon }, delayTicks: 1); } + public void SetSearchText(string searchText) + { + Services.Framework.RunOnTick(() => + { + if(IsOpen) _searchInputNode.SearchString = searchText; + RefreshCategoriesCore(autosize: true); + }, delayTicks: 1); + } + + protected override unsafe void OnFinalize(AtkUnitBase* addon) { Services.AddonLifecycle.UnregisterListener(OnInventoryUpdate); diff --git a/AetherBags/Commands/CommandHandler.cs b/AetherBags/Commands/CommandHandler.cs new file mode 100644 index 0000000..2cecabd --- /dev/null +++ b/AetherBags/Commands/CommandHandler.cs @@ -0,0 +1,134 @@ +using System; +using AetherBags.Helpers; +using Dalamud.Game.Command; + +namespace AetherBags.Commands; + +public class CommandHandler : IDisposable +{ + private const string MainCommand = "/aetherbags"; + private const string ShortCommand = "/ab"; + private const string HelpDescription = "Opens your inventory. Use '/ab help' for more options."; + + public CommandHandler() + { + Services.CommandManager.AddHandler(MainCommand, new CommandInfo(OnCommand) + { + DisplayOrder = 1, + ShowInHelp = true, + HelpMessage = HelpDescription + }); + + Services.CommandManager.AddHandler(ShortCommand, new CommandInfo(OnCommand) + { + DisplayOrder = 2, + ShowInHelp = true, + HelpMessage = HelpDescription + }); + } + + private void OnCommand(string command, string args) + { + var argsParts = args.Trim().Split(' ', 2, StringSplitOptions.RemoveEmptyEntries); + var subCommand = argsParts.Length > 0 ? argsParts[0].ToLowerInvariant() : string.Empty; + var subArgs = argsParts.Length > 1 ? argsParts[1] : string.Empty; + + switch (subCommand) + { + case "": + case "toggle": + System.AddonInventoryWindow.Toggle(); + break; + + case "config": + case "settings": + System.AddonConfigurationWindow.Toggle(); + break; + + case "show": + case "open": + System.AddonInventoryWindow.Open(); + break; + + case "hide": + case "close": + System.AddonInventoryWindow.Close(); + break; + + case "refresh": + System.AddonInventoryWindow.ManualInventoryRefresh(); + PrintChat("Inventory refreshed."); + break; + + case "search": + HandleSearch(subArgs); + break; + + case "import-sk": + ImportExportResetHelper.TryImportSortaKindaFromClipboard(true); + System.AddonInventoryWindow.ManualInventoryRefresh(); + break; + + case "export": + HandleExport(); + break; + + case "help": + case "?": + PrintHelp(); + break; + + default: + PrintChat($"Unknown command: {subCommand}. Use '/ab help' for available commands."); + break; + } + } + + private void HandleSearch(string searchTerm) + { + if (!System.AddonInventoryWindow.IsOpen) + { + System.AddonInventoryWindow.Open(); + } + + if (!string.IsNullOrWhiteSpace(searchTerm)) + { + System.AddonInventoryWindow.SetSearchText(searchTerm); + } + + PrintChat($"Searching for: {searchTerm}"); + } + + private void HandleExport() + { + // TODO: Implement export functionality + PrintChat("Export functionality coming soon!"); + } + + private void PrintHelp() + { + var helpText = @"AetherBags Commands: + /ab - Toggle inventory window + /ab config - Toggle configuration window + /ab show - Open inventory window + /ab hide - Close inventory window + /ab refresh - Force refresh inventory + /ab search - Open and search for items + /ab import-sk - Import from SortaKinda clipboard + /ab export - Export config to clipboard + /ab help - Show this help message"; + + PrintChat(helpText); + } + + private static void PrintChat(string message) + { + Services.ChatGui.Print($"[AetherBags] {message}"); + } + + public void Dispose() + { + Services.CommandManager.RemoveHandler(MainCommand); + Services.CommandManager.RemoveHandler(ShortCommand); + } +} \ No newline at end of file diff --git a/AetherBags/Plugin.cs b/AetherBags/Plugin.cs index b22e51a..2ad2522 100644 --- a/AetherBags/Plugin.cs +++ b/AetherBags/Plugin.cs @@ -2,6 +2,7 @@ using System; using System.Numerics; using AetherBags.AddonLifecycles; using AetherBags.Addons; +using AetherBags.Commands; using AetherBags.Helpers; using AetherBags.Hooks; using Dalamud.Plugin; @@ -16,6 +17,7 @@ public unsafe class Plugin : IDalamudPlugin { private static string HelpDescription => "Opens your inventory."; + private readonly CommandHandler _commandHandler; private readonly InventoryHooks _inventoryHooks; private readonly InventoryLifecycles _inventoryLifecycles; @@ -46,18 +48,8 @@ public unsafe class Plugin : IDalamudPlugin Services.PluginInterface.UiBuilder.OpenMainUi += System.AddonInventoryWindow.Toggle; Services.PluginInterface.UiBuilder.OpenConfigUi += System.AddonConfigurationWindow.Toggle; - Services.CommandManager.AddHandler("/aetherbags", new CommandInfo(OnCommand) - { - DisplayOrder = 1, - ShowInHelp = true, - HelpMessage = HelpDescription - }); - Services.CommandManager.AddHandler("/ab", new CommandInfo(OnCommand) - { - DisplayOrder = 2, - ShowInHelp = true, - HelpMessage = HelpDescription - }); + _commandHandler = new CommandHandler(); + Services.ClientState.Login += OnLogin; Services.ClientState.Logout += OnLogout; @@ -76,8 +68,7 @@ public unsafe class Plugin : IDalamudPlugin Services.ClientState.Login -= OnLogin; Services.ClientState.Logout -= OnLogout; - Services.CommandManager.RemoveHandler("/aetherbags"); - Services.CommandManager.RemoveHandler("/ab"); + _commandHandler.Dispose(); System.AddonInventoryWindow.Dispose(); System.AddonConfigurationWindow.Dispose(); @@ -88,26 +79,6 @@ public unsafe class Plugin : IDalamudPlugin _inventoryLifecycles.Dispose(); } - private void OnCommand(string command, string args) - { - switch (command) - { - case "/aetherbags": - case "/ab": - if(args.Length == 0) - System.AddonInventoryWindow.Toggle(); - if(args == "config") - System.AddonConfigurationWindow.Toggle(); - if (args == "import-sk") - { - // Manually import from SortaKinda for testing until we have a proper config window - ImportExportResetHelper.TryImportSortaKindaFromClipboard(true); - System.AddonInventoryWindow.ManualInventoryRefresh(); - } - break; - } - } - private void OnLogin() { System.Config = Util.LoadConfigOrDefault(); diff --git a/AetherBags/Services.cs b/AetherBags/Services.cs index 1569de2..7abf28a 100644 --- a/AetherBags/Services.cs +++ b/AetherBags/Services.cs @@ -8,6 +8,7 @@ namespace AetherBags; public class Services { [PluginService] public static IAddonLifecycle AddonLifecycle { get; set; } = null!; + [PluginService] public static IChatGui ChatGui { get; set; } = null!; [PluginService] public static IClientState ClientState { get; private set; } = null!; [PluginService] public static ICommandManager CommandManager { get; private set; } = null!; [PluginService] public static IDataManager DataManager { get; set; } = null!; From 2d254701d7a93be7f4dd5fee4303b472c5888578 Mon Sep 17 00:00:00 2001 From: Zeffuro Date: Mon, 29 Dec 2025 09:54:35 +0100 Subject: [PATCH 3/4] Category Amount and more State Filters --- AetherBags/Commands/CommandHandler.cs | 15 +++++++++++++-- AetherBags/Configuration/CategorySettings.cs | 4 ++++ AetherBags/Configuration/GeneralSettings.cs | 1 + AetherBags/Inventory/ItemInfo.cs | 6 ++++++ AetherBags/Inventory/UserCategoryMatcher.cs | 4 ++++ .../CategoryDefinitionConfigurationNode.cs | 16 ++++++++++++++++ .../Layout/LayoutConfigurationNode.cs | 14 ++++++++++++++ .../Nodes/Inventory/InventoryCategoryNode.cs | 4 +++- 8 files changed, 61 insertions(+), 3 deletions(-) diff --git a/AetherBags/Commands/CommandHandler.cs b/AetherBags/Commands/CommandHandler.cs index 2cecabd..7c97136 100644 --- a/AetherBags/Commands/CommandHandler.cs +++ b/AetherBags/Commands/CommandHandler.cs @@ -73,6 +73,16 @@ public class CommandHandler : IDisposable HandleExport(); break; + case "import": + ImportExportResetHelper.TryImportConfigFromClipboard(System.Config); + System.AddonInventoryWindow.ManualInventoryRefresh(); + break; + + case "reset": + ImportExportResetHelper.TryResetConfig(); + System.AddonInventoryWindow.ManualInventoryRefresh(); + break; + case "help": case "?": PrintHelp(); @@ -101,8 +111,7 @@ public class CommandHandler : IDisposable private void HandleExport() { - // TODO: Implement export functionality - PrintChat("Export functionality coming soon!"); + ImportExportResetHelper.TryExportConfigToClipboard(System.Config); } private void PrintHelp() @@ -114,8 +123,10 @@ public class CommandHandler : IDisposable /ab hide - Close inventory window /ab refresh - Force refresh inventory /ab search - Open and search for items + /ab import - Import config from clipboard (hold Shift) /ab import-sk - Import from SortaKinda clipboard /ab export - Export config to clipboard + /ab reset - Reset config to default /ab help - Show this help message"; PrintChat(helpText); diff --git a/AetherBags/Configuration/CategorySettings.cs b/AetherBags/Configuration/CategorySettings.cs index 1ab344b..f831eac 100644 --- a/AetherBags/Configuration/CategorySettings.cs +++ b/AetherBags/Configuration/CategorySettings.cs @@ -43,6 +43,10 @@ public class CategoryRuleSet public StateFilter Collectable { get; set; } = new(); public StateFilter Dyeable { get; set; } = new(); public StateFilter Repairable { get; set; } = new(); + public StateFilter HighQuality { get; set; } = new(); + public StateFilter Desynthesizable { get; set; } = new(); + public StateFilter Glamourable { get; set; } = new(); + public StateFilter FullySpiritbonded { get; set; } = new(); } public class RangeFilter where T : struct, IComparable diff --git a/AetherBags/Configuration/GeneralSettings.cs b/AetherBags/Configuration/GeneralSettings.cs index 00f6fb9..3215ba5 100644 --- a/AetherBags/Configuration/GeneralSettings.cs +++ b/AetherBags/Configuration/GeneralSettings.cs @@ -13,6 +13,7 @@ public class GeneralSettings public bool CompactStableInsert { get; set; } = true; public bool OpenWithGameInventory { get; set; } = true; public bool HideGameInventory { get; set; } = false; + public bool ShowCategoryItemCount { get; set; } = false; } public enum InventoryStackMode : byte diff --git a/AetherBags/Inventory/ItemInfo.cs b/AetherBags/Inventory/ItemInfo.cs index 1893dde..3083413 100644 --- a/AetherBags/Inventory/ItemInfo.cs +++ b/AetherBags/Inventory/ItemInfo.cs @@ -57,6 +57,12 @@ public sealed class ItemInfo : IEquatable public bool IsDyeable => Row.DyeCount > 0; public bool IsRepairable => Row.ItemRepair.RowId != 0; + public bool IsHq => Item.Flags.HasFlag(InventoryItem.ItemFlags.HighQuality); + public bool IsDesynthesizable => Row.Desynth > 0; + public bool IsCraftable => Row.ItemAction.RowId != 0 || Row.CanBeHq; // Simplified check + public bool IsGlamourable => Row.IsGlamorous; + public bool IsSpiritbonded => Item.SpiritbondOrCollectability >= 10000; // 100% = 10000 + private string Description => _description ??= Row.Description.ToString(); public InventoryMappedLocation VisualLocation => diff --git a/AetherBags/Inventory/UserCategoryMatcher.cs b/AetherBags/Inventory/UserCategoryMatcher.cs index 7f84052..4e2e29b 100644 --- a/AetherBags/Inventory/UserCategoryMatcher.cs +++ b/AetherBags/Inventory/UserCategoryMatcher.cs @@ -73,6 +73,10 @@ internal static class UserCategoryMatcher if (!MatchesToggle(rules.Collectable, item.IsCollectable)) return false; if (!MatchesToggle(rules.Dyeable, item.IsDyeable)) return false; if (!MatchesToggle(rules.Repairable, item.IsRepairable)) return false; + if (!MatchesToggle(rules.HighQuality, item.IsHq)) return false; + if (!MatchesToggle(rules.Desynthesizable, item.IsDesynthesizable)) return false; + if (!MatchesToggle(rules.Glamourable, item.IsGlamourable)) return false; + if (!MatchesToggle(rules.FullySpiritbonded, item.IsSpiritbonded)) return false; return true; } diff --git a/AetherBags/Nodes/Configuration/Category/CategoryDefinitionConfigurationNode.cs b/AetherBags/Nodes/Configuration/Category/CategoryDefinitionConfigurationNode.cs index 968f8f9..29666ec 100644 --- a/AetherBags/Nodes/Configuration/Category/CategoryDefinitionConfigurationNode.cs +++ b/AetherBags/Nodes/Configuration/Category/CategoryDefinitionConfigurationNode.cs @@ -40,6 +40,10 @@ public sealed class CategoryDefinitionConfigurationNode : VerticalListNode private readonly StateFilterRowNode _collectableFilter; private readonly StateFilterRowNode _dyeableFilter; private readonly StateFilterRowNode _repairableFilter; + private readonly StateFilterRowNode _hqFilter; + private readonly StateFilterRowNode _desynthFilter; + private readonly StateFilterRowNode _glamourFilter; + private readonly StateFilterRowNode _spiritbondFilter; private readonly UintListEditorNode _allowedItemIdsEditor; private readonly StringListEditorNode _allowedNamePatternsEditor; @@ -241,6 +245,18 @@ public sealed class CategoryDefinitionConfigurationNode : VerticalListNode _repairableFilter = new StateFilterRowNode("Repairable", CategoryDefinition.Rules.Repairable, NotifyChanged); AddNode(_repairableFilter); + _hqFilter = new StateFilterRowNode("High Quality", CategoryDefinition.Rules.HighQuality, NotifyChanged); + AddNode(_hqFilter); + + _desynthFilter = new StateFilterRowNode("Desynthesizable", CategoryDefinition.Rules.Desynthesizable, NotifyChanged); + AddNode(_desynthFilter); + + _glamourFilter = new StateFilterRowNode("Glamourable", CategoryDefinition.Rules.Glamourable, NotifyChanged); + AddNode(_glamourFilter); + + _spiritbondFilter = new StateFilterRowNode("Spiritbonded", CategoryDefinition.Rules.FullySpiritbonded, NotifyChanged); + AddNode(_spiritbondFilter); + AddNode(CreateSectionHeader("List Filters")); _allowedItemIdsEditor = new UintListEditorNode( diff --git a/AetherBags/Nodes/Configuration/Layout/LayoutConfigurationNode.cs b/AetherBags/Nodes/Configuration/Layout/LayoutConfigurationNode.cs index c7d3e0e..0669999 100644 --- a/AetherBags/Nodes/Configuration/Layout/LayoutConfigurationNode.cs +++ b/AetherBags/Nodes/Configuration/Layout/LayoutConfigurationNode.cs @@ -24,6 +24,20 @@ internal class LayoutConfigurationNode : TabbedVerticalListNode AddTab(1); + var showCategoryItemAmountCheckboxNode = new CheckboxNode + { + Size = Size with { Y = 18 }, + IsVisible = true, + String = "Show Category Item Amount", + IsChecked = config.ShowCategoryItemCount, + OnClick = isChecked => + { + config.ShowCategoryItemCount = isChecked; + System.AddonInventoryWindow.ManualInventoryRefresh(); + } + }; + AddNode(showCategoryItemAmountCheckboxNode); + var compactPackingCheckboxNode = new CheckboxNode { Size = Size with { Y = 18 }, diff --git a/AetherBags/Nodes/Inventory/InventoryCategoryNode.cs b/AetherBags/Nodes/Inventory/InventoryCategoryNode.cs index 3606974..12b6180 100644 --- a/AetherBags/Nodes/Inventory/InventoryCategoryNode.cs +++ b/AetherBags/Nodes/Inventory/InventoryCategoryNode.cs @@ -72,7 +72,9 @@ public class InventoryCategoryNode : SimpleComponentNode { field = value; - _fullHeaderText = value.Category.Name; + _fullHeaderText = System.Config.General.ShowCategoryItemCount + ? $"{value.Category.Name} ({value.Items.Count})" + : value.Category.Name; _categoryNameTextNode.String = _fullHeaderText; _categoryNameTextNode.TextColor = value.Category.Color; From 399c80d4058e8c089585f14ba7adf9aa1b6732c5 Mon Sep 17 00:00:00 2001 From: Zeffuro Date: Mon, 29 Dec 2025 10:27:14 +0100 Subject: [PATCH 4/4] Item linking, import/export fixed, stats --- AetherBags/Commands/CommandHandler.cs | 9 ++++++- AetherBags/Configuration/GeneralSettings.cs | 1 + AetherBags/Helpers/ImportExportResetHelper.cs | 8 +----- AetherBags/Inventory/InventoryState.cs | 26 +++++++++++++++++++ AetherBags/Inventory/InventoryStats.cs | 12 +++++++++ .../General/FunctionalConfigurationNode.cs | 13 ++++++++++ .../Nodes/Inventory/InventoryDragDropNode.cs | 11 +++++++- 7 files changed, 71 insertions(+), 9 deletions(-) create mode 100644 AetherBags/Inventory/InventoryStats.cs diff --git a/AetherBags/Commands/CommandHandler.cs b/AetherBags/Commands/CommandHandler.cs index 7c97136..3d36d4e 100644 --- a/AetherBags/Commands/CommandHandler.cs +++ b/AetherBags/Commands/CommandHandler.cs @@ -1,5 +1,6 @@ using System; using AetherBags.Helpers; +using AetherBags.Inventory; using Dalamud.Game.Command; namespace AetherBags.Commands; @@ -74,7 +75,7 @@ public class CommandHandler : IDisposable break; case "import": - ImportExportResetHelper.TryImportConfigFromClipboard(System.Config); + ImportExportResetHelper.TryImportConfigFromClipboard(); System.AddonInventoryWindow.ManualInventoryRefresh(); break; @@ -83,6 +84,12 @@ public class CommandHandler : IDisposable System.AddonInventoryWindow.ManualInventoryRefresh(); break; + case "count": + case "stats": + var stats = InventoryState.GetInventoryStats(); + PrintChat($"{stats.UsedSlots}/{stats.TotalSlots} slots used ({stats.UsagePercent:F0}%) | {stats.TotalItems} unique items | {stats.CategoryCount} categories"); + break; + case "help": case "?": PrintHelp(); diff --git a/AetherBags/Configuration/GeneralSettings.cs b/AetherBags/Configuration/GeneralSettings.cs index 3215ba5..72296a8 100644 --- a/AetherBags/Configuration/GeneralSettings.cs +++ b/AetherBags/Configuration/GeneralSettings.cs @@ -14,6 +14,7 @@ public class GeneralSettings public bool OpenWithGameInventory { get; set; } = true; public bool HideGameInventory { get; set; } = false; public bool ShowCategoryItemCount { get; set; } = false; + public bool LinkItemEnabled { get; set; } = false; } public enum InventoryStackMode : byte diff --git a/AetherBags/Helpers/ImportExportResetHelper.cs b/AetherBags/Helpers/ImportExportResetHelper.cs index b667f58..1b7a2a2 100644 --- a/AetherBags/Helpers/ImportExportResetHelper.cs +++ b/AetherBags/Helpers/ImportExportResetHelper.cs @@ -8,11 +8,8 @@ using Dalamud.Interface.ImGuiNotification; namespace AetherBags.Helpers; public abstract class ImportExportResetHelper { - public static void TryImportConfigFromClipboard(SystemConfiguration currentOverlayConfig) + public static void TryImportConfigFromClipboard() { - if (!Services.KeyState[VirtualKey.SHIFT]) - return; - var clipboard = ImGui.GetClipboardText(); var notification = new Notification { Content = "Configuration imported from clipboard.", Type = NotificationType.Success }; @@ -66,9 +63,6 @@ public abstract class ImportExportResetHelper { public static void TryImportSortaKindaFromClipboard(bool replaceExisting) { - //if (!Services.KeyState[VirtualKey.SHIFT]) - // return; - var notification = new Notification { Content = "SortaKinda categories imported.", Type = NotificationType.Success }; if (!SortaKindaImportExport.TryImportFromClipboard(System.Config, replaceExisting, out var error)) diff --git a/AetherBags/Inventory/InventoryState.cs b/AetherBags/Inventory/InventoryState.cs index 1fc3982..0d354ca 100644 --- a/AetherBags/Inventory/InventoryState.cs +++ b/AetherBags/Inventory/InventoryState.cs @@ -97,6 +97,32 @@ public static unsafe class InventoryState invert); } + public static InventoryStats GetInventoryStats() + { + int totalItems = ItemInfoByKey.Count; + int totalQuantity = 0; + + foreach (var kvp in ItemInfoByKey) + { + totalQuantity += kvp.Value.ItemCount; + } + + uint emptySlots = InventoryManager.Instance()->GetEmptySlotsInBag(); + const int totalSlots = 140; + + var categories = GetInventoryItemCategories(string.Empty); + int categoryCount = categories.Count; + + return new InventoryStats + { + TotalItems = totalItems, + TotalQuantity = totalQuantity, + EmptySlots = (int)emptySlots, + TotalSlots = totalSlots, + CategoryCount = categoryCount, + }; + } + public static string GetEmptyItemSlotsString() => InventoryScanner.GetEmptyItemSlotsString(); diff --git a/AetherBags/Inventory/InventoryStats.cs b/AetherBags/Inventory/InventoryStats.cs new file mode 100644 index 0000000..4f9a997 --- /dev/null +++ b/AetherBags/Inventory/InventoryStats.cs @@ -0,0 +1,12 @@ +namespace AetherBags.Inventory; + +public readonly struct InventoryStats +{ + public int TotalItems { get; init; } + public int TotalQuantity { get; init; } + public int EmptySlots { get; init; } + public int TotalSlots { get; init; } + public int CategoryCount { get; init; } + public int UsedSlots => TotalSlots - EmptySlots; + public float UsagePercent => TotalSlots > 0 ? (float)UsedSlots / TotalSlots * 100f : 0f; +} \ No newline at end of file diff --git a/AetherBags/Nodes/Configuration/General/FunctionalConfigurationNode.cs b/AetherBags/Nodes/Configuration/General/FunctionalConfigurationNode.cs index 936548a..19fc24f 100644 --- a/AetherBags/Nodes/Configuration/General/FunctionalConfigurationNode.cs +++ b/AetherBags/Nodes/Configuration/General/FunctionalConfigurationNode.cs @@ -55,6 +55,19 @@ internal sealed class FunctionalConfigurationNode : TabbedVerticalListNode AddNode(_hideDefaultBagsCheckboxNode); SubtractTab(1); + var linkItemCheckBox = new CheckboxNode + { + Size = Size with { Y = 18 }, + IsVisible = true, + String = "Allow item linking with Shift+Click", + IsChecked = config.LinkItemEnabled, + OnClick = isChecked => + { + config.LinkItemEnabled = isChecked; + } + }; + AddNode(linkItemCheckBox); + _stackDropDown = new LabeledDropdownNode { Size = new Vector2(300, 20), diff --git a/AetherBags/Nodes/Inventory/InventoryDragDropNode.cs b/AetherBags/Nodes/Inventory/InventoryDragDropNode.cs index 29b042a..a2435fb 100644 --- a/AetherBags/Nodes/Inventory/InventoryDragDropNode.cs +++ b/AetherBags/Nodes/Inventory/InventoryDragDropNode.cs @@ -1,5 +1,6 @@ using System.Numerics; using AetherBags.Inventory; +using Dalamud.Game.ClientState.Keys; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Component.GUI; @@ -41,13 +42,21 @@ public class InventoryDragDropNode : DragDropFixedNode private unsafe void OnItemMouseDown(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) { InventoryItem item = ItemInfo.Item; + if (Services.KeyState[VirtualKey.SHIFT] && atkEventData->IsLeftClick && System.Config.General.LinkItemEnabled) + { + AgentChatLog.Instance()->LinkItem(item.ItemId); + return; + } + if (!atkEventData->IsRightClick) return; AgentInventoryContext* context = AgentInventoryContext.Instance(); context->OpenForItemSlot(item.Container, item.Slot, 0, context->AddonId); } - private unsafe void OnItemClicked(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) { + private unsafe void OnItemClicked(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) + { + if (Services.KeyState[VirtualKey.SHIFT] && System.Config.General.LinkItemEnabled) return; InventoryItem item = ItemInfo.Item; if (!atkEventData->IsLeftClick) return; item.UseItem();