Merge remote-tracking branch 'origin/master' into dev/pie-lover
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 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<T> where T : struct, IComparable<T>
|
||||
|
||||
@@ -13,6 +13,8 @@ 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 bool LinkItemEnabled { get; set; } = false;
|
||||
}
|
||||
|
||||
public enum InventoryStackMode : byte
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -57,6 +57,12 @@ public sealed class ItemInfo : IEquatable<ItemInfo>
|
||||
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 =>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
+5
-34
@@ -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();
|
||||
|
||||
@@ -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!;
|
||||
|
||||
Reference in New Issue
Block a user