Skip to content

Getting Started

Theo edited this page Jul 18, 2026 · 1 revision

Getting started — authoring interactions

This is a hands-on guide for modpack developers. It builds a real interaction from scratch and adds one feature at a time, so by the end you can write almost anything. Every snippet is copy-paste-ready.

Prefer to read the exhaustive field list instead? See interaction-format.md. Prefer to copy finished files? See configExamples/interactions/v2.

Contents

  1. Setup
  2. Your first interaction
  3. Requiring a tool in hand
  4. Drops: weighted, counts, guaranteed, rolls, fortune
  5. Choosing the target: blocks, states, fluids, air
  6. Transforming the block
  7. Costs: damage and hunger
  8. Effects, sounds and particles
  9. Conditions: biome, time, weather, Y, light, player
  10. Matching item / block NBT
  11. Organising a pack
  12. Cookbook: common recipes
  13. Troubleshooting

1. Setup

  1. Install Forge 1.20.1, JEI, and PunchThemAll.

  2. Launch the game once. This creates the folder:

    config/punchthemall/interactions/
    
  3. Put .json files in that folder (subfolders are fine). Each file is one interaction.

  4. After editing files, run /reload in-game to apply changes. No restart needed.

  5. Open JEI and look at the Interaction category to see what loaded.

Always start a v2 file with "schema_version": 2. It gives you strict JSON validation and the clearest error messages, and unlocks every feature in this guide.

If a file fails to load, the game log shows a line beginning with PunchThemAll - Incorrect Json format - <file> - <reason>. Keep the log open while authoring.


2. Your first interaction

The smallest useful interaction: left-click dirt to get coarse dirt.

config/punchthemall/interactions/coarse_dirt.json

{
  "schema_version": 2,
  "type": "left_click",
  "target": { "kind": "block", "match": "minecraft:dirt" },
  "rewards": {
    "weighted": [
      { "match": "minecraft:coarse_dirt", "weight": 1 }
    ]
  }
}
  • type — which click triggers it: left_click, right_click, shift_left_click, shift_right_click.
  • target.match — the block you click. One id, a list, or a #tag.
  • rewards.weighted — the item(s) you get.

Run /reload, left-click some dirt, and coarse dirt pops out.


3. Requiring a tool in hand

Most interactions want a specific tool. Let's require any shovel and damage it each time.

{
  "schema_version": 2,
  "type": "shift_left_click",
  "hand": {
    "hand": "main",
    "match": "#minecraft:shovels",
    "consume": { "mode": "durability", "chance": 1.0 }
  },
  "target": { "kind": "block", "match": "minecraft:gravel" },
  "rewards": {
    "weighted": [
      { "match": "minecraft:flint", "weight": 25, "count": 1 },
      { "match": "minecraft:air",   "weight": 75 }
    ]
  }
}
  • hand.handmain, off, or any.
  • hand.match — required item(s)/tag. Leave hand out entirely to require an empty hand.
  • hand.consume.mode:
    • durability — damage a damageable tool (breaks when it runs out),
    • shrink — consume one from the stack (good for ingredients like buckets or seeds),
    • none — don't spend it.
  • hand.consume.chance — probability of spending it (e.g. 0.5 = half the time).

The "minecraft:air" entry with weight 75 is a "nothing" filler: 75% of clicks yield nothing. Weights are relative, so 25 vs 75 means a 25% chance of flint.


4. Drops

Everything about outputs lives in rewards.

Weighted pool

"rewards": {
  "weighted": [
    { "match": "minecraft:iron_nugget", "weight": 20, "count": { "min": 1, "max": 2 } },
    { "match": "minecraft:air",         "weight": 80 }
  ]
}

count accepts three shapes: 3, { "count": 3 }, or { "min": 1, "max": 3 }.

Guaranteed drops

Items in guaranteed are always given, on top of the weighted picks:

"rewards": {
  "guaranteed": [ { "match": "minecraft:cobblestone", "count": 1 } ],
  "weighted":   [ { "match": "minecraft:coal", "weight": 15 }, { "match": "minecraft:air", "weight": 85 } ]
}

Multiple rolls

rolls draws the weighted pool several times (guaranteed drops are given once):

"rewards": { "rolls": 3, "weighted": [ { "match": "minecraft:coal", "weight": 15 }, { "match": "minecraft:air", "weight": 85 } ] }

Fortune bonus

Add extra items to weighted picks based on an enchantment on the held item:

"rewards": {
  "weighted": [ { "match": "minecraft:diamond", "weight": 100 } ],
  "fortune":  { "enchant": "minecraft:fortune", "factor": 1.0 }
}

With Fortune III and factor: 1.0, each weighted pick gets round(3 × 1.0) = 3 extra items.


5. Choosing the target

A block, optionally with a required state

"target": {
  "kind": "block",
  "match": "minecraft:furnace",
  "state": {
    "whitelist": { "lit": "true" },
    "blacklist": { "waterlogged": "true" }
  }
}

State values are compared as text: use "true", "north", "3", etc.

A source fluid

Enable fluid interactions in the config (on by default), then:

"target": { "kind": "fluid", "match": "minecraft:water" }

The air (no block)

"target": { "kind": "air" }

Air interactions require an empty hand or a matching hand item and are great for "cast a rod", "swing a tool", etc.

Anything (block or fluid)

"target": { "kind": "any", "match": "#minecraft:logs" }

6. Transforming the block

Turn the clicked block/fluid into another one after a successful interaction:

"transformation": {
  "chance": 0.7,
  "into": { "kind": "block", "id": "minecraft:sand", "state": { "facing": "copy_state_value" } },
  "sound": "minecraft:block.gravel.break",
  "particles": "minecraft:sand"
}
  • chance — probability of the transformation happening.
  • into.kindblock, fluid, or air (air = break the block).
  • into.state — set specific state values, or "copy_state_value" to keep the original block's value.
  • particles — a block id (block-break particles).

Transformations obey the allow_transformations config gate and happen at most once per click.


7. Costs

Make the interaction cost the player something:

"costs": {
  "damage": { "chance": 1.0, "amount": 1 },
  "hunger": { "chance": 0.5, "amount": { "min": 2, "max": 8 } }
}
  • damage.amount is in half-hearts (2 = one heart).
  • hunger.amount is in hunger points.
  • amount accepts the same shapes as count (3, {count}, {min,max}).

These respect the allow_player_damage / allow_food_consumption config gates.


8. Effects, sounds and particles

Grant potion effects and play feedback on the interaction itself:

"effects": [
  { "id": "minecraft:haste", "duration": 200, "amplifier": 0, "chance": 0.3 }
],
"sound": "minecraft:entity.player.levelup",
"particles": "minecraft:sugar_cane"
  • duration is in ticks (20 = 1 second); amplifier 0 = level I.
  • sound / particles here fire on every success (transformations have their own sound/particles).

9. Conditions

Gate an interaction so it only fires in the right situation. Everything is optional — add only what you need.

"conditions": {
  "biomes":       { "whitelist": ["minecraft:desert"], "blacklist": [] },
  "time":         "night",
  "weather":      ["clear", "rain"],
  "y_range":      [-64, 40],
  "light":        { "max": 7 },
  "requires_sneaking": true,
  "player_state": { "min_food": 6, "min_xp_levels": 1 }
}
  • biomes — exact biome or dimension ids (minecraft:the_nether works too). Use whitelist or blacklist, not both.
  • timeany, day, or night.
  • weather — any of clear, rain, thunder. Omit for "any weather".
  • y_range[minY, maxY].
  • light — block light min/max (0–15).
  • requires_sneakingtrue/false.
  • player_state — minimum food and XP levels the player must have.

10. Matching NBT

Two ways to match item or block-entity data. You can use both together.

Typed predicates (recommended)

Clean, validated, and shown in JEI:

"nbt_predicates": [
  { "path": "Damage", "int_range": [0, 500] },
  { "path": "Enchantments[].lvl", "int_range": [2, 7], "where": "{id:\"minecraft:unbreaking\"}" }
]
  • path — dotted path; a segment ending in [] iterates a list.
  • int_range[min, max]; omit to only test that the value exists.
  • where — an SNBT string that filters which list elements count.
  • All predicates in the list must pass (AND).

Put nbt_predicates on hand (to match the held item) or on target (to match a block entity such as a chest's contents).

A predicate on an absent tag fails. A brand-new tool has no Damage tag until it's used, so a Damage predicate won't match it.

Raw SNBT whitelist/blacklist

For exact-shape matching, write NBT as an SNBT string:

"nbt": {
  "whitelist": "{Damage:{RangeTag:[0,500]}}",
  "blacklist": "{Enchantments:[{id:\"minecraft:silk_touch\"}]}"
}

The {RangeTag:[min,max]} helper still works inside these strings.


11. Organising a pack

  • Folders become ids. interactions/create/crushing/gravel.jsonpta:create/crushing/gravel. Keep filenames lowercase with underscores.
  • One interaction per file. It keeps ids meaningful and JEI readable.
  • Toggle without deleting. Add "enabled": false to a file to skip it.
  • Ship in a datapack (optional). Set Loader.load_from_datapacks = true in config/punchthemall/pta-common.toml, then place files at data/<namespace>/pta/interaction/*.json. Datapack files override config files with the same id and are synchronised to clients automatically.
  • Global tuning (cooldowns, click/target gates, fake players, drop physics) lives in pta-common.toml — see configuration.md.

12. Cookbook

Hammer crushing (cobble → gravel → sand → dust), consuming durability:

{
  "schema_version": 2,
  "type": "shift_left_click",
  "hand": { "hand": "main", "match": "#forge:tools/hammers", "consume": { "mode": "durability" } },
  "target": { "kind": "block", "match": "minecraft:cobblestone" },
  "transformation": { "chance": 1.0, "into": { "kind": "block", "id": "minecraft:gravel" }, "particles": "minecraft:gravel" },
  "rewards": { "weighted": [ { "match": "minecraft:air", "weight": 1 } ] }
}

Night-only, deep, dark mining bonus with Fortune:

{
  "schema_version": 2,
  "type": "shift_left_click",
  "hand": { "hand": "main", "match": "#minecraft:pickaxes", "consume": { "mode": "durability" } },
  "target": { "kind": "block", "match": "minecraft:deepslate" },
  "rewards": {
    "weighted": [ { "match": "minecraft:raw_gold", "weight": 20 }, { "match": "minecraft:cobbled_deepslate", "weight": 80 } ],
    "fortune": { "enchant": "minecraft:fortune", "factor": 1.0 }
  },
  "conditions": { "time": "night", "y_range": [-64, 8], "light": { "max": 7 } }
}

Bottle water for a chance at clay (consume the bottle):

{
  "schema_version": 2,
  "type": "right_click",
  "hand": { "hand": "main", "match": "minecraft:glass_bottle", "consume": { "mode": "shrink" } },
  "target": { "kind": "fluid", "match": "minecraft:water" },
  "rewards": { "weighted": [ { "match": "minecraft:clay_ball", "weight": 15 }, { "match": "minecraft:air", "weight": 85 } ] }
}

"Ritual" empty-hand click in the air that costs food and grants an effect:

{
  "schema_version": 2,
  "type": "right_click",
  "target": { "kind": "air" },
  "rewards": { "weighted": [ { "match": "minecraft:air", "weight": 1 } ] },
  "costs": { "hunger": { "chance": 1.0, "amount": 4 } },
  "effects": [ { "id": "minecraft:regeneration", "duration": 100, "amplifier": 0, "chance": 1.0 } ],
  "sound": "minecraft:block.enchantment_table.use"
}

More single-feature examples: configExamples/interactions/v2.


13. Troubleshooting

The file doesn't load. Check the log for Incorrect Json format - <file> - <reason>. Common causes: invalid JSON (a trailing comma, a missing quote), an unknown item/block id, or an unknown tag. Fix and /reload.

It loads but never triggers.

  • Turn on Debug.log_skipped_interactions in pta-common.toml to see why a click is skipped.
  • Confirm the type matches how you're clicking (sneaking or not; left vs right).
  • Check hand — if you omitted it, the interaction requires an empty hand.
  • Check conditions (time/weather/biome/Y/light/food/XP) and the global config gates (allow_left_click, allow_block_interactions, …).
  • Remember the per-player cooldown (Interactions.cooldown_ticks).

An NBT predicate never matches. The tag is probably absent (e.g. Damage on a fresh tool). Loosen the predicate or match a tag that actually exists on the item.

JEI shows nothing / wrong recipes on a dedicated server. The server syncs its interactions to clients on join and on /reload. Reconnect or /reload on the server. On singleplayer this is automatic.

I see a "legacy format" deprecation warning. That file has no schema_version (or 1). It still works, but consider migrating to schema_version: 2 — see the migration table in interaction-format.md.


Where to go next