Skip to main content
Multiblock structures are machines that span multiple blocks placed in a specific 3D pattern. They unlock processing capabilities that are too complex or power-hungry to fit in a single block. The multiblock system in General Mechanics handles automatic detection, rotation, mirroring, and world-save persistence.

Core Concepts

ClassRole
LayoutDefines the 3D block pattern using a char grid
BlockPredicateTests whether a world block satisfies a layout cell
MultiblockDefinitionPairs a Layout with an optional Multiblock object factory
MultiblockValidatorValidates a Layout against live world blocks
MultiblockManagerTracks active multiblocks, fires events, persists data
MultiblockWorldDataNBT-backed SavedData for cross-session persistence
RotationUtilRotates 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.
Layout layout = Layout.builder()
    // Bottom layer (y=0)
    .layer(
        "FFF",
        "FAF",
        "FFF"
    )
    // Top layer (y=1)
    .layer(
        "FFF",
        "FCF",
        "FFF"
    )
    // Map characters to block predicates
    .key('F', CoreBlocks.POLYETHYLENE_MACHINE_FRAME)
    .key('A', BlockPredicate.of(Blocks.AIR))
    .key('C', coreBlock)          // controller block
    // Mark the controller as the anchor (validation origin)
    .anchor('C')
    .build();
Layout coordinate conventions:
  • 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:
@FunctionalInterface
interface BlockPredicate {
    boolean test(Level level, BlockPos pos, BlockState state);

    static BlockPredicate of(Block block) { ... }
}
Pass any Block, BlockDefinition<?>, or a custom lambda to .key().

Defining a MultiblockDefinition

MultiblockDefinition MY_MULTIBLOCK = new MultiblockDefinition(
    ResourceLocation.fromNamespaceAndPath("mymod", "my_multiblock"),
    layout,
    MyMultiblock::new   // optional Multiblock object factory
);
Register the definition in CoreRegistries.MULTIBLOCK_DEFINITIONS so MultiblockManager can discover it. If an objectFactory is supplied, the Multiblock instance receives lifecycle callbacks:
void onCreate(Level level, BlockPos anchorPos, Direction facing, boolean mirrored);
void onDestroy(Level level, BlockPos anchorPos, Direction facing, boolean mirrored);
void onTick(Level level, BlockPos anchorPos, Direction facing, boolean mirrored);
void onBlockChange(Level level, BlockPos anchorPos, Direction facing, boolean mirrored, BlockPos changed);
InteractionResult onInteract(Level level, BlockPos anchorPos, Direction facing, boolean mirrored,
                             Player player, InteractionHand hand, ItemStack item, BlockPos hitPos);

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:
// Layout → World
BlockPos worldOffset = RotationUtil.rotateAndMirror(lx, ly, lz, facing, mirrorX);

// World → Layout (inverse, used for hit-testing)
BlockPos layoutPos = RotationUtil.rotateAndMirrorInverse(wx, wy, wz, facing, mirrorX);
FacingTransformation
SOUTH(x, z) → (x, z)
WEST(x, z) → (z, -x)
NORTH(x, z) → (-x, -z)
EAST(x, z) → (-z, x)
Mirroring reflects across the Z axis before rotation: x → -x.

Validation

MultiblockValidator.findMatch() tries all 8 orientations (4 facings × mirrored/not) and returns the first successful ValidationResult:
public record ValidationResult(
    boolean success,
    BlockPos firstMismatch,  // null on success
    Direction facing,
    boolean mirrored
) {}
Optionally, block-count constraints can be enforced:
// Exact count required
Map<Block, Integer> requiredExact = Map.of(Blocks.FURNACE, 1);
// Minimum count required
Map<Block, Integer> requiredMinimum = Map.of(CoreBlocks.POLYETHYLENE_MACHINE_FRAME.block(), 8);

Optional<ValidationResult> result = MultiblockValidator.findMatch(
    layout, level, anchorPos, requiredExact, requiredMinimum
);
The Multiblock interface also exposes getRequiredExactBlocks() and getRequiredMinimumBlocks() which MultiblockDefinition.findMatch() automatically applies.

Assembly Process

1

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.
2

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.
3

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.
4

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.
5

Destruction

MultiblockManager also listens for BlockEvent.BreakEvent. Before the block is removed it simulates the break by temporarily setting that position to air and re-validates. If validation fails, MultiblockDestroyedEvent is fired and Multiblock.onDestroy() is called.

World Data Persistence

Active multiblocks survive server restarts through MultiblockWorldData, a SavedData stored under the key "gm_multiblocks". Each entry serializes:
  • pos — anchor BlockPos as a long
  • definitionResourceLocation ID of the MultiblockDefinition
  • facing — direction name string
  • mirrored — boolean
On world load (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.
public static void onWorldLoad(LevelEvent.Load event) {
    if (event.getLevel() instanceof ServerLevel level) {
        loadMultiblocksFromWorldData(level);
    }
}

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.

Build docs developers (and LLMs) love