feat(recipedata): add hardcoded mechanics tables
Three game-mechanics tables for the simulator-relevant constants that the Lumina sheets do not expose, plus the reverse name map the adapter uses to walk the CraftAction + Action sheets. - ActionMechanicsTable: 38 entries with Category, CP cost, durability, efficiency, IQ-stack bonus, charges, granted buff, and flags. Cross-checked against the spec mechanics table in 01-RecipeData.md §2.3.2 (verified there against Artisan's RawInformation/Character/ Skills.cs). Combo-state CP costs (Standard/Advanced Touch) carry the base value; the simulator applies the discount. TrainedEye uses short.MaxValue as the sentinel for "fill Recipe.QualityMax in one step"; ByregotsBlessing carries base EfficiencyQuality=100 with the IQ multiplier added by the simulator. - BuffMechanicsTable: 14 entries with StatusId, Icon, StackMax, Behavior, the three duration fields (Steps / Seconds / Actions populated per Behavior), Category, and LegacyStatusId for the older Innovation 259 and Manipulation 258 ids. Cross-checked against ffxiv-datamining Status.csv (StatusId / Icon / StackMax) and Artisan's tooltip-derived step durations. Expedience uses the explicit StatusId 3812 - the other two "Expedience" rows (2712 / 3092) are non-crafting status effects that would otherwise produce an ambiguous name match. - ConditionMechanicsTable: 11 entries with Quality / Progress / CP / Durability multipliers plus a BaseProbability slot. Robust mirrors Sturdy's durability discount and keeps Quality neutral (per the spec mechanics table - the enum doc comment in AnvilCondition.cs predates rev 5 and is noted as superseded in the table header). SplendorCosmic's Good=1.75 override lives in the simulator. BaseProbability stays 0.0; the static catalog has no useful per-recipe spawn distribution because FFXIV derives that from Recipe.IsExpert + RecipeLevelTable.Stars at runtime. - ActionKindByName: reverse map from English action names to AnvilActionKind, plus the Action-sheet whitelist (seven step-counter buff actions + two Cosmic singletons) used to filter the Action sheet walk down to the crafting subset.
This commit is contained in:
@@ -0,0 +1,94 @@
|
|||||||
|
// Reverse map from FFXIV English action names to AnvilActionKind. Used by
|
||||||
|
// the adapter when walking CraftAction + Action sheets - the adapter looks
|
||||||
|
// each Name up here and skips (with a warning) anything that does not
|
||||||
|
// resolve. The map is keyed by the exact English string the game ships
|
||||||
|
// (Lumina ExtractText with the en-US client). Localised game strings are
|
||||||
|
// resolved through the catalog's DisplayName field, not here.
|
||||||
|
//
|
||||||
|
// Patch maintenance: if a future FFXIV patch renames an action or adds a
|
||||||
|
// new crafting action, this map and the AnvilActionKind enum need a
|
||||||
|
// parallel update. The adapter's "unknown action name" warning is the
|
||||||
|
// detection signal.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Anvil.RecipeData.Mechanics;
|
||||||
|
|
||||||
|
internal static class ActionKindByName
|
||||||
|
{
|
||||||
|
public static IReadOnlyDictionary<string, AnvilActionKind> Map { get; } =
|
||||||
|
new Dictionary<string, AnvilActionKind>
|
||||||
|
{
|
||||||
|
// Progress
|
||||||
|
["Basic Synthesis"] = AnvilActionKind.BasicSynthesis,
|
||||||
|
["Careful Synthesis"] = AnvilActionKind.CarefulSynthesis,
|
||||||
|
["Rapid Synthesis"] = AnvilActionKind.RapidSynthesis,
|
||||||
|
["Intensive Synthesis"] = AnvilActionKind.IntensiveSynthesis,
|
||||||
|
["Prudent Synthesis"] = AnvilActionKind.PrudentSynthesis,
|
||||||
|
["Groundwork"] = AnvilActionKind.Groundwork,
|
||||||
|
["Muscle Memory"] = AnvilActionKind.MuscleMemory,
|
||||||
|
["Delicate Synthesis"] = AnvilActionKind.DelicateSynthesis,
|
||||||
|
|
||||||
|
// Quality
|
||||||
|
["Basic Touch"] = AnvilActionKind.BasicTouch,
|
||||||
|
["Standard Touch"] = AnvilActionKind.StandardTouch,
|
||||||
|
["Advanced Touch"] = AnvilActionKind.AdvancedTouch,
|
||||||
|
["Precise Touch"] = AnvilActionKind.PreciseTouch,
|
||||||
|
["Preparatory Touch"] = AnvilActionKind.PreparatoryTouch,
|
||||||
|
["Prudent Touch"] = AnvilActionKind.PrudentTouch,
|
||||||
|
["Refined Touch"] = AnvilActionKind.RefinedTouch,
|
||||||
|
["Byregot's Blessing"] = AnvilActionKind.ByregotsBlessing,
|
||||||
|
["Hasty Touch"] = AnvilActionKind.HastyTouch,
|
||||||
|
["Daring Touch"] = AnvilActionKind.DaringTouch,
|
||||||
|
["Trained Eye"] = AnvilActionKind.TrainedEye,
|
||||||
|
["Trained Finesse"] = AnvilActionKind.TrainedFinesse,
|
||||||
|
["Reflect"] = AnvilActionKind.Reflect,
|
||||||
|
["Tricks of the Trade"] = AnvilActionKind.TricksOfTheTrade,
|
||||||
|
|
||||||
|
// Buff
|
||||||
|
["Veneration"] = AnvilActionKind.Veneration,
|
||||||
|
["Innovation"] = AnvilActionKind.Innovation,
|
||||||
|
["Great Strides"] = AnvilActionKind.GreatStrides,
|
||||||
|
["Final Appraisal"] = AnvilActionKind.FinalAppraisal,
|
||||||
|
["Manipulation"] = AnvilActionKind.Manipulation,
|
||||||
|
["Waste Not"] = AnvilActionKind.WasteNot,
|
||||||
|
["Waste Not II"] = AnvilActionKind.WasteNot2,
|
||||||
|
|
||||||
|
// Repair
|
||||||
|
["Master's Mend"] = AnvilActionKind.MastersMend,
|
||||||
|
["Immaculate Mend"] = AnvilActionKind.ImmaculateMend,
|
||||||
|
|
||||||
|
// Observe / Specialist
|
||||||
|
["Observe"] = AnvilActionKind.Observe,
|
||||||
|
["Careful Observation"] = AnvilActionKind.CarefulObservation,
|
||||||
|
["Heart and Soul"] = AnvilActionKind.HeartAndSoul,
|
||||||
|
["Quick Innovation"] = AnvilActionKind.QuickInnovation,
|
||||||
|
["Trained Perfection"] = AnvilActionKind.TrainedPerfection,
|
||||||
|
|
||||||
|
// Cosmic Exploration
|
||||||
|
["Material Miracle"] = AnvilActionKind.MaterialMiracle,
|
||||||
|
["Stellar Steady Hand"] = AnvilActionKind.StellarSteadyHand,
|
||||||
|
};
|
||||||
|
|
||||||
|
// The whitelist of Action-sheet (vs CraftAction-sheet) names. Used by the
|
||||||
|
// adapter's Action-sheet walk: only rows whose Name matches one of these
|
||||||
|
// are considered for inclusion (the Action sheet otherwise carries
|
||||||
|
// thousands of combat / fishing / gathering rows that would pollute the
|
||||||
|
// catalog). Note that Action-sheet entries with PrimaryCostValue == 0
|
||||||
|
// (legacy ARR singletons) are filtered out separately.
|
||||||
|
public static IReadOnlySet<string> ActionSheetWhitelist { get; } =
|
||||||
|
new HashSet<string>
|
||||||
|
{
|
||||||
|
// Step-counter buff actions
|
||||||
|
"Veneration",
|
||||||
|
"Innovation",
|
||||||
|
"Manipulation",
|
||||||
|
"Waste Not",
|
||||||
|
"Waste Not II",
|
||||||
|
"Great Strides",
|
||||||
|
"Final Appraisal",
|
||||||
|
// Cosmic singletons
|
||||||
|
"Material Miracle",
|
||||||
|
"Stellar Steady Hand",
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// Per-action mechanics row. Source of truth for everything the CraftAction
|
||||||
|
// and Action Lumina sheets do not carry: category, CP cost, durability,
|
||||||
|
// efficiency, IQ-stack bonus, charges, granted buff, and flags. The adapter
|
||||||
|
// merges this with the per-job sheet rows (DisplayName, IconId,
|
||||||
|
// LevelRequired, RowIdByClassJob, SpecialistOnly) to build an AnvilAction.
|
||||||
|
//
|
||||||
|
// CP cost lives in the table because the spec mechanics table is the
|
||||||
|
// curated, version-verified source. The sheet's Cost column matches in
|
||||||
|
// practice; if a patch drifts the two apart, the adapter logs the diff
|
||||||
|
// while keeping the table value (so the simulator stays deterministic
|
||||||
|
// against the spec-pinned numbers).
|
||||||
|
//
|
||||||
|
// Combo-state CP costs (StandardTouch is 18 CP after BasicTouch etc.) are
|
||||||
|
// not modelled here - the table holds the base value and the simulator
|
||||||
|
// applies the combo discount in its own scope.
|
||||||
|
|
||||||
|
namespace Anvil.RecipeData.Mechanics;
|
||||||
|
|
||||||
|
internal sealed record ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
public required AnvilActionCategory Category { get; init; }
|
||||||
|
public required short CpCost { get; init; }
|
||||||
|
public required short DurabilityCost { get; init; }
|
||||||
|
public required short EfficiencyProgress { get; init; }
|
||||||
|
public required short EfficiencyQuality { get; init; }
|
||||||
|
public required byte IqStackBonus { get; init; }
|
||||||
|
public required byte ChargesPerCraft { get; init; }
|
||||||
|
public required AnvilBuffKind? GrantsBuff { get; init; }
|
||||||
|
public required AnvilActionFlags Flags { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,526 @@
|
|||||||
|
// Hardcoded action mechanics table. Values are FFXIV game data (Square
|
||||||
|
// Enix' domain) and are cross-checked against the spec mechanics table in
|
||||||
|
// 01-RecipeData.md §2.3.2 - which was itself verified against Artisan
|
||||||
|
// (RawInformation/Character/Skills.cs). The table is the source of truth
|
||||||
|
// for everything the simulator needs that the Lumina sheets do not carry:
|
||||||
|
// category, CP cost, durability, efficiency, IQ-stack bonus, charges,
|
||||||
|
// granted buff, and flags.
|
||||||
|
//
|
||||||
|
// Note on combo-state CP costs: StandardTouch and AdvancedTouch are listed
|
||||||
|
// at their base CP (32 / 46). The combo discount (StandardTouch costs 18
|
||||||
|
// after BasicTouch, AdvancedTouch costs 18 after StandardTouch) lives in
|
||||||
|
// the simulator, not in the data layer.
|
||||||
|
//
|
||||||
|
// Note on TrainedEye efficiency: short.MaxValue is the sentinel for
|
||||||
|
// "max-quality override" - the simulator recognises the Kind and applies
|
||||||
|
// Recipe.QualityMax in one step instead of using the efficiency arithmetic.
|
||||||
|
// Likewise ByregotsBlessing has base EfficiencyQuality=100; the simulator
|
||||||
|
// adds the 20% per IQ stack itself.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Anvil.RecipeData.Mechanics;
|
||||||
|
|
||||||
|
internal static class ActionMechanicsTable
|
||||||
|
{
|
||||||
|
public static IReadOnlyDictionary<AnvilActionKind, ActionMechanicsEntry> Entries { get; } =
|
||||||
|
new Dictionary<AnvilActionKind, ActionMechanicsEntry>
|
||||||
|
{
|
||||||
|
// ---- Progress actions ----
|
||||||
|
[AnvilActionKind.BasicSynthesis] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.ProgressAction,
|
||||||
|
CpCost = 0,
|
||||||
|
DurabilityCost = 10,
|
||||||
|
EfficiencyProgress = 120,
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = null,
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.CarefulSynthesis] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.ProgressAction,
|
||||||
|
CpCost = 7,
|
||||||
|
DurabilityCost = 10,
|
||||||
|
EfficiencyProgress = 180,
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = null,
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.RapidSynthesis] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.ProgressAction,
|
||||||
|
CpCost = 0,
|
||||||
|
DurabilityCost = 10,
|
||||||
|
EfficiencyProgress = 500,
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = null,
|
||||||
|
// 50% base success rate lives in the simulator, not in flags.
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.IntensiveSynthesis] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.ProgressAction,
|
||||||
|
CpCost = 6,
|
||||||
|
DurabilityCost = 10,
|
||||||
|
EfficiencyProgress = 400,
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = null,
|
||||||
|
Flags = AnvilActionFlags.RequiresGoodOrExcellent,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.PrudentSynthesis] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.ProgressAction,
|
||||||
|
CpCost = 18,
|
||||||
|
DurabilityCost = 5,
|
||||||
|
EfficiencyProgress = 180,
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = null,
|
||||||
|
// WasteNot-lockout is a simulator rule (cannot use under WasteNot/2).
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.Groundwork] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.ProgressAction,
|
||||||
|
CpCost = 18,
|
||||||
|
DurabilityCost = 20,
|
||||||
|
EfficiencyProgress = 360,
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = null,
|
||||||
|
// Halved efficiency when remaining durability < cost - simulator rule.
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.MuscleMemory] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.ProgressAction,
|
||||||
|
CpCost = 6,
|
||||||
|
DurabilityCost = 10,
|
||||||
|
EfficiencyProgress = 300,
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = AnvilBuffKind.MuscleMemory,
|
||||||
|
Flags = AnvilActionFlags.RequiresFirstStep,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.DelicateSynthesis] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.HybridProgressQuality,
|
||||||
|
CpCost = 32,
|
||||||
|
DurabilityCost = 10,
|
||||||
|
EfficiencyProgress = 100,
|
||||||
|
EfficiencyQuality = 100,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = null,
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
|
||||||
|
// ---- Quality actions ----
|
||||||
|
[AnvilActionKind.BasicTouch] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.QualityAction,
|
||||||
|
CpCost = 18,
|
||||||
|
DurabilityCost = 10,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 100,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = null,
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.StandardTouch] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.QualityAction,
|
||||||
|
// Base 32 CP; simulator drops to 18 after BasicTouch combo.
|
||||||
|
CpCost = 32,
|
||||||
|
DurabilityCost = 10,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 125,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = null,
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.AdvancedTouch] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.QualityAction,
|
||||||
|
// Base 46 CP; simulator drops to 18 after StandardTouch combo.
|
||||||
|
CpCost = 46,
|
||||||
|
DurabilityCost = 10,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 150,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = null,
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.PreciseTouch] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.QualityAction,
|
||||||
|
CpCost = 18,
|
||||||
|
DurabilityCost = 10,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 150,
|
||||||
|
IqStackBonus = 1,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = null,
|
||||||
|
Flags = AnvilActionFlags.RequiresGoodOrExcellent,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.PreparatoryTouch] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.QualityAction,
|
||||||
|
CpCost = 40,
|
||||||
|
DurabilityCost = 20,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 200,
|
||||||
|
IqStackBonus = 1,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = null,
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.PrudentTouch] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.QualityAction,
|
||||||
|
CpCost = 25,
|
||||||
|
DurabilityCost = 5,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 100,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = null,
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.RefinedTouch] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.QualityAction,
|
||||||
|
CpCost = 24,
|
||||||
|
DurabilityCost = 10,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 100,
|
||||||
|
// +1 only after BasicTouch combo; simulator gates the bonus.
|
||||||
|
IqStackBonus = 1,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = null,
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.ByregotsBlessing] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.QualityAction,
|
||||||
|
CpCost = 24,
|
||||||
|
DurabilityCost = 10,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
// Base 100; simulator adds 20 per IQ stack and consumes them.
|
||||||
|
EfficiencyQuality = 100,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = null,
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.HastyTouch] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.QualityAction,
|
||||||
|
CpCost = 0,
|
||||||
|
DurabilityCost = 10,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 100,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = null,
|
||||||
|
// 60% base success rate is a simulator rule, not a flag.
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.DaringTouch] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.QualityAction,
|
||||||
|
CpCost = 0,
|
||||||
|
DurabilityCost = 10,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 150,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = null,
|
||||||
|
Flags = AnvilActionFlags.RequiresExpedience,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.TrainedEye] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.QualityAction,
|
||||||
|
CpCost = 250,
|
||||||
|
DurabilityCost = 10,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
// Sentinel: simulator treats short.MaxValue as
|
||||||
|
// "fill Recipe.QualityMax in one step".
|
||||||
|
EfficiencyQuality = short.MaxValue,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 1,
|
||||||
|
GrantsBuff = null,
|
||||||
|
Flags = AnvilActionFlags.RequiresFirstStep | AnvilActionFlags.RequiresLowLevelRecipe,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.TrainedFinesse] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.QualityAction,
|
||||||
|
CpCost = 32,
|
||||||
|
DurabilityCost = 0,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 100,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = null,
|
||||||
|
Flags = AnvilActionFlags.RequiresFullIQ,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.Reflect] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.QualityAction,
|
||||||
|
CpCost = 6,
|
||||||
|
DurabilityCost = 10,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 100,
|
||||||
|
IqStackBonus = 1,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = null,
|
||||||
|
Flags = AnvilActionFlags.RequiresFirstStep,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.TricksOfTheTrade] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.QualityAction,
|
||||||
|
CpCost = 0,
|
||||||
|
DurabilityCost = 0,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
// 0 EffQuality; simulator returns 20 CP via the Kind-specific path.
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = null,
|
||||||
|
Flags = AnvilActionFlags.RequiresGoodOrExcellent,
|
||||||
|
},
|
||||||
|
|
||||||
|
// ---- Buff actions ----
|
||||||
|
[AnvilActionKind.Veneration] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.BuffAction,
|
||||||
|
CpCost = 18,
|
||||||
|
DurabilityCost = 0,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = AnvilBuffKind.Veneration,
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.Innovation] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.BuffAction,
|
||||||
|
CpCost = 18,
|
||||||
|
DurabilityCost = 0,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = AnvilBuffKind.Innovation,
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.GreatStrides] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.BuffAction,
|
||||||
|
CpCost = 32,
|
||||||
|
DurabilityCost = 0,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = AnvilBuffKind.GreatStrides,
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.FinalAppraisal] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.BuffAction,
|
||||||
|
CpCost = 1,
|
||||||
|
DurabilityCost = 0,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = AnvilBuffKind.FinalAppraisal,
|
||||||
|
Flags = AnvilActionFlags.NoBuffTick | AnvilActionFlags.NoConditionChange,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.Manipulation] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.BuffAction,
|
||||||
|
CpCost = 96,
|
||||||
|
DurabilityCost = 0,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = AnvilBuffKind.Manipulation,
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.WasteNot] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.BuffAction,
|
||||||
|
CpCost = 56,
|
||||||
|
DurabilityCost = 0,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = AnvilBuffKind.WasteNot,
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.WasteNot2] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.BuffAction,
|
||||||
|
CpCost = 98,
|
||||||
|
DurabilityCost = 0,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = AnvilBuffKind.WasteNot2,
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
|
||||||
|
// ---- Repair actions ----
|
||||||
|
[AnvilActionKind.MastersMend] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.RepairAction,
|
||||||
|
CpCost = 88,
|
||||||
|
DurabilityCost = 0,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = null,
|
||||||
|
// +30 durability restoration lives in the simulator.
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.ImmaculateMend] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.RepairAction,
|
||||||
|
CpCost = 112,
|
||||||
|
DurabilityCost = 0,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = null,
|
||||||
|
// Full durability restoration lives in the simulator.
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
|
||||||
|
// ---- Observe / Specialist ----
|
||||||
|
[AnvilActionKind.Observe] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.ObserveAction,
|
||||||
|
CpCost = 7,
|
||||||
|
DurabilityCost = 0,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 0,
|
||||||
|
GrantsBuff = null,
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.CarefulObservation] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.ObserveAction,
|
||||||
|
CpCost = 0,
|
||||||
|
DurabilityCost = 0,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 3,
|
||||||
|
GrantsBuff = null,
|
||||||
|
Flags =
|
||||||
|
AnvilActionFlags.SpecialistOnly
|
||||||
|
| AnvilActionFlags.NoBuffTick
|
||||||
|
| AnvilActionFlags.NoConditionChange,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.HeartAndSoul] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.ObserveAction,
|
||||||
|
CpCost = 0,
|
||||||
|
DurabilityCost = 0,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 1,
|
||||||
|
GrantsBuff = AnvilBuffKind.HeartAndSoul,
|
||||||
|
Flags =
|
||||||
|
AnvilActionFlags.SpecialistOnly
|
||||||
|
| AnvilActionFlags.NoBuffTick
|
||||||
|
| AnvilActionFlags.NoConditionChange,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.QuickInnovation] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.BuffAction,
|
||||||
|
CpCost = 0,
|
||||||
|
DurabilityCost = 0,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 1,
|
||||||
|
// Grants the regular Innovation buff; simulator hardcodes the
|
||||||
|
// 1-step duration override on this trigger.
|
||||||
|
GrantsBuff = AnvilBuffKind.Innovation,
|
||||||
|
Flags =
|
||||||
|
AnvilActionFlags.SpecialistOnly
|
||||||
|
| AnvilActionFlags.NoBuffTick
|
||||||
|
| AnvilActionFlags.NoConditionChange,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.TrainedPerfection] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.BuffAction,
|
||||||
|
CpCost = 0,
|
||||||
|
DurabilityCost = 0,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 1,
|
||||||
|
GrantsBuff = AnvilBuffKind.TrainedPerfection,
|
||||||
|
Flags = AnvilActionFlags.None,
|
||||||
|
},
|
||||||
|
|
||||||
|
// ---- Cosmic Exploration (v0.1.0 catalog-present, recipe-gated off) ----
|
||||||
|
[AnvilActionKind.MaterialMiracle] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.Cosmic,
|
||||||
|
CpCost = 1,
|
||||||
|
DurabilityCost = 0,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 1,
|
||||||
|
GrantsBuff = AnvilBuffKind.MaterialMiracleBuff,
|
||||||
|
Flags =
|
||||||
|
AnvilActionFlags.CosmicOnly
|
||||||
|
| AnvilActionFlags.NoBuffTick
|
||||||
|
| AnvilActionFlags.NoConditionChange,
|
||||||
|
},
|
||||||
|
[AnvilActionKind.StellarSteadyHand] = new ActionMechanicsEntry
|
||||||
|
{
|
||||||
|
Category = AnvilActionCategory.Cosmic,
|
||||||
|
CpCost = 1,
|
||||||
|
DurabilityCost = 0,
|
||||||
|
EfficiencyProgress = 0,
|
||||||
|
EfficiencyQuality = 0,
|
||||||
|
IqStackBonus = 0,
|
||||||
|
ChargesPerCraft = 2,
|
||||||
|
// 100% success-rate effect lives in the StellarSteadyHand buff
|
||||||
|
// via ActionCounter behavior - no Action-flag duplication.
|
||||||
|
GrantsBuff = AnvilBuffKind.StellarSteadyHandBuff,
|
||||||
|
Flags = AnvilActionFlags.CosmicOnly,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// Per-buff mechanics row. Carries the constants that the FFXIV Status sheet
|
||||||
|
// does not expose directly: the AnvilBuffBehavior class and the
|
||||||
|
// duration triple (steps / seconds / actions, populated according to
|
||||||
|
// Behavior). The Status sheet does deliver StatusId / Icon / StackMax;
|
||||||
|
// the table mirrors them so consumers and the adapter cross-checks share
|
||||||
|
// a single source.
|
||||||
|
//
|
||||||
|
// Step durations live in the table because FFXIV exposes them only in the
|
||||||
|
// Status tooltip text ("for the next four steps"). String-parsing the
|
||||||
|
// tooltip would be fragile across locales and patches.
|
||||||
|
|
||||||
|
namespace Anvil.RecipeData.Mechanics;
|
||||||
|
|
||||||
|
internal sealed record BuffMechanicsEntry
|
||||||
|
{
|
||||||
|
public required uint StatusId { get; init; }
|
||||||
|
public required uint IconId { get; init; }
|
||||||
|
public required byte StackMax { get; init; }
|
||||||
|
public required AnvilBuffBehavior Behavior { get; init; }
|
||||||
|
public required byte? DefaultDurationSteps { get; init; }
|
||||||
|
public required float? DefaultDurationSeconds { get; init; }
|
||||||
|
public required byte? DefaultDurationActions { get; init; }
|
||||||
|
public required AnvilBuffCategory Category { get; init; }
|
||||||
|
|
||||||
|
// Older Status-sheet RowIds that still resolve to the same Anvil buff
|
||||||
|
// (e.g. Innovation 259 -> 2189, Manipulation 258 -> 1164). Used by the
|
||||||
|
// adapter's reverse mapping so save-state restore from earlier patches
|
||||||
|
// still resolves correctly. Null when no legacy id exists.
|
||||||
|
public required uint? LegacyStatusId { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
// Hardcoded buff mechanics table. Values cross-checked against the spec
|
||||||
|
// mechanics table in 01-RecipeData.md §2.4 - which was itself verified
|
||||||
|
// against ffxiv-datamining Status.csv (StatusId, Icon, StackMax) and
|
||||||
|
// Artisan's tooltip-derived duration constants (Steps / Seconds / Actions).
|
||||||
|
//
|
||||||
|
// All step-counter durations come from the in-game tooltip text rather
|
||||||
|
// than a structured Status sheet column - that is why the table is the
|
||||||
|
// source of truth instead of a sheet read.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Anvil.RecipeData.Mechanics;
|
||||||
|
|
||||||
|
internal static class BuffMechanicsTable
|
||||||
|
{
|
||||||
|
public static IReadOnlyDictionary<AnvilBuffKind, BuffMechanicsEntry> Entries { get; } =
|
||||||
|
new Dictionary<AnvilBuffKind, BuffMechanicsEntry>
|
||||||
|
{
|
||||||
|
[AnvilBuffKind.InnerQuiet] = new BuffMechanicsEntry
|
||||||
|
{
|
||||||
|
StatusId = 251,
|
||||||
|
IconId = 217321,
|
||||||
|
StackMax = 10,
|
||||||
|
Behavior = AnvilBuffBehavior.StackBased,
|
||||||
|
DefaultDurationSteps = null,
|
||||||
|
DefaultDurationSeconds = null,
|
||||||
|
DefaultDurationActions = null,
|
||||||
|
Category = AnvilBuffCategory.QualityBooster,
|
||||||
|
LegacyStatusId = null,
|
||||||
|
},
|
||||||
|
[AnvilBuffKind.WasteNot] = new BuffMechanicsEntry
|
||||||
|
{
|
||||||
|
StatusId = 252,
|
||||||
|
IconId = 211701,
|
||||||
|
StackMax = 1,
|
||||||
|
Behavior = AnvilBuffBehavior.StepCounter,
|
||||||
|
DefaultDurationSteps = 4,
|
||||||
|
DefaultDurationSeconds = null,
|
||||||
|
DefaultDurationActions = null,
|
||||||
|
Category = AnvilBuffCategory.DurabilitySaver,
|
||||||
|
LegacyStatusId = null,
|
||||||
|
},
|
||||||
|
[AnvilBuffKind.WasteNot2] = new BuffMechanicsEntry
|
||||||
|
{
|
||||||
|
StatusId = 257,
|
||||||
|
IconId = 211702,
|
||||||
|
StackMax = 1,
|
||||||
|
Behavior = AnvilBuffBehavior.StepCounter,
|
||||||
|
DefaultDurationSteps = 8,
|
||||||
|
DefaultDurationSeconds = null,
|
||||||
|
DefaultDurationActions = null,
|
||||||
|
Category = AnvilBuffCategory.DurabilitySaver,
|
||||||
|
LegacyStatusId = null,
|
||||||
|
},
|
||||||
|
[AnvilBuffKind.Veneration] = new BuffMechanicsEntry
|
||||||
|
{
|
||||||
|
StatusId = 2226,
|
||||||
|
IconId = 216126,
|
||||||
|
StackMax = 1,
|
||||||
|
Behavior = AnvilBuffBehavior.StepCounter,
|
||||||
|
DefaultDurationSteps = 4,
|
||||||
|
DefaultDurationSeconds = null,
|
||||||
|
DefaultDurationActions = null,
|
||||||
|
Category = AnvilBuffCategory.ProgressBooster,
|
||||||
|
LegacyStatusId = null,
|
||||||
|
},
|
||||||
|
[AnvilBuffKind.GreatStrides] = new BuffMechanicsEntry
|
||||||
|
{
|
||||||
|
StatusId = 254,
|
||||||
|
IconId = 216105,
|
||||||
|
StackMax = 1,
|
||||||
|
Behavior = AnvilBuffBehavior.HybridStepOrUse,
|
||||||
|
DefaultDurationSteps = 3,
|
||||||
|
DefaultDurationSeconds = null,
|
||||||
|
DefaultDurationActions = null,
|
||||||
|
Category = AnvilBuffCategory.QualityBooster,
|
||||||
|
LegacyStatusId = null,
|
||||||
|
},
|
||||||
|
[AnvilBuffKind.Innovation] = new BuffMechanicsEntry
|
||||||
|
{
|
||||||
|
StatusId = 2189,
|
||||||
|
IconId = 211652,
|
||||||
|
StackMax = 1,
|
||||||
|
Behavior = AnvilBuffBehavior.StepCounter,
|
||||||
|
DefaultDurationSteps = 4,
|
||||||
|
DefaultDurationSeconds = null,
|
||||||
|
DefaultDurationActions = null,
|
||||||
|
Category = AnvilBuffCategory.QualityBooster,
|
||||||
|
LegacyStatusId = 259,
|
||||||
|
},
|
||||||
|
[AnvilBuffKind.FinalAppraisal] = new BuffMechanicsEntry
|
||||||
|
{
|
||||||
|
StatusId = 2190,
|
||||||
|
IconId = 216124,
|
||||||
|
StackMax = 1,
|
||||||
|
Behavior = AnvilBuffBehavior.HybridStepOrUse,
|
||||||
|
DefaultDurationSteps = 5,
|
||||||
|
DefaultDurationSeconds = null,
|
||||||
|
DefaultDurationActions = null,
|
||||||
|
Category = AnvilBuffCategory.Utility,
|
||||||
|
LegacyStatusId = null,
|
||||||
|
},
|
||||||
|
[AnvilBuffKind.MuscleMemory] = new BuffMechanicsEntry
|
||||||
|
{
|
||||||
|
StatusId = 2191,
|
||||||
|
IconId = 216125,
|
||||||
|
StackMax = 1,
|
||||||
|
Behavior = AnvilBuffBehavior.HybridStepOrUse,
|
||||||
|
DefaultDurationSteps = 5,
|
||||||
|
DefaultDurationSeconds = null,
|
||||||
|
DefaultDurationActions = null,
|
||||||
|
Category = AnvilBuffCategory.ProgressBooster,
|
||||||
|
LegacyStatusId = null,
|
||||||
|
},
|
||||||
|
[AnvilBuffKind.Manipulation] = new BuffMechanicsEntry
|
||||||
|
{
|
||||||
|
StatusId = 1164,
|
||||||
|
IconId = 211651,
|
||||||
|
StackMax = 1,
|
||||||
|
Behavior = AnvilBuffBehavior.StepCounter,
|
||||||
|
DefaultDurationSteps = 8,
|
||||||
|
DefaultDurationSeconds = null,
|
||||||
|
DefaultDurationActions = null,
|
||||||
|
Category = AnvilBuffCategory.DurabilityRepair,
|
||||||
|
LegacyStatusId = 258,
|
||||||
|
},
|
||||||
|
[AnvilBuffKind.HeartAndSoul] = new BuffMechanicsEntry
|
||||||
|
{
|
||||||
|
StatusId = 2665,
|
||||||
|
IconId = 216127,
|
||||||
|
StackMax = 1,
|
||||||
|
Behavior = AnvilBuffBehavior.UseConsumed,
|
||||||
|
DefaultDurationSteps = null,
|
||||||
|
DefaultDurationSeconds = null,
|
||||||
|
DefaultDurationActions = null,
|
||||||
|
Category = AnvilBuffCategory.ConditionBypass,
|
||||||
|
LegacyStatusId = null,
|
||||||
|
},
|
||||||
|
[AnvilBuffKind.Expedience] = new BuffMechanicsEntry
|
||||||
|
{
|
||||||
|
// Two non-crafting Status rows also carry the name "Expedience"
|
||||||
|
// (RowIds 2712 / 3092). Reverse mapping must use the explicit
|
||||||
|
// StatusId, not a name lookup, to avoid ambiguity.
|
||||||
|
StatusId = 3812,
|
||||||
|
IconId = 216128,
|
||||||
|
StackMax = 1,
|
||||||
|
Behavior = AnvilBuffBehavior.InstantTrigger,
|
||||||
|
DefaultDurationSteps = null,
|
||||||
|
DefaultDurationSeconds = null,
|
||||||
|
DefaultDurationActions = null,
|
||||||
|
Category = AnvilBuffCategory.Utility,
|
||||||
|
LegacyStatusId = null,
|
||||||
|
},
|
||||||
|
[AnvilBuffKind.TrainedPerfection] = new BuffMechanicsEntry
|
||||||
|
{
|
||||||
|
StatusId = 3813,
|
||||||
|
IconId = 216129,
|
||||||
|
StackMax = 1,
|
||||||
|
Behavior = AnvilBuffBehavior.UseConsumed,
|
||||||
|
DefaultDurationSteps = null,
|
||||||
|
DefaultDurationSeconds = null,
|
||||||
|
DefaultDurationActions = null,
|
||||||
|
Category = AnvilBuffCategory.Utility,
|
||||||
|
LegacyStatusId = null,
|
||||||
|
},
|
||||||
|
|
||||||
|
// ---- Cosmic Exploration (Patch 7.x; catalog-present in v0.1.0) ----
|
||||||
|
[AnvilBuffKind.MaterialMiracleBuff] = new BuffMechanicsEntry
|
||||||
|
{
|
||||||
|
StatusId = 4220,
|
||||||
|
IconId = 216254,
|
||||||
|
StackMax = 1,
|
||||||
|
Behavior = AnvilBuffBehavior.TimedSeconds,
|
||||||
|
DefaultDurationSteps = null,
|
||||||
|
DefaultDurationSeconds = 45.0f,
|
||||||
|
DefaultDurationActions = null,
|
||||||
|
Category = AnvilBuffCategory.CosmicConditionShaper,
|
||||||
|
LegacyStatusId = null,
|
||||||
|
},
|
||||||
|
[AnvilBuffKind.StellarSteadyHandBuff] = new BuffMechanicsEntry
|
||||||
|
{
|
||||||
|
StatusId = 4839,
|
||||||
|
IconId = 211551,
|
||||||
|
StackMax = 1,
|
||||||
|
Behavior = AnvilBuffBehavior.ActionCounter,
|
||||||
|
DefaultDurationSteps = null,
|
||||||
|
DefaultDurationSeconds = null,
|
||||||
|
DefaultDurationActions = 3,
|
||||||
|
Category = AnvilBuffCategory.CosmicSuccessGuarantee,
|
||||||
|
LegacyStatusId = null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// Per-condition mechanics row. Mirrors AnvilCondition's stat multipliers and
|
||||||
|
// the per-step base probability. BaseProbability defaults to 0.0 in the
|
||||||
|
// table because FFXIV computes the spawn distribution from Recipe.IsExpert
|
||||||
|
// and RecipeLevelTable.Stars at simulator runtime - the static catalog has
|
||||||
|
// no useful per-recipe value to expose.
|
||||||
|
|
||||||
|
namespace Anvil.RecipeData.Mechanics;
|
||||||
|
|
||||||
|
internal sealed record ConditionMechanicsEntry
|
||||||
|
{
|
||||||
|
public required float QualityMultiplier { get; init; }
|
||||||
|
public required float ProgressMultiplier { get; init; }
|
||||||
|
public required float CpDiscountMultiplier { get; init; }
|
||||||
|
public required float DurabilityDiscountMultiplier { get; init; }
|
||||||
|
public required float BaseProbability { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
// Hardcoded condition mechanics table. Values follow the spec mechanics
|
||||||
|
// table in 01-RecipeData.md §2.5, which was itself verified against
|
||||||
|
// Artisan's Simulator.cs.
|
||||||
|
//
|
||||||
|
// Robust is the Cosmic-Exploration condition variant. Its mechanics follow
|
||||||
|
// the spec mechanics table (Quality-Mult = 1.0, Durability-Discount = 0.5,
|
||||||
|
// no other stat changes) and Artisan Simulator.cs:430. The Sturdy follow-up
|
||||||
|
// mapping that Artisan applies belongs to the simulator, not the catalog.
|
||||||
|
// (Note: the enum doc comment in AnvilCondition.cs mentions a "+50% quality"
|
||||||
|
// behaviour for Robust - that wording predates the verified mechanics table
|
||||||
|
// and the values below are the source of truth.)
|
||||||
|
//
|
||||||
|
// SplendorCosmic raises Good's quality multiplier from 1.5 to 1.75 on
|
||||||
|
// recipes flagged IsSplendorCosmic. The override lives in the simulator;
|
||||||
|
// the table holds the non-Splendor default.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Anvil.RecipeData.Mechanics;
|
||||||
|
|
||||||
|
internal static class ConditionMechanicsTable
|
||||||
|
{
|
||||||
|
public static IReadOnlyDictionary<AnvilConditionKind, ConditionMechanicsEntry> Entries { get; } =
|
||||||
|
new Dictionary<AnvilConditionKind, ConditionMechanicsEntry>
|
||||||
|
{
|
||||||
|
[AnvilConditionKind.Normal] = new ConditionMechanicsEntry
|
||||||
|
{
|
||||||
|
QualityMultiplier = 1.0f,
|
||||||
|
ProgressMultiplier = 1.0f,
|
||||||
|
CpDiscountMultiplier = 1.0f,
|
||||||
|
DurabilityDiscountMultiplier = 1.0f,
|
||||||
|
BaseProbability = 0.0f,
|
||||||
|
},
|
||||||
|
[AnvilConditionKind.Good] = new ConditionMechanicsEntry
|
||||||
|
{
|
||||||
|
QualityMultiplier = 1.5f,
|
||||||
|
ProgressMultiplier = 1.0f,
|
||||||
|
CpDiscountMultiplier = 1.0f,
|
||||||
|
DurabilityDiscountMultiplier = 1.0f,
|
||||||
|
BaseProbability = 0.0f,
|
||||||
|
},
|
||||||
|
[AnvilConditionKind.Excellent] = new ConditionMechanicsEntry
|
||||||
|
{
|
||||||
|
QualityMultiplier = 4.0f,
|
||||||
|
ProgressMultiplier = 1.0f,
|
||||||
|
CpDiscountMultiplier = 1.0f,
|
||||||
|
DurabilityDiscountMultiplier = 1.0f,
|
||||||
|
BaseProbability = 0.0f,
|
||||||
|
},
|
||||||
|
[AnvilConditionKind.Poor] = new ConditionMechanicsEntry
|
||||||
|
{
|
||||||
|
QualityMultiplier = 0.5f,
|
||||||
|
ProgressMultiplier = 1.0f,
|
||||||
|
CpDiscountMultiplier = 1.0f,
|
||||||
|
DurabilityDiscountMultiplier = 1.0f,
|
||||||
|
BaseProbability = 0.0f,
|
||||||
|
},
|
||||||
|
[AnvilConditionKind.Centered] = new ConditionMechanicsEntry
|
||||||
|
{
|
||||||
|
QualityMultiplier = 1.0f,
|
||||||
|
ProgressMultiplier = 1.0f,
|
||||||
|
CpDiscountMultiplier = 1.0f,
|
||||||
|
DurabilityDiscountMultiplier = 1.0f,
|
||||||
|
// +25% success rate is a simulator rule, not a stat multiplier.
|
||||||
|
BaseProbability = 0.0f,
|
||||||
|
},
|
||||||
|
[AnvilConditionKind.Sturdy] = new ConditionMechanicsEntry
|
||||||
|
{
|
||||||
|
QualityMultiplier = 1.0f,
|
||||||
|
ProgressMultiplier = 1.0f,
|
||||||
|
CpDiscountMultiplier = 1.0f,
|
||||||
|
DurabilityDiscountMultiplier = 0.5f,
|
||||||
|
BaseProbability = 0.0f,
|
||||||
|
},
|
||||||
|
[AnvilConditionKind.Pliant] = new ConditionMechanicsEntry
|
||||||
|
{
|
||||||
|
QualityMultiplier = 1.0f,
|
||||||
|
ProgressMultiplier = 1.0f,
|
||||||
|
CpDiscountMultiplier = 0.5f,
|
||||||
|
DurabilityDiscountMultiplier = 1.0f,
|
||||||
|
BaseProbability = 0.0f,
|
||||||
|
},
|
||||||
|
[AnvilConditionKind.Malleable] = new ConditionMechanicsEntry
|
||||||
|
{
|
||||||
|
QualityMultiplier = 1.0f,
|
||||||
|
ProgressMultiplier = 1.5f,
|
||||||
|
CpDiscountMultiplier = 1.0f,
|
||||||
|
DurabilityDiscountMultiplier = 1.0f,
|
||||||
|
BaseProbability = 0.0f,
|
||||||
|
},
|
||||||
|
[AnvilConditionKind.Primed] = new ConditionMechanicsEntry
|
||||||
|
{
|
||||||
|
QualityMultiplier = 1.0f,
|
||||||
|
ProgressMultiplier = 1.0f,
|
||||||
|
CpDiscountMultiplier = 1.0f,
|
||||||
|
DurabilityDiscountMultiplier = 1.0f,
|
||||||
|
// +2 step duration on newly-applied buffs is a simulator rule.
|
||||||
|
BaseProbability = 0.0f,
|
||||||
|
},
|
||||||
|
[AnvilConditionKind.GoodOmen] = new ConditionMechanicsEntry
|
||||||
|
{
|
||||||
|
QualityMultiplier = 1.0f,
|
||||||
|
ProgressMultiplier = 1.0f,
|
||||||
|
CpDiscountMultiplier = 1.0f,
|
||||||
|
DurabilityDiscountMultiplier = 1.0f,
|
||||||
|
// Forces Good as the next condition - simulator rule.
|
||||||
|
BaseProbability = 0.0f,
|
||||||
|
},
|
||||||
|
[AnvilConditionKind.Robust] = new ConditionMechanicsEntry
|
||||||
|
{
|
||||||
|
// Quality stays neutral; only durability is discounted.
|
||||||
|
QualityMultiplier = 1.0f,
|
||||||
|
ProgressMultiplier = 1.0f,
|
||||||
|
CpDiscountMultiplier = 1.0f,
|
||||||
|
DurabilityDiscountMultiplier = 0.5f,
|
||||||
|
BaseProbability = 0.0f,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user