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

This commit is contained in:
Shawrkie Williams
2025-12-29 05:02:47 -05:00
17 changed files with 283 additions and 47 deletions
+10
View File
@@ -297,6 +297,16 @@ public class AddonInventoryWindow : NativeAddon
}, delayTicks: 1); }, 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) protected override unsafe void OnFinalize(AtkUnitBase* addon)
{ {
Services.AddonLifecycle.UnregisterListener(OnInventoryUpdate); Services.AddonLifecycle.UnregisterListener(OnInventoryUpdate);
+152
View File
@@ -0,0 +1,152 @@
using System;
using AetherBags.Helpers;
using AetherBags.Inventory;
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 "import":
ImportExportResetHelper.TryImportConfigFromClipboard();
System.AddonInventoryWindow.ManualInventoryRefresh();
break;
case "reset":
ImportExportResetHelper.TryResetConfig();
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();
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()
{
ImportExportResetHelper.TryExportConfigToClipboard(System.Config);
}
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 <term> - 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);
}
private static void PrintChat(string message)
{
Services.ChatGui.Print($"[AetherBags] {message}");
}
public void Dispose()
{
Services.CommandManager.RemoveHandler(MainCommand);
Services.CommandManager.RemoveHandler(ShortCommand);
}
}
@@ -43,6 +43,10 @@ public class CategoryRuleSet
public StateFilter Collectable { get; set; } = new(); public StateFilter Collectable { get; set; } = new();
public StateFilter Dyeable { get; set; } = new(); public StateFilter Dyeable { get; set; } = new();
public StateFilter Repairable { 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<T> where T : struct, IComparable<T> public class RangeFilter<T> where T : struct, IComparable<T>
@@ -13,6 +13,8 @@ public class GeneralSettings
public bool CompactStableInsert { get; set; } = true; public bool CompactStableInsert { get; set; } = true;
public bool OpenWithGameInventory { get; set; } = true; public bool OpenWithGameInventory { get; set; } = true;
public bool HideGameInventory { get; set; } = false; public bool HideGameInventory { get; set; } = false;
public bool ShowCategoryItemCount { get; set; } = false;
public bool LinkItemEnabled { get; set; } = false;
} }
public enum InventoryStackMode : byte public enum InventoryStackMode : byte
@@ -8,11 +8,8 @@ using Dalamud.Interface.ImGuiNotification;
namespace AetherBags.Helpers; namespace AetherBags.Helpers;
public abstract class ImportExportResetHelper { 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 clipboard = ImGui.GetClipboardText();
var notification = new Notification { Content = "Configuration imported from clipboard.", Type = NotificationType.Success }; 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) public static void TryImportSortaKindaFromClipboard(bool replaceExisting)
{ {
//if (!Services.KeyState[VirtualKey.SHIFT])
// return;
var notification = new Notification { Content = "SortaKinda categories imported.", Type = NotificationType.Success }; var notification = new Notification { Content = "SortaKinda categories imported.", Type = NotificationType.Success };
if (!SortaKindaImportExport.TryImportFromClipboard(System.Config, replaceExisting, out var error)) if (!SortaKindaImportExport.TryImportFromClipboard(System.Config, replaceExisting, out var error))
@@ -15,8 +15,8 @@ public static unsafe class InventoryContextState
public static void RefreshMaps() public static void RefreshMaps()
{ {
EligibleSlots. Clear(); EligibleSlots.Clear();
VisualLocationMap. Clear(); VisualLocationMap.Clear();
var sorter = ItemOrderModule.Instance()->InventorySorter; var sorter = ItemOrderModule.Instance()->InventorySorter;
if (sorter == null) return; if (sorter == null) return;
@@ -31,7 +31,7 @@ public static unsafe class InventoryContextState
for (int displayIdx = 0; displayIdx < 140; displayIdx++) for (int displayIdx = 0; displayIdx < 140; displayIdx++)
{ {
var entry = sorter->Items[displayIdx]. Value; var entry = sorter->Items[displayIdx].Value;
if (entry == null) continue; if (entry == null) continue;
int realPage = entry->Page; int realPage = entry->Page;
@@ -46,7 +46,7 @@ public static unsafe class InventoryContextState
if (hasContext && invArray != null) if (hasContext && invArray != null)
{ {
var itemData = invArray->Items[displayIdx]; var itemData = invArray->Items[displayIdx];
if (itemData. IconId == 0) continue; if (itemData.IconId == 0) continue;
bool eligible = itemData.ItemFlags.MirageFlag == 0; bool eligible = itemData.ItemFlags.MirageFlag == 0;
if (eligible) if (eligible)
+26
View File
@@ -97,6 +97,32 @@ public static unsafe class InventoryState
invert); 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() public static string GetEmptyItemSlotsString()
=> InventoryScanner.GetEmptyItemSlotsString(); => InventoryScanner.GetEmptyItemSlotsString();
+12
View File
@@ -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;
}
+6
View File
@@ -57,6 +57,12 @@ public sealed class ItemInfo : IEquatable<ItemInfo>
public bool IsDyeable => Row.DyeCount > 0; public bool IsDyeable => Row.DyeCount > 0;
public bool IsRepairable => Row.ItemRepair.RowId != 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(); private string Description => _description ??= Row.Description.ToString();
public InventoryMappedLocation VisualLocation => public InventoryMappedLocation VisualLocation =>
@@ -73,6 +73,10 @@ internal static class UserCategoryMatcher
if (!MatchesToggle(rules.Collectable, item.IsCollectable)) return false; if (!MatchesToggle(rules.Collectable, item.IsCollectable)) return false;
if (!MatchesToggle(rules.Dyeable, item.IsDyeable)) return false; if (!MatchesToggle(rules.Dyeable, item.IsDyeable)) return false;
if (!MatchesToggle(rules.Repairable, item.IsRepairable)) 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; return true;
} }
@@ -40,6 +40,10 @@ public sealed class CategoryDefinitionConfigurationNode : VerticalListNode
private readonly StateFilterRowNode _collectableFilter; private readonly StateFilterRowNode _collectableFilter;
private readonly StateFilterRowNode _dyeableFilter; private readonly StateFilterRowNode _dyeableFilter;
private readonly StateFilterRowNode _repairableFilter; 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 UintListEditorNode _allowedItemIdsEditor;
private readonly StringListEditorNode _allowedNamePatternsEditor; private readonly StringListEditorNode _allowedNamePatternsEditor;
@@ -241,6 +245,18 @@ public sealed class CategoryDefinitionConfigurationNode : VerticalListNode
_repairableFilter = new StateFilterRowNode("Repairable", CategoryDefinition.Rules.Repairable, NotifyChanged); _repairableFilter = new StateFilterRowNode("Repairable", CategoryDefinition.Rules.Repairable, NotifyChanged);
AddNode(_repairableFilter); 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")); AddNode(CreateSectionHeader("List Filters"));
_allowedItemIdsEditor = new UintListEditorNode( _allowedItemIdsEditor = new UintListEditorNode(
@@ -55,6 +55,19 @@ internal sealed class FunctionalConfigurationNode : TabbedVerticalListNode
AddNode(_hideDefaultBagsCheckboxNode); AddNode(_hideDefaultBagsCheckboxNode);
SubtractTab(1); 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 _stackDropDown = new LabeledDropdownNode
{ {
Size = new Vector2(300, 20), Size = new Vector2(300, 20),
@@ -24,6 +24,20 @@ internal class LayoutConfigurationNode : TabbedVerticalListNode
AddTab(1); 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 var compactPackingCheckboxNode = new CheckboxNode
{ {
Size = Size with { Y = 18 }, Size = Size with { Y = 18 },
@@ -72,7 +72,9 @@ public class InventoryCategoryNode : SimpleComponentNode
{ {
field = value; field = value;
_fullHeaderText = value.Category.Name; _fullHeaderText = System.Config.General.ShowCategoryItemCount
? $"{value.Category.Name} ({value.Items.Count})"
: value.Category.Name;
_categoryNameTextNode.String = _fullHeaderText; _categoryNameTextNode.String = _fullHeaderText;
_categoryNameTextNode.TextColor = value.Category.Color; _categoryNameTextNode.TextColor = value.Category.Color;
@@ -1,5 +1,6 @@
using System.Numerics; using System.Numerics;
using AetherBags.Inventory; using AetherBags.Inventory;
using Dalamud.Game.ClientState.Keys;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI; 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) { private unsafe void OnItemMouseDown(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
InventoryItem item = ItemInfo.Item; 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; if (!atkEventData->IsRightClick) return;
AgentInventoryContext* context = AgentInventoryContext.Instance(); AgentInventoryContext* context = AgentInventoryContext.Instance();
context->OpenForItemSlot(item.Container, item.Slot, 0, context->AddonId); 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; InventoryItem item = ItemInfo.Item;
if (!atkEventData->IsLeftClick) return; if (!atkEventData->IsLeftClick) return;
item.UseItem(); item.UseItem();
+5 -34
View File
@@ -2,6 +2,7 @@ using System;
using System.Numerics; using System.Numerics;
using AetherBags.AddonLifecycles; using AetherBags.AddonLifecycles;
using AetherBags.Addons; using AetherBags.Addons;
using AetherBags.Commands;
using AetherBags.Helpers; using AetherBags.Helpers;
using AetherBags.Hooks; using AetherBags.Hooks;
using Dalamud.Plugin; using Dalamud.Plugin;
@@ -16,6 +17,7 @@ public unsafe class Plugin : IDalamudPlugin
{ {
private static string HelpDescription => "Opens your inventory."; private static string HelpDescription => "Opens your inventory.";
private readonly CommandHandler _commandHandler;
private readonly InventoryHooks _inventoryHooks; private readonly InventoryHooks _inventoryHooks;
private readonly InventoryLifecycles _inventoryLifecycles; private readonly InventoryLifecycles _inventoryLifecycles;
@@ -46,18 +48,8 @@ public unsafe class Plugin : IDalamudPlugin
Services.PluginInterface.UiBuilder.OpenMainUi += System.AddonInventoryWindow.Toggle; Services.PluginInterface.UiBuilder.OpenMainUi += System.AddonInventoryWindow.Toggle;
Services.PluginInterface.UiBuilder.OpenConfigUi += System.AddonConfigurationWindow.Toggle; Services.PluginInterface.UiBuilder.OpenConfigUi += System.AddonConfigurationWindow.Toggle;
Services.CommandManager.AddHandler("/aetherbags", new CommandInfo(OnCommand) _commandHandler = new CommandHandler();
{
DisplayOrder = 1,
ShowInHelp = true,
HelpMessage = HelpDescription
});
Services.CommandManager.AddHandler("/ab", new CommandInfo(OnCommand)
{
DisplayOrder = 2,
ShowInHelp = true,
HelpMessage = HelpDescription
});
Services.ClientState.Login += OnLogin; Services.ClientState.Login += OnLogin;
Services.ClientState.Logout += OnLogout; Services.ClientState.Logout += OnLogout;
@@ -76,8 +68,7 @@ public unsafe class Plugin : IDalamudPlugin
Services.ClientState.Login -= OnLogin; Services.ClientState.Login -= OnLogin;
Services.ClientState.Logout -= OnLogout; Services.ClientState.Logout -= OnLogout;
Services.CommandManager.RemoveHandler("/aetherbags"); _commandHandler.Dispose();
Services.CommandManager.RemoveHandler("/ab");
System.AddonInventoryWindow.Dispose(); System.AddonInventoryWindow.Dispose();
System.AddonConfigurationWindow.Dispose(); System.AddonConfigurationWindow.Dispose();
@@ -88,26 +79,6 @@ public unsafe class Plugin : IDalamudPlugin
_inventoryLifecycles.Dispose(); _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() private void OnLogin()
{ {
System.Config = Util.LoadConfigOrDefault(); System.Config = Util.LoadConfigOrDefault();
+1
View File
@@ -8,6 +8,7 @@ namespace AetherBags;
public class Services public class Services
{ {
[PluginService] public static IAddonLifecycle AddonLifecycle { get; set; } = null!; [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 IClientState ClientState { get; private set; } = null!;
[PluginService] public static ICommandManager CommandManager { get; private set; } = null!; [PluginService] public static ICommandManager CommandManager { get; private set; } = null!;
[PluginService] public static IDataManager DataManager { get; set; } = null!; [PluginService] public static IDataManager DataManager { get; set; } = null!;