Implement macro scoring and suggesting
This commit is contained in:
@@ -8,9 +8,6 @@ namespace Craftimizer.Plugin;
|
|||||||
|
|
||||||
internal static class ImGuiUtils
|
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();
|
private static readonly Stack<(Vector2 Min, Vector2 Max)> GroupPanelLabelStack = new();
|
||||||
|
|
||||||
// Adapted from https://github.com/ocornut/imgui/issues/1496#issuecomment-655048353
|
// 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);
|
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
|
// https://stackoverflow.com/a/67855985
|
||||||
public static void TextCentered(string text)
|
public static void TextCentered(string text)
|
||||||
{
|
{
|
||||||
@@ -172,6 +178,12 @@ internal static class ImGuiUtils
|
|||||||
ImGui.TextUnformatted(text);
|
ImGui.TextUnformatted(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void TextMiddle(string text)
|
||||||
|
{
|
||||||
|
AlignMiddle(ImGui.CalcTextSize(text));
|
||||||
|
ImGui.TextUnformatted(text);
|
||||||
|
}
|
||||||
|
|
||||||
public static bool ButtonCentered(string text)
|
public static bool ButtonCentered(string text)
|
||||||
{
|
{
|
||||||
AlignCentered(ImGui.CalcTextSize(text).X + ImGui.GetStyle().FramePadding.Y * 2);
|
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);
|
var cogWidth = ImGui.CalcTextSize(FontAwesomeIcon.Cog.ToIconString()).X + (ImGui.GetStyle().FramePadding.X * 2);
|
||||||
ImGui.PopFont();
|
ImGui.PopFont();
|
||||||
|
|
||||||
DrawSolveButton(new(WindowWidth - ImGui.GetStyle().ItemSpacing.X - cogWidth, ImGuiUtils.ButtonHeight));
|
DrawSolveButton(new(WindowWidth - ImGui.GetStyle().ItemSpacing.X - cogWidth, ImGui.GetFrameHeight()));
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGuiComponents.IconButton("synthSettingsButton", FontAwesomeIcon.Cog))
|
if (ImGuiComponents.IconButton("synthSettingsButton", FontAwesomeIcon.Cog))
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using Craftimizer.Solver;
|
|||||||
using Craftimizer.Utils;
|
using Craftimizer.Utils;
|
||||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.Components;
|
using Dalamud.Interface.Components;
|
||||||
using Dalamud.Interface.GameFonts;
|
using Dalamud.Interface.GameFonts;
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
@@ -22,8 +23,11 @@ using FFXIVClientStructs.FFXIV.Component.GUI;
|
|||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using ImGuiScene;
|
using ImGuiScene;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using ActionType = Craftimizer.Simulator.Actions.ActionType;
|
using ActionType = Craftimizer.Simulator.Actions.ActionType;
|
||||||
using ClassJob = Craftimizer.Simulator.ClassJob;
|
using ClassJob = Craftimizer.Simulator.ClassJob;
|
||||||
using CSRecipeNote = FFXIVClientStructs.FFXIV.Client.Game.UI.RecipeNote;
|
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 CharacterStats? CharacterStats { get; private set; }
|
||||||
public CraftableStatus CraftStatus { get; private set; }
|
public CraftableStatus CraftStatus { get; private set; }
|
||||||
|
|
||||||
private TextureWrap ExpertBadge { get; }
|
private CancellationTokenSource? BestMacroTokenSource { get; set; }
|
||||||
private TextureWrap CollectibleBadge { get; }
|
private Exception? BestMacroException { get; set; }
|
||||||
private TextureWrap SplendorousBadge { get; }
|
public (Macro, SimulationState)? BestSavedMacro { get; private set; }
|
||||||
private TextureWrap SpecialistBadge { get; }
|
public SolverSolution? BestSuggestedMacro { get; private set; }
|
||||||
private TextureWrap NoManipulationBadge { get; }
|
|
||||||
private IDalamudTextureWrap ExpertBadge { get; }
|
private IDalamudTextureWrap ExpertBadge { get; }
|
||||||
private IDalamudTextureWrap CollectibleBadge { get; }
|
private IDalamudTextureWrap CollectibleBadge { get; }
|
||||||
private IDalamudTextureWrap SplendorousBadge { get; }
|
private IDalamudTextureWrap SplendorousBadge { get; }
|
||||||
@@ -190,8 +194,10 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
ImGuiUtils.TextCentered("Best Saved Macro");
|
ImGuiUtils.TextCentered("Best Saved Macro");
|
||||||
|
DrawMacro("savedMacro", BestSavedMacro == null ? null : (BestSavedMacro.Value.Item1.Actions, BestSavedMacro.Value.Item2));
|
||||||
ImGuiUtils.ButtonCentered("View Saved Macros");
|
ImGuiUtils.ButtonCentered("View Saved Macros");
|
||||||
ImGuiUtils.TextCentered("Suggested Macro");
|
ImGuiUtils.TextCentered("Suggested Macro");
|
||||||
|
DrawMacro("suggestedMacro", BestSuggestedMacro == null ? null : (BestSuggestedMacro.Value.Actions, BestSuggestedMacro.Value.State));
|
||||||
ImGuiUtils.ButtonCentered("Open Simulator");
|
ImGuiUtils.ButtonCentered("Open Simulator");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +216,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
var levelText = string.Empty;
|
var levelText = string.Empty;
|
||||||
if (level != 0)
|
if (level != 0)
|
||||||
levelText = SqText.ToLevelString(level);
|
levelText = SqText.ToLevelString(level);
|
||||||
var imageSize = ImGuiUtils.ButtonHeight;
|
var imageSize = ImGui.GetFrameHeight();
|
||||||
bool hasSplendorous = false, hasSpecialist = false, shouldHaveManip = false;
|
bool hasSplendorous = false, hasSpecialist = false, shouldHaveManip = false;
|
||||||
if (CraftStatus is not (CraftableStatus.LockedClassJob or CraftableStatus.WrongClassJob))
|
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 (questGiver, questTerritory, questLocation, mapPayload) = ResolveLevelData(unlockQuest.IssuerLocation.Row);
|
||||||
|
|
||||||
var unlockText = $"Unlock it from {questGiver}";
|
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.AlignTextToFramePadding();
|
||||||
ImGui.Text(unlockText);
|
ImGui.Text(unlockText);
|
||||||
ImGui.SameLine(0, 5);
|
ImGui.SameLine(0, 5);
|
||||||
@@ -319,7 +325,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
var (vendorName, vendorTerritory, vendorLoation, mapPayload) = ResolveLevelData(5891399);
|
var (vendorName, vendorTerritory, vendorLoation, mapPayload) = ResolveLevelData(5891399);
|
||||||
|
|
||||||
var unlockText = $"Trade a Soul of the Crafter to {vendorName}";
|
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.AlignTextToFramePadding();
|
||||||
ImGui.Text(unlockText);
|
ImGui.Text(unlockText);
|
||||||
ImGui.SameLine(0, 5);
|
ImGui.SameLine(0, 5);
|
||||||
@@ -335,7 +341,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
{
|
{
|
||||||
var item = RecipeData.Recipe.ItemRequired.Value!;
|
var item = RecipeData.Recipe.ItemRequired.Value!;
|
||||||
var itemName = item.Name.ToDalamudString().ToString();
|
var itemName = item.Name.ToDalamudString().ToString();
|
||||||
var imageSize = ImGuiUtils.ButtonHeight;
|
var imageSize = ImGui.GetFrameHeight();
|
||||||
|
|
||||||
ImGuiUtils.TextCentered($"You are missing the required equipment.");
|
ImGuiUtils.TextCentered($"You are missing the required equipment.");
|
||||||
ImGuiUtils.AlignCentered(imageSize + 5 + ImGui.CalcTextSize(itemName).X);
|
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 status = RecipeData.Recipe.StatusRequired.Value!;
|
||||||
var statusName = status.Name.ToDalamudString().ToString();
|
var statusName = status.Name.ToDalamudString().ToString();
|
||||||
var statusIcon = Service.IconManager.GetIcon(status.Icon);
|
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.TextCentered($"You are missing the required status effect.");
|
||||||
ImGuiUtils.AlignCentered(imageSize.X + 5 + ImGui.CalcTextSize(statusName).X);
|
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 textLevel = SqText.ToLevelString(RecipeData.RecipeInfo.ClassJobLevel);
|
||||||
var isExpert = RecipeData.RecipeInfo.IsExpert;
|
var isExpert = RecipeData.RecipeInfo.IsExpert;
|
||||||
var isCollectable = RecipeData.Recipe.ItemResult.Value!.IsCollectable;
|
var isCollectable = RecipeData.Recipe.ItemResult.Value!.IsCollectable;
|
||||||
var imageSize = ImGuiUtils.ButtonHeight;
|
var imageSize = ImGui.GetFrameHeight();
|
||||||
var textSize = ImGui.CalcTextSize("A").Y;
|
var textSize = ImGui.GetFontSize();
|
||||||
var badgeSize = new Vector2(textSize * ExpertBadge.Width / ExpertBadge.Height, textSize);
|
var badgeSize = new Vector2(textSize * ExpertBadge.Width / ExpertBadge.Height, textSize);
|
||||||
var badgeOffset = (imageSize - badgeSize.Y) / 2;
|
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)
|
private static void DrawRequiredStatsTable(int current, int required)
|
||||||
{
|
{
|
||||||
if (current >= required)
|
if (current >= required)
|
||||||
@@ -586,7 +617,71 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
|
|
||||||
private void CalculateBestMacros()
|
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()
|
public void Dispose()
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public class Settings : Window
|
|||||||
private static Configuration Config => Service.Configuration;
|
private static Configuration Config => Service.Configuration;
|
||||||
|
|
||||||
private const int OptionWidth = 200;
|
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 TabGeneral = "General";
|
||||||
public const string TabSimulator = "Simulator";
|
public const string TabSimulator = "Simulator";
|
||||||
|
|||||||
@@ -310,8 +310,8 @@ public sealed partial class Simulator : Window, IDisposable
|
|||||||
var totalWidth = drawParams.Total;
|
var totalWidth = drawParams.Total;
|
||||||
var halfWidth = (totalWidth - ImGui.GetStyle().ItemSpacing.X) / 2f;
|
var halfWidth = (totalWidth - ImGui.GetStyle().ItemSpacing.X) / 2f;
|
||||||
var quarterWidth = (halfWidth - ImGui.GetStyle().ItemSpacing.X) / 2f;
|
var quarterWidth = (halfWidth - ImGui.GetStyle().ItemSpacing.X) / 2f;
|
||||||
var halfButtonSize = new Vector2(halfWidth, ImGuiUtils.ButtonHeight);
|
var halfButtonSize = new Vector2(halfWidth, ImGui.GetFrameHeight());
|
||||||
var quarterButtonSize = new Vector2(quarterWidth, ImGuiUtils.ButtonHeight);
|
var quarterButtonSize = new Vector2(quarterWidth, ImGui.GetFrameHeight());
|
||||||
|
|
||||||
var conditionRandomnessText = "Condition Randomness";
|
var conditionRandomnessText = "Condition Randomness";
|
||||||
var conditionRandomness = Config.ConditionRandomness;
|
var conditionRandomness = Config.ConditionRandomness;
|
||||||
|
|||||||
+1
-1
@@ -87,7 +87,7 @@ public sealed class Solver : IDisposable
|
|||||||
}
|
}
|
||||||
catch (AggregateException e)
|
catch (AggregateException e)
|
||||||
{
|
{
|
||||||
e.Handle(ex => ex is OperationCanceledException);
|
e.Flatten().Handle(ex => ex is OperationCanceledException);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user