using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using PhoenixLib.Caching; using PhoenixLib.Logging; using WingsAPI.Communication.Bazaar; using WingsAPI.Data.Bazaar; using WingsAPI.Data.GameData; using WingsAPI.Game.Extensions.Bazaar; using WingsAPI.Packets.Enums.Bazaar; using WingsAPI.Packets.Enums.Bazaar.Filter; using WingsAPI.Packets.Enums.Bazaar.SubFilter; using WingsEmu.Core.Generics; using WingsEmu.DTOs.Items; using WingsEmu.Game._enum; using WingsEmu.Packets.Enums; using WingsEmu.Packets.Enums.Battle; using WingsEmu.Packets.Enums.Character; namespace BazaarServer.Managers { public class BazaarSearchManager { private readonly ILongKeyCachedRepository> _bazaarItemByItemVnum; private readonly ILongKeyCachedRepository<(ItemDTO, BazaarLevelFilterType)> _cachedItems; private readonly IResourceLoader _itemDao; private readonly Dictionary>> _itemVnumByCategoryAndSubCategory = new(); private readonly ThreadSafeHashSet _itemVnumsRegistered = new(); public BazaarSearchManager(ILongKeyCachedRepository> bazaarItemByItemVnum, IResourceLoader itemDao, ILongKeyCachedRepository<(ItemDTO, BazaarLevelFilterType)> cachedItems) { _bazaarItemByItemVnum = bazaarItemByItemVnum; _itemDao = itemDao; _cachedItems = cachedItems; } public async Task Initialize(IEnumerable bazaarItemDtos) { await CategorizeItems(); await CacheSearchableBazaarItems(bazaarItemDtos); } private async Task CacheSearchableBazaarItems(IEnumerable bazaarItemDtos) { Log.Info("[BAZAAR_SEARCH_MANAGER] Caching searchable BazaarItems"); int count = 0; foreach (BazaarItemDTO bazaarItemDto in bazaarItemDtos) { if (bazaarItemDto.GetBazaarItemStatus() != BazaarListedItemType.ForSale) { continue; } AddItem(bazaarItemDto); count++; } Log.Info($"[BAZAAR_SEARCH_MANAGER] Cached: {count} searchable BazaarItems"); } private async Task CategorizeItems() { Log.Info("[BAZAAR_SEARCH_MANAGER] Categorizing items from DB"); IEnumerable items = await _itemDao.LoadAsync(); int count = 0; var itemsToRegister = new Dictionary>(); foreach (ItemDTO item in items) { (BazaarCategoryFilterType category, IEnumerable subCategories, bool registerForAllSubcategories) = GetCategoryAndSubCategoriesByItemVnum(item); if (registerForAllSubcategories && !itemsToRegister.TryAdd(category, new HashSet { item.Id })) { itemsToRegister[category].Add(item.Id); } if (!_itemVnumByCategoryAndSubCategory.ContainsKey(category)) { _itemVnumByCategoryAndSubCategory.Add(category, new Dictionary>()); } foreach (byte subCategory in subCategories) { if (!_itemVnumByCategoryAndSubCategory[category].ContainsKey(subCategory)) { _itemVnumByCategoryAndSubCategory[category].Add(subCategory, new HashSet()); } _itemVnumByCategoryAndSubCategory[category][subCategory].Add(item.Id); } if (!_itemVnumByCategoryAndSubCategory[category].ContainsKey(0)) { _itemVnumByCategoryAndSubCategory[category].Add(0, new HashSet()); } _itemVnumByCategoryAndSubCategory[category][0].Add(item.Id); _cachedItems.Set(item.Id, (item, GetItemLevelFilterByLevel(item.LevelMinimum, item.IsHeroic))); count++; } foreach (KeyValuePair> itemToRegister in itemsToRegister) { Type type = GetSubCategoryTypeByCategory(itemToRegister.Key); if (type == null) { continue; } foreach (byte value in Enum.GetValues(type)) { if (!_itemVnumByCategoryAndSubCategory[itemToRegister.Key].ContainsKey(value)) { _itemVnumByCategoryAndSubCategory[itemToRegister.Key].Add(value, new HashSet()); } foreach (int vnum in itemToRegister.Value) { _itemVnumByCategoryAndSubCategory[itemToRegister.Key][value].Add(vnum); } } } Log.Info($"[BAZAAR_SEARCH_MANAGER] Categorized: {count.ToString()} items"); } private static Type GetSubCategoryTypeByCategory(BazaarCategoryFilterType category) { switch (category) { case BazaarCategoryFilterType.Specialist: return typeof(BazaarCategorySpecialistSubFilterType); case BazaarCategoryFilterType.Partner: return typeof(BazaarCategoryPartnerSubFilterType); case BazaarCategoryFilterType.Pet: return typeof(BazaarCategoryPetSubFilterType); case BazaarCategoryFilterType.StoreMount: return typeof(BazaarCategoryConsumerItemSubFilterType); default: return null; } } public void AddItem(BazaarItemDTO bazaarItemDto) { ItemInstanceDTO itemInstanceDto = bazaarItemDto?.ItemInstance; if (itemInstanceDto == null) { return; } ConcurrentDictionary dictionary = _bazaarItemByItemVnum.GetOrSet(itemInstanceDto.ItemVNum, () => new ConcurrentDictionary()); dictionary[bazaarItemDto.Id] = bazaarItemDto; _itemVnumsRegistered.Add(itemInstanceDto.ItemVNum); } public void RemoveItem(BazaarItemDTO bazaarItemDto) { if (bazaarItemDto == null) { return; } ConcurrentDictionary dictionary = _bazaarItemByItemVnum.GetOrSet(bazaarItemDto.ItemInstance.ItemVNum, () => new ConcurrentDictionary()); dictionary.Remove(bazaarItemDto.Id, out _); if (!dictionary.IsEmpty) { return; } _bazaarItemByItemVnum.Remove(bazaarItemDto.ItemInstance.ItemVNum); _itemVnumsRegistered.Remove(bazaarItemDto.ItemInstance.ItemVNum); } public IReadOnlyCollection SearchBazaarItems(BazaarSearchContext bazaarSearchContext) { IReadOnlyCollection desiredItemVNums = (bazaarSearchContext.ItemVNumFilter ?? GetItemVNumsByCategoryAndSubCategory(bazaarSearchContext.CategoryFilterType, bazaarSearchContext.SubTypeFilter)) ?? _itemVnumsRegistered.ToArray(); var itemList = new List(); var tempItemList = new List(); int ignoreCounter = bazaarSearchContext.Index * bazaarSearchContext.AmountOfItemsPerIndex; int sendCounter = 0; foreach (int itemVnum in desiredItemVNums.OrderBy(x => x)) { ConcurrentDictionary dictionary = _bazaarItemByItemVnum.Get(itemVnum); if (dictionary == null) { continue; } if (ignoreCounter > dictionary.Count && bazaarSearchContext.LevelFilter == BazaarLevelFilterType.All && bazaarSearchContext.RareFilter == BazaarRarityFilterType.All && bazaarSearchContext.UpgradeFilter == BazaarUpgradeFilterType.All) { ignoreCounter -= dictionary.Count; continue; } (ItemDTO itemDto, BazaarLevelFilterType baseItemLevelFilter) = _cachedItems.Get(itemVnum); bool itemLevelIsInstanceDependant = false; if (baseItemLevelFilter == BazaarLevelFilterType.All) { itemLevelIsInstanceDependant = true; } else { if (bazaarSearchContext.LevelFilter != BazaarLevelFilterType.All && !ItemLevelFilterChecker(bazaarSearchContext.LevelFilter, baseItemLevelFilter)) { continue; } } IOrderedEnumerable> values; switch (bazaarSearchContext.OrderFilter) { case BazaarSortFilterType.PriceAscending: values = dictionary.OrderBy(x => x.Value.PricePerItem); break; case BazaarSortFilterType.PriceDescending: values = dictionary.OrderByDescending(x => x.Value.PricePerItem); break; case BazaarSortFilterType.AmountAscending: values = dictionary.OrderBy(x => x.Value.Amount - x.Value.SoldAmount); break; case BazaarSortFilterType.AmountDescending: values = dictionary.OrderByDescending(x => x.Value.Amount - x.Value.SoldAmount); break; default: return null; } foreach ((long _, BazaarItemDTO bazaarItemDto) in values) { BazaarListedItemType itemStatus = bazaarItemDto.GetBazaarItemStatus(); if (itemStatus != BazaarListedItemType.ForSale) { RemoveItem(bazaarItemDto); continue; } if (bazaarItemDto.ItemInstance.Type == ItemInstanceType.BoxInstance) { BazaarPerfectionFilterType itemRarityFilter = GetPerfectionFilterByInstance(bazaarItemDto.ItemInstance); if ((int)bazaarSearchContext.RareFilter != (int)BazaarPerfectionFilterType.All && (int)itemRarityFilter != (int)bazaarSearchContext.RareFilter) { continue; } } else { BazaarRarityFilterType itemRarityFilter = GetRarityFilterByInstance(bazaarItemDto.ItemInstance); if (bazaarSearchContext.RareFilter != BazaarRarityFilterType.All && itemRarityFilter != bazaarSearchContext.RareFilter) { continue; } } BazaarUpgradeFilterType itemUpgradeFilter = GetUpgradeFilterByInstance(bazaarItemDto.ItemInstance); if (bazaarSearchContext.UpgradeFilter != BazaarUpgradeFilterType.All && itemUpgradeFilter != bazaarSearchContext.UpgradeFilter) { continue; } if (itemLevelIsInstanceDependant) { BazaarLevelFilterType itemLevelFilter = GetItemLevelFilterByInstance(bazaarItemDto.ItemInstance, bazaarItemDto.ItemInstance.Type); if (bazaarSearchContext.LevelFilter != BazaarLevelFilterType.All && itemLevelFilter != bazaarSearchContext.LevelFilter) { continue; } } if (bazaarSearchContext.SubTypeFilter != 0 && bazaarItemDto.ItemInstance.Type == ItemInstanceType.BoxInstance) { if ((UsableItemSubType)itemDto.ItemSubType != UsableItemSubType.RaidBoxOrSealedJajamaruSpOrSealedSakuraBead) { ItemInstanceDTO boxInstanceDto = bazaarItemDto.ItemInstance; if (!GetBoxInstanceSubCategories(boxInstanceDto, itemDto).Contains(bazaarSearchContext.SubTypeFilter)) { continue; } } } if (ignoreCounter > 0) { ignoreCounter--; continue; } if (sendCounter >= bazaarSearchContext.AmountOfItemsPerIndex) { break; } sendCounter++; tempItemList.Add(bazaarItemDto); } itemList.AddRange(tempItemList); tempItemList.Clear(); if (sendCounter >= bazaarSearchContext.AmountOfItemsPerIndex) { break; } } return itemList; } private List GetBoxInstanceSubCategories(ItemInstanceDTO boxInstanceDto, ItemDTO itemInfo) { var subCategory = new List(); switch ((UsableItemSubType)itemInfo.ItemSubType) { case UsableItemSubType.PetBead: subCategory.Add( (byte)(boxInstanceDto.HoldingVNum is 0 or null ? BazaarCategoryPetSubFilterType.EmptyPetBead : BazaarCategoryPetSubFilterType.PetBead)); break; case UsableItemSubType.PartnerBead: if (boxInstanceDto.HoldingVNum is 0 or null) { subCategory.Add((byte)BazaarCategoryPartnerSubFilterType.EmptyPartnerBead); break; } subCategory.Add((byte)BazaarCategoryPartnerSubFilterType.PartnerBead); subCategory.Add((byte)GetPartnerSubCategoryOfAttack(_cachedItems.Get(boxInstanceDto.HoldingVNum.Value).Item1)); break; case UsableItemSubType.PartnerSpHolder: if (boxInstanceDto.HoldingVNum is 0 or null) { subCategory.Add((byte)BazaarCategoryPartnerSubFilterType.EmptyCardHolder); break; } subCategory.Add((byte)GetPartnerSubCategoryOfAttack(_cachedItems.Get(boxInstanceDto.HoldingVNum.Value).Item1)); break; case UsableItemSubType.SpecialistHolder: if (boxInstanceDto.HoldingVNum is 0 or null) { subCategory.Add((byte)BazaarCategorySpecialistSubFilterType.EmptyCardHolder); break; } subCategory.Add(SpMorphToSubCategory(_cachedItems.Get(boxInstanceDto.HoldingVNum.Value).Item1)); break; case UsableItemSubType.VehicleBead: subCategory.Add((byte)(boxInstanceDto.HoldingVNum is 0 or null ? BazaarCategoryStoreMountSubFilterType.EmptyMountBead : BazaarCategoryStoreMountSubFilterType.MountBead)); break; } return subCategory; } private (BazaarCategoryFilterType, IEnumerable, bool) GetCategoryAndSubCategoriesByItemVnum(ItemDTO itemDto) { BazaarCategoryFilterType category = BazaarCategoryFilterType.Miscellaneous; var subCategories = new List(); bool registerForAllSubcategories = false; switch (itemDto.ItemType) { case ItemType.Weapon: category = BazaarCategoryFilterType.Weapon; subCategories.Add(ItemTypeClassToSubCategory(itemDto.Class)); break; case ItemType.Armor: category = BazaarCategoryFilterType.Armour; subCategories.Add(ItemTypeClassToSubCategory(itemDto.Class)); break; case ItemType.Fashion: category = BazaarCategoryFilterType.Equipment; subCategories.Add(EquipmentTypeToSubCategory1(itemDto.EquipmentSlot)); byte? secondSubCategory = EquipmentTypeToSubCategory2(itemDto.EquipmentSlot, itemDto.Sex switch { 0 => GenderType.Unisex, 1 => GenderType.Male, _ => GenderType.Female }); if (secondSubCategory != null) { subCategories.Add(secondSubCategory.Value); } break; case ItemType.Jewelry: category = BazaarCategoryFilterType.Accessories; subCategories.Add(EquipmentTypeToSubCategory3(itemDto.EquipmentSlot)); break; case ItemType.Specialist: category = BazaarCategoryFilterType.Specialist; subCategories.Add(SpMorphToSubCategory(itemDto)); break; case ItemType.Box: var usableItemSubType = (UsableItemSubType)itemDto.ItemSubType; switch (usableItemSubType) { case UsableItemSubType.PetBead: category = BazaarCategoryFilterType.Pet; registerForAllSubcategories = true; break; case UsableItemSubType.PartnerBead: category = BazaarCategoryFilterType.Partner; registerForAllSubcategories = true; break; case UsableItemSubType.PartnerSpHolder: category = BazaarCategoryFilterType.Partner; registerForAllSubcategories = true; break; case UsableItemSubType.SpecialistHolder: category = BazaarCategoryFilterType.Specialist; registerForAllSubcategories = true; break; case UsableItemSubType.FairyBead: category = BazaarCategoryFilterType.Accessories; subCategories.Add((byte)BazaarCategoryAccessoriesSubFilterType.Fairy); break; case UsableItemSubType.VehicleBead: category = BazaarCategoryFilterType.StoreMount; registerForAllSubcategories = true; break; } break; case ItemType.Shell: category = BazaarCategoryFilterType.Shell; var shellItemSubType = (ShellItemSubType)itemDto.ItemSubType; subCategories.Add(shellItemSubType switch { ShellItemSubType.Weapon => (byte)BazaarCategoryShellSubFilterType.Weapon, ShellItemSubType.Armor => (byte)BazaarCategoryShellSubFilterType.Clothing, _ => default }); break; case ItemType.Main: category = BazaarCategoryFilterType.MainItem; subCategories.Add((byte)BazaarCategoryMainItemSubFilterType.GeneralItems); break; case ItemType.Upgrade: category = BazaarCategoryFilterType.MainItem; subCategories.Add((byte)BazaarCategoryMainItemSubFilterType.Material); break; case ItemType.Production: category = BazaarCategoryFilterType.MainItem; subCategories.Add((byte)BazaarCategoryMainItemSubFilterType.ProductionItem); break; case ItemType.Special: category = BazaarCategoryFilterType.MainItem; subCategories.Add((byte)BazaarCategoryMainItemSubFilterType.SpecialItems); break; case ItemType.Potion: category = BazaarCategoryFilterType.MainItem; subCategories.Add((byte)BazaarCategoryMainItemSubFilterType.HealingPotion); break; case ItemType.Event: category = BazaarCategoryFilterType.MainItem; subCategories.Add((byte)BazaarCategoryMainItemSubFilterType.Event); break; case ItemType.Title: category = BazaarCategoryFilterType.MainItem; subCategories.Add((byte)BazaarCategoryMainItemSubFilterType.Title); break; case ItemType.Sell: category = BazaarCategoryFilterType.ConsumerItem; subCategories.Add((byte)BazaarCategoryConsumerItemSubFilterType.SaleItem); break; case ItemType.Food: category = BazaarCategoryFilterType.ConsumerItem; subCategories.Add((byte)BazaarCategoryConsumerItemSubFilterType.Food); break; case ItemType.Snack: category = BazaarCategoryFilterType.ConsumerItem; subCategories.Add((byte)BazaarCategoryConsumerItemSubFilterType.Snack); break; case ItemType.Magical: category = BazaarCategoryFilterType.ConsumerItem; subCategories.Add((byte)BazaarCategoryConsumerItemSubFilterType.MagicItem); break; case ItemType.Material: category = BazaarCategoryFilterType.ConsumerItem; subCategories.Add((byte)BazaarCategoryConsumerItemSubFilterType.Ingredients); break; case ItemType.PetPartnerItem: category = BazaarCategoryFilterType.ConsumerItem; subCategories.Add((byte)BazaarCategoryConsumerItemSubFilterType.PartnerItem); break; } return (category, subCategories, registerForAllSubcategories); } private static byte ItemTypeClassToSubCategory(byte itemTypeClass) { return itemTypeClass switch { (int)ItemClassType.Adventurer => (byte)BazaarCategoryWeaponArmourSubFilterType.Adventurer, (int)ItemClassType.Swordsman => (byte)BazaarCategoryWeaponArmourSubFilterType.Swordsman, (int)ItemClassType.Archer => (byte)BazaarCategoryWeaponArmourSubFilterType.Archer, (int)ItemClassType.Mage => (byte)BazaarCategoryWeaponArmourSubFilterType.Magician, (int)ItemClassType.MartialArtist => (byte)BazaarCategoryWeaponArmourSubFilterType.MartialArtist, _ => default }; } private static byte EquipmentTypeToSubCategory1(EquipmentType equipmentType) { return equipmentType switch { EquipmentType.Hat => (byte)BazaarCategoryEquipmentSubFilterType.Hat, EquipmentType.Mask => (byte)BazaarCategoryEquipmentSubFilterType.Accessory, EquipmentType.Gloves => (byte)BazaarCategoryEquipmentSubFilterType.Gloves, EquipmentType.Boots => (byte)BazaarCategoryEquipmentSubFilterType.Shoes, EquipmentType.CostumeSuit => (byte)BazaarCategoryEquipmentSubFilterType.Costume, EquipmentType.CostumeHat => (byte)BazaarCategoryEquipmentSubFilterType.CostumeHat, EquipmentType.WeaponSkin => (byte)BazaarCategoryEquipmentSubFilterType.CostumeWeapon, EquipmentType.Wings => (byte)BazaarCategoryEquipmentSubFilterType.CostumeWings, _ => default }; } private static byte? EquipmentTypeToSubCategory2(EquipmentType equipmentType, GenderType genderType) { if (genderType == GenderType.Unisex) { return null; } bool isMale = genderType == GenderType.Male; return equipmentType switch { EquipmentType.CostumeSuit => (byte)(isMale ? BazaarCategoryEquipmentSubFilterType.CostumeMale : BazaarCategoryEquipmentSubFilterType.CostumeFemale), EquipmentType.CostumeHat => (byte)(isMale ? BazaarCategoryEquipmentSubFilterType.CostumeHatMale : BazaarCategoryEquipmentSubFilterType.CostumeHatFemale), _ => null }; } private static byte EquipmentTypeToSubCategory3(EquipmentType equipmentType) { return equipmentType switch { EquipmentType.Necklace => (byte)BazaarCategoryAccessoriesSubFilterType.Necklace, EquipmentType.Ring => (byte)BazaarCategoryAccessoriesSubFilterType.Ring, EquipmentType.Bracelet => (byte)BazaarCategoryAccessoriesSubFilterType.Bracelet, EquipmentType.Fairy => (byte)BazaarCategoryAccessoriesSubFilterType.Fairy, EquipmentType.Amulet => (byte)BazaarCategoryAccessoriesSubFilterType.Amulet, _ => default }; } private static byte SpMorphToSubCategory(ItemDTO item) { int morphId = Convert.ToInt32(item.Morph); if (!Enum.IsDefined(typeof(MorphIdType), morphId)) { return default; } var morph = (MorphIdType)morphId; return morph switch { MorphIdType.Pyjama => (byte)BazaarCategorySpecialistSubFilterType.Pyjama, MorphIdType.Warrior => (byte)BazaarCategorySpecialistSubFilterType.Warrior, MorphIdType.Ninja => (byte)BazaarCategorySpecialistSubFilterType.Ninja, MorphIdType.Ranger => (byte)BazaarCategorySpecialistSubFilterType.Ranger, MorphIdType.Assassin => (byte)BazaarCategorySpecialistSubFilterType.Assassin, MorphIdType.RedMagician => (byte)BazaarCategorySpecialistSubFilterType.RedMagician, MorphIdType.HolyMage => (byte)BazaarCategorySpecialistSubFilterType.HolyMage, MorphIdType.Chicken => (byte)BazaarCategorySpecialistSubFilterType.ChickenCostume, MorphIdType.Jajamaru => (byte)BazaarCategorySpecialistSubFilterType.Jajamaru, MorphIdType.Crusader => (byte)BazaarCategorySpecialistSubFilterType.Crusader, MorphIdType.Berserker => (byte)BazaarCategorySpecialistSubFilterType.Berserker, MorphIdType.Destroyer => (byte)BazaarCategorySpecialistSubFilterType.Destroyer, MorphIdType.WildKeeper => (byte)BazaarCategorySpecialistSubFilterType.WildKeeper, MorphIdType.BlueMagician => (byte)BazaarCategorySpecialistSubFilterType.BlueMagician, MorphIdType.DarkGunner => (byte)BazaarCategorySpecialistSubFilterType.DarkGunner, MorphIdType.Pirate => (byte)BazaarCategorySpecialistSubFilterType.Pirate, MorphIdType.Gladiator => (byte)BazaarCategorySpecialistSubFilterType.Gladiator, MorphIdType.FireCannoneer => (byte)BazaarCategorySpecialistSubFilterType.FireCannoneer, MorphIdType.Volcano => (byte)BazaarCategorySpecialistSubFilterType.Volcano, MorphIdType.BattleMonk => (byte)BazaarCategorySpecialistSubFilterType.BattleMonk, MorphIdType.Scout => (byte)BazaarCategorySpecialistSubFilterType.Scout, MorphIdType.TideLord => (byte)BazaarCategorySpecialistSubFilterType.TideLord, MorphIdType.DeathReaper => (byte)BazaarCategorySpecialistSubFilterType.DeathReaper, MorphIdType.DemonHunter => (byte)BazaarCategorySpecialistSubFilterType.DemonHunter, MorphIdType.Seer => (byte)BazaarCategorySpecialistSubFilterType.Seer, MorphIdType.Renegade => (byte)BazaarCategorySpecialistSubFilterType.Renegade, MorphIdType.AvengingAngel => (byte)BazaarCategorySpecialistSubFilterType.AvengingAngel, MorphIdType.Archmage => (byte)BazaarCategorySpecialistSubFilterType.Archmage, MorphIdType.DraconicFist => (byte)BazaarCategorySpecialistSubFilterType.DraconicFist, MorphIdType.MysticArts => (byte)BazaarCategorySpecialistSubFilterType.MysticArts, MorphIdType.WeddingCostume => (byte)BazaarCategorySpecialistSubFilterType.WeddingCostume, MorphIdType.MasterWolf => (byte)BazaarCategorySpecialistSubFilterType.MasterWolf, MorphIdType.DemonWarrior => (byte)BazaarCategorySpecialistSubFilterType.DemonWarrior, _ => default }; } private BazaarLevelFilterType GetItemLevelFilterByInstance(ItemInstanceDTO itemInstanceDto, ItemInstanceType instanceType) { switch (instanceType) { case ItemInstanceType.BoxInstance: case ItemInstanceType.SpecialistInstance: ItemInstanceDTO specialistInstanceDto = itemInstanceDto; return GetItemLevelFilterByLevel(specialistInstanceDto.SpLevel, false); default: (ItemDTO itemDto, BazaarLevelFilterType bazaarLevelFilterType) = _cachedItems.Get(itemInstanceDto.ItemVNum); return itemDto.ItemType == ItemType.Shell ? GetItemLevelFilterByLevel(itemInstanceDto.Upgrade, false) : bazaarLevelFilterType; } } private static BazaarLevelFilterType GetItemLevelFilterByLevel(byte level, bool isHeroic) { if (isHeroic) { if (level is < 1 or > 60) { return BazaarLevelFilterType.ChampionGear; } return (BazaarLevelFilterType)(Convert.ToByte(Math.Ceiling(level / 10f)) + (byte)BazaarLevelFilterType.ChampionGear); } if (level is < 1 or > 99) { return BazaarLevelFilterType.All; } return (BazaarLevelFilterType)Convert.ToByte(Math.Ceiling(level / 10f)); } private static bool ItemLevelFilterChecker(BazaarLevelFilterType demandedFilterType, BazaarLevelFilterType itemFilterType) { return demandedFilterType == itemFilterType || demandedFilterType == BazaarLevelFilterType.All || demandedFilterType == BazaarLevelFilterType.ChampionGear && itemFilterType switch { BazaarLevelFilterType.ChampionLevelOneToTen => true, BazaarLevelFilterType.ChampionLevelElevenToTwenty => true, BazaarLevelFilterType.ChampionLevelTwentyOneToThirty => true, BazaarLevelFilterType.ChampionLevelThirtyOneToForty => true, BazaarLevelFilterType.ChampionLevelFortyOneToFifty => true, BazaarLevelFilterType.ChampionLevelFiftyOneToSixty => true, _ => false }; } private static BazaarUpgradeFilterType GetUpgradeFilterByInstance(ItemInstanceDTO itemInstanceDto) { if (15 < itemInstanceDto.Upgrade) { return BazaarUpgradeFilterType.All; } return (BazaarUpgradeFilterType)(itemInstanceDto.Upgrade + 1); } private static BazaarRarityFilterType GetRarityFilterByInstance(ItemInstanceDTO itemInstanceDto) { if (itemInstanceDto.Rarity is < 0 or > 8) { return BazaarRarityFilterType.All; } return (BazaarRarityFilterType)(itemInstanceDto.Rarity + 1); } private static BazaarPerfectionFilterType GetPerfectionFilterByInstance(ItemInstanceDTO itemInstanceDto) { const int max = 100; return itemInstanceDto.SpStoneUpgrade switch { < 1 or < max => BazaarPerfectionFilterType.All, max => BazaarPerfectionFilterType.NintyOneToHundred, _ => (BazaarPerfectionFilterType)Convert.ToByte(Math.Ceiling(itemInstanceDto.SpStoneUpgrade / 10f)) }; } private static BazaarCategoryPartnerSubFilterType GetPartnerSubCategoryOfAttack(ItemDTO item) { return item.PartnerClass switch { (byte)AttackType.Melee => BazaarCategoryPartnerSubFilterType.CloseAttack, (byte)AttackType.Ranged => BazaarCategoryPartnerSubFilterType.RemoteAttack, (byte)AttackType.Magical => BazaarCategoryPartnerSubFilterType.Magic, _ => BazaarCategoryPartnerSubFilterType.All }; } private IReadOnlyCollection GetItemVNumsByCategoryAndSubCategory(BazaarCategoryFilterType categoryFilterType, byte subTypeFilter) { if (categoryFilterType == BazaarCategoryFilterType.All || !_itemVnumByCategoryAndSubCategory.ContainsKey(categoryFilterType) || !_itemVnumByCategoryAndSubCategory[categoryFilterType].ContainsKey(subTypeFilter)) { return null; } return _itemVnumByCategoryAndSubCategory[categoryFilterType][subTypeFilter]; } } }