Skip to content

Quality System ​

The quality system picks the tier a forge result lands on and also scales material effect values and the item presentation

Global configuration ​

Quality is configured globally under the quality node of config.yml. Shipped defaults:

yaml
quality:
  tiers:
    - name: "平庸"
      weight: 60
      multiplier: 1.0
    - name: "精良"
      weight: 30
      multiplier: 1.05
    - name: "有缺"
      weight: 10
      multiplier: 0.95
    - name: "优质"
      weight: 5
      multiplier: 1.1
    - name: "无暇"
      weight: 2
      multiplier: 1.15
    - name: "完美"
      weight: 1
      multiplier: 1.2
  default_tier: "平庸"
  guarantee:
    enabled: true
    threshold: 60
    minimum: "无暇"
  item_meta:
    enabled: true
    tiers:
      无暇:
        name_actions:
          - action: "prepend_prefix"
            value: "<color:#4B64C8>[无暇] </color>"
        lore_actions:
        action:
          - 'broadcast_message text="<color:#4B64C8>%player_name% forged a 无暇 item: %var.forge_show_item%</color>"'
      完美:
        name_actions:
          - action: "prepend_prefix"
            value: "<color:#FFD700>[完美] </color>"
        lore_actions:
        action:
          - 'broadcast_message text="<color:#FFD700>%player_name% forged a 完美 item: %var.forge_show_item%</color>"'
      # 平庸 / 精良 / 有缺 / 优质 only define name_actions and no broadcast action

config.yml registers 6 item_meta.tiers entries; the sample above omits the other 4, which share the same shape as 无暇 but define only name_actions

quality fields ​

FieldTypeRequiredDefaultAllowed valuesDescription
quality.tierslist<object>NoA single 普通 tier (weight 100, multiplier 1.0)Each entry is a record or a "name-weight-multiplier" stringQuality pool. When empty, or when every entry is invalid, Forge falls back to the built-in default pool and disables pity and item_meta as well
quality.tiers[].namestringYes (when the record exists)—Non-blank stringUnique tier id and display name. Entries with a blank name are ignored
quality.tiers[].weightintegerYes (when the record exists)—> 0Draw weight; higher means more common. Entries with <= 0 are ignored
quality.tiers[].multipliernumberNo1.0Any numberMultiplier applied to material effects (configured value × material amount × quality multiplier)
quality.default_tierstringNo普通A tier name present in the poolFallback tier when no valid tier name matched. If the name does not exist, the first pool entry is used
quality.guaranteeobjectNosee below—Pity configuration, see Pity
quality.item_metaobjectNosee below—Quality item metadata, see Item meta by quality
yaml
quality:
  tiers:
    - name: "普通"
      weight: 70
      multiplier: 1.0
    - name: "精良"
      weight: 25
      multiplier: 1.05
    - name: "史诗"
      weight: 5
      multiplier: 1.15
  default_tier: "普通"

tiers format ​

Each quality.tiers entry can be written either as the record above or as a "name-weight-multiplier" string split by -; an entry that fails both parses is ignored

NOTE

If every tiers entry fails to parse, Forge falls back to the built-in default pool (a single 普通 tier with weight 100 and multiplier 1.0), with pity and item_meta both disabled

Pity ​

The "Default" column below is what the code uses when the key is missing from your config. It differs from the shipped config.yml, which sets enabled: true, threshold: 60, and minimum: "无暇" — so the default configuration does have pity enabled

FieldTypeRequiredDefaultAllowed valuesDescription
quality.guarantee.enabledbooleanNofalsetrue / falseWhether global pity is enabled. The shipped value is true
quality.guarantee.thresholdintegerNo10>= 1 (values below 1 are raised to 1)Number of consecutive attempts below the minimum tier before pity triggers. The shipped value is 60
quality.guarantee.minimumstringNo普通A tier name present in the poolMinimum tier granted when pity triggers. When the name does not exist it falls back to default_tier. The shipped value is 无暇

Pity rules:

  • After each forge, the drawn tier's position in the quality pool is compared with the pity minimum tier's position
  • If the drawn tier ranks lower, the pity counter increases by 1; otherwise it resets to 0
  • Once the counter reaches threshold - 1, the next forge directly grants the pity tier and resets the counter
yaml
quality:
  guarantee:
    enabled: true
    threshold: 30
    minimum: "精良"

NOTE

Pity counters are tracked per player and recipe and stored in data/<player-uuid>.yml. The force mode of quality_modify skips all pity logic: it neither triggers pity nor updates the counter. Recipe-level pity (attempts) and global pity never both take effect; see Recipe override

Item meta by quality ​

When quality.item_meta.enabled is true, the result item receives quality-specific name operations, lore operations, and actions

FieldTypeRequiredDefaultAllowed valuesDescription
quality.item_meta.enabledbooleanNofalsetrue / falseWhether the tier's name, lore, and actions are written to the forge result. The shipped value is true
quality.item_meta.tiers.<tier>objectNoemptyKey is a tier name, case-insensitiveMetadata registered per tier. An entry whose tier name is not in the registry has no effect
quality.item_meta.tiers.<tier>.name_actionsobject | listNonot registeredCoreLib name operationsName operations applied to the result item. A null value (key written without a value) does not register a name action for that tier
quality.item_meta.tiers.<tier>.lore_actionsobject | listNonot registeredCoreLib lore operationsLore operations applied to the result item. In the shipped config.yml this key is written without a value (null), which means "do not register a lore action for this tier"; that differs from an explicit empty list [], which registers an empty action chain
quality.item_meta.tiers.<tier>.actionlist<string>Noempty listCoreLib action linesAction lines executed per tier, such as a server-wide broadcast

A copyable example: prefix 精良 items and let 完美 trigger a broadcast

yaml
quality:
  item_meta:
    enabled: true
    tiers:
      精良:
        name_actions:
          - action: "prepend_prefix"
            value: "<color:#1F871F>[精良] </color>"
      完美:
        name_actions:
          - action: "prepend_prefix"
            value: "<color:#FFD700>[完美] </color>"
        action:
          - 'broadcast_message text="<color:#FFD700>%player_name% forged a 完美 item: %var.forge_show_item%</color>"'

Quality metadata actions, recipe result.success actions, and material effect actions are merged and then applied to the result item together

Deterministic calculation ​

Quality uses a deterministic algorithm: the tier is derived from a fingerprint of "player UUID + recipe id + current pity counter + target and per-slot item signatures" plus a preview seed, rather than rolling a fresh random number each time. Internally that roll key is hashed with SHA-256, and the first 12 hex characters are converted into a 0~1 ratio used for weighted selection over the quality pool

The prepared forge result cache holds at most 128 entries with a 30-second TTL, so:

  • The quality shown in the GUI matches the actual forging result
  • Resubmitting the same input within 30 seconds does not change the result
  • Changing materials or waiting for the cache to expire changes the result

Recipe override ​

Recipes can override global quality configuration under their own quality node. Note the recipe-level field names differ from the global config:

yaml
quality:
  enabled: true
  custom_pool:
    - "Common-50-1.0"
    - "Fine-35-1.08"
    - "Epic-15-1.25"
  guarantee:
    enabled: true
    attempts: 5
    minimum: "Fine"
FieldTypeRequiredDefaultAllowed valuesDescription
enabledbooleanNofalsetrue / falseWhether recipe-level quality configuration is enabled. When false, neither custom_pool nor recipe-level pity applies
custom_poollist<string>Noempty"name-weight-multiplier" stringsRecipe-specific quality pool. When empty, or when every entry is invalid, the global quality.tiers is used
guarantee.enabledbooleanNofalsetrue / falseWhether recipe-level pity is enabled
guarantee.attemptsintegerNo10>= 1 (values below 1 are raised to 1)Recipe-level pity threshold. Corresponds to threshold in the global config
guarantee.minimumstringNo普通A tier name present in the poolRecipe-level pity minimum tier. When the name does not exist the first pool entry is used, and failing that the global pity minimum

NOTE

When recipe-level pity is disabled but global pity is enabled, Forge falls back to the global threshold and minimum. attempts / minimum are only used when both recipe quality.enabled and quality.guarantee.enabled are true

Interaction with material effects ​

  • variables effect value = configured value × material amount × quality multiplier
  • ea_attribute effect value = configured value × material amount × quality multiplier
  • The force mode of quality_modify skips pity evaluation and counter updates
  • The minimum mode of quality_modify raises the result only when the drawn tier is below the specified tier

See Materials for the full material effect syntax