Implement macro scoring and suggesting
This commit is contained in:
@@ -8,9 +8,6 @@ namespace Craftimizer.Plugin;
|
||||
|
||||
internal static class ImGuiUtils
|
||||
{
|
||||
public static float ButtonHeight =>
|
||||
ImGui.CalcTextSize("A").Y + (ImGui.GetStyle().FramePadding.Y * 2);
|
||||
|
||||
private static readonly Stack<(Vector2 Min, Vector2 Max)> GroupPanelLabelStack = new();
|
||||
|
||||
// Adapted from https://github.com/ocornut/imgui/issues/1496#issuecomment-655048353
|
||||
@@ -165,6 +162,15 @@ internal static class ImGuiUtils
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPos().X + (availWidth - width) / 2);
|
||||
}
|
||||
|
||||
public static void AlignMiddle(Vector2 size)
|
||||
{
|
||||
var availSize = ImGui.GetContentRegionAvail();
|
||||
if (availSize.X > size.X)
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPos().X + (availSize.X - size.X) / 2);
|
||||
if (availSize.Y > size.Y)
|
||||
ImGui.SetCursorPosY(ImGui.GetCursorPos().Y + (availSize.Y - size.Y) / 2);
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/67855985
|
||||
public static void TextCentered(string text)
|
||||
{
|
||||
@@ -172,6 +178,12 @@ internal static class ImGuiUtils
|
||||
ImGui.TextUnformatted(text);
|
||||
}
|
||||
|
||||
public static void TextMiddle(string text)
|
||||
{
|
||||
AlignMiddle(ImGui.CalcTextSize(text));
|
||||
ImGui.TextUnformatted(text);
|
||||
}
|
||||
|
||||
public static bool ButtonCentered(string text)
|
||||
{
|
||||
AlignCentered(ImGui.CalcTextSize(text).X + ImGui.GetStyle().FramePadding.Y * 2);
|
||||
|
||||
@@ -58,7 +58,7 @@ public sealed unsafe partial class Craft : Window, IDisposable
|
||||
var cogWidth = ImGui.CalcTextSize(FontAwesomeIcon.Cog.ToIconString()).X + (ImGui.GetStyle().FramePadding.X * 2);
|
||||
ImGui.PopFont();
|
||||
|
||||
DrawSolveButton(new(WindowWidth - ImGui.GetStyle().ItemSpacing.X - cogWidth, ImGuiUtils.ButtonHeight));
|
||||
DrawSolveButton(new(WindowWidth - ImGui.GetStyle().ItemSpacing.X - cogWidth, ImGui.GetFrameHeight()));
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGuiComponents.IconButton("synthSettingsButton", FontAwesomeIcon.Cog))
|
||||
|
||||
@@ -6,6 +6,7 @@ using Craftimizer.Solver;
|
||||
using Craftimizer.Utils;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Components;
|
||||
using Dalamud.Interface.GameFonts;
|
||||
using Dalamud.Interface.Internal;
|
||||
@@ -22,8 +23,11 @@ using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using ImGuiNET;
|
||||
using ImGuiScene;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ActionType = Craftimizer.Simulator.Actions.ActionType;
|
||||
using ClassJob = Craftimizer.Simulator.ClassJob;
|
||||
using CSRecipeNote = FFXIVClientStructs.FFXIV.Client.Game.UI.RecipeNote;
|
||||
@@ -55,11 +59,11 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
||||
public CharacterStats? CharacterStats { get; private set; }
|
||||
public CraftableStatus CraftStatus { get; private set; }
|
||||
|
||||
private TextureWrap ExpertBadge { get; }
|
||||
private TextureWrap CollectibleBadge { get; }
|
||||
private TextureWrap SplendorousBadge { get; }
|
||||
private TextureWrap SpecialistBadge { get; }
|
||||
private TextureWrap NoManipulationBadge { get; }
|
||||
private CancellationTokenSource? BestMacroTokenSource { get; set; }
|
||||
private Exception? BestMacroException { get; set; }
|
||||
public (Macro, SimulationState)? BestSavedMacro { get; private set; }
|
||||
public SolverSolution? BestSuggestedMacro { get; private set; }
|
||||
|
||||
private IDalamudTextureWrap ExpertBadge { get; }
|
||||
private IDalamudTextureWrap CollectibleBadge { get; }
|
||||
private IDalamudTextureWrap SplendorousBadge { get; }
|
||||
@@ -190,8 +194,10 @@ 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");
|
||||
}
|
||||
|
||||
@@ -210,7 +216,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
||||
var levelText = string.Empty;
|
||||
if (level != 0)
|
||||
levelText = SqText.ToLevelString(level);
|
||||
var imageSize = ImGuiUtils.ButtonHeight;
|
||||
var imageSize = ImGui.GetFrameHeight();
|
||||
bool hasSplendorous = false, hasSpecialist = false, shouldHaveManip = false;
|
||||
if (CraftStatus is not (CraftableStatus.LockedClassJob or CraftableStatus.WrongClassJob))
|
||||
{
|
||||
@@ -285,7 +291,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
||||
var (questGiver, questTerritory, questLocation, mapPayload) = ResolveLevelData(unlockQuest.IssuerLocation.Row);
|
||||
|
||||
var unlockText = $"Unlock it from {questGiver}";
|
||||
ImGuiUtils.AlignCentered(ImGui.CalcTextSize(unlockText).X + 5 + ImGuiUtils.ButtonHeight);
|
||||
ImGuiUtils.AlignCentered(ImGui.CalcTextSize(unlockText).X + 5 + ImGui.GetFrameHeight());
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.Text(unlockText);
|
||||
ImGui.SameLine(0, 5);
|
||||
@@ -319,7 +325,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
||||
var (vendorName, vendorTerritory, vendorLoation, mapPayload) = ResolveLevelData(5891399);
|
||||
|
||||
var unlockText = $"Trade a Soul of the Crafter to {vendorName}";
|
||||
ImGuiUtils.AlignCentered(ImGui.CalcTextSize(unlockText).X + 5 + ImGuiUtils.ButtonHeight);
|
||||
ImGuiUtils.AlignCentered(ImGui.CalcTextSize(unlockText).X + 5 + ImGui.GetFrameHeight());
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.Text(unlockText);
|
||||
ImGui.SameLine(0, 5);
|
||||
@@ -335,7 +341,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
||||
{
|
||||
var item = RecipeData.Recipe.ItemRequired.Value!;
|
||||
var itemName = item.Name.ToDalamudString().ToString();
|
||||
var imageSize = ImGuiUtils.ButtonHeight;
|
||||
var imageSize = ImGui.GetFrameHeight();
|
||||
|
||||
ImGuiUtils.TextCentered($"You are missing the required equipment.");
|
||||
ImGuiUtils.AlignCentered(imageSize + 5 + ImGui.CalcTextSize(itemName).X);
|
||||
@@ -350,7 +356,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
||||
var status = RecipeData.Recipe.StatusRequired.Value!;
|
||||
var statusName = status.Name.ToDalamudString().ToString();
|
||||
var statusIcon = Service.IconManager.GetIcon(status.Icon);
|
||||
var imageSize = new Vector2(ImGuiUtils.ButtonHeight * statusIcon.Width / statusIcon.Height, ImGuiUtils.ButtonHeight);
|
||||
var imageSize = new Vector2(ImGui.GetFrameHeight() * statusIcon.Width / statusIcon.Height, ImGui.GetFrameHeight());
|
||||
|
||||
ImGuiUtils.TextCentered($"You are missing the required status effect.");
|
||||
ImGuiUtils.AlignCentered(imageSize.X + 5 + ImGui.CalcTextSize(statusName).X);
|
||||
@@ -416,8 +422,8 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
||||
var textLevel = SqText.ToLevelString(RecipeData.RecipeInfo.ClassJobLevel);
|
||||
var isExpert = RecipeData.RecipeInfo.IsExpert;
|
||||
var isCollectable = RecipeData.Recipe.ItemResult.Value!.IsCollectable;
|
||||
var imageSize = ImGuiUtils.ButtonHeight;
|
||||
var textSize = ImGui.CalcTextSize("A").Y;
|
||||
var imageSize = ImGui.GetFrameHeight();
|
||||
var textSize = ImGui.GetFontSize();
|
||||
var badgeSize = new Vector2(textSize * ExpertBadge.Width / ExpertBadge.Height, textSize);
|
||||
var badgeOffset = (imageSize - badgeSize.Y) / 2;
|
||||
|
||||
@@ -488,6 +494,31 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMacro(string macroName, (List<ActionType> Actions, SimulationState State)? macro)
|
||||
{
|
||||
var availWidth = ImGui.GetContentRegionAvail().X;
|
||||
|
||||
using var window = ImRaii.Child(macroName, new(availWidth, 2 * ImGui.GetFrameHeight()), false);
|
||||
|
||||
if (macro == null)
|
||||
{
|
||||
if (BestMacroException == null)
|
||||
ImGuiUtils.TextMiddle("Calculating...");
|
||||
else
|
||||
{
|
||||
ImGui.AlignTextToFramePadding();
|
||||
using (var color = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed))
|
||||
ImGuiUtils.TextCentered("An exception occurred");
|
||||
if (ImGuiUtils.ButtonCentered("Copy Error Message"))
|
||||
ImGui.SetClipboardText(BestMacroException.ToString());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ImGuiUtils.TextCentered($"{macro.Value.Actions.Count} Actions");
|
||||
ImGuiUtils.TextCentered($"{macro.Value.State.Quality} Quality");
|
||||
}
|
||||
|
||||
private static void DrawRequiredStatsTable(int current, int required)
|
||||
{
|
||||
if (current >= required)
|
||||
@@ -586,7 +617,71 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
||||
|
||||
private void CalculateBestMacros()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
BestMacroTokenSource?.Cancel();
|
||||
BestMacroTokenSource = new();
|
||||
BestMacroException = null;
|
||||
BestSavedMacro = null;
|
||||
BestSuggestedMacro = null;
|
||||
|
||||
var token = BestMacroTokenSource.Token;
|
||||
_ = Task.Run(() => CalculateBestMacrosTask(token), token)
|
||||
.ContinueWith(t =>
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
t.Exception!.Flatten().Handle(ex => ex is TaskCanceledException or OperationCanceledException);
|
||||
}
|
||||
catch (AggregateException e)
|
||||
{
|
||||
BestMacroException = e;
|
||||
Log.Error(e, "Calculating macros failed");
|
||||
}
|
||||
}, TaskContinuationOptions.OnlyOnFaulted);
|
||||
}
|
||||
|
||||
private void CalculateBestMacrosTask(CancellationToken token)
|
||||
{
|
||||
var input = new SimulationInput(CharacterStats!, RecipeData!.RecipeInfo);
|
||||
var state = new SimulationState(input);
|
||||
var config = Service.Configuration.SimulatorSolverConfig;
|
||||
var mctsConfig = new MCTSConfig(config);
|
||||
var simulator = new Solver.Simulator(state, mctsConfig.MaxStepCount);
|
||||
List<Macro> macros = new(Service.Configuration.Macros);
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var bestSaved = macros
|
||||
.Select(macro =>
|
||||
{
|
||||
var (resp, outState, failedIdx) = simulator.ExecuteMultiple(state, macro.Actions);
|
||||
outState.ActionCount = macro.Actions.Count;
|
||||
var score = SimulationNode.CalculateScoreForState(outState, simulator.CompletionState, mctsConfig) ?? 0;
|
||||
if (resp != ActionResponse.SimulationComplete)
|
||||
{
|
||||
if (failedIdx != -1)
|
||||
score /= 2;
|
||||
}
|
||||
return (macro, outState, score);
|
||||
})
|
||||
.MaxBy(m => m.score);
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
BestSavedMacro = (bestSaved.macro, bestSaved.outState);
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var solver = new Solver.Solver(config, state) { Token = token };
|
||||
solver.OnLog += Log.Debug;
|
||||
solver.Start();
|
||||
var solution = solver.GetTask().GetAwaiter().GetResult();
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
BestSuggestedMacro = solution;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -13,7 +13,7 @@ public class Settings : Window
|
||||
private static Configuration Config => Service.Configuration;
|
||||
|
||||
private const int OptionWidth = 200;
|
||||
private static Vector2 OptionButtonSize => new(OptionWidth, ImGuiUtils.ButtonHeight);
|
||||
private static Vector2 OptionButtonSize => new(OptionWidth, ImGui.GetFrameHeight());
|
||||
|
||||
public const string TabGeneral = "General";
|
||||
public const string TabSimulator = "Simulator";
|
||||
|
||||
@@ -310,8 +310,8 @@ public sealed partial class Simulator : Window, IDisposable
|
||||
var totalWidth = drawParams.Total;
|
||||
var halfWidth = (totalWidth - ImGui.GetStyle().ItemSpacing.X) / 2f;
|
||||
var quarterWidth = (halfWidth - ImGui.GetStyle().ItemSpacing.X) / 2f;
|
||||
var halfButtonSize = new Vector2(halfWidth, ImGuiUtils.ButtonHeight);
|
||||
var quarterButtonSize = new Vector2(quarterWidth, ImGuiUtils.ButtonHeight);
|
||||
var halfButtonSize = new Vector2(halfWidth, ImGui.GetFrameHeight());
|
||||
var quarterButtonSize = new Vector2(quarterWidth, ImGui.GetFrameHeight());
|
||||
|
||||
var conditionRandomnessText = "Condition Randomness";
|
||||
var conditionRandomness = Config.ConditionRandomness;
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ public sealed class Solver : IDisposable
|
||||
}
|
||||
catch (AggregateException e)
|
||||
{
|
||||
e.Handle(ex => ex is OperationCanceledException);
|
||||
e.Flatten().Handle(ex => ex is OperationCanceledException);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user