Skip to content

A custom block

Status: Implemented. Everything in this guide works today. Blocks have one state (no lit or facing properties 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.