Skip to content

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_fallback item (default minecraft: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_name and max_stack_size. /give @s my_mod:ruby works.
  • A mod block is its vanilla base state in the chunk (vanilla_fallback, else copy_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 own destroy_time, friction, speed_factor, jump_factor, light_emission and sound_type are 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, so block.break handlers 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_register work only during lifecycle.register. Afterwards they return CL_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.