qstbak/LLib/LLib.Gear/BestItemFinder.cs
2026-08-17 20:29:32 +10:00

164 lines
4.9 KiB
C#

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<InventoryType> ArmoryAndEquipped = new global::_003C_003Ez__ReadOnlyArray<InventoryType>(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<InventoryType> ArmoryEquippedAndBags = new global::_003C_003Ez__ReadOnlyArray<InventoryType>(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<Item> _itemSheet;
private readonly ExcelSheet<ClassJobCategory> _classJobCategorySheet;
public BestItemFinder(GearStatsCalculator calculator, IDataManager dataManager)
{
ArgumentNullException.ThrowIfNull(calculator, "calculator");
ArgumentNullException.ThrowIfNull(dataManager, "dataManager");
_calculator = calculator;
_itemSheet = dataManager.GetExcelSheet<Item>();
_classJobCategorySheet = dataManager.GetExcelSheet<ClassJobCategory>();
}
public unsafe BestItemRef? FindBest(EClassJob job, IReadOnlyList<uint> acceptedSlotCategories, StatPriority priority, IReadOnlyList<InventoryType> 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<uint> accepted)
{
foreach (uint item in accepted)
{
if (item == itemSlotCategory)
{
return true;
}
}
return false;
}
}