using System; using System.Collections.Generic; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; using LLib.GameData; using Lumina.Excel; using Lumina.Excel.Sheets; namespace LLib.Gear; public sealed class BestItemFinder { public static readonly IReadOnlyList ArmoryAndEquipped = new global::_003C_003Ez__ReadOnlyArray(new InventoryType[12] { InventoryType.EquippedItems, InventoryType.ArmoryMainHand, InventoryType.ArmoryOffHand, InventoryType.ArmoryHead, InventoryType.ArmoryBody, InventoryType.ArmoryHands, InventoryType.ArmoryLegs, InventoryType.ArmoryFeets, InventoryType.ArmoryEar, InventoryType.ArmoryNeck, InventoryType.ArmoryWrist, InventoryType.ArmoryRings }); public static readonly IReadOnlyList ArmoryEquippedAndBags = new global::_003C_003Ez__ReadOnlyArray(new InventoryType[16] { InventoryType.EquippedItems, InventoryType.ArmoryMainHand, InventoryType.ArmoryOffHand, InventoryType.ArmoryHead, InventoryType.ArmoryBody, InventoryType.ArmoryHands, InventoryType.ArmoryLegs, InventoryType.ArmoryFeets, InventoryType.ArmoryEar, InventoryType.ArmoryNeck, InventoryType.ArmoryWrist, InventoryType.ArmoryRings, InventoryType.Inventory1, InventoryType.Inventory2, InventoryType.Inventory3, InventoryType.Inventory4 }); private readonly GearStatsCalculator _calculator; private readonly ExcelSheet _itemSheet; private readonly ExcelSheet _classJobCategorySheet; public BestItemFinder(GearStatsCalculator calculator, IDataManager dataManager) { ArgumentNullException.ThrowIfNull(calculator, "calculator"); ArgumentNullException.ThrowIfNull(dataManager, "dataManager"); _calculator = calculator; _itemSheet = dataManager.GetExcelSheet(); _classJobCategorySheet = dataManager.GetExcelSheet(); } public unsafe BestItemRef? FindBest(EClassJob job, IReadOnlyList acceptedSlotCategories, StatPriority priority, IReadOnlyList containers, byte playerLevel, EItemRankingMode rankingMode, BestItemRef? excludeContainerSlot = null) { BestItemRef? result = null; ItemRankingKey best = ItemRankingKey.None; EBaseParam param = (job.DealsMagicDamage() ? EBaseParam.DamageMag : EBaseParam.DamagePhys); InventoryManager* ptr = InventoryManager.Instance(); if (ptr == null) { return null; } foreach (InventoryType container in containers) { InventoryContainer* inventoryContainer = ptr->GetInventoryContainer(container); if (inventoryContainer == null) { continue; } for (int i = 0; i < inventoryContainer->Size; i++) { InventoryItem* inventorySlot = inventoryContainer->GetInventorySlot(i); if (inventorySlot == null || inventorySlot->ItemId == 0) { continue; } if (excludeContainerSlot.HasValue) { BestItemRef valueOrDefault = excludeContainerSlot.GetValueOrDefault(); if (valueOrDefault.Container == container && valueOrDefault.Slot == i) { continue; } } Item? rowOrDefault = _itemSheet.GetRowOrDefault(inventorySlot->ItemId); if (!rowOrDefault.HasValue) { continue; } Item value = rowOrDefault.Value; if (IsAcceptedSlot(value.EquipSlotCategory.RowId, acceptedSlotCategories) && value.LevelEquip <= playerLevel && IsItemForJob(value, job)) { EquipmentStats equipmentStats = _calculator.CalculateGearStats(inventorySlot); ItemRankingKey itemRankingKey = new ItemRankingKey(value.LevelItem.RowId, priority.Score(equipmentStats), equipmentStats.Get(param)); if (IsUpgrade(rankingMode, itemRankingKey, best)) { result = new BestItemRef(container, i, inventorySlot->ItemId, itemRankingKey.ItemLevel, itemRankingKey.Score); best = itemRankingKey; } } } } return result; } public static bool IsUpgrade(EItemRankingMode mode, ItemRankingKey candidate, ItemRankingKey best) { if (mode == EItemRankingMode.DamageFirst && candidate.Damage != best.Damage) { return candidate.Damage > best.Damage; } if (mode == EItemRankingMode.ItemLevelFirst) { if (candidate.ItemLevel != best.ItemLevel) { return candidate.ItemLevel > best.ItemLevel; } return candidate.Score > best.Score; } if (candidate.Score != best.Score) { return candidate.Score > best.Score; } return candidate.ItemLevel > best.ItemLevel; } public bool IsItemForJob(Item item, EClassJob job) { uint rowId = item.ClassJobCategory.RowId; if (rowId == 0) { return true; } ClassJobCategory? rowOrDefault = _classJobCategorySheet.GetRowOrDefault(rowId); if (!rowOrDefault.HasValue) { return false; } return job.IsInCategory(rowOrDefault.Value); } private static bool IsAcceptedSlot(uint itemSlotCategory, IReadOnlyList accepted) { foreach (uint item in accepted) { if (item == itemSlotCategory) { return true; } } return false; } }