diff --git a/AetherBags/Addons/AddonCategoryConfigurationWindow.cs b/AetherBags/Addons/AddonCategoryConfigurationWindow.cs new file mode 100644 index 0000000..3f0ac09 --- /dev/null +++ b/AetherBags/Addons/AddonCategoryConfigurationWindow.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using AetherBags.Configuration; +using AetherBags.Nodes.Configuration.Category; +using FFXIVClientStructs.FFXIV.Component.GUI; +using KamiToolKit; +using KamiToolKit.Classes; +using KamiToolKit.Nodes; +using KamiToolKit.Premade.Nodes; + +namespace AetherBags.Addons; + +public class AddonCategoryConfigurationWindow : NativeAddon +{ + private ModifyListNode? _selectionListNode; + private VerticalLineNode? _separatorLine; + private CategoryConfigurationNode? _configNode; + private TextNode? _nothingSelectedTextNode; + + private List _categoryWrappers = new(); + + protected override unsafe void OnSetup(AtkUnitBase* addon) + { + _categoryWrappers = CreateCategoryWrappers(); + + _selectionListNode = new ModifyListNode + { + Position = ContentStartPosition, + Size = new Vector2(250.0f, ContentSize.Y), + SelectionOptions = _categoryWrappers, + OnOptionChanged = OnOptionChanged, + AddNewEntry = OnAddNewCategory, + RemoveEntry = OnRemoveCategory, + }; + _selectionListNode.AttachNode(this); + + _separatorLine = new VerticalLineNode + { + Position = ContentStartPosition + new Vector2(250.0f + 8.0f, 0.0f), + Size = new Vector2(4.0f, ContentSize.Y), + }; + _separatorLine.AttachNode(this); + + _nothingSelectedTextNode = new TextNode + { + Position = ContentStartPosition + new Vector2(250.0f + 16.0f, 0.0f), + Size = ContentSize - new Vector2(250.0f + 16.0f, 0.0f), + AlignmentType = AlignmentType.Center, + TextFlags = TextFlags.WordWrap | TextFlags.MultiLine, + FontSize = 14, + LineSpacing = 22, + FontType = FontType.Axis, + String = "Please select a category on the left or add one.", + TextColor = ColorHelper.GetColor(1), + }; + _nothingSelectedTextNode.AttachNode(this); + + _configNode = new CategoryConfigurationNode + { + Position = ContentStartPosition + new Vector2(250.0f + 16.0f, 0.0f), + Size = ContentSize - new Vector2(250.0f + 16.0f, 0.0f), + IsVisible = false, + OnCategoryChanged = RefreshSelectionList, + }; + + _configNode.AttachNode(this); + } + + private List CreateCategoryWrappers() + { + return System.Config.Categories.UserCategories + .Select(categoryDefinition => new CategoryWrapper(categoryDefinition)) + .ToList(); + } + + private void OnOptionChanged(CategoryWrapper? newOption) + { + if (_configNode is null) return; + + _configNode.IsVisible = newOption is not null; + if (_nothingSelectedTextNode is not null) + _nothingSelectedTextNode.IsVisible = newOption is null; + + _configNode.ConfigurationOption = newOption; + } + + private void OnAddNewCategory(ModifyListNode listNode) + { + var newCategory = new UserCategoryDefinition + { + Name = $"New Category {System.Config.Categories.UserCategories.Count + 1}", + Order = System.Config.Categories.UserCategories.Count, + }; + + System.Config.Categories.UserCategories.Add(newCategory); + + var newWrapper = new CategoryWrapper(newCategory); + _categoryWrappers.Add(newWrapper); + listNode.AddOption(newWrapper); + } + + private void OnRemoveCategory(CategoryWrapper categoryWrapper) + { + if (categoryWrapper.CategoryDefinition is null) return; + + System.Config.Categories.UserCategories.Remove(categoryWrapper.CategoryDefinition); + _categoryWrappers.Remove(categoryWrapper); + } + + private void RefreshSelectionList() + { + _selectionListNode?.UpdateList(); + } +} \ No newline at end of file diff --git a/AetherBags/Addons/AddonConfigurationWindow.cs b/AetherBags/Addons/AddonConfigurationWindow.cs index 981d0d4..228b4ed 100644 --- a/AetherBags/Addons/AddonConfigurationWindow.cs +++ b/AetherBags/Addons/AddonConfigurationWindow.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; using AetherBags.Nodes.Configuration; +using AetherBags.Nodes.Configuration.Category; +using AetherBags.Nodes.Configuration.Currency; +using AetherBags.Nodes.Configuration.General; using FFXIVClientStructs.FFXIV.Component.GUI; using KamiToolKit; using KamiToolKit.Nodes; diff --git a/AetherBags/Addons/AddonInventoryWindow.cs b/AetherBags/Addons/AddonInventoryWindow.cs index f732071..2cf5597 100644 --- a/AetherBags/Addons/AddonInventoryWindow.cs +++ b/AetherBags/Addons/AddonInventoryWindow.cs @@ -122,12 +122,14 @@ public class AddonInventoryWindow : NativeAddon public void ManualInventoryRefresh() { + if (!Services.ClientState.IsLoggedIn) return; InventoryState.RefreshFromGame(); RefreshCategoriesCore(true); } public void ManualCurrencyRefresh() { + if (!Services.ClientState.IsLoggedIn) return; _footerNode.RefreshCurrencies(); } diff --git a/AetherBags/Addons/CategoryWrapper.cs b/AetherBags/Addons/CategoryWrapper.cs new file mode 100644 index 0000000..68b7f85 --- /dev/null +++ b/AetherBags/Addons/CategoryWrapper.cs @@ -0,0 +1,33 @@ +using AetherBags.Configuration; +using KamiToolKit.Premade; + +namespace AetherBags.Addons; + +public class CategoryWrapper(UserCategoryDefinition categoryDefinition) : IInfoNodeData +{ + public UserCategoryDefinition? CategoryDefinition { get; } = categoryDefinition; + + public string GetLabel() { + + return CategoryDefinition!.Name; + } + + public string GetSubLabel() { + return CategoryDefinition!.Enabled ? "Enabled" : "Disabled"; + } + + public uint? GetId() => null; + + public uint? GetIconId() { + return 0; + } + + public string? GetTexturePath() + => null; + + public int Compare(IInfoNodeData other, string sortingMode) { + if (other is not CategoryWrapper otherWrapper) return 0; + + return CategoryDefinition!.Order.CompareTo(otherWrapper.CategoryDefinition!.Order); + } +} \ No newline at end of file diff --git a/AetherBags/Configuration/CategorySettings.cs b/AetherBags/Configuration/CategorySettings.cs index 3aa5d18..1ab344b 100644 --- a/AetherBags/Configuration/CategorySettings.cs +++ b/AetherBags/Configuration/CategorySettings.cs @@ -16,6 +16,7 @@ public class CategorySettings public class UserCategoryDefinition { + public bool Enabled { get; set; } = true; public string Id { get; set; } = Guid.NewGuid().ToString("N"); public string Name { get; set; } = "New Category"; public string Description { get; set; } = string.Empty; diff --git a/AetherBags/Currency/CurrencyState.cs b/AetherBags/Currency/CurrencyState.cs index c0a852a..a357bab 100644 --- a/AetherBags/Currency/CurrencyState.cs +++ b/AetherBags/Currency/CurrencyState.cs @@ -71,12 +71,12 @@ public static unsafe class CurrencyState return currencyInfoList; } - private static uint? GetLimitedTomestoneItemIdCached() + private static uint? GetLimitedTomestoneItemIdCached() { if (_cachedLimitedTomestoneItemId.HasValue) return _cachedLimitedTomestoneItemId.Value; - uint? itemId = Services.DataManager.GetExcelSheet() + uint? itemId = Services.DataManager.GetExcelSheet() .FirstOrDefault(t => t.Tomestones.RowId == 3) .Item.RowId; @@ -84,7 +84,7 @@ public static unsafe class CurrencyState return itemId; } - private static uint? GetNonLimitedTomestoneItemIdCached() + private static uint? GetNonLimitedTomestoneItemIdCached() { if (_cachedNonLimitedTomestoneItemId.HasValue) return _cachedNonLimitedTomestoneItemId.Value; diff --git a/AetherBags/Extensions/InventoryTypeExtensions.cs b/AetherBags/Extensions/InventoryTypeExtensions.cs index 628462b..4c28d8f 100644 --- a/AetherBags/Extensions/InventoryTypeExtensions.cs +++ b/AetherBags/Extensions/InventoryTypeExtensions.cs @@ -158,7 +158,7 @@ public static unsafe class InventoryTypeExtensions { _ when inventoryType.IsMainInventory => InventoryType.Inventory1, _ when inventoryType.IsSaddleBag => inventoryType is InventoryType. SaddleBag1 or InventoryType.SaddleBag2 - ? InventoryType. SaddleBag1 + ? InventoryType. SaddleBag1 : InventoryType.PremiumSaddleBag1, _ => inventoryType, }; diff --git a/AetherBags/Hooks/InventoryHook.cs b/AetherBags/Hooks/InventoryHook.cs index 1ed98de..5e821fc 100644 --- a/AetherBags/Hooks/InventoryHook.cs +++ b/AetherBags/Hooks/InventoryHook.cs @@ -17,14 +17,14 @@ public sealed unsafe class InventoryHooks : IDisposable ushort dstSlot, bool unk); - private readonly Hook? _moveItemSlotHook; + private readonly Hook? _moveItemSlotHook; public InventoryHooks() { try { _moveItemSlotHook = Services.GameInteropProvider.HookFromSignature( - "E8 ?? ?? ?? ?? 48 8B 03 66 FF C5", + "E8 ?? ?? ?? ?? 48 8B 03 66 FF C5", MoveItemSlotDetour); _moveItemSlotHook.Enable(); @@ -36,8 +36,7 @@ public sealed unsafe class InventoryHooks : IDisposable } } - private int MoveItemSlotDetour( - InventoryManager* manager, + private int MoveItemSlotDetour(InventoryManager* manager, InventoryType srcType, ushort srcSlot, InventoryType dstType, @@ -47,8 +46,7 @@ public sealed unsafe class InventoryHooks : IDisposable InventoryItem* sourceItem = InventoryManager.Instance()->GetInventorySlot(srcType, srcSlot); InventoryItem* destItem = InventoryManager.Instance()->GetInventorySlot(dstType, dstSlot); - Services.Logger.Info( - $"[MoveItemSlot] Moving {srcType}@{srcSlot} ID:{sourceItem->ItemId} -> {dstType}@{dstSlot} ID:{destItem->ItemId} Unk: {unk}"); + Services.Logger.Debug($"[MoveItemSlot] Moving {srcType}@{srcSlot} ID:{sourceItem->ItemId} -> {dstType}@{dstSlot} ID:{destItem->ItemId} Unk: {unk}"); return _moveItemSlotHook!.Original(manager, srcType, srcSlot, dstType, dstSlot, unk); } diff --git a/AetherBags/Inventory/CategoryBucketManager.cs b/AetherBags/Inventory/CategoryBucketManager.cs index f87ead0..6d67130 100644 --- a/AetherBags/Inventory/CategoryBucketManager.cs +++ b/AetherBags/Inventory/CategoryBucketManager.cs @@ -56,7 +56,7 @@ public static class CategoryBucketManager UserCategoryDefinition category = sortedScratch[i]; uint bucketKey = MakeUserCategoryKey(category.Order); - if (!bucketsByKey.TryGetValue(bucketKey, out CategoryBucket? bucket)) + if (!bucketsByKey.TryGetValue(bucketKey, out CategoryBucket? bucket)) { bucket = new CategoryBucket { diff --git a/AetherBags/Inventory/InventoryScanner.cs b/AetherBags/Inventory/InventoryScanner.cs index 0bb921b..00e9d2b 100644 --- a/AetherBags/Inventory/InventoryScanner.cs +++ b/AetherBags/Inventory/InventoryScanner.cs @@ -85,7 +85,7 @@ public static unsafe class InventoryScanner bool isHq = (item.Flags & InventoryItem.ItemFlags.HighQuality) != 0; ulong key = stackMode == InventoryStackMode.AggregateByItemId - ? MakeAggregatedItemKey(id, isHq) + ? MakeAggregatedItemKey(id, isHq) : MakeNaturalSlotKey(inventoryType, slot); Services.Logger.DebugOnly($"Slot {inventoryType}[{slot}] ItemId={id} Qty={quantity} Key=0x{key: X16}"); @@ -120,7 +120,7 @@ public static unsafe class InventoryScanner ulong key = kvp.Key; AggregatedItem agg = kvp.Value; - if (!itemInfoByKey.TryGetValue(key, out ItemInfo? info)) + if (!itemInfoByKey.TryGetValue(key, out ItemInfo? info)) { info = new ItemInfo { diff --git a/AetherBags/Inventory/InventoryState.cs b/AetherBags/Inventory/InventoryState.cs index b5599fa..d4e279a 100644 --- a/AetherBags/Inventory/InventoryState.cs +++ b/AetherBags/Inventory/InventoryState.cs @@ -38,7 +38,7 @@ public static unsafe class InventoryState InventoryStackMode stackMode = config.General.StackMode; bool userCategoriesEnabled = config.Categories.UserCategoriesEnabled; bool gameCategoriesEnabled = config.Categories.GameCategoriesEnabled; - List userCategories = config.Categories.UserCategories; + List userCategories = config.Categories.UserCategories.Where(category => category.Enabled).ToList(); Services.Logger.DebugOnly($"RefreshFromGame StackMode={stackMode}"); diff --git a/AetherBags/Nodes/Configuration/Category/CategoryConfigurationNode.cs b/AetherBags/Nodes/Configuration/Category/CategoryConfigurationNode.cs new file mode 100644 index 0000000..8a8eb5a --- /dev/null +++ b/AetherBags/Nodes/Configuration/Category/CategoryConfigurationNode.cs @@ -0,0 +1,75 @@ +using System; +using System.Numerics; +using AetherBags.Addons; +using AetherBags.Configuration; +using KamiToolKit.Nodes; +using KamiToolKit.Premade.Nodes; + +namespace AetherBags.Nodes.Configuration.Category; + +public class CategoryConfigurationNode : ConfigNode +{ + private readonly ScrollingAreaNode _categoryList; + private CategoryDefinitionConfigurationNode? _activeNode; + + public Action? OnCategoryChanged { get; set; } + + public CategoryConfigurationNode() + { + _categoryList = new ScrollingAreaNode + { + ContentHeight = 100.0f, + AutoHideScrollBar = true, + }; + _categoryList.ContentNode.FitContents = true; + _categoryList.AttachNode(this); + } + + protected override void OptionChanged(CategoryWrapper? option) + { + if (option?.CategoryDefinition is null) + { + _categoryList.IsVisible = false; + return; + } + + _categoryList.IsVisible = true; + + if (_activeNode is null) + { + _activeNode = new CategoryDefinitionConfigurationNode(option.CategoryDefinition) + { + Size = _categoryList.ContentNode.Size, + OnLayoutChanged = UpdateScrollHeight, + OnCategoryPropertyChanged = OnCategoryChanged, + }; + _categoryList.ContentNode.AddNode(_activeNode); + } + else + { + _activeNode.SetCategory(option.CategoryDefinition); + } + + UpdateScrollHeight(); + } + + private void UpdateScrollHeight() + { + _categoryList.ContentNode.RecalculateLayout(); + _categoryList.ContentHeight = _categoryList.ContentNode.Height; + } + + protected override void OnSizeChanged() + { + base.OnSizeChanged(); + _categoryList.Size = Size; + _categoryList.ContentNode.Width = Width; + + foreach (var node in _categoryList.ContentNode.GetNodes()) + { + node.Width = Width; + } + + UpdateScrollHeight(); + } +} \ No newline at end of file diff --git a/AetherBags/Nodes/Configuration/Category/CategoryDefinitionConfigurationNode.cs b/AetherBags/Nodes/Configuration/Category/CategoryDefinitionConfigurationNode.cs new file mode 100644 index 0000000..d5d261f --- /dev/null +++ b/AetherBags/Nodes/Configuration/Category/CategoryDefinitionConfigurationNode.cs @@ -0,0 +1,467 @@ +using System; +using System.Numerics; +using AetherBags.Configuration; +using AetherBags.Nodes.Color; +using Dalamud.Utility; +using KamiToolKit.Classes; +using KamiToolKit.Nodes; +using Lumina.Excel; +using Lumina.Excel.Sheets; +using Action = System.Action; + +namespace AetherBags.Nodes.Configuration.Category; + +public sealed class CategoryDefinitionConfigurationNode : VerticalListNode +{ + private readonly CheckboxNode _enabledCheckbox; + private readonly TextInputNode _nameInputNode; + private readonly TextInputNode _descriptionInputNode; + private readonly ColorInputRow _colorInputNode; + private readonly NumericInputNode _priorityInputNode; + private readonly NumericInputNode _orderInputNode; + + private readonly CheckboxNode _levelEnabledCheckbox; + private readonly NumericInputNode _levelMinNode; + private readonly NumericInputNode _levelMaxNode; + + private readonly CheckboxNode _itemLevelEnabledCheckbox; + private readonly NumericInputNode _itemLevelMinNode; + private readonly NumericInputNode _itemLevelMaxNode; + + private readonly CheckboxNode _vendorPriceEnabledCheckbox; + private readonly NumericInputNode _vendorPriceMinNode; + private readonly NumericInputNode _vendorPriceMaxNode; + + private readonly StateFilterRowNode _untradableFilter; + private readonly StateFilterRowNode _uniqueFilter; + private readonly StateFilterRowNode _collectableFilter; + private readonly StateFilterRowNode _dyeableFilter; + private readonly StateFilterRowNode _repairableFilter; + + private readonly UintListEditorNode _allowedItemIdsEditor; + private readonly StringListEditorNode _allowedNamePatternsEditor; + private readonly UintListEditorNode _allowedUiCategoriesEditor; + private readonly RarityEditorNode _allowedRaritiesEditor; + + private bool _isInitialized; + + private static ExcelSheet? _sItemSheet; + private static ExcelSheet? _sUICategorySheet; + + public Action? OnLayoutChanged { get; set; } + + public Action? OnCategoryPropertyChanged { get; set; } + + private UserCategoryDefinition CategoryDefinition { get; set; } + + public CategoryDefinitionConfigurationNode(UserCategoryDefinition categoryDefinition) + { + CategoryDefinition = categoryDefinition; + + _sItemSheet ??= Services.DataManager.GetExcelSheet(); + _sUICategorySheet ??= Services.DataManager.GetExcelSheet(); + + FitContents = true; + ItemSpacing = 4.0f; + + AddNode(CreateSectionHeader("Basic Settings")); + + _enabledCheckbox = new CheckboxNode + { + Size = new Vector2(200, 20), + String = "Enabled", + IsChecked = CategoryDefinition.Enabled, + OnClick = isChecked => + { + CategoryDefinition.Enabled = isChecked; + NotifyChanged(); + NotifyCategoryPropertyChanged(); + }, + }; + AddNode(_enabledCheckbox); + + AddNode(new LabelTextNode { Size = new Vector2(80, 20), String = "Name:" }); + _nameInputNode = new TextInputNode + { + Size = new Vector2(250, 28), + String = CategoryDefinition.Name, + PlaceholderString = CategoryDefinition.Name.IsNullOrWhitespace() ? "Category Name" : "", + OnInputReceived = name => + { + CategoryDefinition.Name = name.ExtractText(); + NotifyChanged(); + NotifyCategoryPropertyChanged(); + }, + }; + AddNode(_nameInputNode); + + AddNode(new LabelTextNode { Size = new Vector2(80, 20), String = "Description:" }); + _descriptionInputNode = new TextInputNode + { + Size = new Vector2(250, 28), + String = CategoryDefinition.Description, + PlaceholderString = CategoryDefinition.Description.IsNullOrWhitespace() ? "Optional description" : "", + OnInputReceived = desc => + { + CategoryDefinition.Description = desc.ExtractText(); + NotifyChanged(); + }, + }; + AddNode(_descriptionInputNode); + + _colorInputNode = new ColorInputRow + { + Label = "Color", + Size = new Vector2(300, 28), + CurrentColor = CategoryDefinition.Color, + DefaultColor = new UserCategoryDefinition().Color, + OnColorConfirmed = color => + { + CategoryDefinition.Color = color; + NotifyChanged(); + }, + OnColorCanceled = color => + { + CategoryDefinition.Color = color; + NotifyChanged(); + }, + }; + AddNode(_colorInputNode); + + AddNode(new LabelTextNode { Size = new Vector2(80, 20), String = "Priority:" }); + _priorityInputNode = new NumericInputNode + { + Size = new Vector2(120, 28), + Min = 0, + Max = 1000, + Step = 1, + Value = CategoryDefinition.Priority, + OnValueUpdate = val => + { + CategoryDefinition.Priority = val; + NotifyChanged(); + }, + }; + AddNode(_priorityInputNode); + + AddNode(new LabelTextNode { Size = new Vector2(80, 20), String = "Order:" }); + _orderInputNode = new NumericInputNode + { + Size = new Vector2(120, 28), + Min = 0, + Max = 9999, + Step = 1, + Value = CategoryDefinition.Order, + OnValueUpdate = val => + { + CategoryDefinition.Order = val; + NotifyChanged(); + NotifyCategoryPropertyChanged(); + }, + }; + AddNode(_orderInputNode); + + AddNode(CreateSectionHeader("Range Filters")); + + (_levelEnabledCheckbox, _levelMinNode, _levelMaxNode) = CreateRangeFilter( + "Level", + CategoryDefinition.Rules.Level, + 0, 200, + (enabled, min, max) => + { + CategoryDefinition.Rules.Level.Enabled = enabled; + CategoryDefinition.Rules.Level.Min = min; + CategoryDefinition.Rules.Level.Max = max; + NotifyChanged(); + } + ); + + (_itemLevelEnabledCheckbox, _itemLevelMinNode, _itemLevelMaxNode) = CreateRangeFilter( + "Item Level", + CategoryDefinition.Rules.ItemLevel, + 0, 2000, + (enabled, min, max) => + { + CategoryDefinition.Rules.ItemLevel.Enabled = enabled; + CategoryDefinition.Rules.ItemLevel.Min = min; + CategoryDefinition.Rules.ItemLevel.Max = max; + NotifyChanged(); + } + ); + + (_vendorPriceEnabledCheckbox, _vendorPriceMinNode, _vendorPriceMaxNode) = CreateRangeFilterUint( + "Vendor Price", + CategoryDefinition.Rules.VendorPrice, + 0, 9_999_999 + ); + + AddNode(CreateSectionHeader("State Filters")); + + _untradableFilter = new StateFilterRowNode("Untradable", CategoryDefinition.Rules.Untradable, NotifyChanged); + AddNode(_untradableFilter); + + _uniqueFilter = new StateFilterRowNode("Unique", CategoryDefinition.Rules.Unique, NotifyChanged); + AddNode(_uniqueFilter); + + _collectableFilter = new StateFilterRowNode("Collectable", CategoryDefinition.Rules.Collectable, NotifyChanged); + AddNode(_collectableFilter); + + _dyeableFilter = new StateFilterRowNode("Dyeable", CategoryDefinition.Rules.Dyeable, NotifyChanged); + AddNode(_dyeableFilter); + + _repairableFilter = new StateFilterRowNode("Repairable", CategoryDefinition.Rules.Repairable, NotifyChanged); + AddNode(_repairableFilter); + + AddNode(CreateSectionHeader("List Filters")); + + _allowedItemIdsEditor = new UintListEditorNode( + "Allowed Item IDs:", + CategoryDefinition.Rules.AllowedItemIds, + OnListChanged, + ResolveItemName + ); + AddNode(_allowedItemIdsEditor); + + _allowedNamePatternsEditor = new StringListEditorNode( + "Name Patterns (Regex):", + CategoryDefinition.Rules.AllowedItemNamePatterns, + OnListChanged + ); + AddNode(_allowedNamePatternsEditor); + + _allowedUiCategoriesEditor = new UintListEditorNode( + "UI Categories:", + CategoryDefinition.Rules.AllowedUiCategoryIds, + OnListChanged, + ResolveUiCategoryName + ); + AddNode(_allowedUiCategoriesEditor); + + _allowedRaritiesEditor = new RarityEditorNode( + CategoryDefinition.Rules.AllowedRarities, + NotifyChanged + ); + AddNode(_allowedRaritiesEditor); + + _isInitialized = true; + } + + private void OnListChanged() + { + NotifyChanged(); + RecalculateLayout(); + OnLayoutChanged?.Invoke(); + } + + private static string ResolveItemName(uint itemId) + { + try + { + var item = _sItemSheet?.GetRow(itemId); + return item?.Name.ToString() ?? "Unknown"; + } + catch + { + return "Unknown"; + } + } + + private static string ResolveUiCategoryName(uint categoryId) + { + try + { + var category = _sUICategorySheet?.GetRow(categoryId); + return category?.Name.ToString() ?? "Unknown"; + } + catch + { + return "Unknown"; + } + } + + private static void NotifyChanged() + { + System.AddonInventoryWindow?.ManualInventoryRefresh(); + } + + private void NotifyCategoryPropertyChanged() + { + OnCategoryPropertyChanged?.Invoke(); + } + + private static LabelTextNode CreateSectionHeader(string text) + { + return new LabelTextNode + { + Size = new Vector2(300, 22), + String = text, + TextColor = ColorHelper.GetColor(2), + TextOutlineColor = ColorHelper.GetColor(0), + }; + } + + private (CheckboxNode enabled, NumericInputNode min, NumericInputNode max) CreateRangeFilter( + string label, + RangeFilter filter, + int minBound, + int maxBound, + Action onUpdate) + { + var enabledCheckbox = new CheckboxNode + { + Size = new Vector2(200, 20), + String = $"{label} Filter", + IsChecked = filter.Enabled, + }; + AddNode(enabledCheckbox); + + var minNode = new NumericInputNode + { + Size = new Vector2(120, 28), + Min = minBound, + Max = maxBound, + Value = filter.Min, + IsEnabled = filter.Enabled, + }; + + var maxNode = new NumericInputNode + { + Size = new Vector2(120, 28), + Min = minBound, + Max = maxBound, + Value = filter.Max, + IsEnabled = filter.Enabled, + }; + + var rangeRow = new HorizontalListNode { Size = new Vector2(300, 28), ItemSpacing = 8.0f }; + rangeRow.AddNode(new LabelTextNode { Size = new Vector2(30, 28), String = "Min:" }); + rangeRow.AddNode(minNode); + rangeRow.AddNode(new LabelTextNode { Size = new Vector2(30, 28), String = "Max:" }); + rangeRow.AddNode(maxNode); + AddNode(rangeRow); + + enabledCheckbox.OnClick = isChecked => + { + minNode.IsEnabled = isChecked; + maxNode.IsEnabled = isChecked; + onUpdate(isChecked, minNode.Value, maxNode.Value); + }; + + minNode.OnValueUpdate = val => onUpdate(enabledCheckbox.IsChecked, val, maxNode.Value); + maxNode.OnValueUpdate = val => onUpdate(enabledCheckbox.IsChecked, minNode.Value, val); + + return (enabledCheckbox, minNode, maxNode); + } + + private (CheckboxNode enabled, NumericInputNode min, NumericInputNode max) CreateRangeFilterUint( + string label, + RangeFilter filter, + int minBound, + int maxBound) + { + var enabledCheckbox = new CheckboxNode + { + Size = new Vector2(200, 20), + String = $"{label} Filter", + IsChecked = filter.Enabled, + }; + AddNode(enabledCheckbox); + + var minNode = new NumericInputNode + { + Size = new Vector2(120, 28), + Min = minBound, + Max = maxBound, + Value = (int)filter.Min, + IsEnabled = filter.Enabled, + }; + + var maxNode = new NumericInputNode + { + Size = new Vector2(120, 28), + Min = minBound, + Max = maxBound, + Value = (int)Math.Min(filter.Max, maxBound), + IsEnabled = filter.Enabled, + }; + + var rangeRow = new HorizontalListNode { Size = new Vector2(300, 28), ItemSpacing = 8.0f }; + rangeRow.AddNode(new LabelTextNode { Size = new Vector2(30, 28), String = "Min:" }); + rangeRow.AddNode(minNode); + rangeRow.AddNode(new LabelTextNode { Size = new Vector2(30, 28), String = "Max:" }); + rangeRow.AddNode(maxNode); + AddNode(rangeRow); + + enabledCheckbox.OnClick = isChecked => + { + minNode.IsEnabled = isChecked; + maxNode.IsEnabled = isChecked; + CategoryDefinition.Rules.VendorPrice.Enabled = isChecked; + NotifyChanged(); + }; + + minNode.OnValueUpdate = value => + { + CategoryDefinition.Rules.VendorPrice.Min = (uint)value; + NotifyChanged(); + }; + + maxNode.OnValueUpdate = value => + { + CategoryDefinition.Rules.VendorPrice.Max = (uint)value; + NotifyChanged(); + }; + + return (enabledCheckbox, minNode, maxNode); + } + + public void SetCategory(UserCategoryDefinition newCategory) + { + CategoryDefinition = newCategory; + RefreshValues(); + } + + private void RefreshValues() + { + if (! _isInitialized) return; + + _enabledCheckbox.IsChecked = CategoryDefinition.Enabled; + _colorInputNode.CurrentColor = CategoryDefinition.Color; + _nameInputNode.String = CategoryDefinition.Name; + _descriptionInputNode.String = CategoryDefinition.Description; + _priorityInputNode.Value = CategoryDefinition.Priority; + _orderInputNode.Value = CategoryDefinition.Order; + + RefreshRangeFilter(_levelEnabledCheckbox, _levelMinNode, _levelMaxNode, CategoryDefinition.Rules.Level); + RefreshRangeFilter(_itemLevelEnabledCheckbox, _itemLevelMinNode, _itemLevelMaxNode, CategoryDefinition.Rules.ItemLevel); + + _vendorPriceEnabledCheckbox.IsChecked = CategoryDefinition.Rules.VendorPrice.Enabled; + _vendorPriceMinNode.Value = (int)CategoryDefinition.Rules.VendorPrice.Min; + _vendorPriceMaxNode.Value = (int)Math.Min(CategoryDefinition.Rules.VendorPrice.Max, int.MaxValue); + _vendorPriceMinNode.IsEnabled = CategoryDefinition.Rules.VendorPrice.Enabled; + _vendorPriceMaxNode.IsEnabled = CategoryDefinition.Rules.VendorPrice.Enabled; + + _untradableFilter.SetState(CategoryDefinition.Rules.Untradable); + _uniqueFilter.SetState(CategoryDefinition.Rules.Unique); + _collectableFilter.SetState(CategoryDefinition.Rules.Collectable); + _dyeableFilter.SetState(CategoryDefinition.Rules.Dyeable); + _repairableFilter.SetState(CategoryDefinition.Rules.Repairable); + + _allowedItemIdsEditor.SetList(CategoryDefinition.Rules.AllowedItemIds); + _allowedNamePatternsEditor.SetList(CategoryDefinition.Rules.AllowedItemNamePatterns); + _allowedUiCategoriesEditor.SetList(CategoryDefinition.Rules.AllowedUiCategoryIds); + _allowedRaritiesEditor.SetList(CategoryDefinition.Rules.AllowedRarities); + + RecalculateLayout(); + OnLayoutChanged?.Invoke(); + } + + private static void RefreshRangeFilter(CheckboxNode enabled, NumericInputNode min, NumericInputNode max, RangeFilter filter) + { + enabled.IsChecked = filter.Enabled; + min.Value = filter.Min; + max.Value = filter.Max; + min.IsEnabled = filter.Enabled; + max.IsEnabled = filter.Enabled; + } +} \ No newline at end of file diff --git a/AetherBags/Nodes/Configuration/Category/CategoryScrollingAreaNode.cs b/AetherBags/Nodes/Configuration/Category/CategoryScrollingAreaNode.cs new file mode 100644 index 0000000..7d489c9 --- /dev/null +++ b/AetherBags/Nodes/Configuration/Category/CategoryScrollingAreaNode.cs @@ -0,0 +1,34 @@ +using System.Numerics; +using AetherBags.Addons; +using KamiToolKit.Nodes; + +namespace AetherBags.Nodes.Configuration.Category; + +public class CategoryScrollingAreaNode : ScrollingAreaNode +{ + private AddonCategoryConfigurationWindow? _categoryConfigurationAddon; + private readonly TextButtonNode _categoryConfigurationButtonNode; + + public CategoryScrollingAreaNode() + { + InitializeCategoryAddon(); + + _categoryConfigurationButtonNode = new TextButtonNode + { + Size = new Vector2(300, 28), + String = "Configure Categories", + OnClick = () => _categoryConfigurationAddon?.Toggle(), + }; + _categoryConfigurationButtonNode.AttachNode(this); + } + + private void InitializeCategoryAddon() { + if (_categoryConfigurationAddon is not null) return; + + _categoryConfigurationAddon = new AddonCategoryConfigurationWindow { + Size = new Vector2(700.0f, 500.0f), + InternalName = "AetherBags_CategoryConfig", + Title = "Category Configuration Window", + }; + } +} \ No newline at end of file diff --git a/AetherBags/Nodes/Configuration/Category/RarityEditorNode.cs b/AetherBags/Nodes/Configuration/Category/RarityEditorNode.cs new file mode 100644 index 0000000..3182704 --- /dev/null +++ b/AetherBags/Nodes/Configuration/Category/RarityEditorNode.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using KamiToolKit.Classes; +using KamiToolKit.Nodes; + +namespace AetherBags.Nodes.Configuration.Category; + +public sealed class RarityEditorNode : VerticalListNode +{ + private static readonly string[] RarityNames = { "Common (White)", "Uncommon (Green)", "Rare (Blue)", "Relic (Purple)", "Aetherial (Pink)" }; + + private List _list; + private readonly List _checkboxes = new(); + private readonly Action? _onChanged; + + public RarityEditorNode(List list, Action? onChanged = null) + { + _list = list; + _onChanged = onChanged; + + FitContents = true; + ItemSpacing = 2.0f; + + var headerLabel = new LabelTextNode + { + Size = new Vector2(280, 18), + String = "Allowed Rarities:", + TextColor = ColorHelper.GetColor(8), + }; + AddNode(headerLabel); + + for (int i = 0; i < RarityNames.Length; i++) + { + var rarity = i; + var checkbox = new CheckboxNode + { + Size = new Vector2(200, 20), + String = RarityNames[i], + IsChecked = _list.Contains(i), + OnClick = isChecked => + { + if (isChecked && !_list.Contains(rarity)) + { + _list.Add(rarity); + _list.Sort(); + } + else if (!isChecked && _list.Contains(rarity)) + { + _list.Remove(rarity); + } + _onChanged?.Invoke(); + }, + }; + _checkboxes.Add(checkbox); + AddNode(checkbox); + } + } + + public void SetList(List newList) + { + _list = newList; + Refresh(); + } + + public void Refresh() + { + for (int i = 0; i < _checkboxes.Count; i++) + { + _checkboxes[i].IsChecked = _list.Contains(i); + } + } +} \ No newline at end of file diff --git a/AetherBags/Nodes/Configuration/Category/StateFilterRowNode.cs b/AetherBags/Nodes/Configuration/Category/StateFilterRowNode.cs new file mode 100644 index 0000000..f96a972 --- /dev/null +++ b/AetherBags/Nodes/Configuration/Category/StateFilterRowNode.cs @@ -0,0 +1,54 @@ +using System; +using System.Numerics; +using AetherBags.Configuration; +using KamiToolKit.Classes; +using KamiToolKit.Nodes; + +namespace AetherBags.Nodes.Configuration.Category; + +public sealed class StateFilterRowNode : HorizontalListNode +{ + private readonly LabelTextNode _labelNode; + private readonly TextButtonNode _stateButton; + private readonly Action? _onChanged; + private StateFilter _filter; + + private static readonly string[] StateLabels = { "Ignored", "Allow", "Disallow" }; + + public StateFilterRowNode(string label, StateFilter filter, Action? onChanged = null) + { + _filter = filter; + _onChanged = onChanged; + Size = new Vector2(280, 24); + ItemSpacing = 8.0f; + + _labelNode = new LabelTextNode + { + Size = new Vector2(100, 24), + String = $"{label}:", + TextColor = ColorHelper.GetColor(8), + }; + AddNode(_labelNode); + + _stateButton = new TextButtonNode + { + Size = new Vector2(100, 24), + String = StateLabels[_filter.State], + OnClick = CycleState, + }; + AddNode(_stateButton); + } + + private void CycleState() + { + _filter.State = (_filter.State + 1) % 3; + _stateButton.String = StateLabels[_filter.State]; + _onChanged?.Invoke(); + } + + public void SetState(StateFilter newFilter) + { + _filter = newFilter; + _stateButton.String = StateLabels[_filter.State]; + } +} \ No newline at end of file diff --git a/AetherBags/Nodes/Configuration/Category/StringListEditorNode.cs b/AetherBags/Nodes/Configuration/Category/StringListEditorNode.cs new file mode 100644 index 0000000..e8378b2 --- /dev/null +++ b/AetherBags/Nodes/Configuration/Category/StringListEditorNode.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using KamiToolKit.Classes; +using KamiToolKit.Nodes; + +namespace AetherBags.Nodes.Configuration.Category; + +public sealed class StringListEditorNode : VerticalListNode +{ + private List _list; + private readonly TextInputNode _addInput; + private readonly VerticalListNode _itemsContainer; + private readonly Action? _onChanged; + + public StringListEditorNode(string label, List list, Action? onChanged = null) + { + _list = list; + _onChanged = onChanged; + + FitContents = true; + ItemSpacing = 4.0f; + + var headerLabel = new LabelTextNode + { + Size = new Vector2(280, 18), + String = label, + TextColor = ColorHelper.GetColor(8), + }; + AddNode(headerLabel); + + _itemsContainer = new VerticalListNode + { + FitContents = true, + ItemSpacing = 2.0f, + }; + AddNode(_itemsContainer); + + var addRow = new HorizontalListNode { Size = new Vector2(300, 28), ItemSpacing = 4.0f }; + + _addInput = new TextInputNode + { + Size = new Vector2(200, 28), + PlaceholderString = "Add new...", + OnInputComplete = text => + { + var value = text.ExtractText(); + if (!string.IsNullOrWhiteSpace(value) && ! _list.Contains(value)) + { + _list.Add(value); + _addInput.String = ""; + RefreshItems(); + _onChanged?.Invoke(); + } + }, + }; + addRow.AddNode(_addInput); + + var addButton = new TextButtonNode + { + Size = new Vector2(60, 28), + String = "Add", + OnClick = () => + { + var value = _addInput.String; + if (!string.IsNullOrWhiteSpace(value) && !_list.Contains(value)) + { + _list.Add(value); + _addInput.String = ""; + RefreshItems(); + _onChanged?.Invoke(); + } + }, + }; + addRow.AddNode(addButton); + + AddNode(addRow); + + RefreshItems(); + } + + public void SetList(List newList) + { + _list = newList; + RefreshItems(); + } + + private void RefreshItems() + { + _itemsContainer.SyncWithListData( + _list, + node => node.Value, + value => new StringListItemNode(value) + { + Size = new Vector2(280, 22), + OnRemove = () => + { + _list.Remove(value); + RefreshItems(); + _onChanged?.Invoke(); + }, + } + ); + + _itemsContainer.RecalculateLayout(); + RecalculateLayout(); + } + + public void Refresh() + { + RefreshItems(); + } +} + +public sealed class StringListItemNode : HorizontalListNode +{ + public string Value { get; } + public Action? OnRemove { get; init; } + + public StringListItemNode(string value) + { + Value = value; + ItemSpacing = 4.0f; + + var itemLabel = new LabelTextNode + { + Size = new Vector2(220, 22), + String = value, + TextColor = ColorHelper.GetColor(3), + }; + AddNode(itemLabel); + + var removeButton = new TextButtonNode + { + Size = new Vector2(50, 22), + String = "X", + OnClick = () => OnRemove?.Invoke(), + }; + AddNode(removeButton); + } +} \ No newline at end of file diff --git a/AetherBags/Nodes/Configuration/Category/UintListEditorNode.cs b/AetherBags/Nodes/Configuration/Category/UintListEditorNode.cs new file mode 100644 index 0000000..6aae6fb --- /dev/null +++ b/AetherBags/Nodes/Configuration/Category/UintListEditorNode.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using KamiToolKit.Classes; +using KamiToolKit.Nodes; + +namespace AetherBags.Nodes.Configuration.Category; + +public sealed class UintListEditorNode : VerticalListNode +{ + private List _list; + private readonly NumericInputNode _addInput; + private readonly VerticalListNode _itemsContainer; + private readonly Action? _onChanged; + private readonly Func? _labelResolver; + + public UintListEditorNode(string label, List list, Action? onChanged = null, Func? labelResolver = null) + { + _list = list; + _onChanged = onChanged; + _labelResolver = labelResolver; + + FitContents = true; + ItemSpacing = 4.0f; + + var headerLabel = new LabelTextNode + { + Size = new Vector2(280, 18), + String = label, + TextColor = ColorHelper.GetColor(8), + }; + AddNode(headerLabel); + + _itemsContainer = new VerticalListNode + { + FitContents = true, + ItemSpacing = 2.0f, + }; + AddNode(_itemsContainer); + + var addRow = new HorizontalListNode { Size = new Vector2(300, 28), ItemSpacing = 4.0f }; + + _addInput = new NumericInputNode + { + Size = new Vector2(120, 28), + Min = 0, + Max = int.MaxValue, + Value = 0, + }; + addRow.AddNode(_addInput); + + var addButton = new TextButtonNode + { + Size = new Vector2(60, 28), + String = "Add", + OnClick = () => + { + var value = (uint)_addInput.Value; + if (! _list.Contains(value)) + { + _list.Add(value); + RefreshItems(); + _onChanged?.Invoke(); + } + }, + }; + addRow.AddNode(addButton); + + AddNode(addRow); + + RefreshItems(); + } + + public void SetList(List newList) + { + _list = newList; + RefreshItems(); + } + + private void RefreshItems() + { + _itemsContainer.SyncWithListData( + _list, + node => node.Value, + value => new UintListItemNode(value, _labelResolver) + { + Size = new Vector2(280, 22), + OnRemove = () => + { + _list.Remove(value); + RefreshItems(); + _onChanged?.Invoke(); + }, + } + ); + + _itemsContainer.RecalculateLayout(); + RecalculateLayout(); + } + + public void Refresh() + { + RefreshItems(); + } +} + +public sealed class UintListItemNode : HorizontalListNode +{ + public uint Value { get; } + public Action? OnRemove { get; init; } + + public UintListItemNode(uint value, Func? labelResolver = null) + { + Value = value; + ItemSpacing = 4.0f; + + var displayText = labelResolver != null ? $"{value} - {labelResolver(value)}" : value.ToString(); + var itemLabel = new LabelTextNode + { + Size = new Vector2(220, 22), + String = displayText, + TextColor = ColorHelper.GetColor(3), + }; + AddNode(itemLabel); + + var removeButton = new TextButtonNode + { + Size = new Vector2(50, 22), + String = "X", + OnClick = () => OnRemove?.Invoke(), + }; + AddNode(removeButton); + } +} \ No newline at end of file diff --git a/AetherBags/Nodes/Configuration/CategoryScrollingAreaNode.cs b/AetherBags/Nodes/Configuration/CategoryScrollingAreaNode.cs deleted file mode 100644 index fd33e4c..0000000 --- a/AetherBags/Nodes/Configuration/CategoryScrollingAreaNode.cs +++ /dev/null @@ -1,7 +0,0 @@ -using KamiToolKit.Nodes; - -namespace AetherBags.Nodes.Configuration; - -public class CategoryScrollingAreaNode : ScrollingAreaNode -{ -} \ No newline at end of file diff --git a/AetherBags/Nodes/Configuration/Currency/CurrencyConfigurationNode.cs b/AetherBags/Nodes/Configuration/Currency/CurrencyGeneralConfigurationNode.cs similarity index 96% rename from AetherBags/Nodes/Configuration/Currency/CurrencyConfigurationNode.cs rename to AetherBags/Nodes/Configuration/Currency/CurrencyGeneralConfigurationNode.cs index 0d2814b..f8c1006 100644 --- a/AetherBags/Nodes/Configuration/Currency/CurrencyConfigurationNode.cs +++ b/AetherBags/Nodes/Configuration/Currency/CurrencyGeneralConfigurationNode.cs @@ -6,9 +6,9 @@ using KamiToolKit.Nodes; namespace AetherBags.Nodes.Configuration.Currency; -public sealed class CurrencyConfigurationNode : TabbedVerticalListNode +public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode { - public CurrencyConfigurationNode() + public CurrencyGeneralConfigurationNode() { CurrencySettings config = System.Config.Currency; diff --git a/AetherBags/Nodes/Configuration/CurrencyScrollingAreaNode.cs b/AetherBags/Nodes/Configuration/Currency/CurrencyScrollingAreaNode.cs similarity index 58% rename from AetherBags/Nodes/Configuration/CurrencyScrollingAreaNode.cs rename to AetherBags/Nodes/Configuration/Currency/CurrencyScrollingAreaNode.cs index 70847d4..f0e074f 100644 --- a/AetherBags/Nodes/Configuration/CurrencyScrollingAreaNode.cs +++ b/AetherBags/Nodes/Configuration/Currency/CurrencyScrollingAreaNode.cs @@ -1,13 +1,12 @@ -using AetherBags.Nodes.Configuration.Currency; using KamiToolKit.Nodes; -namespace AetherBags.Nodes.Configuration; +namespace AetherBags.Nodes.Configuration.Currency; public sealed class CurrencyScrollingAreaNode : ScrollingAreaNode { public CurrencyScrollingAreaNode() { - ContentNode.AddNode(new CurrencyConfigurationNode + ContentNode.AddNode(new CurrencyGeneralConfigurationNode { Size = Size }); diff --git a/AetherBags/Nodes/Configuration/GeneralScrollingAreaNode.cs b/AetherBags/Nodes/Configuration/General/GeneralScrollingAreaNode.cs similarity index 90% rename from AetherBags/Nodes/Configuration/GeneralScrollingAreaNode.cs rename to AetherBags/Nodes/Configuration/General/GeneralScrollingAreaNode.cs index 1d2544c..f744cb6 100644 --- a/AetherBags/Nodes/Configuration/GeneralScrollingAreaNode.cs +++ b/AetherBags/Nodes/Configuration/General/GeneralScrollingAreaNode.cs @@ -1,11 +1,11 @@ -using AetherBags.Configuration; -using AetherBags.Nodes.Configuration.Layout; -using KamiToolKit.Nodes; using System; using System.Linq; using System.Numerics; +using AetherBags.Configuration; +using AetherBags.Nodes.Configuration.Layout; +using KamiToolKit.Nodes; -namespace AetherBags.Nodes.Configuration; +namespace AetherBags.Nodes.Configuration.General; public sealed class GeneralScrollingAreaNode : ScrollingAreaNode { @@ -28,8 +28,10 @@ public sealed class GeneralScrollingAreaNode : ScrollingAreaNode { if (Enum.TryParse(selected, out var parsed)) + { config.StackMode = parsed; - RefreshInventory(); + System.AddonInventoryWindow.ManualInventoryRefresh(); + } } }; ContentNode.AddNode(_stackDropDown); diff --git a/AetherBags/Nodes/Inventory/InventoryCategoryNode.cs b/AetherBags/Nodes/Inventory/InventoryCategoryNode.cs index 3702545..f8fbd36 100644 --- a/AetherBags/Nodes/Inventory/InventoryCategoryNode.cs +++ b/AetherBags/Nodes/Inventory/InventoryCategoryNode.cs @@ -23,7 +23,7 @@ public class InventoryCategoryNode : SimpleComponentNode private const float HeaderHeight = 16; private const float MinWidth = 40; - private float? _fixedWidth; + private float? _fixedWidth; private int _hoverRefs; private bool _headerSuppressed; private bool _headerExpanded; @@ -92,7 +92,7 @@ public class InventoryCategoryNode : SimpleComponentNode } } - public float? FixedWidth + public float? FixedWidth { get => _fixedWidth; set @@ -184,8 +184,8 @@ public class InventoryCategoryNode : SimpleComponentNode int rows = (itemCount + itemsPerLine - 1) / itemsPerLine; int actualColumns = Math.Min(itemCount, itemsPerLine); - float cellW = _itemGridNode.Nodes.Count > 0 ? _itemGridNode.Nodes[0].Width : FallbackItemSize; - float cellH = _itemGridNode.Nodes.Count > 0 ? _itemGridNode.Nodes[0].Height : FallbackItemSize; + float cellW = _itemGridNode.Nodes.Count > 0 ? _itemGridNode.Nodes[0].Width : FallbackItemSize; + float cellH = _itemGridNode.Nodes.Count > 0 ? _itemGridNode.Nodes[0].Height : FallbackItemSize; float hPad = _itemGridNode.HorizontalPadding; float vPad = _itemGridNode.VerticalPadding; @@ -259,7 +259,7 @@ public class InventoryCategoryNode : SimpleComponentNode InventoryType targetContainer = targetItemInfo.Item.Container; ushort targetSlot = (ushort)targetItemInfo.Item.Slot; - Services.Logger.Info($"[OnPayload] Moving {sourceContainer}@{sourceSlot} -> {targetContainer}@{targetSlot}"); + Services.Logger.Debug($"[OnPayload] Moving {sourceContainer}@{sourceSlot} -> {targetContainer}@{targetSlot}"); InventoryMoveHelper.MoveItem(sourceContainer, sourceSlot, targetContainer, targetSlot); }