diff --git a/AetherBags.sln.DotSettings.user b/AetherBags.sln.DotSettings.user
index a860f90..4930879 100644
--- a/AetherBags.sln.DotSettings.user
+++ b/AetherBags.sln.DotSettings.user
@@ -1,3 +1,5 @@
ForceIncluded
+ ForceIncluded
+ ForceIncluded
ForceIncluded
\ No newline at end of file
diff --git a/AetherBags/Addons/AddonConfigurationWindow.cs b/AetherBags/Addons/AddonConfigurationWindow.cs
new file mode 100644
index 0000000..aa9f970
--- /dev/null
+++ b/AetherBags/Addons/AddonConfigurationWindow.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using AetherBags.Nodes.Configuration;
+using FFXIVClientStructs.FFXIV.Component.GUI;
+using KamiToolKit;
+using KamiToolKit.Nodes;
+
+namespace AetherBags.Addons;
+
+public class AddonConfigurationWindow : NativeAddon
+{
+ private TabBarNode _tabBarNode = null!;
+
+ private GeneralScrollingAreaNode _generalScrollingAreaNode = null!;
+ private CategoryScrollingAreaNode _categoryScrollingAreaNode = null!;
+ private CurrencyScrollingAreaNode _currencyScrollingAreaNode = null!;
+
+ private readonly List _tabContent = new();
+
+ protected override unsafe void OnSetup(AtkUnitBase* addon)
+ {
+ var tabContentY = ContentStartPosition.Y + 40;
+ var tabContentHeight = ContentSize.Y - 40;
+
+ _tabBarNode = new TabBarNode
+ {
+ Position = ContentStartPosition,
+ Size = ContentSize with { Y = 24 },
+ IsVisible = true
+ };
+ _tabBarNode.AttachNode(this);
+
+ _generalScrollingAreaNode = new GeneralScrollingAreaNode
+ {
+ Position = ContentStartPosition with { Y = tabContentY },
+ Size = ContentSize with { Y = tabContentHeight },
+ ContentHeight = 400,
+ ScrollSpeed = 25,
+ IsVisible = true,
+ };
+ _generalScrollingAreaNode.AttachNode(this);
+
+ _categoryScrollingAreaNode = new CategoryScrollingAreaNode
+ {
+ Position = ContentStartPosition with { Y = tabContentY },
+ Size = ContentSize with { Y = tabContentHeight },
+ ContentHeight = 400,
+ ScrollSpeed = 25,
+ IsVisible = false,
+ };
+ _categoryScrollingAreaNode.AttachNode(this);
+
+ _currencyScrollingAreaNode = new CurrencyScrollingAreaNode
+ {
+ Position = ContentStartPosition with { Y = tabContentY },
+ Size = ContentSize with { Y = tabContentHeight },
+ ContentHeight = 400,
+ ScrollSpeed = 25,
+ IsVisible = false,
+ };
+ _currencyScrollingAreaNode.AttachNode(this);
+
+ _tabContent.Add(_generalScrollingAreaNode);
+ _tabContent.Add(_categoryScrollingAreaNode);
+ _tabContent.Add(_currencyScrollingAreaNode);
+
+ _tabBarNode.AddTab("General", () => SwitchTab(0));
+ _tabBarNode.AddTab("Categories", () => SwitchTab(1));
+ _tabBarNode.AddTab("Currency", () => SwitchTab(2));
+
+ base.OnSetup(addon);
+ }
+
+ private void SwitchTab(int index)
+ {
+ for (var i = 0; i < _tabContent.Count; i++)
+ _tabContent[i].IsVisible = i == index;
+ }
+}
\ No newline at end of file
diff --git a/AetherBags/Configuration/GeneralSettings.cs b/AetherBags/Configuration/GeneralSettings.cs
new file mode 100644
index 0000000..40589cb
--- /dev/null
+++ b/AetherBags/Configuration/GeneralSettings.cs
@@ -0,0 +1,16 @@
+using System.Numerics;
+using KamiToolKit.Classes;
+
+namespace AetherBags.Configuration;
+
+public class GeneralSettings
+{
+ public InventoryStackMode StackMode { get; set; } = InventoryStackMode.AggregateByItemId;
+ public bool DebugEnabled { get; set; } = false;
+}
+
+public enum InventoryStackMode : byte
+{
+ NaturalStacks = 0,
+ AggregateByItemId = 1,
+}
\ No newline at end of file
diff --git a/AetherBags/Configuration/SystemConfiguration.cs b/AetherBags/Configuration/SystemConfiguration.cs
index 5cc8251..4e58599 100644
--- a/AetherBags/Configuration/SystemConfiguration.cs
+++ b/AetherBags/Configuration/SystemConfiguration.cs
@@ -7,6 +7,8 @@ public class SystemConfiguration
{
public const string FileName = "AetherBags.json";
- public CurrencySettings Currency { get; set; } = new();
+
+ public GeneralSettings General { get; set; } = new();
public CategorySettings Categories { get; set; } = new();
+ public CurrencySettings Currency { get; set; } = new();
}
\ No newline at end of file
diff --git a/AetherBags/Extensions/AddonLifecycleExtensions.cs b/AetherBags/Extensions/AddonLifecycleExtensions.cs
index 03324f4..0c7bf4d 100644
--- a/AetherBags/Extensions/AddonLifecycleExtensions.cs
+++ b/AetherBags/Extensions/AddonLifecycleExtensions.cs
@@ -43,11 +43,11 @@ public static class AddonLifecycleExtensions {
private static void Logger(AddonEvent type, AddonArgs args) {
switch (args) {
case AddonReceiveEventArgs receiveEventArgs:
- Services.Logger.Debug($"[{args.AddonName}] {(AtkEventType)receiveEventArgs.AtkEventType}: {receiveEventArgs.EventParam}");
+ Services.Logger.DebugOnly($"[{args.AddonName}] {(AtkEventType)receiveEventArgs.AtkEventType}: {receiveEventArgs.EventParam}");
break;
default:
- Services.Logger.Debug($"{args.AddonName} called {type.ToString().Replace("Post", string.Empty)}");
+ Services.Logger.DebugOnly($"{args.AddonName} called {type.ToString().Replace("Post", string.Empty)}");
break;
}
}
diff --git a/AetherBags/Extensions/LoggerExtensions.cs b/AetherBags/Extensions/LoggerExtensions.cs
new file mode 100644
index 0000000..c0a9765
--- /dev/null
+++ b/AetherBags/Extensions/LoggerExtensions.cs
@@ -0,0 +1,16 @@
+using System.Diagnostics;
+
+namespace AetherBags.Extensions;
+
+public static class LoggerExtensions
+{
+ public static void DebugOnly(this object logger, string message)
+ {
+ if(System.Config.General.DebugEnabled) Services.Logger.Debug(message);
+ }
+
+ public static void DebugOnly(this object logger, string message, params object[] args)
+ {
+ if(System.Config.General.DebugEnabled) Services.Logger.Debug(message);
+ }
+}
\ No newline at end of file
diff --git a/AetherBags/GlobalUsing.cs b/AetherBags/GlobalUsing.cs
index 8cbabd6..b691c20 100644
--- a/AetherBags/GlobalUsing.cs
+++ b/AetherBags/GlobalUsing.cs
@@ -1 +1,2 @@
-global using KamiToolKit.Extensions;
\ No newline at end of file
+global using KamiToolKit.Extensions;
+global using AetherBags.Extensions;
\ No newline at end of file
diff --git a/AetherBags/Helpers/BackupHelper.cs b/AetherBags/Helpers/BackupHelper.cs
index 39760ec..60466cc 100644
--- a/AetherBags/Helpers/BackupHelper.cs
+++ b/AetherBags/Helpers/BackupHelper.cs
@@ -62,7 +62,7 @@ public static class BackupHelper {
File.Move(latestFile.FullName, archivePath);
moved = true;
} catch (IOException ioEx) when (i < 4) {
- Services.Logger.Debug($"Move failed, retrying in 100ms: {ioEx.Message}");
+ Services.Logger.DebugOnly($"Move failed, retrying in 100ms: {ioEx.Message}");
global::System.Threading.Thread.Sleep(100);
}
}
@@ -79,7 +79,7 @@ public static class BackupHelper {
var allBackups = dir.GetFiles().Where(f => f.Name.StartsWith($"{Name}.2") && f.Name.EndsWith(".zip"))
.OrderBy(f => f.LastWriteTime.Ticks).ToList();
if (allBackups.Count > MaxBackups) {
- Services.Logger.Debug($"Removing Oldest Backup: {allBackups[0].FullName}");
+ Services.Logger.DebugOnly($"Removing Oldest Backup: {allBackups[0].FullName}");
File.Delete(allBackups[0].FullName);
}
} catch (Exception exception) {
diff --git a/AetherBags/Inventory/InventoryState.cs b/AetherBags/Inventory/InventoryState.cs
index df7aaa5..be76a3a 100644
--- a/AetherBags/Inventory/InventoryState.cs
+++ b/AetherBags/Inventory/InventoryState.cs
@@ -1,3 +1,4 @@
+using AetherBags.Configuration;
using AetherBags.Currency;
using Dalamud.Game.Inventory;
using FFXIVClientStructs.FFXIV.Client.Game;
@@ -6,7 +7,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
-using AetherBags.Configuration;
namespace AetherBags.Inventory;
@@ -46,19 +46,17 @@ public static unsafe class InventoryState
private static readonly Dictionary CategoryInfoCache = new(capacity: 256);
- private static readonly Dictionary AggByItemId = new(capacity: 512);
- private static readonly Dictionary ItemInfoByItemId = new(capacity: 512);
+ private static readonly Dictionary AggByKey = new(capacity: 512);
+ private static readonly Dictionary ItemInfoByKey = new(capacity: 512);
private static readonly Dictionary BucketsByKey = new(capacity: 256);
private static readonly List SortedCategoryKeys = new(capacity: 256);
private static readonly List AllCategories = new(capacity: 256);
-
private static readonly List FilteredCategories = new(capacity: 256);
private static readonly List UserCategoriesSortedScratch = new(capacity: 64);
-
- private static readonly List RemoveKeysScratch = new(capacity: 256);
+ private static readonly List RemoveKeysScratch = new(capacity: 256);
private const uint UserCategoryKeyFlag = 0x8000_0000;
@@ -71,6 +69,9 @@ public static unsafe class InventoryState
public static bool Contains(this IReadOnlyCollection inventoryTypes, GameInventoryType type)
=> inventoryTypes.Contains((InventoryType)type);
+ private static ulong MakeNaturalSlotKey(InventoryType container, int slot)
+ => ((ulong)(uint)container << 32) | (uint)slot;
+
public static void RefreshFromGame()
{
InventoryManager* inventoryManager = InventoryManager.Instance();
@@ -82,41 +83,78 @@ public static unsafe class InventoryState
var config = System.Config;
+ InventoryStackMode stackMode = config.General.StackMode;
+
bool userCategoriesEnabled = config.Categories.UserCategoriesEnabled;
bool gameCategoriesEnabled = config.Categories.GameCategoriesEnabled;
-
List userCategories = config.Categories.UserCategories;
- AggByItemId.Clear();
+ AggByKey.Clear();
+ ItemInfoByKey.Clear();
+
+ BucketsByKey.Clear();
+ SortedCategoryKeys.Clear();
+ AllCategories.Clear();
+ FilteredCategories.Clear();
+
+ Services.Logger.DebugOnly($"RefreshFromGame StackMode={stackMode}");
+
+ int scannedSlots = 0;
+ int nonEmptySlots = 0;
+ int collisions = 0;
for (int inventoryIndex = 0; inventoryIndex < BagInventories.Length; inventoryIndex++)
{
- var container = inventoryManager->GetInventoryContainer(BagInventories[inventoryIndex]);
+ var inventoryType = BagInventories[inventoryIndex];
+ var container = inventoryManager->GetInventoryContainer(inventoryType);
if (container == null)
+ {
+ Services.Logger.DebugOnly($"Container null: {inventoryType}");
continue;
+ }
int size = container->Size;
+ Services.Logger.DebugOnly($"Scanning {inventoryType} Size={size}");
+
for (int slot = 0; slot < size; slot++)
{
+ scannedSlots++;
+
ref var item = ref container->Items[slot];
uint id = item.ItemId;
if (id == 0)
continue;
+ nonEmptySlots++;
+
int quantity = item.Quantity;
- if (AggByItemId.TryGetValue(id, out AggregatedItem agg))
+ ulong key = stackMode == InventoryStackMode.AggregateByItemId
+ ? id
+ : MakeNaturalSlotKey(inventoryType, slot);
+
+ Services.Logger.DebugOnly($"Slot {inventoryType}[{slot}] ItemId={id} Qty={quantity} Key=0x{key:X16}");
+
+ if (AggByKey.TryGetValue(key, out AggregatedItem agg))
{
+ if (stackMode == InventoryStackMode.NaturalStacks)
+ {
+ collisions++;
+ Services.Logger.DebugOnly($"COLLISION Key=0x{key:X16}: existing ItemId={agg.First.ItemId} new ItemId={id}");
+ }
+
agg.Total += quantity;
- AggByItemId[id] = agg;
+ AggByKey[key] = agg;
}
else
{
- AggByItemId.Add(id, new AggregatedItem { First = item, Total = quantity });
+ AggByKey.Add(key, new AggregatedItem { First = item, Total = quantity });
}
}
}
+ Services.Logger.DebugOnly($"ScannedSlots={scannedSlots} NonEmptySlots={nonEmptySlots} AggByKey.Count={AggByKey.Count} Collisions={collisions}");
+
foreach (var kvp in BucketsByKey)
{
CategoryBucket bucket = kvp.Value;
@@ -125,19 +163,20 @@ public static unsafe class InventoryState
bucket.FilteredItems.Clear();
}
- foreach (var kvp in AggByItemId)
+ foreach (var kvp in AggByKey)
{
- uint itemId = kvp.Key;
+ ulong key = kvp.Key;
AggregatedItem agg = kvp.Value;
- if (!ItemInfoByItemId.TryGetValue(itemId, out ItemInfo? info))
+ if (!ItemInfoByKey.TryGetValue(key, out ItemInfo? info))
{
info = new ItemInfo
{
+ Key = key,
Item = agg.First,
ItemCount = agg.Total,
};
- ItemInfoByItemId.Add(itemId, info);
+ ItemInfoByKey.Add(key, info);
}
else
{
@@ -146,8 +185,10 @@ public static unsafe class InventoryState
}
}
+ Services.Logger.DebugOnly($"ItemInfoByKey.Count={ItemInfoByKey.Count}");
+
// Bucket by user category
- HashSet claimedItemIds = new(capacity: ItemInfoByItemId.Count);
+ HashSet claimedKeys = new HashSet(capacity: ItemInfoByKey.Count);
if (userCategoriesEnabled && userCategories.Count > 0)
{
@@ -167,13 +208,13 @@ public static unsafe class InventoryState
for (int c = 0; c < UserCategoriesSortedScratch.Count; c++)
{
UserCategoryDefinition category = UserCategoriesSortedScratch[c];
- uint key = MakeUserCategoryKey(category.Order);
+ uint bucketKey = MakeUserCategoryKey(category.Order);
- if (!BucketsByKey.TryGetValue(key, out CategoryBucket? bucket))
+ if (!BucketsByKey.TryGetValue(bucketKey, out CategoryBucket? bucket))
{
bucket = new CategoryBucket
{
- Key = key,
+ Key = bucketKey,
Category = new CategoryInfo
{
Name = category.Name,
@@ -184,7 +225,7 @@ public static unsafe class InventoryState
FilteredItems = new List(capacity: 16),
Used = true,
};
- BucketsByKey.Add(key, bucket);
+ BucketsByKey.Add(bucketKey, bucket);
}
else
{
@@ -194,18 +235,18 @@ public static unsafe class InventoryState
bucket.Category.Color = category.Color;
}
- foreach (var itemKvp in ItemInfoByItemId)
+ foreach (var itemKvp in ItemInfoByKey)
{
+ ulong itemKey = itemKvp.Key;
ItemInfo item = itemKvp.Value;
- uint itemId = item.Item.ItemId;
- if (claimedItemIds.Contains(itemId))
+ if (claimedKeys.Contains(itemKey))
continue;
if (UserCategoryMatcher.Matches(item, category))
{
bucket.Items.Add(item);
- claimedItemIds.Add(itemId);
+ claimedKeys.Add(itemKey);
}
}
@@ -217,11 +258,12 @@ public static unsafe class InventoryState
// Game category bucket
if (gameCategoriesEnabled)
{
- foreach (var itemKvp in ItemInfoByItemId)
+ foreach (var itemKvp in ItemInfoByKey)
{
+ ulong itemKey = itemKvp.Key;
ItemInfo info = itemKvp.Value;
- if (userCategoriesEnabled && claimedItemIds.Contains(info.Item.ItemId))
+ if (userCategoriesEnabled && claimedKeys.Contains(itemKey))
continue;
uint categoryKey = info.UiCategory.RowId;
@@ -253,9 +295,9 @@ public static unsafe class InventoryState
if (!BucketsByKey.TryGetValue(0u, out CategoryBucket? miscBucket))
{
CategoryInfo miscInfo;
- if (ItemInfoByItemId.Count > 0)
+ if (ItemInfoByKey.Count > 0)
{
- var sample = ItemInfoByItemId.Values.First();
+ var sample = ItemInfoByKey.Values.First();
miscInfo = GetCategoryInfoForKeyCached(0u, sample);
}
else
@@ -278,11 +320,12 @@ public static unsafe class InventoryState
miscBucket.Used = true;
}
- foreach (var itemKvp in ItemInfoByItemId)
+ foreach (var itemKvp in ItemInfoByKey)
{
+ ulong itemKey = itemKvp.Key;
ItemInfo info = itemKvp.Value;
- if (userCategoriesEnabled && claimedItemIds.Contains(info.Item.ItemId))
+ if (userCategoriesEnabled && claimedKeys.Contains(itemKey))
continue;
miscBucket.Items.Add(info);
@@ -292,19 +335,19 @@ public static unsafe class InventoryState
miscBucket.Used = false;
}
- if (ItemInfoByItemId.Count != AggByItemId.Count)
+ if (ItemInfoByKey.Count != AggByKey.Count)
{
RemoveKeysScratch.Clear();
- foreach (var kvp in ItemInfoByItemId)
+ foreach (var kvp in ItemInfoByKey)
{
- uint itemId = kvp.Key;
- if (!AggByItemId.ContainsKey(itemId))
- RemoveKeysScratch.Add(itemId);
+ ulong key = kvp.Key;
+ if (!AggByKey.ContainsKey(key))
+ RemoveKeysScratch.Add(key);
}
for (int i = 0; i < RemoveKeysScratch.Count; i++)
- ItemInfoByItemId.Remove(RemoveKeysScratch[i]);
+ ItemInfoByKey.Remove(RemoveKeysScratch[i]);
}
SortedCategoryKeys.Clear();
@@ -336,6 +379,11 @@ public static unsafe class InventoryState
CategoryBucket bucket = BucketsByKey[key];
AllCategories.Add(new CategorizedInventory(bucket.Key, bucket.Category, bucket.Items));
}
+ int displayed = 0;
+ for (int i = 0; i < AllCategories.Count; i++)
+ displayed += AllCategories[i].Items.Count;
+
+ Services.Logger.DebugOnly($"AllCategories={AllCategories.Count} DisplayedItemsTotal={displayed}");
}
public static IReadOnlyList GetInventoryItemCategories(string filterString = "", bool invert = false)
@@ -431,7 +479,6 @@ public static unsafe class InventoryState
return new CurrencyItem(itemId, isLimited);
}
-
public static IReadOnlyList GetCurrencyInfoList(uint[] currencyIds)
{
if (currencyIds.Length == 0) return Array.Empty();
@@ -455,7 +502,7 @@ public static unsafe class InventoryState
InventoryManager* inventoryManager = InventoryManager.Instance();
var item = Services.DataManager.GetExcelSheet- ().GetRow(currencyItem.ItemId);
- uint amount = (uint) inventoryManager->GetInventoryItemCount(currencyItem.ItemId);
+ uint amount = (uint)inventoryManager->GetInventoryItemCount(currencyItem.ItemId);
uint maxAmount = item.StackSize;
bool isCapped = false;
if (currencyItem.IsLimited)
@@ -478,8 +525,8 @@ public static unsafe class InventoryState
private static void ClearAll()
{
- AggByItemId.Clear();
- ItemInfoByItemId.Clear();
+ AggByKey.Clear();
+ ItemInfoByKey.Clear();
foreach (var kvp in BucketsByKey)
{
@@ -540,7 +587,7 @@ public static unsafe class InventoryState
public int Compare(ItemInfo? x, ItemInfo? y)
{
if (ReferenceEquals(x, y)) return 0;
- if (x is null) return 1; // nulls last
+ if (x is null) return 1;
if (y is null) return -1;
int a = x.ItemCount;
@@ -562,4 +609,4 @@ public static unsafe class InventoryState
}
private record CurrencyItem(uint ItemId, bool IsLimited);
-}
+}
\ No newline at end of file
diff --git a/AetherBags/Inventory/ItemInfo.cs b/AetherBags/Inventory/ItemInfo.cs
index cb98128..83fca5f 100644
--- a/AetherBags/Inventory/ItemInfo.cs
+++ b/AetherBags/Inventory/ItemInfo.cs
@@ -10,6 +10,8 @@ namespace AetherBags.Inventory;
public sealed class ItemInfo : IEquatable
{
+ public required ulong Key { get; set; }
+
public required InventoryItem Item { get; set; }
public required int ItemCount { get; set; }
@@ -44,6 +46,7 @@ public sealed class ItemInfo : IEquatable
public int ItemLevel => (int)Row.LevelItem.RowId;
public int Rarity => Row.Rarity;
public uint VendorPrice => Row.PriceLow;
+ public uint StackSize => Row.StackSize;
public RowRef UiCategory => Row.ItemUICategory;
@@ -87,11 +90,11 @@ public sealed class ItemInfo : IEquatable
=> Description.Contains(value, StringComparison.OrdinalIgnoreCase);
public bool Equals(ItemInfo? other)
- => other is not null && Item.ItemId == other.Item.ItemId && ItemCount == other.ItemCount;
+ => other is not null && Key == other.Key;
public override bool Equals(object? obj)
=> obj is ItemInfo other && Equals(other);
public override int GetHashCode()
- => HashCode.Combine(Item.ItemId, ItemCount);
+ => Key.GetHashCode();
}
diff --git a/AetherBags/Nodes/Configuration/CategoryScrollingAreaNode.cs b/AetherBags/Nodes/Configuration/CategoryScrollingAreaNode.cs
new file mode 100644
index 0000000..fd33e4c
--- /dev/null
+++ b/AetherBags/Nodes/Configuration/CategoryScrollingAreaNode.cs
@@ -0,0 +1,7 @@
+using KamiToolKit.Nodes;
+
+namespace AetherBags.Nodes.Configuration;
+
+public class CategoryScrollingAreaNode : ScrollingAreaNode
+{
+}
\ No newline at end of file
diff --git a/AetherBags/Nodes/Configuration/CurrencyScrollingAreaNode.cs b/AetherBags/Nodes/Configuration/CurrencyScrollingAreaNode.cs
new file mode 100644
index 0000000..c15980a
--- /dev/null
+++ b/AetherBags/Nodes/Configuration/CurrencyScrollingAreaNode.cs
@@ -0,0 +1,8 @@
+using KamiToolKit.Nodes;
+
+namespace AetherBags.Nodes.Configuration;
+
+public class CurrencyScrollingAreaNode : ScrollingAreaNode
+{
+
+}
\ No newline at end of file
diff --git a/AetherBags/Nodes/Configuration/GeneralScrollingAreaNode.cs b/AetherBags/Nodes/Configuration/GeneralScrollingAreaNode.cs
new file mode 100644
index 0000000..b121e95
--- /dev/null
+++ b/AetherBags/Nodes/Configuration/GeneralScrollingAreaNode.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Linq;
+using System.Numerics;
+using AetherBags.Configuration;
+using KamiToolKit.Nodes;
+
+namespace AetherBags.Nodes.Configuration;
+
+public sealed class GeneralScrollingAreaNode : ScrollingAreaNode
+{
+ private readonly CheckboxNode _debugCheckboxNode = null!;
+ private readonly LabeledDropdownNode _stackDropDown = null!;
+ public GeneralScrollingAreaNode()
+ {
+ GeneralSettings config = System.Config.General;
+
+ ContentNode.ItemSpacing = 32;
+
+ _stackDropDown = new LabeledDropdownNode
+ {
+ Size = new Vector2(300, 20),
+ LabelText = "Stack Mode",
+ Options = Enum.GetNames(typeof(InventoryStackMode)).ToList(),
+ SelectedOption = config.StackMode.ToString(),
+ OnOptionSelected = selected =>
+ {
+ if (Enum.TryParse(selected, out var parsed))
+ config.StackMode = parsed;
+ RefreshInventory();
+ }
+ };
+ ContentNode.AddNode(_stackDropDown);
+
+ _debugCheckboxNode = new CheckboxNode
+ {
+ Size = new Vector2(300, 20),
+ IsVisible = true,
+ String = "Debug Mode",
+ IsChecked = config.DebugEnabled,
+ OnClick = isChecked => { config.DebugEnabled = isChecked; }
+ };
+ ContentNode.AddNode(_debugCheckboxNode);
+
+ }
+
+ private void RefreshInventory() => System.AddonInventoryWindow.ManualRefresh();
+}
\ No newline at end of file
diff --git a/AetherBags/Nodes/LabeledDropdownNode.cs b/AetherBags/Nodes/LabeledDropdownNode.cs
new file mode 100644
index 0000000..9f285fa
--- /dev/null
+++ b/AetherBags/Nodes/LabeledDropdownNode.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using FFXIVClientStructs.FFXIV.Component.GUI;
+using KamiToolKit.Classes;
+using KamiToolKit.Nodes;
+
+namespace AetherBags.Nodes;
+
+public class LabeledDropdownNode : SimpleComponentNode {
+ private readonly GridNode _gridNode;
+ private readonly TextNode _labelNode;
+ private readonly TextDropDownNode _dropDownNode;
+
+ public LabeledDropdownNode() {
+ _gridNode = new GridNode {
+ GridSize = new GridSize(2, 1),
+ };
+ _gridNode.AttachNode(this);
+
+ _labelNode = new TextNode {
+ AlignmentType = AlignmentType.Bottom,
+ FontType = FontType.Axis,
+ FontSize = 14,
+ LineSpacing = 14,
+ TextColor = ColorHelper.GetColor(8),
+ TextOutlineColor = ColorHelper.GetColor(7),
+ TextFlags = TextFlags.Edge | TextFlags.AutoAdjustNodeSize,
+ String = string.Empty,
+ };
+ _labelNode.AttachNode(_gridNode[0, 0]);
+
+ _dropDownNode = new TextDropDownNode {
+ Options = new List(),
+ };
+ _dropDownNode.AttachNode(_gridNode[1, 0]);
+ }
+
+ protected override void OnSizeChanged() {
+ base.OnSizeChanged();
+
+ _gridNode.Size = Size;
+
+ _labelNode.Size = _gridNode[0, 0].Size;
+ _dropDownNode.Size = _gridNode[1, 0].Size;
+ }
+
+ public required string LabelText
+ {
+ get => _labelNode.String;
+ set => _labelNode.String = value;
+ }
+
+ public Action? OnOptionSelected
+ {
+ get => _dropDownNode.OnOptionSelected;
+ set => _dropDownNode.OnOptionSelected = value;
+ }
+
+ public string? SelectedOption
+ {
+ get => _dropDownNode.SelectedOption;
+ set => _dropDownNode.SelectedOption = value;
+ }
+
+ public required List Options
+ {
+ get => _dropDownNode.Options!;
+ set => _dropDownNode.Options = value;
+ }
+}
diff --git a/AetherBags/Plugin.cs b/AetherBags/Plugin.cs
index 43018e9..dbc9310 100644
--- a/AetherBags/Plugin.cs
+++ b/AetherBags/Plugin.cs
@@ -28,6 +28,16 @@ public class Plugin : IDalamudPlugin
Size = new Vector2(750, 750),
};
+ System.AddonConfigurationWindow = new AddonConfigurationWindow
+ {
+ InternalName = "AetherBags Config",
+ Title = "AetherBags Config",
+ Size = new Vector2(640, 512),
+ };
+
+ Services.PluginInterface.UiBuilder.OpenMainUi += System.AddonInventoryWindow.Toggle;
+ Services.PluginInterface.UiBuilder.OpenConfigUi += System.AddonConfigurationWindow.Toggle;
+
Services.CommandManager.AddHandler("/aetherbags", new CommandInfo(OnCommand)
{
DisplayOrder = 1,
@@ -59,6 +69,7 @@ public class Plugin : IDalamudPlugin
Services.CommandManager.RemoveHandler("/ab");
System.AddonInventoryWindow.Dispose();
+ System.AddonConfigurationWindow.Dispose();
KamiToolKitLibrary.Dispose();
}
@@ -72,7 +83,7 @@ public class Plugin : IDalamudPlugin
if(args.Length == 0)
System.AddonInventoryWindow.Toggle();
if(args == "config")
- System.AddonInventoryWindow.Toggle();
+ System.AddonConfigurationWindow.Toggle();
if (args == "import-sk")
{
// Manually import from SortaKinda for testing until we have a proper config window
@@ -89,6 +100,7 @@ public class Plugin : IDalamudPlugin
#if DEBUG
System.AddonInventoryWindow.Toggle();
+ System.AddonConfigurationWindow.Toggle();
#endif
}
diff --git a/AetherBags/System.cs b/AetherBags/System.cs
index 28ff178..05dee40 100644
--- a/AetherBags/System.cs
+++ b/AetherBags/System.cs
@@ -6,5 +6,6 @@ namespace AetherBags;
public static class System
{
public static AddonInventoryWindow AddonInventoryWindow { get; set; } = null!;
+ public static AddonConfigurationWindow AddonConfigurationWindow { get; set; } = null!;
public static SystemConfiguration Config { get; set; } = null!;
}
\ No newline at end of file