Improve category matching

This commit is contained in:
Zeffuro
2025-12-28 15:26:49 +01:00
parent 98742482e3
commit e46be61b1c
6 changed files with 101 additions and 34 deletions
@@ -119,6 +119,7 @@ public class AddonCategoryConfigurationWindow : NativeAddon
listNode.AddOption(newWrapper);
RefreshSelectionList();
System.AddonInventoryWindow.ManualInventoryRefresh();
}
private void OnRemoveCategory(CategoryWrapper categoryWrapper)
@@ -134,6 +135,7 @@ public class AddonCategoryConfigurationWindow : NativeAddon
{
OnOptionChanged(null);
}
System.AddonInventoryWindow.ManualInventoryRefresh();
}
private void RefreshSelectionList()
+5 -1
View File
@@ -1,5 +1,8 @@
using AetherBags.Configuration;
using AetherBags.Inventory;
using Dalamud.Game.Text.SeStringHandling;
using KamiToolKit.Premade;
using SeStringBuilder = Lumina.Text.SeStringBuilder;
namespace AetherBags.Addons;
@@ -13,7 +16,8 @@ public class CategoryWrapper(UserCategoryDefinition categoryDefinition) : IInfoN
}
public string GetSubLabel() {
return CategoryDefinition!.Enabled ? "Enabled" : "Disabled";
if(UserCategoryMatcher.IsCatchAll(CategoryDefinition!)) return " No valid rules!";
return CategoryDefinition!.Enabled ? "✓ Enabled" : " Disabled";
}
public uint? GetId() => null;
@@ -54,6 +54,13 @@ public static class CategoryBucketManager
for (int i = 0; i < sortedScratch.Count; i++)
{
UserCategoryDefinition category = sortedScratch[i];
if (!category.Enabled)
continue;
if (UserCategoryMatcher.IsCatchAll(category))
continue;
uint bucketKey = MakeUserCategoryKey(category.Order);
if (!bucketsByKey.TryGetValue(bucketKey, out CategoryBucket? bucket))
+72 -31
View File
@@ -11,6 +11,44 @@ internal static class UserCategoryMatcher
{
var rules = userCategory.Rules;
bool hasIdentificationFilters = rules.AllowedItemIds.Count > 0 || rules.AllowedItemNamePatterns.Count > 0;
if (hasIdentificationFilters)
{
bool matchesAnyIdentification = false;
if (rules.AllowedItemIds.Count > 0 && rules.AllowedItemIds.Contains(item.Item.ItemId))
{
matchesAnyIdentification = true;
}
if (!matchesAnyIdentification && rules.AllowedItemNamePatterns.Count > 0)
{
for (int i = 0; i < rules.AllowedItemNamePatterns.Count; i++)
{
string pattern = rules.AllowedItemNamePatterns[i];
if (string.IsNullOrWhiteSpace(pattern))
continue;
try
{
if (Regex.IsMatch(item.Name, pattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase))
{
matchesAnyIdentification = true;
break;
}
}
catch
{
// Invalid regex: ignore it.
}
}
}
if (!matchesAnyIdentification)
return false;
}
if (rules.AllowedUiCategoryIds.Count > 0)
{
uint uiCategoryId = item.UiCategory.RowId;
@@ -18,9 +56,6 @@ internal static class UserCategoryMatcher
return false;
}
if (rules.AllowedItemIds.Count > 0 && !rules.AllowedItemIds.Contains(item.Item.ItemId))
return false;
if (rules.AllowedRarities.Count > 0 && !rules.AllowedRarities.Contains(item.Rarity))
return false;
@@ -39,40 +74,46 @@ internal static class UserCategoryMatcher
if (!MatchesToggle(rules.Dyeable, item.IsDyeable)) return false;
if (!MatchesToggle(rules.Repairable, item.IsRepairable)) return false;
if (rules.AllowedItemNamePatterns.Count > 0)
{
bool any = false;
for (int i = 0; i < rules.AllowedItemNamePatterns.Count; i++)
{
string pattern = rules.AllowedItemNamePatterns[i];
if (string.IsNullOrWhiteSpace(pattern))
continue;
// Treat patterns as regex for now.
try
{
if (Regex.IsMatch(item.Name, pattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase))
{
any = true;
break;
}
}
catch
{
// Invalid regex: ignore it.
}
}
if (!any)
return false;
}
return true;
}
private static bool InRange<T>(T value, T min, T max) where T : struct, IComparable<T>
=> value.CompareTo(min) >= 0 && value.CompareTo(max) <= 0;
public static bool IsCatchAll(UserCategoryDefinition userCategory)
{
var rules = userCategory.Rules;
if (rules.AllowedItemIds.Count > 0)
return false;
if (rules.AllowedItemNamePatterns.Count > 0)
return false;
if (rules.AllowedUiCategoryIds.Count > 0)
return false;
if (rules.AllowedRarities.Count > 0)
return false;
if (rules.Level.Enabled)
return false;
if (rules.ItemLevel.Enabled)
return false;
if (rules.VendorPrice.Enabled)
return false;
if (rules.Untradable.ToggleState != ToggleFilterState.Ignored)
return false;
if (rules.Unique.ToggleState != ToggleFilterState.Ignored)
return false;
if (rules.Collectable.ToggleState != ToggleFilterState.Ignored)
return false;
if (rules.Dyeable.ToggleState != ToggleFilterState.Ignored)
return false;
if (rules.Repairable.ToggleState != ToggleFilterState.Ignored)
return false;
return true;
}
private static bool MatchesToggle(StateFilter filter, bool itemHasProperty)
=> filter.ToggleState switch
{
@@ -1,6 +1,7 @@
using System;
using System.Numerics;
using AetherBags.Configuration;
using AetherBags.Inventory;
using AetherBags.Nodes.Color;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Component.GUI;
@@ -8,6 +9,7 @@ using KamiToolKit.Classes;
using KamiToolKit.Nodes;
using Lumina.Excel;
using Lumina.Excel.Sheets;
using Lumina.Text;
using Action = System.Action;
namespace AetherBags.Nodes.Configuration.Category;
@@ -65,6 +67,17 @@ public sealed class CategoryDefinitionConfigurationNode : VerticalListNode
FitContents = true;
ItemSpacing = 4.0f;
var catchAllWarningNode = new TextNode
{
Size = new Vector2(300, 40),
TextFlags = TextFlags.MultiLine | TextFlags.AutoAdjustNodeSize,
SeString = new SeStringBuilder().Append(" Warning: No rules configured\nThis category won't match anything!").ToReadOnlySeString(),
TextColor = ColorHelper.GetColor(17),
LineSpacing = 20,
IsVisible = UserCategoryMatcher.IsCatchAll(CategoryDefinition),
};
AddNode(catchAllWarningNode);
AddNode(CreateSectionHeader("Basic Settings"));
_enabledCheckbox = new CheckboxNode
@@ -297,7 +310,7 @@ public sealed class CategoryDefinitionConfigurationNode : VerticalListNode
private static void NotifyChanged()
{
System.AddonInventoryWindow?.ManualInventoryRefresh();
System.AddonInventoryWindow.ManualInventoryRefresh();
}
private void NotifyCategoryPropertyChanged()
@@ -87,7 +87,7 @@ public sealed class InventoryNotificationNode : SimpleComponentNode
Timeline?.PlayAnimation(101);
}
} = new("sdsdsd", "sdsd");
}
// Future Zeff, this always goes on a parent
private Timeline ParentLabels => new TimelineBuilder()