API

Blocks, items and recipes

You declare content as a list of properties. Crafting creates the real game objects at the right time and generates the models, loot tables, tags, recipes and translations they need. You don't write any game data files.

Items

content/items.craftCraftScript
item "ruby" { rarity: "rare" }

item "ruby_apple" {
    food: { nutrition: 8, saturation: 1.2, always_edible: true }
    glint: true
}

item "ruby_pickaxe" { tool: { type: "pickaxe", tier: "diamond", speed: 9 }, tab: "tools_and_utilities" }
Property Values Default
stack or max_stack 1–99 64
rarity common, uncommon, rare, epic (name color) common
glint enchantment glint false
durability number; forces stack: 1 0
fireproof doesn't burn in lava or fire false
food number, or { nutrition, saturation, always_edible } —
tool "pickaxe", or { type, tier, damage, speed }; type: pickaxe, axe, shovel, hoe, sword —
tab creative inventory tab (below) ingredients
texture texture path item/<id>
on_use fun(player, stack); returns true if it handles the use —
armor { slot, defense, toughness, knockback_resistance, texture } —
on_hit fun(attacker, target, stack) —
cooldown seconds 0

Armor, weapons and cooldowns

An item with armor can be worn. slot is head, chest, legs or feet. defense, toughness and knockback_resistance (0 to 1) are the game's armor values: an iron chestplate has 6 defense, diamond has 2 toughness.

content/armor.craftCraftScript
item "frost_helmet" { texture: "minecraft:item/iron_helmet", armor: { slot: "head", defense: 3, texture: "frost" } }
item "frost_chestplate" { armor: { slot: "chest", defense: 8, toughness: 2 }, durability: 400 }

texture is how it looks worn. frost means textures/entity/equipment/humanoid/frost.png in the mod and, for legs, textures/entity/equipment/humanoid_leggings/frost.png, with the same layout as the game's armor textures. Without it, the armor looks like iron.

on_hit runs when the item is used to hit something, whether by a player or by a mob.

content/frost_blade.craftCraftScript
item "frost_blade" {
    texture: "minecraft:item/diamond_sword"
    tool: "sword"
    cooldown: 3
    on_hit: fun(attacker, target, stack) {
        target.add_effect("minecraft:slowness", 4, 4)
        target.world.spawn_particle("minecraft:snowflake", target.location.add(0, 1, 0), 20, 0.4, 0.02)
    }
}

cooldown (in seconds) blocks the item for that long after each use that its on_use took, and the hotbar shows it. From code: player.set_cooldown("minecraft:feather", 2) and player.has_cooldown(id). In Kotlin: armor(EquipmentSlot.HEAD, defense = 3, texture = "frost") for armor and onHit = { ctx -> … } for weapons (ctx.attacker, ctx.target, ctx.stack).

content/Armor.ktKotlin
item("frost_helmet") { armor(EquipmentSlot.HEAD, defense = 3, texture = "frost") }

Blocks

content/ores.craftCraftScript
block "ruby_ore" {
    hardness: 3
    tool: "pickaxe"
    tier: "iron"
    loot: [
        { item: "rubies:ruby", count: [1, 2] },
        { item: "minecraft:diamond", chance: 0.1 }
    ]
}

block "ruby_block" { hardness: 5, light: 7, tool: "pickaxe", sound: "metal" }

block "ruby_lamp" { hardness: 0.3, light: 15, sound: "glass", transparent: true, tab: "functional_blocks" }

block "ruby_bricks" { hardness: 2, tool: "pickaxe", drops: "ruby", drop_count: [2, 4] }
Property Values Default
hardness number; -1 = unbreakable 1.5
resistance explosion resistance = hardness
light 0–15 0
light_when a toggle state, like "lit": the block shines only while it is on (see Block states) always
sound stone, wood, gravel, grass, metal, glass, wool, sand stone
like a block it is made like, the game's ("minecraft:end_stone") or yours: its hardness, resistance, sound and whether it needs the right tool to drop anything are that block's —
kind one of the game's kinds: stairs, door, chest, crop… (see Block kinds) cube
tool, tier tool required for the block to drop anything, and the minimum material of that tool: wood, stone, iron, diamond, netherite —, wood
drops, drop_count a single item: id; number or [min, max] the block itself, 1
loot several items: [{ item, count, chance, tool, fortune }, …]. Each one is rolled separately; count is a number or [min, max] and chance goes from 0 to 1 (default 1). tool and fortune: see Drops below —
friction 0.6 normal, 0.98 ice 0.6
transparent you can see through it (glass, leaves) false
has_item also register the block's item; without one, the block drops nothing unless drops or loot say what true
tab creative inventory tab building_blocks
on_use fun(player, block); returns true if it handles the use. A player who sneaks with something in hand places it instead, as with the game's blocks —

Tabs: building_blocks, colored_blocks, natural_blocks, functional_blocks, redstone_blocks, tools_and_utilities, combat, food_and_drinks, ingredients, spawn_eggs. In Kotlin they are in CreativeTabs.

To make a creature drop something extra when it dies, add it to e.drops in the entity_death event (see Events).

Blocks that aren't cubes, blocks of the game's kinds, creatures, ores, sounds and particles are declared the same way and have their own pages: Block shapes, Block kinds, Creatures, World generation, Sounds and particles.

Drops: tools and fortune

A drop in loot can need a tool, and fortune can give more of it:

content/drops.craftCraftScript
block "ruby_ore" {
    kind: "ore"
    tool: "pickaxe"
    tier: "iron"
    xp: [1, 4]
    loot: [
        { item: "ruby", count: [1, 2], fortune: true },
        { item: "ruby_ore", tool: "silk_touch" }
    ]
}

block "frost_leaves" {
    kind: "leaves"
    loot: [
        { item: "frost_leaves", tool: "shears" },
        { item: "frost_sapling", chance: 0.05, fortune: true }
    ]
}
  • tool: "silk_touch": it drops only when the block is broken with silk touch. tool: "shears": only with shears or silk touch, as the game's leaves and vines drop themselves.
  • When some drops need a tool, the drops without one happen only when that tool isn't used: the ore above drops itself with silk touch and its rubies otherwise.
  • fortune: true: fortune gives more of it, more of its count as with the game's ores, or a better chance as with saplings from leaves.

In Kotlin: drop("ruby", 1..2, tool = null, fortune = true) and drop("ruby_ore", tool = DropTool.SILK_TOUCH).

content/Drops.ktKotlin
block("ruby_ore") {
    kind = BlockKind.ORE
    xp = 1..4
    drop("ruby", 1..2, tool = null, fortune = true)
    drop("ruby_ore", tool = DropTool.SILK_TOUCH)
}

Recipes

content/recipes.craftCraftScript
recipe shaped "ruby_block" {
    pattern: ["RRR", "RRR", "RRR"]
    keys: { R: "ruby" }
    result: "ruby_block"
}

recipe shapeless "rubies_from_block" { ingredients: ["ruby_block"], result: "ruby", count: 9 }

recipe smelting "ruby_from_ore" { input: "ruby_ore", result: "ruby", xp: 1.0 }

Use behavior

on_use is the only logic that lives next to the data. If it grows beyond a couple of lines, write the function in lib/ and import it:

content/wands.craftCraftScript
import { lift } from "/lib/teleport"

item "lift_wand" { stack: 1, rarity: "uncommon", on_use: lift }
lib/teleport.craftCraftScript
fun lift(player, stack) {
    player.teleport(player.location.add(0, 10, 0))
    return true
}

Blocks that react

A block can run code when something happens to it or around it. Each handler receives the block and, where it applies, the entity.

Property Runs when
on_step: fun(block, entity) an entity walks on the block.
on_inside: fun(block, entity) an entity is inside the block. The block has to let entities through: a portal, a plant or a non-solid shape.
on_random_tick: fun(block) now and then, at random, like crops growing: about once every 68 seconds per block.
on_tick: fun(block) the time asked for with block.schedule(ticks) has passed (20 ticks = 1 second).
on_neighbor_change: fun(block) a block next to it changed: it was placed, broken or powered.
on_placed: fun(block, entity) someone placed the block; entity is who.
content/reactions.craftCraftScript
block "jump_pad" { on_step: fun(b, e) { e.velocity = vector(0, 1.1, 0) } }

block "blinker" {
    light: 7
    on_tick: fun(b) {
        b.world.spawn_particle("minecraft:end_rod", b.location.add(0.5, 1.2, 0.5), 4, 0.1, 0.01)
        b.schedule(20)
    }
    on_neighbor_change: fun(b) { b.schedule(1) }
}

In Kotlin the handlers are onStep, onInside, onRandomTick, onTick, onNeighborChange and onPlaced. Each receives a BlockEventContext with block and, when there is one, entity:

content/Reactions.ktKotlin
block("pad") { onStep = { ctx -> ctx.entity?.velocity = Vector(0.0, 1.1, 0.0) } }

Portals

A block with portal takes whoever is inside it to another dimension. Give the dimension to go to, or to and back to choose where it leads from there:

content/portals.craftCraftScript
block "sky_gate" { portal: "gatemod:sky", light: 11 }

block "sky_gate_two_way" { portal: { to: "gatemod:sky", back: "minecraft:overworld" } }

The entity arrives on the ground at the same x and z and finds a portal block there to come back by; if there is none nearby, one is placed beside the arrival. Entities wait a moment before using a portal again. Portal blocks let entities through. Declare the dimension itself with dimension (see @dim@). In Kotlin:

content/Portals.ktKotlin
block("sky_gate") { portal("gatemod:sky") }

Block states

states gives a block values it remembers and can show:

content/states.craftCraftScript
block "lamp" {
    model: "block/lamp"
    states: ["facing", "lit"]
    on_use: fun(p, b) { b.set_state("lit", !b.state("lit")); return true }
    on_neighbor_change: fun(b) { b.set_state("lit", b.redstone_power > 0) }
}

block "crop" { states: [{ stage: 3 }, { wet: true }] }
  • "facing": north, east, south or west. It is set facing the player who places the block, and the model turns with it.
  • "lit": a name alone is an on/off toggle, off by default. { lit: true } makes it on by default.
  • { stage: 3 }: a number from 0 to the maximum you give (1 to 15).
  • "facing_all": like "facing", but also up and down. It is set facing the player who places it, and the model is tipped over for up and down.
  • { shape: ["top", "middle", "bottom"] }: one of a few names, the first by default. Names are lowercase letters, digits and _.

Read and change them with block.state("lit") and block.set_state("lit", true). They work on the game's own blocks too, for example a furnace's facing and lit.

A state that is off its default can look different. Add a texture at textures/block/<id>_<name>.png (a toggle that is on) or <id>_<name><n>.png (a number) or <id>_<value>.png (a name), or a model at models/block/<id>_<name>.json. The whole combination (<id>_lit_stage2) is tried first, then each state alone. In the example, lamp.json and lamp_lit.json are the mod's two models, for instance with the parent minecraft:block/orientable and front, side and top textures.

Light by state. light_when: "lit" makes the block's light shine only while that toggle is on, as in a lamp:

content/lamp.craftCraftScript
block "lamp" {
    light: 15
    light_when: "lit"
    states: ["lit"]
    on_use: fun(p, b) { b.set_state("lit", !b.state("lit")); return true }
}

In Kotlin: facing(), facingAll(), toggle("lit"), number("stage", 3), choice("shape", "top", "middle", "bottom"), and block.state(…) and block.setState(…) in code:

content/States.ktKotlin
block("lamp") {
    model = "block/lamp"
    facing()
    toggle("lit")
    number("stage", 3)
}

Effects

A mod can add its own status effects. They appear on the player's screen like the game's and are given and read the same way.

content/frozen.craftCraftScript
effect "frozen" {
    color: "#88CCFF"
    kind: "harmful"
    on_tick: fun(e, level) { e.damage(level) }
}
  • color: the color of its particles and its icon background.
  • kind: beneficial, harmful or neutral.
  • on_tick: fun(e, level), runs every second while an entity has the effect, with its level (1 is I).
  • The icon is the mod's textures/mob_effect/<id>.png.

Give it with e.add_effect("mymod:frozen", 3, 1) (id, seconds, level) and check it with e.has_effect("mymod:frozen"), as with the game's effects (see The world and players). In Kotlin:

content/Frozen.ktKotlin
content.effect("frozen") {
    color = 0x88CCFF
    kind = EffectKind.HARMFUL
    onTick = { e, level -> /* … */ }
}

Redstone

content/battery.craftCraftScript
block "battery" {
    texture: "minecraft:block/copper_block"
    redstone: true
    on_use: fun(p, b) { if b.state("power") > 0 { b.set_state("power", 0) } else { b.set_state("power", 15) }; return true }
}

redstone: true makes the block give off a signal as strong as its power state, a number from 0 to 15 that is added automatically. b.set_state("power", 15) turns it on, and the wires, lamps and doors around it follow. block.redstone_power is the power the block receives from around it, and on_neighbor_change runs when that changes (the lamp above uses it).

In Kotlin: redstone = true and block.redstonePower.

content/Battery.ktKotlin
block("battery") {
    texture = "minecraft:block/copper_block"
    redstone = true
    // in a handler: ctx.block.redstonePower
}

Inventories

content/crate.craftCraftScript
block "crate" { texture: "minecraft:block/barrel_side", inventory: 27 }

inventory: 27 gives the block that many slots (9 to 54, in rows of 9). It keeps its items across restarts, using it opens a chest screen (unless its on_use took the click), and breaking it drops what it holds. From 1 to 8 slots there is no screen: the mod puts items in and takes them out itself.

From code, block.inventory (it also works for the game's chests and barrels) offers set(slot, item(…)), get(slot), count(id) and add(…). In Kotlin: inventory = 27.

content/Crate.ktKotlin
block("crate") {
    texture = "minecraft:block/barrel_side"
    inventory = 27
}

Items shown on a block. shows_item: 14 makes the item in the block's first slot float above it, turning, that many pixels above its bottom. Every player sees it, and it goes when the slot empties or the block is broken. A pedestal takes the item in the player's hand and gives it back:

content/pedestal.craftCraftScript
block "pedestal" {
    shape: [[2, 0, 2, 14, 12, 14]]
    inventory: 1
    shows_item: 14
    on_use: fun(p, b) {
        let inv = b.inventory
        if inv.get(0) == null and p.held_item != null {
            inv.set(0, p.held_item)
            p.set_held_item(null)
        } else if inv.get(0) != null {
            p.give(inv.get(0))
            inv.set(0, null)
        }
        return true
    }
}

player.set_held_item(stack) puts an item in the player's main hand, or empties it with null. In Kotlin: showsItem = 14 and player.setHeldItem(stack).

content/Pedestal.ktKotlin
block("pedestal") {
    box(2, 0, 2, 14, 12, 14)
    inventory = 1
    showsItem = 14
    onUse { ctx ->
        val inv = ctx.block.inventory!!
        val held = ctx.player.heldItem
        val shown = inv[0]
        if (shown == null && held != null) { inv[0] = held; ctx.player.setHeldItem(null) }
        else if (shown != null) { ctx.player.give(shown); inv[0] = null }
        true
    }
}

Textures

Textures ship inside the mod; nobody has to install a resource pack. The file name is the id:

Mod Where
CraftScript (folder) mods/rubies/textures/item/ruby.png, mods/rubies/textures/block/ruby_ore.png
Kotlin or Java src/main/resources/assets/rubies/textures/item/ruby.png

If a mod ships its own models, recipes or loot tables in those same folders, Crafting uses them.

In Kotlin

content/Blocks.ktKotlin
package com.example.rubies.content

object Blocks {
    fun register(content: ContentBuilder) {
        content.block("ruby_ore") {
            hardness = 3f
            requiresTool = ToolType.PICKAXE
            minTier = ToolTier.IRON
            drop("ruby", 1..2)
            drop("minecraft:diamond", chance = 0.1)
        }
        content.block("ruby_block") { hardness = 5f; luminance = 7; sound = SoundGroup.METAL }
    }
}

Ids. Without a namespace, an id belongs to your mod ("ruby" is rubies:ruby). For game content, write the namespace: "minecraft:diamond". You can't register content in the minecraft namespace.