Registering content
Golem Core’s dev.golem.core.registry.Registrar registers an entry and states its boilerplate (model, blockstate, item
model, loot table, tags, creative tab) in one chain. HBM’s own com.hbm.registration.Reg extends it. At build time,
Dejson writes the declared JSON files on all six nodes.
A registration class
Section titled “A registration class”One class per family of content. It owns its DeferredRegisters and a Registrar over them.
public final class SpaceBlocks {
public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks(NtmSpace.MOD_ID); public static final DeferredRegister.Items ITEMS = DeferredRegister.createItems(NtmSpace.MOD_ID); private static final Registrar REG = Registrar.of(BLOCKS, ITEMS);
public static final RegistryObject<Block> MOON_ROCK = REG.simpleBlock("moon_rock", p -> p.strength(1.5F, 10.0F).sound(SoundType.STONE).requiresCorrectToolForDrops()) .transform(Presets.cubeAll()).loot(Loot.self()).tag(BlockTag.PICKAXE); public static final RegistryObject<BlockItem> MOON_ROCK_ITEM = REG.blockItem(MOON_ROCK).tab(HbmTab.BLOCKS);
private SpaceBlocks() {}
public static void init() {}}Call SpaceBlocks.init() from your declare(). Registers flush in the order they were created and entries in the order
they were added, so registry order follows your code.
Typed register factories
Section titled “Typed register factories”| Factory | Register | Registry |
|---|---|---|
DeferredRegister.createBlocks(ns) |
DeferredRegister.Blocks |
blocks |
DeferredRegister.createItems(ns) |
DeferredRegister.Items |
items |
DeferredRegister.createBlockEntityTypes(ns) |
DeferredRegister.BlockEntityTypes |
block entity types |
DeferredRegister.createMenuTypes(ns) |
DeferredRegister.MenuTypes |
menu types |
DeferredRegister.createSounds(ns) |
DeferredRegister.Sounds |
sound events |
DeferredRegister.createEntityTypes(ns) |
DeferredRegister.EntityTypes |
entity types |
DeferredRegister.create(() -> Registries.X, ns) |
DeferredRegister<T> |
any other registry |
Prefer the typed factories. On 1.20.1 a ResourceKey cannot be created outside a bootstrapped game, so a class whose
static initialiser names Registries.X directly fails at build time (see the plain-JVM rule below). All registers of
one Registrar must share one namespace.
Registrar methods
Section titled “Registrar methods”| Method | Registers |
|---|---|
block(name, factory, properties) |
a block of your class; factory takes BlockBehaviour.Properties |
block(name, factory) |
the same with default properties |
simpleBlock(name, properties) |
a plain Block |
item(name, factory, properties) |
an item of your class |
item(name, factory) |
the same with default properties |
simpleItem(name, properties) |
a plain item |
item(name) |
a plain item with default properties |
blockItem(block) |
the block’s plain BlockItem, under the block’s name |
blockItem(block, properties) |
the same with item properties |
blockItem(block, (b, p) -> new MyItem(b, p), properties) |
a block item of your class |
blockEntity(name, () -> type) |
a block entity type |
menu(name, () -> type) |
a menu type |
sound(name) |
a variable-range sound event <ns>:<name> |
entity(name, () -> type) / entity(name, key -> type) |
an entity type (returns an EntityHandle) |
register(registryKey, name, () -> value) |
anything else, on a register of that registry |
simpleBlock and simpleItem exist because an implicitly typed lambda (REG.block("x", p -> ...)) would be ambiguous.
A factory that returns a subclass may need a type witness: REG.<MyBlock, BlockItem>blockItem(...).
Handle chain methods
Section titled “Handle chain methods”Every method above except entity returns a Registrar.Handle<T>, which is the RegistryObject<T> your field
holds (get(), getId(), getKey() work as usual). Each declaring method states one surface for the id; stating a
surface twice fails at build time (“declared twice”).
| Method | On | States |
|---|---|---|
.model(d -> ...) |
block | model, blockstate and optionally the block item’s model, as one Dejson.Declaration |
.icon(d -> ...) |
item or block | the item model and item definition, as one Dejson.ItemDeclaration |
.loot(Loot) |
block | the block loot table data/<ns>/loot_table/blocks/<path>.json |
.tag(BlockTag) |
block | block tag membership |
.tag(ItemTag) |
item or block | item tag membership (on a block, for its same-id item) |
.tab(TabTarget) |
item | creative tab listing, in call order |
.transform(Preset) |
any | a reusable bundle of declarations |
.andThen(handle -> ...) |
any | runs an action on the handle now |
.addTo(collection) |
any | adds the handle to a list |
path(), namespace(), id() and isBlock() read the handle.
Registrar.EntityHandle<E> has .tag(EntityTag), .addTo(collection), path() and id().
Presets
Section titled “Presets”HBM’s com.hbm.registration.Presets holds the shared declaration shapes. Texture defaults are under the registering
mod’s namespace, so an addon finds its own textures (<ns>:blocks/<path>, <ns>:items/<path>).
| Preset | Writes |
|---|---|
cubeAll() |
cube_all over <ns>:blocks/<path> |
cubeAll(texture) |
cube_all over the given texture |
cubeAllCutout() |
cubeAll() on the cutout layer |
cubeBottomTop(bottom, side, top) |
cube_bottom_top |
column(end, side) |
cube_column |
orientable(front, side, top) |
orientable |
cross(texture) |
cross |
shape(shape) |
any other Dejson Shape |
rendered(sharedModel, states) |
a blockstate pointing every state at a shared model; the block is drawn by a renderer |
rendered(states) |
the same with hbm:block/machine_dummy_empty |
statesOnly(states) |
only the blockstate; the model <ns>:block/<path> is an authored file |
statesOnly(modelId, states) |
only the blockstate, pointing at another authored model |
sprite() |
item/generated over <ns>:items/<path> |
sprite(layers...) |
item/generated over the given layers |
handheld() / handheld(texture) |
HBM’s held full-3D item model |
shares(modelId) |
an item definition naming an existing model |
Block presets default to a single-variant blockstate and a block item whose definition names the block model. Change that
with .states(Variants.facing()) (or axis(), uniformRange(property, from, to), …), .viaItemModel(),
.directItem() or .noItem(). Item presets take .tints(Tint...). Two presets combine with preset.and(other).
public static final RegistryObject<NukeAntimatter> NUKE_ANTIMATTER = REG.block("nuke_antimatter", NukeAntimatter::new, p -> p.strength(5.0F, 6000.0F).sound(SoundType.METAL).requiresCorrectToolForDrops().noOcclusion()) .transform(Presets.rendered(Variants.uniformRange("facing", 2, 5))) .loot(Loot.self()).tag(BlockTag.PICKAXE);dev.golem.core.registry.Loot:
| Loot | Drops |
|---|---|
self() |
the block (survives explosion) |
selfAlways() |
the block, explosion or not |
nothing() |
an empty loot table |
only(drop) |
one other item |
slab() |
slab drops |
container(components...) |
the block, copying the named data components onto the item |
silkTouchOnly() |
the block with silk touch only |
oreNoFortune(drop, min, max) |
silk touch: the block; else min..max of drop, no fortune |
oreMultiplied(drop, min, max) |
the 1.7 ore shape: silk touch gives the block, else min..max multiplied by fortune |
dropsMultiplied(drop, min, max) |
oreMultiplied without the silk touch branch |
oreOrRare(drop, min, max, rare, chance) |
silk touch: the block; else rare with chance; else min..max of drop |
selfWhen(property, value) |
the block when a state property has a value |
chance(drop, chance) |
drop with a chance |
stacks(id, count, id, count, ...) |
fixed stacks |
Tags and tabs
Section titled “Tags and tabs”BlockTag constants: PICKAXE, AXE, SHOVEL, HOE (the minecraft:mineable/* tags), NEEDS_IRON_TOOL,
CLIMBABLE. Any other: BlockTag.of("ns:path"), ItemTag.of("ns:path"), EntityTag.of("ns:path"). Tag memberships
from every mod accumulate.
.tab(...) takes any TabTarget. HBM’s com.hbm.creativetabs.HbmTab lists your items in HBM’s tabs: MATERIALS,
CONTROL, TEMPLATES, RESOURCES, BLOCKS, MACHINES, BOMBS, MISSILES, WEAPONS, CONSUMABLES.
Block entities, menus, sounds and entities
Section titled “Block entities, menus, sounds and entities”public static final RegistryObject<BlockEntityType<TileEntityAntimatter>> NUKE_ANTIMATTER_BE = REG.blockEntity("nuke_antimatter", () -> BlockEntities.serverTicking( TileEntityAntimatter::new, TileEntityAntimatter::serverTick, NUKE_ANTIMATTER.get()));
public static final RegistryObject<MenuType<ContainerNukeAntimatter>> NUKE_ANTIMATTER_MENU = REG.menu("nuke_antimatter", () -> Menus.typeWithPos(ContainerNukeAntimatter::new));dev.golem.core.registry.BlockEntities builds the type on every loader and records its ticker:
| Method | Type |
|---|---|
type(factory, blocks...) |
no ticker |
serverTicking(factory, serverTick, blocks...) |
server ticker only (most machines) |
clientTicking(factory, clientTick, blocks...) |
client ticker only |
ticking(factory, clientTick, serverTick, blocks...) |
either side; null for none |
A block implementing Golem Core’s TickingBlock gets its ticker from the type. dev.golem.core.gui.Menus builds menu
types whose client side receives a position (typeWithPos), a hand (typeWithHand) or an entity (typeWithEntity), and
opens them on the server (openWithPos, openWithHand, openWithEntity).
Sounds: REG.sound("alarm.ducc") registers <ns>:alarm.ducc. assets/<ns>/sounds.json stays an authored file.
Entities use dev.golem.core.registry.Entities to build the type on every node:
public static final DeferredRegister.EntityTypes ENTITY_TYPES = DeferredRegister.createEntityTypes(NtmSpace.MOD_ID);private static final Registrar REG = Registrar.of(ENTITY_TYPES);
public static final Registrar.EntityHandle<EntityRideableRocket> RIDEABLE_ROCKET = REG.entity("entity_rideable_rocket", key -> Entities.build(Entities.noLoot(EntityType.Builder.<EntityRideableRocket>of(EntityRideableRocket::new, MobCategory.MISC)).fireImmune().sized(2.0F, 8.0F).clientTrackingRange(63).updateInterval(1), key));Client registrations
Section titled “Client registrations”Screens, renderers and other client-only bindings go through Golem Core’s dev.golem.core.client facades, called from
your client set-up (Mod entry). Each runs at the right moment on every loader.
ClientRegistrations.screens(screens -> screens.register(DriveProcessorBlocks.DRIVE_PROCESSOR_MENU.get(), GUIMachineDriveProcessor::new));ClientRegistrations.renderers(r -> r.blockEntity(DriveProcessorBlocks.DRIVE_PROCESSOR_BE.get(), context -> new RenderDriveProcessor()));| Call | Registers |
|---|---|
ClientRegistrations.screens(screens -> screens.register(menuType, ScreenClass::new)) |
a menu’s screen |
ClientRegistrations.renderers(r -> r.blockEntity(type, provider)) |
a block entity renderer (CompatBER) |
ClientRegistrations.renderers(r -> r.entity(type, provider)) |
an entity renderer |
Hud.addLast(ns, path, graphics -> ...) |
a HUD overlay drawn after vanilla’s HUD |
KeyMappings.register(ns, translationKey, glfwKey, categoryPath) |
a key binding; returns a KeyMapping to poll with isDown() / consumeClick() |
ItemModelProperties.register(ns, name, stack -> int) |
an item model property <ns>:<name> for item definitions to select on |
ItemModelProperties.includeItems(propertyNs, name, itemNamespaces...) |
applies another mod’s property (such as hbm:model_state) to your items below 26.1 |
HbmEntityRenderers.addExtension(sink -> sink.register(type, provider)) binds an entity renderer inside HBM’s own
renderer registration, so HBM’s renderer coverage check sees it. Call it on the client before the loader registers
renderers. For HBM’s own registries of client objects (custom item renderers, tracers, bomber skins), see
Registries.
Network payloads
Section titled “Network payloads”A payload is declared once, in declare(), and Golem Core registers it with every loader’s networking.
public record Ping(int value) implements GolemPayload { @Override public Identifier id() { return CODEC.id(); }}
public static final GolemCodec<Ping> CODEC = GolemCodec.of(Ping.class, Ids.of(ProbeEntry.MOD_ID, "ping"), (payload, buf) -> buf.writeVarInt(payload.value()), buf -> new Ping(buf.readVarInt()));
public static void init() { // from declare() GolemNetwork.declarePlayToClient(CODEC);}
public static void initClient() { // from client set-up GolemNetwork.registerPlayToClient(CODEC, (payload, context) -> lastPing = payload.value());}
public static boolean send(ServerPlayer player, int value) { if (!GolemNetwork.canSendToPlayer(player, CODEC)) return false; GolemNetwork.sendToPlayer(player, CODEC, new Ping(value)); return true;}dev.golem.core.net.GolemNetwork |
Does |
|---|---|
declarePlayToClient(codec) |
declares a server-to-client payload (common code, declare()) |
registerPlayToClient(codec, (payload, context) -> ...) |
its client handler, on the client thread; context has level() and player() |
registerPlayToServer(codec, (payload, player) -> ...) |
a client-to-server payload and its server handler, on the server thread (common code, declare()) |
sendToPlayer(player, codec, payload) |
sends to one player; never throws when the player’s client lacks the channel |
sendToTracking(level, pos, codec, payload) |
sends to every player tracking the chunk at pos |
sendToServer(codec, payload) |
sends from the client |
canSendToPlayer(player, codec) |
whether the player’s client has the channel |
GolemCodec.of(type, id, encoder, decoder) uses a plain FriendlyByteBuf; GolemCodec.ofRegistry(...) the
registry-aware buffer (plain below 1.20.5), for payloads that write item stacks or components. Off-thread variants
(declarePlayToClientOffThread, registerPlayToClientOffThread, registerPlayToServerOffThread) run the handler on
the network thread and give it the payload only. Keep a payload’s id stable once released.
Dejson: what to declare and what to author
Section titled “Dejson: what to declare and what to author”Dejson is for uniform boilerplate only: simple blocks and items, their loot and tags, and crafting and smelting recipes
(see Recipes and data). Hand-tuned JSON stays an authored file under src/main/resources: custom
elements geometry, display transforms, one-off OBJ item definitions, dimensions and worldgen. The chain only points
at such a file (Presets.statesOnly(...), .icon(d -> d.shares("ns:item/x"))).
Two lists in gradle/ tell the build which is which:
# Classes whose static declarations Dejson collects, one per line, in initialisation order.com.ntmspace.registry.SpaceBlockscom.ntmspace.registry.DunaBlockscom.ntmspace.recipe.SpaceCraftingRecipes# Authored JSON under Dejson-owned folders: exact paths, or a folder ending in a slash.data/ntmspace/dimension/data/ntmspace/dimension_type/data/ntmspace/worldgen/assets/ntmspace/blockstates/vinyl_vine.jsonassets/ntmspace/models/block/vinyl_vine.json- Every class with chain declarations must be listed in
dejson-classes.txt. Dejson initialises each one and calls its staticdeclare()if it has one. - Every authored file in a Dejson-owned folder must be listed in
dejson-authored.txt. The owned folders, in any namespace:assets/*/items,models,blockstates,equipment,atlases, anddata/*/tags,loot_table,recipe,advancement,damage_type,worldgen,data_maps,jukebox_song,neoforge/biome_modifier. A hand-written machine recipe indata/<ns>/recipe/counts too. Other data folders (value_maps,fluid_property,recipe_removals,dimension,structure, …) are not owned. ThedejsonOwnershiptask fails the build on an unlisted authored file, and on a listed path that no longer exists. - An authored file at a declared path silently wins over the declaration. When you take a file back, delete its declaration in the same change.
- Run
:26.1-neoforge:dejsonVerifyafter declaring: it is the check that every declared texture exists.
The plain-JVM rule
Section titled “The plain-JVM rule”Dejson runs your listed classes’ static initialisers in a plain JVM, with Minecraft on the classpath but never bootstrapped. 26.1 is lenient here; 1.20.1 is not. In a listed class, a static initialiser must not:
- create a
ResourceKeyor nameRegistries.X(use the typed register factories), - read a registry or call
get()/getId()on a handle, - touch a
SoundTypeconstant or build anItem.Properties.
Keep all of that inside the factory and properties lambdas, which registration only stores. SoundType.STONE inside
p -> p.sound(SoundType.STONE) is fine. For lazy values Golem Core offers GolemComponent.of(ns, name, () -> CODEC) and
ToolTier.of(..., () -> repairTag).