Merge branch 'dev/pie-lover'

This commit is contained in:
Shawrkie Williams
2026-01-09 18:00:16 -05:00
38 changed files with 802 additions and 268 deletions
@@ -1,7 +1,6 @@
using System;
using System.Linq;
using AetherBags.Configuration;
using AetherBags.Inventory;
using AetherBags.Inventory.Context;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
+70 -3
View File
@@ -1,5 +1,7 @@
using System.Collections.Generic;
using System.Numerics;
using AetherBags.Inventory.Context;
using AetherBags.Inventory.Items;
using AetherBags.Inventory.State;
using AetherBags.Nodes.Input;
using AetherBags.Nodes.Inventory;
@@ -15,6 +17,7 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
{
private readonly MainBagState _inventoryState = new();
private InventoryNotificationNode _notificationNode = null!;
private LootedItemsCategoryNode _lootedCategoryNode = null!;
protected override InventoryStateBase InventoryState => _inventoryState;
@@ -22,7 +25,7 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
{
InitializeBackgroundDropTarget();
CategoriesNode = new WrappingGridNode<InventoryCategoryNode>
CategoriesNode = new WrappingGridNode<InventoryCategoryNodeBase>
{
Position = ContentStartPosition,
Size = ContentSize,
@@ -33,6 +36,13 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
};
CategoriesNode.AttachNode(this);
_lootedCategoryNode = new LootedItemsCategoryNode
{
ItemsPerLine = 10,
OnDismissItem = OnDismissLootedItem,
OnClearAll = OnClearAllLootedItems,
};
var header = CalculateHeaderLayout(addon);
_notificationNode = new InventoryNotificationNode
@@ -71,14 +81,69 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
addon->SubscribeAtkArrayData(1, (int)NumberArrayType.Inventory);
_isSetupComplete = true;
System.LootedItemsTracker.OnLootedItemsChanged += OnLootedItemsChanged;
IsSetupComplete = true;
_inventoryState.RefreshFromGame();
var existingLoot = System.LootedItemsTracker.LootedItems;
if (existingLoot.Count > 0)
{
UpdateLootedCategory(existingLoot);
}
RefreshCategoriesCore(autosize: true);
base.OnSetup(addon);
}
private void OnLootedItemsChanged(IReadOnlyList<LootedItemInfo> lootedItems)
{
if (!IsOpen || !IsSetupComplete) return;
Services.Framework.RunOnTick(() =>
{
if (!IsOpen) return;
UpdateLootedCategory(lootedItems);
}, delayTicks: 1);
}
private void UpdateLootedCategory(IReadOnlyList<LootedItemInfo> lootedItems)
{
_lootedCategoryNode.UpdateLootedItems(lootedItems);
if (lootedItems.Count > 0)
{
if (CategoriesNode.HoistedNode != _lootedCategoryNode)
{
CategoriesNode.SetHoistedNode(_lootedCategoryNode);
}
}
else
{
using (CategoriesNode.DeferRecalculateLayout())
{
if (CategoriesNode.HoistedNode == _lootedCategoryNode)
{
CategoriesNode.SetHoistedNode(null);
}
CategoriesNode.RemoveNode(_lootedCategoryNode);
}
}
}
private void OnDismissLootedItem(int index)
{
System.LootedItemsTracker.RemoveByIndex(index);
}
private void OnClearAllLootedItems()
{
System.LootedItemsTracker.Clear();
}
public void ManualCurrencyRefresh()
{
if (!Services.ClientState.IsLoggedIn) return;
@@ -95,6 +160,8 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
protected override void OnFinalize(AtkUnitBase* addon)
{
System.LootedItemsTracker.OnLootedItemsChanged -= OnLootedItemsChanged;
ref var blockingAddonId = ref AgentInventoryContext.Instance()->BlockingAddonId;
if (blockingAddonId != 0)
{
@@ -103,7 +170,7 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
addon->UnsubscribeAtkArrayData(1, (int)NumberArrayType.Inventory);
_isSetupComplete = false;
IsSetupComplete = false;
base.OnFinalize(addon);
}
}
+4 -4
View File
@@ -38,7 +38,7 @@ public unsafe class AddonRetainerWindow : InventoryAddonBase
WindowNode?.AddColor = _tintColor;
CategoriesNode = new WrappingGridNode<InventoryCategoryNode>
CategoriesNode = new WrappingGridNode<InventoryCategoryNodeBase>
{
Position = ContentStartPosition,
Size = ContentSize,
@@ -107,7 +107,7 @@ public unsafe class AddonRetainerWindow : InventoryAddonBase
LayoutContent();
_inventoryState.RefreshFromGame();
_isSetupComplete = true;
IsSetupComplete = true;
RefreshCategoriesCore(autosize: true);
@@ -116,7 +116,7 @@ public unsafe class AddonRetainerWindow : InventoryAddonBase
protected override void RefreshCategoriesCore(bool autosize)
{
if (!_isSetupComplete)
if (!IsSetupComplete)
return;
_slotCounterNode.String = _inventoryState.GetEmptySlotsString();
@@ -179,7 +179,7 @@ public unsafe class AddonRetainerWindow : InventoryAddonBase
protected override void OnFinalize(AtkUnitBase* addon)
{
_isSetupComplete = false;
IsSetupComplete = false;
CloseRetainerWindows();
+4 -4
View File
@@ -31,7 +31,7 @@ public unsafe class AddonSaddleBagWindow : InventoryAddonBase
WindowNode?.AddColor = _tintColor;
CategoriesNode = new WrappingGridNode<InventoryCategoryNode>
CategoriesNode = new WrappingGridNode<InventoryCategoryNodeBase>
{
Position = ContentStartPosition,
Size = ContentSize,
@@ -80,7 +80,7 @@ public unsafe class AddonSaddleBagWindow : InventoryAddonBase
_inventoryState.RefreshFromGame();
_isSetupComplete = true;
IsSetupComplete = true;
RefreshCategoriesCore(autosize: true);
@@ -89,7 +89,7 @@ public unsafe class AddonSaddleBagWindow : InventoryAddonBase
protected override void RefreshCategoriesCore(bool autosize)
{
if (!_isSetupComplete)
if (!IsSetupComplete)
return;
_slotCounterNode.String = _inventoryState.GetEmptySlotsString();
@@ -99,7 +99,7 @@ public unsafe class AddonSaddleBagWindow : InventoryAddonBase
protected override void OnFinalize(AtkUnitBase* addon)
{
_isSetupComplete = false;
IsSetupComplete = false;
if (System.Config.General.HideGameSaddleBags)
{
-1
View File
@@ -1,5 +1,4 @@
using AetherBags.Configuration;
using AetherBags.Inventory;
using AetherBags.Inventory.Categories;
using KamiToolKit.Premade;
+3
View File
@@ -1,3 +1,5 @@
using AetherBags.Inventory.Items;
namespace AetherBags.Addons;
public interface IInventoryWindow
@@ -8,4 +10,5 @@ public interface IInventoryWindow
void ManualRefresh();
void ItemRefresh();
void SetSearchText(string searchText);
InventoryStats GetStats();
}
+17 -8
View File
@@ -6,6 +6,7 @@ using AetherBags.Helpers;
using AetherBags.Inventory;
using AetherBags.Inventory.Categories;
using AetherBags.Inventory.Context;
using AetherBags.Inventory.Items;
using AetherBags.Inventory.Scanning;
using AetherBags.Inventory.State;
using AetherBags.Nodes.Input;
@@ -27,7 +28,7 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
protected readonly HashSet<InventoryCategoryNode> HoverSubscribed = new();
protected DragDropNode BackgroundDropTarget = null!;
protected WrappingGridNode<InventoryCategoryNode> CategoriesNode = null!;
protected WrappingGridNode<InventoryCategoryNodeBase> CategoriesNode = null!;
protected TextInputWithButtonNode SearchInputNode = null!;
protected InventoryFooterNode FooterNode = null!;
protected TextNode? SlotCounterNode { get; set; }
@@ -49,8 +50,7 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
protected bool RefreshQueued;
protected bool RefreshAutosizeQueued;
private bool _isRefreshing;
protected bool _isSetupComplete;
protected bool IsSetupComplete;
protected abstract InventoryStateBase InventoryState { get; }
@@ -59,13 +59,14 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
protected virtual bool HasSlotCounter => false;
private readonly HashSet<uint> _searchMatchScratch = new();
private bool _isRefreshing;
public void ManualRefresh()
{
if (!IsOpen) return;
if (!Services.ClientState.IsLoggedIn) return;
if (_isRefreshing) return;
if (!_isSetupComplete) return;
if (!IsSetupComplete) return;
try
{
@@ -82,6 +83,8 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
public string GetSearchText() => SearchInputNode?.SearchString.ExtractText() ?? string.Empty;
public InventoryStats GetStats() => InventoryState.GetStats();
public virtual void SetSearchText(string searchText)
{
Services.Framework.RunOnTick(() =>
@@ -93,7 +96,7 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
public void RefreshFromLifecycle()
{
if (!_isSetupComplete) return;
if (!IsSetupComplete) return;
if (!IsOpen) return;
if (_isRefreshing) return;
@@ -111,7 +114,7 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
protected virtual void RefreshCategoriesCore(bool autosize)
{
if (!_isSetupComplete)
if (!IsSetupComplete)
return;
var config = System.Config.General;
@@ -166,7 +169,7 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
CategoriesNode.SyncWithListDataByKey<CategorizedInventory, InventoryCategoryNode, uint>(
dataList: categories,
getKeyFromData: categorizedInventory => categorizedInventory.Key,
getKeyFromNode: node => node.CategorizedInventory.Key,
getKeyFromNode: node => node.Key,
updateNode: (node, data) =>
{
node.CategorizedInventory = data;
@@ -394,7 +397,13 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
protected void ResizeWindow(float width, float height)
=> ResizeWindow(width, height, recalcLayout: true);
public void ItemRefresh() => RefreshCategoriesCore(false);
public void ItemRefresh()
{
if (!IsOpen) return;
if (!IsSetupComplete) return;
RefreshCategoriesCore(false);
}
protected override void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
{
@@ -1,4 +1,3 @@
using System;
using AetherBags.Configuration;
using AetherBags.Inventory;
using AetherBags.Inventory.Context;
+39 -5
View File
@@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using AetherBags.Addons;
using AetherBags.Helpers;
using AetherBags.Inventory;
using AetherBags.Inventory.State;
using AetherBags.Inventory.Items;
using Dalamud.Game.Command;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
namespace AetherBags.Commands;
@@ -30,7 +31,7 @@ public class CommandHandler : IDisposable
});
}
private unsafe void OnCommand(string command, string args)
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;
@@ -88,8 +89,7 @@ public class CommandHandler : IDisposable
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");
PrintInventoryStats();
break;
case "saddle":
@@ -111,6 +111,40 @@ public class CommandHandler : IDisposable
}
}
private void PrintInventoryStats()
{
var openWindows = new List<(string Name, IInventoryWindow Window)>();
if (System.AddonInventoryWindow.IsOpen)
openWindows.Add(("Main", System.AddonInventoryWindow));
if (System.AddonSaddleBagWindow.IsOpen)
openWindows.Add(("Saddle", System.AddonSaddleBagWindow));
if (System.AddonRetainerWindow.IsOpen)
openWindows.Add(("Retainer", System.AddonRetainerWindow));
if (openWindows.Count == 0)
{
PrintChat("No inventory windows are open. Open an inventory to see stats.");
return;
}
foreach (var (name, window) in openWindows)
{
var stats = window.GetStats();
PrintChat($"[{name}] {stats.UsedSlots}/{stats.TotalSlots} slots ({stats.UsagePercent:F0}%) | {stats.TotalItems} items | {stats.CategoryCount} categories");
}
if (openWindows.Count > 1)
{
var combined = new InventoryStats();
foreach (var (_, window) in openWindows)
{
combined += window.GetStats();
}
PrintChat($"[Total] {combined.UsedSlots}/{combined.TotalSlots} slots ({combined.UsagePercent:F0}%) | {combined.TotalItems} items | {combined.CategoryCount} categories");
}
}
private void HandleSearch(string searchTerm)
{
if (!System.AddonInventoryWindow.IsOpen)
@@ -4,8 +4,36 @@ public class SystemConfiguration
{
public const string FileName = "AetherBags.json";
private GeneralSettings _general = new();
private CategorySettings _categories = new();
private CurrencySettings _currency = new();
public GeneralSettings General { get; set; } = new();
public CategorySettings Categories { get; set; } = new();
public CurrencySettings Currency { get; set; } = new();
public GeneralSettings General
{
get => _general;
set => _general = value ?? new();
}
public CategorySettings Categories
{
get => _categories;
set => _categories = value ?? new();
}
public CurrencySettings Currency
{
get => _currency;
set => _currency = value ?? new();
}
/// <summary>
/// Ensures all nested config objects are initialized. Call after deserialization.
/// </summary>
public void EnsureInitialized()
{
_general ??= new();
_categories ??= new();
_currency ??= new();
_categories.UserCategories ??= new();
}
}
@@ -1,11 +1,12 @@
using System.Collections.Generic;
using System.Linq;
using AetherBags;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace AetherBags.Extensions;
public static class AddonLifecycleExtensions {
extension(IAddonLifecycle addonLifecycle) {
public void LogAddon(string addonName, params AddonEvent[] loggedModules) {
@@ -1,14 +1,11 @@
using AetherBags.Inventory;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using Lumina.Text.ReadOnly;
using Lumina.Text;
namespace AetherBags.Extensions;
public static unsafe class DragDropPayloadExtensions
public static class DragDropPayloadExtensions
{
extension(DragDropPayload payload)
{
+8 -2
View File
@@ -8,10 +8,16 @@ public static class LoggerExtensions
{
if (System.Config?.General?.DebugEnabled == true)
{
Services.Logger.DebugOnly(message);
Services.Logger.Debug(message);
}
}
public void DebugOnly(string message, params object[] args) => DebugOnly(logger, string.Format(message, args));
public void DebugOnly(string message, params object[] args)
{
if (System.Config?.General?.DebugEnabled == true)
{
Services.Logger.Debug(message, args);
}
}
}
}
@@ -1,7 +1,5 @@
using System;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
+8 -2
View File
@@ -87,11 +87,17 @@ public static class Util
private static SystemConfiguration LoadConfig()
{
FileInfo file = JsonFileHelper.GetFileInfo(SystemConfiguration.FileName);
return JsonFileHelper.LoadFile<SystemConfiguration>(file.FullName);
var config = JsonFileHelper.LoadFile<SystemConfiguration>(file.FullName);
config?.EnsureInitialized();
return config;
}
public static SystemConfiguration LoadConfigOrDefault()
=> LoadConfig() ?? new SystemConfiguration();
{
var config = LoadConfig() ?? new SystemConfiguration();
config.EnsureInitialized();
return config;
}
public static SystemConfiguration ResetConfig()
=> new SystemConfiguration();
-2
View File
@@ -1,6 +1,4 @@
using System;
using Dalamud.Plugin;
using Dalamud.Plugin.Ipc;
namespace AetherBags.IPC;
+34 -3
View File
@@ -8,31 +8,46 @@ namespace AetherBags.Inventory;
public static unsafe class InventoryOrchestrator
{
private static readonly InventoryNotificationState NotificationState = new();
private static bool _isRefreshing;
public static void RefreshAll(bool updateMaps = true)
{
if (_isRefreshing)
return;
try
{
_isRefreshing = true;
if (updateMaps)
{
InventoryContextState.RefreshMaps();
InventoryContextState.RefreshBlockedSlots();
}
if (!HasAnyWindowOpen())
return;
var agent = AgentInventory.Instance();
var contextId = agent != null ? agent->OpenTitleId : 0;
var notification = NotificationState.GetNotificationInfo(contextId);
Services.Framework.RunOnTick(() =>
{
if (System.AddonInventoryWindow.IsOpen)
System.AddonInventoryWindow.SetNotification(notification!);
if (notification != null && System.AddonInventoryWindow.IsOpen)
System.AddonInventoryWindow.SetNotification(notification);
foreach (var window in GetAllWindows())
{
if (window.IsOpen)
window.ManualRefresh();
}
});
}
finally
{
_isRefreshing = false;
}
}
public static void CloseAll()
{
@@ -44,6 +59,9 @@ public static unsafe class InventoryOrchestrator
public static void RefreshHighlights()
{
if (!HasAnyWindowOpen())
return;
Services.Framework.RunOnTick(() =>
{
foreach (var window in GetAllWindows())
@@ -53,10 +71,23 @@ public static unsafe class InventoryOrchestrator
});
}
private static bool HasAnyWindowOpen()
{
foreach (var window in GetAllWindows())
{
if (window.IsOpen)
return true;
}
return false;
}
private static IEnumerable<IInventoryWindow> GetAllWindows()
{
if (System.AddonInventoryWindow != null)
yield return System.AddonInventoryWindow;
if (System.AddonSaddleBagWindow != null)
yield return System.AddonSaddleBagWindow;
if (System.AddonRetainerWindow != null)
yield return System.AddonRetainerWindow;
}
}
@@ -9,4 +9,13 @@ public readonly struct InventoryStats
public int CategoryCount { get; init; }
public int UsedSlots => TotalSlots - EmptySlots;
public float UsagePercent => TotalSlots > 0 ? (float)UsedSlots / TotalSlots * 100f : 0f;
public static InventoryStats operator +(InventoryStats a, InventoryStats b) => new()
{
TotalItems = a.TotalItems + b.TotalItems,
TotalQuantity = a.TotalQuantity + b.TotalQuantity,
EmptySlots = a.EmptySlots + b.EmptySlots,
TotalSlots = a.TotalSlots + b.TotalSlots,
CategoryCount = a.CategoryCount + b.CategoryCount,
};
}
+101
View File
@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AetherBags.Inventory.Items;
using AetherBags.Inventory.Scanning;
using Dalamud.Game.Inventory.InventoryEventArgTypes;
using FFXIVClientStructs.FFXIV.Client.Game;
namespace AetherBags.Inventory;
public sealed unsafe class LootedItemsTracker : IDisposable
{
private static IReadOnlyList<InventoryType> StandardInventories => InventoryScanner.StandardInventories;
private readonly List<LootedItemInfo> _lootedItems = new(capacity: 64);
private bool _isEnabled;
public event Action<IReadOnlyList<LootedItemInfo>>? OnLootedItemsChanged;
public IReadOnlyList<LootedItemInfo> LootedItems => _lootedItems;
public void Enable()
{
if (_isEnabled) return;
_isEnabled = true;
_lootedItems.Clear();
Services.GameInventory.InventoryChangedRaw += OnInventoryChangedRaw;
}
public void Disable()
{
if (!_isEnabled) return;
_isEnabled = false;
Services.GameInventory.InventoryChangedRaw -= OnInventoryChangedRaw;
_lootedItems.Clear();
}
public void Clear()
{
_lootedItems.Clear();
OnLootedItemsChanged?.Invoke(_lootedItems);
}
public void RemoveByIndex(int index)
{
for (int i = 0; i < _lootedItems.Count; i++)
{
if (_lootedItems[i].Index == index)
{
_lootedItems.RemoveAt(i);
OnLootedItemsChanged?.Invoke(_lootedItems);
return;
}
}
}
public void Dispose()
{
Disable();
}
private void OnInventoryChangedRaw(IReadOnlyCollection<InventoryEventArgs> events)
{
if (!_isEnabled) return;
if (!Services.ClientState.IsLoggedIn) return;
bool hasChanges = false;
foreach (var eventData in events)
{
if (!StandardInventories.Contains((InventoryType)eventData.Item.ContainerType)) continue;
if (eventData is not (InventoryItemAddedArgs or InventoryItemChangedArgs)) continue;
if (eventData is InventoryItemChangedArgs changedArgs &&
changedArgs.OldItemState.Quantity >= changedArgs.Item.Quantity)
{
continue;
}
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));
hasChanges = true;
}
if (hasChanges)
{
OnLootedItemsChanged?.Invoke(_lootedItems);
}
}
}
@@ -180,19 +180,21 @@ public static unsafe class InventoryScanner
return InventoryLocation.Invalid;
}
public static string GetEmptySlotsString(InventorySourceType source)
{
int total = InventorySourceDefinitions.GetTotalSlots(source);
uint empty = source switch
public static int GetEmptySlots(InventorySourceType source) => (int)(source switch
{
InventorySourceType.MainBags => InventoryManager.Instance()->GetEmptySlotsInBag(),
InventorySourceType.SaddleBag => GetEmptySlotsInContainer(InventorySourceDefinitions.SaddleBag),
InventorySourceType.PremiumSaddleBag => GetEmptySlotsInContainer(InventorySourceDefinitions.PremiumSaddleBag),
InventorySourceType.AllSaddleBags => GetEmptySlotsInContainer(InventorySourceDefinitions.AllSaddleBags),
InventorySourceType.Retainer => GetEmptySlotsInContainer(InventorySourceDefinitions.Retainer),
_ => 0,
};
uint used = (uint)total - empty;
_ => 0u,
});
public static string GetEmptySlotsString(InventorySourceType source)
{
int total = InventorySourceDefinitions.GetTotalSlots(source);
int empty = GetEmptySlots(source);
int used = total - empty;
return $"{used}/{total}";
}
@@ -1,130 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using AetherBags.Configuration;
using AetherBags.Currency;
using AetherBags.Inventory.Categories;
using AetherBags.Inventory.Items;
using AetherBags.Inventory.Scanning;
using Dalamud.Game.Inventory;
using Dalamud.Game.Inventory.InventoryEventArgTypes;
using FFXIVClientStructs.FFXIV.Client.Game;
namespace AetherBags.Inventory.State;
public static unsafe class InventoryState
{
private 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 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 = FFXIVClientStructs.FFXIV.Client.Game.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 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();
}
}
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using AetherBags.Configuration;
using AetherBags.Currency;
using AetherBags.Inventory.Categories;
using AetherBags.Inventory.Context;
using AetherBags.Inventory.Items;
@@ -165,6 +166,38 @@ public abstract class InventoryStateBase
public string GetEmptySlotsString() => InventoryScanner.GetEmptySlotsString(SourceType);
public InventoryStats GetStats()
{
int totalItems = ItemInfoByKey.Count;
int totalQuantity = 0;
foreach (var kvp in ItemInfoByKey)
{
totalQuantity += kvp.Value.ItemCount;
}
int totalSlots = InventorySourceDefinitions.GetTotalSlots(SourceType);
int emptySlots = InventoryScanner.GetEmptySlots(SourceType);
var categories = GetCategories(string.Empty);
int categoryCount = categories.Count;
return new InventoryStats
{
TotalItems = totalItems,
TotalQuantity = totalQuantity,
EmptySlots = emptySlots,
TotalSlots = totalSlots,
CategoryCount = categoryCount,
};
}
public static IReadOnlyList<CurrencyInfo> GetCurrencyInfoList(uint[] currencyIds)
=> CurrencyState.GetCurrencyInfoList(currencyIds);
public static void InvalidateCurrencyCaches()
=> CurrencyState.InvalidateCaches();
protected virtual void ClearAll()
{
AggByKey.Clear();
@@ -1,6 +1,5 @@
using AetherBags. Inventory.Scanning;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
namespace AetherBags. Inventory.State;
@@ -1,6 +1,5 @@
using System;
using AetherBags.Addons;
using KamiToolKit.Nodes;
using KamiToolKit.Premade.Nodes;
namespace AetherBags.Nodes.Configuration.Category;
@@ -3,15 +3,12 @@ using System.Collections.Generic;
using System.Numerics;
using AetherBags.Configuration;
using AetherBags.Inventory;
using AetherBags.Inventory.Categories;
using AetherBags.Nodes.Color;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Nodes;
using Lumina.Excel;
using Lumina.Excel.Sheets;
using Lumina.Text;
using Action = System.Action;
namespace AetherBags.Nodes.Configuration.Category;
@@ -1,4 +1,3 @@
using System;
using System.IO;
using AetherBags.Helpers;
using AetherBags.Inventory;
@@ -1,4 +1,4 @@
using AetherBags.Nodes.Layout;
using AetherBags.Nodes.Layout;
namespace AetherBags.Nodes.Inventory;
@@ -6,12 +6,19 @@ public sealed class InventoryCategoryHoverCoordinator
{
private InventoryCategoryNode? _active;
private int _activeRowIndex = -1;
private bool _isProcessing;
public void OnCategoryHoverChanged(
WrappingGridNode<InventoryCategoryNode> grid,
WrappingGridNode<InventoryCategoryNodeBase> grid,
InventoryCategoryNode source,
bool hovering)
{
if (_isProcessing)
return;
try
{
_isProcessing = true;
grid.RecalculateLayout();
if (hovering)
@@ -59,23 +66,34 @@ public sealed class InventoryCategoryHoverCoordinator
_activeRowIndex = -1;
}
finally
{
_isProcessing = false;
}
}
public void ResetAll(WrappingGridNode<InventoryCategoryNode> grid)
public void ResetAll(WrappingGridNode<InventoryCategoryNodeBase> grid)
{
_active = null;
_activeRowIndex = -1;
ClearAll(grid);
}
private static void ClearAll(WrappingGridNode<InventoryCategoryNode> grid)
private static void ClearAll(WrappingGridNode<InventoryCategoryNodeBase> grid)
{
foreach (var cat in grid.GetNodes<InventoryCategoryNode>())
foreach (var node in grid.GetNodes<InventoryCategoryNodeBase>())
{
if (node is InventoryCategoryNode cat)
cat.SetHeaderSuppressed(false);
}
}
private static void SuppressAllExcept(WrappingGridNode<InventoryCategoryNode> grid, InventoryCategoryNode source)
private static void SuppressAllExcept(WrappingGridNode<InventoryCategoryNodeBase> grid, InventoryCategoryNode source)
{
foreach (var cat in grid.GetNodes<InventoryCategoryNode>())
foreach (var node in grid.GetNodes<InventoryCategoryNodeBase>())
{
if (node is InventoryCategoryNode cat)
cat.SetHeaderSuppressed(!ReferenceEquals(cat, source));
}
}
}
@@ -1,7 +1,6 @@
using System;
using System.Numerics;
using AetherBags.Helpers;
using AetherBags.Hooks;
using AetherBags.Inventory;
using AetherBags.Inventory.Categories;
using AetherBags.Inventory.Items;
@@ -9,15 +8,17 @@ using AetherBags.Nodes.Layout;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Nodes;
namespace AetherBags.Nodes.Inventory;
public class InventoryCategoryNode : SimpleComponentNode
public class InventoryCategoryNode : InventoryCategoryNodeBase
{
private const uint CategoryNodeKeyBase = 0x10000000;
public override uint Key => CategoryNodeKeyBase | CategorizedInventory.Key;
private readonly TextNode _categoryNameTextNode;
private readonly HybridDirectionalFlexNode<DragDropNode> _itemGridNode;
@@ -109,7 +110,7 @@ public class InventoryCategoryNode : SimpleComponentNode
}
}
public bool IsPinnedInConfig => CategorizedInventory.Category?.IsPinned ?? false;
public override bool IsPinnedInConfig => CategorizedInventory.Category?.IsPinned ?? false;
public void BeginHeaderHover()
{
@@ -0,0 +1,20 @@
using KamiToolKit.Nodes;
namespace AetherBags.Nodes.Inventory;
/// <summary>
/// Base class for category-like nodes that can be displayed in the inventory grid.
/// Used to allow both regular categories and special categories (like looted items) to be hoisted/pinned.
/// </summary>
public abstract class InventoryCategoryNodeBase : SimpleComponentNode
{
/// <summary>
/// Unique key for this category, used for sync operations.
/// </summary>
public abstract uint Key { get; }
/// <summary>
/// Whether this category should be pinned in the layout.
/// </summary>
public virtual bool IsPinnedInConfig => false;
}
@@ -4,13 +4,13 @@ namespace AetherBags.Nodes.Inventory;
public sealed class InventoryCategoryPinCoordinator
{
public bool ApplyPinnedStates(WrappingGridNode<InventoryCategoryNode> grid)
public bool ApplyPinnedStates(WrappingGridNode<InventoryCategoryNodeBase> grid)
{
bool changed = false;
using (grid.DeferRecalculateLayout())
{
foreach (var node in grid.GetNodes<InventoryCategoryNode>())
foreach (var node in grid.GetNodes<InventoryCategoryNodeBase>())
{
bool shouldBePinned = node.IsPinnedInConfig;
@@ -38,7 +38,7 @@ public sealed class InventoryCategoryPinCoordinator
return changed;
}
public bool PrunePinnedNotInGrid(WrappingGridNode<InventoryCategoryNode> grid)
public bool PrunePinnedNotInGrid(WrappingGridNode<InventoryCategoryNodeBase> grid)
{
return false;
}
@@ -1,5 +1,4 @@
using System.Numerics;
using AetherBags.Inventory;
using AetherBags.Inventory.Items;
using Dalamud.Game.ClientState.Keys;
using FFXIVClientStructs.FFXIV.Client.Game;
@@ -1,13 +1,13 @@
using System.Collections.Generic;
using System.Numerics;
using AetherBags.Currency;
using AetherBags.Inventory;
using AetherBags.Inventory.State;
using AetherBags.Nodes.Currency;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Nodes;
using static AetherBags.Inventory.State.InventoryStateBase;
namespace AetherBags.Nodes.Inventory;
public sealed class InventoryFooterNode : SimpleComponentNode
@@ -44,7 +44,7 @@ public sealed class InventoryFooterNode : SimpleComponentNode
{
_currencyListNode.IsVisible = System.Config.Currency.Enabled;
IReadOnlyList<CurrencyInfo> currencyInfoList = InventoryState.GetCurrencyInfoList([1, 28, 0xFFFF_FFFE, 0xFFFF_FFFD]);
IReadOnlyList<CurrencyInfo> currencyInfoList = GetCurrencyInfoList([1, 28, 0xFFFF_FFFE, 0xFFFF_FFFD]);
_currencyListNode.SyncWithListDataByKey<CurrencyInfo, CurrencyNode, uint>(
dataList: currencyInfoList,
getKeyFromData: currencyInfo => currencyInfo.ItemId,
@@ -1,5 +1,4 @@
using System.Numerics;
using AetherBags.Inventory;
using AetherBags.Inventory.Context;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
@@ -0,0 +1,84 @@
using System;
using System.Numerics;
using AetherBags.Inventory.Items;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Nodes;
namespace AetherBags.Nodes.Inventory;
/// <summary>
/// A display-only item node for looted items. Not draggable, but shows tooltip and can be dismissed.
/// </summary>
public unsafe class LootedItemDisplayNode : SimpleComponentNode
{
private readonly IconNode _iconNode;
private readonly TextNode _quantityTextNode;
private readonly ResNode _collisionNode;
public Action<LootedItemDisplayNode>? OnDismiss { get; set; }
public LootedItemDisplayNode()
{
Size = new Vector2(42, 46);
_iconNode = new IconNode
{
Position = new Vector2(0, 0),
Size = new Vector2(42, 46),
NodeFlags = NodeFlags.Visible | NodeFlags.Enabled,
};
_iconNode.AttachNode(this);
_quantityTextNode = new TextNode
{
Size = new Vector2(40.0f, 12.0f),
Position = new Vector2(4.0f, 34.0f),
NodeFlags = NodeFlags.Enabled | NodeFlags.EmitsEvents,
Color = ColorHelper.GetColor(50),
TextOutlineColor = ColorHelper.GetColor(51),
TextFlags = TextFlags.Edge,
AlignmentType = AlignmentType.Right,
};
_quantityTextNode.AttachNode(this);
_collisionNode = new ResNode
{
Size = new Vector2(42, 46),
NodeFlags = NodeFlags.Enabled | NodeFlags.EmitsEvents | NodeFlags.HasCollision,
};
_collisionNode.AddEvent(AtkEventType.MouseOver, OnMouseOver);
_collisionNode.AddEvent(AtkEventType.MouseOut, OnMouseOut);
_collisionNode.AddEvent(AtkEventType.MouseClick, OnMouseClick);
_collisionNode.AttachNode(this);
}
public LootedItemInfo LootedItem { get; private set; } = null!;
public void SetLootedItem(LootedItemInfo lootedItem)
{
LootedItem = lootedItem;
var item = lootedItem.Item;
_iconNode.IconId = item.IconId;
_quantityTextNode.String = lootedItem.Quantity > 1 ? lootedItem.Quantity.ToString() : string.Empty;
}
private void OnMouseOver(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData)
{
var item = LootedItem.Item;
_collisionNode.ShowInventoryItemTooltip(item.Container, item.Slot);
}
private void OnMouseOut(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData)
{
ushort addonId = RaptureAtkUnitManager.Instance()->GetAddonByNode(_collisionNode)->Id;
AtkStage.Instance()->TooltipManager.HideTooltip(addonId);
}
private void OnMouseClick(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData)
{
if (!atkEventData->IsLeftClick) return;
OnDismiss?.Invoke(this);
}
}
@@ -0,0 +1,229 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using AetherBags.Inventory.Items;
using AetherBags.Nodes.Layout;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Nodes;
namespace AetherBags.Nodes.Inventory;
/// <summary>
/// A special category node for displaying recently looted items.
/// Items are not draggable but can be dismissed individually or cleared entirely.
/// </summary>
public class LootedItemsCategoryNode : InventoryCategoryNodeBase
{
private const uint LootedCategoryKey = 0x20000001;
public override uint Key => LootedCategoryKey;
private readonly TextNode _headerTextNode;
private readonly CircleButtonNode _clearButton;
private readonly HybridDirectionalFlexNode<LootedItemDisplayNode> _itemGridNode;
private const float HeaderHeight = 20;
private const float ClearButtonSize = 20;
private const float MinWidth = 100;
private IReadOnlyList<LootedItemInfo> _lootedItems = Array.Empty<LootedItemInfo>();
private int _hoverRefs;
private bool _headerExpanded;
private float _baseHeaderWidth = 96f;
private string _fullHeaderText = "Recently Looted";
public event Action<LootedItemsCategoryNode, bool>? HeaderHoverChanged;
public Action<int>? OnDismissItem { get; set; }
public Action? OnClearAll { get; set; }
public int ItemsPerLine
{
get => _itemGridNode.ItemsPerLine;
set
{
if (_itemGridNode.ItemsPerLine == value) return;
_itemGridNode.ItemsPerLine = value;
RecalculateSize();
}
}
public bool HasItems => _lootedItems.Count > 0;
public LootedItemsCategoryNode()
{
_headerTextNode = new TextNode
{
Position = Vector2.Zero,
Size = new Vector2(96, HeaderHeight),
AlignmentType = AlignmentType.Left,
String = "Recently Looted",
TextFlags = TextFlags.OverflowHidden | TextFlags.Ellipsis,
TextColor = new Vector4(0.9f, 0.8f, 0.5f, 1.0f), // Gold-ish color
};
_headerTextNode.AddEvent(AtkEventType.MouseOver, BeginHeaderHover);
_headerTextNode.AddEvent(AtkEventType.MouseOut, EndHeaderHover);
_headerTextNode.TextFlags |= TextFlags.OverflowHidden | TextFlags.Ellipsis;
_headerTextNode.TextFlags &= ~(TextFlags.WordWrap | TextFlags.MultiLine);
_headerTextNode.AddFlags(NodeFlags.EmitsEvents | NodeFlags.HasCollision);
_headerTextNode.AttachNode(this);
_clearButton = new CircleButtonNode
{
Size = new Vector2(ClearButtonSize),
Icon = ButtonIcon.CrossSmall,
OnClick = () => OnClearAll?.Invoke(),
};
_clearButton.AttachNode(this);
_itemGridNode = new HybridDirectionalFlexNode<LootedItemDisplayNode>
{
Position = new Vector2(0, HeaderHeight),
Size = new Vector2(240, 92),
FillRowsFirst = true,
ItemsPerLine = 10,
HorizontalPadding = 5,
VerticalPadding = 2,
};
_itemGridNode.NodeFlags |= NodeFlags.EmitsEvents;
_itemGridNode.AttachNode(this);
RecalculateSize();
}
public void UpdateLootedItems(IReadOnlyList<LootedItemInfo> lootedItems)
{
_lootedItems = lootedItems;
UpdateHeaderText();
SyncItemGrid();
RecalculateSize();
}
private void UpdateHeaderText()
{
_fullHeaderText = _lootedItems.Count > 0
? $"Recently Looted ({_lootedItems.Count})"
: "Recently Looted";
_headerTextNode.String = _fullHeaderText;
}
public void BeginHeaderHover()
{
_hoverRefs++;
if (_hoverRefs != 1) return;
_headerExpanded = true;
ApplyHeaderVisualStateAndSize();
HeaderHoverChanged?.Invoke(this, true);
}
public void EndHeaderHover()
{
if (_hoverRefs <= 0) return;
_hoverRefs--;
if (_hoverRefs != 0) return;
_headerExpanded = false;
ApplyHeaderVisualStateAndSize();
HeaderHoverChanged?.Invoke(this, false);
}
private void ApplyHeaderVisualStateAndSize()
{
var flags = _headerTextNode.TextFlags;
flags &= ~(TextFlags.WordWrap | TextFlags.MultiLine);
if (_headerExpanded)
{
flags &= ~(TextFlags.OverflowHidden | TextFlags.Ellipsis);
_headerTextNode.TextFlags = flags;
if (!string.IsNullOrEmpty(_fullHeaderText))
_headerTextNode.String = _fullHeaderText;
Vector2 drawSize = _headerTextNode.GetTextDrawSize();
float expandedWidth = MathF.Max(_baseHeaderWidth, drawSize.X + 4f);
_headerTextNode.Size = _headerTextNode.Size with { X = expandedWidth };
}
else
{
_headerTextNode.Size = _headerTextNode.Size with { X = _baseHeaderWidth };
if (!string.IsNullOrEmpty(_fullHeaderText))
_headerTextNode.String = _fullHeaderText;
flags |= TextFlags.OverflowHidden | TextFlags.Ellipsis;
_headerTextNode.TextFlags = flags;
}
}
private void SyncItemGrid()
{
_itemGridNode.SyncWithListData(
_lootedItems,
node => node.LootedItem,
CreateLootedItemNode);
}
private LootedItemDisplayNode CreateLootedItemNode(LootedItemInfo lootedItem)
{
var node = new LootedItemDisplayNode
{
OnDismiss = OnItemDismissed,
};
node.SetLootedItem(lootedItem);
return node;
}
private void OnItemDismissed(LootedItemDisplayNode node)
{
int index = node.LootedItem.Index;
OnDismissItem?.Invoke(index);
}
private void RecalculateSize()
{
int itemCount = _lootedItems.Count;
if (itemCount == 0)
{
float width = MinWidth;
Size = new Vector2(width, HeaderHeight);
_baseHeaderWidth = width - ClearButtonSize - 4;
_headerTextNode.Size = new Vector2(_baseHeaderWidth, HeaderHeight);
_clearButton.Position = new Vector2(width - ClearButtonSize, (HeaderHeight - ClearButtonSize) / 2);
_clearButton.IsVisible = false;
_itemGridNode.Position = new Vector2(0, HeaderHeight);
_itemGridNode.Size = new Vector2(width, 0);
ApplyHeaderVisualStateAndSize();
return;
}
int itemsPerLine = Math.Max(1, _itemGridNode.ItemsPerLine);
int rows = (itemCount + itemsPerLine - 1) / itemsPerLine;
int actualColumns = Math.Min(itemCount, itemsPerLine);
const float cellW = 42f;
const float cellH = 46f;
float hPad = _itemGridNode.HorizontalPadding;
float vPad = _itemGridNode.VerticalPadding;
float calculatedWidth = Math.Max(MinWidth, actualColumns * cellW + (actualColumns - 1) * hPad);
float gridHeight = rows * cellH + (rows - 1) * vPad;
float totalHeight = HeaderHeight + gridHeight;
Size = new Vector2(calculatedWidth, totalHeight);
_baseHeaderWidth = calculatedWidth - ClearButtonSize - 4;
_headerTextNode.Size = new Vector2(_baseHeaderWidth, HeaderHeight);
_clearButton.Position = new Vector2(calculatedWidth - ClearButtonSize, (HeaderHeight - ClearButtonSize) / 2);
_clearButton.IsVisible = true;
_itemGridNode.Position = new Vector2(0, HeaderHeight);
_itemGridNode.Size = new Vector2(calculatedWidth, gridHeight);
ApplyHeaderVisualStateAndSize();
}
}
@@ -1,6 +1,5 @@
using System. Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Nodes;
namespace AetherBags.Nodes.Inventory;
+5 -6
View File
@@ -6,16 +6,13 @@ using AetherBags.Helpers;
using AetherBags.Hooks;
using AetherBags.Inventory;
using AetherBags.Inventory.Context;
using AetherBags.Inventory.State;
using AetherBags.IPC;
using Dalamud.Game.Gui;
using Dalamud.Plugin;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using KamiToolKit;
namespace AetherBags;
public unsafe class Plugin : IDalamudPlugin
public class Plugin : IDalamudPlugin
{
private readonly CommandHandler _commandHandler;
private readonly InventoryHooks _inventoryHooks;
@@ -32,6 +29,7 @@ public unsafe class Plugin : IDalamudPlugin
KamiToolKitLibrary.Initialize(pluginInterface);
System.IPC = new IPCService();
System.LootedItemsTracker = new LootedItemsTracker();
System.AddonInventoryWindow = new AddonInventoryWindow
{
@@ -83,6 +81,7 @@ public unsafe class Plugin : IDalamudPlugin
_inventoryHooks.Dispose();
_inventoryLifecycles.Dispose();
System.LootedItemsTracker.Dispose();
System.IPC.Dispose();
HighlightState.ClearAll();
@@ -98,7 +97,7 @@ public unsafe class Plugin : IDalamudPlugin
private void OnLogin()
{
System.Config = Util.LoadConfigOrDefault();
InventoryState.TrackLootedItems = true;
System.LootedItemsTracker.Enable();
#if DEBUG
System.AddonInventoryWindow.Toggle();
@@ -109,7 +108,7 @@ public unsafe class Plugin : IDalamudPlugin
private void OnLogout(int type, int code)
{
Util.SaveConfig(System.Config);
InventoryState.TrackLootedItems = false;
System.LootedItemsTracker.Disable();
System.AddonInventoryWindow.Close();
System.AddonSaddleBagWindow.Close();
System.AddonRetainerWindow.Close();
+2
View File
@@ -1,5 +1,6 @@
using AetherBags.Addons;
using AetherBags.Configuration;
using AetherBags.Inventory;
using AetherBags.IPC;
namespace AetherBags;
@@ -12,4 +13,5 @@ public static class System
public static AddonConfigurationWindow AddonConfigurationWindow { get; set; } = null!;
public static IPCService IPC { get; set; } = null!;
public static SystemConfiguration Config { get; set; } = null!;
public static LootedItemsTracker LootedItemsTracker { get; set; } = null!;
}