Skip to content

Recipes ​

recipes/*.yml describes one item's strengthening rules: success rate per star, required materials, failure penalties, effects, and actions. Recipe files live in recipes/*.yml; a recipe's identity comes from the required id field, not the file name -- the file name only organizes files and locates errors

Top-level fields ​

FieldTypeRequiredDefaultRangeDescription
schema_versionintegerNo11~2 (outside this range the whole recipe is skipped)Material schema version; material entries are canonicalized by version
idstringYes——Unique recipe id; missing or blank skips the whole recipe
display_namestringNoThe id—Recipe display name (MiniMessage)
gui_templatestringNostrengthen_gui—GUI template reference (a template name under gui/, without .yml)
economyobjectNoenabled depends on currencies—Economy configuration charged per attempt
limitsobjectNoLimits.defaults()—Star/temper limits; see Limits
success_ratesmapNoEmpty (falls back to the global config.yml values)Keys are positive target starsBase success chance per star, in percent
matcherobjectNononeCoreLib general matcherNon-item-source conditions deciding which items can use this recipe. See Match rules
source_patternslistNoEmpty (any source)Regex stringsItem source matching (case-insensitive partial match), AND-combined with the other three fields. See Match rules
slot_groupslistNoEmpty (any slot group)weapon / armor / offhand / genericSlot group matching (lower-cased), AND-combined with the other three fields. See Match rules
stats_anylistNoEmpty (any attributes)EmakiAttribute attribute idsPasses when any one attribute is present; AND-combined with the other three fields
matchobjectNonone—Deprecated and no longer read. See Migrating from match
stat_linesmapNoEmptyKeys are attribute ids; subkeys belowStat-line template definitions
name_actionslistNononeName operation entriesGlobal name operations
lore_actionslistNononeLore operation entriesGlobal lore operations
starsmapNoEmptyKeys are positive target stars (≤0 skipped)Per-star stage definitions; see Star stage definition
branch_treeobjectNonone (linear recipe)—Branch tree definition. See Branch Trees
conditionobjectNononeCoreLib ConditionBlockActivation conditions for this recipe

Subkeys of stat_lines.<attribute id>:

FieldTypeRequiredDefaultRangeDescription
templatestringNo""—Stat-line template; blank falls back to the built-in default line
section_idstringNo""—Display section this line belongs to; blank uses strengthen.stats.secondary
section_orderintegerNo0≥ 0Section ordering weight; 0 infers from whether the section id contains primary

Economy ​

yaml
economy:
  enabled: true
  currencies:
    - provider: "vault"
      currency_id: ""
      base_cost: 320
      cost_formula: "%base_cost% * %star%"
      display_name: "<gold>Coins</gold>"

cost_formula is an expression; you can reference variables such as %base_cost% and %star%

FieldTypeRequiredDefaultRangeDescription
enabledbooleanNotrue when currencies is non-emptytrue / falseWhether currency costs apply; when off, no currency is charged
currencieslist<map>No[]—Currency entries, listed below
currencies[].providerstringNo""Economy provider idEconomy provider identifier (e.g. vault)
currencies[].currency_idstringNo""—Currency id within the provider; blank lets the provider decide
currencies[].base_costlongNo0≥ 0 (negatives become 0)Base cost referenced by cost_formula
currencies[].cost_formulastringNo%base_cost%ExpressionEffective cost expression; may reference %base_cost%, %star%, and similar
currencies[].display_namestringNo""—Cost display label

Example: a cost that grows linearly with the target star:

yaml
economy:
  enabled: true
  currencies:
    - provider: "vault"
      currency_id: ""
      base_cost: 100
      cost_formula: "%base_cost% * %star%"
      display_name: "<gold>Coins</gold>"

Limits ​

yaml
limits:
  max_star: 12
  max_temper: 4
  temper_chance_bonus_per_level: 5.0
  success_chance_cap: 90.0
FieldTypeRequiredDefaultRangeDescription
max_starintegerNo12≥ 1 (below 1 becomes 1)Maximum reachable star; strengthening stops once reached
max_temperintegerNo4≥ 0 (negatives become 0)Maximum temper level accumulated from failures
temper_chance_bonus_per_leveldoubleNo5.0≥ 0 (negatives become 0)Success chance bonus per temper level, in percentage points
success_chance_capdoubleNo90.00~100 (clamped)Upper success chance after all bonuses

Each star stage can also override its own cost with economy_override.currencies (same shape as the top-level economy.currencies)

Match rules ​

Four top-level fields decide whether the target matches, and all four must hold:

yaml
slot_groups:
  - "weapon"

# Item source condition at the top level goes in source_patterns
# Anchor the pattern to bind one exact source
source_patterns:
  - "^emakiitem-example_item$"
FieldTypeDescription
matcherobjectNon-item-source conditions only: component, PDC, lore, variable. Item source conditions written inside it (type: item_source and aliases) are rejected at load time and never match. Syntax is in Item Matcher
source_patternslistMatch the item source shorthand by regex. Case-insensitive partial match, so anchor it (^...$) to bind one exact source. Matching any one pattern passes
slot_groupslistCoarse slot/equipment type groups (weapon/armor/offhand/generic), not concrete slots such as main_hand or helmet
stats_anylistHas any of the given attributes

Migrating from match ​

The old match block is no longer read. Each field moves like this:

Old fieldCurrent form
match.source_idsAn anchored entry in the top-level source_patterns, such as "^minecraft-diamond_sword$"
match.lore_containsA type: component child with component: lore and operator: contains, one per fragment
match.slot_groupsPromoted to a top-level slot_groups
match.stats_anyPromoted to a top-level stats_any
match.source_patternsPromoted to a top-level source_patterns
match.source_typesCannot be converted automatically; rewrite it as a matcher condition by hand

/emakistrengthen convert-legacy performs the conversion:

CommandBehaviour
/emakistrengthen convert-legacyDry run; prints the diff it would produce without touching files
/emakistrengthen convert-legacy confirmRewrites recipes/*.yml and leaves a .bak backup per file

--apply is the same as confirm, and the command requires emakistrengthen.admin

WARNING

Files containing match.source_types are reported as unconvertible and skipped. Files declaring both match and an existing matcher (or the corresponding top-level field) are reported as conflicts and skipped. Both cases need manual handling

match.source_ids is converted automatically into anchored regexes merged into the top-level source_patterns, never into a node inside matcher, so the output needs no manual fix-up. Those regexes are exact matches: ^ / $ anchors are added and special characters in the ID are escaped, giving entries like ^\Qemakiitem-xxx\E$

Legacy material normalization and rejection ​

Stage-material parsing normalizes a legacy scalar item into item_sources, then resolves material_id in this order: explicit material_id, legacy id, count_key, a single source, and finally the stage index. An omitted count_key falls back to material_id. When no identity is declared but multiple sources are supplied, the parser cannot choose a safe unique identity and rejects the material without rewriting the source map. Note: these rules are established by the current JVM parser and local tests; old configuration and persisted-save behavior has not been verified on a live Paper or Folia server

TIP

To bind an exact item ID, anchor the source_patterns entry so the recipe does not accidentally match every weapon

General item matcher ​

A recipe has two matcher spots: the top-level matcher narrows the target item, and stars.*.materials[].matcher re-verifies strengthening materials. The syntax is the same, but the item source lives in a different place:

LocationHow the item source is expressed
Top-level matcherThrough the top-level source_patterns (regex). source_patterns, matcher, slot_groups, and stats_any are ANDed; all must hold
materials[].matcherThrough the sibling item_sources. Both are evaluated by the same ItemRequirement.test path: the source and non-item-source conditions must both hold

NOTE

Item source conditions never belong inside matcher here either -- at the top level use source_patterns, in materials use the sibling item_sources

The full syntax is in Item Matcher

yaml
id: enchanted_sword_strengthen

# Item source condition: only diamond swords
source_patterns:
  - "^minecraft-diamond_sword$"

# Component condition: with Sharpness 3+
matcher:
  type: component
  component: enchantments
  path: sharpness
  operator: '>='
  value: 3

stars:
  1:
    materials:
      # Source and matcher conditions are evaluated together; both must hold
      - item_sources:
          - minecraft-copper_ingot
        matcher:
          type: component
          component: custom_name
          operator: absent
        amount: 3

WARNING

Item sources use a hyphen (minecraft-copper_ingot), not a colon. Written as minecraft:copper_ingot in item_sources the parser treats it as invalid and silently discards it, leaving the item source unrestricted. See Item sources use hyphens, component IDs use colons

target.filter in enhancement recipes ​

enhancement_recipes/*.yml is a separate recipe set from recipes/*.yml, and its target.filter also accepts a general item matcher: when filter contains a type key it is parsed as a matcher, otherwise it keeps the existing provider-specific key/value behaviour

yaml
target:
  provider: equipment
  # Has a type key -> parsed as a general item matcher
  filter:
    type: component
    component: enchantments
    operator: exists
yaml
target:
  provider: equipment
  # No type key -> still interpreted as free-form provider keys (existing behaviour)
  filter:
    item_type: SWORD

Its materials[] entries follow the standard shape: item_sources for the allowed item sources plus a sibling matcher for non-item-source conditions, ANDed. Item source conditions that used to sit inside matcher go in item_sources now

NOTE

The remaining fields of the enhancement_recipes/ set are documented in Generic enhancement recipes

Star stage materials ​

FieldTypeRequiredDefaultRangeDescription
material_idstringNoFalls back to id, count_key, a single source, then the stage index—Stable material-selection and stage-rule identity. New configurations should declare it explicitly; declaring multiple sources without an identity rejects that material
count_keystringNomaterial_id—Quantity aggregation and consumption identity. Reuse one explicit value when several definitions share a counter
item_sourceslistNoEmpty (any source)Item source shorthand (hyphenated, e.g. minecraft-copper_ingot)Allowed material item sources. Legacy scalar item is normalized only while reading
matcherobjectNononeCoreLib general matcherNon-item-source conditions. The sibling item_sources and matcher are ANDed through the same ItemRequirement.test path; with both omitted the material never matches. See Item Matcher
amountintegerNo1-1 or ≥ 1Required amount. Use -1 to require possession without consuming
optionalbooleanNofalsetrue / falseWhether the material is optional
protectionbooleanNofalsetrue / falsePrevents a star drop on failure
temper_boostintegerNo0≥ 0 (negatives become 0)Extra temper levels supplied for this attempt
yaml
materials:
  - material_id: "copper_ingot"
    count_key: "copper_ingot"
    item_sources:
      - "minecraft-copper_ingot"
    amount: 3
  # Protection material: not consumed, only checked
  - material_id: "protection_gold_nugget"
    count_key: "protection_gold_nugget"
    item_sources:
      - "minecraft-gold_nugget"
    amount: -1
    optional: true
    protection: true

Success rates ​

yaml
success_rates:
  1: 100.0
  2: 95.0
  3: 90.0
  4: 80.0
  5: 70.0

Keys are the target star, values are percentages. Temper bonuses apply on top of the base rate and are capped by limits.success_chance_cap

Star stage definition (stars) ​

Each star stage can carry these fields:

FieldTypeRequiredDefaultRangeDescription
namestringNo""—Milestone name for this star (used by the GUI)
effectslist<map>No[]effect entries, type values under Effect typesEffect list (one unified format)
materialslist<map>No[]see Star stage materialsMaterials required by this star
economy_overrideobjectNocurrencies: []same shape as the top-level economy.currenciesOverrides this star's cost; when non-empty it takes precedence over the top-level economy
actionsobjectNononekeys success / failureActions run on success/failure

Star example ​

yaml
stars:
  1:
    effects:
      - type: "variables"
        variables:
          physical_attack: 4
      - type: "lore_action"
        lore_actions:
          - action: "append"
            content:
              - "<gray>Physical Damage: <gold>+4</gold></gray>"
    materials:
      - item_sources:
          - "minecraft-copper_ingot"
        amount: 1
      - item_sources:
          - "minecraft-gunpowder"
        amount: 2

  2:
    effects:
      - type: "variables"
        variables:
          physical_attack: 4
      - type: "lore_action"
        lore_actions:
          - action: "replace_line"
            anchor: "Physical Damage:"
            content:
              - "<gray>Physical Damage: <gold>+8</gold></gray>"
    materials:
      - item_sources:
          - "minecraft-copper_ingot"
        amount: 1

Effect types ​

FieldTypeRequiredDefaultRangeDescription
effects[].typestringYes—variables / ea_attribute / es_skill / lore_action / name_actionEffect type; entries outside this list are ignored
effects[].variablesmapNononeattribute id → number or expressionRead for type: variables; used by lore, name/lore templates, and placeholder context. They do not automatically become Attribute PDC attributes
effects[].ea_attributesmapNononeattribute id → number or expressionRead for type: ea_attribute; written to the EmakiAttribute PDC attribute layer (explicit override)
effects[].es_skillslistNononeEmakiSkills skill idsRead for type: es_skill; skill attachment
effects[].lore_actionslistNononelore operation entriesRead for type: lore_action; lore modification
effects[].name_actionslistNononename operation entriesRead for type: name_action; item name modification

Besides the effects list, a star stage also accepts the sibling keys variables, ea_attributes, name_action, and lore_action; they accumulate with the matching effects entries and are read with the same semantics

Current effect boundaries:

  • variables only provide text, placeholder, and operation-template context
  • Real attribute output must be configured explicitly with type: "ea_attribute" and ea_attributes
  • Skill attachments use type: "es_skill", while concrete skill ids are stored in the es_skills list

Notes ​

  • Use high success rates for early stars and raise material cost gradually
  • Use protection materials for high-risk levels when item downgrade or destruction is enabled by recipe logic
  • Prefer replace_line for repeated stat lines so lore never duplicates
  • After you change recipe ids or match rules, test old strengthened items

Forge quality variables ​

The variable.<name> keys of target.filter, the target matcher, and recipe conditions can all reference forge-quality variables read from the target item's PDC; they come from the emakiforge namespace PDC that EmakiForge writes onto items

VariableTypeWhen the item has no forge dataDescription
forge_quality_idstring""Quality tier id
forge_quality_displaystring""Quality display name
forge_quality_multiplierdouble1.0Quality multiplier
forge_recipe_idstring""Forge recipe id

Every variable has three equivalent spellings -- pick any one:

Canonical nameDotted spelling (legacy)Short spelling
forge_quality_idforge.quality_idquality_id
forge_quality_displayforge.quality_displayquality_display
forge_quality_multiplierforge.quality_multiplierquality_multiplier
forge_recipe_idforge.forge_recipe_idnone

Why the dotted spelling is kept

Forge's PDC keys were renamed from forge.quality_id to forge_quality_id (see PDC key naming). The dotted spelling stays as a legacy alias so existing configuration needs no change; use the canonical name in new configuration

yaml
target:
  provider: equipment
  filter:
    # filter is an equality check: matches only tier legendary
    variable.forge_quality_id: legendary

filter is equality-only

Every entry in filter is an equality comparison; >= and < are not supported. Numbers compare numerically, everything else compares as case-insensitive strings. Writing variable.forge_quality_multiplier: '>= 1.5' does not error, but compares the string ">= 1.5" against the real multiplier and never matches

For numeric ranges, use the target matcher (type: variable_expr) or recipe conditions instead

There is also a helper variable forge_quality_multiplier_valid: it is 1 when the item's multiplier parses as a finite positive number and 0 when it is broken (non-numeric, negative, NaN), and then forge_quality_multiplier falls back to 1.0. Use it to tell "never forged" apart from "forge data is corrupt"

Unknown keys in filter make the whole rule non-matching

A filter key that is neither built-in nor in the variable table makes that filter rule non-matching. The four variables above always exist (taking their defaults without forge data), so they are safe to reference

Generic enhancement recipes (enhancement_recipes) ​

enhancement_recipes/*.yml holds recipes for the generic enhancement framework (target / materials / costs / chance / pity / actions), a set separate from recipes/*.yml. The split: EmakiStrengthen owns material matching and consumption, currency charging, chance, pity, idempotency, and compensation; target recognition and the level/stage write-back belong to the registered Target Provider

Top-level fields ​

FieldTypeRequiredDefaultRangeDescription
schema_versionintegerNo11~2 (outside this range the recipe is skipped)Material schema version
idstringYes——Unique recipe id; missing or blank skips the recipe
modestringYes—equipment / affix / gemSemantic group; missing, blank, or outside the range skips the recipe
targetobjectYes—see targetTarget configuration; a blank provider skips the recipe
materialslist<map>No[]see materials[]Material slot list; one slot failing to parse skips the recipe
costslist<map>No[]see costs[]Currency cost list; one entry failing to parse skips the recipe
chancenumber | objectNo1.00~1 probability, or CoreLib Quantity (fixed / formula / lookup_table)Success chance; an invalid value skips the recipe
pityobjectNononesee pitySingle pity track; present but invalid (e.g. missing group) skips the recipe
pity_trackslist<map>No[]see pityMulti-track pity; one invalid track skips the recipe
conditionobjectNononeCoreLib ConditionBlockEntry conditions, evaluated inside prepare() so they apply to both preview and the real attempt
actionsmapNo{}free-named CoreLib action line listsAction groups dispatched per result phase (e.g. on_success / on_failure)

target ​

FieldTypeRequiredDefaultRangeDescription
providerstringYes—a registered Target Provider id (built-ins: equipment / affix / gem)Target provider; blank skips the recipe
filtermapNononeparsed as a general item matcher when it contains a type key, otherwise provider-specific key/value pairsTarget filter, see target.filter in enhancement recipes

materials[] ​

FieldTypeRequiredDefaultRangeDescription
item_sourceslistNoEmpty (any source)item source shorthandAND-combined with the sibling matcher; with both omitted the slot fails
matcherobjectNononeCoreLib matcherComponent, PDC, lore, and other non-item-source conditions
quantitynumber | objectNo1CoreLib QuantityRequired quantity
consumestringNoalwaysalways / success / failure / neverConsumption timing; an invalid value falls back to always
requiredbooleanNotruetrue / falsefalse makes a shortage non-blocking (equivalent to optional: true)
optionalbooleanNononetrue / falseThe inverse spelling of required; when both appear, optional wins
material_idstringNocount_key, then source identity, then matcher identity—Rule and selection identity
count_keystringNomaterial_id—Quantity aggregation and consumption identity
target_comparestringNoEmpty (no comparison)same_affix / same_affix_set / same_item_type / same_level / level_at_leastCompares this slot's material with the current target; an invalid value means no comparison

costs[] ​

FieldTypeRequiredDefaultRangeDescription
providerstringNo""economy provider idEconomy provider identifier
currency_idstringYes——Currency id; the alias currency also works. Blank makes the entry fail to parse and, because the entry count then differs, skips the whole recipe
amountnumber | objectNoQuantity defaultCoreLib QuantityCost amount

pity / pity_tracks[] ​

FieldTypeRequiredDefaultRangeDescription
scopestringNoitemitem / playerCounter scope; an invalid value falls back to item
groupstringYes——Counter group; blank discards the whole pity block
thresholdintegerNo10≥ 1 (≤0 discards the block)Trigger threshold; mutually exclusive with formula
formulaobjectNononeCoreLib QuantityTrigger expression; mutually exclusive with threshold
effectstringNoforce_successforce_success / chance_bonusPity effect; an invalid value falls back to force_success
bonus_valuedoubleNononerequired and > 0 when chance_bonusBonus for chance_bonus; a non-finite value discards the block
decay.typestringNoresetreset / fixed_decay / proportionalCounter decay mode; an invalid value falls back to reset, an out-of-range value discards the block
decay.valuedoubleNo0.0fixed_decay ≥ 0; proportional 0~1Decay amount
isolatelistNoEmptyrecipe / mode / target / affix / levelBuckets this track's counter by dimension; unknown or duplicate dimensions make the recipe fail to load

pity and pity_tracks merge into a single track list; tracks with the same scope + group are silently de-duplicated, and the first one wins

Below is the shipped example (excerpt from enhancement_recipes/example_enhancement_recipe.yml):

yaml
schema_version: 2
id: example_equipment_enhance
mode: equipment

target:
  provider: equipment
  filter:
    item_type: SWORD

materials:
  - item_sources:
      - "minecraft-diamond"
    quantity: 2
    consume: always
  - matcher:
      type: pdc_match
      key: "emakiitem:item_type"
      value: "enhance_stone"
    quantity:
      type: formula
      expression: "1 + floor(target.level / 5)"
    consume: success

costs:
  - provider: vault
    currency_id: money
    amount:
      type: formula
      expression: "100 * (target.level + 1)"

chance:
  type: formula
  expression: "max(0.05, 0.9 - target.level * 0.05)"

pity:
  scope: item
  group: example_equipment_enhance
  threshold: 10
  effect: force_success
  decay:
    type: reset
    value: 0

actions:
  on_success:
    - "sound: ITEM_TOTEM_USE"
  on_failure:
    - "sound: BLOCK_ANVIL_USE"