From 1da2acc5c565b217a9e5802f21d30a063ebf4321 Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Tue, 10 Oct 2023 18:38:01 -0700 Subject: [PATCH] Finish crafting log helper window --- Craftimizer/Configuration.cs | 1 + Craftimizer/ImGuiUtils.cs | 128 +++++++++++++++++-- Craftimizer/Utils/RecipeNote.cs | 2 +- Craftimizer/Windows/RecipeNote.cs | 169 ++++++++++++++++++++++--- Craftimizer/Windows/Settings.cs | 10 ++ Craftimizer/Windows/SimulatorDrawer.cs | 10 +- Solver/SimulationNode.cs | 2 +- 7 files changed, 288 insertions(+), 34 deletions(-) diff --git a/Craftimizer/Configuration.cs b/Craftimizer/Configuration.cs index b0c00a6..c37a89e 100644 --- a/Craftimizer/Configuration.cs +++ b/Craftimizer/Configuration.cs @@ -26,6 +26,7 @@ public class Configuration : IPluginConfiguration public SolverConfig SimulatorSolverConfig { get; set; } = SolverConfig.SimulatorDefault; public SolverConfig SynthHelperSolverConfig { get; set; } = SolverConfig.SynthHelperDefault; public bool EnableSynthHelper { get; set; } = true; + public bool ShowOptimalMacroStat { get; set; } = true; public int SynthHelperStepCount { get; set; } = 5; public Simulator.Simulator CreateSimulator(SimulationState state) => diff --git a/Craftimizer/ImGuiUtils.cs b/Craftimizer/ImGuiUtils.cs index c50b7a7..24132e2 100644 --- a/Craftimizer/ImGuiUtils.cs +++ b/Craftimizer/ImGuiUtils.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Numerics; + namespace Craftimizer.Plugin; internal static class ImGuiUtils @@ -124,6 +125,96 @@ internal static class ImGuiUtils ImGui.EndGroup(); } + private static Vector2 UnitCircle(float theta) + { + var (s, c) = MathF.SinCos(theta); + // SinCos positive y is downwards, but we want it upwards for ImGui + return new Vector2(c, -s); + } + + private static float Lerp(float a, float b, float t) => + MathF.FusedMultiplyAdd(b - a, t, a); + + private static void ArcSegment(Vector2 o, Vector2 prev, Vector2 cur, Vector2? next, float radius, float ratio, uint color) + { + var d = ImGui.GetWindowDrawList(); + + d.PathLineTo(o + cur * radius); + d.PathLineTo(o + prev * radius); + d.PathLineTo(o + prev * radius * ratio); + d.PathLineTo(o + cur * radius * ratio); + if (next is { } nextValue) + d.PathLineTo(o + nextValue * radius); + d.PathFillConvex(color); + } + + 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) + (startAngle, endAngle) = (endAngle, startAngle); + + var offset = ImGui.GetCursorScreenPos() + new Vector2(radius); + + var segments = ImGui.GetWindowDrawList()._CalcCircleAutoSegmentCount(radius * 2); + var incrementAngle = MathF.Tau / segments; + var isFullCircle = (endAngle - startAngle) % MathF.Tau == 0; + + var prevA = startAngle; + var prev = UnitCircle(prevA); + for (var i = 1; i <= segments; ++i) + { + var a = startAngle + incrementAngle * i; + var cur = UnitCircle(a); + + var nextA = a + incrementAngle; + var next = UnitCircle(nextA); + + // full segment is background + if (prevA >= endAngle) + { + // don't overlap with the first segment + if (i == segments && !isFullCircle) + ArcSegment(offset, prev, cur, null, radius, ratio, backgroundColor); + else + ArcSegment(offset, prev, cur, next, radius, ratio, backgroundColor); + } + // segment is partially filled + else if (a > endAngle && !isFullCircle) + { + // we split the drawing in two + var end = UnitCircle(endAngle); + ArcSegment(offset, prev, end, null, radius, ratio, filledColor); + ArcSegment(offset, end, cur, next, radius, ratio, backgroundColor); + // set the previous segment to endAngle + a = endAngle; + cur = end; + } + // full segment is filled + else + { + // if the next segment will be partially filled, the next segment will be the endAngle + if (nextA > endAngle && !isFullCircle) + { + var end = UnitCircle(endAngle); + ArcSegment(offset, prev, cur, end, radius, ratio, filledColor); + } + else + ArcSegment(offset, prev, cur, next, radius, ratio, filledColor); + } + prevA = a; + prev = cur; + } + + if (addDummy) + ImGui.Dummy(new Vector2(radius * 2)); + } + + public static void ArcProgress(float value, float radiusInner, float radiusOuter, uint backgroundColor, uint filledColor) + { + Arc(MathF.PI / 2, MathF.PI / 2 - MathF.Tau * Math.Clamp(value, 0, 1), radiusInner, radiusOuter, backgroundColor, filledColor); + } + public static bool IconButtonSized(FontAwesomeIcon icon, Vector2 size) { ImGui.PushFont(UiBuilder.IconFont); @@ -155,16 +246,18 @@ internal static class ImGuiUtils } } - public static void AlignCentered(float width) + public static void AlignCentered(float width, float availWidth = default) { - var availWidth = ImGui.GetContentRegionAvail().X; + if (availWidth == default) + availWidth = ImGui.GetContentRegionAvail().X; if (availWidth > width) ImGui.SetCursorPosX(ImGui.GetCursorPos().X + (availWidth - width) / 2); } - public static void AlignMiddle(Vector2 size) + public static void AlignMiddle(Vector2 size, Vector2 availSize = default) { - var availSize = ImGui.GetContentRegionAvail(); + if (availSize == default) + availSize = ImGui.GetContentRegionAvail(); if (availSize.X > size.X) ImGui.SetCursorPosX(ImGui.GetCursorPos().X + (availSize.X - size.X) / 2); if (availSize.Y > size.Y) @@ -172,21 +265,34 @@ internal static class ImGuiUtils } // https://stackoverflow.com/a/67855985 - public static void TextCentered(string text) + public static void TextCentered(string text, float availWidth = default) { - AlignCentered(ImGui.CalcTextSize(text).X); + AlignCentered(ImGui.CalcTextSize(text).X, availWidth); ImGui.TextUnformatted(text); } - public static void TextMiddle(string text) + public static void TextMiddle(string text, Vector2 availSize = default) { - AlignMiddle(ImGui.CalcTextSize(text)); + AlignMiddle(ImGui.CalcTextSize(text), availSize); ImGui.TextUnformatted(text); } - public static bool ButtonCentered(string text) + public static void TextMiddleNewLine(string text, Vector2 availSize) { - AlignCentered(ImGui.CalcTextSize(text).X + ImGui.GetStyle().FramePadding.Y * 2); - return ImGui.Button(text); + if (availSize == default) + availSize = ImGui.GetContentRegionAvail(); + var c = ImGui.GetCursorPos(); + AlignMiddle(ImGui.CalcTextSize(text), availSize); + ImGui.TextUnformatted(text); + ImGui.SetCursorPos(c + new Vector2(0, availSize.Y + ImGui.GetStyle().ItemSpacing.Y - 1)); + } + + public static bool ButtonCentered(string text, Vector2 buttonSize = default) + { + var buttonWidth = buttonSize.X; + if (buttonSize == default) + buttonWidth = ImGui.CalcTextSize(text).X + ImGui.GetStyle().FramePadding.X * 2; + AlignCentered(buttonWidth); + return ImGui.Button(text, buttonSize); } } diff --git a/Craftimizer/Utils/RecipeNote.cs b/Craftimizer/Utils/RecipeNote.cs index 3a372d7..6d4f4b3 100644 --- a/Craftimizer/Utils/RecipeNote.cs +++ b/Craftimizer/Utils/RecipeNote.cs @@ -45,7 +45,7 @@ public record RecipeData RLvl = (int)Table.RowId, ConditionsFlag = Table.ConditionsFlag, MaxDurability = Table.Durability * Recipe.DurabilityFactor / 100, - MaxQuality = (int)Table.Quality * Recipe.QualityFactor / 100, + MaxQuality = (Recipe.CanHq || Recipe.IsExpert) ? (int)Table.Quality * Recipe.QualityFactor / 100 : 0, MaxProgress = Table.Difficulty * Recipe.DifficultyFactor / 100, QualityModifier = Table.QualityModifier, QualityDivider = Table.QualityDivider, diff --git a/Craftimizer/Windows/RecipeNote.cs b/Craftimizer/Windows/RecipeNote.cs index f4d4f3e..02ab6d4 100644 --- a/Craftimizer/Windows/RecipeNote.cs +++ b/Craftimizer/Windows/RecipeNote.cs @@ -10,8 +10,6 @@ using Dalamud.Interface.Colors; using Dalamud.Interface.Components; using Dalamud.Interface.GameFonts; using Dalamud.Interface.Internal; -using Dalamud.Interface.Style; -using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Windowing; using Dalamud.Utility; @@ -21,7 +19,6 @@ using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; -using ImGuiScene; using System; using System.Collections.Generic; using System.Linq; @@ -193,12 +190,37 @@ public sealed unsafe class RecipeNote : Window, IDisposable ImGui.Separator(); - ImGuiUtils.TextCentered("Best Saved Macro"); - DrawMacro("savedMacro", BestSavedMacro == null ? null : (BestSavedMacro.Value.Item1.Actions, BestSavedMacro.Value.Item2)); - ImGuiUtils.ButtonCentered("View Saved Macros"); - ImGuiUtils.TextCentered("Suggested Macro"); - DrawMacro("suggestedMacro", BestSuggestedMacro == null ? null : (BestSuggestedMacro.Value.Actions, BestSuggestedMacro.Value.State)); - ImGuiUtils.ButtonCentered("Open Simulator"); + { + using var table = ImRaii.Table("macros", 1, ImGuiTableFlags.SizingStretchSame); + if (table) + { + ImGui.TableNextColumn(); + + ImGui.AlignTextToFramePadding(); + ImGuiUtils.TextCentered("Best Saved Macro"); + if (BestSavedMacro is { } savedMacro) + { + ImGuiUtils.TextCentered(savedMacro.Item1.Name); + DrawMacro("savedMacro", (savedMacro.Item1.Actions, savedMacro.Item2)); + } + else + { + ImGui.Text(""); + DrawMacro("savedMacro", null); + } + ImGui.Button("View Saved Macros", new(-1, 0)); + + ImGui.Separator(); + + ImGui.AlignTextToFramePadding(); + ImGuiUtils.TextCentered("Suggested Macro"); + if (BestSuggestedMacro is { } suggestedMacro) + DrawMacro("suggestedMacro", (suggestedMacro.Actions, suggestedMacro.State)); + else + DrawMacro("suggestedMacro", null); + ImGui.Button("Open Simulator", new(-1, 0)); + } + } } private void DrawCharacterStats() @@ -494,16 +516,16 @@ public sealed unsafe class RecipeNote : Window, IDisposable } } - private void DrawMacro(string macroName, (List Actions, SimulationState State)? macro) + private void DrawMacro(string imGuiId, (List Actions, SimulationState State)? macroValue) { - var availWidth = ImGui.GetContentRegionAvail().X; + //using var window = ImRaii.Child(imGuiId, new(-1, (name != null ? ImGui.GetTextLineHeightWithSpacing() : 0) + 2 * ImGui.GetFrameHeightWithSpacing()), false, ImGuiWindowFlags.AlwaysAutoResize); - using var window = ImRaii.Child(macroName, new(availWidth, 2 * ImGui.GetFrameHeight()), false); + var windowHeight = 2 * ImGui.GetFrameHeightWithSpacing(); - if (macro == null) + if (macroValue == null) { if (BestMacroException == null) - ImGuiUtils.TextMiddle("Calculating..."); + ImGuiUtils.TextMiddleNewLine("Calculating...", new(ImGui.GetContentRegionAvail().X, windowHeight + 1 + ImGui.GetStyle().ItemSpacing.Y)); else { ImGui.AlignTextToFramePadding(); @@ -515,8 +537,123 @@ public sealed unsafe class RecipeNote : Window, IDisposable return; } - ImGuiUtils.TextCentered($"{macro.Value.Actions.Count} Actions"); - ImGuiUtils.TextCentered($"{macro.Value.State.Quality} Quality"); + var macro = macroValue!.Value; + + using var table = ImRaii.Table("table", 3, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.SizingStretchSame); + if (table) + { + ImGui.TableSetupColumn("desc", ImGuiTableColumnFlags.WidthFixed, 0); + ImGui.TableSetupColumn("actions", ImGuiTableColumnFlags.WidthFixed, 0); + ImGui.TableSetupColumn("steps", ImGuiTableColumnFlags.WidthStretch); + + ImGui.TableNextRow(ImGuiTableRowFlags.None, windowHeight); + ImGui.TableNextColumn(); + + var spacing = ImGui.GetStyle().ItemSpacing.Y; + var miniRowHeight = (windowHeight - spacing) / 2f; + + //ImGui.Text($"{macro.Actions.Count}"); + { + if (Service.Configuration.ShowOptimalMacroStat) + { + var progressHeight = windowHeight; + if (macro.State.Progress >= macro.State.Input.Recipe.MaxProgress && macro.State.Input.Recipe.MaxQuality > 0) + { + ImGuiUtils.ArcProgress( + (float)macro.State.Quality / macro.State.Input.Recipe.MaxQuality, + progressHeight / 2f, + .5f, + ImGui.GetColorU32(ImGuiCol.TableBorderLight), + ImGui.GetColorU32(Plugin.Windows.Simulator.QualityColor)); + if (ImGui.IsItemHovered()) + ImGui.SetTooltip($"Quality: {macro.State.Quality} / {macro.State.Input.Recipe.MaxQuality}"); + } + else + { + ImGuiUtils.ArcProgress( + (float)macro.State.Progress / macro.State.Input.Recipe.MaxProgress, + progressHeight / 2f, + .5f, + ImGui.GetColorU32(ImGuiCol.TableBorderLight), + ImGui.GetColorU32(Plugin.Windows.Simulator.ProgressColor)); + if (ImGui.IsItemHovered()) + ImGui.SetTooltip($"Progress: {macro.State.Progress} / {macro.State.Input.Recipe.MaxProgress}"); + } + } + else + { + ImGuiUtils.ArcProgress( + (float)macro.State.Progress / macro.State.Input.Recipe.MaxProgress, + miniRowHeight / 2f, + .5f, + ImGui.GetColorU32(ImGuiCol.TableBorderLight), + ImGui.GetColorU32(Plugin.Windows.Simulator.ProgressColor)); + if (ImGui.IsItemHovered()) + ImGui.SetTooltip($"Progress: {macro.State.Progress} / {macro.State.Input.Recipe.MaxProgress}"); + + ImGui.SameLine(0, spacing); + ImGuiUtils.ArcProgress( + (float)macro.State.Quality / macro.State.Input.Recipe.MaxQuality, + miniRowHeight / 2f, + .5f, + ImGui.GetColorU32(ImGuiCol.TableBorderLight), + ImGui.GetColorU32(Plugin.Windows.Simulator.QualityColor)); + if (ImGui.IsItemHovered()) + ImGui.SetTooltip($"Quality: {macro.State.Quality} / {macro.State.Input.Recipe.MaxQuality}"); + + ImGuiUtils.ArcProgress((float)macro.State.Durability / macro.State.Input.Recipe.MaxDurability, + miniRowHeight / 2f, + .5f, + ImGui.GetColorU32(ImGuiCol.TableBorderLight), + ImGui.GetColorU32(Plugin.Windows.Simulator.DurabilityColor)); + if (ImGui.IsItemHovered()) + ImGui.SetTooltip($"Remaining Durability: {macro.State.Durability} / {macro.State.Input.Recipe.MaxDurability}"); + + ImGui.SameLine(0, spacing); + ImGuiUtils.ArcProgress( + (float)macro.State.CP / macro.State.Input.Stats.CP, + miniRowHeight / 2f, + .5f, + ImGui.GetColorU32(ImGuiCol.TableBorderLight), + ImGui.GetColorU32(Plugin.Windows.Simulator.CPColor)); + if (ImGui.IsItemHovered()) + ImGui.SetTooltip($"Remaining CP: {macro.State.CP} / {macro.State.Input.Stats.CP}"); + } + } + + ImGui.TableNextColumn(); + { + ImGuiUtils.TextMiddleNewLine($"{macro.Actions.Count}", new(miniRowHeight)); + if (ImGui.IsItemHovered()) + ImGui.SetTooltip($"{macro.Actions.Count} Step{(macro.Actions.Count != 1 ? "s" : "")}"); + using (var iconFont = ImRaii.PushFont(UiBuilder.IconFont)) + if (ImGuiUtils.ButtonCentered(FontAwesomeIcon.Copy.ToIconString(), new(miniRowHeight))) + { + throw new NotImplementedException(); + } + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Copy to Clipboard"); + } + + ImGui.TableNextColumn(); + { + var itemsPerRow = (int)MathF.Ceiling((ImGui.GetContentRegionAvail().X + spacing) / (miniRowHeight + spacing)); + var itemCount = Math.Min(macro.Actions.Count, itemsPerRow * 2); + for (var i = 0; i < itemsPerRow * 2; i++) + { + if (i % itemsPerRow != 0) + ImGui.SameLine(0, spacing); + if (i < itemCount) + { + ImGui.Image(macro.Actions[i].GetIcon(RecipeData!.ClassJob).ImGuiHandle, new(miniRowHeight)); + if (ImGui.IsItemHovered()) + ImGui.SetTooltip(macro.Actions[i].GetName(RecipeData!.ClassJob)); + } + else + ImGui.Dummy(new(miniRowHeight)); + } + } + } } private static void DrawRequiredStatsTable(int current, int required) diff --git a/Craftimizer/Windows/Settings.cs b/Craftimizer/Windows/Settings.cs index 7fe8ce3..a41261e 100644 --- a/Craftimizer/Windows/Settings.cs +++ b/Craftimizer/Windows/Settings.cs @@ -154,6 +154,16 @@ public class Settings : Window ref isDirty ); + DrawOption( + "Show Only One Macro Stat", + "Only one stat will be shown for a macro. If a craft will be finished, quality\n" + + "is shown. Otherwise, progress is shown. Durability and remaining CP will be\n" + + "hidden.", + Config.ShowOptimalMacroStat, + v => Config.ShowOptimalMacroStat = v, + ref isDirty + ); + if (isDirty) Config.Save(); diff --git a/Craftimizer/Windows/SimulatorDrawer.cs b/Craftimizer/Windows/SimulatorDrawer.cs index af6a8da..615eadc 100644 --- a/Craftimizer/Windows/SimulatorDrawer.cs +++ b/Craftimizer/Windows/SimulatorDrawer.cs @@ -24,11 +24,11 @@ public sealed partial class Simulator : Window, IDisposable private static readonly Vector2 ProgressBarSizeOld = new(200, 20); public static readonly Vector2 TooltipProgressBarSize = new(100, 5); - private static readonly Vector4 ProgressColor = new(0.44f, 0.65f, 0.18f, 1f); - private static readonly Vector4 QualityColor = new(0.26f, 0.71f, 0.69f, 1f); - private static readonly Vector4 DurabilityColor = new(0.13f, 0.52f, 0.93f, 1f); - private static readonly Vector4 HQColor = new(0.592f, 0.863f, 0.376f, 1f); - private static readonly Vector4 CPColor = new(0.63f, 0.37f, 0.75f, 1f); + public static readonly Vector4 ProgressColor = new(0.44f, 0.65f, 0.18f, 1f); + public static readonly Vector4 QualityColor = new(0.26f, 0.71f, 0.69f, 1f); + public static readonly Vector4 DurabilityColor = new(0.13f, 0.52f, 0.93f, 1f); + public static readonly Vector4 HQColor = new(0.592f, 0.863f, 0.376f, 1f); + public static readonly Vector4 CPColor = new(0.63f, 0.37f, 0.75f, 1f); private static readonly Vector4 BadActionImageTint = new(1f, .5f, .5f, 1f); private static readonly Vector4 BadActionImageColor = new(1f, .3f, .3f, 1f); diff --git a/Solver/SimulationNode.cs b/Solver/SimulationNode.cs index e17c35f..3f8639e 100644 --- a/Solver/SimulationNode.cs +++ b/Solver/SimulationNode.cs @@ -47,7 +47,7 @@ public struct SimulationNode return null; static float Apply(float bonus, float value, float target) => - bonus * Math.Min(1f, value / target); + bonus * (target > 0 ? Math.Min(1f, value / target) : 1); var progressScore = Apply( config.ScoreProgress,