219 lines
7.6 KiB
C#
219 lines
7.6 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Dalamud.Plugin.Services;
|
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
using Lumina.Excel;
|
|
using Lumina.Excel.Sheets;
|
|
|
|
namespace LLib.Gear;
|
|
|
|
public sealed class GearStatsCalculator
|
|
{
|
|
private sealed record MateriaInfo(EBaseParam BaseParam, Collection<short> Values, bool HasItem);
|
|
|
|
private const uint EternityRingItemId = 8575u;
|
|
|
|
private static readonly uint[] CanHaveOffhand = new uint[14]
|
|
{
|
|
2u, 6u, 8u, 12u, 14u, 16u, 18u, 20u, 22u, 24u,
|
|
26u, 28u, 30u, 32u
|
|
};
|
|
|
|
private readonly ExcelSheet<Item> _itemSheet;
|
|
|
|
private readonly Dictionary<(uint ItemLevel, EBaseParam BaseParam), ushort> _itemLevelStatCaps = new Dictionary<(uint, EBaseParam), ushort>();
|
|
|
|
private readonly Dictionary<(EBaseParam BaseParam, int EquipSlotCategory), ushort> _equipSlotCategoryPct;
|
|
|
|
private readonly Dictionary<uint, MateriaInfo> _materiaStats;
|
|
|
|
public GearStatsCalculator(IDataManager? dataManager)
|
|
: this(dataManager?.GetExcelSheet<ItemLevel>() ?? throw new ArgumentNullException("dataManager"), dataManager.GetExcelSheet<ExtendedBaseParam>(), dataManager.GetExcelSheet<Materia>(), dataManager.GetExcelSheet<Item>())
|
|
{
|
|
}
|
|
|
|
public GearStatsCalculator(ExcelSheet<ItemLevel> itemLevelSheet, ExcelSheet<ExtendedBaseParam> baseParamSheet, ExcelSheet<Materia> materiaSheet, ExcelSheet<Item> itemSheet)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(itemLevelSheet, "itemLevelSheet");
|
|
ArgumentNullException.ThrowIfNull(baseParamSheet, "baseParamSheet");
|
|
ArgumentNullException.ThrowIfNull(materiaSheet, "materiaSheet");
|
|
ArgumentNullException.ThrowIfNull(itemSheet, "itemSheet");
|
|
_itemSheet = itemSheet;
|
|
foreach (ItemLevel item in itemLevelSheet)
|
|
{
|
|
_itemLevelStatCaps[(item.RowId, EBaseParam.Strength)] = item.Strength;
|
|
_itemLevelStatCaps[(item.RowId, EBaseParam.Dexterity)] = item.Dexterity;
|
|
_itemLevelStatCaps[(item.RowId, EBaseParam.Vitality)] = item.Vitality;
|
|
_itemLevelStatCaps[(item.RowId, EBaseParam.Intelligence)] = item.Intelligence;
|
|
_itemLevelStatCaps[(item.RowId, EBaseParam.Mind)] = item.Mind;
|
|
_itemLevelStatCaps[(item.RowId, EBaseParam.Piety)] = item.Piety;
|
|
_itemLevelStatCaps[(item.RowId, EBaseParam.GP)] = item.GP;
|
|
_itemLevelStatCaps[(item.RowId, EBaseParam.CP)] = item.CP;
|
|
_itemLevelStatCaps[(item.RowId, EBaseParam.DamagePhys)] = item.PhysicalDamage;
|
|
_itemLevelStatCaps[(item.RowId, EBaseParam.DamageMag)] = item.MagicalDamage;
|
|
_itemLevelStatCaps[(item.RowId, EBaseParam.DefensePhys)] = item.Defense;
|
|
_itemLevelStatCaps[(item.RowId, EBaseParam.DefenseMag)] = item.MagicDefense;
|
|
_itemLevelStatCaps[(item.RowId, EBaseParam.Tenacity)] = item.Tenacity;
|
|
_itemLevelStatCaps[(item.RowId, EBaseParam.Crit)] = item.CriticalHit;
|
|
_itemLevelStatCaps[(item.RowId, EBaseParam.DirectHit)] = item.DirectHitRate;
|
|
_itemLevelStatCaps[(item.RowId, EBaseParam.Determination)] = item.Determination;
|
|
_itemLevelStatCaps[(item.RowId, EBaseParam.SpellSpeed)] = item.SpellSpeed;
|
|
_itemLevelStatCaps[(item.RowId, EBaseParam.SkillSpeed)] = item.SkillSpeed;
|
|
_itemLevelStatCaps[(item.RowId, EBaseParam.Gathering)] = item.Gathering;
|
|
_itemLevelStatCaps[(item.RowId, EBaseParam.Perception)] = item.Perception;
|
|
_itemLevelStatCaps[(item.RowId, EBaseParam.Craftsmanship)] = item.Craftsmanship;
|
|
_itemLevelStatCaps[(item.RowId, EBaseParam.Control)] = item.Control;
|
|
}
|
|
_equipSlotCategoryPct = baseParamSheet.SelectMany((ExtendedBaseParam x) => from y in Enumerable.Range(0, x.EquipSlotCategoryPct.Count)
|
|
select ((EBaseParam)x.RowId, y: y, x.EquipSlotCategoryPct[y])).ToDictionary(((EBaseParam, int y, ushort) x) => (x.Item1, x.y), ((EBaseParam, int y, ushort) x) => x.Item3);
|
|
_materiaStats = materiaSheet.Where((Materia x) => x.RowId != 0 && x.BaseParam.RowId != 0).ToDictionary((Materia x) => x.RowId, (Materia x) => new MateriaInfo((EBaseParam)x.BaseParam.RowId, x.Value, x.Item[0].RowId != 0));
|
|
}
|
|
|
|
public unsafe EquipmentStats CalculateGearStats(InventoryItem* item)
|
|
{
|
|
List<(uint, byte)> list = new List<(uint, byte)>();
|
|
byte b = 0;
|
|
if (item->ItemId != 8575)
|
|
{
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
ushort num = item->Materia[i];
|
|
if (num != 0)
|
|
{
|
|
b++;
|
|
list.Add((num, item->MateriaGrades[i]));
|
|
}
|
|
}
|
|
}
|
|
return CalculateGearStats(_itemSheet.GetRow(item->ItemId), item->Flags.HasFlag(InventoryItem.ItemFlags.HighQuality), list)with
|
|
{
|
|
MateriaCount = b
|
|
};
|
|
}
|
|
|
|
public EquipmentStats CalculateGearStats(Item item, bool highQuality, IReadOnlyList<(uint MateriaId, byte Grade)> materias)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(materias, "materias");
|
|
Dictionary<EBaseParam, StatInfo> dictionary = new Dictionary<EBaseParam, StatInfo>();
|
|
for (int i = 0; i < item.BaseParam.Count; i++)
|
|
{
|
|
AddEquipmentStat(dictionary, item.BaseParam[i], item.BaseParamValue[i]);
|
|
}
|
|
if (highQuality)
|
|
{
|
|
for (int j = 0; j < item.BaseParamSpecial.Count; j++)
|
|
{
|
|
AddEquipmentStat(dictionary, item.BaseParamSpecial[j], item.BaseParamValueSpecial[j]);
|
|
}
|
|
}
|
|
foreach (var materia in materias)
|
|
{
|
|
if (_materiaStats.TryGetValue(materia.MateriaId, out MateriaInfo value))
|
|
{
|
|
AddMateriaStat(item, dictionary, value, materia.Grade);
|
|
}
|
|
}
|
|
return new EquipmentStats(dictionary, 0);
|
|
}
|
|
|
|
private static void AddEquipmentStat(Dictionary<EBaseParam, StatInfo> result, RowRef<BaseParam> baseParam, short value)
|
|
{
|
|
if (baseParam.RowId != 0)
|
|
{
|
|
if (result.TryGetValue((EBaseParam)baseParam.RowId, out StatInfo value2))
|
|
{
|
|
result[(EBaseParam)baseParam.RowId] = value2 with
|
|
{
|
|
EquipmentValue = (short)(value2.EquipmentValue + value)
|
|
};
|
|
}
|
|
else
|
|
{
|
|
result[(EBaseParam)baseParam.RowId] = new StatInfo(value, 0, Overcapped: false);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void AddMateriaStat(Item item, Dictionary<EBaseParam, StatInfo> result, MateriaInfo materiaInfo, short grade)
|
|
{
|
|
if (!result.TryGetValue(materiaInfo.BaseParam, out StatInfo value))
|
|
{
|
|
value = (result[materiaInfo.BaseParam] = new StatInfo(0, 0, Overcapped: false));
|
|
}
|
|
if (materiaInfo.HasItem)
|
|
{
|
|
short num = (short)(GetMaximumStatValue(item, materiaInfo.BaseParam) - value.EquipmentValue);
|
|
if (value.MateriaValue + materiaInfo.Values[grade] > num)
|
|
{
|
|
result[materiaInfo.BaseParam] = value with
|
|
{
|
|
MateriaValue = num,
|
|
Overcapped = true
|
|
};
|
|
}
|
|
else
|
|
{
|
|
result[materiaInfo.BaseParam] = value with
|
|
{
|
|
MateriaValue = (short)(value.MateriaValue + materiaInfo.Values[grade])
|
|
};
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result[materiaInfo.BaseParam] = value with
|
|
{
|
|
MateriaValue = (short)(value.MateriaValue + materiaInfo.Values[grade])
|
|
};
|
|
}
|
|
}
|
|
|
|
public short GetMaximumStatValue(Item item, EBaseParam baseParamValue)
|
|
{
|
|
if (_itemLevelStatCaps.TryGetValue((item.LevelItem.RowId, baseParamValue), out var value))
|
|
{
|
|
return (short)Math.Round((float)(value * _equipSlotCategoryPct[(baseParamValue, (int)item.EquipSlotCategory.RowId)]) / 1000f, MidpointRounding.AwayFromZero);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
public unsafe short CalculateAverageItemLevel(InventoryContainer* container)
|
|
{
|
|
uint num = 0u;
|
|
int num2 = 12;
|
|
for (int i = 0; i < 13; i++)
|
|
{
|
|
if (i == 5)
|
|
{
|
|
continue;
|
|
}
|
|
InventoryItem* inventorySlot = container->GetInventorySlot(i);
|
|
if (inventorySlot == null || inventorySlot->ItemId == 0)
|
|
{
|
|
continue;
|
|
}
|
|
Item? rowOrDefault = _itemSheet.GetRowOrDefault(inventorySlot->ItemId);
|
|
if (!rowOrDefault.HasValue)
|
|
{
|
|
continue;
|
|
}
|
|
if (rowOrDefault.Value.ItemUICategory.RowId == 105)
|
|
{
|
|
if (i == 0)
|
|
{
|
|
num2--;
|
|
}
|
|
num2--;
|
|
continue;
|
|
}
|
|
if (i == 0 && !CanHaveOffhand.Contains(rowOrDefault.Value.ItemUICategory.RowId))
|
|
{
|
|
num += rowOrDefault.Value.LevelItem.RowId;
|
|
i++;
|
|
}
|
|
num += rowOrDefault.Value.LevelItem.RowId;
|
|
}
|
|
return (short)(num / num2);
|
|
}
|
|
}
|