1.9.0.0 (Testing) Release
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
using Craftimizer.Simulator;
|
|
||||||
using Craftimizer.Simulator.Actions;
|
using Craftimizer.Simulator.Actions;
|
||||||
using Craftimizer.Solver;
|
using Craftimizer.Solver;
|
||||||
using Dalamud.Configuration;
|
using Dalamud.Configuration;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
@@ -10,8 +10,64 @@ namespace Craftimizer.Plugin;
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public class Macro
|
public class Macro
|
||||||
{
|
{
|
||||||
|
public static event Action<Macro>? OnMacroChanged;
|
||||||
|
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
public List<ActionType> Actions { get; set; } = new();
|
[JsonProperty(PropertyName = "Actions")]
|
||||||
|
private List<ActionType> actions { get; set; } = new();
|
||||||
|
[JsonIgnore]
|
||||||
|
public IReadOnlyList<ActionType> Actions
|
||||||
|
{
|
||||||
|
get => actions;
|
||||||
|
set => ActionEnumerable = value;
|
||||||
|
}
|
||||||
|
[JsonIgnore]
|
||||||
|
public IEnumerable<ActionType> ActionEnumerable
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
actions = new(value);
|
||||||
|
OnMacroChanged?.Invoke(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class MacroCopyConfiguration
|
||||||
|
{
|
||||||
|
public enum CopyType
|
||||||
|
{
|
||||||
|
OpenWindow, // useful for big macros
|
||||||
|
CopyToMacro, // (add option for down or right) (max macro count; open copy-paste window if too much)
|
||||||
|
CopyToClipboard,
|
||||||
|
}
|
||||||
|
|
||||||
|
public CopyType Type { get; set; } = CopyType.OpenWindow;
|
||||||
|
|
||||||
|
// CopyToMacro
|
||||||
|
public bool CopyDown { get; set; }
|
||||||
|
public bool SharedMacro { get; set; }
|
||||||
|
public int StartMacroIdx { get; set; } = 1;
|
||||||
|
public int MaxMacroCount { get; set; } = 5;
|
||||||
|
|
||||||
|
// Add /nextmacro [down]
|
||||||
|
public bool UseNextMacro { get; set; }
|
||||||
|
|
||||||
|
// Add /mlock
|
||||||
|
public bool UseMacroLock { get; set; }
|
||||||
|
|
||||||
|
public bool AddNotification { get; set; } = true;
|
||||||
|
|
||||||
|
// Requires AddNotification
|
||||||
|
public bool AddNotificationSound { get; set; } = true;
|
||||||
|
public int IntermediateNotificationSound { get; set; } = 10;
|
||||||
|
public int EndNotificationSound { get; set; } = 6;
|
||||||
|
|
||||||
|
// For SND
|
||||||
|
public bool RemoveWaitTimes { get; set; }
|
||||||
|
|
||||||
|
// For SND; Cannot use CopyToMacro
|
||||||
|
public bool CombineMacro { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
@@ -19,9 +75,12 @@ public class Configuration : IPluginConfiguration
|
|||||||
{
|
{
|
||||||
public int Version { get; set; } = 1;
|
public int Version { get; set; } = 1;
|
||||||
|
|
||||||
public bool OverrideUncraftability { get; set; } = true;
|
public static event Action? OnMacroListChanged;
|
||||||
public bool HideUnlearnedActions { get; set; } = true;
|
|
||||||
public List<Macro> Macros { get; set; } = new();
|
[JsonProperty(PropertyName = "Macros")]
|
||||||
|
private List<Macro> macros { get; set; } = new();
|
||||||
|
[JsonIgnore]
|
||||||
|
public IReadOnlyList<Macro> Macros => macros;
|
||||||
public bool ConditionRandomness { get; set; } = true;
|
public bool ConditionRandomness { get; set; } = true;
|
||||||
public SolverConfig SimulatorSolverConfig { get; set; } = SolverConfig.SimulatorDefault;
|
public SolverConfig SimulatorSolverConfig { get; set; } = SolverConfig.SimulatorDefault;
|
||||||
public SolverConfig SynthHelperSolverConfig { get; set; } = SolverConfig.SynthHelperDefault;
|
public SolverConfig SynthHelperSolverConfig { get; set; } = SolverConfig.SynthHelperDefault;
|
||||||
@@ -29,10 +88,23 @@ public class Configuration : IPluginConfiguration
|
|||||||
public bool ShowOptimalMacroStat { get; set; } = true;
|
public bool ShowOptimalMacroStat { get; set; } = true;
|
||||||
public int SynthHelperStepCount { get; set; } = 5;
|
public int SynthHelperStepCount { get; set; } = 5;
|
||||||
|
|
||||||
public Simulator.Simulator CreateSimulator(SimulationState state) =>
|
public MacroCopyConfiguration MacroCopy { get; set; } = new();
|
||||||
ConditionRandomness ?
|
|
||||||
new Simulator.Simulator(state) :
|
public void AddMacro(Macro macro)
|
||||||
new SimulatorNoRandom(state);
|
{
|
||||||
|
macros.Add(macro);
|
||||||
|
Save();
|
||||||
|
OnMacroListChanged?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveMacro(Macro macro)
|
||||||
|
{
|
||||||
|
if (macros.Remove(macro))
|
||||||
|
{
|
||||||
|
Save();
|
||||||
|
OnMacroListChanged?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Save() =>
|
public void Save() =>
|
||||||
Service.PluginInterface.SavePluginConfig(this);
|
Service.PluginInterface.SavePluginConfig(this);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Asriel Camora</Authors>
|
<Authors>Asriel Camora</Authors>
|
||||||
<Version>1.2.0.0</Version>
|
<Version>1.9.0.0</Version>
|
||||||
<PackageProjectUrl>https://github.com/WorkingRobot/craftimizer.git</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/WorkingRobot/craftimizer.git</PackageProjectUrl>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
+333
-96
@@ -1,130 +1,149 @@
|
|||||||
|
using Craftimizer.Utils;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Craftimizer.Plugin;
|
namespace Craftimizer.Plugin;
|
||||||
|
|
||||||
internal static class ImGuiUtils
|
internal static class ImGuiUtils
|
||||||
{
|
{
|
||||||
private static readonly Stack<(Vector2 Min, Vector2 Max)> GroupPanelLabelStack = new();
|
private static readonly Stack<(Vector2 Min, Vector2 Max, float TopPadding)> GroupPanelLabelStack = new();
|
||||||
|
|
||||||
// Adapted from https://github.com/ocornut/imgui/issues/1496#issuecomment-655048353
|
// Adapted from https://github.com/ocornut/imgui/issues/1496#issuecomment-655048353
|
||||||
public static void BeginGroupPanel(float width = -1, bool addPadding = true)
|
// width = -1 -> size to parent
|
||||||
|
// width = 0 -> size to content
|
||||||
|
// returns available width (better since it accounts for the right side padding)
|
||||||
|
// ^ only useful if width = -1
|
||||||
|
public static float BeginGroupPanel(string name, float width)
|
||||||
{
|
{
|
||||||
|
// container group
|
||||||
ImGui.BeginGroup();
|
ImGui.BeginGroup();
|
||||||
|
|
||||||
var itemSpacing = ImGui.GetStyle().ItemSpacing;
|
var itemSpacing = ImGui.GetStyle().ItemSpacing;
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero);
|
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
|
||||||
|
|
||||||
var frameHeight = ImGui.GetFrameHeight();
|
var frameHeight = ImGui.GetFrameHeight();
|
||||||
|
width = width < 0 ? ImGui.GetContentRegionAvail().X - (2 * itemSpacing.X) : width;
|
||||||
|
var fullWidth = width > 0 ? width + (2 * itemSpacing.X) : 0;
|
||||||
|
{
|
||||||
|
using var noPadding = ImRaii.PushStyle(ImGuiStyleVar.FramePadding, Vector2.Zero);
|
||||||
|
using var noSpacing = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||||
|
|
||||||
ImGui.BeginGroup();
|
// inner group
|
||||||
ImGui.Dummy(new Vector2(width < 0 ? ImGui.GetContentRegionAvail().X : width, 0));
|
ImGui.BeginGroup();
|
||||||
ImGui.Dummy(new Vector2(frameHeight * 0.5f, 0));
|
ImGui.Dummy(new Vector2(fullWidth, 0));
|
||||||
ImGui.SameLine(0, 0);
|
ImGui.Dummy(new Vector2(itemSpacing.X, 0)); // shifts next group by is.x
|
||||||
|
ImGui.SameLine(0, 0);
|
||||||
|
|
||||||
ImGui.BeginGroup();
|
// label group
|
||||||
ImGui.Dummy(new Vector2(frameHeight * 0.5f, 0));
|
ImGui.BeginGroup();
|
||||||
GroupPanelLabelStack.Push((ImGui.GetItemRectMin(), ImGui.GetItemRectMax()));
|
ImGui.Dummy(new Vector2(frameHeight / 2, 0)); // shifts text by fh/2
|
||||||
ImGui.SameLine(0, 0);
|
ImGui.SameLine(0, 0);
|
||||||
ImGui.Dummy(new Vector2(0f, frameHeight * (addPadding ? 1 : .5f) + itemSpacing.Y));
|
var textFrameHeight = ImGui.GetFrameHeight();
|
||||||
|
ImGui.AlignTextToFramePadding();
|
||||||
|
ImGui.TextUnformatted(name);
|
||||||
|
GroupPanelLabelStack.Push((ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), textFrameHeight / 2f)); // push rect to stack
|
||||||
|
ImGui.SameLine(0, 0);
|
||||||
|
ImGui.Dummy(new Vector2(0f, textFrameHeight + itemSpacing.Y)); // shifts content by fh + is.y
|
||||||
|
|
||||||
ImGui.BeginGroup();
|
// content group
|
||||||
|
ImGui.BeginGroup();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.PopStyleVar(2);
|
return width;
|
||||||
|
|
||||||
ImGui.PushItemWidth(MathF.Max(0, ImGui.CalcItemWidth() - frameHeight));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void BeginGroupPanel(string name, float width = -1, bool addPadding = true)
|
|
||||||
{
|
|
||||||
ImGui.BeginGroup();
|
|
||||||
|
|
||||||
var itemSpacing = ImGui.GetStyle().ItemSpacing;
|
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero);
|
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
|
||||||
|
|
||||||
var frameHeight = ImGui.GetFrameHeight();
|
|
||||||
|
|
||||||
ImGui.BeginGroup();
|
|
||||||
ImGui.Dummy(new Vector2(width < 0 ? ImGui.GetContentRegionAvail().X : width, 0));
|
|
||||||
ImGui.Dummy(new Vector2(frameHeight * 0.5f, 0));
|
|
||||||
ImGui.SameLine(0, 0);
|
|
||||||
|
|
||||||
ImGui.BeginGroup();
|
|
||||||
ImGui.Dummy(new Vector2(frameHeight * 0.5f, 0));
|
|
||||||
ImGui.SameLine(0, 0);
|
|
||||||
ImGui.TextUnformatted(name);
|
|
||||||
GroupPanelLabelStack.Push((ImGui.GetItemRectMin(), ImGui.GetItemRectMax()));
|
|
||||||
ImGui.SameLine(0, 0);
|
|
||||||
ImGui.Dummy(new Vector2(0f, frameHeight * (addPadding ? 1 : .5f) + itemSpacing.Y));
|
|
||||||
|
|
||||||
ImGui.BeginGroup();
|
|
||||||
|
|
||||||
ImGui.PopStyleVar(2);
|
|
||||||
|
|
||||||
ImGui.PushItemWidth(MathF.Max(0, ImGui.CalcItemWidth() - frameHeight));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void EndGroupPanel()
|
public static void EndGroupPanel()
|
||||||
{
|
{
|
||||||
ImGui.PopItemWidth();
|
|
||||||
|
|
||||||
var itemSpacing = ImGui.GetStyle().ItemSpacing;
|
var itemSpacing = ImGui.GetStyle().ItemSpacing;
|
||||||
|
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero);
|
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
|
||||||
|
|
||||||
var frameHeight = ImGui.GetFrameHeight();
|
|
||||||
|
|
||||||
ImGui.EndGroup();
|
|
||||||
|
|
||||||
ImGui.EndGroup();
|
|
||||||
|
|
||||||
ImGui.SameLine(0, 0);
|
|
||||||
ImGui.Dummy(new Vector2(frameHeight * 0.5f, 0));
|
|
||||||
ImGui.Dummy(new Vector2(0f, frameHeight * 0.5f - itemSpacing.Y));
|
|
||||||
|
|
||||||
ImGui.EndGroup();
|
|
||||||
|
|
||||||
var itemMin = ImGui.GetItemRectMin();
|
|
||||||
var itemMax = ImGui.GetItemRectMax();
|
|
||||||
var labelRect = GroupPanelLabelStack.Pop();
|
|
||||||
|
|
||||||
var halfFrame = new Vector2(frameHeight * 0.25f, frameHeight) * 0.5f;
|
|
||||||
(Vector2 Min, Vector2 Max) frameRect = (itemMin + halfFrame, itemMax - new Vector2(halfFrame.X, 0));
|
|
||||||
labelRect.Min.X -= itemSpacing.X;
|
|
||||||
labelRect.Max.X += itemSpacing.X;
|
|
||||||
for (var i = 0; i < 4; ++i)
|
|
||||||
{
|
{
|
||||||
var (minClip, maxClip) = i switch
|
using var noPadding = ImRaii.PushStyle(ImGuiStyleVar.FramePadding, Vector2.Zero);
|
||||||
{
|
using var noSpacing = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||||
0 => (new Vector2(float.NegativeInfinity), new Vector2(labelRect.Min.X, float.PositiveInfinity)),
|
|
||||||
1 => (new Vector2(labelRect.Max.X, float.NegativeInfinity), new Vector2(float.PositiveInfinity)),
|
|
||||||
2 => (new Vector2(labelRect.Min.X, float.NegativeInfinity), new Vector2(labelRect.Max.X, labelRect.Min.Y)),
|
|
||||||
3 => (new Vector2(labelRect.Min.X, labelRect.Max.Y), new Vector2(labelRect.Max.X, float.PositiveInfinity)),
|
|
||||||
_ => (Vector2.Zero, Vector2.Zero)
|
|
||||||
};
|
|
||||||
|
|
||||||
ImGui.PushClipRect(minClip, maxClip, true);
|
// content group
|
||||||
ImGui.GetWindowDrawList().AddRect(
|
ImGui.EndGroup();
|
||||||
frameRect.Min, frameRect.Max,
|
|
||||||
ImGui.GetColorU32(ImGuiCol.Border),
|
// label group
|
||||||
halfFrame.X);
|
ImGui.EndGroup();
|
||||||
ImGui.PopClipRect();
|
|
||||||
|
ImGui.SameLine(0, 0);
|
||||||
|
// shifts full size by is (for rect placement)
|
||||||
|
ImGui.Dummy(new(itemSpacing.X, 0));
|
||||||
|
ImGui.Dummy(new(0, itemSpacing.Y * 2)); // * 2 for some reason (otherwise the bottom is too skinny)
|
||||||
|
|
||||||
|
// inner group
|
||||||
|
ImGui.EndGroup();
|
||||||
|
|
||||||
|
var labelRect = GroupPanelLabelStack.Pop();
|
||||||
|
var innerMin = ImGui.GetItemRectMin() + new Vector2(0, labelRect.TopPadding);
|
||||||
|
var innerMax = ImGui.GetItemRectMax();
|
||||||
|
|
||||||
|
(Vector2 Min, Vector2 Max) frameRect = (innerMin, innerMax);
|
||||||
|
// add itemspacing padding on the label's sides
|
||||||
|
labelRect.Min.X -= itemSpacing.X / 2;
|
||||||
|
labelRect.Max.X += itemSpacing.X / 2;
|
||||||
|
for (var i = 0; i < 4; ++i)
|
||||||
|
{
|
||||||
|
var (minClip, maxClip) = i switch
|
||||||
|
{
|
||||||
|
0 => (new Vector2(float.NegativeInfinity), new Vector2(labelRect.Min.X, float.PositiveInfinity)),
|
||||||
|
1 => (new Vector2(labelRect.Max.X, float.NegativeInfinity), new Vector2(float.PositiveInfinity)),
|
||||||
|
2 => (new Vector2(labelRect.Min.X, float.NegativeInfinity), new Vector2(labelRect.Max.X, labelRect.Min.Y)),
|
||||||
|
3 => (new Vector2(labelRect.Min.X, labelRect.Max.Y), new Vector2(labelRect.Max.X, float.PositiveInfinity)),
|
||||||
|
_ => (Vector2.Zero, Vector2.Zero)
|
||||||
|
};
|
||||||
|
|
||||||
|
ImGui.PushClipRect(minClip, maxClip, true);
|
||||||
|
ImGui.GetWindowDrawList().AddRect(
|
||||||
|
frameRect.Min, frameRect.Max,
|
||||||
|
ImGui.GetColorU32(ImGuiCol.Border),
|
||||||
|
itemSpacing.X);
|
||||||
|
ImGui.PopClipRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Dummy(Vector2.Zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.PopStyleVar(2);
|
|
||||||
|
|
||||||
ImGui.Dummy(Vector2.Zero);
|
|
||||||
|
|
||||||
ImGui.EndGroup();
|
ImGui.EndGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct EndUnconditionally : ImRaii.IEndObject, IDisposable
|
||||||
|
{
|
||||||
|
private Action EndAction { get; }
|
||||||
|
|
||||||
|
public bool Success { get; }
|
||||||
|
|
||||||
|
public bool Disposed { get; private set; }
|
||||||
|
|
||||||
|
public EndUnconditionally(Action endAction, bool success)
|
||||||
|
{
|
||||||
|
EndAction = endAction;
|
||||||
|
Success = success;
|
||||||
|
Disposed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (!Disposed)
|
||||||
|
{
|
||||||
|
EndAction();
|
||||||
|
Disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ImRaii.IEndObject GroupPanel(string name, float width, out float internalWidth)
|
||||||
|
{
|
||||||
|
internalWidth = BeginGroupPanel(name, width);
|
||||||
|
return new EndUnconditionally(EndGroupPanel, true);
|
||||||
|
}
|
||||||
|
|
||||||
private static Vector2 UnitCircle(float theta)
|
private static Vector2 UnitCircle(float theta)
|
||||||
{
|
{
|
||||||
var (s, c) = MathF.SinCos(theta);
|
var (s, c) = MathF.SinCos(theta);
|
||||||
@@ -156,7 +175,7 @@ internal static class ImGuiUtils
|
|||||||
|
|
||||||
var offset = ImGui.GetCursorScreenPos() + new Vector2(radius);
|
var offset = ImGui.GetCursorScreenPos() + new Vector2(radius);
|
||||||
|
|
||||||
var segments = ImGui.GetWindowDrawList()._CalcCircleAutoSegmentCount(radius * 2);
|
var segments = ImGui.GetWindowDrawList()._CalcCircleAutoSegmentCount(radius);
|
||||||
var incrementAngle = MathF.Tau / segments;
|
var incrementAngle = MathF.Tau / segments;
|
||||||
var isFullCircle = (endAngle - startAngle) % MathF.Tau == 0;
|
var isFullCircle = (endAngle - startAngle) % MathF.Tau == 0;
|
||||||
|
|
||||||
@@ -215,6 +234,216 @@ internal static class ImGuiUtils
|
|||||||
Arc(MathF.PI / 2, MathF.PI / 2 - MathF.Tau * Math.Clamp(value, 0, 1), radiusInner, radiusOuter, backgroundColor, filledColor);
|
Arc(MathF.PI / 2, MathF.PI / 2 - MathF.Tau * Math.Clamp(value, 0, 1), radiusInner, radiusOuter, backgroundColor, filledColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class SearchableComboData<T> where T : class
|
||||||
|
{
|
||||||
|
public readonly ImmutableArray<T> items;
|
||||||
|
public List<T> filteredItems;
|
||||||
|
public T selectedItem;
|
||||||
|
public string input;
|
||||||
|
public bool wasTextActive;
|
||||||
|
public bool wasPopupActive;
|
||||||
|
public CancellationTokenSource? cts;
|
||||||
|
public Task? task;
|
||||||
|
|
||||||
|
private readonly Func<T, string> getString;
|
||||||
|
|
||||||
|
public SearchableComboData(IEnumerable<T> items, T selectedItem, Func<T, string> getString)
|
||||||
|
{
|
||||||
|
this.items = items.ToImmutableArray();
|
||||||
|
filteredItems = new() { selectedItem };
|
||||||
|
this.selectedItem = selectedItem;
|
||||||
|
this.getString = getString;
|
||||||
|
input = GetString(selectedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetItem(T selectedItem)
|
||||||
|
{
|
||||||
|
if (this.selectedItem != selectedItem)
|
||||||
|
{
|
||||||
|
input = GetString(selectedItem);
|
||||||
|
this.selectedItem = selectedItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetString(T item) => getString(item);
|
||||||
|
|
||||||
|
public void Filter()
|
||||||
|
{
|
||||||
|
cts?.Cancel();
|
||||||
|
var inp = input;
|
||||||
|
cts = new();
|
||||||
|
var token = cts.Token;
|
||||||
|
task = Task.Run(() => FilterTask(inp, token), token)
|
||||||
|
.ContinueWith(t =>
|
||||||
|
{
|
||||||
|
if (cts.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
t.Exception!.Flatten().Handle(ex => ex is TaskCanceledException or OperationCanceledException);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Filtering recipes failed");
|
||||||
|
}
|
||||||
|
}, TaskContinuationOptions.OnlyOnFaulted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FilterTask(string input, CancellationToken token)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
|
{
|
||||||
|
filteredItems = items.ToList();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var matcher = new FuzzyMatcher(input.ToLowerInvariant(), MatchMode.FuzzyParts);
|
||||||
|
var query = items.AsParallel().Select(i => (Item: i, Score: matcher.Matches(getString(i).ToLowerInvariant())))
|
||||||
|
.Where(t => t.Score > 0)
|
||||||
|
.OrderByDescending(t => t.Score)
|
||||||
|
.Select(t => t.Item);
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
filteredItems = query.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static readonly Dictionary<uint, object> ComboData = new();
|
||||||
|
|
||||||
|
private static SearchableComboData<T> GetComboData<T>(uint comboKey, IEnumerable<T> items, T selectedItem, Func<T, string> getString) where T : class =>
|
||||||
|
(SearchableComboData<T>)(
|
||||||
|
ComboData.TryGetValue(comboKey, out var data)
|
||||||
|
? data
|
||||||
|
: ComboData[comboKey] = new SearchableComboData<T>(items, selectedItem, getString));
|
||||||
|
|
||||||
|
// https://github.com/ocornut/imgui/issues/718#issuecomment-1563162222
|
||||||
|
public static bool SearchableCombo<T>(string id, ref T selectedItem, IEnumerable<T> items, ImFontPtr selectableFont, float width, Func<T, string> getString, Func<T, string> getId, Action<T> draw) where T : class
|
||||||
|
{
|
||||||
|
var comboKey = ImGui.GetID(id);
|
||||||
|
var data = GetComboData(comboKey, items, selectedItem, getString);
|
||||||
|
data.SetItem(selectedItem);
|
||||||
|
|
||||||
|
using var pushId = ImRaii.PushId(id);
|
||||||
|
|
||||||
|
width = width == 0 ? ImGui.GetContentRegionAvail().X : width;
|
||||||
|
var availableSpace = Math.Min(ImGui.GetContentRegionAvail().X, width);
|
||||||
|
ImGui.SetNextItemWidth(availableSpace);
|
||||||
|
var isInputTextEnterPressed = ImGui.InputText("##input", ref data.input, 256, ImGuiInputTextFlags.EnterReturnsTrue);
|
||||||
|
var min = ImGui.GetItemRectMin();
|
||||||
|
var size = ImGui.GetItemRectSize();
|
||||||
|
size.X = Math.Min(size.X, availableSpace);
|
||||||
|
|
||||||
|
var isInputTextActivated = ImGui.IsItemActivated();
|
||||||
|
|
||||||
|
if (isInputTextActivated)
|
||||||
|
{
|
||||||
|
ImGui.SetNextWindowPos(min - ImGui.GetStyle().WindowPadding);
|
||||||
|
ImGui.OpenPopup("##popup");
|
||||||
|
data.wasTextActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var popup = ImRaii.Popup("##popup", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoSavedSettings))
|
||||||
|
{
|
||||||
|
if (popup)
|
||||||
|
{
|
||||||
|
data.wasPopupActive = true;
|
||||||
|
|
||||||
|
if (isInputTextActivated)
|
||||||
|
{
|
||||||
|
ImGui.SetKeyboardFocusHere(0);
|
||||||
|
data.Filter();
|
||||||
|
}
|
||||||
|
ImGui.SetNextItemWidth(size.X);
|
||||||
|
if (ImGui.InputText("##input_popup", ref data.input, 256))
|
||||||
|
data.Filter();
|
||||||
|
var isActive = ImGui.IsItemActive();
|
||||||
|
if (!isActive && data.wasTextActive && ImGui.IsKeyPressed(ImGuiKey.Enter))
|
||||||
|
isInputTextEnterPressed = true;
|
||||||
|
data.wasTextActive = isActive;
|
||||||
|
|
||||||
|
using (var scrollingRegion = ImRaii.Child("scrollingRegion", new Vector2(size.X, size.Y * 10), false, ImGuiWindowFlags.HorizontalScrollbar))
|
||||||
|
{
|
||||||
|
T? _selectedItem = default;
|
||||||
|
var height = ImGui.GetTextLineHeight();
|
||||||
|
var r = ListClip(data.filteredItems, height, t =>
|
||||||
|
{
|
||||||
|
var name = getString(t);
|
||||||
|
using (var selectFont = ImRaii.PushFont(selectableFont))
|
||||||
|
{
|
||||||
|
if (ImGui.Selectable($"##{getId(t)}"))
|
||||||
|
{
|
||||||
|
_selectedItem = t;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X / 2f);
|
||||||
|
draw(t);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (r)
|
||||||
|
{
|
||||||
|
selectedItem = _selectedItem!;
|
||||||
|
data.SetItem(selectedItem);
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInputTextEnterPressed || ImGui.IsKeyPressed(ImGuiKey.Escape))
|
||||||
|
{
|
||||||
|
if (isInputTextEnterPressed && data.filteredItems.Count > 0)
|
||||||
|
{
|
||||||
|
selectedItem = data.filteredItems[0];
|
||||||
|
data.SetItem(selectedItem);
|
||||||
|
}
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (data.wasPopupActive)
|
||||||
|
{
|
||||||
|
data.wasPopupActive = false;
|
||||||
|
data.input = getString(selectedItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ListClip<T>(IReadOnlyList<T> data, float lineHeight, Predicate<T> func)
|
||||||
|
{
|
||||||
|
ImGuiListClipperPtr imGuiListClipperPtr;
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
imGuiListClipperPtr = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper());
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
imGuiListClipperPtr.Begin(data.Count, lineHeight);
|
||||||
|
while (imGuiListClipperPtr.Step())
|
||||||
|
{
|
||||||
|
for (var i = imGuiListClipperPtr.DisplayStart; i <= imGuiListClipperPtr.DisplayEnd; i++)
|
||||||
|
{
|
||||||
|
if (i >= data.Count)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (i >= 0)
|
||||||
|
{
|
||||||
|
if (func(data[i]))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
imGuiListClipperPtr.End();
|
||||||
|
imGuiListClipperPtr.Destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static bool IconButtonSized(FontAwesomeIcon icon, Vector2 size)
|
public static bool IconButtonSized(FontAwesomeIcon icon, Vector2 size)
|
||||||
{
|
{
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
@@ -254,6 +483,14 @@ internal static class ImGuiUtils
|
|||||||
ImGui.SetCursorPosX(ImGui.GetCursorPos().X + (availWidth - width) / 2);
|
ImGui.SetCursorPosX(ImGui.GetCursorPos().X + (availWidth - width) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void AlignRight(float width, float availWidth = default)
|
||||||
|
{
|
||||||
|
if (availWidth == default)
|
||||||
|
availWidth = ImGui.GetContentRegionAvail().X;
|
||||||
|
if (availWidth > width)
|
||||||
|
ImGui.SetCursorPosX(ImGui.GetCursorPos().X + availWidth - width);
|
||||||
|
}
|
||||||
|
|
||||||
public static void AlignMiddle(Vector2 size, Vector2 availSize = default)
|
public static void AlignMiddle(Vector2 size, Vector2 availSize = default)
|
||||||
{
|
{
|
||||||
if (availSize == default)
|
if (availSize == default)
|
||||||
@@ -271,9 +508,9 @@ internal static class ImGuiUtils
|
|||||||
ImGui.TextUnformatted(text);
|
ImGui.TextUnformatted(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void TextMiddle(string text, Vector2 availSize = default)
|
public static void TextRight(string text, float availWidth = default)
|
||||||
{
|
{
|
||||||
AlignMiddle(ImGui.CalcTextSize(text), availSize);
|
AlignRight(ImGui.CalcTextSize(text).X, availWidth);
|
||||||
ImGui.TextUnformatted(text);
|
ImGui.TextUnformatted(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+66
-15
@@ -1,21 +1,22 @@
|
|||||||
|
using Craftimizer.Plugin.Utils;
|
||||||
using Craftimizer.Plugin.Windows;
|
using Craftimizer.Plugin.Windows;
|
||||||
using Craftimizer.Simulator;
|
using Craftimizer.Simulator;
|
||||||
|
using Craftimizer.Simulator.Actions;
|
||||||
using Craftimizer.Utils;
|
using Craftimizer.Utils;
|
||||||
using Craftimizer.Windows;
|
using Craftimizer.Windows;
|
||||||
|
using Dalamud.Game.Command;
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using ImGuiScene;
|
using System;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using ClassJob = Craftimizer.Simulator.ClassJob;
|
|
||||||
|
|
||||||
namespace Craftimizer.Plugin;
|
namespace Craftimizer.Plugin;
|
||||||
|
|
||||||
public sealed class Plugin : IDalamudPlugin
|
public sealed class Plugin : IDalamudPlugin
|
||||||
{
|
{
|
||||||
public string Name => "Craftimizer";
|
|
||||||
public string Version { get; }
|
public string Version { get; }
|
||||||
public string Author { get; }
|
public string Author { get; }
|
||||||
public string BuildConfiguration { get; }
|
public string BuildConfiguration { get; }
|
||||||
@@ -23,23 +24,23 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
|
|
||||||
public WindowSystem WindowSystem { get; }
|
public WindowSystem WindowSystem { get; }
|
||||||
public Settings SettingsWindow { get; }
|
public Settings SettingsWindow { get; }
|
||||||
public Craftimizer.Windows.RecipeNote RecipeNoteWindow { get; }
|
public RecipeNote RecipeNoteWindow { get; }
|
||||||
public Craft SynthesisWindow { get; }
|
public MacroList ListWindow { get; private set; }
|
||||||
|
public MacroEditor? EditorWindow { get; private set; }
|
||||||
|
public MacroClipboard? ClipboardWindow { get; private set; }
|
||||||
|
|
||||||
public Configuration Configuration { get; }
|
public Configuration Configuration { get; }
|
||||||
public Hooks Hooks { get; }
|
public Hooks Hooks { get; }
|
||||||
public Craftimizer.Utils.RecipeNote RecipeNote { get; }
|
|
||||||
public IconManager IconManager { get; }
|
public IconManager IconManager { get; }
|
||||||
|
|
||||||
public Plugin([RequiredVersion("1.0")] DalamudPluginInterface pluginInterface)
|
public Plugin([RequiredVersion("1.0")] DalamudPluginInterface pluginInterface)
|
||||||
{
|
{
|
||||||
Service.Initialize(this, pluginInterface);
|
Service.Initialize(this, pluginInterface);
|
||||||
|
|
||||||
|
WindowSystem = new("Craftimizer");
|
||||||
Configuration = pluginInterface.GetPluginConfig() as Configuration ?? new();
|
Configuration = pluginInterface.GetPluginConfig() as Configuration ?? new();
|
||||||
Hooks = new();
|
Hooks = new();
|
||||||
RecipeNote = new();
|
|
||||||
IconManager = new();
|
IconManager = new();
|
||||||
WindowSystem = new(Name);
|
|
||||||
|
|
||||||
var assembly = Assembly.GetExecutingAssembly();
|
var assembly = Assembly.GetExecutingAssembly();
|
||||||
Version = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion;
|
Version = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion;
|
||||||
@@ -49,16 +50,41 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
|
|
||||||
SettingsWindow = new();
|
SettingsWindow = new();
|
||||||
RecipeNoteWindow = new();
|
RecipeNoteWindow = new();
|
||||||
SynthesisWindow = new();
|
ListWindow = new();
|
||||||
|
|
||||||
|
// Trigger static constructors so a huge hitch doesn't occur on first RecipeNote frame.
|
||||||
|
FoodStatus.Initialize();
|
||||||
|
Gearsets.Initialize();
|
||||||
|
ActionUtils.Initialize();
|
||||||
|
|
||||||
Service.PluginInterface.UiBuilder.Draw += WindowSystem.Draw;
|
Service.PluginInterface.UiBuilder.Draw += WindowSystem.Draw;
|
||||||
Service.PluginInterface.UiBuilder.OpenConfigUi += OpenSettingsWindow;
|
Service.PluginInterface.UiBuilder.OpenConfigUi += OpenSettingsWindow;
|
||||||
|
Service.PluginInterface.UiBuilder.OpenMainUi += OpenCraftingLog;
|
||||||
|
|
||||||
|
Service.CommandManager.AddHandler("/craftimizer", new CommandInfo((_, _) => OpenSettingsWindow())
|
||||||
|
{
|
||||||
|
HelpMessage = "Open the settings window.",
|
||||||
|
});
|
||||||
|
Service.CommandManager.AddHandler("/craftmacros", new CommandInfo((_, _) => OpenMacroListWindow())
|
||||||
|
{
|
||||||
|
HelpMessage = "Open the crafting macros window.",
|
||||||
|
});
|
||||||
|
Service.CommandManager.AddHandler("/crafteditor", new CommandInfo((_, _) => OpenSettingsWindow())
|
||||||
|
{
|
||||||
|
HelpMessage = "Open the crafting macro editor.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenMacroEditor(CharacterStats characterStats, RecipeData recipeData, MacroEditor.CrafterBuffs buffs, IEnumerable<ActionType> actions, Action<IEnumerable<ActionType>>? setter)
|
||||||
|
{
|
||||||
|
EditorWindow?.Dispose();
|
||||||
|
EditorWindow = new(characterStats, recipeData, buffs, actions, setter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenSettingsWindow()
|
public void OpenSettingsWindow()
|
||||||
{
|
{
|
||||||
SettingsWindow.IsOpen = true;
|
if (SettingsWindow.IsOpen ^= true)
|
||||||
SettingsWindow.BringToFront();
|
SettingsWindow.BringToFront();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenSettingsTab(string selectedTabLabel)
|
public void OpenSettingsTab(string selectedTabLabel)
|
||||||
@@ -67,11 +93,36 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
SettingsWindow.SelectTab(selectedTabLabel);
|
SettingsWindow.SelectTab(selectedTabLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OpenMacroListWindow()
|
||||||
|
{
|
||||||
|
ListWindow.IsOpen = true;
|
||||||
|
ListWindow.BringToFront();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenCraftingLog()
|
||||||
|
{
|
||||||
|
Chat.SendMessage("/craftinglog");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenMacroClipboard(List<string> macros)
|
||||||
|
{
|
||||||
|
ClipboardWindow?.Dispose();
|
||||||
|
ClipboardWindow = new(macros);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyMacro(IReadOnlyList<ActionType> actions) =>
|
||||||
|
MacroCopy.Copy(actions);
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
SimulatorWindow?.Dispose();
|
Service.CommandManager.RemoveHandler("/craftimizer");
|
||||||
SynthesisWindow.Dispose();
|
Service.CommandManager.RemoveHandler("/craftmacros");
|
||||||
RecipeNote.Dispose();
|
Service.CommandManager.RemoveHandler("/crafteditor");
|
||||||
|
SettingsWindow.Dispose();
|
||||||
|
RecipeNoteWindow.Dispose();
|
||||||
|
ListWindow.Dispose();
|
||||||
|
EditorWindow?.Dispose();
|
||||||
|
ClipboardWindow?.Dispose();
|
||||||
Hooks.Dispose();
|
Hooks.Dispose();
|
||||||
IconManager.Dispose();
|
IconManager.Dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Craftimizer.Utils;
|
using Craftimizer.Utils;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
|
||||||
using Dalamud.Game.ClientState.Objects;
|
using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ using Dalamud.Utility;
|
|||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
using ImGuiScene;
|
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@@ -64,6 +63,8 @@ internal static class ActionUtils
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Initialize() { }
|
||||||
|
|
||||||
public static (CraftAction? CraftAction, Action? Action) GetActionRow(this ActionType me, ClassJob classJob) =>
|
public static (CraftAction? CraftAction, Action? Action) GetActionRow(this ActionType me, ClassJob classJob) =>
|
||||||
ActionRows[(int)me, (int)classJob];
|
ActionRows[(int)me, (int)classJob];
|
||||||
|
|
||||||
@@ -309,11 +310,14 @@ internal static class EffectUtils
|
|||||||
EffectType.FinalAppraisal => 2190,
|
EffectType.FinalAppraisal => 2190,
|
||||||
EffectType.WasteNot2 => 257,
|
EffectType.WasteNot2 => 257,
|
||||||
EffectType.MuscleMemory => 2191,
|
EffectType.MuscleMemory => 2191,
|
||||||
EffectType.Manipulation => 258,
|
EffectType.Manipulation => 1164,
|
||||||
EffectType.HeartAndSoul => 2665,
|
EffectType.HeartAndSoul => 2665,
|
||||||
_ => 3412,
|
_ => throw new ArgumentOutOfRangeException(nameof(me)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static bool IsIndefinite(this EffectType me) =>
|
||||||
|
me is EffectType.InnerQuiet or EffectType.HeartAndSoul;
|
||||||
|
|
||||||
public static Status Status(this EffectType me) =>
|
public static Status Status(this EffectType me) =>
|
||||||
LuminaSheets.StatusSheet.GetRow(me.StatusId())!;
|
LuminaSheets.StatusSheet.GetRow(me.StatusId())!;
|
||||||
|
|
||||||
|
|||||||
+15
-14
@@ -1,6 +1,7 @@
|
|||||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Memory;
|
using FFXIVClientStructs.FFXIV.Client.System.Memory;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -8,7 +9,7 @@ using System.Text;
|
|||||||
namespace Craftimizer.Plugin.Utils;
|
namespace Craftimizer.Plugin.Utils;
|
||||||
|
|
||||||
// https://github.com/Caraxi/SimpleTweaksPlugin/blob/0973b93931cdf8a1b01153984d62f76d998747ff/Utility/ChatHelper.cs#L17
|
// https://github.com/Caraxi/SimpleTweaksPlugin/blob/0973b93931cdf8a1b01153984d62f76d998747ff/Utility/ChatHelper.cs#L17
|
||||||
public static class Chat
|
public static unsafe class Chat
|
||||||
{
|
{
|
||||||
private static class Signatures
|
private static class Signatures
|
||||||
{
|
{
|
||||||
@@ -16,11 +17,11 @@ public static class Chat
|
|||||||
internal const string SanitiseString = "E8 ?? ?? ?? ?? EB 0A 48 8D 4C 24 ?? E8 ?? ?? ?? ?? 48 8D 8D";
|
internal const string SanitiseString = "E8 ?? ?? ?? ?? EB 0A 48 8D 4C 24 ?? E8 ?? ?? ?? ?? 48 8D 8D";
|
||||||
}
|
}
|
||||||
|
|
||||||
private delegate void ProcessChatBoxDelegate(IntPtr uiModule, IntPtr message, IntPtr unused, byte a4);
|
private delegate void ProcessChatBoxDelegate(UIModule* uiModule, IntPtr message, IntPtr unused, byte a4);
|
||||||
|
|
||||||
private static ProcessChatBoxDelegate? ProcessChatBox { get; }
|
private static ProcessChatBoxDelegate? ProcessChatBox { get; }
|
||||||
|
|
||||||
private static readonly unsafe delegate* unmanaged<Utf8String*, int, IntPtr, void> _sanitiseString = null!;
|
private static readonly unsafe delegate* unmanaged<Utf8String*, int, IntPtr, void> SanitiseString = null!;
|
||||||
|
|
||||||
static Chat()
|
static Chat()
|
||||||
{
|
{
|
||||||
@@ -33,7 +34,7 @@ public static class Chat
|
|||||||
{
|
{
|
||||||
if (Service.SigScanner.TryScanText(Signatures.SanitiseString, out var sanitisePtr))
|
if (Service.SigScanner.TryScanText(Signatures.SanitiseString, out var sanitisePtr))
|
||||||
{
|
{
|
||||||
_sanitiseString = (delegate* unmanaged<Utf8String*, int, IntPtr, void>)sanitisePtr;
|
SanitiseString = (delegate* unmanaged<Utf8String*, int, IntPtr, void>)sanitisePtr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,7 +59,7 @@ public static class Chat
|
|||||||
throw new InvalidOperationException("Could not find signature for chat sending");
|
throw new InvalidOperationException("Could not find signature for chat sending");
|
||||||
}
|
}
|
||||||
|
|
||||||
var uiModule = (IntPtr)Framework.Instance()->GetUiModule();
|
var uiModule = Framework.Instance()->GetUiModule();
|
||||||
|
|
||||||
using var payload = new ChatPayload(message);
|
using var payload = new ChatPayload(message);
|
||||||
var mem1 = Marshal.AllocHGlobal(400);
|
var mem1 = Marshal.AllocHGlobal(400);
|
||||||
@@ -118,14 +119,14 @@ public static class Chat
|
|||||||
/// <exception cref="InvalidOperationException">If the signature for this function could not be found</exception>
|
/// <exception cref="InvalidOperationException">If the signature for this function could not be found</exception>
|
||||||
public static unsafe string SanitiseText(string text)
|
public static unsafe string SanitiseText(string text)
|
||||||
{
|
{
|
||||||
if (_sanitiseString == null)
|
if (SanitiseString == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Could not find signature for chat sanitisation");
|
throw new InvalidOperationException("Could not find signature for chat sanitisation");
|
||||||
}
|
}
|
||||||
|
|
||||||
var uText = Utf8String.FromString(text);
|
var uText = Utf8String.FromString(text);
|
||||||
|
|
||||||
_sanitiseString(uText, 0x27F, IntPtr.Zero);
|
SanitiseString(uText, 0x27F, IntPtr.Zero);
|
||||||
var sanitised = uText->ToString();
|
var sanitised = uText->ToString();
|
||||||
|
|
||||||
uText->Dtor();
|
uText->Dtor();
|
||||||
@@ -151,19 +152,19 @@ public static class Chat
|
|||||||
|
|
||||||
internal ChatPayload(byte[] stringBytes)
|
internal ChatPayload(byte[] stringBytes)
|
||||||
{
|
{
|
||||||
this.textPtr = Marshal.AllocHGlobal(stringBytes.Length + 30);
|
textPtr = Marshal.AllocHGlobal(stringBytes.Length + 30);
|
||||||
Marshal.Copy(stringBytes, 0, this.textPtr, stringBytes.Length);
|
Marshal.Copy(stringBytes, 0, textPtr, stringBytes.Length);
|
||||||
Marshal.WriteByte(this.textPtr + stringBytes.Length, 0);
|
Marshal.WriteByte(textPtr + stringBytes.Length, 0);
|
||||||
|
|
||||||
this.textLen = (ulong)(stringBytes.Length + 1);
|
textLen = (ulong)(stringBytes.Length + 1);
|
||||||
|
|
||||||
this.unk1 = 64;
|
unk1 = 64;
|
||||||
this.unk2 = 0;
|
unk2 = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Marshal.FreeHGlobal(this.textPtr);
|
Marshal.FreeHGlobal(textPtr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Craftimizer.Utils;
|
||||||
|
|
||||||
|
public static class Colors
|
||||||
|
{
|
||||||
|
public static readonly Vector4 Progress = new(0.44f, 0.65f, 0.18f, 1f);
|
||||||
|
public static readonly Vector4 Quality = new(0.26f, 0.71f, 0.69f, 1f);
|
||||||
|
public static readonly Vector4 Durability = new(0.13f, 0.52f, 0.93f, 1f);
|
||||||
|
public static readonly Vector4 HQ = new(0.592f, 0.863f, 0.376f, 1f);
|
||||||
|
public static readonly Vector4 CP = new(0.63f, 0.37f, 0.75f, 1f);
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
using Craftimizer.Plugin;
|
||||||
|
using Craftimizer.Plugin.Utils;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Craftimizer.Utils;
|
||||||
|
|
||||||
|
public static class FoodStatus
|
||||||
|
{
|
||||||
|
private static readonly ReadOnlyDictionary<uint, uint> ItemFoodToItemLUT;
|
||||||
|
private static readonly ReadOnlyDictionary<uint, Food> FoodItems;
|
||||||
|
private static readonly ReadOnlyDictionary<uint, Food> MedicineItems;
|
||||||
|
private static readonly ImmutableArray<uint> FoodOrder;
|
||||||
|
private static readonly ImmutableArray<uint> MedicineOrder;
|
||||||
|
|
||||||
|
public readonly record struct FoodStat(bool IsRelative, int Value, int Max, int ValueHQ, int MaxHQ);
|
||||||
|
public readonly record struct Food(Item Item, FoodStat? Craftsmanship, FoodStat? Control, FoodStat? CP);
|
||||||
|
|
||||||
|
static FoodStatus()
|
||||||
|
{
|
||||||
|
var lut = new Dictionary<uint, uint>();
|
||||||
|
foreach (var item in LuminaSheets.ItemSheet)
|
||||||
|
{
|
||||||
|
var isFood = item.ItemUICategory.Row == 46;
|
||||||
|
var isMedicine = item.ItemUICategory.Row == 44;
|
||||||
|
if (!isFood && !isMedicine)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (item.ItemAction.Value == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!(item.ItemAction.Value.Type is 844 or 845 or 846))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var itemFood = LuminaSheets.ItemFoodSheet.GetRow(item.ItemAction.Value.Data[1]);
|
||||||
|
if (itemFood == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
lut.TryAdd(itemFood.RowId, item.RowId);
|
||||||
|
}
|
||||||
|
ItemFoodToItemLUT = lut.AsReadOnly();
|
||||||
|
|
||||||
|
var foods = new Dictionary<uint, Food>();
|
||||||
|
var medicines = new Dictionary<uint, Food>();
|
||||||
|
foreach (var item in LuminaSheets.ItemSheet)
|
||||||
|
{
|
||||||
|
var isFood = item.ItemUICategory.Row == 46;
|
||||||
|
var isMedicine = item.ItemUICategory.Row == 44;
|
||||||
|
if (!isFood && !isMedicine)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (item.ItemAction.Value == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!(item.ItemAction.Value.Type is 844 or 845 or 846))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var itemFood = LuminaSheets.ItemFoodSheet.GetRow(item.ItemAction.Value.Data[1]);
|
||||||
|
if (itemFood == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
FoodStat? craftsmanship = null, control = null, cp = null;
|
||||||
|
foreach (var stat in itemFood.UnkData1)
|
||||||
|
{
|
||||||
|
if (stat.BaseParam == 0)
|
||||||
|
continue;
|
||||||
|
var foodStat = new FoodStat(stat.IsRelative, stat.Value, stat.Max, stat.ValueHQ, stat.MaxHQ);
|
||||||
|
switch (stat.BaseParam)
|
||||||
|
{
|
||||||
|
case Gearsets.ParamCraftsmanship: craftsmanship = foodStat; break;
|
||||||
|
case Gearsets.ParamControl: control = foodStat; break;
|
||||||
|
case Gearsets.ParamCP: cp = foodStat; break;
|
||||||
|
default: continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (craftsmanship != null || control != null || cp != null)
|
||||||
|
{
|
||||||
|
var food = new Food(item, craftsmanship, control, cp);
|
||||||
|
if (isFood)
|
||||||
|
foods.Add(item.RowId, food);
|
||||||
|
if (isMedicine)
|
||||||
|
medicines.Add(item.RowId, food);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FoodItems = foods.AsReadOnly();
|
||||||
|
MedicineItems = medicines.AsReadOnly();
|
||||||
|
|
||||||
|
FoodOrder = FoodItems.OrderByDescending(a => a.Value.Item.LevelItem.Row).Select(a => a.Key).ToImmutableArray();
|
||||||
|
MedicineOrder = MedicineItems.OrderByDescending(a => a.Value.Item.LevelItem.Row).Select(a => a.Key).ToImmutableArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Initialize() { }
|
||||||
|
|
||||||
|
public static IEnumerable<Food> OrderedFoods => FoodOrder.Select(id => FoodItems[id]);
|
||||||
|
public static IEnumerable<Food> OrderedMedicines => MedicineOrder.Select(id => MedicineItems[id]);
|
||||||
|
|
||||||
|
public static (uint ItemId, bool IsHQ)? ResolveFoodParam(ushort param)
|
||||||
|
{
|
||||||
|
var isHq = param > 10000;
|
||||||
|
param -= 10000;
|
||||||
|
|
||||||
|
if (!ItemFoodToItemLUT.TryGetValue(param, out var itemId))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return (itemId, isHq);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Food? TryGetFood(uint itemId)
|
||||||
|
{
|
||||||
|
if (FoodItems.TryGetValue(itemId, out var food))
|
||||||
|
return food;
|
||||||
|
if (MedicineItems.TryGetValue(itemId, out food))
|
||||||
|
return food;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Craftimizer.Utils;
|
||||||
|
|
||||||
|
internal readonly struct FuzzyMatcher
|
||||||
|
{
|
||||||
|
private const bool IsBorderMatching = true;
|
||||||
|
private static readonly (int, int)[] EmptySegArray = Array.Empty<(int, int)>();
|
||||||
|
|
||||||
|
private readonly string needleString = string.Empty;
|
||||||
|
private readonly int needleFinalPosition = -1;
|
||||||
|
private readonly (int Start, int End)[] needleSegments = EmptySegArray;
|
||||||
|
private readonly MatchMode mode = MatchMode.Simple;
|
||||||
|
|
||||||
|
public FuzzyMatcher(string term, MatchMode matchMode)
|
||||||
|
{
|
||||||
|
needleString = term;
|
||||||
|
needleFinalPosition = needleString.Length - 1;
|
||||||
|
mode = matchMode;
|
||||||
|
|
||||||
|
needleSegments = matchMode switch
|
||||||
|
{
|
||||||
|
MatchMode.FuzzyParts => FindNeedleSegments(needleString),
|
||||||
|
MatchMode.Fuzzy or MatchMode.Simple => EmptySegArray,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(matchMode), matchMode, "Invalid match mode"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int Start, int End)[] FindNeedleSegments(ReadOnlySpan<char> span)
|
||||||
|
{
|
||||||
|
var segments = new List<(int, int)>();
|
||||||
|
var wordStart = -1;
|
||||||
|
|
||||||
|
for (var i = 0; i < span.Length; i++)
|
||||||
|
{
|
||||||
|
if (span[i] is not ' ' and not '\u3000')
|
||||||
|
{
|
||||||
|
if (wordStart < 0)
|
||||||
|
wordStart = i;
|
||||||
|
}
|
||||||
|
else if (wordStart >= 0)
|
||||||
|
{
|
||||||
|
segments.Add((wordStart, i - 1));
|
||||||
|
wordStart = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wordStart >= 0)
|
||||||
|
segments.Add((wordStart, span.Length - 1));
|
||||||
|
|
||||||
|
return segments.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Matches(string value)
|
||||||
|
{
|
||||||
|
if (needleFinalPosition < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (mode == MatchMode.Simple)
|
||||||
|
return value.Contains(needleString, StringComparison.InvariantCultureIgnoreCase) ? 1 : 0;
|
||||||
|
|
||||||
|
if (mode == MatchMode.Fuzzy)
|
||||||
|
return GetRawScore(value, 0, needleFinalPosition);
|
||||||
|
|
||||||
|
if (mode == MatchMode.FuzzyParts)
|
||||||
|
{
|
||||||
|
if (needleSegments.Length < 2)
|
||||||
|
return GetRawScore(value, 0, needleFinalPosition);
|
||||||
|
|
||||||
|
var total = 0;
|
||||||
|
for (var i = 0; i < needleSegments.Length; i++)
|
||||||
|
{
|
||||||
|
var (start, end) = needleSegments[i];
|
||||||
|
var cur = GetRawScore(value, start, end);
|
||||||
|
if (cur == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
total += cur;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int MatchesAny(params string[] values)
|
||||||
|
{
|
||||||
|
var max = 0;
|
||||||
|
for (var i = 0; i < values.Length; i++)
|
||||||
|
{
|
||||||
|
var cur = Matches(values[i]);
|
||||||
|
if (cur > max)
|
||||||
|
max = cur;
|
||||||
|
}
|
||||||
|
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetRawScore(ReadOnlySpan<char> haystack, int needleStart, int needleEnd)
|
||||||
|
{
|
||||||
|
var (startPos, gaps, consecutive, borderMatches, endPos) = FindForward(haystack, needleStart, needleEnd);
|
||||||
|
if (startPos < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var needleSize = needleEnd - needleStart + 1;
|
||||||
|
|
||||||
|
var score = CalculateRawScore(needleSize, startPos, gaps, consecutive, borderMatches);
|
||||||
|
|
||||||
|
(startPos, gaps, consecutive, borderMatches) = FindReverse(haystack, endPos, needleStart, needleEnd);
|
||||||
|
var revScore = CalculateRawScore(needleSize, startPos, gaps, consecutive, borderMatches);
|
||||||
|
|
||||||
|
return int.Max(score, revScore);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static int CalculateRawScore(int needleSize, int startPos, int gaps, int consecutive, int borderMatches)
|
||||||
|
{
|
||||||
|
var score = 100
|
||||||
|
+ needleSize * 3
|
||||||
|
+ borderMatches * 3
|
||||||
|
+ consecutive * 5
|
||||||
|
- startPos
|
||||||
|
- gaps * 2;
|
||||||
|
if (startPos == 0)
|
||||||
|
score += 5;
|
||||||
|
return score < 1 ? 1 : score;
|
||||||
|
}
|
||||||
|
|
||||||
|
private (int StartPos, int Gaps, int Consecutive, int BorderMatches, int HaystackIndex) FindForward(
|
||||||
|
ReadOnlySpan<char> haystack, int needleStart, int needleEnd)
|
||||||
|
{
|
||||||
|
var needleIndex = needleStart;
|
||||||
|
var lastMatchIndex = -10;
|
||||||
|
|
||||||
|
var startPos = 0;
|
||||||
|
var gaps = 0;
|
||||||
|
var consecutive = 0;
|
||||||
|
var borderMatches = 0;
|
||||||
|
|
||||||
|
for (var haystackIndex = 0; haystackIndex < haystack.Length; haystackIndex++)
|
||||||
|
{
|
||||||
|
if (haystack[haystackIndex] == needleString[needleIndex])
|
||||||
|
{
|
||||||
|
if (IsBorderMatching)
|
||||||
|
{
|
||||||
|
if (haystackIndex > 0)
|
||||||
|
{
|
||||||
|
if (!char.IsLetterOrDigit(haystack[haystackIndex - 1]))
|
||||||
|
borderMatches++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
needleIndex++;
|
||||||
|
|
||||||
|
if (haystackIndex == lastMatchIndex + 1)
|
||||||
|
consecutive++;
|
||||||
|
|
||||||
|
if (needleIndex > needleEnd)
|
||||||
|
return (startPos, gaps, consecutive, borderMatches, haystackIndex);
|
||||||
|
|
||||||
|
lastMatchIndex = haystackIndex;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (needleIndex > needleStart)
|
||||||
|
gaps++;
|
||||||
|
else
|
||||||
|
startPos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (-1, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private (int StartPos, int Gaps, int Consecutive, int BorderMatches) FindReverse(
|
||||||
|
ReadOnlySpan<char> haystack, int haystackLastMatchIndex, int needleStart, int needleEnd)
|
||||||
|
{
|
||||||
|
var needleIndex = needleEnd;
|
||||||
|
var revLastMatchIndex = haystack.Length + 10;
|
||||||
|
|
||||||
|
var gaps = 0;
|
||||||
|
var consecutive = 0;
|
||||||
|
var borderMatches = 0;
|
||||||
|
|
||||||
|
for (var haystackIndex = haystackLastMatchIndex; haystackIndex >= 0; haystackIndex--)
|
||||||
|
{
|
||||||
|
if (haystack[haystackIndex] == needleString[needleIndex])
|
||||||
|
{
|
||||||
|
if (IsBorderMatching)
|
||||||
|
{
|
||||||
|
if (haystackIndex > 0)
|
||||||
|
{
|
||||||
|
if (!char.IsLetterOrDigit(haystack[haystackIndex - 1]))
|
||||||
|
borderMatches++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
needleIndex--;
|
||||||
|
|
||||||
|
if (haystackIndex == revLastMatchIndex - 1)
|
||||||
|
consecutive++;
|
||||||
|
|
||||||
|
if (needleIndex < needleStart)
|
||||||
|
return (haystackIndex, gaps, consecutive, borderMatches);
|
||||||
|
|
||||||
|
revLastMatchIndex = haystackIndex;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
gaps++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (-1, 0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum MatchMode
|
||||||
|
{
|
||||||
|
Simple,
|
||||||
|
Fuzzy,
|
||||||
|
FuzzyParts,
|
||||||
|
}
|
||||||
@@ -20,6 +20,24 @@ public static unsafe class Gearsets
|
|||||||
public const int ParamCraftsmanship = 70;
|
public const int ParamCraftsmanship = 70;
|
||||||
public const int ParamControl = 71;
|
public const int ParamControl = 71;
|
||||||
|
|
||||||
|
private static readonly int[] LevelToCLvlLUT;
|
||||||
|
|
||||||
|
static Gearsets()
|
||||||
|
{
|
||||||
|
LevelToCLvlLUT = new int[90];
|
||||||
|
for (uint i = 0; i < 80; ++i) {
|
||||||
|
var level = i + 1;
|
||||||
|
LevelToCLvlLUT[i] = LuminaSheets.ParamGrowSheet.GetRow(level)!.CraftingLevel;
|
||||||
|
}
|
||||||
|
for (var i = 80; i < 90; ++i)
|
||||||
|
{
|
||||||
|
var level = i + 1;
|
||||||
|
LevelToCLvlLUT[i] = (int)LuminaSheets.RecipeLevelTableSheet.First(r => r.ClassJobLevel == level).RowId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Initialize() { }
|
||||||
|
|
||||||
public static GearsetItem[] GetGearsetItems(InventoryContainer* container)
|
public static GearsetItem[] GetGearsetItems(InventoryContainer* container)
|
||||||
{
|
{
|
||||||
var items = new GearsetItem[(int)container->Size];
|
var items = new GearsetItem[(int)container->Size];
|
||||||
@@ -128,10 +146,10 @@ public static unsafe class Gearsets
|
|||||||
public static bool IsSplendorousTool(GearsetItem item) =>
|
public static bool IsSplendorousTool(GearsetItem item) =>
|
||||||
LuminaSheets.ItemSheetEnglish.GetRow(item.itemId)!.Description.ToDalamudString().TextValue.Contains("Increases to quality are 1.75 times higher than normal when material condition is Good.", StringComparison.Ordinal);
|
LuminaSheets.ItemSheetEnglish.GetRow(item.itemId)!.Description.ToDalamudString().TextValue.Contains("Increases to quality are 1.75 times higher than normal when material condition is Good.", StringComparison.Ordinal);
|
||||||
|
|
||||||
public static int CalculateCLvl(int characterLevel) =>
|
public static int CalculateCLvl(int level) =>
|
||||||
characterLevel <= 80
|
(level > 0 && level <= 90) ?
|
||||||
? LuminaSheets.ParamGrowSheet.GetRow((uint)characterLevel)!.CraftingLevel
|
LevelToCLvlLUT[level - 1] :
|
||||||
: (int)LuminaSheets.RecipeLevelTableSheet.First(r => r.ClassJobLevel == characterLevel).RowId;
|
throw new ArgumentOutOfRangeException(nameof(level), level, "Level is out of range.");
|
||||||
|
|
||||||
// https://github.com/ffxiv-teamcraft/ffxiv-teamcraft/blob/24d0db2d9676f264edf53651b21005305267c84c/apps/client/src/app/modules/gearsets/materia.service.ts#L265
|
// https://github.com/ffxiv-teamcraft/ffxiv-teamcraft/blob/24d0db2d9676f264edf53651b21005305267c84c/apps/client/src/app/modules/gearsets/materia.service.ts#L265
|
||||||
private static int CalculateParamCap(Item item, int paramId)
|
private static int CalculateParamCap(Item item, int paramId)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using Craftimizer.Plugin;
|
using Craftimizer.Plugin;
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
using ImGuiScene;
|
using Dalamud.Plugin.Services;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -11,6 +11,7 @@ namespace Craftimizer.Utils;
|
|||||||
public sealed class IconManager : IDisposable
|
public sealed class IconManager : IDisposable
|
||||||
{
|
{
|
||||||
private readonly Dictionary<uint, IDalamudTextureWrap> iconCache = new();
|
private readonly Dictionary<uint, IDalamudTextureWrap> iconCache = new();
|
||||||
|
private readonly Dictionary<uint, IDalamudTextureWrap> hqIconCache = new();
|
||||||
private readonly Dictionary<string, IDalamudTextureWrap> textureCache = new();
|
private readonly Dictionary<string, IDalamudTextureWrap> textureCache = new();
|
||||||
private readonly Dictionary<string, IDalamudTextureWrap> assemblyCache = new();
|
private readonly Dictionary<string, IDalamudTextureWrap> assemblyCache = new();
|
||||||
|
|
||||||
@@ -22,6 +23,16 @@ public sealed class IconManager : IDisposable
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IDalamudTextureWrap GetHqIcon(uint id, bool isHq = true)
|
||||||
|
{
|
||||||
|
if (!isHq)
|
||||||
|
return GetIcon(id);
|
||||||
|
if (!hqIconCache.TryGetValue(id, out var ret))
|
||||||
|
hqIconCache.Add(id, ret = Service.TextureProvider.GetIcon(id, ITextureProvider.IconFlags.HiRes | ITextureProvider.IconFlags.ItemHighQuality) ??
|
||||||
|
throw new ArgumentException($"Invalid hq icon id {id}", nameof(id)));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
public IDalamudTextureWrap GetTexture(string path)
|
public IDalamudTextureWrap GetTexture(string path)
|
||||||
{
|
{
|
||||||
if (!textureCache.TryGetValue(path, out var ret))
|
if (!textureCache.TryGetValue(path, out var ret))
|
||||||
@@ -55,6 +66,10 @@ public sealed class IconManager : IDisposable
|
|||||||
image.Dispose();
|
image.Dispose();
|
||||||
iconCache.Clear();
|
iconCache.Clear();
|
||||||
|
|
||||||
|
foreach (var image in hqIconCache.Values)
|
||||||
|
image.Dispose();
|
||||||
|
hqIconCache.Clear();
|
||||||
|
|
||||||
foreach (var image in textureCache.Values)
|
foreach (var image in textureCache.Values)
|
||||||
image.Dispose();
|
image.Dispose();
|
||||||
textureCache.Clear();
|
textureCache.Clear();
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
using Craftimizer.Plugin;
|
||||||
|
using Craftimizer.Simulator;
|
||||||
|
using Craftimizer.Simulator.Actions;
|
||||||
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.Memory;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||||
|
using ImGuiNET;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Craftimizer.Utils;
|
||||||
|
|
||||||
|
public static class MacroCopy
|
||||||
|
{
|
||||||
|
private const ClassJob DefaultJob = ClassJob.Carpenter;
|
||||||
|
private const int MacroSize = 15;
|
||||||
|
|
||||||
|
public static void Copy(IReadOnlyList<ActionType> actions)
|
||||||
|
{
|
||||||
|
if (actions.Count == 0)
|
||||||
|
{
|
||||||
|
Service.PluginInterface.UiBuilder.AddNotification("Could not copy macro. It's empty!", "Craftimizer Macro Not Copied", NotificationType.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = Service.Configuration.MacroCopy;
|
||||||
|
var macros = new List<string>();
|
||||||
|
var s = new List<string>();
|
||||||
|
foreach(var action in actions)
|
||||||
|
{
|
||||||
|
if (s.Count == 0)
|
||||||
|
{
|
||||||
|
if (config.UseMacroLock)
|
||||||
|
s.Add("/mlock");
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Add(GetActionCommand(action, config));
|
||||||
|
|
||||||
|
if (config.Type == MacroCopyConfiguration.CopyType.CopyToMacro || !config.CombineMacro)
|
||||||
|
{
|
||||||
|
if (s.Count == MacroSize - 1)
|
||||||
|
{
|
||||||
|
if (GetEndCommand(macros.Count, true, config) is { } endCommand)
|
||||||
|
s.Add(endCommand);
|
||||||
|
}
|
||||||
|
if (s.Count == MacroSize)
|
||||||
|
{
|
||||||
|
macros.Add(string.Join("\n", s));
|
||||||
|
s.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (s.Count > 0)
|
||||||
|
{
|
||||||
|
if (GetEndCommand(macros.Count, true, config) is { } endCommand)
|
||||||
|
s.Add(endCommand);
|
||||||
|
macros.Add(string.Join("\n", s));
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (config.Type)
|
||||||
|
{
|
||||||
|
case MacroCopyConfiguration.CopyType.OpenWindow:
|
||||||
|
Service.Plugin.OpenMacroClipboard(macros);
|
||||||
|
break;
|
||||||
|
case MacroCopyConfiguration.CopyType.CopyToMacro:
|
||||||
|
CopyToMacro(macros, config);
|
||||||
|
break;
|
||||||
|
case MacroCopyConfiguration.CopyType.CopyToClipboard:
|
||||||
|
CopyToClipboard(macros, config);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetActionCommand(ActionType action, MacroCopyConfiguration config)
|
||||||
|
{
|
||||||
|
var actionBase = action.Base();
|
||||||
|
if (actionBase is BaseComboAction)
|
||||||
|
throw new ArgumentException("Combo actions are not supported", nameof(action));
|
||||||
|
if (config.Type != MacroCopyConfiguration.CopyType.CopyToMacro && config.RemoveWaitTimes)
|
||||||
|
return $"/ac \"{action.GetName(DefaultJob)}\"";
|
||||||
|
else
|
||||||
|
return $"/ac \"{action.GetName(DefaultJob)}\" <wait.{actionBase.MacroWaitTime}>";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? GetEndCommand(int macroIdx, bool isEnd, MacroCopyConfiguration config)
|
||||||
|
{
|
||||||
|
if (config.UseNextMacro && !isEnd)
|
||||||
|
{
|
||||||
|
if (config.Type == MacroCopyConfiguration.CopyType.CopyToMacro && config.CopyDown)
|
||||||
|
return $"/nextmacro down";
|
||||||
|
else
|
||||||
|
return $"/nextmacro";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.AddNotification)
|
||||||
|
{
|
||||||
|
if (isEnd)
|
||||||
|
{
|
||||||
|
if (config.AddNotificationSound)
|
||||||
|
return $"/echo Craft complete! <se.{config.EndNotificationSound}>";
|
||||||
|
else
|
||||||
|
return $"/echo Craft complete!";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (config.AddNotificationSound)
|
||||||
|
return $"/echo Macro #{macroIdx + 1} complete! <se.{config.IntermediateNotificationSound}>";
|
||||||
|
else
|
||||||
|
return $"/echo Macro #{macroIdx + 1} complete!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CopyToMacro(List<string> macros, MacroCopyConfiguration config)
|
||||||
|
{
|
||||||
|
int i, macroIdx;
|
||||||
|
for (
|
||||||
|
i = 0, macroIdx = config.StartMacroIdx;
|
||||||
|
i < macros.Count && i < config.MaxMacroCount && macroIdx < 100;
|
||||||
|
i++, macroIdx += config.CopyDown ? 10 : 1)
|
||||||
|
SetMacro(macroIdx, config.SharedMacro, macros[i]);
|
||||||
|
|
||||||
|
Service.PluginInterface.UiBuilder.AddNotification(i > 1 ? "Copied macro to User Macros." : $"Copied {i} macros to User Macros.", "Craftimizer Macro Copied", NotificationType.Success);
|
||||||
|
if (i < macros.Count)
|
||||||
|
{
|
||||||
|
Service.Plugin.OpenMacroClipboard(macros);
|
||||||
|
var rest = macros.Count - i;
|
||||||
|
Service.PluginInterface.UiBuilder.AddNotification($"Couldn't copy {rest} macro{(rest == 1 ? "" : "s")}, so a window was opened with all of them.", "Craftimizer Macro Copied", NotificationType.Info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe void SetMacro(int idx, bool isShared, string macroText)
|
||||||
|
{
|
||||||
|
if (idx >= 100 || idx < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(idx), "Macro index must be between 0 and 99");
|
||||||
|
|
||||||
|
var module = RaptureMacroModule.Instance();
|
||||||
|
var macro = module->GetMacro(isShared ? 1u : 0u, (uint)idx);
|
||||||
|
var text = Utf8String.FromString(macroText);
|
||||||
|
module->ReplaceMacroLines(macro, text);
|
||||||
|
text->Dtor();
|
||||||
|
IMemorySpace.Free(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CopyToClipboard(List<string> macros, MacroCopyConfiguration config)
|
||||||
|
{
|
||||||
|
ImGui.SetClipboardText(string.Join("\n\n", macros));
|
||||||
|
Service.PluginInterface.UiBuilder.AddNotification(macros.Count > 1 ? "Copied macro to clipboard." : $"Copied {macros.Count} macros to clipboard.", "Craftimizer Macro Copied", NotificationType.Success);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
using Craftimizer.Plugin;
|
||||||
|
using Craftimizer.Simulator;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using ClassJob = Craftimizer.Simulator.ClassJob;
|
||||||
|
|
||||||
|
namespace Craftimizer.Utils;
|
||||||
|
|
||||||
|
public sealed record RecipeData
|
||||||
|
{
|
||||||
|
public ushort RecipeId { get; }
|
||||||
|
|
||||||
|
public Recipe Recipe { get; }
|
||||||
|
public RecipeLevelTable Table { get; }
|
||||||
|
|
||||||
|
public ClassJob ClassJob { get; }
|
||||||
|
public RecipeInfo RecipeInfo { get; }
|
||||||
|
public IReadOnlyList<(Item Item, int Amount)> Ingredients { get; }
|
||||||
|
public int MaxStartingQuality { get; }
|
||||||
|
private int TotalHqILvls { get; }
|
||||||
|
|
||||||
|
public RecipeData(ushort recipeId)
|
||||||
|
{
|
||||||
|
RecipeId = recipeId;
|
||||||
|
|
||||||
|
Recipe = LuminaSheets.RecipeSheet.GetRow(recipeId) ??
|
||||||
|
throw new ArgumentException($"Invalid recipe id {recipeId}", nameof(recipeId));
|
||||||
|
|
||||||
|
Table = Recipe.RecipeLevelTable.Value!;
|
||||||
|
ClassJob = (ClassJob)Recipe.CraftType.Row;
|
||||||
|
RecipeInfo = new()
|
||||||
|
{
|
||||||
|
IsExpert = Recipe.IsExpert,
|
||||||
|
ClassJobLevel = Table.ClassJobLevel,
|
||||||
|
RLvl = (int)Table.RowId,
|
||||||
|
ConditionsFlag = Table.ConditionsFlag,
|
||||||
|
MaxDurability = Table.Durability * Recipe.DurabilityFactor / 100,
|
||||||
|
MaxQuality = (Recipe.CanHq || Recipe.IsExpert) ? (int)Table.Quality * Recipe.QualityFactor / 100 : 0,
|
||||||
|
MaxProgress = Table.Difficulty * Recipe.DifficultyFactor / 100,
|
||||||
|
QualityModifier = Table.QualityModifier,
|
||||||
|
QualityDivider = Table.QualityDivider,
|
||||||
|
ProgressModifier = Table.ProgressModifier,
|
||||||
|
ProgressDivider = Table.ProgressDivider,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ingredients = Recipe.UnkData5.Take(6)
|
||||||
|
.Where(i => i != null && i.ItemIngredient != 0)
|
||||||
|
.Select(i => (LuminaSheets.ItemSheet.GetRow((uint)i.ItemIngredient)!, (int)i.AmountIngredient))
|
||||||
|
.Where(i => i.Item1 != null).ToList();
|
||||||
|
MaxStartingQuality = (int)Math.Floor(Recipe.MaterialQualityFactor * RecipeInfo.MaxQuality / 100f);
|
||||||
|
|
||||||
|
TotalHqILvls = (int)Ingredients.Where(i => i.Item.CanBeHq).Sum(i => i.Item.LevelItem.Row * i.Amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CalculateItemStartingQuality(int itemIdx, int amount)
|
||||||
|
{
|
||||||
|
if (itemIdx >= Ingredients.Count)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(itemIdx));
|
||||||
|
|
||||||
|
var ingredient = Ingredients[itemIdx];
|
||||||
|
if (amount > ingredient.Amount)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(amount));
|
||||||
|
|
||||||
|
if (!ingredient.Item.CanBeHq)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var iLvls = ingredient.Item.LevelItem.Row * amount;
|
||||||
|
return (int)Math.Floor((float)iLvls / TotalHqILvls * MaxStartingQuality);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CalculateStartingQuality(IEnumerable<int> hqQuantities)
|
||||||
|
{
|
||||||
|
if (TotalHqILvls == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var iLvls = Ingredients.Zip(hqQuantities).Sum(i => i.First.Item.LevelItem.Row * i.Second);
|
||||||
|
|
||||||
|
return (int)Math.Floor((float)iLvls / TotalHqILvls * MaxStartingQuality);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
using Craftimizer.Plugin;
|
|
||||||
using Craftimizer.Simulator;
|
|
||||||
using Dalamud.Game;
|
|
||||||
using Dalamud.Logging;
|
|
||||||
using Dalamud.Plugin.Services;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|
||||||
using Lumina.Excel.GeneratedSheets;
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using ActionType = Craftimizer.Simulator.Actions.ActionType;
|
|
||||||
using ClassJob = Craftimizer.Simulator.ClassJob;
|
|
||||||
using CSRecipeNote = FFXIVClientStructs.FFXIV.Client.Game.UI.RecipeNote;
|
|
||||||
|
|
||||||
namespace Craftimizer.Utils;
|
|
||||||
|
|
||||||
public record RecipeData
|
|
||||||
{
|
|
||||||
public ushort RecipeId { get; }
|
|
||||||
|
|
||||||
public Recipe Recipe { get; }
|
|
||||||
public RecipeLevelTable Table { get; }
|
|
||||||
|
|
||||||
public ClassJob ClassJob { get; }
|
|
||||||
public RecipeInfo RecipeInfo { get; }
|
|
||||||
public int HQIngredientCount { get; }
|
|
||||||
public int MaxStartingQuality { get; }
|
|
||||||
|
|
||||||
public RecipeData(ushort recipeId)
|
|
||||||
{
|
|
||||||
RecipeId = recipeId;
|
|
||||||
|
|
||||||
Recipe = LuminaSheets.RecipeSheet.GetRow(recipeId) ??
|
|
||||||
throw new ArgumentException($"Invalid recipe id {recipeId}", nameof(recipeId));
|
|
||||||
|
|
||||||
Table = Recipe.RecipeLevelTable.Value!;
|
|
||||||
ClassJob = (ClassJob)Recipe.CraftType.Row;
|
|
||||||
RecipeInfo = new()
|
|
||||||
{
|
|
||||||
IsExpert = Recipe.IsExpert,
|
|
||||||
ClassJobLevel = Table.ClassJobLevel,
|
|
||||||
RLvl = (int)Table.RowId,
|
|
||||||
ConditionsFlag = Table.ConditionsFlag,
|
|
||||||
MaxDurability = Table.Durability * Recipe.DurabilityFactor / 100,
|
|
||||||
MaxQuality = (Recipe.CanHq || Recipe.IsExpert) ? (int)Table.Quality * Recipe.QualityFactor / 100 : 0,
|
|
||||||
MaxProgress = Table.Difficulty * Recipe.DifficultyFactor / 100,
|
|
||||||
QualityModifier = Table.QualityModifier,
|
|
||||||
QualityDivider = Table.QualityDivider,
|
|
||||||
ProgressModifier = Table.ProgressModifier,
|
|
||||||
ProgressDivider = Table.ProgressDivider,
|
|
||||||
};
|
|
||||||
|
|
||||||
HQIngredientCount = Recipe.UnkData5
|
|
||||||
.Where(i =>
|
|
||||||
i != null &&
|
|
||||||
i.ItemIngredient != 0 &&
|
|
||||||
(LuminaSheets.ItemSheet.GetRow((uint)i.ItemIngredient)?.CanBeHq ?? false)
|
|
||||||
).Sum(i => i.AmountIngredient);
|
|
||||||
MaxStartingQuality = (int)Math.Floor(Recipe.MaterialQualityFactor * RecipeInfo.MaxQuality / 100f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed unsafe class RecipeNote : IDisposable
|
|
||||||
{
|
|
||||||
public AddonRecipeNote* AddonRecipe { get; private set; }
|
|
||||||
public AddonSynthesis* AddonSynthesis { get; private set; }
|
|
||||||
public bool IsCrafting { get; private set; }
|
|
||||||
public ushort RecipeId { get; private set; }
|
|
||||||
public Recipe Recipe { get; private set; } = null!;
|
|
||||||
public bool HasValidRecipe { get; private set; }
|
|
||||||
|
|
||||||
public RecipeLevelTable Table { get; private set; } = null!;
|
|
||||||
public RecipeInfo Info { get; private set; } = null!;
|
|
||||||
public ClassJob ClassJob { get; private set; }
|
|
||||||
public short CharacterLevel { get; private set; }
|
|
||||||
public bool CanUseManipulation { get; private set; }
|
|
||||||
public int HQIngredientCount { get; private set; }
|
|
||||||
public int MaxStartingQuality { get; private set; }
|
|
||||||
|
|
||||||
public RecipeNote()
|
|
||||||
{
|
|
||||||
Service.Framework.Update += FrameworkUpdate;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FrameworkUpdate(IFramework f)
|
|
||||||
{
|
|
||||||
HasValidRecipe = false;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
HasValidRecipe = Update();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "RecipeNote framework update failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Update()
|
|
||||||
{
|
|
||||||
if (Service.ClientState.LocalPlayer == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
AddonRecipe = (AddonRecipeNote*)Service.GameGui.GetAddonByName("RecipeNote");
|
|
||||||
AddonSynthesis = (AddonSynthesis*)Service.GameGui.GetAddonByName("Synthesis");
|
|
||||||
|
|
||||||
var recipeId = GetRecipeIdFromList();
|
|
||||||
if (recipeId == null)
|
|
||||||
{
|
|
||||||
recipeId = GetRecipeIdFromAgent();
|
|
||||||
if (recipeId == null)
|
|
||||||
return false;
|
|
||||||
else
|
|
||||||
IsCrafting = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
IsCrafting = false;
|
|
||||||
|
|
||||||
var isNewRecipe = RecipeId != recipeId.Value;
|
|
||||||
|
|
||||||
RecipeId = recipeId.Value;
|
|
||||||
|
|
||||||
var recipe = LuminaSheets.RecipeSheet.GetRow(RecipeId);
|
|
||||||
|
|
||||||
if (recipe == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
Recipe = recipe;
|
|
||||||
|
|
||||||
if (isNewRecipe)
|
|
||||||
CalculateStats();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ushort? GetRecipeIdFromList()
|
|
||||||
{
|
|
||||||
var instance = CSRecipeNote.Instance();
|
|
||||||
|
|
||||||
var list = instance->RecipeList;
|
|
||||||
|
|
||||||
if (list == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var recipeEntry = list->SelectedRecipe;
|
|
||||||
|
|
||||||
if (recipeEntry == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return recipeEntry->RecipeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ushort? GetRecipeIdFromAgent()
|
|
||||||
{
|
|
||||||
var instance = AgentRecipeNote.Instance();
|
|
||||||
|
|
||||||
var recipeId = instance->ActiveCraftRecipeId;
|
|
||||||
|
|
||||||
if (recipeId == 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return (ushort)recipeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CalculateStats()
|
|
||||||
{
|
|
||||||
Table = Recipe.RecipeLevelTable.Value!;
|
|
||||||
Info = CreateInfo();
|
|
||||||
ClassJob = (ClassJob)Recipe.CraftType.Row;
|
|
||||||
CharacterLevel = PlayerState.Instance()->ClassJobLevelArray[ClassJob.GetExpArrayIdx()];
|
|
||||||
CanUseManipulation = ActionManager.CanUseActionOnTarget(ActionType.Manipulation.GetId(ClassJob), (GameObject*)Service.ClientState.LocalPlayer!.Address);
|
|
||||||
HQIngredientCount = Recipe.UnkData5
|
|
||||||
.Where(i =>
|
|
||||||
i != null &&
|
|
||||||
i.ItemIngredient != 0 &&
|
|
||||||
(LuminaSheets.ItemSheet.GetRow((uint)i.ItemIngredient)?.CanBeHq ?? false)
|
|
||||||
).Sum(i => i.AmountIngredient);
|
|
||||||
MaxStartingQuality = (int)Math.Floor(Recipe.MaterialQualityFactor * Info.MaxQuality / 100f);
|
|
||||||
}
|
|
||||||
|
|
||||||
private RecipeInfo CreateInfo() =>
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
IsExpert = Recipe.IsExpert,
|
|
||||||
ClassJobLevel = Table.ClassJobLevel,
|
|
||||||
RLvl = (int)Table.RowId,
|
|
||||||
ConditionsFlag = Table.ConditionsFlag,
|
|
||||||
MaxDurability = Table.Durability * Recipe.DurabilityFactor / 100,
|
|
||||||
MaxQuality = (int)Table.Quality * Recipe.QualityFactor / 100,
|
|
||||||
MaxProgress = Table.Difficulty * Recipe.DifficultyFactor / 100,
|
|
||||||
QualityModifier = Table.QualityModifier,
|
|
||||||
QualityDivider = Table.QualityDivider,
|
|
||||||
ProgressModifier = Table.ProgressModifier,
|
|
||||||
ProgressDivider = Table.ProgressDivider,
|
|
||||||
};
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Service.Framework.Update -= FrameworkUpdate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
using Dalamud.Game.Text;
|
using Dalamud.Game.Text;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace Craftimizer.Utils;
|
namespace Craftimizer.Utils;
|
||||||
|
|
||||||
public static class SqText
|
public static class SqText
|
||||||
{
|
{
|
||||||
private static ReadOnlyDictionary<char, SeIconChar> levelNumReplacements = new(new Dictionary<char, SeIconChar>
|
public static SeIconChar LevelPrefix => SeIconChar.LevelEn;
|
||||||
|
|
||||||
|
public static readonly ReadOnlyDictionary<char, SeIconChar> LevelNumReplacements = new(new Dictionary<char, SeIconChar>
|
||||||
{
|
{
|
||||||
['0'] = SeIconChar.Number0,
|
['0'] = SeIconChar.Number0,
|
||||||
['1'] = SeIconChar.Number1,
|
['1'] = SeIconChar.Number1,
|
||||||
@@ -26,8 +27,15 @@ public static class SqText
|
|||||||
public static string ToLevelString<T>(T value) where T : IBinaryInteger<T>
|
public static string ToLevelString<T>(T value) where T : IBinaryInteger<T>
|
||||||
{
|
{
|
||||||
var str = value.ToString() ?? throw new FormatException("Failed to format value");
|
var str = value.ToString() ?? throw new FormatException("Failed to format value");
|
||||||
foreach(var (k, v) in levelNumReplacements)
|
foreach(var (k, v) in LevelNumReplacements)
|
||||||
str = str.Replace(k, v.ToIconChar());
|
str = str.Replace(k, v.ToIconChar());
|
||||||
return SeIconChar.LevelEn.ToIconChar() + str;
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryParseLevelString(string str, out int result)
|
||||||
|
{
|
||||||
|
foreach(var (k, v) in LevelNumReplacements)
|
||||||
|
str = str.Replace(v.ToIconChar(), k);
|
||||||
|
return int.TryParse(str, out result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,226 +0,0 @@
|
|||||||
using Craftimizer.Plugin.Utils;
|
|
||||||
using Craftimizer.Utils;
|
|
||||||
using Dalamud.Interface;
|
|
||||||
using Dalamud.Interface.Components;
|
|
||||||
using Dalamud.Interface.Utility;
|
|
||||||
using Dalamud.Interface.Windowing;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
||||||
using ImGuiNET;
|
|
||||||
using System;
|
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace Craftimizer.Plugin.Windows;
|
|
||||||
|
|
||||||
public sealed unsafe partial class Craft : Window, IDisposable
|
|
||||||
{
|
|
||||||
private const ImGuiWindowFlags WindowFlags = ImGuiWindowFlags.NoDecoration
|
|
||||||
| ImGuiWindowFlags.AlwaysAutoResize
|
|
||||||
| ImGuiWindowFlags.NoSavedSettings
|
|
||||||
| ImGuiWindowFlags.NoFocusOnAppearing
|
|
||||||
| ImGuiWindowFlags.NoNavFocus;
|
|
||||||
|
|
||||||
private const float WindowWidth = 300;
|
|
||||||
private const int ActionsPerRow = 5;
|
|
||||||
private static readonly Vector2 CraftProgressBarSize = new(WindowWidth, 15);
|
|
||||||
|
|
||||||
private static Configuration Config => Service.Configuration;
|
|
||||||
|
|
||||||
private static Random Random { get; } = new();
|
|
||||||
private static RecipeNote RecipeUtils => Service.Plugin.RecipeNote;
|
|
||||||
|
|
||||||
private bool WasOpen { get; set; }
|
|
||||||
|
|
||||||
public Craft() : base("Craftimizer SynthesisHelper", WindowFlags, true)
|
|
||||||
{
|
|
||||||
Service.WindowSystem.AddWindow(this);
|
|
||||||
Service.Plugin.Hooks.OnActionUsed += OnActionUsed;
|
|
||||||
|
|
||||||
IsOpen = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Draw()
|
|
||||||
{
|
|
||||||
SolveTick();
|
|
||||||
DequeueSolver();
|
|
||||||
|
|
||||||
DrawActions();
|
|
||||||
|
|
||||||
ImGui.SameLine(0, 0);
|
|
||||||
ImGui.Dummy(default);
|
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(5);
|
|
||||||
|
|
||||||
Simulator.DrawAllProgressBars(SolverLatestState, CraftProgressBarSize);
|
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(5);
|
|
||||||
|
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
|
||||||
var cogWidth = ImGui.CalcTextSize(FontAwesomeIcon.Cog.ToIconString()).X + (ImGui.GetStyle().FramePadding.X * 2);
|
|
||||||
ImGui.PopFont();
|
|
||||||
|
|
||||||
DrawSolveButton(new(WindowWidth - ImGui.GetStyle().ItemSpacing.X - cogWidth, ImGui.GetFrameHeight()));
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (ImGuiComponents.IconButton("synthSettingsButton", FontAwesomeIcon.Cog))
|
|
||||||
Service.Plugin.OpenSettingsTab(Settings.TabSynthHelper);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawActions()
|
|
||||||
{
|
|
||||||
var actionSize = new Vector2((WindowWidth / ActionsPerRow) - (ImGui.GetStyle().ItemSpacing.X * ((ActionsPerRow - 1f) / ActionsPerRow)));
|
|
||||||
ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero);
|
|
||||||
ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Zero);
|
|
||||||
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Zero);
|
|
||||||
|
|
||||||
ImGui.Dummy(new(0, actionSize.Y));
|
|
||||||
ImGui.SameLine(0, 0);
|
|
||||||
for (var i = 0; i < SolverActions.Count; ++i)
|
|
||||||
{
|
|
||||||
var (action, tooltip, state) = SolverActions[i];
|
|
||||||
ImGui.PushID(i);
|
|
||||||
if (ImGui.ImageButton(action.GetIcon(RecipeUtils.ClassJob).ImGuiHandle, actionSize, Vector2.Zero, Vector2.One, 0))
|
|
||||||
{
|
|
||||||
if (i == 0)
|
|
||||||
Chat.SendMessage($"/ac \"{action.GetName(RecipeUtils.ClassJob)}\"");
|
|
||||||
}
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
{
|
|
||||||
ImGui.BeginTooltip();
|
|
||||||
ImGui.Text($"{action.GetName(RecipeUtils.ClassJob)}\n{tooltip}");
|
|
||||||
Simulator.DrawAllProgressTooltips(state);
|
|
||||||
if (i == 0)
|
|
||||||
ImGui.Text("Click to Execute");
|
|
||||||
ImGui.EndTooltip();
|
|
||||||
}
|
|
||||||
ImGui.PopID();
|
|
||||||
if (i % ActionsPerRow != (ActionsPerRow - 1))
|
|
||||||
ImGui.SameLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.PopStyleColor(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawSolveButton(Vector2 buttonSize)
|
|
||||||
{
|
|
||||||
string buttonText;
|
|
||||||
string tooltipText;
|
|
||||||
bool isEnabled;
|
|
||||||
var taskCompleted = SolverTask?.IsCompleted ?? true;
|
|
||||||
var taskCancelled = SolverTaskToken?.IsCancellationRequested ?? false;
|
|
||||||
if (!taskCompleted)
|
|
||||||
{
|
|
||||||
if (taskCancelled)
|
|
||||||
{
|
|
||||||
buttonText = "Cancelling...";
|
|
||||||
tooltipText = "Cancelling action generation. This shouldn't take long.";
|
|
||||||
isEnabled = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
buttonText = "Cancel";
|
|
||||||
tooltipText = "Cancel action generation";
|
|
||||||
isEnabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
buttonText = "Retry";
|
|
||||||
tooltipText = "Retry and regenerate a new set of recommended actions to finish the craft.";
|
|
||||||
isEnabled = true;
|
|
||||||
}
|
|
||||||
ImGui.BeginDisabled(!isEnabled);
|
|
||||||
if (ImGui.Button(buttonText, buttonSize))
|
|
||||||
{
|
|
||||||
if (!taskCompleted)
|
|
||||||
{
|
|
||||||
if (!taskCancelled)
|
|
||||||
SolverTaskToken?.Cancel();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
QueueSolve(GetNextState()!.Value);
|
|
||||||
}
|
|
||||||
ImGui.EndDisabled();
|
|
||||||
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
|
||||||
ImGui.SetTooltip(tooltipText);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void PreDraw()
|
|
||||||
{
|
|
||||||
var addon = RecipeUtils.AddonSynthesis;
|
|
||||||
ref var unit = ref addon->AtkUnitBase;
|
|
||||||
var scale = unit.Scale;
|
|
||||||
var pos = new Vector2(unit.X, unit.Y);
|
|
||||||
var size = new Vector2(unit.WindowNode->AtkResNode.Width, unit.WindowNode->AtkResNode.Height) * scale;
|
|
||||||
|
|
||||||
var node = unit.GetNodeById(79);
|
|
||||||
|
|
||||||
Position = pos + new Vector2(size.X, node->Y * scale);
|
|
||||||
SizeConstraints = new WindowSizeConstraints
|
|
||||||
{
|
|
||||||
MinimumSize = new(-1),
|
|
||||||
MaximumSize = new(10000, 10000)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (Input == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
base.PreDraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool DrawConditionsInner()
|
|
||||||
{
|
|
||||||
if (!RecipeUtils.HasValidRecipe)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!RecipeUtils.IsCrafting)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (RecipeUtils.AddonSynthesis == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Check if Synthesis addon is visible
|
|
||||||
if (RecipeUtils.AddonSynthesis->AtkUnitBase.WindowNode == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (RecipeUtils.AddonSynthesis->AtkUnitBase.GetNodeById(79) == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return base.DrawConditions();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool DrawConditions()
|
|
||||||
{
|
|
||||||
if (!Config.EnableSynthHelper)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var ret = DrawConditionsInner();
|
|
||||||
if (ret && !WasOpen)
|
|
||||||
ResetSimulation();
|
|
||||||
|
|
||||||
WasOpen = ret;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ResetSimulation()
|
|
||||||
{
|
|
||||||
var container = InventoryManager.Instance()->GetInventoryContainer(InventoryType.EquippedItems);
|
|
||||||
if (container == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
CharacterStats = Gearsets.CalculateCharacterStats(Gearsets.CalculateGearsetCurrentStats(), Gearsets.GetGearsetItems(container), RecipeUtils.CharacterLevel, RecipeUtils.CanUseManipulation);
|
|
||||||
Input = new(CharacterStats, RecipeUtils.Info, 0, Random);
|
|
||||||
ActionCount = 0;
|
|
||||||
ActionStates = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
StopSolve();
|
|
||||||
SolverTaskToken?.Cancel();
|
|
||||||
SolverTask?.TryWait();
|
|
||||||
SolverTask?.Dispose();
|
|
||||||
SolverTaskToken?.Dispose();
|
|
||||||
|
|
||||||
Service.Plugin.Hooks.OnActionUsed -= OnActionUsed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
using Craftimizer.Simulator;
|
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
|
||||||
using Dalamud.Interface.Windowing;
|
|
||||||
using Dalamud.Memory;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
||||||
using System;
|
|
||||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
|
||||||
|
|
||||||
namespace Craftimizer.Plugin.Windows;
|
|
||||||
|
|
||||||
public sealed unsafe partial class Craft : Window, IDisposable
|
|
||||||
{
|
|
||||||
// State variables, manually kept track of outside of the addon
|
|
||||||
private CharacterStats CharacterStats = null!;
|
|
||||||
private SimulationInput Input = null!;
|
|
||||||
private int ActionCount;
|
|
||||||
private ActionStates ActionStates;
|
|
||||||
|
|
||||||
private sealed class AddonValues
|
|
||||||
{
|
|
||||||
public AddonSynthesis* Addon { get; }
|
|
||||||
public AtkValue* Values => Addon->AtkUnitBase.AtkValues;
|
|
||||||
public ushort ValueCount => Addon->AtkUnitBase.AtkValuesCount;
|
|
||||||
|
|
||||||
public AddonValues(AddonSynthesis* addon)
|
|
||||||
{
|
|
||||||
Addon = addon;
|
|
||||||
if (ValueCount != 26)
|
|
||||||
throw new ArgumentException("AddonSynthesis must have 26 AtkValues", nameof(addon));
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe AtkValue* this[int i] => Values + i;
|
|
||||||
|
|
||||||
// Always 0?
|
|
||||||
private uint Unk0 => GetUInt(0);
|
|
||||||
// Always true?
|
|
||||||
private bool Unk1 => GetBool(1);
|
|
||||||
|
|
||||||
public SeString ItemName => GetString(2);
|
|
||||||
public uint ItemIconId => GetUInt(3);
|
|
||||||
public uint ItemCount => GetUInt(4);
|
|
||||||
public uint Progress => GetUInt(5);
|
|
||||||
public uint MaxProgress => GetUInt(6);
|
|
||||||
public uint Durability => GetUInt(7);
|
|
||||||
public uint MaxDurability => GetUInt(8);
|
|
||||||
public uint Quality => GetUInt(9);
|
|
||||||
public uint HQChance => GetUInt(10);
|
|
||||||
private uint IsShowingCollectibleInfoValue => GetUInt(11);
|
|
||||||
private uint ConditionValue => GetUInt(12);
|
|
||||||
public SeString ConditionName => GetString(13);
|
|
||||||
public SeString ConditionNameAndTooltip => GetString(14);
|
|
||||||
public uint StepCount => GetUInt(15);
|
|
||||||
public uint ResultItemId => GetUInt(16);
|
|
||||||
public uint MaxQuality => GetUInt(17);
|
|
||||||
public uint RequiredQuality => GetUInt(18);
|
|
||||||
private uint IsCollectibleValue => GetUInt(19);
|
|
||||||
public uint Collectability => GetUInt(20);
|
|
||||||
public uint MaxCollectability => GetUInt(21);
|
|
||||||
public uint CollectabilityCheckpoint1 => GetUInt(22);
|
|
||||||
public uint CollectabilityCheckpoint2 => GetUInt(23);
|
|
||||||
public uint CollectabilityCheckpoint3 => GetUInt(24);
|
|
||||||
public bool IsExpertRecipe => GetBool(25);
|
|
||||||
|
|
||||||
public bool IsShowingCollectibleInfo => IsShowingCollectibleInfoValue != 0;
|
|
||||||
public Condition Condition => (Condition)(1 << (int)ConditionValue);
|
|
||||||
public bool IsCollectible => IsCollectibleValue != 0;
|
|
||||||
|
|
||||||
private uint GetUInt(int i)
|
|
||||||
{
|
|
||||||
var value = this[i];
|
|
||||||
return value->Type == ValueType.UInt ?
|
|
||||||
value->UInt :
|
|
||||||
throw new ArgumentException($"Value {i} is not a uint", nameof(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool GetBool(int i)
|
|
||||||
{
|
|
||||||
var value = this[i];
|
|
||||||
return value->Type == ValueType.Bool ?
|
|
||||||
value->Byte != 0 :
|
|
||||||
throw new ArgumentException($"Value {i} is not a boolean", nameof(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
private SeString GetString(int i)
|
|
||||||
{
|
|
||||||
var value = this[i];
|
|
||||||
return value->Type switch
|
|
||||||
{
|
|
||||||
ValueType.AllocatedString or
|
|
||||||
ValueType.String =>
|
|
||||||
MemoryHelper.ReadSeStringNullTerminated((nint)value->String),
|
|
||||||
_ => throw new ArgumentException($"Value {i} is not a string", nameof(i))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const ushort StatusInnerQuiet = 251;
|
|
||||||
private const ushort StatusWasteNot = 252;
|
|
||||||
private const ushort StatusVeneration = 2226;
|
|
||||||
private const ushort StatusGreatStrides = 254;
|
|
||||||
private const ushort StatusInnovation = 2189;
|
|
||||||
private const ushort StatusFinalAppraisal = 2190;
|
|
||||||
private const ushort StatusWasteNot2 = 257;
|
|
||||||
private const ushort StatusMuscleMemory = 2191;
|
|
||||||
private const ushort StatusManipulation = 1164;
|
|
||||||
private const ushort StatusHeartAndSoul = 2665;
|
|
||||||
|
|
||||||
private SimulationState GetAddonSimulationState()
|
|
||||||
{
|
|
||||||
var player = Service.ClientState.LocalPlayer!;
|
|
||||||
var values = new AddonValues(RecipeUtils.AddonSynthesis);
|
|
||||||
var statusManager = ((Character*)player.Address)->GetStatusManager();
|
|
||||||
|
|
||||||
byte GetEffectStack(ushort id)
|
|
||||||
{
|
|
||||||
foreach (var status in statusManager->StatusSpan)
|
|
||||||
if (status.StatusID == id)
|
|
||||||
return status.StackCount;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
bool HasEffect(ushort id)
|
|
||||||
{
|
|
||||||
foreach (var status in statusManager->StatusSpan)
|
|
||||||
if (status.StatusID == id)
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new(Input)
|
|
||||||
{
|
|
||||||
ActionCount = ActionCount,
|
|
||||||
StepCount = (int)values.StepCount - 1,
|
|
||||||
Progress = (int)values.Progress,
|
|
||||||
Quality = (int)values.Quality,
|
|
||||||
Durability = (int)values.Durability,
|
|
||||||
CP = (int)player.CurrentCp,
|
|
||||||
Condition = values.Condition,
|
|
||||||
ActiveEffects = new()
|
|
||||||
{
|
|
||||||
InnerQuiet = GetEffectStack(StatusInnerQuiet),
|
|
||||||
WasteNot = GetEffectStack(StatusWasteNot),
|
|
||||||
Veneration = GetEffectStack(StatusVeneration),
|
|
||||||
GreatStrides = GetEffectStack(StatusGreatStrides),
|
|
||||||
Innovation = GetEffectStack(StatusInnovation),
|
|
||||||
FinalAppraisal = GetEffectStack(StatusFinalAppraisal),
|
|
||||||
WasteNot2 = GetEffectStack(StatusWasteNot2),
|
|
||||||
MuscleMemory = GetEffectStack(StatusMuscleMemory),
|
|
||||||
Manipulation = GetEffectStack(StatusManipulation),
|
|
||||||
HeartAndSoul = HasEffect(StatusHeartAndSoul),
|
|
||||||
},
|
|
||||||
ActionStates = ActionStates
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
using Craftimizer.Simulator;
|
|
||||||
using Craftimizer.Simulator.Actions;
|
|
||||||
using Craftimizer.Utils;
|
|
||||||
using Dalamud.Interface.Windowing;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace Craftimizer.Plugin.Windows;
|
|
||||||
|
|
||||||
public sealed unsafe partial class Craft : Window, IDisposable
|
|
||||||
{
|
|
||||||
private SimulationState? SolverState { get; set; }
|
|
||||||
private Solver.Solver? SolverTask { get; set; }
|
|
||||||
private CancellationTokenSource? SolverTaskToken { get; set; }
|
|
||||||
private ConcurrentQueue<ActionType> SolverActionQueue { get; } = new();
|
|
||||||
|
|
||||||
// State is the state of the simulation *after* its corresponding action is executed.
|
|
||||||
private List<(ActionType Action, string Tooltip, SimulationState State)> SolverActions { get; } = new();
|
|
||||||
private SimulatorNoRandom SolverSim { get; set; } = null!;
|
|
||||||
private SimulationState SolverLatestState => SolverActions.Count == 0 ? SolverState!.Value : SolverActions[^1].State;
|
|
||||||
|
|
||||||
private void StopSolve()
|
|
||||||
{
|
|
||||||
if (SolverTask == null || SolverTaskToken == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!SolverTask.IsCompleted)
|
|
||||||
SolverTaskToken.Cancel();
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SolverTaskToken.Dispose();
|
|
||||||
SolverTask.Dispose();
|
|
||||||
|
|
||||||
SolverTask = null;
|
|
||||||
SolverTaskToken = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void QueueSolve(SimulationState state)
|
|
||||||
{
|
|
||||||
StopSolve();
|
|
||||||
|
|
||||||
SolverActionQueue.Clear();
|
|
||||||
SolverActions.Clear();
|
|
||||||
SolverState = state;
|
|
||||||
SolverSim = new(state);
|
|
||||||
|
|
||||||
SolverTaskToken = new();
|
|
||||||
SolverTask = new(Config.SynthHelperSolverConfig, state) { Token = SolverTaskToken.Token };
|
|
||||||
SolverTask.OnLog += s => Log.Debug(s);
|
|
||||||
SolverTask.OnNewAction += SolverActionQueue.Enqueue;
|
|
||||||
SolverTask.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SolveTick()
|
|
||||||
{
|
|
||||||
var newState = GetNextState();
|
|
||||||
if (SolverState == newState)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (newState == null)
|
|
||||||
StopSolve();
|
|
||||||
else
|
|
||||||
QueueSolve(newState.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DequeueSolver()
|
|
||||||
{
|
|
||||||
while (SolverActionQueue.TryDequeue(out var poppedAction))
|
|
||||||
AppendSolverAction(poppedAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AppendSolverAction(ActionType action)
|
|
||||||
{
|
|
||||||
var actionBase = action.Base();
|
|
||||||
if (actionBase is BaseComboAction comboActionBase)
|
|
||||||
{
|
|
||||||
AppendSolverAction(comboActionBase.ActionTypeA);
|
|
||||||
AppendSolverAction(comboActionBase.ActionTypeB);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (SolverActions.Count >= Config.SynthHelperStepCount)
|
|
||||||
{
|
|
||||||
StopSolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var tooltip = actionBase.GetTooltip(SolverSim, false);
|
|
||||||
var (_, state) = SolverSim.Execute(SolverLatestState, action);
|
|
||||||
SolverActions.Add((action, tooltip, state));
|
|
||||||
|
|
||||||
if (SolverActions.Count >= Config.SynthHelperStepCount)
|
|
||||||
StopSolve();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
using Craftimizer.Simulator;
|
|
||||||
using Craftimizer.Simulator.Actions;
|
|
||||||
using Dalamud.Interface.Windowing;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Craftimizer.Plugin.Windows;
|
|
||||||
|
|
||||||
public sealed unsafe partial class Craft : Window, IDisposable
|
|
||||||
{
|
|
||||||
private ConcurrentQueue<ActionType> UsedActionQueue { get; set; } = new();
|
|
||||||
private IEnumerator<SimulationState>? StateTicker { get; set; }
|
|
||||||
|
|
||||||
private SimulationState? GetNextState()
|
|
||||||
{
|
|
||||||
if (RecipeUtils.IsCrafting && StateTicker == null)
|
|
||||||
StateTicker = TickState();
|
|
||||||
if (!RecipeUtils.IsCrafting && StateTicker != null)
|
|
||||||
StateTicker = null;
|
|
||||||
|
|
||||||
if (StateTicker == null)
|
|
||||||
return null;
|
|
||||||
StateTicker.MoveNext();
|
|
||||||
return StateTicker.Current;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerator<SimulationState> TickState()
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
SimulationState state;
|
|
||||||
|
|
||||||
// Dequeue used actions
|
|
||||||
var sim = new SimulatorNoRandom(new());
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
state = GetAddonSimulationState();
|
|
||||||
|
|
||||||
var dequeued = false;
|
|
||||||
while (UsedActionQueue.TryDequeue(out var action))
|
|
||||||
{
|
|
||||||
dequeued = true;
|
|
||||||
(_, state) = sim.Execute(state, action);
|
|
||||||
ActionCount++;
|
|
||||||
ActionStates.MutateState(action.Base());
|
|
||||||
}
|
|
||||||
if (dequeued)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// If nothing is dequeued and executed, just return the addon state
|
|
||||||
yield return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Intermediate state, wait for addon change
|
|
||||||
var intermediateState = GetAddonSimulationState();
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
yield return state;
|
|
||||||
var newState = GetAddonSimulationState();
|
|
||||||
if (!IsStateInIntermediate(newState, intermediateState))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsStateInIntermediate(SimulationState a, SimulationState b)
|
|
||||||
{
|
|
||||||
b.CP = a.CP;
|
|
||||||
b.ActiveEffects = a.ActiveEffects;
|
|
||||||
return a == b;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnActionUsed(ActionType action)
|
|
||||||
{
|
|
||||||
if (!RecipeUtils.IsCrafting || RecipeUtils.AddonSynthesis == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
UsedActionQueue.Enqueue(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
using Craftimizer.Plugin;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
|
using Dalamud.Interface.Utility.Raii;
|
||||||
|
using Dalamud.Interface.Windowing;
|
||||||
|
using ImGuiNET;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Craftimizer.Windows;
|
||||||
|
|
||||||
|
public sealed class MacroClipboard : Window, IDisposable
|
||||||
|
{
|
||||||
|
private const ImGuiWindowFlags WindowFlags = ImGuiWindowFlags.None;
|
||||||
|
|
||||||
|
private List<string> Macros { get; }
|
||||||
|
|
||||||
|
public MacroClipboard(IEnumerable<string> macros) : base("Macro Clipboard", WindowFlags)
|
||||||
|
{
|
||||||
|
Macros = new(macros);
|
||||||
|
|
||||||
|
IsOpen = true;
|
||||||
|
|
||||||
|
Service.WindowSystem.AddWindow(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Draw()
|
||||||
|
{
|
||||||
|
var idx = 0;
|
||||||
|
foreach(var macro in Macros)
|
||||||
|
DrawMacro(idx++, macro);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
var cursor = ImGui.GetCursorPos();
|
||||||
|
|
||||||
|
ImGuiUtils.AlignRight(ImGui.GetFrameHeight(), availWidth);
|
||||||
|
var buttonCursor = ImGui.GetCursorPos();
|
||||||
|
ImGui.InvisibleButton("##copyInvButton", new(ImGui.GetFrameHeight()));
|
||||||
|
var buttonHovered = ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenOverlapped | ImGuiHoveredFlags.AllowWhenBlockedByActiveItem);
|
||||||
|
var buttonActive = buttonHovered && ImGui.GetIO().MouseDown[(int)ImGuiMouseButton.Left];
|
||||||
|
var buttonClicked = buttonHovered && ImGui.GetIO().MouseReleased[(int)ImGuiMouseButton.Left];
|
||||||
|
ImGui.SetCursorPos(buttonCursor);
|
||||||
|
{
|
||||||
|
using var color = ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(buttonActive ? ImGuiCol.ButtonActive : ImGuiCol.ButtonHovered), buttonHovered);
|
||||||
|
ImGuiUtils.IconButtonSized(FontAwesomeIcon.Copy, new(ImGui.GetFrameHeight()));
|
||||||
|
if (buttonClicked)
|
||||||
|
{
|
||||||
|
ImGui.SetClipboardText(macro);
|
||||||
|
Service.PluginInterface.UiBuilder.AddNotification($"Macro {idx + 1} copied to clipboard.", "Craftimizer Macro Copied", NotificationType.Success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (buttonHovered)
|
||||||
|
ImGui.SetTooltip("Copy to Clipboard");
|
||||||
|
|
||||||
|
ImGui.SetCursorPos(cursor);
|
||||||
|
{
|
||||||
|
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||||
|
using var padding = ImRaii.PushStyle(ImGuiStyleVar.FramePadding, Vector2.Zero);
|
||||||
|
using var bg = ImRaii.PushColor(ImGuiCol.FrameBg, Vector4.Zero);
|
||||||
|
var lineCount = macro.Count(c => c == '\n') + 1;
|
||||||
|
ImGui.InputTextMultiline("", ref macro, (uint)macro.Length + 1, new(availWidth, ImGui.GetTextLineHeight() * Math.Max(15, lineCount) + ImGui.GetStyle().FramePadding.Y), ImGuiInputTextFlags.ReadOnly | ImGuiInputTextFlags.AutoSelectAll);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buttonHovered)
|
||||||
|
ImGui.SetMouseCursor(ImGuiMouseCursor.Arrow);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Service.WindowSystem.RemoveWindow(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,361 @@
|
|||||||
|
using Craftimizer.Plugin;
|
||||||
|
using Craftimizer.Utils;
|
||||||
|
using Dalamud.Interface.Utility.Raii;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.Windowing;
|
||||||
|
using ImGuiNET;
|
||||||
|
using System;
|
||||||
|
using Craftimizer.Simulator;
|
||||||
|
using Craftimizer.Simulator.Actions;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using Sim = Craftimizer.Simulator.SimulatorNoRandom;
|
||||||
|
using Dalamud.Interface.Utility;
|
||||||
|
|
||||||
|
namespace Craftimizer.Windows;
|
||||||
|
|
||||||
|
public sealed class MacroList : Window, IDisposable
|
||||||
|
{
|
||||||
|
private const ImGuiWindowFlags WindowFlags = ImGuiWindowFlags.None;
|
||||||
|
|
||||||
|
public CharacterStats? CharacterStats { get; private set; }
|
||||||
|
public RecipeData? RecipeData { get; private set; }
|
||||||
|
private MacroEditor? EditorWindow { get; set; }
|
||||||
|
|
||||||
|
private IReadOnlyList<Macro> Macros => Service.Configuration.Macros;
|
||||||
|
private Dictionary<Macro, SimulationState> MacroStateCache { get; } = new();
|
||||||
|
|
||||||
|
public MacroList() : base("Craftimizer Macro List", WindowFlags, false)
|
||||||
|
{
|
||||||
|
RefreshSearch();
|
||||||
|
|
||||||
|
Macro.OnMacroChanged += OnMacroChanged;
|
||||||
|
Configuration.OnMacroListChanged += OnMacroListChanged;
|
||||||
|
|
||||||
|
CollapsedCondition = ImGuiCond.Appearing;
|
||||||
|
Collapsed = false;
|
||||||
|
|
||||||
|
SizeConstraints = new() { MinimumSize = new(500, 520), MaximumSize = new(float.PositiveInfinity) };
|
||||||
|
|
||||||
|
Service.WindowSystem.AddWindow(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool DrawConditions()
|
||||||
|
{
|
||||||
|
return Service.ClientState.LocalPlayer != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void PreDraw()
|
||||||
|
{
|
||||||
|
var oldCharacterStats = CharacterStats;
|
||||||
|
var oldRecipeData = RecipeData;
|
||||||
|
|
||||||
|
EditorWindow = Service.Plugin.EditorWindow;
|
||||||
|
EditorWindow = (EditorWindow?.IsOpen ?? false) ? EditorWindow : null;
|
||||||
|
RecipeData = EditorWindow?.RecipeData ?? Service.Plugin.RecipeNoteWindow.RecipeData;
|
||||||
|
CharacterStats = EditorWindow?.CharacterStats ?? Service.Plugin.RecipeNoteWindow.CharacterStats;
|
||||||
|
|
||||||
|
if (oldCharacterStats != CharacterStats || oldRecipeData != RecipeData)
|
||||||
|
RecalculateStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Draw()
|
||||||
|
{
|
||||||
|
DrawSearchBar();
|
||||||
|
using var group = ImRaii.Child("macros", new(-1, -1));
|
||||||
|
if (sortedMacros.Count > 0)
|
||||||
|
{
|
||||||
|
var macros = new List<Macro>(sortedMacros);
|
||||||
|
foreach (var macro in macros)
|
||||||
|
DrawMacro(macro);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var text1 = "You have no macros! Create one by opening";
|
||||||
|
var text2 = "the Macro Editor here or from the Crafting Log.";
|
||||||
|
var text3 = "Open Crafting Log";
|
||||||
|
var text4 = "Open Macro Editor";
|
||||||
|
var buttonRowWidth = ImGui.CalcTextSize(text3).X + ImGui.CalcTextSize(text4).X + ImGui.GetStyle().ItemSpacing.X * 5;
|
||||||
|
var size = new Vector2(
|
||||||
|
Math.Max(
|
||||||
|
Math.Max(ImGui.CalcTextSize(text1).X, ImGui.CalcTextSize(text2).X),
|
||||||
|
buttonRowWidth
|
||||||
|
),
|
||||||
|
ImGui.GetTextLineHeightWithSpacing() * 2 + ImGui.GetFrameHeight()
|
||||||
|
);
|
||||||
|
ImGuiUtils.AlignMiddle(size);
|
||||||
|
using var child = ImRaii.Child("##macroMessage", size);
|
||||||
|
ImGuiUtils.TextCentered(text1);
|
||||||
|
ImGuiUtils.TextCentered(text2);
|
||||||
|
ImGuiUtils.AlignCentered(buttonRowWidth);
|
||||||
|
if (ImGui.Button(text3))
|
||||||
|
Service.Plugin.OpenCraftingLog();
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button(text4))
|
||||||
|
OpenEditor(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string searchText = string.Empty;
|
||||||
|
private List<Macro> sortedMacros = null!;
|
||||||
|
private void DrawSearchBar()
|
||||||
|
{
|
||||||
|
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
|
||||||
|
if (ImGui.InputTextWithHint("##search", "Search", ref searchText, 100))
|
||||||
|
RefreshSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawMacro(Macro macro)
|
||||||
|
{
|
||||||
|
var windowHeight = 2 * ImGui.GetFrameHeightWithSpacing();
|
||||||
|
|
||||||
|
if (macro.Actions.Any(a => a.Category() == ActionCategory.Combo))
|
||||||
|
throw new InvalidOperationException("Combo actions should be sanitized away");
|
||||||
|
|
||||||
|
var stateNullable = GetMacroState(macro);
|
||||||
|
|
||||||
|
using var panel = ImGuiUtils.GroupPanel(macro.Name, -1, out var availWidth);
|
||||||
|
var stepsAvailWidthOffset = ImGui.GetContentRegionAvail().X - availWidth;
|
||||||
|
var spacing = ImGui.GetStyle().ItemSpacing.Y;
|
||||||
|
var miniRowHeight = (windowHeight - spacing) / 2f;
|
||||||
|
|
||||||
|
using var table = ImRaii.Table("table", stateNullable.HasValue ? 3 : 2, ImGuiTableFlags.BordersInnerV);
|
||||||
|
if (table)
|
||||||
|
{
|
||||||
|
if (stateNullable.HasValue)
|
||||||
|
ImGui.TableSetupColumn("stats", ImGuiTableColumnFlags.WidthFixed, 0);
|
||||||
|
ImGui.TableSetupColumn("actions", ImGuiTableColumnFlags.WidthFixed, 0);
|
||||||
|
ImGui.TableSetupColumn("steps", ImGuiTableColumnFlags.WidthStretch, 0);
|
||||||
|
|
||||||
|
ImGui.TableNextRow(ImGuiTableRowFlags.None, windowHeight);
|
||||||
|
if (stateNullable is { } state)
|
||||||
|
{
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if (Service.Configuration.ShowOptimalMacroStat)
|
||||||
|
{
|
||||||
|
var progressHeight = windowHeight;
|
||||||
|
if (state.Progress >= state.Input.Recipe.MaxProgress && state.Input.Recipe.MaxQuality > 0)
|
||||||
|
{
|
||||||
|
ImGuiUtils.ArcProgress(
|
||||||
|
(float)state.Quality / state.Input.Recipe.MaxQuality,
|
||||||
|
progressHeight / 2f,
|
||||||
|
.5f,
|
||||||
|
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
||||||
|
ImGui.GetColorU32(Colors.Quality));
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip($"Quality: {state.Quality} / {state.Input.Recipe.MaxQuality}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGuiUtils.ArcProgress(
|
||||||
|
(float)state.Progress / state.Input.Recipe.MaxProgress,
|
||||||
|
progressHeight / 2f,
|
||||||
|
.5f,
|
||||||
|
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
||||||
|
ImGui.GetColorU32(Colors.Progress));
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip($"Progress: {state.Progress} / {state.Input.Recipe.MaxProgress}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGuiUtils.ArcProgress(
|
||||||
|
(float)state.Progress / state.Input.Recipe.MaxProgress,
|
||||||
|
miniRowHeight / 2f,
|
||||||
|
.5f,
|
||||||
|
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
||||||
|
ImGui.GetColorU32(Colors.Progress));
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip($"Progress: {state.Progress} / {state.Input.Recipe.MaxProgress}");
|
||||||
|
|
||||||
|
ImGui.SameLine(0, spacing);
|
||||||
|
ImGuiUtils.ArcProgress(
|
||||||
|
(float)state.Quality / state.Input.Recipe.MaxQuality,
|
||||||
|
miniRowHeight / 2f,
|
||||||
|
.5f,
|
||||||
|
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
||||||
|
ImGui.GetColorU32(Colors.Quality));
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip($"Quality: {state.Quality} / {state.Input.Recipe.MaxQuality}");
|
||||||
|
|
||||||
|
ImGuiUtils.ArcProgress((float)state.Durability / state.Input.Recipe.MaxDurability,
|
||||||
|
miniRowHeight / 2f,
|
||||||
|
.5f,
|
||||||
|
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
||||||
|
ImGui.GetColorU32(Colors.Durability));
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip($"Remaining Durability: {state.Durability} / {state.Input.Recipe.MaxDurability}");
|
||||||
|
|
||||||
|
ImGui.SameLine(0, spacing);
|
||||||
|
ImGuiUtils.ArcProgress(
|
||||||
|
(float)state.CP / state.Input.Stats.CP,
|
||||||
|
miniRowHeight / 2f,
|
||||||
|
.5f,
|
||||||
|
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
||||||
|
ImGui.GetColorU32(Colors.CP));
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip($"Remaining CP: {state.CP} / {state.Input.Stats.CP}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
{
|
||||||
|
if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Copy, new(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)
|
||||||
|
Service.Configuration.RemoveMacro(macro);
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip("Delete (Hold Shift)");
|
||||||
|
|
||||||
|
if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.PencilAlt, new(miniRowHeight)))
|
||||||
|
ShowRenamePopup(macro);
|
||||||
|
DrawRenamePopup(macro);
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip("Rename");
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Edit, new(miniRowHeight)))
|
||||||
|
OpenEditor(macro);
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip("Open in Simulator");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
{
|
||||||
|
var itemsPerRow = (int)MathF.Floor((ImGui.GetContentRegionAvail().X - stepsAvailWidthOffset + spacing) / (miniRowHeight + spacing));
|
||||||
|
var itemCount = macro.Actions.Count;
|
||||||
|
for (var i = 0; i < itemsPerRow * 2; i++)
|
||||||
|
{
|
||||||
|
if (i % itemsPerRow != 0)
|
||||||
|
ImGui.SameLine(0, spacing);
|
||||||
|
if (i < itemCount)
|
||||||
|
{
|
||||||
|
var shouldShowMore = i + 1 == itemsPerRow * 2 && i + 1 < itemCount;
|
||||||
|
if (!shouldShowMore)
|
||||||
|
{
|
||||||
|
ImGui.Image(macro.Actions[i].GetIcon(RecipeData!.ClassJob).ImGuiHandle, new(miniRowHeight));
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip(macro.Actions[i].GetName(RecipeData!.ClassJob));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var amtMore = itemCount - itemsPerRow * 2;
|
||||||
|
var pos = ImGui.GetCursorPos();
|
||||||
|
ImGui.Image(macro.Actions[i].GetIcon(RecipeData!.ClassJob).ImGuiHandle, new(miniRowHeight), default, Vector2.One, new(1, 1, 1, .5f));
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip($"{macro.Actions[i].GetName(RecipeData!.ClassJob)}\nand {amtMore} more");
|
||||||
|
ImGui.SetCursorPos(pos);
|
||||||
|
ImGui.GetWindowDrawList().AddRectFilled(ImGui.GetCursorScreenPos(), ImGui.GetCursorScreenPos() + new Vector2(miniRowHeight), ImGui.GetColorU32(ImGuiCol.FrameBg), miniRowHeight / 8f);
|
||||||
|
ImGui.GetWindowDrawList().AddTextClippedEx(ImGui.GetCursorScreenPos(), ImGui.GetCursorScreenPos() + new Vector2(miniRowHeight), $"+{amtMore}", null, new(.5f), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ImGui.Dummy(new(miniRowHeight));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string popupMacroName = string.Empty;
|
||||||
|
private Macro? popupMacro;
|
||||||
|
private void ShowRenamePopup(Macro macro)
|
||||||
|
{
|
||||||
|
ImGui.OpenPopup($"##renamePopup-{macro.GetHashCode()}");
|
||||||
|
popupMacro = macro;
|
||||||
|
popupMacroName = macro.Name;
|
||||||
|
ImGui.SetNextWindowPos(ImGui.GetMousePos() - new Vector2(ImGui.CalcItemWidth() * .25f, ImGui.GetFrameHeight() + ImGui.GetStyle().WindowPadding.Y * 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawRenamePopup(Macro macro)
|
||||||
|
{
|
||||||
|
using var popup = ImRaii.Popup($"##renamePopup-{macro.GetHashCode()}");
|
||||||
|
if (popup)
|
||||||
|
{
|
||||||
|
if (ImGui.IsWindowAppearing())
|
||||||
|
ImGui.SetKeyboardFocusHere();
|
||||||
|
ImGui.SetNextItemWidth(ImGui.CalcItemWidth());
|
||||||
|
if (ImGui.InputTextWithHint($"##setName", "Name", ref popupMacroName, 100, ImGuiInputTextFlags.AutoSelectAll | ImGuiInputTextFlags.EnterReturnsTrue))
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(popupMacroName))
|
||||||
|
{
|
||||||
|
popupMacro!.Name = popupMacroName;
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RecalculateStats()
|
||||||
|
{
|
||||||
|
MacroStateCache.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshSearch()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(searchText))
|
||||||
|
{
|
||||||
|
sortedMacros = new(Macros);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var matcher = new FuzzyMatcher(searchText.ToLowerInvariant(), MatchMode.FuzzyParts);
|
||||||
|
var query = Macros.AsParallel().Select(i => (Item: i, Score: matcher.Matches(i.Name.ToLowerInvariant())))
|
||||||
|
.Where(t => t.Score > 0)
|
||||||
|
.OrderByDescending(t => t.Score)
|
||||||
|
.Select(t => t.Item);
|
||||||
|
sortedMacros = query.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenEditor(Macro? macro)
|
||||||
|
{
|
||||||
|
var character = CharacterStats ?? new()
|
||||||
|
{
|
||||||
|
Craftsmanship = 100,
|
||||||
|
Control = 100,
|
||||||
|
CP = 200,
|
||||||
|
Level = 10,
|
||||||
|
CanUseManipulation = false,
|
||||||
|
HasSplendorousBuff = false,
|
||||||
|
IsSpecialist = false,
|
||||||
|
CLvl = 10,
|
||||||
|
};
|
||||||
|
var recipe = RecipeData ?? new(1023);
|
||||||
|
|
||||||
|
var buffs = EditorWindow?.Buffs ?? new(Service.Plugin.RecipeNoteWindow.CharacterStats != null ? Service.ClientState.LocalPlayer?.StatusList : null);
|
||||||
|
Service.Plugin.OpenMacroEditor(character, recipe, buffs, macro?.Actions ?? Enumerable.Empty<ActionType>(), macro != null ? (actions => { macro.ActionEnumerable = actions; Service.Configuration.Save(); }) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMacroChanged(Macro macro)
|
||||||
|
{
|
||||||
|
MacroStateCache.Remove(macro);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMacroListChanged()
|
||||||
|
{
|
||||||
|
RefreshSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
private SimulationState? GetMacroState(Macro macro)
|
||||||
|
{
|
||||||
|
if (CharacterStats == null || RecipeData == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (MacroStateCache.TryGetValue(macro, out var state))
|
||||||
|
return state;
|
||||||
|
|
||||||
|
state = new SimulationState(new(CharacterStats, RecipeData.RecipeInfo));
|
||||||
|
var sim = new Sim(state);
|
||||||
|
(_, state, _) = sim.ExecuteMultiple(state, macro.Actions);
|
||||||
|
return MacroStateCache[macro] = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Macro.OnMacroChanged -= OnMacroChanged;
|
||||||
|
Configuration.OnMacroListChanged -= OnMacroListChanged;
|
||||||
|
|
||||||
|
Service.WindowSystem.RemoveWindow(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,12 +4,14 @@ using Craftimizer.Simulator;
|
|||||||
using Craftimizer.Simulator.Actions;
|
using Craftimizer.Simulator.Actions;
|
||||||
using Craftimizer.Solver;
|
using Craftimizer.Solver;
|
||||||
using Craftimizer.Utils;
|
using Craftimizer.Utils;
|
||||||
|
using Dalamud.Game.Text;
|
||||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.Colors;
|
using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.Components;
|
using Dalamud.Interface.Components;
|
||||||
using Dalamud.Interface.GameFonts;
|
using Dalamud.Interface.GameFonts;
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
@@ -59,6 +61,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
private CancellationTokenSource? BestMacroTokenSource { get; set; }
|
private CancellationTokenSource? BestMacroTokenSource { get; set; }
|
||||||
private Exception? BestMacroException { get; set; }
|
private Exception? BestMacroException { get; set; }
|
||||||
public (Macro, SimulationState)? BestSavedMacro { get; private set; }
|
public (Macro, SimulationState)? BestSavedMacro { get; private set; }
|
||||||
|
public bool HasSavedMacro { get; private set; }
|
||||||
public SolverSolution? BestSuggestedMacro { get; private set; }
|
public SolverSolution? BestSuggestedMacro { get; private set; }
|
||||||
|
|
||||||
private IDalamudTextureWrap ExpertBadge { get; }
|
private IDalamudTextureWrap ExpertBadge { get; }
|
||||||
@@ -82,7 +85,21 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
IsOpen = true;
|
IsOpen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool wasOpen;
|
||||||
public override bool DrawConditions()
|
public override bool DrawConditions()
|
||||||
|
{
|
||||||
|
var isOpen = ShouldDraw();
|
||||||
|
if (isOpen != wasOpen)
|
||||||
|
{
|
||||||
|
if (wasOpen)
|
||||||
|
BestMacroTokenSource?.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
wasOpen = isOpen;
|
||||||
|
return isOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldDraw()
|
||||||
{
|
{
|
||||||
if (Service.ClientState.LocalPlayer == null)
|
if (Service.ClientState.LocalPlayer == null)
|
||||||
return false;
|
return false;
|
||||||
@@ -146,7 +163,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
statsChanged = true;
|
statsChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (statsChanged && CraftStatus == CraftableStatus.OK)
|
if ((statsChanged || (BestMacroTokenSource?.IsCancellationRequested ?? false)) && CraftStatus == CraftableStatus.OK)
|
||||||
CalculateBestMacros();
|
CalculateBestMacros();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -172,16 +189,22 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
|
|
||||||
public override void Draw()
|
public override void Draw()
|
||||||
{
|
{
|
||||||
|
var availWidth = ImGui.GetContentRegionAvail().X;
|
||||||
|
using (var table = ImRaii.Table("stats", 2, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.SizingFixedSame))
|
||||||
{
|
{
|
||||||
using var table = ImRaii.Table("stats", 2, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.SizingStretchSame);
|
|
||||||
if (table)
|
if (table)
|
||||||
{
|
{
|
||||||
ImGui.TableSetupColumn("col1", ImGuiTableColumnFlags.WidthStretch);
|
ImGui.TableSetupColumn("col1", ImGuiTableColumnFlags.WidthFixed, 0);
|
||||||
ImGui.TableSetupColumn("col2", ImGuiTableColumnFlags.WidthStretch);
|
ImGui.TableSetupColumn("col2", ImGuiTableColumnFlags.WidthFixed, 0);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
DrawCharacterStats();
|
DrawCharacterStats();
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
DrawRecipeStats();
|
DrawRecipeStats();
|
||||||
|
|
||||||
|
// Ensure that we know the window should be the same size as this table. Any more and it'll grow slowly and won't shrink when it could
|
||||||
|
ImGui.SameLine(0, 0);
|
||||||
|
// The -1 is to account for the extra vertical separator on the right that ImGui draws for some reason
|
||||||
|
availWidth = ImGui.GetCursorPosX() - ImGui.GetStyle().WindowPadding.X + ImGui.GetStyle().CellPadding.X - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,35 +213,44 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
|
using (var table = ImRaii.Table("macros", 1, ImGuiTableFlags.SizingStretchSame))
|
||||||
{
|
{
|
||||||
using var table = ImRaii.Table("macros", 1, ImGuiTableFlags.SizingStretchSame);
|
|
||||||
if (table)
|
if (table)
|
||||||
{
|
{
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
|
|
||||||
ImGui.AlignTextToFramePadding();
|
availWidth -= ImGui.GetStyle().ItemSpacing.X * 2;
|
||||||
ImGuiUtils.TextCentered("Best Saved Macro");
|
using (var panel = ImGuiUtils.GroupPanel("Best Saved Macro", availWidth, out _))
|
||||||
if (BestSavedMacro is { } savedMacro)
|
|
||||||
{
|
{
|
||||||
ImGuiUtils.TextCentered(savedMacro.Item1.Name);
|
var stepsAvailWidthOffset = ImGui.GetContentRegionAvail().X - availWidth;
|
||||||
DrawMacro("savedMacro", (savedMacro.Item1.Actions, savedMacro.Item2));
|
if (BestSavedMacro is { } savedMacro)
|
||||||
|
{
|
||||||
|
ImGuiUtils.TextCentered(savedMacro.Item1.Name, availWidth);
|
||||||
|
DrawMacro((savedMacro.Item1.Actions, savedMacro.Item2), a => { savedMacro.Item1.ActionEnumerable = a; Service.Configuration.Save(); }, stepsAvailWidthOffset, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.Text("");
|
||||||
|
DrawMacro(null, null, stepsAvailWidthOffset, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
using (var panel = ImGuiUtils.GroupPanel("Suggested Macro", availWidth, out _))
|
||||||
{
|
{
|
||||||
ImGui.Text("");
|
var stepsAvailWidthOffset = ImGui.GetContentRegionAvail().X - availWidth;
|
||||||
DrawMacro("savedMacro", null);
|
if (BestSuggestedMacro is { } suggestedMacro)
|
||||||
|
DrawMacro((suggestedMacro.Actions, suggestedMacro.State), null, stepsAvailWidthOffset, false);
|
||||||
|
else
|
||||||
|
DrawMacro(null, null, stepsAvailWidthOffset, false);
|
||||||
}
|
}
|
||||||
ImGui.Button("View Saved Macros", new(-1, 0));
|
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGuiHelpers.ScaledDummy(5);
|
||||||
|
|
||||||
ImGui.AlignTextToFramePadding();
|
if (ImGui.Button("View Saved Macros", new(-1, 0)))
|
||||||
ImGuiUtils.TextCentered("Suggested Macro");
|
Service.Plugin.OpenMacroListWindow();
|
||||||
if (BestSuggestedMacro is { } suggestedMacro)
|
|
||||||
DrawMacro("suggestedMacro", (suggestedMacro.Actions, suggestedMacro.State));
|
if (ImGui.Button("Open in Simulator", new(-1, 0)))
|
||||||
else
|
Service.Plugin.OpenMacroEditor(CharacterStats!, RecipeData!, new(Service.ClientState.LocalPlayer!.StatusList), Enumerable.Empty<ActionType>(), null);
|
||||||
DrawMacro("suggestedMacro", null);
|
|
||||||
ImGui.Button("Open Simulator", new(-1, 0));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -237,7 +269,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
}
|
}
|
||||||
var levelText = string.Empty;
|
var levelText = string.Empty;
|
||||||
if (level != 0)
|
if (level != 0)
|
||||||
levelText = SqText.ToLevelString(level);
|
levelText = SqText.LevelPrefix.ToIconChar() + SqText.ToLevelString(level);
|
||||||
var imageSize = ImGui.GetFrameHeight();
|
var imageSize = ImGui.GetFrameHeight();
|
||||||
bool hasSplendorous = false, hasSpecialist = false, shouldHaveManip = false;
|
bool hasSplendorous = false, hasSpecialist = false, shouldHaveManip = false;
|
||||||
if (CraftStatus is not (CraftableStatus.LockedClassJob or CraftableStatus.WrongClassJob))
|
if (CraftStatus is not (CraftableStatus.LockedClassJob or CraftableStatus.WrongClassJob))
|
||||||
@@ -338,6 +370,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
ImGuiUtils.TextCentered($"You do not have any {RecipeData.ClassJob.GetName().ToLowerInvariant()} gearsets.");
|
ImGuiUtils.TextCentered($"You do not have any {RecipeData.ClassJob.GetName().ToLowerInvariant()} gearsets.");
|
||||||
|
ImGui.Dummy(default);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CraftableStatus.SpecialistRequired:
|
case CraftableStatus.SpecialistRequired:
|
||||||
@@ -439,9 +472,9 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
var textStarsSize = Vector2.Zero;
|
var textStarsSize = Vector2.Zero;
|
||||||
if (!string.IsNullOrEmpty(textStars)) {
|
if (!string.IsNullOrEmpty(textStars)) {
|
||||||
var layout = AxisFont.LayoutBuilder(textStars).Build();
|
var layout = AxisFont.LayoutBuilder(textStars).Build();
|
||||||
textStarsSize = new(layout.Width + 3, layout.Height);
|
textStarsSize = new(layout.Width, layout.Height);
|
||||||
}
|
}
|
||||||
var textLevel = SqText.ToLevelString(RecipeData.RecipeInfo.ClassJobLevel);
|
var textLevel = SqText.LevelPrefix.ToIconChar() + SqText.ToLevelString(RecipeData.RecipeInfo.ClassJobLevel);
|
||||||
var isExpert = RecipeData.RecipeInfo.IsExpert;
|
var isExpert = RecipeData.RecipeInfo.IsExpert;
|
||||||
var isCollectable = RecipeData.Recipe.ItemResult.Value!.IsCollectable;
|
var isCollectable = RecipeData.Recipe.ItemResult.Value!.IsCollectable;
|
||||||
var imageSize = ImGui.GetFrameHeight();
|
var imageSize = ImGui.GetFrameHeight();
|
||||||
@@ -452,7 +485,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
ImGuiUtils.AlignCentered(
|
ImGuiUtils.AlignCentered(
|
||||||
imageSize + 5 +
|
imageSize + 5 +
|
||||||
ImGui.CalcTextSize(textLevel).X +
|
ImGui.CalcTextSize(textLevel).X +
|
||||||
textStarsSize.X +
|
(textStarsSize != Vector2.Zero ? textStarsSize.X + 3 : 0) +
|
||||||
(isCollectable ? badgeSize.X + 3 : 0) +
|
(isCollectable ? badgeSize.X + 3 : 0) +
|
||||||
(isExpert ? badgeSize.X + 3 : 0)
|
(isExpert ? badgeSize.X + 3 : 0)
|
||||||
);
|
);
|
||||||
@@ -516,16 +549,16 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawMacro(string imGuiId, (List<ActionType> Actions, SimulationState State)? macroValue)
|
private void DrawMacro((IReadOnlyList<ActionType> Actions, SimulationState State)? macroValue, Action<IEnumerable<ActionType>>? setter, float stepsAvailWidthOffset, bool isSavedMacro)
|
||||||
{
|
{
|
||||||
//using var window = ImRaii.Child(imGuiId, new(-1, (name != null ? ImGui.GetTextLineHeightWithSpacing() : 0) + 2 * ImGui.GetFrameHeightWithSpacing()), false, ImGuiWindowFlags.AlwaysAutoResize);
|
|
||||||
|
|
||||||
var windowHeight = 2 * ImGui.GetFrameHeightWithSpacing();
|
var windowHeight = 2 * ImGui.GetFrameHeightWithSpacing();
|
||||||
|
|
||||||
if (macroValue == null)
|
if (macroValue is not { } macro)
|
||||||
{
|
{
|
||||||
if (BestMacroException == null)
|
if (isSavedMacro && !HasSavedMacro)
|
||||||
ImGuiUtils.TextMiddleNewLine("Calculating...", new(ImGui.GetContentRegionAvail().X, windowHeight + 1 + ImGui.GetStyle().ItemSpacing.Y));
|
ImGuiUtils.TextMiddleNewLine("You have no macros!", new(ImGui.GetContentRegionAvail().X - stepsAvailWidthOffset, windowHeight + 1));
|
||||||
|
else if (BestMacroException == null)
|
||||||
|
ImGuiUtils.TextMiddleNewLine("Calculating...", new(ImGui.GetContentRegionAvail().X - stepsAvailWidthOffset, windowHeight + 1));
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
@@ -536,8 +569,8 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (macro.Actions.Any(a => a.Category() == ActionCategory.Combo))
|
||||||
var macro = macroValue!.Value;
|
throw new InvalidOperationException("Combo actions should be sanitized away");
|
||||||
|
|
||||||
using var table = ImRaii.Table("table", 3, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.SizingStretchSame);
|
using var table = ImRaii.Table("table", 3, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.SizingStretchSame);
|
||||||
if (table)
|
if (table)
|
||||||
@@ -552,7 +585,6 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
var spacing = ImGui.GetStyle().ItemSpacing.Y;
|
var spacing = ImGui.GetStyle().ItemSpacing.Y;
|
||||||
var miniRowHeight = (windowHeight - spacing) / 2f;
|
var miniRowHeight = (windowHeight - spacing) / 2f;
|
||||||
|
|
||||||
//ImGui.Text($"{macro.Actions.Count}");
|
|
||||||
{
|
{
|
||||||
if (Service.Configuration.ShowOptimalMacroStat)
|
if (Service.Configuration.ShowOptimalMacroStat)
|
||||||
{
|
{
|
||||||
@@ -564,7 +596,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
progressHeight / 2f,
|
progressHeight / 2f,
|
||||||
.5f,
|
.5f,
|
||||||
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
||||||
ImGui.GetColorU32(Plugin.Windows.Simulator.QualityColor));
|
ImGui.GetColorU32(Colors.Quality));
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
ImGui.SetTooltip($"Quality: {macro.State.Quality} / {macro.State.Input.Recipe.MaxQuality}");
|
ImGui.SetTooltip($"Quality: {macro.State.Quality} / {macro.State.Input.Recipe.MaxQuality}");
|
||||||
}
|
}
|
||||||
@@ -575,7 +607,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
progressHeight / 2f,
|
progressHeight / 2f,
|
||||||
.5f,
|
.5f,
|
||||||
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
||||||
ImGui.GetColorU32(Plugin.Windows.Simulator.ProgressColor));
|
ImGui.GetColorU32(Colors.Progress));
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
ImGui.SetTooltip($"Progress: {macro.State.Progress} / {macro.State.Input.Recipe.MaxProgress}");
|
ImGui.SetTooltip($"Progress: {macro.State.Progress} / {macro.State.Input.Recipe.MaxProgress}");
|
||||||
}
|
}
|
||||||
@@ -587,7 +619,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
miniRowHeight / 2f,
|
miniRowHeight / 2f,
|
||||||
.5f,
|
.5f,
|
||||||
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
||||||
ImGui.GetColorU32(Plugin.Windows.Simulator.ProgressColor));
|
ImGui.GetColorU32(Colors.Progress));
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
ImGui.SetTooltip($"Progress: {macro.State.Progress} / {macro.State.Input.Recipe.MaxProgress}");
|
ImGui.SetTooltip($"Progress: {macro.State.Progress} / {macro.State.Input.Recipe.MaxProgress}");
|
||||||
|
|
||||||
@@ -597,7 +629,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
miniRowHeight / 2f,
|
miniRowHeight / 2f,
|
||||||
.5f,
|
.5f,
|
||||||
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
||||||
ImGui.GetColorU32(Plugin.Windows.Simulator.QualityColor));
|
ImGui.GetColorU32(Colors.Quality));
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
ImGui.SetTooltip($"Quality: {macro.State.Quality} / {macro.State.Input.Recipe.MaxQuality}");
|
ImGui.SetTooltip($"Quality: {macro.State.Quality} / {macro.State.Input.Recipe.MaxQuality}");
|
||||||
|
|
||||||
@@ -605,7 +637,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
miniRowHeight / 2f,
|
miniRowHeight / 2f,
|
||||||
.5f,
|
.5f,
|
||||||
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
||||||
ImGui.GetColorU32(Plugin.Windows.Simulator.DurabilityColor));
|
ImGui.GetColorU32(Colors.Durability));
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
ImGui.SetTooltip($"Remaining Durability: {macro.State.Durability} / {macro.State.Input.Recipe.MaxDurability}");
|
ImGui.SetTooltip($"Remaining Durability: {macro.State.Durability} / {macro.State.Input.Recipe.MaxDurability}");
|
||||||
|
|
||||||
@@ -615,7 +647,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
miniRowHeight / 2f,
|
miniRowHeight / 2f,
|
||||||
.5f,
|
.5f,
|
||||||
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
ImGui.GetColorU32(ImGuiCol.TableBorderLight),
|
||||||
ImGui.GetColorU32(Plugin.Windows.Simulator.CPColor));
|
ImGui.GetColorU32(Colors.CP));
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
ImGui.SetTooltip($"Remaining CP: {macro.State.CP} / {macro.State.Input.Stats.CP}");
|
ImGui.SetTooltip($"Remaining CP: {macro.State.CP} / {macro.State.Input.Stats.CP}");
|
||||||
}
|
}
|
||||||
@@ -623,31 +655,44 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
{
|
{
|
||||||
ImGuiUtils.TextMiddleNewLine($"{macro.Actions.Count}", new(miniRowHeight));
|
if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Edit, new(miniRowHeight)))
|
||||||
|
Service.Plugin.OpenMacroEditor(CharacterStats!, RecipeData!, new(Service.ClientState.LocalPlayer!.StatusList), macro.Actions, setter);
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
ImGui.SetTooltip($"{macro.Actions.Count} Step{(macro.Actions.Count != 1 ? "s" : "")}");
|
ImGui.SetTooltip("Open in Simulator");
|
||||||
using (var iconFont = ImRaii.PushFont(UiBuilder.IconFont))
|
if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Copy, new(miniRowHeight)))
|
||||||
if (ImGuiUtils.ButtonCentered(FontAwesomeIcon.Copy.ToIconString(), new(miniRowHeight)))
|
Service.Plugin.CopyMacro(macro.Actions);
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
ImGui.SetTooltip("Copy to Clipboard");
|
ImGui.SetTooltip("Copy to Clipboard");
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
{
|
{
|
||||||
var itemsPerRow = (int)MathF.Ceiling((ImGui.GetContentRegionAvail().X + spacing) / (miniRowHeight + spacing));
|
var itemsPerRow = (int)MathF.Floor((ImGui.GetContentRegionAvail().X - stepsAvailWidthOffset + spacing) / (miniRowHeight + spacing));
|
||||||
var itemCount = Math.Min(macro.Actions.Count, itemsPerRow * 2);
|
var itemCount = macro.Actions.Count;
|
||||||
for (var i = 0; i < itemsPerRow * 2; i++)
|
for (var i = 0; i < itemsPerRow * 2; i++)
|
||||||
{
|
{
|
||||||
if (i % itemsPerRow != 0)
|
if (i % itemsPerRow != 0)
|
||||||
ImGui.SameLine(0, spacing);
|
ImGui.SameLine(0, spacing);
|
||||||
if (i < itemCount)
|
if (i < itemCount)
|
||||||
{
|
{
|
||||||
ImGui.Image(macro.Actions[i].GetIcon(RecipeData!.ClassJob).ImGuiHandle, new(miniRowHeight));
|
var shouldShowMore = i + 1 == itemsPerRow * 2 && i + 1 < itemCount;
|
||||||
if (ImGui.IsItemHovered())
|
if (!shouldShowMore)
|
||||||
ImGui.SetTooltip(macro.Actions[i].GetName(RecipeData!.ClassJob));
|
{
|
||||||
|
ImGui.Image(macro.Actions[i].GetIcon(RecipeData!.ClassJob).ImGuiHandle, new(miniRowHeight));
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip(macro.Actions[i].GetName(RecipeData!.ClassJob));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var amtMore = itemCount - itemsPerRow * 2;
|
||||||
|
var pos = ImGui.GetCursorPos();
|
||||||
|
ImGui.Image(macro.Actions[i].GetIcon(RecipeData!.ClassJob).ImGuiHandle, new(miniRowHeight), default, Vector2.One, new(1, 1, 1, .5f));
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip($"{macro.Actions[i].GetName(RecipeData!.ClassJob)}\nand {amtMore} more");
|
||||||
|
ImGui.SetCursorPos(pos);
|
||||||
|
ImGui.GetWindowDrawList().AddRectFilled(ImGui.GetCursorScreenPos(), ImGui.GetCursorScreenPos() + new Vector2(miniRowHeight), ImGui.GetColorU32(ImGuiCol.FrameBg), miniRowHeight / 8f);
|
||||||
|
ImGui.GetWindowDrawList().AddTextClippedEx(ImGui.GetCursorScreenPos(), ImGui.GetCursorScreenPos() + new Vector2(miniRowHeight), $"+{amtMore}", null, new(.5f), null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
ImGui.Dummy(new(miniRowHeight));
|
ImGui.Dummy(new(miniRowHeight));
|
||||||
@@ -758,25 +803,31 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
BestMacroTokenSource = new();
|
BestMacroTokenSource = new();
|
||||||
BestMacroException = null;
|
BestMacroException = null;
|
||||||
BestSavedMacro = null;
|
BestSavedMacro = null;
|
||||||
|
HasSavedMacro = false;
|
||||||
BestSuggestedMacro = null;
|
BestSuggestedMacro = null;
|
||||||
|
|
||||||
var token = BestMacroTokenSource.Token;
|
var token = BestMacroTokenSource.Token;
|
||||||
_ = Task.Run(() => CalculateBestMacrosTask(token), token)
|
var task = Task.Run(() => CalculateBestMacrosTask(token), token);
|
||||||
.ContinueWith(t =>
|
_ = task.ContinueWith(t =>
|
||||||
{
|
{
|
||||||
if (token.IsCancellationRequested)
|
if (token == BestMacroTokenSource.Token)
|
||||||
return;
|
BestMacroTokenSource = null;
|
||||||
|
});
|
||||||
|
_ = task.ContinueWith(t =>
|
||||||
|
{
|
||||||
|
if (token.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
t.Exception!.Flatten().Handle(ex => ex is TaskCanceledException or OperationCanceledException);
|
t.Exception!.Flatten().Handle(ex => ex is TaskCanceledException or OperationCanceledException);
|
||||||
}
|
}
|
||||||
catch (AggregateException e)
|
catch (AggregateException e)
|
||||||
{
|
{
|
||||||
BestMacroException = e;
|
BestMacroException = e;
|
||||||
Log.Error(e, "Calculating macros failed");
|
Log.Error(e, "Calculating macros failed");
|
||||||
}
|
}
|
||||||
}, TaskContinuationOptions.OnlyOnFaulted);
|
}, TaskContinuationOptions.OnlyOnFaulted);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CalculateBestMacrosTask(CancellationToken token)
|
private void CalculateBestMacrosTask(CancellationToken token)
|
||||||
@@ -790,26 +841,30 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
|
|
||||||
token.ThrowIfCancellationRequested();
|
token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
var bestSaved = macros
|
HasSavedMacro = macros.Count > 0;
|
||||||
.Select(macro =>
|
if (HasSavedMacro)
|
||||||
{
|
{
|
||||||
var (resp, outState, failedIdx) = simulator.ExecuteMultiple(state, macro.Actions);
|
var bestSaved = macros
|
||||||
outState.ActionCount = macro.Actions.Count;
|
.Select(macro =>
|
||||||
var score = SimulationNode.CalculateScoreForState(outState, simulator.CompletionState, mctsConfig) ?? 0;
|
|
||||||
if (resp != ActionResponse.SimulationComplete)
|
|
||||||
{
|
{
|
||||||
if (failedIdx != -1)
|
var (resp, outState, failedIdx) = simulator.ExecuteMultiple(state, macro.Actions);
|
||||||
score /= 2;
|
outState.ActionCount = macro.Actions.Count;
|
||||||
}
|
var score = SimulationNode.CalculateScoreForState(outState, simulator.CompletionState, mctsConfig) ?? 0;
|
||||||
return (macro, outState, score);
|
if (resp != ActionResponse.SimulationComplete)
|
||||||
})
|
{
|
||||||
.MaxBy(m => m.score);
|
if (failedIdx != -1)
|
||||||
|
score /= 2;
|
||||||
|
}
|
||||||
|
return (macro, outState, score);
|
||||||
|
})
|
||||||
|
.MaxBy(m => m.score);
|
||||||
|
|
||||||
token.ThrowIfCancellationRequested();
|
token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
BestSavedMacro = (bestSaved.macro, bestSaved.outState);
|
BestSavedMacro = (bestSaved.macro, bestSaved.outState);
|
||||||
|
|
||||||
token.ThrowIfCancellationRequested();
|
token.ThrowIfCancellationRequested();
|
||||||
|
}
|
||||||
|
|
||||||
var solver = new Solver.Solver(config, state) { Token = token };
|
var solver = new Solver.Solver(config, state) { Token = token };
|
||||||
solver.OnLog += Log.Debug;
|
solver.OnLog += Log.Debug;
|
||||||
@@ -819,10 +874,13 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
token.ThrowIfCancellationRequested();
|
token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
BestSuggestedMacro = solution;
|
BestSuggestedMacro = solution;
|
||||||
|
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
Service.WindowSystem.RemoveWindow(this);
|
||||||
AxisFont?.Dispose();
|
AxisFont?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+479
-289
@@ -1,38 +1,34 @@
|
|||||||
using Craftimizer.Solver;
|
using Craftimizer.Solver;
|
||||||
using Dalamud.Interface;
|
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using System;
|
using System;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Craftimizer.Plugin.Windows;
|
namespace Craftimizer.Plugin.Windows;
|
||||||
|
|
||||||
public class Settings : Window
|
public sealed class Settings : Window, IDisposable
|
||||||
{
|
{
|
||||||
|
private const ImGuiWindowFlags WindowFlags = ImGuiWindowFlags.None;
|
||||||
|
|
||||||
private static Configuration Config => Service.Configuration;
|
private static Configuration Config => Service.Configuration;
|
||||||
|
|
||||||
private const int OptionWidth = 200;
|
private const int OptionWidth = 200;
|
||||||
private static Vector2 OptionButtonSize => new(OptionWidth, ImGui.GetFrameHeight());
|
private static Vector2 OptionButtonSize => new(OptionWidth, ImGui.GetFrameHeight());
|
||||||
|
|
||||||
public const string TabGeneral = "General";
|
|
||||||
public const string TabSimulator = "Simulator";
|
|
||||||
public const string TabSynthHelper = "Synthesis Helper";
|
|
||||||
public const string TabAbout = "About";
|
|
||||||
|
|
||||||
private string? SelectedTab { get; set; }
|
private string? SelectedTab { get; set; }
|
||||||
|
|
||||||
public Settings() : base("Craftimizer Settings")
|
public Settings() : base("Craftimizer Settings", WindowFlags)
|
||||||
{
|
{
|
||||||
Service.WindowSystem.AddWindow(this);
|
Service.WindowSystem.AddWindow(this);
|
||||||
|
|
||||||
SizeConstraints = new WindowSizeConstraints()
|
SizeConstraints = new WindowSizeConstraints()
|
||||||
{
|
{
|
||||||
MinimumSize = new Vector2(400, 400),
|
MinimumSize = new(450, 400),
|
||||||
MaximumSize = new Vector2(float.MaxValue, float.MaxValue)
|
MaximumSize = new(float.PositiveInfinity)
|
||||||
};
|
};
|
||||||
Size = SizeConstraints.Value.MinimumSize;
|
|
||||||
SizeCondition = ImGuiCond.Appearing;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SelectTab(string label)
|
public void SelectTab(string label)
|
||||||
@@ -40,16 +36,16 @@ public class Settings : Window
|
|||||||
SelectedTab = label;
|
SelectedTab = label;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool BeginTabItem(string label)
|
private ImRaii.IEndObject TabItem(string label)
|
||||||
{
|
{
|
||||||
var isSelected = string.Equals(SelectedTab, label, StringComparison.Ordinal);
|
var isSelected = string.Equals(SelectedTab, label, StringComparison.Ordinal);
|
||||||
if (isSelected)
|
if (isSelected)
|
||||||
{
|
{
|
||||||
SelectedTab = null;
|
SelectedTab = null;
|
||||||
var open = true;
|
var open = true;
|
||||||
return ImGui.BeginTabItem(label, ref open, ImGuiTabItemFlags.SetSelected);
|
return ImRaii.TabItem(label, ref open, ImGuiTabItemFlags.SetSelected);
|
||||||
}
|
}
|
||||||
return ImGui.BeginTabItem(label);
|
return ImRaii.TabItem(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DrawOption(string label, string tooltip, bool val, Action<bool> setter, ref bool isDirty)
|
private static void DrawOption(string label, string tooltip, bool val, Action<bool> setter, ref bool isDirty)
|
||||||
@@ -63,25 +59,44 @@ public class Settings : Window
|
|||||||
ImGui.SetTooltip(tooltip);
|
ImGui.SetTooltip(tooltip);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DrawOption(string label, string tooltip, int val, Action<int> setter, ref bool isDirty)
|
private static void DrawOption<T>(string label, string tooltip, T value, T min, T max, Action<T> setter, ref bool isDirty) where T : struct, INumber<T>
|
||||||
{
|
{
|
||||||
ImGui.SetNextItemWidth(OptionWidth);
|
ImGui.SetNextItemWidth(OptionWidth);
|
||||||
if (ImGui.InputInt(label, ref val))
|
var text = value.ToString();
|
||||||
|
if (ImGui.InputText(label, ref text, 8, ImGuiInputTextFlags.AutoSelectAll | ImGuiInputTextFlags.CharsDecimal))
|
||||||
{
|
{
|
||||||
setter(val);
|
if (T.TryParse(text, null, out var newValue))
|
||||||
isDirty = true;
|
{
|
||||||
|
newValue = T.Clamp(newValue, min, max);
|
||||||
|
if (value != newValue)
|
||||||
|
{
|
||||||
|
setter(newValue);
|
||||||
|
isDirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
ImGui.SetTooltip(tooltip);
|
ImGui.SetTooltip(tooltip);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DrawOption(string label, string tooltip, float val, Action<float> setter, ref bool isDirty)
|
private static void DrawOption<T>(string label, string tooltip, Func<T, string> getName, Func<T, string> getTooltip, T value, Action<T> setter, ref bool isDirty) where T : struct, Enum
|
||||||
{
|
{
|
||||||
ImGui.SetNextItemWidth(OptionWidth);
|
ImGui.SetNextItemWidth(OptionWidth);
|
||||||
if (ImGui.InputFloat(label, ref val))
|
using (var combo = ImRaii.Combo(label, getName(value)))
|
||||||
{
|
{
|
||||||
setter(val);
|
if (combo)
|
||||||
isDirty = true;
|
{
|
||||||
|
foreach (var type in Enum.GetValues<T>())
|
||||||
|
{
|
||||||
|
if (ImGui.Selectable(getName(type), value.Equals(type)))
|
||||||
|
{
|
||||||
|
setter(type);
|
||||||
|
isDirty = true;
|
||||||
|
}
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip(getTooltip(type));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
ImGui.SetTooltip(tooltip);
|
ImGui.SetTooltip(tooltip);
|
||||||
@@ -112,14 +127,33 @@ public class Settings : Window
|
|||||||
_ => "Unknown"
|
_ => "Unknown"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static string GetCopyTypeName(MacroCopyConfiguration.CopyType type) =>
|
||||||
|
type switch
|
||||||
|
{
|
||||||
|
MacroCopyConfiguration.CopyType.OpenWindow => "Open a Window",
|
||||||
|
MacroCopyConfiguration.CopyType.CopyToMacro => "Copy to Macros",
|
||||||
|
MacroCopyConfiguration.CopyType.CopyToClipboard => "Copy to Clipboard",
|
||||||
|
_ => "Unknown",
|
||||||
|
};
|
||||||
|
|
||||||
|
private static string GetCopyTypeTooltip(MacroCopyConfiguration.CopyType type) =>
|
||||||
|
type switch
|
||||||
|
{
|
||||||
|
MacroCopyConfiguration.CopyType.OpenWindow => "Open a dedicated window with all macros being copied.\n" +
|
||||||
|
"Copy, view, and choose at your own leisure.",
|
||||||
|
MacroCopyConfiguration.CopyType.CopyToMacro => "Copy directly to the game's macro system.",
|
||||||
|
MacroCopyConfiguration.CopyType.CopyToClipboard => "Copy to your clipboard. Macros are separated by a blank line.",
|
||||||
|
_ => "Unknown"
|
||||||
|
};
|
||||||
|
|
||||||
public override void Draw()
|
public override void Draw()
|
||||||
{
|
{
|
||||||
if (ImGui.BeginTabBar("settingsTabBar"))
|
if (ImGui.BeginTabBar("settingsTabBar"))
|
||||||
{
|
{
|
||||||
DrawTabGeneral();
|
DrawTabGeneral();
|
||||||
DrawTabSimulator();
|
DrawTabSimulator();
|
||||||
if (Config.EnableSynthHelper)
|
//if (Config.EnableSynthHelper)
|
||||||
DrawTabSynthHelper();
|
// DrawTabSynthHelper();
|
||||||
DrawTabAbout();
|
DrawTabAbout();
|
||||||
|
|
||||||
ImGui.EndTabBar();
|
ImGui.EndTabBar();
|
||||||
@@ -128,31 +162,30 @@ public class Settings : Window
|
|||||||
|
|
||||||
private void DrawTabGeneral()
|
private void DrawTabGeneral()
|
||||||
{
|
{
|
||||||
if (!BeginTabItem("General"))
|
using var tab = TabItem("General");
|
||||||
|
if (!tab)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(5);
|
ImGuiHelpers.ScaledDummy(5);
|
||||||
|
|
||||||
var isDirty = false;
|
var isDirty = false;
|
||||||
|
|
||||||
DrawOption(
|
|
||||||
"Override Uncraftability Warning",
|
|
||||||
"Allow simulation for crafts that otherwise wouldn't\n" +
|
|
||||||
"be able to be crafted with your current gear",
|
|
||||||
Config.OverrideUncraftability,
|
|
||||||
v => Config.OverrideUncraftability = v,
|
|
||||||
ref isDirty
|
|
||||||
);
|
|
||||||
|
|
||||||
DrawOption(
|
using (var g = ImRaii.Group())
|
||||||
"Enable Synthesis Helper",
|
{
|
||||||
"Adds a helper next to your synthesis window to help solve for the best craft.\n" +
|
using var d = ImRaii.Disabled();
|
||||||
"Extremely useful for expert recipes, where the condition can greatly affect\n" +
|
DrawOption(
|
||||||
"which actions you take.",
|
"Enable Synthesis Helper",
|
||||||
Config.EnableSynthHelper,
|
"Adds a helper next to your synthesis window to help solve for the best craft.\n" +
|
||||||
v => Config.EnableSynthHelper = v,
|
"Extremely useful for expert recipes, where the condition can greatly affect\n" +
|
||||||
ref isDirty
|
"which actions you take.",
|
||||||
);
|
Config.EnableSynthHelper,
|
||||||
|
v => Config.EnableSynthHelper = v,
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip("Disabled temporarily for testing");
|
||||||
|
|
||||||
DrawOption(
|
DrawOption(
|
||||||
"Show Only One Macro Stat",
|
"Show Only One Macro Stat",
|
||||||
@@ -164,10 +197,151 @@ public class Settings : Window
|
|||||||
ref isDirty
|
ref isDirty
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ImGuiHelpers.ScaledDummy(5);
|
||||||
|
|
||||||
|
using (var panel = ImGuiUtils.GroupPanel("Copying Settings", -1, out _))
|
||||||
|
{
|
||||||
|
DrawOption(
|
||||||
|
"Macro Copy Method",
|
||||||
|
"The method to copy a macro with.",
|
||||||
|
GetCopyTypeName,
|
||||||
|
GetCopyTypeTooltip,
|
||||||
|
Config.MacroCopy.Type,
|
||||||
|
v => Config.MacroCopy.Type = v,
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Config.MacroCopy.Type == MacroCopyConfiguration.CopyType.CopyToMacro)
|
||||||
|
{
|
||||||
|
DrawOption(
|
||||||
|
"Copy Downwards",
|
||||||
|
"Copy subsequent macros downward (#1 -> #11) instead of to the right.",
|
||||||
|
Config.MacroCopy.CopyDown,
|
||||||
|
v => Config.MacroCopy.CopyDown = v,
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
|
||||||
|
DrawOption(
|
||||||
|
"Copy to Shared Macros",
|
||||||
|
"Copy to the shared macros tab. Leaving this unchecked copies to the\n" +
|
||||||
|
"individual tab.",
|
||||||
|
Config.MacroCopy.SharedMacro,
|
||||||
|
v => Config.MacroCopy.SharedMacro = v,
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
|
||||||
|
DrawOption(
|
||||||
|
"Macro Number",
|
||||||
|
"The # of the macro to being copying to. Subsequent macros will be\n" +
|
||||||
|
"copied relative to this macro.",
|
||||||
|
Config.MacroCopy.StartMacroIdx,
|
||||||
|
0, 99,
|
||||||
|
v => Config.MacroCopy.StartMacroIdx = v,
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
|
||||||
|
DrawOption(
|
||||||
|
"Max Macro Copy Count",
|
||||||
|
"The maximum number of macros to be copied. Any more and a window is\n" +
|
||||||
|
"displayed with the rest of them.",
|
||||||
|
Config.MacroCopy.MaxMacroCount,
|
||||||
|
1, 99,
|
||||||
|
v => Config.MacroCopy.MaxMacroCount = v,
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawOption(
|
||||||
|
"Use MacroChain's /nextmacro",
|
||||||
|
"Replaces the last step with /nextmacro to run the next macro\n" +
|
||||||
|
"automatically. Overrides Add End Notification except for the\n" +
|
||||||
|
"last macro.",
|
||||||
|
Config.MacroCopy.UseNextMacro,
|
||||||
|
v => Config.MacroCopy.UseNextMacro = v,
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
|
||||||
|
DrawOption(
|
||||||
|
"Add Macro Lock",
|
||||||
|
"Adds /mlock to the beginning of every macro. Prevents other\n" +
|
||||||
|
"macros from being run.",
|
||||||
|
Config.MacroCopy.UseMacroLock,
|
||||||
|
v => Config.MacroCopy.UseMacroLock = v,
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
|
||||||
|
DrawOption(
|
||||||
|
"Add Notification",
|
||||||
|
"Replaces the last step of every macro with a /echo notification.",
|
||||||
|
Config.MacroCopy.AddNotification,
|
||||||
|
v => Config.MacroCopy.AddNotification = v,
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Config.MacroCopy.AddNotification)
|
||||||
|
{
|
||||||
|
DrawOption(
|
||||||
|
"Add Notification Sound",
|
||||||
|
"Adds a sound to the end of every macro.",
|
||||||
|
Config.MacroCopy.AddNotificationSound,
|
||||||
|
v => Config.MacroCopy.AddNotificationSound = v,
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Config.MacroCopy.AddNotificationSound)
|
||||||
|
{
|
||||||
|
DrawOption(
|
||||||
|
"Intermediate Notification Sound",
|
||||||
|
"Ending notification sound for an intermediary macro.\n" +
|
||||||
|
"Uses <se.#>",
|
||||||
|
Config.MacroCopy.IntermediateNotificationSound,
|
||||||
|
1, 16,
|
||||||
|
v =>
|
||||||
|
{
|
||||||
|
Config.MacroCopy.IntermediateNotificationSound = v;
|
||||||
|
UIModule.PlayChatSoundEffect((uint)v);
|
||||||
|
},
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
|
||||||
|
DrawOption(
|
||||||
|
"Final Notification Sound",
|
||||||
|
"Ending notification sound for the final macro.\n" +
|
||||||
|
"Uses <se.#>",
|
||||||
|
Config.MacroCopy.EndNotificationSound,
|
||||||
|
1, 16,
|
||||||
|
v =>
|
||||||
|
{
|
||||||
|
Config.MacroCopy.EndNotificationSound = v;
|
||||||
|
UIModule.PlayChatSoundEffect((uint)v);
|
||||||
|
},
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Config.MacroCopy.Type != MacroCopyConfiguration.CopyType.CopyToMacro)
|
||||||
|
{
|
||||||
|
DrawOption(
|
||||||
|
"Remove Wait Times",
|
||||||
|
"Remove <wait.#> at the end of every action. Useful for SomethingNeedDoing.",
|
||||||
|
Config.MacroCopy.RemoveWaitTimes,
|
||||||
|
v => Config.MacroCopy.RemoveWaitTimes = v,
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
|
||||||
|
DrawOption(
|
||||||
|
"Combine Macro",
|
||||||
|
"Doesn't split the macro into smaller macros. Useful for SomethingNeedDoing.",
|
||||||
|
Config.MacroCopy.CombineMacro,
|
||||||
|
v => Config.MacroCopy.CombineMacro = v,
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isDirty)
|
if (isDirty)
|
||||||
Config.Save();
|
Config.Save();
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DrawSolverConfig(ref SolverConfig configRef, SolverConfig defaultConfig, out bool isDirty)
|
private static void DrawSolverConfig(ref SolverConfig configRef, SolverConfig defaultConfig, out bool isDirty)
|
||||||
@@ -176,228 +350,240 @@ public class Settings : Window
|
|||||||
|
|
||||||
var config = configRef;
|
var config = configRef;
|
||||||
|
|
||||||
ImGuiUtils.BeginGroupPanel("General");
|
using (var panel = ImGuiUtils.GroupPanel("General", -1, out _))
|
||||||
|
|
||||||
if (ImGui.Button("Reset to Default", OptionButtonSize))
|
|
||||||
{
|
{
|
||||||
config = defaultConfig;
|
if (ImGui.Button("Reset to Default", OptionButtonSize))
|
||||||
isDirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SetNextItemWidth(OptionWidth);
|
|
||||||
if (ImGui.BeginCombo("Algorithm", GetAlgorithmName(config.Algorithm)))
|
|
||||||
{
|
|
||||||
foreach (var alg in Enum.GetValues<SolverAlgorithm>())
|
|
||||||
{
|
{
|
||||||
if (ImGui.Selectable(GetAlgorithmName(alg), config.Algorithm == alg))
|
config = defaultConfig;
|
||||||
{
|
isDirty = true;
|
||||||
config = config with { Algorithm = alg };
|
|
||||||
isDirty = true;
|
|
||||||
}
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
ImGui.SetTooltip(GetAlgorithmTooltip(alg));
|
|
||||||
}
|
}
|
||||||
ImGui.EndCombo();
|
|
||||||
}
|
DrawOption(
|
||||||
if (ImGui.IsItemHovered())
|
"Algorithm",
|
||||||
ImGui.SetTooltip(
|
|
||||||
"The algorithm to use when solving for a macro. Different\n" +
|
"The algorithm to use when solving for a macro. Different\n" +
|
||||||
"algorithms provide different pros and cons for using them.\n" +
|
"algorithms provide different pros and cons for using them.\n" +
|
||||||
"By far, the Stepwise Furcated algorithm provides the best\n" +
|
"By far, the Stepwise Furcated algorithm provides the best\n" +
|
||||||
"results, especially for very difficult crafts."
|
"results, especially for very difficult crafts.",
|
||||||
|
GetAlgorithmName,
|
||||||
|
GetAlgorithmTooltip,
|
||||||
|
config.Algorithm,
|
||||||
|
v => config = config with { Algorithm = v },
|
||||||
|
ref isDirty
|
||||||
);
|
);
|
||||||
|
|
||||||
DrawOption(
|
DrawOption(
|
||||||
"Iterations",
|
"Iterations",
|
||||||
"The total number of iterations to run per crafting step.\n" +
|
"The total number of iterations to run per crafting step.\n" +
|
||||||
"Higher values require more computational power. Higher values\n" +
|
"Higher values require more computational power. Higher values\n" +
|
||||||
"also may decrease variance, so other values should be tweaked\n" +
|
"also may decrease variance, so other values should be tweaked\n" +
|
||||||
"as necessary to get a more favorable outcome.",
|
"as necessary to get a more favorable outcome.",
|
||||||
config.Iterations,
|
config.Iterations,
|
||||||
v => config = config with { Iterations = v },
|
1000,
|
||||||
ref isDirty
|
500000,
|
||||||
);
|
v => config = config with { Iterations = v },
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
|
||||||
DrawOption(
|
DrawOption(
|
||||||
"Max Step Count",
|
"Max Step Count",
|
||||||
"The maximum number of crafting steps; this is generally the only\n" +
|
"The maximum number of crafting steps; this is generally the only\n" +
|
||||||
"setting you should change, and it should be set to around 5 steps\n" +
|
"setting you should change, and it should be set to around 5 steps\n" +
|
||||||
"more than what you'd expect. If this value is too low, the solver\n" +
|
"more than what you'd expect. If this value is too low, the solver\n" +
|
||||||
"won't learn much per iteration; too high and it will waste time\n" +
|
"won't learn much per iteration; too high and it will waste time\n" +
|
||||||
"on useless extra steps.",
|
"on useless extra steps.",
|
||||||
config.MaxStepCount,
|
config.MaxStepCount,
|
||||||
v => config = config with { MaxStepCount = v },
|
1,
|
||||||
ref isDirty
|
100,
|
||||||
);
|
v => config = config with { MaxStepCount = v },
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
|
||||||
DrawOption(
|
DrawOption(
|
||||||
"Exploration Constant",
|
"Exploration Constant",
|
||||||
"A constant that decides how often the solver will explore new,\n" +
|
"A constant that decides how often the solver will explore new,\n" +
|
||||||
"possibly good paths. If this value is too high,\n" +
|
"possibly good paths. If this value is too high,\n" +
|
||||||
"moves will mostly be decided at random.",
|
"moves will mostly be decided at random.",
|
||||||
config.ExplorationConstant,
|
config.ExplorationConstant,
|
||||||
v => config = config with { ExplorationConstant = v },
|
0,
|
||||||
ref isDirty
|
10,
|
||||||
);
|
v => config = config with { ExplorationConstant = v },
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
|
||||||
DrawOption(
|
DrawOption(
|
||||||
"Score Weighting Constant",
|
"Score Weighting Constant",
|
||||||
"A constant ranging from 0 to 1 that configures how the solver\n" +
|
"A constant ranging from 0 to 1 that configures how the solver\n" +
|
||||||
"scores and picks paths to travel to next. A value of 0 means\n" +
|
"scores and picks paths to travel to next. A value of 0 means\n" +
|
||||||
"actions will be chosen based on their average outcome, whereas\n" +
|
"actions will be chosen based on their average outcome, whereas\n" +
|
||||||
"1 uses their best outcome achieved so far.",
|
"1 uses their best outcome achieved so far.",
|
||||||
config.MaxScoreWeightingConstant,
|
config.MaxScoreWeightingConstant,
|
||||||
v => config = config with { MaxScoreWeightingConstant = v },
|
0,
|
||||||
ref isDirty
|
1,
|
||||||
);
|
v => config = config with { MaxScoreWeightingConstant = v },
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
|
||||||
ImGui.BeginDisabled(config.Algorithm is not (SolverAlgorithm.OneshotForked or SolverAlgorithm.StepwiseForked or SolverAlgorithm.StepwiseFurcated));
|
using (var d = ImRaii.Disabled(config.Algorithm is not (SolverAlgorithm.OneshotForked or SolverAlgorithm.StepwiseForked or SolverAlgorithm.StepwiseFurcated)))
|
||||||
DrawOption(
|
DrawOption(
|
||||||
"Max Core Count",
|
"Max Core Count",
|
||||||
"The number of cores to use when solving. You should use as many\n" +
|
"The number of cores to use when solving. You should use as many\n" +
|
||||||
"as you can. If it's too high, it will have an effect on your gameplay\n" +
|
"as you can. If it's too high, it will have an effect on your gameplay\n" +
|
||||||
$"experience. A good estimate would be 1 or 2 cores less than your\n" +
|
$"experience. A good estimate would be 1 or 2 cores less than your\n" +
|
||||||
$"system (FYI, you have {Environment.ProcessorCount} cores,) but\n" +
|
$"system (FYI, you have {Environment.ProcessorCount} cores), but make sure to accomodate\n" +
|
||||||
$"make sure to accomodate for any other tasks you have in the\n" +
|
$"for any other tasks you have in the background, if you have any.\n" +
|
||||||
$"background, if you have any.\n" +
|
"(Only used in the Forked and Furcated algorithms)",
|
||||||
"(Only used in the Forked and Furcated algorithms)",
|
config.MaxThreadCount,
|
||||||
config.MaxThreadCount,
|
1,
|
||||||
v => config = config with { MaxThreadCount = v },
|
Environment.ProcessorCount,
|
||||||
ref isDirty
|
v => config = config with { MaxThreadCount = v },
|
||||||
);
|
ref isDirty
|
||||||
ImGui.EndDisabled();
|
);
|
||||||
|
|
||||||
ImGui.BeginDisabled(config.Algorithm is not (SolverAlgorithm.OneshotForked or SolverAlgorithm.StepwiseForked or SolverAlgorithm.StepwiseFurcated));
|
using (var d = ImRaii.Disabled(config.Algorithm is not (SolverAlgorithm.OneshotForked or SolverAlgorithm.StepwiseForked or SolverAlgorithm.StepwiseFurcated)))
|
||||||
DrawOption(
|
DrawOption(
|
||||||
"Fork Count",
|
"Fork Count",
|
||||||
"Split the number of iterations across different solvers. In general,\n" +
|
"Split the number of iterations across different solvers. In general,\n" +
|
||||||
"you should increase this value to at least the number of cores in\n" +
|
"you should increase this value to at least the number of cores in\n" +
|
||||||
$"your system (FYI, you have {Environment.ProcessorCount} cores) to attain the most speedup.\n" +
|
$"your system (FYI, you have {Environment.ProcessorCount} cores) to attain the most speedup.\n" +
|
||||||
"The higher the number, the more chance you have of finding a\n" +
|
"The higher the number, the more chance you have of finding a\n" +
|
||||||
"better local maximum; this concept similar but not equivalent\n" +
|
"better local maximum; this concept similar but not equivalent\n" +
|
||||||
"to the exploration constant.\n" +
|
"to the exploration constant.\n" +
|
||||||
"(Only used in the Forked and Furcated algorithms)",
|
"(Only used in the Forked and Furcated algorithms)",
|
||||||
config.ForkCount,
|
config.ForkCount,
|
||||||
v => config = config with { ForkCount = v },
|
1,
|
||||||
ref isDirty
|
500,
|
||||||
);
|
v => config = config with { ForkCount = v },
|
||||||
ImGui.EndDisabled();
|
ref isDirty
|
||||||
|
);
|
||||||
|
|
||||||
ImGui.BeginDisabled(config.Algorithm is not SolverAlgorithm.StepwiseFurcated);
|
using (var d = ImRaii.Disabled(config.Algorithm is not SolverAlgorithm.StepwiseFurcated))
|
||||||
DrawOption(
|
DrawOption(
|
||||||
"Furcated Action Count",
|
"Furcated Action Count",
|
||||||
"On every craft step, pick this many top solutions and use them as\n" +
|
"On every craft step, pick this many top solutions and use them as\n" +
|
||||||
"the input for the next craft step. For best results, use Fork Count / 2\n" +
|
"the input for the next craft step. For best results, use Fork Count / 2\n" +
|
||||||
"and add about 1 or 2 more if needed.\n" +
|
"and add about 1 or 2 more if needed.\n" +
|
||||||
"(Only used in the Stepwise Furcated algorithm)",
|
"(Only used in the Stepwise Furcated algorithm)",
|
||||||
config.FurcatedActionCount,
|
config.FurcatedActionCount,
|
||||||
v => config = config with { FurcatedActionCount = v },
|
1,
|
||||||
ref isDirty
|
500,
|
||||||
);
|
v => config = config with { FurcatedActionCount = v },
|
||||||
ImGui.EndDisabled();
|
ref isDirty
|
||||||
|
);
|
||||||
ImGuiUtils.EndGroupPanel();
|
|
||||||
|
|
||||||
ImGuiUtils.BeginGroupPanel("Advanced");
|
|
||||||
|
|
||||||
DrawOption(
|
|
||||||
"Score Storage Threshold",
|
|
||||||
"If a craft achieves this certain arbitrary score, the solver will\n" +
|
|
||||||
"throw away all other possible combinations in favor of that one.\n" +
|
|
||||||
"Only change this value if you absolutely know what you're doing.",
|
|
||||||
config.ScoreStorageThreshold,
|
|
||||||
v => config = config with { ScoreStorageThreshold = v },
|
|
||||||
ref isDirty
|
|
||||||
);
|
|
||||||
|
|
||||||
DrawOption(
|
|
||||||
"Max Rollout Step Count",
|
|
||||||
"The maximum number of crafting steps every iteration can consider.\n" +
|
|
||||||
"Decreasing this value can have unintended side effects. Only change\n" +
|
|
||||||
"this value if you absolutely know what you're doing.",
|
|
||||||
config.MaxRolloutStepCount,
|
|
||||||
v => config = config with { MaxRolloutStepCount = v },
|
|
||||||
ref isDirty
|
|
||||||
);
|
|
||||||
|
|
||||||
DrawOption(
|
|
||||||
"Strict Actions",
|
|
||||||
"When finding the next possible actions to execute, use a heuristic\n" +
|
|
||||||
"to restrict which actions to attempt taking. This results in a much\n" +
|
|
||||||
"better macro at the cost of not finding an extremely creative one.",
|
|
||||||
config.StrictActions,
|
|
||||||
v => config = config with { StrictActions = v },
|
|
||||||
ref isDirty
|
|
||||||
);
|
|
||||||
|
|
||||||
ImGuiUtils.EndGroupPanel();
|
|
||||||
|
|
||||||
ImGuiUtils.BeginGroupPanel("Score Weights (Advanced)");
|
|
||||||
ImGui.TextWrapped("All values should add up to 1. Otherwise, the Score Storage Threshold should be changed.");
|
|
||||||
ImGuiHelpers.ScaledDummy(10);
|
|
||||||
|
|
||||||
DrawOption(
|
|
||||||
"Progress",
|
|
||||||
"Amount of weight to give to the craft's progress.",
|
|
||||||
config.ScoreProgress,
|
|
||||||
v => config = config with { ScoreProgress = v },
|
|
||||||
ref isDirty
|
|
||||||
);
|
|
||||||
|
|
||||||
DrawOption(
|
|
||||||
"Quality",
|
|
||||||
"Amount of weight to give to the craft's quality.",
|
|
||||||
config.ScoreQuality,
|
|
||||||
v => config = config with { ScoreQuality = v },
|
|
||||||
ref isDirty
|
|
||||||
);
|
|
||||||
|
|
||||||
DrawOption(
|
|
||||||
"Durability",
|
|
||||||
"Amount of weight to give to the craft's remaining durability.",
|
|
||||||
config.ScoreDurability,
|
|
||||||
v => config = config with { ScoreDurability = v },
|
|
||||||
ref isDirty
|
|
||||||
);
|
|
||||||
|
|
||||||
DrawOption(
|
|
||||||
"CP",
|
|
||||||
"Amount of weight to give to the craft's remaining CP.",
|
|
||||||
config.ScoreCP,
|
|
||||||
v => config = config with { ScoreCP = v },
|
|
||||||
ref isDirty
|
|
||||||
);
|
|
||||||
|
|
||||||
DrawOption(
|
|
||||||
"Steps",
|
|
||||||
"Amount of weight to give to the craft's number of steps. The lower\n" +
|
|
||||||
"the step count, the higher the score.",
|
|
||||||
config.ScoreSteps,
|
|
||||||
v => config = config with { ScoreSteps = v },
|
|
||||||
ref isDirty
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ImGui.Button("Normalize Weights", OptionButtonSize))
|
|
||||||
{
|
|
||||||
var total = config.ScoreProgress +
|
|
||||||
config.ScoreQuality +
|
|
||||||
config.ScoreDurability +
|
|
||||||
config.ScoreCP +
|
|
||||||
config.ScoreSteps;
|
|
||||||
config = config with
|
|
||||||
{
|
|
||||||
ScoreProgress = config.ScoreProgress / total,
|
|
||||||
ScoreQuality = config.ScoreQuality / total,
|
|
||||||
ScoreDurability = config.ScoreDurability / total,
|
|
||||||
ScoreCP = config.ScoreCP / total,
|
|
||||||
ScoreSteps = config.ScoreSteps / total
|
|
||||||
};
|
|
||||||
isDirty = true;
|
|
||||||
}
|
}
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
ImGui.SetTooltip("Normalize all weights to sum up to 1");
|
|
||||||
|
|
||||||
ImGuiUtils.EndGroupPanel();
|
using (var panel = ImGuiUtils.GroupPanel("Advanced", -1, out _))
|
||||||
|
{
|
||||||
|
DrawOption(
|
||||||
|
"Score Storage Threshold",
|
||||||
|
"If a craft achieves this certain arbitrary score, the solver will\n" +
|
||||||
|
"throw away all other possible combinations in favor of that one.\n" +
|
||||||
|
"Only change this value if you absolutely know what you're doing.",
|
||||||
|
config.ScoreStorageThreshold,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
v => config = config with { ScoreStorageThreshold = v },
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
|
||||||
|
DrawOption(
|
||||||
|
"Max Rollout Step Count",
|
||||||
|
"The maximum number of crafting steps every iteration can consider.\n" +
|
||||||
|
"Decreasing this value can have unintended side effects. Only change\n" +
|
||||||
|
"this value if you absolutely know what you're doing.",
|
||||||
|
config.MaxRolloutStepCount,
|
||||||
|
1,
|
||||||
|
50,
|
||||||
|
v => config = config with { MaxRolloutStepCount = v },
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
|
||||||
|
DrawOption(
|
||||||
|
"Strict Actions",
|
||||||
|
"When finding the next possible actions to execute, use a heuristic\n" +
|
||||||
|
"to restrict which actions to attempt taking. This results in a much\n" +
|
||||||
|
"better macro at the cost of not finding an extremely creative one.",
|
||||||
|
config.StrictActions,
|
||||||
|
v => config = config with { StrictActions = v },
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var panel = ImGuiUtils.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);
|
||||||
|
|
||||||
|
DrawOption(
|
||||||
|
"Progress",
|
||||||
|
"Amount of weight to give to the craft's progress.",
|
||||||
|
config.ScoreProgress,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
v => config = config with { ScoreProgress = v },
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
|
||||||
|
DrawOption(
|
||||||
|
"Quality",
|
||||||
|
"Amount of weight to give to the craft's quality.",
|
||||||
|
config.ScoreQuality,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
v => config = config with { ScoreQuality = v },
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
|
||||||
|
DrawOption(
|
||||||
|
"Durability",
|
||||||
|
"Amount of weight to give to the craft's remaining durability.",
|
||||||
|
config.ScoreDurability,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
v => config = config with { ScoreDurability = v },
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
|
||||||
|
DrawOption(
|
||||||
|
"CP",
|
||||||
|
"Amount of weight to give to the craft's remaining CP.",
|
||||||
|
config.ScoreCP,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
v => config = config with { ScoreCP = v },
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
|
||||||
|
DrawOption(
|
||||||
|
"Steps",
|
||||||
|
"Amount of weight to give to the craft's number of steps. The lower\n" +
|
||||||
|
"the step count, the higher the score.",
|
||||||
|
config.ScoreSteps,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
v => config = config with { ScoreSteps = v },
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
|
||||||
|
if (ImGui.Button("Normalize Weights", OptionButtonSize))
|
||||||
|
{
|
||||||
|
var total = config.ScoreProgress +
|
||||||
|
config.ScoreQuality +
|
||||||
|
config.ScoreDurability +
|
||||||
|
config.ScoreCP +
|
||||||
|
config.ScoreSteps;
|
||||||
|
config = config with
|
||||||
|
{
|
||||||
|
ScoreProgress = config.ScoreProgress / total,
|
||||||
|
ScoreQuality = config.ScoreQuality / total,
|
||||||
|
ScoreDurability = config.ScoreDurability / total,
|
||||||
|
ScoreCP = config.ScoreCP / total,
|
||||||
|
ScoreSteps = config.ScoreSteps / total
|
||||||
|
};
|
||||||
|
isDirty = true;
|
||||||
|
}
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip("Normalize all weights to sum up to 1");
|
||||||
|
}
|
||||||
|
|
||||||
if (isDirty)
|
if (isDirty)
|
||||||
configRef = config;
|
configRef = config;
|
||||||
@@ -405,30 +591,28 @@ public class Settings : Window
|
|||||||
|
|
||||||
private void DrawTabSimulator()
|
private void DrawTabSimulator()
|
||||||
{
|
{
|
||||||
if (!BeginTabItem("Simulator"))
|
using var tab = TabItem("Simulator");
|
||||||
|
if (!tab)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(5);
|
ImGuiHelpers.ScaledDummy(5);
|
||||||
|
|
||||||
var isDirty = false;
|
var isDirty = false;
|
||||||
|
|
||||||
DrawOption(
|
using (var g = ImRaii.Group())
|
||||||
"Show Only Learned Actions",
|
{
|
||||||
"Don't show crafting actions that haven't been\n" +
|
using var d = ImRaii.Disabled();
|
||||||
"learned yet with your current job on the simulator sidebar",
|
DrawOption(
|
||||||
Config.HideUnlearnedActions,
|
"Condition Randomness",
|
||||||
v => Config.HideUnlearnedActions = v,
|
"Allows the simulator condition to fluctuate randomly like a real craft.\n" +
|
||||||
ref isDirty
|
"Turns off when generating a macro.",
|
||||||
);
|
Config.ConditionRandomness,
|
||||||
|
v => Config.ConditionRandomness = v,
|
||||||
DrawOption(
|
ref isDirty
|
||||||
"Condition Randomness",
|
);
|
||||||
"Allows the simulator condition to fluctuate randomly like a real craft.\n" +
|
}
|
||||||
"Turns off when generating a macro.",
|
if (ImGui.IsItemHovered())
|
||||||
Config.ConditionRandomness,
|
ImGui.SetTooltip("Disabled temporarily for testing");
|
||||||
v => Config.ConditionRandomness = v,
|
|
||||||
ref isDirty
|
|
||||||
);
|
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(5);
|
ImGuiHelpers.ScaledDummy(5);
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
@@ -444,13 +628,12 @@ public class Settings : Window
|
|||||||
|
|
||||||
if (isDirty)
|
if (isDirty)
|
||||||
Config.Save();
|
Config.Save();
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawTabSynthHelper()
|
private void DrawTabSynthHelper()
|
||||||
{
|
{
|
||||||
if (!BeginTabItem("Synthesis Helper"))
|
using var tab = TabItem("Synthesis Helper");
|
||||||
|
if (!tab)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(5);
|
ImGuiHelpers.ScaledDummy(5);
|
||||||
@@ -461,6 +644,8 @@ public class Settings : Window
|
|||||||
"Step Count",
|
"Step Count",
|
||||||
"The number of future actions to solve for during an in-game craft.",
|
"The number of future actions to solve for during an in-game craft.",
|
||||||
Config.SynthHelperStepCount,
|
Config.SynthHelperStepCount,
|
||||||
|
1,
|
||||||
|
100,
|
||||||
v => Config.SynthHelperStepCount = v,
|
v => Config.SynthHelperStepCount = v,
|
||||||
ref isDirty
|
ref isDirty
|
||||||
);
|
);
|
||||||
@@ -479,13 +664,12 @@ public class Settings : Window
|
|||||||
|
|
||||||
if (isDirty)
|
if (isDirty)
|
||||||
Config.Save();
|
Config.Save();
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawTabAbout()
|
private void DrawTabAbout()
|
||||||
{
|
{
|
||||||
if (!BeginTabItem("About"))
|
using var tab = TabItem("About");
|
||||||
|
if (!tab)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(5);
|
ImGuiHelpers.ScaledDummy(5);
|
||||||
@@ -493,28 +677,34 @@ public class Settings : Window
|
|||||||
var plugin = Service.Plugin;
|
var plugin = Service.Plugin;
|
||||||
var icon = plugin.Icon;
|
var icon = plugin.Icon;
|
||||||
|
|
||||||
ImGui.BeginTable("settingsAboutTable", 2);
|
using (var table = ImRaii.Table("settingsAboutTable", 2))
|
||||||
ImGui.TableSetupColumn("", ImGuiTableColumnFlags.WidthFixed, icon.Width);
|
{
|
||||||
|
if (table)
|
||||||
|
{
|
||||||
|
ImGui.TableSetupColumn("", ImGuiTableColumnFlags.WidthFixed, icon.Width);
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.Image(icon.ImGuiHandle, new(icon.Width, icon.Height));
|
ImGui.Image(icon.ImGuiHandle, new(icon.Width, icon.Height));
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.Text($"{plugin.Name} v{plugin.Version} {plugin.BuildConfiguration}");
|
ImGui.Text($"Craftimizer v{plugin.Version} {plugin.BuildConfiguration}");
|
||||||
ImGui.Text($"By {plugin.Author} (");
|
ImGui.Text($"By {plugin.Author} (");
|
||||||
ImGui.SameLine(0, 0);
|
ImGui.SameLine(0, 0);
|
||||||
ImGuiUtils.Hyperlink("WorkingRobot", "https://github.com/WorkingRobot");
|
ImGuiUtils.Hyperlink("WorkingRobot", "https://github.com/WorkingRobot");
|
||||||
ImGui.SameLine(0, 0);
|
ImGui.SameLine(0, 0);
|
||||||
ImGui.Text(")");
|
ImGui.Text(")");
|
||||||
|
}
|
||||||
ImGui.EndTable();
|
}
|
||||||
|
|
||||||
ImGui.Text("Credit to altosock's ");
|
ImGui.Text("Credit to altosock's ");
|
||||||
ImGui.SameLine(0, 0);
|
ImGui.SameLine(0, 0);
|
||||||
ImGuiUtils.Hyperlink("Craftingway", "https://craftingway.app");
|
ImGuiUtils.Hyperlink("Craftingway", "https://craftingway.app");
|
||||||
ImGui.SameLine(0, 0);
|
ImGui.SameLine(0, 0);
|
||||||
ImGui.Text(" for the original solver algorithm");
|
ImGui.Text(" for the original solver algorithm");
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Service.WindowSystem.RemoveWindow(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
using Craftimizer.Simulator.Actions;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
namespace Craftimizer.Simulator;
|
namespace Craftimizer.Simulator;
|
||||||
|
|
||||||
public enum ActionCategory
|
public enum ActionCategory
|
||||||
@@ -13,6 +16,25 @@ public enum ActionCategory
|
|||||||
|
|
||||||
public static class ActionCategoryUtils
|
public static class ActionCategoryUtils
|
||||||
{
|
{
|
||||||
|
private static readonly ReadOnlyDictionary<ActionCategory, ActionType[]> SortedActions;
|
||||||
|
|
||||||
|
static ActionCategoryUtils()
|
||||||
|
{
|
||||||
|
SortedActions = new(
|
||||||
|
Enum.GetValues<ActionType>()
|
||||||
|
.Where(a => a.Category() != ActionCategory.Combo)
|
||||||
|
.GroupBy(a => a.Category())
|
||||||
|
.ToDictionary(g => g.Key, g => g.OrderBy(a => a.Level()).ToArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IReadOnlyList<ActionType> GetActions(this ActionCategory me)
|
||||||
|
{
|
||||||
|
if (SortedActions.TryGetValue(me, out var actions))
|
||||||
|
return actions;
|
||||||
|
|
||||||
|
throw new ArgumentException($"Unknown action category {me}", nameof(me));
|
||||||
|
}
|
||||||
|
|
||||||
public static string GetDisplayName(this ActionCategory category) =>
|
public static string GetDisplayName(this ActionCategory category) =>
|
||||||
category switch
|
category switch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ public abstract class BaseAction
|
|||||||
s.ActionStates.MutateState(this);
|
s.ActionStates.MutateState(this);
|
||||||
s.ActionCount++;
|
s.ActionCount++;
|
||||||
|
|
||||||
s.ActiveEffects.DecrementDuration();
|
if (IncreasesStepCount)
|
||||||
|
s.ActiveEffects.DecrementDuration();
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void UseSuccess(Simulator s)
|
public virtual void UseSuccess(Simulator s)
|
||||||
|
|||||||
@@ -14,10 +14,13 @@ internal abstract class BaseBuffAction : BaseAction
|
|||||||
public override void UseSuccess(Simulator s) =>
|
public override void UseSuccess(Simulator s) =>
|
||||||
s.AddEffect(Effect, Duration);
|
s.AddEffect(Effect, Duration);
|
||||||
|
|
||||||
public sealed override string GetTooltip(Simulator s, bool addUsability)
|
public override string GetTooltip(Simulator s, bool addUsability)
|
||||||
{
|
{
|
||||||
var builder = new StringBuilder(base.GetTooltip(s, addUsability));
|
var builder = new StringBuilder(base.GetTooltip(s, addUsability));
|
||||||
builder.AppendLine($"{Duration} Steps");
|
builder.AppendLine($"{Duration} Steps");
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected string GetBaseTooltip(Simulator s, bool addUsability) =>
|
||||||
|
base.GetTooltip(s, addUsability);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,4 +15,7 @@ internal sealed class CarefulObservation : BaseAction
|
|||||||
public override bool CanUse(Simulator s) => s.Input.Stats.IsSpecialist && s.ActionStates.CarefulObservationCount < 3;
|
public override bool CanUse(Simulator s) => s.Input.Stats.IsSpecialist && s.ActionStates.CarefulObservationCount < 3;
|
||||||
|
|
||||||
public override void UseSuccess(Simulator s) => s.StepCondition();
|
public override void UseSuccess(Simulator s) => s.StepCondition();
|
||||||
|
|
||||||
|
public override string GetTooltip(Simulator s, bool addUsability) =>
|
||||||
|
$"{base.GetTooltip(s, addUsability)}Specialist Only";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,4 +14,7 @@ internal sealed class HeartAndSoul : BaseBuffAction
|
|||||||
public override int CPCost(Simulator s) => 0;
|
public override int CPCost(Simulator s) => 0;
|
||||||
|
|
||||||
public override bool CanUse(Simulator s) => s.Input.Stats.IsSpecialist && !s.ActionStates.UsedHeartAndSoul;
|
public override bool CanUse(Simulator s) => s.Input.Stats.IsSpecialist && !s.ActionStates.UsedHeartAndSoul;
|
||||||
|
|
||||||
|
public override string GetTooltip(Simulator s, bool addUsability) =>
|
||||||
|
$"{GetBaseTooltip(s, addUsability)}Specialist Only";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,14 +14,16 @@ internal sealed class Manipulation : BaseBuffAction
|
|||||||
|
|
||||||
public override void Use(Simulator s)
|
public override void Use(Simulator s)
|
||||||
{
|
{
|
||||||
if (s.HasEffect(EffectType.Manipulation))
|
|
||||||
s.RestoreDurability(5);
|
|
||||||
|
|
||||||
s.ReduceCP(CPCost(s));
|
|
||||||
s.ReduceDurability(DurabilityCost);
|
|
||||||
|
|
||||||
UseSuccess(s);
|
UseSuccess(s);
|
||||||
|
|
||||||
|
s.ReduceCP(CPCost(s));
|
||||||
|
|
||||||
s.IncreaseStepCount();
|
s.IncreaseStepCount();
|
||||||
|
|
||||||
|
s.ActionStates.MutateState(this);
|
||||||
|
s.ActionCount++;
|
||||||
|
|
||||||
|
if (IncreasesStepCount)
|
||||||
|
s.ActiveEffects.DecrementDuration();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,4 +18,7 @@ internal sealed class TrainedEye : BaseAction
|
|||||||
|
|
||||||
public override void UseSuccess(Simulator s) =>
|
public override void UseSuccess(Simulator s) =>
|
||||||
s.IncreaseQualityRaw(s.Input.Recipe.MaxQuality - s.Quality);
|
s.IncreaseQualityRaw(s.Input.Recipe.MaxQuality - s.Quality);
|
||||||
|
|
||||||
|
public override string GetTooltip(Simulator s, bool addUsability) =>
|
||||||
|
$"{base.GetTooltip(s, addUsability)}+{s.Input.Recipe.MaxQuality - s.Quality} Quality";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,11 @@ public record struct Effects
|
|||||||
_ => 0
|
_ => 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[Pure]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static bool IsIndefinite(EffectType effect) =>
|
||||||
|
effect is EffectType.InnerQuiet or EffectType.HeartAndSoul;
|
||||||
|
|
||||||
[Pure]
|
[Pure]
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly byte GetStrength(EffectType effect) =>
|
public readonly byte GetStrength(EffectType effect) =>
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ public record struct SimulationState
|
|||||||
74, 76, 78, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 94, 96, 98, 100
|
74, 76, 78, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 94, 96, 98, 100
|
||||||
};
|
};
|
||||||
public readonly int HQPercent => HQPercentTable[(int)Math.Clamp((float)Quality / Input.Recipe.MaxQuality * 100, 0, 100)];
|
public readonly int HQPercent => HQPercentTable[(int)Math.Clamp((float)Quality / Input.Recipe.MaxQuality * 100, 0, 100)];
|
||||||
|
public readonly int Collectability => Math.Max(Quality / 10, 1);
|
||||||
|
public readonly int MaxCollectability => Math.Max(Input.Recipe.MaxQuality / 10, 1);
|
||||||
|
|
||||||
public readonly bool IsFirstStep => StepCount == 0;
|
public readonly bool IsFirstStep => StepCount == 0;
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ public class Simulator
|
|||||||
return ActionResponse.ActionNotUnlocked;
|
return ActionResponse.ActionNotUnlocked;
|
||||||
if (action == ActionType.Manipulation && !Input.Stats.CanUseManipulation)
|
if (action == ActionType.Manipulation && !Input.Stats.CanUseManipulation)
|
||||||
return ActionResponse.ActionNotUnlocked;
|
return ActionResponse.ActionNotUnlocked;
|
||||||
|
if (action is ActionType.CarefulObservation or ActionType.HeartAndSoul && !Input.Stats.IsSpecialist)
|
||||||
|
return ActionResponse.ActionNotUnlocked;
|
||||||
if (baseAction.CPCost(this) > CP)
|
if (baseAction.CPCost(this) > CP)
|
||||||
return ActionResponse.NotEnoughCP;
|
return ActionResponse.NotEnoughCP;
|
||||||
return ActionResponse.CannotUseAction;
|
return ActionResponse.CannotUseAction;
|
||||||
|
|||||||
+20
-17
@@ -35,9 +35,6 @@ public sealed class Solver : IDisposable
|
|||||||
// Always called when a new step is generated.
|
// Always called when a new step is generated.
|
||||||
public event NewActionDelegate? OnNewAction;
|
public event NewActionDelegate? OnNewAction;
|
||||||
|
|
||||||
// Always called when the solver is fully complete.
|
|
||||||
public event SolutionDelegate? OnSolution;
|
|
||||||
|
|
||||||
public Solver(SolverConfig config, SimulationState state)
|
public Solver(SolverConfig config, SimulationState state)
|
||||||
{
|
{
|
||||||
Config = config;
|
Config = config;
|
||||||
@@ -107,6 +104,12 @@ public sealed class Solver : IDisposable
|
|||||||
CompletionTask?.Dispose();
|
CompletionTask?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void InvokeNewAction(ActionType action)
|
||||||
|
{
|
||||||
|
foreach (var sanitizedAction in SolverSolution.SanitizeCombo(action))
|
||||||
|
OnNewAction?.Invoke(sanitizedAction);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<SolverSolution> SearchStepwiseFurcated()
|
private async Task<SolverSolution> SearchStepwiseFurcated()
|
||||||
{
|
{
|
||||||
var definiteActionCount = 0;
|
var definiteActionCount = 0;
|
||||||
@@ -115,7 +118,7 @@ public sealed class Solver : IDisposable
|
|||||||
var state = State;
|
var state = State;
|
||||||
var sim = new Simulator(state, Config.MaxStepCount);
|
var sim = new Simulator(state, Config.MaxStepCount);
|
||||||
|
|
||||||
var activeStates = new List<SolverSolution>() { new(new(), state) };
|
var activeStates = new List<SolverSolution>() { new(Array.Empty<ActionType>(), state) };
|
||||||
|
|
||||||
while (activeStates.Count != 0)
|
while (activeStates.Count != 0)
|
||||||
{
|
{
|
||||||
@@ -162,12 +165,12 @@ public sealed class Solver : IDisposable
|
|||||||
if (bestAction.MaxScore >= Config.ScoreStorageThreshold)
|
if (bestAction.MaxScore >= Config.ScoreStorageThreshold)
|
||||||
{
|
{
|
||||||
var (_, furcatedActionIdx, solution) = bestAction;
|
var (_, furcatedActionIdx, solution) = bestAction;
|
||||||
var (activeActions, _) = activeStates[furcatedActionIdx];
|
(IEnumerable<ActionType> activeActions, _) = activeStates[furcatedActionIdx];
|
||||||
|
|
||||||
activeActions.AddRange(solution.Actions);
|
activeActions = activeActions.Concat(solution.Actions);
|
||||||
foreach (var action in activeActions.Skip(definiteActionCount))
|
foreach (var action in activeActions.Skip(definiteActionCount))
|
||||||
OnNewAction?.Invoke(action);
|
InvokeNewAction(action);
|
||||||
return solution with { Actions = activeActions };
|
return solution with { ActionEnumerable = activeActions };
|
||||||
}
|
}
|
||||||
|
|
||||||
var newStates = new List<SolverSolution>(Config.FurcatedActionCount);
|
var newStates = new List<SolverSolution>(Config.FurcatedActionCount);
|
||||||
@@ -214,7 +217,7 @@ public sealed class Solver : IDisposable
|
|||||||
if (definiteCount != equalCount)
|
if (definiteCount != equalCount)
|
||||||
{
|
{
|
||||||
foreach(var action in refActions.Take(equalCount).Skip(definiteCount))
|
foreach(var action in refActions.Take(equalCount).Skip(definiteCount))
|
||||||
OnNewAction?.Invoke(action);
|
InvokeNewAction(action);
|
||||||
|
|
||||||
definiteActionCount = equalCount;
|
definiteActionCount = equalCount;
|
||||||
}
|
}
|
||||||
@@ -224,11 +227,11 @@ public sealed class Solver : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (bestSims.Count == 0)
|
if (bestSims.Count == 0)
|
||||||
return new(new(), state);
|
return new(Array.Empty<ActionType>(), state);
|
||||||
|
|
||||||
var result = bestSims.MaxBy(s => s.Score).Result;
|
var result = bestSims.MaxBy(s => s.Score).Result;
|
||||||
foreach (var action in result.Actions.Skip(definiteActionCount))
|
foreach (var action in result.Actions.Skip(definiteActionCount))
|
||||||
OnNewAction?.Invoke(action);
|
InvokeNewAction(action);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -282,12 +285,12 @@ public sealed class Solver : IDisposable
|
|||||||
{
|
{
|
||||||
actions.AddRange(solution.Actions);
|
actions.AddRange(solution.Actions);
|
||||||
foreach (var action in solution.Actions)
|
foreach (var action in solution.Actions)
|
||||||
OnNewAction?.Invoke(action);
|
InvokeNewAction(action);
|
||||||
return solution with { Actions = actions };
|
return solution with { Actions = actions };
|
||||||
}
|
}
|
||||||
|
|
||||||
var chosenAction = solution.Actions[0];
|
var chosenAction = solution.Actions[0];
|
||||||
OnNewAction?.Invoke(chosenAction);
|
InvokeNewAction(chosenAction);
|
||||||
|
|
||||||
(_, state) = sim.Execute(state, chosenAction);
|
(_, state) = sim.Execute(state, chosenAction);
|
||||||
actions.Add(chosenAction);
|
actions.Add(chosenAction);
|
||||||
@@ -321,12 +324,12 @@ public sealed class Solver : IDisposable
|
|||||||
{
|
{
|
||||||
actions.AddRange(solution.Actions);
|
actions.AddRange(solution.Actions);
|
||||||
foreach (var action in solution.Actions)
|
foreach (var action in solution.Actions)
|
||||||
OnNewAction?.Invoke(action);
|
InvokeNewAction(action);
|
||||||
return Task.FromResult(solution with { Actions = actions });
|
return Task.FromResult(solution with { Actions = actions });
|
||||||
}
|
}
|
||||||
|
|
||||||
var chosenAction = solution.Actions[0];
|
var chosenAction = solution.Actions[0];
|
||||||
OnNewAction?.Invoke(chosenAction);
|
InvokeNewAction(chosenAction);
|
||||||
|
|
||||||
(_, state) = sim.Execute(state, chosenAction);
|
(_, state) = sim.Execute(state, chosenAction);
|
||||||
actions.Add(chosenAction);
|
actions.Add(chosenAction);
|
||||||
@@ -365,7 +368,7 @@ public sealed class Solver : IDisposable
|
|||||||
|
|
||||||
var solution = tasks.Select(t => t.Result).MaxBy(r => r.MaxScore).Solution;
|
var solution = tasks.Select(t => t.Result).MaxBy(r => r.MaxScore).Solution;
|
||||||
foreach (var action in solution.Actions)
|
foreach (var action in solution.Actions)
|
||||||
OnNewAction?.Invoke(action);
|
InvokeNewAction(action);
|
||||||
|
|
||||||
return solution;
|
return solution;
|
||||||
}
|
}
|
||||||
@@ -376,7 +379,7 @@ public sealed class Solver : IDisposable
|
|||||||
solver.Search(Config.Iterations, Token);
|
solver.Search(Config.Iterations, Token);
|
||||||
var solution = solver.Solution();
|
var solution = solver.Solution();
|
||||||
foreach (var action in solution.Actions)
|
foreach (var action in solution.Actions)
|
||||||
OnNewAction?.Invoke(action);
|
InvokeNewAction(action);
|
||||||
|
|
||||||
return Task.FromResult(solution);
|
return Task.FromResult(solution);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,4 +3,43 @@ using Craftimizer.Simulator.Actions;
|
|||||||
|
|
||||||
namespace Craftimizer.Solver;
|
namespace Craftimizer.Solver;
|
||||||
|
|
||||||
public readonly record struct SolverSolution(List<ActionType> Actions, SimulationState State);
|
public readonly record struct SolverSolution {
|
||||||
|
private readonly List<ActionType> actions = null!;
|
||||||
|
public readonly IReadOnlyList<ActionType> Actions { get => actions; init => ActionEnumerable = value; }
|
||||||
|
public readonly IEnumerable<ActionType> ActionEnumerable { init => actions = SanitizeCombos(value).ToList(); }
|
||||||
|
public readonly SimulationState State { get; init; }
|
||||||
|
|
||||||
|
public SolverSolution(IEnumerable<ActionType> actions, SimulationState state)
|
||||||
|
{
|
||||||
|
ActionEnumerable = actions;
|
||||||
|
State = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Deconstruct(out IReadOnlyList<ActionType> actions, out SimulationState state)
|
||||||
|
{
|
||||||
|
actions = Actions;
|
||||||
|
state = State;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static IEnumerable<ActionType> SanitizeCombo(ActionType action)
|
||||||
|
{
|
||||||
|
if (action.Base() is BaseComboAction combo)
|
||||||
|
{
|
||||||
|
foreach (var a in SanitizeCombo(combo.ActionTypeA))
|
||||||
|
yield return a;
|
||||||
|
foreach (var b in SanitizeCombo(combo.ActionTypeB))
|
||||||
|
yield return b;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
yield return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static IEnumerable<ActionType> SanitizeCombos(IEnumerable<ActionType> actions)
|
||||||
|
{
|
||||||
|
foreach (var action in actions)
|
||||||
|
{
|
||||||
|
foreach (var sanitizedAction in SanitizeCombo(action))
|
||||||
|
yield return sanitizedAction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user