# Ingredients and components

Counted inputs, custom ingredients and stack components.

NTM recipes use vanilla ingredients, a counted-ingredient wrapper, custom ingredient types, loader-provided component matchers, and NTM component predicates.

## Counted NTM ingredients

The shared machine input wrapper is:

```json
{
  "ingredient": "#c:ingots/steel",
  "count": 2
}
```

`count` defaults to 1 and must be positive. `ingredient` is a vanilla ingredient. Optional `components` and `predicates` fields narrow the stack through vanilla component matchers. Component values use the registered component's value codec; predicates use the registered predicate's value codec. Other fields fail parsing.

```json
{
  "ingredient": "hbm:waste_uranium",
  "components": {"hbm:decay_heat": false}
}
```

> **until NTM 1.0.1**
>
> An `ingredient` naming items, rather than a tag or custom ingredient, refuses damaged stacks. Other component changes match unless `components` or `predicates` constrain them.

> **since NTM 1.0.1**
>
> A stack with changed components matches only when its [changes are claimed](#changed-component-state).

## Custom ingredients

Use the custom-ingredient form shown for your selected loader. The payload fields below are otherwise the same. Use the shape from the installed jar when nesting one ingredient inside another.

| Type | Payload | Meaning |
| - | - | - |
| `hbm:fluid_content` | `fluid`: registered fluid ID; `amount`: integer in mB, `[1, i32::MAX]`[since NTM 1.0.4: ; optional `drain`: boolean, default `false`] | An NTM fluid container other than a fluid pack or refillable equipment, holding exactly that quantity; or another item whose fluid handler can extract exactly that quantity[since NTM 1.0.1: . An NTM container may differ from its defaults only in `hbm:fluid_content`; another item must be unmodified][since NTM 1.0.4: . With `drain: true`, see [draining containers](#draining-containers)] |
| `hbm:matchers` | `base`: ingredient; optional `components`, `predicates` | Narrows `base` by exact component values and registered component predicates |
| `hbm:narrowed` | `item`: item ID; `within`: ingredient | Matches `item` where `within` also matches |

> **since NTM 1.0.1**
>
> | Type | Payload | Meaning |
> | - | - | - |
> | `hbm:any` | `ingredients`: ingredient list, length `[1, i32::MAX]` | Matches when an alternative matches; changed components need a claim from a matching alternative |
> | `hbm:recipe_input` | `base`: ingredient; optional `remainder`: boolean, default `false` | Matches `base` with only its own claims; `remainder: true` also accepts changes retained by the stack's actual crafting remainder |
>
> NTM wraps the ingredients of its bundled smelting, blasting and `hbm:tag_result` recipes in `hbm:recipe_input`.

> **Fabric; since NTM 1.0.1**
>
> Ingredient of the generated `hbm:basalt_smooth_from_basalt` smelting recipe:
>
> ```json
> {
>   "fabric:type": "hbm:recipe_input",
>   "base": "hbm:basalt"
> }
> ```

> **NeoForge; since NTM 1.0.1**
>
> Ingredient of the generated `hbm:basalt_smooth_from_basalt` smelting recipe:
>
> ```json
> {
>   "neoforge:ingredient_type": "hbm:recipe_input",
>   "base": "hbm:basalt"
> }
> ```

> **Fabric**
>
> Fluid-container ingredient from the generated `hbm:jetpack_tank` recipe:
>
> *NTM 1.0.0–1.0.3:*
>
> ```json
> {
>   "fabric:type": "hbm:fluid_content",
>   "fluid": "hbm:kerosene",
>   "amount": 1000
> }
> ```
>
> *NTM 1.0.4:*
>
> ```json
> {
>   "fabric:type": "hbm:fluid_content",
>   "drain": true,
>   "fluid": "hbm:kerosene",
>   "amount": 1000
> }
> ```

> **NeoForge**
>
> Fluid-container ingredient from the generated `hbm:jetpack_tank` recipe:
>
> *NTM 1.0.0–1.0.3:*
>
> ```json
> {
>   "neoforge:ingredient_type": "hbm:fluid_content",
>   "fluid": "hbm:kerosene",
>   "amount": 1000
> }
> ```
>
> *NTM 1.0.4:*
>
> ```json
> {
>   "neoforge:ingredient_type": "hbm:fluid_content",
>   "drain": true,
>   "fluid": "hbm:kerosene",
>   "amount": 1000
> }
> ```

As an NTM counted input, put this object under `ingredient`; `count` specifies the number of containers.

> **since NTM 1.0.1**
>
> An unmodified `hbm:bdcl` also matches a fluid-container ingredient for exactly 1000 mB of `hbm:lubricant`.

> **since NTM 1.0.4**
>
> ### Draining containers
>
> With `drain: true`, the ingredient matches a container holding at least `amount` of the fluid whose other component changes survive draining. Crafting with `hbm:crafting_shaped` or `hbm:crafting_shapeless` takes `amount` from the container and leaves it in the grid: an NTM container keeps its remaining content or becomes its empty form, and another item drains through its fluid handler. An unmodified `hbm:bdcl` still matches 1000 mB of `hbm:lubricant` and is consumed.
>
> Crafting that discards remainders, such as the Combinator Funnel, refuses a draining ingredient. An [`input_state`](https://movblock.mov/ntm-next/wiki/recipes/input-state.md) rule with `preserve` on a draining ingredient fails recipe loading.

## Component-matching ingredients

> **until NTM 1.0.4**
>
> The generated `hbm:ajr_boots` crafting recipe requires an undamaged `hbm:titanium_boots` item. Its `key.B` ingredient is:
>
> *NTM 1.0.0–1.0.4 · Fabric:*
>
> ```json
> {
>   "fabric:type": "fabric:components",
>   "base": "hbm:titanium_boots",
>   "components": {"minecraft:damage": 0}
> }
> ```
>
> *NTM 1.0.0–1.0.4 · NeoForge:*
>
> ```json
> {
>   "neoforge:ingredient_type": "neoforge:components",
>   "items": "hbm:titanium_boots",
>   "components": {"minecraft:damage": 0}
> }
> ```

> **since NTM 1.0.4**
>
> The generated `hbm:ajr_plate` crafting recipe requires an empty `hbm:gas_tank`. Its `key.T` ingredient removes the fluid-content component:
>
> *NTM 1.0.0–1.0.4 · Fabric:*
>
> ```json
> {
>   "fabric:type": "fabric:components",
>   "base": "hbm:gas_tank",
>   "components": {"!hbm:fluid_content": {}}
> }
> ```
>
> *NTM 1.0.0–1.0.4 · NeoForge:*
>
> ```json
> {
>   "neoforge:ingredient_type": "neoforge:components",
>   "items": "hbm:gas_tank",
>   "components": {"!hbm:fluid_content": {}}
> }
> ```

NTM counted machine inputs put `components` beside `ingredient` in the wrapper shown above.

## The `hbm:fluid_content` component predicate

The predicate checks **fluid type only**, ignoring quantity. Its value is a fluid-ID string:

```json
{
  "ingredient": "hbm:canister",
  "predicates": {"hbm:fluid_content": "minecraft:water"}
}
```

An absent fluid-content component is treated as `minecraft:empty`. This predicate and the custom fluid-container ingredient above have different purposes: use the ingredient to demand a quantity through container handling, or the predicate to match the component's type without demanding a quantity.

> **since NTM 1.0.4**
>
> ## The `hbm:consume_component` predicate
>
> The value is a registered component ID. The predicate matches every stack and [claims](#changed-component-state) that whole component, whether changed or removed, so the recipe consumes a stack carrying it. The generated `hbm:ajr_helmet` recipe accepts a repaired gas mask:
>
> ```json
> {
>   "base": "hbm:gas_mask_m65",
>   "components": {"minecraft:damage": 0},
>   "predicates": {"hbm:consume_component": "minecraft:repair_cost"}
> }
> ```
>
> This object is the payload of an `hbm:matchers` ingredient. Machine recipes consuming a charged battery use the same predicate on a counted input:
>
> ```json
> {
>   "ingredient": "hbm:battery_pack_battery_lithium",
>   "predicates": {"hbm:consume_component": "hbm:battery_charge"}
> }
> ```
>
> ## The `hbm:unmodified_gun` predicate
>
> The value is `{}`. The predicate matches a stack without gun state or whose gun state holds only the values an unused gun receives in an inventory: no installed mods, zero wear, an empty magazine, firing mode 0, and an idle or drawing state. It claims the gun-state component. Generated gun-upgrade recipes such as `hbm:gun_autoshotgun_shredder` require it:
>
> *NTM 1.0.0–1.0.4 · Fabric:*
>
> ```json
> {
>   "fabric:type": "hbm:matchers",
>   "base": "hbm:gun_autoshotgun",
>   "predicates": {"hbm:unmodified_gun": {}}
> }
> ```
>
> *NTM 1.0.0–1.0.4 · NeoForge:*
>
> ```json
> {
>   "neoforge:ingredient_type": "hbm:matchers",
>   "base": "hbm:gun_autoshotgun",
>   "predicates": {"hbm:unmodified_gun": {}}
> }
> ```

> **since NTM 1.0.1**
>
> ## Changed component state
>
> A stack has changed state when its components differ from its item's defaults. NTM recipe inputs accept such a stack only when its requirements claim every changed component:
>
> | Requirement | Claims |
> | - | - |
> | `components` entry | That component |
> | `predicates` entry keyed by a component ID that is not also a predicate type, value `{}` | That component |
> | `minecraft:damage` predicate | `minecraft:damage` |
> | `minecraft:custom_data` predicate | `minecraft:custom_data` when the stack's tag equals the stated tag; typed SNBT such as `"{required:7}"` keeps numeric tag types |
> | `hbm:fluid_content` predicate | `hbm:fluid_content` |
> | `hbm:consume_component` predicate (since NTM 1.0.4) | The named component |
> | `hbm:unmodified_gun` predicate (since NTM 1.0.4) | The gun-state component |
> | Other predicates | Nothing |
> | `fabric:components` or `neoforge:components` ingredient | Components whose value equals the stated value |
> | `hbm:fluid_content` ingredient | `hbm:fluid_content` of an NTM container[since NTM 1.0.4: ; with `drain: true`, every change] |
> | `hbm:any` | The claims of a matching alternative |
> | `hbm:matchers`, `hbm:narrowed`, `hbm:recipe_input` | The claims of their matchers or enclosed ingredient |
> | Item ID, item tag or other ingredient | Nothing |
>
> A `minecraft:container` predicate requiring some items claims nothing, so other stored items still refuse the stack. Damage predicates do not claim changes to maximum durability.
>
> NTM counted inputs, NTM crafting recipe types, `hbm:recipe_input` and `hbm:fluid_content` apply the rule wherever they are used. The Bricked Furnace, Iron Furnace, Steel Furnace, Electric Furnace, Microwave, Mining Laser and Tungsten Crate apply it to vanilla smelting recipes; the Automatic Crafting Table and Combinator Funnel apply it to crafting recipes.
>
> Crafting also accepts a changed stack whose actual crafting remainder retains the change: through [`input_state`](https://movblock.mov/ntm-next/wiki/recipes/input-state.md) in `hbm:crafting_shaped` and `hbm:crafting_shapeless`, and without declaration in other crafting types. The Combinator Funnel discards remainders and does not apply this.

## Stack components in outputs

An output template carries `components` beside `id` and optional `count`:

```json
{
  "id": "hbm:custom_machine",
  "components": {"hbm:custom_machine_type": "example:paper_press"}
}
```

The controller component names the custom-machine definition ID. In a fluid-content **component value** `{type, amount, pressure?}`, `type` is the registered fluid ID, `amount` is mB, and optional `pressure` is PU. Exact matching includes all authored fields. The type-only predicate uses a fluid-ID string.

| Context using the same name | Shape |
| - | - |
| Fluid-content ingredient | `{loader discriminator, fluid, amount}` |
| `predicates["hbm:fluid_content"]` | Fluid-ID string |
| `components["hbm:fluid_content"]` | `{type, amount, pressure?}` |

Items use their registered prototype defaults when no patch is supplied. New item identities have separate item IDs; stack state uses registered typed components. Copy stateful component values from a relevant installed example.

> **since NTM 1.0.2**
>
> ### Bedrock ore samples
>
> `hbm:bedrock_ore_sample` stores one numeric amount per kind in the sample's set. The NTM set remains implicit and requires the six fields `light`, `heavy`, `rare`, `actinide`, `nonmetal` and `crystal`. An addon sample also supplies `set`, a registered set ID, and the amount fields for that set's kinds. The six-field NTM form remains valid.
