A custom block
Status: Implemented. Everything in this guide works today. Blocks have one state (no
litorfacingproperties yet) and a full-cube model; see Blocks and block states.
Goal: a Ruby Doorbell block that:
- glows (light level 10) and sounds like amethyst
- is mined with a pickaxe, like an emerald block
- rings a bell when a player right-clicks it with an empty hand
- drops itself (or what its loot table says)
- shows as an emerald block to players without the mod
Languages: Lua (main), with C++ and C at the end. APIs: Blocks and block states, Sound and music.
1. Register the block
scripts/main.lua:
cl.on("lifecycle.register", function()
cl.blocks.register{
id = "ruby_doorbell",
copy_properties_from = "minecraft:emerald_block", -- hardness, tool, blast resistance
vanilla_fallback = "minecraft:emerald_block", -- the base block in the world and for vanilla clients
item = "my_mod:ruby_doorbell", -- also register its block item
creative_tab = "minecraft:functional_blocks", -- the block item's creative tab
light_emission = 10,
sound_type = "amethyst_block",
behavior = {
use_without_item = function(ctx, player, hand)
ctx.level:play(ctx.pos:center(), "minecraft:block.note_block.bell", "blocks", 1.0, 1.0)
player:send_action_bar("Ding dong!")
return "success" -- handled: swing the arm, no vanilla action
end,
},
}
end)
Blocks register once, at startup. After a change, restart the game (a hot reload logs restart required).
2. Assets
assets/my_mod/
blockstates/ruby_doorbell.json
models/block/ruby_doorbell.json
textures/block/ruby_doorbell.png 16 x 16
items/ruby_doorbell.json
lang/en_us.json
blockstates/ruby_doorbell.json (one state, written ""):
{ "variants": { "": { "model": "my_mod:block/ruby_doorbell" } } }
models/block/ruby_doorbell.json:
{ "parent": "minecraft:block/cube_all", "textures": { "all": "my_mod:block/ruby_doorbell" } }
items/ruby_doorbell.json (the item uses the block model):
{ "model": { "type": "minecraft:model", "model": "my_mod:block/ruby_doorbell" } }
lang/en_us.json:
{ "block.my_mod.ruby_doorbell": "Ruby Doorbell" }
3. Drops (optional)
Without a loot table the block drops its item. For anything else, add datapack/my_mod/loot_table/blocks/ruby_doorbell.json in the vanilla format (mod items are allowed in it). Copying a vanilla block table and changing the names is the easiest start.
4. Try it
clight run
/give @s my_mod:ruby_doorbell, place it, right-click it with an empty hand.
The same block in C++
#include <clight.hpp>
class DoorbellMod final : public clight::Mod {
public:
void on_register() override {
cl_block_desc d = CL_BLOCK_DESC_INIT;
d.id = CL_STR("my_mod:ruby_doorbell");
d.copy_properties_from = CL_STR("minecraft:emerald_block");
d.vanilla_fallback = CL_STR("minecraft:emerald_block");
d.item = CL_STR("my_mod:ruby_doorbell");
d.light_emission = 10;
d.sound_type = CL_STR("amethyst_block");
clight::registry::block(d, {.use_without_item = [](const cl_block_ctx& c, clight::Entity player, cl_hand,
cl_interaction_result& out) {
const cl_vec3 at{c.pos.x + 0.5, c.pos.y + 0.5, c.pos.z + 0.5};
clight::Level(c.level).sound_play(at, "minecraft:block.note_block.bell", CL_SOUND_BLOCKS, 1.0f, 1.0f);
player.send_action_bar("Ding dong!");
out = CL_RESULT_SUCCESS;
return CL_OVERRIDE_HANDLED;
}});
}
};
CLIGHT_MOD(DoorbellMod)
And in C
#include <clight_easy.h>
static cl_override ring(const cl_block_ctx* c, cl_entity player, cl_hand hand, cl_interaction_result* out) {
cl_vec3 at = { c->pos.x + 0.5, c->pos.y + 0.5, c->pos.z + 0.5 };
(void)hand;
cl_sound_play(c->level, at, CL_STR("minecraft:block.note_block.bell"), CL_SOUND_BLOCKS, 1.0f, 1.0f);
cl_easy_send_action_bar(player, "Ding dong!");
*out = CL_RESULT_SUCCESS;
return CL_OVERRIDE_HANDLED;
}
static cl_block_behavior g_bell = CL_BLOCK_BEHAVIOR_INIT;
static void on_register(void* payload, void* user) {
cl_block_desc d = CL_BLOCK_DESC_INIT;
(void)payload; (void)user;
g_bell.use_without_item = ring;
d.id = CL_STR("my_mod:ruby_doorbell");
d.copy_properties_from = CL_STR("minecraft:emerald_block");
d.vanilla_fallback = CL_STR("minecraft:emerald_block");
d.item = CL_STR("my_mod:ruby_doorbell");
d.light_emission = 10;
d.sound_type = CL_STR("amethyst_block");
d.behavior = &g_bell;
cl_block_register(&d, NULL);
}
static cl_result mod_main(void) { return CL_ON(CL_EV_LIFECYCLE_REGISTER, on_register); }
CLIGHT_MOD_ENTRY(mod_main)
What’s next
- A block that opens a shop menu: A shop block (block + menu).
- Several states (
lit,facing), custom shapes and per-position data are planned.