From c1986631844825656a43d6e1fe416c590e007b58 Mon Sep 17 00:00:00 2001 From: Zeffuro Date: Mon, 22 Dec 2025 05:42:36 +0100 Subject: [PATCH] More import changes and more options --- AetherBags/Configuration/CategorySettings.cs | 27 ++++++ .../Import/SortaKindaCategory.cs | 36 ++++++-- .../Helpers/Import/SortaKindaImportExport.cs | 83 ++++++++++++++++--- AetherBags/Inventory/InventoryState.cs | 25 +++++- AetherBags/Inventory/ItemInfo.cs | 6 ++ AetherBags/Inventory/UserCategoryMatcher.cs | 18 ++++ 6 files changed, 174 insertions(+), 21 deletions(-) diff --git a/AetherBags/Configuration/CategorySettings.cs b/AetherBags/Configuration/CategorySettings.cs index e4edd15..3aa5d18 100644 --- a/AetherBags/Configuration/CategorySettings.cs +++ b/AetherBags/Configuration/CategorySettings.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Numerics; +using System.Text.Json.Serialization; using KamiToolKit.Classes; namespace AetherBags.Configuration; @@ -33,8 +34,14 @@ public class CategoryRuleSet public List AllowedUiCategoryIds { get; set; } = new(); public List AllowedRarities { get; set; } = new(); + public RangeFilter Level { get; set; } = new() { Enabled = false, Min = 0, Max = 200 }; public RangeFilter ItemLevel { get; set; } = new() { Enabled = false, Min = 0, Max = 2000 }; public RangeFilter VendorPrice { get; set; } = new() { Enabled = false, Min = 0, Max = 9_999_999 }; + public StateFilter Untradable { get; set; } = new(); + public StateFilter Unique { get; set; } = new(); + public StateFilter Collectable { get; set; } = new(); + public StateFilter Dyeable { get; set; } = new(); + public StateFilter Repairable { get; set; } = new(); } public class RangeFilter where T : struct, IComparable @@ -42,4 +49,24 @@ public class RangeFilter where T : struct, IComparable public bool Enabled { get; set; } public T Min { get; set; } public T Max { get; set; } +} + +public class StateFilter +{ + public int State { get; set; } = 0; + public int Filter { get; set; } = 0; + + [JsonIgnore] + public ToggleFilterState ToggleState + { + get => Enum.IsDefined(typeof(ToggleFilterState), State) ? (ToggleFilterState)State : ToggleFilterState.Ignored; + set => State = (int)value; + } +} + +public enum ToggleFilterState +{ + Ignored = 0, + Allow = 1, + Disallow = 2, } \ No newline at end of file diff --git a/AetherBags/Configuration/Import/SortaKindaCategory.cs b/AetherBags/Configuration/Import/SortaKindaCategory.cs index a3a3c97..2241bba 100644 --- a/AetherBags/Configuration/Import/SortaKindaCategory.cs +++ b/AetherBags/Configuration/Import/SortaKindaCategory.cs @@ -3,13 +3,12 @@ using System.Numerics; namespace AetherBags.Configuration.Import; -// Possible Mapping: -// Index -> Order -// Color/Id/Name -// AllowedItemNames -> AllowedItemNamePatterns -// AllowedItemTypes -> AllowedUiCategoryIds -// AllowedItemRarities -> AllowedRarities -// ItemLevelFilter / VendorPriceFilter -> RangeFilter +public sealed class SortaKindaImportFile +{ + public List Rules { get; set; } = new(); + + public object? MainInventory { get; set; } +} public sealed class SortaKindaCategory { @@ -19,15 +18,38 @@ public sealed class SortaKindaCategory public int Index { get; set; } public List AllowedItemNames { get; set; } = new(); + + public List AllowedNameRegexes { get; set; } = new(); + + // Common public List AllowedItemTypes { get; set; } = new(); public List AllowedItemRarities { get; set; } = new(); + public ExternalRangeFilterDto? LevelFilter { get; set; } public ExternalRangeFilterDto ItemLevelFilter { get; set; } = new(); public ExternalRangeFilterDto VendorPriceFilter { get; set; } = new(); + public ExternalStateFilterDto? UntradableFilter { get; set; } + public ExternalStateFilterDto? UniqueFilter { get; set; } + public ExternalStateFilterDto? CollectableFilter { get; set; } + public ExternalStateFilterDto? DyeableFilter { get; set; } + public ExternalStateFilterDto? RepairableFilter { get; set; } + public int Direction { get; set; } public int FillMode { get; set; } public int SortMode { get; set; } + public bool InclusiveAnd { get; set; } +} + +public sealed class AllowedNameRegexDto +{ + public string Text { get; set; } = string.Empty; +} + +public sealed class ExternalStateFilterDto +{ + public int State { get; set; } + public int Filter { get; set; } } public sealed class ExternalRangeFilterDto where T : struct diff --git a/AetherBags/Helpers/Import/SortaKindaImportExport.cs b/AetherBags/Helpers/Import/SortaKindaImportExport.cs index 7826397..84b28fc 100644 --- a/AetherBags/Helpers/Import/SortaKindaImportExport.cs +++ b/AetherBags/Helpers/Import/SortaKindaImportExport.cs @@ -52,7 +52,19 @@ public static class SortaKindaImportExport return false; } - var external = Util.DeserializeCompressed(input.Trim(), ExternalJsonOptions); + string trimmed = input.Trim(); + + SortaKindaCategory[]? external = null; + + SortaKindaImportFile? file = Util.DeserializeCompressed(trimmed, ExternalJsonOptions); + if (file?.Rules is { Count: > 0 }) + { + external = file.Rules.ToArray(); + } + else + { + external = Util.DeserializeCompressed(trimmed, ExternalJsonOptions); + } if (external is null) { @@ -104,10 +116,15 @@ public static class SortaKindaImportExport public static string ExportToJson(SystemConfiguration sourceConfig) { - var exported = sourceConfig.Categories.UserCategories - .OrderBy(c => c.Order) - .Select(MapToExternal) - .ToArray(); + var exported = new SortaKindaImportFile + { + Rules = sourceConfig.Categories.UserCategories + .OrderBy(c => c.Priority) + .Select(MapToExternal) + .ToList(), + + // MainInventory = new { InventoryConfigs = new[] { new { } } } + }; return Util.SerializeCompressed(exported, ExternalJsonOptions); } @@ -122,14 +139,28 @@ public static class SortaKindaImportExport Name = external.Name, Description = string.Empty, Order = external.Index, - Priority = 100, + Priority = external.Index, Color = external.Color, Rules = new CategoryRuleSet { AllowedItemIds = new List(), - AllowedItemNamePatterns = external.AllowedItemNames?.ToList() ?? new List(), + + AllowedItemNamePatterns = + (external.AllowedItemNames ?? new List()) + .Concat((external.AllowedNameRegexes ?? new List()) + .Select(r => r.Text) + .Where(t => !string.IsNullOrWhiteSpace(t))) + .ToList(), + AllowedUiCategoryIds = external.AllowedItemTypes?.ToList() ?? new List(), AllowedRarities = external.AllowedItemRarities?.ToList() ?? new List(), + + Level = new RangeFilter + { + Enabled = external.LevelFilter?.Enable ?? false, + Min = external.LevelFilter?.MinValue ?? 0, + Max = external.LevelFilter?.MaxValue ?? 200, + }, ItemLevel = new RangeFilter { Enabled = external.ItemLevelFilter?.Enable ?? false, @@ -141,7 +172,13 @@ public static class SortaKindaImportExport Enabled = external.VendorPriceFilter?.Enable ?? false, Min = external.VendorPriceFilter?.MinValue ?? 0u, Max = external.VendorPriceFilter?.MaxValue ?? 9_999_999u, - } + }, + + Untradable = new StateFilter { State = external.UntradableFilter?.State ?? 0, Filter = external.UntradableFilter?.Filter ?? 0 }, + Unique = new StateFilter { State = external.UniqueFilter?.State ?? 0, Filter = external.UniqueFilter?.Filter ?? 0 }, + Collectable= new StateFilter { State = external.CollectableFilter?.State ?? 0,Filter = external.CollectableFilter?.Filter ?? 0 }, + Dyeable = new StateFilter { State = external.DyeableFilter?.State ?? 0, Filter = external.DyeableFilter?.Filter ?? 0 }, + Repairable = new StateFilter { State = external.RepairableFilter?.State ?? 0, Filter = external.RepairableFilter?.Filter ?? 0 }, } }; @@ -151,10 +188,26 @@ public static class SortaKindaImportExport Color = internalCat.Color, Id = internalCat.Id, Name = internalCat.Name, - Index = internalCat.Order, - AllowedItemNames = internalCat.Rules.AllowedItemNamePatterns?.ToList() ?? new List(), + Index = internalCat.Priority, + + AllowedItemNames = new List(), + AllowedNameRegexes = + (internalCat.Rules.AllowedItemNamePatterns ?? new List()) + .Where(s => !string.IsNullOrWhiteSpace(s)) + .Select(s => new AllowedNameRegexDto { Text = s }) + .ToList(), + AllowedItemTypes = internalCat.Rules.AllowedUiCategoryIds?.ToList() ?? new List(), AllowedItemRarities = internalCat.Rules.AllowedRarities?.ToList() ?? new List(), + + LevelFilter = new ExternalRangeFilterDto + { + Enable = internalCat.Rules.Level.Enabled, + Label = "Level Filter", + MinValue = internalCat.Rules.Level.Min, + MaxValue = internalCat.Rules.Level.Max + }, + ItemLevelFilter = new ExternalRangeFilterDto { Enable = internalCat.Rules.ItemLevel.Enabled, @@ -169,8 +222,16 @@ public static class SortaKindaImportExport MinValue = internalCat.Rules.VendorPrice.Min, MaxValue = internalCat.Rules.VendorPrice.Max }, + + UntradableFilter = new ExternalStateFilterDto { State = internalCat.Rules.Untradable.State, Filter = internalCat.Rules.Untradable.Filter }, + UniqueFilter = new ExternalStateFilterDto { State = internalCat.Rules.Unique.State, Filter = internalCat.Rules.Unique.Filter }, + CollectableFilter= new ExternalStateFilterDto { State = internalCat.Rules.Collectable.State,Filter = internalCat.Rules.Collectable.Filter }, + DyeableFilter = new ExternalStateFilterDto { State = internalCat.Rules.Dyeable.State, Filter = internalCat.Rules.Dyeable.Filter }, + RepairableFilter = new ExternalStateFilterDto { State = internalCat.Rules.Repairable.State, Filter = internalCat.Rules.Repairable.Filter }, + Direction = 0, FillMode = 0, - SortMode = 0 + SortMode = 0, + InclusiveAnd = false, }; } \ No newline at end of file diff --git a/AetherBags/Inventory/InventoryState.cs b/AetherBags/Inventory/InventoryState.cs index 5df7996..32dd315 100644 --- a/AetherBags/Inventory/InventoryState.cs +++ b/AetherBags/Inventory/InventoryState.cs @@ -56,6 +56,8 @@ public static unsafe class InventoryState private static readonly List FilteredCategories = new(capacity: 256); + private static readonly List UserCategoriesSortedScratch = new(capacity: 64); + private static readonly List RemoveKeysScratch = new(capacity: 256); private const uint UserCategoryKeyFlag = 0x8000_0000; @@ -149,9 +151,22 @@ public static unsafe class InventoryState if (userCategoriesEnabled && userCategories.Count > 0) { - for (int c = 0; c < userCategories.Count; c++) + UserCategoriesSortedScratch.Clear(); + UserCategoriesSortedScratch.AddRange(userCategories); + UserCategoriesSortedScratch.Sort((a, b) => { - UserCategoryDefinition category = userCategories[c]; + int p = b.Priority.CompareTo(a.Priority); + if (p != 0) return p; + + int o = a.Order.CompareTo(b.Order); + if (o != 0) return o; + + return string.Compare(a.Id, b.Id, StringComparison.OrdinalIgnoreCase); + }); + + for (int c = 0; c < UserCategoriesSortedScratch.Count; c++) + { + UserCategoryDefinition category = UserCategoriesSortedScratch[c]; uint key = MakeUserCategoryKey(category.Order); if (!BucketsByKey.TryGetValue(key, out CategoryBucket? bucket)) @@ -182,11 +197,15 @@ public static unsafe class InventoryState foreach (var itemKvp in ItemInfoByItemId) { ItemInfo item = itemKvp.Value; + uint itemId = item.Item.ItemId; + + if (claimedItemIds.Contains(itemId)) + continue; if (UserCategoryMatcher.Matches(item, category)) { bucket.Items.Add(item); - claimedItemIds.Add(item.Item.ItemId); + claimedItemIds.Add(itemId); } } diff --git a/AetherBags/Inventory/ItemInfo.cs b/AetherBags/Inventory/ItemInfo.cs index 80ac4dc..cb98128 100644 --- a/AetherBags/Inventory/ItemInfo.cs +++ b/AetherBags/Inventory/ItemInfo.cs @@ -47,6 +47,12 @@ public sealed class ItemInfo : IEquatable public RowRef UiCategory => Row.ItemUICategory; + public bool IsUntradable => Row.IsUntradable; + public bool IsUnique => Row.IsUnique; + public bool IsCollectable => Row.IsCollectable; + public bool IsDyeable => Row.DyeCount > 0; + public bool IsRepairable => Row.ItemRepair.RowId != 0; + private string Description => _description ??= Row.Description.ToString(); public bool IsRegexMatch(string searchTerms) diff --git a/AetherBags/Inventory/UserCategoryMatcher.cs b/AetherBags/Inventory/UserCategoryMatcher.cs index 35b421e..3caf34a 100644 --- a/AetherBags/Inventory/UserCategoryMatcher.cs +++ b/AetherBags/Inventory/UserCategoryMatcher.cs @@ -24,12 +24,21 @@ internal static class UserCategoryMatcher if (rules.AllowedRarities.Count > 0 && !rules.AllowedRarities.Contains(item.Rarity)) return false; + if (rules.Level.Enabled && !InRange(item.Level, rules.Level.Min, rules.Level.Max)) + return false; + if (rules.ItemLevel.Enabled && !InRange(item.ItemLevel, rules.ItemLevel.Min, rules.ItemLevel.Max)) return false; if (rules.VendorPrice.Enabled && !InRange(item.VendorPrice, rules.VendorPrice.Min, rules.VendorPrice.Max)) return false; + if (!MatchesToggle(rules.Untradable, item.IsUntradable)) return false; + if (!MatchesToggle(rules.Unique, item.IsUnique)) return false; + if (!MatchesToggle(rules.Collectable, item.IsCollectable)) return false; + if (!MatchesToggle(rules.Dyeable, item.IsDyeable)) return false; + if (!MatchesToggle(rules.Repairable, item.IsRepairable)) return false; + if (rules.AllowedItemNamePatterns.Count > 0) { bool any = false; @@ -63,4 +72,13 @@ internal static class UserCategoryMatcher private static bool InRange(T value, T min, T max) where T : struct, IComparable => value.CompareTo(min) >= 0 && value.CompareTo(max) <= 0; + + private static bool MatchesToggle(StateFilter filter, bool itemHasProperty) + => filter.ToggleState switch + { + ToggleFilterState.Ignored => true, + ToggleFilterState.Allow => itemHasProperty, + ToggleFilterState.Disallow => !itemHasProperty, + _ => true + }; } \ No newline at end of file