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
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gearsetter", "Gearsetter\Gearsetter.csproj", "{3E87693D-1FEE-486D-80E9-C6D95E68160F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gearsetter.Test", "Gearsetter.Test\Gearsetter.Test.csproj", "{19044F87-4C6D-4926-B5C8-5BB7760DC44C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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}.Release|Any CPU.ActiveCfg = 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
|
||||
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">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Version>0.5</Version>
|
||||
<Version>0.6</Version>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
|
@ -2,8 +2,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Dalamud.Logging;
|
||||
using Gearsetter.GameData;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Gearsetter.Model;
|
||||
|
||||
@ -25,88 +25,26 @@ internal sealed class ItemList
|
||||
|
||||
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();
|
||||
|
||||
private int Sort(EquipmentItem a, EquipmentItem b)
|
||||
{
|
||||
// special items
|
||||
if (PreferredItems.ContainsKey(a.ItemId) || PreferredItems.ContainsKey(b.ItemId))
|
||||
// insert the preferred items
|
||||
foreach (EquipmentItem preferredItem in preferredItems)
|
||||
{
|
||||
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);
|
||||
}
|
||||
int level = PreferredItems[preferredItem.ItemId];
|
||||
int index = defaultItems.FindIndex(x => x.Level < level);
|
||||
if (index >= 0)
|
||||
defaultItems.Insert(index, preferredItem);
|
||||
else
|
||||
defaultItems.Add(preferredItem);
|
||||
}
|
||||
|
||||
// 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);
|
||||
Items = defaultItems;
|
||||
}
|
||||
|
||||
public void UpdateStats(Dictionary<EClassJob, EBaseParam> primaryStats, Configuration configuration)
|
||||
@ -135,4 +73,81 @@ internal sealed class ItemList
|
||||
.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