mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Merge pull request #1554 from Soreepeong/GameInventory
This commit is contained in:
commit
711d5e2859
16 changed files with 1676 additions and 0 deletions
547
Dalamud/Game/Inventory/GameInventory.cs
Normal file
547
Dalamud/Game/Inventory/GameInventory.cs
Normal file
|
|
@ -0,0 +1,547 @@
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Dalamud.Game.Inventory.InventoryEventArgTypes;
|
||||||
|
using Dalamud.Hooking;
|
||||||
|
using Dalamud.IoC;
|
||||||
|
using Dalamud.IoC.Internal;
|
||||||
|
using Dalamud.Logging.Internal;
|
||||||
|
using Dalamud.Plugin.Internal;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Inventory;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class provides events for the players in-game inventory.
|
||||||
|
/// </summary>
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
|
internal class GameInventory : IDisposable, IServiceType
|
||||||
|
{
|
||||||
|
private readonly List<GameInventoryPluginScoped> subscribersPendingChange = new();
|
||||||
|
private readonly List<GameInventoryPluginScoped> subscribers = new();
|
||||||
|
|
||||||
|
private readonly List<InventoryItemAddedArgs> addedEvents = new();
|
||||||
|
private readonly List<InventoryItemRemovedArgs> removedEvents = new();
|
||||||
|
private readonly List<InventoryItemChangedArgs> changedEvents = new();
|
||||||
|
private readonly List<InventoryItemMovedArgs> movedEvents = new();
|
||||||
|
private readonly List<InventoryItemSplitArgs> splitEvents = new();
|
||||||
|
private readonly List<InventoryItemMergedArgs> mergedEvents = new();
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly Framework framework = Service<Framework>.Get();
|
||||||
|
|
||||||
|
private readonly Hook<RaptureAtkModuleUpdateDelegate> raptureAtkModuleUpdateHook;
|
||||||
|
|
||||||
|
private readonly GameInventoryType[] inventoryTypes;
|
||||||
|
private readonly GameInventoryItem[]?[] inventoryItems;
|
||||||
|
|
||||||
|
private bool subscribersChanged;
|
||||||
|
private bool inventoriesMightBeChanged;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceConstructor]
|
||||||
|
private GameInventory()
|
||||||
|
{
|
||||||
|
this.inventoryTypes = Enum.GetValues<GameInventoryType>();
|
||||||
|
this.inventoryItems = new GameInventoryItem[this.inventoryTypes.Length][];
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
this.raptureAtkModuleUpdateHook = Hook<RaptureAtkModuleUpdateDelegate>.FromFunctionPointerVariable(
|
||||||
|
new(&((RaptureAtkModule.RaptureAtkModuleVTable*)RaptureAtkModule.StaticAddressPointers.VTable)->Update),
|
||||||
|
this.RaptureAtkModuleUpdateDetour);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.raptureAtkModuleUpdateHook.Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe delegate void RaptureAtkModuleUpdateDelegate(RaptureAtkModule* ram, float f1);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
lock (this.subscribersPendingChange)
|
||||||
|
{
|
||||||
|
this.subscribers.Clear();
|
||||||
|
this.subscribersPendingChange.Clear();
|
||||||
|
this.subscribersChanged = false;
|
||||||
|
this.framework.Update -= this.OnFrameworkUpdate;
|
||||||
|
this.raptureAtkModuleUpdateHook.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subscribe to events.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="s">The event target.</param>
|
||||||
|
public void Subscribe(GameInventoryPluginScoped s)
|
||||||
|
{
|
||||||
|
lock (this.subscribersPendingChange)
|
||||||
|
{
|
||||||
|
this.subscribersPendingChange.Add(s);
|
||||||
|
this.subscribersChanged = true;
|
||||||
|
if (this.subscribersPendingChange.Count == 1)
|
||||||
|
{
|
||||||
|
this.inventoriesMightBeChanged = true;
|
||||||
|
this.framework.Update += this.OnFrameworkUpdate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsubscribe from events.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="s">The event target.</param>
|
||||||
|
public void Unsubscribe(GameInventoryPluginScoped s)
|
||||||
|
{
|
||||||
|
lock (this.subscribersPendingChange)
|
||||||
|
{
|
||||||
|
if (!this.subscribersPendingChange.Remove(s))
|
||||||
|
return;
|
||||||
|
this.subscribersChanged = true;
|
||||||
|
if (this.subscribersPendingChange.Count == 0)
|
||||||
|
this.framework.Update -= this.OnFrameworkUpdate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnFrameworkUpdate(IFramework framework1)
|
||||||
|
{
|
||||||
|
if (!this.inventoriesMightBeChanged)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.inventoriesMightBeChanged = false;
|
||||||
|
|
||||||
|
for (var i = 0; i < this.inventoryTypes.Length; i++)
|
||||||
|
{
|
||||||
|
var newItems = GameInventoryItem.GetReadOnlySpanOfInventory(this.inventoryTypes[i]);
|
||||||
|
if (newItems.IsEmpty)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Assumption: newItems is sorted by slots, and the last item has the highest slot number.
|
||||||
|
var oldItems = this.inventoryItems[i] ??= new GameInventoryItem[newItems[^1].InternalItem.Slot + 1];
|
||||||
|
|
||||||
|
foreach (ref readonly var newItem in newItems)
|
||||||
|
{
|
||||||
|
ref var oldItem = ref oldItems[newItem.InternalItem.Slot];
|
||||||
|
|
||||||
|
if (oldItem.IsEmpty)
|
||||||
|
{
|
||||||
|
if (!newItem.IsEmpty)
|
||||||
|
{
|
||||||
|
this.addedEvents.Add(new(newItem));
|
||||||
|
oldItem = newItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (newItem.IsEmpty)
|
||||||
|
{
|
||||||
|
this.removedEvents.Add(new(oldItem));
|
||||||
|
oldItem = newItem;
|
||||||
|
}
|
||||||
|
else if (!oldItem.Equals(newItem))
|
||||||
|
{
|
||||||
|
this.changedEvents.Add(new(oldItem, newItem));
|
||||||
|
oldItem = newItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Was there any change? If not, stop further processing.
|
||||||
|
// Note that only these three are checked; the rest will be populated after this check.
|
||||||
|
if (this.addedEvents.Count == 0 && this.removedEvents.Count == 0 && this.changedEvents.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Make a copy of subscribers, to accommodate self removal during the loop.
|
||||||
|
if (this.subscribersChanged)
|
||||||
|
{
|
||||||
|
bool isNew;
|
||||||
|
lock (this.subscribersPendingChange)
|
||||||
|
{
|
||||||
|
isNew = this.subscribersPendingChange.Any() && !this.subscribers.Any();
|
||||||
|
this.subscribers.Clear();
|
||||||
|
this.subscribers.AddRange(this.subscribersPendingChange);
|
||||||
|
this.subscribersChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is this the first time (resuming) scanning for changes? Then discard the "changes".
|
||||||
|
if (isNew)
|
||||||
|
{
|
||||||
|
this.addedEvents.Clear();
|
||||||
|
this.removedEvents.Clear();
|
||||||
|
this.changedEvents.Clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Broadcast InventoryChangedRaw.
|
||||||
|
// Same reason with the above on why are there 3 lists of events involved.
|
||||||
|
var allRawEventsCollection = new DeferredReadOnlyCollection<InventoryEventArgs>(
|
||||||
|
this.addedEvents.Count +
|
||||||
|
this.removedEvents.Count +
|
||||||
|
this.changedEvents.Count,
|
||||||
|
() => Array.Empty<InventoryEventArgs>()
|
||||||
|
.Concat(this.addedEvents)
|
||||||
|
.Concat(this.removedEvents)
|
||||||
|
.Concat(this.changedEvents));
|
||||||
|
foreach (var s in this.subscribers)
|
||||||
|
s.InvokeChangedRaw(allRawEventsCollection);
|
||||||
|
|
||||||
|
// Resolve moved items, from 1 added + 1 removed event.
|
||||||
|
for (var iAdded = this.addedEvents.Count - 1; iAdded >= 0; --iAdded)
|
||||||
|
{
|
||||||
|
var added = this.addedEvents[iAdded];
|
||||||
|
for (var iRemoved = this.removedEvents.Count - 1; iRemoved >= 0; --iRemoved)
|
||||||
|
{
|
||||||
|
var removed = this.removedEvents[iRemoved];
|
||||||
|
if (added.Item.ItemId != removed.Item.ItemId)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
this.movedEvents.Add(new(removed, added));
|
||||||
|
|
||||||
|
// Remove the reinterpreted entries.
|
||||||
|
this.addedEvents.RemoveAt(iAdded);
|
||||||
|
this.removedEvents.RemoveAt(iRemoved);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve moved items, from 2 changed events.
|
||||||
|
for (var i = this.changedEvents.Count - 1; i >= 0; --i)
|
||||||
|
{
|
||||||
|
var e1 = this.changedEvents[i];
|
||||||
|
for (var j = i - 1; j >= 0; --j)
|
||||||
|
{
|
||||||
|
var e2 = this.changedEvents[j];
|
||||||
|
if (e1.Item.ItemId != e2.OldItemState.ItemId || e1.OldItemState.ItemId != e2.Item.ItemId)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Move happened, and e2 has an item.
|
||||||
|
if (!e2.Item.IsEmpty)
|
||||||
|
this.movedEvents.Add(new(e1, e2));
|
||||||
|
|
||||||
|
// Move happened, and e1 has an item.
|
||||||
|
if (!e1.Item.IsEmpty)
|
||||||
|
this.movedEvents.Add(new(e2, e1));
|
||||||
|
|
||||||
|
// Remove the reinterpreted entries. Note that i > j.
|
||||||
|
this.changedEvents.RemoveAt(i);
|
||||||
|
this.changedEvents.RemoveAt(j);
|
||||||
|
|
||||||
|
// We've removed two. Adjust the outer counter.
|
||||||
|
--i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve split items, from 1 added + 1 changed event.
|
||||||
|
for (var iAdded = this.addedEvents.Count - 1; iAdded >= 0; --iAdded)
|
||||||
|
{
|
||||||
|
var added = this.addedEvents[iAdded];
|
||||||
|
for (var iChanged = this.changedEvents.Count - 1; iChanged >= 0; --iChanged)
|
||||||
|
{
|
||||||
|
var changed = this.changedEvents[iChanged];
|
||||||
|
if (added.Item.ItemId != changed.Item.ItemId || added.Item.ItemId != changed.OldItemState.ItemId)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
this.splitEvents.Add(new(changed, added));
|
||||||
|
|
||||||
|
// Remove the reinterpreted entries.
|
||||||
|
this.addedEvents.RemoveAt(iAdded);
|
||||||
|
this.changedEvents.RemoveAt(iChanged);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve merged items, from 1 removed + 1 changed event.
|
||||||
|
for (var iRemoved = this.removedEvents.Count - 1; iRemoved >= 0; --iRemoved)
|
||||||
|
{
|
||||||
|
var removed = this.removedEvents[iRemoved];
|
||||||
|
for (var iChanged = this.changedEvents.Count - 1; iChanged >= 0; --iChanged)
|
||||||
|
{
|
||||||
|
var changed = this.changedEvents[iChanged];
|
||||||
|
if (removed.Item.ItemId != changed.Item.ItemId || removed.Item.ItemId != changed.OldItemState.ItemId)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
this.mergedEvents.Add(new(removed, changed));
|
||||||
|
|
||||||
|
// Remove the reinterpreted entries.
|
||||||
|
this.removedEvents.RemoveAt(iRemoved);
|
||||||
|
this.changedEvents.RemoveAt(iChanged);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a collection view of all events.
|
||||||
|
var allEventsCollection = new DeferredReadOnlyCollection<InventoryEventArgs>(
|
||||||
|
this.addedEvents.Count +
|
||||||
|
this.removedEvents.Count +
|
||||||
|
this.changedEvents.Count +
|
||||||
|
this.movedEvents.Count +
|
||||||
|
this.splitEvents.Count +
|
||||||
|
this.mergedEvents.Count,
|
||||||
|
() => Array.Empty<InventoryEventArgs>()
|
||||||
|
.Concat(this.addedEvents)
|
||||||
|
.Concat(this.removedEvents)
|
||||||
|
.Concat(this.changedEvents)
|
||||||
|
.Concat(this.movedEvents)
|
||||||
|
.Concat(this.splitEvents)
|
||||||
|
.Concat(this.mergedEvents));
|
||||||
|
|
||||||
|
// Broadcast the rest.
|
||||||
|
foreach (var s in this.subscribers)
|
||||||
|
{
|
||||||
|
s.InvokeChanged(allEventsCollection);
|
||||||
|
s.Invoke(this.addedEvents);
|
||||||
|
s.Invoke(this.removedEvents);
|
||||||
|
s.Invoke(this.changedEvents);
|
||||||
|
s.Invoke(this.movedEvents);
|
||||||
|
s.Invoke(this.splitEvents);
|
||||||
|
s.Invoke(this.mergedEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're done using the lists. Clean them up.
|
||||||
|
this.addedEvents.Clear();
|
||||||
|
this.removedEvents.Clear();
|
||||||
|
this.changedEvents.Clear();
|
||||||
|
this.movedEvents.Clear();
|
||||||
|
this.splitEvents.Clear();
|
||||||
|
this.mergedEvents.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void RaptureAtkModuleUpdateDetour(RaptureAtkModule* ram, float f1)
|
||||||
|
{
|
||||||
|
this.inventoriesMightBeChanged |= ram->AgentUpdateFlag != 0;
|
||||||
|
this.raptureAtkModuleUpdateHook.Original(ram, f1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="IReadOnlyCollection{T}"/> view of <see cref="IEnumerable{T}"/>, so that the number of items
|
||||||
|
/// contained within can be known in advance, and it can be enumerated multiple times.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of elements being enumerated.</typeparam>
|
||||||
|
private class DeferredReadOnlyCollection<T> : IReadOnlyCollection<T>
|
||||||
|
{
|
||||||
|
private readonly Func<IEnumerable<T>> enumerableGenerator;
|
||||||
|
|
||||||
|
public DeferredReadOnlyCollection(int count, Func<IEnumerable<T>> enumerableGenerator)
|
||||||
|
{
|
||||||
|
this.enumerableGenerator = enumerableGenerator;
|
||||||
|
this.Count = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count { get; }
|
||||||
|
|
||||||
|
public IEnumerator<T> GetEnumerator() => this.enumerableGenerator().GetEnumerator();
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => this.enumerableGenerator().GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plugin-scoped version of a GameInventory service.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.ScopedService]
|
||||||
|
#pragma warning disable SA1015
|
||||||
|
[ResolveVia<IGameInventory>]
|
||||||
|
#pragma warning restore SA1015
|
||||||
|
internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInventory
|
||||||
|
{
|
||||||
|
private static readonly ModuleLog Log = new(nameof(GameInventoryPluginScoped));
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly GameInventory gameInventoryService = Service<GameInventory>.Get();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="GameInventoryPluginScoped"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public GameInventoryPluginScoped() => this.gameInventoryService.Subscribe(this);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event IGameInventory.InventoryChangelogDelegate? InventoryChanged;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event IGameInventory.InventoryChangelogDelegate? InventoryChangedRaw;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event IGameInventory.InventoryChangedDelegate? ItemAdded;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event IGameInventory.InventoryChangedDelegate? ItemRemoved;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event IGameInventory.InventoryChangedDelegate? ItemChanged;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event IGameInventory.InventoryChangedDelegate? ItemMoved;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event IGameInventory.InventoryChangedDelegate? ItemSplit;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event IGameInventory.InventoryChangedDelegate? ItemMerged;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event IGameInventory.InventoryChangedDelegate<InventoryItemAddedArgs>? ItemAddedExplicit;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event IGameInventory.InventoryChangedDelegate<InventoryItemRemovedArgs>? ItemRemovedExplicit;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event IGameInventory.InventoryChangedDelegate<InventoryItemChangedArgs>? ItemChangedExplicit;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event IGameInventory.InventoryChangedDelegate<InventoryItemMovedArgs>? ItemMovedExplicit;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event IGameInventory.InventoryChangedDelegate<InventoryItemSplitArgs>? ItemSplitExplicit;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event IGameInventory.InventoryChangedDelegate<InventoryItemMergedArgs>? ItemMergedExplicit;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.gameInventoryService.Unsubscribe(this);
|
||||||
|
|
||||||
|
this.InventoryChanged = null;
|
||||||
|
this.InventoryChangedRaw = null;
|
||||||
|
this.ItemAdded = null;
|
||||||
|
this.ItemRemoved = null;
|
||||||
|
this.ItemChanged = null;
|
||||||
|
this.ItemMoved = null;
|
||||||
|
this.ItemSplit = null;
|
||||||
|
this.ItemMerged = null;
|
||||||
|
this.ItemAddedExplicit = null;
|
||||||
|
this.ItemRemovedExplicit = null;
|
||||||
|
this.ItemChangedExplicit = null;
|
||||||
|
this.ItemMovedExplicit = null;
|
||||||
|
this.ItemSplitExplicit = null;
|
||||||
|
this.ItemMergedExplicit = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoke <see cref="InventoryChanged"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The data.</param>
|
||||||
|
internal void InvokeChanged(IReadOnlyCollection<InventoryEventArgs> data)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.InventoryChanged?.Invoke(data);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(
|
||||||
|
e,
|
||||||
|
"[{plugin}] Exception during {argType} callback",
|
||||||
|
Service<PluginManager>.GetNullable()?.FindCallingPlugin(new(e))?.Name ?? "(unknown plugin)",
|
||||||
|
nameof(this.InventoryChanged));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoke <see cref="InventoryChangedRaw"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The data.</param>
|
||||||
|
internal void InvokeChangedRaw(IReadOnlyCollection<InventoryEventArgs> data)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.InventoryChangedRaw?.Invoke(data);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(
|
||||||
|
e,
|
||||||
|
"[{plugin}] Exception during {argType} callback",
|
||||||
|
Service<PluginManager>.GetNullable()?.FindCallingPlugin(new(e))?.Name ?? "(unknown plugin)",
|
||||||
|
nameof(this.InventoryChangedRaw));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note below: using List<T> instead of IEnumerable<T>, since List<T> has a specialized lightweight enumerator.
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoke the appropriate event handler.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="events">The data.</param>
|
||||||
|
internal void Invoke(List<InventoryItemAddedArgs> events) =>
|
||||||
|
Invoke(this.ItemAdded, this.ItemAddedExplicit, events);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoke the appropriate event handler.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="events">The data.</param>
|
||||||
|
internal void Invoke(List<InventoryItemRemovedArgs> events) =>
|
||||||
|
Invoke(this.ItemRemoved, this.ItemRemovedExplicit, events);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoke the appropriate event handler.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="events">The data.</param>
|
||||||
|
internal void Invoke(List<InventoryItemChangedArgs> events) =>
|
||||||
|
Invoke(this.ItemChanged, this.ItemChangedExplicit, events);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoke the appropriate event handler.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="events">The data.</param>
|
||||||
|
internal void Invoke(List<InventoryItemMovedArgs> events) =>
|
||||||
|
Invoke(this.ItemMoved, this.ItemMovedExplicit, events);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoke the appropriate event handler.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="events">The data.</param>
|
||||||
|
internal void Invoke(List<InventoryItemSplitArgs> events) =>
|
||||||
|
Invoke(this.ItemSplit, this.ItemSplitExplicit, events);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoke the appropriate event handler.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="events">The data.</param>
|
||||||
|
internal void Invoke(List<InventoryItemMergedArgs> events) =>
|
||||||
|
Invoke(this.ItemMerged, this.ItemMergedExplicit, events);
|
||||||
|
|
||||||
|
private static void Invoke<T>(
|
||||||
|
IGameInventory.InventoryChangedDelegate? cb,
|
||||||
|
IGameInventory.InventoryChangedDelegate<T>? cbt,
|
||||||
|
List<T> events) where T : InventoryEventArgs
|
||||||
|
{
|
||||||
|
foreach (var evt in events)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
cb?.Invoke(evt.Type, evt);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(
|
||||||
|
e,
|
||||||
|
"[{plugin}] Exception during untyped callback for {evt}",
|
||||||
|
Service<PluginManager>.GetNullable()?.FindCallingPlugin(new(e))?.Name ?? "(unknown plugin)",
|
||||||
|
evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
cbt?.Invoke(evt);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(
|
||||||
|
e,
|
||||||
|
"[{plugin}] Exception during typed callback for {evt}",
|
||||||
|
Service<PluginManager>.GetNullable()?.FindCallingPlugin(new(e))?.Name ?? "(unknown plugin)",
|
||||||
|
evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
Dalamud/Game/Inventory/GameInventoryEvent.cs
Normal file
43
Dalamud/Game/Inventory/GameInventoryEvent.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
namespace Dalamud.Game.Inventory;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class representing a item's changelog state.
|
||||||
|
/// </summary>
|
||||||
|
public enum GameInventoryEvent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A value indicating that there was no event.<br />
|
||||||
|
/// You should not see this value, unless you explicitly used it yourself, or APIs using this enum say otherwise.
|
||||||
|
/// </summary>
|
||||||
|
Empty = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Item was added to an inventory.
|
||||||
|
/// </summary>
|
||||||
|
Added = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Item was removed from an inventory.
|
||||||
|
/// </summary>
|
||||||
|
Removed = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Properties are changed for an item in an inventory.
|
||||||
|
/// </summary>
|
||||||
|
Changed = 3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Item has been moved, possibly across different inventories.
|
||||||
|
/// </summary>
|
||||||
|
Moved = 4,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Item has been split into two stacks from one, possibly across different inventories.
|
||||||
|
/// </summary>
|
||||||
|
Split = 5,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Item has been merged into one stack from two, possibly across different inventories.
|
||||||
|
/// </summary>
|
||||||
|
Merged = 6,
|
||||||
|
}
|
||||||
203
Dalamud/Game/Inventory/GameInventoryItem.cs
Normal file
203
Dalamud/Game/Inventory/GameInventoryItem.cs
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Inventory;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dalamud wrapper around a ClientStructs InventoryItem.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Explicit, Size = StructSizeInBytes)]
|
||||||
|
public unsafe struct GameInventoryItem : IEquatable<GameInventoryItem>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The actual data.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(0)]
|
||||||
|
internal readonly InventoryItem InternalItem;
|
||||||
|
|
||||||
|
private const int StructSizeInBytes = 0x38;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The view of the backing data, in <see cref="ulong"/>.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(0)]
|
||||||
|
private fixed ulong dataUInt64[StructSizeInBytes / 0x8];
|
||||||
|
|
||||||
|
static GameInventoryItem()
|
||||||
|
{
|
||||||
|
Debug.Assert(
|
||||||
|
sizeof(InventoryItem) == StructSizeInBytes,
|
||||||
|
$"Definition of {nameof(InventoryItem)} has been changed. " +
|
||||||
|
$"Update {nameof(StructSizeInBytes)} to {sizeof(InventoryItem)} to accommodate for the size change.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="GameInventoryItem"/> struct.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">Inventory item to wrap.</param>
|
||||||
|
internal GameInventoryItem(InventoryItem item) => this.InternalItem = item;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the this <see cref="GameInventoryItem"/> is empty.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsEmpty => this.InternalItem.ItemID == 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the container inventory type.
|
||||||
|
/// </summary>
|
||||||
|
public GameInventoryType ContainerType => (GameInventoryType)this.InternalItem.Container;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the inventory slot index this item is in.
|
||||||
|
/// </summary>
|
||||||
|
public uint InventorySlot => (uint)this.InternalItem.Slot;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the item id.
|
||||||
|
/// </summary>
|
||||||
|
public uint ItemId => this.InternalItem.ItemID;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the quantity of items in this item stack.
|
||||||
|
/// </summary>
|
||||||
|
public uint Quantity => this.InternalItem.Quantity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the spiritbond of this item.
|
||||||
|
/// </summary>
|
||||||
|
public uint Spiritbond => this.InternalItem.Spiritbond;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the repair condition of this item.
|
||||||
|
/// </summary>
|
||||||
|
public uint Condition => this.InternalItem.Condition;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the item is High Quality.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsHq => (this.InternalItem.Flags & InventoryItem.ItemFlags.HQ) != 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the item has a company crest applied.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsCompanyCrestApplied => (this.InternalItem.Flags & InventoryItem.ItemFlags.CompanyCrestApplied) != 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the item is a relic.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsRelic => (this.InternalItem.Flags & InventoryItem.ItemFlags.Relic) != 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the is a collectable.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsCollectable => (this.InternalItem.Flags & InventoryItem.ItemFlags.Collectable) != 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the array of materia types.
|
||||||
|
/// </summary>
|
||||||
|
public ReadOnlySpan<ushort> Materia => new(Unsafe.AsPointer(ref Unsafe.AsRef(in this.InternalItem.Materia[0])), 5);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the array of materia grades.
|
||||||
|
/// </summary>
|
||||||
|
public ReadOnlySpan<ushort> MateriaGrade =>
|
||||||
|
new(Unsafe.AsPointer(ref Unsafe.AsRef(in this.InternalItem.MateriaGrade[0])), 5);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of native inventory item in the game.<br />
|
||||||
|
/// Can be 0 if this instance of <see cref="GameInventoryItem"/> does not point to a valid set of container type and slot.<br />
|
||||||
|
/// Note that this instance of <see cref="GameInventoryItem"/> can be a snapshot; it may not necessarily match the
|
||||||
|
/// data you can query from the game using this address value.
|
||||||
|
/// </summary>
|
||||||
|
public nint Address
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var s = GetReadOnlySpanOfInventory(this.ContainerType);
|
||||||
|
if (s.IsEmpty)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
foreach (ref readonly var i in s)
|
||||||
|
{
|
||||||
|
if (i.InventorySlot == this.InventorySlot)
|
||||||
|
return (nint)Unsafe.AsPointer(ref Unsafe.AsRef(in i));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the color used for this item.
|
||||||
|
/// </summary>
|
||||||
|
public byte Stain => this.InternalItem.Stain;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the glamour id for this item.
|
||||||
|
/// </summary>
|
||||||
|
public uint GlamourId => this.InternalItem.GlamourID;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the items crafter's content id.
|
||||||
|
/// NOTE: I'm not sure if this is a good idea to include or not in the dalamud api. Marked internal for now.
|
||||||
|
/// </summary>
|
||||||
|
internal ulong CrafterContentId => this.InternalItem.CrafterContentID;
|
||||||
|
|
||||||
|
public static bool operator ==(in GameInventoryItem l, in GameInventoryItem r) => l.Equals(r);
|
||||||
|
|
||||||
|
public static bool operator !=(in GameInventoryItem l, in GameInventoryItem r) => !l.Equals(r);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
readonly bool IEquatable<GameInventoryItem>.Equals(GameInventoryItem other) => this.Equals(other);
|
||||||
|
|
||||||
|
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
|
||||||
|
/// <param name="other">An object to compare with this object.</param>
|
||||||
|
/// <returns><c>true</c> if the current object is equal to the <paramref name="other" /> parameter; otherwise, <c>false</c>.</returns>
|
||||||
|
public readonly bool Equals(in GameInventoryItem other)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < StructSizeInBytes / 8; i++)
|
||||||
|
{
|
||||||
|
if (this.dataUInt64[i] != other.dataUInt64[i])
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="object.Equals(object?)" />
|
||||||
|
public override bool Equals(object obj) => obj is GameInventoryItem gii && this.Equals(gii);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="object.GetHashCode" />
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
var k = 0x5a8447b91aff51b4UL;
|
||||||
|
for (var i = 0; i < StructSizeInBytes / 8; i++)
|
||||||
|
k ^= this.dataUInt64[i];
|
||||||
|
return unchecked((int)(k ^ (k >> 32)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="object.ToString"/>
|
||||||
|
public override string ToString() =>
|
||||||
|
this.IsEmpty
|
||||||
|
? "empty"
|
||||||
|
: $"item({this.ItemId}@{this.ContainerType}#{this.InventorySlot})";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="Span{T}"/> view of <see cref="InventoryItem"/>s, wrapped as <see cref="GameInventoryItem"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The inventory type.</param>
|
||||||
|
/// <returns>The span.</returns>
|
||||||
|
internal static ReadOnlySpan<GameInventoryItem> GetReadOnlySpanOfInventory(GameInventoryType type)
|
||||||
|
{
|
||||||
|
var inventoryManager = InventoryManager.Instance();
|
||||||
|
if (inventoryManager is null) return default;
|
||||||
|
|
||||||
|
var inventory = inventoryManager->GetInventoryContainer((InventoryType)type);
|
||||||
|
if (inventory is null) return default;
|
||||||
|
|
||||||
|
return new ReadOnlySpan<GameInventoryItem>(inventory->Items, (int)inventory->Size);
|
||||||
|
}
|
||||||
|
}
|
||||||
356
Dalamud/Game/Inventory/GameInventoryType.cs
Normal file
356
Dalamud/Game/Inventory/GameInventoryType.cs
Normal file
|
|
@ -0,0 +1,356 @@
|
||||||
|
namespace Dalamud.Game.Inventory;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enum representing various player inventories.
|
||||||
|
/// </summary>
|
||||||
|
public enum GameInventoryType : ushort
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// First panel of main player inventory.
|
||||||
|
/// </summary>
|
||||||
|
Inventory1 = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Second panel of main player inventory.
|
||||||
|
/// </summary>
|
||||||
|
Inventory2 = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Third panel of main player inventory.
|
||||||
|
/// </summary>
|
||||||
|
Inventory3 = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fourth panel of main player inventory.
|
||||||
|
/// </summary>
|
||||||
|
Inventory4 = 3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Items that are currently equipped by the player.
|
||||||
|
/// </summary>
|
||||||
|
EquippedItems = 1000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Player currency container.
|
||||||
|
/// ie, gil, serpent seals, sacks of nuts.
|
||||||
|
/// </summary>
|
||||||
|
Currency = 2000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Crystal container.
|
||||||
|
/// </summary>
|
||||||
|
Crystals = 2001,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mail container.
|
||||||
|
/// </summary>
|
||||||
|
Mail = 2003,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Key item container.
|
||||||
|
/// </summary>
|
||||||
|
KeyItems = 2004,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Quest item hand-in inventory.
|
||||||
|
/// </summary>
|
||||||
|
HandIn = 2005,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DamagedGear container.
|
||||||
|
/// </summary>
|
||||||
|
DamagedGear = 2007,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Examine window container.
|
||||||
|
/// </summary>
|
||||||
|
Examine = 2009,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Doman Enclave Reconstruction Reclamation Box.
|
||||||
|
/// </summary>
|
||||||
|
ReconstructionBuyback = 2013,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Armory off-hand weapon container.
|
||||||
|
/// </summary>
|
||||||
|
ArmoryOffHand = 3200,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Armory head container.
|
||||||
|
/// </summary>
|
||||||
|
ArmoryHead = 3201,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Armory body container.
|
||||||
|
/// </summary>
|
||||||
|
ArmoryBody = 3202,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Armory hand/gloves container.
|
||||||
|
/// </summary>
|
||||||
|
ArmoryHands = 3203,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Armory waist container.
|
||||||
|
/// <remarks>
|
||||||
|
/// This container should be unused as belt items were removed from the game in Shadowbringers.
|
||||||
|
/// </remarks>
|
||||||
|
/// </summary>
|
||||||
|
ArmoryWaist = 3204,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Armory legs/pants/skirt container.
|
||||||
|
/// </summary>
|
||||||
|
ArmoryLegs = 3205,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Armory feet/boots/shoes container.
|
||||||
|
/// </summary>
|
||||||
|
ArmoryFeets = 3206,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Armory earring container.
|
||||||
|
/// </summary>
|
||||||
|
ArmoryEar = 3207,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Armory necklace container.
|
||||||
|
/// </summary>
|
||||||
|
ArmoryNeck = 3208,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Armory bracelet container.
|
||||||
|
/// </summary>
|
||||||
|
ArmoryWrist = 3209,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Armory ring container.
|
||||||
|
/// </summary>
|
||||||
|
ArmoryRings = 3300,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Armory soul crystal container.
|
||||||
|
/// </summary>
|
||||||
|
ArmorySoulCrystal = 3400,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Armory main-hand weapon container.
|
||||||
|
/// </summary>
|
||||||
|
ArmoryMainHand = 3500,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// First panel of saddelbag inventory.
|
||||||
|
/// </summary>
|
||||||
|
SaddleBag1 = 4000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Second panel of Saddlebag inventory.
|
||||||
|
/// </summary>
|
||||||
|
SaddleBag2 = 4001,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// First panel of premium saddlebag inventory.
|
||||||
|
/// </summary>
|
||||||
|
PremiumSaddleBag1 = 4100,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Second panel of premium saddlebag inventory.
|
||||||
|
/// </summary>
|
||||||
|
PremiumSaddleBag2 = 4101,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// First panel of retainer inventory.
|
||||||
|
/// </summary>
|
||||||
|
RetainerPage1 = 10000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Second panel of retainer inventory.
|
||||||
|
/// </summary>
|
||||||
|
RetainerPage2 = 10001,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Third panel of retainer inventory.
|
||||||
|
/// </summary>
|
||||||
|
RetainerPage3 = 10002,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fourth panel of retainer inventory.
|
||||||
|
/// </summary>
|
||||||
|
RetainerPage4 = 10003,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fifth panel of retainer inventory.
|
||||||
|
/// </summary>
|
||||||
|
RetainerPage5 = 10004,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sixth panel of retainer inventory.
|
||||||
|
/// </summary>
|
||||||
|
RetainerPage6 = 10005,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Seventh panel of retainer inventory.
|
||||||
|
/// </summary>
|
||||||
|
RetainerPage7 = 10006,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retainer equipment container.
|
||||||
|
/// </summary>
|
||||||
|
RetainerEquippedItems = 11000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retainer currency container.
|
||||||
|
/// </summary>
|
||||||
|
RetainerGil = 12000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retainer crystal container.
|
||||||
|
/// </summary>
|
||||||
|
RetainerCrystals = 12001,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retainer market item container.
|
||||||
|
/// </summary>
|
||||||
|
RetainerMarket = 12002,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// First panel of Free Company inventory.
|
||||||
|
/// </summary>
|
||||||
|
FreeCompanyPage1 = 20000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Second panel of Free Company inventory.
|
||||||
|
/// </summary>
|
||||||
|
FreeCompanyPage2 = 20001,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Third panel of Free Company inventory.
|
||||||
|
/// </summary>
|
||||||
|
FreeCompanyPage3 = 20002,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fourth panel of Free Company inventory.
|
||||||
|
/// </summary>
|
||||||
|
FreeCompanyPage4 = 20003,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fifth panel of Free Company inventory.
|
||||||
|
/// </summary>
|
||||||
|
FreeCompanyPage5 = 20004,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Free Company currency container.
|
||||||
|
/// </summary>
|
||||||
|
FreeCompanyGil = 22000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Free Company crystal container.
|
||||||
|
/// </summary>
|
||||||
|
FreeCompanyCrystals = 22001,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Housing exterior appearance container.
|
||||||
|
/// </summary>
|
||||||
|
HousingExteriorAppearance = 25000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Housing exterior placed items container.
|
||||||
|
/// </summary>
|
||||||
|
HousingExteriorPlacedItems = 25001,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Housing interior appearance container.
|
||||||
|
/// </summary>
|
||||||
|
HousingInteriorAppearance = 25002,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// First panel of housing interior inventory.
|
||||||
|
/// </summary>
|
||||||
|
HousingInteriorPlacedItems1 = 25003,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Second panel of housing interior inventory.
|
||||||
|
/// </summary>
|
||||||
|
HousingInteriorPlacedItems2 = 25004,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Third panel of housing interior inventory.
|
||||||
|
/// </summary>
|
||||||
|
HousingInteriorPlacedItems3 = 25005,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fourth panel of housing interior inventory.
|
||||||
|
/// </summary>
|
||||||
|
HousingInteriorPlacedItems4 = 25006,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fifth panel of housing interior inventory.
|
||||||
|
/// </summary>
|
||||||
|
HousingInteriorPlacedItems5 = 25007,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sixth panel of housing interior inventory.
|
||||||
|
/// </summary>
|
||||||
|
HousingInteriorPlacedItems6 = 25008,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Seventh panel of housing interior inventory.
|
||||||
|
/// </summary>
|
||||||
|
HousingInteriorPlacedItems7 = 25009,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Eighth panel of housing interior inventory.
|
||||||
|
/// </summary>
|
||||||
|
HousingInteriorPlacedItems8 = 25010,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Housing exterior storeroom inventory.
|
||||||
|
/// </summary>
|
||||||
|
HousingExteriorStoreroom = 27000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// First panel of housing interior storeroom inventory.
|
||||||
|
/// </summary>
|
||||||
|
HousingInteriorStoreroom1 = 27001,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Second panel of housing interior storeroom inventory.
|
||||||
|
/// </summary>
|
||||||
|
HousingInteriorStoreroom2 = 27002,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Third panel of housing interior storeroom inventory.
|
||||||
|
/// </summary>
|
||||||
|
HousingInteriorStoreroom3 = 27003,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fourth panel of housing interior storeroom inventory.
|
||||||
|
/// </summary>
|
||||||
|
HousingInteriorStoreroom4 = 27004,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fifth panel of housing interior storeroom inventory.
|
||||||
|
/// </summary>
|
||||||
|
HousingInteriorStoreroom5 = 27005,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sixth panel of housing interior storeroom inventory.
|
||||||
|
/// </summary>
|
||||||
|
HousingInteriorStoreroom6 = 27006,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Seventh panel of housing interior storeroom inventory.
|
||||||
|
/// </summary>
|
||||||
|
HousingInteriorStoreroom7 = 27007,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Eighth panel of housing interior storeroom inventory.
|
||||||
|
/// </summary>
|
||||||
|
HousingInteriorStoreroom8 = 27008,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An invalid value.
|
||||||
|
/// </summary>
|
||||||
|
Invalid = ushort.MaxValue,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
namespace Dalamud.Game.Inventory.InventoryEventArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the data associated with an item being affected across different slots, possibly in different containers.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class InventoryComplexEventArgs : InventoryEventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="InventoryComplexEventArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">Type of the event.</param>
|
||||||
|
/// <param name="sourceEvent">The item at before slot.</param>
|
||||||
|
/// <param name="targetEvent">The item at after slot.</param>
|
||||||
|
internal InventoryComplexEventArgs(
|
||||||
|
GameInventoryEvent type, InventoryEventArgs sourceEvent, InventoryEventArgs targetEvent)
|
||||||
|
: base(type, targetEvent.Item)
|
||||||
|
{
|
||||||
|
this.SourceEvent = sourceEvent;
|
||||||
|
this.TargetEvent = targetEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the inventory this item was at.
|
||||||
|
/// </summary>
|
||||||
|
public GameInventoryType SourceInventory => this.SourceEvent.Item.ContainerType;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the inventory this item now is.
|
||||||
|
/// </summary>
|
||||||
|
public GameInventoryType TargetInventory => this.Item.ContainerType;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the slot this item was at.
|
||||||
|
/// </summary>
|
||||||
|
public uint SourceSlot => this.SourceEvent.Item.InventorySlot;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the slot this item now is.
|
||||||
|
/// </summary>
|
||||||
|
public uint TargetSlot => this.Item.InventorySlot;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the associated source event.
|
||||||
|
/// </summary>
|
||||||
|
public InventoryEventArgs SourceEvent { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the associated target event.
|
||||||
|
/// </summary>
|
||||||
|
public InventoryEventArgs TargetEvent { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string ToString() => $"{this.Type}({this.SourceEvent}, {this.TargetEvent})";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
namespace Dalamud.Game.Inventory.InventoryEventArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Abstract base class representing inventory changed events.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class InventoryEventArgs
|
||||||
|
{
|
||||||
|
private readonly GameInventoryItem item;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="InventoryEventArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">Type of the event.</param>
|
||||||
|
/// <param name="item">Item about the event.</param>
|
||||||
|
protected InventoryEventArgs(GameInventoryEvent type, in GameInventoryItem item)
|
||||||
|
{
|
||||||
|
this.Type = type;
|
||||||
|
this.item = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of event for these args.
|
||||||
|
/// </summary>
|
||||||
|
public GameInventoryEvent Type { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the item associated with this event.
|
||||||
|
/// <remarks><em>This is a copy of the item data.</em></remarks>
|
||||||
|
/// </summary>
|
||||||
|
// impl note: we return a ref readonly view, to avoid making copies every time this property is accessed.
|
||||||
|
// see: https://devblogs.microsoft.com/premier-developer/avoiding-struct-and-readonly-reference-performance-pitfalls-with-errorprone-net/
|
||||||
|
// "Consider using ref readonly locals and ref return for library code"
|
||||||
|
public ref readonly GameInventoryItem Item => ref this.item;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string ToString() => $"{this.Type}({this.Item})";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
namespace Dalamud.Game.Inventory.InventoryEventArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the data associated with an item being added to an inventory.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class InventoryItemAddedArgs : InventoryEventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="InventoryItemAddedArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item.</param>
|
||||||
|
internal InventoryItemAddedArgs(in GameInventoryItem item)
|
||||||
|
: base(GameInventoryEvent.Added, item)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the inventory this item was added to.
|
||||||
|
/// </summary>
|
||||||
|
public GameInventoryType Inventory => this.Item.ContainerType;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the slot this item was added to.
|
||||||
|
/// </summary>
|
||||||
|
public uint Slot => this.Item.InventorySlot;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
namespace Dalamud.Game.Inventory.InventoryEventArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the data associated with an items properties being changed.
|
||||||
|
/// This also includes an items stack count changing.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class InventoryItemChangedArgs : InventoryEventArgs
|
||||||
|
{
|
||||||
|
private readonly GameInventoryItem oldItemState;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="InventoryItemChangedArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="oldItem">The item before change.</param>
|
||||||
|
/// <param name="newItem">The item after change.</param>
|
||||||
|
internal InventoryItemChangedArgs(in GameInventoryItem oldItem, in GameInventoryItem newItem)
|
||||||
|
: base(GameInventoryEvent.Changed, newItem)
|
||||||
|
{
|
||||||
|
this.oldItemState = oldItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the inventory this item is in.
|
||||||
|
/// </summary>
|
||||||
|
public GameInventoryType Inventory => this.Item.ContainerType;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the inventory slot this item is in.
|
||||||
|
/// </summary>
|
||||||
|
public uint Slot => this.Item.InventorySlot;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the state of the item from before it was changed.
|
||||||
|
/// <remarks><em>This is a copy of the item data.</em></remarks>
|
||||||
|
/// </summary>
|
||||||
|
// impl note: see InventoryEventArgs.Item.
|
||||||
|
public ref readonly GameInventoryItem OldItemState => ref this.oldItemState;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
namespace Dalamud.Game.Inventory.InventoryEventArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the data associated with an item being merged from two stacks into one.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class InventoryItemMergedArgs : InventoryComplexEventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="InventoryItemMergedArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sourceEvent">The item at before slot.</param>
|
||||||
|
/// <param name="targetEvent">The item at after slot.</param>
|
||||||
|
internal InventoryItemMergedArgs(InventoryEventArgs sourceEvent, InventoryEventArgs targetEvent)
|
||||||
|
: base(GameInventoryEvent.Merged, sourceEvent, targetEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string ToString() =>
|
||||||
|
this.TargetEvent is InventoryItemChangedArgs iica
|
||||||
|
? $"{this.Type}(" +
|
||||||
|
$"item({this.Item.ItemId}), " +
|
||||||
|
$"{this.SourceInventory}#{this.SourceSlot}({this.SourceEvent.Item.Quantity} to 0), " +
|
||||||
|
$"{this.TargetInventory}#{this.TargetSlot}({iica.OldItemState.Quantity} to {iica.Item.Quantity}))"
|
||||||
|
: base.ToString();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
namespace Dalamud.Game.Inventory.InventoryEventArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the data associated with an item being moved from one inventory and added to another.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class InventoryItemMovedArgs : InventoryComplexEventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="InventoryItemMovedArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sourceEvent">The item at before slot.</param>
|
||||||
|
/// <param name="targetEvent">The item at after slot.</param>
|
||||||
|
internal InventoryItemMovedArgs(InventoryEventArgs sourceEvent, InventoryEventArgs targetEvent)
|
||||||
|
: base(GameInventoryEvent.Moved, sourceEvent, targetEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string ToString() =>
|
||||||
|
$"{this.Type}(item({this.Item.ItemId}) from {this.SourceInventory}#{this.SourceSlot} to {this.TargetInventory}#{this.TargetSlot})";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
namespace Dalamud.Game.Inventory.InventoryEventArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the data associated with an item being removed from an inventory.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class InventoryItemRemovedArgs : InventoryEventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="InventoryItemRemovedArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item.</param>
|
||||||
|
internal InventoryItemRemovedArgs(in GameInventoryItem item)
|
||||||
|
: base(GameInventoryEvent.Removed, item)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the inventory this item was removed from.
|
||||||
|
/// </summary>
|
||||||
|
public GameInventoryType Inventory => this.Item.ContainerType;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the slot this item was removed from.
|
||||||
|
/// </summary>
|
||||||
|
public uint Slot => this.Item.InventorySlot;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
namespace Dalamud.Game.Inventory.InventoryEventArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the data associated with an item being split from one stack into two.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class InventoryItemSplitArgs : InventoryComplexEventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="InventoryItemSplitArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sourceEvent">The item at before slot.</param>
|
||||||
|
/// <param name="targetEvent">The item at after slot.</param>
|
||||||
|
internal InventoryItemSplitArgs(InventoryEventArgs sourceEvent, InventoryEventArgs targetEvent)
|
||||||
|
: base(GameInventoryEvent.Split, sourceEvent, targetEvent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string ToString() =>
|
||||||
|
this.SourceEvent is InventoryItemChangedArgs iica
|
||||||
|
? $"{this.Type}(" +
|
||||||
|
$"item({this.Item.ItemId}), " +
|
||||||
|
$"{this.SourceInventory}#{this.SourceSlot}({iica.OldItemState.Quantity} to {iica.Item.Quantity}), " +
|
||||||
|
$"{this.TargetInventory}#{this.TargetSlot}(0 to {this.Item.Quantity}))"
|
||||||
|
: base.ToString();
|
||||||
|
}
|
||||||
|
|
@ -679,6 +679,9 @@ internal class ConsoleWindow : Window, IDisposable
|
||||||
|
|
||||||
private bool IsFilterApplicable(LogEntry entry)
|
private bool IsFilterApplicable(LogEntry entry)
|
||||||
{
|
{
|
||||||
|
if (this.regexError)
|
||||||
|
return false;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// If this entry is below a newly set minimum level, fail it
|
// If this entry is below a newly set minimum level, fail it
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ internal class DataWindow : Window
|
||||||
new FateTableWidget(),
|
new FateTableWidget(),
|
||||||
new FlyTextWidget(),
|
new FlyTextWidget(),
|
||||||
new FontAwesomeTestWidget(),
|
new FontAwesomeTestWidget(),
|
||||||
|
new GameInventoryTestWidget(),
|
||||||
new GamepadWidget(),
|
new GamepadWidget(),
|
||||||
new GaugeWidget(),
|
new GaugeWidget(),
|
||||||
new HookWidget(),
|
new HookWidget(),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,163 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Dalamud.Configuration.Internal;
|
||||||
|
using Dalamud.Game.Inventory;
|
||||||
|
using Dalamud.Game.Inventory.InventoryEventArgTypes;
|
||||||
|
using Dalamud.Interface.Colors;
|
||||||
|
using Dalamud.Interface.Utility;
|
||||||
|
using Dalamud.Interface.Utility.Raii;
|
||||||
|
using Dalamud.Logging.Internal;
|
||||||
|
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
|
using Serilog.Events;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tester for <see cref="GameInventory"/>.
|
||||||
|
/// </summary>
|
||||||
|
internal class GameInventoryTestWidget : IDataWindowWidget
|
||||||
|
{
|
||||||
|
private static readonly ModuleLog Log = new(nameof(GameInventoryTestWidget));
|
||||||
|
|
||||||
|
private GameInventoryPluginScoped? scoped;
|
||||||
|
private bool standardEnabled;
|
||||||
|
private bool rawEnabled;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string[]? CommandShortcuts { get; init; } = { "gameinventorytest" };
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string DisplayName { get; init; } = "GameInventory Test";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Ready { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Load() => this.Ready = true;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
if (Service<DalamudConfiguration>.Get().LogLevel > LogEventLevel.Information)
|
||||||
|
{
|
||||||
|
ImGuiHelpers.SafeTextColoredWrapped(
|
||||||
|
ImGuiColors.DalamudRed,
|
||||||
|
"Enable LogLevel=Information display to see the logs.");
|
||||||
|
}
|
||||||
|
|
||||||
|
using var table = ImRaii.Table(this.DisplayName, 3, ImGuiTableFlags.SizingFixedFit);
|
||||||
|
if (!table.Success)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TextUnformatted("Standard Logging");
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
using (ImRaii.Disabled(this.standardEnabled))
|
||||||
|
{
|
||||||
|
if (ImGui.Button("Enable##standard-enable") && !this.standardEnabled)
|
||||||
|
{
|
||||||
|
this.scoped ??= new();
|
||||||
|
this.scoped.InventoryChanged += ScopedOnInventoryChanged;
|
||||||
|
this.standardEnabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
using (ImRaii.Disabled(!this.standardEnabled))
|
||||||
|
{
|
||||||
|
if (ImGui.Button("Disable##standard-disable") && this.scoped is not null && this.standardEnabled)
|
||||||
|
{
|
||||||
|
this.scoped.InventoryChanged -= ScopedOnInventoryChanged;
|
||||||
|
this.standardEnabled = false;
|
||||||
|
if (!this.rawEnabled)
|
||||||
|
{
|
||||||
|
this.scoped.Dispose();
|
||||||
|
this.scoped = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TextUnformatted("Raw Logging");
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
using (ImRaii.Disabled(this.rawEnabled))
|
||||||
|
{
|
||||||
|
if (ImGui.Button("Enable##raw-enable") && !this.rawEnabled)
|
||||||
|
{
|
||||||
|
this.scoped ??= new();
|
||||||
|
this.scoped.InventoryChangedRaw += ScopedOnInventoryChangedRaw;
|
||||||
|
this.rawEnabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
using (ImRaii.Disabled(!this.rawEnabled))
|
||||||
|
{
|
||||||
|
if (ImGui.Button("Disable##raw-disable") && this.scoped is not null && this.rawEnabled)
|
||||||
|
{
|
||||||
|
this.scoped.InventoryChangedRaw -= ScopedOnInventoryChangedRaw;
|
||||||
|
this.rawEnabled = false;
|
||||||
|
if (!this.standardEnabled)
|
||||||
|
{
|
||||||
|
this.scoped.Dispose();
|
||||||
|
this.scoped = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TextUnformatted("All");
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
using (ImRaii.Disabled(this.standardEnabled && this.rawEnabled))
|
||||||
|
{
|
||||||
|
if (ImGui.Button("Enable##all-enable"))
|
||||||
|
{
|
||||||
|
this.scoped ??= new();
|
||||||
|
if (!this.standardEnabled)
|
||||||
|
this.scoped.InventoryChanged += ScopedOnInventoryChanged;
|
||||||
|
if (!this.rawEnabled)
|
||||||
|
this.scoped.InventoryChangedRaw += ScopedOnInventoryChangedRaw;
|
||||||
|
this.standardEnabled = this.rawEnabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
using (ImRaii.Disabled(this.scoped is null))
|
||||||
|
{
|
||||||
|
if (ImGui.Button("Disable##all-disable"))
|
||||||
|
{
|
||||||
|
this.scoped?.Dispose();
|
||||||
|
this.scoped = null;
|
||||||
|
this.standardEnabled = this.rawEnabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ScopedOnInventoryChangedRaw(IReadOnlyCollection<InventoryEventArgs> events)
|
||||||
|
{
|
||||||
|
var i = 0;
|
||||||
|
foreach (var e in events)
|
||||||
|
Log.Information($"[{++i}/{events.Count}] Raw: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ScopedOnInventoryChanged(IReadOnlyCollection<InventoryEventArgs> events)
|
||||||
|
{
|
||||||
|
var i = 0;
|
||||||
|
foreach (var e in events)
|
||||||
|
{
|
||||||
|
if (e is InventoryComplexEventArgs icea)
|
||||||
|
Log.Information($"[{++i}/{events.Count}] {icea}\n\t├ {icea.SourceEvent}\n\t└ {icea.TargetEvent}");
|
||||||
|
else
|
||||||
|
Log.Information($"[{++i}/{events.Count}] {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
106
Dalamud/Plugin/Services/IGameInventory.cs
Normal file
106
Dalamud/Plugin/Services/IGameInventory.cs
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Dalamud.Game.Inventory;
|
||||||
|
using Dalamud.Game.Inventory.InventoryEventArgTypes;
|
||||||
|
|
||||||
|
namespace Dalamud.Plugin.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class provides events for the in-game inventory.
|
||||||
|
/// </summary>
|
||||||
|
public interface IGameInventory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate function to be called when inventories have been changed.
|
||||||
|
/// This delegate sends the entire set of changes recorded.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="events">The events.</param>
|
||||||
|
public delegate void InventoryChangelogDelegate(IReadOnlyCollection<InventoryEventArgs> events);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate function to be called for each change to inventories.
|
||||||
|
/// This delegate sends individual events for changes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The event try that triggered this message.</param>
|
||||||
|
/// <param name="data">Data for the triggered event.</param>
|
||||||
|
public delegate void InventoryChangedDelegate(GameInventoryEvent type, InventoryEventArgs data);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate function to be called for each change to inventories.
|
||||||
|
/// This delegate sends individual events for changes.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The event arg type.</typeparam>
|
||||||
|
/// <param name="data">Data for the triggered event.</param>
|
||||||
|
public delegate void InventoryChangedDelegate<in T>(T data) where T : InventoryEventArgs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is fired when the inventory has been changed.<br />
|
||||||
|
/// Note that some events, such as <see cref="ItemAdded"/>, <see cref="ItemRemoved"/>, and <see cref="ItemChanged"/>
|
||||||
|
/// currently is subject to reinterpretation as <see cref="ItemMoved"/>, <see cref="ItemMerged"/>, and
|
||||||
|
/// <see cref="ItemSplit"/>.<br />
|
||||||
|
/// Use <see cref="InventoryChangedRaw"/> if you do not want such reinterpretation.
|
||||||
|
/// </summary>
|
||||||
|
public event InventoryChangelogDelegate InventoryChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is fired when the inventory has been changed, without trying to interpret two inventory slot changes
|
||||||
|
/// as a move event as appropriate.<br />
|
||||||
|
/// In other words, <see cref="GameInventoryEvent.Moved"/>, <see cref="GameInventoryEvent.Merged"/>, and
|
||||||
|
/// <see cref="GameInventoryEvent.Split"/> currently do not fire in this event.
|
||||||
|
/// </summary>
|
||||||
|
public event InventoryChangelogDelegate InventoryChangedRaw;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is fired when an item is added to an inventory.<br />
|
||||||
|
/// If this event is a part of multi-step event, then this event will not be called.<br />
|
||||||
|
/// Use <see cref="InventoryChangedRaw"/> if you do not want such reinterpretation.
|
||||||
|
/// </summary>
|
||||||
|
public event InventoryChangedDelegate ItemAdded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is fired when an item is removed from an inventory.<br />
|
||||||
|
/// If this event is a part of multi-step event, then this event will not be called.<br />
|
||||||
|
/// Use <see cref="InventoryChangedRaw"/> if you do not want such reinterpretation.
|
||||||
|
/// </summary>
|
||||||
|
public event InventoryChangedDelegate ItemRemoved;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is fired when an items properties are changed.<br />
|
||||||
|
/// If this event is a part of multi-step event, then this event will not be called.<br />
|
||||||
|
/// Use <see cref="InventoryChangedRaw"/> if you do not want such reinterpretation.
|
||||||
|
/// </summary>
|
||||||
|
public event InventoryChangedDelegate ItemChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is fired when an item is moved from one inventory into another.
|
||||||
|
/// </summary>
|
||||||
|
public event InventoryChangedDelegate ItemMoved;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is fired when an item is split from one stack into two.
|
||||||
|
/// </summary>
|
||||||
|
public event InventoryChangedDelegate ItemSplit;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is fired when an item is merged from two stacks into one.
|
||||||
|
/// </summary>
|
||||||
|
public event InventoryChangedDelegate ItemMerged;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ItemAdded"/>
|
||||||
|
public event InventoryChangedDelegate<InventoryItemAddedArgs> ItemAddedExplicit;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ItemRemoved"/>
|
||||||
|
public event InventoryChangedDelegate<InventoryItemRemovedArgs> ItemRemovedExplicit;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ItemChanged"/>
|
||||||
|
public event InventoryChangedDelegate<InventoryItemChangedArgs> ItemChangedExplicit;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ItemMoved"/>
|
||||||
|
public event InventoryChangedDelegate<InventoryItemMovedArgs> ItemMovedExplicit;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ItemSplit"/>
|
||||||
|
public event InventoryChangedDelegate<InventoryItemSplitArgs> ItemSplitExplicit;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ItemMerged"/>
|
||||||
|
public event InventoryChangedDelegate<InventoryItemMergedArgs> ItemMergedExplicit;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue