diff --git a/Benchmark/Program.cs b/Benchmark/Program.cs index 4ba614e..eb87a15 100644 --- a/Benchmark/Program.cs +++ b/Benchmark/Program.cs @@ -106,7 +106,7 @@ private static async Task RunOther() 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); diff --git a/Craftimizer/Configuration.cs b/Craftimizer/Configuration.cs index d88d579..7eda6ce 100644 --- a/Craftimizer/Configuration.cs +++ b/Craftimizer/Configuration.cs @@ -82,6 +82,7 @@ public class Configuration : IPluginConfiguration private List macros { get; set; } = new(); [JsonIgnore] public IReadOnlyList 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; diff --git a/Craftimizer/Craftimizer.csproj b/Craftimizer/Craftimizer.csproj index aa4cd9f..94acb1e 100644 --- a/Craftimizer/Craftimizer.csproj +++ b/Craftimizer/Craftimizer.csproj @@ -39,6 +39,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Craftimizer/ImGuiExtras.cs b/Craftimizer/ImGuiExtras.cs index bc60e1a..14adb76 100644 --- a/Craftimizer/ImGuiExtras.cs +++ b/Craftimizer/ImGuiExtras.cs @@ -5,7 +5,7 @@ using System.Runtime.InteropServices; using System.Text; -namespace Craftimizer; +namespace Craftimizer.Plugin; internal static unsafe class ImGuiExtras { diff --git a/Craftimizer/ImGuiUtils.cs b/Craftimizer/ImGuiUtils.cs index 68b83f1..b86e6f5 100644 --- a/Craftimizer/ImGuiUtils.cs +++ b/Craftimizer/ImGuiUtils.cs @@ -2,12 +2,16 @@ 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 @@ public static void EndGroupPanel() 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 @@ private static Vector2 UnitCircle(float theta) 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 @@ public static void ArcProgress(float value, float radiusInner, float radiusOuter 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 Data => (DataArray ?? Array.Empty()).AsSpan(); + private Point[]? DataArray { get; set; } + public readonly float Min; + public readonly float Max; + + public ViolinData(IEnumerable 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 where T : class { public readonly ImmutableArray items; @@ -467,10 +507,50 @@ public static bool InputTextMultilineWithHint(string label, string hint, ref str 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; } diff --git a/Craftimizer/ImRaii2.cs b/Craftimizer/ImRaii2.cs new file mode 100644 index 0000000..f2499e8 --- /dev/null +++ b/Craftimizer/ImRaii2.cs @@ -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); + } +} diff --git a/Craftimizer/Windows/MacroClipboard.cs b/Craftimizer/Windows/MacroClipboard.cs index a320b93..989ce6a 100644 --- a/Craftimizer/Windows/MacroClipboard.cs +++ b/Craftimizer/Windows/MacroClipboard.cs @@ -36,7 +36,7 @@ public override void Draw() 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 @@ private void DrawMacro(int idx, string macro) 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); diff --git a/Craftimizer/Windows/MacroEditor.cs b/Craftimizer/Windows/MacroEditor.cs index 0114ec7..89fe2b7 100644 --- a/Craftimizer/Windows/MacroEditor.cs +++ b/Craftimizer/Windows/MacroEditor.cs @@ -21,7 +21,8 @@ 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 CrafterBuffs(StatusList? statuses) private List HQIngredientCounts { get; set; } private int StartingQuality => RecipeData.CalculateStartingQuality(HQIngredientCounts); + private readonly record struct SimulationReliablity + { + public sealed class ParamReliability + { + private List 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 actions, int iterCount, RecipeData recipeData) + { + Func 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 actionSet, RecipeData recipeData) => + Reliability ??= + new(initialState, actionSet, Service.Configuration.ReliabilitySimulationCount, recipeData); }; + private List 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(), 0, RecipeData); private ActionType[] DefaultActions { get; } private Action>? 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? SolverQueuedSteps { get; set; } private bool SolverRunning => SolverTokenSource != null; private IDalamudTextureWrap ExpertBadge { get; } @@ -114,7 +218,7 @@ private sealed record SimulatedActionStep private CancellationTokenSource? popupImportUrlTokenSource; private MacroImport.RetrievedMacro? popupImportUrlMacro; - public MacroEditor(CharacterStats characterStats, RecipeData recipeData, CrafterBuffs buffs, IEnumerable actions, Action>? setter) : base("Craftimizer Macro Editor", WindowFlags, false) + public MacroEditor(CharacterStats characterStats, RecipeData recipeData, CrafterBuffs buffs, IEnumerable actions, Action>? setter) : base("Craftimizer Macro Editor", WindowFlags) { CharacterStats = characterStats; RecipeData = recipeData; @@ -156,6 +260,11 @@ public override void OnClose() SolverTokenSource?.Cancel(); } + public override void Update() + { + TryFlushSolvedSteps(); + } + public override void Draw() { var modifiedInput = false; @@ -298,24 +407,39 @@ void DrawStat(string name, int value, Action setter) 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 @@ private void DrawIngredientHQEntry(int idx) 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 @@ private void DrawActionHotbars() 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 @@ private void DrawMacroInfo() ImGui.TableNextColumn(); var datas = new List(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(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 @@ private void DrawMacroInfo() } } - 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 @@ private void DrawMacroInfo() } } - 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 bars) { var spacing = ImGui.GetStyle().ItemSpacing.X; @@ -1053,13 +1180,16 @@ private void DrawBars(IEnumerable bars) 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 @@ private void DrawBars(IEnumerable bars) 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 @@ private void DrawMacro() 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 @@ private void DrawMacro() } 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 @@ private void DrawMacroActions(float availWidth) "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 @@ private void DrawMacroActions(float availWidth) { 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 @@ private void DrawMacroActions(float availWidth) 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 @@ private void DrawImportPopup() { 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 @@ private void DrawImportPopup() 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 @@ private void DrawImportPopup() 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 @@ private void DrawImportPopup() 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 @@ private void CalculateBestMacroTask(SimulationState state, CancellationToken tok 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 @@ private void SaveMacro() 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 @@ private void RemoveStep(int index) 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() diff --git a/Craftimizer/Windows/MacroList.cs b/Craftimizer/Windows/MacroList.cs index 3601af6..dbceea8 100644 --- a/Craftimizer/Windows/MacroList.cs +++ b/Craftimizer/Windows/MacroList.cs @@ -111,7 +111,7 @@ private void DrawMacro(Macro 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 spacing = ImGui.GetStyle().ItemSpacing.Y; var miniRowHeight = (windowHeight - spacing) / 2f; @@ -197,23 +197,23 @@ private void DrawMacro(Macro macro) 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 @@ private void OnMacroListChanged() 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; } diff --git a/Craftimizer/Windows/RecipeNote.cs b/Craftimizer/Windows/RecipeNote.cs index 5abd350..fcc4895 100644 --- a/Craftimizer/Windows/RecipeNote.cs +++ b/Craftimizer/Windows/RecipeNote.cs @@ -216,7 +216,7 @@ public override void Draw() 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 override void Draw() 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 @@ private void DrawMacro((IReadOnlyList Actions, SimulationState State 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 @@ private void CalculateBestMacrosTask(CancellationToken token) 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 macros = new(Service.Configuration.Macros); token.ThrowIfCancellationRequested(); diff --git a/Craftimizer/Windows/Settings.cs b/Craftimizer/Windows/Settings.cs index a11ca3c..4bd3482 100644 --- a/Craftimizer/Windows/Settings.cs +++ b/Craftimizer/Windows/Settings.cs @@ -191,7 +191,7 @@ ref isDirty 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 @@ 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); - 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 @@ private static void DrawSolverConfig(ref SolverConfig configRef, SolverConfig de 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 @@ ref isDirty ); } - using (var panel = ImGuiUtils.GroupPanel("Advanced", -1, out _)) + using (var panel = ImRaii2.GroupPanel("Advanced", -1, out _)) { DrawOption( "Score Storage Threshold", @@ -538,7 +550,7 @@ ref isDirty ); } - 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 @@ private void DrawTabSimulator() 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) diff --git a/Craftimizer/packages.lock.json b/Craftimizer/packages.lock.json index 2308529..fb17722 100644 --- a/Craftimizer/packages.lock.json +++ b/Craftimizer/packages.lock.json @@ -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, )", diff --git a/Simulator/Simulator.cs b/Simulator/Simulator.cs index 03dbb12..75364f5 100644 --- a/Simulator/Simulator.cs +++ b/Simulator/Simulator.cs @@ -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 virtual CompletionState CompletionState { public IEnumerable 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 @@ private ActionResponse Execute(ActionType action) public (ActionResponse Response, SimulationState NewState, int FailedActionIdx) ExecuteMultiple(in SimulationState state, IEnumerable 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] diff --git a/Simulator/SimulatorNoRandom.cs b/Simulator/SimulatorNoRandom.cs index e6c4738..9c39996 100644 --- a/Simulator/SimulatorNoRandom.cs +++ b/Simulator/SimulatorNoRandom.cs @@ -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; } diff --git a/Solver/ActionSet.cs b/Solver/ActionSet.cs index b7cc97d..9c7710c 100644 --- a/Solver/ActionSet.cs +++ b/Solver/ActionSet.cs @@ -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, }; diff --git a/Solver/MCTS.cs b/Solver/MCTS.cs index 76251db..99e6147 100644 --- a/Solver/MCTS.cs +++ b/Solver/MCTS.cs @@ -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 @@ static bool NodesIncomplete(Node node, Stack path) [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++) diff --git a/Solver/Simulator.cs b/Solver/Simulator.cs index a3a29fa..ea0404f 100644 --- a/Solver/Simulator.cs +++ b/Solver/Simulator.cs @@ -20,7 +20,7 @@ public override CompletionState CompletionState } } - public Simulator(in SimulationState state, int maxStepCount) : base(state) + public Simulator(int maxStepCount) { this.maxStepCount = maxStepCount; } @@ -42,22 +42,12 @@ private bool CanUseAction(ActionType action, bool strict) 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 @@ private bool CanUseAction(ActionType action, bool strict) (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; diff --git a/Solver/Solver.cs b/Solver/Solver.cs index 7c9efcf..add8bd1 100644 --- a/Solver/Solver.cs +++ b/Solver/Solver.cs @@ -116,7 +116,7 @@ private async Task SearchStepwiseFurcated() 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() { new(Array.Empty(), state) }; @@ -240,7 +240,7 @@ private async Task SearchStepwiseForked() { var actions = new List(); 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 @@ private Task SearchStepwise() { var actions = new List(); var state = State; - var sim = new Simulator(state, Config.MaxStepCount); + var sim = new Simulator(Config.MaxStepCount) { State = state }; while (true) { Token.ThrowIfCancellationRequested(); diff --git a/Test/Simulator/Simulator.cs b/Test/Simulator/Simulator.cs index 20756fc..886f056 100644 --- a/Test/Simulator/Simulator.cs +++ b/Test/Simulator/Simulator.cs @@ -68,7 +68,7 @@ private static SimulationState AssertCraft(SimulationInput input, IEnumerable