Files
AetherBags/AetherBags/Inventory/InventoryState.cs
T
2025-12-29 15:36:06 -05:00

192 lines
6.9 KiB
C#

using AetherBags.Configuration;
using AetherBags.Currency;
using Dalamud.Game.Inventory;
using Dalamud.Game.Inventory.InventoryEventArgTypes;
using FFXIVClientStructs.FFXIV.Client.Game;
using System.Collections.Generic;
using System.Linq;
namespace AetherBags.Inventory;
public static unsafe class InventoryState
{
public static IReadOnlyList<InventoryType> StandardInventories => InventoryScanner.StandardInventories;
private static readonly Dictionary<ulong, AggregatedItem> AggByKey = new(capacity: 512);
private static readonly Dictionary<ulong, ItemInfo> ItemInfoByKey = 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<UserCategoryDefinition> UserCategoriesSortedScratch = new(capacity: 64);
private static readonly List<ulong> RemoveKeysScratch = new(capacity: 256);
private static readonly HashSet<ulong> ClaimedKeys = new(capacity: 512);
private static readonly List<LootedItemInfo>? LootedItems = new(capacity: 512);
public static bool TrackLootedItems = false;
public static bool Contains(this IReadOnlyCollection<InventoryType> inventoryTypes, GameInventoryType type)
=> inventoryTypes.Contains((InventoryType)type);
public static void RefreshFromGame()
{
InventoryManager* inventoryManager = InventoryManager.Instance();
if (inventoryManager == null)
{
ClearAll();
return;
}
var config = System.Config;
InventoryStackMode stackMode = config.General.StackMode;
bool userCategoriesEnabled = config.Categories.UserCategoriesEnabled;
bool gameCategoriesEnabled = config.Categories.GameCategoriesEnabled;
List<UserCategoryDefinition> userCategories = config.Categories.UserCategories.Where(category => category.Enabled).ToList();
Services.Logger.DebugOnly($"RefreshFromGame StackMode={stackMode}");
AggByKey.Clear();
ItemInfoByKey.Clear();
SortedCategoryKeys.Clear();
AllCategories.Clear();
FilteredCategories.Clear();
ClaimedKeys.Clear();
InventoryScanner.ScanBags(inventoryManager, stackMode, AggByKey);
CategoryBucketManager.ResetBuckets(BucketsByKey);
InventoryScanner.BuildItemInfos(AggByKey, ItemInfoByKey);
InventoryContextState.RefreshMaps();
InventoryContextState.RefreshBlockedSlots();
if (userCategoriesEnabled && userCategories.Count > 0)
{
CategoryBucketManager.BucketByUserCategories(
ItemInfoByKey,
userCategories,
BucketsByKey,
ClaimedKeys,
UserCategoriesSortedScratch);
}
if (gameCategoriesEnabled)
{
CategoryBucketManager.BucketByGameCategories(
ItemInfoByKey,
BucketsByKey,
ClaimedKeys,
userCategoriesEnabled);
}
else
{
CategoryBucketManager.BucketUnclaimedToMisc(
ItemInfoByKey,
BucketsByKey,
ClaimedKeys,
userCategoriesEnabled);
}
InventoryScanner.PruneStaleItemInfos(AggByKey, ItemInfoByKey, RemoveKeysScratch);
CategoryBucketManager.SortBucketsAndBuildKeyList(BucketsByKey, SortedCategoryKeys);
CategoryBucketManager.BuildCategorizedList(BucketsByKey, SortedCategoryKeys, AllCategories);
}
public static IReadOnlyList<CategorizedInventory> GetInventoryItemCategories(string filterString = "", bool invert = false)
{
return InventoryFilter.FilterCategories(
AllCategories,
BucketsByKey,
FilteredCategories,
filterString,
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();
public static IReadOnlyList<CurrencyInfo> GetCurrencyInfoList(uint[] currencyIds)
=> CurrencyState.GetCurrencyInfoList(currencyIds);
public static void InvalidateCurrencyCaches()
=> CurrencyState.InvalidateCaches();
public static InventoryContainer* GetInventoryContainer(InventoryType inventoryType)
=> InventoryScanner.GetInventoryContainer(inventoryType);
internal static void OnRawItemAdded(IReadOnlyCollection<InventoryEventArgs> events)
{
if (!TrackLootedItems) return;
bool updateRequested = false;
foreach (var eventData in events)
{
if (!StandardInventories.Contains(eventData.Item.ContainerType)) continue;
if (!Services.ClientState.IsLoggedIn) return;
if (eventData is not (InventoryItemAddedArgs or InventoryItemChangedArgs)) return;
if (eventData is InventoryItemChangedArgs changedArgs && changedArgs.OldItemState.Quantity >= changedArgs.Item.Quantity) return;
var inventoryItem = (InventoryItem*)eventData.Item.Address;
var changeAmount = eventData is InventoryItemChangedArgs changed ? changed.Item.Quantity - changed.OldItemState.Quantity : eventData.Item.Quantity;
LootedItems?.Add(new LootedItemInfo(
LootedItems.Count,
*inventoryItem,
changeAmount)
);
updateRequested = true;
}
if (updateRequested)
{
System.AddonInventoryWindow?.UpdateLootedCategory(LootedItems ?? []);
}
}
private static void ClearAll()
{
AggByKey.Clear();
ItemInfoByKey.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();
ClaimedKeys.Clear();
LootedItems?.Clear();
}
}