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
void on_register() override {
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 (!clight::registry::block_register(&b)) {
clight::log::error("ruby_block failed to register");
}
}
copy_properties_fromtakes 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_fallbackis what players without your mod see.itemalso 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 on_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. The base decides how the block behaves in the world:
- how long it takes to break, and which tool it needs,
- its sounds and how it reacts to explosions,
- light, friction, walking speed and jump height, unless you set your own (below).
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).
cl_override doorbell_use(const cl_block_ctx* ctx, cl_entity, cl_hand, cl_interaction_result* out) {
const cl_vec3 at{ctx->pos.x + 0.5, ctx->pos.y + 0.5, ctx->pos.z + 0.5};
clight::Level(ctx->level).sound_play(at, "minecraft:block.note_block.bell", CL_SOUND_BLOCKS, 1.0f, 1.0f);
*out = CL_RESULT_SUCCESS;
return CL_OVERRIDE_HANDLED;
}
const cl_block_behavior kDoorbellBehavior = {sizeof(cl_block_behavior), doorbell_use, nullptr, nullptr};
class DoorbellMod final : public clight::Mod {
public:
void on_register() override {
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 = &kDoorbellBehavior;
clight::registry::block_register(&b);
}
};
The three function fields are in the order of the table, so { size, use, attack, random_tick }. nullptr means “no code for this”. Parameters you don’t use can stay without a name, like cl_entity and cl_hand above, and the compiler won’t warn. 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:
cl_block_behavior make_moss_behavior() {
cl_block_behavior bh = CL_BLOCK_BEHAVIOR_INIT;
bh.random_tick = [](const cl_block_ctx* ctx, int32_t*) -> cl_override {
clight::Level(ctx->level).set_block(ctx->pos, "minecraft:moss_block");
return CL_OVERRIDE_HANDLED;
};
return bh;
}
A lambda without captures works as well as a named function here. 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.
With lambdas, clight::registry::block(desc, behaviour) does the same. Its fields are .use_without_item, .attack and .random_tick, in that order, and each lambda returns true (handled) or false (do what the base block does):
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");
clight::registry::block(b, {.use_without_item = [](const cl_block_ctx& ctx, clight::Entity, cl_hand, cl_interaction_result& out) {
const cl_vec3 at{ctx.pos.x + 0.5, ctx.pos.y + 0.5, ctx.pos.z + 0.5};
clight::Level(ctx.level).sound_play(at, "minecraft:block.note_block.bell", CL_SOUND_BLOCKS, 1.0f, 1.0f);
out = CL_RESULT_SUCCESS;
return true;
}});
The lambdas may capture state; as for items, the helper uses b.user itself.
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.hpp>
cl_override bounce_use(const cl_block_ctx*, cl_entity player, cl_hand, cl_interaction_result* out) {
clight::Entity(player).send_action_bar("Boing!");
*out = CL_RESULT_SUCCESS;
return CL_OVERRIDE_HANDLED;
}
const cl_block_behavior kBounceBehavior = {sizeof(cl_block_behavior), bounce_use, nullptr, nullptr};
class BounceMod final : public clight::Mod {
public:
void on_register() override {
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 = &kBounceBehavior;
if (!clight::registry::block_register(&b)) clight::log::error("bounce_pad failed");
}
};
CLIGHT_MOD(BounceMod)
{
"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 in on_register with a cl_block_desc from CL_BLOCK_DESC_INIT and clight::registry::block_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 or clight::registry::block(b, {...}) with lambdas for clicks and random ticks.