Revamp UI and all sortsa stuff
This commit is contained in:
@@ -1,15 +1,43 @@
|
|||||||
|
using Craftimizer.Simulator;
|
||||||
|
using Craftimizer.Simulator.Actions;
|
||||||
using Dalamud.Configuration;
|
using Dalamud.Configuration;
|
||||||
|
using Dalamud.Logging;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Craftimizer.Plugin;
|
namespace Craftimizer.Plugin;
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class Macro
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public List<ActionType> Actions { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class Configuration : IPluginConfiguration
|
public class Configuration : IPluginConfiguration
|
||||||
{
|
{
|
||||||
public int Version { get; set; } = 1;
|
public int Version { get; set; } = 1;
|
||||||
|
|
||||||
public void Save()
|
public bool OverrideUncraftability { get; set; } = true;
|
||||||
|
public List<Macro> Macros { get; set; } = new();
|
||||||
|
public string SimulatorType { get; set; } = typeof(Simulator.Simulator).AssemblyQualifiedName!;
|
||||||
|
|
||||||
|
public Simulator.Simulator CreateSimulator(SimulationState state)
|
||||||
{
|
{
|
||||||
Service.PluginInterface.SavePluginConfig(this);
|
var type = Type.GetType(SimulatorType);
|
||||||
|
if (type == null)
|
||||||
|
PluginLog.LogError($"Failed to resolve simulator type ({SimulatorType})");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Activator.CreateInstance(type, state) is Simulator.Simulator sim)
|
||||||
|
return sim;
|
||||||
|
|
||||||
|
PluginLog.LogError($"Failed to create simulator ({SimulatorType})");
|
||||||
|
}
|
||||||
|
return new Simulator.Simulator(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Save() =>
|
||||||
|
Service.PluginInterface.SavePluginConfig(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,4 +18,5 @@ public static class LuminaSheets
|
|||||||
public static readonly ExcelSheet<Item> ItemSheetEnglish = Service.DataManager.GetExcelSheet<Item>(ClientLanguage.English)!;
|
public static readonly ExcelSheet<Item> ItemSheetEnglish = Service.DataManager.GetExcelSheet<Item>(ClientLanguage.English)!;
|
||||||
public static readonly ExcelSheet<Materia> MateriaSheet = Service.DataManager.GetExcelSheet<Materia>()!;
|
public static readonly ExcelSheet<Materia> MateriaSheet = Service.DataManager.GetExcelSheet<Materia>()!;
|
||||||
public static readonly ExcelSheet<BaseParam> BaseParamSheet = Service.DataManager.GetExcelSheet<BaseParam>()!;
|
public static readonly ExcelSheet<BaseParam> BaseParamSheet = Service.DataManager.GetExcelSheet<BaseParam>()!;
|
||||||
|
public static readonly ExcelSheet<ItemFood> ItemFoodSheet = Service.DataManager.GetExcelSheet<ItemFood>()!;
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-11
@@ -1,8 +1,13 @@
|
|||||||
using Craftimizer.Plugin.Windows;
|
using Craftimizer.Plugin.Windows;
|
||||||
|
using Craftimizer.Simulator;
|
||||||
|
using Craftimizer.Simulator.Actions;
|
||||||
using Dalamud.Game.Command;
|
using Dalamud.Game.Command;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using ClassJob = Craftimizer.Simulator.ClassJob;
|
||||||
|
|
||||||
namespace Craftimizer.Plugin;
|
namespace Craftimizer.Plugin;
|
||||||
|
|
||||||
@@ -10,22 +15,21 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
{
|
{
|
||||||
public string Name => "Craftimizer";
|
public string Name => "Craftimizer";
|
||||||
|
|
||||||
public Configuration Configuration { get; }
|
public WindowSystem WindowSystem { get; }
|
||||||
public WindowSystem WindowSystem { get; } = new("Craftimizer");
|
public SettingsWindow SettingsWindow { get; }
|
||||||
public SimulatorWindow SimulatorWindow { get; }
|
|
||||||
public CraftingLog RecipeNoteWindow { get; }
|
public CraftingLog RecipeNoteWindow { get; }
|
||||||
|
public SimulatorWindow? SimulatorWindow { get; set; }
|
||||||
|
|
||||||
public Plugin(
|
public Plugin([RequiredVersion("1.0")] DalamudPluginInterface pluginInterface)
|
||||||
[RequiredVersion("1.0")] DalamudPluginInterface pluginInterface)
|
|
||||||
{
|
{
|
||||||
|
Service.Plugin = this;
|
||||||
pluginInterface.Create<Service>();
|
pluginInterface.Create<Service>();
|
||||||
|
Service.Configuration = pluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
|
||||||
|
|
||||||
Configuration = pluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
|
WindowSystem = new(Name);
|
||||||
|
|
||||||
SimulatorWindow = new();
|
|
||||||
WindowSystem.AddWindow(SimulatorWindow);
|
|
||||||
RecipeNoteWindow = new();
|
RecipeNoteWindow = new();
|
||||||
WindowSystem.AddWindow(RecipeNoteWindow);
|
SettingsWindow = new();
|
||||||
|
|
||||||
Service.CommandManager.AddHandler("/craft", new CommandInfo(OnCommand)
|
Service.CommandManager.AddHandler("/craft", new CommandInfo(OnCommand)
|
||||||
{
|
{
|
||||||
@@ -33,8 +37,17 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
});
|
});
|
||||||
|
|
||||||
Service.PluginInterface.UiBuilder.Draw += WindowSystem.Draw;
|
Service.PluginInterface.UiBuilder.Draw += WindowSystem.Draw;
|
||||||
Service.PluginInterface.UiBuilder.OpenConfigUi += () => SimulatorWindow.IsOpen = true;
|
Service.PluginInterface.UiBuilder.OpenConfigUi += () => SettingsWindow.IsOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenSimulatorWindow(Item item, SimulationInput input, ClassJob classJob, List<ActionType> actions)
|
||||||
|
{
|
||||||
|
if (SimulatorWindow != null)
|
||||||
|
{
|
||||||
|
SimulatorWindow.IsOpen = false;
|
||||||
|
WindowSystem.RemoveWindow(SimulatorWindow);
|
||||||
|
}
|
||||||
|
SimulatorWindow = new(item, input, classJob, actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@@ -47,6 +60,6 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
if (command != "/craft")
|
if (command != "/craft")
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SimulatorWindow.IsOpen = true;
|
SettingsWindow.IsOpen = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Dalamud.IoC;
|
|||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Game.Command;
|
using Dalamud.Game.Command;
|
||||||
|
using Dalamud.Interface.Windowing;
|
||||||
|
|
||||||
namespace Craftimizer.Plugin;
|
namespace Craftimizer.Plugin;
|
||||||
|
|
||||||
@@ -22,5 +23,10 @@ public sealed class Service
|
|||||||
[PluginService] public static DataManager DataManager { get; private set; }
|
[PluginService] public static DataManager DataManager { get; private set; }
|
||||||
[PluginService] public static TargetManager TargetManager { get; private set; }
|
[PluginService] public static TargetManager TargetManager { get; private set; }
|
||||||
[PluginService] public static Condition Condition { get; private set; }
|
[PluginService] public static Condition Condition { get; private set; }
|
||||||
|
|
||||||
|
public static Plugin Plugin { get; internal set; }
|
||||||
|
public static Configuration Configuration { get; internal set; }
|
||||||
|
public static WindowSystem WindowSystem => Plugin.WindowSystem;
|
||||||
#pragma warning restore CS8618
|
#pragma warning restore CS8618
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,37 +17,50 @@ namespace Craftimizer.Plugin;
|
|||||||
|
|
||||||
internal static class ActionUtils
|
internal static class ActionUtils
|
||||||
{
|
{
|
||||||
private static (CraftAction? CraftAction, Action? Action) GetActionRow(this ActionType me, ClassJob classJob)
|
private static (CraftAction? CraftAction, Action? Action)[,] ActionRows;
|
||||||
|
|
||||||
|
static ActionUtils()
|
||||||
{
|
{
|
||||||
var actionId = me.Base().ActionId;
|
var actionTypes = Enum.GetValues<ActionType>();
|
||||||
if (LuminaSheets.CraftActionSheet.GetRow(actionId) is CraftAction baseCraftAction)
|
var classJobs = Enum.GetValues<ClassJob>();
|
||||||
|
ActionRows = new (CraftAction? CraftAction, Action? Action)[actionTypes.Length, classJobs.Length];
|
||||||
|
foreach (var actionType in actionTypes)
|
||||||
{
|
{
|
||||||
return (classJob switch
|
var actionId = actionType.Base().ActionId;
|
||||||
|
if (LuminaSheets.CraftActionSheet.GetRow(actionId) is CraftAction baseCraftAction)
|
||||||
{
|
{
|
||||||
ClassJob.Carpenter => baseCraftAction.CRP.Value!,
|
foreach(var classJob in classJobs)
|
||||||
ClassJob.Blacksmith => baseCraftAction.BSM.Value!,
|
{
|
||||||
ClassJob.Armorer => baseCraftAction.ARM.Value!,
|
ActionRows[(int)actionType, (int)classJob] = (classJob switch
|
||||||
ClassJob.Goldsmith => baseCraftAction.GSM.Value!,
|
{
|
||||||
ClassJob.Leatherworker => baseCraftAction.LTW.Value!,
|
ClassJob.Carpenter => baseCraftAction.CRP.Value!,
|
||||||
ClassJob.Weaver => baseCraftAction.WVR.Value!,
|
ClassJob.Blacksmith => baseCraftAction.BSM.Value!,
|
||||||
ClassJob.Alchemist => baseCraftAction.ALC.Value!,
|
ClassJob.Armorer => baseCraftAction.ARM.Value!,
|
||||||
ClassJob.Culinarian => baseCraftAction.CUL.Value!,
|
ClassJob.Goldsmith => baseCraftAction.GSM.Value!,
|
||||||
_ => baseCraftAction
|
ClassJob.Leatherworker => baseCraftAction.LTW.Value!,
|
||||||
}, null);
|
ClassJob.Weaver => baseCraftAction.WVR.Value!,
|
||||||
|
ClassJob.Alchemist => baseCraftAction.ALC.Value!,
|
||||||
|
ClassJob.Culinarian => baseCraftAction.CUL.Value!,
|
||||||
|
_ => baseCraftAction
|
||||||
|
}, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (LuminaSheets.ActionSheet.GetRow(actionId) is Action baseAction)
|
||||||
|
{
|
||||||
|
var possibleActions = LuminaSheets.ActionSheet.Where(r =>
|
||||||
|
r.Icon == baseAction.Icon &&
|
||||||
|
r.ActionCategory.Row == baseAction.ActionCategory.Row &&
|
||||||
|
r.Name.RawString.Equals(baseAction.Name.RawString, StringComparison.Ordinal)).ToArray();
|
||||||
|
|
||||||
|
foreach (var classJob in classJobs)
|
||||||
|
ActionRows[(int)actionType, (int)classJob] = (null, possibleActions.First(r => r.ClassJobCategory.Value?.IsClassJob(classJob) ?? false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (LuminaSheets.ActionSheet.GetRow(actionId) is Action baseAction)
|
|
||||||
{
|
|
||||||
return (null,
|
|
||||||
LuminaSheets.ActionSheet.First(r =>
|
|
||||||
r.Icon == baseAction.Icon &&
|
|
||||||
r.ActionCategory.Row == baseAction.ActionCategory.Row &&
|
|
||||||
r.Name.RawString.Equals(baseAction.Name.RawString, StringComparison.Ordinal) &&
|
|
||||||
(r.ClassJobCategory.Value?.IsClassJob(classJob) ?? false)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
return (null, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static (CraftAction? CraftAction, Action? Action) GetActionRow(this ActionType me, ClassJob classJob) =>
|
||||||
|
ActionRows[(int)me, (int)classJob];
|
||||||
|
|
||||||
public static uint GetId(this ActionType me, ClassJob classJob)
|
public static uint GetId(this ActionType me, ClassJob classJob)
|
||||||
{
|
{
|
||||||
var (craftAction, action) = GetActionRow(me, classJob);
|
var (craftAction, action) = GetActionRow(me, classJob);
|
||||||
|
|||||||
@@ -1,126 +0,0 @@
|
|||||||
using Craftimizer.Plugin.Utils;
|
|
||||||
using Craftimizer.Simulator;
|
|
||||||
using Craftimizer.Simulator.Actions;
|
|
||||||
using Dalamud.Interface;
|
|
||||||
using Dalamud.Interface.Windowing;
|
|
||||||
using ImGuiNET;
|
|
||||||
using Lumina.Excel.GeneratedSheets;
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
|
||||||
using ClassJob = Craftimizer.Simulator.ClassJob;
|
|
||||||
|
|
||||||
namespace Craftimizer.Plugin;
|
|
||||||
|
|
||||||
public class SimulatorWindow : Window
|
|
||||||
{
|
|
||||||
public Simulator.Simulator Simulation { get; }
|
|
||||||
private SimulationState State { get; set; }
|
|
||||||
|
|
||||||
private bool showOnlyGuaranteedActions = true;
|
|
||||||
|
|
||||||
public SimulatorWindow() : base("Craftimizer")
|
|
||||||
{
|
|
||||||
SizeConstraints = new WindowSizeConstraints()
|
|
||||||
{
|
|
||||||
MinimumSize = new Vector2(400, 400),
|
|
||||||
MaximumSize = new Vector2(float.MaxValue, float.MaxValue)
|
|
||||||
};
|
|
||||||
|
|
||||||
State = new(new(
|
|
||||||
new CharacterStats { Craftsmanship = 4041, Control = 3905, CP = 609, Level = 90, CLvl = Gearsets.CalculateCLvl(90) },
|
|
||||||
CreateRecipeInfo(LuminaSheets.RecipeSheet.GetRow(35499)!),
|
|
||||||
0
|
|
||||||
));
|
|
||||||
Simulation = new(State);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RecipeInfo CreateRecipeInfo(Recipe recipe)
|
|
||||||
{
|
|
||||||
var recipeTable = recipe.RecipeLevelTable.Value!;
|
|
||||||
return new() {
|
|
||||||
IsExpert = recipe.IsExpert,
|
|
||||||
ClassJobLevel = recipeTable.ClassJobLevel,
|
|
||||||
RLvl = (int)recipeTable.RowId,
|
|
||||||
ConditionsFlag = recipeTable.ConditionsFlag,
|
|
||||||
MaxDurability = recipeTable.Durability * recipe.DurabilityFactor / 100,
|
|
||||||
MaxQuality = (int)recipeTable.Quality * recipe.QualityFactor / 100,
|
|
||||||
MaxProgress = recipeTable.Difficulty * recipe.DifficultyFactor / 100,
|
|
||||||
QualityModifier = recipeTable.QualityModifier,
|
|
||||||
QualityDivider = recipeTable.QualityDivider,
|
|
||||||
ProgressModifier = recipeTable.ProgressModifier,
|
|
||||||
ProgressDivider = recipeTable.ProgressDivider,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Draw()
|
|
||||||
{
|
|
||||||
ImGui.BeginTable("CraftimizerTable", 2, ImGuiTableFlags.Resizable);
|
|
||||||
ImGui.TableSetupColumn("CraftimizerActionsColumn", ImGuiTableColumnFlags.WidthFixed, 300);
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
ImGui.BeginChild("CraftimizerActions", Vector2.Zero, true, ImGuiWindowFlags.NoDecoration);
|
|
||||||
ImGui.Checkbox("Show only guaranteed actions", ref showOnlyGuaranteedActions);
|
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
|
||||||
foreach (var category in Enum.GetValues<ActionType>().GroupBy(a => a.Category()))
|
|
||||||
{
|
|
||||||
var i = 0;
|
|
||||||
ImGuiUtils.BeginGroupPanel(category.Key.GetDisplayName());
|
|
||||||
foreach (var action in category.OrderBy(a => a.Level()))
|
|
||||||
{
|
|
||||||
var baseAction = action.Base();
|
|
||||||
if (showOnlyGuaranteedActions && baseAction.SuccessRate(Simulation) != 1)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
ImGui.BeginDisabled(!baseAction.CanUse(Simulation) || Simulation.IsComplete);
|
|
||||||
if (ImGui.ImageButton(action.GetIcon(ClassJob.Carpenter).ImGuiHandle, new Vector2(ImGui.GetFontSize() * 2)))
|
|
||||||
(_, State) = Simulation.Execute(State, action);
|
|
||||||
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
|
||||||
ImGui.SetTooltip($"{action.GetName(ClassJob.Carpenter)}\n{baseAction.GetTooltip(Simulation, true)}");
|
|
||||||
ImGui.EndDisabled();
|
|
||||||
if (++i % 5 != 0)
|
|
||||||
ImGui.SameLine();
|
|
||||||
}
|
|
||||||
ImGuiUtils.EndGroupPanel();
|
|
||||||
}
|
|
||||||
ImGui.PopStyleVar();
|
|
||||||
ImGui.EndChild();
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
ImGui.BeginChild("CraftimizerSimulator", Vector2.Zero, true, ImGuiWindowFlags.NoDecoration);
|
|
||||||
ImGui.Text($"Step {State.StepCount + 1}");
|
|
||||||
ImGui.Text(State.Condition.Name());
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
ImGui.SetTooltip(State.Condition.Description(State.Input.Stats.HasSplendorousBuff));
|
|
||||||
ImGui.Text($"{State.HQPercent}%% HQ");
|
|
||||||
ImGui.PushStyleColor(ImGuiCol.PlotHistogram, new Vector4(.2f, 1f, .2f, 1f));
|
|
||||||
ImGui.ProgressBar(Math.Min((float)State.Progress / State.Input.Recipe.MaxProgress, 1f), new Vector2(200, 20), $"{State.Progress} / {State.Input.Recipe.MaxProgress}");
|
|
||||||
ImGui.PopStyleColor();
|
|
||||||
ImGui.PushStyleColor(ImGuiCol.PlotHistogram, new Vector4(.2f, .2f, 1f, 1f));
|
|
||||||
ImGui.ProgressBar(Math.Min((float)State.Quality / State.Input.Recipe.MaxQuality, 1f), new Vector2(200, 20), $"{State.Quality} / {State.Input.Recipe.MaxQuality}");
|
|
||||||
ImGui.PopStyleColor();
|
|
||||||
ImGui.PushStyleColor(ImGuiCol.PlotHistogram, new Vector4(1f, 1f, .2f, 1f));
|
|
||||||
ImGui.ProgressBar(Math.Clamp((float)State.Durability / State.Input.Recipe.MaxDurability, 0f, 1f), new Vector2(200, 20), $"{State.Durability} / {State.Input.Recipe.MaxDurability}");
|
|
||||||
ImGui.PopStyleColor();
|
|
||||||
ImGui.PushStyleColor(ImGuiCol.PlotHistogram, new Vector4(1f, .2f, 1f, 1f));
|
|
||||||
ImGui.ProgressBar(Math.Clamp((float)State.CP / State.Input.Stats.CP, 0f, 1f), new Vector2(200, 20), $"{State.CP} / {State.Input.Stats.CP}");
|
|
||||||
ImGui.PopStyleColor();
|
|
||||||
ImGuiHelpers.ScaledDummy(5);
|
|
||||||
ImGui.Text($"Effects:");
|
|
||||||
foreach (var effect in Enum.GetValues<EffectType>())
|
|
||||||
{
|
|
||||||
var strength = Simulation.GetEffectStrength(effect);
|
|
||||||
var duration = Simulation.GetEffectDuration(effect);
|
|
||||||
var icon = effect.GetIcon(strength);
|
|
||||||
var h = ImGui.GetFontSize() * 1.25f;
|
|
||||||
var w = icon.Width * h / icon.Height;
|
|
||||||
ImGui.Image(icon.ImGuiHandle, new Vector2(w, h));
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.Text(effect.GetTooltip(strength, duration));
|
|
||||||
}
|
|
||||||
ImGuiHelpers.ScaledDummy(5);
|
|
||||||
{
|
|
||||||
ImGui.Text("TODO: Action History");
|
|
||||||
}
|
|
||||||
ImGui.EndChild();
|
|
||||||
ImGui.EndTable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,91 +9,42 @@ using System.Linq;
|
|||||||
namespace Craftimizer.Plugin.Utils;
|
namespace Craftimizer.Plugin.Utils;
|
||||||
internal static unsafe class Gearsets
|
internal static unsafe class Gearsets
|
||||||
{
|
{
|
||||||
private static readonly (int CP, int Craftsmanship, int Control, bool HasSplendorous, bool HasSpecialist) BaseStats = (180, 0, 0, false, false);
|
public record struct GearsetStats(int CP, int Craftsmanship, int Control);
|
||||||
|
public record struct GearsetMateria(ushort Type, ushort Grade);
|
||||||
|
public record struct GearsetItem(uint itemId, bool isHq, GearsetMateria[] materia);
|
||||||
|
|
||||||
private const int ParamCP = 11;
|
private static readonly GearsetStats BaseStats = new(180, 0, 0);
|
||||||
private const int ParamCraftsmanship = 70;
|
|
||||||
private const int ParamControl = 71;
|
|
||||||
|
|
||||||
public static CharacterStats CalculateCharacterStats(InventoryContainer* container, int characterLevel, bool canUseManipulation)
|
public const int ParamCP = 11;
|
||||||
|
public const int ParamCraftsmanship = 70;
|
||||||
|
public const int ParamControl = 71;
|
||||||
|
|
||||||
|
public static GearsetItem[] GetGearsetItems(InventoryContainer* container)
|
||||||
{
|
{
|
||||||
var stats = CalculateGearsetStats(container);
|
var items = new GearsetItem[(int)container->Size];
|
||||||
return new CharacterStats
|
|
||||||
{
|
|
||||||
CP = stats.CP,
|
|
||||||
Craftsmanship = stats.Craftsmanship,
|
|
||||||
Control = stats.Control,
|
|
||||||
Level = characterLevel,
|
|
||||||
CanUseManipulation = canUseManipulation,
|
|
||||||
HasSplendorousBuff = stats.HasSplendorous,
|
|
||||||
IsSpecialist = stats.HasSpecialist,
|
|
||||||
CLvl = CalculateCLvl(characterLevel),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CharacterStats CalculateCharacterStats(RaptureGearsetModule.GearsetEntry* entry, int characterLevel, bool canUseManipulation)
|
|
||||||
{
|
|
||||||
var stats = CalculateGearsetStats(entry);
|
|
||||||
return new CharacterStats
|
|
||||||
{
|
|
||||||
CP = stats.CP,
|
|
||||||
Craftsmanship = stats.Craftsmanship,
|
|
||||||
Control = stats.Control,
|
|
||||||
Level = characterLevel,
|
|
||||||
CanUseManipulation = canUseManipulation,
|
|
||||||
HasSplendorousBuff = stats.HasSplendorous,
|
|
||||||
IsSpecialist = stats.HasSpecialist,
|
|
||||||
CLvl = CalculateCLvl(characterLevel),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static (int CP, int Craftsmanship, int Control, bool HasSplendorous, bool HasSpecialist) CalculateGearsetStats(InventoryContainer* container)
|
|
||||||
{
|
|
||||||
var stats = BaseStats;
|
|
||||||
for (var i = 0; i < container->Size; ++i)
|
for (var i = 0; i < container->Size; ++i)
|
||||||
{
|
{
|
||||||
var itemStats = CalculateGearsetItemStats(container->Items[i]);
|
var item = container->Items[i];
|
||||||
stats.CP += itemStats.CP;
|
items[i] = new(item.ItemID, item.Flags.HasFlag(InventoryItem.ItemFlags.HQ), GetMaterias(item.Materia, item.MateriaGrade));
|
||||||
stats.Craftsmanship += itemStats.Craftsmanship;
|
|
||||||
stats.Control += itemStats.Control;
|
|
||||||
stats.HasSplendorous = stats.HasSplendorous || itemStats.HasSplendorous;
|
|
||||||
stats.HasSpecialist = stats.HasSpecialist || itemStats.HasSpecialist;
|
|
||||||
}
|
}
|
||||||
return stats;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static (int CP, int Craftsmanship, int Control, bool HasSplendorous, bool HasSpecialist) CalculateGearsetStats(RaptureGearsetModule.GearsetEntry* entry)
|
public static GearsetItem[] GetGearsetItems(RaptureGearsetModule.GearsetEntry* entry)
|
||||||
{
|
{
|
||||||
var stats = new[]
|
var gearsetItems = new Span<RaptureGearsetModule.GearsetItem>(entry->ItemsData, 14);
|
||||||
|
var items = new GearsetItem[14];
|
||||||
|
for (var i = 0; i < 14; ++i)
|
||||||
{
|
{
|
||||||
BaseStats,
|
var item = gearsetItems[i];
|
||||||
CalculateGearsetItemStats(entry->MainHand),
|
items[i] = new(item.ItemID % 1000000, item.ItemID > 1000000, GetMaterias(item.Materia, item.MateriaGrade));
|
||||||
CalculateGearsetItemStats(entry->OffHand),
|
}
|
||||||
CalculateGearsetItemStats(entry->Head),
|
return items;
|
||||||
CalculateGearsetItemStats(entry->Body),
|
|
||||||
CalculateGearsetItemStats(entry->Hands),
|
|
||||||
// CalculateGearsetItemStats(entry->Belt),
|
|
||||||
CalculateGearsetItemStats(entry->Legs),
|
|
||||||
CalculateGearsetItemStats(entry->Feet),
|
|
||||||
CalculateGearsetItemStats(entry->Ears),
|
|
||||||
CalculateGearsetItemStats(entry->Neck),
|
|
||||||
CalculateGearsetItemStats(entry->Wrists),
|
|
||||||
CalculateGearsetItemStats(entry->RingRight),
|
|
||||||
CalculateGearsetItemStats(entry->RightLeft),
|
|
||||||
CalculateGearsetItemStats(entry->SoulStone),
|
|
||||||
};
|
|
||||||
return stats.Aggregate((a, b) => (a.CP + b.CP, a.Craftsmanship + b.Craftsmanship, a.Control + b.Control, a.HasSplendorous || b.HasSplendorous, a.HasSpecialist || b.HasSpecialist));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static (int CP, int Craftsmanship, int Control, bool HasSplendorous, bool HasSpecialist) CalculateGearsetItemStats(InventoryItem item) =>
|
public static GearsetStats CalculateGearsetItemStats(GearsetItem gearsetItem)
|
||||||
CalculateGearsetItemStats(item.ItemID, item.Flags.HasFlag(InventoryItem.ItemFlags.HQ), item.Materia, item.MateriaGrade);
|
|
||||||
|
|
||||||
private static (int CP, int Craftsmanship, int Control, bool HasSplendorous, bool HasSpecialist) CalculateGearsetItemStats(RaptureGearsetModule.GearsetItem item) =>
|
|
||||||
CalculateGearsetItemStats(item.ItemID % 1000000, item.ItemID > 1000000, item.Materia, item.MateriaGrade);
|
|
||||||
|
|
||||||
private static (int CP, int Craftsmanship, int Control, bool HasSplendorous, bool HasSpecialist) CalculateGearsetItemStats(uint itemId, bool isHq, ushort* materiaTypes, byte* materiaGrades)
|
|
||||||
{
|
{
|
||||||
var item = LuminaSheets.ItemSheet.GetRow(itemId)!;
|
var item = LuminaSheets.ItemSheet.GetRow(gearsetItem.itemId)!;
|
||||||
|
|
||||||
int cp = 0, craftsmanship = 0, control = 0;
|
int cp = 0, craftsmanship = 0, control = 0;
|
||||||
|
|
||||||
@@ -109,28 +60,63 @@ internal static unsafe class Gearsets
|
|||||||
|
|
||||||
foreach (var statIncrease in item.UnkData59)
|
foreach (var statIncrease in item.UnkData59)
|
||||||
IncreaseStat(statIncrease.BaseParam, statIncrease.BaseParamValue);
|
IncreaseStat(statIncrease.BaseParam, statIncrease.BaseParamValue);
|
||||||
|
if (gearsetItem.isHq)
|
||||||
if (isHq)
|
|
||||||
{
|
|
||||||
foreach (var statIncrease in item.UnkData73)
|
foreach (var statIncrease in item.UnkData73)
|
||||||
IncreaseStat(statIncrease.BaseParamSpecial, statIncrease.BaseParamValueSpecial);
|
IncreaseStat(statIncrease.BaseParamSpecial, statIncrease.BaseParamValueSpecial);
|
||||||
}
|
|
||||||
for (var i = 0; i < 5; ++i)
|
|
||||||
{
|
|
||||||
if (materiaTypes[i] == 0)
|
|
||||||
continue;
|
|
||||||
var materia = LuminaSheets.MateriaSheet.GetRow(materiaTypes[i])!;
|
|
||||||
|
|
||||||
IncreaseStat((int)materia.BaseParam.Row, materia.Value[materiaGrades[i]]);
|
foreach(var gearsetMateria in gearsetItem.materia)
|
||||||
|
{
|
||||||
|
if (gearsetMateria.Type == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var materia = LuminaSheets.MateriaSheet.GetRow(gearsetMateria.Type)!;
|
||||||
|
IncreaseStat((int)materia.BaseParam.Row, materia.Value[gearsetMateria.Grade]);
|
||||||
}
|
}
|
||||||
|
|
||||||
cp = Math.Min(cp, CalculateParamCap(item, ParamCP));
|
cp = Math.Min(cp, CalculateParamCap(item, ParamCP));
|
||||||
craftsmanship = Math.Min(craftsmanship, CalculateParamCap(item, ParamCraftsmanship));
|
craftsmanship = Math.Min(craftsmanship, CalculateParamCap(item, ParamCraftsmanship));
|
||||||
control = Math.Min(control, CalculateParamCap(item, ParamControl));
|
control = Math.Min(control, CalculateParamCap(item, ParamControl));
|
||||||
|
|
||||||
return (cp, craftsmanship, control, IsSpecialistSoulCrystal(item), IsSplendorousTool(itemId));
|
return new(cp, craftsmanship, control);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static GearsetStats CalculateGearsetStats(GearsetItem[] gearsetItems) =>
|
||||||
|
gearsetItems.Select(CalculateGearsetItemStats).Aggregate(BaseStats, (a, b) => new(a.CP + b.CP, a.Craftsmanship + b.Craftsmanship, a.Control + b.Control));
|
||||||
|
|
||||||
|
public static CharacterStats CalculateCharacterStats(GearsetItem[] gearsetItems, int characterLevel, bool canUseManipulation)
|
||||||
|
{
|
||||||
|
var stats = CalculateGearsetStats(gearsetItems);
|
||||||
|
return new CharacterStats
|
||||||
|
{
|
||||||
|
CP = stats.CP,
|
||||||
|
Craftsmanship = stats.Craftsmanship,
|
||||||
|
Control = stats.Control,
|
||||||
|
Level = characterLevel,
|
||||||
|
CanUseManipulation = canUseManipulation,
|
||||||
|
HasSplendorousBuff = gearsetItems.Any(IsSplendorousTool),
|
||||||
|
IsSpecialist = gearsetItems.Any(IsSpecialistSoulCrystal),
|
||||||
|
CLvl = CalculateCLvl(characterLevel),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsItem(GearsetItem item, uint itemId) =>
|
||||||
|
item.itemId == itemId;
|
||||||
|
|
||||||
|
public static bool IsSpecialistSoulCrystal(GearsetItem item)
|
||||||
|
{
|
||||||
|
var luminaItem = LuminaSheets.ItemSheet.GetRow(item.itemId)!;
|
||||||
|
// Soul Crystal ItemUICategory DoH Category
|
||||||
|
return luminaItem.ItemUICategory.Row != 62 && luminaItem.ClassJobUse.Value!.ClassJobCategory.Row == 33;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
public static int CalculateCLvl(int characterLevel) =>
|
||||||
|
characterLevel <= 80
|
||||||
|
? LuminaSheets.ParamGrowSheet.GetRow((uint)characterLevel)!.CraftingLevel
|
||||||
|
: (int)LuminaSheets.RecipeLevelTableSheet.First(r => r.ClassJobLevel == characterLevel).RowId;
|
||||||
|
|
||||||
// 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)
|
||||||
{
|
{
|
||||||
@@ -176,16 +162,11 @@ internal static unsafe class Gearsets
|
|||||||
return cap == 0 ? int.MaxValue : cap;
|
return cap == 0 ? int.MaxValue : cap;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsSpecialistSoulCrystal(Item item) =>
|
private static GearsetMateria[] GetMaterias(ushort* types, byte* grades)
|
||||||
// Soul Crystal ItemUICategory DoH Category
|
{
|
||||||
item.ItemUICategory.Row != 62 && item.ClassJobUse.Value!.ClassJobCategory.Row == 33;
|
var materia = new GearsetMateria[5];
|
||||||
|
for (var i = 0; i < 5; ++i)
|
||||||
private static bool IsSplendorousTool(uint itemId) =>
|
materia[i] = new(types[i], grades[i]);
|
||||||
LuminaSheets.ItemSheetEnglish.GetRow(itemId)!.Description.ToDalamudString().TextValue.Contains("Increases to quality are 1.75 times higher than normal when material condition is Good.", StringComparison.Ordinal);
|
return materia;
|
||||||
// 38737 <= itemId && itemId <= 38744;
|
}
|
||||||
|
|
||||||
public static int CalculateCLvl(int characterLevel) =>
|
|
||||||
characterLevel <= 80
|
|
||||||
? LuminaSheets.ParamGrowSheet.GetRow((uint)characterLevel)!.CraftingLevel
|
|
||||||
: (int)LuminaSheets.RecipeLevelTableSheet.First(r => r.ClassJobLevel == characterLevel).RowId;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
using Craftimizer.Plugin.Utils;
|
using Craftimizer.Plugin.Utils;
|
||||||
using Craftimizer.Simulator;
|
using Craftimizer.Simulator;
|
||||||
using Craftimizer.Simulator.Actions;
|
|
||||||
using Dalamud;
|
using Dalamud;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.Components;
|
using Dalamud.Interface.Components;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
|
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 FFXIVClientStructs.FFXIV.Client.UI;
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -26,46 +29,189 @@ public unsafe class CraftingLog : Window
|
|||||||
{
|
{
|
||||||
private const ImGuiWindowFlags WindowFlags = ImGuiWindowFlags.NoDecoration
|
private const ImGuiWindowFlags WindowFlags = ImGuiWindowFlags.NoDecoration
|
||||||
| ImGuiWindowFlags.AlwaysAutoResize
|
| ImGuiWindowFlags.AlwaysAutoResize
|
||||||
|
| ImGuiWindowFlags.NoSavedSettings
|
||||||
| ImGuiWindowFlags.NoFocusOnAppearing
|
| ImGuiWindowFlags.NoFocusOnAppearing
|
||||||
| ImGuiWindowFlags.NoNavFocus;
|
| ImGuiWindowFlags.NoNavFocus;
|
||||||
|
|
||||||
|
private const int LeftSideWidth = 350;
|
||||||
|
|
||||||
|
// If relative, increase stat by Value's % (rounded down), and cap increase to Max
|
||||||
|
// If not relative, increase stat by Value, and ignore Max
|
||||||
|
[StructLayout(LayoutKind.Auto)]
|
||||||
|
private record struct FoodStat(bool IsRelative, sbyte Value, short Max, sbyte ValueHQ, short MaxHQ);
|
||||||
|
private sealed record Food(Item Item, string Name, FoodStat? Craftsmanship, FoodStat? Control, FoodStat? CP);
|
||||||
|
|
||||||
|
private static Food[] FoodItems { get; }
|
||||||
|
private static Food[] MedicineItems { get; }
|
||||||
|
private static Random Random { get; }
|
||||||
|
|
||||||
|
private TimeSpan FrameTime { get; set; }
|
||||||
|
private Stopwatch Stopwatch { get; } = new();
|
||||||
|
|
||||||
|
// Set in DrawConditions
|
||||||
private AddonRecipeNote* Addon { get; set; }
|
private AddonRecipeNote* Addon { get; set; }
|
||||||
private RecipeNote* State { get; set; }
|
private RecipeNote* State { get; set; }
|
||||||
private ushort RecipeId { get; set; }
|
private ushort RecipeId { get; set; }
|
||||||
private Recipe Recipe { get; set; } = null!;
|
private Recipe Recipe { get; set; } = null!;
|
||||||
|
|
||||||
|
// Set in CalculateRecipeStats (in DrawConditions)
|
||||||
|
private RecipeLevelTable RecipeTable { get; set; } = null!;
|
||||||
private RecipeInfo RecipeInfo { get; set; } = null!;
|
private RecipeInfo RecipeInfo { get; set; } = null!;
|
||||||
|
private ClassJob RecipeClassJob { get; set; }
|
||||||
|
private short RecipeCharacterLevel { get; set; }
|
||||||
|
private bool RecipeCanUseManipulation { get; set; }
|
||||||
|
private int RecipeHQIngredientCount { get; set; }
|
||||||
|
private int RecipeMaxStartingQuality { get; set; }
|
||||||
|
|
||||||
private ClassJob RecipeClassJob => (ClassJob)Recipe.CraftType.Row;
|
// Set in CalculateCharacterStats (in PreDraw)
|
||||||
private short RecipeCharacterLevel => PlayerState.Instance()->ClassJobLevelArray[RecipeClassJob.GetClassJobIndex()];
|
private Gearsets.GearsetItem[] CharacterEquipment { get; set; } = null!;
|
||||||
private bool RecipeCanUseManipulation => ActionManager.CanUseActionOnTarget(ActionType.Manipulation.GetId(RecipeClassJob), (GameObject*)Service.ClientState.LocalPlayer!.Address);
|
private CharacterStats CharacterStatsNoConsumable { get; set; } = null!;
|
||||||
private RecipeLevelTable RecipeTable => Recipe.RecipeLevelTable.Value!;
|
private Gearsets.GearsetStats CharacterConsumableBonus { get; set; }
|
||||||
|
private CharacterStats CharacterStatsConsumable { get; set; } = null!;
|
||||||
|
private CannotCraftReason CharacterCannotCraftReason { get; set; }
|
||||||
|
private SimulationInput CharacterSimulationInput { get; set; } = null!;
|
||||||
|
|
||||||
private int startingQuality;
|
// Set in UI
|
||||||
|
private int QualityNotches { get; set; }
|
||||||
|
private int StartingQuality => RecipeHQIngredientCount == 0 ? 0 : (int)((float)QualityNotches * RecipeMaxStartingQuality / RecipeHQIngredientCount);
|
||||||
|
|
||||||
|
private Food? SelectedFood { get; set; }
|
||||||
|
private bool SelectedFoodHQ { get; set; }
|
||||||
|
|
||||||
|
private Food? SelectedMedicine { get; set; }
|
||||||
|
private bool SelectedMedicineHQ { get; set; }
|
||||||
|
|
||||||
|
static CraftingLog()
|
||||||
|
{
|
||||||
|
var foods = new List<Food>();
|
||||||
|
var medicines = new List<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, item.Name.ToDalamudString().TextValue ?? $"Unknown ({item.RowId})", craftsmanship, control, cp);
|
||||||
|
if (isFood)
|
||||||
|
foods.Add(food);
|
||||||
|
if (isMedicine)
|
||||||
|
medicines.Add(food);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foods.Sort((a, b) => b.Item.LevelItem.Row.CompareTo(a.Item.LevelItem.Row));
|
||||||
|
medicines.Sort((a, b) => b.Item.LevelItem.Row.CompareTo(a.Item.LevelItem.Row));
|
||||||
|
FoodItems = foods.ToArray();
|
||||||
|
MedicineItems = medicines.ToArray();
|
||||||
|
|
||||||
|
Random = new();
|
||||||
|
}
|
||||||
|
|
||||||
public CraftingLog() : base("RecipeNoteHelper", WindowFlags, true)
|
public CraftingLog() : base("RecipeNoteHelper", WindowFlags, true)
|
||||||
{
|
{
|
||||||
|
Service.WindowSystem.AddWindow(this);
|
||||||
|
|
||||||
IsOpen = true;
|
IsOpen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CalculateRecipeStats()
|
||||||
|
{
|
||||||
|
RecipeTable = Recipe.RecipeLevelTable.Value!;
|
||||||
|
RecipeInfo = CreateRecipeInfo(Recipe);
|
||||||
|
RecipeClassJob = (ClassJob)Recipe.CraftType.Row;
|
||||||
|
RecipeCharacterLevel = PlayerState.Instance()->ClassJobLevelArray[RecipeClassJob.GetClassJobIndex()];
|
||||||
|
RecipeCanUseManipulation = ActionManager.CanUseActionOnTarget(ActionType.Manipulation.GetId(RecipeClassJob), (GameObject*)Service.ClientState.LocalPlayer!.Address);
|
||||||
|
RecipeHQIngredientCount = Recipe.UnkData5
|
||||||
|
.Where(i =>
|
||||||
|
i != null &&
|
||||||
|
i.ItemIngredient != 0 &&
|
||||||
|
(LuminaSheets.ItemSheet.GetRow((uint)i.ItemIngredient)?.CanBeHq ?? false)
|
||||||
|
).Sum(i => i.AmountIngredient);
|
||||||
|
RecipeMaxStartingQuality = (int)Math.Floor(Recipe.MaterialQualityFactor * RecipeInfo.MaxQuality / 100f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CalculateCharacterStats()
|
||||||
|
{
|
||||||
|
var container = InventoryManager.Instance()->GetInventoryContainer(InventoryType.EquippedItems);
|
||||||
|
if (container == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CharacterEquipment = Gearsets.GetGearsetItems(container);
|
||||||
|
CharacterStatsNoConsumable = Gearsets.CalculateCharacterStats(CharacterEquipment, RecipeCharacterLevel, RecipeCanUseManipulation);
|
||||||
|
CharacterConsumableBonus = CalculateConsumableBonus(CharacterStatsNoConsumable);
|
||||||
|
CharacterStatsConsumable = CharacterStatsNoConsumable with
|
||||||
|
{
|
||||||
|
Craftsmanship = CharacterStatsNoConsumable.Craftsmanship + CharacterConsumableBonus.Craftsmanship,
|
||||||
|
Control = CharacterStatsNoConsumable.Control + CharacterConsumableBonus.Control,
|
||||||
|
CP = CharacterStatsNoConsumable.CP + CharacterConsumableBonus.CP,
|
||||||
|
};
|
||||||
|
CharacterCannotCraftReason = Service.Configuration.OverrideUncraftability ? CannotCraftReason.OK : CanCraftRecipe(CharacterEquipment, CharacterStatsConsumable);
|
||||||
|
|
||||||
|
if (CharacterCannotCraftReason == CannotCraftReason.OK)
|
||||||
|
CharacterSimulationInput = new(CharacterStatsConsumable, RecipeInfo, StartingQuality, Random);
|
||||||
|
}
|
||||||
|
|
||||||
public override void Draw()
|
public override void Draw()
|
||||||
{
|
{
|
||||||
if (Service.ClientState.LocalPlayer == null)
|
ImGui.BeginTable("craftlog", 2, ImGuiTableFlags.BordersInnerV);
|
||||||
return;
|
|
||||||
|
|
||||||
|
ImGui.TableSetupColumn("", ImGuiTableColumnFlags.WidthFixed, LeftSideWidth);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
DrawCraftInfo();
|
DrawCraftInfo();
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
DrawGearsets();
|
DrawGearsets();
|
||||||
|
|
||||||
|
ImGui.EndTable();
|
||||||
|
|
||||||
|
ImGui.TextUnformatted($"{FrameTime.TotalMilliseconds:0.00}ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawCraftInfo()
|
private void DrawCraftInfo()
|
||||||
{
|
{
|
||||||
DrawRecipeInfo();
|
ImGui.BeginTable("craftinfo", 2, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.SizingStretchSame);
|
||||||
DrawCharacterInfo();
|
|
||||||
|
|
||||||
DrawCraftActions();
|
ImGui.TableNextColumn();
|
||||||
|
DrawRecipeInfo();
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
DrawCharacterInfo();
|
||||||
|
ImGui.EndTable();
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
DrawCraftParameters();
|
||||||
|
DrawMacros();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawRecipeInfo()
|
private void DrawRecipeInfo()
|
||||||
{
|
{
|
||||||
var s = new StringBuilder();
|
var s = new StringBuilder();
|
||||||
s.AppendLine($"{RecipeClassJob.GetName()} {new string('★', RecipeTable.Stars)}");
|
s.AppendLine($"{RecipeClassJob.GetName()} {new string('★', RecipeTable.Stars)}");
|
||||||
@@ -73,46 +219,110 @@ public unsafe class CraftingLog : Window
|
|||||||
s.AppendLine($"Durability: {RecipeInfo.MaxDurability}");
|
s.AppendLine($"Durability: {RecipeInfo.MaxDurability}");
|
||||||
s.AppendLine($"Progress: {RecipeInfo.MaxProgress}");
|
s.AppendLine($"Progress: {RecipeInfo.MaxProgress}");
|
||||||
s.AppendLine($"Quality: {RecipeInfo.MaxQuality}");
|
s.AppendLine($"Quality: {RecipeInfo.MaxQuality}");
|
||||||
s.AppendLine($"Starting Quality: {startingQuality}");
|
|
||||||
ImGui.Text(s.ToString());
|
ImGui.Text(s.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawCharacterInfo()
|
private void DrawCharacterInfo()
|
||||||
{
|
{
|
||||||
var classJob = (byte)Service.ClientState.LocalPlayer!.ClassJob.Id;
|
if (CharacterCannotCraftReason != CannotCraftReason.OK)
|
||||||
|
|
||||||
if (!ClassJobUtils.IsClassJob(classJob, RecipeClassJob))
|
|
||||||
{
|
{
|
||||||
ImGui.Text("Your current class cannot craft this recipe.");
|
ImGui.TextWrapped(GetCannotCraftReasonText(CharacterCannotCraftReason));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var container = InventoryManager.Instance()->GetInventoryContainer(InventoryType.EquippedItems);
|
ImGui.Text(GetCharacterStatsText(CharacterStatsConsumable));
|
||||||
if (container == null)
|
}
|
||||||
return;
|
|
||||||
|
|
||||||
var stats = Gearsets.CalculateCharacterStats(container, RecipeCharacterLevel, RecipeCanUseManipulation);
|
private void DrawCraftParameters()
|
||||||
|
{
|
||||||
|
ImGui.BeginDisabled(RecipeHQIngredientCount == 0);
|
||||||
|
var qualityNotches = QualityNotches;
|
||||||
|
ImGui.SetNextItemWidth(LeftSideWidth - 115);
|
||||||
|
if (ImGui.SliderInt("Starting Quality", ref qualityNotches, 0, RecipeHQIngredientCount, StartingQuality.ToString(), ImGuiSliderFlags.NoInput | ImGuiSliderFlags.AlwaysClamp))
|
||||||
|
QualityNotches = qualityNotches;
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
|
||||||
|
ImGui.BeginTable("craftfood", 2, ImGuiTableFlags.BordersInnerV);
|
||||||
|
|
||||||
|
ImGui.TableSetupColumn("", ImGuiTableColumnFlags.WidthFixed, LeftSideWidth - 120);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
|
||||||
|
if (ImGui.BeginCombo("Food", SelectedFood?.Name ?? "None"))
|
||||||
|
{
|
||||||
|
if (ImGui.Selectable("None", SelectedFood == null))
|
||||||
|
SelectedFood = null;
|
||||||
|
|
||||||
|
foreach (var food in FoodItems)
|
||||||
|
if (ImGui.Selectable(food.Name, food == SelectedFood))
|
||||||
|
SelectedFood = food;
|
||||||
|
|
||||||
|
ImGui.EndCombo();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.BeginCombo("Medicine", SelectedMedicine?.Name ?? "None"))
|
||||||
|
{
|
||||||
|
if (ImGui.Selectable("None", SelectedMedicine == null))
|
||||||
|
SelectedMedicine = null;
|
||||||
|
|
||||||
|
foreach (var food in MedicineItems)
|
||||||
|
if (ImGui.Selectable(food.Name, food == SelectedMedicine))
|
||||||
|
SelectedMedicine = food;
|
||||||
|
|
||||||
|
ImGui.EndCombo();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
|
||||||
var s = new StringBuilder();
|
var s = new StringBuilder();
|
||||||
s.AppendLine($"{RecipeClassJob.GetName()}");
|
s.AppendLine($"+{CharacterConsumableBonus.Craftsmanship} Craftsmanship");
|
||||||
s.AppendLine($"Level {stats.Level} (CLvl {stats.CLvl})");
|
s.AppendLine($"+{CharacterConsumableBonus.Control} Control");
|
||||||
s.AppendLine($"Craftsmanship {stats.Craftsmanship}");
|
s.AppendLine($"+{CharacterConsumableBonus.CP} CP");
|
||||||
s.AppendLine($"Control {stats.Control}");
|
|
||||||
s.AppendLine($"CP {stats.CP}");
|
|
||||||
if (stats.IsSpecialist)
|
|
||||||
s.AppendLine($" + Specialist");
|
|
||||||
if (stats.HasSplendorousBuff)
|
|
||||||
s.AppendLine($" + Splendorous");
|
|
||||||
ImGui.Text(s.ToString());
|
ImGui.Text(s.ToString());
|
||||||
|
|
||||||
|
ImGui.EndTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawCraftActions()
|
private void DrawMacros()
|
||||||
{
|
{
|
||||||
ImGui.Button("Open Simulator");
|
var padding = ImGui.GetStyle().FramePadding;
|
||||||
ImGui.Button("Generate a new macro");
|
var itemPadding = ImGui.GetStyle().ItemInnerSpacing;
|
||||||
|
|
||||||
|
var fontSize = ImGui.GetFontSize();
|
||||||
|
var height = fontSize + (padding.Y * 2);
|
||||||
|
var width = (ImGui.GetContentRegionAvail().X / 2) - itemPadding.X;
|
||||||
|
var size = new Vector2(width, height);
|
||||||
|
|
||||||
|
if (ImGui.Button("Open Simulator", size))
|
||||||
|
{
|
||||||
|
Service.Plugin.OpenSimulatorWindow(Recipe.ItemResult.Value!, CharacterSimulationInput, RecipeClassJob, new());
|
||||||
|
}
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Button("Generate a new macro", size);
|
||||||
|
|
||||||
|
ImGui.BeginTable("macrotable", 3, ImGuiTableFlags.BordersInner);
|
||||||
|
ImGui.TableSetupColumn("", ImGuiTableColumnFlags.WidthFixed, width);
|
||||||
|
foreach(var macro in Service.Configuration.Macros)
|
||||||
|
{
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TextUnformatted(macro.Name);
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
foreach (var action in macro.Actions)
|
||||||
|
{
|
||||||
|
ImGui.Image(action.GetIcon(RecipeClassJob).ImGuiHandle, new(fontSize * 2));
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
ImGui.Dummy(Vector2.Zero);
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text("Sim Results Here!");
|
||||||
|
}
|
||||||
|
ImGui.EndTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawGearsets()
|
private void DrawGearsets()
|
||||||
{
|
{
|
||||||
ImGui.Text("Available Gearsets");
|
ImGui.Text("Available Gearsets");
|
||||||
|
|
||||||
@@ -131,35 +341,26 @@ public unsafe class CraftingLog : Window
|
|||||||
if (!ClassJobUtils.IsClassJob(gearset->ClassJob, RecipeClassJob))
|
if (!ClassJobUtils.IsClassJob(gearset->ClassJob, RecipeClassJob))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var stats = Gearsets.CalculateCharacterStats(gearset, RecipeCharacterLevel, RecipeCanUseManipulation);
|
var items = Gearsets.GetGearsetItems(gearset);
|
||||||
|
var stats = Gearsets.CalculateCharacterStats(items, RecipeCharacterLevel, RecipeCanUseManipulation);
|
||||||
var gearsetId = gearset->ID + 1;
|
var gearsetId = gearset->ID + 1;
|
||||||
|
|
||||||
var s = new StringBuilder();
|
ImGuiUtils.BeginGroupPanel($"{SafeMemory.ReadString((nint)gearset->Name, 47)} ({gearsetId})");
|
||||||
s.AppendLine($"{SafeMemory.ReadString((nint)gearset->Name, 47)} ({gearsetId})");
|
ImGui.Text(GetCharacterStatsText(stats));
|
||||||
s.AppendLine($"Level {stats.Level} (CLvl {stats.CLvl})");
|
|
||||||
s.AppendLine($"Craftsmanship {stats.Craftsmanship}");
|
|
||||||
s.AppendLine($"Control {stats.Control}");
|
|
||||||
s.AppendLine($"CP {stats.CP}");
|
|
||||||
if (stats.IsSpecialist)
|
|
||||||
s.AppendLine($" + Specialist");
|
|
||||||
if (stats.HasSplendorousBuff)
|
|
||||||
s.AppendLine($" + Splendorous");
|
|
||||||
ImGui.Text(s.ToString());
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGuiComponents.IconButton($"SwapGearset{gearsetId}", FontAwesomeIcon.SyncAlt))
|
if (ImGuiComponents.IconButton($"SwapGearset{gearsetId}", FontAwesomeIcon.SyncAlt))
|
||||||
Chat.SendMessage($"/gearset change {gearsetId}");
|
Chat.SendMessage($"/gearset change {gearsetId}");
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
ImGui.SetTooltip($"Swap to gearset {gearsetId}");
|
ImGui.SetTooltip($"Swap to gearset {gearsetId}");
|
||||||
|
ImGuiUtils.EndGroupPanel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnNewRecipe()
|
|
||||||
{
|
|
||||||
startingQuality = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool DrawConditions()
|
public override bool DrawConditions()
|
||||||
{
|
{
|
||||||
|
if (Service.ClientState.LocalPlayer == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
Addon = (AddonRecipeNote*)Service.GameGui.GetAddonByName("RecipeNote");
|
Addon = (AddonRecipeNote*)Service.GameGui.GetAddonByName("RecipeNote");
|
||||||
|
|
||||||
if (Addon == null)
|
if (Addon == null)
|
||||||
@@ -180,8 +381,7 @@ public unsafe class CraftingLog : Window
|
|||||||
if (recipeEntry == null)
|
if (recipeEntry == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (RecipeId != recipeEntry->RecipeId)
|
var isNewRecipe = RecipeId != recipeEntry->RecipeId;
|
||||||
OnNewRecipe();
|
|
||||||
|
|
||||||
RecipeId = recipeEntry->RecipeId;
|
RecipeId = recipeEntry->RecipeId;
|
||||||
|
|
||||||
@@ -192,16 +392,21 @@ public unsafe class CraftingLog : Window
|
|||||||
|
|
||||||
Recipe = recipe;
|
Recipe = recipe;
|
||||||
|
|
||||||
RecipeInfo = SimulatorWindow.CreateRecipeInfo(Recipe);
|
|
||||||
|
|
||||||
if (!Addon->Unk258->IsVisible)
|
if (!Addon->Unk258->IsVisible)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (isNewRecipe)
|
||||||
|
{
|
||||||
|
QualityNotches = 0;
|
||||||
|
CalculateRecipeStats();
|
||||||
|
}
|
||||||
|
|
||||||
return base.DrawConditions();
|
return base.DrawConditions();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override unsafe void PreDraw()
|
public override unsafe void PreDraw()
|
||||||
{
|
{
|
||||||
|
Stopwatch.Restart();
|
||||||
ref var unit = ref Addon->AtkUnitBase;
|
ref var unit = ref Addon->AtkUnitBase;
|
||||||
var scale = unit.Scale;
|
var scale = unit.Scale;
|
||||||
var pos = new Vector2(unit.X, unit.Y);
|
var pos = new Vector2(unit.X, unit.Y);
|
||||||
@@ -210,14 +415,6 @@ public unsafe class CraftingLog : Window
|
|||||||
var node = (AtkResNode*)Addon->Unk458; // unit.GetNodeById(59);
|
var node = (AtkResNode*)Addon->Unk458; // unit.GetNodeById(59);
|
||||||
var nodeParent = Addon->Unk258; // unit.GetNodeById(57);
|
var nodeParent = Addon->Unk258; // unit.GetNodeById(57);
|
||||||
|
|
||||||
//for (var i = 544; i <= 1960; i += 8)
|
|
||||||
//{
|
|
||||||
// if (Marshal.ReadIntPtr((nint)Addon, i) == (nint)nodeParent)
|
|
||||||
// {
|
|
||||||
// PluginLog.LogDebug($"{i}");
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
Position = pos + new Vector2(size.X, (nodeParent->Y + node->Y) * scale);
|
Position = pos + new Vector2(size.X, (nodeParent->Y + node->Y) * scale);
|
||||||
SizeConstraints = new WindowSizeConstraints
|
SizeConstraints = new WindowSizeConstraints
|
||||||
{
|
{
|
||||||
@@ -225,6 +422,148 @@ public unsafe class CraftingLog : Window
|
|||||||
MaximumSize = new(10000, 10000)
|
MaximumSize = new(10000, 10000)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
CalculateCharacterStats();
|
||||||
|
|
||||||
base.PreDraw();
|
base.PreDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void PostDraw()
|
||||||
|
{
|
||||||
|
Stopwatch.Stop();
|
||||||
|
FrameTime = Stopwatch.Elapsed;
|
||||||
|
base.PostDraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Gearsets.GearsetStats CalculateConsumableBonus(CharacterStats stats)
|
||||||
|
{
|
||||||
|
static int CalculateBonus(int param, bool isHq, FoodStat? stat)
|
||||||
|
{
|
||||||
|
if (stat == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var foodStat = stat.Value;
|
||||||
|
var (value, max) = isHq ? (foodStat.ValueHQ, foodStat.MaxHQ) : (foodStat.Value, foodStat.Max);
|
||||||
|
|
||||||
|
if (!foodStat.IsRelative)
|
||||||
|
return value;
|
||||||
|
|
||||||
|
return Math.Min((int)MathF.Floor((float)value * param), max);
|
||||||
|
}
|
||||||
|
|
||||||
|
Gearsets.GearsetStats ret = new();
|
||||||
|
|
||||||
|
if (SelectedFood != null)
|
||||||
|
{
|
||||||
|
ret.CP += CalculateBonus(stats.CP, SelectedFoodHQ, SelectedFood.CP);
|
||||||
|
ret.Craftsmanship += CalculateBonus(stats.Craftsmanship, SelectedFoodHQ, SelectedFood.Craftsmanship);
|
||||||
|
ret.Control += CalculateBonus(stats.Control, SelectedFoodHQ, SelectedFood.Control);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SelectedMedicine != null)
|
||||||
|
{
|
||||||
|
ret.CP += CalculateBonus(stats.CP, SelectedMedicineHQ, SelectedMedicine.CP);
|
||||||
|
ret.Craftsmanship += CalculateBonus(stats.Craftsmanship, SelectedMedicineHQ, SelectedMedicine.Craftsmanship);
|
||||||
|
ret.Control += CalculateBonus(stats.Control, SelectedMedicineHQ, SelectedMedicine.Control);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CannotCraftReason
|
||||||
|
{
|
||||||
|
OK,
|
||||||
|
WrongClassJob,
|
||||||
|
SpecialistRequired,
|
||||||
|
RequiredItem,
|
||||||
|
RequiredStatus,
|
||||||
|
CraftsmanshipTooLow,
|
||||||
|
ControlTooLow,
|
||||||
|
}
|
||||||
|
|
||||||
|
private CannotCraftReason CanCraftRecipe(Gearsets.GearsetItem[] items, CharacterStats stats)
|
||||||
|
{
|
||||||
|
if (!ClassJobUtils.IsClassJob((byte)Service.ClientState.LocalPlayer!.ClassJob.Id, RecipeClassJob))
|
||||||
|
{
|
||||||
|
return CannotCraftReason.WrongClassJob;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Recipe.IsSpecializationRequired && !stats.IsSpecialist)
|
||||||
|
return CannotCraftReason.SpecialistRequired;
|
||||||
|
|
||||||
|
if (Recipe.ItemRequired.Row != 0)
|
||||||
|
{
|
||||||
|
if (Recipe.ItemRequired.Value != null)
|
||||||
|
{
|
||||||
|
if (!items.Any(i => Gearsets.IsItem(i, Recipe.ItemRequired.Row)))
|
||||||
|
{
|
||||||
|
return CannotCraftReason.RequiredItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Recipe.StatusRequired.Row != 0)
|
||||||
|
{
|
||||||
|
if (Recipe.StatusRequired.Value != null)
|
||||||
|
{
|
||||||
|
if (!Service.ClientState.LocalPlayer.StatusList.Any(s => s.StatusId == Recipe.StatusRequired.Row))
|
||||||
|
{
|
||||||
|
return CannotCraftReason.RequiredStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Recipe.RequiredCraftsmanship > stats.Craftsmanship)
|
||||||
|
return CannotCraftReason.CraftsmanshipTooLow;
|
||||||
|
|
||||||
|
if (Recipe.RequiredControl > stats.Control)
|
||||||
|
return CannotCraftReason.ControlTooLow;
|
||||||
|
|
||||||
|
return CannotCraftReason.OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RecipeInfo CreateRecipeInfo(Recipe recipe)
|
||||||
|
{
|
||||||
|
var recipeTable = recipe.RecipeLevelTable.Value!;
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
IsExpert = recipe.IsExpert,
|
||||||
|
ClassJobLevel = recipeTable.ClassJobLevel,
|
||||||
|
RLvl = (int)recipeTable.RowId,
|
||||||
|
ConditionsFlag = recipeTable.ConditionsFlag,
|
||||||
|
MaxDurability = recipeTable.Durability * recipe.DurabilityFactor / 100,
|
||||||
|
MaxQuality = (int)recipeTable.Quality * recipe.QualityFactor / 100,
|
||||||
|
MaxProgress = recipeTable.Difficulty * recipe.DifficultyFactor / 100,
|
||||||
|
QualityModifier = recipeTable.QualityModifier,
|
||||||
|
QualityDivider = recipeTable.QualityDivider,
|
||||||
|
ProgressModifier = recipeTable.ProgressModifier,
|
||||||
|
ProgressDivider = recipeTable.ProgressDivider,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetCannotCraftReasonText(CannotCraftReason reason) =>
|
||||||
|
reason switch
|
||||||
|
{
|
||||||
|
CannotCraftReason.OK => "You can craft this recipe.",
|
||||||
|
CannotCraftReason.WrongClassJob => "Your current class cannot craft this recipe.",
|
||||||
|
CannotCraftReason.SpecialistRequired => "You must be a specialist to craft this recipe.",
|
||||||
|
CannotCraftReason.RequiredItem => "You do not have the required item to craft this recipe.",
|
||||||
|
CannotCraftReason.RequiredStatus => "You do not have the required status effect to craft this recipe.",
|
||||||
|
CannotCraftReason.CraftsmanshipTooLow => "Your craftsmanship is too low to craft this recipe.",
|
||||||
|
CannotCraftReason.ControlTooLow => "Your control is too low to craft this recipe.",
|
||||||
|
_ => "Unknown reason.",
|
||||||
|
};
|
||||||
|
|
||||||
|
private static string GetCharacterStatsText(CharacterStats stats)
|
||||||
|
{
|
||||||
|
var s = new StringBuilder();
|
||||||
|
s.AppendLine($"Level {stats.Level} (CLvl {stats.CLvl})");
|
||||||
|
s.AppendLine($"Craftsmanship {stats.Craftsmanship}");
|
||||||
|
s.AppendLine($"Control {stats.Control}");
|
||||||
|
s.AppendLine($"CP {stats.CP}");
|
||||||
|
if (stats.IsSpecialist)
|
||||||
|
s.AppendLine($" + Specialist");
|
||||||
|
if (stats.HasSplendorousBuff)
|
||||||
|
s.AppendLine($" + Splendorous Tool");
|
||||||
|
return s.ToString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using Craftimizer.Simulator;
|
||||||
|
using Dalamud.Interface.Windowing;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using ImGuiNET;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Craftimizer.Plugin.Windows;
|
||||||
|
|
||||||
|
public class SettingsWindow : Window
|
||||||
|
{
|
||||||
|
private static Configuration Config => Service.Configuration;
|
||||||
|
|
||||||
|
public SettingsWindow() : base("Craftimizer")
|
||||||
|
{
|
||||||
|
Service.WindowSystem.AddWindow(this);
|
||||||
|
|
||||||
|
SizeConstraints = new WindowSizeConstraints()
|
||||||
|
{
|
||||||
|
MinimumSize = new Vector2(400, 400),
|
||||||
|
MaximumSize = new Vector2(float.MaxValue, float.MaxValue)
|
||||||
|
};
|
||||||
|
Size = SizeConstraints.Value.MinimumSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Draw()
|
||||||
|
{
|
||||||
|
var val = Config.OverrideUncraftability;
|
||||||
|
if (ImGui.Checkbox("Override Uncraftability Warning", ref val))
|
||||||
|
Config.OverrideUncraftability = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,391 @@
|
|||||||
|
using Craftimizer.Simulator;
|
||||||
|
using Craftimizer.Simulator.Actions;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.Windowing;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Dalamud.Utility;
|
||||||
|
using ImGuiNET;
|
||||||
|
using ImGuiScene;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using ActionCategory = Craftimizer.Simulator.ActionCategory;
|
||||||
|
using ClassJob = Craftimizer.Simulator.ClassJob;
|
||||||
|
|
||||||
|
namespace Craftimizer.Plugin.Windows;
|
||||||
|
|
||||||
|
public class SimulatorWindow : Window
|
||||||
|
{
|
||||||
|
private static readonly Vector2 ProgressBarSize = new(200, 20);
|
||||||
|
private static readonly Vector2 TooltipProgressBarSize = new(100, 5);
|
||||||
|
|
||||||
|
private static readonly Vector4 ProgressColor = new(.2f, 1f, .2f, 1f);
|
||||||
|
private static readonly Vector4 QualityColor = new(.2f, .2f, 1f, 1f);
|
||||||
|
private static readonly Vector4 DurabilityColor = new(1f, 1f, .2f, 1f);
|
||||||
|
private static readonly Vector4 CPColor = new(1f, .2f, 1f, 1f);
|
||||||
|
|
||||||
|
private static readonly Vector4 CPColorNew = new(0.38f, 0.77f, 1f, 1f);
|
||||||
|
|
||||||
|
private static readonly Vector4 BadActionImageTint = new(1f, .5f, .5f, 1f);
|
||||||
|
private static readonly Vector4 BadActionImageColor = new(1f, .3f, .3f, 1f);
|
||||||
|
|
||||||
|
private static readonly Vector4 BadActionTextColor = new(1f, .2f, .2f, 1f);
|
||||||
|
|
||||||
|
private static readonly (ActionCategory Category, ActionType[] Actions)[] SortedActions;
|
||||||
|
|
||||||
|
private TimeSpan FrameTime { get; set; }
|
||||||
|
private Stopwatch Stopwatch { get; } = new();
|
||||||
|
|
||||||
|
private Item Item { get; }
|
||||||
|
private SimulationInput Input { get; }
|
||||||
|
private ClassJob ClassJob { get; }
|
||||||
|
// State is the state of the simulation *after* its corresponding action is executed.
|
||||||
|
private List<(ActionType Action, string Tooltip, ActionResponse Response, SimulationState State)> Actions { get; }
|
||||||
|
private Simulator.Simulator Simulator { get; }
|
||||||
|
|
||||||
|
private SimulationState LatestState => Actions.Count == 0 ? new(Input) : Actions[^1].State;
|
||||||
|
|
||||||
|
private ActionType? DraggedAction { get; set; }
|
||||||
|
|
||||||
|
static SimulatorWindow()
|
||||||
|
{
|
||||||
|
SortedActions = Enum.GetValues<ActionType>().GroupBy(a => a.Category()).Select(g => (g.Key, g.OrderBy(a => a.Level()).ToArray())).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimulatorWindow(Item item, SimulationInput input, ClassJob classJob, List<ActionType> actions) : base("Simulator")
|
||||||
|
{
|
||||||
|
Service.WindowSystem.AddWindow(this);
|
||||||
|
|
||||||
|
Item = item;
|
||||||
|
Input = input;
|
||||||
|
ClassJob = classJob;
|
||||||
|
Actions = new();
|
||||||
|
Simulator = Service.Configuration.CreateSimulator(new(input));
|
||||||
|
|
||||||
|
foreach(var action in actions)
|
||||||
|
AppendAction(action);
|
||||||
|
|
||||||
|
IsOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void PreDraw()
|
||||||
|
{
|
||||||
|
Stopwatch.Restart();
|
||||||
|
|
||||||
|
base.PreDraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void PostDraw()
|
||||||
|
{
|
||||||
|
Stopwatch.Stop();
|
||||||
|
FrameTime = Stopwatch.Elapsed;
|
||||||
|
|
||||||
|
base.PostDraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Draw()
|
||||||
|
{
|
||||||
|
ImGui.BeginTable("simulatorWindow", 2, ImGuiTableFlags.Resizable);
|
||||||
|
|
||||||
|
ImGui.TableSetupColumn("", ImGuiTableColumnFlags.WidthFixed, 300);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
DrawActions();
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
DrawSimulationInfo();
|
||||||
|
|
||||||
|
ImGui.EndTable();
|
||||||
|
|
||||||
|
ImGui.TextUnformatted($"{FrameTime.TotalMilliseconds:0.00}ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawActions()
|
||||||
|
{
|
||||||
|
ImGui.BeginChild("CraftimizerActions", Vector2.Zero, true, ImGuiWindowFlags.NoDecoration);
|
||||||
|
//ImGui.Checkbox("Show only guaranteed actions", ref showOnlyGuaranteedActions);
|
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||||
|
|
||||||
|
var actionSize = new Vector2(ImGui.GetFontSize() * 2);
|
||||||
|
foreach (var (category, actions) in SortedActions)
|
||||||
|
{
|
||||||
|
var i = 0;
|
||||||
|
ImGuiUtils.BeginGroupPanel(category.GetDisplayName());
|
||||||
|
foreach (var action in actions)
|
||||||
|
{
|
||||||
|
var baseAction = action.Base();
|
||||||
|
|
||||||
|
var cannotUse = action.Level() > Input.Stats.Level || (action == ActionType.Manipulation && !Input.Stats.CanUseManipulation);
|
||||||
|
var shouldNotUse = !baseAction.CanUse(Simulator) || Simulator.IsComplete;
|
||||||
|
|
||||||
|
ImGui.BeginDisabled(cannotUse);
|
||||||
|
|
||||||
|
if (shouldNotUse)
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Button, BadActionImageColor);
|
||||||
|
|
||||||
|
if (ImGui.ImageButton(action.GetIcon(ClassJob).ImGuiHandle, actionSize, Vector2.Zero, Vector2.One, -1, default, shouldNotUse ? BadActionImageTint : Vector4.One))
|
||||||
|
AppendAction(action);
|
||||||
|
|
||||||
|
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
||||||
|
ImGui.SetTooltip($"{action.GetName(ClassJob)}\n{baseAction.GetTooltip(Simulator, true)}");
|
||||||
|
|
||||||
|
if (shouldNotUse)
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
|
||||||
|
if (++i % 5 != 0)
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
ImGuiUtils.EndGroupPanel();
|
||||||
|
}
|
||||||
|
ImGui.PopStyleVar();
|
||||||
|
ImGui.EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawSimulationInfo()
|
||||||
|
{
|
||||||
|
ImGui.BeginChild("simulationInfo", Vector2.Zero, true, ImGuiWindowFlags.NoDecoration);
|
||||||
|
DrawSimulationSynth();
|
||||||
|
ImGuiHelpers.ScaledDummy(5);
|
||||||
|
DrawSimulationEffects();
|
||||||
|
ImGuiHelpers.ScaledDummy(5);
|
||||||
|
DrawSimulationActions();
|
||||||
|
ImGui.EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawSimulationSynth()
|
||||||
|
{
|
||||||
|
var state = LatestState;
|
||||||
|
var imageSize = new Vector2(ImGui.GetFontSize() * 2f);
|
||||||
|
|
||||||
|
ImGui.Image(Icons.GetIconFromId(Item.Icon).ImGuiHandle, imageSize);
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.AlignTextToFramePadding();
|
||||||
|
ImGui.SetCursorPosY(ImGui.GetFontSize()*.75f);
|
||||||
|
ImGui.TextUnformatted(Item.Name.ToDalamudString().ToString());
|
||||||
|
var availWidth = ImGui.GetContentRegionAvail().X;
|
||||||
|
var text = $"Step {state.StepCount + 1}";
|
||||||
|
var textWidth = ImGui.CalcTextSize(text).X;
|
||||||
|
ImGui.SameLine(availWidth - textWidth);
|
||||||
|
ImGui.AlignTextToFramePadding();
|
||||||
|
ImGui.SetCursorPosY(ImGui.GetFontSize() * .75f);
|
||||||
|
ImGui.TextUnformatted(text);
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
ImGui.BeginTable("simSynth", 2);
|
||||||
|
|
||||||
|
ImGui.TableSetupColumn("", ImGuiTableColumnFlags.WidthFixed, 110);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGuiUtils.BeginGroupPanel("Durability");
|
||||||
|
ImGui.TextUnformatted($"{state.Durability} / {Input.Recipe.MaxDurability}");
|
||||||
|
DrawProgressBar(state.Durability, Input.Recipe.MaxDurability, new(100, 20), CPColorNew);
|
||||||
|
ImGuiUtils.EndGroupPanel();
|
||||||
|
|
||||||
|
ImGuiUtils.BeginGroupPanel("Condition");
|
||||||
|
ImGui.TextUnformatted(state.Condition.Name());
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip(state.Condition.Description(state.Input.Stats.HasSplendorousBuff));
|
||||||
|
ImGuiUtils.EndGroupPanel();
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
|
||||||
|
ImGuiUtils.BeginGroupPanel("Progress");
|
||||||
|
DrawProgressBar(state.Progress, Input.Recipe.MaxProgress, new(200, 20), ProgressColor);
|
||||||
|
availWidth = ImGui.GetContentRegionAvail().X;
|
||||||
|
text = $"{state.Progress} / {Input.Recipe.MaxProgress}";
|
||||||
|
textWidth = ImGui.CalcTextSize(text).X;
|
||||||
|
ImGui.SameLine(availWidth - textWidth - 10);
|
||||||
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - ((ImGui.GetFrameHeight() - ImGui.GetFontSize()) / 2f));
|
||||||
|
ImGui.TextUnformatted(text);
|
||||||
|
ImGuiUtils.EndGroupPanel();
|
||||||
|
|
||||||
|
ImGuiUtils.BeginGroupPanel("Quality");
|
||||||
|
DrawProgressBar(state.Quality, Input.Recipe.MaxQuality, new(200, 20), QualityColor);
|
||||||
|
availWidth = ImGui.GetContentRegionAvail().X;
|
||||||
|
text = $"{state.Quality} / {Input.Recipe.MaxQuality}";
|
||||||
|
textWidth = ImGui.CalcTextSize(text).X;
|
||||||
|
ImGui.SameLine(availWidth - textWidth - 10);
|
||||||
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - ((ImGui.GetFrameHeight() - ImGui.GetFontSize()) / 2f));
|
||||||
|
ImGui.TextUnformatted(text);
|
||||||
|
ImGuiUtils.EndGroupPanel();
|
||||||
|
|
||||||
|
ImGuiUtils.BeginGroupPanel("CP");
|
||||||
|
DrawProgressBar(state.CP, Input.Stats.CP, new(200, 20), CPColor);
|
||||||
|
availWidth = ImGui.GetContentRegionAvail().X;
|
||||||
|
text = $"{state.CP} / {Input.Stats.CP}";
|
||||||
|
textWidth = ImGui.CalcTextSize(text).X;
|
||||||
|
ImGui.SameLine(availWidth - textWidth - 10);
|
||||||
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - ((ImGui.GetFrameHeight() - ImGui.GetFontSize()) / 2f));
|
||||||
|
ImGui.TextUnformatted(text);
|
||||||
|
ImGuiUtils.EndGroupPanel();
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
ImGui.TextUnformatted($"HQ {state.HQPercent}%");
|
||||||
|
|
||||||
|
ImGui.EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawSimulationSynthOld()
|
||||||
|
{
|
||||||
|
var state = LatestState;
|
||||||
|
|
||||||
|
ImGui.Text($"Step {state.StepCount + 1}");
|
||||||
|
ImGui.Text(state.Condition.Name());
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip(state.Condition.Description(state.Input.Stats.HasSplendorousBuff));
|
||||||
|
ImGui.Text($"{state.HQPercent}%% HQ");
|
||||||
|
DrawProgressBarOld(state.Progress, Input.Recipe.MaxProgress, ProgressColor);
|
||||||
|
DrawProgressBarOld(state.Quality, Input.Recipe.MaxQuality, QualityColor);
|
||||||
|
DrawProgressBarOld(state.Durability, Input.Recipe.MaxDurability, DurabilityColor);
|
||||||
|
DrawProgressBarOld(state.CP, Input.Stats.CP, CPColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawSimulationEffects()
|
||||||
|
{
|
||||||
|
ImGui.Text($"Effects:");
|
||||||
|
|
||||||
|
var effectHeight = ImGui.GetFontSize() * 2f;
|
||||||
|
Vector2 GetEffectSize(TextureWrap icon) => new(icon.Width * effectHeight / icon.Height, effectHeight);
|
||||||
|
|
||||||
|
foreach (var effect in Enum.GetValues<EffectType>())
|
||||||
|
{
|
||||||
|
var duration = Simulator.GetEffectDuration(effect);
|
||||||
|
if (duration == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var strength = Simulator.GetEffectStrength(effect);
|
||||||
|
var icon = effect.GetIcon(strength);
|
||||||
|
|
||||||
|
ImGui.Image(icon.ImGuiHandle, GetEffectSize(icon));
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip(effect.GetTooltip(strength, duration));
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
ImGui.Dummy(Vector2.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawSimulationActions()
|
||||||
|
{
|
||||||
|
ImGui.Text($"Actions:");
|
||||||
|
|
||||||
|
var actionSize = new Vector2(ImGui.GetFontSize() * 2f);
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero);
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Zero);
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Zero);
|
||||||
|
for (var i = 0; i < Actions.Count; ++i)
|
||||||
|
{
|
||||||
|
var (action, tooltip, response, state) = Actions[i];
|
||||||
|
if (ImGui.ImageButton(action.GetIcon(ClassJob).ImGuiHandle, actionSize, Vector2.Zero, Vector2.One, 0, default, response != ActionResponse.UsedAction ? BadActionImageTint : Vector4.One))
|
||||||
|
RemoveAction(i);
|
||||||
|
if (ImGui.BeginDragDropSource())
|
||||||
|
{
|
||||||
|
unsafe { ImGui.SetDragDropPayload("simulationAction", (nint)(void*)&i, sizeof(int)); }
|
||||||
|
ImGui.ImageButton(action.GetIcon(ClassJob).ImGuiHandle, actionSize);
|
||||||
|
ImGui.EndDragDropSource();
|
||||||
|
}
|
||||||
|
if (ImGui.BeginDragDropTarget())
|
||||||
|
{
|
||||||
|
var payload = ImGui.AcceptDragDropPayload("simulationAction");
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
if (payload.NativePtr != null)
|
||||||
|
{
|
||||||
|
int droppedIdx;
|
||||||
|
droppedIdx = *(int*)payload.Data;
|
||||||
|
var droppedAction = Actions[droppedIdx].Action;
|
||||||
|
RemoveAction(droppedIdx);
|
||||||
|
InsertAction(i, droppedAction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
{
|
||||||
|
ImGui.BeginTooltip();
|
||||||
|
var responseText = response switch
|
||||||
|
{
|
||||||
|
ActionResponse.SimulationComplete => "Recipe Complete",
|
||||||
|
ActionResponse.ActionNotUnlocked => "Action Not Unlocked",
|
||||||
|
ActionResponse.NotEnoughCP => "Not Enough CP",
|
||||||
|
ActionResponse.NoDurability => "No More Durability",
|
||||||
|
ActionResponse.CannotUseAction => "Cannot Use",
|
||||||
|
_ => string.Empty,
|
||||||
|
};
|
||||||
|
if (response != ActionResponse.UsedAction)
|
||||||
|
ImGui.TextColored(BadActionTextColor, responseText);
|
||||||
|
ImGui.Text($"{action.GetName(ClassJob)}\n{tooltip}");
|
||||||
|
DrawProgressBarTooltip(state.Progress, Input.Recipe.MaxProgress, ProgressColor);
|
||||||
|
DrawProgressBarTooltip(state.Quality, Input.Recipe.MaxQuality, QualityColor);
|
||||||
|
DrawProgressBarTooltip(state.Durability, Input.Recipe.MaxDurability, DurabilityColor);
|
||||||
|
DrawProgressBarTooltip(state.CP, Input.Stats.CP, CPColor);
|
||||||
|
ImGui.Text("Right Click to Remove\nDrag to Move");
|
||||||
|
ImGui.EndTooltip();
|
||||||
|
}
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
ImGui.PopStyleColor(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AppendAction(ActionType action)
|
||||||
|
{
|
||||||
|
var tooltip = action.Base().GetTooltip(Simulator, false);
|
||||||
|
var (response, state) = Simulator.Execute(LatestState, action);
|
||||||
|
Actions.Add((action, tooltip, response, state));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveAction(int actionIndex)
|
||||||
|
{
|
||||||
|
// Remove action
|
||||||
|
Actions.RemoveAt(actionIndex);
|
||||||
|
|
||||||
|
// Take note of all actions afterwards
|
||||||
|
Span<ActionType> succeedingActions = stackalloc ActionType[Actions.Count - actionIndex];
|
||||||
|
for (var i = 0; i < succeedingActions.Length; i++)
|
||||||
|
succeedingActions[i] = Actions[i + actionIndex].Action;
|
||||||
|
|
||||||
|
// Remove all future actions
|
||||||
|
Actions.RemoveRange(actionIndex, succeedingActions.Length);
|
||||||
|
|
||||||
|
// Re-execute all future actions
|
||||||
|
foreach (var action in succeedingActions)
|
||||||
|
AppendAction(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InsertAction(int actionIndex, ActionType action)
|
||||||
|
{
|
||||||
|
// Take note of all actions afterwards
|
||||||
|
Span<ActionType> succeedingActions = stackalloc ActionType[Actions.Count - actionIndex];
|
||||||
|
for (var i = 0; i < succeedingActions.Length; i++)
|
||||||
|
succeedingActions[i] = Actions[i + actionIndex].Action;
|
||||||
|
|
||||||
|
// Remove all future actions
|
||||||
|
Actions.RemoveRange(actionIndex, succeedingActions.Length);
|
||||||
|
|
||||||
|
// Execute new action
|
||||||
|
AppendAction(action);
|
||||||
|
|
||||||
|
// Re-execute all future actions
|
||||||
|
foreach (var succeededAction in succeedingActions)
|
||||||
|
AppendAction(succeededAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawProgressBarTooltip(int progress, int maxProgress, Vector4 color)
|
||||||
|
{
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.PlotHistogram, color);
|
||||||
|
ImGui.ProgressBar(Math.Clamp((float)progress / maxProgress, 0f, 1f), TooltipProgressBarSize);
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawProgressBarOld(int progress, int maxProgress, Vector4 color) =>
|
||||||
|
DrawProgressBar(progress, maxProgress, ProgressBarSize, color, $"{progress} / {maxProgress}");
|
||||||
|
|
||||||
|
private static void DrawProgressBar(int progress, int maxProgress, Vector2 size, Vector4 color, string overlay = "")
|
||||||
|
{
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.PlotHistogram, color);
|
||||||
|
ImGui.ProgressBar(Math.Clamp((float)progress / maxProgress, 0f, 1f), size, overlay);
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user