wip; needs testing and more thinking

This commit is contained in:
Soreepeong 2023-12-01 18:10:09 +09:00
parent 7c6f98dc9f
commit 34e3adb3f2
10 changed files with 286 additions and 232 deletions

View file

@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.InteropServices;
using Dalamud.Configuration.Internal;
using Dalamud.Game.Inventory.InventoryChangeArgsTypes;
using Dalamud.IoC; using Dalamud.IoC;
using Dalamud.IoC.Internal; using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal; using Dalamud.Logging.Internal;
@ -8,7 +9,9 @@ using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
namespace Dalamud.Game.GameInventory; using Serilog.Events;
namespace Dalamud.Game.Inventory;
/// <summary> /// <summary>
/// This class provides events for the players in-game inventory. /// This class provides events for the players in-game inventory.
@ -19,11 +22,18 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
{ {
private static readonly ModuleLog Log = new("GameInventory"); private static readonly ModuleLog Log = new("GameInventory");
private readonly List<InventoryEventArgs> changelog = new(); private readonly List<InventoryEventArgs> allEvents = 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();
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly Framework framework = Service<Framework>.Get(); private readonly Framework framework = Service<Framework>.Get();
[ServiceManager.ServiceDependency]
private readonly DalamudConfiguration dalamudConfiguration = Service<DalamudConfiguration>.Get();
private readonly GameInventoryType[] inventoryTypes; private readonly GameInventoryType[] inventoryTypes;
private readonly GameInventoryItem[]?[] inventoryItems; private readonly GameInventoryItem[]?[] inventoryItems;
@ -39,6 +49,9 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
/// <inheritdoc/> /// <inheritdoc/>
public event IGameInventory.InventoryChangelogDelegate? InventoryChanged; public event IGameInventory.InventoryChangelogDelegate? InventoryChanged;
/// <inheritdoc/>
public event IGameInventory.InventoryChangelogDelegate? InventoryChangedRaw;
/// <inheritdoc/> /// <inheritdoc/>
public event IGameInventory.InventoryChangedDelegate? ItemAdded; public event IGameInventory.InventoryChangedDelegate? ItemAdded;
@ -73,6 +86,32 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
return new ReadOnlySpan<GameInventoryItem>(inventory->Items, (int)inventory->Size); return new ReadOnlySpan<GameInventoryItem>(inventory->Items, (int)inventory->Size);
} }
private static void InvokeSafely(
IGameInventory.InventoryChangelogDelegate? cb,
IReadOnlyCollection<InventoryEventArgs> data)
{
try
{
cb?.Invoke(data);
}
catch (Exception e)
{
Log.Error(e, "Exception during batch callback");
}
}
private static void InvokeSafely(IGameInventory.InventoryChangedDelegate? cb, InventoryEventArgs arg)
{
try
{
cb?.Invoke(arg.Type, arg);
}
catch (Exception e)
{
Log.Error(e, "Exception during {argType} callback", arg.Type);
}
}
private void OnFrameworkUpdate(IFramework framework1) private void OnFrameworkUpdate(IFramework framework1)
{ {
for (var i = 0; i < this.inventoryTypes.Length; i++) for (var i = 0; i < this.inventoryTypes.Length; i++)
@ -90,208 +129,146 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
if (oldItem.IsEmpty) if (oldItem.IsEmpty)
{ {
if (newItem.IsEmpty) if (!newItem.IsEmpty)
continue;
this.changelog.Add(new InventoryItemAddedArgs
{ {
Item = newItem, this.addedEvents.Add(new(newItem));
Inventory = newItem.ContainerType, oldItem = newItem;
Slot = newItem.InventorySlot, }
});
} }
else else
{ {
if (newItem.IsEmpty) if (newItem.IsEmpty)
{ {
this.changelog.Add(new InventoryItemRemovedArgs this.removedEvents.Add(new(oldItem));
{ oldItem = newItem;
Item = oldItem,
Inventory = oldItem.ContainerType,
Slot = oldItem.InventorySlot,
});
} }
else if (!oldItem.Equals(newItem)) else if (!oldItem.Equals(newItem))
{ {
this.changelog.Add(new InventoryItemChangedArgs this.changedEvents.Add(new(oldItem, newItem));
{
OldItemState = oldItem,
Item = newItem,
Inventory = newItem.ContainerType,
Slot = newItem.InventorySlot,
});
}
else
{
continue;
}
}
Log.Verbose($"[{this.changelog.Count - 1}] {this.changelog[^1]}");
oldItem = newItem; oldItem = newItem;
} }
} }
}
}
// Was there any change? If not, stop further processing. // Was there any change? If not, stop further processing.
if (this.changelog.Count == 0) // Note that...
// * this.movedEvents is not checked; it will be populated after this check.
// * this.allEvents is not checked; it is a temporary list to be used after this check.
if (this.addedEvents.Count == 0 && this.removedEvents.Count == 0 && this.changedEvents.Count == 0)
return; return;
try try
{ {
// From this point, the size of changelog shall not change. // Broadcast InventoryChangedRaw, if necessary.
var span = CollectionsMarshal.AsSpan(this.changelog); if (this.InventoryChangedRaw is not null)
// Ensure that changelog is in order of Added, Removed, and then Changed.
span.Sort((a, b) => a.Type.CompareTo(b.Type));
var removedFrom = 0;
while (removedFrom < span.Length && span[removedFrom].Type != GameInventoryEvent.Removed)
removedFrom++;
var changedFrom = removedFrom;
while (changedFrom < span.Length && span[changedFrom].Type != GameInventoryEvent.Changed)
changedFrom++;
var addedSpan = span[..removedFrom];
var removedSpan = span[removedFrom..changedFrom];
var changedSpan = span[changedFrom..];
// Resolve changelog for item moved, from 1 added + 1 removed
foreach (ref var added in addedSpan)
{ {
foreach (ref var removed in removedSpan) this.allEvents.Clear();
{ this.allEvents.EnsureCapacity(
if (added.Item.ItemId == removed.Item.ItemId) this.addedEvents.Count
{ + this.removedEvents.Count
Log.Verbose($"Move: reinterpreting {removed} + {added}"); + this.changedEvents.Count);
added = new InventoryItemMovedArgs this.allEvents.AddRange(this.addedEvents);
{ this.allEvents.AddRange(this.removedEvents);
Item = removed.Item, this.allEvents.AddRange(this.changedEvents);
SourceInventory = removed.Item.ContainerType, InvokeSafely(this.InventoryChangedRaw, this.allEvents);
SourceSlot = removed.Item.InventorySlot,
TargetInventory = added.Item.ContainerType,
TargetSlot = added.Item.InventorySlot,
};
removed = default;
break;
}
}
} }
// Resolve changelog for item moved, from 2 changes // Resolve changelog for item moved, from 1 added + 1 removed event.
for (var i = 0; i < changedSpan.Length; i++) for (var iAdded = this.addedEvents.Count - 1; iAdded >= 0; --iAdded)
{ {
if (span[i].Type is GameInventoryEvent.Empty) 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; continue;
ref var e1 = ref changedSpan[i]; this.movedEvents.Add(new(removed, added));
for (var j = i + 1; j < changedSpan.Length; j++)
{
ref var e2 = ref changedSpan[j];
if (e1.Item.ItemId == e2.Item.ItemId && e1.Item.ItemId == e2.Item.ItemId)
{
if (e1.Item.IsEmpty)
{
// e1 got moved to e2
Log.Verbose($"Move: reinterpreting {e1} + {e2}");
e1 = new InventoryItemMovedArgs
{
Item = e2.Item,
SourceInventory = e1.Item.ContainerType,
SourceSlot = e1.Item.InventorySlot,
TargetInventory = e2.Item.ContainerType,
TargetSlot = e2.Item.InventorySlot,
};
e2 = default;
}
else if (e2.Item.IsEmpty)
{
// e2 got moved to e1
Log.Verbose($"Move: reinterpreting {e2} + {e1}");
e1 = new InventoryItemMovedArgs
{
Item = e1.Item,
SourceInventory = e2.Item.ContainerType,
SourceSlot = e2.Item.InventorySlot,
TargetInventory = e1.Item.ContainerType,
TargetSlot = e1.Item.InventorySlot,
};
e2 = default;
}
else
{
// e1 and e2 got swapped
Log.Verbose($"Move(Swap): reinterpreting {e1} + {e2}");
var newEvent1 = new InventoryItemMovedArgs
{
Item = e2.Item,
SourceInventory = e1.Item.ContainerType,
SourceSlot = e1.Item.InventorySlot,
TargetInventory = e2.Item.ContainerType,
TargetSlot = e2.Item.InventorySlot,
};
var newEvent2 = new InventoryItemMovedArgs // Remove the reinterpreted entries.
{ this.addedEvents.RemoveAt(iAdded);
Item = e1.Item, this.removedEvents.RemoveAt(iRemoved);
SourceInventory = e2.Item.ContainerType,
SourceSlot = e2.Item.InventorySlot,
TargetInventory = e1.Item.ContainerType,
TargetSlot = e1.Item.InventorySlot,
};
(e1, e2) = (newEvent1, newEvent2);
}
}
}
}
// Filter out the emptied out entries.
// We do not care about the order of items in the changelog anymore.
for (var i = 0; i < span.Length;)
{
if (span[i] is null || span[i].Type is GameInventoryEvent.Empty)
{
span[i] = span[^1];
span = span[..^1];
}
else
{
i++;
}
}
// Actually broadcast the changes to subscribers.
if (!span.IsEmpty)
{
this.InventoryChanged?.Invoke(span);
foreach (var change in span)
{
switch (change)
{
case InventoryItemAddedArgs:
this.ItemAdded?.Invoke(GameInventoryEvent.Added, change);
break;
case InventoryItemRemovedArgs:
this.ItemRemoved?.Invoke(GameInventoryEvent.Removed, change);
break;
case InventoryItemMovedArgs:
this.ItemMoved?.Invoke(GameInventoryEvent.Moved, change);
break;
case InventoryItemChangedArgs:
this.ItemChanged?.Invoke(GameInventoryEvent.Changed, change);
break; break;
} }
} }
// Resolve changelog for item moved, 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.Item.ItemId || e1.Item.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);
break;
} }
} }
// Log only if it matters.
if (this.dalamudConfiguration.LogLevel >= LogEventLevel.Verbose)
{
foreach (var x in this.addedEvents)
Log.Verbose($"{x}");
foreach (var x in this.removedEvents)
Log.Verbose($"{x}");
foreach (var x in this.changedEvents)
Log.Verbose($"{x}");
foreach (var x in this.movedEvents)
Log.Verbose($"{x} (({x.SourceEvent}) + ({x.TargetEvent}))");
}
// Broadcast InventoryChanged, if necessary.
if (this.InventoryChanged is not null)
{
this.allEvents.Clear();
this.allEvents.EnsureCapacity(
this.addedEvents.Count
+ this.removedEvents.Count
+ this.changedEvents.Count
+ this.movedEvents.Count);
this.allEvents.AddRange(this.addedEvents);
this.allEvents.AddRange(this.removedEvents);
this.allEvents.AddRange(this.changedEvents);
this.allEvents.AddRange(this.movedEvents);
InvokeSafely(this.InventoryChanged, this.allEvents);
}
// Broadcast the rest.
foreach (var x in this.addedEvents)
InvokeSafely(this.ItemAdded, x);
foreach (var x in this.removedEvents)
InvokeSafely(this.ItemRemoved, x);
foreach (var x in this.changedEvents)
InvokeSafely(this.ItemChanged, x);
foreach (var x in this.movedEvents)
InvokeSafely(this.ItemMoved, x);
}
finally finally
{ {
this.changelog.Clear(); this.addedEvents.Clear();
this.removedEvents.Clear();
this.changedEvents.Clear();
this.movedEvents.Clear();
} }
} }
} }
@ -316,6 +293,7 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
public GameInventoryPluginScoped() public GameInventoryPluginScoped()
{ {
this.gameInventoryService.InventoryChanged += this.OnInventoryChangedForward; this.gameInventoryService.InventoryChanged += this.OnInventoryChangedForward;
this.gameInventoryService.InventoryChangedRaw += this.OnInventoryChangedRawForward;
this.gameInventoryService.ItemAdded += this.OnInventoryItemAddedForward; this.gameInventoryService.ItemAdded += this.OnInventoryItemAddedForward;
this.gameInventoryService.ItemRemoved += this.OnInventoryItemRemovedForward; this.gameInventoryService.ItemRemoved += this.OnInventoryItemRemovedForward;
this.gameInventoryService.ItemMoved += this.OnInventoryItemMovedForward; this.gameInventoryService.ItemMoved += this.OnInventoryItemMovedForward;
@ -325,6 +303,9 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
/// <inheritdoc/> /// <inheritdoc/>
public event IGameInventory.InventoryChangelogDelegate? InventoryChanged; public event IGameInventory.InventoryChangelogDelegate? InventoryChanged;
/// <inheritdoc/>
public event IGameInventory.InventoryChangelogDelegate? InventoryChangedRaw;
/// <inheritdoc/> /// <inheritdoc/>
public event IGameInventory.InventoryChangedDelegate? ItemAdded; public event IGameInventory.InventoryChangedDelegate? ItemAdded;
@ -341,21 +322,26 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
public void Dispose() public void Dispose()
{ {
this.gameInventoryService.InventoryChanged -= this.OnInventoryChangedForward; this.gameInventoryService.InventoryChanged -= this.OnInventoryChangedForward;
this.gameInventoryService.InventoryChangedRaw -= this.OnInventoryChangedRawForward;
this.gameInventoryService.ItemAdded -= this.OnInventoryItemAddedForward; this.gameInventoryService.ItemAdded -= this.OnInventoryItemAddedForward;
this.gameInventoryService.ItemRemoved -= this.OnInventoryItemRemovedForward; this.gameInventoryService.ItemRemoved -= this.OnInventoryItemRemovedForward;
this.gameInventoryService.ItemMoved -= this.OnInventoryItemMovedForward; this.gameInventoryService.ItemMoved -= this.OnInventoryItemMovedForward;
this.gameInventoryService.ItemChanged -= this.OnInventoryItemChangedForward; this.gameInventoryService.ItemChanged -= this.OnInventoryItemChangedForward;
this.InventoryChanged = null; this.InventoryChanged = null;
this.InventoryChangedRaw = null;
this.ItemAdded = null; this.ItemAdded = null;
this.ItemRemoved = null; this.ItemRemoved = null;
this.ItemMoved = null; this.ItemMoved = null;
this.ItemChanged = null; this.ItemChanged = null;
} }
private void OnInventoryChangedForward(ReadOnlySpan<InventoryEventArgs> events) private void OnInventoryChangedForward(IReadOnlyCollection<InventoryEventArgs> events)
=> this.InventoryChanged?.Invoke(events); => this.InventoryChanged?.Invoke(events);
private void OnInventoryChangedRawForward(IReadOnlyCollection<InventoryEventArgs> events)
=> this.InventoryChangedRaw?.Invoke(events);
private void OnInventoryItemAddedForward(GameInventoryEvent type, InventoryEventArgs data) private void OnInventoryItemAddedForward(GameInventoryEvent type, InventoryEventArgs data)
=> this.ItemAdded?.Invoke(type, data); => this.ItemAdded?.Invoke(type, data);

View file

@ -1,4 +1,4 @@
namespace Dalamud.Game.GameInventory; namespace Dalamud.Game.Inventory;
/// <summary> /// <summary>
/// Class representing a item's changelog state. /// Class representing a item's changelog state.

View file

@ -4,7 +4,7 @@ using System.Runtime.InteropServices;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
namespace Dalamud.Game.GameInventory; namespace Dalamud.Game.Inventory;
/// <summary> /// <summary>
/// Dalamud wrapper around a ClientStructs InventoryItem. /// Dalamud wrapper around a ClientStructs InventoryItem.

View file

@ -1,4 +1,4 @@
namespace Dalamud.Game.GameInventory; namespace Dalamud.Game.Inventory;
/// <summary> /// <summary>
/// Enum representing various player inventories. /// Enum representing various player inventories.

View file

@ -1,29 +1,35 @@
namespace Dalamud.Game.GameInventory; using System.Diagnostics.CodeAnalysis;
namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
/// <summary> /// <summary>
/// Abstract base class representing inventory changed events. /// Abstract base class representing inventory changed events.
/// </summary> /// </summary>
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206:Declaration keywords should follow order", Justification = "It literally says <access modifiers>, <static>, and then <all other keywords>. required is not an access modifier.")]
public abstract class InventoryEventArgs public abstract class InventoryEventArgs
{ {
/// <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> /// <summary>
/// Gets the type of event for these args. /// Gets the type of event for these args.
/// </summary> /// </summary>
public abstract GameInventoryEvent Type { get; } public GameInventoryEvent Type { get; }
/// <summary> /// <summary>
/// Gets the item associated with this event. /// Gets the item associated with this event.
/// <remarks><em>This is a copy of the item data.</em></remarks> /// <remarks><em>This is a copy of the item data.</em></remarks>
/// </summary> /// </summary>
required public GameInventoryItem Item { get; init; } public GameInventoryItem Item { get; }
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString() => this.Type switch public override string ToString() => $"<{this.Type}> ({this.Item})";
{
GameInventoryEvent.Empty => $"<{this.Type}>",
GameInventoryEvent.Added => $"<{this.Type}> ({this.Item})",
GameInventoryEvent.Removed => $"<{this.Type}> ({this.Item})",
GameInventoryEvent.Changed => $"<{this.Type}> ({this.Item})",
GameInventoryEvent.Moved when this is InventoryItemMovedArgs args => $"<{this.Type}> (Item #{this.Item.ItemId}) from (slot {args.SourceSlot} in {args.SourceInventory}) to (slot {args.TargetSlot} in {args.TargetInventory})",
_ => $"<Type={this.Type}> {this.Item}",
};
} }

View file

@ -1,20 +1,26 @@
namespace Dalamud.Game.GameInventory; namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
/// <summary> /// <summary>
/// Represents the data associated with an item being added to an inventory. /// Represents the data associated with an item being added to an inventory.
/// </summary> /// </summary>
public class InventoryItemAddedArgs : InventoryEventArgs public class InventoryItemAddedArgs : InventoryEventArgs
{ {
/// <inheritdoc/> /// <summary>
public override GameInventoryEvent Type => GameInventoryEvent.Added; /// 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> /// <summary>
/// Gets the inventory this item was added to. /// Gets the inventory this item was added to.
/// </summary> /// </summary>
required public GameInventoryType Inventory { get; init; } public GameInventoryType Inventory => this.Item.ContainerType;
/// <summary> /// <summary>
/// Gets the slot this item was added to. /// Gets the slot this item was added to.
/// </summary> /// </summary>
required public uint Slot { get; init; } public uint Slot => this.Item.InventorySlot;
} }

View file

@ -1,4 +1,4 @@
namespace Dalamud.Game.GameInventory; namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
/// <summary> /// <summary>
/// Represents the data associated with an items properties being changed. /// Represents the data associated with an items properties being changed.
@ -6,21 +6,29 @@
/// </summary> /// </summary>
public class InventoryItemChangedArgs : InventoryEventArgs public class InventoryItemChangedArgs : InventoryEventArgs
{ {
/// <inheritdoc/> /// <summary>
public override GameInventoryEvent Type => GameInventoryEvent.Changed; /// 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> /// <summary>
/// Gets the inventory this item is in. /// Gets the inventory this item is in.
/// </summary> /// </summary>
required public GameInventoryType Inventory { get; init; } public GameInventoryType Inventory => this.Item.ContainerType;
/// <summary> /// <summary>
/// Gets the inventory slot this item is in. /// Gets the inventory slot this item is in.
/// </summary> /// </summary>
required public uint Slot { get; init; } public uint Slot => this.Item.InventorySlot;
/// <summary> /// <summary>
/// Gets the state of the item from before it was changed. /// Gets the state of the item from before it was changed.
/// </summary> /// </summary>
required public GameInventoryItem OldItemState { get; init; } public GameInventoryItem OldItemState { get; init; }
} }

View file

@ -1,30 +1,56 @@
namespace Dalamud.Game.GameInventory; using System.Diagnostics.CodeAnalysis;
namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
/// <summary> /// <summary>
/// Represents the data associated with an item being moved from one inventory and added to another. /// Represents the data associated with an item being moved from one inventory and added to another.
/// </summary> /// </summary>
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206:Declaration keywords should follow order", Justification = "It literally says <access modifiers>, <static>, and then <all other keywords>. required is not an access modifier.")]
public class InventoryItemMovedArgs : InventoryEventArgs public class InventoryItemMovedArgs : InventoryEventArgs
{ {
/// <inheritdoc/> /// <summary>
public override GameInventoryEvent Type => GameInventoryEvent.Moved; /// 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, targetEvent.Item)
{
this.SourceEvent = sourceEvent;
this.TargetEvent = targetEvent;
}
/// <summary> /// <summary>
/// Gets the inventory this item was moved from. /// Gets the inventory this item was moved from.
/// </summary> /// </summary>
required public GameInventoryType SourceInventory { get; init; } public GameInventoryType SourceInventory => this.SourceEvent.Item.ContainerType;
/// <summary> /// <summary>
/// Gets the inventory this item was moved to. /// Gets the inventory this item was moved to.
/// </summary> /// </summary>
required public GameInventoryType TargetInventory { get; init; } public GameInventoryType TargetInventory => this.Item.ContainerType;
/// <summary> /// <summary>
/// Gets the slot this item was moved from. /// Gets the slot this item was moved from.
/// </summary> /// </summary>
required public uint SourceSlot { get; init; } public uint SourceSlot => this.SourceEvent.Item.InventorySlot;
/// <summary> /// <summary>
/// Gets the slot this item was moved to. /// Gets the slot this item was moved to.
/// </summary> /// </summary>
required public uint TargetSlot { get; init; } public uint TargetSlot => this.Item.InventorySlot;
/// <summary>
/// Gets the associated source event.
/// </summary>
internal InventoryEventArgs SourceEvent { get; }
/// <summary>
/// Gets the associated target event.
/// </summary>
internal InventoryEventArgs TargetEvent { get; }
/// <inheritdoc/>
public override string ToString() =>
$"<{this.Type}> (Item #{this.Item.ItemId}) from (slot {this.SourceSlot} in {this.SourceInventory}) to (slot {this.TargetSlot} in {this.TargetInventory})";
} }

View file

@ -1,20 +1,26 @@
namespace Dalamud.Game.GameInventory; namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
/// <summary> /// <summary>
/// Represents the data associated with an item being removed from an inventory. /// Represents the data associated with an item being removed from an inventory.
/// </summary> /// </summary>
public class InventoryItemRemovedArgs : InventoryEventArgs public class InventoryItemRemovedArgs : InventoryEventArgs
{ {
/// <inheritdoc/> /// <summary>
public override GameInventoryEvent Type => GameInventoryEvent.Removed; /// 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> /// <summary>
/// Gets the inventory this item was removed from. /// Gets the inventory this item was removed from.
/// </summary> /// </summary>
required public GameInventoryType Inventory { get; init; } public GameInventoryType Inventory => this.Item.ContainerType;
/// <summary> /// <summary>
/// Gets the slot this item was removed from. /// Gets the slot this item was removed from.
/// </summary> /// </summary>
required public uint Slot { get; init; } public uint Slot => this.Item.InventorySlot;
} }

View file

@ -1,4 +1,7 @@
using Dalamud.Game.GameInventory; using System.Collections.Generic;
using Dalamud.Game.Inventory;
using Dalamud.Game.Inventory.InventoryChangeArgsTypes;
namespace Dalamud.Plugin.Services; namespace Dalamud.Plugin.Services;
@ -12,7 +15,7 @@ public interface IGameInventory
/// This delegate sends the entire set of changes recorded. /// This delegate sends the entire set of changes recorded.
/// </summary> /// </summary>
/// <param name="events">The events.</param> /// <param name="events">The events.</param>
public delegate void InventoryChangelogDelegate(ReadOnlySpan<InventoryEventArgs> events); public delegate void InventoryChangelogDelegate(IReadOnlyCollection<InventoryEventArgs> events);
/// <summary> /// <summary>
/// Delegate function to be called for each change to inventories. /// Delegate function to be called for each change to inventories.
@ -28,22 +31,35 @@ public interface IGameInventory
public event InventoryChangelogDelegate InventoryChanged; public event InventoryChangelogDelegate InventoryChanged;
/// <summary> /// <summary>
/// Event that is fired when an item is added to an inventory. /// 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"/> does 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 an accompanying item remove event happens, then <see cref="ItemMoved"/> will be called instead.<br />
/// Use <see cref="InventoryChangedRaw"/> if you do not want such reinterpretation.
/// </summary> /// </summary>
public event InventoryChangedDelegate ItemAdded; public event InventoryChangedDelegate ItemAdded;
/// <summary> /// <summary>
/// Event that is fired when an item is removed from an inventory. /// Event that is fired when an item is removed from an inventory.<br />
/// If an accompanying item add event happens, then <see cref="ItemMoved"/> will be called instead.<br />
/// Use <see cref="InventoryChangedRaw"/> if you do not want such reinterpretation.
/// </summary> /// </summary>
public event InventoryChangedDelegate ItemRemoved; public event InventoryChangedDelegate ItemRemoved;
/// <summary>
/// Event that is fired when an items properties are changed.<br />
/// If an accompanying item change event happens, then <see cref="ItemMoved"/> will be called instead.<br />
/// Use <see cref="InventoryChangedRaw"/> if you do not want such reinterpretation.
/// </summary>
public event InventoryChangedDelegate ItemChanged;
/// <summary> /// <summary>
/// Event that is fired when an item is moved from one inventory into another. /// Event that is fired when an item is moved from one inventory into another.
/// </summary> /// </summary>
public event InventoryChangedDelegate ItemMoved; public event InventoryChangedDelegate ItemMoved;
/// <summary>
/// Event that is fired when an items properties are changed.
/// </summary>
public event InventoryChangedDelegate ItemChanged;
} }