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.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Craftimizer.Plugin;
|
||||
|
||||
[Serializable]
|
||||
public class Macro
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public List<ActionType> Actions { get; set; } = new();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Configuration : IPluginConfiguration
|
||||
{
|
||||
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<Materia> MateriaSheet = Service.DataManager.GetExcelSheet<Materia>()!;
|
||||
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.Simulator;
|
||||
using Craftimizer.Simulator.Actions;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.Plugin;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using System.Collections.Generic;
|
||||
using ClassJob = Craftimizer.Simulator.ClassJob;
|
||||
|
||||
namespace Craftimizer.Plugin;
|
||||
|
||||
@@ -10,22 +15,21 @@ public sealed class Plugin : IDalamudPlugin
|
||||
{
|
||||
public string Name => "Craftimizer";
|
||||
|
||||
public Configuration Configuration { get; }
|
||||
public WindowSystem WindowSystem { get; } = new("Craftimizer");
|
||||
public SimulatorWindow SimulatorWindow { get; }
|
||||
public WindowSystem WindowSystem { get; }
|
||||
public SettingsWindow SettingsWindow { get; }
|
||||
public CraftingLog RecipeNoteWindow { get; }
|
||||
public SimulatorWindow? SimulatorWindow { get; set; }
|
||||
|
||||
public Plugin(
|
||||
[RequiredVersion("1.0")] DalamudPluginInterface pluginInterface)
|
||||
public Plugin([RequiredVersion("1.0")] DalamudPluginInterface pluginInterface)
|
||||
{
|
||||
Service.Plugin = this;
|
||||
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();
|
||||
WindowSystem.AddWindow(RecipeNoteWindow);
|
||||
SettingsWindow = new();
|
||||
|
||||
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.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()
|
||||
@@ -47,6 +60,6 @@ public sealed class Plugin : IDalamudPlugin
|
||||
if (command != "/craft")
|
||||
return;
|
||||
|
||||
SimulatorWindow.IsOpen = true;
|
||||
SettingsWindow.IsOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using Dalamud.IoC;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Interface.Windowing;
|
||||
|
||||
namespace Craftimizer.Plugin;
|
||||
|
||||
@@ -22,5 +23,10 @@ public sealed class Service
|
||||
[PluginService] public static DataManager DataManager { get; private set; }
|
||||
[PluginService] public static TargetManager TargetManager { 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
|
||||
|
||||
}
|
||||
|
||||
@@ -17,12 +17,21 @@ namespace Craftimizer.Plugin;
|
||||
|
||||
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>();
|
||||
var classJobs = Enum.GetValues<ClassJob>();
|
||||
ActionRows = new (CraftAction? CraftAction, Action? Action)[actionTypes.Length, classJobs.Length];
|
||||
foreach (var actionType in actionTypes)
|
||||
{
|
||||
var actionId = actionType.Base().ActionId;
|
||||
if (LuminaSheets.CraftActionSheet.GetRow(actionId) is CraftAction baseCraftAction)
|
||||
{
|
||||
return (classJob switch
|
||||
foreach(var classJob in classJobs)
|
||||
{
|
||||
ActionRows[(int)actionType, (int)classJob] = (classJob switch
|
||||
{
|
||||
ClassJob.Carpenter => baseCraftAction.CRP.Value!,
|
||||
ClassJob.Blacksmith => baseCraftAction.BSM.Value!,
|
||||
@@ -35,18 +44,22 @@ internal static class ActionUtils
|
||||
_ => baseCraftAction
|
||||
}, null);
|
||||
}
|
||||
}
|
||||
if (LuminaSheets.ActionSheet.GetRow(actionId) is Action baseAction)
|
||||
{
|
||||
return (null,
|
||||
LuminaSheets.ActionSheet.First(r =>
|
||||
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) &&
|
||||
(r.ClassJobCategory.Value?.IsClassJob(classJob) ?? false)
|
||||
));
|
||||
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));
|
||||
}
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
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 const int ParamCraftsmanship = 70;
|
||||
private const int ParamControl = 71;
|
||||
private static readonly GearsetStats BaseStats = new(180, 0, 0);
|
||||
|
||||
public static CharacterStats CalculateCharacterStats(InventoryContainer* container, int characterLevel, bool canUseManipulation)
|
||||
{
|
||||
var stats = CalculateGearsetStats(container);
|
||||
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 const int ParamCP = 11;
|
||||
public const int ParamCraftsmanship = 70;
|
||||
public const int ParamControl = 71;
|
||||
|
||||
public static CharacterStats CalculateCharacterStats(RaptureGearsetModule.GearsetEntry* entry, int characterLevel, bool canUseManipulation)
|
||||
public static GearsetItem[] GetGearsetItems(InventoryContainer* container)
|
||||
{
|
||||
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;
|
||||
var items = new GearsetItem[(int)container->Size];
|
||||
for (var i = 0; i < container->Size; ++i)
|
||||
{
|
||||
var itemStats = CalculateGearsetItemStats(container->Items[i]);
|
||||
stats.CP += itemStats.CP;
|
||||
stats.Craftsmanship += itemStats.Craftsmanship;
|
||||
stats.Control += itemStats.Control;
|
||||
stats.HasSplendorous = stats.HasSplendorous || itemStats.HasSplendorous;
|
||||
stats.HasSpecialist = stats.HasSpecialist || itemStats.HasSpecialist;
|
||||
var item = container->Items[i];
|
||||
items[i] = new(item.ItemID, item.Flags.HasFlag(InventoryItem.ItemFlags.HQ), GetMaterias(item.Materia, item.MateriaGrade));
|
||||
}
|
||||
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,
|
||||
CalculateGearsetItemStats(entry->MainHand),
|
||||
CalculateGearsetItemStats(entry->OffHand),
|
||||
CalculateGearsetItemStats(entry->Head),
|
||||
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));
|
||||
var item = gearsetItems[i];
|
||||
items[i] = new(item.ItemID % 1000000, item.ItemID > 1000000, GetMaterias(item.Materia, item.MateriaGrade));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
private static (int CP, int Craftsmanship, int Control, bool HasSplendorous, bool HasSpecialist) CalculateGearsetItemStats(InventoryItem item) =>
|
||||
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)
|
||||
public static GearsetStats CalculateGearsetItemStats(GearsetItem gearsetItem)
|
||||
{
|
||||
var item = LuminaSheets.ItemSheet.GetRow(itemId)!;
|
||||
var item = LuminaSheets.ItemSheet.GetRow(gearsetItem.itemId)!;
|
||||
|
||||
int cp = 0, craftsmanship = 0, control = 0;
|
||||
|
||||
@@ -109,28 +60,63 @@ internal static unsafe class Gearsets
|
||||
|
||||
foreach (var statIncrease in item.UnkData59)
|
||||
IncreaseStat(statIncrease.BaseParam, statIncrease.BaseParamValue);
|
||||
|
||||
if (isHq)
|
||||
{
|
||||
if (gearsetItem.isHq)
|
||||
foreach (var statIncrease in item.UnkData73)
|
||||
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));
|
||||
craftsmanship = Math.Min(craftsmanship, CalculateParamCap(item, ParamCraftsmanship));
|
||||
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
|
||||
private static int CalculateParamCap(Item item, int paramId)
|
||||
{
|
||||
@@ -176,16 +162,11 @@ internal static unsafe class Gearsets
|
||||
return cap == 0 ? int.MaxValue : cap;
|
||||
}
|
||||
|
||||
private static bool IsSpecialistSoulCrystal(Item item) =>
|
||||
// Soul Crystal ItemUICategory DoH Category
|
||||
item.ItemUICategory.Row != 62 && item.ClassJobUse.Value!.ClassJobCategory.Row == 33;
|
||||
|
||||
private static bool IsSplendorousTool(uint itemId) =>
|
||||
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);
|
||||
// 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;
|
||||
private static GearsetMateria[] GetMaterias(ushort* types, byte* grades)
|
||||
{
|
||||
var materia = new GearsetMateria[5];
|
||||
for (var i = 0; i < 5; ++i)
|
||||
materia[i] = new(types[i], grades[i]);
|
||||
return materia;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
using Craftimizer.Plugin.Utils;
|
||||
using Craftimizer.Simulator;
|
||||
using Craftimizer.Simulator.Actions;
|
||||
using Dalamud;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Components;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Utility;
|
||||
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 FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using ImGuiNET;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
@@ -26,46 +29,189 @@ public unsafe class CraftingLog : Window
|
||||
{
|
||||
private const ImGuiWindowFlags WindowFlags = ImGuiWindowFlags.NoDecoration
|
||||
| ImGuiWindowFlags.AlwaysAutoResize
|
||||
| ImGuiWindowFlags.NoSavedSettings
|
||||
| ImGuiWindowFlags.NoFocusOnAppearing
|
||||
| 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 RecipeNote* State { get; set; }
|
||||
private ushort RecipeId { get; set; }
|
||||
private Recipe Recipe { get; set; } = null!;
|
||||
|
||||
// Set in CalculateRecipeStats (in DrawConditions)
|
||||
private RecipeLevelTable RecipeTable { 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;
|
||||
private short RecipeCharacterLevel => PlayerState.Instance()->ClassJobLevelArray[RecipeClassJob.GetClassJobIndex()];
|
||||
private bool RecipeCanUseManipulation => ActionManager.CanUseActionOnTarget(ActionType.Manipulation.GetId(RecipeClassJob), (GameObject*)Service.ClientState.LocalPlayer!.Address);
|
||||
private RecipeLevelTable RecipeTable => Recipe.RecipeLevelTable.Value!;
|
||||
// Set in CalculateCharacterStats (in PreDraw)
|
||||
private Gearsets.GearsetItem[] CharacterEquipment { get; set; } = null!;
|
||||
private CharacterStats CharacterStatsNoConsumable { get; set; } = null!;
|
||||
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)
|
||||
{
|
||||
Service.WindowSystem.AddWindow(this);
|
||||
|
||||
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()
|
||||
{
|
||||
if (Service.ClientState.LocalPlayer == null)
|
||||
return;
|
||||
ImGui.BeginTable("craftlog", 2, ImGuiTableFlags.BordersInnerV);
|
||||
|
||||
ImGui.TableSetupColumn("", ImGuiTableColumnFlags.WidthFixed, LeftSideWidth);
|
||||
ImGui.TableNextColumn();
|
||||
DrawCraftInfo();
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
DrawGearsets();
|
||||
|
||||
ImGui.EndTable();
|
||||
|
||||
ImGui.TextUnformatted($"{FrameTime.TotalMilliseconds:0.00}ms");
|
||||
}
|
||||
|
||||
void DrawCraftInfo()
|
||||
private void DrawCraftInfo()
|
||||
{
|
||||
DrawRecipeInfo();
|
||||
DrawCharacterInfo();
|
||||
ImGui.BeginTable("craftinfo", 2, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.SizingStretchSame);
|
||||
|
||||
DrawCraftActions();
|
||||
ImGui.TableNextColumn();
|
||||
DrawRecipeInfo();
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
DrawCharacterInfo();
|
||||
ImGui.EndTable();
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
DrawCraftParameters();
|
||||
DrawMacros();
|
||||
}
|
||||
|
||||
void DrawRecipeInfo()
|
||||
private void DrawRecipeInfo()
|
||||
{
|
||||
var s = new StringBuilder();
|
||||
s.AppendLine($"{RecipeClassJob.GetName()} {new string('★', RecipeTable.Stars)}");
|
||||
@@ -73,46 +219,110 @@ public unsafe class CraftingLog : Window
|
||||
s.AppendLine($"Durability: {RecipeInfo.MaxDurability}");
|
||||
s.AppendLine($"Progress: {RecipeInfo.MaxProgress}");
|
||||
s.AppendLine($"Quality: {RecipeInfo.MaxQuality}");
|
||||
s.AppendLine($"Starting Quality: {startingQuality}");
|
||||
ImGui.Text(s.ToString());
|
||||
}
|
||||
|
||||
void DrawCharacterInfo()
|
||||
private void DrawCharacterInfo()
|
||||
{
|
||||
var classJob = (byte)Service.ClientState.LocalPlayer!.ClassJob.Id;
|
||||
|
||||
if (!ClassJobUtils.IsClassJob(classJob, RecipeClassJob))
|
||||
if (CharacterCannotCraftReason != CannotCraftReason.OK)
|
||||
{
|
||||
ImGui.Text("Your current class cannot craft this recipe.");
|
||||
ImGui.TextWrapped(GetCannotCraftReasonText(CharacterCannotCraftReason));
|
||||
return;
|
||||
}
|
||||
|
||||
var container = InventoryManager.Instance()->GetInventoryContainer(InventoryType.EquippedItems);
|
||||
if (container == null)
|
||||
return;
|
||||
ImGui.Text(GetCharacterStatsText(CharacterStatsConsumable));
|
||||
}
|
||||
|
||||
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();
|
||||
s.AppendLine($"{RecipeClassJob.GetName()}");
|
||||
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");
|
||||
s.AppendLine($"+{CharacterConsumableBonus.Craftsmanship} Craftsmanship");
|
||||
s.AppendLine($"+{CharacterConsumableBonus.Control} Control");
|
||||
s.AppendLine($"+{CharacterConsumableBonus.CP} CP");
|
||||
ImGui.Text(s.ToString());
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
|
||||
void DrawCraftActions()
|
||||
private void DrawMacros()
|
||||
{
|
||||
ImGui.Button("Open Simulator");
|
||||
ImGui.Button("Generate a new macro");
|
||||
var padding = ImGui.GetStyle().FramePadding;
|
||||
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");
|
||||
|
||||
@@ -131,35 +341,26 @@ public unsafe class CraftingLog : Window
|
||||
if (!ClassJobUtils.IsClassJob(gearset->ClassJob, RecipeClassJob))
|
||||
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 s = new StringBuilder();
|
||||
s.AppendLine($"{SafeMemory.ReadString((nint)gearset->Name, 47)} ({gearsetId})");
|
||||
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());
|
||||
ImGuiUtils.BeginGroupPanel($"{SafeMemory.ReadString((nint)gearset->Name, 47)} ({gearsetId})");
|
||||
ImGui.Text(GetCharacterStatsText(stats));
|
||||
ImGui.SameLine();
|
||||
if (ImGuiComponents.IconButton($"SwapGearset{gearsetId}", FontAwesomeIcon.SyncAlt))
|
||||
Chat.SendMessage($"/gearset change {gearsetId}");
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip($"Swap to gearset {gearsetId}");
|
||||
ImGuiUtils.EndGroupPanel();
|
||||
}
|
||||
}
|
||||
|
||||
void OnNewRecipe()
|
||||
{
|
||||
startingQuality = 0;
|
||||
}
|
||||
|
||||
public override bool DrawConditions()
|
||||
{
|
||||
if (Service.ClientState.LocalPlayer == null)
|
||||
return false;
|
||||
|
||||
Addon = (AddonRecipeNote*)Service.GameGui.GetAddonByName("RecipeNote");
|
||||
|
||||
if (Addon == null)
|
||||
@@ -180,8 +381,7 @@ public unsafe class CraftingLog : Window
|
||||
if (recipeEntry == null)
|
||||
return false;
|
||||
|
||||
if (RecipeId != recipeEntry->RecipeId)
|
||||
OnNewRecipe();
|
||||
var isNewRecipe = RecipeId != recipeEntry->RecipeId;
|
||||
|
||||
RecipeId = recipeEntry->RecipeId;
|
||||
|
||||
@@ -192,16 +392,21 @@ public unsafe class CraftingLog : Window
|
||||
|
||||
Recipe = recipe;
|
||||
|
||||
RecipeInfo = SimulatorWindow.CreateRecipeInfo(Recipe);
|
||||
|
||||
if (!Addon->Unk258->IsVisible)
|
||||
return false;
|
||||
|
||||
if (isNewRecipe)
|
||||
{
|
||||
QualityNotches = 0;
|
||||
CalculateRecipeStats();
|
||||
}
|
||||
|
||||
return base.DrawConditions();
|
||||
}
|
||||
|
||||
public override unsafe void PreDraw()
|
||||
{
|
||||
Stopwatch.Restart();
|
||||
ref var unit = ref Addon->AtkUnitBase;
|
||||
var scale = unit.Scale;
|
||||
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 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);
|
||||
SizeConstraints = new WindowSizeConstraints
|
||||
{
|
||||
@@ -225,6 +422,148 @@ public unsafe class CraftingLog : Window
|
||||
MaximumSize = new(10000, 10000)
|
||||
};
|
||||
|
||||
CalculateCharacterStats();
|
||||
|
||||
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