Files
Anvil/Anvil/RecipeData/AnvilRecipe.cs
T
JonKazama-Hellion 825c5d3c5c Modul 01 Hotfix A4: AnvilRecipe.IsCollectable ergänzt
01-RecipeData Rev 6 (A4) added the field as a Summarize-branch
discriminator for module 02. Resolved as IsExpertRecipe OR the
output item's AlwaysCollectable flag, the latter being the canonical
FFXIV signal for collectable submissions (custom delivery, masterpiece
supply, collectables shop).

- AnvilRecipe.cs: new required bool property after CanHQ, with a
  why-comment pointing at module 02's Summarize branch and noting
  the OR-resolve.
- LuminaRecipeAdapter.cs: resolve folded into the recipe walk via
  the same itemSheet.TryGetRow call that already fetches the
  display name - no bootstrap-order question, no 2-pass needed.
  hasOutputItem is hoisted so the OR-branch can guard against a
  missing item-sheet row without touching the default(Item) struct.
- RecipeDataAdapterLoadStep.cs: new pass criterion #10 that asserts
  at least one recipe surfaces IsCollectable == true. Catches a
  silent resolve-path failure. Existing Cosmic-surface check shifts
  to #11 to keep numbering stable.

Build clean (0/0), csharpier clean.
2026-05-28 19:26:53 +02:00

74 lines
3.7 KiB
C#

// 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; }
// Character-level equivalent of the recipe's difficulty (e.g. 90 for an
// Endwalker 3-star, 100 for a Dawntrail endgame recipe). Not the same as
// RecipeLevel above - that field is the RecipeLevelTable row id, this one
// is its ClassJobLevel column. Module 02 uses it for the mod-penalty
// branch in BaseProgress and BaseQuality.
public required byte RecipeLevelTableClassJobLevel { 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; }
// True when the recipe is a collectable submission (custom delivery,
// masterpiece supply, collectables shop). Module 02's Summarize branch
// uses this discriminator. Resolved as IsExpertRecipe OR the output
// item's AlwaysCollectable flag - the latter is the canonical FFXIV
// signal for collectable submissions.
public required bool IsCollectable { 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 is still set by the recipe-detection path
// so the catalog knows which recipes belong to the Cosmic surface even
// while the mission flags stay dormant. The Splendorous-tool bonus is
// character-equipment state rather than recipe state, so it lives in
// the simulator's stats layer (CraftStats.HasSplendorousTool in module
// 02) instead of carrying a flag on this record.
//
// Adapter invariant (validated when the catalog is built):
// 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 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);