InventoryMonitor, LootedItemsTracker Fix
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+39
-31
@@ -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);
|
||||
}
|
||||
}
|
||||
+58
-38
@@ -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)
|
||||
@@ -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();
|
||||
|
||||
@@ -2,6 +2,7 @@ using AetherBags.Addons;
|
||||
using AetherBags.Configuration;
|
||||
using AetherBags.Inventory;
|
||||
using AetherBags.IPC;
|
||||
using AetherBags.Monitoring;
|
||||
|
||||
namespace AetherBags;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user