From ff32b9bf6031a34991160b395df313748dbe466b Mon Sep 17 00:00:00 2001 From: Liza Carvelli Date: Fri, 22 Nov 2024 00:29:02 +0100 Subject: [PATCH] Move gear stat calculation to LLib --- Gearsetter.Test/ItemSortingTest.cs | 13 ++-- Gearsetter/Configuration.cs | 2 +- Gearsetter/GameData/EBaseParam.cs | 34 -------- Gearsetter/GameData/GameDataHolder.cs | 25 +++--- Gearsetter/GameData/ItemLevelCaps.cs | 80 ------------------- Gearsetter/GearsetterPlugin.cs | 47 +++++------ Gearsetter/Model/BaseItem.cs | 9 ++- Gearsetter/Model/EquipmentItem.cs | 3 +- Gearsetter/Model/EquipmentStats.cs | 82 +------------------- Gearsetter/Model/GearsetData.cs | 35 +++++---- Gearsetter/Model/InventoryItem.cs | 5 +- Gearsetter/Model/ItemList.cs | 38 ++++----- Gearsetter/Model/MateriaStat.cs | 11 --- Gearsetter/Model/MateriaStats.cs | 64 --------------- Gearsetter/Windows/EquipmentBrowserWindow.cs | 17 ++-- LLib | 2 +- 16 files changed, 101 insertions(+), 366 deletions(-) delete mode 100644 Gearsetter/GameData/EBaseParam.cs delete mode 100644 Gearsetter/GameData/ItemLevelCaps.cs delete mode 100644 Gearsetter/Model/MateriaStat.cs delete mode 100644 Gearsetter/Model/MateriaStats.cs diff --git a/Gearsetter.Test/ItemSortingTest.cs b/Gearsetter.Test/ItemSortingTest.cs index 28727cc..cb4656e 100644 --- a/Gearsetter.Test/ItemSortingTest.cs +++ b/Gearsetter.Test/ItemSortingTest.cs @@ -4,6 +4,7 @@ using System.Linq; using Gearsetter.GameData; using Gearsetter.Model; using LLib.GameData; +using LLib.Gear; using Lumina.Excel.Sheets; using Xunit; @@ -16,6 +17,10 @@ public sealed class ItemSortingTest [Fact] public void Test1() { + var gearStatsCalculator = new GearStatsCalculator(_lumina.GetExcelSheet()!, + _lumina.GetExcelSheet()!, + _lumina.GetExcelSheet()!, + _lumina.GetExcelSheet()!); var items = _lumina.GetExcelSheet()!; List initialItemIds = [ @@ -30,12 +35,13 @@ public sealed class ItemSortingTest 32558, ]; + var itemList = new ItemList { ClassJob = EClassJob.Marauder, EquipSlotCategory = EEquipSlotCategory.Ears, ItemUiCategory = 41, - Items = initialItemIds.Select(rowId => new EquipmentItem(items.GetRow(rowId), false)) + Items = initialItemIds.Select(rowId => new EquipmentItem(items.GetRow(rowId), false, gearStatsCalculator.CalculateGearStats(items.GetRow(rowId), false, []))) .Cast() .ToList(), }; @@ -45,10 +51,7 @@ public sealed class ItemSortingTest .Where(x => x.PrimaryStat > 0) .ToDictionary(x => (EClassJob)x.RowId, x => (EBaseParam)x.PrimaryStat); - var itemLevelCaps = new ItemLevelCaps(_lumina.GetExcelSheet()!, - _lumina.GetExcelSheet()!); - - itemList.UpdateStats(primaryStats, new Configuration(), itemLevelCaps); + itemList.UpdateStats(primaryStats, new Configuration()); itemList.Sort(); List expectedItems = diff --git a/Gearsetter/Configuration.cs b/Gearsetter/Configuration.cs index ffc5d7f..8b458de 100644 --- a/Gearsetter/Configuration.cs +++ b/Gearsetter/Configuration.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using Dalamud.Configuration; -using Gearsetter.GameData; +using LLib.Gear; namespace Gearsetter; diff --git a/Gearsetter/GameData/EBaseParam.cs b/Gearsetter/GameData/EBaseParam.cs deleted file mode 100644 index dc5e4f0..0000000 --- a/Gearsetter/GameData/EBaseParam.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace Gearsetter.GameData; - -internal enum EBaseParam : byte -{ - None = 0, - - Strength = 1, - Dexterity = 2, - Vitality = 3, - Intelligence = 4, - Mind = 5, - Piety = 6, - - GP = 10, - CP = 11, - - DamagePhys = 12, - DamageMag = 13, - - DefensePhys = 21, - DefenseMag = 24, - - Tenacity = 19, - Crit = 27, - DirectHit = 22, - Determination = 44, - SpellSpeed = 46, - SkillSpeed = 45, - - Craftsmanship = 70, - Control = 71, - Gathering = 72, - Perception = 73, -} diff --git a/Gearsetter/GameData/GameDataHolder.cs b/Gearsetter/GameData/GameDataHolder.cs index d4b0ad9..4d2a4ce 100644 --- a/Gearsetter/GameData/GameDataHolder.cs +++ b/Gearsetter/GameData/GameDataHolder.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; -using Dalamud; using Dalamud.Game; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; using Gearsetter.Model; using LLib.GameData; +using LLib.Gear; using Lumina.Excel.Sheets; namespace Gearsetter.GameData; @@ -14,14 +14,15 @@ namespace Gearsetter.GameData; internal sealed class GameDataHolder { private readonly Configuration _configuration; - private readonly ItemLevelCaps _itemLevelCaps; + private readonly GearStatsCalculator _gearStatsCalculator; private readonly Dictionary> _classJobCategories; private readonly IReadOnlyList _allItemLists; - public GameDataHolder(IDataManager dataManager, Configuration configuration) + public GameDataHolder(IDataManager dataManager, Configuration configuration, + GearStatsCalculator gearStatsCalculator) { _configuration = configuration; - _itemLevelCaps = new ItemLevelCaps(dataManager); + _gearStatsCalculator = gearStatsCalculator; _classJobCategories = dataManager.GetExcelSheet() .ToDictionary(x => x.RowId, x => new Dictionary @@ -89,9 +90,6 @@ internal sealed class GameDataHolder .ThenBy(x => x.OrderMinor) .Select(x => (x.RowId, x.Name.ToString())) .ToList(); - Materias = dataManager.GetExcelSheet() - .Where(x => x.RowId > 0 && Enum.IsDefined(typeof(EBaseParam), (byte)x.BaseParam.RowId)) - .ToDictionary(x => x.RowId, x => new MateriaStat((EBaseParam)x.BaseParam.RowId, x.Value.ToArray())); _allItemLists = dataManager.GetExcelSheet() @@ -137,7 +135,6 @@ internal sealed class GameDataHolder public IReadOnlyList<(EClassJob ClassJob, string Name)> ClassJobNames { get; } public IReadOnlyList<(uint ItemUiCategory, string Name)> ItemUiCategoryNames { get; } public Dictionary PrimaryStats { get; } - public Dictionary Materias { get; set; } public Dictionary StatNames { get; } = new() { @@ -181,7 +178,7 @@ internal sealed class GameDataHolder { foreach (ItemList itemList in _allItemLists) { - itemList.UpdateStats(PrimaryStats, _configuration, _itemLevelCaps); + itemList.UpdateStats(PrimaryStats, _configuration); itemList.Sort(); } } @@ -209,8 +206,14 @@ internal sealed class GameDataHolder private IEnumerable<(EquipmentItem Item, List ClassJobs)> LoadItem(Item item) { var classJobCategories = _classJobCategories[item.ClassJobCategory.RowId]; - yield return (new EquipmentItem(item, false), classJobCategories); + yield return ( + new EquipmentItem(item, false, _gearStatsCalculator.CalculateGearStats(item, false, [])), + classJobCategories); if (item.CanBeHq) - yield return (new EquipmentItem(item, true), classJobCategories); + { + yield return ( + new EquipmentItem(item, true, _gearStatsCalculator.CalculateGearStats(item, true, [])), + classJobCategories); + } } } diff --git a/Gearsetter/GameData/ItemLevelCaps.cs b/Gearsetter/GameData/ItemLevelCaps.cs deleted file mode 100644 index 0ef087c..0000000 --- a/Gearsetter/GameData/ItemLevelCaps.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Collections.Generic; -using Dalamud.Plugin.Services; -using Lumina.Excel; -using Lumina.Excel.Sheets; - -namespace Gearsetter.GameData; - -internal sealed class ItemLevelCaps -{ - private readonly ExcelSheet _baseParamSheet; - private readonly Dictionary<(uint, EBaseParam), ushort> _caps = []; - - public ItemLevelCaps(IDataManager dataManager) - : this(dataManager.GetExcelSheet(), dataManager.GetExcelSheet()) - { - } - - public ItemLevelCaps(ExcelSheet itemLevelSheet, ExcelSheet baseParamSheet) - { - _baseParamSheet = baseParamSheet; - foreach (var itemLevel in itemLevelSheet) - { - _caps[(itemLevel.RowId, EBaseParam.Strength)] = itemLevel.Strength; - _caps[(itemLevel.RowId, EBaseParam.Dexterity)] = itemLevel.Dexterity; - _caps[(itemLevel.RowId, EBaseParam.Vitality)] = itemLevel.Vitality; - _caps[(itemLevel.RowId, EBaseParam.Intelligence)] = itemLevel.Intelligence; - _caps[(itemLevel.RowId, EBaseParam.Mind)] = itemLevel.Mind; - _caps[(itemLevel.RowId, EBaseParam.Piety)] = itemLevel.Piety; - - _caps[(itemLevel.RowId, EBaseParam.GP)] = itemLevel.GP; - _caps[(itemLevel.RowId, EBaseParam.CP)] = itemLevel.CP; - - _caps[(itemLevel.RowId, EBaseParam.DamagePhys)] = itemLevel.PhysicalDamage; - _caps[(itemLevel.RowId, EBaseParam.DamageMag)] = itemLevel.MagicalDamage; - - _caps[(itemLevel.RowId, EBaseParam.DefensePhys)] = itemLevel.Defense; - _caps[(itemLevel.RowId, EBaseParam.DefenseMag)] = itemLevel.MagicDefense; - - _caps[(itemLevel.RowId, EBaseParam.Tenacity)] = itemLevel.Tenacity; - _caps[(itemLevel.RowId, EBaseParam.Crit)] = itemLevel.CriticalHit; - _caps[(itemLevel.RowId, EBaseParam.DirectHit)] = itemLevel.DirectHitRate; - _caps[(itemLevel.RowId, EBaseParam.Determination)] = itemLevel.Determination; - _caps[(itemLevel.RowId, EBaseParam.SpellSpeed)] = itemLevel.SpellSpeed; - _caps[(itemLevel.RowId, EBaseParam.SkillSpeed)] = itemLevel.SkillSpeed; - - _caps[(itemLevel.RowId, EBaseParam.Gathering)] = itemLevel.Gathering; - _caps[(itemLevel.RowId, EBaseParam.Perception)] = itemLevel.Perception; - _caps[(itemLevel.RowId, EBaseParam.Craftsmanship)] = itemLevel.Craftsmanship; - _caps[(itemLevel.RowId, EBaseParam.Control)] = itemLevel.Control; - } - } - - public short GetMaximum(Item item, EBaseParam baseParamValue) - { - var baseParam = _baseParamSheet.GetRow((uint)baseParamValue); - return (short)Math.Round( - _caps[(item.LevelItem.RowId, baseParamValue)] * - (baseParam.EquipSlotCategoryPct[(int)item.EquipSlotCategory.RowId] / 1000f), MidpointRounding.AwayFromZero); - } - - // From caraxi/SimpleTWeaks - [Sheet("BaseParam")] - public readonly unsafe struct ExtendedBaseParam(ExcelPage page, uint offset, uint row) - : IExcelRow - { - private const int ParamCount = 23; - - public BaseParam BaseParam => new(page, offset, row); - - public Collection EquipSlotCategoryPct => - new(page, offset, offset, &EquipSlotCategoryPctCtor, ParamCount); - - private static ushort EquipSlotCategoryPctCtor(ExcelPage page, uint parentOffset, uint offset, uint i) => - i == 0 ? (ushort)0 : page.ReadUInt16(offset + 8 + (i - 1) * 2); - - public static ExtendedBaseParam Create(ExcelPage page, uint offset, uint row) => new(page, offset, row); - public uint RowId => row; - } -} diff --git a/Gearsetter/GearsetterPlugin.cs b/Gearsetter/GearsetterPlugin.cs index c222fa0..8ef3e4f 100644 --- a/Gearsetter/GearsetterPlugin.cs +++ b/Gearsetter/GearsetterPlugin.cs @@ -17,6 +17,7 @@ using Gearsetter.GameData; using Gearsetter.Model; using Gearsetter.Windows; using LLib.GameData; +using LLib.Gear; using Lumina.Excel.Sheets; using GrandCompany = FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany; using InventoryItem = FFXIVClientStructs.FFXIV.Client.Game.InventoryItem; @@ -35,6 +36,7 @@ public sealed class GearsetterPlugin : IDalamudPlugin private readonly IClientState _clientState; private readonly GearsetterIpc _gearsetterIpc; private readonly Configuration _configuration; + private readonly GearStatsCalculator _gearStatsCalculator; private readonly GameDataHolder _gameDataHolder; private readonly EquipmentBrowserWindow _equipmentBrowserWindow; private readonly ConfigWindow _configWindow; @@ -62,7 +64,8 @@ public sealed class GearsetterPlugin : IDalamudPlugin } _configuration = configuration; - _gameDataHolder = new GameDataHolder(dataManager, _configuration); + _gearStatsCalculator = new GearStatsCalculator(dataManager); + _gameDataHolder = new GameDataHolder(dataManager, _configuration, _gearStatsCalculator); _equipmentBrowserWindow = new EquipmentBrowserWindow(this, _gameDataHolder, _clientState, _chatGui); _windowSystem.AddWindow(_equipmentBrowserWindow); _configWindow = new ConfigWindow(_pluginInterface, _configuration); @@ -175,19 +178,19 @@ public sealed class GearsetterPlugin : IDalamudPlugin name.Contains("Bozja", StringComparison.OrdinalIgnoreCase)) return null; - return new GearsetData(_dataManager, gearset, name); + return new GearsetData(_dataManager, _gearStatsCalculator, gearset, name); } internal unsafe List GetRecommendedUpgrades(RaptureGearsetModule.GearsetEntry* gearset, byte? level = null) { - GearsetData gearsetData = new GearsetData(_dataManager, gearset, GetGearsetName(gearset)); - Dictionary<(uint ItemId, bool Hq), List> inventoryItems = GetAllInventoryItems(); + GearsetData gearsetData = new GearsetData(_dataManager, _gearStatsCalculator, gearset, GetGearsetName(gearset)); + Dictionary<(uint ItemId, bool Hq), List> inventoryItems = GetAllInventoryItems(); return GetRecommendedUpgrades(gearsetData, inventoryItems, level); } private List GetRecommendedUpgrades(GearsetData gearset, - Dictionary<(uint ItemId, bool Hql), List> inventoryItems, byte? level) + Dictionary<(uint ItemId, bool Hql), List> inventoryItems, byte? level) { List Handle(string label, (EquipmentItem?, RaptureGearsetModule.GearsetItemIndex)[] gearsetItems, @@ -225,7 +228,7 @@ public sealed class GearsetterPlugin : IDalamudPlugin private bool HandleGearset(GearsetData gearset, - Dictionary<(uint ItemId, bool Hq), List> inventoryItems, byte? level) + Dictionary<(uint ItemId, bool Hq), List> inventoryItems, byte? level) { List upgrades = GetRecommendedUpgrades(gearset, inventoryItems, level); if (upgrades.Count == 0) @@ -258,7 +261,7 @@ public sealed class GearsetterPlugin : IDalamudPlugin private List HandleGearsetItem(string label, GearsetData gearset, (EquipmentItem? Item, RaptureGearsetModule.GearsetItemIndex Slot)[] gearsetItems, - Dictionary<(uint ItemId, bool Hq), List> inventoryItems, + Dictionary<(uint ItemId, bool Hq), List> inventoryItems, EEquipSlotCategory equipSlotCategory, byte? level) { EClassJob classJob = gearset.ClassJob; @@ -338,7 +341,7 @@ public sealed class GearsetterPlugin : IDalamudPlugin } private List HandleOffHand(GearsetData gearset, - Dictionary<(uint ItemId, bool Hq), List> inventoryItems, byte? level) + Dictionary<(uint ItemId, bool Hq), List> inventoryItems, byte? level) { var mainHand = gearset.MainHand; if (mainHand == null) @@ -372,8 +375,8 @@ public sealed class GearsetterPlugin : IDalamudPlugin if (item != null && item->ItemId == baseItem.ItemId && item->Flags.HasFlag(InventoryItem.ItemFlags.HighQuality) == baseItem.Hq) { - MateriaStats expectedMateriaStats = baseItem.MateriaStats ?? new([]); - MateriaStats actualMateriaStats = FetchMateriaStats(item); + EquipmentStats expectedMateriaStats = baseItem.Stats; + EquipmentStats actualMateriaStats = FetchMateriaStats(item); if (expectedMateriaStats == actualMateriaStats) return new RecommendedItemChange(item->ItemId, inventoryType, i, targetSlot, text); @@ -384,9 +387,9 @@ public sealed class GearsetterPlugin : IDalamudPlugin return new RecommendedItemChange(baseItem.ItemId, null, null, targetSlot, text); } - internal unsafe Dictionary<(uint ItemId, bool Hq), List> GetAllInventoryItems() + internal unsafe Dictionary<(uint ItemId, bool Hq), List> GetAllInventoryItems() { - Dictionary<(uint, bool), List> inventoryItems = new(); + Dictionary<(uint, bool), List> inventoryItems = new(); InventoryManager* inventoryManager = InventoryManager.Instance(); foreach (var inventoryType in _gameDataHolder.DefaultInventoryTypes) { @@ -399,7 +402,7 @@ public sealed class GearsetterPlugin : IDalamudPlugin var key = (item->ItemId, item->Flags.HasFlag(InventoryItem.ItemFlags.HighQuality)); if (!inventoryItems.TryGetValue(key, out var list)) { - list = new List(); + list = new List(); inventoryItems[key] = list; } @@ -411,22 +414,8 @@ public sealed class GearsetterPlugin : IDalamudPlugin return inventoryItems; } - private unsafe MateriaStats FetchMateriaStats(InventoryItem* item) - { - // FIXME item->GetMateriaCount is broken on API 10, so this seems to be somewhat slow - List<(MateriaStat, byte)> materias = new(); - for (int slot = 0; slot < 5; ++slot) - { - var materiaId = item->Materia[slot]; - if (materiaId == 0) - break; - - if (_gameDataHolder.Materias.TryGetValue(materiaId, out MateriaStat? value)) - materias.Add((value, item->MateriaGrades[slot])); - } - - return new MateriaStats(materias); - } + private unsafe EquipmentStats FetchMateriaStats(InventoryItem* item) + => _gearStatsCalculator.CalculateGearStats(item); internal unsafe byte GetLevel(EClassJob classJob) { diff --git a/Gearsetter/Model/BaseItem.cs b/Gearsetter/Model/BaseItem.cs index bc277f3..1ce08b9 100644 --- a/Gearsetter/Model/BaseItem.cs +++ b/Gearsetter/Model/BaseItem.cs @@ -1,11 +1,12 @@ using System.Linq; using Gearsetter.GameData; using LLib.GameData; +using LLib.Gear; using Lumina.Excel.Sheets; namespace Gearsetter.Model; -internal abstract record BaseItem(Item Item, bool Hq, MateriaStats? MateriaStats = null) +internal abstract record BaseItem(Item Item, bool Hq, EquipmentStats Stats) { public Item Item { get; } = Item; public uint ItemId { get; } = Item.RowId; @@ -18,7 +19,7 @@ internal abstract record BaseItem(Item Item, bool Hq, MateriaStats? MateriaStats public EEquipSlotCategory EquipSlotCategory { get; } = (EEquipSlotCategory)Item.EquipSlotCategory.RowId; public uint ItemUiCategory { get; } = Item.ItemUICategory.RowId; public abstract EClassJob ClassJob { get; init; } - public EquipmentStats Stats { get; } = new(Item, Hq, MateriaStats); + public EquipmentStats Stats { get; } = Stats; public int PrimaryStat { get; init; } = -1; @@ -27,9 +28,9 @@ internal abstract record BaseItem(Item Item, bool Hq, MateriaStats? MateriaStats get { if (ClassJob.DealsMagicDamage()) - return Item.DamageMag + Stats.Get(EBaseParam.DamageMag, null); + return Item.DamageMag + Stats.Get(EBaseParam.DamageMag); else if (ClassJob.DealsPhysicalDamage()) - return Item.DamagePhys + Stats.Get(EBaseParam.DamagePhys, null); + return Item.DamagePhys + Stats.Get(EBaseParam.DamagePhys); else return 0; } diff --git a/Gearsetter/Model/EquipmentItem.cs b/Gearsetter/Model/EquipmentItem.cs index e144b83..73aef1a 100644 --- a/Gearsetter/Model/EquipmentItem.cs +++ b/Gearsetter/Model/EquipmentItem.cs @@ -1,9 +1,10 @@ using LLib.GameData; +using LLib.Gear; using Lumina.Excel.Sheets; namespace Gearsetter.Model; -internal sealed record EquipmentItem(Item Item, bool Hq) : BaseItem(Item, Hq) +internal sealed record EquipmentItem(Item Item, bool Hq, EquipmentStats Stats) : BaseItem(Item, Hq, Stats) { public override EClassJob ClassJob { get; init; } = EClassJob.Adventurer; } diff --git a/Gearsetter/Model/EquipmentStats.cs b/Gearsetter/Model/EquipmentStats.cs index 2f3d5d7..c34365a 100644 --- a/Gearsetter/Model/EquipmentStats.cs +++ b/Gearsetter/Model/EquipmentStats.cs @@ -1,83 +1,5 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using Gearsetter.GameData; -using Lumina.Excel.Sheets; +using LLib.Gear; namespace Gearsetter.Model; - -internal sealed class EquipmentStats -{ - private readonly Item _item; - private readonly Dictionary _equipmentValues; - private readonly Dictionary _materiaValues; - - public EquipmentStats(Item item, bool hq, MateriaStats? materiaStats) - { - _item = item; - _equipmentValues = Enumerable.Range(0, item.BaseParam.Count) - .Where(i => item.BaseParam[i].RowId > 0) - .ToDictionary(i => (EBaseParam)item.BaseParam[i].RowId, i => item.BaseParamValue[i]); - if (hq) - { - for (int i = 0; i < item.BaseParamSpecial.Count; ++i) - { - EBaseParam baseParam = (EBaseParam)item.BaseParamSpecial[i].RowId; - if (baseParam == EBaseParam.None) - continue; - - var baseParamValue = item.BaseParamValueSpecial[i]; - if (_equipmentValues.TryGetValue(baseParam, out var stat)) - _equipmentValues[baseParam] = (short)(stat + baseParamValue); - else - _equipmentValues[baseParam] = baseParamValue; - } - } - - _materiaValues = new(); - if (materiaStats != null) - { - foreach (var materiaStat in materiaStats.Values) - { - if (_materiaValues.TryGetValue(materiaStat.Key, out var stat)) - _materiaValues[materiaStat.Key] = (short)(stat + materiaStat.Value); - else - _materiaValues[materiaStat.Key] = materiaStat.Value; - } - } - } - - public short Get(EBaseParam param, ItemLevelCaps? itemLevelCaps) - { - return (short)(GetEquipment(param) + GetMateria(param, itemLevelCaps)); - } - - public short GetEquipment(EBaseParam param) - { - _equipmentValues.TryGetValue(param, out short v); - return v; - } - - public short GetMateria(EBaseParam param, ItemLevelCaps? itemLevelCaps) - { - _materiaValues.TryGetValue(param, out short v); - if (v == 0) - return v; - - if (param != EBaseParam.DamagePhys && param != EBaseParam.DamageMag) - { - // This isn't necessary accurate for Eureka relics, which can (in theory) have +1000 critical hit, but - // they're both outdated and the limits aren't relevant for a decision of 'which gear piece is better'; - // worst case it'll suggest the i405 savage weapon instead. - ArgumentNullException.ThrowIfNull(itemLevelCaps); - short max = itemLevelCaps.GetMaximum(_item, param); - short equipped = _equipmentValues.GetValueOrDefault(param); - if (v + equipped > max) - return (short)(max - equipped); - } - - return v; - } - - public bool Has(EBaseParam substat) => _equipmentValues.ContainsKey(substat) || _materiaValues.ContainsKey(substat); -} diff --git a/Gearsetter/Model/GearsetData.cs b/Gearsetter/Model/GearsetData.cs index 1523318..a32bbc8 100644 --- a/Gearsetter/Model/GearsetData.cs +++ b/Gearsetter/Model/GearsetData.cs @@ -1,40 +1,43 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using LLib.GameData; +using LLib.Gear; using Lumina.Excel.Sheets; namespace Gearsetter.Model; internal sealed class GearsetData { - public unsafe GearsetData(IDataManager dataManager, RaptureGearsetModule.GearsetEntry* gearset, string name) + public unsafe GearsetData(IDataManager dataManager, GearStatsCalculator gearStatsCalculator, + RaptureGearsetModule.GearsetEntry* gearset, string name) { Id = gearset->Id; ClassJob = (EClassJob)gearset->ClassJob; Name = name; - MainHand = GetItem(dataManager, gearset, RaptureGearsetModule.GearsetItemIndex.MainHand); - OffHand = GetItem(dataManager, gearset, RaptureGearsetModule.GearsetItemIndex.OffHand); - Head = GetItem(dataManager, gearset, RaptureGearsetModule.GearsetItemIndex.Head); - Body = GetItem(dataManager, gearset, RaptureGearsetModule.GearsetItemIndex.Body); - Hands = GetItem(dataManager, gearset, RaptureGearsetModule.GearsetItemIndex.Hands); - Legs = GetItem(dataManager, gearset, RaptureGearsetModule.GearsetItemIndex.Legs); - Feet = GetItem(dataManager, gearset, RaptureGearsetModule.GearsetItemIndex.Feet); - Ears = GetItem(dataManager, gearset, RaptureGearsetModule.GearsetItemIndex.Ears); - Neck = GetItem(dataManager, gearset, RaptureGearsetModule.GearsetItemIndex.Neck); - Wrists = GetItem(dataManager, gearset, RaptureGearsetModule.GearsetItemIndex.Wrists); - RingLeft = GetItem(dataManager, gearset, RaptureGearsetModule.GearsetItemIndex.RingLeft); - RingRight = GetItem(dataManager, gearset, RaptureGearsetModule.GearsetItemIndex.RingRight); + MainHand = GetItem(dataManager, gearStatsCalculator, gearset, RaptureGearsetModule.GearsetItemIndex.MainHand); + OffHand = GetItem(dataManager, gearStatsCalculator, gearset, RaptureGearsetModule.GearsetItemIndex.OffHand); + Head = GetItem(dataManager, gearStatsCalculator, gearset, RaptureGearsetModule.GearsetItemIndex.Head); + Body = GetItem(dataManager, gearStatsCalculator, gearset, RaptureGearsetModule.GearsetItemIndex.Body); + Hands = GetItem(dataManager, gearStatsCalculator, gearset, RaptureGearsetModule.GearsetItemIndex.Hands); + Legs = GetItem(dataManager, gearStatsCalculator, gearset, RaptureGearsetModule.GearsetItemIndex.Legs); + Feet = GetItem(dataManager, gearStatsCalculator, gearset, RaptureGearsetModule.GearsetItemIndex.Feet); + Ears = GetItem(dataManager, gearStatsCalculator, gearset, RaptureGearsetModule.GearsetItemIndex.Ears); + Neck = GetItem(dataManager, gearStatsCalculator, gearset, RaptureGearsetModule.GearsetItemIndex.Neck); + Wrists = GetItem(dataManager, gearStatsCalculator, gearset, RaptureGearsetModule.GearsetItemIndex.Wrists); + RingLeft = GetItem(dataManager, gearStatsCalculator, gearset, RaptureGearsetModule.GearsetItemIndex.RingLeft); + RingRight = GetItem(dataManager, gearStatsCalculator, gearset, RaptureGearsetModule.GearsetItemIndex.RingRight); } - private static unsafe EquipmentItem? GetItem(IDataManager dataManager, RaptureGearsetModule.GearsetEntry* gearset, - RaptureGearsetModule.GearsetItemIndex index) + private static unsafe EquipmentItem? GetItem(IDataManager dataManager, GearStatsCalculator gearStatsCalculator, + RaptureGearsetModule.GearsetEntry* gearset, RaptureGearsetModule.GearsetItemIndex index) { var gearsetItem = gearset->GetItem(index); if (gearsetItem.ItemId == 0) return null; var item = dataManager.GetExcelSheet().GetRow(gearsetItem.ItemId % 1_000_000); - return new EquipmentItem(item, gearsetItem.ItemId > 1_000_000); + bool hq = gearsetItem.ItemId > 1_000_000; + return new EquipmentItem(item, hq, gearStatsCalculator.CalculateGearStats(item, hq, [])); } public byte Id { get; } diff --git a/Gearsetter/Model/InventoryItem.cs b/Gearsetter/Model/InventoryItem.cs index d42e4fe..735c54f 100644 --- a/Gearsetter/Model/InventoryItem.cs +++ b/Gearsetter/Model/InventoryItem.cs @@ -1,9 +1,10 @@ using LLib.GameData; +using LLib.Gear; using Lumina.Excel.Sheets; namespace Gearsetter.Model; -internal sealed record InventoryItem(Item Item, bool Hq, MateriaStats MateriaStats, EClassJob ClassJob) - : BaseItem(Item, Hq, MateriaStats) +internal sealed record InventoryItem(Item Item, bool Hq, EquipmentStats Stats, EClassJob ClassJob) + : BaseItem(Item, Hq, Stats) { } diff --git a/Gearsetter/Model/ItemList.cs b/Gearsetter/Model/ItemList.cs index 788767b..86460c7 100644 --- a/Gearsetter/Model/ItemList.cs +++ b/Gearsetter/Model/ItemList.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using System.Linq; using Gearsetter.GameData; using LLib.GameData; +using LLib.Gear; namespace Gearsetter.Model; @@ -23,17 +24,15 @@ internal sealed class ItemList public required List Items { get; set; } public EBaseParam PrimaryStat { get; set; } public IReadOnlyList SubstatPriorities { get; private set; } = new List(); - public ItemLevelCaps ItemLevelCaps { get; private set; } = null!; public void Sort() { Items = Items - .OrderDescending(new ItemComparer(SubstatPriorities, ItemLevelCaps)) + .OrderDescending(new ItemComparer(SubstatPriorities)) .ToList(); } - public void UpdateStats(Dictionary primaryStats, Configuration configuration, - ItemLevelCaps itemLevelCaps) + public void UpdateStats(Dictionary primaryStats, Configuration configuration) { if (ClassJob.IsTank()) SubstatPriorities = configuration.StatPriorityTanks; @@ -52,21 +51,19 @@ internal sealed class ItemList else SubstatPriorities = []; - ItemLevelCaps = itemLevelCaps; - if (primaryStats.TryGetValue(ClassJob, out EBaseParam primaryStat)) { PrimaryStat = primaryStat; Items = Items .Where(x => x is EquipmentItem) .Cast() - .Select(x => x with { PrimaryStat = x.Stats.Get(primaryStat, itemLevelCaps) }) + .Select(x => x with { PrimaryStat = x.Stats.Get(primaryStat) }) .Cast() .ToList(); } } - public void ApplyFromInventory(Dictionary<(uint ItemId, bool Hq), List> inventoryItems, + public void ApplyFromInventory(Dictionary<(uint ItemId, bool Hq), List> inventoryItems, bool includeWithoutMateria) { foreach (var inventoryItem in inventoryItems) @@ -76,13 +73,13 @@ internal sealed class ItemList if (basicItem == null) continue; - foreach (var materias in inventoryItem.Value) + foreach (var inventoryStats in inventoryItem.Value) { - if (includeWithoutMateria || materias.Values.Count > 0) + if (includeWithoutMateria || inventoryStats.HasMateria()) Items.Add( - new InventoryItem(basicItem.Item, basicItem.Hq, materias, basicItem.ClassJob) + new InventoryItem(basicItem.Item, basicItem.Hq, inventoryStats, basicItem.ClassJob) { - PrimaryStat = basicItem.Stats.Get(PrimaryStat, ItemLevelCaps) + PrimaryStat = basicItem.Stats.Get(PrimaryStat) }); } } @@ -95,10 +92,7 @@ internal sealed class ItemList Items.RemoveAll(x => x is InventoryItem); } - private sealed class ItemComparer( - IReadOnlyList substatPriorities, - ItemLevelCaps itemLevelCaps - ) : IComparer + private sealed class ItemComparer(IReadOnlyList substatPriorities) : IComparer { public int Compare(BaseItem? a, BaseItem? b) { @@ -127,14 +121,14 @@ internal sealed class ItemList return primaryStatA.CompareTo(primaryStatB); // gear: vitality wins - int vitalityA = a.Stats.Get(EBaseParam.Vitality, itemLevelCaps); - int vitalityB = b.Stats.Get(EBaseParam.Vitality, itemLevelCaps); + int vitalityA = a.Stats.Get(EBaseParam.Vitality); + int vitalityB = b.Stats.Get(EBaseParam.Vitality); if (vitalityA != vitalityB) return vitalityA.CompareTo(vitalityB); // sum of relevant substats - int sumOfSubstatsA = substatPriorities.Sum(x => a.Stats.Get(x, itemLevelCaps)); - int sumOfSubstatsB = substatPriorities.Sum(x => b.Stats.Get(x, itemLevelCaps)); + int sumOfSubstatsA = substatPriorities.Sum(x => a.Stats.Get(x)); + int sumOfSubstatsB = substatPriorities.Sum(x => b.Stats.Get(x)); // some relics have no substats in the sheets, since they can be allocated dynamically // they are -generally- better/equal to any other weapon on that ilvl @@ -169,8 +163,8 @@ internal sealed class ItemList // individual substats foreach (EBaseParam substat in substatPriorities) { - int substatA = a.Stats.Get(substat, itemLevelCaps); - int substatB = b.Stats.Get(substat, itemLevelCaps); + int substatA = a.Stats.Get(substat); + int substatB = b.Stats.Get(substat); if (substatA != substatB) return substatA.CompareTo(substatB); } diff --git a/Gearsetter/Model/MateriaStat.cs b/Gearsetter/Model/MateriaStat.cs deleted file mode 100644 index e0dd6b5..0000000 --- a/Gearsetter/Model/MateriaStat.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Gearsetter.GameData; - -namespace Gearsetter.Model; - -internal sealed class MateriaStat(EBaseParam baseParam, short[] values) -{ - public EBaseParam BaseParam { get; } = baseParam; - public List Values { get; } = values.ToList(); -} diff --git a/Gearsetter/Model/MateriaStats.cs b/Gearsetter/Model/MateriaStats.cs deleted file mode 100644 index fda218a..0000000 --- a/Gearsetter/Model/MateriaStats.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Gearsetter.GameData; - -namespace Gearsetter.Model; - -internal sealed class MateriaStats : IEquatable -{ - private readonly List<(EBaseParam, short)> _list; - - public MateriaStats(IEnumerable<(MateriaStat Stat, byte Grade)> materias) - { - foreach (var materia in materias) - { - if (Values.TryGetValue(materia.Stat.BaseParam, out short value)) - Values[materia.Stat.BaseParam] = (short)(value + materia.Stat.Values[materia.Grade]); - else - Values[materia.Stat.BaseParam] = materia.Stat.Values[materia.Grade]; - - ++Count; - } - - _list = Enum.GetValues() - .Select(x => (x, Values.GetValueOrDefault(x, (short)0))) - .Where(x => x.Item2 > 0) - .ToList(); - } - - public Dictionary Values { get; } = new(); - public byte Count { get; } - - public override bool Equals(object? obj) - { - return ReferenceEquals(this, obj) || obj is MateriaStats other && Equals(other); - } - - public override int GetHashCode() - { - return _list.GetHashCode(); - } - - public bool Equals(MateriaStats? other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return _list.SequenceEqual(other._list); - } - - public static bool operator ==(MateriaStats? left, MateriaStats? right) - { - return Equals(left, right); - } - - public static bool operator !=(MateriaStats? left, MateriaStats? right) - { - return !Equals(left, right); - } - - public override string ToString() - { - return $"Materias[{string.Join(", ", _list.Select(x => $"{x.Item1}:{x.Item2}"))}]"; - } -} diff --git a/Gearsetter/Windows/EquipmentBrowserWindow.cs b/Gearsetter/Windows/EquipmentBrowserWindow.cs index 85c1873..8f37ea5 100644 --- a/Gearsetter/Windows/EquipmentBrowserWindow.cs +++ b/Gearsetter/Windows/EquipmentBrowserWindow.cs @@ -11,6 +11,7 @@ using Gearsetter.GameData; using Gearsetter.Model; using ImGuiNET; using LLib.GameData; +using LLib.Gear; using LLib.ImGui; namespace Gearsetter.Windows; @@ -111,7 +112,7 @@ internal sealed class EquipmentBrowserWindow : LWindow ImGui.SameLine(); ImGui.Checkbox("Hide normal quality items", ref _hideNormalQualityItems); - Dictionary<(uint ItemId, bool Hq), List> ownedItems = _plugin.GetAllInventoryItems(); + Dictionary<(uint ItemId, bool Hq), List> ownedItems = _plugin.GetAllInventoryItems(); try { itemList.ApplyFromInventory(ownedItems, _onlyShowOwnedItems); @@ -144,7 +145,7 @@ internal sealed class EquipmentBrowserWindow : LWindow ImGui.PushStyleColor(ImGuiCol.HeaderHovered, hoverColor); foreach (var item in itemList.Items.DistinctBy(x => new - { x.ItemId, x.Hq, Materia = x.MateriaStats?.GetHashCode() })) + { x.ItemId, x.Hq, Stats = x.Stats.GetHashCode() })) { if (item is not InventoryItem) { @@ -174,9 +175,9 @@ internal sealed class EquipmentBrowserWindow : LWindow string name = item.Name; if (item.Hq) name += $" {SeIconChar.HighQuality.ToIconString()}"; - if (item is InventoryItem { MateriaStats: not null } inventoryItem) + if (item is InventoryItem { Stats.MateriaCount: > 0 } inventoryItem) name += - $" {string.Join("", Enumerable.Repeat(SeIconChar.Circle.ToIconString(), inventoryItem.MateriaStats!.Count))}"; + $" {string.Join("", Enumerable.Repeat(SeIconChar.Circle.ToIconString(), inventoryItem.Stats.MateriaCount))}"; if (color != null) ImGui.PushStyleColor(ImGuiCol.Text, color.Value); @@ -215,13 +216,19 @@ internal sealed class EquipmentBrowserWindow : LWindow if (ImGui.TableNextColumn()) { var estat = item.Stats.GetEquipment(substat); - var mstat = item.Stats.GetMateria(substat, itemList.ItemLevelCaps); + var mstat = item.Stats.GetMateria(substat); if (estat == 0 && mstat == 0) ImGui.Text("-"); else if (mstat == 0) ImGui.Text(string.Create(CultureInfo.InvariantCulture, $"{estat}")); else ImGui.Text(string.Create(CultureInfo.InvariantCulture, $"{estat} +{mstat}")); + + if (item.Stats.IsOvercapped(substat)) + { + ImGui.SameLine(); + ImGui.Text(SeIconChar.Debuff.ToIconString()); + } } } } diff --git a/LLib b/LLib index e4bbc05..47ec844 160000 --- a/LLib +++ b/LLib @@ -1 +1 @@ -Subproject commit e4bbc05ede6f6f01e7028b24614ed8cb333e909c +Subproject commit 47ec844d4cdba2c42872414682695f88574978f5