support merge/split events

This commit is contained in:
Soreepeong 2023-12-01 22:35:44 +09:00
parent f8dff15fe0
commit 6dd34ebda4
12 changed files with 234 additions and 60 deletions

View file

@ -28,6 +28,8 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
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();
@ -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}");
}
};
}
/// <inheritdoc/>
@ -65,6 +82,12 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
/// <inheritdoc/>
public event IGameInventory.InventoryChangedDelegate? ItemMoved;
/// <inheritdoc/>
public event IGameInventory.InventoryChangedDelegate? ItemSplit;
/// <inheritdoc/>
public event IGameInventory.InventoryChangedDelegate? ItemMerged;
/// <inheritdoc/>
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<InventoryEventArgs>(
@ -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<InventoryEventArgs>()
.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();
}
/// <summary>
@ -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;
}
/// <inheritdoc/>
@ -333,6 +393,12 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
/// <inheritdoc/>
public event IGameInventory.InventoryChangedDelegate? ItemMoved;
/// <inheritdoc/>
public event IGameInventory.InventoryChangedDelegate? ItemSplit;
/// <inheritdoc/>
public event IGameInventory.InventoryChangedDelegate? ItemMerged;
/// <inheritdoc/>
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<InventoryEventArgs> 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);
}

View file

@ -30,4 +30,14 @@ public enum GameInventoryEvent
/// 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,
}

View file

@ -158,6 +158,6 @@ public unsafe struct GameInventoryItem : IEquatable<GameInventoryItem>
/// <inheritdoc cref="object.ToString"/>
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})";
}

View file

@ -0,0 +1,54 @@
namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
/// <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})";
}

View file

@ -33,5 +33,5 @@ public abstract class InventoryEventArgs
public ref readonly GameInventoryItem Item => ref this.item;
/// <inheritdoc/>
public override string ToString() => $"<{this.Type}> ({this.Item})";
public override string ToString() => $"{this.Type}({this.Item})";
}

View file

@ -3,7 +3,7 @@
/// <summary>
/// Represents the data associated with an item being added to an inventory.
/// </summary>
public class InventoryItemAddedArgs : InventoryEventArgs
public sealed class InventoryItemAddedArgs : InventoryEventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="InventoryItemAddedArgs"/> class.

View file

@ -4,7 +4,7 @@
/// Represents the data associated with an items properties being changed.
/// This also includes an items stack count changing.
/// </summary>
public class InventoryItemChangedArgs : InventoryEventArgs
public sealed class InventoryItemChangedArgs : InventoryEventArgs
{
private readonly GameInventoryItem oldItemState;

View file

@ -0,0 +1,26 @@
namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
/// <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();
}

View file

@ -3,7 +3,7 @@
/// <summary>
/// Represents the data associated with an item being moved from one inventory and added to another.
/// </summary>
public class InventoryItemMovedArgs : InventoryEventArgs
public sealed class InventoryItemMovedArgs : InventoryComplexEventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="InventoryItemMovedArgs"/> class.
@ -11,43 +11,14 @@ public class InventoryItemMovedArgs : InventoryEventArgs
/// <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)
: base(GameInventoryEvent.Moved, sourceEvent, targetEvent)
{
this.SourceEvent = sourceEvent;
this.TargetEvent = targetEvent;
}
/// <summary>
/// Gets the inventory this item was moved from.
/// </summary>
public GameInventoryType SourceInventory => this.SourceEvent.Item.ContainerType;
/// <summary>
/// Gets the inventory this item was moved to.
/// </summary>
public GameInventoryType TargetInventory => this.Item.ContainerType;
/// <summary>
/// Gets the slot this item was moved from.
/// </summary>
public uint SourceSlot => this.SourceEvent.Item.InventorySlot;
/// <summary>
/// Gets the slot this item was moved to.
/// </summary>
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}({this.SourceEvent}, {this.TargetEvent})";
/// <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})";
$"{this.Type}(item({this.Item.ItemId}) from {this.SourceInventory}#{this.SourceSlot} to {this.TargetInventory}#{this.TargetSlot})";
}

View file

@ -3,7 +3,7 @@
/// <summary>
/// Represents the data associated with an item being removed from an inventory.
/// </summary>
public class InventoryItemRemovedArgs : InventoryEventArgs
public sealed class InventoryItemRemovedArgs : InventoryEventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="InventoryItemRemovedArgs"/> class.

View file

@ -0,0 +1,26 @@
namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
/// <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();
}

View file

@ -33,27 +33,28 @@ public interface IGameInventory
/// <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"/> does not fire in this event.
/// In other words, <see cref="GameInventoryEvent.Moved"/>, <see cref="GameInventoryEvent.Merged"/>, and
/// <see cref="GameInventoryEvent.Split"/> 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 an accompanying item remove event happens, then <see cref="ItemMoved"/> will be called instead.<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 an accompanying item add event happens, then <see cref="ItemMoved"/> will be called instead.<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 an accompanying item change event happens, then <see cref="ItemMoved"/> will be called instead.<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;
@ -62,4 +63,14 @@ public interface IGameInventory
/// 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;
}