Skip to content

Blocks

Lesson 7.2

A block is like an item that lives in the world. You register it the same way, and it needs a few more asset files: one for how it looks as a block and one for how it looks in your hand.

Registering a block

static void on_register(void* payload, void* user) {
    (void)payload;
    (void)user;
    cl_block_desc b = CL_BLOCK_DESC_INIT;
    b.id = CL_STR("my_mod:ruby_block");
    b.copy_properties_from = CL_STR("minecraft:emerald_block");
    b.vanilla_fallback = CL_STR("minecraft:emerald_block");
    b.item = CL_STR("my_mod:ruby_block");
    if (cl_block_register(&b, NULL) != CL_OK) {
        CL_LOGF(CL_LOG_ERROR, "ruby_block failed to register");
    }
}
  • copy_properties_from takes hardness, blast resistance, sounds and the right tool from a vanilla block. It is the easiest way to get a block that feels right.
  • vanilla_fallback is what players without your mod see.
  • item also registers an item for the block, so players can hold and place it. Use the block’s own id.

Like items, blocks are registered once at startup in CL_EV_LIFECYCLE_REGISTER.

How a mod block behaves

Under the hood, a mod block stands on a vanilla block. That base block is vanilla_fallback, or copy_properties_from if there is no fallback, or stone if you set neither. Players without your mod see the base block, and it decides what the properties below don’t cover: the shape, the right tool, explosions and pistons.

So pick the base that feels closest to what you want. A glowing crystal could stand on minecraft:glowstone, a slippery block on minecraft:ice, a bouncy one on minecraft:slime_block.

Your own block properties

These cl_block_desc fields change how the block feels, on top of the base:

Field Means Minecraft examples
destroy_time How long it takes to break (hardness) dirt 0.5, stone 1.5, obsidian 50
friction How slippery it is to walk on most blocks 0.6, slime 0.8, ice 0.98
speed_factor Walking speed on it most blocks 1.0, soul sand and honey 0.4
jump_factor Jump height from it most blocks 1.0, honey 0.5
light_emission The light it gives, 0 to 15 torch 14, glowstone 15
sound_type Its break, place and step sounds "glass", "wood", "amethyst_block", or a block id like "minecraft:glass"
b.destroy_time = 0.8f;
b.friction = 0.98f;                 // slides like ice
b.light_emission = 7;
b.sound_type = CL_STR("amethyst_block");

A field left at its default (CL_BLOCK_DESC_INIT) keeps the value of copy_properties_from, or of the base block. The server uses these values for what it simulates (mobs and items sliding, mining time, light, sounds), and players with your mod predict the same, so walking and mining feel smooth. Players without it walk on the base block on their screen and get corrected by the server now and then.

Blocks are simple cubes for now: one look, no properties like “facing” or “lit”, and a full-cube model.

Assets for a block

For my_mod:ruby_block:

assets/my_mod/
  blockstates/ruby_block.json       which model the block uses
  models/block/ruby_block.json      the cube model
  textures/block/ruby_block.png     the texture, 16 x 16
  items/ruby_block.json             the model in hand and in inventories
  lang/en_us.json                   "block.my_mod.ruby_block": "Block of Ruby"

blockstates/ruby_block.json. The block has a single state, written as "":

{
  "variants": {
    "": { "model": "my_mod:block/ruby_block" }
  }
}

models/block/ruby_block.json, a cube with the same texture on all six sides:

{
  "parent": "minecraft:block/cube_all",
  "textures": {
    "all": "my_mod:block/ruby_block"
  }
}

items/ruby_block.json reuses the block model for the item:

{
  "model": {
    "type": "minecraft:model",
    "model": "my_mod:block/ruby_block"
  }
}

Block names use the key block.<mod id>.<name>.

When a player mines the block with the right tool, it drops its item. In creative mode it drops nothing, like vanilla blocks.

Behaviour

Blocks can have code too, in a cl_block_behavior. Three kinds exist:

Function Runs when Gets
use_without_item A player right-clicks the block ctx, player, hand, out
attack A player left-clicks (starts hitting) it ctx, player
random_tick The block gets a random tick ctx

ctx is a cl_block_ctx with ctx->level, ctx->pos, ctx->state and ctx->user (the descriptor’s user pointer).

static cl_override doorbell_use(const cl_block_ctx* ctx, cl_entity player, cl_hand hand, cl_interaction_result* out) {
    (void)player;
    (void)hand;
    cl_vec3 at = {ctx->pos.x + 0.5, ctx->pos.y + 0.5, ctx->pos.z + 0.5};
    cl_sound_play(ctx->level, at, CL_STR("minecraft:block.note_block.bell"), CL_SOUND_BLOCKS, 1.0f, 1.0f);
    *out = CL_RESULT_SUCCESS;
    return CL_OVERRIDE_HANDLED;
}

static const cl_block_behavior doorbell_behavior = {
    sizeof(cl_block_behavior), doorbell_use, NULL, NULL
};

static void on_register(void* payload, void* user) {
    (void)payload;
    (void)user;
    cl_block_desc b = CL_BLOCK_DESC_INIT;
    b.id = CL_STR("my_mod:doorbell");
    b.copy_properties_from = CL_STR("minecraft:oak_planks");
    b.vanilla_fallback = CL_STR("minecraft:oak_planks");
    b.item = CL_STR("my_mod:doorbell");
    b.behavior = &doorbell_behavior;
    cl_block_register(&b, NULL);
}

The three function fields are in the order of the table, so { size, use, attack, random_tick }. NULL means “no code for this”. CL_BLOCK_BEHAVIOR_INIT gives an empty behaviour you can fill field by field, which is easier to read when you only need random_tick:

static cl_override moss_random_tick(const cl_block_ctx* ctx, int32_t* unused) {
    (void)unused;
    cl_easy_set_block(ctx->level, ctx->pos, "minecraft:moss_block");
    return CL_OVERRIDE_HANDLED;
}

static cl_block_behavior make_behavior(void) {
    cl_block_behavior bh = CL_BLOCK_BEHAVIOR_INIT;
    bh.random_tick = moss_random_tick;
    return bh;
}

Return CL_OVERRIDE_HANDLED when your code handled the click, or CL_OVERRIDE_CALL_ORIGINAL to let the game do what the base block would do. attack and random_tick have an extra int32_t* parameter that isn’t used yet; ignore it.

random_tick runs now and then, at the same rate crops grow (on average about every 68 seconds per block, with the default randomTickSpeed). It is the right tool for slow changes, like a block that turns into another one over time.

Try it

Register a my_mod:bounce_pad block that bounces like a slime block and says “Boing!” in the action bar when right-clicked. Add the language entry.

Show a solution
#include <clight_easy.h>

static cl_override bounce_use(const cl_block_ctx* ctx, cl_entity player, cl_hand hand, cl_interaction_result* out) {
    (void)ctx;
    (void)hand;
    cl_easy_send_action_bar(player, "Boing!");
    *out = CL_RESULT_SUCCESS;
    return CL_OVERRIDE_HANDLED;
}

static const cl_block_behavior bounce_behavior = { sizeof(cl_block_behavior), bounce_use, NULL, NULL };

static void on_register(void* payload, void* user) {
    (void)payload;
    (void)user;
    cl_block_desc b = CL_BLOCK_DESC_INIT;
    b.id = CL_STR("my_mod:bounce_pad");
    b.vanilla_fallback = CL_STR("minecraft:slime_block");
    b.item = CL_STR("my_mod:bounce_pad");
    b.behavior = &bounce_behavior;
    if (cl_block_register(&b, NULL) != CL_OK) CL_LOGF(CL_LOG_ERROR, "bounce_pad failed");
}

static cl_result mod_main(void) {
    return CL_ON(CL_EV_LIFECYCLE_REGISTER, on_register);
}

CLIGHT_MOD_ENTRY(mod_main)
{
  "block.my_mod.bounce_pad": "Bounce Pad"
}

The slime block as base gives the bounce, the sounds and the quick breaking. Players without the mod see a slime block.

Recap: register blocks with a cl_block_desc from CL_BLOCK_DESC_INIT in CL_EV_LIFECYCLE_REGISTER, pick a base block with vanilla_fallback, add item for the block item, give them a blockstate, model, texture and name, and use a cl_block_behavior for clicks and random ticks.