Merge branch 'condition-furcation' into main

This commit is contained in:
Asriel Camora
2023-11-14 00:59:00 -08:00
20 changed files with 552 additions and 187 deletions
+1 -1
View File
@@ -106,7 +106,7 @@ internal static class Program
MaxStepCount = 30, MaxStepCount = 30,
}; };
var sim = new SimulatorNoRandom(new(input)); var sim = new SimulatorNoRandom();
(_, var state) = sim.Execute(new(input), ActionType.MuscleMemory); (_, var state) = sim.Execute(new(input), ActionType.MuscleMemory);
(_, state) = sim.Execute(state, ActionType.PrudentTouch); (_, state) = sim.Execute(state, ActionType.PrudentTouch);
//(_, state) = sim.Execute(state, ActionType.Manipulation); //(_, state) = sim.Execute(state, ActionType.Manipulation);
+1
View File
@@ -82,6 +82,7 @@ public class Configuration : IPluginConfiguration
private List<Macro> macros { get; set; } = new(); private List<Macro> macros { get; set; } = new();
[JsonIgnore] [JsonIgnore]
public IReadOnlyList<Macro> Macros => macros; public IReadOnlyList<Macro> Macros => macros;
public int ReliabilitySimulationCount { get; set; } = 500;
public bool ConditionRandomness { get; set; } = true; public bool ConditionRandomness { get; set; } = true;
public SolverConfig SimulatorSolverConfig { get; set; } = SolverConfig.SimulatorDefault; public SolverConfig SimulatorSolverConfig { get; set; } = SolverConfig.SimulatorDefault;
public SolverConfig SynthHelperSolverConfig { get; set; } = SolverConfig.SynthHelperDefault; public SolverConfig SynthHelperSolverConfig { get; set; } = SolverConfig.SynthHelperDefault;
+1
View File
@@ -39,6 +39,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="DalamudPackager" Version="2.1.12" /> <PackageReference Include="DalamudPackager" Version="2.1.12" />
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.106"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.106">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+1 -1
View File
@@ -5,7 +5,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
namespace Craftimizer; namespace Craftimizer.Plugin;
internal static unsafe class ImGuiExtras internal static unsafe class ImGuiExtras
{ {
+113 -33
View File
@@ -2,12 +2,16 @@ using Craftimizer.Utils;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using ImGuiNET; using ImGuiNET;
using ImPlotNET;
using MathNet.Numerics.Statistics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -130,37 +134,6 @@ internal static class ImGuiUtils
ImGui.PopID(); ImGui.PopID();
} }
private struct EndUnconditionally : ImRaii.IEndObject, IDisposable
{
private Action EndAction { get; }
public bool Success { get; }
public bool Disposed { get; private set; }
public EndUnconditionally(Action endAction, bool success)
{
EndAction = endAction;
Success = success;
Disposed = false;
}
public void Dispose()
{
if (!Disposed)
{
EndAction();
Disposed = true;
}
}
}
public static ImRaii.IEndObject GroupPanel(string name, float width, out float internalWidth)
{
internalWidth = BeginGroupPanel(name, width);
return new EndUnconditionally(EndGroupPanel, true);
}
private static Vector2 UnitCircle(float theta) private static Vector2 UnitCircle(float theta)
{ {
var (s, c) = MathF.SinCos(theta); var (s, c) = MathF.SinCos(theta);
@@ -168,6 +141,7 @@ internal static class ImGuiUtils
return new Vector2(c, -s); return new Vector2(c, -s);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float Lerp(float a, float b, float t) => private static float Lerp(float a, float b, float t) =>
MathF.FusedMultiplyAdd(b - a, t, a); MathF.FusedMultiplyAdd(b - a, t, a);
@@ -251,6 +225,72 @@ internal static class ImGuiUtils
Arc(MathF.PI / 2, MathF.PI / 2 - MathF.Tau * Math.Clamp(value, 0, 1), radiusInner, radiusOuter, backgroundColor, filledColor); Arc(MathF.PI / 2, MathF.PI / 2 - MathF.Tau * Math.Clamp(value, 0, 1), radiusInner, radiusOuter, backgroundColor, filledColor);
} }
public sealed class ViolinData
{
[StructLayout(LayoutKind.Sequential)]
public struct Point
{
public float X, Y, Y2;
public Point(float x, float y, float y2)
{
X = x;
Y = y;
Y2 = y2;
}
}
public ReadOnlySpan<Point> Data => (DataArray ?? Array.Empty<Point>()).AsSpan();
private Point[]? DataArray { get; set; }
public readonly float Min;
public readonly float Max;
public ViolinData(IEnumerable<int> samples, float min, float max, int resolution, double bandwidth)
{
Min = min;
Max = max;
bandwidth *= Max - Min;
var samplesList = samples.AsParallel().Select(s => (double)s).ToArray();
_ = Task.Run(() => {
var s = Stopwatch.StartNew();
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 => new Point(n.n, n.Item2, -n.Item2));
DataArray = data.ToArray();
s.Stop();
Log.Debug($"Violin plot processing took {s.Elapsed.TotalMilliseconds:0.00}ms");
});
}
}
public static void ViolinPlot(in ViolinData data, Vector2 size)
{
using var padding = ImRaii2.PushStyle(ImPlotStyleVar.PlotPadding, Vector2.Zero);
using var plotBg = ImRaii2.PushColor(ImPlotCol.PlotBg, 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);
if (plot)
{
ImPlot.SetupAxes(null, null, ImPlotAxisFlags.NoDecorations, ImPlotAxisFlags.NoDecorations | ImPlotAxisFlags.AutoFit);
ImPlot.SetupAxisLimits(ImAxis.X1, data.Min, data.Max, ImPlotCond.Always);
ImPlot.SetupFinish();
if (data.Data is { } points && !points.IsEmpty)
{
unsafe
{
var label_id = stackalloc byte[] { (byte)'\0' };
fixed (ViolinData.Point* p = points)
{
ImPlotNative.ImPlot_PlotShaded_FloatPtrFloatPtrFloatPtr(label_id, &p->X, &p->Y, &p->Y2, points.Length, ImPlotShadedFlags.None, 0, sizeof(ViolinData.Point));
}
}
}
}
}
private sealed class SearchableComboData<T> where T : class private sealed class SearchableComboData<T> where T : class
{ {
public readonly ImmutableArray<T> items; public readonly ImmutableArray<T> items;
@@ -467,10 +507,50 @@ internal static class ImGuiUtils
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);
} }
public static bool IconButtonSized(FontAwesomeIcon icon, Vector2 size) private static Vector2 GetIconSize(FontAwesomeIcon icon)
{ {
using var font = ImRaii.PushFont(UiBuilder.IconFont); using var font = ImRaii.PushFont(UiBuilder.IconFont);
var ret = ImGui.Button(icon.ToIconString(), size); return ImGui.CalcTextSize(icon.ToIconString());
}
private static void DrawCenteredIcon(FontAwesomeIcon icon, Vector2 offset, Vector2 size)
{
var iconSize = GetIconSize(icon);
float scale;
Vector2 iconOffset;
if (iconSize.X > iconSize.Y)
{
scale = size.X / iconSize.X;
iconOffset = new(0, (size.Y - (iconSize.Y * scale)) / 2f);
}
else if (iconSize.Y > iconSize.X)
{
scale = size.Y / iconSize.Y;
iconOffset = new((size.X - (iconSize.X * scale)) / 2f, 0);
}
else
{
scale = size.X / iconSize.X;
iconOffset = Vector2.Zero;
}
ImGui.GetWindowDrawList().AddText(UiBuilder.IconFont, UiBuilder.IconFont.FontSize * scale, offset + iconOffset, ImGui.GetColorU32(ImGuiCol.Text), icon.ToIconString());
}
public static bool IconButtonSquare(FontAwesomeIcon icon, float size = -1)
{
var ret = false;
var buttonSize = new Vector2(size == -1 ? ImGui.GetFrameHeight() : size);
var pos = ImGui.GetCursorScreenPos();
var spacing = new Vector2(ImGui.GetStyle().FramePadding.Y);
if (ImGui.Button($"###{icon.ToIconString()}", buttonSize))
ret = true;
DrawCenteredIcon(icon, pos + spacing, buttonSize - spacing * 2);
return ret; return ret;
} }
+92
View File
@@ -0,0 +1,92 @@
using Dalamud.Interface.Utility.Raii;
using ImPlotNET;
using System;
using System.Numerics;
namespace Craftimizer.Plugin;
public static class ImRaii2
{
private struct EndUnconditionally : ImRaii.IEndObject, IDisposable
{
private Action EndAction { get; }
public bool Success { get; }
public bool Disposed { get; private set; }
public EndUnconditionally(Action endAction, bool success)
{
EndAction = endAction;
Success = success;
Disposed = false;
}
public void Dispose()
{
if (!Disposed)
{
EndAction();
Disposed = true;
}
}
}
private struct EndConditionally : ImRaii.IEndObject, IDisposable
{
public bool Success { get; }
public bool Disposed { get; private set; }
private Action EndAction { get; }
public EndConditionally(Action endAction, bool success)
{
EndAction = endAction;
Success = success;
Disposed = false;
}
public void Dispose()
{
if (!Disposed)
{
if (Success)
{
EndAction();
}
Disposed = true;
}
}
}
public static ImRaii.IEndObject GroupPanel(string name, float width, out float internalWidth)
{
internalWidth = ImGuiUtils.BeginGroupPanel(name, width);
return new EndUnconditionally(ImGuiUtils.EndGroupPanel, true);
}
public static ImRaii.IEndObject Plot(string title_id, Vector2 size, ImPlotFlags flags)
{
return new EndConditionally(new Action(ImPlot.EndPlot), ImPlot.BeginPlot(title_id, size, flags));
}
public static ImRaii.IEndObject PushStyle(ImPlotStyleVar idx, Vector2 val)
{
ImPlot.PushStyleVar(idx, val);
return new EndUnconditionally(ImPlot.PopStyleVar, true);
}
public static ImRaii.IEndObject PushStyle(ImPlotStyleVar idx, float val)
{
ImPlot.PushStyleVar(idx, val);
return new EndUnconditionally(ImPlot.PopStyleVar, true);
}
public static ImRaii.IEndObject PushColor(ImPlotCol idx, Vector4 col)
{
ImPlot.PushStyleColor(idx, col);
return new EndUnconditionally(ImPlot.PopStyleColor, true);
}
}
+2 -2
View File
@@ -36,7 +36,7 @@ public sealed class MacroClipboard : Window, IDisposable
private void DrawMacro(int idx, string macro) private void DrawMacro(int idx, string macro)
{ {
using var id = ImRaii.PushId(idx); using var id = ImRaii.PushId(idx);
using var panel = ImGuiUtils.GroupPanel($"Macro {idx + 1}", -1, out var availWidth); using var panel = ImRaii2.GroupPanel($"Macro {idx + 1}", -1, out var availWidth);
var cursor = ImGui.GetCursorPos(); var cursor = ImGui.GetCursorPos();
@@ -49,7 +49,7 @@ public sealed class MacroClipboard : Window, IDisposable
ImGui.SetCursorPos(buttonCursor); 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.IconButtonSized(FontAwesomeIcon.Paste, new(ImGui.GetFrameHeight())); ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Paste);
if (buttonClicked) if (buttonClicked)
{ {
ImGui.SetClipboardText(macro); ImGui.SetClipboardText(macro);
+268 -47
View File
@@ -21,7 +21,8 @@ using System.Numerics;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Sim = Craftimizer.Simulator.SimulatorNoRandom; using Sim = Craftimizer.Simulator.Simulator;
using SimNoRandom = Craftimizer.Simulator.SimulatorNoRandom;
namespace Craftimizer.Windows; namespace Craftimizer.Windows;
@@ -76,22 +77,125 @@ public sealed class MacroEditor : Window, IDisposable
private List<int> HQIngredientCounts { get; set; } private List<int> HQIngredientCounts { get; set; }
private int StartingQuality => RecipeData.CalculateStartingQuality(HQIngredientCounts); private int StartingQuality => RecipeData.CalculateStartingQuality(HQIngredientCounts);
private readonly record struct SimulationReliablity
{
public sealed class ParamReliability
{
private List<int> DataList { get; }
private ImGuiUtils.ViolinData? ViolinData { get; set; }
public int Max { get; private set; }
public int Min { get; private set; }
public float Median { get; private set; }
public float Average { get; private set; }
public ParamReliability()
{
DataList = new();
}
public void Add(int value)
{
DataList.Add(value);
}
public void FinalizeData()
{
if (DataList.Count == 0)
{
Average = Median = Max = Min = 0;
return;
}
Max = DataList.Max();
Min = DataList.Min();
if (DataList.Count % 2 == 0)
Median = (float)DataList.Order().Skip(DataList.Count / 2 - 1).Take(2).Average();
else
Median = DataList.Order().ElementAt(DataList.Count / 2);
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 readonly ParamReliability Progress = new();
public readonly ParamReliability Quality = new();
// Param is either collectability, quality, or hq%, depending on the recipe
public readonly ParamReliability Param = new();
public SimulationReliablity(in SimulationState startState, IEnumerable<ActionType> actions, int iterCount, RecipeData recipeData)
{
Func<SimulationState, int> getParam;
if (recipeData.Recipe.ItemResult.Value!.IsCollectable)
getParam = s => s.Collectability;
else if (recipeData.Recipe.RequiredQuality > 0)
{
var reqQual = recipeData.Recipe.RequiredQuality;
getParam = s => (int)((float)s.Quality / reqQual * 100);
}
else if (recipeData.RecipeInfo.MaxQuality > 0)
getParam = s => s.HQPercent;
else
getParam = s => 0;
for (var i = 0; i < iterCount; ++i)
{
var sim = new Sim();
var (_, state, _) = sim.ExecuteMultiple(startState, actions);
Progress.Add(state.Progress);
Quality.Add(state.Quality);
Param.Add(getParam(state));
}
Progress.FinalizeData();
Quality.FinalizeData();
Param.FinalizeData();
}
}
private sealed record SimulatedActionStep private sealed record SimulatedActionStep
{ {
public ActionType Action { get; init; } public ActionType Action { get; }
// State *after* executing the action // State *after* executing the action
public ActionResponse Response { get; set; } public ActionResponse Response { get; private set; }
public SimulationState State { get; set; } public SimulationState State { get; private set; }
private SimulationReliablity? Reliability { get; set; }
public SimulatedActionStep(ActionType action, Sim sim, in SimulationState lastState, out SimulationState newState)
{
Action = action;
newState = Recalculate(sim, lastState);
}
public SimulationState Recalculate(Sim sim, in SimulationState lastState)
{
(Response, State) = sim.Execute(lastState, Action);
Reliability = null;
return State;
}
public SimulationReliablity GetReliability(in SimulationState initialState, IEnumerable<ActionType> actionSet, RecipeData recipeData) =>
Reliability ??=
new(initialState, actionSet, Service.Configuration.ReliabilitySimulationCount, recipeData);
}; };
private List<SimulatedActionStep> Macro { get; set; } = new(); private List<SimulatedActionStep> Macro { get; set; } = new();
private SimulationState InitialState { get; set; } private SimulationState InitialState { get; set; }
private SimulationState State => Macro.Count > 0 ? Macro[^1].State : InitialState; private SimulationState State => Macro.Count > 0 ? Macro[^1].State : InitialState;
private SimulationReliablity Reliability => Macro.Count > 0 ? Macro[^1].GetReliability(InitialState, Macro.Select(m => m.Action), RecipeData) : new(InitialState, Array.Empty<ActionType>(), 0, RecipeData);
private ActionType[] DefaultActions { get; } private ActionType[] DefaultActions { get; }
private Action<IEnumerable<ActionType>>? MacroSetter { get; set; } private Action<IEnumerable<ActionType>>? MacroSetter { get; set; }
private CancellationTokenSource? SolverTokenSource { get; set; } private CancellationTokenSource? SolverTokenSource { get; set; }
private Exception? SolverException { get; set; } private Exception? SolverException { get; set; }
private int? SolverStartStepCount { get; set; } private int? SolverStartStepCount { get; set; }
private object? SolverQueueLock { get; set; }
private List<SimulatedActionStep>? SolverQueuedSteps { get; set; }
private bool SolverRunning => SolverTokenSource != null; private bool SolverRunning => SolverTokenSource != null;
private IDalamudTextureWrap ExpertBadge { get; } private IDalamudTextureWrap ExpertBadge { get; }
@@ -114,7 +218,7 @@ public sealed class MacroEditor : Window, IDisposable
private CancellationTokenSource? popupImportUrlTokenSource; private CancellationTokenSource? popupImportUrlTokenSource;
private MacroImport.RetrievedMacro? popupImportUrlMacro; private MacroImport.RetrievedMacro? popupImportUrlMacro;
public MacroEditor(CharacterStats characterStats, RecipeData recipeData, CrafterBuffs buffs, IEnumerable<ActionType> actions, Action<IEnumerable<ActionType>>? setter) : base("Craftimizer Macro Editor", WindowFlags, false) public MacroEditor(CharacterStats characterStats, RecipeData recipeData, CrafterBuffs buffs, IEnumerable<ActionType> actions, Action<IEnumerable<ActionType>>? setter) : base("Craftimizer Macro Editor", WindowFlags)
{ {
CharacterStats = characterStats; CharacterStats = characterStats;
RecipeData = recipeData; RecipeData = recipeData;
@@ -156,6 +260,11 @@ public sealed class MacroEditor : Window, IDisposable
SolverTokenSource?.Cancel(); SolverTokenSource?.Cancel();
} }
public override void Update()
{
TryFlushSolvedSteps();
}
public override void Draw() public override void Draw()
{ {
var modifiedInput = false; var modifiedInput = false;
@@ -297,25 +406,40 @@ public sealed class MacroEditor : Window, IDisposable
var disabledTint = new Vector4(0.5f, 0.5f, 0.5f, 0.75f); var disabledTint = new Vector4(0.5f, 0.5f, 0.5f, 0.75f);
var imageButtonPadding = (int)(ImGui.GetStyle().FramePadding.Y / 2f); var imageButtonPadding = (int)(ImGui.GetStyle().FramePadding.Y / 2f);
var imageButtonSize = imageSize - imageButtonPadding * 2; var imageButtonSize = imageSize - imageButtonPadding * 2;
{
var splendorousLevel = 90;
if (CharacterStats.HasSplendorousBuff && splendorousLevel > CharacterStats.Level)
CharacterStats = CharacterStats with { HasSplendorousBuff = false };
using (var d = ImRaii.Disabled(splendorousLevel > CharacterStats.Level))
{ {
var v = CharacterStats.HasSplendorousBuff; var v = CharacterStats.HasSplendorousBuff;
var tint = v ? Vector4.One : disabledTint; var tint = v ? Vector4.One : disabledTint;
if (ImGui.ImageButton(SplendorousBadge.ImGuiHandle, new Vector2(imageButtonSize), default, Vector2.One, imageButtonPadding, default, tint)) if (ImGui.ImageButton(SplendorousBadge.ImGuiHandle, new Vector2(imageButtonSize), default, Vector2.One, imageButtonPadding, default, tint))
CharacterStats = CharacterStats with { HasSplendorousBuff = !v }; CharacterStats = CharacterStats with { HasSplendorousBuff = !v };
if (ImGui.IsItemHovered()) }
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
ImGui.SetTooltip(CharacterStats.HasSplendorousBuff ? $"Splendorous Tool" : "No Splendorous Tool"); ImGui.SetTooltip(CharacterStats.HasSplendorousBuff ? $"Splendorous Tool" : "No Splendorous Tool");
} }
ImGui.SameLine(0, 5); ImGui.SameLine(0, 5);
bool? newIsSpecialist = null; bool? newIsSpecialist = null;
{ {
var v = CharacterStats.IsSpecialist; var v = CharacterStats.IsSpecialist;
var specialistLevel = 55;
if (CharacterStats.IsSpecialist && specialistLevel > CharacterStats.Level)
newIsSpecialist = v = false;
using (var d = ImRaii.Disabled(specialistLevel > CharacterStats.Level))
{
var tint = new Vector4(0.99f, 0.97f, 0.62f, 1f) * (v ? Vector4.One : disabledTint); var tint = new Vector4(0.99f, 0.97f, 0.62f, 1f) * (v ? Vector4.One : disabledTint);
if (ImGui.ImageButton(SpecialistBadge.ImGuiHandle, new Vector2(imageButtonSize), default, Vector2.One, imageButtonPadding, default, tint)) if (ImGui.ImageButton(SpecialistBadge.ImGuiHandle, new Vector2(imageButtonSize), default, Vector2.One, imageButtonPadding, default, tint))
{ {
v = !v; v = !v;
newIsSpecialist = v; newIsSpecialist = v;
} }
if (ImGui.IsItemHovered()) }
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
ImGui.SetTooltip(v ? $"Specialist" : "Not a Specialist"); ImGui.SetTooltip(v ? $"Specialist" : "Not a Specialist");
} }
ImGui.SameLine(0, 5); ImGui.SameLine(0, 5);
@@ -904,7 +1028,7 @@ public sealed class MacroEditor : Window, IDisposable
private void DrawActionHotbars() private void DrawActionHotbars()
{ {
var sim = new Sim(State); var sim = CreateSim(State);
var imageSize = ImGui.GetFrameHeight() * 2; var imageSize = ImGui.GetFrameHeight() * 2;
var spacing = ImGui.GetStyle().ItemSpacing.Y; var spacing = ImGui.GetStyle().ItemSpacing.Y;
@@ -919,7 +1043,7 @@ public sealed class MacroEditor : Window, IDisposable
continue; continue;
var actions = category.GetActions(); var actions = category.GetActions();
using var panel = ImGuiUtils.GroupPanel(category.GetDisplayName(), -1, out var availSpace); using var panel = ImRaii2.GroupPanel(category.GetDisplayName(), -1, out var availSpace);
var itemsPerRow = (int)MathF.Floor((availSpace + spacing) / (imageSize + spacing)); var itemsPerRow = (int)MathF.Floor((availSpace + spacing) / (imageSize + spacing));
var itemCount = actions.Count; var itemCount = actions.Count;
var iterCount = (int)(Math.Ceiling((float)itemCount / itemsPerRow) * itemsPerRow); var iterCount = (int)(Math.Ceiling((float)itemCount / itemsPerRow) * itemsPerRow);
@@ -971,23 +1095,26 @@ public sealed class MacroEditor : Window, IDisposable
ImGui.TableNextColumn(); ImGui.TableNextColumn();
var datas = new List<BarData>(3) var datas = new List<BarData>(3)
{ {
new("Durability", Colors.Durability, State.Durability, RecipeData.RecipeInfo.MaxDurability, null, null), new("Durability", Colors.Durability, null, State.Durability, RecipeData.RecipeInfo.MaxDurability, null, null),
new("Condition", default, 0, 0, null, State.Condition) new("Condition", default, null, 0, 0, null, State.Condition)
}; };
if (RecipeData.Recipe.ItemResult.Value!.IsCollectable) if (RecipeData.Recipe.ItemResult.Value!.IsCollectable)
datas.Add(new("Collectability", Colors.HQ, State.Collectability, State.MaxCollectability, $"{State.Collectability}", null)); datas.Add(new("Collectability", Colors.HQ, Reliability.Param, State.Collectability, State.MaxCollectability, $"{State.Collectability}", null));
else if (RecipeData.Recipe.RequiredQuality > 0) else if (RecipeData.Recipe.RequiredQuality > 0)
datas.Add(new("Quality %", Colors.HQ, State.Quality, RecipeData.Recipe.RequiredQuality, $"{(float)State.Quality / RecipeData.Recipe.RequiredQuality * 100:0}%", null)); {
var qualityPercent = (float)State.Quality / RecipeData.Recipe.RequiredQuality * 100;
datas.Add(new("Quality %%", Colors.HQ, Reliability.Param, qualityPercent, 100, $"{qualityPercent:0}%", null));
}
else if (RecipeData.RecipeInfo.MaxQuality > 0) else if (RecipeData.RecipeInfo.MaxQuality > 0)
datas.Add(new("HQ %", Colors.HQ, State.HQPercent, 100, $"{State.HQPercent}%", null)); datas.Add(new("HQ %%", Colors.HQ, Reliability.Param, State.HQPercent, 100, $"{State.HQPercent}%", null));
DrawBars(datas); DrawBars(datas);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
datas = new List<BarData>(3) datas = new List<BarData>(3)
{ {
new("Progress", Colors.Progress, State.Progress, RecipeData.RecipeInfo.MaxProgress, null, null), new("Progress", Colors.Progress, Reliability.Progress, State.Progress, RecipeData.RecipeInfo.MaxProgress, null, null),
new("Quality", Colors.Quality, State.Quality, RecipeData.RecipeInfo.MaxQuality, null, null), new("Quality", Colors.Quality, Reliability.Quality, State.Quality, RecipeData.RecipeInfo.MaxQuality, null, null),
new("CP", Colors.CP, State.CP, CharacterStats.CP, null, null) new("CP", Colors.CP, null, State.CP, CharacterStats.CP, null, null)
}; };
if (RecipeData.RecipeInfo.MaxQuality <= 0) if (RecipeData.RecipeInfo.MaxQuality <= 0)
datas.RemoveAt(1); datas.RemoveAt(1);
@@ -995,7 +1122,7 @@ public sealed class MacroEditor : Window, IDisposable
} }
} }
using (var panel = ImGuiUtils.GroupPanel("Buffs", -1, out _)) using (var panel = ImRaii2.GroupPanel("Buffs", -1, out _))
{ {
using var _font = ImRaii.PushFont(AxisFont.ImFont); using var _font = ImRaii.PushFont(AxisFont.ImFont);
@@ -1034,7 +1161,7 @@ public sealed class MacroEditor : Window, IDisposable
} }
} }
private readonly record struct BarData(string Name, Vector4 Color, float Value, float Max, string? Caption, Condition? Condition); private readonly record struct BarData(string Name, Vector4 Color, SimulationReliablity.ParamReliability? Reliability, float Value, float Max, string? Caption, Condition? Condition);
private void DrawBars(IEnumerable<BarData> bars) private void DrawBars(IEnumerable<BarData> bars)
{ {
var spacing = ImGui.GetStyle().ItemSpacing.X; var spacing = ImGui.GetStyle().ItemSpacing.X;
@@ -1053,13 +1180,16 @@ public sealed class MacroEditor : Window, IDisposable
var barSize = totalSize - textSize - spacing; var barSize = totalSize - textSize - spacing;
foreach (var bar in bars) foreach (var bar in bars)
{ {
using var panel = ImGuiUtils.GroupPanel(bar.Name, totalSize, out _); using var panel = ImRaii2.GroupPanel(bar.Name, totalSize, out _);
if (bar.Condition is { } condition) if (bar.Condition is { } condition)
{ {
var pos = ImGui.GetCursorPos();
using (var g = ImRaii.Group()) using (var g = ImRaii.Group())
{ {
var availSize = totalSize - (spacing + ImGui.GetFrameHeight());
var size = ImGui.GetFrameHeight() + spacing + ImGui.CalcTextSize(condition.Name()).X; var size = ImGui.GetFrameHeight() + spacing + ImGui.CalcTextSize(condition.Name()).X;
ImGuiUtils.AlignCentered(size, totalSize);
ImGuiUtils.AlignCentered(size, availSize);
ImGui.GetWindowDrawList().AddCircleFilled( ImGui.GetWindowDrawList().AddCircleFilled(
ImGui.GetCursorScreenPos() + new Vector2(ImGui.GetFrameHeight() / 2), ImGui.GetCursorScreenPos() + new Vector2(ImGui.GetFrameHeight() / 2),
ImGui.GetFrameHeight() / 2, ImGui.GetFrameHeight() / 2,
@@ -1070,12 +1200,49 @@ public sealed class MacroEditor : Window, IDisposable
ImGui.Text(condition.Name()); ImGui.Text(condition.Name());
} }
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
ImGui.SetTooltip(condition.Description(CharacterStats.HasSplendorousBuff)); ImGui.SetTooltip(condition.Description(CharacterStats.HasSplendorousBuff).Replace("%", "%%"));
ImGui.SetCursorPos(pos);
ImGuiUtils.AlignRight(ImGui.GetFrameHeight(), totalSize);
using (var disabled = ImRaii.Disabled(SolverRunning))
{
using var tint = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), !Service.Configuration.ConditionRandomness);
if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Dice))
{
Service.Configuration.ConditionRandomness ^= true;
Service.Configuration.Save();
RecalculateState();
}
}
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
ImGui.SetTooltip("Condition Randomness\n" +
"Allows the condition to fluctuate randomly like a real craft.\n" +
"Turns off when generating a macro.");
} }
else else
{ {
var pos = ImGui.GetCursorPos();
using (var color = ImRaii.PushColor(ImGuiCol.PlotHistogram, bar.Color)) using (var color = ImRaii.PushColor(ImGuiCol.PlotHistogram, bar.Color))
ImGui.ProgressBar(Math.Clamp(bar.Value / bar.Max, 0, 1), new(barSize, ImGui.GetFrameHeight()), string.Empty); ImGui.ProgressBar(Math.Clamp(bar.Value / bar.Max, 0, 1), new(barSize, ImGui.GetFrameHeight()), string.Empty);
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenOverlapped))
{
if (bar.Reliability is { } reliability)
{
if (reliability.GetViolinData(bar.Max, (int)(barSize / 5), 0.02) is { } violinData)
{
ImGui.SetCursorPos(pos);
ImGuiUtils.ViolinPlot(violinData, new(barSize, ImGui.GetFrameHeight()));
if (ImGui.IsItemHovered())
ImGui.SetTooltip(
$"Min: {reliability.Min}\n" +
$"Med: {reliability.Median:0.##}\n" +
$"Avg: {reliability.Average:0.##}\n" +
$"Max: {reliability.Max}");
}
}
}
ImGui.SameLine(0, spacing); ImGui.SameLine(0, spacing);
ImGui.AlignTextToFramePadding(); ImGui.AlignTextToFramePadding();
if (bar.Caption is { } caption) if (bar.Caption is { } caption)
@@ -1098,7 +1265,7 @@ public sealed class MacroEditor : Window, IDisposable
var imageSize = ImGui.GetFrameHeight() * 2; var imageSize = ImGui.GetFrameHeight() * 2;
var lastState = InitialState; var lastState = InitialState;
using var panel = ImGuiUtils.GroupPanel("Macro", -1, out var availSpace); using var panel = ImRaii2.GroupPanel("Macro", -1, out var availSpace);
ImGui.Dummy(new(0, imageSize)); ImGui.Dummy(new(0, imageSize));
ImGui.SameLine(0, 0); ImGui.SameLine(0, 0);
@@ -1131,8 +1298,7 @@ public sealed class MacroEditor : Window, IDisposable
} }
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{ {
var sim = new Sim(lastState); ImGui.SetTooltip($"{action.GetName(RecipeData!.ClassJob)}\n{actionBase.GetTooltip(CreateSim(lastState), true)}");
ImGui.SetTooltip($"{action.GetName(RecipeData!.ClassJob)}\n{actionBase.GetTooltip(sim, true)}");
} }
lastState = state; lastState = state;
} }
@@ -1197,14 +1363,14 @@ public sealed class MacroEditor : Window, IDisposable
"can vary wildly depending on the solver's settings."); "can vary wildly depending on the solver's settings.");
} }
ImGui.SameLine(); ImGui.SameLine();
if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Paste, new(height))) if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Paste))
Service.Plugin.CopyMacro(Macro.Select(s => s.Action).ToArray()); Service.Plugin.CopyMacro(Macro.Select(s => s.Action).ToArray());
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
ImGui.SetTooltip("Copy to Clipboard"); ImGui.SetTooltip("Copy to Clipboard");
ImGui.SameLine(); ImGui.SameLine();
using (var _disabled = ImRaii.Disabled(SolverRunning)) using (var _disabled = ImRaii.Disabled(SolverRunning))
{ {
if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.FileImport, new(height))) if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.FileImport))
ShowImportPopup(); ShowImportPopup();
} }
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
@@ -1215,7 +1381,7 @@ public sealed class MacroEditor : Window, IDisposable
{ {
using (var _disabled = ImRaii.Disabled(SolverRunning)) using (var _disabled = ImRaii.Disabled(SolverRunning))
{ {
if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Undo, new(height))) if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Undo))
{ {
SolverStartStepCount = null; SolverStartStepCount = null;
Macro.Clear(); Macro.Clear();
@@ -1229,7 +1395,7 @@ public sealed class MacroEditor : Window, IDisposable
ImGui.SameLine(); ImGui.SameLine();
using (var _disabled = ImRaii.Disabled(SolverRunning)) using (var _disabled = ImRaii.Disabled(SolverRunning))
{ {
if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Trash, new(height))) if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Trash))
{ {
SolverStartStepCount = null; SolverStartStepCount = null;
Macro.Clear(); Macro.Clear();
@@ -1293,7 +1459,7 @@ public sealed class MacroEditor : Window, IDisposable
{ {
bool submittedText, submittedUrl; bool submittedText, submittedUrl;
using (var panel = ImGuiUtils.GroupPanel("##text", -1, out var availWidth)) using (var panel = ImRaii2.GroupPanel("##text", -1, out var availWidth))
{ {
ImGui.AlignTextToFramePadding(); ImGui.AlignTextToFramePadding();
ImGuiUtils.TextCentered("Paste your macro here"); ImGuiUtils.TextCentered("Paste your macro here");
@@ -1305,7 +1471,7 @@ public sealed class MacroEditor : Window, IDisposable
submittedText = ImGui.Button("Import", new(availWidth, 0)); submittedText = ImGui.Button("Import", new(availWidth, 0));
} }
using (var panel = ImGuiUtils.GroupPanel("##url", -1, out var availWidth)) using (var panel = ImRaii2.GroupPanel("##url", -1, out var availWidth))
{ {
var availOffset = ImGui.GetContentRegionAvail().X - availWidth; var availOffset = ImGui.GetContentRegionAvail().X - availWidth;
@@ -1420,13 +1586,32 @@ public sealed class MacroEditor : Window, IDisposable
popupImportUrlTokenSource = null; popupImportUrlTokenSource = null;
} }
} }
private void CalculateBestMacro() private void CalculateBestMacro()
{ {
SolverTokenSource?.Cancel(); SolverTokenSource?.Cancel();
SolverTokenSource = new(); SolverTokenSource = new();
SolverException = null; SolverException = null;
if (SolverQueueLock is { })
{
lock (SolverQueueLock)
{
SolverQueuedSteps!.Clear();
SolverQueueLock = null;
}
}
SolverQueueLock = new();
SolverQueuedSteps ??= new();
RevertPreviousMacro(); RevertPreviousMacro();
if (Service.Configuration.ConditionRandomness)
{
Service.Configuration.ConditionRandomness = false;
Service.Configuration.Save();
RecalculateState();
}
SolverStartStepCount = Macro.Count; SolverStartStepCount = Macro.Count;
var token = SolverTokenSource.Token; var token = SolverTokenSource.Token;
@@ -1462,7 +1647,7 @@ public sealed class MacroEditor : Window, IDisposable
var solver = new Solver.Solver(config, state) { Token = token }; var solver = new Solver.Solver(config, state) { Token = token };
solver.OnLog += Log.Debug; solver.OnLog += Log.Debug;
solver.OnNewAction += a => AddStep(a, isSolver: true); solver.OnNewAction += QueueSolverStep;
solver.Start(); solver.Start();
_ = solver.GetTask().GetAwaiter().GetResult(); _ = solver.GetTask().GetAwaiter().GetResult();
@@ -1483,36 +1668,72 @@ public sealed class MacroEditor : Window, IDisposable
private void RecalculateState() private void RecalculateState()
{ {
InitialState = new SimulationState(new(CharacterStats, RecipeData.RecipeInfo, StartingQuality)); InitialState = new SimulationState(new(CharacterStats, RecipeData.RecipeInfo, StartingQuality));
var sim = new Sim(InitialState); var sim = CreateSim();
var lastState = InitialState; var lastState = InitialState;
foreach (var step in Macro) for (var i = 0; i < Macro.Count; i++)
lastState = ((step.Response, step.State) = sim.Execute(lastState, step.Action)).State; lastState = Macro[i].Recalculate(sim, lastState);
} }
private void AddStep(ActionType action, int index = -1, bool isSolver = false) private static Sim CreateSim() =>
Service.Configuration.ConditionRandomness ? new Sim() : new SimNoRandom();
private static Sim CreateSim(in SimulationState state) =>
Service.Configuration.ConditionRandomness ? new Sim() { State = state } : new SimNoRandom() { State = state };
private void AddStep(ActionType action, int index = -1)
{ {
if (index < -1 || index >= Macro.Count) if (index < -1 || index >= Macro.Count)
throw new ArgumentOutOfRangeException(nameof(index)); throw new ArgumentOutOfRangeException(nameof(index));
if (!isSolver && SolverRunning) if (SolverRunning)
throw new InvalidOperationException("Cannot add steps while solver is running"); throw new InvalidOperationException("Cannot add steps while solver is running");
if (!SolverRunning) if (!SolverRunning)
SolverStartStepCount = null; SolverStartStepCount = null;
if (index == -1) if (index == -1)
{ {
var sim = new Sim(State); var sim = CreateSim();
var resp = sim.Execute(State, action); Macro.Add(new(action, sim, State, out _));
Macro.Add(new() { Action = action, Response = resp.Response, State = resp.NewState });
} }
else else
{ {
var state = index == 0 ? InitialState : Macro[index - 1].State; var state = index == 0 ? InitialState : Macro[index - 1].State;
var sim = new Sim(state); var sim = CreateSim();
var resp = sim.Execute(state, action); Macro.Insert(index, new(action, sim, state, out state));
Macro.Insert(index, new() { Action = action, Response = resp.Response, State = resp.NewState });
state = resp.NewState;
for (var i = index + 1; i < Macro.Count; i++) for (var i = index + 1; i < Macro.Count; i++)
state = ((Macro[i].Response, Macro[i].State) = sim.Execute(state, Macro[i].Action)).State; state = Macro[i].Recalculate(sim, state);
}
}
private void QueueSolverStep(ActionType action)
{
if (!SolverRunning)
throw new InvalidOperationException("Cannot queue steps while solver isn't running");
lock (SolverQueueLock!)
{
var lastState = SolverQueuedSteps!.Count > 0 ? SolverQueuedSteps[^1].State : State;
SolverQueuedSteps.Add(new(action, CreateSim(), lastState, out _));
}
}
private void TryFlushSolvedSteps()
{
if (SolverQueueLock == null)
return;
lock (SolverQueueLock!)
{
if (SolverQueuedSteps!.Count > 0)
{
Macro.AddRange(SolverQueuedSteps);
SolverQueuedSteps.Clear();
}
if (!SolverRunning)
{
SolverQueuedSteps.Clear();
SolverQueueLock = null;
}
} }
} }
@@ -1527,9 +1748,9 @@ public sealed class MacroEditor : Window, IDisposable
Macro.RemoveAt(index); Macro.RemoveAt(index);
var state = index == 0 ? InitialState : Macro[index - 1].State; var state = index == 0 ? InitialState : Macro[index - 1].State;
var sim = new Sim(state); var sim = CreateSim();
for (var i = index; i < Macro.Count; i++) for (var i = index; i < Macro.Count; i++)
state = ((Macro[i].Response, Macro[i].State) = sim.Execute(state, Macro[i].Action)).State; state = Macro[i].Recalculate(sim, state);
} }
public void Dispose() public void Dispose()
+6 -6
View File
@@ -111,7 +111,7 @@ public sealed class MacroList : Window, IDisposable
var stateNullable = GetMacroState(macro); var stateNullable = GetMacroState(macro);
using var panel = ImGuiUtils.GroupPanel(macro.Name, -1, out var availWidth); using var panel = ImRaii2.GroupPanel(macro.Name, -1, out var availWidth);
var stepsAvailWidthOffset = ImGui.GetContentRegionAvail().X - availWidth; var stepsAvailWidthOffset = ImGui.GetContentRegionAvail().X - availWidth;
var spacing = ImGui.GetStyle().ItemSpacing.Y; var spacing = ImGui.GetStyle().ItemSpacing.Y;
var miniRowHeight = (windowHeight - spacing) / 2f; var miniRowHeight = (windowHeight - spacing) / 2f;
@@ -197,23 +197,23 @@ public sealed class MacroList : Window, IDisposable
ImGui.TableNextColumn(); ImGui.TableNextColumn();
{ {
if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Paste, new(miniRowHeight))) if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Paste, miniRowHeight))
Service.Plugin.CopyMacro(macro.Actions); Service.Plugin.CopyMacro(macro.Actions);
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
ImGui.SetTooltip("Copy to Clipboard"); ImGui.SetTooltip("Copy to Clipboard");
ImGui.SameLine(); ImGui.SameLine();
if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Trash, new(miniRowHeight)) && ImGui.GetIO().KeyShift) if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Trash, miniRowHeight) && ImGui.GetIO().KeyShift)
Service.Configuration.RemoveMacro(macro); Service.Configuration.RemoveMacro(macro);
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
ImGui.SetTooltip("Delete (Hold Shift)"); ImGui.SetTooltip("Delete (Hold Shift)");
if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.PencilAlt, new(miniRowHeight))) if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.PencilAlt, miniRowHeight))
ShowRenamePopup(macro); ShowRenamePopup(macro);
DrawRenamePopup(macro); DrawRenamePopup(macro);
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
ImGui.SetTooltip("Rename"); ImGui.SetTooltip("Rename");
ImGui.SameLine(); ImGui.SameLine();
if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Edit, new(miniRowHeight))) if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Edit, miniRowHeight))
OpenEditor(macro); OpenEditor(macro);
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
ImGui.SetTooltip("Open in Simulator"); ImGui.SetTooltip("Open in Simulator");
@@ -329,7 +329,7 @@ public sealed class MacroList : Window, IDisposable
return state; return state;
state = new SimulationState(new(CharacterStats, RecipeData.RecipeInfo)); state = new SimulationState(new(CharacterStats, RecipeData.RecipeInfo));
var sim = new Sim(state); var sim = new Sim();
(_, state, _) = sim.ExecuteMultiple(state, macro.Actions); (_, state, _) = sim.ExecuteMultiple(state, macro.Actions);
return MacroStateCache[macro] = state; return MacroStateCache[macro] = state;
} }
+5 -5
View File
@@ -216,7 +216,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
ImGui.Separator(); ImGui.Separator();
var panelWidth = availWidth - ImGui.GetStyle().ItemSpacing.X * 2; var panelWidth = availWidth - ImGui.GetStyle().ItemSpacing.X * 2;
using (var panel = ImGuiUtils.GroupPanel("Best Saved Macro", panelWidth, out _)) using (var panel = ImRaii2.GroupPanel("Best Saved Macro", panelWidth, out _))
{ {
var stepsPanelWidthOffset = ImGui.GetContentRegionAvail().X - panelWidth; var stepsPanelWidthOffset = ImGui.GetContentRegionAvail().X - panelWidth;
if (BestSavedMacro is { } savedMacro) if (BestSavedMacro is { } savedMacro)
@@ -228,7 +228,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
DrawMacro(null, null, stepsPanelWidthOffset, true); DrawMacro(null, null, stepsPanelWidthOffset, true);
} }
using (var panel = ImGuiUtils.GroupPanel("Suggested Macro", panelWidth, out _)) using (var panel = ImRaii2.GroupPanel("Suggested Macro", panelWidth, out _))
{ {
var stepsPanelWidthOffset = ImGui.GetContentRegionAvail().X - panelWidth; var stepsPanelWidthOffset = ImGui.GetContentRegionAvail().X - panelWidth;
if (BestSuggestedMacro is { } suggestedMacro) if (BestSuggestedMacro is { } suggestedMacro)
@@ -644,11 +644,11 @@ public sealed unsafe class RecipeNote : Window, IDisposable
ImGui.TableNextColumn(); ImGui.TableNextColumn();
{ {
if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Edit, new(miniRowHeight))) if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Edit, miniRowHeight))
Service.Plugin.OpenMacroEditor(CharacterStats!, RecipeData!, new(Service.ClientState.LocalPlayer!.StatusList), macro.Actions, setter); Service.Plugin.OpenMacroEditor(CharacterStats!, RecipeData!, new(Service.ClientState.LocalPlayer!.StatusList), macro.Actions, setter);
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
ImGui.SetTooltip("Open in Simulator"); ImGui.SetTooltip("Open in Simulator");
if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Paste, new(miniRowHeight))) if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Paste, miniRowHeight))
Service.Plugin.CopyMacro(macro.Actions); Service.Plugin.CopyMacro(macro.Actions);
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
ImGui.SetTooltip("Copy to Clipboard"); ImGui.SetTooltip("Copy to Clipboard");
@@ -824,7 +824,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
var state = new SimulationState(input); var state = new SimulationState(input);
var config = Service.Configuration.SimulatorSolverConfig; var config = Service.Configuration.SimulatorSolverConfig;
var mctsConfig = new MCTSConfig(config); var mctsConfig = new MCTSConfig(config);
var simulator = new SimulatorNoRandom(state); var simulator = new SimulatorNoRandom();
List<Macro> macros = new(Service.Configuration.Macros); List<Macro> macros = new(Service.Configuration.Macros);
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
+17 -24
View File
@@ -191,7 +191,7 @@ public sealed class Settings : Window, IDisposable
ImGui.SetTooltip("Disabled temporarily for testing"); ImGui.SetTooltip("Disabled temporarily for testing");
DrawOption( DrawOption(
"Show Only One Macro Stat", "Show Only One Macro Stat in Crafting Log",
"Only one stat will be shown for a macro. If a craft will be finished, quality\n" + "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" + "is shown. Otherwise, progress is shown. Durability and remaining CP will be\n" +
"hidden.", "hidden.",
@@ -200,9 +200,21 @@ public sealed class Settings : Window, IDisposable
ref isDirty ref isDirty
); );
DrawOption(
"Reliability Trial Count",
"When testing for reliability of a macro in the editor, this many trials will be\n" +
"run. You should set this value to at least 100 to get a reliable spread of data.\n" +
"If it's too low, you may not find an outlier, and the average might be skewed.",
Config.ReliabilitySimulationCount,
5,
5000,
v => Config.ReliabilitySimulationCount = v,
ref isDirty
);
ImGuiHelpers.ScaledDummy(5); ImGuiHelpers.ScaledDummy(5);
using (var panel = ImGuiUtils.GroupPanel("Copying Settings", -1, out _)) using (var panel = ImRaii2.GroupPanel("Copying Settings", -1, out _))
{ {
DrawOption( DrawOption(
"Macro Copy Method", "Macro Copy Method",
@@ -380,7 +392,7 @@ public sealed class Settings : Window, IDisposable
var config = configRef; var config = configRef;
using (var panel = ImGuiUtils.GroupPanel("General", -1, out _)) using (var panel = ImRaii2.GroupPanel("General", -1, out _))
{ {
if (ImGui.Button("Reset to Default", OptionButtonSize)) if (ImGui.Button("Reset to Default", OptionButtonSize))
{ {
@@ -501,7 +513,7 @@ public sealed class Settings : Window, IDisposable
); );
} }
using (var panel = ImGuiUtils.GroupPanel("Advanced", -1, out _)) using (var panel = ImRaii2.GroupPanel("Advanced", -1, out _))
{ {
DrawOption( DrawOption(
"Score Storage Threshold", "Score Storage Threshold",
@@ -538,7 +550,7 @@ public sealed class Settings : Window, IDisposable
); );
} }
using (var panel = ImGuiUtils.GroupPanel("Score Weights (Advanced)", -1, out _)) using (var panel = ImRaii2.GroupPanel("Score Weights (Advanced)", -1, out _))
{ {
ImGui.TextWrapped("All values should add up to 1. Otherwise, the Score Storage Threshold should be changed."); ImGui.TextWrapped("All values should add up to 1. Otherwise, the Score Storage Threshold should be changed.");
ImGuiHelpers.ScaledDummy(10); ImGuiHelpers.ScaledDummy(10);
@@ -629,25 +641,6 @@ public sealed class Settings : Window, IDisposable
var isDirty = false; var isDirty = false;
using (var g = ImRaii.Group())
{
using var d = ImRaii.Disabled();
DrawOption(
"Condition Randomness",
"Allows the simulator condition to fluctuate randomly like a real craft.\n" +
"Turns off when generating a macro.",
Config.ConditionRandomness,
v => Config.ConditionRandomness = v,
ref isDirty
);
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Disabled temporarily for testing");
ImGuiHelpers.ScaledDummy(5);
ImGui.Separator();
ImGuiHelpers.ScaledDummy(5);
var solverConfig = Config.SimulatorSolverConfig; var solverConfig = Config.SimulatorSolverConfig;
DrawSolverConfig(ref solverConfig, SolverConfig.SimulatorDefault, out var isSolverDirty); DrawSolverConfig(ref solverConfig, SolverConfig.SimulatorDefault, out var isSolverDirty);
if (isSolverDirty) if (isSolverDirty)
+6
View File
@@ -8,6 +8,12 @@
"resolved": "2.1.12", "resolved": "2.1.12",
"contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg==" "contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg=="
}, },
"MathNet.Numerics": {
"type": "Direct",
"requested": "[5.0.0, )",
"resolved": "5.0.0",
"contentHash": "pg1W2VwaEQMAiTpGK840hZgzavnqjlCMTVSbtVCXVyT+7AX4mc1o89SPv4TBlAjhgCOo9c1Y+jZ5m3ti2YgGgA=="
},
"Meziantou.Analyzer": { "Meziantou.Analyzer": {
"type": "Direct", "type": "Direct",
"requested": "[2.0.106, )", "requested": "[2.0.106, )",
+19 -27
View File
@@ -6,20 +6,22 @@ namespace Craftimizer.Simulator;
public class Simulator public class Simulator
{ {
protected SimulationState State; public SimulationState State { init => state = value; }
public SimulationInput Input => State.Input; private SimulationState state;
public ref int ActionCount => ref State.ActionCount;
public ref int StepCount => ref State.StepCount;
public ref int Progress => ref State.Progress;
public ref int Quality => ref State.Quality;
public ref int Durability => ref State.Durability;
public ref int CP => ref State.CP;
public ref Condition Condition => ref State.Condition;
public ref Effects ActiveEffects => ref State.ActiveEffects;
public ref ActionStates ActionStates => ref State.ActionStates;
public bool IsFirstStep => State.StepCount == 0; public SimulationInput Input => state.Input;
public ref int ActionCount => ref state.ActionCount;
public ref int StepCount => ref state.StepCount;
public ref int Progress => ref state.Progress;
public ref int Quality => ref state.Quality;
public ref int Durability => ref state.Durability;
public ref int CP => ref state.CP;
public ref Condition Condition => ref state.Condition;
public ref Effects ActiveEffects => ref state.ActiveEffects;
public ref ActionStates ActionStates => ref state.ActionStates;
public bool IsFirstStep => state.StepCount == 0;
public virtual CompletionState CompletionState { public virtual CompletionState CompletionState {
get get
@@ -35,20 +37,10 @@ public class Simulator
public IEnumerable<ActionType> AvailableActions => ActionUtils.AvailableActions(this); public IEnumerable<ActionType> AvailableActions => ActionUtils.AvailableActions(this);
public Simulator(in SimulationState state)
{
State = state;
}
public void SetState(in SimulationState state)
{
State = state;
}
public (ActionResponse Response, SimulationState NewState) Execute(in SimulationState state, ActionType action) public (ActionResponse Response, SimulationState NewState) Execute(in SimulationState state, ActionType action)
{ {
State = state; this.state = state;
return (Execute(action), State); return (Execute(action), this.state);
} }
private ActionResponse Execute(ActionType action) private ActionResponse Execute(ActionType action)
@@ -77,16 +69,16 @@ public class Simulator
public (ActionResponse Response, SimulationState NewState, int FailedActionIdx) ExecuteMultiple(in SimulationState state, IEnumerable<ActionType> actions) public (ActionResponse Response, SimulationState NewState, int FailedActionIdx) ExecuteMultiple(in SimulationState state, IEnumerable<ActionType> actions)
{ {
State = state; this.state = state;
var i = 0; var i = 0;
foreach(var action in actions) foreach(var action in actions)
{ {
var resp = Execute(action); var resp = Execute(action);
if (resp != ActionResponse.UsedAction) if (resp != ActionResponse.UsedAction)
return (resp, State, i); return (resp, this.state, i);
i++; i++;
} }
return (ActionResponse.UsedAction, State, -1); return (ActionResponse.UsedAction, this.state, -1);
} }
[Pure] [Pure]
-4
View File
@@ -2,10 +2,6 @@ namespace Craftimizer.Simulator;
public class SimulatorNoRandom : Simulator public class SimulatorNoRandom : Simulator
{ {
public SimulatorNoRandom(in SimulationState state) : base(state)
{
}
public sealed override bool RollSuccessRaw(float successRate) => successRate == 1; public sealed override bool RollSuccessRaw(float successRate) => successRate == 1;
public sealed override Condition GetNextRandomCondition() => Condition.Normal; public sealed override Condition GetNextRandomCondition() => Condition.Normal;
} }
-3
View File
@@ -24,8 +24,6 @@ public struct ActionSet
ActionType.DelicateSynthesis, ActionType.DelicateSynthesis,
ActionType.PreparatoryTouch, ActionType.PreparatoryTouch,
ActionType.Reflect, ActionType.Reflect,
ActionType.FocusedTouch,
ActionType.FocusedSynthesis,
ActionType.PrudentTouch, ActionType.PrudentTouch,
ActionType.Manipulation, ActionType.Manipulation,
ActionType.MuscleMemory, ActionType.MuscleMemory,
@@ -37,7 +35,6 @@ public struct ActionSet
ActionType.StandardTouch, ActionType.StandardTouch,
ActionType.Veneration, ActionType.Veneration,
ActionType.WasteNot, ActionType.WasteNot,
ActionType.Observe,
ActionType.MastersMend, ActionType.MastersMend,
ActionType.BasicTouch, ActionType.BasicTouch,
}; };
+2 -2
View File
@@ -20,7 +20,7 @@ public sealed class MCTS
public MCTS(in MCTSConfig config, in SimulationState state) public MCTS(in MCTSConfig config, in SimulationState state)
{ {
this.config = config; this.config = config;
var sim = new Simulator(state, config.MaxStepCount); var sim = new Simulator(config.MaxStepCount) { State = state };
rootNode = new(new( rootNode = new(new(
state, state,
null, null,
@@ -280,7 +280,7 @@ public sealed class MCTS
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Search(int iterations, CancellationToken token) public void Search(int iterations, CancellationToken token)
{ {
Simulator simulator = new(rootNode.State.State, config.MaxStepCount); Simulator simulator = new(config.MaxStepCount);
var random = rootNode.State.State.Input.Random; var random = rootNode.State.State.Input.Random;
var n = 0; var n = 0;
for (var i = 0; i < iterations || MaxScore == 0; i++) for (var i = 0; i < iterations || MaxScore == 0; i++)
+1 -15
View File
@@ -20,7 +20,7 @@ internal sealed class Simulator : SimulatorNoRandom
} }
} }
public Simulator(in SimulationState state, int maxStepCount) : base(state) public Simulator(int maxStepCount)
{ {
this.maxStepCount = maxStepCount; this.maxStepCount = maxStepCount;
} }
@@ -42,22 +42,12 @@ internal sealed class Simulator : SimulatorNoRandom
if (Quality >= Input.Recipe.MaxQuality && baseAction.IncreasesQuality) if (Quality >= Input.Recipe.MaxQuality && baseAction.IncreasesQuality)
return false; return false;
if (action == ActionType.Observe &&
ActionStates.Observed)
return false;
if (strict) if (strict)
{ {
// always use Trained Eye if it's available // always use Trained Eye if it's available
if (action == ActionType.TrainedEye) if (action == ActionType.TrainedEye)
return baseAction.CanUse(this); return baseAction.CanUse(this);
// only allow Focused moves after Observe
if (ActionStates.Observed &&
action != ActionType.FocusedSynthesis &&
action != ActionType.FocusedTouch)
return false;
// don't allow quality moves under Muscle Memory for difficult crafts // don't allow quality moves under Muscle Memory for difficult crafts
if (Input.Recipe.ClassJobLevel == 90 && if (Input.Recipe.ClassJobLevel == 90 &&
HasEffect(EffectType.MuscleMemory) && HasEffect(EffectType.MuscleMemory) &&
@@ -116,10 +106,6 @@ internal sealed class Simulator : SimulatorNoRandom
(HasEffect(EffectType.WasteNot) || HasEffect(EffectType.WasteNot2))) (HasEffect(EffectType.WasteNot) || HasEffect(EffectType.WasteNot2)))
return false; return false;
if (action == ActionType.Observe &&
CP < 12)
return false;
if (action == ActionType.MastersMend && if (action == ActionType.MastersMend &&
Input.Recipe.MaxDurability - Durability < 25) Input.Recipe.MaxDurability - Durability < 25)
return false; return false;
+3 -3
View File
@@ -116,7 +116,7 @@ public sealed class Solver : IDisposable
var bestSims = new List<(float Score, SolverSolution Result)>(); var bestSims = new List<(float Score, SolverSolution Result)>();
var state = State; var state = State;
var sim = new Simulator(state, Config.MaxStepCount); var sim = new Simulator(Config.MaxStepCount);
var activeStates = new List<SolverSolution>() { new(Array.Empty<ActionType>(), state) }; var activeStates = new List<SolverSolution>() { new(Array.Empty<ActionType>(), state) };
@@ -240,7 +240,7 @@ public sealed class Solver : IDisposable
{ {
var actions = new List<ActionType>(); var actions = new List<ActionType>();
var state = State; var state = State;
var sim = new Simulator(state, Config.MaxStepCount); var sim = new Simulator(Config.MaxStepCount) { State = state };
while (true) while (true)
{ {
Token.ThrowIfCancellationRequested(); Token.ThrowIfCancellationRequested();
@@ -303,7 +303,7 @@ public sealed class Solver : IDisposable
{ {
var actions = new List<ActionType>(); var actions = new List<ActionType>();
var state = State; var state = State;
var sim = new Simulator(state, Config.MaxStepCount); var sim = new Simulator(Config.MaxStepCount) { State = state };
while (true) while (true)
{ {
Token.ThrowIfCancellationRequested(); Token.ThrowIfCancellationRequested();
+2 -2
View File
@@ -68,7 +68,7 @@ public class SimulatorTests
int progress, int quality, int progress, int quality,
int durability, int cp) int durability, int cp)
{ {
var simulator = new SimulatorNoRandom(new(input)); var simulator = new SimulatorNoRandom();
var (_, state, _) = simulator.ExecuteMultiple(new(input), actions); var (_, state, _) = simulator.ExecuteMultiple(new(input), actions);
Assert.AreEqual(progress, state.Progress); Assert.AreEqual(progress, state.Progress);
Assert.AreEqual(quality, state.Quality); Assert.AreEqual(quality, state.Quality);
@@ -170,7 +170,7 @@ public class SimulatorTests
}, },
0, 4064, 15, 332); 0, 4064, 15, 332);
Assert.AreEqual(10, state.ActiveEffects.InnerQuiet); Assert.AreEqual(10, state.ActiveEffects.InnerQuiet);
Assert.IsTrue(ActionType.TrainedFinesse.Base().CanUse(new SimulatorNoRandom(state))); Assert.IsTrue(ActionType.TrainedFinesse.Base().CanUse(new SimulatorNoRandom() { State = state }));
} }
[TestMethod] [TestMethod]
+3 -3
View File
@@ -83,16 +83,16 @@ public class ActionSetTests
set.AddAction(ActionType.BasicSynthesis); set.AddAction(ActionType.BasicSynthesis);
set.AddAction(ActionType.ByregotsBlessing); set.AddAction(ActionType.ByregotsBlessing);
set.AddAction(ActionType.DelicateSynthesis); set.AddAction(ActionType.DelicateSynthesis);
set.AddAction(ActionType.FocusedTouch); set.AddAction(ActionType.Reflect);
Assert.AreEqual(4, set.Count); Assert.AreEqual(4, set.Count);
Assert.AreEqual(ActionType.DelicateSynthesis, set.ElementAt(0)); Assert.AreEqual(ActionType.DelicateSynthesis, set.ElementAt(0));
Assert.AreEqual(ActionType.FocusedTouch, set.ElementAt(1)); Assert.AreEqual(ActionType.Reflect, set.ElementAt(1));
Assert.AreEqual(ActionType.ByregotsBlessing, set.ElementAt(2)); Assert.AreEqual(ActionType.ByregotsBlessing, set.ElementAt(2));
Assert.AreEqual(ActionType.BasicSynthesis, set.ElementAt(3)); Assert.AreEqual(ActionType.BasicSynthesis, set.ElementAt(3));
set.RemoveAction(ActionType.FocusedTouch); set.RemoveAction(ActionType.Reflect);
Assert.AreEqual(3, set.Count); Assert.AreEqual(3, set.Count);