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_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 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.