biiiig changes

This commit is contained in:
Asriel Camora
2023-06-15 16:19:43 -07:00
parent 83abfba4d5
commit faa73d238c
34 changed files with 414 additions and 208 deletions
+1
View File
@@ -11,4 +11,5 @@ public static class LuminaSheets
public static readonly ExcelSheet<Action> ActionSheet = Service.DataManager.GetExcelSheet<Action>()!; public static readonly ExcelSheet<Action> ActionSheet = Service.DataManager.GetExcelSheet<Action>()!;
public static readonly ExcelSheet<CraftAction> CraftActionSheet = Service.DataManager.GetExcelSheet<CraftAction>()!; public static readonly ExcelSheet<CraftAction> CraftActionSheet = Service.DataManager.GetExcelSheet<CraftAction>()!;
public static readonly ExcelSheet<Status> StatusSheet = Service.DataManager.GetExcelSheet<Status>()!; public static readonly ExcelSheet<Status> StatusSheet = Service.DataManager.GetExcelSheet<Status>()!;
public static readonly ExcelSheet<Addon> AddonSheet = Service.DataManager.GetExcelSheet<Addon>()!;
} }
+19 -14
View File
@@ -2,6 +2,8 @@ using Craftimizer.Simulator;
using Craftimizer.Simulator.Actions; using Craftimizer.Simulator.Actions;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
using Dalamud.Logging;
using Dalamud.Utility;
using ImGuiNET; using ImGuiNET;
using System; using System;
using System.Linq; using System.Linq;
@@ -14,6 +16,8 @@ public class SimulatorWindow : Window
public Simulation Simulation { get; } public Simulation Simulation { get; }
public BaseAction[] AvailableActions { get; } public BaseAction[] AvailableActions { get; }
private bool showOnlyGuaranteedActions = true;
public SimulatorWindow() : base("Craftimizer") public SimulatorWindow() : base("Craftimizer")
{ {
SizeConstraints = new WindowSizeConstraints() SizeConstraints = new WindowSizeConstraints()
@@ -22,7 +26,7 @@ public class SimulatorWindow : Window
MaximumSize = new Vector2(float.MaxValue, float.MaxValue) MaximumSize = new Vector2(float.MaxValue, float.MaxValue)
}; };
Simulation = new(new CharacterStats { Craftsmanship = 4041, Control = 3905, CP = 609, Level = 90 }, LuminaSheets.RecipeSheet.GetRow(35573)!); Simulation = new(new CharacterStats { Craftsmanship = 4041, Control = 3905, CP = 609, Level = 90 }, LuminaSheets.RecipeSheet.GetRow(35499)!);
AvailableActions = BaseAction.Actions.Select(a => (Activator.CreateInstance(a, Simulation)! as BaseAction)!).ToArray(); AvailableActions = BaseAction.Actions.Select(a => (Activator.CreateInstance(a, Simulation)! as BaseAction)!).ToArray();
} }
@@ -32,18 +36,22 @@ public class SimulatorWindow : Window
ImGui.TableSetupColumn("CraftimizerActionsColumn", ImGuiTableColumnFlags.WidthFixed, 300); ImGui.TableSetupColumn("CraftimizerActionsColumn", ImGuiTableColumnFlags.WidthFixed, 300);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.BeginChild("CraftimizerActions", Vector2.Zero, true, ImGuiWindowFlags.NoDecoration); ImGui.BeginChild("CraftimizerActions", Vector2.Zero, true, ImGuiWindowFlags.NoDecoration);
ImGui.Checkbox("Show only guaranteed actions", ref showOnlyGuaranteedActions);
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero); ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
foreach(var category in AvailableActions.GroupBy(a => a.Category)) foreach(var category in AvailableActions.GroupBy(a => a.Category))
{ {
var i = 0; var i = 0;
ImGuiUtils.BeginGroupPanel(category.Key.ToString()); ImGuiUtils.BeginGroupPanel(category.Key.GetDisplayName());
foreach (var action in category) foreach (var action in category.OrderBy(a => a.Level))
{ {
if (showOnlyGuaranteedActions && !action.IsGuaranteedAction)
continue;
ImGui.BeginDisabled(!action.CanUse); ImGui.BeginDisabled(!action.CanUse);
if (ImGui.ImageButton(action.GetIcon(ClassJob.Carpenter).ImGuiHandle, new Vector2(ImGui.GetFontSize() * 2))) if (ImGui.ImageButton(action.GetIcon(ClassJob.Carpenter).ImGuiHandle, new Vector2(ImGui.GetFontSize() * 2)))
Simulation.Execute(action); Simulation.Execute(action);
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
ImGui.SetTooltip(action.Tooltip); ImGui.SetTooltip(action.GetTooltip(true));
ImGui.EndDisabled(); ImGui.EndDisabled();
if (++i % 5 != 0) if (++i % 5 != 0)
ImGui.SameLine(); ImGui.SameLine();
@@ -55,6 +63,9 @@ public class SimulatorWindow : Window
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.BeginChild("CraftimizerSimulator", Vector2.Zero, true, ImGuiWindowFlags.NoDecoration); ImGui.BeginChild("CraftimizerSimulator", Vector2.Zero, true, ImGuiWindowFlags.NoDecoration);
ImGui.Text($"Step {Simulation.StepCount + 1}"); ImGui.Text($"Step {Simulation.StepCount + 1}");
ImGui.Text(Simulation.Condition.Name());
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Simulation.Condition.Description(Simulation.Stats.HasRelic));
ImGui.Text($"{Simulation.HQPercent}%% HQ"); ImGui.Text($"{Simulation.HQPercent}%% HQ");
ImGui.PushStyleColor(ImGuiCol.PlotHistogram, new Vector4(.2f, 1f, .2f, 1f)); ImGui.PushStyleColor(ImGuiCol.PlotHistogram, new Vector4(.2f, 1f, .2f, 1f));
ImGui.ProgressBar(Math.Min((float)Simulation.Progress / Simulation.MaxProgress, 1f), new Vector2(200, 20), $"{Simulation.Progress} / {Simulation.MaxProgress}"); ImGui.ProgressBar(Math.Min((float)Simulation.Progress / Simulation.MaxProgress, 1f), new Vector2(200, 20), $"{Simulation.Progress} / {Simulation.MaxProgress}");
@@ -70,20 +81,14 @@ public class SimulatorWindow : Window
ImGui.PopStyleColor(); ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(5); ImGuiHelpers.ScaledDummy(5);
ImGui.Text($"Effects:"); ImGui.Text($"Effects:");
foreach (var (effect, strength, stepsLeft) in Simulation.ActiveEffects) foreach (var effect in Simulation.ActiveEffects)
{ {
var status = effect.Status(); var icon = effect.Icon;
var icon = Icons.GetIconFromId((ushort)status.Icon);
var h = ImGui.GetFontSize() * 1.25f; var h = ImGui.GetFontSize() * 1.25f;
var w = icon.Width * h / icon.Height; var w = icon.Width * h / icon.Height;
ImGui.Image(icon.ImGuiHandle, new Vector2(w, h)); ImGui.Image(icon.ImGuiHandle, new Vector2(w, h));
if (ImGui.IsItemHovered())
ImGui.SetTooltip(status.Name.ToString());
ImGui.SameLine(); ImGui.SameLine();
if (stepsLeft < 0) ImGui.Text(effect.Tooltip);
ImGui.Text($"{strength}");
else
ImGui.Text($"> {stepsLeft}");
} }
ImGuiHelpers.ScaledDummy(5); ImGuiHelpers.ScaledDummy(5);
{ {
@@ -92,7 +97,7 @@ public class SimulatorWindow : Window
{ {
ImGui.Image(action.GetIcon(ClassJob.Carpenter).ImGuiHandle, new Vector2(ImGui.GetFontSize() * 2f)); ImGui.Image(action.GetIcon(ClassJob.Carpenter).ImGuiHandle, new Vector2(ImGui.GetFontSize() * 2f));
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
ImGui.SetTooltip(action.GetName(ClassJob.Carpenter)); ImGui.SetTooltip(action.GetTooltip(false));
if (++i % 5 != 0) if (++i % 5 != 0)
ImGui.SameLine(); ImGui.SameLine();
} }
+15
View File
@@ -9,3 +9,18 @@ public enum ActionCategory
Buffs, Buffs,
Other Other
} }
internal static class ActionCategoryUtils
{
public static string GetDisplayName(this ActionCategory category) =>
category switch
{
ActionCategory.FirstTurn => "First Turn",
ActionCategory.Synthesis => "Synthesis",
ActionCategory.Quality => "Quality",
ActionCategory.Durability => "Durability",
ActionCategory.Buffs => "Buffs",
ActionCategory.Other => "Other",
_ => category.ToString()
};
}
@@ -8,7 +8,7 @@ internal class AdvancedTouch : BaseAction
public override int Level => 84; public override int Level => 84;
public override int ActionId => 100411; public override int ActionId => 100411;
public override int CPCost => Simulation.GetPreviousAction() is StandardTouch && Simulation.GetPreviousAction(2) is BasicTouch ? 18 : 46; public override int CPCost => Simulation.IsPreviousAction<StandardTouch>() && Simulation.IsPreviousAction<BasicTouch>(2) ? 18 : 46;
public override float Efficiency => 1.50f; public override float Efficiency => 1.50f;
public override bool IncreasesQuality => true; public override bool IncreasesQuality => true;
} }
+11 -11
View File
@@ -1,4 +1,5 @@
using Craftimizer.Plugin; using Craftimizer.Plugin;
using Dalamud.Utility;
using ImGuiScene; using ImGuiScene;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
using System; using System;
@@ -32,6 +33,7 @@ public abstract class BaseAction
public virtual float SuccessRate => 1f; public virtual float SuccessRate => 1f;
public virtual int DurabilityCost => 10; public virtual int DurabilityCost => 10;
public virtual bool IncreasesStepCount => true; public virtual bool IncreasesStepCount => true;
public virtual bool IsGuaranteedAction => SuccessRate == 1f;
private (CraftAction? CraftAction, Action? Action) GetActionRow(ClassJob classJob) private (CraftAction? CraftAction, Action? Action) GetActionRow(ClassJob classJob)
{ {
@@ -67,9 +69,9 @@ public abstract class BaseAction
{ {
var (craftAction, action) = GetActionRow(classJob); var (craftAction, action) = GetActionRow(classJob);
if (craftAction != null) if (craftAction != null)
return craftAction.Name; return craftAction.Name.ToDalamudString().TextValue;
else if (action != null) else if (action != null)
return action.Name; return action.Name.ToDalamudString().TextValue;
return "Unknown"; return "Unknown";
} }
@@ -92,7 +94,7 @@ public abstract class BaseAction
Simulation.ReduceCP(CPCost); Simulation.ReduceCP(CPCost);
Simulation.ReduceDurability(DurabilityCost); Simulation.ReduceDurability(DurabilityCost);
if (Simulation.HasEffect(Effect.Manipulation)) if (Simulation.HasEffect(EffectType.Manipulation))
Simulation.RestoreDurability(5); Simulation.RestoreDurability(5);
if (Simulation.RollSuccess(SuccessRate)) if (Simulation.RollSuccess(SuccessRate))
@@ -113,18 +115,17 @@ public abstract class BaseAction
} }
} }
public virtual string Tooltip public virtual string GetTooltip(bool addUsability)
{
get
{ {
var builder = new StringBuilder(); var builder = new StringBuilder();
builder.AppendLine(GetName(ClassJob.Carpenter)); builder.AppendLine(GetName(ClassJob.Carpenter));
if (!CanUse) if (addUsability && !CanUse)
builder.AppendLine($"Cannot Use"); builder.AppendLine($"Cannot Use");
builder.AppendLine($"Level {Level}"); builder.AppendLine($"Level {Level}");
builder.AppendLine($"CP Cost: {CPCost}"); if (CPCost != 0)
builder.AppendLine($"-{Simulation.CalculateCPCost(CPCost)} CP");
if (DurabilityCost != 0) if (DurabilityCost != 0)
builder.AppendLine($"Durability Cost: {DurabilityCost}"); builder.AppendLine($"-{Simulation.CalculateDurabilityCost(DurabilityCost)} Durability");
if (Efficiency != 0) if (Efficiency != 0)
{ {
if (IncreasesProgress) if (IncreasesProgress)
@@ -135,8 +136,7 @@ public abstract class BaseAction
if (!IncreasesStepCount) if (!IncreasesStepCount)
builder.AppendLine($"Does Not Increase Step Count"); builder.AppendLine($"Does Not Increase Step Count");
if (SuccessRate != 1f) if (SuccessRate != 1f)
builder.AppendLine($"{SuccessRate * 100}%% Success Rate"); builder.AppendLine($"{Simulation.CalculateSuccessRate(SuccessRate) * 100}%% Success Rate");
return builder.ToString(); return builder.ToString();
} }
} }
}
@@ -8,8 +8,7 @@ public abstract class BaseBuffAction : BaseAction
public BaseBuffAction(Simulation simulation) : base(simulation) { } public BaseBuffAction(Simulation simulation) : base(simulation) { }
public abstract Effect Effect { get; } public abstract Effect Effect { get; }
public abstract int EffectDuration { get; } public virtual EffectType[] ConflictingEffects => Array.Empty<EffectType>();
public virtual Effect[] ConflictingEffects => Array.Empty<Effect>();
public override int DurabilityCost => 0; public override int DurabilityCost => 0;
@@ -18,18 +17,13 @@ public abstract class BaseBuffAction : BaseAction
if (ConflictingEffects.Length != 0) if (ConflictingEffects.Length != 0)
foreach(var effect in ConflictingEffects) foreach(var effect in ConflictingEffects)
Simulation.RemoveEffect(effect); Simulation.RemoveEffect(effect);
Simulation.AddEffect(Effect, EffectDuration); Simulation.AddEffect(Effect.Type, Effect.Duration, Effect.Strength);
} }
public override string Tooltip { public override string GetTooltip(bool addUsability)
get
{ {
var builder = new StringBuilder(base.Tooltip); var builder = new StringBuilder(base.GetTooltip(addUsability));
builder.AppendLine($"Effect: {Effect.Status().Name}"); builder.AppendLine($"Effect: {Effect.Tooltip}");
builder.AppendLine($"Duration: {EffectDuration} steps");
foreach(var effect in ConflictingEffects)
builder.AppendLine($"Conflicts with: {effect.Status().Name}");
return builder.ToString(); return builder.ToString();
} }
} }
}
@@ -9,14 +9,14 @@ internal class ByregotsBlessing : BaseAction
public override int ActionId => 100339; public override int ActionId => 100339;
public override int CPCost => 24; public override int CPCost => 24;
public override float Efficiency => 1.00f + (0.20f * (Simulation.GetEffect(Effect.InnerQuiet)?.Strength ?? 0)); public override float Efficiency => 1.00f + (0.20f * (Simulation.GetEffect(EffectType.InnerQuiet)?.Strength ?? 0));
public override bool IncreasesQuality => true; public override bool IncreasesQuality => true;
public override bool CanUse => Simulation.HasEffect(Effect.InnerQuiet) && base.CanUse; public override bool CanUse => Simulation.HasEffect(EffectType.InnerQuiet) && base.CanUse;
public override void UseSuccess() public override void UseSuccess()
{ {
base.UseSuccess(); base.UseSuccess();
Simulation.RemoveEffect(Effect.InnerQuiet); Simulation.RemoveEffect(EffectType.InnerQuiet);
} }
} }
@@ -0,0 +1,19 @@
namespace Craftimizer.Simulator.Actions;
internal class CarefulObservation : BaseAction
{
public CarefulObservation(Simulation simulation) : base(simulation) { }
public override ActionCategory Category => ActionCategory.Other;
public override int Level => 55;
public override int ActionId => 100395;
public override int CPCost => 0;
public override int DurabilityCost => 0;
public override bool IncreasesStepCount => false;
public override bool CanUse => Simulation.Stats.IsSpecialist && Simulation.CountPreviousAction<CarefulObservation>() < 3;
public override void UseSuccess() =>
Simulation.StepCondition();
}
@@ -11,6 +11,5 @@ internal class FinalAppraisal : BaseBuffAction
public override int CPCost => 1; public override int CPCost => 1;
public override bool IncreasesStepCount => false; public override bool IncreasesStepCount => false;
public override Effect Effect => Effect.FinalAppraisal; public override Effect Effect => new() { Type = EffectType.FinalAppraisal, Duration = 5 };
public override int EffectDuration => 5;
} }
@@ -11,5 +11,5 @@ internal class FocusedSynthesis : BaseAction
public override int CPCost => 5; public override int CPCost => 5;
public override float Efficiency => 2.00f; public override float Efficiency => 2.00f;
public override bool IncreasesProgress => true; public override bool IncreasesProgress => true;
public override float SuccessRate => Simulation.GetPreviousAction() is Observe ? 1.00f : 0.50f; public override float SuccessRate => Simulation.IsPreviousAction<Observe>() ? 1.00f : 0.50f;
} }
@@ -11,5 +11,5 @@ internal class FocusedTouch : BaseAction
public override int CPCost => 18; public override int CPCost => 18;
public override float Efficiency => 1.50f; public override float Efficiency => 1.50f;
public override bool IncreasesQuality => true; public override bool IncreasesQuality => true;
public override float SuccessRate => Simulation.GetPreviousAction() is Observe ? 1.00f : 0.50f; public override float SuccessRate => Simulation.IsPreviousAction<Observe>() ? 1.00f : 0.50f;
} }
@@ -10,6 +10,5 @@ internal class GreatStrides : BaseBuffAction
public override int CPCost => 32; public override int CPCost => 32;
public override Effect Effect => Effect.GreatStrides; public override Effect Effect => new() { Type = EffectType.GreatStrides, Duration = 3 };
public override int EffectDuration => 3;
} }
@@ -15,6 +15,7 @@ internal class Groundwork : BaseAction
get get
{ {
var ret = Simulation.Stats.Level >= 86 ? 3.60f : 3.00f; var ret = Simulation.Stats.Level >= 86 ? 3.60f : 3.00f;
// TODO: does not account for waste not
return Simulation.Durability < DurabilityCost ? ret / 2 : ret; return Simulation.Durability < DurabilityCost ? ret / 2 : ret;
} }
} }
@@ -0,0 +1,17 @@
namespace Craftimizer.Simulator.Actions;
internal class HeartAndSoul : BaseBuffAction
{
public HeartAndSoul(Simulation simulation) : base(simulation) { }
public override ActionCategory Category => ActionCategory.Other;
public override int Level => 86;
public override int ActionId => 100419;
public override int CPCost => 0;
public override bool IncreasesStepCount => false;
public override Effect Effect => new() { Type = EffectType.HeartAndSoul };
public override bool CanUse => Simulation.Stats.IsSpecialist && Simulation.CountPreviousAction<HeartAndSoul>() == 0;
}
+1 -2
View File
@@ -10,6 +10,5 @@ internal class Innovation : BaseBuffAction
public override int CPCost => 18; public override int CPCost => 18;
public override Effect Effect => Effect.Innovation; public override Effect Effect => new() { Type = EffectType.Innovation, Duration = 4 };
public override int EffectDuration => 4;
} }
@@ -11,8 +11,16 @@ internal class IntensiveSynthesis : BaseAction
public override int CPCost => 6; public override int CPCost => 6;
public override float Efficiency => 4.00f; public override float Efficiency => 4.00f;
public override bool IncreasesProgress => true; public override bool IncreasesProgress => true;
public override bool IsGuaranteedAction => false;
public override bool CanUse => public override bool CanUse =>
(Simulation.Condition == Condition.Good || Simulation.Condition == Condition.Excellent) (Simulation.Condition == Condition.Good || Simulation.Condition == Condition.Excellent || Simulation.HasEffect(EffectType.HeartAndSoul))
&& base.CanUse; && base.CanUse;
public override void UseSuccess()
{
base.UseSuccess();
if (Simulation.Condition != Condition.Good && Simulation.Condition != Condition.Excellent)
Simulation.RemoveEffect(EffectType.HeartAndSoul);
}
} }
@@ -10,6 +10,5 @@ internal class Manipulation : BaseBuffAction
public override int CPCost => 96; public override int CPCost => 96;
public override Effect Effect => Effect.Manipulation; public override Effect Effect => new() { Type = EffectType.Manipulation, Duration = 8 };
public override int EffectDuration => 8;
} }
@@ -17,6 +17,6 @@ internal class MuscleMemory : BaseAction
public override void UseSuccess() public override void UseSuccess()
{ {
base.UseSuccess(); base.UseSuccess();
Simulation.AddEffect(Effect.MuscleMemory, 5); Simulation.AddEffect(EffectType.MuscleMemory, 5);
} }
} }
@@ -11,14 +11,17 @@ internal class PreciseTouch : BaseAction
public override int CPCost => 18; public override int CPCost => 18;
public override float Efficiency => 1.50f; public override float Efficiency => 1.50f;
public override bool IncreasesQuality => true; public override bool IncreasesQuality => true;
public override bool IsGuaranteedAction => false;
public override bool CanUse => public override bool CanUse =>
(Simulation.Condition == Condition.Good || Simulation.Condition == Condition.Excellent) (Simulation.Condition == Condition.Good || Simulation.Condition == Condition.Excellent || Simulation.HasEffect(EffectType.HeartAndSoul))
&& base.CanUse; && base.CanUse;
public override void UseSuccess() public override void UseSuccess()
{ {
base.UseSuccess(); base.UseSuccess();
Simulation.StrengthenEffect(Effect.InnerQuiet); Simulation.StrengthenEffect(EffectType.InnerQuiet);
if (Simulation.Condition != Condition.Good && Simulation.Condition != Condition.Excellent)
Simulation.RemoveEffect(EffectType.HeartAndSoul);
} }
} }
@@ -16,6 +16,6 @@ internal class PreparatoryTouch : BaseAction
public override void UseSuccess() public override void UseSuccess()
{ {
base.UseSuccess(); base.UseSuccess();
Simulation.StrengthenEffect(Effect.InnerQuiet); Simulation.StrengthenEffect(EffectType.InnerQuiet);
} }
} }
@@ -14,6 +14,6 @@ internal class PrudentSynthesis : BaseAction
public override int DurabilityCost => base.DurabilityCost / 2; public override int DurabilityCost => base.DurabilityCost / 2;
public override bool CanUse => public override bool CanUse =>
!(Simulation.HasEffect(Effect.WasteNot) || Simulation.HasEffect(Effect.WasteNot2)) !(Simulation.HasEffect(EffectType.WasteNot) || Simulation.HasEffect(EffectType.WasteNot2))
&& base.CanUse; && base.CanUse;
} }
@@ -14,6 +14,6 @@ internal class PrudentTouch : BaseAction
public override int DurabilityCost => base.DurabilityCost / 2; public override int DurabilityCost => base.DurabilityCost / 2;
public override bool CanUse => public override bool CanUse =>
!(Simulation.HasEffect(Effect.WasteNot) || Simulation.HasEffect(Effect.WasteNot2)) !(Simulation.HasEffect(EffectType.WasteNot) || Simulation.HasEffect(EffectType.WasteNot2))
&& base.CanUse; && base.CanUse;
} }
+1 -1
View File
@@ -17,6 +17,6 @@ internal class Reflect : BaseAction
public override void UseSuccess() public override void UseSuccess()
{ {
base.UseSuccess(); base.UseSuccess();
Simulation.StrengthenEffect(Effect.InnerQuiet); Simulation.StrengthenEffect(EffectType.InnerQuiet);
} }
} }
@@ -8,7 +8,7 @@ internal class StandardTouch : BaseAction
public override int Level => 18; public override int Level => 18;
public override int ActionId => 100004; public override int ActionId => 100004;
public override int CPCost => Simulation.GetPreviousAction() is BasicTouch ? 18 : 32; public override int CPCost => Simulation.IsPreviousAction<BasicTouch>() ? 18 : 32;
public override float Efficiency => 1.25f; public override float Efficiency => 1.25f;
public override bool IncreasesQuality => true; public override bool IncreasesQuality => true;
} }
@@ -14,6 +14,6 @@ internal class TrainedFinesse : BaseAction
public override int DurabilityCost => 0; public override int DurabilityCost => 0;
public override bool CanUse => public override bool CanUse =>
(Simulation.GetEffect(Effect.InnerQuiet)?.Strength ?? 0) == 10 (Simulation.GetEffect(EffectType.InnerQuiet)?.Strength ?? 0) == 10
&& base.CanUse; && base.CanUse;
} }
@@ -10,11 +10,16 @@ internal class TricksOfTheTrade : BaseAction
public override int CPCost => 0; public override int CPCost => 0;
public override int DurabilityCost => 0; public override int DurabilityCost => 0;
public override bool IsGuaranteedAction => false;
public override bool CanUse => public override bool CanUse =>
(Simulation.Condition == Condition.Good || Simulation.Condition == Condition.Excellent) (Simulation.Condition == Condition.Good || Simulation.Condition == Condition.Excellent || Simulation.HasEffect(EffectType.HeartAndSoul))
&& base.CanUse; && base.CanUse;
public override void UseSuccess() => public override void UseSuccess()
{
Simulation.RestoreCP(20); Simulation.RestoreCP(20);
if (Simulation.Condition != Condition.Good && Simulation.Condition != Condition.Excellent)
Simulation.RemoveEffect(EffectType.HeartAndSoul);
}
} }
+1 -2
View File
@@ -11,6 +11,5 @@ internal class Veneration : BaseBuffAction
public override int CPCost => 18; public override int CPCost => 18;
public override int DurabilityCost => 0; public override int DurabilityCost => 0;
public override Effect Effect => Effect.Veneration; public override Effect Effect => new() { Type = EffectType.Veneration, Duration = 4 };
public override int EffectDuration => 4;
} }
+2 -3
View File
@@ -10,7 +10,6 @@ internal class WasteNot : BaseBuffAction
public override int CPCost => 56; public override int CPCost => 56;
public override Effect Effect => Effect.WasteNot; public override Effect Effect => new() { Type = EffectType.WasteNot, Duration = 4 };
public override int EffectDuration => 4; public override EffectType[] ConflictingEffects => new[] { EffectType.WasteNot2 };
public override Effect[] ConflictingEffects => new[] { Effect.WasteNot2 };
} }
+2 -3
View File
@@ -10,7 +10,6 @@ internal class WasteNot2 : BaseBuffAction
public override int CPCost => 98; public override int CPCost => 98;
public override Effect Effect => Effect.WasteNot2; public override Effect Effect => new() { Type = EffectType.WasteNot2, Duration = 8 };
public override int EffectDuration => 8; public override EffectType[] ConflictingEffects => new[] { EffectType.WasteNot };
public override Effect[] ConflictingEffects => new[] { Effect.WasteNot };
} }
+2
View File
@@ -9,6 +9,8 @@ public record CharacterStats
public int Control { get; init; } public int Control { get; init; }
public int CP { get; init; } public int CP { get; init; }
public int Level { get; init; } public int Level { get; init; }
public bool HasRelic { get; init; }
public bool IsSpecialist { get; init; }
public int CLvl => Level <= 80 public int CLvl => Level <= 80
? LuminaSheets.ParamGrowSheet.GetRow((uint)Level)!.CraftingLevel ? LuminaSheets.ParamGrowSheet.GetRow((uint)Level)!.CraftingLevel
+55 -10
View File
@@ -1,15 +1,60 @@
using Craftimizer.Plugin;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Utility;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Craftimizer.Simulator; namespace Craftimizer.Simulator;
public enum Condition public enum Condition : ushort
{ {
Poor, Poor = 0x0008,
Normal, Normal = 0x0001,
Good, Good = 0x0002,
Excellent, Excellent = 0x0004,
Centered = 0x0010,
Sturdy = 0x0020,
Pliant = 0x0040,
Malleable = 0x0080,
Primed = 0x0100,
GoodOmen = 0x0200,
}
internal static class ConditionUtils
{
public static Condition[] GetPossibleConditions(ushort conditionsFlag) =>
Enum.GetValues<Condition>().Where(c => ((Condition)conditionsFlag).HasFlag(c)).ToArray();
public static (uint Name, uint Description) AddonIds(this Condition me) =>
me switch
{
Condition.Poor => (229, 14203),
Condition.Normal => (226, 14200),
Condition.Good => (227, 14201),
Condition.Excellent => (228, 14202),
Condition.Centered => (239, 14204),
Condition.Sturdy => (240, 14205),
Condition.Pliant => (241, 14206),
Condition.Malleable => (13455, 14208),
Condition.Primed => (13454, 14207),
Condition.GoodOmen => (14214, 14215),
_ => (226, 14200) // Unknown
};
public static string Name(this Condition me) =>
LuminaSheets.AddonSheet.GetRow(me.AddonIds().Name)!.Text.ToDalamudString().TextValue;
public static string Description(this Condition me, bool isRelic)
{
var text = LuminaSheets.AddonSheet.GetRow(me.AddonIds().Description)!.Text.ToDalamudString();
for (var i = 0; i < text.Payloads.Count; ++i)
if (text.Payloads[i] is RawPayload)
text.Payloads[i] = new TextPayload(isRelic ? "1.75" : "1.5");
return text.TextValue;
}
//Centered,
//Sturdy,
//Pliant,
//Malleable,
//Primed
} }
+30 -29
View File
@@ -1,38 +1,39 @@
using Craftimizer.Plugin; using Craftimizer.Plugin;
using Lumina.Excel.GeneratedSheets; using Dalamud.Utility;
using ImGuiScene;
using System;
using System.Text;
namespace Craftimizer.Simulator; namespace Craftimizer.Simulator;
public enum Effect public record Effect
{ {
InnerQuiet, public EffectType Type { get; init; }
WasteNot, public int? Duration { get; set; }
Veneration, public int? Strength { get; set; }
GreatStrides,
Innovation, public ushort IconId { get
FinalAppraisal, {
WasteNot2, var status = Type.Status();
MuscleMemory, var iconId = status.Icon;
Manipulation, if (status.MaxStacks != 0 && Strength != null)
iconId += (uint)Math.Clamp(Strength.Value, 1, status.MaxStacks) - 1;
return (ushort)iconId;
}
} }
internal static class EffectExtensions public TextureWrap Icon => Icons.GetIconFromId(IconId);
{
public static uint StatusId(this Effect me) =>
me switch
{
Effect.InnerQuiet => 251,
Effect.WasteNot => 252,
Effect.Veneration => 2226,
Effect.GreatStrides => 254,
Effect.Innovation => 2189,
Effect.FinalAppraisal => 2190,
Effect.WasteNot2 => 257,
Effect.MuscleMemory => 2191,
Effect.Manipulation => 258,
_ => 3412,
};
public static Status Status(this Effect me) => public string Tooltip { get
LuminaSheets.StatusSheet.GetRow(me.StatusId())!; {
var status = Type.Status();
var name = new StringBuilder();
name.Append(status.Name.ToDalamudString().TextValue);
if (status.MaxStacks != 0 && Strength != null)
name.Append($" {Strength}");
if (!status.IsPermanent && Duration != null)
name.Append($" > {Duration}");
return name.ToString();
}
}
} }
+40
View File
@@ -0,0 +1,40 @@
using Craftimizer.Plugin;
using Lumina.Excel.GeneratedSheets;
namespace Craftimizer.Simulator;
public enum EffectType
{
InnerQuiet,
WasteNot,
Veneration,
GreatStrides,
Innovation,
FinalAppraisal,
WasteNot2,
MuscleMemory,
Manipulation,
HeartAndSoul,
}
internal static class EffectExtensions
{
public static uint StatusId(this EffectType me) =>
me switch
{
EffectType.InnerQuiet => 251,
EffectType.WasteNot => 252,
EffectType.Veneration => 2226,
EffectType.GreatStrides => 254,
EffectType.Innovation => 2189,
EffectType.FinalAppraisal => 2190,
EffectType.WasteNot2 => 257,
EffectType.MuscleMemory => 2191,
EffectType.Manipulation => 258,
EffectType.HeartAndSoul => 2665,
_ => 3412,
};
public static Status Status(this EffectType me) =>
LuminaSheets.StatusSheet.GetRow(me.StatusId())!;
}
+140 -83
View File
@@ -3,6 +3,7 @@ using Dalamud.Logging;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace Craftimizer.Simulator; namespace Craftimizer.Simulator;
@@ -12,6 +13,7 @@ public class Simulation
public Recipe Recipe { get; } public Recipe Recipe { get; }
public RecipeLevelTable RecipeTable => Recipe.RecipeLevelTable.Value!; public RecipeLevelTable RecipeTable => Recipe.RecipeLevelTable.Value!;
public int RLvl => (int)RecipeTable.RowId; public int RLvl => (int)RecipeTable.RowId;
public readonly Condition[] AvailableConditions;
public int MaxDurability => RecipeTable.Durability * Recipe.DurabilityFactor / 100; public int MaxDurability => RecipeTable.Durability * Recipe.DurabilityFactor / 100;
public int MaxQuality => (int)RecipeTable.Quality * Recipe.QualityFactor / 100; public int MaxQuality => (int)RecipeTable.Quality * Recipe.QualityFactor / 100;
@@ -24,7 +26,7 @@ public class Simulation
public int Durability { get; private set; } public int Durability { get; private set; }
public int CP { get; private set; } public int CP { get; private set; }
public Condition Condition { get; private set; } public Condition Condition { get; private set; }
public List<(Effect effect, int strength, int stepsLeft)> ActiveEffects { get; } = new(); public List<Effect> ActiveEffects { get; } = new();
public List<BaseAction> ActionHistory { get; } = new(); public List<BaseAction> ActionHistory { get; } = new();
// https://github.com/ffxiv-teamcraft/simulator/blob/0682dfa76043ff4ccb38832c184d046ceaff0733/src/model/tables.ts#L2 // https://github.com/ffxiv-teamcraft/simulator/blob/0682dfa76043ff4ccb38832c184d046ceaff0733/src/model/tables.ts#L2
@@ -51,6 +53,7 @@ public class Simulation
Durability = MaxDurability; Durability = MaxDurability;
CP = Stats.CP; CP = Stats.CP;
Condition = Condition.Normal; Condition = Condition.Normal;
AvailableConditions = ConditionUtils.GetPossibleConditions(RecipeTable.ConditionsFlag);
} }
public ActionResponse Execute(BaseAction action) public ActionResponse Execute(BaseAction action)
@@ -72,14 +75,13 @@ public class Simulation
for (var i = 0; i < ActiveEffects.Count; ++i) for (var i = 0; i < ActiveEffects.Count; ++i)
{ {
var (effect, strength, stepsLeft) = ActiveEffects[i]; var effect = ActiveEffects[i];
if (stepsLeft == 1) effect.Duration--;
if (effect.Duration == 0)
{ {
ActiveEffects.RemoveAt(i); ActiveEffects.RemoveAt(i);
--i; --i;
} }
else
ActiveEffects[i] = (effect, strength, stepsLeft - 1);
} }
if (Progress >= MaxProgress) if (Progress >= MaxProgress)
@@ -99,59 +101,91 @@ public class Simulation
public ActionResponse Execute<T>() where T : BaseAction => public ActionResponse Execute<T>() where T : BaseAction =>
Execute((T)Activator.CreateInstance(typeof(T), this)!); Execute((T)Activator.CreateInstance(typeof(T), this)!);
public (int Strength, int Duration)? GetEffect(Effect effect) public Effect? GetEffect(EffectType effect) =>
ActiveEffects.FirstOrDefault(e => e.Type == effect);
public void AddEffect(EffectType effect, int? duration = null, int? strength = null)
{ {
var idx = ActiveEffects.FindIndex(x => x.effect == effect); if (Condition == Condition.Primed && duration != null)
if (idx == -1) duration += 2;
return null;
var (_, strength, duration) = ActiveEffects[idx]; // Duration will be decreased in the next step, so we need to add 1
return (strength, duration); if (duration != null)
duration++;
var currentEffect = GetEffect(effect);
if (currentEffect != null) {
currentEffect.Duration = duration;
currentEffect.Strength = strength;
}
else
ActiveEffects.Add(new Effect { Type = effect, Duration = duration, Strength = strength });
} }
public void AddEffect(Effect effect, int duration, int strength = 1) public void StrengthenEffect(EffectType effect, int? duration = null)
{ {
// Duration will be decreased in the next step, so we need to add 1 if (duration != null)
duration += 1; duration += 1;
var idx = ActiveEffects.FindIndex(x => x.effect == effect); var currentEffect = GetEffect(effect);
if (idx == -1) if (currentEffect != null)
ActiveEffects.Add((effect, strength, duration));
else
ActiveEffects[idx] = (effect, strength, duration);
}
public void StrengthenEffect(Effect effect, int duration = -1)
{ {
var idx = ActiveEffects.FindIndex(x => x.effect == effect); if (effect.Status().MaxStacks > currentEffect.Strength)
if (idx == -1) currentEffect.Strength++;
ActiveEffects.Add((effect, 1, duration)); }
else else
ActiveEffects[idx] = (effect, ActiveEffects[idx].strength + 1, duration); AddEffect(effect, duration, 1);
} }
public void RemoveEffect(Effect effect) public void RemoveEffect(EffectType effect) =>
{ ActiveEffects.RemoveAll(e => e.Type == effect);
var idx = ActiveEffects.FindIndex(x => x.effect == effect);
if (idx != -1)
ActiveEffects.RemoveAt(idx);
}
public bool HasEffect(Effect effect) => GetEffect(effect) != null; public bool HasEffect(EffectType effect) =>
ActiveEffects.Any(e => e.Type == effect);
public BaseAction? GetPreviousAction(int stepsBack = 1) => public bool IsPreviousAction<T>(int stepsBack = 1) where T : BaseAction =>
ActionHistory.Count < stepsBack ? null : ActionHistory[^stepsBack]; ActionHistory.Count >= stepsBack && ActionHistory[^stepsBack] is T;
public bool RollSuccess(float successRate) => public int CountPreviousAction<T>() where T : BaseAction =>
ActionHistory.Count(x => x is T);
public bool RollSuccessRaw(float successRate) =>
successRate >= Random.NextSingle(); successRate >= Random.NextSingle();
public void IncreaseStepCount() => public bool RollSuccess(float successRate) =>
StepCount++; RollSuccessRaw(CalculateSuccessRate(successRate));
public void ReduceDurability(int amount) public void IncreaseStepCount()
{ {
if (HasEffect(Effect.WasteNot) || HasEffect(Effect.WasteNot2)) StepCount++;
amount /= 2; StepCondition();
Durability -= amount; }
private float GetConditionChance(Condition condition) =>
condition switch
{
Condition.Good => Recipe.IsExpert ? 0.12f : (Stats.Level >= 63 ? 0.15f : 0.18f),
Condition.Excellent => 0.04f,
Condition.Centered => 0.15f,
Condition.Sturdy => 0.15f,
Condition.Pliant => 0.10f,
Condition.Malleable => 0.13f,
Condition.Primed => 0.15f,
Condition.GoodOmen => 0.12f, // https://github.com/ffxiv-teamcraft/simulator/issues/77
_ => 0.00f
};
public void StepCondition()
{
var conditionChance = Random.NextSingle();
Condition = Condition switch {
Condition.Poor => Condition.Normal,
Condition.Good => Condition.Normal,
Condition.Excellent => Condition.Poor,
Condition.GoodOmen => Condition.Good,
_ => AvailableConditions.FirstOrDefault(c => (conditionChance -= GetConditionChance(c)) < 0, Condition.Normal)
};
} }
public void RestoreDurability(int amount) public void RestoreDurability(int amount)
@@ -162,11 +196,6 @@ public class Simulation
Durability = MaxDurability; Durability = MaxDurability;
} }
public void ReduceCP(int amount)
{
CP -= amount;
}
public void RestoreCP(int amount) public void RestoreCP(int amount)
{ {
CP += amount; CP += amount;
@@ -175,82 +204,104 @@ public class Simulation
CP = Stats.CP; CP = Stats.CP;
} }
public int CalculateProgressGain(float efficiency) public float CalculateSuccessRate(float successRate)
{
if (Condition == Condition.Centered)
successRate += 0.25f;
return Math.Clamp(successRate, 0, 1);
}
public int CalculateDurabilityCost(int amount)
{
var amt = (double)amount;
if (HasEffect(EffectType.WasteNot) || HasEffect(EffectType.WasteNot2))
amt /= 2;
if (Condition == Condition.Sturdy)
amt /= 2;
return (int)Math.Ceiling(amt);
}
public int CalculateCPCost(int amount)
{
var amt = (double)amount;
if (Condition == Condition.Pliant)
amt /= 2;
return (int)Math.Ceiling(amt);
}
public int CalculateProgressGain(float efficiency, bool dryRun = true)
{ {
var buffModifier = 1.00f; var buffModifier = 1.00f;
if (HasEffect(Effect.MuscleMemory)) if (HasEffect(EffectType.MuscleMemory))
{ {
buffModifier += 1.00f; buffModifier += 1.00f;
RemoveEffect(Effect.MuscleMemory); if (!dryRun)
RemoveEffect(EffectType.MuscleMemory);
} }
if (HasEffect(Effect.Veneration)) if (HasEffect(EffectType.Veneration))
buffModifier += 0.50f; buffModifier += 0.50f;
// https://github.com/NotRanged/NotRanged.github.io/blob/0f4aee074f969fb05aad34feaba605057c08ffd1/app/js/ffxivcraftmodel.js#L88 var conditionModifier = Condition switch
PluginLog.LogDebug($"Efficiency: {efficiency}");
PluginLog.LogDebug($"Buff Modifier: {buffModifier}");
var baseIncrease = (Stats.Craftsmanship * 10f / RecipeTable.ProgressDivider) + 2;
PluginLog.LogDebug($"Increase: {baseIncrease}");
if (Stats.CLvl <= RLvl)
{ {
baseIncrease *= RecipeTable.ProgressModifier / 100f; Condition.Malleable => 1.50f,
PluginLog.LogDebug($"Boosted Increase: {baseIncrease}"); _ => 1.00f
} };
baseIncrease = MathF.Floor(baseIncrease);
PluginLog.LogDebug($"Adj. Increase: {baseIncrease}");
var progressGain = (int)(baseIncrease * efficiency * buffModifier); // https://github.com/NotRanged/NotRanged.github.io/blob/0f4aee074f969fb05aad34feaba605057c08ffd1/app/js/ffxivcraftmodel.js#L88
PluginLog.LogDebug($"Progress Gain: {progressGain}"); var baseIncrease = (Stats.Craftsmanship * 10f / RecipeTable.ProgressDivider) + 2;
if (Stats.CLvl <= RLvl)
baseIncrease *= RecipeTable.ProgressModifier / 100f;
baseIncrease = MathF.Floor(baseIncrease);
var progressGain = (int)(baseIncrease * efficiency * conditionModifier * buffModifier);
return progressGain; return progressGain;
} }
public int CalculateQualityGain(float efficiency) public int CalculateQualityGain(float efficiency, bool dryRun = true)
{ {
var buffModifier = 1.00f; var buffModifier = 1.00f;
if (HasEffect(Effect.GreatStrides)) if (HasEffect(EffectType.GreatStrides))
{ {
buffModifier += 1.00f; buffModifier += 1.00f;
RemoveEffect(Effect.GreatStrides); if (!dryRun)
RemoveEffect(EffectType.GreatStrides);
} }
if (HasEffect(Effect.Innovation)) if (HasEffect(EffectType.Innovation))
buffModifier += 0.50f; buffModifier += 0.50f;
buffModifier *= 1 + ((GetEffect(Effect.InnerQuiet)?.Strength ?? 0) * 0.10f); buffModifier *= 1 + ((GetEffect(EffectType.InnerQuiet)?.Strength ?? 0) * 0.10f);
var conditionModifier = Condition switch var conditionModifier = Condition switch
{ {
Condition.Poor => 0.50f, Condition.Poor => 0.50f,
Condition.Good => 1.50f, // 1.75f if relic tool Condition.Good => Stats.HasRelic ? 1.75f : 1.50f,
Condition.Excellent => 4.00f, Condition.Excellent => 4.00f,
_ => 1.00f, _ => 1.00f,
}; };
PluginLog.LogDebug($"Efficiency: {efficiency}");
PluginLog.LogDebug($"Buff Modifier: {buffModifier}");
PluginLog.LogDebug($"Cond Modifier: {conditionModifier}");
var baseIncrease = (Stats.Control * 10f / RecipeTable.QualityDivider) + 35; var baseIncrease = (Stats.Control * 10f / RecipeTable.QualityDivider) + 35;
PluginLog.LogDebug($"Increase: {baseIncrease}");
if (Stats.CLvl <= RLvl) if (Stats.CLvl <= RLvl)
{
baseIncrease *= RecipeTable.QualityModifier / 100f; baseIncrease *= RecipeTable.QualityModifier / 100f;
PluginLog.LogDebug($"Boosted Increase: {baseIncrease}");
}
baseIncrease = MathF.Floor(baseIncrease); baseIncrease = MathF.Floor(baseIncrease);
PluginLog.LogDebug($"Adj. Increase: {baseIncrease}");
var qualityGain = (int)(baseIncrease * efficiency * conditionModifier * buffModifier); var qualityGain = (int)(baseIncrease * efficiency * conditionModifier * buffModifier);
PluginLog.LogDebug($"Quality Gain: {qualityGain}");
return qualityGain; return qualityGain;
} }
public void ReduceDurabilityRaw(int amount) =>
Durability -= amount;
public void ReduceCPRaw(int amount) =>
CP -= amount;
public void IncreaseProgressRaw(int progressGain) public void IncreaseProgressRaw(int progressGain)
{ {
Progress += progressGain; Progress += progressGain;
if (HasEffect(Effect.FinalAppraisal) && Progress >= MaxProgress) if (HasEffect(EffectType.FinalAppraisal) && Progress >= MaxProgress)
{ {
Progress = MaxProgress - 1; Progress = MaxProgress - 1;
RemoveEffect(Effect.FinalAppraisal); RemoveEffect(EffectType.FinalAppraisal);
} }
} }
@@ -259,12 +310,18 @@ public class Simulation
Quality += qualityGain; Quality += qualityGain;
if (Stats.Level >= 11) if (Stats.Level >= 11)
StrengthenEffect(Effect.InnerQuiet); StrengthenEffect(EffectType.InnerQuiet);
} }
public void ReduceDurability(int amount) =>
ReduceDurabilityRaw(CalculateDurabilityCost(amount));
public void ReduceCP(int amount) =>
ReduceCPRaw(CalculateCPCost(amount));
public void IncreaseProgress(float efficiency) => public void IncreaseProgress(float efficiency) =>
IncreaseProgressRaw(CalculateProgressGain(efficiency)); IncreaseProgressRaw(CalculateProgressGain(efficiency, false));
public void IncreaseQuality(float efficiency) => public void IncreaseQuality(float efficiency) =>
IncreaseQualityRaw(CalculateQualityGain(efficiency)); IncreaseQualityRaw(CalculateQualityGain(efficiency, false));
} }