diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs index b5e3029b9..36e6756bf 100644 --- a/Dalamud/Game/Inventory/GameInventory.cs +++ b/Dalamud/Game/Inventory/GameInventory.cs @@ -28,6 +28,8 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory private readonly List removedEvents = new(); private readonly List changedEvents = new(); private readonly List movedEvents = new(); + private readonly List splitEvents = new(); + private readonly List mergedEvents = new(); [ServiceManager.ServiceDependency] private readonly Framework framework = Service.Get(); @@ -45,6 +47,21 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory this.inventoryItems = new GameInventoryItem[this.inventoryTypes.Length][]; this.framework.Update += this.OnFrameworkUpdate; + + // Separate log logic as an event handler. + this.InventoryChanged += events => + { + if (this.dalamudConfiguration.LogLevel > LogEventLevel.Verbose) + return; + + foreach (var e in events) + { + if (e is InventoryComplexEventArgs icea) + Log.Verbose($"{icea}\n\t├ {icea.SourceEvent}\n\t└ {icea.TargetEvent}"); + else + Log.Verbose($"{e}"); + } + }; } /// @@ -65,6 +82,12 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory /// public event IGameInventory.InventoryChangedDelegate? ItemMoved; + /// + public event IGameInventory.InventoryChangedDelegate? ItemSplit; + + /// + public event IGameInventory.InventoryChangedDelegate? ItemMerged; + /// public void Dispose() { @@ -153,11 +176,12 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory } // Was there any change? If not, stop further processing. - // Note that this.movedEvents is not checked; it will be populated after this check. + // 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; // Broadcast InventoryChangedRaw. + // Same reason with the above on why are there 3 lists of events involved. InvokeSafely( this.InventoryChangedRaw, new DeferredReadOnlyCollection( @@ -169,7 +193,7 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory .Concat(this.removedEvents) .Concat(this.changedEvents))); - // Resolve changelog for item moved, from 1 added + 1 removed event. + // Resolve moved items, from 1 added + 1 removed event. for (var iAdded = this.addedEvents.Count - 1; iAdded >= 0; --iAdded) { var added = this.addedEvents[iAdded]; @@ -188,7 +212,7 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory } } - // Resolve changelog for item moved, from 2 changed events. + // Resolve moved items, from 2 changed events. for (var i = this.changedEvents.Count - 1; i >= 0; --i) { var e1 = this.changedEvents[i]; @@ -209,27 +233,49 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory // 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; } } - // Log only if it matters. - if (this.dalamudConfiguration.LogLevel <= LogEventLevel.Verbose) + // Resolve split items, from 1 added + 1 changed event. + for (var iAdded = this.addedEvents.Count - 1; iAdded >= 0; --iAdded) { - foreach (var x in this.addedEvents) - Log.Verbose($"{x}"); + 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; - foreach (var x in this.removedEvents) - Log.Verbose($"{x}"); + this.splitEvents.Add(new(changed, added)); - foreach (var x in this.changedEvents) - Log.Verbose($"{x}"); + // Remove the reinterpreted entries. + this.addedEvents.RemoveAt(iAdded); + this.changedEvents.RemoveAt(iChanged); + break; + } + } - foreach (var x in this.movedEvents) - Log.Verbose($"{x} (({x.SourceEvent}) + ({x.TargetEvent}))"); + // 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; + } } // Broadcast the rest. @@ -239,12 +285,16 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory this.addedEvents.Count + this.removedEvents.Count + this.changedEvents.Count + - this.movedEvents.Count, + this.movedEvents.Count + + this.splitEvents.Count + + this.mergedEvents.Count, () => Array.Empty() .Concat(this.addedEvents) .Concat(this.removedEvents) .Concat(this.changedEvents) - .Concat(this.movedEvents))); + .Concat(this.movedEvents) + .Concat(this.splitEvents) + .Concat(this.mergedEvents))); foreach (var x in this.addedEvents) InvokeSafely(this.ItemAdded, x); @@ -258,11 +308,19 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory foreach (var x in this.movedEvents) InvokeSafely(this.ItemMoved, x); + foreach (var x in this.splitEvents) + InvokeSafely(this.ItemSplit, x); + + foreach (var x in this.mergedEvents) + InvokeSafely(this.ItemMerged, x); + // 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(); } /// @@ -313,6 +371,8 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven this.gameInventoryService.ItemRemoved += this.OnInventoryItemRemovedForward; this.gameInventoryService.ItemMoved += this.OnInventoryItemMovedForward; this.gameInventoryService.ItemChanged += this.OnInventoryItemChangedForward; + this.gameInventoryService.ItemSplit += this.OnInventoryItemSplitForward; + this.gameInventoryService.ItemMerged += this.OnInventoryItemMergedForward; } /// @@ -333,6 +393,12 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven /// public event IGameInventory.InventoryChangedDelegate? ItemMoved; + /// + public event IGameInventory.InventoryChangedDelegate? ItemSplit; + + /// + public event IGameInventory.InventoryChangedDelegate? ItemMerged; + /// public void Dispose() { @@ -342,6 +408,8 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven this.gameInventoryService.ItemRemoved -= this.OnInventoryItemRemovedForward; this.gameInventoryService.ItemChanged -= this.OnInventoryItemChangedForward; this.gameInventoryService.ItemMoved -= this.OnInventoryItemMovedForward; + this.gameInventoryService.ItemSplit -= this.OnInventoryItemSplitForward; + this.gameInventoryService.ItemMerged -= this.OnInventoryItemMergedForward; this.InventoryChanged = null; this.InventoryChangedRaw = null; @@ -349,6 +417,8 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven this.ItemRemoved = null; this.ItemChanged = null; this.ItemMoved = null; + this.ItemSplit = null; + this.ItemMerged = null; } private void OnInventoryChangedForward(IReadOnlyCollection events) @@ -368,4 +438,10 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven private void OnInventoryItemMovedForward(GameInventoryEvent type, InventoryEventArgs data) => this.ItemMoved?.Invoke(type, data); + + private void OnInventoryItemSplitForward(GameInventoryEvent type, InventoryEventArgs data) + => this.ItemSplit?.Invoke(type, data); + + private void OnInventoryItemMergedForward(GameInventoryEvent type, InventoryEventArgs data) + => this.ItemMerged?.Invoke(type, data); } diff --git a/Dalamud/Game/Inventory/GameInventoryEvent.cs b/Dalamud/Game/Inventory/GameInventoryEvent.cs index 6a4bb86e2..16efab648 100644 --- a/Dalamud/Game/Inventory/GameInventoryEvent.cs +++ b/Dalamud/Game/Inventory/GameInventoryEvent.cs @@ -30,4 +30,14 @@ public enum GameInventoryEvent /// Item has been moved, possibly across different inventories. /// Moved = 4, + + /// + /// Item has been split into two stacks from one, possibly across different inventories. + /// + Split = 5, + + /// + /// Item has been merged into one stack from two, possibly across different inventories. + /// + Merged = 6, } diff --git a/Dalamud/Game/Inventory/GameInventoryItem.cs b/Dalamud/Game/Inventory/GameInventoryItem.cs index 52a5c5e3c..4958574aa 100644 --- a/Dalamud/Game/Inventory/GameInventoryItem.cs +++ b/Dalamud/Game/Inventory/GameInventoryItem.cs @@ -158,6 +158,6 @@ public unsafe struct GameInventoryItem : IEquatable /// public override string ToString() => this.IsEmpty - ? "no item" - : $"item #{this.ItemId} at slot {this.InventorySlot} in {this.ContainerType}"; + ? "empty" + : $"item({this.ItemId}@{this.ContainerType}#{this.InventorySlot})"; } diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryComplexEventArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryComplexEventArgs.cs new file mode 100644 index 000000000..c44bfb991 --- /dev/null +++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryComplexEventArgs.cs @@ -0,0 +1,54 @@ +namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes; + +/// +/// Represents the data associated with an item being affected across different slots, possibly in different containers. +/// +public abstract class InventoryComplexEventArgs : InventoryEventArgs +{ + /// + /// Initializes a new instance of the class. + /// + /// Type of the event. + /// The item at before slot. + /// The item at after slot. + internal InventoryComplexEventArgs( + GameInventoryEvent type, InventoryEventArgs sourceEvent, InventoryEventArgs targetEvent) + : base(type, targetEvent.Item) + { + this.SourceEvent = sourceEvent; + this.TargetEvent = targetEvent; + } + + /// + /// Gets the inventory this item was at. + /// + public GameInventoryType SourceInventory => this.SourceEvent.Item.ContainerType; + + /// + /// Gets the inventory this item now is. + /// + public GameInventoryType TargetInventory => this.Item.ContainerType; + + /// + /// Gets the slot this item was at. + /// + public uint SourceSlot => this.SourceEvent.Item.InventorySlot; + + /// + /// Gets the slot this item now is. + /// + public uint TargetSlot => this.Item.InventorySlot; + + /// + /// Gets the associated source event. + /// + public InventoryEventArgs SourceEvent { get; } + + /// + /// Gets the associated target event. + /// + public InventoryEventArgs TargetEvent { get; } + + /// + public override string ToString() => $"{this.Type}({this.SourceEvent}, {this.TargetEvent})"; +} diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs index 301715bf2..8197e28f5 100644 --- a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs +++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs @@ -33,5 +33,5 @@ public abstract class InventoryEventArgs public ref readonly GameInventoryItem Item => ref this.item; /// - public override string ToString() => $"<{this.Type}> ({this.Item})"; + public override string ToString() => $"{this.Type}({this.Item})"; } diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemAddedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemAddedArgs.cs index f68b23106..45a35739a 100644 --- a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemAddedArgs.cs +++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemAddedArgs.cs @@ -3,7 +3,7 @@ /// /// Represents the data associated with an item being added to an inventory. /// -public class InventoryItemAddedArgs : InventoryEventArgs +public sealed class InventoryItemAddedArgs : InventoryEventArgs { /// /// Initializes a new instance of the class. diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs index 1682ae32d..191cfa1d8 100644 --- a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs +++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs @@ -4,7 +4,7 @@ /// Represents the data associated with an items properties being changed. /// This also includes an items stack count changing. /// -public class InventoryItemChangedArgs : InventoryEventArgs +public sealed class InventoryItemChangedArgs : InventoryEventArgs { private readonly GameInventoryItem oldItemState; diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMergedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMergedArgs.cs new file mode 100644 index 000000000..0f088f24b --- /dev/null +++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMergedArgs.cs @@ -0,0 +1,26 @@ +namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes; + +/// +/// Represents the data associated with an item being merged from two stacks into one. +/// +public sealed class InventoryItemMergedArgs : InventoryComplexEventArgs +{ + /// + /// Initializes a new instance of the class. + /// + /// The item at before slot. + /// The item at after slot. + internal InventoryItemMergedArgs(InventoryEventArgs sourceEvent, InventoryEventArgs targetEvent) + : base(GameInventoryEvent.Merged, sourceEvent, targetEvent) + { + } + + /// + 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(); +} diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs index b6f490a2c..ac33acd0d 100644 --- a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs +++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs @@ -3,7 +3,7 @@ /// /// Represents the data associated with an item being moved from one inventory and added to another. /// -public class InventoryItemMovedArgs : InventoryEventArgs +public sealed class InventoryItemMovedArgs : InventoryComplexEventArgs { /// /// Initializes a new instance of the class. @@ -11,43 +11,14 @@ public class InventoryItemMovedArgs : InventoryEventArgs /// The item at before slot. /// The item at after slot. internal InventoryItemMovedArgs(InventoryEventArgs sourceEvent, InventoryEventArgs targetEvent) - : base(GameInventoryEvent.Moved, targetEvent.Item) + : base(GameInventoryEvent.Moved, sourceEvent, targetEvent) { - this.SourceEvent = sourceEvent; - this.TargetEvent = targetEvent; } - /// - /// Gets the inventory this item was moved from. - /// - public GameInventoryType SourceInventory => this.SourceEvent.Item.ContainerType; - - /// - /// Gets the inventory this item was moved to. - /// - public GameInventoryType TargetInventory => this.Item.ContainerType; - - /// - /// Gets the slot this item was moved from. - /// - public uint SourceSlot => this.SourceEvent.Item.InventorySlot; - - /// - /// Gets the slot this item was moved to. - /// - public uint TargetSlot => this.Item.InventorySlot; - - /// - /// Gets the associated source event. - /// - internal InventoryEventArgs SourceEvent { get; } - - /// - /// Gets the associated target event. - /// - internal InventoryEventArgs TargetEvent { get; } + // /// + // public override string ToString() => $"{this.Type}({this.SourceEvent}, {this.TargetEvent})"; /// public override string ToString() => - $"<{this.Type}> (Item #{this.Item.ItemId}) from (slot {this.SourceSlot} in {this.SourceInventory}) to (slot {this.TargetSlot} in {this.TargetInventory})"; + $"{this.Type}(item({this.Item.ItemId}) from {this.SourceInventory}#{this.SourceSlot} to {this.TargetInventory}#{this.TargetSlot})"; } diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs index 41ca9d380..fe40c870b 100644 --- a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs +++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs @@ -3,7 +3,7 @@ /// /// Represents the data associated with an item being removed from an inventory. /// -public class InventoryItemRemovedArgs : InventoryEventArgs +public sealed class InventoryItemRemovedArgs : InventoryEventArgs { /// /// Initializes a new instance of the class. diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemSplitArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemSplitArgs.cs new file mode 100644 index 000000000..2a3d41c09 --- /dev/null +++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemSplitArgs.cs @@ -0,0 +1,26 @@ +namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes; + +/// +/// Represents the data associated with an item being split from one stack into two. +/// +public sealed class InventoryItemSplitArgs : InventoryComplexEventArgs +{ + /// + /// Initializes a new instance of the class. + /// + /// The item at before slot. + /// The item at after slot. + internal InventoryItemSplitArgs(InventoryEventArgs sourceEvent, InventoryEventArgs targetEvent) + : base(GameInventoryEvent.Split, sourceEvent, targetEvent) + { + } + + /// + 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(); +} diff --git a/Dalamud/Plugin/Services/IGameInventory.cs b/Dalamud/Plugin/Services/IGameInventory.cs index 058bcbd27..a6f4b4adf 100644 --- a/Dalamud/Plugin/Services/IGameInventory.cs +++ b/Dalamud/Plugin/Services/IGameInventory.cs @@ -33,27 +33,28 @@ public interface IGameInventory /// /// Event that is fired when the inventory has been changed, without trying to interpret two inventory slot changes /// as a move event as appropriate.
- /// In other words, does not fire in this event. + /// In other words, , , and + /// do not fire in this event. ///
public event InventoryChangelogDelegate InventoryChangedRaw; /// /// Event that is fired when an item is added to an inventory.
- /// If an accompanying item remove event happens, then will be called instead.
+ /// If this event is a part of multi-step event, then this event will not be called.
/// Use if you do not want such reinterpretation. ///
public event InventoryChangedDelegate ItemAdded; /// /// Event that is fired when an item is removed from an inventory.
- /// If an accompanying item add event happens, then will be called instead.
+ /// If this event is a part of multi-step event, then this event will not be called.
/// Use if you do not want such reinterpretation. ///
public event InventoryChangedDelegate ItemRemoved; /// /// Event that is fired when an items properties are changed.
- /// If an accompanying item change event happens, then will be called instead.
+ /// If this event is a part of multi-step event, then this event will not be called.
/// Use if you do not want such reinterpretation. ///
public event InventoryChangedDelegate ItemChanged; @@ -62,4 +63,14 @@ public interface IGameInventory /// Event that is fired when an item is moved from one inventory into another. ///
public event InventoryChangedDelegate ItemMoved; + + /// + /// Event that is fired when an item is split from one stack into two. + /// + public event InventoryChangedDelegate ItemSplit; + + /// + /// Event that is fired when an item is merged from two stacks into one. + /// + public event InventoryChangedDelegate ItemMerged; }