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