Apply csharpier reflow across source tree

Reformats the entire Craftimizer source tree with dotnet csharpier 1.2.6
to match the Hellion Forge house style (matches what HellionChat enforces
in its pre-push pipeline). Pure whitespace + using-block sorting; no
semantic changes.

This is a one-time noisy commit. Future code edits in this fork should
land csharpier-clean because the pre-push hook (introduced in the next
commit) runs `dotnet csharpier check Craftimizer/` as Block C of the
preflight gate.

Trade-off acknowledged: this widens the merge gap with upstream
Craftimizer should Asriel ever resume maintenance. Given the upstream
has been dormant since FFXIV 7.4 and the fork is light-rename only
(internal namespaces unchanged), the marginal cost is acceptable.
This commit is contained in:
2026-05-26 20:21:21 +02:00
parent a52b9e8d76
commit b598c03e9e
35 changed files with 3329 additions and 1298 deletions
+20 -13
View File
@@ -1,10 +1,10 @@
using Craftimizer.Simulator.Actions;
using Craftimizer.Solver;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using Craftimizer.Simulator.Actions;
using Craftimizer.Solver;
namespace Craftimizer.Plugin;
@@ -13,7 +13,8 @@ public class StoredActionTypeConverter : JsonConverter<ActionType[]>
public override ActionType[] Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
JsonSerializerOptions options
)
{
if (reader.TokenType != JsonTokenType.StartArray)
throw new JsonException();
@@ -87,7 +88,8 @@ public class StoredActionTypeConverter : JsonConverter<ActionType[]>
public override void Write(
Utf8JsonWriter writer,
ActionType[] value,
JsonSerializerOptions options)
JsonSerializerOptions options
)
{
writer.WriteStartArray();
foreach (var item in value)
@@ -101,14 +103,18 @@ public class Macro
public static event Action<Macro>? OnMacroChanged;
public string Name { get; set; } = string.Empty;
[JsonInclude] [JsonPropertyName("Actions")]
[JsonInclude]
[JsonPropertyName("Actions")]
internal ActionType[] actions { get; set; } = [];
[JsonIgnore]
public IReadOnlyList<ActionType> Actions
{
get => actions;
set => ActionEnumerable = value;
}
[JsonIgnore]
public IEnumerable<ActionType> ActionEnumerable
{
@@ -127,7 +133,7 @@ public class MacroCopyConfiguration
OpenWindow, // useful for big macros
CopyToMacro, // (add option for down or right) (max macro count; open copy-paste window if too much)
CopyToClipboard,
CopyToMacroMate
CopyToMacroMate,
}
public CopyType Type { get; set; } = CopyType.OpenWindow;
@@ -172,13 +178,15 @@ public partial class Configuration
{
Colorful,
Simple,
None
None,
}
public static event Action? OnMacroListChanged;
[JsonInclude] [JsonPropertyName("Macros")]
[JsonInclude]
[JsonPropertyName("Macros")]
internal List<Macro> macros { get; private set; } = [];
[JsonIgnore]
public IReadOnlyList<Macro> Macros => macros;
public int ReliabilitySimulationCount { get; set; } = 1000;
@@ -244,10 +252,8 @@ public partial class Configuration
[JsonSerializable(typeof(Configuration))]
internal sealed partial class JsonContext : JsonSerializerContext
{
public static JsonSerializerOptions DeserializeOptions { get; } = new()
{
Converters = { new StoredActionTypeConverter() }
};
public static JsonSerializerOptions DeserializeOptions { get; } =
new() { Converters = { new StoredActionTypeConverter() } };
}
public void Save()
@@ -265,7 +271,8 @@ public partial class Configuration
using var stream = f.OpenRead();
// System.InvalidOperationException: Setting init-only properties is not supported in source generation mode.
return JsonSerializer.Deserialize<Configuration>(stream, JsonContext.DeserializeOptions) ?? new();
return JsonSerializer.Deserialize<Configuration>(stream, JsonContext.DeserializeOptions)
?? new();
}
return new();
}
+86 -20
View File
@@ -1,9 +1,9 @@
using Dalamud.Bindings.ImGui;
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using Dalamud.Bindings.ImGui;
namespace Craftimizer.Plugin;
@@ -11,22 +11,50 @@ internal static unsafe class ImGuiExtras
{
// https://github.com/ImGuiNET/ImGui.NET/blob/069363672fed940ebdaa02f9b032c282b66467c7/src/CodeGenerator/definitions/cimgui/definitions.json#L25394
[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
private static extern unsafe byte igInputTextEx(byte* label, byte* hint, byte* buf, int buf_size, Vector2 size, ImGuiInputTextFlags flags, ImGuiInputTextCallback? callback, void* user_data);
private static extern unsafe byte igInputTextEx(
byte* label,
byte* hint,
byte* buf,
int buf_size,
Vector2 size,
ImGuiInputTextFlags flags,
ImGuiInputTextCallback? callback,
void* user_data
);
[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
private static extern bool igItemAdd(Vector4 bb, uint id, Vector4* navBb, uint flags);
[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
private static extern bool igButtonBehavior(Vector4 bb, uint id, bool* outHovered, bool* outHeld, ImGuiButtonFlags flags);
private static extern bool igButtonBehavior(
Vector4 bb,
uint id,
bool* outHovered,
bool* outHeld,
ImGuiButtonFlags flags
);
[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
private static extern bool igItemSize_Vec2(Vector2 size, float text_baseline_y = -1.0f);
[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
private static extern void igRenderFrame(Vector2 p_min, Vector2 p_max, uint fill_col, bool border = true, float rounding = 0.0f);
private static extern void igRenderFrame(
Vector2 p_min,
Vector2 p_max,
uint fill_col,
bool border = true,
float rounding = 0.0f
);
[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
private static extern void igRenderRectFilledRangeH(ImDrawList* draw_list, Vector4* rect, uint col, float x_start_norm, float x_end_norm, float rounding);
private static extern void igRenderRectFilledRangeH(
ImDrawList* draw_list,
Vector4* rect,
uint col,
float x_start_norm,
float x_end_norm,
float rounding
);
[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
private static extern ImGuiItemFlags igGetItemFlags();
@@ -74,7 +102,16 @@ internal static unsafe class ImGuiExtras
#endregion
// Based off of code from InputTextWithHint: https://github.com/ImGuiNET/ImGui.NET/blob/069363672fed940ebdaa02f9b032c282b66467c7/src/ImGui.NET/ImGui.Manual.cs#L271
public static unsafe bool InputTextEx(string label, string hint, ref string input, int maxLength, Vector2 size, ImGuiInputTextFlags flags = ImGuiInputTextFlags.None, ImGuiInputTextCallback? callback = null, IntPtr user_data = default)
public static unsafe bool InputTextEx(
string label,
string hint,
ref string input,
int maxLength,
Vector2 size,
ImGuiInputTextFlags flags = ImGuiInputTextFlags.None,
ImGuiInputTextCallback? callback = null,
IntPtr user_data = default
)
{
var utf8LabelByteCount = Encoding.UTF8.GetByteCount(label);
byte* utf8LabelBytes;
@@ -132,7 +169,8 @@ internal static unsafe class ImGuiExtras
size,
flags,
callback,
user_data.ToPointer());
user_data.ToPointer()
);
if (!AreStringsEqual(originalUtf8InputBytes, inputBufSize, utf8InputBytes))
{
input = StringFromPtr(utf8InputBytes);
@@ -155,8 +193,7 @@ internal static unsafe class ImGuiExtras
return result != 0;
}
public static unsafe bool ItemAdd(Vector4 bb, uint id) =>
ItemAdd(bb, id, out _);
public static unsafe bool ItemAdd(Vector4 bb, uint id) => ItemAdd(bb, id, out _);
public static unsafe bool ItemAdd(Vector4 bb, uint id, out Vector4 navBb, uint flags = 0)
{
@@ -166,7 +203,13 @@ internal static unsafe class ImGuiExtras
}
}
public static unsafe bool ButtonBehavior(Vector4 bb, uint id, out bool hovered, out bool held, ImGuiButtonFlags flags)
public static unsafe bool ButtonBehavior(
Vector4 bb,
uint id,
out bool hovered,
out bool held,
ImGuiButtonFlags flags
)
{
fixed (bool* hoveredPtr = &hovered)
fixed (bool* heldPtr = &held)
@@ -175,19 +218,34 @@ internal static unsafe class ImGuiExtras
}
}
public static unsafe void RenderFrame(Vector2 p_min, Vector2 p_max, uint fill_col, bool border = true, float rounding = 0.0f) =>
igRenderFrame(p_min, p_max, fill_col, border, rounding);
public static unsafe void RenderFrame(
Vector2 p_min,
Vector2 p_max,
uint fill_col,
bool border = true,
float rounding = 0.0f
) => igRenderFrame(p_min, p_max, fill_col, border, rounding);
public static unsafe void RenderRectFilledRangeH(ImDrawListPtr draw_list, Vector4 rect, uint col, float x_start_norm, float x_end_norm, float rounding) =>
igRenderRectFilledRangeH(draw_list, &rect, col, x_start_norm, x_end_norm, rounding);
public static unsafe void RenderRectFilledRangeH(
ImDrawListPtr draw_list,
Vector4 rect,
uint col,
float x_start_norm,
float x_end_norm,
float rounding
) => igRenderRectFilledRangeH(draw_list, &rect, col, x_start_norm, x_end_norm, rounding);
public static unsafe bool ItemSize(Vector2 size, float text_baseline_y = -1.0f) =>
igItemSize_Vec2(size, text_baseline_y);
public static unsafe ImGuiItemFlags GetItemFlags() =>
igGetItemFlags();
public static unsafe ImGuiItemFlags GetItemFlags() => igGetItemFlags();
public static unsafe int? CalcWordWrapPositionA(this ImFontPtr font, float scale, ReadOnlySpan<char> text, float wrap_width)
public static unsafe int? CalcWordWrapPositionA(
this ImFontPtr font,
float scale,
ReadOnlySpan<char> text,
float wrap_width
)
{
var utf8TextByteCount = Encoding.UTF8.GetByteCount(text);
byte* utf8TextBytes;
@@ -202,7 +260,13 @@ internal static unsafe class ImGuiExtras
}
GetUtf8(text, utf8TextBytes, utf8TextByteCount);
var ret = ImGuiNative.CalcWordWrapPositionA(font, scale, utf8TextBytes, utf8TextBytes + utf8TextByteCount, wrap_width);
var ret = ImGuiNative.CalcWordWrapPositionA(
font,
scale,
utf8TextBytes,
utf8TextBytes + utf8TextByteCount,
wrap_width
);
int? retVal = null;
if (utf8TextBytes <= ret && ret <= utf8TextBytes + utf8TextByteCount)
@@ -217,10 +281,12 @@ internal static unsafe class ImGuiExtras
return retVal;
}
public static unsafe bool SetDragDropPayload<T>(string type, T data) where T : unmanaged =>
public static unsafe bool SetDragDropPayload<T>(string type, T data)
where T : unmanaged =>
ImGui.SetDragDropPayload(type, MemoryMarshal.AsBytes(new ReadOnlySpan<T>(&data, 1)));
public static unsafe bool AcceptDragDropPayload<T>(string type, out T data) where T : unmanaged
public static unsafe bool AcceptDragDropPayload<T>(string type, out T data)
where T : unmanaged
{
var payload = ImGui.AcceptDragDropPayload(type);
if (payload.IsNull || payload.DataSize != sizeof(T))
+241 -56
View File
@@ -1,11 +1,3 @@
using Craftimizer.Utils;
using Dalamud.Interface;
using Dalamud.Interface.ManagedFontAtlas;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Bindings.ImGui;
using Dalamud.Bindings.ImPlot;
using MathNet.Numerics.Statistics;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
@@ -15,12 +7,24 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Craftimizer.Utils;
using Dalamud.Bindings.ImGui;
using Dalamud.Bindings.ImPlot;
using Dalamud.Interface;
using Dalamud.Interface.ManagedFontAtlas;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using MathNet.Numerics.Statistics;
namespace Craftimizer.Plugin;
internal static class ImGuiUtils
{
private static readonly Stack<(Vector2 Min, Vector2 Max, float TopPadding)> GroupPanelLabelStack = new();
private static readonly Stack<(
Vector2 Min,
Vector2 Max,
float TopPadding
)> GroupPanelLabelStack = new();
// Adapted from https://github.com/ocornut/imgui/issues/1496#issuecomment-655048353
// width = -1 -> size to parent
@@ -62,7 +66,9 @@ internal static class ImGuiUtils
var textFrameHeight = ImGui.GetFrameHeight();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(name);
GroupPanelLabelStack.Push((ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), textFrameHeight / 2f)); // push rect to stack
GroupPanelLabelStack.Push(
(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), textFrameHeight / 2f)
); // push rect to stack
ImGui.SameLine(0, 0);
ImGui.Dummy(new Vector2(0f, textFrameHeight + itemSpacing.Y)); // shifts content by fh + is.y
}
@@ -112,18 +118,29 @@ internal static class ImGuiUtils
{
var (minClip, maxClip) = i switch
{
0 => (new Vector2(float.NegativeInfinity), new Vector2(labelMin.X, float.PositiveInfinity)),
1 => (new Vector2(labelMax.X, float.NegativeInfinity), new Vector2(float.PositiveInfinity)),
2 => (new Vector2(labelMin.X, float.NegativeInfinity), new Vector2(labelMax.X, labelMin.Y)),
3 => (new Vector2(labelMin.X, labelMax.Y), new Vector2(labelMax.X, float.PositiveInfinity)),
_ => (Vector2.Zero, Vector2.Zero)
0 => (
new Vector2(float.NegativeInfinity),
new Vector2(labelMin.X, float.PositiveInfinity)
),
1 => (
new Vector2(labelMax.X, float.NegativeInfinity),
new Vector2(float.PositiveInfinity)
),
2 => (
new Vector2(labelMin.X, float.NegativeInfinity),
new Vector2(labelMax.X, labelMin.Y)
),
3 => (
new Vector2(labelMin.X, labelMax.Y),
new Vector2(labelMax.X, float.PositiveInfinity)
),
_ => (Vector2.Zero, Vector2.Zero),
};
ImGui.PushClipRect(minClip, maxClip, true);
ImGui.GetWindowDrawList().AddRect(
innerMin, innerMax,
ImGui.GetColorU32(ImGuiCol.Border),
itemSpacing.X);
ImGui
.GetWindowDrawList()
.AddRect(innerMin, innerMax, ImGui.GetColorU32(ImGuiCol.Border), itemSpacing.X);
ImGui.PopClipRect();
}
@@ -143,15 +160,23 @@ internal static class ImGuiUtils
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float Lerp(float a, float b, float t) =>
MathF.FusedMultiplyAdd(b - a, t, a);
private static float Lerp(float a, float b, float t) => MathF.FusedMultiplyAdd(b - a, t, a);
private readonly record struct ArcEdge(float Angle, Vector2 Point)
{
public ArcEdge(float angle) : this(angle, UnitCircle(angle)) { }
public ArcEdge(float angle)
: this(angle, UnitCircle(angle)) { }
}
private static void ArcSegment(Vector2 o, ArcEdge prev, ArcEdge cur, ArcEdge? next, float radius, float ratio, uint color)
private static void ArcSegment(
Vector2 o,
ArcEdge prev,
ArcEdge cur,
ArcEdge? next,
float radius,
float ratio,
uint color
)
{
var d = ImGui.GetWindowDrawList();
@@ -164,7 +189,15 @@ internal static class ImGuiUtils
d.PathFillConvex(color);
}
public static void Arc(float startAngle, float endAngle, float radius, float ratio, uint backgroundColor, uint filledColor, bool addDummy = true)
public static void Arc(
float startAngle,
float endAngle,
float radius,
float ratio,
uint backgroundColor,
uint filledColor,
bool addDummy = true
)
{
// Fix normals when drawing (for antialiasing)
if (startAngle > endAngle)
@@ -224,9 +257,16 @@ internal static class ImGuiUtils
ImGui.Dummy(new Vector2(radius * 2));
}
public static void ArcProgress(float value, float radius, float ratio, uint backgroundColor, uint filledColor)
public static void ArcProgress(
float value,
float radius,
float ratio,
uint backgroundColor,
uint filledColor
)
{
float startAngle, endAngle;
float startAngle,
endAngle;
// https://github.com/ocornut/imgui/commit/c895e987adf746a997b655c64a6a8916c549ff6f#diff-d750e175eb584ba76bc560b8e54cf113ccbb31dd33f75078c1588925e197a3afR1304-R1310
if (value < 0)
@@ -271,19 +311,34 @@ internal static class ImGuiUtils
bar_end = bar_begin + bar_fraction;
}
ImGuiExtras.RenderFrame(bbMin, bbMax, ImGui.GetColorU32(ImGuiCol.FrameBg), true, style.FrameRounding);
ImGuiExtras.RenderFrame(
bbMin,
bbMax,
ImGui.GetColorU32(ImGuiCol.FrameBg),
true,
style.FrameRounding
);
bbMin += new Vector2(style.FrameBorderSize);
bbMax -= new Vector2(style.FrameBorderSize);
ImGuiExtras.RenderRectFilledRangeH(ImGui.GetWindowDrawList(), new(bbMin.X, bbMin.Y, bbMax.X, bbMax.Y), ImGui.GetColorU32(ImGuiCol.PlotHistogram), bar_begin, bar_end, style.FrameRounding);
ImGuiExtras.RenderRectFilledRangeH(
ImGui.GetWindowDrawList(),
new(bbMin.X, bbMin.Y, bbMax.X, bbMax.Y),
ImGui.GetColorU32(ImGuiCol.PlotHistogram),
bar_begin,
bar_end,
style.FrameRounding
);
}
public sealed class ViolinData
{
public struct Point(float x, float y, float y2)
{
public float X = x, Y = y, Y2 = y2;
public float X = x,
Y = y,
Y2 = y2;
}
public ReadOnlySpan<Point> Data => (DataArray ?? []).AsSpan();
@@ -291,7 +346,13 @@ internal static class ImGuiUtils
public readonly float Min;
public readonly float Max;
public ViolinData(IEnumerable<int> samples, float min, float max, int resolution, double bandwidth)
public ViolinData(
IEnumerable<int> samples,
float min,
float max,
int resolution,
double bandwidth
)
{
Min = min;
Max = max;
@@ -300,9 +361,12 @@ internal static class ImGuiUtils
_ = Task.Run(() =>
{
var s = Stopwatch.StartNew();
var data = ParallelEnumerable.Range(0, resolution + 1)
var data = ParallelEnumerable
.Range(0, resolution + 1)
.Select(n => Lerp(min, max, n / (float)resolution))
.Select(n => (n, (float)KernelDensity.EstimateGaussian(n, bandwidth, samplesList)))
.Select(n =>
(n, (float)KernelDensity.EstimateGaussian(n, bandwidth, samplesList))
)
.Select(n => new Point(n.n, n.Item2, -n.Item2));
// ParallelQuery doesn't support [.. data] correctly. The plots look very wrong.
#pragma warning disable IDE0305 // Simplify collection initialization
@@ -320,10 +384,22 @@ internal static class ImGuiUtils
using var plotBg = ImRaii2.PushColor(ImPlotCol.Bg, Vector4.Zero);
using var fill = ImRaii2.PushColor(ImPlotCol.Fill, Vector4.One.WithAlpha(.5f));
using var plot = ImRaii2.Plot("##violin", size, ImPlotFlags.CanvasOnly | ImPlotFlags.NoInputs | ImPlotFlags.NoChild | ImPlotFlags.NoFrame);
using var plot = ImRaii2.Plot(
"##violin",
size,
ImPlotFlags.CanvasOnly
| ImPlotFlags.NoInputs
| ImPlotFlags.NoChild
| ImPlotFlags.NoFrame
);
if (plot.Success)
{
ImPlot.SetupAxes([], [], ImPlotAxisFlags.NoDecorations, ImPlotAxisFlags.NoDecorations | ImPlotAxisFlags.AutoFit);
ImPlot.SetupAxes(
[],
[],
ImPlotAxisFlags.NoDecorations,
ImPlotAxisFlags.NoDecorations | ImPlotAxisFlags.AutoFit
);
ImPlot.SetupAxisLimits(ImAxis.X1, data.Min, data.Max, ImPlotCond.Always);
ImPlot.SetupFinish();
@@ -334,14 +410,24 @@ internal static class ImGuiUtils
var label_id = stackalloc byte[] { (byte)'\0' };
fixed (ViolinData.Point* p = points)
{
ImPlot.PlotShaded(label_id, &p->X, &p->Y, &p->Y2, points.Length, ImPlotShadedFlags.None, 0, sizeof(ViolinData.Point));
ImPlot.PlotShaded(
label_id,
&p->X,
&p->Y,
&p->Y2,
points.Length,
ImPlotShadedFlags.None,
0,
sizeof(ViolinData.Point)
);
}
}
}
}
}
private sealed class SearchableComboData<T> where T : IEquatable<T>
private sealed class SearchableComboData<T>
where T : IEquatable<T>
{
public readonly ImmutableArray<T> items;
public List<T> filteredItems;
@@ -381,20 +467,26 @@ internal static class ImGuiUtils
cts = new();
var token = cts.Token;
task = Task.Run(() => FilterTask(inp, token), token)
.ContinueWith(t =>
.ContinueWith(
t =>
{
if (cts.IsCancellationRequested)
return;
try
{
t.Exception!.Flatten().Handle(ex => ex is TaskCanceledException or OperationCanceledException);
t.Exception!.Flatten()
.Handle(ex =>
ex is TaskCanceledException or OperationCanceledException
);
}
catch (Exception e)
{
Log.Error(e, "Filtering recipes failed");
}
}, TaskContinuationOptions.OnlyOnFaulted);
},
TaskContinuationOptions.OnlyOnFaulted
);
}
private void FilterTask(string input, CancellationToken token)
@@ -405,7 +497,9 @@ internal static class ImGuiUtils
return;
}
var matcher = new FuzzyMatcher(input.ToLowerInvariant(), MatchMode.FuzzyParts);
var query = items.AsParallel().Select(i => (Item: i, Score: matcher.Matches(getString(i).ToLowerInvariant())))
var query = items
.AsParallel()
.Select(i => (Item: i, Score: matcher.Matches(getString(i).ToLowerInvariant())))
.Where(t => t.Score > 0)
.OrderByDescending(t => t.Score)
.Select(t => t.Item);
@@ -413,16 +507,34 @@ internal static class ImGuiUtils
filteredItems = [.. query];
}
}
private static readonly Dictionary<uint, object> ComboData = [];
private static SearchableComboData<T> GetComboData<T>(uint comboKey, IEnumerable<T> items, T selectedItem, Func<T, string> getString) where T : IEquatable<T> =>
private static SearchableComboData<T> GetComboData<T>(
uint comboKey,
IEnumerable<T> items,
T selectedItem,
Func<T, string> getString
)
where T : IEquatable<T> =>
(SearchableComboData<T>)(
ComboData.TryGetValue(comboKey, out var data)
? data
: ComboData[comboKey] = new SearchableComboData<T>(items, selectedItem, getString));
: ComboData[comboKey] = new SearchableComboData<T>(items, selectedItem, getString)
);
// https://github.com/ocornut/imgui/issues/718#issuecomment-1563162222
public static bool SearchableCombo<T>(string id, ref T selectedItem, IEnumerable<T> items, ImFontPtr selectableFont, float width, Func<T, string> getString, Func<T, string> getId, Action<T> draw) where T : IEquatable<T>
public static bool SearchableCombo<T>(
string id,
ref T selectedItem,
IEnumerable<T> items,
ImFontPtr selectableFont,
float width,
Func<T, string> getString,
Func<T, string> getId,
Action<T> draw
)
where T : IEquatable<T>
{
var comboKey = ImGui.GetID(id);
var data = GetComboData(comboKey, items, selectedItem, getString);
@@ -433,7 +545,12 @@ internal static class ImGuiUtils
width = width == 0 ? ImGui.GetContentRegionAvail().X : width;
var availableSpace = Math.Min(ImGui.GetContentRegionAvail().X, width);
ImGui.SetNextItemWidth(availableSpace);
var isInputTextEnterPressed = ImGui.InputText("##input", ref data.input, 256, ImGuiInputTextFlags.EnterReturnsTrue);
var isInputTextEnterPressed = ImGui.InputText(
"##input",
ref data.input,
256,
ImGuiInputTextFlags.EnterReturnsTrue
);
var min = ImGui.GetItemRectMin();
var size = ImGui.GetItemRectSize();
size.X = Math.Min(size.X, availableSpace);
@@ -447,7 +564,15 @@ internal static class ImGuiUtils
data.wasTextActive = false;
}
using (var popup = ImRaii.Popup("##popup", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoSavedSettings))
using (
var popup = ImRaii.Popup(
"##popup",
ImGuiWindowFlags.NoMove
| ImGuiWindowFlags.NoResize
| ImGuiWindowFlags.AlwaysAutoResize
| ImGuiWindowFlags.NoSavedSettings
)
)
{
if (popup)
{
@@ -466,11 +591,21 @@ internal static class ImGuiUtils
isInputTextEnterPressed = true;
data.wasTextActive = isActive;
using (var scrollingRegion = ImRaii.Child("scrollingRegion", new Vector2(size.X, size.Y * 10), false, ImGuiWindowFlags.HorizontalScrollbar))
using (
var scrollingRegion = ImRaii.Child(
"scrollingRegion",
new Vector2(size.X, size.Y * 10),
false,
ImGuiWindowFlags.HorizontalScrollbar
)
)
{
T? _selectedItem = default;
var height = ImGui.GetTextLineHeight();
var r = ListClip(data.filteredItems, height, t =>
var r = ListClip(
data.filteredItems,
height,
t =>
{
var name = getString(t);
using (var selectFont = ImRaii.PushFont(selectableFont))
@@ -484,7 +619,8 @@ internal static class ImGuiUtils
ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X / 2f);
draw(t);
return false;
});
}
);
if (r)
{
selectedItem = _selectedItem!;
@@ -530,7 +666,11 @@ internal static class ImGuiUtils
imGuiListClipperPtr.Begin(data.Count, lineHeight);
while (imGuiListClipperPtr.Step())
{
for (var i = imGuiListClipperPtr.DisplayStart; i <= imGuiListClipperPtr.DisplayEnd; i++)
for (
var i = imGuiListClipperPtr.DisplayStart;
i <= imGuiListClipperPtr.DisplayEnd;
i++
)
{
if (i >= data.Count)
return false;
@@ -551,10 +691,28 @@ internal static class ImGuiUtils
}
}
public static bool InputTextMultilineWithHint(string label, string hint, ref string input, int maxLength, Vector2 size, ImGuiInputTextFlags flags = ImGuiInputTextFlags.None, ImGuiInputTextCallback? callback = null, IntPtr user_data = default)
public static bool InputTextMultilineWithHint(
string label,
string hint,
ref string input,
int maxLength,
Vector2 size,
ImGuiInputTextFlags flags = ImGuiInputTextFlags.None,
ImGuiInputTextCallback? callback = null,
IntPtr user_data = default
)
{
const ImGuiInputTextFlags Multiline = (ImGuiInputTextFlags)(1 << 26);
return ImGuiExtras.InputTextEx(label, hint, ref input, maxLength, size, flags | Multiline, callback, user_data);
return ImGuiExtras.InputTextEx(
label,
hint,
ref input,
maxLength,
size,
flags | Multiline,
callback,
user_data
);
}
private static Vector2 GetIconSize(FontAwesomeIcon icon)
@@ -563,7 +721,12 @@ internal static class ImGuiUtils
return ImGui.CalcTextSize(icon.ToIconString());
}
private static void DrawCenteredIcon(FontAwesomeIcon icon, Vector2 offset, Vector2 size, bool isDisabled = false)
private static void DrawCenteredIcon(
FontAwesomeIcon icon,
Vector2 offset,
Vector2 size,
bool isDisabled = false
)
{
var iconSize = GetIconSize(icon);
@@ -585,7 +748,15 @@ internal static class ImGuiUtils
iconOffset = Vector2.Zero;
}
ImGui.GetWindowDrawList().AddText(UiBuilder.IconFont, UiBuilder.IconFont.FontSize * ImGuiHelpers.GlobalScale * scale, offset + iconOffset, ImGui.GetColorU32(!isDisabled ? ImGuiCol.Text : ImGuiCol.TextDisabled), icon.ToIconString());
ImGui
.GetWindowDrawList()
.AddText(
UiBuilder.IconFont,
UiBuilder.IconFont.FontSize * ImGuiHelpers.GlobalScale * scale,
offset + iconOffset,
ImGui.GetColorU32(!isDisabled ? ImGuiCol.Text : ImGuiCol.TextDisabled),
icon.ToIconString()
);
}
public static bool IconButtonSquare(FontAwesomeIcon icon, float size = -1)
@@ -627,7 +798,13 @@ internal static class ImGuiUtils
Dalamud.Utility.Util.OpenLink(url);
var urlWithoutScheme = url;
if (Uri.TryCreate(url, UriKind.Absolute, out var uri))
urlWithoutScheme = uri.Host + (string.Equals(uri.PathAndQuery, "/", StringComparison.Ordinal) ? string.Empty : uri.PathAndQuery);
urlWithoutScheme =
uri.Host
+ (
string.Equals(uri.PathAndQuery, "/", StringComparison.Ordinal)
? string.Empty
: uri.PathAndQuery
);
Tooltip(urlWithoutScheme);
}
}
@@ -647,7 +824,11 @@ internal static class ImGuiUtils
ImGui.TextUnformatted(text);
}
public static void TextWrappedTo(string text, float wrapPosX = default, float basePosX = default)
public static void TextWrappedTo(
string text,
float wrapPosX = default,
float basePosX = default
)
{
var font = ImGui.GetFont();
@@ -663,7 +844,11 @@ internal static class ImGuiUtils
currentWrapWidth = wrapPosX - currentPos;
var textBuf = text.AsSpan();
var lineSize = font.CalcWordWrapPositionA(ImGuiHelpers.GlobalScale, textBuf, currentWrapWidth);
var lineSize = font.CalcWordWrapPositionA(
ImGuiHelpers.GlobalScale,
textBuf,
currentWrapWidth
);
if (lineSize == 0)
lineSize = textBuf.Length;
var lineBuf = textBuf[..lineSize];
+7 -4
View File
@@ -1,8 +1,8 @@
using Dalamud.Interface.Utility.Raii;
using Dalamud.Bindings.ImGui;
using Dalamud.Bindings.ImPlot;
using System;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Bindings.ImPlot;
using Dalamud.Interface.Utility.Raii;
namespace Craftimizer.Plugin;
@@ -61,7 +61,10 @@ public static class ImRaii2
public static IEndObject Plot(string title_id, Vector2 size, ImPlotFlags flags)
{
return new EndConditionally(new Action(ImPlot.EndPlot), ImPlot.BeginPlot(title_id, size, flags));
return new EndConditionally(
new Action(ImPlot.EndPlot),
ImPlot.BeginPlot(title_id, size, flags)
);
}
public static IEndObject PushStyle(ImPlotStyleVar idx, Vector2 val)
+11 -5
View File
@@ -10,18 +10,24 @@ public static class LuminaSheets
public static readonly ExcelSheet<Recipe> RecipeSheet = Module.GetSheet<Recipe>();
public static readonly ExcelSheet<Action> ActionSheet = Module.GetSheet<Action>();
public static readonly ExcelSheet<CraftAction> CraftActionSheet = Module.GetSheet<CraftAction>();
public static readonly ExcelSheet<CraftAction> CraftActionSheet =
Module.GetSheet<CraftAction>();
public static readonly ExcelSheet<Status> StatusSheet = Module.GetSheet<Status>();
public static readonly ExcelSheet<Addon> AddonSheet = Module.GetSheet<Addon>();
public static readonly ExcelSheet<ClassJob> ClassJobSheet = Module.GetSheet<ClassJob>();
public static readonly ExcelSheet<Item> ItemSheet = Module.GetSheet<Item>();
public static readonly ExcelSheet<Item> ItemSheetEnglish = Module.GetSheet<Item>(Language.English)!;
public static readonly ExcelSheet<Item> ItemSheetEnglish = Module.GetSheet<Item>(
Language.English
)!;
public static readonly ExcelSheet<Level> LevelSheet = Module.GetSheet<Level>();
public static readonly ExcelSheet<Quest> QuestSheet = Module.GetSheet<Quest>();
public static readonly ExcelSheet<Materia> MateriaSheet = Module.GetSheet<Materia>();
public static readonly ExcelSheet<BaseParam> BaseParamSheet = Module.GetSheet<BaseParam>();
public static readonly ExcelSheet<ItemFood> ItemFoodSheet = Module.GetSheet<ItemFood>();
public static readonly ExcelSheet<WKSMissionToDoEvalutionRefin> WKSMissionToDoEvalutionRefinSheet = Module.GetSheet<WKSMissionToDoEvalutionRefin>();
public static readonly ExcelSheet<RecipeLevelTable> RecipeLevelTableSheet = Module.GetSheet<RecipeLevelTable>();
public static readonly ExcelSheet<GathererCrafterLvAdjustTable> GathererCrafterLvAdjustTableSheet = Module.GetSheet<GathererCrafterLvAdjustTable>();
public static readonly ExcelSheet<WKSMissionToDoEvalutionRefin> WKSMissionToDoEvalutionRefinSheet =
Module.GetSheet<WKSMissionToDoEvalutionRefin>();
public static readonly ExcelSheet<RecipeLevelTable> RecipeLevelTableSheet =
Module.GetSheet<RecipeLevelTable>();
public static readonly ExcelSheet<GathererCrafterLvAdjustTable> GathererCrafterLvAdjustTableSheet =
Module.GetSheet<GathererCrafterLvAdjustTable>();
}
+69 -25
View File
@@ -1,3 +1,6 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Craftimizer.Plugin.Windows;
using Craftimizer.Simulator;
using Craftimizer.Simulator.Actions;
@@ -6,9 +9,6 @@ using Craftimizer.Windows;
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin;
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Craftimizer.Plugin;
@@ -50,9 +50,13 @@ public sealed class Plugin : IDalamudPlugin
AttributeCommandManager = new();
var assembly = Assembly.GetExecutingAssembly();
Version = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion.Split('+')[0];
Version = assembly
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!
.InformationalVersion.Split('+')[0];
Author = assembly.GetCustomAttribute<AssemblyCompanyAttribute>()!.Company;
BuildConfiguration = assembly.GetCustomAttribute<AssemblyConfigurationAttribute>()!.Configuration;
BuildConfiguration = assembly
.GetCustomAttribute<AssemblyConfigurationAttribute>()!
.Configuration;
if (DateTime.Now is { Day: 1, Month: 4 })
Icon = IconManager.GetAssemblyTexture("Graphics.horse_icon.png");
else
@@ -72,21 +76,37 @@ public sealed class Plugin : IDalamudPlugin
Service.PluginInterface.UiBuilder.OpenMainUi += OpenCraftingLog;
}
public (CharacterStats? Character, RecipeData? Recipe, MacroEditor.CrafterBuffs? Buffs) GetOpenedStats()
public (
CharacterStats? Character,
RecipeData? Recipe,
MacroEditor.CrafterBuffs? Buffs
) GetOpenedStats()
{
var editorWindow = (EditorWindow?.IsOpen ?? false) ? EditorWindow : null;
var recipeData = editorWindow?.RecipeData ?? Service.Plugin.RecipeNoteWindow.RecipeData;
var characterStats = editorWindow?.CharacterStats ?? Service.Plugin.RecipeNoteWindow.CharacterStats;
var buffs = editorWindow?.Buffs ?? (RecipeNoteWindow.CharacterStats != null ? new(Service.Objects.LocalPlayer?.StatusList) : null);
var characterStats =
editorWindow?.CharacterStats ?? Service.Plugin.RecipeNoteWindow.CharacterStats;
var buffs =
editorWindow?.Buffs
?? (
RecipeNoteWindow.CharacterStats != null
? new(Service.Objects.LocalPlayer?.StatusList)
: null
);
return (characterStats, recipeData, buffs);
}
public (CharacterStats Character, RecipeData Recipe, MacroEditor.CrafterBuffs Buffs) GetDefaultStats()
public (
CharacterStats Character,
RecipeData Recipe,
MacroEditor.CrafterBuffs Buffs
) GetDefaultStats()
{
var stats = GetOpenedStats();
return (
stats.Character ?? new()
stats.Character
?? new()
{
Craftsmanship = 100,
Control = 100,
@@ -101,30 +121,48 @@ public sealed class Plugin : IDalamudPlugin
);
}
[Command(name: "/crafteditor", aliases: "/macroeditor", description: "Open the crafting macro editor.")]
[Command(
name: "/crafteditor",
aliases: "/macroeditor",
description: "Open the crafting macro editor."
)]
public void OpenEmptyMacroEditor()
{
var stats = GetDefaultStats();
OpenMacroEditor(stats.Character, stats.Recipe, stats.Buffs, null, [], null);
}
public void OpenMacroEditor(CharacterStats characterStats, RecipeData recipeData, MacroEditor.CrafterBuffs buffs, IEnumerable<int>? ingredientHqCounts, IEnumerable<ActionType> actions, Action<IEnumerable<ActionType>>? setter)
public void OpenMacroEditor(
CharacterStats characterStats,
RecipeData recipeData,
MacroEditor.CrafterBuffs buffs,
IEnumerable<int>? ingredientHqCounts,
IEnumerable<ActionType> actions,
Action<IEnumerable<ActionType>>? setter
)
{
EditorWindow?.Dispose();
EditorWindow = new(characterStats, recipeData, buffs, ingredientHqCounts, actions, setter);
}
[Command(name: "/craftaction", description: "Execute the suggested action in the synthesis helper. Can also be run inside a macro. This command is useful for controller players.")]
public void ExecuteSuggestedSynthHelperAction() =>
SynthHelperWindow.ExecuteNextAction();
[Command(
name: "/craftaction",
description: "Execute the suggested action in the synthesis helper. Can also be run inside a macro. This command is useful for controller players."
)]
public void ExecuteSuggestedSynthHelperAction() => SynthHelperWindow.ExecuteNextAction();
[Command(name: "/craftretry", description: "Clicks \"Retry\" in the synthesis helper. Can also be run inside a macro. This command is useful for controller players.")]
public void ExecuteRetrySynthHelper() =>
SynthHelperWindow.AttemptRetry();
[Command(
name: "/craftretry",
description: "Clicks \"Retry\" in the synthesis helper. Can also be run inside a macro. This command is useful for controller players."
)]
public void ExecuteRetrySynthHelper() => SynthHelperWindow.AttemptRetry();
[Command(name: "/craftimizer", aliases: "/forgeimizer", description: "Open the settings window.")]
private void OpenSettingsWindowForced() =>
OpenSettingsWindow(true);
[Command(
name: "/craftimizer",
aliases: "/forgeimizer",
description: "Open the settings window."
)]
private void OpenSettingsWindowForced() => OpenSettingsWindow(true);
public void OpenSettingsWindow(bool force = false)
{
@@ -138,7 +176,11 @@ public sealed class Plugin : IDalamudPlugin
SettingsWindow.SelectTab(selectedTabLabel);
}
[Command(name: "/craftmacros", aliases: "/macrolist", description: "Open the crafting macros window.")]
[Command(
name: "/craftmacros",
aliases: "/macrolist",
description: "Open the crafting macros window."
)]
public void OpenMacroListWindow()
{
ListWindow.IsOpen = true;
@@ -157,12 +199,14 @@ public sealed class Plugin : IDalamudPlugin
}
public static IActiveNotification DisplaySolverWarning(string text) =>
DisplayNotification(new()
DisplayNotification(
new()
{
Content = text,
Title = "Solver Warning",
Type = NotificationType.Warning
});
Type = NotificationType.Warning,
}
);
public static IActiveNotification DisplayNotification(Notification notification)
{
+47 -16
View File
@@ -14,22 +14,53 @@ namespace Craftimizer.Plugin;
public sealed class Service
{
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
[PluginService] public static IDalamudPluginInterface PluginInterface { get; private set; }
[PluginService] public static ICommandManager CommandManager { get; private set; }
[PluginService] public static IObjectTable Objects { get; private set; }
[PluginService] public static ISigScanner SigScanner { get; private set; }
[PluginService] public static IGameGui GameGui { get; private set; }
[PluginService] public static IClientState ClientState { get; private set; }
[PluginService] public static IDataManager DataManager { get; private set; }
[PluginService] public static ITextureProvider TextureProvider { get; private set; }
[PluginService] public static IDalamudAssetManager DalamudAssetManager { get; private set; }
[PluginService] public static ITargetManager TargetManager { get; private set; }
[PluginService] public static ICondition Condition { get; private set; }
[PluginService] public static IFramework Framework { get; private set; }
[PluginService] public static IPluginLog PluginLog { get; private set; }
[PluginService] public static IGameInteropProvider GameInteropProvider { get; private set; }
[PluginService] public static INotificationManager NotificationManager { get; private set; }
[PluginService] public static ISeStringEvaluator SeStringEvaluator { get; private set; }
[PluginService]
public static IDalamudPluginInterface PluginInterface { get; private set; }
[PluginService]
public static ICommandManager CommandManager { get; private set; }
[PluginService]
public static IObjectTable Objects { get; private set; }
[PluginService]
public static ISigScanner SigScanner { get; private set; }
[PluginService]
public static IGameGui GameGui { get; private set; }
[PluginService]
public static IClientState ClientState { get; private set; }
[PluginService]
public static IDataManager DataManager { get; private set; }
[PluginService]
public static ITextureProvider TextureProvider { get; private set; }
[PluginService]
public static IDalamudAssetManager DalamudAssetManager { get; private set; }
[PluginService]
public static ITargetManager TargetManager { get; private set; }
[PluginService]
public static ICondition Condition { get; private set; }
[PluginService]
public static IFramework Framework { get; private set; }
[PluginService]
public static IPluginLog PluginLog { get; private set; }
[PluginService]
public static IGameInteropProvider GameInteropProvider { get; private set; }
[PluginService]
public static INotificationManager NotificationManager { get; private set; }
[PluginService]
public static ISeStringEvaluator SeStringEvaluator { get; private set; }
public static Plugin Plugin { get; private set; }
public static Configuration Configuration => Plugin.Configuration;
+85 -37
View File
@@ -1,21 +1,21 @@
using Craftimizer.Simulator;
using Craftimizer.Simulator.Actions;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using System;
using System.Linq;
using System.Numerics;
using System.Text;
using Craftimizer.Simulator;
using Craftimizer.Simulator.Actions;
using Craftimizer.Utils;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game.Event;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using Lumina.Excel.Sheets;
using Lumina.Text.Payloads;
using Lumina.Text.ReadOnly;
using Action = Lumina.Excel.Sheets.Action;
using ActionType = Craftimizer.Simulator.Actions.ActionType;
using ClassJob = Craftimizer.Simulator.ClassJob;
using Condition = Craftimizer.Simulator.Condition;
using Status = Lumina.Excel.Sheets.Status;
using Craftimizer.Utils;
using Lumina.Text.ReadOnly;
using Lumina.Text.Payloads;
using Lumina.Excel.Sheets;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game.Event;
namespace Craftimizer.Plugin;
@@ -27,7 +27,10 @@ internal static class ActionUtils
{
var actionTypes = Enum.GetValues<ActionType>();
var classJobs = Enum.GetValues<ClassJob>();
ActionRows = new (CraftAction? CraftAction, Action? Action)[actionTypes.Length, classJobs.Length];
ActionRows = new (CraftAction? CraftAction, Action? Action)[
actionTypes.Length,
classJobs.Length
];
foreach (var actionType in actionTypes)
{
var actionId = actionType.Base().ActionId;
@@ -35,7 +38,8 @@ internal static class ActionUtils
{
foreach (var classJob in classJobs)
{
ActionRows[(int)actionType, (int)classJob] = (classJob switch
ActionRows[(int)actionType, (int)classJob] = (
classJob switch
{
ClassJob.Carpenter => baseCraftAction.CRP.Value,
ClassJob.Blacksmith => baseCraftAction.BSM.Value,
@@ -45,27 +49,37 @@ internal static class ActionUtils
ClassJob.Weaver => baseCraftAction.WVR.Value,
ClassJob.Alchemist => baseCraftAction.ALC.Value,
ClassJob.Culinarian => baseCraftAction.CUL.Value,
_ => baseCraftAction
}, null);
_ => baseCraftAction,
},
null
);
}
}
if (LuminaSheets.ActionSheet.GetRowOrDefault(actionId) is { } baseAction)
{
var possibleActions = LuminaSheets.ActionSheet.Where(r =>
r.Icon == baseAction.Icon &&
r.ActionCategory.RowId == baseAction.ActionCategory.RowId &&
r.Name.Equals(baseAction.Name));
r.Icon == baseAction.Icon
&& r.ActionCategory.RowId == baseAction.ActionCategory.RowId
&& r.Name.Equals(baseAction.Name)
);
foreach (var classJob in classJobs)
ActionRows[(int)actionType, (int)classJob] = (null, possibleActions.First(r => r.ClassJobCategory.ValueNullable?.IsClassJob(classJob) ?? false));
ActionRows[(int)actionType, (int)classJob] = (
null,
possibleActions.First(r =>
r.ClassJobCategory.ValueNullable?.IsClassJob(classJob) ?? false
)
);
}
}
}
public static void Initialize() { }
public static (CraftAction? CraftAction, Action? Action) GetActionRow(this ActionType me, ClassJob classJob) =>
ActionRows[(int)me, (int)classJob];
public static (CraftAction? CraftAction, Action? Action) GetActionRow(
this ActionType me,
ClassJob classJob
) => ActionRows[(int)me, (int)classJob];
public static uint GetId(this ActionType me, ClassJob classJob)
{
@@ -86,7 +100,11 @@ internal static class ActionUtils
return Service.IconManager.GetIconCached(craftAction?.Icon ?? action?.Icon ?? 1953);
}
public static ActionType? GetActionTypeFromId(uint actionId, ClassJob classJob, bool isCraftAction)
public static ActionType? GetActionTypeFromId(
uint actionId,
ClassJob classJob,
bool isCraftAction
)
{
foreach (var action in Enum.GetValues<ActionType>())
{
@@ -119,7 +137,7 @@ internal static class ClassJobUtils
ClassJob.Weaver => 13,
ClassJob.Alchemist => 14,
ClassJob.Culinarian => 15,
_ => 0
_ => 0,
};
public static ClassJob? GetClassJobFromIdx(byte classJobIdx) =>
@@ -133,7 +151,7 @@ internal static class ClassJobUtils
13 => ClassJob.Weaver,
14 => ClassJob.Alchemist,
15 => ClassJob.Culinarian,
_ => null
_ => null,
};
public static sbyte GetExpArrayIdx(this ClassJob me) =>
@@ -158,7 +176,11 @@ internal static class ClassJobUtils
}
public static unsafe bool CanPlayerUseManipulation(this ClassJob me) =>
UIState.Instance()->IsUnlockLinkUnlockedOrQuestCompleted(ActionType.Manipulation.GetActionRow(me).Action!.Value.UnlockLink.RowId);
UIState
.Instance()
->IsUnlockLinkUnlockedOrQuestCompleted(
ActionType.Manipulation.GetActionRow(me).Action!.Value.UnlockLink.RowId
);
public static string GetName(this ClassJob me)
{
@@ -186,8 +208,7 @@ internal static class ClassJobUtils
public static Quest GetUnlockQuest(this ClassJob me) =>
LuminaSheets.QuestSheet.GetRow(65720 + (uint)me);
public static ushort GetIconId(this ClassJob me) =>
(ushort)(62000 + me.GetClassJobIndex());
public static ushort GetIconId(this ClassJob me) => (ushort)(62000 + me.GetClassJobIndex());
public static bool IsClassJob(this ClassJobCategory me, ClassJob classJob) =>
classJob switch
@@ -200,7 +221,7 @@ internal static class ClassJobUtils
ClassJob.Weaver => me.WVR,
ClassJob.Alchemist => me.ALC,
ClassJob.Culinarian => me.CUL,
_ => false
_ => false,
};
}
@@ -219,7 +240,7 @@ internal static class ConditionUtils
Condition.Malleable => (13455, 14208),
Condition.Primed => (13454, 14207),
Condition.GoodOmen => (14214, 14215),
_ => (226, 14200) // Unknown
_ => (226, 14200), // Unknown
};
private static Vector3 AddRGB(this Condition me) =>
@@ -235,10 +256,11 @@ internal static class ConditionUtils
Condition.Malleable => new(-80, -40, 180),
Condition.Primed => new(30, -155, 200),
Condition.GoodOmen => new(100, 20, 0),
_ => Vector3.Zero // Unknown
_ => Vector3.Zero, // Unknown
};
private const float ConditionCyclePeriod = 19 / 30f;
// The real period of all condition color cycles are 0.633... (19/30) seconds
// Interp accepts 0-1
public static Vector4 GetColor(this Condition me, float interp)
@@ -252,12 +274,32 @@ internal static class ConditionUtils
addRgb = interp switch
{
< 0.155f => Vector3.Lerp(new(128, 0, 0), new(128, 80, 0), (interp - 0) / 0.155f),
< 0.315f => Vector3.Lerp(new(128, 80, 0), new(128, 128, 0), (interp - 0.155f) / 0.16f),
< 0.475f => Vector3.Lerp(new(128, 128, 0), new(0, 64, 0), (interp - 0.315f) / 0.16f),
< 0.630f => Vector3.Lerp(new(0, 64, 0), new(0, 128, 128), (interp - 0.475f) / 0.155f),
< 0.790f => Vector3.Lerp(new(0, 128, 128), new(0, 0, 128), (interp - 0.630f) / 0.16f),
< 0.945f => Vector3.Lerp(new(0, 0, 128), new(64, 0, 64), (interp - 0.790f) / 0.155f),
_ => new(64, 0, 64)
< 0.315f => Vector3.Lerp(
new(128, 80, 0),
new(128, 128, 0),
(interp - 0.155f) / 0.16f
),
< 0.475f => Vector3.Lerp(
new(128, 128, 0),
new(0, 64, 0),
(interp - 0.315f) / 0.16f
),
< 0.630f => Vector3.Lerp(
new(0, 64, 0),
new(0, 128, 128),
(interp - 0.475f) / 0.155f
),
< 0.790f => Vector3.Lerp(
new(0, 128, 128),
new(0, 0, 128),
(interp - 0.630f) / 0.16f
),
< 0.945f => Vector3.Lerp(
new(0, 0, 128),
new(64, 0, 64),
(interp - 0.790f) / 0.155f
),
_ => new(64, 0, 64),
};
}
// Period is twice as fast so we oscillate at twice that speed
@@ -285,7 +327,7 @@ internal static class ConditionUtils
Condition.Pliant => Vector3.Lerp(new(0, 150, 0), new(0, 249, 0), interp),
Condition.Primed => Vector3.Lerp(new(-30, -255, 50), new(29, -156, 199), interp),
Condition.GoodOmen => Vector3.Lerp(new(100, 20, 0), new(100, 99, 99), interp),
_ => default
_ => default,
};
}
@@ -294,7 +336,9 @@ internal static class ConditionUtils
public static Vector4 GetColor(this Condition me, TimeSpan time)
{
return me.GetColor((float)(time.TotalSeconds % ConditionCyclePeriod / ConditionCyclePeriod));
return me.GetColor(
(float)(time.TotalSeconds % ConditionCyclePeriod / ConditionCyclePeriod)
);
}
public static string Name(this Condition me) =>
@@ -310,7 +354,11 @@ internal static class ConditionUtils
foreach (var payload in text)
{
if (payload is { Type: ReadOnlySePayloadType.Macro, MacroCode: MacroCode.Float })
finalText += new ReadOnlySePayload(ReadOnlySePayloadType.Text, default, Encoding.UTF8.GetBytes(isRelic ? "1.75" : "1.5"));
finalText += new ReadOnlySePayload(
ReadOnlySePayloadType.Text,
default,
Encoding.UTF8.GetBytes(isRelic ? "1.75" : "1.5")
);
else
finalText += payload;
}
+22 -7
View File
@@ -1,13 +1,18 @@
using Craftimizer.Plugin;
using Dalamud.Game.Command;
using System;
using System.Collections.Generic;
using System.Reflection;
using Craftimizer.Plugin;
using Dalamud.Game.Command;
namespace Craftimizer.Utils;
[AttributeUsage(AttributeTargets.Method)]
public sealed class CommandAttribute(string name, string description, bool hidden = false, params string[] aliases) : Attribute
public sealed class CommandAttribute(
string name,
string description,
bool hidden = false,
params string[] aliases
) : Attribute
{
public string Name { get; } = name;
public string Description { get; } = description;
@@ -22,7 +27,11 @@ public sealed class AttributeCommandManager : IDisposable
public AttributeCommandManager()
{
var target = Service.Plugin;
foreach (var method in target.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
foreach (
var method in target
.GetType()
.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
)
{
if (method.GetCustomAttribute<CommandAttribute>() is not { } command)
continue;
@@ -51,15 +60,21 @@ public sealed class AttributeCommandManager : IDisposable
};
if (!RegisteredCommands.Add(command.Name))
throw new InvalidOperationException($"Command '{command.Name}' is already registered.");
throw new InvalidOperationException(
$"Command '{command.Name}' is already registered."
);
if (!Service.CommandManager.AddHandler(command.Name, info))
throw new InvalidOperationException($"Failed to register command '{command.Name}'.");
throw new InvalidOperationException(
$"Failed to register command '{command.Name}'."
);
foreach (var alias in command.Aliases)
{
if (!RegisteredCommands.Add(alias))
throw new InvalidOperationException($"Command '{alias}' is already registered.");
throw new InvalidOperationException(
$"Command '{alias}' is already registered."
);
if (!Service.CommandManager.AddHandler(alias, aliasInfo))
throw new InvalidOperationException($"Failed to register command '{alias}'.");
+11 -8
View File
@@ -4,7 +4,8 @@ using System.Threading.Tasks;
namespace Craftimizer.Utils;
public sealed class BackgroundTask<T>(Func<CancellationToken, T> func) : IDisposable where T : struct
public sealed class BackgroundTask<T>(Func<CancellationToken, T> func) : IDisposable
where T : struct
{
public T? Result { get; private set; }
public Exception? Exception { get; private set; }
@@ -19,26 +20,28 @@ public sealed class BackgroundTask<T>(Func<CancellationToken, T> func) : IDispos
var token = TokenSource.Token;
var task = Task.Run(() => Result = Func(token), token);
_ = task.ContinueWith(t => Completed = true);
_ = task.ContinueWith(t =>
_ = task.ContinueWith(
t =>
{
if (token.IsCancellationRequested)
return;
try
{
t.Exception!.Flatten().Handle(ex => ex is TaskCanceledException or OperationCanceledException);
t.Exception!.Flatten()
.Handle(ex => ex is TaskCanceledException or OperationCanceledException);
}
catch (AggregateException e)
{
Exception = e;
Log.Error(e, "Background task failed");
}
}, TaskContinuationOptions.OnlyOnFaulted);
},
TaskContinuationOptions.OnlyOnFaulted
);
}
public void Cancel() =>
TokenSource.Cancel();
public void Cancel() => TokenSource.Cancel();
public void Dispose() =>
Cancel();
public void Dispose() => Cancel();
}
+3 -2
View File
@@ -1,13 +1,14 @@
using System.Runtime.InteropServices;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Client.System.String;
using System.Runtime.InteropServices;
namespace Craftimizer.Utils;
[StructLayout(LayoutKind.Explicit, Size = 2880)]
public unsafe struct CSRecipeNote
{
[FieldOffset(0x118)] public ushort ActiveCraftRecipeId;
[FieldOffset(0x118)]
public ushort ActiveCraftRecipeId;
public static CSRecipeNote* Instance()
{
+22 -10
View File
@@ -1,6 +1,6 @@
using System;
using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI;
using System;
namespace Craftimizer.Utils;
@@ -19,16 +19,28 @@ public static unsafe class Chat
var unsanitizedLength = str->Length;
str->SanitizeString(
AllowedEntities.Unknown9 | // 200
AllowedEntities.Payloads | // 40
AllowedEntities.OtherCharacters | // 20
AllowedEntities.CharacterList | // 10
AllowedEntities.SpecialCharacters | // 8
AllowedEntities.Numbers | // 4
AllowedEntities.LowercaseLetters | // 2
AllowedEntities.Unknown9
| // 200
AllowedEntities.Payloads
| // 40
AllowedEntities.OtherCharacters
| // 20
AllowedEntities.CharacterList
| // 10
AllowedEntities.SpecialCharacters
| // 8
AllowedEntities.Numbers
| // 4
AllowedEntities.LowercaseLetters
| // 2
AllowedEntities.UppercaseLetters, // 1
null);
ArgumentOutOfRangeException.ThrowIfNotEqual(unsanitizedLength, str->Length, nameof(message));
null
);
ArgumentOutOfRangeException.ThrowIfNotEqual(
unsanitizedLength,
str->Length,
nameof(message)
);
UIModule.Instance()->ProcessChatBoxEntry(str);
}
+6 -5
View File
@@ -1,8 +1,8 @@
using Craftimizer.Plugin;
using Dalamud.Interface.Colors;
using Dalamud.Bindings.ImGui;
using System;
using System.Numerics;
using Craftimizer.Plugin;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Colors;
namespace Craftimizer.Utils;
@@ -15,7 +15,8 @@ public static class Colors
public static readonly Vector4 Collectability = new(0.99f, 0.56f, 0.57f, 1f);
public static readonly Vector4 CP = new(0.63f, 0.37f, 0.75f, 1f);
private static Vector4 SolverProgressBg => ImGui.ColorConvertU32ToFloat4(ImGui.GetColorU32(ImGuiCol.TableBorderLight));
private static Vector4 SolverProgressBg =>
ImGui.ColorConvertU32ToFloat4(ImGui.GetColorU32(ImGuiCol.TableBorderLight));
private static Vector4 SolverProgressFgBland => ImGuiColors.DalamudWhite2;
private static readonly Vector4[] SolverProgressFgColorful =
@@ -52,7 +53,7 @@ public static class Colors
{
Configuration.ProgressBarType.Colorful => SolverProgressFgColorful,
Configuration.ProgressBarType.Simple => SolverProgressFgMonochromatic,
_ => throw new InvalidOperationException("No progress bar should be visible")
_ => throw new InvalidOperationException("No progress bar should be visible"),
};
if (stageValue is not { } stage)
+97 -47
View File
@@ -1,18 +1,18 @@
using Dalamud.Networking.Http;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using System.Text.Json.Serialization;
using System.Net.Http.Json;
using System.Text.Json;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Craftimizer.Simulator.Actions;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Craftimizer.Simulator;
using Craftimizer.Simulator.Actions;
using Craftimizer.Solver;
using Dalamud.Networking.Http;
namespace Craftimizer.Utils;
@@ -40,7 +40,9 @@ public sealed class CommunityMacros
{
[JsonPropertyName("integerValue")]
[JsonRequired]
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
[JsonNumberHandling(
JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString
)]
public required int Value { get; set; }
public static implicit operator int(IntegerValue v) => v.Value;
@@ -59,6 +61,7 @@ public sealed class CommunityMacros
public required ValueData Data { get; set; }
public T Value => Data.Fields;
public static implicit operator T(MapValue<T> v) => v.Value;
}
@@ -74,6 +77,7 @@ public sealed class CommunityMacros
public required ValueData Data { get; set; }
public T[] Value => Data.Values ?? [];
public static implicit operator T[](ArrayValue<T> v) => v.Value;
}
@@ -88,8 +92,10 @@ public sealed class CommunityMacros
{
[JsonRequired]
public required List<CollectionSelector> From { get; set; }
[JsonRequired]
public required Filter Where { get; set; }
[JsonRequired]
public required List<Order> OrderBy { get; set; }
}
@@ -109,6 +115,7 @@ public sealed class CommunityMacros
{
[JsonRequired]
public required List<Filter> Filters { get; set; }
[JsonRequired]
public required CompositeOperator Op { get; set; }
}
@@ -117,13 +124,14 @@ public sealed class CommunityMacros
{
OPERATOR_UNSPECIFIED,
AND,
OR
OR,
}
public sealed record FieldFilter
{
[JsonRequired]
public required FieldReference Field { get; set; }
[JsonRequired]
public required FieldOperator Op { get; set; }
public object? Value { get; set; }
@@ -141,13 +149,14 @@ public sealed class CommunityMacros
ARRAY_CONTAINS,
IN,
ARRAY_CONTAINS_ANY,
NOT_IN
NOT_IN,
}
public sealed record Order
{
[JsonRequired]
public required FieldReference Field { get; set; }
[JsonRequired]
public required Direction Direction { get; set; }
}
@@ -162,7 +171,7 @@ public sealed class CommunityMacros
{
DIRECTION_UNSPECIFIED,
ASCENDING,
DESCENDING
DESCENDING,
}
private sealed record RunQueryRequest
@@ -178,6 +187,7 @@ public sealed class CommunityMacros
[JsonRequired]
[JsonPropertyName("rlvl")]
public required IntegerValue RLvl { get; set; }
[JsonRequired]
public required IntegerValue Durability { get; set; }
}
@@ -185,6 +195,7 @@ public sealed class CommunityMacros
public sealed record FieldData
{
public StringValue? Name { get; set; }
[JsonRequired]
public required ArrayValue<StringValue> Rotation { get; set; }
public MapValue<RecipeFieldData>? Recipe { get; set; }
@@ -212,6 +223,7 @@ public sealed class CommunityMacros
public string? Slug { get; set; }
public string? Version { get; set; }
public string? Job { get; set; }
[JsonPropertyName("job_level")]
public int JobLevel { get; set; }
public int Craftsmanship { get; set; }
@@ -219,11 +231,14 @@ public sealed class CommunityMacros
public int CP { get; set; }
public string? Food { get; set; }
public string? Potion { get; set; }
[JsonPropertyName("recipe_job_level")]
public int RecipeJobLevel { get; set; }
public string? Recipe { get; set; }
// HqIngredients
public string? Actions { get; set; }
[JsonPropertyName("created_at")]
public long CreatedAt { get; set; }
public string? Error { get; set; }
@@ -241,10 +256,13 @@ public sealed class CommunityMacros
throw new Exception($"Internal error; No fields were returned");
// https://github.com/ffxiv-teamcraft/ffxiv-teamcraft/blob/67f453041c6b2b31d32fcf6e1fd53aa38ed7a12b/apps/client/src/app/model/other/crafting-rotation.ts#L49
Name = rotation.Name?.Value ??
(rotation.Recipe is { Value: var recipe } ?
$"rlvl{recipe.RLvl.Value} - {rotation.Rotation.Value.Length} steps, {recipe.Durability.Value} dur" :
"New Teamcraft Rotation");
Name =
rotation.Name?.Value
?? (
rotation.Recipe is { Value: var recipe }
? $"rlvl{recipe.RLvl.Value} - {rotation.Rotation.Value.Length} steps, {recipe.Durability.Value} dur"
: "New Teamcraft Rotation"
);
var actions = new List<ActionType>();
foreach (var action in rotation.Rotation.Value)
@@ -290,7 +308,7 @@ public sealed class CommunityMacros
"RemoveFinalAppraisal" => null,
// Old actions?
_ => null
_ => null,
};
if (actionType.HasValue)
actions.Add(actionType.Value);
@@ -353,7 +371,7 @@ public sealed class CommunityMacros
"DelicateSynthesisTraited" => ActionType.DelicateSynthesis,
// Old actions?
_ => null
_ => null,
};
if (actionType.HasValue)
actions.Add(actionType.Value);
@@ -361,16 +379,30 @@ public sealed class CommunityMacros
Actions = actions;
}
public (float Score, SimulationState FinalState) CalculateScore(SimulatorNoRandom simulator, in SimulationState startingState, in MCTSConfig mctsConfig)
public (float Score, SimulationState FinalState) CalculateScore(
SimulatorNoRandom simulator,
in SimulationState startingState,
in MCTSConfig mctsConfig
)
{
return CalculateScore(Actions, simulator, startingState, mctsConfig);
}
public static (float Score, SimulationState FinalState) CalculateScore(IReadOnlyCollection<ActionType> actions, SimulatorNoRandom simulator, in SimulationState startingState, in MCTSConfig mctsConfig)
public static (float Score, SimulationState FinalState) CalculateScore(
IReadOnlyCollection<ActionType> actions,
SimulatorNoRandom simulator,
in SimulationState startingState,
in MCTSConfig mctsConfig
)
{
var (resp, outState, failedIdx) = simulator.ExecuteMultiple(startingState, actions);
outState.ActionCount = actions.Count;
var score = SimulationNode.CalculateScoreForState(outState, simulator.CompletionState, mctsConfig) ?? 0;
var score =
SimulationNode.CalculateScoreForState(
outState,
simulator.CompletionState,
mctsConfig
) ?? 0;
if (resp != ActionResponse.SimulationComplete)
{
if (failedIdx != -1)
@@ -382,7 +414,10 @@ public sealed class CommunityMacros
private Dictionary<int, List<CommunityMacro>> CachedRotations { get; } = [];
public async Task<IReadOnlyList<CommunityMacro>> RetrieveRotations(int rlvl, CancellationToken token)
public async Task<IReadOnlyList<CommunityMacro>> RetrieveRotations(
int rlvl,
CancellationToken token
)
{
lock (CachedRotations)
{
@@ -397,23 +432,25 @@ public sealed class CommunityMacros
return macros;
}
private static async Task<List<TeamcraftMacro>> RetrieveRotationsInternal(int rlvl, CancellationToken token)
private static async Task<List<TeamcraftMacro>> RetrieveRotationsInternal(
int rlvl,
CancellationToken token
)
{
using var heCallback = new HappyEyeballsCallback();
using var client = new HttpClient(new SocketsHttpHandler
using var client = new HttpClient(
new SocketsHttpHandler
{
AutomaticDecompression = DecompressionMethods.All,
ConnectCallback = heCallback.ConnectCallback,
});
}
);
var request = new RunQueryRequest
{
StructuredQuery = new StructuredQuery
{
From =
[
new() { CollectionId = "rotations" }
],
From = [new() { CollectionId = "rotations" }],
Where = new Filter
{
CompositeFilter = new CompositeFilter
@@ -427,8 +464,8 @@ public sealed class CommunityMacros
{
Field = new FieldReference { FieldPath = "public" },
Op = FieldOperator.EQUAL,
Value = new BooleanValue { Value = true }
}
Value = new BooleanValue { Value = true },
},
},
new()
{
@@ -436,10 +473,10 @@ public sealed class CommunityMacros
{
Field = new FieldReference { FieldPath = "community.rlvl" },
Op = FieldOperator.EQUAL,
Value = new IntegerValue { Value = rlvl }
}
}
]
Value = new IntegerValue { Value = rlvl },
},
},
],
},
},
OrderBy =
@@ -447,39 +484,50 @@ public sealed class CommunityMacros
new()
{
Field = new FieldReference { FieldPath = "xivVersion" },
Direction = Direction.DESCENDING
Direction = Direction.DESCENDING,
},
new()
{
Field = new FieldReference { FieldPath = "__name__" },
Direction = Direction.DESCENDING
}
]
Direction = Direction.DESCENDING,
},
],
},
};
var resp = await PostFromJsonAsync<RunQueryRequest, List<QueriedTeamcraftMacro>>(
client,
$"https://firestore.googleapis.com/v1beta1/projects/ffxivteamcraft/databases/(default)/documents:runQuery",
request, new JsonSerializerOptions
request,
new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
}, token).
ConfigureAwait(false);
},
token
)
.ConfigureAwait(false);
if (resp is null)
throw new Exception("Internal server error; failed to retrieve macro");
foreach(var macro in resp)
foreach (var macro in resp)
{
if (macro.Error is { } error)
throw new Exception($"Internal server error ({error.Status}); {error.Message}");
}
return resp.Where(macro => macro.Document is not null).Select(macro => macro.Document!).ToList();
return resp.Where(macro => macro.Document is not null)
.Select(macro => macro.Document!)
.ToList();
}
private static async Task<TResponse?> PostFromJsonAsync<TRequest, TResponse>(HttpClient client, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, TRequest value, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default)
private static async Task<TResponse?> PostFromJsonAsync<TRequest, TResponse>(
HttpClient client,
[StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri,
TRequest value,
JsonSerializerOptions? options = null,
CancellationToken cancellationToken = default
)
{
ArgumentNullException.ThrowIfNull(client);
@@ -487,6 +535,8 @@ public sealed class CommunityMacros
using var message = await resp.ConfigureAwait(false);
message.EnsureSuccessStatusCode();
return await message.Content!.ReadFromJsonAsync<TResponse>(options, cancellationToken).ConfigureAwait(false);
return await message
.Content!.ReadFromJsonAsync<TResponse>(options, cancellationToken)
.ConfigureAwait(false);
}
}
+53 -25
View File
@@ -1,28 +1,34 @@
using Craftimizer.Plugin;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Bindings.ImGui;
using System.Collections.Generic;
using System;
using System.Numerics;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Utility.Numerics;
using System.Numerics;
using Craftimizer.Plugin;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Utility.Numerics;
namespace Craftimizer.Utils;
internal static class DynamicBars
{
public readonly record struct BarData(string Name, Vector4 Color, SimulatedMacro.Reliablity.Param? Reliability, float Value, float Max, IReadOnlyList<int?>? Collectability = null, string? Caption = null, string? DefaultCaptionSizeText = null, Action<DrawerParams>? CustomDrawer = null)
{
public BarData(string name, Action<DrawerParams> customDrawer) : this(name, default, null, 0, 0, null, null, null, customDrawer)
public readonly record struct BarData(
string Name,
Vector4 Color,
SimulatedMacro.Reliablity.Param? Reliability,
float Value,
float Max,
IReadOnlyList<int?>? Collectability = null,
string? Caption = null,
string? DefaultCaptionSizeText = null,
Action<DrawerParams>? CustomDrawer = null
)
{
public BarData(string name, Action<DrawerParams> customDrawer)
: this(name, default, null, 0, 0, null, null, null, customDrawer) { }
}
public BarData(string name, Vector4 color, float value, float max) : this(name, color, null, value, max, null, null, null)
{
}
public BarData(string name, Vector4 color, float value, float max)
: this(name, color, null, value, max, null, null, null) { }
}
public readonly record struct DrawerParams(float TotalSize, float Spacing);
@@ -39,13 +45,19 @@ internal static class DynamicBars
return Math.Max(ImGui.CalcTextSize(caption).X, defaultSize);
// max (sp/2) "/" (sp/2) max
return Math.Max(
Math.Max(ImGui.CalcTextSize($"{b.Value:0}").X, ImGui.CalcTextSize($"{b.Max:0}").X) * 2
Math.Max(ImGui.CalcTextSize($"{b.Value:0}").X, ImGui.CalcTextSize($"{b.Max:0}").X)
* 2
+ ImGui.GetStyle().ItemSpacing.X
+ ImGui.CalcTextSize("/").X,
defaultSize);
defaultSize
);
});
private static ImRaii.ColorDisposable? PushCollectableColor(this in BarData bar, float collectability, bool colorUnmetThreshold = true)
private static ImRaii.ColorDisposable? PushCollectableColor(
this in BarData bar,
float collectability,
bool colorUnmetThreshold = true
)
{
if (bar.Collectability is not { } collectabilities)
return null;
@@ -88,7 +100,10 @@ internal static class DynamicBars
var pos = ImGui.GetCursorPos();
var screenPos = ImGui.GetCursorScreenPos();
using (var color = ImRaii.PushColor(ImGuiCol.PlotHistogram, bar.Color))
ImGuiUtils.ProgressBar(Math.Clamp(bar.Value / bar.Max, 0, 1), new(barSize, ImGui.GetFrameHeight()));
ImGuiUtils.ProgressBar(
Math.Clamp(bar.Value / bar.Max, 0, 1),
new(barSize, ImGui.GetFrameHeight())
);
if (bar.Collectability is { } collectability)
{
var i = 0;
@@ -101,15 +116,21 @@ internal static class DynamicBars
continue;
var offset = barSize * threshold / bar.Max;
var isLast = i == collectability.Count;
var offsetNext = isLast ? barSize : barSize * collectability[i]!.Value / bar.Max;
var offsetNext = isLast
? barSize
: barSize * collectability[i]!.Value / bar.Max;
var passedThreshold = bar.Value >= threshold;
ImGui.GetWindowDrawList().AddRectFilled(
ImGui
.GetWindowDrawList()
.AddRectFilled(
screenPos + new Vector2(offset, 0),
screenPos + new Vector2(offsetNext, height),
ImGui.GetColorU32(color.WithW(passedThreshold ? 0.6f : 0.2f)),
isLast ? rounding : 0
);
ImGui.GetWindowDrawList().AddLine(
ImGui
.GetWindowDrawList()
.AddLine(
screenPos + new Vector2(offset, 0),
screenPos + new Vector2(offset, height),
ImGui.GetColorU32(color),
@@ -121,7 +142,10 @@ internal static class DynamicBars
{
if (bar.Reliability is { } reliability)
{
if (reliability.GetViolinData(bar.Max, (int)(barSize / 5), 0.02) is { } violinData)
if (
reliability.GetViolinData(bar.Max, (int)(barSize / 5), 0.02) is
{ } violinData
)
{
ImGui.SetCursorPos(pos);
ImGuiUtils.ViolinPlot(violinData, new(barSize, ImGui.GetFrameHeight()));
@@ -129,7 +153,10 @@ internal static class DynamicBars
{
using var _font = ImRaii.PushFont(UiBuilder.DefaultFont);
using var _tooltip = ImRaii.Tooltip();
using var _ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
using var _ = ImRaii.PushStyle(
ImGuiStyleVar.ItemSpacing,
Vector2.Zero
);
ImGui.TextUnformatted("Min: ");
ImGui.SameLine(0, 0);
@@ -221,7 +248,8 @@ internal static class DynamicBars
{
tooltip = $"Solver Progress: {solver.ProgressValue:N0} / {solver.ProgressMax:N0}";
if (solver.ProgressValue > solver.ProgressMax)
tooltip += $"\n\nThis is taking longer than expected. Check to see if your gear stats are good and the solver settings are adequate.";
tooltip +=
$"\n\nThis is taking longer than expected. Check to see if your gear stats are good and the solver settings are adequate.";
}
ImGuiUtils.TooltipWrapped(tooltip);
}
+49 -13
View File
@@ -1,9 +1,9 @@
using Craftimizer.Plugin;
using Lumina.Excel.Sheets;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Craftimizer.Plugin;
using Lumina.Excel.Sheets;
namespace Craftimizer.Utils;
@@ -15,8 +15,20 @@ public static class FoodStatus
private static readonly ImmutableArray<uint> FoodOrder;
private static readonly ImmutableArray<uint> MedicineOrder;
public readonly record struct FoodStat(bool IsRelative, int Value, int Max, int ValueHQ, int MaxHQ);
public readonly record struct Food(Item Item, FoodStat? Craftsmanship, FoodStat? Control, FoodStat? CP);
public readonly record struct FoodStat(
bool IsRelative,
int Value,
int Max,
int ValueHQ,
int MaxHQ
);
public readonly record struct Food(
Item Item,
FoodStat? Craftsmanship,
FoodStat? Control,
FoodStat? CP
);
static FoodStatus()
{
@@ -39,18 +51,33 @@ public static class FoodStatus
if (LuminaSheets.ItemFoodSheet.GetRowOrDefault(itemAction.Data[1]) is not { } itemFood)
continue;
FoodStat? craftsmanship = null, control = null, cp = null;
FoodStat? craftsmanship = null,
control = null,
cp = null;
foreach (var stat in itemFood.Params)
{
if (stat.BaseParam.RowId == 0)
continue;
var foodStat = new FoodStat(stat.IsRelative, stat.Value, stat.Max, stat.ValueHQ, stat.MaxHQ);
var foodStat = new FoodStat(
stat.IsRelative,
stat.Value,
stat.Max,
stat.ValueHQ,
stat.MaxHQ
);
switch (stat.BaseParam.RowId)
{
case Gearsets.ParamCraftsmanship: craftsmanship = foodStat; break;
case Gearsets.ParamControl: control = foodStat; break;
case Gearsets.ParamCP: cp = foodStat; break;
default: continue;
case Gearsets.ParamCraftsmanship:
craftsmanship = foodStat;
break;
case Gearsets.ParamControl:
control = foodStat;
break;
case Gearsets.ParamCP:
cp = foodStat;
break;
default:
continue;
}
}
@@ -70,14 +97,23 @@ public static class FoodStatus
FoodItems = foods.ToFrozenDictionary();
MedicineItems = medicines.ToFrozenDictionary();
FoodOrder = [.. FoodItems.OrderByDescending(a => a.Value.Item.LevelItem.RowId).Select(a => a.Key)];
MedicineOrder = [.. MedicineItems.OrderByDescending(a => a.Value.Item.LevelItem.RowId).Select(a => a.Key)];
FoodOrder =
[
.. FoodItems.OrderByDescending(a => a.Value.Item.LevelItem.RowId).Select(a => a.Key),
];
MedicineOrder =
[
.. MedicineItems
.OrderByDescending(a => a.Value.Item.LevelItem.RowId)
.Select(a => a.Key),
];
}
public static void Initialize() { }
public static IEnumerable<Food> OrderedFoods => FoodOrder.Select(id => FoodItems[id]);
public static IEnumerable<Food> OrderedMedicines => MedicineOrder.Select(id => MedicineItems[id]);
public static IEnumerable<Food> OrderedMedicines =>
MedicineOrder.Select(id => MedicineItems[id]);
public static (uint ItemId, bool IsHQ)? ResolveFoodParam(ushort param)
{
+40 -14
View File
@@ -24,7 +24,11 @@ internal readonly struct FuzzyMatcher
{
MatchMode.FuzzyParts => FindNeedleSegments(needleString),
MatchMode.Fuzzy or MatchMode.Simple => EmptySegArray,
_ => throw new ArgumentOutOfRangeException(nameof(matchMode), matchMode, "Invalid match mode"),
_ => throw new ArgumentOutOfRangeException(
nameof(matchMode),
matchMode,
"Invalid match mode"
),
};
}
@@ -59,7 +63,9 @@ internal readonly struct FuzzyMatcher
return 0;
if (mode == MatchMode.Simple)
return value.Contains(needleString, StringComparison.InvariantCultureIgnoreCase) ? 1 : 0;
return value.Contains(needleString, StringComparison.InvariantCultureIgnoreCase)
? 1
: 0;
if (mode == MatchMode.Fuzzy)
return GetRawScore(value, 0, needleFinalPosition);
@@ -101,7 +107,11 @@ internal readonly struct FuzzyMatcher
private int GetRawScore(ReadOnlySpan<char> haystack, int needleStart, int needleEnd)
{
var (startPos, gaps, consecutive, borderMatches, endPos) = FindForward(haystack, needleStart, needleEnd);
var (startPos, gaps, consecutive, borderMatches, endPos) = FindForward(
haystack,
needleStart,
needleEnd
);
if (startPos < 0)
return 0;
@@ -109,28 +119,40 @@ internal readonly struct FuzzyMatcher
var score = CalculateRawScore(needleSize, startPos, gaps, consecutive, borderMatches);
(startPos, gaps, consecutive, borderMatches) = FindReverse(haystack, endPos, needleStart, needleEnd);
(startPos, gaps, consecutive, borderMatches) = FindReverse(
haystack,
endPos,
needleStart,
needleEnd
);
var revScore = CalculateRawScore(needleSize, startPos, gaps, consecutive, borderMatches);
return int.Max(score, revScore);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int CalculateRawScore(int needleSize, int startPos, int gaps, int consecutive, int borderMatches)
private static int CalculateRawScore(
int needleSize,
int startPos,
int gaps,
int consecutive,
int borderMatches
)
{
var score = 100
+ needleSize * 3
+ borderMatches * 3
+ consecutive * 5
- startPos
- gaps * 2;
var score =
100 + needleSize * 3 + borderMatches * 3 + consecutive * 5 - startPos - gaps * 2;
if (startPos == 0)
score += 5;
return score < 1 ? 1 : score;
}
private (int StartPos, int Gaps, int Consecutive, int BorderMatches, int HaystackIndex) FindForward(
ReadOnlySpan<char> haystack, int needleStart, int needleEnd)
private (
int StartPos,
int Gaps,
int Consecutive,
int BorderMatches,
int HaystackIndex
) FindForward(ReadOnlySpan<char> haystack, int needleStart, int needleEnd)
{
var needleIndex = needleStart;
var lastMatchIndex = -10;
@@ -176,7 +198,11 @@ internal readonly struct FuzzyMatcher
}
private (int StartPos, int Gaps, int Consecutive, int BorderMatches) FindReverse(
ReadOnlySpan<char> haystack, int haystackLastMatchIndex, int needleStart, int needleEnd)
ReadOnlySpan<char> haystack,
int haystackLastMatchIndex,
int needleStart,
int needleEnd
)
{
var needleIndex = needleEnd;
var revLastMatchIndex = haystack.Length + 10;
+59 -18
View File
@@ -1,18 +1,20 @@
using System;
using System.Linq;
using Craftimizer.Plugin;
using Craftimizer.Simulator;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using Lumina.Excel.Sheets;
using System;
using System.Linq;
using Craftimizer.Plugin;
namespace Craftimizer.Utils;
public static unsafe class Gearsets
{
public record struct GearsetStats(int CP, int Craftsmanship, int Control);
public record struct GearsetMateria(ushort Type, ushort Grade);
public record struct GearsetItem(uint ItemId, bool IsHq, GearsetMateria[] Materia);
private static readonly GearsetStats BaseStats = new(180, 0, 0);
@@ -27,7 +29,11 @@ public static unsafe class Gearsets
for (var i = 0; i < container->Size; ++i)
{
var item = container->Items[i];
items[i] = new(item.ItemId, item.Flags.HasFlag(InventoryItem.ItemFlags.HighQuality), GetMaterias(item.Materia, item.MateriaGrades));
items[i] = new(
item.ItemId,
item.Flags.HasFlag(InventoryItem.ItemFlags.HighQuality),
GetMaterias(item.Materia, item.MateriaGrades)
);
}
return items;
}
@@ -39,7 +45,11 @@ public static unsafe class Gearsets
for (var i = 0; i < 14; ++i)
{
var item = gearsetItems[i];
items[i] = new(item.ItemId % 1000000, item.ItemId > 1000000, GetMaterias(item.Materia, item.MateriaGrades));
items[i] = new(
item.ItemId % 1000000,
item.ItemId > 1000000,
GetMaterias(item.Materia, item.MateriaGrades)
);
}
return items;
}
@@ -48,7 +58,9 @@ public static unsafe class Gearsets
{
var item = LuminaSheets.ItemSheet.GetRow(gearsetItem.ItemId)!;
int cp = 0, craftsmanship = 0, control = 0;
int cp = 0,
craftsmanship = 0,
control = 0;
void IncreaseStat(uint baseParam, int amount)
{
@@ -83,7 +95,12 @@ public static unsafe class Gearsets
}
public static GearsetStats CalculateGearsetStats(GearsetItem[] gearsetItems) =>
gearsetItems.Select(CalculateGearsetItemStats).Aggregate(BaseStats, (a, b) => new(a.CP + b.CP, a.Craftsmanship + b.Craftsmanship, a.Control + b.Control));
gearsetItems
.Select(CalculateGearsetItemStats)
.Aggregate(
BaseStats,
(a, b) => new(a.CP + b.CP, a.Craftsmanship + b.Craftsmanship, a.Control + b.Control)
);
public static GearsetStats CalculateGearsetCurrentStats()
{
@@ -97,10 +114,24 @@ public static unsafe class Gearsets
};
}
public static CharacterStats CalculateCharacterStats(GearsetItem[] gearsetItems, int characterLevel, bool canUseManipulation) =>
CalculateCharacterStats(CalculateGearsetStats(gearsetItems), gearsetItems, characterLevel, canUseManipulation);
public static CharacterStats CalculateCharacterStats(
GearsetItem[] gearsetItems,
int characterLevel,
bool canUseManipulation
) =>
CalculateCharacterStats(
CalculateGearsetStats(gearsetItems),
gearsetItems,
characterLevel,
canUseManipulation
);
public static CharacterStats CalculateCharacterStats(GearsetStats gearsetStats, GearsetItem[] gearsetItems, int characterLevel, bool canUseManipulation) =>
public static CharacterStats CalculateCharacterStats(
GearsetStats gearsetStats,
GearsetItem[] gearsetItems,
int characterLevel,
bool canUseManipulation
) =>
new()
{
CP = gearsetStats.CP,
@@ -115,8 +146,7 @@ public static unsafe class Gearsets
public static bool HasDelineations() =>
InventoryManager.Instance()->GetInventoryItemCount(28724) > 0;
public static bool IsItem(GearsetItem item, uint itemId) =>
item.ItemId == itemId;
public static bool IsItem(GearsetItem item, uint itemId) => item.ItemId == itemId;
public static bool IsSpecialistSoulCrystal(GearsetItem item)
{
@@ -125,11 +155,18 @@ public static unsafe class Gearsets
var luminaItem = LuminaSheets.ItemSheet.GetRow(item.ItemId)!;
// Soul Crystal ItemUICategory DoH Category
return luminaItem.ItemUICategory.RowId == 62 && luminaItem.ClassJobUse.Value.ClassJobCategory.RowId == 33;
return luminaItem.ItemUICategory.RowId == 62
&& luminaItem.ClassJobUse.Value.ClassJobCategory.RowId == 33;
}
public static bool IsSplendorousTool(GearsetItem item) =>
LuminaSheets.ItemSheetEnglish.GetRow(item.ItemId).Description.ToString().Contains("Increases to quality are 1.75 times higher than normal when material condition is Good.", StringComparison.Ordinal);
LuminaSheets
.ItemSheetEnglish.GetRow(item.ItemId)
.Description.ToString()
.Contains(
"Increases to quality are 1.75 times higher than normal when material condition is Good.",
StringComparison.Ordinal
);
// 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, uint paramId)
@@ -142,7 +179,7 @@ public static unsafe class Gearsets
ParamCP => ilvl.CP,
ParamCraftsmanship => ilvl.Craftsmanship,
ParamControl => ilvl.Control,
_ => 0
_ => 0,
};
// https://github.com/ffxiv-teamcraft/ffxiv-teamcraft/blob/24d0db2d9676f264edf53651b21005305267c84c/apps/data-extraction/src/extractors/items.extractor.ts#L6
var slotMod = item.EquipSlotCategory.RowId switch
@@ -168,16 +205,20 @@ public static unsafe class Gearsets
19 => param.HeadChestHandsLegsFeetPercent,
20 => param.ChestLegsGlovesPercent,
21 => param.ChestLegsFeetPercent,
_ => 0
_ => 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);
var cap = (int)
Math.Round((float)baseValue * slotMod / (roleMod * 10f), MidpointRounding.AwayFromZero);
return cap == 0 ? int.MaxValue : cap;
}
private static GearsetMateria[] GetMaterias(ReadOnlySpan<ushort> types, ReadOnlySpan<byte> grades)
private static GearsetMateria[] GetMaterias(
ReadOnlySpan<ushort> types,
ReadOnlySpan<byte> grades
)
{
var materia = new GearsetMateria[5];
for (var i = 0; i < 5; ++i)
+63 -11
View File
@@ -1,7 +1,7 @@
using System;
using Craftimizer.Plugin;
using Dalamud.Hooking;
using FFXIVClientStructs.FFXIV.Client.Game;
using System;
using ActionType = Craftimizer.Simulator.Actions.ActionType;
using ActionUtils = Craftimizer.Plugin.ActionUtils;
using CSActionType = FFXIVClientStructs.FFXIV.Client.Game.ActionType;
@@ -14,33 +14,77 @@ public sealed unsafe class Hooks : IDisposable
public event OnActionUsedDelegate? OnActionUsed;
public delegate bool UseActionDelegate(ActionManager* manager, CSActionType actionType, uint actionId, ulong targetId, uint extraParam, ActionManager.UseActionMode mode, uint comboRouteId, bool* outOptAreaTargeted);
public delegate bool UseActionDelegate(
ActionManager* manager,
CSActionType actionType,
uint actionId,
ulong targetId,
uint extraParam,
ActionManager.UseActionMode mode,
uint comboRouteId,
bool* outOptAreaTargeted
);
public readonly Hook<UseActionDelegate> UseActionHook = null!;
public delegate byte IsActionHighlightedDelegate(ActionManager* manager, CSActionType actionType, uint actionId);
public delegate byte IsActionHighlightedDelegate(
ActionManager* manager,
CSActionType actionType,
uint actionId
);
public readonly Hook<IsActionHighlightedDelegate> IsActionHighlightedHook = null!;
public Hooks()
{
UseActionHook = Service.GameInteropProvider.HookFromAddress<UseActionDelegate>((nint)ActionManager.MemberFunctionPointers.UseAction, UseActionDetour);
IsActionHighlightedHook = Service.GameInteropProvider.HookFromAddress<IsActionHighlightedDelegate>((nint)ActionManager.MemberFunctionPointers.IsActionHighlighted, IsActionHighlightedDetour);
UseActionHook = Service.GameInteropProvider.HookFromAddress<UseActionDelegate>(
(nint)ActionManager.MemberFunctionPointers.UseAction,
UseActionDetour
);
IsActionHighlightedHook =
Service.GameInteropProvider.HookFromAddress<IsActionHighlightedDelegate>(
(nint)ActionManager.MemberFunctionPointers.IsActionHighlighted,
IsActionHighlightedDetour
);
UseActionHook.Enable();
IsActionHighlightedHook.Enable();
}
private bool UseActionDetour(ActionManager* manager, CSActionType actionType, uint actionId, ulong targetId, uint extraParam, ActionManager.UseActionMode mode, uint comboRouteId, bool* optOutAreaTargeted)
private bool UseActionDetour(
ActionManager* manager,
CSActionType actionType,
uint actionId,
ulong targetId,
uint extraParam,
ActionManager.UseActionMode mode,
uint comboRouteId,
bool* optOutAreaTargeted
)
{
var canCast = manager->GetActionStatus(actionType, actionId) == 0;
var ret = UseActionHook.Original(manager, actionType, actionId, targetId, extraParam, mode, comboRouteId, optOutAreaTargeted);
var ret = UseActionHook.Original(
manager,
actionType,
actionId,
targetId,
extraParam,
mode,
comboRouteId,
optOutAreaTargeted
);
if (canCast && ret && actionType is CSActionType.CraftAction or CSActionType.Action)
{
var classJob = ClassJobUtils.GetClassJobFromIdx((byte)(Service.Objects.LocalPlayer?.ClassJob.RowId ?? 0));
var classJob = ClassJobUtils.GetClassJobFromIdx(
(byte)(Service.Objects.LocalPlayer?.ClassJob.RowId ?? 0)
);
if (classJob != null)
{
var simActionType = ActionUtils.GetActionTypeFromId(actionId, classJob.Value, actionType == CSActionType.CraftAction);
var simActionType = ActionUtils.GetActionTypeFromId(
actionId,
classJob.Value,
actionType == CSActionType.CraftAction
);
if (simActionType != null)
{
try
@@ -57,7 +101,11 @@ public sealed unsafe class Hooks : IDisposable
return ret;
}
private byte IsActionHighlightedDetour(ActionManager* manager, CSActionType actionType, uint actionId)
private byte IsActionHighlightedDetour(
ActionManager* manager,
CSActionType actionType,
uint actionId
)
{
var ret = IsActionHighlightedHook.Original(manager, actionType, actionId);
@@ -83,7 +131,11 @@ public sealed unsafe class Hooks : IDisposable
if (classJob == null)
return ret;
var simActionType = ActionUtils.GetActionTypeFromId(actionId, classJob.Value, actionType == CSActionType.CraftAction);
var simActionType = ActionUtils.GetActionTypeFromId(
actionId,
classJob.Value,
actionType == CSActionType.CraftAction
);
if (simActionType == null)
return ret;
+11 -7
View File
@@ -1,14 +1,14 @@
using Craftimizer.Plugin;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Utility;
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Craftimizer.Plugin;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Utility;
namespace Craftimizer.Utils;
@@ -52,7 +52,8 @@ public sealed class IconManager : IDisposable
return null;
}
public IDalamudTextureWrap GetWrapOrEmpty() => GetWrap() ?? Service.DalamudAssetManager.Empty4X4;
public IDalamudTextureWrap GetWrapOrEmpty() =>
GetWrap() ?? Service.DalamudAssetManager.Empty4X4;
public void Dispose()
{
@@ -85,7 +86,10 @@ public sealed class IconManager : IDisposable
Service.TextureProvider.GetFromGameIcon(new GameIconLookup(id, itemHq: isHq));
private static ISharedImmediateTexture GetAssemblyTextureInternal(string filename) =>
Service.TextureProvider.GetFromManifestResource(Assembly.GetExecutingAssembly(), $"Craftimizer.{filename}");
Service.TextureProvider.GetFromManifestResource(
Assembly.GetExecutingAssembly(),
$"Craftimizer.{filename}"
);
public static ILoadedTextureIcon GetIcon(uint id, bool isHq = false) =>
new LoadedIcon(GetIconInternal(id, isHq));
+19 -7
View File
@@ -1,10 +1,10 @@
using Craftimizer.Plugin;
using Dalamud.Plugin;
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using DotNext.Reflection;
using Craftimizer.Plugin;
using Dalamud.Plugin;
using DotNext.Collections.Generic;
using DotNext.Reflection;
namespace Craftimizer.Utils;
@@ -39,16 +39,27 @@ public sealed class Ipc
var returnsVoid = typeMethod.ReturnType == typeof(void);
var propSubscriber = typeof(IDalamudPluginInterface).GetMethod("GetIpcSubscriber", typeMethod.GetParameters().Length + 1, [typeof(string)]);
var propSubscriber = typeof(IDalamudPluginInterface).GetMethod(
"GetIpcSubscriber",
typeMethod.GetParameters().Length + 1,
[typeof(string)]
);
if (propSubscriber is null)
throw new InvalidOperationException("GetIpcSubscriber method not found");
var callGateSubscriber = propSubscriber.MakeGenericMethod([.. typeMethod.GetParameterTypes(), returnsVoid ? typeof(int) : typeMethod.ReturnType]).Invoke(Service.PluginInterface, [attr.Name ?? prop.Name]);
var callGateSubscriber = propSubscriber
.MakeGenericMethod([
.. typeMethod.GetParameterTypes(),
returnsVoid ? typeof(int) : typeMethod.ReturnType,
])
.Invoke(Service.PluginInterface, [attr.Name ?? prop.Name]);
if (callGateSubscriber is null)
throw new InvalidOperationException("CallGateSubscriber is null");
var invokeFunc = callGateSubscriber.GetType().GetMethod(returnsVoid ? "InvokeAction" : "InvokeFunc");
var invokeFunc = callGateSubscriber
.GetType()
.GetMethod(returnsVoid ? "InvokeAction" : "InvokeFunc");
if (invokeFunc is null)
throw new InvalidOperationException("Subscriber Invoke method not found");
@@ -62,7 +73,8 @@ public sealed class Ipc
public Func<bool> MacroMateIsAvailable { get; private set; } = null!;
[IPCCall("MacroMate.CreateOrUpdateMacro")]
public Func<string, string, string?, uint?, bool> MacroMateCreateMacro { get; private set; } = null!;
public Func<string, string, string?, uint?, bool> MacroMateCreateMacro { get; private set; } =
null!;
[IPCCall("MacroMate.ValidateGroupPath")]
public Func<string, (bool, string?)> MacroMateValidateGroupPath { get; private set; } = null!;
+2 -1
View File
@@ -1,5 +1,5 @@
using Craftimizer.Plugin;
using System;
using Craftimizer.Plugin;
namespace Craftimizer.Utils;
@@ -8,5 +8,6 @@ public static class Log
public static void Debug(string line) => Service.PluginLog.Debug(line);
public static void Error(string line) => Service.PluginLog.Error(line);
public static void Error(Exception e, string line) => Service.PluginLog.Error(e, line);
}
+71 -34
View File
@@ -1,13 +1,13 @@
using System;
using System.Collections.Generic;
using Craftimizer.Plugin;
using Craftimizer.Simulator;
using Craftimizer.Simulator.Actions;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.ImGuiNotification;
using FFXIVClientStructs.FFXIV.Client.System.Memory;
using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using Dalamud.Bindings.ImGui;
using System;
using System.Collections.Generic;
namespace Craftimizer.Utils;
@@ -20,13 +20,15 @@ public static class MacroCopy
{
if (actions.Count == 0)
{
Plugin.Plugin.DisplayNotification(new()
Plugin.Plugin.DisplayNotification(
new()
{
Content = "Cannot copy an empty macro.",
MinimizedText = "Cannot copy empty macro",
Title = "Macro Not Copied",
Type = NotificationType.Error
});
Type = NotificationType.Error,
}
);
return;
}
@@ -49,9 +51,14 @@ public static class MacroCopy
}
}
private static List<string> GetMacros(IReadOnlyList<ActionType> actions, MacroCopyConfiguration config)
private static List<string> GetMacros(
IReadOnlyList<ActionType> actions,
MacroCopyConfiguration config
)
{
var mustSplit = (config.Type == MacroCopyConfiguration.CopyType.CopyToMacro || !config.CombineMacro) && config.Type != MacroCopyConfiguration.CopyType.CopyToMacroMate;
var mustSplit =
(config.Type == MacroCopyConfiguration.CopyType.CopyToMacro || !config.CombineMacro)
&& config.Type != MacroCopyConfiguration.CopyType.CopyToMacroMate;
var macros = new List<string>();
@@ -143,41 +150,54 @@ public static class MacroCopy
{
var config = Service.Configuration.MacroCopy;
int i, macroIdx;
int i,
macroIdx;
for (
i = 0, macroIdx = config.StartMacroIdx;
i < macros.Count && i < config.MaxMacroCount && macroIdx < 100;
i++, macroIdx += config.CopyDown ? 10 : 1)
i++, macroIdx += config.CopyDown ? 10 : 1
)
SetMacro(macroIdx, config.SharedMacro, macros[i], i + 1);
if (config.ShowCopiedMessage)
{
Plugin.Plugin.DisplayNotification(new()
Plugin.Plugin.DisplayNotification(
new()
{
Content = i > 1 ? "Copied macro to User Macros." : $"Copied {i} macros to User Macros.",
Content =
i > 1
? "Copied macro to User Macros."
: $"Copied {i} macros to User Macros.",
MinimizedText = i > 1 ? "Copied macro" : $"Copied {i} macros",
Title = "Macro Copied",
Type = NotificationType.Success
});
Type = NotificationType.Success,
}
);
}
if (i < macros.Count)
{
Service.Plugin.OpenMacroClipboard(macros);
var rest = macros.Count - i;
Plugin.Plugin.DisplayNotification(new()
Plugin.Plugin.DisplayNotification(
new()
{
Content = $"Couldn't copy {rest} macro{(rest == 1 ? "" : "s")}, so a window was opened with all of them.",
Content =
$"Couldn't copy {rest} macro{(rest == 1 ? "" : "s")}, so a window was opened with all of them.",
Minimized = false,
Title = "Macro Copied",
Type = NotificationType.Warning
});
Type = NotificationType.Warning,
}
);
}
}
private static unsafe void SetMacro(int idx, bool isShared, string macroText, int macroIdx)
{
if (idx >= 100 || idx < 0)
throw new ArgumentOutOfRangeException(nameof(idx), "Macro index must be between 0 and 99");
throw new ArgumentOutOfRangeException(
nameof(idx),
"Macro index must be between 0 and 99"
);
var set = isShared ? 1u : 0u;
@@ -201,13 +221,19 @@ public static class MacroCopy
ImGui.SetClipboardText(string.Join(Environment.NewLine + Environment.NewLine, macros));
if (Service.Configuration.MacroCopy.ShowCopiedMessage)
{
Plugin.Plugin.DisplayNotification(new()
Plugin.Plugin.DisplayNotification(
new()
{
Content = macros.Count == 1 ? "Copied macro to clipboard." : $"Copied {macros.Count} macros to clipboard.",
MinimizedText = macros.Count == 1 ? "Copied macro" : $"Copied {macros.Count} macros",
Content =
macros.Count == 1
? "Copied macro to clipboard."
: $"Copied {macros.Count} macros to clipboard.",
MinimizedText =
macros.Count == 1 ? "Copied macro" : $"Copied {macros.Count} macros",
Title = "Macro Copied",
Type = NotificationType.Success
});
Type = NotificationType.Success,
}
);
}
}
@@ -215,13 +241,15 @@ public static class MacroCopy
{
if (!Service.Ipc.MacroMateIsAvailable())
{
Plugin.Plugin.DisplayNotification(new()
Plugin.Plugin.DisplayNotification(
new()
{
Content = "Please check if it installed and enabled.",
MinimizedText = "Macro Mate is unavailable",
Title = "Macro Mate Unavailable",
Type = NotificationType.Error
});
Type = NotificationType.Error,
}
);
return;
}
@@ -232,27 +260,36 @@ public static class MacroCopy
var (isValidParent, parentError) = Service.Ipc.MacroMateValidateGroupPath(parentPath);
if (!isValidParent)
{
Plugin.Plugin.DisplayNotification(new()
Plugin.Plugin.DisplayNotification(
new()
{
Content = parentError!,
MinimizedText = parentError,
Title = "Macro Mate Invalid Parent",
Type = NotificationType.Error
});
Type = NotificationType.Error,
}
);
return;
}
Service.Ipc.MacroMateCreateMacro(Service.Configuration.MacroCopy.MacroMateName, macro, parentPath, null);
Service.Ipc.MacroMateCreateMacro(
Service.Configuration.MacroCopy.MacroMateName,
macro,
parentPath,
null
);
if (Service.Configuration.MacroCopy.ShowCopiedMessage)
{
Plugin.Plugin.DisplayNotification(new()
Plugin.Plugin.DisplayNotification(
new()
{
Content = "Copied macro to Macro Mate.",
MinimizedText = "Copied macro",
Title = "Macro Copied",
Type = NotificationType.Success
});
Type = NotificationType.Success,
}
);
}
}
}
+46 -21
View File
@@ -1,7 +1,3 @@
using Craftimizer.Plugin;
using Craftimizer.Simulator;
using Craftimizer.Simulator.Actions;
using Dalamud.Networking.Http;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -12,6 +8,10 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Craftimizer.Plugin;
using Craftimizer.Simulator;
using Craftimizer.Simulator.Actions;
using Dalamud.Networking.Http;
using static Craftimizer.Utils.CommunityMacros;
namespace Craftimizer.Utils;
@@ -69,7 +69,10 @@ public static class MacroImport
if (!Uri.TryCreate(url, UriKind.Absolute, out uri!))
return false;
if (!string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) && !string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
if (
!string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)
&& !string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)
)
return false;
if (!uri.IsDefaultPort)
@@ -87,34 +90,46 @@ public static class MacroImport
{
"ffxivteamcraft.com" => RetrieveTeamcraftUrl(uri, token),
"craftingway.app" => RetrieveCraftingwayUrl(uri, token),
_ => throw new UnreachableException("TryParseUrl should handle miscellaneous edge cases"),
_ => throw new UnreachableException(
"TryParseUrl should handle miscellaneous edge cases"
),
};
}
private static async Task<CommunityMacro> RetrieveTeamcraftUrl(Uri uri, CancellationToken token)
{
using var heCallback = new HappyEyeballsCallback();
using var client = new HttpClient(new SocketsHttpHandler
using var client = new HttpClient(
new SocketsHttpHandler
{
AutomaticDecompression = DecompressionMethods.All,
ConnectCallback = heCallback.ConnectCallback,
});
}
);
var path = uri.GetComponents(UriComponents.Path, UriFormat.SafeUnescaped);
if (!path.StartsWith("simulator/", StringComparison.Ordinal))
throw new ArgumentException("Teamcraft macro url should start with /simulator", nameof(uri));
throw new ArgumentException(
"Teamcraft macro url should start with /simulator",
nameof(uri)
);
path = path[10..];
var lastSlash = path.LastIndexOf('/');
if (lastSlash == -1)
throw new ArgumentException("Teamcraft macro url is not in the right format", nameof(uri));
throw new ArgumentException(
"Teamcraft macro url is not in the right format",
nameof(uri)
);
var id = path[(lastSlash + 1)..];
var resp = await client.GetFromJsonAsync<TeamcraftMacro>(
var resp = await client
.GetFromJsonAsync<TeamcraftMacro>(
$"https://firestore.googleapis.com/v1beta1/projects/ffxivteamcraft/databases/(default)/documents/rotations/{id}",
token).
ConfigureAwait(false);
token
)
.ConfigureAwait(false);
if (resp is null)
throw new Exception("Internal error; failed to retrieve macro");
if (resp.Error is { } error)
@@ -122,31 +137,41 @@ public static class MacroImport
return new(resp);
}
private static async Task<CommunityMacro> RetrieveCraftingwayUrl(Uri uri, CancellationToken token)
private static async Task<CommunityMacro> RetrieveCraftingwayUrl(
Uri uri,
CancellationToken token
)
{
using var heCallback = new HappyEyeballsCallback();
using var client = new HttpClient(new SocketsHttpHandler
using var client = new HttpClient(
new SocketsHttpHandler
{
AutomaticDecompression = DecompressionMethods.All,
ConnectCallback = heCallback.ConnectCallback,
});
}
);
// https://craftingway.app/rotation/variable-blueprint-KmrvS
var path = uri.GetComponents(UriComponents.Path, UriFormat.SafeUnescaped);
if (!path.StartsWith("rotation/", StringComparison.Ordinal))
throw new ArgumentException("Craftingway macro url should start with /rotation", nameof(uri));
throw new ArgumentException(
"Craftingway macro url should start with /rotation",
nameof(uri)
);
path = path[9..];
var lastSlash = path.LastIndexOf('/');
if (lastSlash != -1)
throw new ArgumentException("Craftingway macro url is not in the right format", nameof(uri));
throw new ArgumentException(
"Craftingway macro url is not in the right format",
nameof(uri)
);
var id = path;
var resp = await client.GetFromJsonAsync<CraftingwayMacro>(
$"https://servingway.fly.dev/rotation/{id}",
token)
var resp = await client
.GetFromJsonAsync<CraftingwayMacro>($"https://servingway.fly.dev/rotation/{id}", token)
.ConfigureAwait(false);
if (resp is null)
throw new Exception("Internal error; failed to retrieve macro");
+56 -18
View File
@@ -1,9 +1,9 @@
using Craftimizer.Plugin;
using Craftimizer.Simulator;
using Lumina.Excel.Sheets;
using System;
using System.Collections.Generic;
using System.Linq;
using Craftimizer.Plugin;
using Craftimizer.Simulator;
using Lumina.Excel.Sheets;
using ClassJob = Craftimizer.Simulator.ClassJob;
namespace Craftimizer.Utils;
@@ -28,16 +28,22 @@ public sealed record RecipeData
{
RecipeId = recipeId;
Recipe = LuminaSheets.RecipeSheet.GetRowOrDefault(recipeId) ??
throw new ArgumentException($"Invalid recipe id {recipeId}", nameof(recipeId));
Recipe =
LuminaSheets.RecipeSheet.GetRowOrDefault(recipeId)
?? throw new ArgumentException($"Invalid recipe id {recipeId}", nameof(recipeId));
ClassJob = (ClassJob)Recipe.CraftType.RowId;
var resolvedLevelTableRow = Recipe.RecipeLevelTable.RowId;
if (Recipe.MaxAdjustableJobLevel.RowId != 0)
{
AdjustedJobLevel = Math.Min(explicitlyAdjustedJobLevel ?? ClassJob.GetWKSSyncedLevel(), (ushort)Recipe.MaxAdjustableJobLevel.RowId);
resolvedLevelTableRow = LuminaSheets.GathererCrafterLvAdjustTableSheet.GetRow(AdjustedJobLevel.Value).RecipeLevel.RowId;
AdjustedJobLevel = Math.Min(
explicitlyAdjustedJobLevel ?? ClassJob.GetWKSSyncedLevel(),
(ushort)Recipe.MaxAdjustableJobLevel.RowId
);
resolvedLevelTableRow = LuminaSheets
.GathererCrafterLvAdjustTableSheet.GetRow(AdjustedJobLevel.Value)
.RecipeLevel.RowId;
}
Table = LuminaSheets.RecipeLevelTableSheet.GetRow(resolvedLevelTableRow);
@@ -46,8 +52,14 @@ public sealed record RecipeData
IsExpert = Recipe.IsExpert,
ClassJobLevel = Table.ClassJobLevel,
ConditionsFlag = Table.ConditionsFlag,
MaxDurability = (Recipe.MaxAdjustableJobLevel.RowId != 0 ? 80 : Table.Durability) * Recipe.DurabilityFactor / 100,
MaxQuality = (Recipe.CanHq || Recipe.RequiredQuality > 0) ? (int)Table.Quality * Recipe.QualityFactor / 100 : 0,
MaxDurability =
(Recipe.MaxAdjustableJobLevel.RowId != 0 ? 80 : Table.Durability)
* Recipe.DurabilityFactor
/ 100,
MaxQuality =
(Recipe.CanHq || Recipe.RequiredQuality > 0)
? (int)Table.Quality * Recipe.QualityFactor / 100
: 0,
MaxProgress = Table.Difficulty * Recipe.DifficultyFactor / 100,
QualityModifier = Table.QualityModifier,
QualityDivider = Table.QualityDivider,
@@ -64,23 +76,37 @@ public sealed record RecipeData
{
if (entry.ItemTradeIn.RowId == Recipe.ItemResult.RowId)
{
thresholds = [entry.BaseCollectableRating, entry.MidCollectableRating, entry.HighCollectableRating];
thresholds =
[
entry.BaseCollectableRating,
entry.MidCollectableRating,
entry.HighCollectableRating,
];
break;
}
}
}
else if (Recipe.CollectableMetadata.GetValueOrDefaultSubrow<SatisfactionSupply>() is { } row3)
else if (
Recipe.CollectableMetadata.GetValueOrDefaultSubrow<SatisfactionSupply>() is { } row3
)
{
foreach (var subrow in row3)
{
if (subrow.Item.RowId == Recipe.ItemResult.RowId)
{
thresholds = [subrow.CollectabilityLow, subrow.CollectabilityMid, subrow.CollectabilityHigh];
thresholds =
[
subrow.CollectabilityLow,
subrow.CollectabilityMid,
subrow.CollectabilityHigh,
];
break;
}
}
}
else if (Recipe.CollectableMetadata.GetValueOrDefault<SharlayanCraftWorksSupply>() is { } row5)
else if (
Recipe.CollectableMetadata.GetValueOrDefault<SharlayanCraftWorksSupply>() is { } row5
)
{
foreach (var item in row5.Item)
{
@@ -93,10 +119,19 @@ public sealed record RecipeData
}
else if (Recipe.CollectableMetadata.GetValueOrDefault<CollectablesRefine>() is { } row6)
thresholds = [row6.CollectabilityLow, row6.CollectabilityMid, row6.CollectabilityHigh];
else if (Recipe.CollectableMetadataKey == 7 && LuminaSheets.WKSMissionToDoEvalutionRefinSheet.TryGetRow(Recipe.CollectableMetadata.RowId, out var row7))
else if (
Recipe.CollectableMetadataKey == 7
&& LuminaSheets.WKSMissionToDoEvalutionRefinSheet.TryGetRow(
Recipe.CollectableMetadata.RowId,
out var row7
)
)
{
thresholds = [row7.Unknown0, row7.Unknown1, row7.Unknown2];
thresholds = [.. thresholds.Select(percentage => RecipeInfo.MaxQuality * percentage / 1000)];
thresholds =
[
.. thresholds.Select(percentage => RecipeInfo.MaxQuality * percentage / 1000),
];
}
if (thresholds != null)
@@ -106,14 +141,17 @@ public sealed record RecipeData
CollectableThresholds = t.ToArray();
}
Ingredients = Recipe.Ingredient.Zip(Recipe.AmountIngredient)
Ingredients = Recipe
.Ingredient.Zip(Recipe.AmountIngredient)
.Take(6)
.Where(i => i.First.IsValid)
.Select(i => (i.First.Value, (int)i.Second))
.ToList();
MaxStartingQuality = (int)Math.Floor(Recipe.MaterialQualityFactor * RecipeInfo.MaxQuality / 100f);
MaxStartingQuality = (int)
Math.Floor(Recipe.MaterialQualityFactor * RecipeInfo.MaxQuality / 100f);
TotalHqILvls = (int)Ingredients.Where(i => i.Item.CanBeHq).Sum(i => i.Item.LevelItem.RowId * i.Amount);
TotalHqILvls = (int)
Ingredients.Where(i => i.Item.CanBeHq).Sum(i => i.Item.LevelItem.RowId * i.Amount);
}
public int CalculateItemStartingQuality(int itemIdx, int amount)
+42 -23
View File
@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Craftimizer.Plugin;
using Craftimizer.Simulator;
using Craftimizer.Simulator.Actions;
using DotNext.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Sim = Craftimizer.Simulator.Simulator;
using SimNoRandom = Craftimizer.Simulator.SimulatorNoRandom;
@@ -51,11 +51,11 @@ internal sealed class SimulatedMacro
Average = (float)DataList.Average();
}
public ImGuiUtils.ViolinData? GetViolinData(float barMax, int resolution, double bandwidth) =>
ViolinData ??=
Min != Max ?
new(DataList, 0, barMax, resolution, bandwidth) :
null;
public ImGuiUtils.ViolinData? GetViolinData(
float barMax,
int resolution,
double bandwidth
) => ViolinData ??= Min != Max ? new(DataList, 0, barMax, resolution, bandwidth) : null;
}
public readonly Param Progress = new();
@@ -64,7 +64,12 @@ internal sealed class SimulatedMacro
// Param is either collectability, quality, or hq%, depending on the recipe
public readonly Param ParamScore = new();
public Reliablity(in SimulationState startState, IEnumerable<ActionType> actions, int iterCount, RecipeData recipeData)
public Reliablity(
in SimulationState startState,
IEnumerable<ActionType> actions,
int iterCount,
RecipeData recipeData
)
{
Func<SimulationState, int> getParam;
if (recipeData.IsCollectable)
@@ -97,12 +102,18 @@ internal sealed class SimulatedMacro
{
public ActionType Action { get; }
public bool IsEphemeral { get; }
// State *after* executing the action
public ActionResponse Response { get; private set; }
public SimulationState State { get; private set; }
private Reliablity? Reliability { get; set; }
public Step(ActionType action, Sim sim, in SimulationState lastState, out SimulationState newState)
public Step(
ActionType action,
Sim sim,
in SimulationState lastState,
out SimulationState newState
)
{
Action = action;
newState = Recalculate(sim, lastState);
@@ -122,9 +133,17 @@ internal sealed class SimulatedMacro
return State;
}
public Reliablity GetReliability(in SimulationState initialState, IEnumerable<ActionType> actionSet, RecipeData recipeData) =>
Reliability ??=
new(initialState, actionSet, Service.Configuration.ReliabilitySimulationCount, recipeData);
public Reliablity GetReliability(
in SimulationState initialState,
IEnumerable<ActionType> actionSet,
RecipeData recipeData
) =>
Reliability ??= new(
initialState,
actionSet,
Service.Configuration.ReliabilitySimulationCount,
recipeData
);
};
private List<Step> Macro { get; set; } = [];
@@ -162,9 +181,9 @@ internal sealed class SimulatedMacro
}
public Reliablity GetReliability(RecipeData recipeData, Index? idx = null) =>
Macro.Count > 0 ?
Macro[idx ?? ^1].GetReliability(InitialState, Actions.ToArray(), recipeData) :
new(InitialState, Array.Empty<ActionType>(), 0, recipeData);
Macro.Count > 0
? Macro[idx ?? ^1].GetReliability(InitialState, Actions.ToArray(), recipeData)
: new(InitialState, Array.Empty<ActionType>(), 0, recipeData);
private void TryRecalculateFrom(int index)
{
@@ -177,14 +196,11 @@ internal sealed class SimulatedMacro
state = Macro[i].Recalculate(sim, state);
}
public void RecalculateState() =>
TryRecalculateFrom(0);
public void RecalculateState() => TryRecalculateFrom(0);
public void RemoveRange(int index, int count) =>
Macro.RemoveRange(index, count);
public void RemoveRange(int index, int count) => Macro.RemoveRange(index, count);
public void Clear() =>
Macro.Clear();
public void Clear() => Macro.Clear();
public void Add(ActionType action)
{
@@ -247,7 +263,10 @@ internal sealed class SimulatedMacro
QueuedEphemeralSteps.Clear();
foreach (var action in actions)
{
if (maxSize is { } size && QueuedSteps.Count + QueuedEphemeralSteps.Count + Macro.Count >= size)
if (
maxSize is { } size
&& QueuedSteps.Count + QueuedEphemeralSteps.Count + Macro.Count >= size
)
return size;
QueuedEphemeralSteps.Add(new(action, true));
+9 -5
View File
@@ -1,8 +1,8 @@
using Dalamud.Game.Text;
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Numerics;
using Dalamud.Game.Text;
namespace Craftimizer.Utils;
@@ -10,7 +10,10 @@ public static class SqText
{
public static SeIconChar LevelPrefix => SeIconChar.LevelEn;
public static readonly FrozenDictionary<char, SeIconChar> LevelNumReplacements = new Dictionary<char, SeIconChar>
public static readonly FrozenDictionary<char, SeIconChar> LevelNumReplacements = new Dictionary<
char,
SeIconChar
>
{
['0'] = SeIconChar.Number0,
['1'] = SeIconChar.Number1,
@@ -24,17 +27,18 @@ public static class SqText
['9'] = SeIconChar.Number9,
}.ToFrozenDictionary();
public static string ToLevelString<T>(T value) where T : IBinaryInteger<T>
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)
foreach (var (k, v) in LevelNumReplacements)
str = str.Replace(k, v.ToIconChar());
return str;
}
public static bool TryParseLevelString(string str, out int result)
{
foreach(var (k, v) in LevelNumReplacements)
foreach (var (k, v) in LevelNumReplacements)
str = str.Replace(v.ToIconChar(), k);
return int.TryParse(str, out result);
}
+9 -12
View File
@@ -1,9 +1,9 @@
using System;
using Craftimizer.Simulator;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Memory;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
using System;
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.AtkValueType;
namespace Craftimizer.Utils;
@@ -12,7 +12,8 @@ internal sealed unsafe class SynthesisValues(AddonSynthesis* addon)
{
private AddonSynthesis* Addon { get; } = addon;
private ReadOnlySpan<AtkValue> Values => new(Addon->AtkUnitBase.AtkValues, Addon->AtkUnitBase.AtkValuesCount);
private ReadOnlySpan<AtkValue> Values =>
new(Addon->AtkUnitBase.AtkValues, Addon->AtkUnitBase.AtkValuesCount);
// Always 0?
private uint IsInitializing => GetUInt(0);
@@ -51,9 +52,7 @@ internal sealed unsafe class SynthesisValues(AddonSynthesis* addon)
if (Addon == null)
return null;
var value = Values[i];
return value.Type == ValueType.UInt ?
value.UInt :
null;
return value.Type == ValueType.UInt ? value.UInt : null;
}
private bool? TryGetBool(int i)
@@ -61,9 +60,7 @@ internal sealed unsafe class SynthesisValues(AddonSynthesis* addon)
if (Addon == null)
return null;
var value = Values[i];
return value.Type == ValueType.Bool ?
value.Byte != 0 :
null;
return value.Type == ValueType.Bool ? value.Byte != 0 : null;
}
private SeString? TryGetString(int i)
@@ -73,10 +70,10 @@ internal sealed unsafe class SynthesisValues(AddonSynthesis* addon)
var value = Values[i];
return value.Type switch
{
ValueType.ManagedString or
ValueType.String =>
MemoryHelper.ReadSeStringNullTerminated((nint)value.String.Value),
_ => null
ValueType.ManagedString or ValueType.String => MemoryHelper.ReadSeStringNullTerminated(
(nint)value.String.Value
),
_ => null,
};
}
+45 -17
View File
@@ -1,13 +1,13 @@
using Craftimizer.Plugin;
using Dalamud.Interface;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
using Dalamud.Bindings.ImGui;
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Linq;
using System.Numerics;
using Craftimizer.Plugin;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
namespace Craftimizer.Windows;
@@ -17,7 +17,8 @@ public sealed class MacroClipboard : Window, IDisposable
private List<string> Macros { get; }
public MacroClipboard(IEnumerable<string> macros) : base("Macro Clipboard", WindowFlags)
public MacroClipboard(IEnumerable<string> macros)
: base("Macro Clipboard", WindowFlags)
{
Macros = [.. macros];
@@ -39,32 +40,49 @@ public sealed class MacroClipboard : Window, IDisposable
private void DrawMacro(int idx, string macro)
{
using var id = ImRaii.PushId(idx);
using var panel = ImRaii2.GroupPanel(Macros.Count == 1 ? "Macro" : $"Macro {idx + 1}", -1, out var availWidth);
using var panel = ImRaii2.GroupPanel(
Macros.Count == 1 ? "Macro" : $"Macro {idx + 1}",
-1,
out var availWidth
);
var cursor = ImGui.GetCursorPos();
ImGuiUtils.AlignRight(ImGui.GetFrameHeight(), availWidth);
var buttonCursor = ImGui.GetCursorPos();
ImGui.InvisibleButton("##copyInvButton", new(ImGui.GetFrameHeight()));
var buttonHovered = ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenOverlapped | ImGuiHoveredFlags.AllowWhenBlockedByActiveItem);
var buttonHovered = ImGui.IsItemHovered(
ImGuiHoveredFlags.AllowWhenOverlapped | ImGuiHoveredFlags.AllowWhenBlockedByActiveItem
);
var buttonActive = buttonHovered && ImGui.GetIO().MouseDown[(int)ImGuiMouseButton.Left];
var buttonClicked = buttonHovered && ImGui.GetIO().MouseReleased[(int)ImGuiMouseButton.Left];
var buttonClicked =
buttonHovered && ImGui.GetIO().MouseReleased[(int)ImGuiMouseButton.Left];
ImGui.SetCursorPos(buttonCursor);
{
using var color = ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(buttonActive ? ImGuiCol.ButtonActive : ImGuiCol.ButtonHovered), buttonHovered);
using var color = ImRaii.PushColor(
ImGuiCol.Button,
ImGui.GetColorU32(buttonActive ? ImGuiCol.ButtonActive : ImGuiCol.ButtonHovered),
buttonHovered
);
ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Paste);
if (buttonClicked)
{
ImGui.SetClipboardText(macro);
if (Service.Configuration.MacroCopy.ShowCopiedMessage)
{
Plugin.Plugin.DisplayNotification(new()
Plugin.Plugin.DisplayNotification(
new()
{
Content = Macros.Count == 1 ? "Copied macro to clipboard." : $"Copied macro {idx + 1} to clipboard.",
MinimizedText = Macros.Count == 1 ? "Copied macro" : $"Copied macro {idx + 1}",
Content =
Macros.Count == 1
? "Copied macro to clipboard."
: $"Copied macro {idx + 1} to clipboard.",
MinimizedText =
Macros.Count == 1 ? "Copied macro" : $"Copied macro {idx + 1}",
Title = "Macro Copied",
Type = NotificationType.Success
});
Type = NotificationType.Success,
}
);
}
}
}
@@ -77,7 +95,17 @@ public sealed class MacroClipboard : Window, IDisposable
using var padding = ImRaii.PushStyle(ImGuiStyleVar.FramePadding, Vector2.Zero);
using var bg = ImRaii.PushColor(ImGuiCol.FrameBg, Vector4.Zero);
var lineCount = macro.Count(c => c == '\n') + 1;
ImGui.InputTextMultiline("", ref macro, macro.Length + 1, new(availWidth, ImGui.GetTextLineHeight() * Math.Max(15, lineCount) + ImGui.GetStyle().FramePadding.Y), ImGuiInputTextFlags.ReadOnly | ImGuiInputTextFlags.AutoSelectAll);
ImGui.InputTextMultiline(
"",
ref macro,
macro.Length + 1,
new(
availWidth,
ImGui.GetTextLineHeight() * Math.Max(15, lineCount)
+ ImGui.GetStyle().FramePadding.Y
),
ImGuiInputTextFlags.ReadOnly | ImGuiInputTextFlags.AutoSelectAll
);
}
if (buttonHovered)
File diff suppressed because it is too large Load Diff
+145 -42
View File
@@ -1,18 +1,18 @@
using Craftimizer.Plugin;
using Craftimizer.Utils;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface;
using Dalamud.Interface.Windowing;
using Dalamud.Bindings.ImGui;
using System;
using Craftimizer.Simulator;
using Craftimizer.Simulator.Actions;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Sim = Craftimizer.Simulator.SimulatorNoRandom;
using Craftimizer.Plugin;
using Craftimizer.Simulator;
using Craftimizer.Simulator.Actions;
using Craftimizer.Utils;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
using Dalamud.Utility;
using Sim = Craftimizer.Simulator.SimulatorNoRandom;
namespace Craftimizer.Windows;
@@ -26,7 +26,8 @@ public sealed class MacroList : Window, IDisposable
private static IReadOnlyList<Macro> Macros => Service.Configuration.Macros;
private Dictionary<Macro, SimulationState> MacroStateCache { get; } = [];
public MacroList() : base("Craftimizer Macro List", WindowFlags, false)
public MacroList()
: base("Craftimizer Macro List", WindowFlags, false)
{
RefreshSearch();
@@ -36,7 +37,11 @@ public sealed class MacroList : Window, IDisposable
CollapsedCondition = ImGuiCond.Appearing;
Collapsed = false;
SizeConstraints = new() { MinimumSize = new(465, 520), MaximumSize = new(float.PositiveInfinity) };
SizeConstraints = new()
{
MinimumSize = new(465, 520),
MaximumSize = new(float.PositiveInfinity),
};
TitleBarButtons =
[
@@ -45,14 +50,15 @@ public sealed class MacroList : Window, IDisposable
Icon = FontAwesomeIcon.Cog,
IconOffset = new(2, 1),
Click = _ => Service.Plugin.OpenSettingsTab("General"),
ShowTooltip = () => ImGuiUtils.Tooltip("Open Settings")
ShowTooltip = () => ImGuiUtils.Tooltip("Open Settings"),
},
new() {
new()
{
Icon = FontAwesomeIcon.Heart,
IconOffset = new(2, 1),
Click = _ => Util.OpenLink(Plugin.Plugin.SupportLink),
ShowTooltip = () => ImGuiUtils.Tooltip("Support me on Ko-fi!")
}
ShowTooltip = () => ImGuiUtils.Tooltip("Support me on Ko-fi!"),
},
];
Service.WindowSystem.AddWindow(this);
@@ -90,7 +96,9 @@ public sealed class MacroList : Window, IDisposable
ImGui.InvisibleButton($"###macroButton{i}", ImGui.GetItemRectSize());
if (isUnsorted)
{
using (var _source = ImRaii.DragDropSource(ImGuiDragDropFlags.SourceNoDisableHover))
using (
var _source = ImRaii.DragDropSource(ImGuiDragDropFlags.SourceNoDisableHover)
)
{
if (_source)
{
@@ -115,7 +123,10 @@ public sealed class MacroList : Window, IDisposable
var text2 = "the Macro Editor here or from the Crafting Log.";
var text3 = "Open Crafting Log";
var text4 = "Open Macro Editor";
var buttonRowWidth = ImGui.CalcTextSize(text3).X + ImGui.CalcTextSize(text4).X + ImGui.GetStyle().ItemSpacing.X * 5;
var buttonRowWidth =
ImGui.CalcTextSize(text3).X
+ ImGui.CalcTextSize(text4).X
+ ImGui.GetStyle().ItemSpacing.X * 5;
var size = new Vector2(
Math.Max(
Math.Max(ImGui.CalcTextSize(text1).X, ImGui.CalcTextSize(text2).X),
@@ -139,6 +150,7 @@ public sealed class MacroList : Window, IDisposable
private string searchText = string.Empty;
private List<Macro> sortedMacros = null!;
private bool isUnsorted = true;
private void DrawSearchBar()
{
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
@@ -157,12 +169,20 @@ public sealed class MacroList : Window, IDisposable
var stateNullable = GetMacroState(macro);
using var panel = ImRaii2.GroupPanel(macro.Name, width - ImGui.GetStyle().ItemSpacing.X * 2, out var availWidth);
using var panel = ImRaii2.GroupPanel(
macro.Name,
width - ImGui.GetStyle().ItemSpacing.X * 2,
out var availWidth
);
var stepsAvailWidthOffset = width - availWidth;
var spacing = ImGui.GetStyle().ItemSpacing.Y;
var miniRowHeight = (windowHeight - spacing) / 2f;
using var table = ImRaii.Table("table", stateNullable.HasValue ? 3 : 2, ImGuiTableFlags.BordersInnerV);
using var table = ImRaii.Table(
"table",
stateNullable.HasValue ? 3 : 2,
ImGuiTableFlags.BordersInnerV
);
if (table)
{
if (stateNullable.HasValue)
@@ -177,16 +197,22 @@ public sealed class MacroList : Window, IDisposable
if (Service.Configuration.ShowOptimalMacroStat)
{
var progressHeight = windowHeight;
if (state.Progress >= state.Input.Recipe.MaxProgress && state.Input.Recipe.MaxQuality > 0)
if (
state.Progress >= state.Input.Recipe.MaxProgress
&& state.Input.Recipe.MaxQuality > 0
)
{
ImGuiUtils.ArcProgress(
(float)state.Quality / state.Input.Recipe.MaxQuality,
progressHeight / 2f,
.5f,
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
ImGui.GetColorU32(Colors.Quality));
ImGui.GetColorU32(Colors.Quality)
);
if (ImGui.IsItemHovered())
ImGuiUtils.Tooltip($"Quality: {state.Quality} / {state.Input.Recipe.MaxQuality}");
ImGuiUtils.Tooltip(
$"Quality: {state.Quality} / {state.Input.Recipe.MaxQuality}"
);
}
else
{
@@ -195,9 +221,12 @@ public sealed class MacroList : Window, IDisposable
progressHeight / 2f,
.5f,
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
ImGui.GetColorU32(Colors.Progress));
ImGui.GetColorU32(Colors.Progress)
);
if (ImGui.IsItemHovered())
ImGuiUtils.Tooltip($"Progress: {state.Progress} / {state.Input.Recipe.MaxProgress}");
ImGuiUtils.Tooltip(
$"Progress: {state.Progress} / {state.Input.Recipe.MaxProgress}"
);
}
}
else
@@ -207,9 +236,12 @@ public sealed class MacroList : Window, IDisposable
miniRowHeight / 2f,
.5f,
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
ImGui.GetColorU32(Colors.Progress));
ImGui.GetColorU32(Colors.Progress)
);
if (ImGui.IsItemHovered())
ImGuiUtils.Tooltip($"Progress: {state.Progress} / {state.Input.Recipe.MaxProgress}");
ImGuiUtils.Tooltip(
$"Progress: {state.Progress} / {state.Input.Recipe.MaxProgress}"
);
ImGui.SameLine(0, spacing);
ImGuiUtils.ArcProgress(
@@ -217,17 +249,24 @@ public sealed class MacroList : Window, IDisposable
miniRowHeight / 2f,
.5f,
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
ImGui.GetColorU32(Colors.Quality));
ImGui.GetColorU32(Colors.Quality)
);
if (ImGui.IsItemHovered())
ImGuiUtils.Tooltip($"Quality: {state.Quality} / {state.Input.Recipe.MaxQuality}");
ImGuiUtils.Tooltip(
$"Quality: {state.Quality} / {state.Input.Recipe.MaxQuality}"
);
ImGuiUtils.ArcProgress((float)state.Durability / state.Input.Recipe.MaxDurability,
ImGuiUtils.ArcProgress(
(float)state.Durability / state.Input.Recipe.MaxDurability,
miniRowHeight / 2f,
.5f,
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
ImGui.GetColorU32(Colors.Durability));
ImGui.GetColorU32(Colors.Durability)
);
if (ImGui.IsItemHovered())
ImGuiUtils.Tooltip($"Remaining Durability: {state.Durability} / {state.Input.Recipe.MaxDurability}");
ImGuiUtils.Tooltip(
$"Remaining Durability: {state.Durability} / {state.Input.Recipe.MaxDurability}"
);
ImGui.SameLine(0, spacing);
ImGuiUtils.ArcProgress(
@@ -235,7 +274,8 @@ public sealed class MacroList : Window, IDisposable
miniRowHeight / 2f,
.5f,
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
ImGui.GetColorU32(Colors.CP));
ImGui.GetColorU32(Colors.CP)
);
if (ImGui.IsItemHovered())
ImGuiUtils.Tooltip($"Remaining CP: {state.CP} / {state.Input.Stats.CP}");
}
@@ -270,7 +310,11 @@ public sealed class MacroList : Window, IDisposable
ImGui.TableNextColumn();
{
var itemsPerRow = (int)MathF.Floor((ImGui.GetContentRegionAvail().X - stepsAvailWidthOffset + spacing * 2) / (miniRowHeight + spacing));
var itemsPerRow = (int)
MathF.Floor(
(ImGui.GetContentRegionAvail().X - stepsAvailWidthOffset + spacing * 2)
/ (miniRowHeight + spacing)
);
var itemCount = macro.Actions.Count;
for (var i = 0; i < itemsPerRow * 2; i++)
{
@@ -281,7 +325,10 @@ public sealed class MacroList : Window, IDisposable
var shouldShowMore = i + 1 == itemsPerRow * 2 && i + 1 < itemCount;
if (!shouldShowMore)
{
ImGui.Image(macro.Actions[i].GetIcon(RecipeData!.ClassJob).Handle, new(miniRowHeight));
ImGui.Image(
macro.Actions[i].GetIcon(RecipeData!.ClassJob).Handle,
new(miniRowHeight)
);
if (ImGui.IsItemHovered())
ImGuiUtils.Tooltip(macro.Actions[i].GetName(RecipeData!.ClassJob));
}
@@ -289,12 +336,36 @@ public sealed class MacroList : Window, IDisposable
{
var amtMore = itemCount - itemsPerRow * 2;
var pos = ImGui.GetCursorPos();
ImGui.Image(macro.Actions[i].GetIcon(RecipeData!.ClassJob).Handle, new(miniRowHeight), default, Vector2.One, new(1, 1, 1, .5f));
ImGui.Image(
macro.Actions[i].GetIcon(RecipeData!.ClassJob).Handle,
new(miniRowHeight),
default,
Vector2.One,
new(1, 1, 1, .5f)
);
if (ImGui.IsItemHovered())
ImGuiUtils.Tooltip($"{macro.Actions[i].GetName(RecipeData!.ClassJob)}\nand {amtMore} more");
ImGuiUtils.Tooltip(
$"{macro.Actions[i].GetName(RecipeData!.ClassJob)}\nand {amtMore} more"
);
ImGui.SetCursorPos(pos);
ImGui.GetWindowDrawList().AddRectFilled(ImGui.GetCursorScreenPos(), ImGui.GetCursorScreenPos() + new Vector2(miniRowHeight), ImGui.GetColorU32(ImGuiCol.FrameBg), miniRowHeight / 8f);
ImGui.GetWindowDrawList().AddTextClippedEx(ImGui.GetCursorScreenPos(), ImGui.GetCursorScreenPos() + new Vector2(miniRowHeight), $"+{amtMore}", null, new(.5f), null);
ImGui
.GetWindowDrawList()
.AddRectFilled(
ImGui.GetCursorScreenPos(),
ImGui.GetCursorScreenPos() + new Vector2(miniRowHeight),
ImGui.GetColorU32(ImGuiCol.FrameBg),
miniRowHeight / 8f
);
ImGui
.GetWindowDrawList()
.AddTextClippedEx(
ImGui.GetCursorScreenPos(),
ImGui.GetCursorScreenPos() + new Vector2(miniRowHeight),
$"+{amtMore}",
null,
new(.5f),
null
);
}
}
else
@@ -306,12 +377,19 @@ public sealed class MacroList : Window, IDisposable
private string popupMacroName = string.Empty;
private Macro? popupMacro;
private void ShowRenamePopup(Macro macro)
{
ImGui.OpenPopup($"##renamePopup-{macro.GetHashCode()}");
popupMacro = macro;
popupMacroName = macro.Name;
ImGui.SetNextWindowPos(ImGui.GetMousePos() - new Vector2(ImGui.CalcItemWidth() * .25f, ImGui.GetFrameHeight() + ImGui.GetStyle().WindowPadding.Y * 2));
ImGui.SetNextWindowPos(
ImGui.GetMousePos()
- new Vector2(
ImGui.CalcItemWidth() * .25f,
ImGui.GetFrameHeight() + ImGui.GetStyle().WindowPadding.Y * 2
)
);
}
private void DrawRenamePopup(Macro macro)
@@ -322,7 +400,15 @@ public sealed class MacroList : Window, IDisposable
if (ImGui.IsWindowAppearing())
ImGui.SetKeyboardFocusHere();
ImGui.SetNextItemWidth(ImGui.CalcItemWidth());
if (ImGui.InputTextWithHint($"##setName", "Name", ref popupMacroName, 100, ImGuiInputTextFlags.AutoSelectAll | ImGuiInputTextFlags.EnterReturnsTrue))
if (
ImGui.InputTextWithHint(
$"##setName",
"Name",
ref popupMacroName,
100,
ImGuiInputTextFlags.AutoSelectAll | ImGuiInputTextFlags.EnterReturnsTrue
)
)
{
if (!string.IsNullOrWhiteSpace(popupMacroName))
{
@@ -349,7 +435,9 @@ public sealed class MacroList : Window, IDisposable
}
isUnsorted = false;
var matcher = new FuzzyMatcher(searchText.ToLowerInvariant(), MatchMode.FuzzyParts);
var query = Macros.AsParallel().Select(i => (Item: i, Score: matcher.Matches(i.Name.ToLowerInvariant())))
var query = Macros
.AsParallel()
.Select(i => (Item: i, Score: matcher.Matches(i.Name.ToLowerInvariant())))
.Where(t => t.Score > 0)
.OrderByDescending(t => t.Score)
.Select(t => t.Item);
@@ -359,7 +447,22 @@ public sealed class MacroList : Window, IDisposable
private static void OpenEditor(Macro? macro)
{
var stats = Service.Plugin.GetDefaultStats();
Service.Plugin.OpenMacroEditor(stats.Character, stats.Recipe, stats.Buffs, null, macro?.Actions ?? Enumerable.Empty<ActionType>(), macro != null ? (actions => { macro.ActionEnumerable = actions; Service.Configuration.Save(); }) : null);
Service.Plugin.OpenMacroEditor(
stats.Character,
stats.Recipe,
stats.Buffs,
null,
macro?.Actions ?? Enumerable.Empty<ActionType>(),
macro != null
? (
actions =>
{
macro.ActionEnumerable = actions;
Service.Configuration.Save();
}
)
: null
);
}
private void OnMacroChanged(Macro macro)
File diff suppressed because it is too large Load Diff
+330 -175
View File
@@ -1,6 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using Craftimizer.Simulator;
using Craftimizer.Simulator.Actions;
using Craftimizer.Solver;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.ManagedFontAtlas;
@@ -8,12 +14,6 @@ using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
using FFXIVClientStructs.FFXIV.Client.UI;
using Dalamud.Bindings.ImGui;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
namespace Craftimizer.Plugin.Windows;
@@ -31,17 +31,22 @@ public sealed class Settings : Window, IDisposable
private IFontHandle HeaderFont { get; }
private IFontHandle SubheaderFont { get; }
public Settings() : base("Craftimizer Settings", WindowFlags)
public Settings()
: base("Craftimizer Settings", WindowFlags)
{
Service.WindowSystem.AddWindow(this);
HeaderFont = Service.PluginInterface.UiBuilder.FontAtlas.NewDelegateFontHandle(e => e.OnPreBuild(tk => tk.AddDalamudDefaultFont(UiBuilder.DefaultFontSizePx * 2f)));
SubheaderFont = Service.PluginInterface.UiBuilder.FontAtlas.NewDelegateFontHandle(e => e.OnPreBuild(tk => tk.AddDalamudDefaultFont(UiBuilder.DefaultFontSizePx * 1.5f)));
HeaderFont = Service.PluginInterface.UiBuilder.FontAtlas.NewDelegateFontHandle(e =>
e.OnPreBuild(tk => tk.AddDalamudDefaultFont(UiBuilder.DefaultFontSizePx * 2f))
);
SubheaderFont = Service.PluginInterface.UiBuilder.FontAtlas.NewDelegateFontHandle(e =>
e.OnPreBuild(tk => tk.AddDalamudDefaultFont(UiBuilder.DefaultFontSizePx * 1.5f))
);
SizeConstraints = new WindowSizeConstraints()
{
MinimumSize = new(450, 400),
MaximumSize = new(float.PositiveInfinity)
MaximumSize = new(float.PositiveInfinity),
};
}
@@ -61,7 +66,13 @@ public sealed class Settings : Window, IDisposable
return ImRaii.TabItem(label);
}
private static void DrawOption(string label, string tooltip, bool val, Action<bool> setter, ref bool isDirty)
private static void DrawOption(
string label,
string tooltip,
bool val,
Action<bool> setter,
ref bool isDirty
)
{
if (ImGui.Checkbox(label, ref val))
{
@@ -72,12 +83,28 @@ public sealed class Settings : Window, IDisposable
ImGuiUtils.TooltipWrapped(tooltip);
}
private static void DrawOption<T>(string label, string tooltip, T value, T min, T max, Action<T> setter, ref bool isDirty) where T : struct, INumber<T>
private static void DrawOption<T>(
string label,
string tooltip,
T value,
T min,
T max,
Action<T> setter,
ref bool isDirty
)
where T : struct, INumber<T>
{
ImGui.SetNextItemWidth(OptionWidth);
var text = value.ToString();
ArgumentNullException.ThrowIfNull(text, nameof(value));
if (ImGui.InputText(label, ref text, 8, ImGuiInputTextFlags.AutoSelectAll | ImGuiInputTextFlags.CharsDecimal))
if (
ImGui.InputText(
label,
ref text,
8,
ImGuiInputTextFlags.AutoSelectAll | ImGuiInputTextFlags.CharsDecimal
)
)
{
if (T.TryParse(text, null, out var newValue))
{
@@ -102,7 +129,13 @@ public sealed class Settings : Window, IDisposable
ImGuiUtils.TooltipWrapped(tooltip);
}
private static void DrawOption(string label, string tooltip, string value, Action<string> setter, ref bool isDirty)
private static void DrawOption(
string label,
string tooltip,
string value,
Action<string> setter,
ref bool isDirty
)
{
ImGui.SetNextItemWidth(OptionWidth);
var text = value;
@@ -118,7 +151,17 @@ public sealed class Settings : Window, IDisposable
ImGuiUtils.TooltipWrapped(tooltip);
}
private static void DrawOption<T>(string label, string tooltip, Func<T, string> getName, Func<T, string> getTooltip, T value, Action<T> setter, ref bool isDirty, params T[] excludedValues) where T : struct, Enum
private static void DrawOption<T>(
string label,
string tooltip,
Func<T, string> getName,
Func<T, string> getTooltip,
T value,
Action<T> setter,
ref bool isDirty,
params T[] excludedValues
)
where T : struct, Enum
{
ImGui.SetNextItemWidth(OptionWidth);
using (var combo = ImRaii.Combo(label, getName(value)))
@@ -160,16 +203,16 @@ public sealed class Settings : Window, IDisposable
{
SolverAlgorithm.Oneshot => "Run through all iterations and pick the best macro",
SolverAlgorithm.OneshotForked => "Oneshot, but using multiple solvers simultaneously",
SolverAlgorithm.Stepwise => "Run through all iterations and pick the next best step, " +
"and repeat using previous steps as a starting point",
SolverAlgorithm.Stepwise => "Run through all iterations and pick the next best step, "
+ "and repeat using previous steps as a starting point",
SolverAlgorithm.StepwiseForked => "Stepwise, but using multiple solvers simultaneously",
SolverAlgorithm.StepwiseGenetic => "Stepwise Forked, but the top N next best steps are " +
"selected from the solvers, and each one is equally " +
"used as a starting point",
SolverAlgorithm.Raphael => "Finds the best solution, every time. This solver has " +
"very different options compared to the rest, as it " +
"is designed using an entirely different algorithm.",
_ => "Unknown"
SolverAlgorithm.StepwiseGenetic => "Stepwise Forked, but the top N next best steps are "
+ "selected from the solvers, and each one is equally "
+ "used as a starting point",
SolverAlgorithm.Raphael => "Finds the best solution, every time. This solver has "
+ "very different options compared to the rest, as it "
+ "is designed using an entirely different algorithm.",
_ => "Unknown",
};
private static string GetCopyTypeName(MacroCopyConfiguration.CopyType type) =>
@@ -185,12 +228,16 @@ public sealed class Settings : Window, IDisposable
private static string GetCopyTypeTooltip(MacroCopyConfiguration.CopyType type) =>
type switch
{
MacroCopyConfiguration.CopyType.OpenWindow => "Open a dedicated window with all macros being copied. " +
"Copy, view, and choose at your own leisure.",
MacroCopyConfiguration.CopyType.CopyToMacro => "Copy directly to the game's macro system.",
MacroCopyConfiguration.CopyType.CopyToClipboard => "Copy to your clipboard. Macros are separated by a blank line.",
MacroCopyConfiguration.CopyType.CopyToMacroMate => "Copy directly to a Macro Mate macro. Requires the Macro Mate plugin.",
_ => "Unknown"
MacroCopyConfiguration.CopyType.OpenWindow =>
"Open a dedicated window with all macros being copied. "
+ "Copy, view, and choose at your own leisure.",
MacroCopyConfiguration.CopyType.CopyToMacro =>
"Copy directly to the game's macro system.",
MacroCopyConfiguration.CopyType.CopyToClipboard =>
"Copy to your clipboard. Macros are separated by a blank line.",
MacroCopyConfiguration.CopyType.CopyToMacroMate =>
"Copy directly to a Macro Mate macro. Requires the Macro Mate plugin.",
_ => "Unknown",
};
private static string GetProgressBarTypeName(Configuration.ProgressBarType type) =>
@@ -207,8 +254,9 @@ public sealed class Settings : Window, IDisposable
{
Configuration.ProgressBarType.Colorful => "Colorful, rainbow colors",
Configuration.ProgressBarType.Simple => "Simple, grayscale colors",
Configuration.ProgressBarType.None => "No progress bar; only percent completion is shown",
_ => "Unknown"
Configuration.ProgressBarType.None =>
"No progress bar; only percent completion is shown",
_ => "Unknown",
};
public override void Draw()
@@ -238,9 +286,9 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Enable Synthesis Helper",
"Adds a helper next to your synthesis window to help solve for the best craft. " +
"Extremely useful for expert recipes, where the condition can greatly affect " +
"which actions you take.",
"Adds a helper next to your synthesis window to help solve for the best craft. "
+ "Extremely useful for expert recipes, where the condition can greatly affect "
+ "which actions you take.",
Config.EnableSynthHelper,
v => Config.EnableSynthHelper = v,
ref isDirty
@@ -248,9 +296,9 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Show Only One Macro Stat in Crafting Log",
"Only one stat will be shown for a macro. If a craft will be finished, quality " +
"is shown. Otherwise, progress is shown. Durability and remaining CP will be " +
"hidden.",
"Only one stat will be shown for a macro. If a craft will be finished, quality "
+ "is shown. Otherwise, progress is shown. Durability and remaining CP will be "
+ "hidden.",
Config.ShowOptimalMacroStat,
v => Config.ShowOptimalMacroStat = v,
ref isDirty
@@ -258,8 +306,8 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Check For Delineations",
"Your inventory will be checked to ensure that you have delineations available " +
"before suggesting any specialist actions.",
"Your inventory will be checked to ensure that you have delineations available "
+ "before suggesting any specialist actions.",
Config.CheckDelineations,
v => Config.CheckDelineations = v,
ref isDirty
@@ -267,9 +315,9 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Reliability Trial Count",
"When testing for reliability of a macro in the editor, this many trials will be " +
"run. You should set this value to at least 100 to get a reliable spread of data. " +
"If it's too low, you may not find an outlier, and the average might be skewed.",
"When testing for reliability of a macro in the editor, this many trials will be "
+ "run. You should set this value to at least 100 to get a reliable spread of data. "
+ "If it's too low, you may not find an outlier, and the average might be skewed.",
Config.ReliabilitySimulationCount,
5,
5000,
@@ -301,8 +349,13 @@ public sealed class Settings : Window, IDisposable
ref isDirty
);
if (Config.MacroCopy.Type == MacroCopyConfiguration.CopyType.CopyToMacroMate &&
!Service.PluginInterface.InstalledPlugins.Any(p => p.IsLoaded && string.Equals(p.InternalName, "MacroMate", StringComparison.Ordinal)))
if (
Config.MacroCopy.Type == MacroCopyConfiguration.CopyType.CopyToMacroMate
&& !Service.PluginInterface.InstalledPlugins.Any(p =>
p.IsLoaded
&& string.Equals(p.InternalName, "MacroMate", StringComparison.Ordinal)
)
)
{
ImGui.SameLine();
using (var color = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudOrange))
@@ -326,8 +379,8 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Copy to Shared Macros",
"Copy to the shared macros tab. Leaving this unchecked copies to the " +
"individual tab.",
"Copy to the shared macros tab. Leaving this unchecked copies to the "
+ "individual tab.",
Config.MacroCopy.SharedMacro,
v => Config.MacroCopy.SharedMacro = v,
ref isDirty
@@ -335,20 +388,22 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Macro Number",
"The # of the macro to being copying to. Subsequent macros will be " +
"copied relative to this macro.",
"The # of the macro to being copying to. Subsequent macros will be "
+ "copied relative to this macro.",
Config.MacroCopy.StartMacroIdx,
0, 99,
0,
99,
v => Config.MacroCopy.StartMacroIdx = v,
ref isDirty
);
DrawOption(
"Max Macro Copy Count",
"The maximum number of macros to be copied. Any more and a window is " +
"displayed with the rest of them.",
"The maximum number of macros to be copied. Any more and a window is "
+ "displayed with the rest of them.",
Config.MacroCopy.MaxMacroCount,
1, 99,
1,
99,
v => Config.MacroCopy.MaxMacroCount = v,
ref isDirty
);
@@ -384,15 +439,20 @@ public sealed class Settings : Window, IDisposable
{
DrawOption(
"Use Macro Chain",
"Replaces the last step with /nextmacro to run the next macro " +
"automatically. Overrides the Intermediate Notification Sound.",
"Replaces the last step with /nextmacro to run the next macro "
+ "automatically. Overrides the Intermediate Notification Sound.",
Config.MacroCopy.UseNextMacro,
v => Config.MacroCopy.UseNextMacro = v,
ref isDirty
);
if (Config.MacroCopy.UseNextMacro &&
!Service.PluginInterface.InstalledPlugins.Any(p => p.IsLoaded && string.Equals(p.InternalName, "MacroChain", StringComparison.Ordinal)))
if (
Config.MacroCopy.UseNextMacro
&& !Service.PluginInterface.InstalledPlugins.Any(p =>
p.IsLoaded
&& string.Equals(p.InternalName, "MacroChain", StringComparison.Ordinal)
)
)
{
ImGui.SameLine();
using (var color = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudOrange))
@@ -407,8 +467,8 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Add Macro Lock",
"Adds /mlock to the beginning of every macro. Prevents other " +
"macros from being run.",
"Adds /mlock to the beginning of every macro. Prevents other "
+ "macros from being run.",
Config.MacroCopy.UseMacroLock,
v => Config.MacroCopy.UseMacroLock = v,
ref isDirty
@@ -424,12 +484,18 @@ public sealed class Settings : Window, IDisposable
if (Config.MacroCopy.AddNotification)
{
if ((Config.MacroCopy.Type == MacroCopyConfiguration.CopyType.CopyToMacro || !Config.MacroCopy.CombineMacro) && Config.MacroCopy.Type != MacroCopyConfiguration.CopyType.CopyToMacroMate)
if (
(
Config.MacroCopy.Type == MacroCopyConfiguration.CopyType.CopyToMacro
|| !Config.MacroCopy.CombineMacro
)
&& Config.MacroCopy.Type != MacroCopyConfiguration.CopyType.CopyToMacroMate
)
{
DrawOption(
"Force Notification",
"Prioritize always having a notification sound at the end of " +
"every macro. Keeping this off prevents macros with only 1 action.",
"Prioritize always having a notification sound at the end of "
+ "every macro. Keeping this off prevents macros with only 1 action.",
Config.MacroCopy.ForceNotification,
v => Config.MacroCopy.ForceNotification = v,
ref isDirty
@@ -446,14 +512,18 @@ public sealed class Settings : Window, IDisposable
if (Config.MacroCopy.AddNotificationSound)
{
if (!Config.MacroCopy.UseNextMacro && Config.MacroCopy.Type != MacroCopyConfiguration.CopyType.CopyToMacroMate)
if (
!Config.MacroCopy.UseNextMacro
&& Config.MacroCopy.Type != MacroCopyConfiguration.CopyType.CopyToMacroMate
)
{
DrawOption(
"Intermediate Notification Sound",
"Ending notification sound for an intermediary macro.\n" +
"Uses <se.#>",
"Ending notification sound for an intermediary macro.\n"
+ "Uses <se.#>",
Config.MacroCopy.IntermediateNotificationSound,
1, 16,
1,
16,
v =>
{
Config.MacroCopy.IntermediateNotificationSound = v;
@@ -465,10 +535,10 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Final Notification Sound",
"Ending notification sound for the final macro.\n" +
"Uses <se.#>",
"Ending notification sound for the final macro.\n" + "Uses <se.#>",
Config.MacroCopy.EndNotificationSound,
1, 16,
1,
16,
v =>
{
Config.MacroCopy.EndNotificationSound = v;
@@ -506,7 +576,12 @@ public sealed class Settings : Window, IDisposable
Config.Save();
}
private static void DrawSolverConfig(ref SolverConfig configRef, SolverConfig defaultConfig, bool disableOptimal, out bool isDirty)
private static void DrawSolverConfig(
ref SolverConfig configRef,
SolverConfig defaultConfig,
bool disableOptimal,
out bool isDirty
)
{
isDirty = false;
@@ -522,10 +597,10 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Algorithm",
"The algorithm to use when solving for a macro. Different " +
"algorithms provide different pros and cons for using them. " +
"By far, the Optimal and Stepwise Genetic algorithms provide " +
"the best results, especially for very difficult crafts.",
"The algorithm to use when solving for a macro. Different "
+ "algorithms provide different pros and cons for using them. "
+ "By far, the Optimal and Stepwise Genetic algorithms provide "
+ "the best results, especially for very difficult crafts.",
GetAlgorithmName,
GetAlgorithmTooltip,
config.Algorithm,
@@ -534,15 +609,25 @@ public sealed class Settings : Window, IDisposable
disableOptimal ? [SolverAlgorithm.Raphael] : []
);
using (ImRaii.Disabled(config.Algorithm is not (SolverAlgorithm.OneshotForked or SolverAlgorithm.StepwiseForked or SolverAlgorithm.StepwiseGenetic or SolverAlgorithm.Raphael)))
using (
ImRaii.Disabled(
config.Algorithm
is not (
SolverAlgorithm.OneshotForked
or SolverAlgorithm.StepwiseForked
or SolverAlgorithm.StepwiseGenetic
or SolverAlgorithm.Raphael
)
)
)
DrawOption(
"Max Core Count",
"The number of cores to use when solving. You should use as many " +
"as you can. If it's too high, it will have an effect on your gameplay " +
$"experience. A good estimate would be 1 or 2 cores less than your " +
$"system (FYI, you have {Environment.ProcessorCount} cores), but make sure to accomodate " +
$"for any other tasks you have in the background, if you have any.\n" +
"(Only used in the Forked, Genetic, and Optimal algorithms)",
"The number of cores to use when solving. You should use as many "
+ "as you can. If it's too high, it will have an effect on your gameplay "
+ $"experience. A good estimate would be 1 or 2 cores less than your "
+ $"system (FYI, you have {Environment.ProcessorCount} cores), but make sure to accomodate "
+ $"for any other tasks you have in the background, if you have any.\n"
+ "(Only used in the Forked, Genetic, and Optimal algorithms)",
config.MaxThreadCount,
1,
Environment.ProcessorCount,
@@ -554,10 +639,10 @@ public sealed class Settings : Window, IDisposable
{
DrawOption(
"Target Iterations",
"The total number of iterations to run per crafting step. " +
"Higher values require more computational power. Higher values " +
"also may decrease variance, so other values should be tweaked " +
"as necessary to get a more favorable outcome.",
"The total number of iterations to run per crafting step. "
+ "Higher values require more computational power. Higher values "
+ "also may decrease variance, so other values should be tweaked "
+ "as necessary to get a more favorable outcome.",
config.Iterations,
1000,
1000000,
@@ -567,11 +652,11 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Max Iterations",
"The solver may go about the target iteration value if the craft " +
"is sufficiently difficult, and it wasn't able to find any way to " +
"complete it yet. In rare cases, the solver might go on for a very " +
"long time. This maximum is here to prevent the solver from stealing " +
"all your RAM.",
"The solver may go about the target iteration value if the craft "
+ "is sufficiently difficult, and it wasn't able to find any way to "
+ "complete it yet. In rare cases, the solver might go on for a very "
+ "long time. This maximum is here to prevent the solver from stealing "
+ "all your RAM.",
config.MaxIterations,
config.Iterations,
5000000,
@@ -581,11 +666,11 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Max Step Count",
"The maximum number of crafting steps; this is generally the only " +
"setting you should change, and it should be set to around 5 steps " +
"more than what you'd expect. If this value is too low, the solver " +
"won't learn much per iteration; too high and it will waste time " +
"on useless extra steps.",
"The maximum number of crafting steps; this is generally the only "
+ "setting you should change, and it should be set to around 5 steps "
+ "more than what you'd expect. If this value is too low, the solver "
+ "won't learn much per iteration; too high and it will waste time "
+ "on useless extra steps.",
config.MaxStepCount,
1,
100,
@@ -595,9 +680,9 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Exploration Constant",
"A constant that decides how often the solver will explore new, " +
"possibly good paths. If this value is too high, " +
"moves will mostly be decided at random.",
"A constant that decides how often the solver will explore new, "
+ "possibly good paths. If this value is too high, "
+ "moves will mostly be decided at random.",
config.ExplorationConstant,
0,
10,
@@ -607,10 +692,10 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Score Weighting Constant",
"A constant ranging from 0 to 1 that configures how the solver " +
"scores and picks paths to travel to next. A value of 0 means " +
"actions will be chosen based on their average outcome, whereas " +
"1 uses their best outcome achieved so far.",
"A constant ranging from 0 to 1 that configures how the solver "
+ "scores and picks paths to travel to next. A value of 0 means "
+ "actions will be chosen based on their average outcome, whereas "
+ "1 uses their best outcome achieved so far.",
config.MaxScoreWeightingConstant,
0,
1,
@@ -618,16 +703,25 @@ public sealed class Settings : Window, IDisposable
ref isDirty
);
using (ImRaii.Disabled(config.Algorithm is not (SolverAlgorithm.OneshotForked or SolverAlgorithm.StepwiseForked or SolverAlgorithm.StepwiseGenetic)))
using (
ImRaii.Disabled(
config.Algorithm
is not (
SolverAlgorithm.OneshotForked
or SolverAlgorithm.StepwiseForked
or SolverAlgorithm.StepwiseGenetic
)
)
)
DrawOption(
"Fork Count",
"Split the number of iterations across different solvers. In general, " +
"you should increase this value to at least the number of cores in " +
$"your system (FYI, you have {Environment.ProcessorCount} cores) to attain the most speedup. " +
"The higher the number, the more chance you have of finding a " +
"better local maximum; this concept similar but not equivalent " +
"to the exploration constant.\n" +
"(Only used in the Forked and Genetic algorithms)",
"Split the number of iterations across different solvers. In general, "
+ "you should increase this value to at least the number of cores in "
+ $"your system (FYI, you have {Environment.ProcessorCount} cores) to attain the most speedup. "
+ "The higher the number, the more chance you have of finding a "
+ "better local maximum; this concept similar but not equivalent "
+ "to the exploration constant.\n"
+ "(Only used in the Forked and Genetic algorithms)",
config.ForkCount,
1,
500,
@@ -638,10 +732,10 @@ public sealed class Settings : Window, IDisposable
using (ImRaii.Disabled(config.Algorithm is not SolverAlgorithm.StepwiseGenetic))
DrawOption(
"Elitist Action Count",
"On every craft step, pick this many top solutions and use them as " +
"the input for the next craft step. For best results, use Fork Count / 2 " +
"and add about 1 or 2 more if needed.\n" +
"(Only used in the Stepwise Genetic algorithm)",
"On every craft step, pick this many top solutions and use them as "
+ "the input for the next craft step. For best results, use Fork Count / 2 "
+ "and add about 1 or 2 more if needed.\n"
+ "(Only used in the Stepwise Genetic algorithm)",
config.FurcatedActionCount,
1,
500,
@@ -653,16 +747,16 @@ public sealed class Settings : Window, IDisposable
{
DrawOption(
"Backload Progress",
"Speeds up solve times. Backloads all Progress " +
"actions to the end of the rotation.",
"Speeds up solve times. Backloads all Progress "
+ "actions to the end of the rotation.",
config.BackloadProgress,
v => config = config with { BackloadProgress = v },
ref isDirty
);
DrawOption(
"Ensure Reliability",
"Find a rotation that can reach the target quality no matter " +
"how unlucky the random conditions are.",
"Find a rotation that can reach the target quality no matter "
+ "how unlucky the random conditions are.",
config.Adversarial,
v => config = config with { Adversarial = v },
ref isDirty
@@ -677,7 +771,9 @@ public sealed class Settings : Window, IDisposable
ImGui.TextUnformatted(FontAwesomeIcon.ExclamationCircle.ToIconString());
}
if (ImGui.IsItemHovered())
ImGuiUtils.TooltipWrapped("\"Ensure Reliability\" uses a lot more memory and can significantly increase solve times.");
ImGuiUtils.TooltipWrapped(
"\"Ensure Reliability\" uses a lot more memory and can significantly increase solve times."
);
}
}
}
@@ -703,9 +799,9 @@ public sealed class Settings : Window, IDisposable
{
DrawOption(
"Max Rollout Step Count",
"The maximum number of crafting steps every iteration can consider. " +
"Decreasing this value can have unintended side effects. Only change " +
"this value if you absolutely know what you're doing.",
"The maximum number of crafting steps every iteration can consider. "
+ "Decreasing this value can have unintended side effects. Only change "
+ "this value if you absolutely know what you're doing.",
config.MaxRolloutStepCount,
1,
50,
@@ -715,9 +811,9 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Strict Actions",
"When finding the next possible actions to execute, use a heuristic " +
"to restrict which actions to attempt taking. This results in a much " +
"better macro at the cost of not finding an extremely creative one.",
"When finding the next possible actions to execute, use a heuristic "
+ "to restrict which actions to attempt taking. This results in a much "
+ "better macro at the cost of not finding an extremely creative one.",
config.StrictActions,
v => config = config with { StrictActions = v },
ref isDirty
@@ -771,8 +867,8 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Steps",
"Amount of weight to give to the craft's number of steps. The lower " +
"the step count, the higher the score.",
"Amount of weight to give to the craft's number of steps. The lower "
+ "the step count, the higher the score.",
config.ScoreSteps,
0,
100,
@@ -786,7 +882,11 @@ public sealed class Settings : Window, IDisposable
configRef = config;
}
private static void DrawActionPool(ref ActionType[] actionPool, float poolWidth, out bool isDirty)
private static void DrawActionPool(
ref ActionType[] actionPool,
float poolWidth,
out bool isDirty
)
{
isDirty = false;
@@ -799,14 +899,21 @@ public sealed class Settings : Window, IDisposable
using var _color = ImRaii.PushColor(ImGuiCol.Button, Vector4.Zero);
using var _color3 = ImRaii.PushColor(ImGuiCol.ButtonHovered, Vector4.Zero);
using var _color2 = ImRaii.PushColor(ImGuiCol.ButtonActive, Vector4.Zero);
using var _alpha = ImRaii.PushStyle(ImGuiStyleVar.DisabledAlpha, ImGui.GetStyle().DisabledAlpha * .5f);
using var _alpha = ImRaii.PushStyle(
ImGuiStyleVar.DisabledAlpha,
ImGui.GetStyle().DisabledAlpha * .5f
);
foreach (var category in Enum.GetValues<ActionCategory>())
{
if (category == ActionCategory.Combo)
continue;
var actions = category.GetActions();
using var panel = ImRaii2.GroupPanel(category.GetDisplayName(), poolWidth, out var availSpace);
using var panel = ImRaii2.GroupPanel(
category.GetDisplayName(),
poolWidth,
out var availSpace
);
var itemsPerRow = (int)MathF.Floor((availSpace + spacing) / (imageSize + spacing));
var itemCount = actions.Count;
var iterCount = (int)(Math.Ceiling((float)itemCount / itemsPerRow) * itemsPerRow);
@@ -827,7 +934,17 @@ public sealed class Settings : Window, IDisposable
iconTint = new(1, 1f, .5f, 1);
else if (isRisky)
iconTint = new(1, .5f, .5f, 1);
if (ImGui.ImageButton(actions[i].GetIcon(recipeData.ClassJob).Handle, new(imageSize), default, Vector2.One, 0, default, iconTint))
if (
ImGui.ImageButton(
actions[i].GetIcon(recipeData.ClassJob).Handle,
new(imageSize),
default,
Vector2.One,
0,
default,
iconTint
)
)
{
isDirty = true;
if (isEnabled)
@@ -841,16 +958,18 @@ public sealed class Settings : Window, IDisposable
s.AppendLine(actions[i].GetName(recipeData.ClassJob));
if (isInefficient)
s.AppendLine(
"Not recommended. This action may be randomly used in a " +
"detrimental way to the rest of the craft. Always use " +
"your best judgement if enabling this action.");
"Not recommended. This action may be randomly used in a "
+ "detrimental way to the rest of the craft. Always use "
+ "your best judgement if enabling this action."
);
if (isRisky)
s.AppendLine(
"Useless; the solver currently doesn't take any risks in " +
"its crafts. It only takes steps that have a 100% chance of " +
"succeeding. If you want have a moment where you want to take " +
"risks in your craft (like in expert recipes), don't rely " +
"on the solver during that time.");
"Useless; the solver currently doesn't take any risks in "
+ "its crafts. It only takes steps that have a 100% chance of "
+ "succeeding. If you want have a moment where you want to take "
+ "risks in your craft (like in expert recipes), don't rely "
+ "on the solver during that time."
);
ImGuiUtils.TooltipWrapped(s.ToString());
}
}
@@ -912,8 +1031,8 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Pin Helper Window",
"Pins the helper window to the right of your crafting log. Disabling this will " +
"allow you to move it around.",
"Pins the helper window to the right of your crafting log. Disabling this will "
+ "allow you to move it around.",
Config.PinRecipeNoteToWindow,
v => Config.PinRecipeNoteToWindow = v,
ref isDirty
@@ -921,8 +1040,8 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Always Collapse Helper Window",
"Enabling this will cause the Helper Window to be collapsed whenever you start " +
"a new craft, preventing the solver from running automatically.",
"Enabling this will cause the Helper Window to be collapsed whenever you start "
+ "a new craft, preventing the solver from running automatically.",
Config.CollapseSynthHelper,
v => Config.CollapseSynthHelper = v,
ref isDirty
@@ -930,11 +1049,11 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Automatically Suggest Macro",
"(Can cause frame drops!) When navigating to a new recipe or changing your gear " +
"stats, automatically suggest a new macro (equivalent to clicking \"Generate\" " +
"in the Macro Editor). This can cause harsh frame drops on some computers or " +
"recipes when underleveled while navigating the crafting log. Turning this off " +
"provides a button to allow you to manually suggest a macro only when you need it.",
"(Can cause frame drops!) When navigating to a new recipe or changing your gear "
+ "stats, automatically suggest a new macro (equivalent to clicking \"Generate\" "
+ "in the Macro Editor). This can cause harsh frame drops on some computers or "
+ "recipes when underleveled while navigating the crafting log. Turning this off "
+ "provides a button to allow you to manually suggest a macro only when you need it.",
Config.SuggestMacroAutomatically,
v => Config.SuggestMacroAutomatically = v,
ref isDirty
@@ -942,10 +1061,10 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Enable Community Macros",
"Use FFXIV Teamcraft's community rotations to search for and find the best possible " +
"crowd-sourced macro for your craft. This sends a request to their servers to retrieve " +
"a list of macros that apply to your craft's rlvl. Requests are only sent once per rlvl " +
"and are always cached to reduce server load.",
"Use FFXIV Teamcraft's community rotations to search for and find the best possible "
+ "crowd-sourced macro for your craft. This sends a request to their servers to retrieve "
+ "a list of macros that apply to your craft's rlvl. Requests are only sent once per rlvl "
+ "and are always cached to reduce server load.",
Config.ShowCommunityMacros,
v => Config.ShowCommunityMacros = v,
ref isDirty
@@ -955,9 +1074,9 @@ public sealed class Settings : Window, IDisposable
{
DrawOption(
"Automatically Search for Community Macro",
"When navigating to a new recipe or changing your gear stats, automatically search " +
"online for a new community macro.\n" +
"This is turned off by default so you don't hammer their servers :)",
"When navigating to a new recipe or changing your gear stats, automatically search "
+ "online for a new community macro.\n"
+ "This is turned off by default so you don't hammer their servers :)",
Config.SearchCommunityMacroAutomatically,
v => Config.SearchCommunityMacroAutomatically = v,
ref isDirty
@@ -969,7 +1088,12 @@ public sealed class Settings : Window, IDisposable
ImGuiHelpers.ScaledDummy(5);
var solverConfig = Config.RecipeNoteSolverConfig;
DrawSolverConfig(ref solverConfig, SolverConfig.RecipeNoteDefault, false, out var isSolverDirty);
DrawSolverConfig(
ref solverConfig,
SolverConfig.RecipeNoteDefault,
false,
out var isSolverDirty
);
if (isSolverDirty)
{
Config.RecipeNoteSolverConfig = solverConfig;
@@ -991,7 +1115,12 @@ public sealed class Settings : Window, IDisposable
var isDirty = false;
var solverConfig = Config.EditorSolverConfig;
DrawSolverConfig(ref solverConfig, SolverConfig.EditorDefault, false, out var isSolverDirty);
DrawSolverConfig(
ref solverConfig,
SolverConfig.EditorDefault,
false,
out var isSolverDirty
);
if (isSolverDirty)
{
Config.EditorSolverConfig = solverConfig;
@@ -1014,8 +1143,8 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Pin Helper Window",
"Pins the synthesis helper to the right of your synthesis window. Disabling this will " +
"allow you to move it around.",
"Pins the synthesis helper to the right of your synthesis window. Disabling this will "
+ "allow you to move it around.",
Config.PinSynthHelperToWindow,
v => Config.PinSynthHelperToWindow = v,
ref isDirty
@@ -1031,9 +1160,9 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Simulate Only First Step",
"Only the first step is simulated by default. You can still " +
"hover over the other steps to view their outcomes, but the " +
"reliability trials (when hovering over the macro stats) are hidden.",
"Only the first step is simulated by default. You can still "
+ "hover over the other steps to view their outcomes, but the "
+ "reliability trials (when hovering over the macro stats) are hidden.",
Config.SynthHelperDisplayOnlyFirstStep,
v => Config.SynthHelperDisplayOnlyFirstStep = v,
ref isDirty
@@ -1041,9 +1170,9 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Draw Ability Ants",
"Turns your hotbar into a whack-a-mole game! Draws ants for " +
"the next action that should be executed. Also disables ants " +
"for things like combo actions and condition procs.",
"Turns your hotbar into a whack-a-mole game! Draws ants for "
+ "the next action that should be executed. Also disables ants "
+ "for things like combo actions and condition procs.",
Config.SynthHelperAbilityAnts,
v => Config.SynthHelperAbilityAnts = v,
ref isDirty
@@ -1051,8 +1180,8 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Solver Step Count",
"The minimum number of future steps to solve for during an in-game craft. " +
"The solver may still give more than this amount if it's at no cost to you.",
"The minimum number of future steps to solve for during an in-game craft. "
+ "The solver may still give more than this amount if it's at no cost to you.",
Config.SynthHelperStepCount,
1,
100,
@@ -1062,8 +1191,8 @@ public sealed class Settings : Window, IDisposable
DrawOption(
"Max Step Display Count",
"Enforces a maximum number of steps to display in the synth helper to " +
"get rid of clutter.",
"Enforces a maximum number of steps to display in the synth helper to "
+ "get rid of clutter.",
Config.SynthHelperMaxDisplayCount,
Config.SynthHelperStepCount,
100,
@@ -1076,7 +1205,12 @@ public sealed class Settings : Window, IDisposable
ImGuiHelpers.ScaledDummy(5);
var solverConfig = Config.SynthHelperSolverConfig;
DrawSolverConfig(ref solverConfig, SolverConfig.SynthHelperDefault, true, out var isSolverDirty);
DrawSolverConfig(
ref solverConfig,
SolverConfig.SynthHelperDefault,
true,
out var isSolverDirty
);
if (isSolverDirty)
{
Config.SynthHelperSolverConfig = solverConfig;
@@ -1109,18 +1243,33 @@ public sealed class Settings : Window, IDisposable
ImGui.Image(icon.Handle, iconDim);
ImGui.TableNextColumn();
ImGuiUtils.AlignMiddle(new(float.PositiveInfinity, HeaderFont.GetFontSize() + SubheaderFont.GetFontSize() + ImGui.GetFontSize() * 3 + ImGui.GetStyle().ItemSpacing.Y * 4), new(0, iconDim.Y));
ImGuiUtils.AlignMiddle(
new(
float.PositiveInfinity,
HeaderFont.GetFontSize()
+ SubheaderFont.GetFontSize()
+ ImGui.GetFontSize() * 3
+ ImGui.GetStyle().ItemSpacing.Y * 4
),
new(0, iconDim.Y)
);
using (HeaderFont.Push())
{
ImGuiUtils.AlignCentered(ImGui.CalcTextSize("Forgeimizer").X);
ImGuiUtils.Hyperlink("Forgeimizer", "https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer", false);
ImGuiUtils.Hyperlink(
"Forgeimizer",
"https://gitea.hellion-forge.cloud/JonKazama-Hellion/Craftimizer",
false
);
}
using (SubheaderFont.Push())
ImGuiUtils.TextCentered($"v{plugin.Version} {plugin.BuildConfiguration}");
ImGuiUtils.AlignCentered(ImGui.CalcTextSize($"By {plugin.Author} (WorkingRobot)").X);
ImGuiUtils.AlignCentered(
ImGui.CalcTextSize($"By {plugin.Author} (WorkingRobot)").X
);
ImGui.TextUnformatted($"By {plugin.Author} (");
ImGui.SameLine(0, 0);
ImGuiUtils.Hyperlink("WorkingRobot", "https://github.com/WorkingRobot");
@@ -1178,11 +1327,17 @@ public sealed class Settings : Window, IDisposable
ImGuiUtils.TextWrappedTo("Thank you to ");
ImGui.SameLine(0, 0);
ImGuiUtils.Hyperlink("this", "https://dke.maastrichtuniversity.nl/m.winands/documents/multithreadedMCTS2.pdf");
ImGuiUtils.Hyperlink(
"this",
"https://dke.maastrichtuniversity.nl/m.winands/documents/multithreadedMCTS2.pdf"
);
ImGui.SameLine(0, 0);
ImGuiUtils.TextWrappedTo(", ");
ImGui.SameLine(0, 0);
ImGuiUtils.Hyperlink("this", "https://liacs.leidenuniv.nl/~plaata1/papers/paper_ICAART18.pdf");
ImGuiUtils.Hyperlink(
"this",
"https://liacs.leidenuniv.nl/~plaata1/papers/paper_ICAART18.pdf"
);
ImGui.SameLine(0, 0);
ImGuiUtils.TextWrappedTo(", and ");
ImGui.SameLine(0, 0);
+190 -55
View File
@@ -1,7 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Threading;
using Craftimizer.Plugin;
using Craftimizer.Simulator;
using Craftimizer.Simulator.Actions;
using Craftimizer.Utils;
using Dalamud.Bindings.ImGui;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
@@ -15,12 +21,6 @@ using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Shell;
using Dalamud.Bindings.ImGui;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Threading;
using ActionType = Craftimizer.Simulator.Actions.ActionType;
using Sim = Craftimizer.Simulator.Simulator;
using SimNoRandom = Craftimizer.Simulator.SimulatorNoRandom;
@@ -29,12 +29,11 @@ namespace Craftimizer.Windows;
public sealed unsafe class SynthHelper : Window, IDisposable
{
private const ImGuiWindowFlags WindowFlagsPinned = WindowFlagsFloating
| ImGuiWindowFlags.NoSavedSettings;
private const ImGuiWindowFlags WindowFlagsPinned =
WindowFlagsFloating | ImGuiWindowFlags.NoSavedSettings;
private const ImGuiWindowFlags WindowFlagsFloating =
ImGuiWindowFlags.AlwaysAutoResize
| ImGuiWindowFlags.NoFocusOnAppearing;
ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoFocusOnAppearing;
private const string WindowNamePinned = "Craftimizer Synthesis Helper###CraftimizerSynthHelper";
private const string WindowNameFloating = $"{WindowNamePinned}Floating";
@@ -69,9 +68,12 @@ public sealed unsafe class SynthHelper : Window, IDisposable
private IFontHandle AxisFont { get; }
public SynthHelper() : base(WindowNamePinned)
public SynthHelper()
: base(WindowNamePinned)
{
AxisFont = Service.PluginInterface.UiBuilder.FontAtlas.NewGameFontHandle(new(GameFontFamilyAndSize.Axis14));
AxisFont = Service.PluginInterface.UiBuilder.FontAtlas.NewGameFontHandle(
new(GameFontFamilyAndSize.Axis14)
);
Service.Plugin.Hooks.OnActionUsed += OnUseAction;
@@ -83,7 +85,7 @@ public sealed unsafe class SynthHelper : Window, IDisposable
SizeConstraints = new WindowSizeConstraints
{
MinimumSize = new(494, -1),
MaximumSize = new(494, 10000)
MaximumSize = new(494, 10000),
};
TitleBarButtons =
@@ -93,14 +95,15 @@ public sealed unsafe class SynthHelper : Window, IDisposable
Icon = FontAwesomeIcon.Cog,
IconOffset = new(2, 1),
Click = _ => Service.Plugin.OpenSettingsTab("Synthesis Helper"),
ShowTooltip = () => ImGuiUtils.Tooltip("Open Settings")
ShowTooltip = () => ImGuiUtils.Tooltip("Open Settings"),
},
new() {
new()
{
Icon = FontAwesomeIcon.Heart,
IconOffset = new(2, 1),
Click = _ => Util.OpenLink(Plugin.Plugin.SupportLink),
ShowTooltip = () => ImGuiUtils.Tooltip("Support me on Ko-fi!")
}
ShowTooltip = () => ImGuiUtils.Tooltip("Support me on Ko-fi!"),
},
];
Service.WindowSystem.AddWindow(this);
@@ -153,10 +156,10 @@ public sealed unsafe class SynthHelper : Window, IDisposable
WasCalculatable = ShouldCalculate;
}
public override bool DrawConditions() =>
ShouldOpen;
public override bool DrawConditions() => ShouldOpen;
private bool wasInCraftAction;
private bool CalculateShouldOpen()
{
if (Service.Objects.LocalPlayer == null)
@@ -209,7 +212,8 @@ public sealed unsafe class SynthHelper : Window, IDisposable
OnStartCrafting(recipeId);
OnStateUpdated();
if (Service.Configuration.CollapseSynthHelper) ShouldCollapse = true;
if (Service.Configuration.CollapseSynthHelper)
ShouldCollapse = true;
}
if (IsRecalculateQueued)
@@ -228,6 +232,7 @@ public sealed unsafe class SynthHelper : Window, IDisposable
private Vector2? LastPosition { get; set; }
private byte? StyleAlpha { get; set; }
private byte? LastAlpha { get; set; }
public override void PreDraw()
{
base.PreDraw();
@@ -239,7 +244,9 @@ public sealed unsafe class SynthHelper : Window, IDisposable
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 size =
new Vector2(unit.WindowNode->AtkResNode.Width, unit.WindowNode->AtkResNode.Height)
* scale;
var offset = 5;
@@ -261,7 +268,10 @@ public sealed unsafe class SynthHelper : Window, IDisposable
WindowName = WindowNameFloating;
}
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, StyleAlpha.HasValue ? (StyleAlpha.Value / 255f) : 1);
ImGui.PushStyleVar(
ImGuiStyleVar.Alpha,
StyleAlpha.HasValue ? (StyleAlpha.Value / 255f) : 1
);
}
public override void PostDraw()
@@ -273,7 +283,6 @@ public sealed unsafe class SynthHelper : Window, IDisposable
public override void Draw()
{
if (ShouldCollapse)
{
ImGui.SetWindowCollapsed(true);
@@ -298,7 +307,9 @@ public sealed unsafe class SynthHelper : Window, IDisposable
}
private SimulationState? hoveredState;
private SimulationState DisplayedState => hoveredState ?? (Service.Configuration.SynthHelperDisplayOnlyFirstStep ? Macro.FirstState : Macro.State);
private SimulationState DisplayedState =>
hoveredState
?? (Service.Configuration.SynthHelperDisplayOnlyFirstStep ? Macro.FirstState : Macro.State);
private void DrawMacro()
{
@@ -308,7 +319,11 @@ public sealed unsafe class SynthHelper : Window, IDisposable
var lastState = Macro.InitialState;
hoveredState = null;
var itemsPerRow = (int)Math.Max(1, MathF.Floor((ImGui.GetContentRegionAvail().X + spacing) / (imageSize + spacing)));
var itemsPerRow = (int)
Math.Max(
1,
MathF.Floor((ImGui.GetContentRegionAvail().X + spacing) / (imageSize + spacing))
);
using var _color = ImRaii.PushColor(ImGuiCol.Button, Vector4.Zero);
using var _color3 = ImRaii.PushColor(ImGuiCol.ButtonHovered, Vector4.Zero);
@@ -328,9 +343,18 @@ public sealed unsafe class SynthHelper : Window, IDisposable
var offsetVec2 = ImGui.GetStyle().ItemSpacing / 2;
var offset = new Vector2((offsetVec2.X + offsetVec2.Y) / 2f);
var color = canExecute ? ImGuiColors.DalamudWhite2 : ImGuiColors.DalamudGrey3;
ImGui.GetWindowDrawList().AddRectFilled(pos - offset, pos + new Vector2(imageSize) + offset, ImGui.GetColorU32(color), 4);
ImGui
.GetWindowDrawList()
.AddRectFilled(
pos - offset,
pos + new Vector2(imageSize) + offset,
ImGui.GetColorU32(color),
4
);
}
bool isHovered, isHeld, isPressed;
bool isHovered,
isHeld,
isPressed;
{
var pos = ImGui.GetCursorScreenPos();
var offset = ImGui.GetStyle().ItemSpacing / 2f;
@@ -344,9 +368,23 @@ public sealed unsafe class SynthHelper : Window, IDisposable
var id = ImGui.GetID($"###ButtonContainer");
var isClipped = !ImGuiExtras.ItemAdd(bb, id, out _, 0);
isPressed = ImGuiExtras.ButtonBehavior(bb, id, out isHovered, out isHeld, ImGuiButtonFlags.None);
isPressed = ImGuiExtras.ButtonBehavior(
bb,
id,
out isHovered,
out isHeld,
ImGuiButtonFlags.None
);
}
ImGui.ImageButton(action.GetIcon(RecipeData!.ClassJob).Handle, new(imageSize), default, Vector2.One, 0, default, failedAction ? new(1, 1, 1, ImGui.GetStyle().DisabledAlpha) : Vector4.One);
ImGui.ImageButton(
action.GetIcon(RecipeData!.ClassJob).Handle,
new(imageSize),
default,
Vector2.One,
0,
default,
failedAction ? new(1, 1, 1, ImGui.GetStyle().DisabledAlpha) : Vector4.One
);
if (isPressed && i == 0)
{
if (ExecuteNextAction())
@@ -354,15 +392,21 @@ public sealed unsafe class SynthHelper : Window, IDisposable
}
if (isHovered)
{
ImGuiUtils.Tooltip($"{action.GetName(RecipeData!.ClassJob)}\n" +
$"{actionBase.GetTooltip(CreateSim(lastState), true)}" +
$"{(canExecute && i == 0 ? "Click or run /craftaction to execute" : string.Empty)}");
ImGuiUtils.Tooltip(
$"{action.GetName(RecipeData!.ClassJob)}\n"
+ $"{actionBase.GetTooltip(CreateSim(lastState), true)}"
+ $"{(canExecute && i == 0 ? "Click or run /craftaction to execute" : string.Empty)}"
);
hoveredState = state;
}
lastState = state;
}
var rows = (int)Math.Max(1, MathF.Ceiling(Service.Configuration.SynthHelperMaxDisplayCount / itemsPerRow));
var rows = (int)
Math.Max(
1,
MathF.Ceiling(Service.Configuration.SynthHelperMaxDisplayCount / itemsPerRow)
);
for (var i = 0; i < rows; ++i)
{
if (count <= i * itemsPerRow)
@@ -381,7 +425,15 @@ public sealed unsafe class SynthHelper : Window, IDisposable
var iconHeight = ImGui.GetFrameHeight() * 1.75f;
var durationShift = iconHeight * .2f;
ImGui.Dummy(new(0, iconHeight + ImGui.GetStyle().ItemSpacing.Y + ImGui.GetTextLineHeight() - durationShift));
ImGui.Dummy(
new(
0,
iconHeight
+ ImGui.GetStyle().ItemSpacing.Y
+ ImGui.GetTextLineHeight()
- durationShift
)
);
ImGui.SameLine(0, 0);
var effects = state.ActiveEffects;
@@ -413,35 +465,92 @@ public sealed unsafe class SynthHelper : Window, IDisposable
}
}
var reliability = Macro.GetReliability(RecipeData!, Service.Configuration.SynthHelperDisplayOnlyFirstStep ? 0 : ^1);
var reliability = Macro.GetReliability(
RecipeData!,
Service.Configuration.SynthHelperDisplayOnlyFirstStep ? 0 : ^1
);
{
var mainBars = new List<DynamicBars.BarData>()
{
new("Progress", Colors.Progress, reliability.Progress, state.Progress, RecipeData!.RecipeInfo.MaxProgress),
new("Quality", Colors.Quality, reliability.Quality, state.Quality, RecipeData.RecipeInfo.MaxQuality),
new(
"Progress",
Colors.Progress,
reliability.Progress,
state.Progress,
RecipeData!.RecipeInfo.MaxProgress
),
new(
"Quality",
Colors.Quality,
reliability.Quality,
state.Quality,
RecipeData.RecipeInfo.MaxQuality
),
new("CP", Colors.CP, state.CP, CharacterStats!.CP),
};
if (RecipeData.RecipeInfo.MaxQuality <= 0)
mainBars.RemoveAt(1);
var halfBars = new List<DynamicBars.BarData>()
{
new("Durability", Colors.Durability, state.Durability, RecipeData.RecipeInfo.MaxDurability),
new(
"Durability",
Colors.Durability,
state.Durability,
RecipeData.RecipeInfo.MaxDurability
),
};
if (RecipeData.IsCollectable)
halfBars.Add(new("Collectability", Colors.Collectability, reliability.ParamScore, state.Collectability, state.MaxCollectability, RecipeData.CollectableThresholds, $"{state.Collectability}", $"{state.MaxCollectability:0}"));
halfBars.Add(
new(
"Collectability",
Colors.Collectability,
reliability.ParamScore,
state.Collectability,
state.MaxCollectability,
RecipeData.CollectableThresholds,
$"{state.Collectability}",
$"{state.MaxCollectability:0}"
)
);
else if (RecipeData.Recipe.RequiredQuality > 0)
{
var qualityPercent = (float)state.Quality / RecipeData.Recipe.RequiredQuality * 100;
halfBars.Add(new("Quality %", Colors.HQ, reliability.ParamScore, qualityPercent, 100, null, $"{qualityPercent:0}%", null));
halfBars.Add(
new(
"Quality %",
Colors.HQ,
reliability.ParamScore,
qualityPercent,
100,
null,
$"{qualityPercent:0}%",
null
)
);
}
else if (RecipeData.RecipeInfo.MaxQuality > 0)
halfBars.Add(new("HQ %", Colors.HQ, reliability.ParamScore, state.HQPercent, 100, null, $"{state.HQPercent}%", null));
halfBars.Add(
new(
"HQ %",
Colors.HQ,
reliability.ParamScore,
state.HQPercent,
100,
null,
$"{state.HQPercent}%",
null
)
);
if (halfBars.Count > 1)
{
var textSize = DynamicBars.GetTextSize(mainBars.Concat(halfBars));
DynamicBars.Draw(mainBars, textSize);
using var table = ImRaii.Table($"##{nameof(SynthHelper)}_halfbars", halfBars.Count, ImGuiTableFlags.NoPadOuterX | ImGuiTableFlags.SizingStretchSame);
using var table = ImRaii.Table(
$"##{nameof(SynthHelper)}_halfbars",
halfBars.Count,
ImGuiTableFlags.NoPadOuterX | ImGuiTableFlags.SizingStretchSame
);
if (table)
{
foreach (var bar in halfBars)
@@ -467,7 +576,9 @@ public sealed unsafe class SynthHelper : Window, IDisposable
using var _disabled = ImRaii.Disabled();
ImGui.Button("Stopping", new(-1, 0));
if (ImGui.IsItemHovered())
ImGuiUtils.TooltipWrapped("This might could a while, sorry! Please report if this takes longer than a second.");
ImGuiUtils.TooltipWrapped(
"This might could a while, sorry! Please report if this takes longer than a second."
);
}
else
{
@@ -480,13 +591,22 @@ public sealed unsafe class SynthHelper : Window, IDisposable
if (ImGui.Button("Retry", new(-1, 0)))
AttemptRetry();
if (ImGui.IsItemHovered())
ImGuiUtils.TooltipWrapped("Suggest a way to finish the crafting recipe. " +
"Results aren't perfect, and levels of success " +
"can vary wildly depending on the solver's settings.");
ImGuiUtils.TooltipWrapped(
"Suggest a way to finish the crafting recipe. "
+ "Results aren't perfect, and levels of success "
+ "can vary wildly depending on the solver's settings."
);
}
if (ImGui.Button("Open in Macro Editor", new(-1, 0)))
Service.Plugin.OpenMacroEditor(CharacterStats!, RecipeData!, new(Service.Objects.LocalPlayer!.StatusList), null, [], null);
Service.Plugin.OpenMacroEditor(
CharacterStats!,
RecipeData!,
new(Service.Objects.LocalPlayer!.StatusList),
null,
[],
null
);
}
public bool ExecuteNextAction()
@@ -519,13 +639,20 @@ public sealed unsafe class SynthHelper : Window, IDisposable
{
var gearStats = Gearsets.CalculateGearsetCurrentStats();
var container = InventoryManager.Instance()->GetInventoryContainer(InventoryType.EquippedItems);
var container = InventoryManager
.Instance()
->GetInventoryContainer(InventoryType.EquippedItems);
if (container == null)
throw new InvalidOperationException("Could not get inventory container");
var gearItems = Gearsets.GetGearsetItems(container);
var characterStats = Gearsets.CalculateCharacterStats(gearStats, gearItems, RecipeData.ClassJob.GetPlayerLevel(), RecipeData.ClassJob.CanPlayerUseManipulation());
var characterStats = Gearsets.CalculateCharacterStats(
gearStats,
gearItems,
RecipeData.ClassJob.GetPlayerLevel(),
RecipeData.ClassJob.CanPlayerUseManipulation()
);
if (characterStats != CharacterStats)
{
CharacterStats = characterStats;
@@ -554,8 +681,7 @@ public sealed unsafe class SynthHelper : Window, IDisposable
CurrentActionStates = CurrentState.ActionStates;
}
private void RefreshCurrentState() =>
CurrentState = GetCurrentState();
private void RefreshCurrentState() => CurrentState = GetCurrentState();
private SimulationState GetCurrentState()
{
@@ -602,7 +728,7 @@ public sealed unsafe class SynthHelper : Window, IDisposable
TrainedPerfection = HasEffect((ushort)EffectType.TrainedPerfection.StatusId()),
HeartAndSoul = HasEffect((ushort)EffectType.HeartAndSoul.StatusId()),
},
ActionStates = CurrentActionStates
ActionStates = CurrentActionStates,
};
}
@@ -638,7 +764,11 @@ public sealed unsafe class SynthHelper : Window, IDisposable
SolverTask.Start();
}
private int CalculateBestMacroTask(SimulationState state, CancellationToken token, bool hasDelineations)
private int CalculateBestMacroTask(
SimulationState state,
CancellationToken token,
bool hasDelineations
)
{
var config = Service.Configuration.SynthHelperSolverConfig;
var canUseDelineations = !Service.Configuration.CheckDelineations || hasDelineations;
@@ -663,12 +793,17 @@ public sealed unsafe class SynthHelper : Window, IDisposable
private void EnqueueAction(ActionType action)
{
var newSize = Macro.Enqueue(action, Service.Configuration.SynthHelperMaxDisplayCount);
if (newSize >= Service.Configuration.SynthHelperStepCount || newSize >= Service.Configuration.SynthHelperMaxDisplayCount)
if (
newSize >= Service.Configuration.SynthHelperStepCount
|| newSize >= Service.Configuration.SynthHelperMaxDisplayCount
)
SolverTask?.Cancel();
}
private static Sim CreateSim(in SimulationState state) =>
Service.Configuration.ConditionRandomness ? new Sim() { State = state } : new SimNoRandom() { State = state };
Service.Configuration.ConditionRandomness
? new Sim() { State = state }
: new SimNoRandom() { State = state };
public void Dispose()
{