164 lines
4.9 KiB
C#
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;
|
|
}
|
|
}
|