Core Concepts
| Class | Role |
|---|---|
Layout | Defines the 3D block pattern using a char grid |
BlockPredicate | Tests whether a world block satisfies a layout cell |
MultiblockDefinition | Pairs a Layout with an optional Multiblock object factory |
MultiblockValidator | Validates a Layout against live world blocks |
MultiblockManager | Tracks active multiblocks, fires events, persists data |
MultiblockWorldData | NBT-backed SavedData for cross-session persistence |
RotationUtil | Rotates and mirrors layout coordinates to world space |
Defining a Layout
Layout is constructed with a fluent builder. Layers are added bottom-to-top; within each layer rows run north to south and columns run west to east.
- X increases eastward (column index within a row string)
- Z increases southward (row index within a layer)
- Y increases upward (layer index, 0 = bottom)
- Space
' 'is always ignored — no block check performed - The anchor character is also ignored during validation but records the origin offset
BlockPredicate
BlockPredicate is a functional interface:
Block, BlockDefinition<?>, or a custom lambda to .key().
Defining a MultiblockDefinition
CoreRegistries.MULTIBLOCK_DEFINITIONS so MultiblockManager can discover it.
If an objectFactory is supplied, the Multiblock instance receives lifecycle callbacks:
Rotation and Mirroring
The validator assumes the unrotated layout faces SOUTH.RotationUtil maps layout-space coordinates to world-space offsets for any of the four horizontal facings, with optional X-axis mirroring:
| Facing | Transformation |
|---|---|
| SOUTH | (x, z) → (x, z) |
| WEST | (x, z) → (z, -x) |
| NORTH | (x, z) → (-x, -z) |
| EAST | (x, z) → (-z, x) |
x → -x.
Validation
MultiblockValidator.findMatch() tries all 8 orientations (4 facings × mirrored/not) and returns the first successful ValidationResult:
Multiblock interface also exposes getRequiredExactBlocks() and getRequiredMinimumBlocks() which MultiblockDefinition.findMatch() automatically applies.
Assembly Process
Build the pattern
Place all required blocks in the exact shape described by the
Layout. Every non-space, non-anchor character must match its mapped BlockPredicate.Automatic detection
MultiblockManager listens for block placement events (BlockEvent.EntityPlaceEvent). When a block is placed, it scans the surrounding 5×5×5 volume (radius 2) and calls MultiblockDefinition.findMatch() for each registered definition at each candidate anchor position.Formation event
On success, a
MultiblockFormedEvent is fired on the NeoForge event bus. The multiblock is recorded in activeMultiblocks and, if the definition has an object factory, the Multiblock object’s onCreate() is called.Use the multiblock
Right-clicking any block within the multiblock’s footprint calls
Multiblock.onInteract() on the active object. The manager routes the click automatically — no special controller block is required.World Data Persistence
Active multiblocks survive server restarts throughMultiblockWorldData, a SavedData stored under the key "gm_multiblocks".
Each entry serializes:
pos— anchorBlockPosas alongdefinition—ResourceLocationID of theMultiblockDefinitionfacing— direction name stringmirrored— boolean
LevelEvent.Load), MultiblockManager.onWorldLoad() reads the saved data, re-validates each entry against the current world, restores valid multiblocks to activeMultiblocks, and removes any that are no longer valid.
Conflict Detection
A block position may only belong to one active multiblock at a time. Before forming a new multiblock,MultiblockManager.hasMultiblockConflict() verifies that none of the positions in the candidate structure already belong to an existing tracked multiblock.