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<CraftAction> CraftActionSheet = Service.DataManager.GetExcelSheet<CraftAction>()!;
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 Dalamud.Interface;
using Dalamud.Interface.Windowing;
using Dalamud.Logging;
using Dalamud.Utility;
using ImGuiNET;
using System;
using System.Linq;
@@ -14,6 +16,8 @@ public class SimulatorWindow : Window
public Simulation Simulation { get; }
public BaseAction[] AvailableActions { get; }
private bool showOnlyGuaranteedActions = true;
public SimulatorWindow() : base("Craftimizer")
{
SizeConstraints = new WindowSizeConstraints()
@@ -22,7 +26,7 @@ public class SimulatorWindow : Window
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();
}
@@ -32,18 +36,22 @@ public class SimulatorWindow : Window
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 AvailableActions.GroupBy(a => a.Category))
{
var i = 0;
ImGuiUtils.BeginGroupPanel(category.Key.ToString());
foreach (var action in category)
ImGuiUtils.BeginGroupPanel(category.Key.GetDisplayName());
foreach (var action in category.OrderBy(a => a.Level))
{
if (showOnlyGuaranteedActions && !action.IsGuaranteedAction)
continue;
ImGui.BeginDisabled(!action.CanUse);
if (ImGui.ImageButton(action.GetIcon(ClassJob.Carpenter).ImGuiHandle, new Vector2(ImGui.GetFontSize() * 2)))
Simulation.Execute(action);
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
ImGui.SetTooltip(action.Tooltip);
ImGui.SetTooltip(action.GetTooltip(true));
ImGui.EndDisabled();
if (++i % 5 != 0)
ImGui.SameLine();
@@ -55,6 +63,9 @@ public class SimulatorWindow : Window
ImGui.TableNextColumn();
ImGui.BeginChild("CraftimizerSimulator", Vector2.Zero, true, ImGuiWindowFlags.NoDecoration);
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.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}");
@@ -70,20 +81,14 @@ public class SimulatorWindow : Window
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(5);
ImGui.Text($"Effects:");
foreach (var (effect, strength, stepsLeft) in Simulation.ActiveEffects)
foreach (var effect in Simulation.ActiveEffects)
{
var status = effect.Status();
var icon = Icons.GetIconFromId((ushort)status.Icon);
var icon = effect.Icon;
var h = ImGui.GetFontSize() * 1.25f;
var w = icon.Width * h / icon.Height;
ImGui.Image(icon.ImGuiHandle, new Vector2(w, h));
if (ImGui.IsItemHovered())
ImGui.SetTooltip(status.Name.ToString());
ImGui.SameLine();
if (stepsLeft < 0)
ImGui.Text($"{strength}");
else
ImGui.Text($"> {stepsLeft}");
ImGui.Text(effect.Tooltip);
}
ImGuiHelpers.ScaledDummy(5);
{
@@ -92,7 +97,7 @@ public class SimulatorWindow : Window
{
ImGui.Image(action.GetIcon(ClassJob.Carpenter).ImGuiHandle, new Vector2(ImGui.GetFontSize() * 2f));
if (ImGui.IsItemHovered())
ImGui.SetTooltip(action.GetName(ClassJob.Carpenter));
ImGui.SetTooltip(action.GetTooltip(false));
if (++i % 5 != 0)
ImGui.SameLine();
}
+15
View File
@@ -9,3 +9,18 @@ public enum ActionCategory
Buffs,
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 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 bool IncreasesQuality => true;
}
+11 -11
View File
@@ -1,4 +1,5 @@
using Craftimizer.Plugin;
using Dalamud.Utility;
using ImGuiScene;
using Lumina.Excel.GeneratedSheets;
using System;
@@ -32,6 +33,7 @@ public abstract class BaseAction
public virtual float SuccessRate => 1f;
public virtual int DurabilityCost => 10;
public virtual bool IncreasesStepCount => true;
public virtual bool IsGuaranteedAction => SuccessRate == 1f;
private (CraftAction? CraftAction, Action? Action) GetActionRow(ClassJob classJob)
{
@@ -67,9 +69,9 @@ public abstract class BaseAction
{
var (craftAction, action) = GetActionRow(classJob);
if (craftAction != null)
return craftAction.Name;
return craftAction.Name.ToDalamudString().TextValue;
else if (action != null)
return action.Name;
return action.Name.ToDalamudString().TextValue;
return "Unknown";
}
@@ -92,7 +94,7 @@ public abstract class BaseAction
Simulation.ReduceCP(CPCost);
Simulation.ReduceDurability(DurabilityCost);
if (Simulation.HasEffect(Effect.Manipulation))
if (Simulation.HasEffect(EffectType.Manipulation))
Simulation.RestoreDurability(5);
if (Simulation.RollSuccess(SuccessRate))
@@ -113,18 +115,17 @@ public abstract class BaseAction
}
}
public virtual string Tooltip
{
get
public virtual string GetTooltip(bool addUsability)
{
var builder = new StringBuilder();
builder.AppendLine(GetName(ClassJob.Carpenter));
if (!CanUse)
if (addUsability && !CanUse)
builder.AppendLine($"Cannot Use");
builder.AppendLine($"Level {Level}");
builder.AppendLine($"CP Cost: {CPCost}");
if (CPCost != 0)
builder.AppendLine($"-{Simulation.CalculateCPCost(CPCost)} CP");
if (DurabilityCost != 0)
builder.AppendLine($"Durability Cost: {DurabilityCost}");
builder.AppendLine($"-{Simulation.CalculateDurabilityCost(DurabilityCost)} Durability");
if (Efficiency != 0)
{
if (IncreasesProgress)
@@ -135,8 +136,7 @@ public abstract class BaseAction
if (!IncreasesStepCount)
builder.AppendLine($"Does Not Increase Step Count");
if (SuccessRate != 1f)
builder.AppendLine($"{SuccessRate * 100}%% Success Rate");
builder.AppendLine($"{Simulation.CalculateSuccessRate(SuccessRate) * 100}%% Success Rate");
return builder.ToString();
}
}
}
@@ -8,8 +8,7 @@ public abstract class BaseBuffAction : BaseAction
public BaseBuffAction(Simulation simulation) : base(simulation) { }
public abstract Effect Effect { get; }
public abstract int EffectDuration { get; }
public virtual Effect[] ConflictingEffects => Array.Empty<Effect>();
public virtual EffectType[] ConflictingEffects => Array.Empty<EffectType>();
public override int DurabilityCost => 0;
@@ -18,18 +17,13 @@ public abstract class BaseBuffAction : BaseAction
if (ConflictingEffects.Length != 0)
foreach(var effect in ConflictingEffects)
Simulation.RemoveEffect(effect);
Simulation.AddEffect(Effect, EffectDuration);
Simulation.AddEffect(Effect.Type, Effect.Duration, Effect.Strength);
}
public override string Tooltip {
get
public override string GetTooltip(bool addUsability)
{
var builder = new StringBuilder(base.Tooltip);
builder.AppendLine($"Effect: {Effect.Status().Name}");
builder.AppendLine($"Duration: {EffectDuration} steps");
foreach(var effect in ConflictingEffects)
builder.AppendLine($"Conflicts with: {effect.Status().Name}");
var builder = new StringBuilder(base.GetTooltip(addUsability));
builder.AppendLine($"Effect: {Effect.Tooltip}");
return builder.ToString();
}
}
}
@@ -9,14 +9,14 @@ internal class ByregotsBlessing : BaseAction
public override int ActionId => 100339;
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 CanUse => Simulation.HasEffect(Effect.InnerQuiet) && base.CanUse;
public override bool CanUse => Simulation.HasEffect(EffectType.InnerQuiet) && base.CanUse;
public override void 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 bool IncreasesStepCount => false;
public override Effect Effect => Effect.FinalAppraisal;
public override int EffectDuration => 5;
public override Effect Effect => new() { Type = EffectType.FinalAppraisal, Duration = 5 };
}
@@ -11,5 +11,5 @@ internal class FocusedSynthesis : BaseAction
public override int CPCost => 5;
public override float Efficiency => 2.00f;
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 float Efficiency => 1.50f;
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 Effect Effect => Effect.GreatStrides;
public override int EffectDuration => 3;
public override Effect Effect => new() { Type = EffectType.GreatStrides, Duration = 3 };
}
@@ -15,6 +15,7 @@ internal class Groundwork : BaseAction
get
{
var ret = Simulation.Stats.Level >= 86 ? 3.60f : 3.00f;
// TODO: does not account for waste not
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 Effect Effect => Effect.Innovation;
public override int EffectDuration => 4;
public override Effect Effect => new() { Type = EffectType.Innovation, Duration = 4 };
}
@@ -11,8 +11,16 @@ internal class IntensiveSynthesis : BaseAction
public override int CPCost => 6;
public override float Efficiency => 4.00f;
public override bool IncreasesProgress => true;
public override bool IsGuaranteedAction => false;
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;
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 Effect Effect => Effect.Manipulation;
public override int EffectDuration => 8;
public override Effect Effect => new() { Type = EffectType.Manipulation, Duration = 8 };
}
@@ -17,6 +17,6 @@ internal class MuscleMemory : BaseAction
public override void 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 float Efficiency => 1.50f;
public override bool IncreasesQuality => true;
public override bool IsGuaranteedAction => false;
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;
public override void 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()
{
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 bool CanUse =>
!(Simulation.HasEffect(Effect.WasteNot) || Simulation.HasEffect(Effect.WasteNot2))
!(Simulation.HasEffect(EffectType.WasteNot) || Simulation.HasEffect(EffectType.WasteNot2))
&& base.CanUse;
}
@@ -14,6 +14,6 @@ internal class PrudentTouch : BaseAction
public override int DurabilityCost => base.DurabilityCost / 2;
public override bool CanUse =>
!(Simulation.HasEffect(Effect.WasteNot) || Simulation.HasEffect(Effect.WasteNot2))
!(Simulation.HasEffect(EffectType.WasteNot) || Simulation.HasEffect(EffectType.WasteNot2))
&& base.CanUse;
}
+1 -1
View File
@@ -17,6 +17,6 @@ internal class Reflect : BaseAction
public override void 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 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 bool IncreasesQuality => true;
}
@@ -14,6 +14,6 @@ internal class TrainedFinesse : BaseAction
public override int DurabilityCost => 0;
public override bool CanUse =>
(Simulation.GetEffect(Effect.InnerQuiet)?.Strength ?? 0) == 10
(Simulation.GetEffect(EffectType.InnerQuiet)?.Strength ?? 0) == 10
&& base.CanUse;
}
@@ -10,11 +10,16 @@ internal class TricksOfTheTrade : BaseAction
public override int CPCost => 0;
public override int DurabilityCost => 0;
public override bool IsGuaranteedAction => false;
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;
public override void UseSuccess() =>
public override void UseSuccess()
{
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 DurabilityCost => 0;
public override Effect Effect => Effect.Veneration;
public override int EffectDuration => 4;
public override Effect Effect => new() { Type = EffectType.Veneration, Duration = 4 };
}
+2 -3
View File
@@ -10,7 +10,6 @@ internal class WasteNot : BaseBuffAction
public override int CPCost => 56;
public override Effect Effect => Effect.WasteNot;
public override int EffectDuration => 4;
public override Effect[] ConflictingEffects => new[] { Effect.WasteNot2 };
public override Effect Effect => new() { Type = EffectType.WasteNot, Duration = 4 };
public override EffectType[] ConflictingEffects => new[] { EffectType.WasteNot2 };
}
+2 -3
View File
@@ -10,7 +10,6 @@ internal class WasteNot2 : BaseBuffAction
public override int CPCost => 98;
public override Effect Effect => Effect.WasteNot2;
public override int EffectDuration => 8;
public override Effect[] ConflictingEffects => new[] { Effect.WasteNot };
public override Effect Effect => new() { Type = EffectType.WasteNot2, Duration = 8 };
public override EffectType[] ConflictingEffects => new[] { EffectType.WasteNot };
}
+2
View File
@@ -9,6 +9,8 @@ public record CharacterStats
public int Control { get; init; }
public int CP { get; init; }
public int Level { get; init; }
public bool HasRelic { get; init; }
public bool IsSpecialist { get; init; }
public int CLvl => Level <= 80
? 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;
public enum Condition
public enum Condition : ushort
{
Poor,
Normal,
Good,
Excellent,
Poor = 0x0008,
Normal = 0x0001,
Good = 0x0002,
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 Lumina.Excel.GeneratedSheets;
using Dalamud.Utility;
using ImGuiScene;
using System;
using System.Text;
namespace Craftimizer.Simulator;
public enum Effect
public record Effect
{
InnerQuiet,
WasteNot,
Veneration,
GreatStrides,
Innovation,
FinalAppraisal,
WasteNot2,
MuscleMemory,
Manipulation,
public EffectType Type { get; init; }
public int? Duration { get; set; }
public int? Strength { get; set; }
public ushort IconId { get
{
var status = Type.Status();
var iconId = status.Icon;
if (status.MaxStacks != 0 && Strength != null)
iconId += (uint)Math.Clamp(Strength.Value, 1, status.MaxStacks) - 1;
return (ushort)iconId;
}
}
internal static class EffectExtensions
{
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 TextureWrap Icon => Icons.GetIconFromId(IconId);
public static Status Status(this Effect me) =>
LuminaSheets.StatusSheet.GetRow(me.StatusId())!;
public string Tooltip { get
{
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 System;
using System.Collections.Generic;
using System.Linq;
namespace Craftimizer.Simulator;
@@ -12,6 +13,7 @@ public class Simulation
public Recipe Recipe { get; }
public RecipeLevelTable RecipeTable => Recipe.RecipeLevelTable.Value!;
public int RLvl => (int)RecipeTable.RowId;
public readonly Condition[] AvailableConditions;
public int MaxDurability => RecipeTable.Durability * Recipe.DurabilityFactor / 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 CP { 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();
// https://github.com/ffxiv-teamcraft/simulator/blob/0682dfa76043ff4ccb38832c184d046ceaff0733/src/model/tables.ts#L2
@@ -51,6 +53,7 @@ public class Simulation
Durability = MaxDurability;
CP = Stats.CP;
Condition = Condition.Normal;
AvailableConditions = ConditionUtils.GetPossibleConditions(RecipeTable.ConditionsFlag);
}
public ActionResponse Execute(BaseAction action)
@@ -72,14 +75,13 @@ public class Simulation
for (var i = 0; i < ActiveEffects.Count; ++i)
{
var (effect, strength, stepsLeft) = ActiveEffects[i];
if (stepsLeft == 1)
var effect = ActiveEffects[i];
effect.Duration--;
if (effect.Duration == 0)
{
ActiveEffects.RemoveAt(i);
--i;
}
else
ActiveEffects[i] = (effect, strength, stepsLeft - 1);
}
if (Progress >= MaxProgress)
@@ -99,59 +101,91 @@ public class Simulation
public ActionResponse Execute<T>() where T : BaseAction =>
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 (idx == -1)
return null;
var (_, strength, duration) = ActiveEffects[idx];
return (strength, duration);
if (Condition == Condition.Primed && duration != null)
duration += 2;
// Duration will be decreased in the next step, so we need to add 1
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;
var idx = ActiveEffects.FindIndex(x => x.effect == effect);
if (idx == -1)
ActiveEffects.Add((effect, strength, duration));
else
ActiveEffects[idx] = (effect, strength, duration);
}
public void StrengthenEffect(Effect effect, int duration = -1)
var currentEffect = GetEffect(effect);
if (currentEffect != null)
{
var idx = ActiveEffects.FindIndex(x => x.effect == effect);
if (idx == -1)
ActiveEffects.Add((effect, 1, duration));
if (effect.Status().MaxStacks > currentEffect.Strength)
currentEffect.Strength++;
}
else
ActiveEffects[idx] = (effect, ActiveEffects[idx].strength + 1, duration);
AddEffect(effect, duration, 1);
}
public void RemoveEffect(Effect effect)
{
var idx = ActiveEffects.FindIndex(x => x.effect == effect);
if (idx != -1)
ActiveEffects.RemoveAt(idx);
}
public void RemoveEffect(EffectType effect) =>
ActiveEffects.RemoveAll(e => e.Type == effect);
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) =>
ActionHistory.Count < stepsBack ? null : ActionHistory[^stepsBack];
public bool IsPreviousAction<T>(int stepsBack = 1) where T : BaseAction =>
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();
public void IncreaseStepCount() =>
StepCount++;
public bool RollSuccess(float successRate) =>
RollSuccessRaw(CalculateSuccessRate(successRate));
public void ReduceDurability(int amount)
public void IncreaseStepCount()
{
if (HasEffect(Effect.WasteNot) || HasEffect(Effect.WasteNot2))
amount /= 2;
Durability -= amount;
StepCount++;
StepCondition();
}
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)
@@ -162,11 +196,6 @@ public class Simulation
Durability = MaxDurability;
}
public void ReduceCP(int amount)
{
CP -= amount;
}
public void RestoreCP(int amount)
{
CP += amount;
@@ -175,82 +204,104 @@ public class Simulation
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;
if (HasEffect(Effect.MuscleMemory))
if (HasEffect(EffectType.MuscleMemory))
{
buffModifier += 1.00f;
RemoveEffect(Effect.MuscleMemory);
if (!dryRun)
RemoveEffect(EffectType.MuscleMemory);
}
if (HasEffect(Effect.Veneration))
if (HasEffect(EffectType.Veneration))
buffModifier += 0.50f;
// https://github.com/NotRanged/NotRanged.github.io/blob/0f4aee074f969fb05aad34feaba605057c08ffd1/app/js/ffxivcraftmodel.js#L88
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)
var conditionModifier = Condition switch
{
baseIncrease *= RecipeTable.ProgressModifier / 100f;
PluginLog.LogDebug($"Boosted Increase: {baseIncrease}");
}
baseIncrease = MathF.Floor(baseIncrease);
PluginLog.LogDebug($"Adj. Increase: {baseIncrease}");
Condition.Malleable => 1.50f,
_ => 1.00f
};
var progressGain = (int)(baseIncrease * efficiency * buffModifier);
PluginLog.LogDebug($"Progress Gain: {progressGain}");
// https://github.com/NotRanged/NotRanged.github.io/blob/0f4aee074f969fb05aad34feaba605057c08ffd1/app/js/ffxivcraftmodel.js#L88
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;
}
public int CalculateQualityGain(float efficiency)
public int CalculateQualityGain(float efficiency, bool dryRun = true)
{
var buffModifier = 1.00f;
if (HasEffect(Effect.GreatStrides))
if (HasEffect(EffectType.GreatStrides))
{
buffModifier += 1.00f;
RemoveEffect(Effect.GreatStrides);
if (!dryRun)
RemoveEffect(EffectType.GreatStrides);
}
if (HasEffect(Effect.Innovation))
if (HasEffect(EffectType.Innovation))
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
{
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,
_ => 1.00f,
};
PluginLog.LogDebug($"Efficiency: {efficiency}");
PluginLog.LogDebug($"Buff Modifier: {buffModifier}");
PluginLog.LogDebug($"Cond Modifier: {conditionModifier}");
var baseIncrease = (Stats.Control * 10f / RecipeTable.QualityDivider) + 35;
PluginLog.LogDebug($"Increase: {baseIncrease}");
if (Stats.CLvl <= RLvl)
{
baseIncrease *= RecipeTable.QualityModifier / 100f;
PluginLog.LogDebug($"Boosted Increase: {baseIncrease}");
}
baseIncrease = MathF.Floor(baseIncrease);
PluginLog.LogDebug($"Adj. Increase: {baseIncrease}");
var qualityGain = (int)(baseIncrease * efficiency * conditionModifier * buffModifier);
PluginLog.LogDebug($"Quality Gain: {qualityGain}");
return qualityGain;
}
public void ReduceDurabilityRaw(int amount) =>
Durability -= amount;
public void ReduceCPRaw(int amount) =>
CP -= amount;
public void IncreaseProgressRaw(int progressGain)
{
Progress += progressGain;
if (HasEffect(Effect.FinalAppraisal) && Progress >= MaxProgress)
if (HasEffect(EffectType.FinalAppraisal) && Progress >= MaxProgress)
{
Progress = MaxProgress - 1;
RemoveEffect(Effect.FinalAppraisal);
RemoveEffect(EffectType.FinalAppraisal);
}
}
@@ -259,12 +310,18 @@ public class Simulation
Quality += qualityGain;
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) =>
IncreaseProgressRaw(CalculateProgressGain(efficiency));
IncreaseProgressRaw(CalculateProgressGain(efficiency, false));
public void IncreaseQuality(float efficiency) =>
IncreaseQualityRaw(CalculateQualityGain(efficiency));
IncreaseQualityRaw(CalculateQualityGain(efficiency, false));
}