Initial Commit
This commit is contained in:
commit
432eeafd3f
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/.idea
|
||||||
|
*.user
|
16
Gearsetter.sln
Normal file
16
Gearsetter.sln
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
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
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{3E87693D-1FEE-486D-80E9-C6D95E68160F}.Debug|Any CPU.ActiveCfg = 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.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
3
Gearsetter/.gitignore
vendored
Normal file
3
Gearsetter/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/dist
|
||||||
|
/obj
|
||||||
|
/bin
|
147
Gearsetter/CachedItem.cs
Normal file
147
Gearsetter/CachedItem.cs
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
|
||||||
|
namespace Gearsetter;
|
||||||
|
|
||||||
|
internal sealed class CachedItem : IEquatable<CachedItem>
|
||||||
|
{
|
||||||
|
public required Item Item { get; init; }
|
||||||
|
public required uint ItemId { get; init; }
|
||||||
|
public required bool Hq { get; init; }
|
||||||
|
public required string Name { get; init; }
|
||||||
|
public required byte Level { get; init; }
|
||||||
|
public required uint ItemLevel { get; init; }
|
||||||
|
public required byte Rarity { get; init; }
|
||||||
|
public required uint EquipSlotCategory { get; init; }
|
||||||
|
public required IReadOnlyList<ClassJob> ClassJobs { get; set; }
|
||||||
|
|
||||||
|
public int CalculateScore(ClassJob classJob, short level)
|
||||||
|
{
|
||||||
|
var stats = new Stats(Item, Hq);
|
||||||
|
|
||||||
|
int score = 0;
|
||||||
|
if (classJob is >= ClassJob.Miner and <= ClassJob.Fisher)
|
||||||
|
{
|
||||||
|
score += stats.Get(BaseParam.Gathering) + stats.Get(BaseParam.Perception) + stats.Get(BaseParam.GP);
|
||||||
|
}
|
||||||
|
else if (classJob is >= ClassJob.Carpenter and <= ClassJob.Culinarian)
|
||||||
|
{
|
||||||
|
score += stats.Get(BaseParam.Craftsmanship) + stats.Get(BaseParam.Control) + stats.Get(BaseParam.CP);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ItemId == 33648 && level < 80)
|
||||||
|
return int.MaxValue - 1;
|
||||||
|
else if (ItemId == 24589 && level < 70)
|
||||||
|
return int.MaxValue - 2;
|
||||||
|
else if (ItemId == 16039 && level < 50)
|
||||||
|
return int.MaxValue - 3;
|
||||||
|
|
||||||
|
if (classJob is ClassJob.Conjurer or ClassJob.WhiteMage or ClassJob.Scholar or ClassJob.Astrologian
|
||||||
|
or ClassJob.Sage or ClassJob.Thaumaturge or ClassJob.BlackMage or ClassJob.Arcanist or ClassJob.Summoner
|
||||||
|
or ClassJob.RedMage or ClassJob.BlueMage)
|
||||||
|
{
|
||||||
|
score += 1_000_000 * (Item.DamageMag + stats.Get(BaseParam.DamageMag));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
score += 1_000_000 * (Item.DamagePhys + stats.Get(BaseParam.DamagePhys));
|
||||||
|
|
||||||
|
score += Item.DefensePhys + stats.Get(BaseParam.DefensePhys);
|
||||||
|
score += Item.DefenseMag + stats.Get(BaseParam.DefenseMag);
|
||||||
|
|
||||||
|
score += 100 * (stats.Get(BaseParam.Strength) + stats.Get(BaseParam.Dexterity) +
|
||||||
|
stats.Get(BaseParam.Intelligence) + stats.Get(BaseParam.Mind));
|
||||||
|
|
||||||
|
score += Rarity;
|
||||||
|
}
|
||||||
|
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(CachedItem? other)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(null, other)) return false;
|
||||||
|
if (ReferenceEquals(this, other)) return true;
|
||||||
|
return ItemId == other.ItemId && Hq == other.Hq;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
return ReferenceEquals(this, obj) || obj is CachedItem other && Equals(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(ItemId, Hq);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(CachedItem? left, CachedItem? right)
|
||||||
|
{
|
||||||
|
return Equals(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(CachedItem? left, CachedItem? right)
|
||||||
|
{
|
||||||
|
return !Equals(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum BaseParam : byte
|
||||||
|
{
|
||||||
|
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 = 24,
|
||||||
|
|
||||||
|
Craftsmanship = 70,
|
||||||
|
Control = 71,
|
||||||
|
Gathering = 72,
|
||||||
|
Perception = 73,
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class Stats
|
||||||
|
{
|
||||||
|
private readonly Dictionary<BaseParam, short> _values;
|
||||||
|
|
||||||
|
public Stats(Item item, bool hq)
|
||||||
|
{
|
||||||
|
_values = item.UnkData59.Where(x => x.BaseParam > 0)
|
||||||
|
.ToDictionary(x => (BaseParam)x.BaseParam, x => x.BaseParamValue);
|
||||||
|
if (hq)
|
||||||
|
{
|
||||||
|
foreach (var hqstat in item.UnkData73.Select(x =>
|
||||||
|
((BaseParam)x.BaseParamSpecial, x.BaseParamValueSpecial)))
|
||||||
|
{
|
||||||
|
if (_values.TryGetValue(hqstat.Item1, out var stat))
|
||||||
|
_values[hqstat.Item1] = (short)(stat + hqstat.BaseParamValueSpecial);
|
||||||
|
else
|
||||||
|
_values[hqstat.Item1] = hqstat.BaseParamValueSpecial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public short Get(BaseParam param)
|
||||||
|
{
|
||||||
|
_values.TryGetValue(param, out short v);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
Gearsetter/ClassJob.cs
Normal file
46
Gearsetter/ClassJob.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
namespace Gearsetter;
|
||||||
|
|
||||||
|
internal enum ClassJob
|
||||||
|
{
|
||||||
|
Adventurer = 0,
|
||||||
|
Gladiator = 1,
|
||||||
|
Pugilist = 2,
|
||||||
|
Marauder = 3,
|
||||||
|
Lancer = 4,
|
||||||
|
Archer = 5,
|
||||||
|
Conjurer = 6,
|
||||||
|
Thaumaturge = 7,
|
||||||
|
Carpenter = 8,
|
||||||
|
Blacksmith = 9,
|
||||||
|
Armorer = 10,
|
||||||
|
Goldsmith = 11,
|
||||||
|
Leatherworker = 12,
|
||||||
|
Weaver = 13,
|
||||||
|
Alchemist = 14,
|
||||||
|
Culinarian = 15,
|
||||||
|
Miner = 16,
|
||||||
|
Botanist = 17,
|
||||||
|
Fisher = 18,
|
||||||
|
Paladin = 19,
|
||||||
|
Monk = 20,
|
||||||
|
Warrior = 21,
|
||||||
|
Dragoon = 22,
|
||||||
|
Bard = 23,
|
||||||
|
WhiteMage = 24,
|
||||||
|
BlackMage = 25,
|
||||||
|
Arcanist = 26,
|
||||||
|
Summoner = 27,
|
||||||
|
Scholar = 28,
|
||||||
|
Rogue = 29,
|
||||||
|
Ninja = 30,
|
||||||
|
Machinist = 31,
|
||||||
|
DarkKnight = 32,
|
||||||
|
Astrologian = 33,
|
||||||
|
Samurai = 34,
|
||||||
|
RedMage = 35,
|
||||||
|
BlueMage = 36,
|
||||||
|
Gunbreaker = 37,
|
||||||
|
Dancer = 38,
|
||||||
|
Reaper = 39,
|
||||||
|
Sage = 40,
|
||||||
|
}
|
21
Gearsetter/DalamudPackager.targets
Normal file
21
Gearsetter/DalamudPackager.targets
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project>
|
||||||
|
<Target Name="PackagePlugin" AfterTargets="Build" Condition="'$(Configuration)' == 'Debug'">
|
||||||
|
<DalamudPackager
|
||||||
|
ProjectDir="$(ProjectDir)"
|
||||||
|
OutputPath="$(OutputPath)"
|
||||||
|
AssemblyName="$(AssemblyName)"
|
||||||
|
MakeZip="false"
|
||||||
|
VersionComponents="2"/>
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
<Target Name="PackagePlugin" AfterTargets="Build" Condition="'$(Configuration)' == 'Release'">
|
||||||
|
<DalamudPackager
|
||||||
|
ProjectDir="$(ProjectDir)"
|
||||||
|
OutputPath="$(OutputPath)"
|
||||||
|
AssemblyName="$(AssemblyName)"
|
||||||
|
MakeZip="true"
|
||||||
|
VersionComponents="2"
|
||||||
|
Exclude="Gearsetter.deps.json"/>
|
||||||
|
</Target>
|
||||||
|
</Project>
|
60
Gearsetter/Gearsetter.csproj
Normal file
60
Gearsetter/Gearsetter.csproj
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0-windows</TargetFramework>
|
||||||
|
<Version>0.1</Version>
|
||||||
|
<LangVersion>11.0</LangVersion>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||||
|
<OutputPath>dist</OutputPath>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DebugType>portable</DebugType>
|
||||||
|
<PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>
|
||||||
|
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||||
|
<DebugType>portable</DebugType>
|
||||||
|
</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="DalamudPackager" Version="2.1.12"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Dalamud">
|
||||||
|
<HintPath>$(DalamudLibPath)Dalamud.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="ImGui.NET">
|
||||||
|
<HintPath>$(DalamudLibPath)ImGui.NET.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Lumina">
|
||||||
|
<HintPath>$(DalamudLibPath)Lumina.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Lumina.Excel">
|
||||||
|
<HintPath>$(DalamudLibPath)Lumina.Excel.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Newtonsoft.Json">
|
||||||
|
<HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="FFXIVClientStructs">
|
||||||
|
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Target Name="RenameLatestZip" AfterTargets="PackagePlugin" Condition="'$(Configuration)' == 'Release'">
|
||||||
|
<Exec Command="rename $(OutDir)$(AssemblyName)\latest.zip $(AssemblyName)-$(Version).zip"/>
|
||||||
|
</Target>
|
||||||
|
</Project>
|
8
Gearsetter/Gearsetter.json
Normal file
8
Gearsetter/Gearsetter.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Name": "Gearsetter",
|
||||||
|
"Author": "Liza Carvelli",
|
||||||
|
"Punchline": "Find gear upgrades",
|
||||||
|
"Description": "",
|
||||||
|
"RepoUrl": "https://git.carvel.li/liza/Gearsetter",
|
||||||
|
"IconUrl": "https://plugins.carvel.li/icons/Gearsetter.png"
|
||||||
|
}
|
309
Gearsetter/GearsetterPlugin.cs
Normal file
309
Gearsetter/GearsetterPlugin.cs
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Dalamud.Game.Command;
|
||||||
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
|
||||||
|
namespace Gearsetter;
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "UnusedType.Global")]
|
||||||
|
public class GearsetterPlugin : IDalamudPlugin
|
||||||
|
{
|
||||||
|
private static readonly InventoryType[] DefaultInventoryTypes =
|
||||||
|
{
|
||||||
|
InventoryType.Inventory1,
|
||||||
|
InventoryType.Inventory2,
|
||||||
|
InventoryType.Inventory3,
|
||||||
|
InventoryType.Inventory4,
|
||||||
|
InventoryType.ArmoryMainHand,
|
||||||
|
InventoryType.ArmoryOffHand,
|
||||||
|
InventoryType.ArmoryHead,
|
||||||
|
InventoryType.ArmoryBody,
|
||||||
|
InventoryType.ArmoryHands,
|
||||||
|
InventoryType.ArmoryLegs,
|
||||||
|
InventoryType.ArmoryFeets,
|
||||||
|
InventoryType.ArmoryEar,
|
||||||
|
InventoryType.ArmoryNeck,
|
||||||
|
InventoryType.ArmoryWrist,
|
||||||
|
InventoryType.ArmoryRings,
|
||||||
|
InventoryType.EquippedItems,
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly DalamudPluginInterface _pluginInterface;
|
||||||
|
private readonly ICommandManager _commandManager;
|
||||||
|
private readonly IChatGui _chatGui;
|
||||||
|
private readonly IDataManager _dataManager;
|
||||||
|
private readonly IPluginLog _pluginLog;
|
||||||
|
private readonly IClientState _clientState;
|
||||||
|
|
||||||
|
private readonly IReadOnlyDictionary<byte, DalamudLinkPayload> _linkPayloads;
|
||||||
|
private readonly Dictionary<uint, List<ClassJob>> _classJobCategories;
|
||||||
|
private readonly Dictionary<byte, byte> _classJobToArrayIndex;
|
||||||
|
private readonly Dictionary<uint, CachedItem> _cachedItems = new();
|
||||||
|
|
||||||
|
public GearsetterPlugin(DalamudPluginInterface pluginInterface, ICommandManager commandManager, IChatGui chatGui,
|
||||||
|
IDataManager dataManager, IPluginLog pluginLog, IClientState clientState)
|
||||||
|
{
|
||||||
|
_pluginInterface = pluginInterface;
|
||||||
|
_commandManager = commandManager;
|
||||||
|
_chatGui = chatGui;
|
||||||
|
_dataManager = dataManager;
|
||||||
|
_pluginLog = pluginLog;
|
||||||
|
_clientState = clientState;
|
||||||
|
|
||||||
|
_commandManager.AddHandler("/gup", new CommandInfo(ProcessCommand));
|
||||||
|
_linkPayloads = Enumerable.Range(0, 100)
|
||||||
|
.ToDictionary(x => (byte)x, x => _pluginInterface.AddChatLinkHandler((byte)x, ChangeGearset)).AsReadOnly();
|
||||||
|
_clientState.TerritoryChanged += TerritoryChanged;
|
||||||
|
|
||||||
|
_classJobToArrayIndex = dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets.ClassJob>()!
|
||||||
|
.Where(x => x.RowId > 0)
|
||||||
|
.ToDictionary(x => (byte)x.RowId, x => (byte)x.ExpArrayIndex);
|
||||||
|
_classJobCategories = _dataManager.GetExcelSheet<ClassJobCategory>()!
|
||||||
|
.ToDictionary(x => x.RowId, x =>
|
||||||
|
new Dictionary<ClassJob, bool>
|
||||||
|
{
|
||||||
|
{ ClassJob.Adventurer, x.ADV },
|
||||||
|
{ ClassJob.Gladiator, x.GLA },
|
||||||
|
{ ClassJob.Pugilist, x.PGL },
|
||||||
|
{ ClassJob.Marauder, x.MRD },
|
||||||
|
{ ClassJob.Lancer, x.LNC },
|
||||||
|
{ ClassJob.Archer, x.ARC },
|
||||||
|
{ ClassJob.Conjurer, x.CNJ },
|
||||||
|
{ ClassJob.Thaumaturge, x.THM },
|
||||||
|
{ ClassJob.Carpenter, x.CRP },
|
||||||
|
{ ClassJob.Blacksmith, x.BSM },
|
||||||
|
{ ClassJob.Armorer, x.ARM },
|
||||||
|
{ ClassJob.Goldsmith, x.GSM },
|
||||||
|
{ ClassJob.Leatherworker, x.LTW },
|
||||||
|
{ ClassJob.Weaver, x.WVR },
|
||||||
|
{ ClassJob.Alchemist, x.ALC },
|
||||||
|
{ ClassJob.Culinarian, x.CUL },
|
||||||
|
{ ClassJob.Miner, x.MIN },
|
||||||
|
{ ClassJob.Botanist, x.BTN },
|
||||||
|
{ ClassJob.Fisher, x.FSH },
|
||||||
|
{ ClassJob.Paladin, x.PLD },
|
||||||
|
{ ClassJob.Monk, x.MNK },
|
||||||
|
{ ClassJob.Warrior, x.WAR },
|
||||||
|
{ ClassJob.Dragoon, x.DRG },
|
||||||
|
{ ClassJob.Bard, x.BRD },
|
||||||
|
{ ClassJob.WhiteMage, x.WHM },
|
||||||
|
{ ClassJob.BlackMage, x.BLM },
|
||||||
|
{ ClassJob.Arcanist, x.ACN },
|
||||||
|
{ ClassJob.Summoner, x.SMN },
|
||||||
|
{ ClassJob.Scholar, x.SCH },
|
||||||
|
{ ClassJob.Rogue, x.ROG },
|
||||||
|
{ ClassJob.Ninja, x.NIN },
|
||||||
|
{ ClassJob.Machinist, x.MCH },
|
||||||
|
{ ClassJob.DarkKnight, x.DRK },
|
||||||
|
{ ClassJob.Astrologian, x.AST },
|
||||||
|
{ ClassJob.Samurai, x.SAM },
|
||||||
|
{ ClassJob.RedMage, x.RDM },
|
||||||
|
{ ClassJob.BlueMage, x.BLU },
|
||||||
|
{ ClassJob.Gunbreaker, x.GNB },
|
||||||
|
{ ClassJob.Dancer, x.DNC },
|
||||||
|
{ ClassJob.Reaper, x.RPR },
|
||||||
|
{ ClassJob.Sage, x.SGE },
|
||||||
|
}
|
||||||
|
.Where(y => y.Value)
|
||||||
|
.Select(y => y.Key)
|
||||||
|
.ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TerritoryChanged(ushort territory)
|
||||||
|
{
|
||||||
|
if (territory == 128)
|
||||||
|
ShowUpgrades();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessCommand(string command, string arguments) => ShowUpgrades();
|
||||||
|
|
||||||
|
private unsafe void ShowUpgrades()
|
||||||
|
{
|
||||||
|
var inventoryManager = InventoryManager.Instance();
|
||||||
|
List<CachedItem> inventoryItems = new();
|
||||||
|
foreach (var inventoryType in DefaultInventoryTypes)
|
||||||
|
{
|
||||||
|
var container = inventoryManager->GetInventoryContainer(inventoryType);
|
||||||
|
for (int i = 0; i < container->Size; ++i)
|
||||||
|
{
|
||||||
|
var item = container->GetInventorySlot(i);
|
||||||
|
if (item != null && item->ItemID != 0)
|
||||||
|
{
|
||||||
|
CachedItem? cachedItem = LookupItem(item->ItemID, item->Flags.HasFlag(InventoryItem.ItemFlags.HQ));
|
||||||
|
if (cachedItem != null)
|
||||||
|
inventoryItems.Add(cachedItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var gearsetModule = RaptureGearsetModule.Instance();
|
||||||
|
if (gearsetModule == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool anyUpgrade = false;
|
||||||
|
for (int i = 0; i < 100; ++i)
|
||||||
|
{
|
||||||
|
var gearset = gearsetModule->GetGearset(i);
|
||||||
|
if (gearset != null && gearset->Flags.HasFlag(RaptureGearsetModule.GearsetFlag.Exists))
|
||||||
|
{
|
||||||
|
anyUpgrade |= HandleGearset(gearset, inventoryItems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!anyUpgrade)
|
||||||
|
_chatGui.Print("All your gearsets are OK.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe bool HandleGearset(RaptureGearsetModule.GearsetEntry* gearset, List<CachedItem> inventoryItems)
|
||||||
|
{
|
||||||
|
string name = GetGearsetName(gearset);
|
||||||
|
if (name.Contains('_') || name.Contains("Eureka") || name.Contains("Bozja"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
List<List<SeString>> upgrades = new()
|
||||||
|
{
|
||||||
|
HandleGearsetItem("Main Hand", gearset, gearset->MainHand, inventoryItems),
|
||||||
|
HandleGearsetItem("Off Hand", gearset, gearset->OffHand, inventoryItems),
|
||||||
|
|
||||||
|
HandleGearsetItem("Head", gearset, gearset->Head, inventoryItems),
|
||||||
|
HandleGearsetItem("Body", gearset, gearset->Body, inventoryItems),
|
||||||
|
HandleGearsetItem("Hands", gearset, gearset->Hands, inventoryItems),
|
||||||
|
HandleGearsetItem("Legs", gearset, gearset->Legs, inventoryItems),
|
||||||
|
HandleGearsetItem("Feet", gearset, gearset->Feet, inventoryItems),
|
||||||
|
|
||||||
|
HandleGearsetItem("Ears", gearset, gearset->Ears, inventoryItems),
|
||||||
|
HandleGearsetItem("Neck", gearset, gearset->Neck, inventoryItems),
|
||||||
|
HandleGearsetItem("Wrists", gearset, gearset->Wrists, inventoryItems),
|
||||||
|
HandleGearsetItem("Rings", gearset, new[] { gearset->RingRight, gearset->RingLeft }, inventoryItems),
|
||||||
|
};
|
||||||
|
|
||||||
|
List<SeString> flatUpgrades = upgrades.SelectMany(x => x).ToList();
|
||||||
|
if (flatUpgrades.Count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_chatGui.Print(
|
||||||
|
new SeStringBuilder()
|
||||||
|
.Append("Gearset ")
|
||||||
|
.AddUiForeground(1)
|
||||||
|
.Add(_linkPayloads[gearset->ID])
|
||||||
|
.Append($"#{gearset->ID + 1}: ")
|
||||||
|
.Append(name)
|
||||||
|
.Add(RawPayload.LinkTerminator)
|
||||||
|
.AddUiForegroundOff()
|
||||||
|
.Build());
|
||||||
|
|
||||||
|
foreach (var upgrade in flatUpgrades)
|
||||||
|
_chatGui.Print(new SeString(new TextPayload(" - ")).Append(upgrade));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This probably includes the ilvl; at the very minimum attempting to print this directly to chat will act as if
|
||||||
|
/// the string ends after the name (and not render ANY text on the same line after the name).
|
||||||
|
/// </summary>
|
||||||
|
private unsafe string GetGearsetName(RaptureGearsetModule.GearsetEntry* gearset)
|
||||||
|
=> Encoding.UTF8.GetString(gearset->Name, 0x2F).Split((char)0)[0];
|
||||||
|
|
||||||
|
private unsafe List<SeString> HandleGearsetItem(string label, RaptureGearsetModule.GearsetEntry* gearset,
|
||||||
|
RaptureGearsetModule.GearsetItem gearsetItem, List<CachedItem> inventoryItems)
|
||||||
|
=> HandleGearsetItem(label, gearset, new[] { gearsetItem }, inventoryItems);
|
||||||
|
|
||||||
|
private unsafe List<SeString> HandleGearsetItem(string label, RaptureGearsetModule.GearsetEntry* gearset,
|
||||||
|
RaptureGearsetModule.GearsetItem[] gearsetItem, List<CachedItem> inventoryItems)
|
||||||
|
{
|
||||||
|
gearsetItem = gearsetItem.Where(x => x.ItemID != 0).ToArray();
|
||||||
|
if (gearsetItem.Length > 0)
|
||||||
|
{
|
||||||
|
ClassJob classJob = (ClassJob)gearset->ClassJob;
|
||||||
|
CachedItem[] currentItems = gearsetItem.Select(x => LookupItem(x.ItemID)).Where(x => x != null)
|
||||||
|
.Select(x => x!).ToArray();
|
||||||
|
if (currentItems.Length == 0)
|
||||||
|
{
|
||||||
|
_pluginLog.Information($"Unable to find gearset items");
|
||||||
|
return new List<SeString>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var level = PlayerState.Instance()->ClassJobLevelArray[
|
||||||
|
_classJobToArrayIndex[gearset->ClassJob]];
|
||||||
|
|
||||||
|
var bestItems = inventoryItems
|
||||||
|
.Where(x => x.EquipSlotCategory == currentItems[0].EquipSlotCategory)
|
||||||
|
.Where(x => x.Level <= level)
|
||||||
|
.Where(x => x.ClassJobs.Contains(classJob))
|
||||||
|
.Where(x => x.CalculateScore(classJob, level) > 0)
|
||||||
|
.OrderByDescending(x => x.CalculateScore(classJob, level))
|
||||||
|
.Take(gearsetItem.Length)
|
||||||
|
.ToList();
|
||||||
|
foreach (var currentItem in currentItems)
|
||||||
|
{
|
||||||
|
if (bestItems.Contains(currentItem))
|
||||||
|
bestItems.Remove(currentItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't make suggestions for equal scores
|
||||||
|
bestItems.RemoveAll(x =>
|
||||||
|
x.CalculateScore(classJob, level) ==
|
||||||
|
currentItems.Select(y => y.CalculateScore(classJob, level)).Max());
|
||||||
|
|
||||||
|
return bestItems
|
||||||
|
.Select(x => new SeString(new TextPayload($"{label}: "))
|
||||||
|
.Append(SeString.CreateItemLink(x.ItemId, x.Hq))).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new List<SeString>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private CachedItem? LookupItem(uint itemId)
|
||||||
|
{
|
||||||
|
if (_cachedItems.TryGetValue(itemId, out CachedItem? cachedItem))
|
||||||
|
return cachedItem;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var item = _dataManager.GetExcelSheet<Item>()!.GetRow(itemId % 1_000_000)!;
|
||||||
|
cachedItem = new CachedItem
|
||||||
|
{
|
||||||
|
Item = item,
|
||||||
|
ItemId = item.RowId,
|
||||||
|
Hq = itemId > 1_000_000,
|
||||||
|
Name = item.Name.ToString(),
|
||||||
|
Level = item.LevelEquip,
|
||||||
|
ItemLevel = item.LevelItem.Row,
|
||||||
|
Rarity = item.Rarity,
|
||||||
|
EquipSlotCategory = item.EquipSlotCategory.Row,
|
||||||
|
ClassJobs = _classJobCategories[item.ClassJobCategory.Row],
|
||||||
|
};
|
||||||
|
_cachedItems[itemId] = cachedItem;
|
||||||
|
return cachedItem;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
_pluginLog.Information($"Unable to lookup item {itemId}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CachedItem? LookupItem(uint itemId, bool hq)
|
||||||
|
=> LookupItem(itemId + (hq ? 1_000_000u : 0));
|
||||||
|
|
||||||
|
private unsafe void ChangeGearset(uint commandId, SeString seString)
|
||||||
|
=> RaptureGearsetModule.Instance()->EquipGearset((byte)commandId);
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_clientState.TerritoryChanged -= TerritoryChanged;
|
||||||
|
_pluginInterface.RemoveChatLinkHandler();
|
||||||
|
_commandManager.RemoveHandler("/gup");
|
||||||
|
}
|
||||||
|
}
|
13
Gearsetter/packages.lock.json
Normal file
13
Gearsetter/packages.lock.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"dependencies": {
|
||||||
|
"net7.0-windows7.0": {
|
||||||
|
"DalamudPackager": {
|
||||||
|
"type": "Direct",
|
||||||
|
"requested": "[2.1.12, )",
|
||||||
|
"resolved": "2.1.12",
|
||||||
|
"contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
global.json
Normal file
7
global.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"sdk": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"rollForward": "latestMinor",
|
||||||
|
"allowPrerelease": false
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user