Registries
Status: Partly implemented. Mods register blocks and items in code, and add data-driven entries (recipes, loot tables, tags, enchantments and the other data-pack registries) through their
datapack/folder. Other code registries (entity types, block entities, particles, menus, custom registries) are Planned.
A registry maps identifiers to game content. Celestial has the same registries as vanilla 26.3, and CLight lets mods add to some of them.
Two kinds of registries
| Kind | Examples | How mods add entries today |
|---|---|---|
| Built-in (code) | block, item |
Code in lifecycle.register: cl_block_register, cl_item_register (Lua cl.blocks.register{...}, cl.items.register{...}) |
| Data-driven | recipes, loot tables, tags, advancements, enchantment, damage_type, painting_variant, jukebox_song, trim_material, … |
Data: JSON in the mod’s datapack/<namespace>/ folder, loaded like a vanilla data pack (pack mod/<id>, above vanilla and below the world’s own data packs) |
Prefer data. An enchantment, a recipe or a painting written as data works for every client, costs nothing at run time, and reloads with
/reload. Use code for behaviour data can’t express.
Vanilla-backed content
The game’s built-in registries are fixed tables (vanilla clients know exactly that list), so mod blocks and items are vanilla-backed:
- A mod item is a stack of its
vanilla_fallbackitem (defaultminecraft:paper) with a component patch:custom_data {clight:"<id>"},item_model "<id>"(its own model on Celestial clients, and on vanilla clients that have the mod’s resource pack),item_nameandmax_stack_size./give @s my_mod:rubyworks. - A mod block is its vanilla base state in the chunk (
vanilla_fallback, elsecopy_properties_from, else stone) plus a server-side list of positions saved with the world. The base gives collision, shape and what vanilla clients see; the mod’s owndestroy_time,friction,speed_factor,jump_factor,light_emissionandsound_typeare used on top. Celestial clients draw the mod’s own block model at those positions. - Lookups (
cl_block_lookup,cl_item_lookup) return virtual ids for mod content, soblock.breakhandlers can compare them like any other block.
Details: Blocks and block states and Items and item stacks.
The registration window
construct ── lifecycle.register ──► FREEZE ── lifecycle.registries_frozen ── ...
(blocks and items open) (closed for the rest of the process)
cl_block_register/cl_item_registerwork only duringlifecycle.register. Afterwards they returnCL_E_WRONG_PHASE.- Data-pack content is read again on every world start and on
/reload. - A hot reload can’t add new blocks or items; the game logs “restart required” and keeps the old set (Hot reload). Re-registering an id that already exists returns the same holder, so a reloaded script doesn’t fail.
Registering
Each content type has a descriptor struct. Descriptors start with uint32_t struct_size, so later API versions can add fields without breaking old mods (ABI stability and API versioning). Always start from the _INIT macro.
cl_block_desc d = CL_BLOCK_DESC_INIT; // struct_size and the defaults
d.id = CL_STR("ruby_tools:ruby_block");
d.copy_properties_from = CL_STR("minecraft:emerald_block");
d.vanilla_fallback = CL_STR("minecraft:emerald_block");
d.destroy_time = 5.0f;
d.flags = CL_BLOCK_REQUIRES_CORRECT_TOOL;
d.item = CL_STR("ruby_tools:ruby_block"); // also register its block item
cl_holder ruby_block;
cl_block_register(&d, &ruby_block);
cl_block_desc d = CL_BLOCK_DESC_INIT;
d.id = CL_STR("ruby_tools:ruby_block");
d.copy_properties_from = CL_STR("minecraft:emerald_block");
d.vanilla_fallback = CL_STR("minecraft:emerald_block");
auto holder = clight::registry::block_register(&d); // Expected<cl_holder>
// or with lambda behaviours: clight::registry::block(d, {.use_without_item = ...})
RUBY_BLOCK = cl.blocks.register{
id = "ruby_tools:ruby_block", destroy_time = 5.0,
copy_properties_from = "minecraft:emerald_block", vanilla_fallback = "minecraft:emerald_block",
requires_correct_tool = true, -- a block flag as a boolean field
item = "ruby_tools:ruby_block",
}
Every field is listed in the generated types page (cl_block_desc, cl_item_desc).
What a registration pairs with
| You register | The engine looks up |
|---|---|
Block ruby_tools:ruby_block |
assets/ruby_tools/blockstates/ruby_block.json (the "" variant) and its models and textures, the loot table datapack/ruby_tools/loot_table/blocks/ruby_block.json, the lang key block.ruby_tools.ruby_block |
Item ruby_tools:ruby |
assets/ruby_tools/items/ruby.json (item model definition), models/item/ruby.json, the lang key item.ruby_tools.ruby |
| Sound event | assets/<ns>/sounds.json |
Code says what exists and how it behaves. Data says how it looks and what it drops: the same split vanilla uses.
Querying
| Operation | C | Lua |
|---|---|---|
| Block / item id by name | cl_block_lookup(CL_STR("minecraft:stone"), &id), cl_item_lookup |
cl.registry.block_lookup("minecraft:stone") |
| Name of an id | cl_block_name(id, buf, cap, &n), cl_item_name |
cl.registry.block_name(id) |
| Default state, block of a state | cl_block_default_state, cl_block_state_block |
cl.registry.block_default_state(id) |
| Is this state that block? | cl_block_state_is(state, CL_STR("minecraft:stone")) |
event.block == "minecraft:stone" |
| Frozen yet? | cl_registries_frozen() |
cl.registry.registries_frozen() |
Iterating registries, tag queries and custom registries are Planned.
Changing vanilla entries
You can’t replace a vanilla block or item. Change its behaviour through events and mixins (Events, behaviours and mixins, Mixins), and its looks, drops and recipes through your mod’s resource and data packs.