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,
};
var sim = new SimulatorNoRandom(new(input));
var sim = new SimulatorNoRandom();
(_, var state) = sim.Execute(new(input), ActionType.MuscleMemory);
(_, state) = sim.Execute(state, ActionType.PrudentTouch);
//(_, state) = sim.Execute(state, ActionType.Manipulation);
+1
View File
@@ -82,6 +82,7 @@ public class Configuration : IPluginConfiguration
private List<Macro> macros { get; set; } = new();
[JsonIgnore]
public IReadOnlyList<Macro> Macros => macros;
public int ReliabilitySimulationCount { get; set; } = 500;
public bool ConditionRandomness { get; set; } = true;
public SolverConfig SimulatorSolverConfig { get; set; } = SolverConfig.SimulatorDefault;
public SolverConfig SynthHelperSolverConfig { get; set; } = SolverConfig.SynthHelperDefault;
+1
View File
@@ -39,6 +39,7 @@
<ItemGroup>
<PackageReference Include="DalamudPackager" Version="2.1.12" />
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.106">
<PrivateAssets>all</PrivateAssets>
<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.Text;
namespace Craftimizer;
namespace Craftimizer.Plugin;
internal static unsafe class ImGuiExtras
{
+113 -33
View File
@@ -2,12 +2,16 @@ using Craftimizer.Utils;
using Dalamud.Interface;
using Dalamud.Interface.Utility.Raii;
using ImGuiNET;
using ImPlotNET;
using MathNet.Numerics.Statistics;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
@@ -130,37 +134,6 @@ internal static class ImGuiUtils
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)
{
var (s, c) = MathF.SinCos(theta);
@@ -168,6 +141,7 @@ internal static class ImGuiUtils
return new Vector2(c, -s);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float Lerp(float a, float b, float t) =>
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);
}
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
{
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);
}
public static bool IconButtonSized(FontAwesomeIcon icon, Vector2 size)
private static Vector2 GetIconSize(FontAwesomeIcon icon)
{
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;
}
+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)
{
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();
@@ -49,7 +49,7 @@ public sealed class MacroClipboard : Window, IDisposable
ImGui.SetCursorPos(buttonCursor);
{
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)
{
ImGui.SetClipboardText(macro);
+277 -56
View File
@@ -21,7 +21,8 @@ using System.Numerics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Sim = Craftimizer.Simulator.SimulatorNoRandom;
using Sim = Craftimizer.Simulator.Simulator;
using SimNoRandom = Craftimizer.Simulator.SimulatorNoRandom;
namespace Craftimizer.Windows;
@@ -76,22 +77,125 @@ public sealed class MacroEditor : Window, IDisposable
private List<int> HQIngredientCounts { get; set; }
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
{
public ActionType Action { get; init; }
public ActionType Action { get; }
// State *after* executing the action
public ActionResponse Response { get; set; }
public SimulationState State { get; set; }
public ActionResponse Response { get; private 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 SimulationState InitialState { get; set; }
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 Action<IEnumerable<ActionType>>? MacroSetter { get; set; }
private CancellationTokenSource? SolverTokenSource { get; set; }
private Exception? SolverException { get; set; }
private int? SolverStartStepCount { get; set; }
private object? SolverQueueLock { get; set; }
private List<SimulatedActionStep>? SolverQueuedSteps { get; set; }
private bool SolverRunning => SolverTokenSource != null;
private IDalamudTextureWrap ExpertBadge { get; }
@@ -114,7 +218,7 @@ public sealed class MacroEditor : Window, IDisposable
private CancellationTokenSource? popupImportUrlTokenSource;
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;
RecipeData = recipeData;
@@ -156,6 +260,11 @@ public sealed class MacroEditor : Window, IDisposable
SolverTokenSource?.Cancel();
}
public override void Update()
{
TryFlushSolvedSteps();
}
public override void Draw()
{
var modifiedInput = false;
@@ -298,24 +407,39 @@ public sealed class MacroEditor : Window, IDisposable
var imageButtonPadding = (int)(ImGui.GetStyle().FramePadding.Y / 2f);
var imageButtonSize = imageSize - imageButtonPadding * 2;
{
var v = CharacterStats.HasSplendorousBuff;
var tint = v ? Vector4.One : disabledTint;
if (ImGui.ImageButton(SplendorousBadge.ImGuiHandle, new Vector2(imageButtonSize), default, Vector2.One, imageButtonPadding, default, tint))
CharacterStats = CharacterStats with { HasSplendorousBuff = !v };
if (ImGui.IsItemHovered())
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 tint = v ? Vector4.One : disabledTint;
if (ImGui.ImageButton(SplendorousBadge.ImGuiHandle, new Vector2(imageButtonSize), default, Vector2.One, imageButtonPadding, default, tint))
CharacterStats = CharacterStats with { HasSplendorousBuff = !v };
}
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
ImGui.SetTooltip(CharacterStats.HasSplendorousBuff ? $"Splendorous Tool" : "No Splendorous Tool");
}
ImGui.SameLine(0, 5);
bool? newIsSpecialist = null;
{
var v = CharacterStats.IsSpecialist;
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))
var specialistLevel = 55;
if (CharacterStats.IsSpecialist && specialistLevel > CharacterStats.Level)
newIsSpecialist = v = false;
using (var d = ImRaii.Disabled(specialistLevel > CharacterStats.Level))
{
v = !v;
newIsSpecialist = v;
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))
{
v = !v;
newIsSpecialist = v;
}
}
if (ImGui.IsItemHovered())
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
ImGui.SetTooltip(v ? $"Specialist" : "Not a Specialist");
}
ImGui.SameLine(0, 5);
@@ -904,7 +1028,7 @@ public sealed class MacroEditor : Window, IDisposable
private void DrawActionHotbars()
{
var sim = new Sim(State);
var sim = CreateSim(State);
var imageSize = ImGui.GetFrameHeight() * 2;
var spacing = ImGui.GetStyle().ItemSpacing.Y;
@@ -919,7 +1043,7 @@ public sealed class MacroEditor : Window, IDisposable
continue;
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 itemCount = actions.Count;
var iterCount = (int)(Math.Ceiling((float)itemCount / itemsPerRow) * itemsPerRow);
@@ -971,23 +1095,26 @@ public sealed class MacroEditor : Window, IDisposable
ImGui.TableNextColumn();
var datas = new List<BarData>(3)
{
new("Durability", Colors.Durability, State.Durability, RecipeData.RecipeInfo.MaxDurability, null, null),
new("Condition", default, 0, 0, null, State.Condition)
new("Durability", Colors.Durability, null, State.Durability, RecipeData.RecipeInfo.MaxDurability, null, null),
new("Condition", default, null, 0, 0, null, State.Condition)
};
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)
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)
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);
ImGui.TableNextColumn();
datas = new List<BarData>(3)
{
new("Progress", Colors.Progress, State.Progress, RecipeData.RecipeInfo.MaxProgress, null, null),
new("Quality", Colors.Quality, State.Quality, RecipeData.RecipeInfo.MaxQuality, null, null),
new("CP", Colors.CP, State.CP, CharacterStats.CP, null, null)
new("Progress", Colors.Progress, Reliability.Progress, State.Progress, RecipeData.RecipeInfo.MaxProgress, null, null),
new("Quality", Colors.Quality, Reliability.Quality, State.Quality, RecipeData.RecipeInfo.MaxQuality, null, null),
new("CP", Colors.CP, null, State.CP, CharacterStats.CP, null, null)
};
if (RecipeData.RecipeInfo.MaxQuality <= 0)
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);
@@ -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)
{
var spacing = ImGui.GetStyle().ItemSpacing.X;
@@ -1053,13 +1180,16 @@ public sealed class MacroEditor : Window, IDisposable
var barSize = totalSize - textSize - spacing;
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)
{
var pos = ImGui.GetCursorPos();
using (var g = ImRaii.Group())
{
var availSize = totalSize - (spacing + ImGui.GetFrameHeight());
var size = ImGui.GetFrameHeight() + spacing + ImGui.CalcTextSize(condition.Name()).X;
ImGuiUtils.AlignCentered(size, totalSize);
ImGuiUtils.AlignCentered(size, availSize);
ImGui.GetWindowDrawList().AddCircleFilled(
ImGui.GetCursorScreenPos() + new Vector2(ImGui.GetFrameHeight() / 2),
ImGui.GetFrameHeight() / 2,
@@ -1070,12 +1200,49 @@ public sealed class MacroEditor : Window, IDisposable
ImGui.Text(condition.Name());
}
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
{
var pos = ImGui.GetCursorPos();
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);
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.AlignTextToFramePadding();
if (bar.Caption is { } caption)
@@ -1098,7 +1265,7 @@ public sealed class MacroEditor : Window, IDisposable
var imageSize = ImGui.GetFrameHeight() * 2;
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.SameLine(0, 0);
@@ -1131,8 +1298,7 @@ public sealed class MacroEditor : Window, IDisposable
}
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
var sim = new Sim(lastState);
ImGui.SetTooltip($"{action.GetName(RecipeData!.ClassJob)}\n{actionBase.GetTooltip(sim, true)}");
ImGui.SetTooltip($"{action.GetName(RecipeData!.ClassJob)}\n{actionBase.GetTooltip(CreateSim(lastState), true)}");
}
lastState = state;
}
@@ -1197,14 +1363,14 @@ public sealed class MacroEditor : Window, IDisposable
"can vary wildly depending on the solver's settings.");
}
ImGui.SameLine();
if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Paste, new(height)))
if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Paste))
Service.Plugin.CopyMacro(Macro.Select(s => s.Action).ToArray());
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Copy to Clipboard");
ImGui.SameLine();
using (var _disabled = ImRaii.Disabled(SolverRunning))
{
if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.FileImport, new(height)))
if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.FileImport))
ShowImportPopup();
}
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
@@ -1215,7 +1381,7 @@ public sealed class MacroEditor : Window, IDisposable
{
using (var _disabled = ImRaii.Disabled(SolverRunning))
{
if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Undo, new(height)))
if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Undo))
{
SolverStartStepCount = null;
Macro.Clear();
@@ -1229,7 +1395,7 @@ public sealed class MacroEditor : Window, IDisposable
ImGui.SameLine();
using (var _disabled = ImRaii.Disabled(SolverRunning))
{
if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Trash, new(height)))
if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Trash))
{
SolverStartStepCount = null;
Macro.Clear();
@@ -1293,7 +1459,7 @@ public sealed class MacroEditor : Window, IDisposable
{
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();
ImGuiUtils.TextCentered("Paste your macro here");
@@ -1305,7 +1471,7 @@ public sealed class MacroEditor : Window, IDisposable
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;
@@ -1405,7 +1571,7 @@ public sealed class MacroEditor : Window, IDisposable
if (popupImportUrlMacro is { Name: var name, Actions: var actions })
{
Macro.Clear();
foreach(var action in actions)
foreach (var action in actions)
AddStep(action);
Service.PluginInterface.UiBuilder.AddNotification($"Imported macro \"{name}\"", "Craftimizer Macro Imported", NotificationType.Success);
@@ -1420,13 +1586,32 @@ public sealed class MacroEditor : Window, IDisposable
popupImportUrlTokenSource = null;
}
}
private void CalculateBestMacro()
{
SolverTokenSource?.Cancel();
SolverTokenSource = new();
SolverException = null;
if (SolverQueueLock is { })
{
lock (SolverQueueLock)
{
SolverQueuedSteps!.Clear();
SolverQueueLock = null;
}
}
SolverQueueLock = new();
SolverQueuedSteps ??= new();
RevertPreviousMacro();
if (Service.Configuration.ConditionRandomness)
{
Service.Configuration.ConditionRandomness = false;
Service.Configuration.Save();
RecalculateState();
}
SolverStartStepCount = Macro.Count;
var token = SolverTokenSource.Token;
@@ -1462,7 +1647,7 @@ public sealed class MacroEditor : Window, IDisposable
var solver = new Solver.Solver(config, state) { Token = token };
solver.OnLog += Log.Debug;
solver.OnNewAction += a => AddStep(a, isSolver: true);
solver.OnNewAction += QueueSolverStep;
solver.Start();
_ = solver.GetTask().GetAwaiter().GetResult();
@@ -1483,36 +1668,72 @@ public sealed class MacroEditor : Window, IDisposable
private void RecalculateState()
{
InitialState = new SimulationState(new(CharacterStats, RecipeData.RecipeInfo, StartingQuality));
var sim = new Sim(InitialState);
var sim = CreateSim();
var lastState = InitialState;
foreach (var step in Macro)
lastState = ((step.Response, step.State) = sim.Execute(lastState, step.Action)).State;
for (var i = 0; i < Macro.Count; i++)
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)
throw new ArgumentOutOfRangeException(nameof(index));
if (!isSolver && SolverRunning)
if (SolverRunning)
throw new InvalidOperationException("Cannot add steps while solver is running");
if (!SolverRunning)
SolverStartStepCount = null;
if (index == -1)
{
var sim = new Sim(State);
var resp = sim.Execute(State, action);
Macro.Add(new() { Action = action, Response = resp.Response, State = resp.NewState });
var sim = CreateSim();
Macro.Add(new(action, sim, State, out _));
}
else
{
var state = index == 0 ? InitialState : Macro[index - 1].State;
var sim = new Sim(state);
var resp = sim.Execute(state, action);
Macro.Insert(index, new() { Action = action, Response = resp.Response, State = resp.NewState });
state = resp.NewState;
var sim = CreateSim();
Macro.Insert(index, new(action, sim, state, out state));
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);
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++)
state = ((Macro[i].Response, Macro[i].State) = sim.Execute(state, Macro[i].Action)).State;
state = Macro[i].Recalculate(sim, state);
}
public void Dispose()
+6 -6
View File
@@ -111,7 +111,7 @@ public sealed class MacroList : Window, IDisposable
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 spacing = ImGui.GetStyle().ItemSpacing.Y;
var miniRowHeight = (windowHeight - spacing) / 2f;
@@ -197,23 +197,23 @@ public sealed class MacroList : Window, IDisposable
ImGui.TableNextColumn();
{
if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Paste, new(miniRowHeight)))
if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Paste, miniRowHeight))
Service.Plugin.CopyMacro(macro.Actions);
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Copy to Clipboard");
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);
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Delete (Hold Shift)");
if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.PencilAlt, new(miniRowHeight)))
if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.PencilAlt, miniRowHeight))
ShowRenamePopup(macro);
DrawRenamePopup(macro);
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Rename");
ImGui.SameLine();
if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Edit, new(miniRowHeight)))
if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Edit, miniRowHeight))
OpenEditor(macro);
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Open in Simulator");
@@ -329,7 +329,7 @@ public sealed class MacroList : Window, IDisposable
return state;
state = new SimulationState(new(CharacterStats, RecipeData.RecipeInfo));
var sim = new Sim(state);
var sim = new Sim();
(_, state, _) = sim.ExecuteMultiple(state, macro.Actions);
return MacroStateCache[macro] = state;
}
+5 -5
View File
@@ -216,7 +216,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
ImGui.Separator();
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;
if (BestSavedMacro is { } savedMacro)
@@ -228,7 +228,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
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;
if (BestSuggestedMacro is { } suggestedMacro)
@@ -644,11 +644,11 @@ public sealed unsafe class RecipeNote : Window, IDisposable
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);
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Open in Simulator");
if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Paste, new(miniRowHeight)))
if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Paste, miniRowHeight))
Service.Plugin.CopyMacro(macro.Actions);
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Copy to Clipboard");
@@ -824,7 +824,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
var state = new SimulationState(input);
var config = Service.Configuration.SimulatorSolverConfig;
var mctsConfig = new MCTSConfig(config);
var simulator = new SimulatorNoRandom(state);
var simulator = new SimulatorNoRandom();
List<Macro> macros = new(Service.Configuration.Macros);
token.ThrowIfCancellationRequested();
+17 -24
View File
@@ -191,7 +191,7 @@ public sealed class Settings : Window, IDisposable
ImGui.SetTooltip("Disabled temporarily for testing");
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" +
"is shown. Otherwise, progress is shown. Durability and remaining CP will be\n" +
"hidden.",
@@ -200,9 +200,21 @@ public sealed class Settings : Window, IDisposable
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);
using (var panel = ImGuiUtils.GroupPanel("Copying Settings", -1, out _))
using (var panel = ImRaii2.GroupPanel("Copying Settings", -1, out _))
{
DrawOption(
"Macro Copy Method",
@@ -380,7 +392,7 @@ public sealed class Settings : Window, IDisposable
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))
{
@@ -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(
"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.");
ImGuiHelpers.ScaledDummy(10);
@@ -629,25 +641,6 @@ public sealed class Settings : Window, IDisposable
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;
DrawSolverConfig(ref solverConfig, SolverConfig.SimulatorDefault, out var isSolverDirty);
if (isSolverDirty)
+6
View File
@@ -8,6 +8,12 @@
"resolved": "2.1.12",
"contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg=="
},
"MathNet.Numerics": {
"type": "Direct",
"requested": "[5.0.0, )",
"resolved": "5.0.0",
"contentHash": "pg1W2VwaEQMAiTpGK840hZgzavnqjlCMTVSbtVCXVyT+7AX4mc1o89SPv4TBlAjhgCOo9c1Y+jZ5m3ti2YgGgA=="
},
"Meziantou.Analyzer": {
"type": "Direct",
"requested": "[2.0.106, )",
+19 -27
View File
@@ -6,20 +6,22 @@ namespace Craftimizer.Simulator;
public class Simulator
{
protected SimulationState State;
public SimulationState State { init => state = value; }
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;
private SimulationState state;
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 {
get
@@ -35,20 +37,10 @@ public class Simulator
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)
{
State = state;
return (Execute(action), State);
this.state = state;
return (Execute(action), this.state);
}
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)
{
State = state;
this.state = state;
var i = 0;
foreach(var action in actions)
{
var resp = Execute(action);
if (resp != ActionResponse.UsedAction)
return (resp, State, i);
return (resp, this.state, i);
i++;
}
return (ActionResponse.UsedAction, State, -1);
return (ActionResponse.UsedAction, this.state, -1);
}
[Pure]
-4
View File
@@ -2,10 +2,6 @@ namespace Craftimizer.Simulator;
public class SimulatorNoRandom : Simulator
{
public SimulatorNoRandom(in SimulationState state) : base(state)
{
}
public sealed override bool RollSuccessRaw(float successRate) => successRate == 1;
public sealed override Condition GetNextRandomCondition() => Condition.Normal;
}
-3
View File
@@ -24,8 +24,6 @@ public struct ActionSet
ActionType.DelicateSynthesis,
ActionType.PreparatoryTouch,
ActionType.Reflect,
ActionType.FocusedTouch,
ActionType.FocusedSynthesis,
ActionType.PrudentTouch,
ActionType.Manipulation,
ActionType.MuscleMemory,
@@ -37,7 +35,6 @@ public struct ActionSet
ActionType.StandardTouch,
ActionType.Veneration,
ActionType.WasteNot,
ActionType.Observe,
ActionType.MastersMend,
ActionType.BasicTouch,
};
+2 -2
View File
@@ -20,7 +20,7 @@ public sealed class MCTS
public MCTS(in MCTSConfig config, in SimulationState state)
{
this.config = config;
var sim = new Simulator(state, config.MaxStepCount);
var sim = new Simulator(config.MaxStepCount) { State = state };
rootNode = new(new(
state,
null,
@@ -280,7 +280,7 @@ public sealed class MCTS
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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 n = 0;
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;
}
@@ -42,22 +42,12 @@ internal sealed class Simulator : SimulatorNoRandom
if (Quality >= Input.Recipe.MaxQuality && baseAction.IncreasesQuality)
return false;
if (action == ActionType.Observe &&
ActionStates.Observed)
return false;
if (strict)
{
// always use Trained Eye if it's available
if (action == ActionType.TrainedEye)
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
if (Input.Recipe.ClassJobLevel == 90 &&
HasEffect(EffectType.MuscleMemory) &&
@@ -116,10 +106,6 @@ internal sealed class Simulator : SimulatorNoRandom
(HasEffect(EffectType.WasteNot) || HasEffect(EffectType.WasteNot2)))
return false;
if (action == ActionType.Observe &&
CP < 12)
return false;
if (action == ActionType.MastersMend &&
Input.Recipe.MaxDurability - Durability < 25)
return false;
+3 -3
View File
@@ -116,7 +116,7 @@ public sealed class Solver : IDisposable
var bestSims = new List<(float Score, SolverSolution Result)>();
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) };
@@ -240,7 +240,7 @@ public sealed class Solver : IDisposable
{
var actions = new List<ActionType>();
var state = State;
var sim = new Simulator(state, Config.MaxStepCount);
var sim = new Simulator(Config.MaxStepCount) { State = state };
while (true)
{
Token.ThrowIfCancellationRequested();
@@ -303,7 +303,7 @@ public sealed class Solver : IDisposable
{
var actions = new List<ActionType>();
var state = State;
var sim = new Simulator(state, Config.MaxStepCount);
var sim = new Simulator(Config.MaxStepCount) { State = state };
while (true)
{
Token.ThrowIfCancellationRequested();
+2 -2
View File
@@ -68,7 +68,7 @@ public class SimulatorTests
int progress, int quality,
int durability, int cp)
{
var simulator = new SimulatorNoRandom(new(input));
var simulator = new SimulatorNoRandom();
var (_, state, _) = simulator.ExecuteMultiple(new(input), actions);
Assert.AreEqual(progress, state.Progress);
Assert.AreEqual(quality, state.Quality);
@@ -170,7 +170,7 @@ public class SimulatorTests
},
0, 4064, 15, 332);
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]
+3 -3
View File
@@ -83,16 +83,16 @@ public class ActionSetTests
set.AddAction(ActionType.BasicSynthesis);
set.AddAction(ActionType.ByregotsBlessing);
set.AddAction(ActionType.DelicateSynthesis);
set.AddAction(ActionType.FocusedTouch);
set.AddAction(ActionType.Reflect);
Assert.AreEqual(4, set.Count);
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.BasicSynthesis, set.ElementAt(3));
set.RemoveAction(ActionType.FocusedTouch);
set.RemoveAction(ActionType.Reflect);
Assert.AreEqual(3, set.Count);