Skip to content

Blocks and block states

Status: Partly implemented. A mod block has one state. In the world it is its vanilla base state (vanilla_fallback, else copy_properties_from, else stone): collision, shape, the right tool and what vanilla clients see come from the base. Its own destroy_time, friction, speed_factor, jump_factor, light_emission and sound_type are used on top; the server simulates them and Celestial clients predict the same. Celestial clients draw the mod’s own model at mod block positions. Breaking uses the loot table <ns>:blocks/<path>, else drops the block’s item. Block states with properties, custom shapes, overrides of vanilla blocks and the larger behaviour table are Planned.

Function tables: registry, level. Concepts: Registries.

Block descriptor

typedef struct cl_block_desc {
    uint32_t        struct_size;
    cl_str          id;                     // "my_mod:ruby_block" (your own namespace only)
    cl_str          copy_properties_from;   // a block whose properties are the defaults ("minecraft:emerald_block")
    float           destroy_time;           // default 1.5; -1 = unbreakable
    float           explosion_resistance;   // default 6.0
    float           friction;               // default 0.6
    float           speed_factor;           // default 1.0
    float           jump_factor;            // default 1.0
    uint8_t         light_emission;         // 0..15
    cl_str          sound_type;             // a SoundType name ("glass", "metal") or a block id
    cl_str          map_color;
    cl_block_flags  flags;                  // CL_BLOCK_*
    const cl_block_behavior* behavior;      // optional; must stay valid (static storage)
    cl_str          vanilla_fallback;       // the base block in the world and for vanilla clients
    cl_str          item;                   // also register a block item with this id ("" = none)
    void*           user;                   // passed to behaviour callbacks as ctx->user
    cl_render_layer render_layer;           // CL_LAYER_SOLID, _CUTOUT_MIPPED, _CUTOUT, _TRANSLUCENT, _TRIPWIRE
    cl_str          creative_tab;           // the creative tab of its block item ("minecraft:building_blocks")
} cl_block_desc;
/* cl_block_desc d = CL_BLOCK_DESC_INIT; */
Field Used today
destroy_time, friction, speed_factor, jump_factor, light_emission, sound_type Yes. A field left at its default keeps copy_properties_from’s value, else the base block’s.
vanilla_fallback, copy_properties_from Yes: they choose the base block (collision, shape, tool, map colour, what vanilla clients see)
item, behavior, user Yes
creative_tab Yes: the block item (item) shows in this tab, unless you register that item yourself with its own creative_tab. Empty = no tab.
flags CL_BLOCK_NO_LOOT_TABLE (drop nothing). The other flags are accepted; the base block’s own behaviour applies.
explosion_resistance, map_color, render_layer Stored; the base block’s values are used until custom block states arrive

Behaviour table

typedef struct cl_block_ctx { uint32_t struct_size; cl_level level; cl_block_pos pos; cl_block_state state; void* user; } cl_block_ctx;

typedef struct cl_block_behavior {
    uint32_t struct_size;
    cl_block_use_fn         use_without_item;  // (ctx, player, hand, cl_interaction_result* out)
    cl_block_attack_fn      attack;            // (ctx, player, int32_t* unused)
    cl_block_random_tick_fn random_tick;       // (ctx, int32_t* unused)
} cl_block_behavior;
/* CL_BLOCK_BEHAVIOR_INIT: every entry NULL */

Every entry returns CL_OVERRIDE_HANDLED (you handled it; for use_without_item write *out) or CL_OVERRIDE_CALL_ORIGINAL (the base block’s behaviour runs). All run on the server tick. random_tick runs with vanilla’s odds (randomTickSpeed chances of 1 in 4096 per section block per tick) for blocks that have the entry. See Events, behaviours and mixins for examples in all three languages.

Block states

C Lua Notes
cl_block_default_state(b) cl.registry.block_default_state(b) The only state of a mod block
cl_block_state_block(s) cl.registry.block_state_block(s)
cl_block_state_is(s, CL_STR("minecraft:stone")) cl.registry.block_state_is(s, "minecraft:stone") In events: event.block == "minecraft:stone"
cl_level_get_block(level, pos, &s) level:get_block(pos) World access
cl_level_set_block(level, pos, s, CL_SET_ALL) level:set_block(pos, "my_mod:ruby_block") Mod blocks too

Assets and data

File Purpose
assets/<ns>/blockstates/<path>.json The "" variant is drawn on Celestial clients
assets/<ns>/models/block/<path>.json, textures/block/... The model and textures
assets/<ns>/lang/en_us.json block.<ns>.<path>
datapack/<ns>/loot_table/blocks/<path>.json Drops (player breaks, explosions, /loot; mod items allowed)

Vanilla clients

A vanilla client (or a Celestial client without the mod) receives the base block in chunk data and block updates. The server-side logic stays your block’s. See Registry sync and vanilla clients.

Planned

Block properties and several states per block (facing, lit, age), state parsing and with, custom shapes, per-state light, overrides of vanilla blocks’ behaviour, and more entries (placement, can_survive, neighbour updates, scheduled ticks, entity contact, redstone signals, client animate_tick).

Block state properties and block facts (API 1.11)

Every vanilla state can be read and changed by property name: cl_block_state_property / cl.registry.state_property(state, "open") (Lua gives true / false, numbers and strings), cl_block_state_with / cl.registry.state_with(state, "age", 7), the property list and values, and the /setblock syntax both ways (cl_block_state_to_string, cl_block_state_parse; Lua accepts "minecraft:oak_stairs[facing=east]" wherever a state goes). cl_block_get_info / cl.registry.block_info(state) describe a state (shape facts, hardness, friction, light, map colour) and cl_block_sound_type names its SoundType ("stone", "wood", …). Mod blocks have no properties and report their own facts. Details: World access, Guide: World Access.