Adjusted sorting order for preferred and weathered items
This commit is contained in:
parent
c60c929502
commit
6c172a84f4
3
Gearsetter.Test/.gitignore
vendored
Normal file
3
Gearsetter.Test/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/dist
|
||||||
|
/obj
|
||||||
|
/bin
|
47
Gearsetter.Test/Gearsetter.Test.csproj
Normal file
47
Gearsetter.Test/Gearsetter.Test.csproj
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<DalamudLibPath>$(appdata)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))'">
|
||||||
|
<DalamudLibPath>$(DALAMUD_HOME)/</DalamudLibPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
|
||||||
|
<PackageReference Include="xunit" Version="2.5.3"/>
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Dalamud">
|
||||||
|
<HintPath>$(DalamudLibPath)Dalamud.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Lumina">
|
||||||
|
<HintPath>$(DalamudLibPath)Lumina.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Lumina.Excel">
|
||||||
|
<HintPath>$(DalamudLibPath)Lumina.Excel.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Gearsetter\Gearsetter.csproj"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Xunit"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
59
Gearsetter.Test/ItemSortingTest.cs
Normal file
59
Gearsetter.Test/ItemSortingTest.cs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
using Gearsetter.GameData;
|
||||||
|
using Gearsetter.Model;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
|
||||||
|
namespace Gearsetter.Test;
|
||||||
|
|
||||||
|
public sealed class ItemSortingTest
|
||||||
|
{
|
||||||
|
Lumina.GameData _lumina = new( "C:/Program Files (x86)/steam/steamapps/common/FINAL FANTASY XIV Online/game/sqpack" );
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Test1()
|
||||||
|
{
|
||||||
|
var items = _lumina.GetExcelSheet<Item>()!;
|
||||||
|
List<uint> initialItemIds =
|
||||||
|
[
|
||||||
|
11851,
|
||||||
|
11853,
|
||||||
|
14447,
|
||||||
|
15131,
|
||||||
|
16039,
|
||||||
|
16240,
|
||||||
|
17436,
|
||||||
|
25928,
|
||||||
|
32558,
|
||||||
|
];
|
||||||
|
|
||||||
|
var itemList = new ItemList
|
||||||
|
{
|
||||||
|
ClassJob = EClassJob.Marauder,
|
||||||
|
EquipSlotCategory = EEquipSlotCategory.Ears,
|
||||||
|
ItemUiCategory = 41,
|
||||||
|
Items = initialItemIds.Select(rowId => new EquipmentItem(items.GetRow(rowId)!, false)).ToList(),
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
var primaryStats = _lumina.GetExcelSheet<ClassJob>()!
|
||||||
|
.Where(x => x.RowId > 0 && Enum.IsDefined(typeof(EClassJob), x.RowId))
|
||||||
|
.Where(x => x.PrimaryStat > 0)
|
||||||
|
.ToDictionary(x => (EClassJob)x.RowId, x => (EBaseParam)x.PrimaryStat);
|
||||||
|
|
||||||
|
itemList.UpdateStats(primaryStats, new Configuration());
|
||||||
|
itemList.Sort();
|
||||||
|
|
||||||
|
List<uint> expectedItems =
|
||||||
|
[
|
||||||
|
32558,
|
||||||
|
25928,
|
||||||
|
11851,
|
||||||
|
17436,
|
||||||
|
16240,
|
||||||
|
14447,
|
||||||
|
11853,
|
||||||
|
16039,
|
||||||
|
15131, // weathered earrings benefit from having primary stats
|
||||||
|
];
|
||||||
|
Assert.Equal(expectedItems, itemList.Items.Select(x => x.ItemId).ToList());
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,8 @@
|
|||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gearsetter", "Gearsetter\Gearsetter.csproj", "{3E87693D-1FEE-486D-80E9-C6D95E68160F}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gearsetter", "Gearsetter\Gearsetter.csproj", "{3E87693D-1FEE-486D-80E9-C6D95E68160F}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gearsetter.Test", "Gearsetter.Test\Gearsetter.Test.csproj", "{19044F87-4C6D-4926-B5C8-5BB7760DC44C}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -12,5 +14,9 @@ Global
|
|||||||
{3E87693D-1FEE-486D-80E9-C6D95E68160F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{3E87693D-1FEE-486D-80E9-C6D95E68160F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{3E87693D-1FEE-486D-80E9-C6D95E68160F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{3E87693D-1FEE-486D-80E9-C6D95E68160F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{3E87693D-1FEE-486D-80E9-C6D95E68160F}.Release|Any CPU.Build.0 = Release|Any CPU
|
{3E87693D-1FEE-486D-80E9-C6D95E68160F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{19044F87-4C6D-4926-B5C8-5BB7760DC44C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{19044F87-4C6D-4926-B5C8-5BB7760DC44C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{19044F87-4C6D-4926-B5C8-5BB7760DC44C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{19044F87-4C6D-4926-B5C8-5BB7760DC44C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
3
Gearsetter/AssemblyInfo.cs
Normal file
3
Gearsetter/AssemblyInfo.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Gearsetter.Test")]
|
@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0-windows</TargetFramework>
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
<Version>0.5</Version>
|
<Version>0.6</Version>
|
||||||
<LangVersion>12</LangVersion>
|
<LangVersion>12</LangVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Dalamud.Logging;
|
||||||
using Gearsetter.GameData;
|
using Gearsetter.GameData;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
|
||||||
|
|
||||||
namespace Gearsetter.Model;
|
namespace Gearsetter.Model;
|
||||||
|
|
||||||
@ -25,88 +25,26 @@ internal sealed class ItemList
|
|||||||
|
|
||||||
public void Sort()
|
public void Sort()
|
||||||
{
|
{
|
||||||
Items.Sort((a, b) => -Sort(a, b));
|
var preferredItems = Items
|
||||||
|
.Where(x => PreferredItems.ContainsKey(x.ItemId))
|
||||||
|
.ToList();
|
||||||
|
var defaultItems = Items
|
||||||
|
.Except(preferredItems)
|
||||||
|
.OrderDescending(new EquipmentItemComparer(SubstatPriorities))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// insert the preferred items
|
||||||
|
foreach (EquipmentItem preferredItem in preferredItems)
|
||||||
|
{
|
||||||
|
int level = PreferredItems[preferredItem.ItemId];
|
||||||
|
int index = defaultItems.FindIndex(x => x.Level < level);
|
||||||
|
if (index >= 0)
|
||||||
|
defaultItems.Insert(index, preferredItem);
|
||||||
|
else
|
||||||
|
defaultItems.Add(preferredItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int Sort(EquipmentItem a, EquipmentItem b)
|
Items = defaultItems;
|
||||||
{
|
|
||||||
// special items
|
|
||||||
if (PreferredItems.ContainsKey(a.ItemId) || PreferredItems.ContainsKey(b.ItemId))
|
|
||||||
{
|
|
||||||
byte? levelA = null;
|
|
||||||
byte? levelB = null;
|
|
||||||
if (PreferredItems.TryGetValue(a.ItemId, out byte overrideA))
|
|
||||||
levelA = overrideA;
|
|
||||||
if (PreferredItems.TryGetValue(b.ItemId, out byte overrideB))
|
|
||||||
levelB = overrideB;
|
|
||||||
|
|
||||||
if (levelA != null && levelB != null)
|
|
||||||
return levelA.Value.CompareTo(levelB.Value);
|
|
||||||
else if (levelA != null)
|
|
||||||
{
|
|
||||||
if (levelA == b.Level)
|
|
||||||
return (a.ItemLevel - 1).CompareTo(b.ItemLevel);
|
|
||||||
return levelA.Value.CompareTo(b.Level);
|
|
||||||
}
|
|
||||||
else if (levelB != null)
|
|
||||||
{
|
|
||||||
if (a.Level == levelB)
|
|
||||||
return a.ItemLevel.CompareTo(b.ItemLevel - 1);
|
|
||||||
return a.Level.CompareTo(levelB.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// weapons: most damage wins
|
|
||||||
int damageA = a.Damage;
|
|
||||||
int damageB = b.Damage;
|
|
||||||
if (damageA != damageB)
|
|
||||||
return damageA.CompareTo(damageB);
|
|
||||||
|
|
||||||
// gear: primary stat wins
|
|
||||||
int primaryStatA = a.PrimaryStat;
|
|
||||||
int primaryStatB = b.PrimaryStat;
|
|
||||||
if (primaryStatA != primaryStatB)
|
|
||||||
return primaryStatA.CompareTo(primaryStatB);
|
|
||||||
|
|
||||||
// gear: vitality wins
|
|
||||||
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));
|
|
||||||
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
|
|
||||||
if (sumOfSubstatsA == 0 && a.IsCombatRelicWithoutSubstats())
|
|
||||||
sumOfSubstatsA = int.MaxValue;
|
|
||||||
if (sumOfSubstatsB == 0 && b.IsCombatRelicWithoutSubstats())
|
|
||||||
sumOfSubstatsB = int.MaxValue;
|
|
||||||
|
|
||||||
if (sumOfSubstatsA != sumOfSubstatsB)
|
|
||||||
return sumOfSubstatsA.CompareTo(sumOfSubstatsB);
|
|
||||||
|
|
||||||
// level-based sorting
|
|
||||||
if (a.Level != b.Level)
|
|
||||||
return a.Level.CompareTo(b.Level);
|
|
||||||
if (a.ItemLevel != b.ItemLevel)
|
|
||||||
return a.ItemLevel.CompareTo(b.ItemLevel);
|
|
||||||
if (a.Rarity != b.Rarity)
|
|
||||||
return a.Rarity.CompareTo(b.Rarity);
|
|
||||||
|
|
||||||
// individual substats
|
|
||||||
foreach (EBaseParam substat in SubstatPriorities)
|
|
||||||
{
|
|
||||||
int substatA = a.Stats.Get(substat);
|
|
||||||
int substatB = b.Stats.Get(substat);
|
|
||||||
if (substatA != substatB)
|
|
||||||
return substatA.CompareTo(substatB);
|
|
||||||
}
|
|
||||||
|
|
||||||
// fallback
|
|
||||||
return string.CompareOrdinal(a.Name, b.Name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateStats(Dictionary<EClassJob, EBaseParam> primaryStats, Configuration configuration)
|
public void UpdateStats(Dictionary<EClassJob, EBaseParam> primaryStats, Configuration configuration)
|
||||||
@ -135,4 +73,81 @@ internal sealed class ItemList
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class EquipmentItemComparer(IReadOnlyList<EBaseParam> substatPriorities) : IComparer<EquipmentItem>
|
||||||
|
{
|
||||||
|
public int Compare(EquipmentItem? a, EquipmentItem? b)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(a);
|
||||||
|
ArgumentNullException.ThrowIfNull(b);
|
||||||
|
|
||||||
|
// weapons: most damage wins
|
||||||
|
int damageA = a.Damage;
|
||||||
|
int damageB = b.Damage;
|
||||||
|
if (damageA != damageB)
|
||||||
|
return damageA.CompareTo(damageB);
|
||||||
|
|
||||||
|
// gear: primary stat wins
|
||||||
|
//
|
||||||
|
// we pretend every gear item has at least 1 primary stat to ensure weathered items are sorted last(ish),
|
||||||
|
// where they would otherwise get sorted as better-than-shire items (while that may be correct, it's also
|
||||||
|
// stupid)
|
||||||
|
int primaryStatA = Math.Max(1, a.PrimaryStat);
|
||||||
|
int primaryStatB = Math.Max(1, b.PrimaryStat);
|
||||||
|
if (primaryStatA != primaryStatB)
|
||||||
|
return primaryStatA.CompareTo(primaryStatB);
|
||||||
|
|
||||||
|
// gear: vitality wins
|
||||||
|
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));
|
||||||
|
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
|
||||||
|
if (sumOfSubstatsA == 0 && a.IsCombatRelicWithoutSubstats())
|
||||||
|
sumOfSubstatsA = int.MaxValue;
|
||||||
|
if (sumOfSubstatsB == 0 && b.IsCombatRelicWithoutSubstats())
|
||||||
|
sumOfSubstatsB = int.MaxValue;
|
||||||
|
|
||||||
|
if (sumOfSubstatsA != sumOfSubstatsB)
|
||||||
|
return sumOfSubstatsA.CompareTo(sumOfSubstatsB);
|
||||||
|
|
||||||
|
// level-based sorting
|
||||||
|
if (a.Level != b.Level)
|
||||||
|
return a.Level.CompareTo(b.Level);
|
||||||
|
if (a.ItemLevel != b.ItemLevel)
|
||||||
|
return a.ItemLevel.CompareTo(b.ItemLevel);
|
||||||
|
if (a.Rarity != b.Rarity)
|
||||||
|
{
|
||||||
|
// aetherial items aren't "special" enough to be sorted higher than normal gear
|
||||||
|
int rarityA = a.Rarity;
|
||||||
|
int rarityB = b.Rarity;
|
||||||
|
|
||||||
|
if (rarityA == 7)
|
||||||
|
rarityA = 1;
|
||||||
|
|
||||||
|
if (rarityB == 7)
|
||||||
|
rarityB = 1;
|
||||||
|
|
||||||
|
return rarityA.CompareTo(rarityB);
|
||||||
|
}
|
||||||
|
|
||||||
|
// individual substats
|
||||||
|
foreach (EBaseParam substat in substatPriorities)
|
||||||
|
{
|
||||||
|
int substatA = a.Stats.Get(substat);
|
||||||
|
int substatB = b.Stats.Get(substat);
|
||||||
|
if (substatA != substatB)
|
||||||
|
return substatA.CompareTo(substatB);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback
|
||||||
|
return string.CompareOrdinal(a.Name, b.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user