From 7c6f98dc9fe6e7fbe5b97e82dbd6c46becff2630 Mon Sep 17 00:00:00 2001 From: MidoriKami <9083275+MidoriKami@users.noreply.github.com> Date: Thu, 30 Nov 2023 22:18:33 -0800 Subject: [PATCH] Proposed API Surface --- Dalamud/Game/Inventory/GameInventory.cs | 178 +++++++++++++++--- Dalamud/Game/Inventory/GameInventoryEvent.cs | 2 +- Dalamud/Game/Inventory/GameInventoryItem.cs | 2 +- Dalamud/Game/Inventory/GameInventoryType.cs | 2 +- .../InventoryEventArgs.cs | 29 +++ .../InventoryItemAddedArgs.cs | 20 ++ .../InventoryItemChangedArgs.cs | 26 +++ .../InventoryItemMovedArgs.cs | 30 +++ .../InventoryItemRemovedArgs.cs | 20 ++ Dalamud/Plugin/Services/IGameInventory.cs | 95 +++------- 10 files changed, 311 insertions(+), 93 deletions(-) create mode 100644 Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs create mode 100644 Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemAddedArgs.cs create mode 100644 Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs create mode 100644 Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs create mode 100644 Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs index d370574d7..c2603f1bf 100644 --- a/Dalamud/Game/Inventory/GameInventory.cs +++ b/Dalamud/Game/Inventory/GameInventory.cs @@ -8,7 +8,7 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; -namespace Dalamud.Game.Inventory; +namespace Dalamud.Game.GameInventory; /// /// This class provides events for the players in-game inventory. @@ -19,7 +19,7 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory { private static readonly ModuleLog Log = new("GameInventory"); - private readonly List changelog = new(); + private readonly List changelog = new(); [ServiceManager.ServiceDependency] private readonly Framework framework = Service.Get(); @@ -37,7 +37,19 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory } /// - public event IGameInventory.InventoryChangeDelegate? InventoryChanged; + public event IGameInventory.InventoryChangelogDelegate? InventoryChanged; + + /// + public event IGameInventory.InventoryChangedDelegate? ItemAdded; + + /// + public event IGameInventory.InventoryChangedDelegate? ItemRemoved; + + /// + public event IGameInventory.InventoryChangedDelegate? ItemMoved; + + /// + public event IGameInventory.InventoryChangedDelegate? ItemChanged; /// public void Dispose() @@ -80,16 +92,39 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory { if (newItem.IsEmpty) continue; - this.changelog.Add(new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Added, default, newItem)); + + this.changelog.Add(new InventoryItemAddedArgs + { + Item = newItem, + Inventory = newItem.ContainerType, + Slot = newItem.InventorySlot, + }); } else { if (newItem.IsEmpty) - this.changelog.Add(new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Removed, oldItem, default)); + { + this.changelog.Add(new InventoryItemRemovedArgs + { + Item = oldItem, + Inventory = oldItem.ContainerType, + Slot = oldItem.InventorySlot, + }); + } else if (!oldItem.Equals(newItem)) - this.changelog.Add(new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Changed, oldItem, newItem)); + { + this.changelog.Add(new InventoryItemChangedArgs + { + OldItemState = oldItem, + Item = newItem, + Inventory = newItem.ContainerType, + Slot = newItem.InventorySlot, + }); + } else + { continue; + } } Log.Verbose($"[{this.changelog.Count - 1}] {this.changelog[^1]}"); @@ -126,48 +161,86 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory { foreach (ref var removed in removedSpan) { - if (added.Target.ItemId == removed.Source.ItemId) + if (added.Item.ItemId == removed.Item.ItemId) { Log.Verbose($"Move: reinterpreting {removed} + {added}"); - added = new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Moved, removed.Source, added.Target); + added = new InventoryItemMovedArgs + { + Item = removed.Item, + SourceInventory = removed.Item.ContainerType, + SourceSlot = removed.Item.InventorySlot, + TargetInventory = added.Item.ContainerType, + TargetSlot = added.Item.InventorySlot, + }; removed = default; break; } } } - // Resolve changelog for item moved, from 2 changeds + // Resolve changelog for item moved, from 2 changes for (var i = 0; i < changedSpan.Length; i++) { - if (span[i].IsEmpty) + if (span[i].Type is GameInventoryEvent.Empty) continue; ref var e1 = ref changedSpan[i]; for (var j = i + 1; j < changedSpan.Length; j++) { ref var e2 = ref changedSpan[j]; - if (e1.Target.ItemId == e2.Source.ItemId && e1.Source.ItemId == e2.Target.ItemId) + if (e1.Item.ItemId == e2.Item.ItemId && e1.Item.ItemId == e2.Item.ItemId) { - if (e1.Target.IsEmpty) + if (e1.Item.IsEmpty) { // e1 got moved to e2 Log.Verbose($"Move: reinterpreting {e1} + {e2}"); - e1 = new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Moved, e1.Source, e2.Target); + 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.Target.IsEmpty) + else if (e2.Item.IsEmpty) { // e2 got moved to e1 Log.Verbose($"Move: reinterpreting {e2} + {e1}"); - e1 = new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Moved, e2.Source, e1.Target); + 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}"); - (e1, e2) = (new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Moved, e1.Target, e2.Target), - new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Moved, e2.Target, e1.Target)); + 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 + { + Item = e1.Item, + SourceInventory = e2.Item.ContainerType, + SourceSlot = e2.Item.InventorySlot, + TargetInventory = e1.Item.ContainerType, + TargetSlot = e1.Item.InventorySlot, + }; + + (e1, e2) = (newEvent1, newEvent2); } } } @@ -177,7 +250,7 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory // We do not care about the order of items in the changelog anymore. for (var i = 0; i < span.Length;) { - if (span[i].IsEmpty) + if (span[i] is null || span[i].Type is GameInventoryEvent.Empty) { span[i] = span[^1]; span = span[..^1]; @@ -190,7 +263,31 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory // 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; + } + } + } } finally { @@ -219,18 +316,55 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven public GameInventoryPluginScoped() { this.gameInventoryService.InventoryChanged += this.OnInventoryChangedForward; + this.gameInventoryService.ItemAdded += this.OnInventoryItemAddedForward; + this.gameInventoryService.ItemRemoved += this.OnInventoryItemRemovedForward; + this.gameInventoryService.ItemMoved += this.OnInventoryItemMovedForward; + this.gameInventoryService.ItemChanged += this.OnInventoryItemChangedForward; } - + /// - public event IGameInventory.InventoryChangeDelegate? InventoryChanged; - + public event IGameInventory.InventoryChangelogDelegate? InventoryChanged; + + /// + public event IGameInventory.InventoryChangedDelegate? ItemAdded; + + /// + public event IGameInventory.InventoryChangedDelegate? ItemRemoved; + + /// + public event IGameInventory.InventoryChangedDelegate? ItemMoved; + + /// + public event IGameInventory.InventoryChangedDelegate? ItemChanged; + /// public void Dispose() { this.gameInventoryService.InventoryChanged -= this.OnInventoryChangedForward; + this.gameInventoryService.ItemAdded -= this.OnInventoryItemAddedForward; + this.gameInventoryService.ItemRemoved -= this.OnInventoryItemRemovedForward; + this.gameInventoryService.ItemMoved -= this.OnInventoryItemMovedForward; + this.gameInventoryService.ItemChanged -= this.OnInventoryItemChangedForward; + this.InventoryChanged = null; + this.ItemAdded = null; + this.ItemRemoved = null; + this.ItemMoved = null; + this.ItemChanged = null; } - private void OnInventoryChangedForward(ReadOnlySpan events) + private void OnInventoryChangedForward(ReadOnlySpan events) => this.InventoryChanged?.Invoke(events); + + private void OnInventoryItemAddedForward(GameInventoryEvent type, InventoryEventArgs data) + => this.ItemAdded?.Invoke(type, data); + + private void OnInventoryItemRemovedForward(GameInventoryEvent type, InventoryEventArgs data) + => this.ItemRemoved?.Invoke(type, data); + + private void OnInventoryItemMovedForward(GameInventoryEvent type, InventoryEventArgs data) + => this.ItemMoved?.Invoke(type, data); + + private void OnInventoryItemChangedForward(GameInventoryEvent type, InventoryEventArgs data) + => this.ItemChanged?.Invoke(type, data); } diff --git a/Dalamud/Game/Inventory/GameInventoryEvent.cs b/Dalamud/Game/Inventory/GameInventoryEvent.cs index c23d79f30..805306671 100644 --- a/Dalamud/Game/Inventory/GameInventoryEvent.cs +++ b/Dalamud/Game/Inventory/GameInventoryEvent.cs @@ -1,4 +1,4 @@ -namespace Dalamud.Game.Inventory; +namespace Dalamud.Game.GameInventory; /// /// Class representing a item's changelog state. diff --git a/Dalamud/Game/Inventory/GameInventoryItem.cs b/Dalamud/Game/Inventory/GameInventoryItem.cs index 9073073cb..794785e5c 100644 --- a/Dalamud/Game/Inventory/GameInventoryItem.cs +++ b/Dalamud/Game/Inventory/GameInventoryItem.cs @@ -4,7 +4,7 @@ using System.Runtime.InteropServices; using FFXIVClientStructs.FFXIV.Client.Game; -namespace Dalamud.Game.Inventory; +namespace Dalamud.Game.GameInventory; /// /// Dalamud wrapper around a ClientStructs InventoryItem. diff --git a/Dalamud/Game/Inventory/GameInventoryType.cs b/Dalamud/Game/Inventory/GameInventoryType.cs index c982fa80f..0eeeebe20 100644 --- a/Dalamud/Game/Inventory/GameInventoryType.cs +++ b/Dalamud/Game/Inventory/GameInventoryType.cs @@ -1,4 +1,4 @@ -namespace Dalamud.Game.Inventory; +namespace Dalamud.Game.GameInventory; /// /// Enum representing various player inventories. diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs new file mode 100644 index 000000000..a427dc840 --- /dev/null +++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs @@ -0,0 +1,29 @@ +namespace Dalamud.Game.GameInventory; + +/// +/// Abstract base class representing inventory changed events. +/// +public abstract class InventoryEventArgs +{ + /// + /// Gets the type of event for these args. + /// + public abstract GameInventoryEvent Type { get; } + + /// + /// Gets the item associated with this event. + /// This is a copy of the item data. + /// + required public GameInventoryItem Item { get; init; } + + /// + public override string ToString() => this.Type switch + { + 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})", + _ => $" {this.Item}", + }; +} diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemAddedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemAddedArgs.cs new file mode 100644 index 000000000..8d3e99823 --- /dev/null +++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemAddedArgs.cs @@ -0,0 +1,20 @@ +namespace Dalamud.Game.GameInventory; + +/// +/// Represents the data associated with an item being added to an inventory. +/// +public class InventoryItemAddedArgs : InventoryEventArgs +{ + /// + public override GameInventoryEvent Type => GameInventoryEvent.Added; + + /// + /// Gets the inventory this item was added to. + /// + required public GameInventoryType Inventory { get; init; } + + /// + /// Gets the slot this item was added to. + /// + required public uint Slot { get; init; } +} diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs new file mode 100644 index 000000000..1e2632722 --- /dev/null +++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs @@ -0,0 +1,26 @@ +namespace Dalamud.Game.GameInventory; + +/// +/// Represents the data associated with an items properties being changed. +/// This also includes an items stack count changing. +/// +public class InventoryItemChangedArgs : InventoryEventArgs +{ + /// + public override GameInventoryEvent Type => GameInventoryEvent.Changed; + + /// + /// Gets the inventory this item is in. + /// + required public GameInventoryType Inventory { get; init; } + + /// + /// Gets the inventory slot this item is in. + /// + required public uint Slot { get; init; } + + /// + /// Gets the state of the item from before it was changed. + /// + required public GameInventoryItem OldItemState { get; init; } +} diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs new file mode 100644 index 000000000..655f43445 --- /dev/null +++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs @@ -0,0 +1,30 @@ +namespace Dalamud.Game.GameInventory; + +/// +/// Represents the data associated with an item being moved from one inventory and added to another. +/// +public class InventoryItemMovedArgs : InventoryEventArgs +{ + /// + public override GameInventoryEvent Type => GameInventoryEvent.Moved; + + /// + /// Gets the inventory this item was moved from. + /// + required public GameInventoryType SourceInventory { get; init; } + + /// + /// Gets the inventory this item was moved to. + /// + required public GameInventoryType TargetInventory { get; init; } + + /// + /// Gets the slot this item was moved from. + /// + required public uint SourceSlot { get; init; } + + /// + /// Gets the slot this item was moved to. + /// + required public uint TargetSlot { get; init; } +} diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs new file mode 100644 index 000000000..2d4db2384 --- /dev/null +++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs @@ -0,0 +1,20 @@ +namespace Dalamud.Game.GameInventory; + +/// +/// Represents the data associated with an item being removed from an inventory. +/// +public class InventoryItemRemovedArgs : InventoryEventArgs +{ + /// + public override GameInventoryEvent Type => GameInventoryEvent.Removed; + + /// + /// Gets the inventory this item was removed from. + /// + required public GameInventoryType Inventory { get; init; } + + /// + /// Gets the slot this item was removed from. + /// + required public uint Slot { get; init; } +} diff --git a/Dalamud/Plugin/Services/IGameInventory.cs b/Dalamud/Plugin/Services/IGameInventory.cs index b2ffe64d0..40b4bd84f 100644 --- a/Dalamud/Plugin/Services/IGameInventory.cs +++ b/Dalamud/Plugin/Services/IGameInventory.cs @@ -1,4 +1,4 @@ -using Dalamud.Game.Inventory; +using Dalamud.Game.GameInventory; namespace Dalamud.Plugin.Services; @@ -9,82 +9,41 @@ public interface IGameInventory { /// /// Delegate function to be called when inventories have been changed. + /// This delegate sends the entire set of changes recorded. /// /// The events. - public delegate void InventoryChangeDelegate(ReadOnlySpan events); + public delegate void InventoryChangelogDelegate(ReadOnlySpan events); + + /// + /// Delegate function to be called for each change to inventories. + /// This delegate sends individual events for changes. + /// + /// The event try that triggered this message. + /// Data for the triggered event. + public delegate void InventoryChangedDelegate(GameInventoryEvent type, InventoryEventArgs data); /// /// Event that is fired when the inventory has been changed. /// - public event InventoryChangeDelegate InventoryChanged; - + public event InventoryChangelogDelegate InventoryChanged; + /// - /// Argument for . + /// Event that is fired when an item is added to an inventory. /// - public readonly struct GameInventoryEventArgs - { - /// - /// The type of the event. - /// - public readonly GameInventoryEvent Type; + public event InventoryChangedDelegate ItemAdded; - /// - /// The content of the item in the source inventory.
- /// Relevant if is , , or . - ///
- public readonly GameInventoryItem Source; - - /// - /// The content of the item in the target inventory
- /// Relevant if is , , or . - ///
- public readonly GameInventoryItem Target; + /// + /// Event that is fired when an item is removed from an inventory. + /// + public event InventoryChangedDelegate ItemRemoved; - /// - /// Initializes a new instance of the struct. - /// - /// The type of the event. - /// The source inventory item. - /// The target inventory item. - public GameInventoryEventArgs(GameInventoryEvent type, GameInventoryItem source, GameInventoryItem target) - { - this.Type = type; - this.Source = source; - this.Target = target; - } + /// + /// Event that is fired when an item is moved from one inventory into another. + /// + public event InventoryChangedDelegate ItemMoved; - /// - /// Gets a value indicating whether this instance of contains no information. - /// - public bool IsEmpty => this.Type == GameInventoryEvent.Empty; - - // TODO: are the following two aliases useful? - - /// - /// Gets the type of the source inventory.
- /// Relevant for and . - ///
- public GameInventoryType SourceType => this.Source.ContainerType; - - /// - /// Gets the type of the target inventory.
- /// Relevant for , , and - /// . - ///
- public GameInventoryType TargetType => this.Target.ContainerType; - - /// - public override string ToString() => this.Type switch - { - GameInventoryEvent.Empty => - $"<{this.Type}>", - GameInventoryEvent.Added => - $"<{this.Type}> ({this.Target})", - GameInventoryEvent.Removed => - $"<{this.Type}> ({this.Source})", - GameInventoryEvent.Changed or GameInventoryEvent.Moved => - $"<{this.Type}> ({this.Source}) to ({this.Target})", - _ => $" {this.Source} => {this.Target}", - }; - } + /// + /// Event that is fired when an items properties are changed. + /// + public event InventoryChangedDelegate ItemChanged; }