Blocks and block states
Status: Partly implemented. A mod block has one state. In the world it is its vanilla base state (
vanilla_fallback, elsecopy_properties_from, else stone): collision, shape, the right tool and what vanilla clients see come from the base. Its owndestroy_time,friction,speed_factor,jump_factor,light_emissionandsound_typeare 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.