InventoryMonitor, LootedItemsTracker Fix

This commit is contained in:
Zeffuro
2026-01-11 19:33:14 +01:00
parent 5f6fa7ff2f
commit 2172ff0a51
6 changed files with 113 additions and 111 deletions
+1 -1
View File
@@ -139,6 +139,7 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
private void OnClearAllLootedItems()
{
System.LootedItemsTracker.Clear();
System.LootedItemsTracker.FlushPendingChanges();
}
public void ManualCurrencyRefresh()
@@ -155,7 +156,6 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
}, delayTicks: 3);
}
protected override void OnFinalize(AtkUnitBase* addon)
{
System.LootedItemsTracker.OnLootedItemsChanged -= OnLootedItemsChanged;
+10 -37
View File
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using AetherBags.AddonLifecycles;
using AetherBags.Configuration;
using AetherBags.Helpers;
using AetherBags.Inventory;
@@ -10,6 +9,7 @@ using AetherBags.Inventory.Context;
using AetherBags.Inventory.Items;
using AetherBags.Inventory.Scanning;
using AetherBags.Inventory.State;
using AetherBags.Monitoring;
using AetherBags.Nodes.Input;
using AetherBags.Nodes.Inventory;
using AetherBags.Nodes.Layout;
@@ -66,25 +66,7 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
private int _refreshFromLifecycleCount;
private long _lastLogTick;
public void ManualRefresh()
{
if (!IsOpen) return;
if (!Services.ClientState.IsLoggedIn) return;
if (_isRefreshing) return;
if (!IsSetupComplete) return;
try
{
_isRefreshing = true;
InventoryState.RefreshFromGame();
RefreshCategoriesCore(autosize: true);
}
finally
{
_isRefreshing = false;
}
}
public void ManualRefresh() => ExecuteRefresh(true);
public string GetSearchText() => SearchInputNode?.SearchString.ExtractText() ?? string.Empty;
@@ -99,21 +81,16 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
}, delayTicks: 3);
}
public void RefreshFromLifecycle()
private void ExecuteRefresh(bool autosize)
{
if (!IsSetupComplete) return;
if (!IsOpen) return;
if (_isRefreshing) return;
if (!IsSetupComplete || !IsOpen || _isRefreshing) return;
try
{
_isRefreshing = true;
_refreshFromLifecycleCount++;
LogRefreshStats();
InventoryState.RefreshFromGame();
RefreshCategoriesCore(autosize: true);
System.LootedItemsTracker.FlushPendingChanges();
RefreshCategoriesCore(autosize);
}
finally
{
@@ -121,6 +98,8 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
}
}
public void RefreshFromLifecycle() => ExecuteRefresh(autosize: true);
protected virtual void RefreshCategoriesCore(bool autosize)
{
if (!IsSetupComplete)
@@ -446,16 +425,10 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
protected override void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
{
_requestedUpdateCount++;
LogRefreshStats();
base.OnRequestedUpdate(addon, numberArrayData, stringArrayData);
if (DragDropState.IsDragging)
return;
InventoryState.RefreshFromGame();
RefreshCategoriesCore(autosize: true);
if (DragDropState.IsDragging) return;
ExecuteRefresh(autosize: true);
}
@@ -1,18 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AetherBags.Configuration;
using AetherBags.Inventory.Context;
using AetherBags.Inventory.Scanning;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.Inventory.InventoryEventArgTypes;
using Dalamud.Game.NativeWrapper;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
using Lumina.Text.ReadOnly;
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
namespace AetherBags.AddonLifecycles;
namespace AetherBags.Monitoring;
public static unsafe class DragDropState
{
@@ -22,10 +26,10 @@ public static unsafe class DragDropState
public static bool IsDragging => AtkStage.Instance()->DragDropManager.IsDragging;
}
public class InventoryLifecycles : IDisposable
public class InventoryMonitor : IDisposable
{
public InventoryLifecycles()
public InventoryMonitor()
{
var bags = new[] { "Inventory", "InventoryLarge", "InventoryExpansion" };
var saddle = new[] { "InventoryBuddy" };
@@ -45,8 +49,8 @@ public class InventoryLifecycles : IDisposable
Services.AddonLifecycle.RegisterListener(AddonEvent.PostRequestedUpdate, "InventoryBuddy", OnSaddleBagUpdate);
Services.AddonLifecycle.RegisterListener(AddonEvent.PostRequestedUpdate, retainer, OnRetainerInventoryUpdate);
// PreShow
Services.AddonLifecycle.RegisterListener(AddonEvent.PreOpen, "InventoryBuddy", OnSaddleBagOpen);
// Dalamud raw event for raw inventory changes (scans once per frame)
Services.GameInventory.InventoryChangedRaw += OnInventoryChangedRaw;
Services.Logger.Verbose("InventoryLifecycles initialized");
}
@@ -122,6 +126,32 @@ public class InventoryLifecycles : IDisposable
values[7] = can use Saddlebags (Agent InventoryBuddy IsActivatable)
*/
private void OnInventoryChangedRaw(IReadOnlyCollection<InventoryEventArgs> events)
{
bool needsRefresh = false;
foreach (var inventoryEventArgs in events)
{
if (InventoryScanner.StandardInventories.Contains((InventoryType)inventoryEventArgs.Item.ContainerType))
{
needsRefresh = true;
break;
}
}
if (needsRefresh)
{
Services.Framework.RunOnTick(() =>
{
if (IsInUnsafeState() || DragDropState.IsDragging) return;
System.LootedItemsTracker.FlushPendingChanges();
System.AddonInventoryWindow?.RefreshFromLifecycle();
System.AddonSaddleBagWindow?.RefreshFromLifecycle();
System.AddonRetainerWindow?.RefreshFromLifecycle();
});
}
}
private unsafe void InventoryPreRefreshHandler(AddonEvent type, AddonArgs args)
{
if (args is not AddonRefreshArgs refreshArgs)
@@ -165,25 +195,6 @@ public class InventoryLifecycles : IDisposable
}
}
// TODO: Inventory/Retainers are not perma open, need some way to close it too.
private void InventoryBuddyPreRefreshHandler(AddonEvent type, AddonArgs args)
{
if (args is not AddonRefreshArgs refreshArgs)
return;
if (IsInUnsafeState())
return;
GeneralSettings config = System.Config.General;
if (config.HideGameSaddleBags) refreshArgs.AtkValueCount = 0;
if (config.OpenSaddleBagsWithGameInventory)
{
System.AddonSaddleBagWindow.Toggle();
}
}
private void OnInventoryUpdate(AddonEvent type, AddonArgs args)
{
if (IsInUnsafeState())
@@ -204,6 +215,7 @@ public class InventoryLifecycles : IDisposable
if (DragDropState.IsDragging)
return;
System.LootedItemsTracker.FlushPendingChanges();
System.AddonSaddleBagWindow?.RefreshFromLifecycle();
}
@@ -215,17 +227,13 @@ public class InventoryLifecycles : IDisposable
if (DragDropState.IsDragging)
return;
System.LootedItemsTracker.FlushPendingChanges();
System.AddonRetainerWindow?.RefreshFromLifecycle();
}
private void OnSaddleBagOpen(AddonEvent type, AddonArgs args)
{
if (args is not AddonShowArgs showArgs)
return;
}
public void Dispose()
{
Services.AddonLifecycle.UnregisterListener(OnPostSetup, OnPreFinalize, OnInventoryUpdate, OnSaddleBagUpdate, OnRetainerInventoryUpdate, OnSaddleBagOpen);
Services.GameInventory.InventoryChangedRaw -= OnInventoryChangedRaw;
Services.AddonLifecycle.UnregisterListener(OnPostSetup, OnPreFinalize, OnInventoryUpdate, OnSaddleBagUpdate, OnRetainerInventoryUpdate);
}
}
@@ -9,7 +9,7 @@ using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using Lumina.Excel.Sheets;
namespace AetherBags.Inventory;
namespace AetherBags.Monitoring;
public sealed unsafe class LootedItemsTracker : IDisposable
{
@@ -32,6 +32,8 @@ public sealed unsafe class LootedItemsTracker : IDisposable
public bool HasPendingChanges => _pendingChanges.Count > 0 || _hasPendingRemoval;
private int GetNextIndex() => _lootedItems.Count > 0 ? _lootedItems.Max(x => x.Index) + 1 : 0;
public void Enable()
{
if (_isEnabled) return;
@@ -81,6 +83,8 @@ public sealed unsafe class LootedItemsTracker : IDisposable
{
if (_pendingChanges.Count == 0 && !_hasPendingRemoval) return;
ProcessPendingChanges();
_hasPendingRemoval = false;
OnLootedItemsChanged?.Invoke(_lootedItems);
}
@@ -90,12 +94,40 @@ public sealed unsafe class LootedItemsTracker : IDisposable
Disable();
}
private void ProcessPendingChanges()
{
if (_pendingChanges.Count == 0) return;
foreach (var ((itemId, isHq), (item, delta)) in _pendingChanges)
{
int existingIndex = _lootedItems.FindIndex(x =>
x.Item.ItemId == itemId &&
x.Item.Flags.HasFlag(InventoryItem.ItemFlags.HighQuality) == isHq);
if (existingIndex >= 0)
{
var current = _lootedItems[existingIndex];
int newQty = current.Quantity + delta;
if (newQty <= 0)
_lootedItems.RemoveAt(existingIndex);
else
_lootedItems[existingIndex] = current with { Quantity = newQty };
}
else if (delta > 0)
{
_lootedItems.Add(new LootedItemInfo(GetNextIndex(), item, delta));
}
}
_pendingChanges.Clear();
}
private void OnInventoryChangedRaw(IReadOnlyCollection<InventoryEventArgs> events)
{
if (!_isEnabled) return;
if (!Services.ClientState.IsLoggedIn) return;
if (!_isEnabled || !Services.ClientState.IsLoggedIn) return;
bool anyAdded = false;
bool anyChanged = false;
foreach (var eventData in events)
{
@@ -105,38 +137,42 @@ public sealed unsafe class LootedItemsTracker : IDisposable
if (eventData.Item.ContainerType == GameInventoryType.DamagedGear)
continue;
if (eventData is not (InventoryItemAddedArgs or InventoryItemChangedArgs))
continue;
if (eventData is InventoryItemChangedArgs changedArgs &&
changedArgs.OldItemState.Quantity >= changedArgs.Item.Quantity)
int changeAmount = eventData switch
{
continue;
}
InventoryItemAddedArgs added => added.Item.Quantity,
InventoryItemRemovedArgs removed => -removed.Item.Quantity,
InventoryItemChangedArgs changed => changed.Item.Quantity - changed.OldItemState.Quantity,
_ => 0
};
if (changeAmount == 0) continue;
if (ShouldFilterItem(eventData.Item.ItemId))
continue;
var inventoryItem = *(InventoryItem*)eventData.Item.Address;
var changeAmount = eventData is InventoryItemChangedArgs changed
? changed.Item.Quantity - changed.OldItemState.Quantity
: eventData.Item.Quantity;
var key = (inventoryItem.ItemId, IsHq: inventoryItem.Flags.HasFlag(InventoryItem.ItemFlags.HighQuality));
uint itemId = eventData.Item.ItemId;
bool isHq = eventData.Item.IsHq;
var key = (itemId, isHq);
if (_pendingChanges.TryGetValue(key, out var existing))
{
_pendingChanges[key] = (inventoryItem, existing.Quantity + changeAmount);
_pendingChanges[key] = (existing.Item, existing.Quantity + changeAmount);
}
else
{
_pendingChanges[key] = (inventoryItem, changeAmount);
InventoryItem itemStruct = default;
if (changeAmount > 0)
{
itemStruct = *(InventoryItem*)eventData.Item.Address;
}
anyAdded = true;
_pendingChanges[key] = (itemStruct, changeAmount);
}
if (anyAdded && _batchStartTick == 0)
anyChanged = true;
}
if (anyChanged && _batchStartTick == 0)
{
_batchStartTick = Environment.TickCount64;
}
@@ -152,23 +188,7 @@ public sealed unsafe class LootedItemsTracker : IDisposable
_batchStartTick = 0;
if (_pendingChanges.Count == 0)
return;
foreach (var ((itemId, isHq), (item, quantity)) in _pendingChanges)
{
if (quantity <= 0)
continue;
_lootedItems.Add(new LootedItemInfo(
_lootedItems.Count,
item,
quantity));
}
_pendingChanges.Clear();
OnLootedItemsChanged?.Invoke(_lootedItems);
FlushPendingChanges();
}
private static bool ShouldFilterItem(uint itemId)
+4 -4
View File
@@ -1,5 +1,4 @@
using System.Numerics;
using AetherBags.AddonLifecycles;
using AetherBags.Addons;
using AetherBags.Commands;
using AetherBags.Helpers;
@@ -7,6 +6,7 @@ using AetherBags.Hooks;
using AetherBags.Inventory;
using AetherBags.Inventory.Context;
using AetherBags.IPC;
using AetherBags.Monitoring;
using Dalamud.Plugin;
using KamiToolKit;
@@ -16,7 +16,7 @@ public class Plugin : IDalamudPlugin
{
private readonly CommandHandler _commandHandler;
private readonly InventoryHooks _inventoryHooks;
private readonly InventoryLifecycles _inventoryLifecycles;
private readonly InventoryMonitor inventoryMonitor;
public Plugin(IDalamudPluginInterface pluginInterface)
{
@@ -72,14 +72,14 @@ public class Plugin : IDalamudPlugin
}
_inventoryHooks = new InventoryHooks();
_inventoryLifecycles = new InventoryLifecycles();
inventoryMonitor = new InventoryMonitor();
}
public void Dispose()
{
InventoryAddonContextMenu.Close();
_inventoryHooks.Dispose();
_inventoryLifecycles.Dispose();
inventoryMonitor.Dispose();
System.LootedItemsTracker.Dispose();
System.IPC.Dispose();
+1
View File
@@ -2,6 +2,7 @@ using AetherBags.Addons;
using AetherBags.Configuration;
using AetherBags.Inventory;
using AetherBags.IPC;
using AetherBags.Monitoring;
namespace AetherBags;