feat(recipedata): add plain-data records and enums
Module 01 public-API surface: six sealed records with init-only properties plus their nine enums. All BCL-only - no Lumina or Dalamud types in any public property, which is what keeps the simulator and catalog xUnit-testable per the Critical Boundary in 00-Anvil-Scope §3.3. - AnvilRecipe + AnvilRecipeIngredient: flat representation of the Recipe + RecipeLevelTable sheet pair with the five Cosmic-Exploration flags. v0.1.0 ships with MissionHas* always false (SH-15 option B); the schema stays in place so v0.2.0 can wire the WKS mission sheet without touching the type. - AnvilItem: slim per-recipe / per-food item view (full ~50k-row item mirror is left to the UI layer). - AnvilAction + AnvilActionKind + AnvilActionCategory + AnvilActionFlags: one logical action per Kind value with RowIdByClassJob mapping; Cosmic actions use ClassJobId 0 as the sentinel for "every crafter". AnvilActionFlags bit 1 << 2 is intentionally vacant (ConsumesGreatStrides was removed in spec rev 5; consumption logic lives in 02-CraftingSimulator). - AnvilBuff + AnvilBuffKind + AnvilBuffBehavior + AnvilBuffCategory: static buff catalog entry with the duration field that matches Behavior (Steps / Seconds / Actions). Two Cosmic buffs (MaterialMiracleBuff, StellarSteadyHandBuff). - AnvilCondition + AnvilConditionKind: eleven conditions including the Cosmic Robust variant. DisplayName comes from AnvilStrings.resx, not Lumina (the Status sheet does not expose the crafting condition labels cleanly). - AnvilFood + AnvilFoodBonus + AnvilFoodKind + AnvilFoodStat: ItemFood mirror with IsRelative flag (percentage vs flat bonus).
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
// One AnvilAction represents the logical action ("Basic Synthesis"), not the
|
||||
// eight per-job sheet rows that back it. The simulator and solver operate on
|
||||
// the logical identifier; the RowIdByClassJob map keeps the per-job RowIds
|
||||
// reachable for UseAction / IsActionHighlighted hooks. Cosmic-Exploration
|
||||
// actions break the eight-row rule and are stored under the sentinel
|
||||
// ClassJobId 0 (covers every crafter job at once).
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Anvil.RecipeData;
|
||||
|
||||
public sealed record AnvilAction
|
||||
{
|
||||
public required AnvilActionKind Kind { get; init; }
|
||||
public required string DisplayName { get; init; }
|
||||
public required uint IconId { get; init; }
|
||||
public required byte LevelRequired { 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 AnvilActionCategory Category { get; init; }
|
||||
public required AnvilBuffKind? GrantsBuff { get; init; }
|
||||
public required byte IqStackBonus { get; init; }
|
||||
public required AnvilActionFlags Flags { get; init; }
|
||||
public required IReadOnlyDictionary<uint, uint> RowIdByClassJob { get; init; }
|
||||
public required bool SpecialistOnly { get; init; }
|
||||
public required byte ChargesPerCraft { get; init; }
|
||||
}
|
||||
|
||||
public enum AnvilActionKind
|
||||
{
|
||||
// Progress actions (8)
|
||||
BasicSynthesis,
|
||||
CarefulSynthesis,
|
||||
RapidSynthesis,
|
||||
IntensiveSynthesis,
|
||||
PrudentSynthesis,
|
||||
Groundwork,
|
||||
MuscleMemory,
|
||||
DelicateSynthesis,
|
||||
|
||||
// Quality actions (14)
|
||||
BasicTouch,
|
||||
StandardTouch,
|
||||
AdvancedTouch,
|
||||
PreciseTouch,
|
||||
PreparatoryTouch,
|
||||
PrudentTouch,
|
||||
RefinedTouch,
|
||||
ByregotsBlessing,
|
||||
HastyTouch,
|
||||
DaringTouch,
|
||||
TrainedEye,
|
||||
TrainedFinesse,
|
||||
Reflect,
|
||||
TricksOfTheTrade,
|
||||
|
||||
// Buff actions (7)
|
||||
Veneration,
|
||||
Innovation,
|
||||
GreatStrides,
|
||||
FinalAppraisal,
|
||||
Manipulation,
|
||||
WasteNot,
|
||||
WasteNot2,
|
||||
|
||||
// Repair actions (2)
|
||||
MastersMend,
|
||||
ImmaculateMend,
|
||||
|
||||
// Observe / Specialist (5)
|
||||
Observe,
|
||||
CarefulObservation,
|
||||
HeartAndSoul,
|
||||
QuickInnovation,
|
||||
TrainedPerfection,
|
||||
|
||||
// Cosmic Exploration (2)
|
||||
MaterialMiracle,
|
||||
StellarSteadyHand,
|
||||
}
|
||||
|
||||
public enum AnvilActionCategory
|
||||
{
|
||||
HybridProgressQuality,
|
||||
Cosmic,
|
||||
ProgressAction,
|
||||
QualityAction,
|
||||
BuffAction,
|
||||
RepairAction,
|
||||
ObserveAction,
|
||||
}
|
||||
|
||||
// Bit 1 << 2 is intentionally vacant. An earlier draft used it for
|
||||
// ConsumesGreatStrides; the rule lives in the simulator (every quality
|
||||
// action with EfficiencyQuality > 0 consumes an active GreatStrides) and
|
||||
// does not need a per-action data-layer flag. Keeping the slot empty avoids
|
||||
// renumbering downstream bits if/when persistence consumes this enum.
|
||||
[System.Flags]
|
||||
public enum AnvilActionFlags : ushort
|
||||
{
|
||||
None = 0,
|
||||
RequiresGoodOrExcellent = 1 << 0,
|
||||
RequiresFirstStep = 1 << 1,
|
||||
// 1 << 2 reserved (see comment above)
|
||||
SpecialistOnly = 1 << 3,
|
||||
NoBuffTick = 1 << 4,
|
||||
NoConditionChange = 1 << 5,
|
||||
RequiresExpedience = 1 << 6,
|
||||
RequiresFullIQ = 1 << 7,
|
||||
RequiresLowLevelRecipe = 1 << 8,
|
||||
CosmicOnly = 1 << 9,
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Static buff definition. The runtime "how many stacks, how many steps left"
|
||||
// state lives in the simulator (02-CraftingSimulator). RecipeData only owns
|
||||
// the immutable catalog entry: StatusId + Icon + StackMax + Behavior plus
|
||||
// the three duration fields that are populated depending on Behavior.
|
||||
//
|
||||
// MaxStacks normalisation: the Status sheet encodes "single-active buff
|
||||
// without a stack counter" as MaxStacks=0. Anvil normalises that to 1 so
|
||||
// consumers do not have to distinguish two zero-states.
|
||||
|
||||
namespace Anvil.RecipeData;
|
||||
|
||||
public sealed record AnvilBuff
|
||||
{
|
||||
public required AnvilBuffKind Kind { get; init; }
|
||||
public required uint StatusId { get; init; }
|
||||
public required string DisplayName { 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; }
|
||||
}
|
||||
|
||||
public enum AnvilBuffKind
|
||||
{
|
||||
InnerQuiet,
|
||||
WasteNot,
|
||||
WasteNot2,
|
||||
Veneration,
|
||||
GreatStrides,
|
||||
Innovation,
|
||||
FinalAppraisal,
|
||||
MuscleMemory,
|
||||
Manipulation,
|
||||
HeartAndSoul,
|
||||
Expedience,
|
||||
TrainedPerfection,
|
||||
|
||||
// Cosmic Exploration (Patch 7.x)
|
||||
MaterialMiracleBuff,
|
||||
StellarSteadyHandBuff,
|
||||
}
|
||||
|
||||
public enum AnvilBuffBehavior
|
||||
{
|
||||
StepCounter,
|
||||
HybridStepOrUse,
|
||||
UseConsumed,
|
||||
StackBased,
|
||||
InstantTrigger,
|
||||
TimedSeconds,
|
||||
ActionCounter,
|
||||
}
|
||||
|
||||
public enum AnvilBuffCategory
|
||||
{
|
||||
QualityBooster,
|
||||
ProgressBooster,
|
||||
DurabilitySaver,
|
||||
DurabilityRepair,
|
||||
ConditionBypass,
|
||||
Utility,
|
||||
CosmicConditionShaper,
|
||||
CosmicSuccessGuarantee,
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Static condition catalog. DisplayName comes from AnvilStrings.resx rather
|
||||
// than Lumina because the in-game Status sheet does not expose the crafting
|
||||
// condition labels (Centered / Sturdy / ...) in a directly consumable form.
|
||||
|
||||
namespace Anvil.RecipeData;
|
||||
|
||||
public sealed record AnvilCondition
|
||||
{
|
||||
public required AnvilConditionKind Kind { get; init; }
|
||||
public required string DisplayName { get; init; }
|
||||
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; }
|
||||
}
|
||||
|
||||
// Robust is the Cosmic-Exploration condition variant. Mechanics follow the
|
||||
// ConditionMechanicsTable: Sturdy-equivalent durability discount, no
|
||||
// quality multiplier. The simulator maps Robust to Sturdy as the default
|
||||
// follow-up condition (Artisan Simulator.cs:522).
|
||||
public enum AnvilConditionKind
|
||||
{
|
||||
Normal,
|
||||
Good,
|
||||
Excellent,
|
||||
Poor,
|
||||
Centered,
|
||||
Sturdy,
|
||||
Pliant,
|
||||
Malleable,
|
||||
Primed,
|
||||
GoodOmen,
|
||||
Robust,
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Food / medicine with crafting-relevant stat bonuses. The adapter filters
|
||||
// ItemFood entries to the three crafting BaseParams (CP=11, Craftsmanship=70,
|
||||
// Control=71) and drops everything else (Spiritbond, Reduced-Durability-Loss
|
||||
// etc. - not solver-relevant).
|
||||
//
|
||||
// AnvilFoodBonus mirrors the Lumina ItemFood.Params shape 1:1 including the
|
||||
// IsRelative flag so the simulator can tell percentage bonuses (typical for
|
||||
// crafting consumables) from flat bonuses without re-walking the sheet.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Anvil.RecipeData;
|
||||
|
||||
public sealed record AnvilFood
|
||||
{
|
||||
public required uint ItemId { get; init; }
|
||||
public required string DisplayName { get; init; }
|
||||
public required uint IconId { get; init; }
|
||||
public required short DurationSeconds { get; init; }
|
||||
public required AnvilFoodKind Kind { get; init; }
|
||||
public required IReadOnlyList<AnvilFoodBonus> Bonuses { get; init; }
|
||||
public required bool HasHqVariant { get; init; }
|
||||
}
|
||||
|
||||
public sealed record AnvilFoodBonus
|
||||
{
|
||||
public required AnvilFoodStat Stat { get; init; }
|
||||
public required short Value { get; init; }
|
||||
public required short ValueHq { get; init; }
|
||||
public required short Max { get; init; }
|
||||
public required short MaxHq { get; init; }
|
||||
public required bool IsRelative { get; init; }
|
||||
}
|
||||
|
||||
public enum AnvilFoodKind
|
||||
{
|
||||
Food,
|
||||
Medicine,
|
||||
}
|
||||
|
||||
public enum AnvilFoodStat
|
||||
{
|
||||
Craftsmanship,
|
||||
Control,
|
||||
Cp,
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Slim item view. The catalog only holds items that appear as recipe output,
|
||||
// recipe ingredients, or food/medicine entries. A full Item-sheet mirror
|
||||
// would be ~50k rows and is the UI layer's problem when (later) a global
|
||||
// item search lands.
|
||||
|
||||
namespace Anvil.RecipeData;
|
||||
|
||||
public sealed record AnvilItem
|
||||
{
|
||||
public required uint ItemId { get; init; }
|
||||
public required string DisplayName { get; init; }
|
||||
public required uint IconId { get; init; }
|
||||
public required byte ItemLevel { get; init; }
|
||||
public required bool CanBeHq { get; init; }
|
||||
public required bool IsCollectable { get; init; }
|
||||
public required bool IsCraftable { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Plain-data record describing a single FFXIV crafting recipe. Public surface
|
||||
// is intentionally BCL-only (no Lumina or Dalamud types) so the simulator and
|
||||
// solver can be tested in xUnit without loading Dalamud.dll into the test
|
||||
// AppDomain. The adapter (Internal/LuminaRecipeAdapter) builds these from the
|
||||
// Recipe + RecipeLevelTable sheet pair and flattens both sheets into one
|
||||
// record so consumers never have to do a two-sheet walk.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Anvil.RecipeData;
|
||||
|
||||
public sealed record AnvilRecipe
|
||||
{
|
||||
public required uint RecipeId { get; init; }
|
||||
public required uint OutputItemId { get; init; }
|
||||
public required byte OutputAmount { get; init; }
|
||||
public required uint ClassJobId { get; init; }
|
||||
public required ushort RecipeLevel { get; init; }
|
||||
public required int Difficulty { get; init; }
|
||||
public required int QualityMax { get; init; }
|
||||
public required byte Durability { get; init; }
|
||||
public required int RequiredCraftsmanship { get; init; }
|
||||
public required int RequiredControl { get; init; }
|
||||
public required int QualityForHQ { get; init; }
|
||||
public required bool IsExpertRecipe { get; init; }
|
||||
public required bool CanHQ { get; init; }
|
||||
|
||||
// Cosmic Exploration (Patch 7.x). v0.1.0 ships with MissionHas* always
|
||||
// false (SH-15 option B) - the data schema stays in place so v0.2.0+ can
|
||||
// light up the surface by resolving the WKS mission sheet without
|
||||
// touching the type. IsCosmic / IsSplendorCosmic are still set by the
|
||||
// recipe-detection path so the catalog knows which recipes belong to the
|
||||
// Cosmic surface even while the action flags stay dormant.
|
||||
//
|
||||
// Adapter invariant (validated when the catalog is built):
|
||||
// IsSplendorCosmic == true implies IsCosmic == true
|
||||
// MissionHasMaterialMiracle == true implies IsCosmic == true
|
||||
// MissionHasSteadyHand == true implies IsCosmic == true
|
||||
//
|
||||
// Inconsistent recipe states (sub-flag without master flag) throw at
|
||||
// catalog-build time instead of silently corrupting the simulator.
|
||||
public required bool IsCosmic { get; init; }
|
||||
public required bool IsSplendorCosmic { get; init; }
|
||||
public required bool MissionHasMaterialMiracle { get; init; }
|
||||
public required bool MissionHasSteadyHand { get; init; }
|
||||
public required bool IsIshgardExpert { get; init; }
|
||||
|
||||
public required byte Stars { get; init; }
|
||||
public required byte ProgressDivider { get; init; }
|
||||
public required byte ProgressModifier { get; init; }
|
||||
public required byte QualityDivider { get; init; }
|
||||
public required byte QualityModifier { get; init; }
|
||||
public required IReadOnlyList<AnvilRecipeIngredient> Ingredients { get; init; }
|
||||
public required string DisplayName { get; init; }
|
||||
}
|
||||
|
||||
public sealed record AnvilRecipeIngredient(uint ItemId, byte Amount);
|
||||
Reference in New Issue
Block a user