Refactors inventory management and UI update logic.
- Improves inventory data handling by introducing a bucket-based system for category management. - Streamlines UI updates by implementing a queued refresh mechanism to prevent redundant calculations - Enhances item filtering by using pre-calculated item lists. - Fixes an issue with autosizing and layout recalculations to ensure responsiveness and correct display.
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using AetherBags.Extensions;
|
using AetherBags.Extensions;
|
||||||
using AetherBags.Inventory;
|
using AetherBags.Inventory;
|
||||||
@@ -11,7 +10,6 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using KamiToolKit;
|
using KamiToolKit;
|
||||||
using KamiToolKit.Classes;
|
using KamiToolKit.Classes;
|
||||||
using Lumina.Data.Parsing.Uld;
|
|
||||||
|
|
||||||
namespace AetherBags.Addons;
|
namespace AetherBags.Addons;
|
||||||
|
|
||||||
@@ -38,6 +36,9 @@ public class AddonInventoryWindow : NativeAddon
|
|||||||
private const float FooterHeight = 28f;
|
private const float FooterHeight = 28f;
|
||||||
private const float FooterTopSpacing = 4f;
|
private const float FooterTopSpacing = 4f;
|
||||||
|
|
||||||
|
private bool _refreshQueued;
|
||||||
|
private bool _refreshAutosizeQueued;
|
||||||
|
|
||||||
protected override unsafe void OnSetup(AtkUnitBase* addon)
|
protected override unsafe void OnSetup(AtkUnitBase* addon)
|
||||||
{
|
{
|
||||||
_categoriesNode = new WrappingGridNode<InventoryCategoryNode>
|
_categoriesNode = new WrappingGridNode<InventoryCategoryNode>
|
||||||
@@ -57,15 +58,17 @@ public class AddonInventoryWindow : NativeAddon
|
|||||||
_searchInputNode = new TextInputWithHintNode
|
_searchInputNode = new TextInputWithHintNode
|
||||||
{
|
{
|
||||||
Position = headerSize / 2.0f - size / 2.0f + new Vector2(25.0f, 10.0f),
|
Position = headerSize / 2.0f - size / 2.0f + new Vector2(25.0f, 10.0f),
|
||||||
|
|
||||||
Size = size,
|
Size = size,
|
||||||
OnInputReceived = _ => RefreshCategories(false),
|
|
||||||
|
OnInputReceived = _ => RefreshCategoriesCore(autosize: false),
|
||||||
};
|
};
|
||||||
_searchInputNode.AttachNode(this);
|
_searchInputNode.AttachNode(this);
|
||||||
|
|
||||||
_footerNode = new InventoryFooterNode
|
_footerNode = new InventoryFooterNode
|
||||||
{
|
{
|
||||||
Size = ContentSize with { Y = FooterHeight },
|
Size = ContentSize with { Y = FooterHeight },
|
||||||
SlotAmountText = InventoryState.GetEmptyItemSlotsString()
|
SlotAmountText = InventoryState.GetEmptyItemSlotsString(),
|
||||||
};
|
};
|
||||||
_footerNode.AttachNode(this);
|
_footerNode.AttachNode(this);
|
||||||
|
|
||||||
@@ -74,50 +77,71 @@ public class AddonInventoryWindow : NativeAddon
|
|||||||
Services.AddonLifecycle.RegisterListener(AddonEvent.PostRequestedUpdate, "Inventory", OnInventoryUpdate);
|
Services.AddonLifecycle.RegisterListener(AddonEvent.PostRequestedUpdate, "Inventory", OnInventoryUpdate);
|
||||||
addon->SubscribeAtkArrayData(1, (int)NumberArrayType.Inventory);
|
addon->SubscribeAtkArrayData(1, (int)NumberArrayType.Inventory);
|
||||||
|
|
||||||
RefreshCategories();
|
InventoryState.RefreshFromGame();
|
||||||
|
|
||||||
|
RefreshCategoriesCore(autosize: true);
|
||||||
|
|
||||||
base.OnSetup(addon);
|
base.OnSetup(addon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected override unsafe void OnUpdate(AtkUnitBase* addon)
|
protected override unsafe void OnUpdate(AtkUnitBase* addon)
|
||||||
{
|
{
|
||||||
|
if (_refreshQueued)
|
||||||
|
{
|
||||||
|
bool doAutosize = _refreshAutosizeQueued;
|
||||||
|
_refreshQueued = false;
|
||||||
|
_refreshAutosizeQueued = false;
|
||||||
|
|
||||||
|
RefreshCategoriesCore(doAutosize);
|
||||||
|
}
|
||||||
|
|
||||||
base.OnUpdate(addon);
|
base.OnUpdate(addon);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnInventoryUpdate(AddonEvent type, AddonArgs args)
|
private void OnInventoryUpdate(AddonEvent type, AddonArgs args)
|
||||||
{
|
{
|
||||||
RefreshCategories();
|
InventoryState.RefreshFromGame();
|
||||||
|
|
||||||
|
RefreshCategoriesCore(autosize: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override unsafe void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
|
protected override unsafe void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
|
||||||
{
|
{
|
||||||
base.OnRequestedUpdate(addon, numberArrayData, stringArrayData);
|
base.OnRequestedUpdate(addon, numberArrayData, stringArrayData);
|
||||||
RefreshCategories();
|
|
||||||
|
InventoryState.RefreshFromGame();
|
||||||
|
|
||||||
|
RefreshCategoriesCore(autosize: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshCategories(bool autosize = true)
|
private void RefreshCategoriesCore(bool autosize)
|
||||||
{
|
{
|
||||||
_footerNode.SlotAmountText = InventoryState.GetEmptyItemSlotsString();
|
_footerNode.SlotAmountText = InventoryState.GetEmptyItemSlotsString();
|
||||||
|
|
||||||
var categories = InventoryState.GetInventoryItemCategories(_searchInputNode.SearchString.ExtractText());
|
string filter = _searchInputNode.SearchString.ExtractText();
|
||||||
|
IReadOnlyList<CategorizedInventory> categories = InventoryState.GetInventoryItemCategories(filter);
|
||||||
|
|
||||||
float maxContentWidth = MaxWindowWidth - (ContentStartPosition.X * 2);
|
float maxContentWidth = MaxWindowWidth - (ContentStartPosition.X * 2);
|
||||||
int maxItemsPerLine = CalculateOptimalItemsPerLine(maxContentWidth);
|
int maxItemsPerLine = CalculateOptimalItemsPerLine(maxContentWidth);
|
||||||
|
|
||||||
_categoriesNode.SyncWithListData(
|
_categoriesNode.SyncWithListDataByKey<CategorizedInventory, InventoryCategoryNode, uint>(
|
||||||
categories,
|
dataList: categories,
|
||||||
node => node.CategorizedInventory,
|
getKeyFromData: c => c.Key,
|
||||||
data => new InventoryCategoryNode
|
getKeyFromNode: n => n.CategorizedInventory.Key,
|
||||||
|
updateNode: (node, data) =>
|
||||||
|
{
|
||||||
|
node.CategorizedInventory = data;
|
||||||
|
node.ItemsPerLine = Math.Min(data.Items.Count, maxItemsPerLine);
|
||||||
|
},
|
||||||
|
createNodeMethod: _ =>
|
||||||
|
{
|
||||||
|
return new InventoryCategoryNode
|
||||||
{
|
{
|
||||||
Size = ContentSize with { Y = 120 },
|
Size = ContentSize with { Y = 120 },
|
||||||
CategorizedInventory = data,
|
};
|
||||||
ItemsPerLine = Math.Min(data.Items.Count, maxItemsPerLine)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
foreach (InventoryCategoryNode node in _categoriesNode.GetNodes<InventoryCategoryNode>())
|
|
||||||
{
|
|
||||||
node.ItemsPerLine = Math.Min(node.CategorizedInventory.Items.Count, maxItemsPerLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
WireHoverHandlers();
|
WireHoverHandlers();
|
||||||
|
|
||||||
if (autosize) AutoSizeWindow();
|
if (autosize) AutoSizeWindow();
|
||||||
@@ -130,11 +154,12 @@ public class AddonInventoryWindow : NativeAddon
|
|||||||
|
|
||||||
private void WireHoverHandlers()
|
private void WireHoverHandlers()
|
||||||
{
|
{
|
||||||
List<InventoryCategoryNode> categoryNodes = _categoriesNode.GetNodes<InventoryCategoryNode>().ToList();
|
var nodes = _categoriesNode.Nodes;
|
||||||
|
|
||||||
for (int i = 0; i < categoryNodes.Count; i++)
|
for (int i = 0; i < nodes.Count; i++)
|
||||||
{
|
{
|
||||||
InventoryCategoryNode node = categoryNodes[i];
|
if (nodes[i] is not InventoryCategoryNode node)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (!_hoverSubscribed.Add(node))
|
if (!_hoverSubscribed.Add(node))
|
||||||
continue;
|
continue;
|
||||||
@@ -148,7 +173,7 @@ public class AddonInventoryWindow : NativeAddon
|
|||||||
|
|
||||||
private int CalculateOptimalItemsPerLine(float availableWidth)
|
private int CalculateOptimalItemsPerLine(float availableWidth)
|
||||||
{
|
{
|
||||||
return Math.Clamp((int)Math.Floor((availableWidth + ItemPadding) / (ItemSize + ItemPadding)), 1, 15);
|
return Math.Clamp((int)MathF.Floor((availableWidth + ItemPadding) / (ItemSize + ItemPadding)), 1, 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LayoutContent()
|
private void LayoutContent()
|
||||||
@@ -170,15 +195,28 @@ public class AddonInventoryWindow : NativeAddon
|
|||||||
|
|
||||||
private void AutoSizeWindow()
|
private void AutoSizeWindow()
|
||||||
{
|
{
|
||||||
List<InventoryCategoryNode> childNodes = _categoriesNode.GetNodes<InventoryCategoryNode>().ToList();
|
var nodes = _categoriesNode.Nodes;
|
||||||
if (childNodes.Count == 0)
|
|
||||||
|
float maxChildWidth = 0f;
|
||||||
|
int childCount = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < nodes.Count; i++)
|
||||||
{
|
{
|
||||||
ResizeWindow(MinWindowWidth, MinWindowHeight);
|
if (nodes[i] is not InventoryCategoryNode cat)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
childCount++;
|
||||||
|
float w = cat.Width;
|
||||||
|
if (w > maxChildWidth) maxChildWidth = w;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (childCount == 0)
|
||||||
|
{
|
||||||
|
ResizeWindow(MinWindowWidth, MinWindowHeight, recalcLayout: true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
float requiredWidth = childNodes.Max(node => node.Width);
|
float requiredWidth = maxChildWidth + (ContentStartPosition.X * 2);
|
||||||
requiredWidth += ContentStartPosition.X * 2;
|
|
||||||
float finalWidth = Math.Clamp(requiredWidth, MinWindowWidth, MaxWindowWidth);
|
float finalWidth = Math.Clamp(requiredWidth, MinWindowWidth, MaxWindowWidth);
|
||||||
|
|
||||||
float contentWidth = finalWidth - (ContentStartPosition.X * 2);
|
float contentWidth = finalWidth - (ContentStartPosition.X * 2);
|
||||||
@@ -194,27 +232,32 @@ public class AddonInventoryWindow : NativeAddon
|
|||||||
float requiredContentHeight = requiredGridHeight + FooterTopSpacing + FooterHeight;
|
float requiredContentHeight = requiredGridHeight + FooterTopSpacing + FooterHeight;
|
||||||
|
|
||||||
float requiredWindowHeight = requiredContentHeight + ContentStartPosition.Y + ContentStartPosition.X;
|
float requiredWindowHeight = requiredContentHeight + ContentStartPosition.Y + ContentStartPosition.X;
|
||||||
|
|
||||||
float finalHeight = Math.Clamp(requiredWindowHeight, MinWindowHeight, MaxWindowHeight);
|
float finalHeight = Math.Clamp(requiredWindowHeight, MinWindowHeight, MaxWindowHeight);
|
||||||
|
|
||||||
ResizeWindow(finalWidth, finalHeight);
|
ResizeWindow(finalWidth, finalHeight, recalcLayout: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResizeWindow(float width, float height)
|
private void ResizeWindow(float width, float height, bool recalcLayout)
|
||||||
{
|
{
|
||||||
SetWindowSize(width, height);
|
SetWindowSize(width, height);
|
||||||
|
|
||||||
LayoutContent();
|
LayoutContent();
|
||||||
|
|
||||||
|
if (recalcLayout)
|
||||||
_categoriesNode.RecalculateLayout();
|
_categoriesNode.RecalculateLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ResizeWindow(float width, float height)
|
||||||
|
=> ResizeWindow(width, height, recalcLayout: true);
|
||||||
|
|
||||||
protected override unsafe void OnFinalize(AtkUnitBase* addon)
|
protected override unsafe void OnFinalize(AtkUnitBase* addon)
|
||||||
{
|
{
|
||||||
base.OnFinalize(addon);
|
|
||||||
Services.AddonLifecycle.UnregisterListener(OnInventoryUpdate);
|
Services.AddonLifecycle.UnregisterListener(OnInventoryUpdate);
|
||||||
addon->UnsubscribeAtkArrayData(1, (int)NumberArrayType.Inventory);
|
addon->UnsubscribeAtkArrayData(1, (int)NumberArrayType.Inventory);
|
||||||
|
|
||||||
_hoverSubscribed.Clear();
|
_hoverSubscribed.Clear();
|
||||||
|
_refreshQueued = false;
|
||||||
|
_refreshAutosizeQueued = false;
|
||||||
|
|
||||||
|
base.OnFinalize(addon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace AetherBags.Inventory;
|
namespace AetherBags.Inventory;
|
||||||
|
|
||||||
public readonly record struct CategorizedInventory(CategoryInfo Category, List<ItemInfo> Items);
|
public readonly record struct CategorizedInventory(uint Key, CategoryInfo Category, List<ItemInfo> Items);
|
||||||
@@ -43,65 +43,33 @@ public static unsafe class InventoryState
|
|||||||
InventoryType.Inventory4,
|
InventoryType.Inventory4,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private static readonly Dictionary<uint, CategoryInfo> CategoryInfoCache = new(capacity: 256);
|
||||||
|
|
||||||
|
private static readonly Dictionary<uint, AggregatedItem> AggByItemId = new(capacity: 512);
|
||||||
|
private static readonly Dictionary<uint, ItemInfo> ItemInfoByItemId = new(capacity: 512);
|
||||||
|
|
||||||
|
private static readonly Dictionary<uint, CategoryBucket> BucketsByKey = new(capacity: 256);
|
||||||
|
private static readonly List<uint> SortedCategoryKeys = new(capacity: 256);
|
||||||
|
|
||||||
|
private static readonly List<CategorizedInventory> AllCategories = new(capacity: 256);
|
||||||
|
|
||||||
|
private static readonly List<CategorizedInventory> FilteredCategories = new(capacity: 256);
|
||||||
|
|
||||||
|
private static readonly List<uint> RemoveKeysScratch = new(capacity: 256);
|
||||||
|
|
||||||
public static bool Contains(this IReadOnlyCollection<InventoryType> inventoryTypes, GameInventoryType type)
|
public static bool Contains(this IReadOnlyCollection<InventoryType> inventoryTypes, GameInventoryType type)
|
||||||
=> inventoryTypes.Contains((InventoryType)type);
|
=> inventoryTypes.Contains((InventoryType)type);
|
||||||
|
|
||||||
private static readonly Dictionary<uint, CategoryInfo> CategoryInfoCache = new(capacity: 256);
|
public static void RefreshFromGame()
|
||||||
|
|
||||||
public static List<CategorizedInventory> GetInventoryItemCategories(string filterString = "", bool invert = false)
|
|
||||||
{
|
{
|
||||||
List<ItemInfo> items = string.IsNullOrEmpty(filterString)
|
|
||||||
? GetInventoryItems()
|
|
||||||
: GetInventoryItems(filterString, invert);
|
|
||||||
|
|
||||||
if (items.Count == 0)
|
|
||||||
return new List<CategorizedInventory>(0);
|
|
||||||
|
|
||||||
var buckets = new Dictionary<uint, CategoryBucket>(capacity: Math.Min(128, items.Count));
|
|
||||||
|
|
||||||
for (int i = 0; i < items.Count; i++)
|
|
||||||
{
|
|
||||||
ItemInfo info = items[i];
|
|
||||||
uint catKey = info.UiCategory.RowId;
|
|
||||||
|
|
||||||
if (!buckets.TryGetValue(catKey, out CategoryBucket? bucket))
|
|
||||||
{
|
|
||||||
bucket = new CategoryBucket
|
|
||||||
{
|
|
||||||
Key = catKey,
|
|
||||||
Category = GetCategoryInfoForKeyCached(catKey, info),
|
|
||||||
Items = new List<ItemInfo>(capacity: 16),
|
|
||||||
};
|
|
||||||
buckets.Add(catKey, bucket);
|
|
||||||
}
|
|
||||||
|
|
||||||
bucket.Items.Add(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint[] keys = new uint[buckets.Count];
|
|
||||||
int k = 0;
|
|
||||||
foreach (var key in buckets.Keys)
|
|
||||||
keys[k++] = key;
|
|
||||||
Array.Sort(keys);
|
|
||||||
|
|
||||||
var result = new List<CategorizedInventory>(keys.Length);
|
|
||||||
for (int i = 0; i < keys.Length; i++)
|
|
||||||
{
|
|
||||||
CategoryBucket bucket = buckets[keys[i]];
|
|
||||||
bucket.Items.Sort(ItemCountDescComparer.Instance);
|
|
||||||
result.Add(new CategorizedInventory(bucket.Category, bucket.Items));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<ItemInfo> GetInventoryItems()
|
|
||||||
{
|
|
||||||
var dict = new Dictionary<uint, AggregatedItem>(capacity: 128);
|
|
||||||
|
|
||||||
InventoryManager* mgr = InventoryManager.Instance();
|
InventoryManager* mgr = InventoryManager.Instance();
|
||||||
if (mgr == null)
|
if (mgr == null)
|
||||||
return new List<ItemInfo>(0);
|
{
|
||||||
|
ClearAll();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AggByItemId.Clear();
|
||||||
|
|
||||||
for (int invIndex = 0; invIndex < BagInventories.Length; invIndex++)
|
for (int invIndex = 0; invIndex < BagInventories.Length; invIndex++)
|
||||||
{
|
{
|
||||||
@@ -119,54 +87,195 @@ public static unsafe class InventoryState
|
|||||||
|
|
||||||
int qty = item.Quantity;
|
int qty = item.Quantity;
|
||||||
|
|
||||||
if (dict.TryGetValue(id, out AggregatedItem agg))
|
if (AggByItemId.TryGetValue(id, out AggregatedItem agg))
|
||||||
{
|
{
|
||||||
agg.Total += qty;
|
agg.Total += qty;
|
||||||
dict[id] = agg;
|
AggByItemId[id] = agg;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dict.Add(id, new AggregatedItem { First = item, Total = qty });
|
AggByItemId.Add(id, new AggregatedItem { First = item, Total = qty });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dict.Count == 0)
|
foreach (var kvp in BucketsByKey)
|
||||||
return new List<ItemInfo>(0);
|
|
||||||
|
|
||||||
var list = new List<ItemInfo>(dict.Count);
|
|
||||||
foreach (var kvp in dict)
|
|
||||||
{
|
{
|
||||||
|
CategoryBucket b = kvp.Value;
|
||||||
|
b.Used = false;
|
||||||
|
b.Items.Clear();
|
||||||
|
b.FilteredItems.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var kvp in AggByItemId)
|
||||||
|
{
|
||||||
|
uint itemId = kvp.Key;
|
||||||
AggregatedItem agg = kvp.Value;
|
AggregatedItem agg = kvp.Value;
|
||||||
|
|
||||||
list.Add(new ItemInfo
|
if (!ItemInfoByItemId.TryGetValue(itemId, out ItemInfo? info))
|
||||||
|
{
|
||||||
|
info = new ItemInfo
|
||||||
{
|
{
|
||||||
Item = agg.First,
|
Item = agg.First,
|
||||||
ItemCount = agg.Total,
|
ItemCount = agg.Total,
|
||||||
});
|
};
|
||||||
|
ItemInfoByItemId.Add(itemId, info);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
info.Item = agg.First;
|
||||||
|
info.ItemCount = agg.Total;
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
uint catKey = info.UiCategory.RowId;
|
||||||
|
|
||||||
|
if (!BucketsByKey.TryGetValue(catKey, out CategoryBucket? bucket))
|
||||||
|
{
|
||||||
|
bucket = new CategoryBucket
|
||||||
|
{
|
||||||
|
Key = catKey,
|
||||||
|
Category = GetCategoryInfoForKeyCached(catKey, info),
|
||||||
|
Items = new List<ItemInfo>(capacity: 16),
|
||||||
|
FilteredItems = new List<ItemInfo>(capacity: 16),
|
||||||
|
Used = true,
|
||||||
|
};
|
||||||
|
BucketsByKey.Add(catKey, bucket);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bucket.Used = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<ItemInfo> GetInventoryItems(string filterString, bool invert = false)
|
bucket.Items.Add(info);
|
||||||
{
|
}
|
||||||
List<ItemInfo> all = GetInventoryItems();
|
|
||||||
if (all.Count == 0)
|
|
||||||
return all;
|
|
||||||
|
|
||||||
var filtered = new List<ItemInfo>(all.Count);
|
if (ItemInfoByItemId.Count != AggByItemId.Count)
|
||||||
var re = new Regex(filterString, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
|
||||||
for (int i = 0; i < all.Count; i++)
|
|
||||||
{
|
{
|
||||||
ItemInfo info = all[i];
|
RemoveKeysScratch.Clear();
|
||||||
|
|
||||||
|
foreach (var kvp in ItemInfoByItemId)
|
||||||
|
{
|
||||||
|
uint itemId = kvp.Key;
|
||||||
|
if (!AggByItemId.ContainsKey(itemId))
|
||||||
|
RemoveKeysScratch.Add(itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < RemoveKeysScratch.Count; i++)
|
||||||
|
ItemInfoByItemId.Remove(RemoveKeysScratch[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
SortedCategoryKeys.Clear();
|
||||||
|
|
||||||
|
foreach (var kvp in BucketsByKey)
|
||||||
|
{
|
||||||
|
CategoryBucket bucket = kvp.Value;
|
||||||
|
if (!bucket.Used)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bucket.Items.Sort(ItemCountDescComparer.Instance);
|
||||||
|
SortedCategoryKeys.Add(bucket.Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
SortedCategoryKeys.Sort();
|
||||||
|
|
||||||
|
AllCategories.Clear();
|
||||||
|
AllCategories.Capacity = Math.Max(AllCategories.Capacity, SortedCategoryKeys.Count);
|
||||||
|
|
||||||
|
for (int i = 0; i < SortedCategoryKeys.Count; i++)
|
||||||
|
{
|
||||||
|
uint key = SortedCategoryKeys[i];
|
||||||
|
CategoryBucket bucket = BucketsByKey[key];
|
||||||
|
AllCategories.Add(new CategorizedInventory(bucket.Key, bucket.Category, bucket.Items));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IReadOnlyList<CategorizedInventory> GetInventoryItemCategories(string filterString = "", bool invert = false)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(filterString))
|
||||||
|
return AllCategories;
|
||||||
|
|
||||||
|
Regex? re = null;
|
||||||
|
bool regexValid = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
re = new Regex(filterString, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
regexValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FilteredCategories.Clear();
|
||||||
|
|
||||||
|
for (int i = 0; i < AllCategories.Count; i++)
|
||||||
|
{
|
||||||
|
CategorizedInventory cat = AllCategories[i];
|
||||||
|
CategoryBucket bucket = BucketsByKey[cat.Key];
|
||||||
|
|
||||||
|
var filtered = bucket.FilteredItems;
|
||||||
|
filtered.Clear();
|
||||||
|
|
||||||
|
var src = bucket.Items;
|
||||||
|
for (int j = 0; j < src.Count; j++)
|
||||||
|
{
|
||||||
|
ItemInfo info = src[j];
|
||||||
|
|
||||||
|
bool isMatch;
|
||||||
|
if (regexValid)
|
||||||
|
{
|
||||||
|
isMatch = info.IsRegexMatch(re!);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isMatch = info.Name.Contains(filterString, StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| info.DescriptionContains(filterString);
|
||||||
|
}
|
||||||
|
|
||||||
bool isMatch = info.IsRegexMatch(re);
|
|
||||||
if (isMatch != invert)
|
if (isMatch != invert)
|
||||||
filtered.Add(info);
|
filtered.Add(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
return filtered;
|
if (filtered.Count != 0)
|
||||||
|
FilteredCategories.Add(new CategorizedInventory(bucket.Key, bucket.Category, filtered));
|
||||||
|
}
|
||||||
|
|
||||||
|
return FilteredCategories;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetEmptyItemSlotsString()
|
||||||
|
{
|
||||||
|
uint empty = InventoryManager.Instance()->GetEmptySlotsInBag();
|
||||||
|
uint used = 140 - empty;
|
||||||
|
return $"{used}/140";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CurrencyInfo GetCurrencyInfo(uint itemId)
|
||||||
|
{
|
||||||
|
return new CurrencyInfo
|
||||||
|
{
|
||||||
|
Amount = InventoryManager.Instance()->GetInventoryItemCount(1),
|
||||||
|
ItemId = itemId,
|
||||||
|
IconId = Services.DataManager.GetExcelSheet<Item>().GetRow(itemId).Icon
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ClearAll()
|
||||||
|
{
|
||||||
|
AggByItemId.Clear();
|
||||||
|
ItemInfoByItemId.Clear();
|
||||||
|
|
||||||
|
foreach (var kvp in BucketsByKey)
|
||||||
|
{
|
||||||
|
kvp.Value.Items.Clear();
|
||||||
|
kvp.Value.FilteredItems.Clear();
|
||||||
|
kvp.Value.Used = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SortedCategoryKeys.Clear();
|
||||||
|
AllCategories.Clear();
|
||||||
|
FilteredCategories.Clear();
|
||||||
|
RemoveKeysScratch.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CategoryInfo GetCategoryInfoForKeyCached(uint key, ItemInfo sample)
|
private static CategoryInfo GetCategoryInfoForKeyCached(uint key, ItemInfo sample)
|
||||||
@@ -202,22 +311,6 @@ public static unsafe class InventoryState
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static uint GetEmptyItemSlots() => InventoryManager.Instance()->GetEmptySlotsInBag();
|
|
||||||
|
|
||||||
private static uint GetUsedItemSlots() => 140 - GetEmptyItemSlots();
|
|
||||||
|
|
||||||
public static string GetEmptyItemSlotsString() => $"{GetUsedItemSlots()}/140";
|
|
||||||
|
|
||||||
public static CurrencyInfo GetCurrencyInfo(uint itemId)
|
|
||||||
{
|
|
||||||
return new CurrencyInfo
|
|
||||||
{
|
|
||||||
Amount = InventoryManager.Instance()->GetInventoryItemCount(1),
|
|
||||||
ItemId = itemId,
|
|
||||||
IconId = Services.DataManager.GetExcelSheet<Item>().GetRow(itemId).Icon
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct AggregatedItem
|
private struct AggregatedItem
|
||||||
{
|
{
|
||||||
public InventoryItem First;
|
public InventoryItem First;
|
||||||
@@ -230,8 +323,10 @@ public static unsafe class InventoryState
|
|||||||
|
|
||||||
public int Compare(ItemInfo x, ItemInfo y)
|
public int Compare(ItemInfo x, ItemInfo y)
|
||||||
{
|
{
|
||||||
if (x.ItemCount > y.ItemCount) return -1;
|
int a = x.ItemCount;
|
||||||
if (x.ItemCount < y.ItemCount) return 1;
|
int b = y.ItemCount;
|
||||||
|
if (a > b) return -1;
|
||||||
|
if (a < b) return 1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,5 +336,7 @@ public static unsafe class InventoryState
|
|||||||
public uint Key;
|
public uint Key;
|
||||||
public CategoryInfo Category = null!;
|
public CategoryInfo Category = null!;
|
||||||
public List<ItemInfo> Items = null!;
|
public List<ItemInfo> Items = null!;
|
||||||
|
public List<ItemInfo> FilteredItems = null!;
|
||||||
|
public bool Used;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ public sealed class ItemInfo : IEquatable<ItemInfo>
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool DescriptionContains(string value)
|
||||||
|
=> Description.Contains(value, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
public bool Equals(ItemInfo? other)
|
public bool Equals(ItemInfo? other)
|
||||||
=> other is not null && Item.ItemId == other.Item.ItemId && ItemCount == other.ItemCount;
|
=> other is not null && Item.ItemId == other.Item.ItemId && ItemCount == other.ItemCount;
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ public class InventoryCategoryNode : SimpleComponentNode
|
|||||||
_itemGridNode.AttachNode(this);
|
_itemGridNode.AttachNode(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public required CategorizedInventory CategorizedInventory
|
public CategorizedInventory CategorizedInventory
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set
|
set
|
||||||
|
|||||||
+1
-1
Submodule KamiToolKit updated: 44ac1e0c3a...aa278153f3
Reference in New Issue
Block a user