Merge remote-tracking branch 'origin/master' into dev/pie-lover

This commit is contained in:
Shawrkie Williams
2026-01-25 03:59:49 -05:00
50 changed files with 1306 additions and 767 deletions
@@ -14,7 +14,7 @@ namespace AetherBags.Addons;
public class AddonCategoryConfigurationWindow : NativeAddon public class AddonCategoryConfigurationWindow : NativeAddon
{ {
private ModifyListNode<CategoryWrapper>? _selectionListNode; private ModifyListNode<CategoryWrapper, CategoryListItemNode>? _selectionListNode;
private VerticalLineNode? _separatorLine; private VerticalLineNode? _separatorLine;
private CategoryConfigurationNode? _configNode; private CategoryConfigurationNode? _configNode;
private TextNode? _nothingSelectedTextNode; private TextNode? _nothingSelectedTextNode;
@@ -28,21 +28,24 @@ public class AddonCategoryConfigurationWindow : NativeAddon
{ {
_categoryWrappers = CreateCategoryWrappers(); _categoryWrappers = CreateCategoryWrappers();
_selectionListNode = new ModifyListNode<CategoryWrapper> _selectionListNode = new ModifyListNode<CategoryWrapper, CategoryListItemNode>
{ {
Position = ContentStartPosition, Position = ContentStartPosition,
Size = new Vector2(250.0f, ContentSize.Y), Size = ContentSize with { X = 250.0f },
SelectionOptions = _categoryWrappers, Options = _categoryWrappers,
OnOptionChanged = OnOptionChanged, SelectionChanged = OnOptionChanged,
AddNewEntry = OnAddNewCategory, AddNewEntry = OnAddNewCategory,
RemoveEntry = OnRemoveCategory, RemoveEntry = OnRemoveCategory,
SortOptions = [ "Order" ],
ItemComparer = (left, right, mode) => left.Compare(right, mode),
IsSearchMatch = (data, search) => data.GetLabel().Contains(search, global::System.StringComparison.OrdinalIgnoreCase)
}; };
_selectionListNode.AttachNode(this); _selectionListNode.AttachNode(this);
_separatorLine = new VerticalLineNode _separatorLine = new VerticalLineNode
{ {
Position = ContentStartPosition + new Vector2(250.0f + 8.0f, 0.0f), Position = ContentStartPosition + new Vector2(250.0f + 8.0f, 0.0f),
Size = new Vector2(4.0f, ContentSize.Y), Size = ContentSize with { X = 4.0f },
}; };
_separatorLine.AttachNode(this); _separatorLine.AttachNode(this);
@@ -78,6 +81,24 @@ public class AddonCategoryConfigurationWindow : NativeAddon
.ToList(); .ToList();
} }
private void OnAddNewCategory()
{
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);
RefreshSelectionList();
_selectionListNode?.RefreshList();
InventoryOrchestrator.RefreshAll(updateMaps: true);
}
private void OnOptionChanged(CategoryWrapper? newOption) private void OnOptionChanged(CategoryWrapper? newOption)
{ {
if (_configNode is null) return; if (_configNode is null) return;
@@ -99,29 +120,11 @@ public class AddonCategoryConfigurationWindow : NativeAddon
if (_pendingSelectionListRefresh) if (_pendingSelectionListRefresh)
{ {
_pendingSelectionListRefresh = false; _pendingSelectionListRefresh = false;
_selectionListNode?.UpdateList(); _selectionListNode?.RefreshList();
} }
} }
} }
private void OnAddNewCategory(ModifyListNode<CategoryWrapper> 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);
RefreshSelectionList();
InventoryOrchestrator.RefreshAll(updateMaps: true);
}
private void OnRemoveCategory(CategoryWrapper categoryWrapper) private void OnRemoveCategory(CategoryWrapper categoryWrapper)
{ {
if (categoryWrapper.CategoryDefinition is null) return; if (categoryWrapper.CategoryDefinition is null) return;
@@ -146,6 +149,15 @@ public class AddonCategoryConfigurationWindow : NativeAddon
return; return;
} }
_selectionListNode?.UpdateList(); _selectionListNode?.RefreshList();
}
protected override unsafe void OnFinalize(AtkUnitBase* addon)
{
_selectionListNode = null;
_configNode = null;
_separatorLine = null;
_nothingSelectedTextNode = null;
base.OnFinalize(addon);
} }
} }
+17 -4
View File
@@ -10,11 +10,11 @@ namespace AetherBags.Addons;
public class AddonConfigurationWindow : NativeAddon public class AddonConfigurationWindow : NativeAddon
{ {
private TabBarNode _tabBarNode = null!; private TabBarNode? _tabBarNode;
private GeneralScrollingAreaNode _generalScrollingAreaNode = null!; private GeneralScrollingAreaNode? _generalScrollingAreaNode;
private CategoryScrollingAreaNode _categoryScrollingAreaNode = null!; private CategoryScrollingAreaNode? _categoryScrollingAreaNode;
private CurrencyScrollingAreaNode _currencyScrollingAreaNode = null!; private CurrencyScrollingAreaNode? _currencyScrollingAreaNode;
private readonly List<NodeBase> _tabContent = new(); private readonly List<NodeBase> _tabContent = new();
@@ -73,4 +73,17 @@ public class AddonConfigurationWindow : NativeAddon
for (var i = 0; i < _tabContent.Count; i++) for (var i = 0; i < _tabContent.Count; i++)
_tabContent[i].IsVisible = i == index; _tabContent[i].IsVisible = i == index;
} }
protected override unsafe void OnFinalize(AtkUnitBase* addon)
{
_tabBarNode?.Dispose();
_tabBarNode = null;
_generalScrollingAreaNode?.Dispose();
_generalScrollingAreaNode = null;
_categoryScrollingAreaNode?.Dispose();
_categoryScrollingAreaNode = null;
_currencyScrollingAreaNode?.Dispose();
_currencyScrollingAreaNode = null;
base.OnFinalize(addon);
}
} }
+28
View File
@@ -0,0 +1,28 @@
using System;
using System.Linq;
using AetherBags.Currency;
using KamiToolKit.Premade.ListItemNodes;
using KamiToolKit.Premade.SearchAddons;
using Lumina.Excel.Sheets;
namespace AetherBags.Addons;
public class AddonCurrencyPicker : BaseSearchAddon<Item, ItemListItemNode> {
public AddonCurrencyPicker() {
var allItems = Services.DataManager.GetExcelSheet<Item>();
var obsoleteTomes = Services.DataManager.GetExcelSheet<TomestonesItem>()
.Where(t => t.Tomestones.RowId == 0)
.Select(t => t.Item.RowId).ToHashSet();
var currentTomestones = CurrencyState.GetCurrentTomestoneIds();
SearchOptions = allItems
.Where(i => (i.ItemUICategory.RowId == 100 || (i.RowId >= 1 && i.RowId < 100)) && !i.Name.IsEmpty)
.Where(i => !obsoleteTomes.Contains(i.RowId))
.Where(i => i.RowId != currentTomestones.Limited && i.RowId != currentTomestones.NonLimited)
.ToList();
}
protected override bool IsMatch(Item item, string search) => item.Name.ToString().Contains(search, StringComparison.OrdinalIgnoreCase);
protected override int Comparer(Item l, Item r, string s, bool rev) => string.CompareOrdinal(l.Name.ToString(), r.Name.ToString());
}
+2 -2
View File
@@ -79,7 +79,7 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
LayoutContent(); LayoutContent();
//addon->SubscribeAtkArrayData(1, (int)NumberArrayType.Inventory); addon->SubscribeAtkArrayData(1, (int)NumberArrayType.Inventory);
System.LootedItemsTracker.OnLootedItemsChanged += OnLootedItemsChanged; System.LootedItemsTracker.OnLootedItemsChanged += OnLootedItemsChanged;
@@ -139,6 +139,7 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
private void OnClearAllLootedItems() private void OnClearAllLootedItems()
{ {
System.LootedItemsTracker.Clear(); System.LootedItemsTracker.Clear();
System.LootedItemsTracker.FlushPendingChanges();
} }
public void ManualCurrencyRefresh() public void ManualCurrencyRefresh()
@@ -155,7 +156,6 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
}, delayTicks: 3); }, delayTicks: 3);
} }
protected override void OnFinalize(AtkUnitBase* addon) protected override void OnFinalize(AtkUnitBase* addon)
{ {
System.LootedItemsTracker.OnLootedItemsChanged -= OnLootedItemsChanged; System.LootedItemsTracker.OnLootedItemsChanged -= OnLootedItemsChanged;
+7
View File
@@ -0,0 +1,7 @@
using KamiToolKit.Premade.ListItemNodes;
using KamiToolKit.Premade.SearchAddons;
namespace AetherBags.Addons;
public class AddonItemPicker : ItemSearchAddonBase<ItemListItemNode> {
}
@@ -0,0 +1,14 @@
using System.Diagnostics.CodeAnalysis;
using AetherBags.Nodes.Configuration.Category;
using KamiToolKit.Premade.SearchAddons;
using Lumina.Excel.Sheets;
namespace AetherBags.Addons;
public class AddonUICategoryPicker : BaseSearchAddon<ItemUICategory, UICategoryListItemNode> {
protected override int Comparer(ItemUICategory left, ItemUICategory right, string sort, bool rev)
=> string.CompareOrdinal(left.Name.ToString(), right.Name.ToString());
protected override bool IsMatch(ItemUICategory item, string search)
=> item.Name.ToString().Contains(search, global::System.StringComparison.OrdinalIgnoreCase);
}
+14
View File
@@ -0,0 +1,14 @@
using KamiToolKit.Premade.GenericListItemNodes;
namespace AetherBags.Addons;
public class CategoryListItemNode : GenericListItemNode<CategoryWrapper>
{
protected override uint GetIconId(CategoryWrapper data) => data.GetIconId() ?? 0;
protected override string GetLabelText(CategoryWrapper data) => data.GetLabel();
protected override string GetSubLabelText(CategoryWrapper data) => data.GetSubLabel();
protected override uint? GetId(CategoryWrapper data) => data.GetId();
}
+6 -16
View File
@@ -1,17 +1,14 @@
using AetherBags.Configuration; using AetherBags.Configuration;
using AetherBags.Inventory.Categories; using AetherBags.Inventory.Categories;
using KamiToolKit.Premade;
namespace AetherBags.Addons; namespace AetherBags.Addons;
public class CategoryWrapper(UserCategoryDefinition categoryDefinition) : IInfoNodeData // Removed IInfoNodeData implementation
public class CategoryWrapper(UserCategoryDefinition categoryDefinition)
{ {
public UserCategoryDefinition? CategoryDefinition { get; } = categoryDefinition; public UserCategoryDefinition? CategoryDefinition { get; } = categoryDefinition;
public string GetLabel() { public string GetLabel() => CategoryDefinition!.Name;
return CategoryDefinition!.Name;
}
public string GetSubLabel() { public string GetSubLabel() {
if(UserCategoryMatcher.IsCatchAll(CategoryDefinition!)) return " No valid rules!"; if(UserCategoryMatcher.IsCatchAll(CategoryDefinition!)) return " No valid rules!";
@@ -20,16 +17,9 @@ public class CategoryWrapper(UserCategoryDefinition categoryDefinition) : IInfoN
public uint? GetId() => null; public uint? GetId() => null;
public uint? GetIconId() { public uint? GetIconId() => 0;
return 0;
}
public string? GetTexturePath() public int Compare(CategoryWrapper other, string sortingMode) {
=> null; return CategoryDefinition!.Order.CompareTo(other.CategoryDefinition!.Order);
public int Compare(IInfoNodeData other, string sortingMode) {
if (other is not CategoryWrapper otherWrapper) return 0;
return CategoryDefinition!.Order.CompareTo(otherWrapper.CategoryDefinition!.Order);
} }
} }
+22 -46
View File
@@ -9,6 +9,7 @@ using AetherBags.Inventory.Context;
using AetherBags.Inventory.Items; using AetherBags.Inventory.Items;
using AetherBags.Inventory.Scanning; using AetherBags.Inventory.Scanning;
using AetherBags.Inventory.State; using AetherBags.Inventory.State;
using AetherBags.Monitoring;
using AetherBags.Nodes.Input; using AetherBags.Nodes.Input;
using AetherBags.Nodes.Inventory; using AetherBags.Nodes.Inventory;
using AetherBags.Nodes.Layout; using AetherBags.Nodes.Layout;
@@ -16,7 +17,7 @@ using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit; using KamiToolKit;
using KamiToolKit.Classes; using KamiToolKit.Classes;
using KamiToolKit.Classes.ContextMenu; using KamiToolKit.ContextMenu;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
namespace AetherBags.Addons; namespace AetherBags.Addons;
@@ -65,25 +66,7 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
private int _refreshFromLifecycleCount; private int _refreshFromLifecycleCount;
private long _lastLogTick; private long _lastLogTick;
public void ManualRefresh() public void ManualRefresh() => ExecuteRefresh(true);
{
if (!IsOpen) return;
if (!Services.ClientState.IsLoggedIn) return;
if (_isRefreshing) return;
if (!IsSetupComplete) return;
try
{
_isRefreshing = true;
InventoryState.RefreshFromGame();
RefreshCategoriesCore(autosize: true);
}
finally
{
_isRefreshing = false;
}
}
public string GetSearchText() => SearchInputNode?.SearchString.ExtractText() ?? string.Empty; public string GetSearchText() => SearchInputNode?.SearchString.ExtractText() ?? string.Empty;
@@ -98,21 +81,16 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
}, delayTicks: 3); }, delayTicks: 3);
} }
public void RefreshFromLifecycle() private void ExecuteRefresh(bool autosize)
{ {
if (!IsSetupComplete) return; if (!IsSetupComplete || !IsOpen || _isRefreshing) return;
if (!IsOpen) return;
if (_isRefreshing) return;
try try
{ {
_isRefreshing = true; _isRefreshing = true;
_refreshFromLifecycleCount++;
LogRefreshStats();
InventoryState.RefreshFromGame(); InventoryState.RefreshFromGame();
RefreshCategoriesCore(autosize: true); System.LootedItemsTracker.FlushPendingChanges();
RefreshCategoriesCore(autosize);
} }
finally finally
{ {
@@ -120,6 +98,8 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
} }
} }
public void RefreshFromLifecycle() => ExecuteRefresh(autosize: true);
protected virtual void RefreshCategoriesCore(bool autosize) protected virtual void RefreshCategoriesCore(bool autosize)
{ {
if (!IsSetupComplete) if (!IsSetupComplete)
@@ -215,25 +195,22 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
{ {
var header = addon->WindowHeaderCollisionNode; var header = addon->WindowHeaderCollisionNode;
float headerW = header->Width; float headerW = header->Width;
float headerH = header->Height;
// Center the search bar, width is 50% of header float settingsX = headerW - 62f;
float searchWidth = headerW * 0.5f; float itemY = header->Y + (header->Height - 28f) * 0.5f;
var searchSize = new Vector2(searchWidth, 28f);
float searchWidth = headerW * 0.45f;
float searchX = (headerW - searchWidth) * 0.5f; float searchX = (headerW - searchWidth) * 0.5f;
float itemY = header->Y + (headerH - 28f) * 0.5f;
return new HeaderLayout return new HeaderLayout
{ {
SearchPosition = new Vector2(searchX, itemY), SearchPosition = new Vector2(searchX, itemY),
SearchSize = searchSize, SearchSize = new Vector2(searchWidth, 28f),
HeaderWidth = headerW, HeaderWidth = headerW,
HeaderY = itemY HeaderY = itemY
}; };
} }
protected void InitializeBackgroundDropTarget() protected void InitializeBackgroundDropTarget()
{ {
BackgroundDropTarget = new DragDropNode BackgroundDropTarget = new DragDropNode
@@ -383,6 +360,11 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
float requiredWidth = maxChildWidth + (ContentStartPosition.X * 2); float requiredWidth = maxChildWidth + (ContentStartPosition.X * 2);
float finalWidth = Math.Clamp(requiredWidth, MinWindowWidth, MaxWindowWidth); float finalWidth = Math.Clamp(requiredWidth, MinWindowWidth, MaxWindowWidth);
if (SettingsButtonNode != null)
{
SettingsButtonNode.X = finalWidth - 62f;
}
float contentWidth = finalWidth - (ContentStartPosition.X * 2); float contentWidth = finalWidth - (ContentStartPosition.X * 2);
float footerSpace = HasFooter || HasSlotCounter ? FooterHeight + FooterTopSpacing : 0; float footerSpace = HasFooter || HasSlotCounter ? FooterHeight + FooterTopSpacing : 0;
@@ -442,21 +424,15 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
} }
} }
/*
protected override void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData) protected override void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
{ {
_requestedUpdateCount++;
LogRefreshStats();
base.OnRequestedUpdate(addon, numberArrayData, stringArrayData); base.OnRequestedUpdate(addon, numberArrayData, stringArrayData);
if (DragDropState.IsDragging) if (DragDropState.IsDragging) return;
return; ExecuteRefresh(autosize: true);
InventoryState.RefreshFromGame();
RefreshCategoriesCore(autosize: true);
} }
*/
protected override void OnSetup(AtkUnitBase* addon) protected override void OnSetup(AtkUnitBase* addon)
{ {
@@ -2,7 +2,7 @@ using AetherBags.Configuration;
using AetherBags.Inventory; using AetherBags.Inventory;
using AetherBags.Inventory.Context; using AetherBags.Inventory.Context;
using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using KamiToolKit.Classes.ContextMenu; using KamiToolKit.ContextMenu;
namespace AetherBags.Addons; namespace AetherBags.Addons;
@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Numerics; using System.Numerics;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using KamiToolKit.Classes; using KamiToolKit.Classes;
@@ -84,6 +85,9 @@ public enum ToggleFilterState
public enum PluginFilterMode public enum PluginFilterMode
{ {
[Description("Create New Categories")]
Categorize = 0, Categorize = 0,
[Description("Apply Highlight Only")]
Highlight = 1, Highlight = 1,
} }
@@ -1,11 +1,20 @@
using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using System.Text.Json.Serialization;
using KamiToolKit.Classes; using KamiToolKit.Classes;
namespace AetherBags.Configuration; namespace AetherBags.Configuration;
public class CurrencySettings public class CurrencySettings
{ {
[JsonIgnore]
public const uint LimitedTomestoneId = 0xFFFF_FFFE;
[JsonIgnore]
public const uint NonLimitedTomestoneId = 0xFFFF_FFFD;
public bool Enabled { get; set; } = true; public bool Enabled { get; set; } = true;
public List<uint> DisplayedCurrencies { get; set; } = new() { 1, LimitedTomestoneId, NonLimitedTomestoneId };
public bool ColorWhenCapped { get; set; } = true; public bool ColorWhenCapped { get; set; } = true;
public bool ColorWhenLimited { get; set; } = true; public bool ColorWhenLimited { get; set; } = true;
public Vector4 DefaultColor { get; set; } = ColorHelper.GetColor(8); public Vector4 DefaultColor { get; set; } = ColorHelper.GetColor(8);
@@ -1,3 +1,5 @@
using System.ComponentModel;
namespace AetherBags.Configuration; namespace AetherBags.Configuration;
public class GeneralSettings public class GeneralSettings
@@ -21,12 +23,18 @@ public class GeneralSettings
public enum InventoryStackMode : byte public enum InventoryStackMode : byte
{ {
[Description("Split Stacks (Game Default)")]
NaturalStacks = 0, NaturalStacks = 0,
[Description("Merge Stacks (By Item ID)")]
AggregateByItemId = 1, AggregateByItemId = 1,
} }
public enum SearchMode : byte public enum SearchMode : byte
{ {
[Description("Filter (Hide non-matches)")]
Filter = 0, Filter = 0,
[Description("Highlight (Dim non-matches)")]
Highlight = 1, Highlight = 1,
} }
+32
View File
@@ -71,6 +71,31 @@ public static unsafe class CurrencyState
return currencyInfoList; return currencyInfoList;
} }
public static (uint Limited, uint NonLimited) GetCurrentTomestoneIds()
{
var tomestonesItemSheet = Services.DataManager.GetExcelSheet<TomestonesItem>();
uint limitedId = 0;
uint nonLimitedId = 0;
foreach (var row in tomestonesItemSheet)
{
var tomeSheetRef = row.Tomestones.ValueNullable;
if (tomeSheetRef == null || tomeSheetRef.Value.RowId == 0) continue;
var itemId = row.Item.RowId;
if (itemId == 0 || itemId == 28) continue;
if (tomeSheetRef.Value.WeeklyLimit > 0)
limitedId = itemId;
else
nonLimitedId = itemId;
}
return (limitedId, nonLimitedId);
}
/*
private static uint? GetLimitedTomestoneItemIdCached() private static uint? GetLimitedTomestoneItemIdCached()
{ {
if (_cachedLimitedTomestoneItemId.HasValue) if (_cachedLimitedTomestoneItemId.HasValue)
@@ -96,6 +121,13 @@ public static unsafe class CurrencyState
_cachedNonLimitedTomestoneItemId = itemId; _cachedNonLimitedTomestoneItemId = itemId;
return itemId; return itemId;
} }
*/
private static uint? GetLimitedTomestoneItemIdCached()
=> _cachedLimitedTomestoneItemId ??= GetCurrentTomestoneIds().Limited;
private static uint? GetNonLimitedTomestoneItemIdCached()
=> _cachedNonLimitedTomestoneItemId ??= GetCurrentTomestoneIds().NonLimited;
private static CurrencyItem ResolveCurrencyItemIdCached(uint currencyId) private static CurrencyItem ResolveCurrencyItemIdCached(uint currencyId)
{ {
+52
View File
@@ -0,0 +1,52 @@
using System;
using System.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
using Dalamud.Utility;
namespace AetherBags.Extensions;
internal static class EnumExtensions {
extension(Enum enumValue) {
public string Description => enumValue.GetDescription();
private string GetDescription() {
var attribute = enumValue.GetAttribute<DescriptionAttribute>();
return attribute?.Description ?? enumValue.ToString();
}
}
extension<T>(ref T flagValue) where T : unmanaged, Enum {
public void SetFlags(params T[] flags) {
foreach (var flag in flags) {
flagValue.SetFlag(flag, true);
}
}
public void ClearFlags(params T[] flags) {
foreach (var flag in flags) {
flagValue.SetFlag(flag, false);
}
}
private unsafe void SetFlag(T flag, bool enable) {
switch (sizeof(T)) {
case 1: flagValue.SetFlag<T, byte>(flag, enable); break;
case 2: flagValue.SetFlag<T, ushort>(flag, enable); break;
case 4: flagValue.SetFlag<T, uint>(flag, enable); break;
case 8: flagValue.SetFlag<T, ulong>(flag, enable); break;
default: throw new NotSupportedException("Unsupported enum size");
}
}
private void SetFlag<TUnderlying>(T flag, bool enable) where TUnderlying : unmanaged, IBinaryInteger<TUnderlying> {
ref var value = ref Unsafe.As<T, TUnderlying>(ref flagValue);
var mask = Unsafe.As<T, TUnderlying>(ref flag);
if (enable)
value |= mask;
else
value &= ~mask;
}
}
}
+8 -3
View File
@@ -1,22 +1,27 @@
using System.Diagnostics;
using Dalamud.Plugin.Services;
namespace AetherBags.Extensions; namespace AetherBags.Extensions;
public static class LoggerExtensions public static class LoggerExtensions
{ {
extension(object logger) extension(IPluginLog logger)
{ {
[Conditional("DEBUG")]
public void DebugOnly(string message) public void DebugOnly(string message)
{ {
if (System.Config?.General?.DebugEnabled == true) if (System.Config?.General?.DebugEnabled == true)
{ {
Services.Logger.Debug(message); logger.Debug(message);
} }
} }
[Conditional("DEBUG")]
public void DebugOnly(string message, params object[] args) public void DebugOnly(string message, params object[] args)
{ {
if (System.Config?.General?.DebugEnabled == true) if (System.Config?.General?.DebugEnabled == true)
{ {
Services.Logger.Debug(message, args); logger.Debug(message, args);
} }
} }
} }
@@ -18,6 +18,7 @@ public abstract class InventoryStateBase
protected readonly List<CategorizedInventory> AllCategories = new(capacity: 256); protected readonly List<CategorizedInventory> AllCategories = new(capacity: 256);
protected readonly List<CategorizedInventory> FilteredCategories = new(capacity: 256); protected readonly List<CategorizedInventory> FilteredCategories = new(capacity: 256);
protected readonly List<UserCategoryDefinition> UserCategoriesSortedScratch = new(capacity: 64); protected readonly List<UserCategoryDefinition> UserCategoriesSortedScratch = new(capacity: 64);
protected readonly List<UserCategoryDefinition> EnabledUserCategoriesScratch = new(capacity: 64);
protected readonly List<ulong> RemoveKeysScratch = new(capacity: 256); protected readonly List<ulong> RemoveKeysScratch = new(capacity: 256);
protected readonly HashSet<ulong> ClaimedKeys = new(capacity: 512); protected readonly HashSet<ulong> ClaimedKeys = new(capacity: 512);
@@ -68,18 +69,22 @@ public abstract class InventoryStateBase
bool allaganCategoriesEnabled = config.Categories.AllaganToolsCategoriesEnabled && categoriesEnabled; bool allaganCategoriesEnabled = config.Categories.AllaganToolsCategoriesEnabled && categoriesEnabled;
bool bisCategoriesEnabled = config.Categories.BisBuddyEnabled && categoriesEnabled; bool bisCategoriesEnabled = config.Categories.BisBuddyEnabled && categoriesEnabled;
// TODO: Cache this when config changes // TODO: Cache this when config changes
UserCategoriesSortedScratch.Clear(); EnabledUserCategoriesScratch.Clear();
foreach (var cat in config.Categories. UserCategories) foreach (var cat in config.Categories.UserCategories)
{ {
if (cat.Enabled) if (cat.Enabled)
UserCategoriesSortedScratch.Add(cat); EnabledUserCategoriesScratch.Add(cat);
} }
var userCategories = UserCategoriesSortedScratch;
if (userCategoriesEnabled && userCategories.Count > 0) if (userCategoriesEnabled && EnabledUserCategoriesScratch.Count > 0)
{ {
CategoryBucketManager.BucketByUserCategories( CategoryBucketManager.BucketByUserCategories(
ItemInfoByKey, userCategories, BucketsByKey, ClaimedKeys, UserCategoriesSortedScratch); ItemInfoByKey,
EnabledUserCategoriesScratch,
BucketsByKey,
ClaimedKeys,
UserCategoriesSortedScratch
);
} }
if (allaganCategoriesEnabled) if (allaganCategoriesEnabled)
@@ -1,18 +1,22 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using AetherBags.Configuration; using AetherBags.Configuration;
using AetherBags.Inventory.Context; using AetherBags.Inventory.Context;
using AetherBags.Inventory.Scanning;
using Dalamud.Game.Addon.Lifecycle; using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.Inventory.InventoryEventArgTypes;
using Dalamud.Game.NativeWrapper; using Dalamud.Game.NativeWrapper;
using Dalamud.Utility; using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using Lumina.Text.ReadOnly; using Lumina.Text.ReadOnly;
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType; using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
namespace AetherBags.AddonLifecycles; namespace AetherBags.Monitoring;
public static unsafe class DragDropState public static unsafe class DragDropState
{ {
@@ -22,10 +26,10 @@ public static unsafe class DragDropState
public static bool IsDragging => AtkStage.Instance()->DragDropManager.IsDragging; public static bool IsDragging => AtkStage.Instance()->DragDropManager.IsDragging;
} }
public class InventoryLifecycles : IDisposable public class InventoryMonitor : IDisposable
{ {
public InventoryLifecycles() public InventoryMonitor()
{ {
var bags = new[] { "Inventory", "InventoryLarge", "InventoryExpansion" }; var bags = new[] { "Inventory", "InventoryLarge", "InventoryExpansion" };
var saddle = new[] { "InventoryBuddy" }; var saddle = new[] { "InventoryBuddy" };
@@ -45,8 +49,8 @@ public class InventoryLifecycles : IDisposable
Services.AddonLifecycle.RegisterListener(AddonEvent.PostRequestedUpdate, "InventoryBuddy", OnSaddleBagUpdate); Services.AddonLifecycle.RegisterListener(AddonEvent.PostRequestedUpdate, "InventoryBuddy", OnSaddleBagUpdate);
Services.AddonLifecycle.RegisterListener(AddonEvent.PostRequestedUpdate, retainer, OnRetainerInventoryUpdate); Services.AddonLifecycle.RegisterListener(AddonEvent.PostRequestedUpdate, retainer, OnRetainerInventoryUpdate);
// PreShow // Dalamud raw event for raw inventory changes (scans once per frame)
Services.AddonLifecycle.RegisterListener(AddonEvent.PreOpen, "InventoryBuddy", OnSaddleBagOpen); Services.GameInventory.InventoryChangedRaw += OnInventoryChangedRaw;
Services.Logger.Verbose("InventoryLifecycles initialized"); Services.Logger.Verbose("InventoryLifecycles initialized");
} }
@@ -122,6 +126,32 @@ public class InventoryLifecycles : IDisposable
values[7] = can use Saddlebags (Agent InventoryBuddy IsActivatable) values[7] = can use Saddlebags (Agent InventoryBuddy IsActivatable)
*/ */
private void OnInventoryChangedRaw(IReadOnlyCollection<InventoryEventArgs> events)
{
bool needsRefresh = false;
foreach (var inventoryEventArgs in events)
{
if (InventoryScanner.StandardInventories.Contains((InventoryType)inventoryEventArgs.Item.ContainerType))
{
needsRefresh = true;
break;
}
}
if (needsRefresh)
{
Services.Framework.RunOnTick(() =>
{
if (IsInUnsafeState() || DragDropState.IsDragging) return;
System.LootedItemsTracker.FlushPendingChanges();
System.AddonInventoryWindow?.RefreshFromLifecycle();
System.AddonSaddleBagWindow?.RefreshFromLifecycle();
System.AddonRetainerWindow?.RefreshFromLifecycle();
});
}
}
private unsafe void InventoryPreRefreshHandler(AddonEvent type, AddonArgs args) private unsafe void InventoryPreRefreshHandler(AddonEvent type, AddonArgs args)
{ {
if (args is not AddonRefreshArgs refreshArgs) if (args is not AddonRefreshArgs refreshArgs)
@@ -165,25 +195,6 @@ public class InventoryLifecycles : IDisposable
} }
} }
// TODO: Inventory/Retainers are not perma open, need some way to close it too.
private void InventoryBuddyPreRefreshHandler(AddonEvent type, AddonArgs args)
{
if (args is not AddonRefreshArgs refreshArgs)
return;
if (IsInUnsafeState())
return;
GeneralSettings config = System.Config.General;
if (config.HideGameSaddleBags) refreshArgs.AtkValueCount = 0;
if (config.OpenSaddleBagsWithGameInventory)
{
System.AddonSaddleBagWindow.Toggle();
}
}
private void OnInventoryUpdate(AddonEvent type, AddonArgs args) private void OnInventoryUpdate(AddonEvent type, AddonArgs args)
{ {
if (IsInUnsafeState()) if (IsInUnsafeState())
@@ -204,6 +215,7 @@ public class InventoryLifecycles : IDisposable
if (DragDropState.IsDragging) if (DragDropState.IsDragging)
return; return;
System.LootedItemsTracker.FlushPendingChanges();
System.AddonSaddleBagWindow?.RefreshFromLifecycle(); System.AddonSaddleBagWindow?.RefreshFromLifecycle();
} }
@@ -215,17 +227,13 @@ public class InventoryLifecycles : IDisposable
if (DragDropState.IsDragging) if (DragDropState.IsDragging)
return; return;
System.LootedItemsTracker.FlushPendingChanges();
System.AddonRetainerWindow?.RefreshFromLifecycle(); System.AddonRetainerWindow?.RefreshFromLifecycle();
} }
private void OnSaddleBagOpen(AddonEvent type, AddonArgs args)
{
if (args is not AddonShowArgs showArgs)
return;
}
public void Dispose() public void Dispose()
{ {
Services.AddonLifecycle.UnregisterListener(OnPostSetup, OnPreFinalize, OnInventoryUpdate, OnSaddleBagUpdate, OnRetainerInventoryUpdate, OnSaddleBagOpen); Services.GameInventory.InventoryChangedRaw -= OnInventoryChangedRaw;
Services.AddonLifecycle.UnregisterListener(OnPostSetup, OnPreFinalize, OnInventoryUpdate, OnSaddleBagUpdate, OnRetainerInventoryUpdate);
} }
} }
@@ -9,7 +9,7 @@ using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using Lumina.Excel.Sheets; using Lumina.Excel.Sheets;
namespace AetherBags.Inventory; namespace AetherBags.Monitoring;
public sealed unsafe class LootedItemsTracker : IDisposable public sealed unsafe class LootedItemsTracker : IDisposable
{ {
@@ -32,6 +32,8 @@ public sealed unsafe class LootedItemsTracker : IDisposable
public bool HasPendingChanges => _pendingChanges.Count > 0 || _hasPendingRemoval; public bool HasPendingChanges => _pendingChanges.Count > 0 || _hasPendingRemoval;
private int GetNextIndex() => _lootedItems.Count > 0 ? _lootedItems.Max(x => x.Index) + 1 : 0;
public void Enable() public void Enable()
{ {
if (_isEnabled) return; if (_isEnabled) return;
@@ -81,6 +83,8 @@ public sealed unsafe class LootedItemsTracker : IDisposable
{ {
if (_pendingChanges.Count == 0 && !_hasPendingRemoval) return; if (_pendingChanges.Count == 0 && !_hasPendingRemoval) return;
ProcessPendingChanges();
_hasPendingRemoval = false; _hasPendingRemoval = false;
OnLootedItemsChanged?.Invoke(_lootedItems); OnLootedItemsChanged?.Invoke(_lootedItems);
} }
@@ -90,12 +94,40 @@ public sealed unsafe class LootedItemsTracker : IDisposable
Disable(); Disable();
} }
private void ProcessPendingChanges()
{
if (_pendingChanges.Count == 0) return;
foreach (var ((itemId, isHq), (item, delta)) in _pendingChanges)
{
int existingIndex = _lootedItems.FindIndex(x =>
x.Item.ItemId == itemId &&
x.Item.Flags.HasFlag(InventoryItem.ItemFlags.HighQuality) == isHq);
if (existingIndex >= 0)
{
var current = _lootedItems[existingIndex];
int newQty = current.Quantity + delta;
if (newQty <= 0)
_lootedItems.RemoveAt(existingIndex);
else
_lootedItems[existingIndex] = current with { Quantity = newQty };
}
else if (delta > 0)
{
_lootedItems.Add(new LootedItemInfo(GetNextIndex(), item, delta));
}
}
_pendingChanges.Clear();
}
private void OnInventoryChangedRaw(IReadOnlyCollection<InventoryEventArgs> events) private void OnInventoryChangedRaw(IReadOnlyCollection<InventoryEventArgs> events)
{ {
if (!_isEnabled) return; if (!_isEnabled || !Services.ClientState.IsLoggedIn) return;
if (!Services.ClientState.IsLoggedIn) return;
bool anyAdded = false; bool anyChanged = false;
foreach (var eventData in events) foreach (var eventData in events)
{ {
@@ -105,38 +137,42 @@ public sealed unsafe class LootedItemsTracker : IDisposable
if (eventData.Item.ContainerType == GameInventoryType.DamagedGear) if (eventData.Item.ContainerType == GameInventoryType.DamagedGear)
continue; continue;
if (eventData is not (InventoryItemAddedArgs or InventoryItemChangedArgs)) int changeAmount = eventData switch
continue;
if (eventData is InventoryItemChangedArgs changedArgs &&
changedArgs.OldItemState.Quantity >= changedArgs.Item.Quantity)
{ {
continue; InventoryItemAddedArgs added => added.Item.Quantity,
} InventoryItemRemovedArgs removed => -removed.Item.Quantity,
InventoryItemChangedArgs changed => changed.Item.Quantity - changed.OldItemState.Quantity,
_ => 0
};
if (changeAmount == 0) continue;
if (ShouldFilterItem(eventData.Item.ItemId)) if (ShouldFilterItem(eventData.Item.ItemId))
continue; continue;
var inventoryItem = *(InventoryItem*)eventData.Item.Address; uint itemId = eventData.Item.ItemId;
var changeAmount = eventData is InventoryItemChangedArgs changed bool isHq = eventData.Item.IsHq;
? changed.Item.Quantity - changed.OldItemState.Quantity var key = (itemId, isHq);
: eventData.Item.Quantity;
var key = (inventoryItem.ItemId, IsHq: inventoryItem.Flags.HasFlag(InventoryItem.ItemFlags.HighQuality));
if (_pendingChanges.TryGetValue(key, out var existing)) if (_pendingChanges.TryGetValue(key, out var existing))
{ {
_pendingChanges[key] = (inventoryItem, existing.Quantity + changeAmount); _pendingChanges[key] = (existing.Item, existing.Quantity + changeAmount);
} }
else else
{ {
_pendingChanges[key] = (inventoryItem, changeAmount); InventoryItem itemStruct = default;
if (changeAmount > 0)
{
itemStruct = *(InventoryItem*)eventData.Item.Address;
}
_pendingChanges[key] = (itemStruct, changeAmount);
} }
anyAdded = true; anyChanged = true;
} }
if (anyAdded && _batchStartTick == 0) if (anyChanged && _batchStartTick == 0)
{ {
_batchStartTick = Environment.TickCount64; _batchStartTick = Environment.TickCount64;
} }
@@ -152,23 +188,7 @@ public sealed unsafe class LootedItemsTracker : IDisposable
_batchStartTick = 0; _batchStartTick = 0;
if (_pendingChanges.Count == 0) FlushPendingChanges();
return;
foreach (var ((itemId, isHq), (item, quantity)) in _pendingChanges)
{
if (quantity <= 0)
continue;
_lootedItems.Add(new LootedItemInfo(
_lootedItems.Count,
item,
quantity));
}
_pendingChanges.Clear();
OnLootedItemsChanged?.Invoke(_lootedItems);
} }
private static bool ShouldFilterItem(uint itemId) private static bool ShouldFilterItem(uint itemId)
+39 -30
View File
@@ -3,6 +3,7 @@ using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
using KamiToolKit.Premade.Addons; using KamiToolKit.Premade.Addons;
using KamiToolKit.Premade.Color;
namespace AetherBags.Nodes.Color; namespace AetherBags.Nodes.Color;
@@ -16,41 +17,49 @@ public class ColorInputRow : HorizontalListNode
{ {
InitializeColorPicker(); InitializeColorPicker();
var initialColor = CurrentColor; _colorPreview = new ColorPreviewButtonNode { Size = new Vector2(28) };
_colorPreview = new ColorPreviewButtonNode
{
Size = new Vector2(28),
Color = CurrentColor,
OnClick = () =>
{
_colorPickerAddon?.InitialColor = CurrentColor;
_colorPickerAddon?.DefaultColor = DefaultColor;
_colorPickerAddon?.Toggle();
_colorPickerAddon?.OnColorConfirmed = color =>
{
CurrentColor = color;
_colorPreview?.Color = color;
initialColor = color;
OnColorConfirmed?.Invoke(color);
};
_colorPickerAddon?.OnColorPreviewed = color =>
{
_colorPreview?.Color = color;
OnColorChange?.Invoke(color);
};
_colorPickerAddon?.OnColorCancelled = () => OnColorCanceled?.Invoke(initialColor);
}
};
_colorPreview.AttachNode(this);
_labelTextNode = new LabelTextNode _labelTextNode = new LabelTextNode
{ {
TextFlags = TextFlags.AutoAdjustNodeSize, TextFlags = TextFlags.AutoAdjustNodeSize,
Position = new Vector2(28, 0), Position = new Vector2(28, 0),
Height = 24, Height = 28,
String = Label ?? string.Empty,
}; };
var node = _colorPreview;
node.OnClick = () =>
{
var snapshot = CurrentColor;
if (_colorPickerAddon is not null)
{
_colorPickerAddon.InitialColor = snapshot;
_colorPickerAddon.DefaultColor = DefaultColor;
_colorPickerAddon.Toggle();
_colorPickerAddon.OnColorConfirmed = color =>
{
CurrentColor = color;
node.Color = color;
OnColorConfirmed?.Invoke(color);
};
_colorPickerAddon.OnColorPreviewed = color =>
{
node.Color = color;
OnColorPreviewed?.Invoke(color);
};
_colorPickerAddon.OnColorCancelled = () =>
{
CurrentColor = snapshot;
node.Color = snapshot;
OnColorCanceled?.Invoke(snapshot);
};
}
};
_colorPreview.AttachNode(this);
_labelTextNode.AttachNode(this); _labelTextNode.AttachNode(this);
} }
+1 -1
View File
@@ -2,7 +2,7 @@ using System.Drawing;
using System.IO; using System.IO;
using System.Numerics; using System.Numerics;
using Dalamud.Interface; using Dalamud.Interface;
using KamiToolKit.Classes; using KamiToolKit.Enums;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
namespace AetherBags.Nodes.Color; namespace AetherBags.Nodes.Color;
@@ -0,0 +1,141 @@
using System;
using System.Numerics;
using AetherBags.Configuration;
using AetherBags.Nodes.Color;
using Dalamud.Utility;
using KamiToolKit.Nodes;
namespace AetherBags.Nodes.Configuration.Category;
public sealed class BasicSettingsSection(Func<UserCategoryDefinition> getCategoryDefinition) : ConfigurationSection(getCategoryDefinition)
{
public Action? OnPropertyChanged { get; init; }
private CheckboxNode? _enabledCheckbox;
private CheckboxNode? _pinnedCheckbox;
private TextInputNode? _nameInput;
private TextInputNode? _descriptionInput;
private ColorInputRow? _colorInput;
private NumericInputNode? _priorityInput;
private NumericInputNode? _orderInput;
private bool _initialized;
private void EnsureInitialized()
{
if (_initialized) return;
_initialized = true;
_enabledCheckbox = new CheckboxNode
{
Size = new Vector2(Width, 20),
String = "Enabled",
OnClick = isChecked =>
{
CategoryDefinition.Enabled = isChecked;
OnPropertyChanged?.Invoke();
},
};
AddNode(_enabledCheckbox);
_pinnedCheckbox = new CheckboxNode
{
Size = new Vector2(Width, 20),
String = "Pinned",
OnClick = isChecked =>
{
CategoryDefinition.Pinned = isChecked;
OnPropertyChanged?.Invoke();
},
};
AddNode(_pinnedCheckbox);
AddNode(CreateLabel("Name: "));
_nameInput = new TextInputNode
{
Size = new Vector2(250, 28),
PlaceholderString = "Category Name",
OnInputReceived = input =>
{
CategoryDefinition.Name = input.ExtractText();
OnPropertyChanged?.Invoke();
},
};
AddNode(_nameInput);
AddNode(CreateLabel("Description:"));
_descriptionInput = new TextInputNode
{
Size = new Vector2(250, 28),
PlaceholderString = "Optional description",
OnInputReceived = input =>
{
CategoryDefinition.Description = input.ExtractText();
OnValueChanged?.Invoke();
},
};
AddNode(_descriptionInput);
_colorInput = new ColorInputRow
{
Label = "Color",
Size = new Vector2(300, 28),
CurrentColor = new UserCategoryDefinition().Color,
DefaultColor = new UserCategoryDefinition().Color,
OnColorConfirmed = color => { CategoryDefinition.Color = color; OnValueChanged?.Invoke(); },
OnColorCanceled = color => { CategoryDefinition.Color = color; OnValueChanged?.Invoke(); },
OnColorPreviewed = color => { CategoryDefinition.Color = color; OnValueChanged?.Invoke(); },
OnColorChange = color => { CategoryDefinition.Color = color; OnValueChanged?.Invoke(); },
};
AddNode(_colorInput);
AddNode(CreateLabel("Priority:"));
_priorityInput = new NumericInputNode
{
Size = new Vector2(120, 28),
Min = 0,
Max = 1000,
Step = 1,
OnValueUpdate = value =>
{
CategoryDefinition.Priority = value;
OnValueChanged?.Invoke();
},
};
AddNode(_priorityInput);
AddNode(CreateLabel("Order: "));
_orderInput = new NumericInputNode
{
Size = new Vector2(120, 28),
Min = 0,
Max = 9999,
Step = 1,
OnValueUpdate = val =>
{
CategoryDefinition.Order = val;
OnPropertyChanged?.Invoke();
},
};
AddNode(_orderInput);
RecalculateLayout();
}
public override void Refresh()
{
EnsureInitialized();
_enabledCheckbox!.IsChecked = CategoryDefinition.Enabled;
_pinnedCheckbox!.IsChecked = CategoryDefinition.Pinned;
_nameInput!.String = CategoryDefinition.Name;
_nameInput.PlaceholderString = CategoryDefinition.Name.IsNullOrWhitespace() ? "Category Name" : "";
_descriptionInput!.String = CategoryDefinition.Description;
_descriptionInput.PlaceholderString = CategoryDefinition.Description.IsNullOrWhitespace() ? "Optional description" : "";
_colorInput!.CurrentColor = CategoryDefinition.Color;
_priorityInput!.Value = CategoryDefinition.Priority;
_orderInput!.Value = CategoryDefinition.Order;
RecalculateLayout();
}
}
@@ -3,8 +3,7 @@ using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using AetherBags.Configuration; using AetherBags.Configuration;
using AetherBags.Inventory; using AetherBags.Inventory;
using AetherBags.Nodes.Color; using AetherBags.Nodes.Layout;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
using Lumina.Excel; using Lumina.Excel;
@@ -23,71 +22,39 @@ public sealed class CategoryDefinitionConfigurationNode : SimpleComponentNode
private UserCategoryDefinition _categoryDefinition = new(); private UserCategoryDefinition _categoryDefinition = new();
private readonly ScrollingAreaNode<TreeListNode> _scrollingArea; private readonly ScrollingAreaNode<VerticalListNode> _scrollingArea;
private readonly BasicSettingsSection _basicSettings; private readonly List<ConfigurationSection> _sections = new();
private readonly RangeFiltersSection _rangeFilters;
private readonly StateFiltersSection _stateFilters;
private readonly ListFiltersSection _listFilters;
public CategoryDefinitionConfigurationNode() public CategoryDefinitionConfigurationNode()
{ {
_scrollingArea = new ScrollingAreaNode<TreeListNode> _scrollingArea = new ScrollingAreaNode<VerticalListNode> {
{
ContentHeight = 100.0f,
AutoHideScrollBar = true, AutoHideScrollBar = true,
ContentHeight = 100f
}; };
_scrollingArea.AttachNode(this); _scrollingArea.AttachNode(this);
_scrollingArea.ContentNode.OnLayoutUpdate = newHeight => var list = _scrollingArea.ContentAreaNode;
{ list.FitContents = true;
_scrollingArea.ContentHeight = newHeight; list.ItemSpacing = 4.0f;
};
_scrollingArea.ContentNode.CategoryVerticalSpacing = 4.0f; _sections.Add(new BasicSettingsSection(() => _categoryDefinition) {
String = "Basic Settings", IsCollapsed = false,
OnPropertyChanged = () => { NotifyChanged(); OnCategoryPropertyChanged?.Invoke(); }
});
var treeListNode = _scrollingArea.ContentAreaNode; _sections.Add(new RangeFiltersSection(() => _categoryDefinition) { String = "Range Filters" });
_sections.Add(new StateFiltersSection(() => _categoryDefinition) { String = "State Filters" });
_basicSettings = new BasicSettingsSection(() => _categoryDefinition) _sections.Add(new ListFiltersSection(() => _categoryDefinition) {
{
String = "Basic Settings",
IsCollapsed = false,
OnPropertyChanged = () =>
{
NotifyChanged();
NotifyCategoryPropertyChanged();
},
OnValueChanged = NotifyChanged,
};
_basicSettings.OnToggle = _ => HandleLayoutChange();
treeListNode.AddCategoryNode(_basicSettings);
_rangeFilters = new RangeFiltersSection(() => _categoryDefinition)
{
String = "Range Filters",
IsCollapsed = true,
OnValueChanged = NotifyChanged,
};
_rangeFilters.OnToggle = _ => HandleLayoutChange();
treeListNode.AddCategoryNode(_rangeFilters);
_stateFilters = new StateFiltersSection(() => _categoryDefinition)
{
String = "State Filters",
IsCollapsed = true,
OnValueChanged = NotifyChanged,
};
_stateFilters.OnToggle = _ => HandleLayoutChange();
treeListNode.AddCategoryNode(_stateFilters);
_listFilters = new ListFiltersSection(() => _categoryDefinition)
{
String = "List Filters", String = "List Filters",
IsCollapsed = true, OnListChanged = HandleLayoutChange
OnValueChanged = NotifyChanged, });
OnListChanged = HandleListChanged,
}; foreach (var section in _sections)
_listFilters.OnToggle = _ => HandleLayoutChange(); {
treeListNode.AddCategoryNode(_listFilters); section.OnToggle = HandleLayoutChange;
section.OnValueChanged = NotifyChanged;
list.AddNode(section);
}
} }
protected override void OnSizeChanged() protected override void OnSizeChanged()
@@ -96,417 +63,56 @@ public sealed class CategoryDefinitionConfigurationNode : SimpleComponentNode
_scrollingArea.Size = Size; _scrollingArea.Size = Size;
foreach (var categoryNode in _scrollingArea.ContentNode.CategoryNodes) foreach (var section in _sections)
{ {
categoryNode.Width = Width - 16.0f; section.Width = Width - 16.0f;
} }
HandleLayoutChange();
_scrollingArea.ContentNode.RefreshLayout();
} }
public void SetCategory(UserCategoryDefinition newCategory) public void SetCategory(UserCategoryDefinition newCategory)
{ {
_categoryDefinition = newCategory; _categoryDefinition = newCategory;
RefreshAllValues(); foreach (var section in _sections) section.Refresh();
}
private void RefreshAllValues()
{
_basicSettings.Refresh();
_rangeFilters.Refresh();
_stateFilters.Refresh();
_listFilters.Refresh();
HandleLayoutChange();
}
private void HandleListChanged()
{
NotifyChanged();
HandleLayoutChange(); HandleLayoutChange();
} }
private void HandleLayoutChange() private void HandleLayoutChange()
{ {
_scrollingArea.ContentNode.RefreshLayout(); _scrollingArea.ContentAreaNode.RecalculateLayout();
_scrollingArea.ContentHeight = _scrollingArea.ContentAreaNode.Height;
OnLayoutChanged?.Invoke(); OnLayoutChanged?.Invoke();
} }
private static void NotifyChanged() => InventoryOrchestrator.RefreshAll(updateMaps: true); private static void NotifyChanged() => InventoryOrchestrator.RefreshAll(updateMaps: true);
private void NotifyCategoryPropertyChanged() => OnCategoryPropertyChanged?.Invoke();
public static string ResolveItemName(uint itemId) => ItemSheet?.GetRow(itemId).Name.ToString() ?? "Unknown"; public static string ResolveItemName(uint itemId) => ItemSheet?.GetRow(itemId).Name.ToString() ?? "Unknown";
public static string ResolveUiCategoryName(uint categoryId) => UICategorySheet?.GetRow(categoryId).Name.ToString() ?? "Unknown"; public static string ResolveUiCategoryName(uint categoryId) => UICategorySheet?.GetRow(categoryId).Name.ToString() ?? "Unknown";
} }
public abstract class ConfigurationSection : TreeListCategoryNode public abstract class ConfigurationSection : CollapsibleSectionNode
{ {
private readonly Func<UserCategoryDefinition> _getCategoryDefinition; private readonly Func<UserCategoryDefinition> _getCategoryDefinition;
public Action? OnValueChanged { get; init; } public Action? OnValueChanged { get; set; }
protected UserCategoryDefinition CategoryDefinition => _getCategoryDefinition(); protected UserCategoryDefinition CategoryDefinition => _getCategoryDefinition();
protected ConfigurationSection(Func<UserCategoryDefinition> getCategoryDefinition) protected ConfigurationSection(Func<UserCategoryDefinition> getCategoryDefinition)
{ {
_getCategoryDefinition = getCategoryDefinition; _getCategoryDefinition = getCategoryDefinition;
VerticalPadding = 4.0f; HeaderHeight = 30.0f;
AddTab();
} }
public abstract void Refresh();
protected static LabelTextNode CreateLabel(string text) => new() protected static LabelTextNode CreateLabel(string text) => new()
{ {
TextFlags = TextFlags.AutoAdjustNodeSize, TextFlags = TextFlags.AutoAdjustNodeSize,
Size = new Vector2(80, 20), Size = new Vector2(80, 20),
String = text, String = text,
}; };
}
public sealed class BasicSettingsSection : ConfigurationSection
{
public Action? OnPropertyChanged { get; init; }
private CheckboxNode? _enabledCheckbox;
private CheckboxNode? _pinnedCheckbox;
private TextInputNode? _nameInput;
private TextInputNode? _descriptionInput;
private ColorInputRow? _colorInput;
private NumericInputNode? _priorityInput;
private NumericInputNode? _orderInput;
private bool _initialized;
public BasicSettingsSection(Func<UserCategoryDefinition> getCategoryDefinition)
: base(getCategoryDefinition)
{
}
private void EnsureInitialized()
{
if (_initialized) return;
_initialized = true;
_enabledCheckbox = new CheckboxNode
{
Size = new Vector2(Width, 20),
String = "Enabled",
OnClick = isChecked =>
{
CategoryDefinition.Enabled = isChecked;
OnPropertyChanged?.Invoke();
},
};
AddNode(_enabledCheckbox);
_pinnedCheckbox = new CheckboxNode
{
Size = new Vector2(Width, 20),
String = "Pinned",
OnClick = isChecked =>
{
CategoryDefinition.Pinned = isChecked;
OnPropertyChanged?.Invoke();
},
};
AddNode(_pinnedCheckbox);
AddNode(CreateLabel("Name: "));
_nameInput = new TextInputNode
{
Size = new Vector2(250, 28),
PlaceholderString = "Category Name",
OnInputReceived = input =>
{
CategoryDefinition.Name = input.ExtractText();
OnPropertyChanged?.Invoke();
},
};
AddNode(_nameInput);
AddNode(CreateLabel("Description:"));
_descriptionInput = new TextInputNode
{
Size = new Vector2(250, 28),
PlaceholderString = "Optional description",
OnInputReceived = input =>
{
CategoryDefinition.Description = input.ExtractText();
OnValueChanged?.Invoke();
},
};
AddNode(_descriptionInput);
_colorInput = new ColorInputRow
{
Label = "Color",
Size = new Vector2(300, 28),
CurrentColor = new UserCategoryDefinition().Color,
DefaultColor = new UserCategoryDefinition().Color,
OnColorConfirmed = c => { CategoryDefinition.Color = c; OnValueChanged?.Invoke(); },
OnColorCanceled = c => { CategoryDefinition.Color = c; OnValueChanged?.Invoke(); },
OnColorPreviewed = c => { CategoryDefinition.Color = c; OnValueChanged?.Invoke(); },
};
AddNode(_colorInput);
AddNode(CreateLabel("Priority:"));
_priorityInput = new NumericInputNode
{
Size = new Vector2(120, 28),
Min = 0,
Max = 1000,
Step = 1,
OnValueUpdate = val =>
{
CategoryDefinition.Priority = val;
OnValueChanged?.Invoke();
},
};
AddNode(_priorityInput);
AddNode(CreateLabel("Order: "));
_orderInput = new NumericInputNode
{
Size = new Vector2(120, 28),
Min = 0,
Max = 9999,
Step = 1,
OnValueUpdate = val =>
{
CategoryDefinition.Order = val;
OnPropertyChanged?.Invoke();
},
};
AddNode(_orderInput);
RecalculateLayout();
}
public void Refresh()
{
EnsureInitialized();
_enabledCheckbox!.IsChecked = CategoryDefinition.Enabled;
_pinnedCheckbox!.IsChecked = CategoryDefinition.Pinned;
_nameInput!.String = CategoryDefinition.Name;
_nameInput.PlaceholderString = CategoryDefinition.Name.IsNullOrWhitespace() ? "Category Name" : "";
_descriptionInput!.String = CategoryDefinition.Description;
_descriptionInput.PlaceholderString = CategoryDefinition.Description.IsNullOrWhitespace() ? "Optional description" : "";
_colorInput!.CurrentColor = CategoryDefinition.Color;
_priorityInput!.Value = CategoryDefinition.Priority;
_orderInput!.Value = CategoryDefinition.Order;
RecalculateLayout();
ParentTreeListNode?.RefreshLayout();
}
}
public sealed class RangeFiltersSection : ConfigurationSection
{
private RangeFilterRow? _levelFilter;
private RangeFilterRow? _itemLevelFilter;
private RangeFilterRowUint? _vendorPriceFilter;
private bool _initialized;
public RangeFiltersSection(Func<UserCategoryDefinition> getCategoryDefinition)
: base(getCategoryDefinition)
{
}
private void EnsureInitialized()
{
if (_initialized) return;
_initialized = true;
_levelFilter = new RangeFilterRow
{
Label = "Level",
MinBound = 0,
MaxBound = 200,
OnFilterChanged = (enabled, min, max) =>
{
CategoryDefinition.Rules.Level.Enabled = enabled;
CategoryDefinition.Rules.Level.Min = min;
CategoryDefinition.Rules.Level.Max = max;
OnValueChanged?.Invoke();
},
};
AddNode(_levelFilter);
_itemLevelFilter = new RangeFilterRow
{
Label = "Item Level",
MinBound = 0,
MaxBound = 2000,
OnFilterChanged = (enabled, min, max) =>
{
CategoryDefinition.Rules.ItemLevel.Enabled = enabled;
CategoryDefinition.Rules.ItemLevel.Min = min;
CategoryDefinition.Rules.ItemLevel.Max = max;
OnValueChanged?.Invoke();
},
};
AddNode(_itemLevelFilter);
_vendorPriceFilter = new RangeFilterRowUint
{
Label = "Vendor Price",
MinBound = 0,
MaxBound = 9_999_999,
OnFilterChanged = (enabled, min, max) =>
{
CategoryDefinition.Rules.VendorPrice.Enabled = enabled;
CategoryDefinition.Rules.VendorPrice.Min = min;
CategoryDefinition.Rules.VendorPrice.Max = max;
OnValueChanged?.Invoke();
},
};
AddNode(_vendorPriceFilter);
RecalculateLayout();
}
public void Refresh()
{
EnsureInitialized();
_levelFilter!.SetFilter(CategoryDefinition.Rules.Level);
_itemLevelFilter!.SetFilter(CategoryDefinition.Rules.ItemLevel);
_vendorPriceFilter!.SetFilter(CategoryDefinition.Rules.VendorPrice);
RecalculateLayout();
ParentTreeListNode?.RefreshLayout();
}
}
public sealed class StateFiltersSection : ConfigurationSection
{
private readonly List<(StateFilterRowNode Node, Func<UserCategoryDefinition, StateFilter> GetFilter)> _filters = [];
private bool _initialized;
public StateFiltersSection(Func<UserCategoryDefinition> getCategoryDefinition)
: base(getCategoryDefinition)
{
}
private void EnsureInitialized()
{
if (_initialized) return;
_initialized = true;
AddFilter("Untradable", def => def.Rules.Untradable);
AddFilter("Unique", def => def.Rules.Unique);
AddFilter("Collectable", def => def.Rules.Collectable);
AddFilter("Dyeable", def => def.Rules.Dyeable);
AddFilter("Repairable", def => def.Rules.Repairable);
AddFilter("High Quality", def => def.Rules.HighQuality);
AddFilter("Desynthesizable", def => def.Rules.Desynthesizable);
AddFilter("Glamourable", def => def.Rules.Glamourable);
AddFilter("Spiritbonded", def => def.Rules.FullySpiritbonded);
RecalculateLayout();
}
private void AddFilter(string label, Func<UserCategoryDefinition, StateFilter> getFilter)
{
var node = new StateFilterRowNode(label, new StateFilter(), () => OnValueChanged?.Invoke());
_filters.Add((node, getFilter));
AddNode(node);
}
public void Refresh()
{
EnsureInitialized();
foreach (var (node, getFilter) in _filters)
{
node.SetState(getFilter(CategoryDefinition));
}
RecalculateLayout();
ParentTreeListNode?.RefreshLayout();
}
}
public sealed class ListFiltersSection : ConfigurationSection
{
public Action? OnListChanged { get; init; }
private UintListEditorNode? _itemIdsEditor;
private StringListEditorNode? _namePatternsEditor;
private UintListEditorNode? _uiCategoriesEditor;
private RarityEditorNode? _raritiesEditor;
private bool _initialized;
public ListFiltersSection(Func<UserCategoryDefinition> getCategoryDefinition)
: base(getCategoryDefinition)
{
}
private void EnsureInitialized()
{
if (_initialized) return;
_initialized = true;
_itemIdsEditor = new UintListEditorNode
{
Label = "Allowed Item IDs:",
LabelResolver = CategoryDefinitionConfigurationNode.ResolveItemName,
OnChanged = () =>
{
OnListChanged?.Invoke();
RecalculateLayout();
ParentTreeListNode?.RefreshLayout();
},
};
AddNode(_itemIdsEditor);
_namePatternsEditor = new StringListEditorNode
{
Label = "Name Patterns (Regex):",
OnChanged = () =>
{
OnListChanged?.Invoke();
RecalculateLayout();
ParentTreeListNode?.RefreshLayout();
},
};
AddNode(_namePatternsEditor);
_uiCategoriesEditor = new UintListEditorNode
{
Label = "UI Categories:",
LabelResolver = CategoryDefinitionConfigurationNode.ResolveUiCategoryName,
OnChanged = () =>
{
OnListChanged?.Invoke();
RecalculateLayout();
ParentTreeListNode?.RefreshLayout();
},
};
AddNode(_uiCategoriesEditor);
_raritiesEditor = new RarityEditorNode
{
OnChanged = () => OnValueChanged?.Invoke(),
};
AddNode(_raritiesEditor);
RecalculateLayout();
}
public void Refresh()
{
EnsureInitialized();
_itemIdsEditor!.SetList(CategoryDefinition.Rules.AllowedItemIds);
_namePatternsEditor!.SetList(CategoryDefinition.Rules.AllowedItemNamePatterns);
_uiCategoriesEditor!.SetList(CategoryDefinition.Rules.AllowedUiCategoryIds);
_raritiesEditor!.SetList(CategoryDefinition.Rules.AllowedRarities);
RecalculateLayout();
ParentTreeListNode?.RefreshLayout();
}
} }
@@ -80,24 +80,21 @@ public sealed class CategoryGeneralConfigurationNode : TabbedVerticalListNode
bool bisBuddyReady = System.IPC.BisBuddy?.IsReady ?? false; bool bisBuddyReady = System.IPC.BisBuddy?.IsReady ?? false;
LabeledDropdownNode? bbModeDropdown = new LabeledDropdownNode LabeledEnumDropdownNode<PluginFilterMode>? bbModeDropdown = new LabeledEnumDropdownNode<PluginFilterMode>
{ {
Size = new Vector2(300, 20), Size = new Vector2(500, 20),
LabelText = "Filter Display Mode", LabelText = "Filter Display Mode",
LabelTextFlags = TextFlags.AutoAdjustNodeSize, LabelTextFlags = TextFlags.AutoAdjustNodeSize,
IsEnabled = config.BisBuddyEnabled && bisBuddyReady, IsEnabled = config.BisBuddyEnabled && bisBuddyReady,
Options = Enum.GetNames(typeof(PluginFilterMode)).ToList(), Options = Enum.GetValues<PluginFilterMode>().ToList(),
SelectedOption = config.BisBuddyMode.ToString(), SelectedOption = config.BisBuddyMode,
OnOptionSelected = selected => OnOptionSelected = selected =>
{ {
if (Enum.TryParse<PluginFilterMode>(selected, out var parsed)) config.BisBuddyMode = selected;
{ if (selected == PluginFilterMode.Categorize)
config.BisBuddyMode = parsed; HighlightState.ClearFilter(HighlightSource.AllaganTools);
if (parsed == PluginFilterMode.Categorize)
HighlightState.ClearFilter(HighlightSource.AllaganTools);
RefreshInventory(); RefreshInventory();
}
} }
}; };
@@ -121,24 +118,23 @@ public sealed class CategoryGeneralConfigurationNode : TabbedVerticalListNode
bool allaganReady = System.IPC.AllaganTools?.IsReady ?? false; bool allaganReady = System.IPC.AllaganTools?.IsReady ?? false;
LabeledDropdownNode? atModeDropdown = new LabeledDropdownNode LabeledEnumDropdownNode<PluginFilterMode>? atModeDropdown = new LabeledEnumDropdownNode<PluginFilterMode>
{ {
Size = new Vector2(300, 20), Size = new Vector2(500, 20),
LabelText = "Filter Display Mode", LabelText = "Filter Display Mode",
LabelTextFlags = TextFlags.AutoAdjustNodeSize, LabelTextFlags = TextFlags.AutoAdjustNodeSize,
IsEnabled = config.AllaganToolsCategoriesEnabled && allaganReady, IsEnabled = config.AllaganToolsCategoriesEnabled && allaganReady,
Options = Enum.GetNames(typeof(PluginFilterMode)).ToList(), Options = Enum.GetValues<PluginFilterMode>().ToList(),
SelectedOption = config.AllaganToolsFilterMode.ToString(), SelectedOption = config.AllaganToolsFilterMode,
OnOptionSelected = selected => OnOptionSelected = selected =>
{ {
if (Enum.TryParse<PluginFilterMode>(selected, out var parsed)) config.AllaganToolsFilterMode = selected;
if (selected == PluginFilterMode.Categorize)
{ {
config.AllaganToolsFilterMode = parsed; HighlightState.ClearFilter(HighlightSource.AllaganTools);
if (parsed == PluginFilterMode.Categorize)
HighlightState.ClearFilter(HighlightSource.AllaganTools);
RefreshInventory();
} }
RefreshInventory();
} }
}; };
@@ -7,7 +7,6 @@ namespace AetherBags.Nodes.Configuration.Category;
public sealed class CategoryScrollingAreaNode : ScrollingListNode public sealed class CategoryScrollingAreaNode : ScrollingListNode
{ {
private AddonCategoryConfigurationWindow? _categoryConfigurationAddon; private AddonCategoryConfigurationWindow? _categoryConfigurationAddon;
private readonly TextButtonNode _categoryConfigurationButtonNode;
public CategoryScrollingAreaNode() public CategoryScrollingAreaNode()
{ {
@@ -15,13 +14,13 @@ public sealed class CategoryScrollingAreaNode : ScrollingListNode
AddNode(new CategoryGeneralConfigurationNode()); AddNode(new CategoryGeneralConfigurationNode());
_categoryConfigurationButtonNode = new TextButtonNode var categoryConfigurationButtonNode = new TextButtonNode
{ {
Size = new Vector2(300, 28), Size = new Vector2(300, 28),
String = "Configure Categories", String = "Configure Categories",
OnClick = () => _categoryConfigurationAddon?.Toggle(), OnClick = () => _categoryConfigurationAddon?.Toggle(),
}; };
AddNode(_categoryConfigurationButtonNode); AddNode(categoryConfigurationButtonNode);
} }
private void InitializeCategoryAddon() { private void InitializeCategoryAddon() {
@@ -33,4 +32,18 @@ public sealed class CategoryScrollingAreaNode : ScrollingListNode
Title = "Category Configuration Window", Title = "Category Configuration Window",
}; };
} }
protected override void Dispose(bool disposing, bool isNativeDestructor)
{
if (disposing)
{
if (_categoryConfigurationAddon != null)
{
_categoryConfigurationAddon.Close();
_categoryConfigurationAddon = null;
}
}
base.Dispose(disposing, isNativeDestructor);
}
} }
@@ -0,0 +1,114 @@
using System;
using System.Linq;
using AetherBags.Addons;
using AetherBags.Configuration;
using Lumina.Excel.Sheets;
using Action = System.Action;
namespace AetherBags.Nodes.Configuration.Category;
public sealed class ListFiltersSection(Func<UserCategoryDefinition> getCategoryDefinition) : ConfigurationSection(getCategoryDefinition)
{
public Action? OnListChanged { get; init; }
private UintListEditorNode? _itemIdsEditor;
private StringListEditorNode? _namePatternsEditor;
private UintListEditorNode? _uiCategoriesEditor;
private RarityEditorNode? _raritiesEditor;
private bool _initialized;
private AddonItemPicker? _itemPicker;
private AddonUICategoryPicker? _categoryPicker;
private void EnsureInitialized()
{
if (_initialized) return;
_initialized = true;
_itemIdsEditor = new UintListEditorNode
{
Label = "Allowed Item IDs:",
LabelResolver = CategoryDefinitionConfigurationNode.ResolveItemName,
OnSearchButtonClicked = OpenItemPicker,
OnChanged = () =>
{
OnListChanged?.Invoke();
RefreshLayout();
},
};
AddNode(_itemIdsEditor);
_namePatternsEditor = new StringListEditorNode
{
Label = "Name Patterns (Regex):",
OnChanged = () =>
{
OnListChanged?.Invoke();
RefreshLayout();
},
};
AddNode(_namePatternsEditor);
_uiCategoriesEditor = new UintListEditorNode
{
Label = "UI Categories:",
LabelResolver = CategoryDefinitionConfigurationNode.ResolveUiCategoryName,
OnSearchButtonClicked = OpenCategoryPicker,
OnChanged = () =>
{
OnListChanged?.Invoke();
RefreshLayout();
},
};
AddNode(_uiCategoriesEditor);
_raritiesEditor = new RarityEditorNode
{
OnChanged = () => OnValueChanged?.Invoke(),
};
AddNode(_raritiesEditor);
RecalculateLayout();
}
private void OpenItemPicker() {
_itemPicker ??= new AddonItemPicker
{
Title = "Select Items to Add",
InternalName = "Aetherbags_ItemPicker",
SearchOptions = Services.DataManager.GetExcelSheet<Item>()
.Where(i => i.RowId > 0 && !i.Name.IsEmpty)
.ToList(),
SortingOptions = ["Alphabetical", "Id"],
ItemSpacing = 3.0f,
};
_itemPicker.SelectionResult = item => _itemIdsEditor?.AddValue(item.RowId);
_itemPicker.Open();
}
private void OpenCategoryPicker() {
_categoryPicker ??= new AddonUICategoryPicker {
Title = "Select Categories to Add",
InternalName = "Aetherbags_CategoryPicker",
SearchOptions = Services.DataManager.GetExcelSheet<ItemUICategory>()
.Where(i => i.RowId > 0)
.ToList()
};
_categoryPicker.SelectionResult = cat => _uiCategoriesEditor?.AddValue(cat.RowId);
_categoryPicker.Open();
}
public override void Refresh()
{
EnsureInitialized();
_itemIdsEditor!.SetList(CategoryDefinition.Rules.AllowedItemIds);
_namePatternsEditor!.SetList(CategoryDefinition.Rules.AllowedItemNamePatterns);
_uiCategoriesEditor!.SetList(CategoryDefinition.Rules.AllowedUiCategoryIds);
_raritiesEditor!.SetList(CategoryDefinition.Rules.AllowedRarities);
RecalculateLayout();
}
}
@@ -3,6 +3,7 @@ using System.Numerics;
using AetherBags.Configuration; using AetherBags.Configuration;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
using Lumina.Text.ReadOnly;
namespace AetherBags.Nodes.Configuration.Category; namespace AetherBags.Nodes.Configuration.Category;
@@ -14,9 +15,9 @@ public sealed class RangeFilterRow : VerticalListNode
public Action<bool, int, int>? OnFilterChanged { get; set; } public Action<bool, int, int>? OnFilterChanged { get; set; }
public required string Label public required ReadOnlySeString Label
{ {
get => _enabledCheckbox.String.Replace(" Filter", ""); get => _enabledCheckbox.String.ExtractText().Replace(" Filter", "");
init => _enabledCheckbox.String = $"{value} Filter"; init => _enabledCheckbox.String = $"{value} Filter";
} }
@@ -113,9 +114,9 @@ public sealed class RangeFilterRowUint : VerticalListNode
public Action<bool, uint, uint>? OnFilterChanged { get; set; } public Action<bool, uint, uint>? OnFilterChanged { get; set; }
public required string Label public required ReadOnlySeString Label
{ {
get => _enabledCheckbox.String.Replace(" Filter", ""); get => _enabledCheckbox.String.ExtractText().Replace(" Filter", "");
init => _enabledCheckbox.String = $"{value} Filter"; init => _enabledCheckbox.String = $"{value} Filter";
} }
@@ -0,0 +1,77 @@
using System;
using AetherBags.Configuration;
namespace AetherBags.Nodes.Configuration.Category;
public sealed class RangeFiltersSection(Func<UserCategoryDefinition> getCategoryDefinition) : ConfigurationSection(getCategoryDefinition)
{
private RangeFilterRow? _levelFilter;
private RangeFilterRow? _itemLevelFilter;
private RangeFilterRowUint? _vendorPriceFilter;
private bool _initialized;
private void EnsureInitialized()
{
if (_initialized) return;
_initialized = true;
_levelFilter = new RangeFilterRow
{
Label = "Level",
MinBound = 0,
MaxBound = 200,
OnFilterChanged = (enabled, min, max) =>
{
CategoryDefinition.Rules.Level.Enabled = enabled;
CategoryDefinition.Rules.Level.Min = min;
CategoryDefinition.Rules.Level.Max = max;
OnValueChanged?.Invoke();
},
};
AddNode(_levelFilter);
_itemLevelFilter = new RangeFilterRow
{
Label = "Item Level",
MinBound = 0,
MaxBound = 2000,
OnFilterChanged = (enabled, min, max) =>
{
CategoryDefinition.Rules.ItemLevel.Enabled = enabled;
CategoryDefinition.Rules.ItemLevel.Min = min;
CategoryDefinition.Rules.ItemLevel.Max = max;
OnValueChanged?.Invoke();
},
};
AddNode(_itemLevelFilter);
_vendorPriceFilter = new RangeFilterRowUint
{
Label = "Vendor Price",
MinBound = 0,
MaxBound = 9_999_999,
OnFilterChanged = (enabled, min, max) =>
{
CategoryDefinition.Rules.VendorPrice.Enabled = enabled;
CategoryDefinition.Rules.VendorPrice.Min = min;
CategoryDefinition.Rules.VendorPrice.Max = max;
OnValueChanged?.Invoke();
},
};
AddNode(_vendorPriceFilter);
RecalculateLayout();
}
public override void Refresh()
{
EnsureInitialized();
_levelFilter!.SetFilter(CategoryDefinition.Rules.Level);
_itemLevelFilter!.SetFilter(CategoryDefinition.Rules.ItemLevel);
_vendorPriceFilter!.SetFilter(CategoryDefinition.Rules.VendorPrice);
RecalculateLayout();
}
}
@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using AetherBags.Configuration;
namespace AetherBags.Nodes.Configuration.Category;
public sealed class StateFiltersSection(Func<UserCategoryDefinition> getCategoryDefinition)
: ConfigurationSection(getCategoryDefinition)
{
private readonly List<(StateFilterRowNode Node, Func<UserCategoryDefinition, StateFilter> GetFilter)> _filters = [];
private bool _initialized;
private void EnsureInitialized()
{
if (_initialized) return;
_initialized = true;
AddFilter("Untradable", def => def.Rules.Untradable);
AddFilter("Unique", def => def.Rules.Unique);
AddFilter("Collectable", def => def.Rules.Collectable);
AddFilter("Dyeable", def => def.Rules.Dyeable);
AddFilter("Repairable", def => def.Rules.Repairable);
AddFilter("High Quality", def => def.Rules.HighQuality);
AddFilter("Desynthesizable", def => def.Rules.Desynthesizable);
AddFilter("Glamourable", def => def.Rules.Glamourable);
AddFilter("Spiritbonded", def => def.Rules.FullySpiritbonded);
RecalculateLayout();
}
private void AddFilter(string label, Func<UserCategoryDefinition, StateFilter> getFilter)
{
var node = new StateFilterRowNode(label, new StateFilter(), () => OnValueChanged?.Invoke());
_filters.Add((node, getFilter));
AddNode(node);
}
public override void Refresh()
{
EnsureInitialized();
foreach (var (node, getFilter) in _filters)
{
node.SetState(getFilter(CategoryDefinition));
}
RecalculateLayout();
}
}
@@ -4,6 +4,7 @@ using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes; using KamiToolKit.Classes;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
using Lumina.Text.ReadOnly;
namespace AetherBags.Nodes.Configuration.Category; namespace AetherBags.Nodes.Configuration.Category;
@@ -16,12 +17,11 @@ public sealed class StringListEditorNode : VerticalListNode
private readonly LabelTextNode _headerLabel; private readonly LabelTextNode _headerLabel;
private readonly VerticalListNode _itemsContainer; private readonly VerticalListNode _itemsContainer;
private readonly HorizontalListNode _addRow;
private readonly TextInputNode _addInput; private readonly TextInputNode _addInput;
public Action? OnChanged { get; set; } public Action? OnChanged { get; set; }
public required string Label public required ReadOnlySeString Label
{ {
get => _headerLabel.String; get => _headerLabel.String;
init => _headerLabel.String = value; init => _headerLabel.String = value;
@@ -49,7 +49,7 @@ public sealed class StringListEditorNode : VerticalListNode
}; };
AddNode(_itemsContainer); AddNode(_itemsContainer);
_addRow = new HorizontalListNode var addRow = new HorizontalListNode
{ {
Size = new Vector2(LabelWidth + 40f, RowHeight), Size = new Vector2(LabelWidth + 40f, RowHeight),
ItemSpacing = 4.0f, ItemSpacing = 4.0f,
@@ -61,7 +61,7 @@ public sealed class StringListEditorNode : VerticalListNode
PlaceholderString = "Add new...", PlaceholderString = "Add new...",
OnInputComplete = _ => AddCurrentValue(), OnInputComplete = _ => AddCurrentValue(),
}; };
_addRow.AddNode(_addInput); addRow.AddNode(_addInput);
var addButton = new TextButtonNode var addButton = new TextButtonNode
{ {
@@ -69,9 +69,9 @@ public sealed class StringListEditorNode : VerticalListNode
String = "Add", String = "Add",
OnClick = AddCurrentValue, OnClick = AddCurrentValue,
}; };
_addRow.AddNode(addButton); addRow.AddNode(addButton);
AddNode(_addRow); AddNode(addRow);
} }
public void SetList(List<string> newList) public void SetList(List<string> newList)
@@ -82,7 +82,7 @@ public sealed class StringListEditorNode : VerticalListNode
private void AddCurrentValue() private void AddCurrentValue()
{ {
var value = _addInput.String; var value = _addInput.String.ExtractText();
if (!string.IsNullOrWhiteSpace(value) && !_list.Contains(value)) if (!string.IsNullOrWhiteSpace(value) && !_list.Contains(value))
{ {
_list.Add(value); _list.Add(value);
@@ -0,0 +1,31 @@
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Nodes;
using Lumina.Excel.Sheets;
namespace AetherBags.Nodes.Configuration.Category;
public class UICategoryListItemNode : ListItemNode<ItemUICategory> {
public override float ItemHeight => 30.0f;
protected readonly TextNode LabelTextNode;
public UICategoryListItemNode() {
LabelTextNode = new TextNode {
FontSize = 14,
AlignmentType = AlignmentType.Left,
TextColor = ColorHelper.GetColor(8),
};
LabelTextNode.AttachNode(this);
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
LabelTextNode.Size = Size with { X = Width - 10 };
LabelTextNode.Position = new Vector2(5, 0);
}
protected override void SetNodeData(ItemUICategory data) {
LabelTextNode.String = data.Name.ToString();
}
}
@@ -1,9 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Numerics; using System.Numerics;
using AetherBags.Configuration;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes; using KamiToolKit.Classes;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
using Lumina.Text.ReadOnly;
namespace AetherBags.Nodes.Configuration.Category; namespace AetherBags.Nodes.Configuration.Category;
@@ -14,15 +17,18 @@ public sealed class UintListEditorNode : VerticalListNode
private List<uint> _list = []; private List<uint> _list = [];
public List<uint> GetList() => _list.ToList();
private readonly LabelTextNode _headerLabel; private readonly LabelTextNode _headerLabel;
private readonly VerticalListNode _itemsContainer; private readonly VerticalListNode _itemsContainer;
private readonly HorizontalListNode _addRow;
private readonly NumericInputNode _addInput; private readonly NumericInputNode _addInput;
public Action? OnSearchButtonClicked { get; init; }
public Func<uint, string>? LabelResolver { get; init; } public Func<uint, string>? LabelResolver { get; init; }
public Action? OnChanged { get; set; } public Action? OnChanged { get; set; }
public required string Label public required ReadOnlySeString Label
{ {
get => _headerLabel.String; get => _headerLabel.String;
init => _headerLabel.String = value; init => _headerLabel.String = value;
@@ -50,12 +56,21 @@ public sealed class UintListEditorNode : VerticalListNode
}; };
AddNode(_itemsContainer); AddNode(_itemsContainer);
_addRow = new HorizontalListNode var addRow = new HorizontalListNode
{ {
Size = new Vector2(LabelWidth + 40f, RowHeight), Size = new Vector2(LabelWidth + 40f, RowHeight),
ItemSpacing = 4.0f, ItemSpacing = 4.0f,
}; };
var searchButton = new CircleButtonNode
{
Size = new Vector2(28),
Icon = ButtonIcon.MagnifyingGlass,
OnClick = () => OnSearchButtonClicked?.Invoke(),
TextTooltip = "Search the game database..."
};
addRow.AddNode(searchButton);
_addInput = new NumericInputNode _addInput = new NumericInputNode
{ {
Size = new Vector2(120, RowHeight), Size = new Vector2(120, RowHeight),
@@ -63,7 +78,7 @@ public sealed class UintListEditorNode : VerticalListNode
Max = int.MaxValue, Max = int.MaxValue,
Value = 0, Value = 0,
}; };
_addRow.AddNode(_addInput); addRow.AddNode(_addInput);
var addButton = new TextButtonNode var addButton = new TextButtonNode
{ {
@@ -71,9 +86,10 @@ public sealed class UintListEditorNode : VerticalListNode
String = "Add", String = "Add",
OnClick = AddCurrentValue, OnClick = AddCurrentValue,
}; };
_addRow.AddNode(addButton); addRow.AddNode(addButton);
addRow.RecalculateLayout();
AddNode(_addRow); AddNode(addRow);
RecalculateLayout();
} }
public void SetList(List<uint> newList) public void SetList(List<uint> newList)
@@ -82,6 +98,16 @@ public sealed class UintListEditorNode : VerticalListNode
RefreshItems(); RefreshItems();
} }
public void AddValue(uint value)
{
if (!_list.Contains(value))
{
_list.Add(value);
RefreshItems();
OnChanged?.Invoke();
}
}
private void AddCurrentValue() private void AddCurrentValue()
{ {
var value = (uint)_addInput.Value; var value = (uint)_addInput.Value;
@@ -109,6 +135,7 @@ public sealed class UintListEditorNode : VerticalListNode
_itemsContainer.RecalculateLayout(); _itemsContainer.RecalculateLayout();
RecalculateLayout(); RecalculateLayout();
OnChanged?.Invoke();
} }
private UintListItemNode CreateItemNode(uint value) => new(value, LabelResolver) private UintListItemNode CreateItemNode(uint value) => new(value, LabelResolver)
@@ -120,8 +147,10 @@ public sealed class UintListEditorNode : VerticalListNode
private void RemoveValue(uint value) private void RemoveValue(uint value)
{ {
_list.Remove(value); _list.Remove(value);
RefreshItems(); Services.Framework.RunOnTick(() => {
OnChanged?.Invoke(); RefreshItems();
OnChanged?.Invoke();
});
} }
} }
@@ -137,9 +166,15 @@ public sealed class UintListItemNode : HorizontalListNode
Value = value; Value = value;
ItemSpacing = 4.0f; ItemSpacing = 4.0f;
string idDisplay = value switch {
0xFFFF_FFFE => "[Weekly]",
0xFFFF_FFFD => "[Tome]",
_ => value.ToString()
};
var displayText = labelResolver is not null var displayText = labelResolver is not null
? $"{value} - {labelResolver(value)}" ? $"{idDisplay} - {labelResolver(value)}"
: value.ToString(); : idDisplay;
AddNode(new LabelTextNode AddNode(new LabelTextNode
{ {
@@ -1,22 +1,29 @@
using System;
using System.Numerics; using System.Numerics;
using AetherBags.Addons;
using AetherBags.Configuration; using AetherBags.Configuration;
using AetherBags.Nodes.Color; using AetherBags.Nodes.Color;
using AetherBags.Nodes.Configuration.Category;
using KamiToolKit.Classes; using KamiToolKit.Classes;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
using Lumina.Excel.Sheets;
namespace AetherBags.Nodes.Configuration.Currency; namespace AetherBags.Nodes.Configuration.Currency;
public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
{ {
private readonly UintListEditorNode? _currencyListEditor;
public CurrencyGeneralConfigurationNode() public CurrencyGeneralConfigurationNode()
{ {
CurrencySettings config = System.Config.Currency; CurrencySettings config = System.Config.Currency;
Width = 600;
ItemVerticalSpacing = 2; ItemVerticalSpacing = 2;
LabelTextNode titleNode = new LabelTextNode LabelTextNode titleNode = new LabelTextNode
{ {
Size = Size with { Y = 18 }, Size = new Vector2(Width, 18),
String = "Currency Configuration", String = "Currency Configuration",
TextColor = ColorHelper.GetColor(2), TextColor = ColorHelper.GetColor(2),
TextOutlineColor = ColorHelper.GetColor(0), TextOutlineColor = ColorHelper.GetColor(0),
@@ -27,7 +34,7 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
CheckboxNode currencyEnabledCheckbox = new CheckboxNode CheckboxNode currencyEnabledCheckbox = new CheckboxNode
{ {
Size = Size with { Y = 18 }, Size = new Vector2(Width, 18),
IsVisible = true, IsVisible = true,
String = "Show Currency", String = "Show Currency",
IsChecked = config.Enabled, IsChecked = config.Enabled,
@@ -41,21 +48,23 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
AddTab(1); AddTab(1);
var defaultColorHandler = CreateColorHandler(color => config.DefaultColor = color);
ColorInputRow defaultCurrencyColorNode = new ColorInputRow ColorInputRow defaultCurrencyColorNode = new ColorInputRow
{ {
Label = "Default Currency Color", Label = "Default Currency Color",
Size = new Vector2(300, 24), Size = new Vector2(300, 24),
CurrentColor = config.DefaultColor, CurrentColor = config.DefaultColor,
DefaultColor = new CurrencySettings().DefaultColor, DefaultColor = new CurrencySettings().DefaultColor,
OnColorConfirmed = ApplyColorChange, OnColorConfirmed = defaultColorHandler,
OnColorChange = ApplyColorChange, OnColorChange = defaultColorHandler,
OnColorCanceled = ApplyColorChange, OnColorCanceled = defaultColorHandler,
OnColorPreviewed = defaultColorHandler,
}; };
AddNode(defaultCurrencyColorNode); AddNode(defaultCurrencyColorNode);
CheckboxNode cappedEnabledCheckbox = new CheckboxNode CheckboxNode cappedEnabledCheckbox = new CheckboxNode
{ {
Size = Size with { Y = 18 }, Size = new Vector2(Width, 18),
IsVisible = true, IsVisible = true,
String = "Color Weekly Cap", String = "Color Weekly Cap",
IsChecked = config.ColorWhenCapped, IsChecked = config.ColorWhenCapped,
@@ -70,18 +79,17 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
AddTab(1); AddTab(1);
var cappedColorHandler = CreateColorHandler(color => config.CappedColor = color);
ColorInputRow cappedCurrencyColorNode = new ColorInputRow ColorInputRow cappedCurrencyColorNode = new ColorInputRow
{ {
Label = "Weekly Cap Color", Label = "Weekly Cap Color",
Size = new Vector2(300, 24), Size = new Vector2(300, 24),
CurrentColor = config.CappedColor, CurrentColor = config.CappedColor,
DefaultColor = new CurrencySettings().CappedColor, DefaultColor = new CurrencySettings().CappedColor,
OnColorConfirmed = color => OnColorConfirmed = cappedColorHandler,
{ OnColorChange = cappedColorHandler,
config.CappedColor = color; OnColorCanceled = cappedColorHandler,
RefreshCurrency(); OnColorPreviewed = cappedColorHandler,
},
}; };
AddNode(cappedCurrencyColorNode); AddNode(cappedCurrencyColorNode);
@@ -89,7 +97,7 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
CheckboxNode limitedEnabledCheckbox = new CheckboxNode CheckboxNode limitedEnabledCheckbox = new CheckboxNode
{ {
Size = Size with { Y = 18 }, Size = new Vector2(Width, 18),
IsVisible = true, IsVisible = true,
String = "Color Max Capacity", String = "Color Max Capacity",
IsChecked = config.ColorWhenLimited, IsChecked = config.ColorWhenLimited,
@@ -104,28 +112,83 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
AddTab(1); AddTab(1);
var limitColorHandler = CreateColorHandler(color => config.LimitColor = color);
ColorInputRow limitCurrencyColorNode = new ColorInputRow ColorInputRow limitCurrencyColorNode = new ColorInputRow
{ {
Label = "Max Capacity Color", Label = "Max Capacity Color",
Size = new Vector2(300, 24), Size = new Vector2(300, 24),
CurrentColor = config.LimitColor, CurrentColor = config.LimitColor,
DefaultColor = new CurrencySettings().LimitColor, DefaultColor = new CurrencySettings().LimitColor,
OnColorConfirmed = color => OnColorConfirmed = limitColorHandler,
{ OnColorChange = limitColorHandler,
config.LimitColor = color; OnColorCanceled = limitColorHandler,
RefreshCurrency(); OnColorPreviewed = limitColorHandler,
},
}; };
AddNode(limitCurrencyColorNode); AddNode(limitCurrencyColorNode);
return; AddNode(new ResNode { Size = new Vector2(15) });
void ApplyColorChange(Vector4 color) SubtractTab(2);
AddNode(new ResNode { Size = new Vector2(15) });
_currencyListEditor = new UintListEditorNode
{ {
config.DefaultColor = color; Label = "Displayed Currencies:",
RefreshCurrency(); LabelResolver = id =>
} {
return id switch
{
CurrencySettings.LimitedTomestoneId => "Current Limited Tomestone",
CurrencySettings.NonLimitedTomestoneId => "Current Non-Limited Tomestone",
_ => Services.DataManager.GetExcelSheet<Item>().GetRow(id).Name.ToString()
};
},
OnSearchButtonClicked = OpenCurrencyPicker,
OnChanged = () => {
System.Config.Currency.DisplayedCurrencies = _currencyListEditor!.GetList();
RefreshCurrency();
RecalculateLayout();
}
};
_currencyListEditor.SetList(System.Config.Currency.DisplayedCurrencies);
AddNode(_currencyListEditor);
var quickAddRow = new HorizontalListNode { Size = new Vector2(600, 30), ItemSpacing = 8.0f };
quickAddRow.AddNode(new TextButtonNode {
String = "+ Gil", Size = new Vector2(70, 24),
OnClick = () => _currencyListEditor?.AddValue(1)
});
quickAddRow.AddNode(new TextButtonNode {
String = "+ Limited Tomestone", Size = new Vector2(150, 24),
OnClick = () => _currencyListEditor?.AddValue(CurrencySettings.LimitedTomestoneId)
});
quickAddRow.AddNode(new TextButtonNode {
String = "+ Non-Limited", Size = new Vector2(110, 24),
OnClick = () => _currencyListEditor?.AddValue(CurrencySettings.NonLimitedTomestoneId)
});
AddNode(quickAddRow);
RecalculateLayout();
} }
private Action<Vector4> CreateColorHandler(Action<Vector4> setter) => newColor =>
{
setter(newColor);
RefreshCurrency();
};
private void RefreshCurrency() => System.AddonInventoryWindow.ManualCurrencyRefresh(); private void RefreshCurrency() => System.AddonInventoryWindow.ManualCurrencyRefresh();
private void OpenCurrencyPicker() {
var picker = new AddonCurrencyPicker
{
Title = "Select Currency to Add",
InternalName = "AetherBags_CurrencyPicker",
};
picker.SelectionResult = item => _currencyListEditor?.AddValue(item.RowId);
picker.Open();
}
} }
@@ -1,3 +1,4 @@
using System.Numerics;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
namespace AetherBags.Nodes.Configuration.Currency; namespace AetherBags.Nodes.Configuration.Currency;
@@ -8,7 +9,7 @@ public sealed class CurrencyScrollingAreaNode : ScrollingListNode
{ {
AddNode(new CurrencyGeneralConfigurationNode AddNode(new CurrencyGeneralConfigurationNode
{ {
Size = Size Width = 600
}); });
} }
} }
@@ -14,7 +14,7 @@ internal sealed class FunctionalConfigurationNode : TabbedVerticalListNode
private readonly CheckboxNode _hideDefaultBagsCheckboxNode; private readonly CheckboxNode _hideDefaultBagsCheckboxNode;
private readonly CheckboxNode _hideSaddlebagsCheckboxNode; private readonly CheckboxNode _hideSaddlebagsCheckboxNode;
private readonly CheckboxNode _hideRetainerbagsCheckboxNode; private readonly CheckboxNode _hideRetainerbagsCheckboxNode;
private readonly LabeledDropdownNode _stackDropDown; private readonly LabeledEnumDropdownNode<InventoryStackMode> _stackDropDown;
public FunctionalConfigurationNode() public FunctionalConfigurationNode()
{ {
@@ -139,39 +139,33 @@ internal sealed class FunctionalConfigurationNode : TabbedVerticalListNode
Height = 6 Height = 6
}); });
var searchModeDropDown = new LabeledDropdownNode var searchModeDropDown = new LabeledEnumDropdownNode<SearchMode>
{ {
Size = new Vector2(300, 20), Size = new Vector2(500, 20),
LabelText = "Search Mode", LabelText = "Search Mode",
LabelTextFlags = TextFlags.AutoAdjustNodeSize, LabelTextFlags = TextFlags.AutoAdjustNodeSize,
Options = Enum.GetNames(typeof(SearchMode)).ToList(), Options = Enum.GetValues<SearchMode>().ToList(),
SelectedOption = config.SearchMode.ToString(), SelectedOption = config.SearchMode,
OnOptionSelected = selected => OnOptionSelected = selected =>
{ {
if (Enum.TryParse<SearchMode>(selected, out var parsed)) config.SearchMode = selected;
{ InventoryOrchestrator.RefreshAll(updateMaps: false);
config.SearchMode = parsed;
InventoryOrchestrator.RefreshAll(updateMaps: false);
}
} }
}; };
AddNode(searchModeDropDown); AddNode(searchModeDropDown);
_stackDropDown = new LabeledDropdownNode _stackDropDown = new LabeledEnumDropdownNode<InventoryStackMode>
{ {
Size = new Vector2(300, 20), Size = new Vector2(500, 20),
IsEnabled = true, IsEnabled = true,
LabelText = "Stack Mode", LabelText = "Stack Mode",
LabelTextFlags = TextFlags.AutoAdjustNodeSize, LabelTextFlags = TextFlags.AutoAdjustNodeSize,
Options = Enum.GetNames(typeof(InventoryStackMode)).ToList(), Options = Enum.GetValues<InventoryStackMode>().ToList(),
SelectedOption = config.StackMode.ToString(), SelectedOption = config.StackMode,
OnOptionSelected = selected => OnOptionSelected = selected =>
{ {
if (Enum.TryParse<InventoryStackMode>(selected, out var parsed)) config.StackMode = selected;
{ InventoryOrchestrator.RefreshAll(updateMaps: true);
config.StackMode = parsed;
InventoryOrchestrator.RefreshAll(updateMaps: true);
}
} }
}; };
AddNode(_stackDropDown); AddNode(_stackDropDown);
@@ -3,6 +3,7 @@ using AetherBags.Helpers;
using AetherBags.Inventory; using AetherBags.Inventory;
using Dalamud.Game.ClientState.Keys; using Dalamud.Game.ClientState.Keys;
using KamiToolKit.Classes; using KamiToolKit.Classes;
using KamiToolKit.Enums;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
namespace AetherBags.Nodes.Configuration.General; namespace AetherBags.Nodes.Configuration.General;
@@ -1,9 +1,9 @@
using AetherBags.Configuration; using AetherBags.Configuration;
using KamiToolKit.Classes.Timelines;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
using System.Numerics; using System.Numerics;
using AetherBags.Inventory; using AetherBags.Inventory;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Timelines;
namespace AetherBags.Nodes.Configuration.Layout; namespace AetherBags.Nodes.Configuration.Layout;
@@ -2,27 +2,28 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
using Lumina.Text.ReadOnly;
namespace AetherBags.Nodes.Input; namespace AetherBags.Nodes.Input;
public class LabeledDropdownNode : SimpleComponentNode { public class LabeledEnumDropdownNode<T> : SimpleComponentNode where T : Enum {
private readonly GridNode _gridNode; private readonly GridNode _gridNode;
private readonly TextNode _labelNode; private readonly TextNode _labelNode;
private readonly TextDropDownNode _dropDownNode; private readonly EnumDropDownNode<T> _dropDownNode;
public LabeledDropdownNode() { public LabeledEnumDropdownNode() {
_gridNode = new GridNode { _gridNode = new GridNode {
GridSize = new GridSize(2, 1), GridSize = new GridSize(2, 1),
}; };
_gridNode.AttachNode(this); _gridNode.AttachNode(this);
_labelNode = new LabelTextNode { _labelNode = new LabelTextNode {
String = String.Empty, String = string.Empty,
}; };
_labelNode.AttachNode(_gridNode[0, 0]); _labelNode.AttachNode(_gridNode[0, 0]);
_dropDownNode = new TextDropDownNode { _dropDownNode = new EnumDropDownNode<T> {
Options = new List<string>(), Options = new List<T>(),
}; };
_dropDownNode.AttachNode(_gridNode[1, 0]); _dropDownNode.AttachNode(_gridNode[1, 0]);
} }
@@ -36,25 +37,38 @@ public class LabeledDropdownNode : SimpleComponentNode {
_dropDownNode.Size = _gridNode[1, 0].Size; _dropDownNode.Size = _gridNode[1, 0].Size;
} }
public required string LabelText public required ReadOnlySeString LabelText
{ {
get => _labelNode.String; get => _labelNode.String;
set => _labelNode.String = value; set => _labelNode.String = value;
} }
public Action<string>? OnOptionSelected public Action<T>? OnOptionSelected
{ {
get => _dropDownNode.OnOptionSelected; get => _dropDownNode.OnOptionSelected;
set => _dropDownNode.OnOptionSelected = value; set => _dropDownNode.OnOptionSelected = value;
} }
public string? SelectedOption public T? SelectedOption
{ {
get => _dropDownNode.SelectedOption; get => _dropDownNode.OptionListNode.SelectedOption;
set => _dropDownNode.SelectedOption = value; set
{
_dropDownNode.OptionListNode.SelectedOption = value;
if (value != null)
{
_dropDownNode.LabelNode.String = value.Description;
}
}
} }
public required List<string> Options public int MaxListOptions
{
get => _dropDownNode.MaxListOptions;
set => _dropDownNode.MaxListOptions = value;
}
public required List<T> Options
{ {
get => _dropDownNode.Options!; get => _dropDownNode.Options!;
set => _dropDownNode.Options = value; set => _dropDownNode.Options = value;
@@ -65,4 +79,4 @@ public class LabeledDropdownNode : SimpleComponentNode {
get => _labelNode.TextFlags; get => _labelNode.TextFlags;
set => _labelNode.TextFlags = value; set => _labelNode.TextFlags = value;
} }
} }
@@ -49,7 +49,7 @@ public class TextInputWithButtonNode : SimpleComponentNode {
} }
public ReadOnlySeString SearchString { public ReadOnlySeString SearchString {
get => _textInputNode.SeString; get => _textInputNode.String;
set => _textInputNode.SeString = value; set => _textInputNode.String = value;
} }
} }
@@ -61,7 +61,7 @@ public class InventoryCategoryNode : InventoryCategoryNodeBase
_categoryNameTextNode.TextFlags |= TextFlags.OverflowHidden | TextFlags.Ellipsis; _categoryNameTextNode.TextFlags |= TextFlags.OverflowHidden | TextFlags.Ellipsis;
_categoryNameTextNode.TextFlags &= ~(TextFlags.WordWrap | TextFlags.MultiLine); _categoryNameTextNode.TextFlags &= ~(TextFlags.WordWrap | TextFlags.MultiLine);
_categoryNameTextNode.AddFlags(NodeFlags.EmitsEvents | NodeFlags.HasCollision); _categoryNameTextNode.AddNodeFlags(NodeFlags.EmitsEvents | NodeFlags.HasCollision);
_categoryNameTextNode.AttachNode(this); _categoryNameTextNode.AttachNode(this);
_itemGridNode = new HybridDirectionalFlexNode<DragDropNode> _itemGridNode = new HybridDirectionalFlexNode<DragDropNode>
@@ -18,7 +18,7 @@ public class InventoryDragDropNode : DragDropNode
Size = new Vector2(40.0f, 12.0f), Size = new Vector2(40.0f, 12.0f),
Position = new Vector2(4.0f, 34.0f), Position = new Vector2(4.0f, 34.0f),
NodeFlags = NodeFlags.Enabled | NodeFlags.EmitsEvents, NodeFlags = NodeFlags.Enabled | NodeFlags.EmitsEvents,
Color = ColorHelper.GetColor(50), TextColor = ColorHelper.GetColor(50),
TextOutlineColor = ColorHelper.GetColor(51), TextOutlineColor = ColorHelper.GetColor(51),
TextFlags = TextFlags.Edge, TextFlags = TextFlags.Edge,
AlignmentType = AlignmentType.Right, AlignmentType = AlignmentType.Right,
@@ -5,7 +5,7 @@ using AetherBags.Nodes.Currency;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes; using KamiToolKit.Classes;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
using Lumina.Text.ReadOnly;
using static AetherBags.Inventory.State.InventoryStateBase; using static AetherBags.Inventory.State.InventoryStateBase;
namespace AetherBags.Nodes.Inventory; namespace AetherBags.Nodes.Inventory;
@@ -33,7 +33,8 @@ public sealed class InventoryFooterNode : SimpleComponentNode
{ {
Position = new Vector2(0, 0), Position = new Vector2(0, 0),
Size = new Vector2(120, 28), Size = new Vector2(120, 28),
IsVisible = System.Config.Currency.Enabled IsVisible = System.Config.Currency.Enabled,
ItemSpacing = 12f,
}; };
_currencyListNode.AttachNode(this); _currencyListNode.AttachNode(this);
@@ -42,9 +43,13 @@ public sealed class InventoryFooterNode : SimpleComponentNode
public void RefreshCurrencies() public void RefreshCurrencies()
{ {
_currencyListNode.IsVisible = System.Config.Currency.Enabled; var config = System.Config.Currency;
_currencyListNode.IsVisible = config.Enabled;
IReadOnlyList<CurrencyInfo> currencyInfoList = GetCurrencyInfoList([1, 28, 0xFFFF_FFFE, 0xFFFF_FFFD]); if (!config.Enabled) return;
//IReadOnlyList<CurrencyInfo> currencyInfoList = GetCurrencyInfoList([1, 28, 0xFFFF_FFFE, 0xFFFF_FFFD]);
IReadOnlyList<CurrencyInfo> currencyInfoList = GetCurrencyInfoList(config.DisplayedCurrencies.ToArray());
_currencyListNode.SyncWithListDataByKey<CurrencyInfo, CurrencyNode, uint>( _currencyListNode.SyncWithListDataByKey<CurrencyInfo, CurrencyNode, uint>(
dataList: currencyInfoList, dataList: currencyInfoList,
getKeyFromData: currencyInfo => currencyInfo.ItemId, getKeyFromData: currencyInfo => currencyInfo.ItemId,
@@ -60,7 +65,7 @@ public sealed class InventoryFooterNode : SimpleComponentNode
}); });
} }
public string SlotAmountText public ReadOnlySeString SlotAmountText
{ {
get => _slotAmountTextNode.String; get => _slotAmountTextNode.String;
set => _slotAmountTextNode.String = value; set => _slotAmountTextNode.String = value;
@@ -2,8 +2,8 @@ using System.Numerics;
using AetherBags.Inventory.Context; using AetherBags.Inventory.Context;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes; using KamiToolKit.Classes;
using KamiToolKit.Classes.Timelines;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
using KamiToolKit.Timelines;
namespace AetherBags.Nodes.Inventory; namespace AetherBags.Nodes.Inventory;
@@ -13,8 +13,6 @@ public sealed class InventoryNotificationNode : SimpleComponentNode
private readonly TextNode titleTextNode; private readonly TextNode titleTextNode;
private readonly TextNode messageTextNode; private readonly TextNode messageTextNode;
private static readonly InventoryNotificationState NotificationState = new();
public InventoryNotificationNode() public InventoryNotificationNode()
{ {
AddTimeline(ParentLabels); AddTimeline(ParentLabels);
@@ -76,8 +74,8 @@ public sealed class InventoryNotificationNode : SimpleComponentNode
{ {
field = value; field = value;
titleTextNode.SeString = value.Title; titleTextNode.String = value.Title;
messageTextNode.SeString = value.Message; messageTextNode.String = value.Message;
if (value.Title.IsEmpty && value.Message.IsEmpty) if (value.Title.IsEmpty && value.Message.IsEmpty)
{ {
@@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
using AetherBags.Inventory.Items; using AetherBags.Inventory.Items;
using AetherBags.Nodes.Layout; using AetherBags.Nodes.Layout;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
namespace AetherBags.Nodes.Inventory; namespace AetherBags.Nodes.Inventory;
@@ -62,7 +63,7 @@ public class LootedItemsCategoryNode : InventoryCategoryNodeBase
AlignmentType = AlignmentType.Left, AlignmentType = AlignmentType.Left,
String = "Recently Looted", String = "Recently Looted",
TextFlags = TextFlags.OverflowHidden | TextFlags.Ellipsis, TextFlags = TextFlags.OverflowHidden | TextFlags.Ellipsis,
TextColor = new Vector4(0.9f, 0.8f, 0.5f, 1.0f), // Gold-ish color TextColor = ColorHelper.GetColor(26), // Gold-ish color
}; };
_headerTextNode.AddEvent(AtkEventType.MouseOver, BeginHeaderHover); _headerTextNode.AddEvent(AtkEventType.MouseOver, BeginHeaderHover);
@@ -71,7 +72,7 @@ public class LootedItemsCategoryNode : InventoryCategoryNodeBase
_headerTextNode.TextFlags |= TextFlags.OverflowHidden | TextFlags.Ellipsis; _headerTextNode.TextFlags |= TextFlags.OverflowHidden | TextFlags.Ellipsis;
_headerTextNode.TextFlags &= ~(TextFlags.WordWrap | TextFlags.MultiLine); _headerTextNode.TextFlags &= ~(TextFlags.WordWrap | TextFlags.MultiLine);
_headerTextNode.AddFlags(NodeFlags.EmitsEvents | NodeFlags.HasCollision); _headerTextNode.AddNodeFlags(NodeFlags.EmitsEvents | NodeFlags.HasCollision);
_headerTextNode.AttachNode(this); _headerTextNode.AttachNode(this);
_clearButton = new CircleButtonNode _clearButton = new CircleButtonNode
@@ -197,7 +198,7 @@ public class LootedItemsCategoryNode : InventoryCategoryNodeBase
private void SyncItemGrid() private void SyncItemGrid()
{ {
_itemGridNode.SyncWithListDataByKey<LootedItemInfo, LootedItemDisplayNode, int>( _itemGridNode.SyncWithListDataByKey(
dataList: _lootedItems, dataList: _lootedItems,
getKeyFromData: item => item.Index, getKeyFromData: item => item.Index,
getKeyFromNode: node => node.LootedItem?.Index ?? -1, getKeyFromNode: node => node.LootedItem?.Index ?? -1,
@@ -221,6 +222,7 @@ public class LootedItemsCategoryNode : InventoryCategoryNodeBase
private void OnItemDismissed(LootedItemDisplayNode node) private void OnItemDismissed(LootedItemDisplayNode node)
{ {
if(node.LootedItem is null) return;
int index = node.LootedItem.Index; int index = node.LootedItem.Index;
OnDismissItem?.Invoke(index); OnDismissItem?.Invoke(index);
} }
@@ -1,6 +1,7 @@
using System. Numerics; using System. Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
using Lumina.Text.ReadOnly;
namespace AetherBags.Nodes.Inventory; namespace AetherBags.Nodes.Inventory;
@@ -23,7 +24,7 @@ public class SaddleBagFooterNode : SimpleComponentNode
_slotCounterNode.AttachNode(this); _slotCounterNode.AttachNode(this);
} }
public string SlotAmountText public ReadOnlySeString SlotAmountText
{ {
get => _slotCounterNode.String; get => _slotCounterNode.String;
set => _slotCounterNode.String = $"Slots: {value}"; set => _slotCounterNode.String = $"Slots: {value}";
@@ -0,0 +1,192 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit;
using KamiToolKit.Classes;
using KamiToolKit.Nodes;
using Lumina.Text.ReadOnly;
namespace AetherBags.Nodes.Layout;
public class CollapsibleSectionNode : VerticalListNode
{
protected readonly NineGridNode BackgroundNode;
protected readonly ImageNode ArrowNode;
protected readonly TextNode LabelNode;
protected new readonly CollisionNode CollisionNode;
protected readonly TabbedVerticalListNode ContentNode;
protected readonly SimpleComponentNode HeaderNode;
private bool isCollapsed = true;
private float headerHeight = 28.0f;
public Action? OnToggle;
public TabbedVerticalListNode CollapsibleContent => ContentNode;
public bool IsCollapsed
{
get => isCollapsed;
set { isCollapsed = value; UpdateState(); }
}
public float HeaderHeight
{
get => headerHeight;
set
{
headerHeight = value;
HeaderNode.Height = value;
BackgroundNode.Height = value;
CollisionNode.Height = value;
ArrowNode.Y = (value - ArrowNode.Height) / 2.0f;
LabelNode.Height = value;
RecalculateLayout();
}
}
public uint FontSize { get => LabelNode.FontSize; set => LabelNode.FontSize = value; }
public float TabSize
{
get => ContentNode.TabSize;
set => ContentNode.TabSize = value;
}
public int TabStep
{
get => ContentNode.TabStep;
set => ContentNode.TabStep = value;
}
public bool FitChildWidth
{
get => ContentNode.FitWidth;
set => ContentNode.FitWidth = value;
}
public float NestingIndent
{
get;
set
{
field = value;
ArrowNode.X = value + 4.0f;
LabelNode.X = value + 23.0f;
ContentNode.X = value + 10.0f;
}
}
public CollapsibleSectionNode()
{
FitContents = true;
ItemSpacing = 0.0f;
HeaderNode = new SimpleComponentNode
{
Size = new Vector2(Width, headerHeight)
};
BackgroundNode = new SimpleNineGridNode {
TexturePath = "ui/uld/ListItemB.tex",
TextureSize = new Vector2(48.0f, 28.0f),
TextureCoordinates = new Vector2(0.0f, 24.0f),
Size = new Vector2(Width, headerHeight),
TopOffset = 10, LeftOffset = 12, RightOffset = 12, BottomOffset = 12,
Color = new Vector4(0.9f, 0.9f, 0.9f, 1.0f)
};
BackgroundNode.AttachNode(HeaderNode);
ArrowNode = new ImageNode { Position = new Vector2(4.0f, 2.0f), Size = new Vector2(24.0f, 24.0f) };
ArrowNode.AddPart(
new Part { TexturePath = "ui/uld/ListItemB.tex", TextureCoordinates = new Vector2(0, 0), Size = new Vector2(24, 24), Id = 0 },
new Part { TexturePath = "ui/uld/ListItemB.tex", TextureCoordinates = new Vector2(24, 0), Size = new Vector2(24, 24), Id = 1 }
);
ArrowNode.AttachNode(HeaderNode);
LabelNode = new TextNode {
Position = new Vector2(30.0f, 0.0f),
Size = new Vector2(Width - 23, headerHeight),
FontSize = 12,
FontType = FontType.Axis,
AlignmentType = AlignmentType.Left,
TextColor = ColorHelper.GetColor(50),
};
LabelNode.AttachNode(HeaderNode);
CollisionNode = new CollisionNode
{
Size = new Vector2(Width, headerHeight),
ShowClickableCursor = true
};
CollisionNode.AddEvent(AtkEventType.MouseClick, () => {
IsCollapsed = !IsCollapsed;
OnToggle?.Invoke();
});
CollisionNode.AttachNode(HeaderNode);
ContentNode = new TabbedVerticalListNode {
IsVisible = false,
X = 18.0f,
ItemVerticalSpacing = 4.0f,
TabSize = 18.0f,
FitWidth = true,
};
base.AddNode([HeaderNode, ContentNode]);
UpdateState();
}
public void RefreshLayout()
{
ContentNode.RecalculateLayout();
RecalculateLayout();
OnToggle?.Invoke();
}
private void UpdateState()
{
ContentNode.IsVisible = !isCollapsed;
ArrowNode.PartId = isCollapsed ? 0u : 1u;
if (!isCollapsed)
{
ContentNode.Width = Math.Max(0, Width - ContentNode.X);
ContentNode.RecalculateLayout();
}
RecalculateLayout();
OnToggle?.Invoke();
}
public void AddTab(int tabAmount = 1) => ContentNode.AddTab(tabAmount);
public void SubtractTab(int tabAmount = 1) => ContentNode.SubtractTab(tabAmount);
public new void AddNode(NodeBase node) => ContentNode.AddNode(node);
public new void AddNode(IEnumerable<NodeBase> nodes) => ContentNode.AddNode(nodes);
public void AddNode(int tabIndex, NodeBase node) => ContentNode.AddNode(tabIndex, node);
public void AddNode(int tabIndex, IEnumerable<NodeBase> nodes) => ContentNode.AddNode(tabIndex, nodes);
public new void RemoveNode(NodeBase node) => ContentNode.RemoveNode(node);
public new void Clear() => ContentNode.Clear();
protected override void OnSizeChanged()
{
base.OnSizeChanged();
if (BackgroundNode == null || LabelNode == null || CollisionNode == null) return;
HeaderNode.Width = Width;
BackgroundNode.Width = Width;
LabelNode.Width = Math.Max(0, Width - LabelNode.X);
CollisionNode.Width = Width;
ContentNode.Width = Math.Max(0, Width - ContentNode.X);
}
public ReadOnlySeString String { get => LabelNode.String; set => LabelNode.String = value; }
}
@@ -49,9 +49,9 @@ public abstract class DeferrableLayoutListNode : SimpleComponentNode
set set
{ {
if (value) if (value)
AddFlags(NodeFlags.Clip); AddNodeFlags(NodeFlags.Clip);
else else
RemoveFlags(NodeFlags.Clip); RemoveNodeFlags(NodeFlags.Clip);
} }
} }
+6 -8
View File
@@ -1,5 +1,4 @@
using System.Numerics; using System.Numerics;
using AetherBags.AddonLifecycles;
using AetherBags.Addons; using AetherBags.Addons;
using AetherBags.Commands; using AetherBags.Commands;
using AetherBags.Helpers; using AetherBags.Helpers;
@@ -7,6 +6,7 @@ using AetherBags.Hooks;
using AetherBags.Inventory; using AetherBags.Inventory;
using AetherBags.Inventory.Context; using AetherBags.Inventory.Context;
using AetherBags.IPC; using AetherBags.IPC;
using AetherBags.Monitoring;
using Dalamud.Plugin; using Dalamud.Plugin;
using KamiToolKit; using KamiToolKit;
@@ -16,7 +16,7 @@ public class Plugin : IDalamudPlugin
{ {
private readonly CommandHandler _commandHandler; private readonly CommandHandler _commandHandler;
private readonly InventoryHooks _inventoryHooks; private readonly InventoryHooks _inventoryHooks;
private readonly InventoryLifecycles _inventoryLifecycles; private readonly InventoryMonitor inventoryMonitor;
public Plugin(IDalamudPluginInterface pluginInterface) public Plugin(IDalamudPluginInterface pluginInterface)
{ {
@@ -72,14 +72,14 @@ public class Plugin : IDalamudPlugin
} }
_inventoryHooks = new InventoryHooks(); _inventoryHooks = new InventoryHooks();
_inventoryLifecycles = new InventoryLifecycles(); inventoryMonitor = new InventoryMonitor();
} }
public void Dispose() public void Dispose()
{ {
InventoryAddonContextMenu.Close(); InventoryAddonContextMenu.Close();
_inventoryHooks.Dispose(); _inventoryHooks.Dispose();
_inventoryLifecycles.Dispose(); inventoryMonitor.Dispose();
System.LootedItemsTracker.Dispose(); System.LootedItemsTracker.Dispose();
System.IPC.Dispose(); System.IPC.Dispose();
@@ -99,10 +99,8 @@ public class Plugin : IDalamudPlugin
System.Config = Util.LoadConfigOrDefault(); System.Config = Util.LoadConfigOrDefault();
System.LootedItemsTracker.Enable(); System.LootedItemsTracker.Enable();
#if DEBUG System.AddonInventoryWindow.DebugOpen();
System.AddonInventoryWindow.Toggle(); System.AddonConfigurationWindow.DebugOpen();
System.AddonConfigurationWindow.Toggle();
#endif
} }
private void OnLogout(int type, int code) private void OnLogout(int type, int code)
+1
View File
@@ -2,6 +2,7 @@ using AetherBags.Addons;
using AetherBags.Configuration; using AetherBags.Configuration;
using AetherBags.Inventory; using AetherBags.Inventory;
using AetherBags.IPC; using AetherBags.IPC;
using AetherBags.Monitoring;
namespace AetherBags; namespace AetherBags;