UI changes and stuff
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
|
using Dalamud;
|
||||||
|
|
||||||
namespace Craftimizer.Plugin;
|
namespace Craftimizer.Plugin;
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ public static class LuminaSheets
|
|||||||
public static readonly ExcelSheet<Addon> AddonSheet = Service.DataManager.GetExcelSheet<Addon>()!;
|
public static readonly ExcelSheet<Addon> AddonSheet = Service.DataManager.GetExcelSheet<Addon>()!;
|
||||||
public static readonly ExcelSheet<ClassJob> ClassJobSheet = Service.DataManager.GetExcelSheet<ClassJob>()!;
|
public static readonly ExcelSheet<ClassJob> ClassJobSheet = Service.DataManager.GetExcelSheet<ClassJob>()!;
|
||||||
public static readonly ExcelSheet<Item> ItemSheet = Service.DataManager.GetExcelSheet<Item>()!;
|
public static readonly ExcelSheet<Item> ItemSheet = Service.DataManager.GetExcelSheet<Item>()!;
|
||||||
|
public static readonly ExcelSheet<Item> ItemSheetEnglish = Service.DataManager.GetExcelSheet<Item>(ClientLanguage.English)!;
|
||||||
public static readonly ExcelSheet<Materia> MateriaSheet = Service.DataManager.GetExcelSheet<Materia>()!;
|
public static readonly ExcelSheet<Materia> MateriaSheet = Service.DataManager.GetExcelSheet<Materia>()!;
|
||||||
public static readonly ExcelSheet<BaseParam> BaseParamSheet = Service.DataManager.GetExcelSheet<BaseParam>()!;
|
public static readonly ExcelSheet<BaseParam> BaseParamSheet = Service.DataManager.GetExcelSheet<BaseParam>()!;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ using ClassJob = Craftimizer.Simulator.ClassJob;
|
|||||||
using Condition = Craftimizer.Simulator.Condition;
|
using Condition = Craftimizer.Simulator.Condition;
|
||||||
using Craftimizer.Simulator;
|
using Craftimizer.Simulator;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Craftimizer.Plugin;
|
namespace Craftimizer.Plugin;
|
||||||
@@ -48,6 +47,16 @@ internal static class ActionUtils
|
|||||||
return (null, null);
|
return (null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static uint GetId(this ActionType me, ClassJob classJob)
|
||||||
|
{
|
||||||
|
var (craftAction, action) = GetActionRow(me, classJob);
|
||||||
|
if (craftAction != null)
|
||||||
|
return craftAction.RowId;
|
||||||
|
if (action != null)
|
||||||
|
return action.RowId;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
public static string GetName(this ActionType me, ClassJob classJob)
|
public static string GetName(this ActionType me, ClassJob classJob)
|
||||||
{
|
{
|
||||||
var (craftAction, action) = GetActionRow(me, classJob);
|
var (craftAction, action) = GetActionRow(me, classJob);
|
||||||
@@ -70,8 +79,31 @@ internal static class ActionUtils
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static class ClassJobExtensions
|
internal static class ClassJobUtils
|
||||||
{
|
{
|
||||||
|
public static byte GetClassJobIndex(this ClassJob me) =>
|
||||||
|
me switch
|
||||||
|
{
|
||||||
|
ClassJob.Carpenter => 8,
|
||||||
|
ClassJob.Blacksmith => 9,
|
||||||
|
ClassJob.Armorer => 10,
|
||||||
|
ClassJob.Goldsmith => 11,
|
||||||
|
ClassJob.Leatherworker => 12,
|
||||||
|
ClassJob.Weaver => 13,
|
||||||
|
ClassJob.Alchemist => 14,
|
||||||
|
ClassJob.Culinarian => 15,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// Index in the actual ClassJob sheet
|
||||||
|
public static bool IsClassJob(byte classJobIdx, ClassJob classJob)
|
||||||
|
{
|
||||||
|
var job = LuminaSheets.ClassJobSheet.GetRow(classJobIdx)!;
|
||||||
|
if (job.ClassJobCategory.Row != 33) // DoH
|
||||||
|
return false;
|
||||||
|
return (ClassJob)job.DohDolJobIndex == classJob;
|
||||||
|
}
|
||||||
|
|
||||||
public static bool IsClassJob(this ClassJobCategory me, ClassJob classJob) =>
|
public static bool IsClassJob(this ClassJobCategory me, ClassJob classJob) =>
|
||||||
classJob switch
|
classJob switch
|
||||||
{
|
{
|
||||||
@@ -134,7 +166,7 @@ internal static class ConditionUtils
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static class EffectExtensions
|
internal static class EffectUtils
|
||||||
{
|
{
|
||||||
public static uint StatusId(this EffectType me) =>
|
public static uint StatusId(this EffectType me) =>
|
||||||
me switch
|
me switch
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Craftimizer.Plugin.Utils;
|
||||||
using Craftimizer.Simulator;
|
using Craftimizer.Simulator;
|
||||||
using Craftimizer.Simulator.Actions;
|
using Craftimizer.Simulator.Actions;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
@@ -27,14 +28,14 @@ public class SimulatorWindow : Window
|
|||||||
};
|
};
|
||||||
|
|
||||||
State = new(new(
|
State = new(new(
|
||||||
new CharacterStats { Craftsmanship = 4041, Control = 3905, CP = 609, Level = 90, CLvl = CalculateCLvl(90) },
|
new CharacterStats { Craftsmanship = 4041, Control = 3905, CP = 609, Level = 90, CLvl = Gearsets.CalculateCLvl(90) },
|
||||||
CreateRecipeInfo(LuminaSheets.RecipeSheet.GetRow(35499)!),
|
CreateRecipeInfo(LuminaSheets.RecipeSheet.GetRow(35499)!),
|
||||||
0
|
0
|
||||||
));
|
));
|
||||||
Simulation = new(State);
|
Simulation = new(State);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RecipeInfo CreateRecipeInfo(Recipe recipe)
|
public static RecipeInfo CreateRecipeInfo(Recipe recipe)
|
||||||
{
|
{
|
||||||
var recipeTable = recipe.RecipeLevelTable.Value!;
|
var recipeTable = recipe.RecipeLevelTable.Value!;
|
||||||
return new() {
|
return new() {
|
||||||
@@ -52,11 +53,6 @@ public class SimulatorWindow : Window
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int CalculateCLvl(int characterLevel) =>
|
|
||||||
characterLevel <= 80
|
|
||||||
? LuminaSheets.ParamGrowSheet.GetRow((uint)characterLevel)!.CraftingLevel
|
|
||||||
: (int)LuminaSheets.RecipeLevelTableSheet.First(r => r.ClassJobLevel == characterLevel).RowId;
|
|
||||||
|
|
||||||
public override void Draw()
|
public override void Draw()
|
||||||
{
|
{
|
||||||
ImGui.BeginTable("CraftimizerTable", 2, ImGuiTableFlags.Resizable);
|
ImGui.BeginTable("CraftimizerTable", 2, ImGuiTableFlags.Resizable);
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.Memory;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Craftimizer.Plugin.Utils;
|
||||||
|
|
||||||
|
// https://github.com/Caraxi/SimpleTweaksPlugin/blob/0973b93931cdf8a1b01153984d62f76d998747ff/Utility/ChatHelper.cs#L17
|
||||||
|
public static class Chat
|
||||||
|
{
|
||||||
|
private static class Signatures
|
||||||
|
{
|
||||||
|
internal const string SendChat = "48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 45 84 C9";
|
||||||
|
internal const string SanitiseString = "E8 ?? ?? ?? ?? EB 0A 48 8D 4C 24 ?? E8 ?? ?? ?? ?? 48 8D 8D";
|
||||||
|
}
|
||||||
|
|
||||||
|
private delegate void ProcessChatBoxDelegate(IntPtr uiModule, IntPtr message, IntPtr unused, byte a4);
|
||||||
|
|
||||||
|
private static ProcessChatBoxDelegate? ProcessChatBox { get; }
|
||||||
|
|
||||||
|
private static readonly unsafe delegate* unmanaged<Utf8String*, int, IntPtr, void> _sanitiseString = null!;
|
||||||
|
|
||||||
|
static Chat()
|
||||||
|
{
|
||||||
|
if (Service.SigScanner.TryScanText(Signatures.SendChat, out var processChatBoxPtr))
|
||||||
|
{
|
||||||
|
ProcessChatBox = Marshal.GetDelegateForFunctionPointer<ProcessChatBoxDelegate>(processChatBoxPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
if (Service.SigScanner.TryScanText(Signatures.SanitiseString, out var sanitisePtr))
|
||||||
|
{
|
||||||
|
_sanitiseString = (delegate* unmanaged<Utf8String*, int, IntPtr, void>)sanitisePtr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
|
/// Send a given message to the chat box. <b>This can send chat to the server.</b>
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// <b>This method is unsafe.</b> This method does no checking on your input and
|
||||||
|
/// may send content to the server that the normal client could not. You must
|
||||||
|
/// verify what you're sending and handle content and length to properly use
|
||||||
|
/// this.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">Message to send</param>
|
||||||
|
/// <exception cref="InvalidOperationException">If the signature for this function could not be found</exception>
|
||||||
|
public static unsafe void SendMessageUnsafe(byte[] message)
|
||||||
|
{
|
||||||
|
if (ProcessChatBox == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Could not find signature for chat sending");
|
||||||
|
}
|
||||||
|
|
||||||
|
var uiModule = (IntPtr)Framework.Instance()->GetUiModule();
|
||||||
|
|
||||||
|
using var payload = new ChatPayload(message);
|
||||||
|
var mem1 = Marshal.AllocHGlobal(400);
|
||||||
|
Marshal.StructureToPtr(payload, mem1, false);
|
||||||
|
|
||||||
|
ProcessChatBox(uiModule, mem1, IntPtr.Zero, 0);
|
||||||
|
|
||||||
|
Marshal.FreeHGlobal(mem1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
|
/// Send a given message to the chat box. <b>This can send chat to the server.</b>
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// This method is slightly less unsafe than <see cref="SendMessageUnsafe"/>. It
|
||||||
|
/// will throw exceptions for certain inputs that the client can't normally send,
|
||||||
|
/// but it is still possible to make mistakes. Use with caution.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">message to send</param>
|
||||||
|
/// <exception cref="ArgumentException">If <paramref name="message"/> is empty, longer than 500 bytes in UTF-8, or contains invalid characters.</exception>
|
||||||
|
/// <exception cref="InvalidOperationException">If the signature for this function could not be found</exception>
|
||||||
|
public static void SendMessage(string message)
|
||||||
|
{
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(message);
|
||||||
|
if (bytes.Length == 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("message is empty", nameof(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes.Length > 500)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("message is longer than 500 bytes", nameof(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.Length != SanitiseText(message).Length)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("message contained invalid characters", nameof(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
SendMessageUnsafe(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
|
/// Sanitises a string by removing any invalid input.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// The result of this method is safe to use with
|
||||||
|
/// <see cref="SendMessage"/>, provided that it is not empty or too
|
||||||
|
/// long.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">text to sanitise</param>
|
||||||
|
/// <returns>sanitised text</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">If the signature for this function could not be found</exception>
|
||||||
|
public static unsafe string SanitiseText(string text)
|
||||||
|
{
|
||||||
|
if (_sanitiseString == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Could not find signature for chat sanitisation");
|
||||||
|
}
|
||||||
|
|
||||||
|
var uText = Utf8String.FromString(text);
|
||||||
|
|
||||||
|
_sanitiseString(uText, 0x27F, IntPtr.Zero);
|
||||||
|
var sanitised = uText->ToString();
|
||||||
|
|
||||||
|
uText->Dtor();
|
||||||
|
IMemorySpace.Free(uText);
|
||||||
|
|
||||||
|
return sanitised;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
private readonly struct ChatPayload : IDisposable
|
||||||
|
{
|
||||||
|
[FieldOffset(0)]
|
||||||
|
private readonly IntPtr textPtr;
|
||||||
|
|
||||||
|
[FieldOffset(16)]
|
||||||
|
private readonly ulong textLen;
|
||||||
|
|
||||||
|
[FieldOffset(8)]
|
||||||
|
private readonly ulong unk1;
|
||||||
|
|
||||||
|
[FieldOffset(24)]
|
||||||
|
private readonly ulong unk2;
|
||||||
|
|
||||||
|
internal ChatPayload(byte[] stringBytes)
|
||||||
|
{
|
||||||
|
this.textPtr = Marshal.AllocHGlobal(stringBytes.Length + 30);
|
||||||
|
Marshal.Copy(stringBytes, 0, this.textPtr, stringBytes.Length);
|
||||||
|
Marshal.WriteByte(this.textPtr + stringBytes.Length, 0);
|
||||||
|
|
||||||
|
this.textLen = (ulong)(stringBytes.Length + 1);
|
||||||
|
|
||||||
|
this.unk1 = 64;
|
||||||
|
this.unk2 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(this.textPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
using Craftimizer.Simulator;
|
||||||
|
using Dalamud.Utility;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Craftimizer.Plugin.Utils;
|
||||||
|
internal static unsafe class Gearsets
|
||||||
|
{
|
||||||
|
private static readonly (int CP, int Craftsmanship, int Control, bool HasSplendorous, bool HasSpecialist) BaseStats = (180, 0, 0, false, false);
|
||||||
|
|
||||||
|
private const int ParamCP = 11;
|
||||||
|
private const int ParamCraftsmanship = 70;
|
||||||
|
private const int ParamControl = 71;
|
||||||
|
|
||||||
|
public static CharacterStats CalculateCharacterStats(InventoryContainer* container, int characterLevel, bool canUseManipulation)
|
||||||
|
{
|
||||||
|
var stats = CalculateGearsetStats(container);
|
||||||
|
return new CharacterStats
|
||||||
|
{
|
||||||
|
CP = stats.CP,
|
||||||
|
Craftsmanship = stats.Craftsmanship,
|
||||||
|
Control = stats.Control,
|
||||||
|
Level = characterLevel,
|
||||||
|
CanUseManipulation = canUseManipulation,
|
||||||
|
HasSplendorousBuff = stats.HasSplendorous,
|
||||||
|
IsSpecialist = stats.HasSpecialist,
|
||||||
|
CLvl = CalculateCLvl(characterLevel),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CharacterStats CalculateCharacterStats(RaptureGearsetModule.GearsetEntry* entry, int characterLevel, bool canUseManipulation)
|
||||||
|
{
|
||||||
|
var stats = CalculateGearsetStats(entry);
|
||||||
|
return new CharacterStats
|
||||||
|
{
|
||||||
|
CP = stats.CP,
|
||||||
|
Craftsmanship = stats.Craftsmanship,
|
||||||
|
Control = stats.Control,
|
||||||
|
Level = characterLevel,
|
||||||
|
CanUseManipulation = canUseManipulation,
|
||||||
|
HasSplendorousBuff = stats.HasSplendorous,
|
||||||
|
IsSpecialist = stats.HasSpecialist,
|
||||||
|
CLvl = CalculateCLvl(characterLevel),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int CP, int Craftsmanship, int Control, bool HasSplendorous, bool HasSpecialist) CalculateGearsetStats(InventoryContainer* container)
|
||||||
|
{
|
||||||
|
var stats = BaseStats;
|
||||||
|
for (var i = 0; i < container->Size; ++i)
|
||||||
|
{
|
||||||
|
var itemStats = CalculateGearsetItemStats(container->Items[i]);
|
||||||
|
stats.CP += itemStats.CP;
|
||||||
|
stats.Craftsmanship += itemStats.Craftsmanship;
|
||||||
|
stats.Control += itemStats.Control;
|
||||||
|
stats.HasSplendorous = stats.HasSplendorous || itemStats.HasSplendorous;
|
||||||
|
stats.HasSpecialist = stats.HasSpecialist || itemStats.HasSpecialist;
|
||||||
|
}
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int CP, int Craftsmanship, int Control, bool HasSplendorous, bool HasSpecialist) CalculateGearsetStats(RaptureGearsetModule.GearsetEntry* entry)
|
||||||
|
{
|
||||||
|
var stats = new[]
|
||||||
|
{
|
||||||
|
BaseStats,
|
||||||
|
CalculateGearsetItemStats(entry->MainHand),
|
||||||
|
CalculateGearsetItemStats(entry->OffHand),
|
||||||
|
CalculateGearsetItemStats(entry->Head),
|
||||||
|
CalculateGearsetItemStats(entry->Body),
|
||||||
|
CalculateGearsetItemStats(entry->Hands),
|
||||||
|
// CalculateGearsetItemStats(entry->Belt),
|
||||||
|
CalculateGearsetItemStats(entry->Legs),
|
||||||
|
CalculateGearsetItemStats(entry->Feet),
|
||||||
|
CalculateGearsetItemStats(entry->Ears),
|
||||||
|
CalculateGearsetItemStats(entry->Neck),
|
||||||
|
CalculateGearsetItemStats(entry->Wrists),
|
||||||
|
CalculateGearsetItemStats(entry->RingRight),
|
||||||
|
CalculateGearsetItemStats(entry->RightLeft),
|
||||||
|
CalculateGearsetItemStats(entry->SoulStone),
|
||||||
|
};
|
||||||
|
return stats.Aggregate((a, b) => (a.CP + b.CP, a.Craftsmanship + b.Craftsmanship, a.Control + b.Control, a.HasSplendorous || b.HasSplendorous, a.HasSpecialist || b.HasSpecialist));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int CP, int Craftsmanship, int Control, bool HasSplendorous, bool HasSpecialist) CalculateGearsetItemStats(InventoryItem item) =>
|
||||||
|
CalculateGearsetItemStats(item.ItemID, item.Flags.HasFlag(InventoryItem.ItemFlags.HQ), item.Materia, item.MateriaGrade);
|
||||||
|
|
||||||
|
private static (int CP, int Craftsmanship, int Control, bool HasSplendorous, bool HasSpecialist) CalculateGearsetItemStats(RaptureGearsetModule.GearsetItem item) =>
|
||||||
|
CalculateGearsetItemStats(item.ItemID % 1000000, item.ItemID > 1000000, item.Materia, item.MateriaGrade);
|
||||||
|
|
||||||
|
private static (int CP, int Craftsmanship, int Control, bool HasSplendorous, bool HasSpecialist) CalculateGearsetItemStats(uint itemId, bool isHq, ushort* materiaTypes, byte* materiaGrades)
|
||||||
|
{
|
||||||
|
var item = LuminaSheets.ItemSheet.GetRow(itemId)!;
|
||||||
|
|
||||||
|
int cp = 0, craftsmanship = 0, control = 0;
|
||||||
|
|
||||||
|
void IncreaseStat(int baseParam, int amount)
|
||||||
|
{
|
||||||
|
if (baseParam == ParamCP)
|
||||||
|
cp += amount;
|
||||||
|
else if (baseParam == ParamCraftsmanship)
|
||||||
|
craftsmanship += amount;
|
||||||
|
else if (baseParam == ParamControl)
|
||||||
|
control += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var statIncrease in item.UnkData59)
|
||||||
|
IncreaseStat(statIncrease.BaseParam, statIncrease.BaseParamValue);
|
||||||
|
|
||||||
|
if (isHq)
|
||||||
|
{
|
||||||
|
foreach (var statIncrease in item.UnkData73)
|
||||||
|
IncreaseStat(statIncrease.BaseParamSpecial, statIncrease.BaseParamValueSpecial);
|
||||||
|
}
|
||||||
|
for (var i = 0; i < 5; ++i)
|
||||||
|
{
|
||||||
|
if (materiaTypes[i] == 0)
|
||||||
|
continue;
|
||||||
|
var materia = LuminaSheets.MateriaSheet.GetRow(materiaTypes[i])!;
|
||||||
|
|
||||||
|
IncreaseStat((int)materia.BaseParam.Row, materia.Value[materiaGrades[i]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
cp = Math.Min(cp, CalculateParamCap(item, ParamCP));
|
||||||
|
craftsmanship = Math.Min(craftsmanship, CalculateParamCap(item, ParamCraftsmanship));
|
||||||
|
control = Math.Min(control, CalculateParamCap(item, ParamControl));
|
||||||
|
|
||||||
|
return (cp, craftsmanship, control, IsSpecialistSoulCrystal(item), IsSplendorousTool(itemId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/ffxiv-teamcraft/ffxiv-teamcraft/blob/24d0db2d9676f264edf53651b21005305267c84c/apps/client/src/app/modules/gearsets/materia.service.ts#L265
|
||||||
|
private static int CalculateParamCap(Item item, int paramId)
|
||||||
|
{
|
||||||
|
var ilvl = item.LevelItem.Value!;
|
||||||
|
var param = LuminaSheets.BaseParamSheet.GetRow((uint)paramId)!;
|
||||||
|
|
||||||
|
var baseValue = paramId switch
|
||||||
|
{
|
||||||
|
ParamCP => ilvl.CP,
|
||||||
|
ParamCraftsmanship => ilvl.Craftsmanship,
|
||||||
|
ParamControl => ilvl.Control,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
// https://github.com/ffxiv-teamcraft/ffxiv-teamcraft/blob/24d0db2d9676f264edf53651b21005305267c84c/apps/data-extraction/src/extractors/items.extractor.ts#L6
|
||||||
|
var slotMod = item.EquipSlotCategory.Row switch
|
||||||
|
{
|
||||||
|
1 => param.oneHWpnPct,
|
||||||
|
2 => param.OHPct,
|
||||||
|
3 => param.HeadPct,
|
||||||
|
4 => param.ChestPct,
|
||||||
|
5 => param.HandsPct,
|
||||||
|
6 => param.WaistPct,
|
||||||
|
7 => param.LegsPct,
|
||||||
|
8 => param.FeetPct,
|
||||||
|
9 => param.EarringPct,
|
||||||
|
10 => param.NecklacePct,
|
||||||
|
11 => param.BraceletPct,
|
||||||
|
12 => param.RingPct,
|
||||||
|
13 => param.twoHWpnPct,
|
||||||
|
14 => param.oneHWpnPct,
|
||||||
|
15 => param.ChestHeadPct,
|
||||||
|
16 => param.ChestHeadLegsFeetPct,
|
||||||
|
18 => param.LegsFeetPct,
|
||||||
|
19 => param.HeadChestHandsLegsFeetPct,
|
||||||
|
20 => param.ChestLegsGlovesPct,
|
||||||
|
21 => param.ChestLegsFeetPct,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
var roleMod = param.MeldParam[item.BaseParamModifier];
|
||||||
|
|
||||||
|
// https://github.com/Caraxi/SimpleTweaksPlugin/pull/595
|
||||||
|
var cap = (int)Math.Round((float)baseValue * slotMod / (roleMod * 10f), MidpointRounding.AwayFromZero);
|
||||||
|
return cap == 0 ? int.MaxValue : cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsSpecialistSoulCrystal(Item item) =>
|
||||||
|
// Soul Crystal ItemUICategory DoH Category
|
||||||
|
item.ItemUICategory.Row != 62 && item.ClassJobUse.Value!.ClassJobCategory.Row == 33;
|
||||||
|
|
||||||
|
private static bool IsSplendorousTool(uint itemId) =>
|
||||||
|
LuminaSheets.ItemSheetEnglish.GetRow(itemId)!.Description.ToDalamudString().TextValue.Contains("Increases to quality are 1.75 times higher than normal when material condition is Good.", StringComparison.Ordinal);
|
||||||
|
// 38737 <= itemId && itemId <= 38744;
|
||||||
|
|
||||||
|
public static int CalculateCLvl(int characterLevel) =>
|
||||||
|
characterLevel <= 80
|
||||||
|
? LuminaSheets.ParamGrowSheet.GetRow((uint)characterLevel)!.CraftingLevel
|
||||||
|
: (int)LuminaSheets.RecipeLevelTableSheet.First(r => r.ClassJobLevel == characterLevel).RowId;
|
||||||
|
}
|
||||||
@@ -1,22 +1,29 @@
|
|||||||
|
using Craftimizer.Plugin.Utils;
|
||||||
|
using Craftimizer.Simulator;
|
||||||
|
using Craftimizer.Simulator.Actions;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.Components;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using ActionType = Craftimizer.Simulator.Actions.ActionType;
|
||||||
|
using ClassJob = Craftimizer.Simulator.ClassJob;
|
||||||
|
|
||||||
namespace Craftimizer.Plugin.Windows;
|
namespace Craftimizer.Plugin.Windows;
|
||||||
|
|
||||||
public unsafe class CraftingLog : Window
|
public unsafe class CraftingLog : Window
|
||||||
{
|
{
|
||||||
private const ImGuiWindowFlags WindowFlags = ImGuiWindowFlags.NoDecoration
|
private const ImGuiWindowFlags WindowFlags = ImGuiWindowFlags.NoDecoration
|
||||||
| ImGuiWindowFlags.NoInputs
|
|
||||||
| ImGuiWindowFlags.AlwaysAutoResize
|
| ImGuiWindowFlags.AlwaysAutoResize
|
||||||
| ImGuiWindowFlags.NoFocusOnAppearing
|
| ImGuiWindowFlags.NoFocusOnAppearing
|
||||||
| ImGuiWindowFlags.NoNavFocus;
|
| ImGuiWindowFlags.NoNavFocus;
|
||||||
@@ -25,6 +32,7 @@ public unsafe class CraftingLog : Window
|
|||||||
private RecipeNote* State { get; set; }
|
private RecipeNote* State { get; set; }
|
||||||
private ushort SelectedRecipeId { get; set; }
|
private ushort SelectedRecipeId { get; set; }
|
||||||
private Recipe SelectedRecipe { get; set; } = null!;
|
private Recipe SelectedRecipe { get; set; } = null!;
|
||||||
|
private RecipeInfo SelectedRecipeInfo { get; set; } = null!;
|
||||||
|
|
||||||
public CraftingLog() : base("RecipeNoteHelper", WindowFlags, true)
|
public CraftingLog() : base("RecipeNoteHelper", WindowFlags, true)
|
||||||
{
|
{
|
||||||
@@ -33,180 +41,53 @@ public unsafe class CraftingLog : Window
|
|||||||
|
|
||||||
public override void Draw()
|
public override void Draw()
|
||||||
{
|
{
|
||||||
ImGui.Text("Hai!! :3333");
|
|
||||||
|
|
||||||
var inst = RaptureGearsetModule.Instance();
|
var inst = RaptureGearsetModule.Instance();
|
||||||
|
|
||||||
|
if (Service.ClientState.LocalPlayer == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var recipeClassJob = (ClassJob)SelectedRecipe.CraftType.Row;
|
||||||
|
|
||||||
|
var characterLevel = PlayerState.Instance()->ClassJobLevelArray[recipeClassJob.GetClassJobIndex()];
|
||||||
|
var canUseManipulation = ActionManager.CanUseActionOnTarget(ActionType.Manipulation.GetId(recipeClassJob), (GameObject*)Service.ClientState.LocalPlayer.Address);
|
||||||
|
|
||||||
for (var i = 0; i < 100; i++)
|
for (var i = 0; i < 100; i++)
|
||||||
{
|
{
|
||||||
var gearset = inst->Gearset[i];
|
var gearset = inst->Gearset[i];
|
||||||
if (gearset == null)
|
if (gearset == null)
|
||||||
continue;
|
continue;
|
||||||
if (!gearset->Flags.HasFlag(RaptureGearsetModule.GearsetFlag.Exists))
|
|
||||||
continue;
|
|
||||||
if (gearset->ID != i)
|
if (gearset->ID != i)
|
||||||
continue;
|
continue;
|
||||||
var job = LuminaSheets.ClassJobSheet.GetRow(gearset->ClassJob)!;
|
if (!gearset->Flags.HasFlag(RaptureGearsetModule.GearsetFlag.Exists))
|
||||||
if (job.ClassJobCategory.Row != 33) // DoH
|
|
||||||
continue;
|
continue;
|
||||||
if (job.DohDolJobIndex != SelectedRecipe.CraftType.Row)
|
|
||||||
|
if (!ClassJobUtils.IsClassJob(gearset->ClassJob, recipeClassJob))
|
||||||
continue;
|
continue;
|
||||||
ImGui.Text($"Supported Gearset: {gearset->ID + 1} {Marshal.PtrToStringUTF8((nint)gearset->Name)}");
|
|
||||||
var stats = CalculateGearsetStats(gearset);
|
var stats = Gearsets.CalculateCharacterStats(gearset, characterLevel, canUseManipulation);
|
||||||
ImGui.Text($"{stats.CP} CP, {stats.Craftsmanship} Craftsmanship, {stats.Control} Control");
|
ImGui.Text($"Gearset: {gearset->ID + 1} {Marshal.PtrToStringUTF8((nint)gearset->Name)}");
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGuiComponents.IconButton($"SwapGearset{gearset->ID}", FontAwesomeIcon.SyncAlt))
|
||||||
|
Chat.SendMessage($"/gearset change {gearset->ID + 1}");
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip($"Swap to gearset {gearset->ID + 1}");
|
||||||
|
ImGui.Text($"{stats}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ShowCurrentGearInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ShowCurrentGearInfo()
|
|
||||||
{
|
{
|
||||||
if (Service.ClientState.LocalPlayer == null)
|
var classJob = (byte)Service.ClientState.LocalPlayer.ClassJob.Id;
|
||||||
return;
|
|
||||||
|
|
||||||
var classJob = Service.ClientState.LocalPlayer.ClassJob.Id;
|
if (!ClassJobUtils.IsClassJob(classJob, recipeClassJob))
|
||||||
|
|
||||||
var job = LuminaSheets.ClassJobSheet.GetRow(classJob)!;
|
|
||||||
if (job.ClassJobCategory.Row != 33) // DoH
|
|
||||||
return;
|
|
||||||
if (job.DohDolJobIndex != SelectedRecipe.CraftType.Row)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var container = InventoryManager.Instance()->GetInventoryContainer(InventoryType.EquippedItems);
|
var container = InventoryManager.Instance()->GetInventoryContainer(InventoryType.EquippedItems);
|
||||||
if (container == null)
|
if (container == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
(int CP, int Craftsmanship, int Control) stats = (180, 0, 0);
|
var stats = Gearsets.CalculateCharacterStats(container, characterLevel, canUseManipulation);
|
||||||
for (var i = 0; i < container->Size; ++i)
|
|
||||||
{
|
|
||||||
var itemStats = CalculateGearsetItemStats(container->Items[i]);
|
|
||||||
stats.CP += itemStats.CP;
|
|
||||||
stats.Craftsmanship += itemStats.Craftsmanship;
|
|
||||||
stats.Control += itemStats.Control;
|
|
||||||
}
|
|
||||||
ImGui.Text($"Currently Equipped");
|
ImGui.Text($"Currently Equipped");
|
||||||
ImGui.Text($"{stats.CP} CP, {stats.Craftsmanship} Craftsmanship, {stats.Control} Control");
|
ImGui.Text($"{stats}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly (int CP, int Craftsmanship, int Control) BaseStats = (180, 0, 0);
|
|
||||||
|
|
||||||
private static (int CP, int Craftsmanship, int Control) CalculateGearsetStats(RaptureGearsetModule.GearsetEntry* entry)
|
|
||||||
{
|
|
||||||
var stats = new[]
|
|
||||||
{
|
|
||||||
BaseStats,
|
|
||||||
CalculateGearsetItemStats(entry->MainHand),
|
|
||||||
CalculateGearsetItemStats(entry->OffHand),
|
|
||||||
CalculateGearsetItemStats(entry->Head),
|
|
||||||
CalculateGearsetItemStats(entry->Body),
|
|
||||||
CalculateGearsetItemStats(entry->Hands),
|
|
||||||
// CalculateGearsetItemStats(entry->Belt),
|
|
||||||
CalculateGearsetItemStats(entry->Legs),
|
|
||||||
CalculateGearsetItemStats(entry->Feet),
|
|
||||||
CalculateGearsetItemStats(entry->Ears),
|
|
||||||
CalculateGearsetItemStats(entry->Neck),
|
|
||||||
CalculateGearsetItemStats(entry->Wrists),
|
|
||||||
CalculateGearsetItemStats(entry->RingRight),
|
|
||||||
CalculateGearsetItemStats(entry->RightLeft),
|
|
||||||
CalculateGearsetItemStats(entry->SoulStone),
|
|
||||||
};
|
|
||||||
return stats.Aggregate((a, b) => (a.CP + b.CP, a.Craftsmanship + b.Craftsmanship, a.Control + b.Control));
|
|
||||||
}
|
|
||||||
|
|
||||||
private const int ParamCP = 11;
|
|
||||||
private const int ParamCraftsmanship = 70;
|
|
||||||
private const int ParamControl = 71;
|
|
||||||
|
|
||||||
private static (int CP, int Craftsmanship, int Control) CalculateGearsetItemStats(InventoryItem item) =>
|
|
||||||
CalculateGearsetItemStats(item.ItemID, item.Flags.HasFlag(InventoryItem.ItemFlags.HQ), item.Materia, item.MateriaGrade);
|
|
||||||
|
|
||||||
private static (int CP, int Craftsmanship, int Control) CalculateGearsetItemStats(RaptureGearsetModule.GearsetItem item) =>
|
|
||||||
CalculateGearsetItemStats(item.ItemID % 1000000, item.ItemID > 1000000, item.Materia, item.MateriaGrade);
|
|
||||||
|
|
||||||
private static (int CP, int Craftsmanship, int Control) CalculateGearsetItemStats(uint itemId, bool isHq, ushort* materiaTypes, byte* materiaGrades)
|
|
||||||
{
|
|
||||||
var item = LuminaSheets.ItemSheet.GetRow(itemId)!;
|
|
||||||
|
|
||||||
int cp = 0, craftsmanship = 0, control = 0;
|
|
||||||
|
|
||||||
void IncreaseStat(int baseParam, int amount)
|
|
||||||
{
|
|
||||||
if (baseParam == ParamCP)
|
|
||||||
cp += amount;
|
|
||||||
else if (baseParam == ParamCraftsmanship)
|
|
||||||
craftsmanship += amount;
|
|
||||||
else if (baseParam == ParamControl)
|
|
||||||
control += amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach(var statIncrease in item.UnkData59)
|
|
||||||
IncreaseStat(statIncrease.BaseParam, statIncrease.BaseParamValue);
|
|
||||||
|
|
||||||
if (isHq)
|
|
||||||
{
|
|
||||||
foreach (var statIncrease in item.UnkData73)
|
|
||||||
IncreaseStat(statIncrease.BaseParamSpecial, statIncrease.BaseParamValueSpecial);
|
|
||||||
}
|
|
||||||
for (var i = 0; i < 5; ++i)
|
|
||||||
{
|
|
||||||
if (materiaTypes[i] == 0)
|
|
||||||
continue;
|
|
||||||
var materia = LuminaSheets.MateriaSheet.GetRow(materiaTypes[i])!;
|
|
||||||
|
|
||||||
IncreaseStat((int)materia.BaseParam.Row, materia.Value[materiaGrades[i]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
cp = Math.Min(cp, CalculateMateriaCap(item, ParamCP));
|
|
||||||
craftsmanship = Math.Min(craftsmanship, CalculateMateriaCap(item, ParamCraftsmanship));
|
|
||||||
control = Math.Min(control, CalculateMateriaCap(item, ParamControl));
|
|
||||||
|
|
||||||
return (cp, craftsmanship, control);
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/ffxiv-teamcraft/ffxiv-teamcraft/blob/24d0db2d9676f264edf53651b21005305267c84c/apps/client/src/app/modules/gearsets/materia.service.ts#L265
|
|
||||||
private static int CalculateMateriaCap(Item item, int paramId)
|
|
||||||
{
|
|
||||||
var ilvl = item.LevelItem.Value!;
|
|
||||||
var param = LuminaSheets.BaseParamSheet.GetRow((uint)paramId)!;
|
|
||||||
|
|
||||||
var baseValue = paramId switch
|
|
||||||
{
|
|
||||||
ParamCP => ilvl.CP,
|
|
||||||
ParamCraftsmanship => ilvl.Craftsmanship,
|
|
||||||
ParamControl => ilvl.Control,
|
|
||||||
_ => 0
|
|
||||||
};
|
|
||||||
// https://github.com/ffxiv-teamcraft/ffxiv-teamcraft/blob/24d0db2d9676f264edf53651b21005305267c84c/apps/data-extraction/src/extractors/items.extractor.ts#L6
|
|
||||||
var slotMod = item.EquipSlotCategory.Row switch
|
|
||||||
{
|
|
||||||
1 => param.oneHWpnPct,
|
|
||||||
2 => param.OHPct,
|
|
||||||
3 => param.HeadPct,
|
|
||||||
4 => param.ChestPct,
|
|
||||||
5 => param.HandsPct,
|
|
||||||
6 => param.WaistPct,
|
|
||||||
7 => param.LegsPct,
|
|
||||||
8 => param.FeetPct,
|
|
||||||
9 => param.EarringPct,
|
|
||||||
10 => param.NecklacePct,
|
|
||||||
11 => param.BraceletPct,
|
|
||||||
12 => param.RingPct,
|
|
||||||
13 => param.twoHWpnPct,
|
|
||||||
14 => param.oneHWpnPct,
|
|
||||||
15 => param.ChestHeadPct,
|
|
||||||
16 => param.ChestHeadLegsFeetPct,
|
|
||||||
18 => param.LegsFeetPct,
|
|
||||||
19 => param.HeadChestHandsLegsFeetPct,
|
|
||||||
20 => param.ChestLegsGlovesPct,
|
|
||||||
21 => param.ChestLegsFeetPct,
|
|
||||||
_ => 0
|
|
||||||
};
|
|
||||||
var roleMod = param.MeldParam[item.BaseParamModifier];
|
|
||||||
|
|
||||||
// https://github.com/Caraxi/SimpleTweaksPlugin/pull/595
|
|
||||||
var cap = (int)Math.Round((float)baseValue * slotMod / (roleMod * 10f), MidpointRounding.AwayFromZero);
|
|
||||||
return cap == 0 ? int.MaxValue : cap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool DrawConditions()
|
public override bool DrawConditions()
|
||||||
@@ -240,6 +121,8 @@ public unsafe class CraftingLog : Window
|
|||||||
|
|
||||||
SelectedRecipe = recipe;
|
SelectedRecipe = recipe;
|
||||||
|
|
||||||
|
SelectedRecipeInfo = SimulatorWindow.CreateRecipeInfo(SelectedRecipe);
|
||||||
|
|
||||||
if (!Addon->Unk258->IsVisible)
|
if (!Addon->Unk258->IsVisible)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user