diff --git a/Craftimizer.sln b/Craftimizer.sln index 3f50abb..1e2265a 100644 --- a/Craftimizer.sln +++ b/Craftimizer.sln @@ -4,6 +4,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 VisualStudioVersion = 17.4.33213.308 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Craftimizer.Plugin", "Craftimizer\Craftimizer.Plugin.csproj", "{13C812E9-0D42-4B95-8646-40EEBF30636F}" + ProjectSection(ProjectDependencies) = postProject + {2B0EA452-6DFC-48DB-9049-EA782E600C21} = {2B0EA452-6DFC-48DB-9049-EA782E600C21} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Craftimizer.Benchmark", "Benchmark\Craftimizer.Benchmark.csproj", "{057C4B64-4D99-4847-9BCF-966571CAE57C}" EndProject diff --git a/Craftimizer/Configuration.cs b/Craftimizer/Configuration.cs index 9d525a7..10138c0 100644 --- a/Craftimizer/Configuration.cs +++ b/Craftimizer/Configuration.cs @@ -1,5 +1,6 @@ using Craftimizer.Simulator; using Craftimizer.Simulator.Actions; +using Craftimizer.Solver.Crafty; using Dalamud.Configuration; using Dalamud.Logging; using System; @@ -22,22 +23,13 @@ public class Configuration : IPluginConfiguration public bool OverrideUncraftability { get; set; } = true; public bool HideUnlearnedActions { get; set; } = true; public List Macros { get; set; } = new(); - public string SimulatorType { get; set; } = typeof(Simulator.Simulator).AssemblyQualifiedName!; + public SolverConfig SolverConfig { get; set; } = new(); + public bool ConditionRandomness { get; set; } = true; - public Simulator.Simulator CreateSimulator(SimulationState state) - { - var type = Type.GetType(SimulatorType); - if (type == null) - PluginLog.LogError($"Failed to resolve simulator type ({SimulatorType})"); - else - { - if (Activator.CreateInstance(type, state) is Simulator.Simulator sim) - return sim; - - PluginLog.LogError($"Failed to create simulator ({SimulatorType})"); - } - return new Simulator.Simulator(state); - } + public Simulator.Simulator CreateSimulator(SimulationState state) => + ConditionRandomness ? + new Simulator.Simulator(state) : + new SimulatorNoRandom(state); public void Save() => Service.PluginInterface.SavePluginConfig(this); diff --git a/Craftimizer/Craftimizer.Plugin.csproj b/Craftimizer/Craftimizer.Plugin.csproj index 14c5f77..309d12b 100644 --- a/Craftimizer/Craftimizer.Plugin.csproj +++ b/Craftimizer/Craftimizer.Plugin.csproj @@ -36,6 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + $(DalamudLibPath)FFXIVClientStructs.dll false diff --git a/Craftimizer/Plugin.cs b/Craftimizer/Plugin.cs index 505f70c..f0758bd 100644 --- a/Craftimizer/Plugin.cs +++ b/Craftimizer/Plugin.cs @@ -53,6 +53,7 @@ public sealed class Plugin : IDalamudPlugin public void Dispose() { Service.CommandManager.RemoveHandler("/craft"); + SimulatorWindow?.Dispose(); } private void OnCommand(string command, string args) diff --git a/Craftimizer/Windows/SimulatorWindow.cs b/Craftimizer/Windows/SimulatorWindow.cs index 86828d9..bbe4d47 100644 --- a/Craftimizer/Windows/SimulatorWindow.cs +++ b/Craftimizer/Windows/SimulatorWindow.cs @@ -1,53 +1,19 @@ using Craftimizer.Simulator; using Craftimizer.Simulator.Actions; -using Dalamud.Interface; using Dalamud.Interface.Windowing; -using Dalamud.Logging; -using Dalamud.Memory; -using Dalamud.Utility; -using FFXIVClientStructs.FFXIV.Client.System.Framework; -using FFXIVClientStructs.FFXIV.Client.UI; -using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; -using ImGuiScene; using Lumina.Excel.GeneratedSheets; using System; using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Numerics; -using ActionCategory = Craftimizer.Simulator.ActionCategory; using ClassJob = Craftimizer.Simulator.ClassJob; -using Condition = Craftimizer.Simulator.Condition; namespace Craftimizer.Plugin.Windows; -public class SimulatorWindow : Window +public sealed partial class SimulatorWindow : Window, IDisposable { private const ImGuiWindowFlags WindowFlags = ImGuiWindowFlags.AlwaysAutoResize; - private static readonly Vector2 ProgressBarSize = new(200, 20); - private static readonly Vector2 DurabilityBarSize = new(100, 20); - private static readonly Vector2 ConditionBarSize = new(20, 20); - private static readonly Vector2 ProgressBarSizeOld = new(200, 20); - private static readonly Vector2 TooltipProgressBarSize = new(100, 5); - - private static readonly Vector4 ProgressColor = new(0.44f, 0.65f, 0.18f, 1f); - private static readonly Vector4 QualityColor = new(0.26f, 0.71f, 0.69f, 1f); - private static readonly Vector4 DurabilityColor = new(0.13f, 0.52f, 0.93f, 1f); - private static readonly Vector4 HQColor = new(0.592f, 0.863f, 0.376f, 1f); - private static readonly Vector4 CPColor = new(0.63f, 0.37f, 0.75f, 1f); - - private static readonly Vector4 BadActionImageTint = new(1f, .5f, .5f, 1f); - private static readonly Vector4 BadActionImageColor = new(1f, .3f, .3f, 1f); - - private static readonly Vector4 BadActionTextColor = new(1f, .2f, .2f, 1f); - - private static readonly (ActionCategory Category, ActionType[] Actions)[] SortedActions; - - private TimeSpan FrameTime { get; set; } - private Stopwatch Stopwatch { get; } = new(); + private static Configuration Configuration => Service.Configuration; private Item Item { get; } private bool IsExpert { get; } @@ -55,16 +21,14 @@ public class SimulatorWindow : Window private ClassJob ClassJob { get; } // State is the state of the simulation *after* its corresponding action is executed. private List<(ActionType Action, string Tooltip, ActionResponse Response, SimulationState State)> Actions { get; } - private Simulator.Simulator Simulator { get; } + private Simulator.Simulator Simulator { get; set; } private SimulationState LatestState => Actions.Count == 0 ? new(Input) : Actions[^1].State; - static SimulatorWindow() - { - SortedActions = Enum.GetValues().GroupBy(a => a.Category()).Select(g => (g.Key, g.OrderBy(a => a.Level()).ToArray())).ToArray(); - } - + // Simulator is set by ResetSimulator() +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. public SimulatorWindow(Item item, bool isExpert, SimulationInput input, ClassJob classJob, List actions) : base("Simulator", WindowFlags) +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. { Service.WindowSystem.AddWindow(this); @@ -73,423 +37,23 @@ public class SimulatorWindow : Window Input = input; ClassJob = classJob; Actions = new(); - Simulator = Service.Configuration.CreateSimulator(new(input)); - - foreach(var action in actions) - AppendAction(action); + ResetSimulator(); IsOpen = true; + CollapsedCondition = ImGuiCond.Appearing; Collapsed = false; - } - public override void PreDraw() - { - Stopwatch.Restart(); + SizeCondition = ImGuiCond.Appearing; + Size = SizeConstraints?.MinimumSize ?? new(10); - base.PreDraw(); - } - - public override void PostDraw() - { - Stopwatch.Stop(); - FrameTime = Stopwatch.Elapsed; - - /* - unsafe { - var unitBase = (AtkUnitBase*)Service.GameGui.GetAddonByName("Synthesis"); - if (unitBase != null) - { - var res = unitBase->GetNodeById(95)->GetAsAtkComponentNode()->Component; - var cond = MemoryHelper.ReadStringNullTerminated((nint)res->GetTextNodeById(4)->GetAsAtkTextNode()->GetText()); - var img = res->GetImageNodeById(3); - - var d = unchecked(((short)img->AddRed, (short)img->AddGreen, (short)img->AddBlue)); - PluginLog.LogDebug($"{cond} -> {d}"); - } - } - */ - base.PostDraw(); - } - - public override void Draw() - { - ImGui.BeginTable("simulatorWindow", 2, ImGuiTableFlags.BordersInnerV); - ImGui.TableSetupColumn("", ImGuiTableColumnFlags.WidthFixed, 260); - ImGui.TableSetupColumn("", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableNextColumn(); - DrawActions(); - ImGui.TableNextColumn(); - DrawSimulationInfo(); - ImGui.EndTable(); - - ImGui.TextUnformatted($"{FrameTime.TotalMilliseconds:0.00}ms"); - return; - } - - private void DrawActions() - { - - var actionSize = new Vector2(ImGui.GetFontSize() * 2f); - ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero); - ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Zero); - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Zero); - - //ImGui.Checkbox("Show only guaranteed actions", ref showOnlyGuaranteedActions); - - foreach (var (category, actions) in SortedActions) - { - var i = 0; - ImGuiUtils.BeginGroupPanel(category.GetDisplayName(), 260); - foreach (var action in actions) - { - var baseAction = action.Base(); - - var cannotUse = action.Level() > Input.Stats.Level || (action == ActionType.Manipulation && !Input.Stats.CanUseManipulation); - if (cannotUse && Service.Configuration.HideUnlearnedActions) - continue; - - var shouldNotUse = !baseAction.CanUse(Simulator) || Simulator.IsComplete; - - ImGui.BeginDisabled(cannotUse); - - if (ImGui.ImageButton(action.GetIcon(ClassJob).ImGuiHandle, actionSize, Vector2.Zero, Vector2.One, 0, default, shouldNotUse ? BadActionImageTint : Vector4.One)) - AppendAction(action); - - if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) - ImGui.SetTooltip($"{action.GetName(ClassJob)}\n{baseAction.GetTooltip(Simulator, true)}"); - - ImGui.EndDisabled(); - - if (++i % 5 != 0) - ImGui.SameLine(); - } - if (i == 0) - ImGui.Dummy(actionSize); - ImGuiUtils.EndGroupPanel(); - } - ImGui.PopStyleColor(3); - } - - private void DrawSimulationInfo() - { - DrawSimulationSynth(); - ImGuiHelpers.ScaledDummy(5); - DrawSimulationEffects(); - ImGuiHelpers.ScaledDummy(5); - DrawSimulationActions(); - } - - private (float leftColumn, float leftText, float rightColumn, float rightText, float totalWidth) CalculateSimulationSynthWidths() - { - var sidePadding = ImGui.GetFrameHeight() / 2; - var separatorTextWidth = ImGui.CalcTextSize(" / ").X; - var itemSpacing = ImGui.GetStyle().ItemSpacing.X * 2; - - var leftDigits = (int)MathF.Floor(MathF.Log10(Input.Recipe.MaxDurability) + 1); - var leftTextWidth = ImGui.CalcTextSize(new string('0', leftDigits)).X; - var leftWidth = DurabilityBarSize.X + sidePadding + itemSpacing + separatorTextWidth + leftTextWidth * 2; - - - var rightDigits = (int)MathF.Floor(MathF.Log10(Math.Max(Math.Max(Input.Recipe.MaxProgress, Input.Recipe.MaxQuality), Input.Stats.CP)) + 1); - var rightTextWidth = ImGui.CalcTextSize(new string('0', rightDigits)).X; - var rightWidth = ProgressBarSize.X + sidePadding + itemSpacing + separatorTextWidth + rightTextWidth * 2; - - return (leftWidth, leftTextWidth, rightWidth, rightTextWidth, leftWidth + rightWidth + itemSpacing / 2); - } - - private void DrawSimulationSynth() - { - var state = LatestState; - var imageSize = new Vector2(ImGui.GetFontSize() * 2f); - - { - ImGui.Image(Icons.GetIconFromId(Item.Icon).ImGuiHandle, imageSize); - ImGui.SameLine(); - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + ImGui.GetFontSize() * .5f); - ImGui.TextUnformatted(Item.Name.ToDalamudString().ToString()); - if (Item.IsCollectable) - { - ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + ImGui.GetFontSize() * .5f); - ImGui.TextColored(new(0.98f, 0.98f, 0.61f, 1), "(Collectible)"); - } - if (IsExpert) - { - ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + ImGui.GetFontSize() * .5f); - ImGui.TextColored(new(0.941f, 0.557f, 0.216f, 1), "(Expert)"); - } - var availWidth = ImGui.GetContentRegionAvail().X; - var text = $"Step {state.StepCount + 1}"; - var textWidth = ImGui.CalcTextSize(text).X; - ImGui.SameLine(availWidth - textWidth); - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + ImGui.GetFontSize() * .5f); - ImGui.TextUnformatted(text); - ImGui.Separator(); - } - - ImGui.BeginTable("simSynth", 2); - - var (leftWidth, leftTextWidth, rightWidth, rightTextWidth, _) = CalculateSimulationSynthWidths(); - - ImGui.TableSetupColumn("", ImGuiTableColumnFlags.WidthFixed, leftWidth); - ImGui.TableSetupColumn("", ImGuiTableColumnFlags.WidthFixed, rightWidth); - ImGui.TableNextColumn(); - - DrawSynthBarCenteredProgress("Durability", state.Durability, Input.Recipe.MaxDurability, DurabilityBarSize, DurabilityColor, leftTextWidth); - - DrawSynthBarCenteredCircle("Condition", state.Condition.Name(), ConditionBarSize, new Vector4(.35f, .35f, .35f, 0) + state.Condition.GetColor(DateTime.UtcNow.TimeOfDay), DurabilityBarSize, leftTextWidth); - if (ImGui.IsItemHovered()) - ImGui.SetTooltip(state.Condition.Description(state.Input.Stats.HasSplendorousBuff)); - - if (Item.IsCollectable) - { - var collectibility = Math.Max(state.Quality / 10, 1); - DrawSynthBarCentered("Collectability", collectibility, Input.Recipe.MaxQuality / 10, $"{collectibility}", DurabilityBarSize, HQColor, leftTextWidth); - } - else - DrawSynthBarCentered("HQ %", state.HQPercent, 100, $"{state.HQPercent}%", DurabilityBarSize, HQColor, leftTextWidth); - - ImGui.TableNextColumn(); - - DrawSynthBarCenteredProgress("Progress", state.Progress, Input.Recipe.MaxProgress, ProgressBarSize, ProgressColor, rightTextWidth); - DrawSynthBarCenteredProgress("Quality", state.Quality, Input.Recipe.MaxQuality, ProgressBarSize, QualityColor, rightTextWidth); - DrawSynthBarCenteredProgress("CP", state.CP, Input.Stats.CP, ProgressBarSize, CPColor, rightTextWidth); - - ImGui.EndTable(); - } - - private void DrawSynthBarCenteredProgress(string name, int current, int max, Vector2 size, Vector4 color, float textWidth) - { - ImGuiUtils.BeginGroupPanel(name); - - DrawProgressBar(current, max, size, color); - - var w = ImGui.GetStyle().ItemSpacing.X; - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0, ImGui.GetStyle().ItemSpacing.Y)); - - ImGui.SameLine(0, textWidth - ImGui.CalcTextSize($"{current}").X + w); - var adjustedHeight = ImGui.GetCursorPosY() - ((ImGui.GetFrameHeight() - ImGui.GetFontSize()) / 2f); - ImGui.SetCursorPosY(adjustedHeight); - ImGui.TextUnformatted($"{current}"); - - ImGui.SameLine(); - ImGui.SetCursorPosY(adjustedHeight); - ImGui.TextUnformatted(" / "); - - ImGui.SameLine(0, textWidth - ImGui.CalcTextSize($"{max}").X); - ImGui.SetCursorPosY(adjustedHeight); - ImGui.TextUnformatted($"{max}"); - - ImGui.PopStyleVar(); - - ImGuiUtils.EndGroupPanel(); - } - - private void DrawSynthBarCenteredCircle(string name, string text, Vector2 size, Vector4 color, Vector2 otherProgressSize, float textWidth) - { - ImGuiUtils.BeginGroupPanel(name); - - var w = ImGui.GetStyle().ItemSpacing.X; - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0, ImGui.GetStyle().ItemSpacing.Y)); - - var contentWidth = size.X + w + ImGui.CalcTextSize(text).X; - var totalWidth = otherProgressSize.X + w + textWidth * 2 + ImGui.CalcTextSize(" / ").X; - - ImGui.Dummy(default); - ImGui.SameLine(0, (totalWidth - contentWidth) / 2); - ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, Math.Max(size.X, size.Y)); - DrawProgressBar(1, 1, size, color); - ImGui.PopStyleVar(); - ImGui.SameLine(0, w); - var adjustedHeight = ImGui.GetCursorPosY() - ((ImGui.GetFrameHeight() - ImGui.GetFontSize()) / 2f); - ImGui.SetCursorPosY(adjustedHeight); - ImGui.TextUnformatted(text); - - ImGui.PopStyleVar(); - - ImGuiUtils.EndGroupPanel(); - } - - private void DrawSynthBarCentered(string name, int current, int max, string text, Vector2 size, Vector4 color, float textWidth) - { - ImGuiUtils.BeginGroupPanel(name); - - DrawProgressBar(current, max, size, color); - - var w = ImGui.GetStyle().ItemSpacing.X; - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0, ImGui.GetStyle().ItemSpacing.Y)); - - var totalWidth = textWidth * 2 + ImGui.CalcTextSize(" / ").X; - - ImGui.SameLine(0, totalWidth - ImGui.CalcTextSize(text).X + w); - var adjustedHeight = ImGui.GetCursorPosY() - ((ImGui.GetFrameHeight() - ImGui.GetFontSize()) / 2f); - ImGui.SetCursorPosY(adjustedHeight); - ImGui.TextUnformatted(text); - - ImGui.PopStyleVar(); - - ImGuiUtils.EndGroupPanel(); - } - - private void DrawSimulationEffects() - { - var (_, _, _, _, totalWidth) = CalculateSimulationSynthWidths(); - - ImGuiUtils.BeginGroupPanel("Effects", totalWidth); - - var effectHeight = ImGui.GetFontSize() * 2f; - Vector2 GetEffectSize(TextureWrap icon) => new(icon.Width * effectHeight / icon.Height, effectHeight); - - ImGui.Dummy(new(0, effectHeight)); - ImGui.SameLine(0, 0); - foreach (var effect in Enum.GetValues()) - { - var duration = Simulator.GetEffectDuration(effect); - if (duration == 0) - continue; - - var strength = Simulator.GetEffectStrength(effect); - var icon = effect.GetIcon(strength); - var iconSize = GetEffectSize(icon); - - ImGui.Image(icon.ImGuiHandle, iconSize); - if (ImGui.IsItemHovered()) - ImGui.SetTooltip(effect.GetTooltip(strength, duration)); - if (duration != 0) - { - ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (effectHeight - ImGui.GetFontSize()) / 2f); - ImGui.Text($"{duration}"); - } - ImGui.SameLine(); - } - ImGui.Dummy(Vector2.Zero); - - ImGuiUtils.EndGroupPanel(); - } - - private void DrawSimulationActions() - { - var (_, _, _, _, totalWidth) = CalculateSimulationSynthWidths(); - - ImGuiUtils.BeginGroupPanel("Actions", totalWidth); - - ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero); - ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Zero); - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Zero); - var actionSize = new Vector2((totalWidth / 10) - ImGui.GetStyle().ItemSpacing.X * (11f/10)); - ImGui.Dummy(new(0, actionSize.Y)); - ImGui.SameLine(0, 0); - for (var i = 0; i < Actions.Count; ++i) - { - var (action, tooltip, response, state) = Actions[i]; - ImGui.PushID(i); - if (ImGui.ImageButton(action.GetIcon(ClassJob).ImGuiHandle, actionSize, Vector2.Zero, Vector2.One, 0, default, response != ActionResponse.UsedAction ? BadActionImageTint : Vector4.One)) - RemoveAction(i); - if (ImGui.BeginDragDropSource()) - { - unsafe { ImGui.SetDragDropPayload("simulationAction", (nint)(&i), sizeof(int)); } - ImGui.ImageButton(Actions[i].Action.GetIcon(ClassJob).ImGuiHandle, actionSize); - ImGui.EndDragDropSource(); - } - if (ImGui.BeginDragDropTarget()) - { - var payload = ImGui.AcceptDragDropPayload("simulationAction"); - bool isValidPayload; - unsafe { isValidPayload = payload.NativePtr != null; } - if (isValidPayload) - { - int draggedIdx; - unsafe { draggedIdx = *(int*)payload.Data; } - var draggedAction = Actions[draggedIdx].Action; - RemoveAction(draggedIdx); - InsertAction(i, draggedAction); - } - ImGui.EndDragDropTarget(); - } - if (ImGui.IsItemHovered()) - { - ImGui.BeginTooltip(); - var responseText = response switch - { - ActionResponse.SimulationComplete => "Recipe Complete", - ActionResponse.ActionNotUnlocked => "Action Not Unlocked", - ActionResponse.NotEnoughCP => "Not Enough CP", - ActionResponse.NoDurability => "No More Durability", - ActionResponse.CannotUseAction => "Cannot Use", - _ => string.Empty, - }; - if (response != ActionResponse.UsedAction) - ImGui.TextColored(BadActionTextColor, responseText); - ImGui.Text($"{action.GetName(ClassJob)}\n{tooltip}"); - DrawProgressBarTooltip(state.Progress, Input.Recipe.MaxProgress, ProgressColor); - DrawProgressBarTooltip(state.Quality, Input.Recipe.MaxQuality, QualityColor); - DrawProgressBarTooltip(state.Durability, Input.Recipe.MaxDurability, DurabilityColor); - DrawProgressBarTooltip(state.CP, Input.Stats.CP, CPColor); - ImGui.Text("Click to Remove\nDrag to Move"); - ImGui.EndTooltip(); - } - ImGui.PopID(); - if (i % 10 != 9) - ImGui.SameLine(); - } - ImGui.PopStyleColor(3); - - ImGuiUtils.EndGroupPanel(); - } - - private void AppendAction(ActionType action) - { - var tooltip = action.Base().GetTooltip(Simulator, false); - var (response, state) = Simulator.Execute(LatestState, action); - Actions.Add((action, tooltip, response, state)); - } - - private void RemoveAction(int actionIndex) - { - // Remove action - Actions.RemoveAt(actionIndex); - - // Take note of all actions afterwards - Span succeedingActions = stackalloc ActionType[Actions.Count - actionIndex]; - for (var i = 0; i < succeedingActions.Length; i++) - succeedingActions[i] = Actions[i + actionIndex].Action; - - // Remove all future actions - Actions.RemoveRange(actionIndex, succeedingActions.Length); - - // Re-execute all future actions - foreach (var action in succeedingActions) + foreach (var action in actions) AppendAction(action); } - private void InsertAction(int actionIndex, ActionType action) + private void ResetSimulator() { - // Take note of all actions afterwards - Span succeedingActions = stackalloc ActionType[Actions.Count - actionIndex]; - for (var i = 0; i < succeedingActions.Length; i++) - succeedingActions[i] = Actions[i + actionIndex].Action; - - // Remove all future actions - Actions.RemoveRange(actionIndex, succeedingActions.Length); - - // Execute new action - AppendAction(action); - - // Re-execute all future actions - foreach (var succeededAction in succeedingActions) - AppendAction(succeededAction); - } - - private static void DrawProgressBarTooltip(int progress, int maxProgress, Vector4 color) => - DrawProgressBar(progress, maxProgress, TooltipProgressBarSize, color); - - private static void DrawProgressBar(int progress, int maxProgress, Vector2 size, Vector4 color, string overlay = "") - { - ImGui.PushStyleColor(ImGuiCol.PlotHistogram, color); - ImGui.ProgressBar(Math.Clamp((float)progress / maxProgress, 0f, 1f), size, overlay); - ImGui.PopStyleColor(); + Simulator = Service.Configuration.CreateSimulator(LatestState); + ReexecuteAllActions(); } } diff --git a/Craftimizer/Windows/SimulatorWindowActions.cs b/Craftimizer/Windows/SimulatorWindowActions.cs new file mode 100644 index 0000000..6370274 --- /dev/null +++ b/Craftimizer/Windows/SimulatorWindowActions.cs @@ -0,0 +1,80 @@ +using Craftimizer.Simulator.Actions; +using Dalamud.Interface.Windowing; +using System; + +namespace Craftimizer.Plugin.Windows; + +public sealed partial class SimulatorWindow : Window, IDisposable +{ + private void AppendAction(ActionType action) + { + OnActionsChanged(); + + AppendGeneratedAction(action); + } + + private void AppendGeneratedAction(ActionType action) + { + var tooltip = action.Base().GetTooltip(Simulator, false); + var (response, state) = Simulator.Execute(LatestState, action); + Actions.Add((action, tooltip, response, state)); + } + + private void RemoveAction(int actionIndex) + { + OnActionsChanged(); + + // Remove action + Actions.RemoveAt(actionIndex); + + // Take note of all actions afterwards + Span succeedingActions = stackalloc ActionType[Actions.Count - actionIndex]; + for (var i = 0; i < succeedingActions.Length; i++) + succeedingActions[i] = Actions[i + actionIndex].Action; + + // Remove all future actions + Actions.RemoveRange(actionIndex, succeedingActions.Length); + + // Re-execute all future actions + foreach (var action in succeedingActions) + AppendAction(action); + } + + private void InsertAction(int actionIndex, ActionType action) + { + OnActionsChanged(); + + // Take note of all actions afterwards + Span succeedingActions = stackalloc ActionType[Actions.Count - actionIndex]; + for (var i = 0; i < succeedingActions.Length; i++) + succeedingActions[i] = Actions[i + actionIndex].Action; + + // Remove all future actions + Actions.RemoveRange(actionIndex, succeedingActions.Length); + + // Execute new action + AppendAction(action); + + // Re-execute all future actions + foreach (var succeededAction in succeedingActions) + AppendAction(succeededAction); + } + + private void ClearAllActions() + { + OnActionsChanged(); + + Actions.Clear(); + } + + private void ReexecuteAllActions() + { + Span actions = stackalloc ActionType[Actions.Count]; + for (var i = 0; i < actions.Length; i++) + actions[i] = Actions[i].Action; + + Actions.Clear(); + foreach (var action in actions) + AppendAction(action); + } +} diff --git a/Craftimizer/Windows/SimulatorWindowDrawer.cs b/Craftimizer/Windows/SimulatorWindowDrawer.cs new file mode 100644 index 0000000..da08396 --- /dev/null +++ b/Craftimizer/Windows/SimulatorWindowDrawer.cs @@ -0,0 +1,386 @@ +using Craftimizer.Simulator.Actions; +using Craftimizer.Simulator; +using Dalamud.Interface.Windowing; +using ImGuiNET; +using System.Numerics; +using Dalamud.Interface; +using Dalamud.Utility; +using System; +using ImGuiScene; +using Dalamud.Interface.Components; +using System.Linq; +using Dalamud.Game.Text; + +namespace Craftimizer.Plugin.Windows; + +public sealed partial class SimulatorWindow : Window, IDisposable +{ + private const int ActionColumnSize = 260; + + private static readonly Vector2 ProgressBarSize = new(200, 20); + private static readonly Vector2 DurabilityBarSize = new(100, 20); + private static readonly Vector2 ConditionBarSize = new(20, 20); + private static readonly Vector2 ProgressBarSizeOld = new(200, 20); + private static readonly Vector2 TooltipProgressBarSize = new(100, 5); + + private static readonly Vector4 ProgressColor = new(0.44f, 0.65f, 0.18f, 1f); + private static readonly Vector4 QualityColor = new(0.26f, 0.71f, 0.69f, 1f); + private static readonly Vector4 DurabilityColor = new(0.13f, 0.52f, 0.93f, 1f); + private static readonly Vector4 HQColor = new(0.592f, 0.863f, 0.376f, 1f); + private static readonly Vector4 CPColor = new(0.63f, 0.37f, 0.75f, 1f); + + private static readonly Vector4 BadActionImageTint = new(1f, .5f, .5f, 1f); + private static readonly Vector4 BadActionImageColor = new(1f, .3f, .3f, 1f); + + private static readonly Vector4 BadActionTextColor = new(1f, .2f, .2f, 1f); + + private static readonly (ActionCategory Category, ActionType[] Actions)[] SortedActions; + + static SimulatorWindow() + { + SortedActions = Enum.GetValues().GroupBy(a => a.Category()).Select(g => (g.Key, g.OrderBy(a => a.Level()).ToArray())).ToArray(); + } + + public override void Draw() + { + while (SolverActionQueue.TryDequeue(out var poppedAction)) + AppendGeneratedAction(poppedAction); + + ImGui.TextUnformatted($"{FrameTime.TotalMilliseconds:0.00}ms"); + + ImGui.BeginTable("simulatorWindow", 2, ImGuiTableFlags.BordersInnerV); + ImGui.TableSetupColumn("", ImGuiTableColumnFlags.WidthFixed, ActionColumnSize); + ImGui.TableSetupColumn("", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableNextColumn(); + DrawActions(); + ImGui.TableNextColumn(); + DrawSimulation(); + ImGui.EndTable(); + } + + private void DrawActions() + { + var hideUnlearnedActions = Configuration.HideUnlearnedActions; + if (ImGui.Checkbox("Show only learned actions", ref hideUnlearnedActions)) + { + Configuration.HideUnlearnedActions = hideUnlearnedActions; + Configuration.Save(); + } + + Simulator.SetState(LatestState); + + var actionSize = new Vector2((ActionColumnSize / 5) - ImGui.GetStyle().ItemSpacing.X * (6f / 5)); + ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero); + ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Zero); + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Zero); + + foreach (var (category, actions) in SortedActions) + { + var i = 0; + ImGuiUtils.BeginGroupPanel(category.GetDisplayName(), ActionColumnSize); + foreach (var action in actions) + { + var baseAction = action.Base(); + + var cannotUse = action.Level() > Input.Stats.Level || (action == ActionType.Manipulation && !Input.Stats.CanUseManipulation); + if (cannotUse && Service.Configuration.HideUnlearnedActions) + continue; + + var shouldNotUse = !baseAction.CanUse(Simulator) || Simulator.IsComplete; + + ImGui.BeginDisabled(cannotUse); + + if (ImGui.ImageButton(action.GetIcon(ClassJob).ImGuiHandle, actionSize, Vector2.Zero, Vector2.One, 0, default, shouldNotUse ? BadActionImageTint : Vector4.One)) + AppendAction(action); + + if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) + ImGui.SetTooltip($"{action.GetName(ClassJob)}\n{baseAction.GetTooltip(Simulator, true)}"); + + ImGui.EndDisabled(); + + if (++i % 5 != 0) + ImGui.SameLine(); + } + if (i == 0) + ImGui.Dummy(actionSize); + ImGuiUtils.EndGroupPanel(); + } + ImGui.PopStyleColor(3); + } + + private void DrawSimulation() + { + var drawParams = CalculateSynthDrawParams(); + + DrawSimulationHeader(); + DrawSimulationBars(drawParams); + ImGuiHelpers.ScaledDummy(5); + DrawSimulationEffects(drawParams); + ImGuiHelpers.ScaledDummy(5); + DrawSimulationActions(drawParams); + var bottom = ImGui.GetContentRegionAvail().Y - ImGui.GetStyle().FramePadding.Y * 2; + var textHeight = ImGui.CalcTextSize("A").Y; + var buttonHeight = ImGui.GetStyle().FramePadding.Y * 2 + textHeight; + ImGuiHelpers.ScaledDummy(bottom - buttonHeight); + DrawSimulationButtons(drawParams); + } + + private void DrawSimulationHeader() + { + var imageSize = new Vector2(ImGui.GetFontSize() * 2.25f); + + ImGui.Image(Icons.GetIconFromId(Item.Icon).ImGuiHandle, imageSize); + ImGui.SameLine(); + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (imageSize.Y - ImGui.GetFontSize()) / 2f); + ImGui.TextUnformatted(Item.Name.ToDalamudString().ToString()); + if (Item.IsCollectable) + { + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (imageSize.Y - ImGui.GetFontSize()) / 2f); + ImGui.TextColored(new(0.98f, 0.98f, 0.61f, 1), SeIconChar.Collectible.ToIconString()); + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Collectable"); + } + if (IsExpert) + { + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (imageSize.Y - ImGui.GetFontSize()) / 2f); + // Using ItemLevel icon instead of '◈' because the game fonts hate + // me and I can't bother to include a font just for this one icon. + ImGui.TextColored(new(0.93f, 0.59f, 0.45f, 1), SeIconChar.ItemLevel.ToIconString()); + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Expert Recipe"); + } + var availWidth = ImGui.GetContentRegionAvail().X; + var text = $"Step {LatestState.StepCount + 1}"; + var textWidth = ImGui.CalcTextSize(text).X; + ImGui.SameLine(availWidth - textWidth); + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (imageSize.Y - ImGui.GetFontSize()) / 2f); + ImGui.TextUnformatted(text); + ImGui.Separator(); + } + + private void DrawSimulationBars(SynthDrawParams drawParams) + { + var state = LatestState; + + var (leftColumn, rightColumn, leftText, rightText) = (drawParams.LeftColumn, drawParams.RightColumn, drawParams.LeftText, drawParams.RightText); + + ImGui.BeginTable("simSynth", 2); + + ImGui.TableSetupColumn("", ImGuiTableColumnFlags.WidthFixed, leftColumn); + ImGui.TableSetupColumn("", ImGuiTableColumnFlags.WidthFixed, rightColumn); + ImGui.TableNextColumn(); + + DrawSynthProgress("Durability", state.Durability, Input.Recipe.MaxDurability, DurabilityBarSize, DurabilityColor, leftText); + + DrawSynthCircle("Condition", state.Condition.Name(), ConditionBarSize, new Vector4(.35f, .35f, .35f, 0) + state.Condition.GetColor(DateTime.UtcNow.TimeOfDay), DurabilityBarSize, leftText); + if (ImGui.IsItemHovered()) + ImGui.SetTooltip(state.Condition.Description(state.Input.Stats.HasSplendorousBuff)); + + if (Item.IsCollectable) + { + var collectibility = Math.Max(state.Quality / 10, 1); + DrawSynthBar("Collectability", collectibility, Input.Recipe.MaxQuality / 10, $"{collectibility}", DurabilityBarSize, HQColor, leftText); + } + else + DrawSynthBar("HQ %", state.HQPercent, 100, $"{state.HQPercent}%", DurabilityBarSize, HQColor, leftText); + + ImGui.TableNextColumn(); + + DrawSynthProgress("Progress", state.Progress, Input.Recipe.MaxProgress, ProgressBarSize, ProgressColor, rightText); + DrawSynthProgress("Quality", state.Quality, Input.Recipe.MaxQuality, ProgressBarSize, QualityColor, rightText); + DrawSynthProgress("CP", state.CP, Input.Stats.CP, ProgressBarSize, CPColor, rightText); + + ImGui.EndTable(); + } + + private void DrawSimulationEffects(SynthDrawParams drawParams) + { + ImGuiUtils.BeginGroupPanel("Effects", drawParams.Total); + + var effectHeight = ImGui.GetFontSize() * 2f; + Vector2 GetEffectSize(TextureWrap icon) => new(icon.Width * effectHeight / icon.Height, effectHeight); + + ImGui.Dummy(new(0, effectHeight)); + ImGui.SameLine(0, 0); + foreach (var effect in Enum.GetValues()) + { + var duration = Simulator.GetEffectDuration(effect); + if (duration == 0) + continue; + + var strength = Simulator.GetEffectStrength(effect); + var icon = effect.GetIcon(strength); + var iconSize = GetEffectSize(icon); + + ImGui.Image(icon.ImGuiHandle, iconSize); + if (ImGui.IsItemHovered()) + ImGui.SetTooltip(effect.GetTooltip(strength, duration)); + if (duration != 0) + { + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (effectHeight - ImGui.GetFontSize()) / 2f); + ImGui.Text($"{duration}"); + } + ImGui.SameLine(); + } + ImGui.Dummy(Vector2.Zero); + + ImGuiUtils.EndGroupPanel(); + } + + private void DrawSimulationActions(SynthDrawParams drawParams) + { + ImGuiUtils.BeginGroupPanel("Actions", drawParams.Total); + + ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero); + ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Zero); + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Zero); + var actionSize = new Vector2((drawParams.Total / 10) - ImGui.GetStyle().ItemSpacing.X * (11f / 10)); + ImGui.Dummy(new(0, actionSize.Y)); + ImGui.SameLine(0, 0); + for (var i = 0; i < Actions.Count; ++i) + { + var (action, tooltip, response, state) = Actions[i]; + ImGui.PushID(i); + if (ImGui.ImageButton(action.GetIcon(ClassJob).ImGuiHandle, actionSize, Vector2.Zero, Vector2.One, 0, default, response != ActionResponse.UsedAction ? BadActionImageTint : Vector4.One)) + RemoveAction(i); + if (ImGui.BeginDragDropSource()) + { + unsafe { ImGui.SetDragDropPayload("simulationAction", (nint)(&i), sizeof(int)); } + ImGui.ImageButton(Actions[i].Action.GetIcon(ClassJob).ImGuiHandle, actionSize); + ImGui.EndDragDropSource(); + } + if (ImGui.BeginDragDropTarget()) + { + var payload = ImGui.AcceptDragDropPayload("simulationAction"); + bool isValidPayload; + unsafe { isValidPayload = payload.NativePtr != null; } + if (isValidPayload) + { + int draggedIdx; + unsafe { draggedIdx = *(int*)payload.Data; } + var draggedAction = Actions[draggedIdx].Action; + RemoveAction(draggedIdx); + InsertAction(i, draggedAction); + } + ImGui.EndDragDropTarget(); + } + if (ImGui.IsItemHovered()) + { + ImGui.BeginTooltip(); + var responseText = response switch + { + ActionResponse.SimulationComplete => "Recipe Complete", + ActionResponse.ActionNotUnlocked => "Action Not Unlocked", + ActionResponse.NotEnoughCP => "Not Enough CP", + ActionResponse.NoDurability => "No More Durability", + ActionResponse.CannotUseAction => "Cannot Use", + _ => string.Empty, + }; + if (response != ActionResponse.UsedAction) + ImGui.TextColored(BadActionTextColor, responseText); + ImGui.Text($"{action.GetName(ClassJob)}\n{tooltip}"); + DrawProgressBarTooltip(state.Progress, Input.Recipe.MaxProgress, ProgressColor); + DrawProgressBarTooltip(state.Quality, Input.Recipe.MaxQuality, QualityColor); + DrawProgressBarTooltip(state.Durability, Input.Recipe.MaxDurability, DurabilityColor); + DrawProgressBarTooltip(state.CP, Input.Stats.CP, CPColor); + ImGui.Text("Click to Remove\nDrag to Move"); + ImGui.EndTooltip(); + } + ImGui.PopID(); + if (i % 10 != 9) + ImGui.SameLine(); + } + ImGui.PopStyleColor(3); + + ImGuiUtils.EndGroupPanel(); + } + + private void DrawSimulationButtons(SynthDrawParams drawParams) + { + ImGui.Button("Save"); + ImGui.SameLine(); + if (ImGui.Button("Reset")) + ClearAllActions(); + ImGui.SameLine(); + var state = GenerateSolverState(); + string buttonText; + string tooltipText; + bool isEnabled; + if (!SolverTask.IsCompleted) + { + if (SolverTaskToken.IsCancellationRequested) + { + buttonText = "Cancelling..."; + tooltipText = "Cancelling macro generation. This shouldn't take long."; + isEnabled = false; + } + else + { + buttonText = "Cancel"; + tooltipText = "Cancel macro generation"; + isEnabled = true; + } + } + else + { + if (SolverActionsChanged) + { + buttonText = "Generate"; + tooltipText = "Generate a set of actions to finish the macro."; + isEnabled = state.HasValue; + if (!isEnabled) + tooltipText += "\nMake sure your craft so far is valid (without random condition changes)"; + } + else + { + buttonText = "Regenerate"; + tooltipText = "Retry and regenerate a new set of actions to finish the macro."; + isEnabled = true; + } + } + ImGui.BeginDisabled(!isEnabled); + if (ImGui.Button(buttonText)) + { + if (!SolverTask.IsCompleted) + { + if (!SolverTaskToken.IsCancellationRequested) + { + SolverTaskToken.Cancel(); + } + } + else + { + if (SolverActionsChanged) + { + if (state.HasValue) + { + SolveMacro(state.Value); + } + } + else + { + Actions.RemoveRange(SolverInitialActionCount, Actions.Count - SolverInitialActionCount); + SolveMacro(GenerateSolverState()!.Value); + } + } + } + ImGui.EndDisabled(); + if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) + ImGui.SetTooltip(tooltipText); + ImGui.SameLine(); + ImGuiComponents.IconButton(FontAwesomeIcon.Cog); + ImGui.SameLine(); + var conditionRandomness = Configuration.ConditionRandomness; + if (ImGui.Checkbox("Condition Randomness", ref conditionRandomness)) + { + Configuration.ConditionRandomness = conditionRandomness; + Configuration.Save(); + ResetSimulator(); + } + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Allows the condition to fluctuate randomly like a real craft.\nTurns off when generating a macro."); + } +} diff --git a/Craftimizer/Windows/SimulatorWindowDrawerUtils.cs b/Craftimizer/Windows/SimulatorWindowDrawerUtils.cs new file mode 100644 index 0000000..06bbe35 --- /dev/null +++ b/Craftimizer/Windows/SimulatorWindowDrawerUtils.cs @@ -0,0 +1,129 @@ +using Dalamud.Interface.Windowing; +using ImGuiNET; +using System; +using System.Numerics; + +namespace Craftimizer.Plugin.Windows; + +public sealed partial class SimulatorWindow : Window, IDisposable +{ + private readonly record struct SynthDrawParams + { + public float LeftColumn { get; init; } + public float RightColumn { get; init; } + public float LeftText { get; init; } + public float RightText { get; init; } + public float Total { get; init; } + } + + private SynthDrawParams CalculateSynthDrawParams() + { + var sidePadding = ImGui.GetFrameHeight() / 2; + var separatorTextWidth = ImGui.CalcTextSize(" / ").X; + var itemSpacing = ImGui.GetStyle().ItemSpacing.X; + + var leftDigits = (int)MathF.Floor(MathF.Log10(Input.Recipe.MaxDurability) + 1); + var leftTextWidth = ImGui.CalcTextSize(new string('0', leftDigits)).X; + var leftWidth = DurabilityBarSize.X + sidePadding + itemSpacing * 2 + separatorTextWidth + leftTextWidth * 2; + + + var rightDigits = (int)MathF.Floor(MathF.Log10(Math.Max(Math.Max(Input.Recipe.MaxProgress, Input.Recipe.MaxQuality), Input.Stats.CP)) + 1); + var rightTextWidth = ImGui.CalcTextSize(new string('0', rightDigits)).X; + var rightWidth = ProgressBarSize.X + sidePadding + itemSpacing * 2 + separatorTextWidth + rightTextWidth * 2; + + return new() + { + LeftColumn = leftWidth, + LeftText = leftTextWidth, + RightColumn = rightWidth, + RightText = rightTextWidth, + Total = leftWidth + rightWidth + itemSpacing + }; + } + + // Generic Progress Bar + private static void DrawSynthProgress(string name, int current, int max, Vector2 size, Vector4 color, float textWidth) + { + ImGuiUtils.BeginGroupPanel(name); + + DrawProgressBar(current, max, size, color); + + var w = ImGui.GetStyle().ItemSpacing.X; + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0, ImGui.GetStyle().ItemSpacing.Y)); + + ImGui.SameLine(0, textWidth - ImGui.CalcTextSize($"{current}").X + w); + var adjustedHeight = ImGui.GetCursorPosY() - ((ImGui.GetFrameHeight() - ImGui.GetFontSize()) / 2f); + ImGui.SetCursorPosY(adjustedHeight); + ImGui.TextUnformatted($"{current}"); + + ImGui.SameLine(); + ImGui.SetCursorPosY(adjustedHeight); + ImGui.TextUnformatted(" / "); + + ImGui.SameLine(0, textWidth - ImGui.CalcTextSize($"{max}").X); + ImGui.SetCursorPosY(adjustedHeight); + ImGui.TextUnformatted($"{max}"); + + ImGui.PopStyleVar(); + + ImGuiUtils.EndGroupPanel(); + } + + // HQ% / Collectability Bar (has no fractional bar to indicate max) + private static void DrawSynthBar(string name, int current, int max, string text, Vector2 size, Vector4 color, float textWidth) + { + ImGuiUtils.BeginGroupPanel(name); + + DrawProgressBar(current, max, size, color); + + var w = ImGui.GetStyle().ItemSpacing.X; + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0, ImGui.GetStyle().ItemSpacing.Y)); + + var totalWidth = textWidth * 2 + ImGui.CalcTextSize(" / ").X; + + ImGui.SameLine(0, totalWidth - ImGui.CalcTextSize(text).X + w); + var adjustedHeight = ImGui.GetCursorPosY() - ((ImGui.GetFrameHeight() - ImGui.GetFontSize()) / 2f); + ImGui.SetCursorPosY(adjustedHeight); + ImGui.TextUnformatted(text); + + ImGui.PopStyleVar(); + + ImGuiUtils.EndGroupPanel(); + } + + // Condition "Bar" Circle (always 100%, is a circle) + private static void DrawSynthCircle(string name, string text, Vector2 size, Vector4 color, Vector2 otherProgressSize, float textWidth) + { + ImGuiUtils.BeginGroupPanel(name); + + var w = ImGui.GetStyle().ItemSpacing.X; + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0, ImGui.GetStyle().ItemSpacing.Y)); + + var contentWidth = size.X + w + ImGui.CalcTextSize(text).X; + var totalWidth = otherProgressSize.X + w + textWidth * 2 + ImGui.CalcTextSize(" / ").X; + + ImGui.Dummy(default); + ImGui.SameLine(0, (totalWidth - contentWidth) / 2); + ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, Math.Max(size.X, size.Y)); + DrawProgressBar(1, 1, size, color); + ImGui.PopStyleVar(); + ImGui.SameLine(0, w); + var adjustedHeight = ImGui.GetCursorPosY() - ((ImGui.GetFrameHeight() - ImGui.GetFontSize()) / 2f); + ImGui.SetCursorPosY(adjustedHeight); + ImGui.TextUnformatted(text); + + ImGui.PopStyleVar(); + + ImGuiUtils.EndGroupPanel(); + } + + private static void DrawProgressBarTooltip(int progress, int maxProgress, Vector4 color) => + DrawProgressBar(progress, maxProgress, TooltipProgressBarSize, color); + + private static void DrawProgressBar(int progress, int maxProgress, Vector2 size, Vector4 color, string overlay = "") + { + ImGui.PushStyleColor(ImGuiCol.PlotHistogram, color); + ImGui.ProgressBar(Math.Clamp((float)progress / maxProgress, 0f, 1f), size, overlay); + ImGui.PopStyleColor(); + } +} diff --git a/Craftimizer/Windows/SimulatorWindowFrameCounter.cs b/Craftimizer/Windows/SimulatorWindowFrameCounter.cs new file mode 100644 index 0000000..7941d6f --- /dev/null +++ b/Craftimizer/Windows/SimulatorWindowFrameCounter.cs @@ -0,0 +1,26 @@ +using Dalamud.Interface.Windowing; +using System; +using System.Diagnostics; + +namespace Craftimizer.Plugin.Windows; + +public sealed partial class SimulatorWindow : Window, IDisposable +{ + private TimeSpan FrameTime { get; set; } + private Stopwatch Stopwatch { get; } = new(); + + public override void PreDraw() + { + Stopwatch.Restart(); + + base.PreDraw(); + } + + public override void PostDraw() + { + Stopwatch.Stop(); + FrameTime = Stopwatch.Elapsed; + + base.PostDraw(); + } +} diff --git a/Craftimizer/Windows/SimulatorWindowSolver.cs b/Craftimizer/Windows/SimulatorWindowSolver.cs new file mode 100644 index 0000000..fafc6a8 --- /dev/null +++ b/Craftimizer/Windows/SimulatorWindowSolver.cs @@ -0,0 +1,80 @@ +using Craftimizer.Simulator; +using Craftimizer.Simulator.Actions; +using Dalamud.Interface.Windowing; +using System; +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; + +namespace Craftimizer.Plugin.Windows; + +public sealed partial class SimulatorWindow : Window, IDisposable +{ + private Task SolverTask { get; set; } = Task.CompletedTask; + private CancellationTokenSource SolverTaskToken { get; set; } = new(); + private ConcurrentQueue SolverActionQueue { get; } = new(); + private int SolverInitialActionCount { get; set; } + private bool SolverActionsChanged { get; set; } = true; + + private void OnActionsChanged() + { + SolverActionsChanged = true; + } + + private SimulationState? GenerateSolverState() + { + if (Simulator is SimulatorNoRandom) + { + if (!Actions.Exists(a => a.Response != ActionResponse.UsedAction)) + return LatestState; + else + return null; + } + + var ret = new SimulationState(Input); + if (Actions.Count != 0) + { + var tmpSim = new SimulatorNoRandom(ret); + foreach (var action in Actions) + { + (var resp, ret) = tmpSim.Execute(ret, action.Action); + if (resp != ActionResponse.UsedAction) + return null; + } + } + return ret; + } + + private void SolveMacro(SimulationState solverState) + { + if (!SolverTask.IsCompleted) + { + SolverTaskToken.Cancel(); + } + + // Prevents the quality bar from being unfair between solves + if (Configuration.ConditionRandomness) + { + Configuration.ConditionRandomness = false; + Configuration.Save(); + + ResetSimulator(); + } + + SolverActionsChanged = false; + + SolverTaskToken.Dispose(); + SolverTask.Dispose(); + SolverActionQueue.Clear(); + + SolverInitialActionCount = Actions.Count; + SolverTaskToken = new(); + SolverTask = Task.Run(() => Solver.Crafty.Solver.SearchStepwise(Service.Configuration.SolverConfig, solverState, SolverActionQueue.Enqueue, SolverTaskToken.Token)); + } + + public void Dispose() + { + SolverTask.Dispose(); + SolverTaskToken.Dispose(); + } +} diff --git a/Craftimizer/packages.lock.json b/Craftimizer/packages.lock.json index c1aa84e..5dc997b 100644 --- a/Craftimizer/packages.lock.json +++ b/Craftimizer/packages.lock.json @@ -16,6 +16,12 @@ }, "craftimizer.simulator": { "type": "Project" + }, + "craftimizer.solver": { + "type": "Project", + "dependencies": { + "Craftimizer.Simulator": "[1.0.0, )" + } } } }