Files
Craftimizer/Craftimizer/Windows/SimulatorDrawer.cs
T
2023-10-10 18:38:01 -07:00

441 lines
18 KiB
C#

using Craftimizer.Simulator;
using Craftimizer.Simulator.Actions;
using Dalamud.Game.Text;
using Dalamud.Interface;
using Dalamud.Interface.Components;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Windowing;
using Dalamud.Utility;
using ImGuiNET;
using System;
using System.Linq;
using System.Numerics;
namespace Craftimizer.Plugin.Windows;
public sealed partial class Simulator : 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);
public static readonly Vector2 TooltipProgressBarSize = new(100, 5);
public static readonly Vector4 ProgressColor = new(0.44f, 0.65f, 0.18f, 1f);
public static readonly Vector4 QualityColor = new(0.26f, 0.71f, 0.69f, 1f);
public static readonly Vector4 DurabilityColor = new(0.13f, 0.52f, 0.93f, 1f);
public static readonly Vector4 HQColor = new(0.592f, 0.863f, 0.376f, 1f);
public 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 Simulator()
{
SortedActions = Enum.GetValues<ActionType>()
.Where(a => a.Category() != ActionCategory.Combo)
.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.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 = Config.HideUnlearnedActions;
if (ImGui.Checkbox("Show only learned actions", ref hideUnlearnedActions))
{
Config.HideUnlearnedActions = hideUnlearnedActions;
Config.Save();
}
Sim.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);
ImGui.BeginDisabled(!CanModifyActions);
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 && Config.HideUnlearnedActions)
continue;
var shouldNotUse = !baseAction.CanUse(Sim) || Sim.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(Sim, true)}");
ImGui.EndDisabled();
if (++i % 5 != 0)
ImGui.SameLine();
}
if (i == 0)
ImGui.Dummy(actionSize);
ImGuiUtils.EndGroupPanel();
}
ImGui.EndDisabled();
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 buttonHeight = ImGui.GetFrameHeightWithSpacing() * 2 + ImGui.GetFrameHeight();
ImGuiHelpers.ScaledDummy(bottom - buttonHeight);
DrawSimulationButtons(drawParams);
}
private void DrawSimulationHeader()
{
var imageSize = new Vector2(ImGui.GetFontSize() * 2.25f);
ImGui.Image(Service.IconManager.GetIcon(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(IDalamudTextureWrap icon) => new(icon.Width * effectHeight / icon.Height, effectHeight);
ImGui.Dummy(new(0, effectHeight));
ImGui.SameLine(0, 0);
foreach (var effect in Enum.GetValues<EffectType>())
{
var duration = Sim.GetEffectDuration(effect);
if (duration == 0)
continue;
var strength = Sim.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))
if (CanModifyActions)
RemoveAction(i);
if (CanModifyActions)
{
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}");
DrawAllProgressTooltips(state);
if (CanModifyActions)
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)
{
var totalWidth = drawParams.Total;
var halfWidth = (totalWidth - ImGui.GetStyle().ItemSpacing.X) / 2f;
var quarterWidth = (halfWidth - ImGui.GetStyle().ItemSpacing.X) / 2f;
var halfButtonSize = new Vector2(halfWidth, ImGui.GetFrameHeight());
var quarterButtonSize = new Vector2(quarterWidth, ImGui.GetFrameHeight());
var conditionRandomnessText = "Condition Randomness";
var conditionRandomness = Config.ConditionRandomness;
ImGui.BeginDisabled(!CanModifyActions);
if (ImGui.Checkbox(conditionRandomnessText, ref conditionRandomness))
{
Config.ConditionRandomness = conditionRandomness;
Config.Save();
ResetSimulator();
}
ImGui.EndDisabled();
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
ImGui.SetTooltip("Allows the condition to fluctuate randomly like a real craft.\nTurns off when generating a macro.");
var labelSize = ImGui.CalcTextSize(conditionRandomnessText);
var checkboxWidth = ImGui.GetFrameHeight() + (labelSize.X > 0 ? ImGui.GetStyle().ItemInnerSpacing.X + labelSize.X : 0);
ImGui.PushFont(UiBuilder.IconFont);
var cogWidth = ImGui.CalcTextSize(FontAwesomeIcon.Cog.ToIconString()).X;
ImGui.PopFont();
ImGui.SameLine(0, totalWidth - ImGui.GetStyle().ItemSpacing.X - checkboxWidth - cogWidth);
if (ImGuiComponents.IconButton("simSettingsButton", FontAwesomeIcon.Cog))
Service.Plugin.OpenSettingsTab(Settings.TabSimulator);
//
var macroName = MacroName;
ImGui.SetNextItemWidth(halfWidth);
if (ImGui.InputTextWithHint("", "Macro Name", ref macroName, 64))
MacroName = macroName;
ImGui.SameLine();
DrawSimulationGenerateButton(halfButtonSize);
//
ImGui.BeginDisabled(!CanModifyActions);
if (Macro != null)
{
if (ImGui.Button("Save", quarterButtonSize))
{
Macro.Name = MacroName;
Macro.Actions = Actions.Select(a => a.Action).ToList();
Config.Save();
}
ImGui.SameLine();
}
if (ImGui.Button("Save New", Macro == null ? halfButtonSize : quarterButtonSize))
{
Macro = new() { Name = MacroName, Actions = Actions.Select(a => a.Action).ToList() };
Config.Macros.Add(Macro);
Config.Save();
}
ImGui.SameLine();
if (ImGui.Button("Reset", halfButtonSize))
ClearAllActions();
ImGui.EndDisabled();
}
private void DrawSimulationGenerateButton(Vector2 buttonSize)
{
var state = GenerateSolverState();
string buttonText;
string tooltipText;
bool isEnabled;
var taskCompleted = SolverTask?.IsCompleted ?? true;
var taskCancelled = SolverTaskToken?.IsCancellationRequested ?? false;
if (!taskCompleted)
{
if (taskCancelled)
{
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, buttonSize))
{
if (!taskCompleted)
{
if (!taskCancelled)
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);
}
}