Updates for v9, begin ui rework

This commit is contained in:
Asriel Camora
2023-10-01 03:43:11 -07:00
parent 36b9e4fb6d
commit d9e78166d9
20 changed files with 785 additions and 62 deletions
+10
View File
@@ -28,6 +28,12 @@
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="../icon.png" /> <EmbeddedResource Include="../icon.png" />
<EmbeddedResource Include="Graphics\collectible_badge.png" />
<EmbeddedResource Include="Graphics\expert.png" />
<EmbeddedResource Include="Graphics\expert_badge.png" />
<EmbeddedResource Include="Graphics\no_manip.png" />
<EmbeddedResource Include="Graphics\specialist.png" />
<EmbeddedResource Include="Graphics\splendorous.png" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -50,6 +56,10 @@
<HintPath>$(DalamudLibPath)Dalamud.dll</HintPath> <HintPath>$(DalamudLibPath)Dalamud.dll</HintPath>
<Private>false</Private> <Private>false</Private>
</Reference> </Reference>
<Reference Include="Dalamud">
<HintPath>$(DalamudLibPath)Dalamud.Interface.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="ImGui.NET"> <Reference Include="ImGui.NET">
<HintPath>$(DalamudLibPath)ImGui.NET.dll</HintPath> <HintPath>$(DalamudLibPath)ImGui.NET.dll</HintPath>
<Private>false</Private> <Private>false</Private>
Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

-19
View File
@@ -1,19 +0,0 @@
using ImGuiScene;
using System.Collections.Generic;
namespace Craftimizer.Plugin;
internal static class Icons
{
private static readonly Dictionary<string, TextureWrap> Cache = new();
public static TextureWrap GetIconFromPath(string path)
{
if (!Cache.TryGetValue(path, out var ret))
Cache.Add(path, ret = Service.DataManager.GetImGuiTexture(path)!);
return ret;
}
public static TextureWrap GetIconFromId(ushort id) =>
GetIconFromPath($"ui/icon/{id / 1000 * 1000:000000}/{id:000000}_hr1.tex");
}
+20 -1
View File
@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Numerics; using System.Numerics;
namespace Craftimizer.Plugin; namespace Craftimizer.Plugin;
internal static class ImGuiUtils internal static class ImGuiUtils
@@ -158,4 +157,24 @@ internal static class ImGuiUtils
ImGui.SetTooltip("Open in Browser"); ImGui.SetTooltip("Open in Browser");
} }
} }
public static void AlignCentered(float width)
{
var availWidth = ImGui.GetContentRegionAvail().X;
if (availWidth > width)
ImGui.SetCursorPosX(ImGui.GetCursorPos().X + (availWidth - width) / 2);
}
// https://stackoverflow.com/a/67855985
public static void TextCentered(string text)
{
AlignCentered(ImGui.CalcTextSize(text).X);
ImGui.TextUnformatted(text);
}
public static bool ButtonCentered(string text)
{
AlignCentered(ImGui.CalcTextSize(text).X + ImGui.GetStyle().FramePadding.Y * 2);
return ImGui.Button(text);
}
} }
+3
View File
@@ -16,6 +16,9 @@ public static class LuminaSheets
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<Item> ItemSheetEnglish = Service.DataManager.GetExcelSheet<Item>(ClientLanguage.English)!;
public static readonly ExcelSheet<ENpcResident> ENpcResidentSheet = Service.DataManager.GetExcelSheet<ENpcResident>()!;
public static readonly ExcelSheet<Level> LevelSheet = Service.DataManager.GetExcelSheet<Level>()!;
public static readonly ExcelSheet<Quest> QuestSheet = Service.DataManager.GetExcelSheet<Quest>()!;
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>()!;
public static readonly ExcelSheet<ItemFood> ItemFoodSheet = Service.DataManager.GetExcelSheet<ItemFood>()!; public static readonly ExcelSheet<ItemFood> ItemFoodSheet = Service.DataManager.GetExcelSheet<ItemFood>()!;
+16 -18
View File
@@ -1,6 +1,7 @@
using Craftimizer.Plugin.Windows; using Craftimizer.Plugin.Windows;
using Craftimizer.Simulator; using Craftimizer.Simulator;
using Craftimizer.Utils; using Craftimizer.Utils;
using Craftimizer.Windows;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
using Dalamud.IoC; using Dalamud.IoC;
using Dalamud.Plugin; using Dalamud.Plugin;
@@ -16,39 +17,35 @@ public sealed class Plugin : IDalamudPlugin
public string Name => "Craftimizer"; public string Name => "Craftimizer";
public string Version { get; } public string Version { get; }
public string Author { get; } public string Author { get; }
public string Configuration { get; } public string BuildConfiguration { get; }
public TextureWrap Icon { get; } public TextureWrap Icon { get; }
public WindowSystem WindowSystem { get; } public WindowSystem WindowSystem { get; }
public Settings SettingsWindow { get; } public Settings SettingsWindow { get; }
public CraftingLog RecipeNoteWindow { get; } public Craftimizer.Windows.RecipeNote RecipeNoteWindow { get; }
public Craft SynthesisWindow { get; } public Craft SynthesisWindow { get; }
public Windows.Simulator? SimulatorWindow { get; set; } public Windows.Simulator? SimulatorWindow { get; set; }
public Configuration Configuration { get; }
public Hooks Hooks { get; } public Hooks Hooks { get; }
public RecipeNote RecipeNote { get; } public Craftimizer.Utils.RecipeNote RecipeNote { get; }
public IconManager IconManager { get; }
public Plugin([RequiredVersion("1.0")] DalamudPluginInterface pluginInterface) public Plugin([RequiredVersion("1.0")] DalamudPluginInterface pluginInterface)
{ {
Service.Plugin = this; Service.Initialize(this, pluginInterface);
pluginInterface.Create<Service>();
Service.Configuration = pluginInterface.GetPluginConfig() as Configuration ?? new Configuration(); Configuration = pluginInterface.GetPluginConfig() as Configuration ?? new();
Hooks = new();
RecipeNote = new();
IconManager = new();
WindowSystem = new(Name);
var assembly = Assembly.GetExecutingAssembly(); var assembly = Assembly.GetExecutingAssembly();
Version = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion; Version = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion;
Author = assembly.GetCustomAttribute<AssemblyCompanyAttribute>()!.Company; Author = assembly.GetCustomAttribute<AssemblyCompanyAttribute>()!.Company;
Configuration = assembly.GetCustomAttribute<AssemblyConfigurationAttribute>()!.Configuration; BuildConfiguration = assembly.GetCustomAttribute<AssemblyConfigurationAttribute>()!.Configuration;
byte[] iconData; Icon = IconManager.GetAssemblyTexture("icon.png");
using (var stream = assembly.GetManifestResourceStream("Craftimizer.icon.png")!)
{
iconData = new byte[stream.Length];
_ = stream.Read(iconData);
}
Icon = Service.PluginInterface.UiBuilder.LoadImage(iconData);
Hooks = new();
RecipeNote = new();
WindowSystem = new(Name);
SettingsWindow = new(); SettingsWindow = new();
RecipeNoteWindow = new(); RecipeNoteWindow = new();
@@ -86,5 +83,6 @@ public sealed class Plugin : IDalamudPlugin
SynthesisWindow.Dispose(); SynthesisWindow.Dispose();
RecipeNote.Dispose(); RecipeNote.Dispose();
Hooks.Dispose(); Hooks.Dispose();
IconManager.Dispose();
} }
} }
+18 -13
View File
@@ -1,13 +1,11 @@
using Dalamud.Data; using Craftimizer.Utils;
using Dalamud.Game; using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.Command;
using Dalamud.Game.Gui;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
using Dalamud.IoC; using Dalamud.IoC;
using Dalamud.Plugin; using Dalamud.Plugin;
using Dalamud.Plugin.Services;
namespace Craftimizer.Plugin; namespace Craftimizer.Plugin;
@@ -15,19 +13,26 @@ public sealed class Service
{ {
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
[PluginService] public static DalamudPluginInterface PluginInterface { get; private set; } [PluginService] public static DalamudPluginInterface PluginInterface { get; private set; }
[PluginService] public static CommandManager CommandManager { get; private set; } [PluginService] public static ICommandManager CommandManager { get; private set; }
[PluginService] public static ObjectTable Objects { get; private set; } [PluginService] public static IObjectTable Objects { get; private set; }
[PluginService] public static SigScanner SigScanner { get; private set; } [PluginService] public static ISigScanner SigScanner { get; private set; }
[PluginService] public static GameGui GameGui { get; private set; } [PluginService] public static IGameGui GameGui { get; private set; }
[PluginService] public static ClientState ClientState { get; private set; } [PluginService] public static IClientState ClientState { get; private set; }
[PluginService] public static DataManager DataManager { get; private set; } [PluginService] public static IDataManager DataManager { get; private set; }
[PluginService] public static TargetManager TargetManager { get; private set; } [PluginService] public static ITextureProvider TextureProvider { get; private set; }
[PluginService] public static ITargetManager TargetManager { get; private set; }
[PluginService] public static Condition Condition { get; private set; } [PluginService] public static Condition Condition { get; private set; }
[PluginService] public static Framework Framework { get; private set; } [PluginService] public static Framework Framework { get; private set; }
public static Plugin Plugin { get; internal set; } public static Plugin Plugin { get; private set; }
public static Configuration Configuration { get; internal set; } public static Configuration Configuration => Plugin.Configuration;
public static WindowSystem WindowSystem => Plugin.WindowSystem; public static WindowSystem WindowSystem => Plugin.WindowSystem;
public static IconManager IconManager => Plugin.IconManager;
#pragma warning restore CS8618 #pragma warning restore CS8618
internal static void Initialize(Plugin plugin, DalamudPluginInterface iface)
{
Plugin = plugin;
iface.Create<Service>();
}
} }
+23 -6
View File
@@ -2,6 +2,9 @@ using Craftimizer.Simulator;
using Craftimizer.Simulator.Actions; using Craftimizer.Simulator.Actions;
using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Utility; using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using ImGuiScene; using ImGuiScene;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
using System; using System;
@@ -10,8 +13,10 @@ using System.Linq;
using System.Numerics; using System.Numerics;
using System.Text; using System.Text;
using Action = Lumina.Excel.GeneratedSheets.Action; using Action = Lumina.Excel.GeneratedSheets.Action;
using ActionType = Craftimizer.Simulator.Actions.ActionType;
using ClassJob = Craftimizer.Simulator.ClassJob; using ClassJob = Craftimizer.Simulator.ClassJob;
using Condition = Craftimizer.Simulator.Condition; using Condition = Craftimizer.Simulator.Condition;
using Status = Lumina.Excel.GeneratedSheets.Status;
namespace Craftimizer.Plugin; namespace Craftimizer.Plugin;
@@ -85,11 +90,11 @@ internal static class ActionUtils
{ {
var (craftAction, action) = GetActionRow(me, classJob); var (craftAction, action) = GetActionRow(me, classJob);
if (craftAction != null) if (craftAction != null)
return Icons.GetIconFromId(craftAction.Icon); return Service.IconManager.GetIcon(craftAction.Icon);
if (action != null) if (action != null)
return Icons.GetIconFromId(action.Icon); return Service.IconManager.GetIcon(action.Icon);
// Old "Steady Hand" action icon // Old "Steady Hand" action icon
return Icons.GetIconFromId(1953); return Service.IconManager.GetIcon(1953);
} }
public static ActionType? GetActionTypeFromId(uint actionId, ClassJob classJob, bool isCraftAction) public static ActionType? GetActionTypeFromId(uint actionId, ClassJob classJob, bool isCraftAction)
@@ -145,12 +150,24 @@ internal static class ClassJobUtils
public static sbyte GetExpArrayIdx(this ClassJob me) => public static sbyte GetExpArrayIdx(this ClassJob me) =>
LuminaSheets.ClassJobSheet.GetRow(me.GetClassJobIndex())!.ExpArrayIndex; LuminaSheets.ClassJobSheet.GetRow(me.GetClassJobIndex())!.ExpArrayIndex;
public static string GetName(this ClassJob classJob) public static unsafe short GetPlayerLevel(this ClassJob me) =>
PlayerState.Instance()->ClassJobLevelArray[me.GetExpArrayIdx()];
public static unsafe bool CanPlayerUseManipulation(this ClassJob me) =>
ActionManager.CanUseActionOnTarget(ActionType.Manipulation.GetId(me), (GameObject*)Service.ClientState.LocalPlayer!.Address);
public static string GetName(this ClassJob me)
{ {
var job = LuminaSheets.ClassJobSheet.GetRow(classJob.GetClassJobIndex())!; var job = LuminaSheets.ClassJobSheet.GetRow(me.GetClassJobIndex())!;
return CultureInfo.InvariantCulture.TextInfo.ToTitleCase(job.Name.ToDalamudString().TextValue); return CultureInfo.InvariantCulture.TextInfo.ToTitleCase(job.Name.ToDalamudString().TextValue);
} }
public static Quest GetUnlockQuest(this ClassJob me) =>
LuminaSheets.QuestSheet.GetRow(65720 + (uint)me) ?? throw new ArgumentException($"Could not get unlock quest for {me}", nameof(me));
public static ushort GetIconId(this ClassJob me) =>
(ushort)(62100 + me.GetClassJobIndex());
public static bool IsClassJob(this ClassJobCategory me, ClassJob classJob) => public static bool IsClassJob(this ClassJobCategory me, ClassJob classJob) =>
classJob switch classJob switch
{ {
@@ -303,7 +320,7 @@ internal static class EffectUtils
} }
public static TextureWrap GetIcon(this EffectType me, int strength) => public static TextureWrap GetIcon(this EffectType me, int strength) =>
Icons.GetIconFromId(me.GetIconId(strength)); Service.IconManager.GetIcon(me.GetIconId(strength));
public static string GetTooltip(this EffectType me, int strength, int duration) public static string GetTooltip(this EffectType me, int strength, int duration)
{ {
+6 -3
View File
@@ -8,7 +8,7 @@ using System;
using System.Linq; using System.Linq;
namespace Craftimizer.Plugin.Utils; namespace Craftimizer.Plugin.Utils;
internal static unsafe class Gearsets public static unsafe class Gearsets
{ {
public record struct GearsetStats(int CP, int Craftsmanship, int Control); public record struct GearsetStats(int CP, int Craftsmanship, int Control);
public record struct GearsetMateria(ushort Type, ushort Grade); public record struct GearsetMateria(ushort Type, ushort Grade);
@@ -117,9 +117,12 @@ internal static unsafe class Gearsets
public static bool IsSpecialistSoulCrystal(GearsetItem item) public static bool IsSpecialistSoulCrystal(GearsetItem item)
{ {
if (item.itemId == 0)
return false;
var luminaItem = LuminaSheets.ItemSheet.GetRow(item.itemId)!; var luminaItem = LuminaSheets.ItemSheet.GetRow(item.itemId)!;
// Soul Crystal ItemUICategory DoH Category // Soul Crystal ItemUICategory DoH Category
return luminaItem.ItemUICategory.Row != 62 && luminaItem.ClassJobUse.Value!.ClassJobCategory.Row == 33; return luminaItem.ItemUICategory.Row == 62 && luminaItem.ClassJobUse.Value!.ClassJobCategory.Row == 33;
} }
public static bool IsSplendorousTool(GearsetItem item) => public static bool IsSplendorousTool(GearsetItem item) =>
+66
View File
@@ -0,0 +1,66 @@
using Craftimizer.Plugin;
using Dalamud.Interface.Internal;
using ImGuiScene;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
namespace Craftimizer.Utils;
public sealed class IconManager : IDisposable
{
private readonly Dictionary<uint, IDalamudTextureWrap> iconCache = new();
private readonly Dictionary<string, IDalamudTextureWrap> textureCache = new();
private readonly Dictionary<string, TextureWrap> assemblyCache = new();
public IDalamudTextureWrap GetIcon(uint id)
{
if (!iconCache.TryGetValue(id, out var ret))
iconCache.Add(id, ret = Service.TextureProvider.GetIcon(id) ??
throw new ArgumentException($"Invalid icon id {id}", nameof(id)));
return ret;
}
public IDalamudTextureWrap GetTexture(string path)
{
if (!textureCache.TryGetValue(path, out var ret))
textureCache.Add(path, ret = Service.TextureProvider.GetTextureFromGame(path) ??
throw new ArgumentException($"Invalid texture {path}", nameof(path)));
return ret;
}
public TextureWrap GetAssemblyTexture(string filename)
{
if (!assemblyCache.TryGetValue(filename, out var ret))
assemblyCache.Add(filename, ret = GetAssemblyTextureInternal(filename));
return ret;
}
private static TextureWrap GetAssemblyTextureInternal(string filename)
{
var assembly = Assembly.GetExecutingAssembly();
byte[] iconData;
using (var stream = assembly.GetManifestResourceStream($"Craftimizer.{filename}") ?? throw new InvalidDataException($"Could not load resource {filename}"))
{
iconData = new byte[stream.Length];
_ = stream.Read(iconData);
}
return Service.PluginInterface.UiBuilder.LoadImage(iconData);
}
public void Dispose()
{
foreach (var image in iconCache.Values)
image.Dispose();
iconCache.Clear();
foreach (var image in textureCache.Values)
image.Dispose();
textureCache.Clear();
foreach (var image in assemblyCache.Values)
image.Dispose();
assemblyCache.Clear();
}
}
+46
View File
@@ -16,6 +16,52 @@ using CSRecipeNote = FFXIVClientStructs.FFXIV.Client.Game.UI.RecipeNote;
namespace Craftimizer.Utils; namespace Craftimizer.Utils;
public record RecipeData
{
public ushort RecipeId { get; }
public Recipe Recipe { get; }
public RecipeLevelTable Table { get; }
public ClassJob ClassJob { get; }
public RecipeInfo RecipeInfo { get; }
public int HQIngredientCount { get; }
public int MaxStartingQuality { get; }
public RecipeData(ushort recipeId)
{
RecipeId = recipeId;
Recipe = LuminaSheets.RecipeSheet.GetRow(recipeId) ??
throw new ArgumentException($"Invalid recipe id {recipeId}", nameof(recipeId));
Table = Recipe.RecipeLevelTable.Value!;
ClassJob = (ClassJob)Recipe.CraftType.Row;
RecipeInfo = new()
{
IsExpert = Recipe.IsExpert,
ClassJobLevel = Table.ClassJobLevel,
RLvl = (int)Table.RowId,
ConditionsFlag = Table.ConditionsFlag,
MaxDurability = Table.Durability * Recipe.DurabilityFactor / 100,
MaxQuality = (int)Table.Quality * Recipe.QualityFactor / 100,
MaxProgress = Table.Difficulty * Recipe.DifficultyFactor / 100,
QualityModifier = Table.QualityModifier,
QualityDivider = Table.QualityDivider,
ProgressModifier = Table.ProgressModifier,
ProgressDivider = Table.ProgressDivider,
};
HQIngredientCount = Recipe.UnkData5
.Where(i =>
i != null &&
i.ItemIngredient != 0 &&
(LuminaSheets.ItemSheet.GetRow((uint)i.ItemIngredient)?.CanBeHq ?? false)
).Sum(i => i.AmountIngredient);
MaxStartingQuality = (int)Math.Floor(Recipe.MaterialQualityFactor * RecipeInfo.MaxQuality / 100f);
}
}
public sealed unsafe class RecipeNote : IDisposable public sealed unsafe class RecipeNote : IDisposable
{ {
public AddonRecipeNote* AddonRecipe { get; private set; } public AddonRecipeNote* AddonRecipe { get; private set; }
+33
View File
@@ -0,0 +1,33 @@
using Dalamud.Game.Text;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Craftimizer.Utils;
public static class SqText
{
private static ReadOnlyDictionary<char, SeIconChar> levelNumReplacements = new(new Dictionary<char, SeIconChar>
{
['0'] = SeIconChar.Number0,
['1'] = SeIconChar.Number1,
['2'] = SeIconChar.Number2,
['3'] = SeIconChar.Number3,
['4'] = SeIconChar.Number4,
['5'] = SeIconChar.Number5,
['6'] = SeIconChar.Number6,
['7'] = SeIconChar.Number7,
['8'] = SeIconChar.Number8,
['9'] = SeIconChar.Number9,
});
public static string ToLevelString<T>(T value) where T : IBinaryInteger<T>
{
var str = value.ToString() ?? throw new FormatException("Failed to format value");
foreach(var (k, v) in levelNumReplacements)
str = str.Replace(k, v.ToIconChar());
return SeIconChar.LevelEn.ToIconChar() + str;
}
}
+542
View File
@@ -0,0 +1,542 @@
using Craftimizer.Plugin;
using Craftimizer.Plugin.Utils;
using Craftimizer.Simulator;
using Craftimizer.Simulator.Actions;
using Craftimizer.Utils;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Interface;
using Dalamud.Interface.Components;
using Dalamud.Interface.GameFonts;
using Dalamud.Interface.Raii;
using Dalamud.Interface.Windowing;
using Dalamud.Logging;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using FFXIVClientStructs.FFXIV.Component.GUI;
using ImGuiNET;
using ImGuiScene;
using System;
using System.Linq;
using System.Numerics;
using ActionType = Craftimizer.Simulator.Actions.ActionType;
using ClassJob = Craftimizer.Simulator.ClassJob;
using CSRecipeNote = FFXIVClientStructs.FFXIV.Client.Game.UI.RecipeNote;
namespace Craftimizer.Windows;
public sealed unsafe class RecipeNote : Window, IDisposable
{
private const ImGuiWindowFlags WindowFlags = ImGuiWindowFlags.NoDecoration
| ImGuiWindowFlags.AlwaysAutoResize
| ImGuiWindowFlags.NoSavedSettings
| ImGuiWindowFlags.NoFocusOnAppearing
| ImGuiWindowFlags.NoNavFocus;
public enum CraftableStatus
{
OK,
LockedClassJob,
WrongClassJob,
SpecialistRequired,
RequiredItem,
RequiredStatus,
CraftsmanshipTooLow,
ControlTooLow,
}
public AddonRecipeNote* Addon { get; private set; }
public RecipeData? RecipeData { get; private set; }
public CharacterStats? CharacterStats { get; private set; }
public CraftableStatus CraftStatus { get; private set; }
private TextureWrap ExpertBadge { get; }
private TextureWrap CollectibleBadge { get; }
private TextureWrap SplendorousBadge { get; }
private TextureWrap SpecialistBadge { get; }
private TextureWrap NoManipulationBadge { get; }
private GameFontHandle AxisFont { get; }
public RecipeNote() : base("Craftimizer RecipeNode", WindowFlags, false)
{
ExpertBadge = Service.IconManager.GetAssemblyTexture("Graphics.expert_badge.png");
CollectibleBadge = Service.IconManager.GetAssemblyTexture("Graphics.collectible_badge.png");
SplendorousBadge = Service.IconManager.GetAssemblyTexture("Graphics.splendorous.png");
SpecialistBadge = Service.IconManager.GetAssemblyTexture("Graphics.specialist.png");
NoManipulationBadge = Service.IconManager.GetAssemblyTexture("Graphics.no_manip.png");
AxisFont = Service.PluginInterface.UiBuilder.GetGameFontHandle(new(GameFontFamilyAndSize.Axis14));
Service.WindowSystem.AddWindow(this);
IsOpen = true;
}
public override bool DrawConditions()
{
if (Service.ClientState.LocalPlayer == null)
return false;
{
Addon = (AddonRecipeNote*)Service.GameGui.GetAddonByName("RecipeNote");
if (Addon == null)
return false;
// Check if RecipeNote addon is visible
if (Addon->AtkUnitBase.WindowNode == null)
return false;
// Check if RecipeNote has a visible selected recipe
if (!Addon->Unk258->IsVisible)
return false;
}
{
var instance = CSRecipeNote.Instance();
var list = instance->RecipeList;
if (list == null)
return false;
var recipeEntry = list->SelectedRecipe;
if (recipeEntry == null)
return false;
var recipeId = recipeEntry->RecipeId;
RecipeData = new(recipeId);
}
Gearsets.GearsetItem[] gearItems;
{
var gearStats = Gearsets.CalculateGearsetCurrentStats();
var container = InventoryManager.Instance()->GetInventoryContainer(InventoryType.EquippedItems);
if (container == null)
return false;
gearItems = Gearsets.GetGearsetItems(container);
CharacterStats = Gearsets.CalculateCharacterStats(gearStats, gearItems, RecipeData.ClassJob.GetPlayerLevel(), RecipeData.ClassJob.CanPlayerUseManipulation());
}
CraftStatus = CalculateCraftStatus(gearItems);
return true;
}
public override void PreDraw()
{
ref var unit = ref Addon->AtkUnitBase;
var scale = unit.Scale;
var pos = new Vector2(unit.X, unit.Y);
var size = new Vector2(unit.WindowNode->AtkResNode.Width, unit.WindowNode->AtkResNode.Height) * scale;
var node = (AtkResNode*)Addon->Unk458; // unit.GetNodeById(59);
var nodeParent = Addon->Unk258; // unit.GetNodeById(57);
Position = pos + new Vector2(size.X, (nodeParent->Y + node->Y) * scale);
SizeConstraints = new WindowSizeConstraints
{
MinimumSize = new(-1),
MaximumSize = new(10000, 10000)
};
}
public override void Draw()
{
using var table = ImRaii.Table("stats", 2, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.SizingStretchSame);
if (table)
{
ImGui.TableSetupColumn("col1", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn("col2", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableNextColumn();
DrawCharacterStats();
ImGui.TableNextColumn();
DrawRecipeStats();
}
}
private void DrawCharacterStats()
{
ImGuiUtils.TextCentered("Crafter");
var level = RecipeData!.ClassJob.GetPlayerLevel();
{
var className = RecipeData.ClassJob.GetName();
var levelText = string.Empty;
if (level != 0)
levelText = SqText.ToLevelString(level);
var imageSize = ImGuiUtils.ButtonHeight;
bool hasSplendorous = false, hasSpecialist = false, shouldHaveManip = false;
if (CraftStatus is not (CraftableStatus.LockedClassJob or CraftableStatus.WrongClassJob))
{
hasSplendorous = CharacterStats!.HasSplendorousBuff;
hasSpecialist = CharacterStats!.IsSpecialist;
shouldHaveManip = !CharacterStats.CanUseManipulation && CharacterStats.Level >= ActionType.Manipulation.Level();
}
ImGuiUtils.AlignCentered(
imageSize + 5 +
ImGui.CalcTextSize(className).X +
(level == 0 ? 0 : (3 + ImGui.CalcTextSize(levelText).X)) +
(hasSplendorous ? (3 + imageSize) : 0) +
(hasSpecialist ? (3 + imageSize) : 0) +
(shouldHaveManip ? (3 + imageSize) : 0)
);
ImGui.AlignTextToFramePadding();
ImGui.Image(Service.IconManager.GetIcon(RecipeData.ClassJob.GetIconId()).ImGuiHandle, new Vector2(imageSize));
ImGui.SameLine(0, 5);
if (level != 0)
{
ImGui.Text(levelText);
if (ImGui.IsItemHovered())
ImGui.SetTooltip($"CLvl {Gearsets.CalculateCLvl(level)}");
ImGui.SameLine(0, 3);
}
ImGui.Text(className);
if (hasSplendorous)
{
ImGui.SameLine(0, 3);
ImGui.Image(SplendorousBadge.ImGuiHandle, new Vector2(imageSize));
if (ImGui.IsItemHovered())
ImGui.SetTooltip($"Splendorous Tool");
}
if (hasSpecialist)
{
ImGui.SameLine(0, 3);
ImGui.Image(SpecialistBadge.ImGuiHandle, new Vector2(imageSize), Vector2.Zero, Vector2.One, new(0.99f, 0.97f, 0.62f, 1f));
if (ImGui.IsItemHovered())
ImGui.SetTooltip($"Specialist");
}
if (shouldHaveManip)
{
ImGui.SameLine(0, 3);
ImGui.Image(NoManipulationBadge.ImGuiHandle, new Vector2(imageSize));
if (ImGui.IsItemHovered())
ImGui.SetTooltip($"No Manipulation (Missing Job Quest)");
}
}
ImGui.Separator();
switch (CraftStatus)
{
case CraftableStatus.LockedClassJob:
{
ImGuiUtils.TextCentered($"You do not have {RecipeData.ClassJob.GetName().ToLowerInvariant()} unlocked.");
ImGui.Separator();
var unlockQuest = RecipeData.ClassJob.GetUnlockQuest();
var (questGiver, questTerritory, questLocation, mapPayload) = ResolveLevelData(unlockQuest.IssuerLocation.Row);
var unlockText = $"Unlock it from {questGiver}";
ImGuiUtils.AlignCentered(ImGui.CalcTextSize(unlockText).X + 5 + ImGuiUtils.ButtonHeight);
ImGui.AlignTextToFramePadding();
ImGui.Text(unlockText);
ImGui.SameLine(0, 5);
if (ImGuiComponents.IconButton(FontAwesomeIcon.Flag))
Service.GameGui.OpenMapWithMapLink(mapPayload);
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Open in map");
ImGuiUtils.TextCentered($"{questTerritory} ({questLocation.X:0.0}, {questLocation.Y:0.0})");
}
break;
case CraftableStatus.WrongClassJob:
{
ImGuiUtils.TextCentered($"You are not a {RecipeData.ClassJob.GetName().ToLowerInvariant()}.");
var gearsetId = GetGearsetForJob(RecipeData.ClassJob);
if (gearsetId.HasValue)
{
if (ImGuiUtils.ButtonCentered("Switch Job"))
Chat.SendMessage($"/gearset change {gearsetId + 1}");
if (ImGui.IsItemHovered())
ImGui.SetTooltip($"Swap to gearset {gearsetId + 1}");
}
else
ImGuiUtils.TextCentered($"You do not have any {RecipeData.ClassJob.GetName().ToLowerInvariant()} gearsets.");
}
break;
case CraftableStatus.SpecialistRequired:
{
ImGuiUtils.TextCentered($"You need to be a specialist to craft this recipe.");
var (vendorName, vendorTerritory, vendorLoation, mapPayload) = ResolveLevelData(5891399);
var unlockText = $"Trade a Soul of the Crafter to {vendorName}";
ImGuiUtils.AlignCentered(ImGui.CalcTextSize(unlockText).X + 5 + ImGuiUtils.ButtonHeight);
ImGui.AlignTextToFramePadding();
ImGui.Text(unlockText);
ImGui.SameLine(0, 5);
if (ImGuiComponents.IconButton(FontAwesomeIcon.Flag))
Service.GameGui.OpenMapWithMapLink(mapPayload);
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Open in map");
ImGuiUtils.TextCentered($"{vendorTerritory} ({vendorLoation.X:0.0}, {vendorLoation.Y:0.0})");
}
break;
case CraftableStatus.RequiredItem:
{
var item = RecipeData.Recipe.ItemRequired.Value!;
var itemName = item.Name.ToDalamudString().ToString();
var imageSize = ImGuiUtils.ButtonHeight;
ImGuiUtils.TextCentered($"You are missing the required equipment.");
ImGuiUtils.AlignCentered(imageSize + 5 + ImGui.CalcTextSize(itemName).X);
ImGui.AlignTextToFramePadding();
ImGui.Image(Service.IconManager.GetIcon(item.Icon).ImGuiHandle, new(imageSize));
ImGui.SameLine(0, 5);
ImGui.Text(itemName);
}
break;
case CraftableStatus.RequiredStatus:
{
var status = RecipeData.Recipe.StatusRequired.Value!;
var statusName = status.Name.ToDalamudString().ToString();
var statusIcon = Service.IconManager.GetIcon(status.Icon);
var imageSize = new Vector2(ImGuiUtils.ButtonHeight * statusIcon.Width / statusIcon.Height, ImGuiUtils.ButtonHeight);
ImGuiUtils.TextCentered($"You are missing the required status effect.");
ImGuiUtils.AlignCentered(imageSize.X + 5 + ImGui.CalcTextSize(statusName).X);
ImGui.AlignTextToFramePadding();
ImGui.Image(statusIcon.ImGuiHandle, imageSize);
ImGui.SameLine(0, 5);
ImGui.Text(statusName);
}
break;
case CraftableStatus.CraftsmanshipTooLow:
{
ImGuiUtils.TextCentered("Your Craftsmanship is too low.");
DrawRequiredStatsTable(CharacterStats!.Craftsmanship, RecipeData.Recipe.RequiredCraftsmanship);
}
break;
case CraftableStatus.ControlTooLow:
{
ImGuiUtils.TextCentered("Your Control is too low.");
DrawRequiredStatsTable(CharacterStats!.Control, RecipeData.Recipe.RequiredControl);
}
break;
case CraftableStatus.OK:
{
using var table = ImRaii.Table("characterStats", 2, ImGuiTableFlags.NoHostExtendX);
if (table)
{
ImGui.TableSetupColumn("ccol1", ImGuiTableColumnFlags.WidthFixed, 100);
ImGui.TableSetupColumn("ccol2", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableNextColumn();
ImGui.Text("Craftsmanship");
ImGui.TableNextColumn();
ImGui.Text($"{CharacterStats!.Craftsmanship}");
ImGui.TableNextColumn();
ImGui.Text("Control");
ImGui.TableNextColumn();
ImGui.Text($"{CharacterStats.Control}");
ImGui.TableNextColumn();
ImGui.Text("CP");
ImGui.TableNextColumn();
ImGui.Text($"{CharacterStats.CP}");
}
}
break;
}
}
private void DrawRecipeStats()
{
ImGuiUtils.TextCentered("Recipe");
{
var textStars = new string('★', RecipeData!.Table.Stars);
var textStarsSize = Vector2.Zero;
if (!string.IsNullOrEmpty(textStars)) {
var layout = AxisFont.LayoutBuilder(textStars).Build();
textStarsSize = new(layout.Width + 3, layout.Height);
}
var textLevel = SqText.ToLevelString(RecipeData.RecipeInfo.ClassJobLevel);
var isExpert = RecipeData.RecipeInfo.IsExpert;
var isCollectable = RecipeData.Recipe.ItemResult.Value!.IsCollectable;
var imageSize = ImGuiUtils.ButtonHeight;
var textSize = ImGui.CalcTextSize("A").Y;
var badgeSize = new Vector2(textSize * ExpertBadge.Width / ExpertBadge.Height, textSize);
var badgeOffset = (imageSize - badgeSize.Y) / 2;
ImGuiUtils.AlignCentered(
imageSize + 5 +
ImGui.CalcTextSize(textLevel).X +
textStarsSize.X +
(isCollectable ? badgeSize.X + 3 : 0) +
(isExpert ? badgeSize.X + 3 : 0)
);
ImGui.AlignTextToFramePadding();
ImGui.Image(Service.IconManager.GetIcon(RecipeData.Recipe.ItemResult.Value!.Icon).ImGuiHandle, new Vector2(imageSize));
ImGui.SameLine(0, 5);
ImGui.Text(textLevel);
if (ImGui.IsItemHovered())
ImGui.SetTooltip($"RLvl {RecipeData.RecipeInfo.RLvl}");
if (textStarsSize != Vector2.Zero)
{
ImGui.SameLine(0, 3);
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (textStarsSize.Y - textSize) / 2);
AxisFont.Text(textStars);
}
if (isCollectable)
{
ImGui.SameLine(0, 3);
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + badgeOffset);
ImGui.Image(CollectibleBadge.ImGuiHandle, badgeSize);
if (ImGui.IsItemHovered())
ImGui.SetTooltip($"Collectible");
}
if (isExpert)
{
ImGui.SameLine(0, 3);
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + badgeOffset);
ImGui.Image(ExpertBadge.ImGuiHandle, badgeSize);
if (ImGui.IsItemHovered())
ImGui.SetTooltip($"Expert Recipe");
}
}
using var table = ImRaii.Table("recipeStats", 2);
if (table)
{
ImGui.TableSetupColumn("rcol1", ImGuiTableColumnFlags.WidthFixed, 100);
ImGui.TableSetupColumn("rcol2", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableNextColumn();
ImGui.Text("Progress");
ImGui.TableNextColumn();
ImGui.Text($"{RecipeData.RecipeInfo.MaxProgress}");
ImGui.TableNextColumn();
ImGui.Text("Quality");
ImGui.TableNextColumn();
ImGui.Text($"{RecipeData.RecipeInfo.MaxQuality}");
ImGui.TableNextColumn();
ImGui.Text("Durability");
ImGui.TableNextColumn();
ImGui.Text($"{RecipeData.RecipeInfo.MaxDurability}");
}
}
private static void DrawRequiredStatsTable(int current, int required)
{
if (current >= required)
throw new ArgumentOutOfRangeException(nameof(current));
using var table = ImRaii.Table("requiredStats", 2, ImGuiTableFlags.NoHostExtendX);
if (table)
{
ImGui.TableSetupColumn("ccol1", ImGuiTableColumnFlags.WidthFixed, 100);
ImGui.TableSetupColumn("ccol2", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableNextColumn();
ImGui.Text("Current");
ImGui.TableNextColumn();
ImGui.TextColored(new(0, 1, 0, 1), $"{current}");
ImGui.TableNextColumn();
ImGui.Text("Required");
ImGui.TableNextColumn();
ImGui.TextColored(new(1, 0, 0, 1), $"{required}");
ImGui.TableNextColumn();
ImGui.Text("You need");
ImGui.TableNextColumn();
ImGui.Text($"{required - current}");
}
}
private CraftableStatus CalculateCraftStatus(Gearsets.GearsetItem[] gearItems)
{
if (RecipeData!.ClassJob.GetPlayerLevel() == 0)
return CraftableStatus.LockedClassJob;
if (PlayerState.Instance()->CurrentClassJobId != RecipeData.ClassJob.GetClassJobIndex())
return CraftableStatus.WrongClassJob;
if (RecipeData.Recipe.IsSpecializationRequired && !(CharacterStats!.IsSpecialist))
return CraftableStatus.SpecialistRequired;
var itemRequired = RecipeData.Recipe.ItemRequired;
if (itemRequired.Row != 0 && itemRequired.Value != null)
{
if (!gearItems.Any(i => Gearsets.IsItem(i, itemRequired.Row)))
return CraftableStatus.RequiredItem;
}
var statusRequired = RecipeData.Recipe.StatusRequired;
if (statusRequired.Row != 0 && statusRequired.Value != null)
{
if (!Service.ClientState.LocalPlayer!.StatusList.Any(s => s.StatusId == statusRequired.Row))
return CraftableStatus.RequiredStatus;
}
if (RecipeData.Recipe.RequiredCraftsmanship > CharacterStats!.Craftsmanship)
return CraftableStatus.CraftsmanshipTooLow;
if (RecipeData.Recipe.RequiredControl > CharacterStats.Control)
return CraftableStatus.ControlTooLow;
return CraftableStatus.OK;
}
private static (string NpcName, string Territory, Vector2 MapLocation, MapLinkPayload Payload) ResolveLevelData(uint levelRowId)
{
var level = LuminaSheets.LevelSheet.GetRow(levelRowId) ??
throw new ArgumentNullException(nameof(levelRowId), $"Invalid level row {levelRowId}");
var territory = level.Territory.Value!.PlaceName.Value!.Name.ToDalamudString().ToString();
var location = MapUtil.WorldToMap(new(level.X, level.Z), level.Map.Value!);
return (ResolveNpcResidentName(level.Object), territory, location, new(level.Territory.Row, level.Map.Row, location.X, location.Y));
}
private static string ResolveNpcResidentName(uint npcRowId)
{
var resident = LuminaSheets.ENpcResidentSheet.GetRow(npcRowId) ??
throw new ArgumentNullException(nameof(npcRowId), $"Invalid npc row {npcRowId}");
return resident.Singular.ToDalamudString().ToString();
}
private static int? GetGearsetForJob(ClassJob job)
{
var gearsetModule = RaptureGearsetModule.Instance();
for (var i = 0; i < 100; i++)
{
var gearset = gearsetModule->Gearset[i];
if (gearset == null)
continue;
if (!gearset->Flags.HasFlag(RaptureGearsetModule.GearsetFlag.Exists))
continue;
if (gearset->ID != i)
continue;
if (gearset->ClassJob != job.GetClassJobIndex())
continue;
return i;
}
return null;
}
public void Dispose()
{
AxisFont?.Dispose();
}
}
+1 -1
View File
@@ -473,7 +473,7 @@ public class Settings : Window
ImGui.Image(icon.ImGuiHandle, new(icon.Width, icon.Height)); ImGui.Image(icon.ImGuiHandle, new(icon.Width, icon.Height));
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.Text($"{plugin.Name} v{plugin.Version} {plugin.Configuration}"); ImGui.Text($"{plugin.Name} v{plugin.Version} {plugin.BuildConfiguration}");
ImGui.Text($"By {plugin.Author} ("); ImGui.Text($"By {plugin.Author} (");
ImGui.SameLine(0, 0); ImGui.SameLine(0, 0);
ImGuiUtils.Hyperlink("WorkingRobot", "https://github.com/WorkingRobot"); ImGuiUtils.Hyperlink("WorkingRobot", "https://github.com/WorkingRobot");
+1 -1
View File
@@ -133,7 +133,7 @@ public sealed partial class Simulator : Window, IDisposable
{ {
var imageSize = new Vector2(ImGui.GetFontSize() * 2.25f); var imageSize = new Vector2(ImGui.GetFontSize() * 2.25f);
ImGui.Image(Icons.GetIconFromId(Item.Icon).ImGuiHandle, imageSize); ImGui.Image(Service.IconManager.GetIcon(Item.Icon).ImGuiHandle, imageSize);
ImGui.SameLine(); ImGui.SameLine();
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (imageSize.Y - ImGui.GetFontSize()) / 2f); ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (imageSize.Y - ImGui.GetFontSize()) / 2f);
ImGui.TextUnformatted(Item.Name.ToDalamudString().ToString()); ImGui.TextUnformatted(Item.Name.ToDalamudString().ToString());