Skip to main content
The Multiblocks API lets you describe 3D block patterns, register them as named definitions, and have the engine automatically detect formation and destruction in the world.

Core types

MultiblockDefinition

MultiblockDefinition is a record that pairs a ResourceLocation ID with a Layout. An optional objectFactory supplies a Multiblock behavior object when the structure forms.
public record MultiblockDefinition(
    ResourceLocation id,
    Layout layout,
    Supplier<Multiblock> objectFactory  // nullable
)

Constructors

// No behavior object; structure detection only
new MultiblockDefinition(ResourceLocation id, Layout layout)

// With a factory that creates a Multiblock behavior object on formation
new MultiblockDefinition(ResourceLocation id, Layout layout, Supplier<Multiblock> objectFactory)

Validation methods

// Try all 8 orientations (4 facings × mirrored/not)
Optional<ValidationResult> findMatch(Level level, BlockPos anchorPos)

// Try a specific set of facings
Optional<ValidationResult> findMatch(
    Level level, BlockPos anchorPos,
    EnumSet<Direction> facings, boolean includeMirror
)

// Validate at an exact orientation
MultiblockValidator.ValidationResult validateAt(
    Level level, BlockPos anchorPos,
    Direction facing, boolean mirrored
)

// Create the associated Multiblock object (returns null if no objectFactory)
Multiblock createObject()

// True if an objectFactory is present
boolean hasObject()

Layout

Layout describes the 3D block pattern as a character grid. Build one with Layout.builder(). Coordinate conventions:
  • X increases east; comes from column index within a row string.
  • Z increases south; comes from row index within a layer.
  • Y increases upward; comes from layer index (0 = bottom).
Special characters:
  • ' ' (space) — position is ignored; no block check is performed.
  • The anchor character (if set) marks the layout origin and is also skipped during validation.

Builder API

Layout layout = Layout.builder()
    .layer("AAA", "ABA", "AAA")  // bottom layer (Y=0)
    .layer("A A", "A A", "A A")  // middle layer (Y=1)
    .layer("AAA", "ACA", "AAA")  // top layer (Y=2)
    .key('A', BlockPredicate.of(Blocks.IRON_BLOCK))
    .key('B', BlockPredicate.of(Blocks.FURNACE))
    .key('C', BlockPredicate.any())  // accepts any block
    .anchor('C')                     // C is the anchor origin (optional)
    .build();
Builder methodDescription
layer(String... rows)Add one horizontal layer. All layers must share the same width and depth.
key(char symbol, BlockPredicate predicate)Map a character to a predicate.
key(char symbol, Block block)Convenience overload; creates BlockPredicate.of(block).
key(char symbol, BlockDefinition<?> block)Convenience overload for BlockDefinition.
anchor(char symbol)Mark a character as the anchor; it must appear exactly once.
build()Construct and validate the Layout.

Layout accessors

int width()                             // X size
int depth()                             // Z size
int height()                            // Y size
Optional<Character> anchorChar()        // anchor character if set
BlockPos anchorOffset()                 // layout-space coordinates of the anchor
Map<Character, BlockPredicate> keys()   // character-to-predicate map
char getCharAt(int x, int y, int z)     // character at layout coordinates
void forEachVoxel(VoxelConsumer consumer) // iterate all voxels

BlockPredicate

BlockPredicate is a functional interface used to validate whether a world block matches a pattern position.
@FunctionalInterface
public interface BlockPredicate {
    boolean test(Level level, BlockPos pos, BlockState state);
}

Factory methods

// Always matches
BlockPredicate.any()

// Matches exactly the given block
BlockPredicate.of(Block block)

// Matches blocks in the given tag
BlockPredicate.of(TagKey<Block> tag)

// Wraps an existing BlockState predicate
BlockPredicate.of(Predicate<BlockState> statePredicate)

Composition

BlockPredicate a = BlockPredicate.of(Blocks.IRON_BLOCK);
BlockPredicate b = BlockPredicate.of(Blocks.GOLD_BLOCK);

BlockPredicate either  = a.or(b);   // true if either matches
BlockPredicate both    = a.and(b);  // true only if both match
BlockPredicate neither = a.negate(); // logical NOT

MultiblockValidator

MultiblockValidator validates a Layout against actual world blocks. The base (unrotated) layout is assumed to face south.
// Try all 8 orientations (4 horizontal facings × mirrored/not)
Optional<ValidationResult> findMatch(Layout layout, Level level, BlockPos anchorPos)

// Try all orientations, also checking block count requirements
Optional<ValidationResult> findMatch(
    Layout layout, Level level, BlockPos anchorPos,
    Map<Block, Integer> requiredExact, Map<Block, Integer> requiredMinimum
)

// Validate at a fixed orientation
ValidationResult validateAt(
    Layout layout, Level level, BlockPos anchorPos,
    Direction facing, boolean mirrorX
)

// Validate at a fixed orientation with block count requirements
ValidationResult validateAt(
    Layout layout, Level level, BlockPos anchorPos,
    Direction facing, boolean mirrorX,
    Map<Block, Integer> requiredExact, Map<Block, Integer> requiredMinimum
)

ValidationResult

public record ValidationResult(
    boolean success,
    BlockPos firstMismatch,  // null on success
    Direction facing,
    boolean mirrored
)

MultiblockManager

MultiblockManager drives automatic detection, formation, and destruction. Initialize it once during mod setup.
// Register event listeners
MultiblockManager.init(IEventBus modEventBus)

// Programmatically check for a multiblock at a position
Optional<MultiblockFormedEvent> checkForMultiblock(Level level, BlockPos pos)

// Return a copy of all currently active multiblocks
Map<BlockPos, MultiblockInfo> getActiveMultiblocks()

// Clear all tracked multiblocks (use on server restart)
void clearAll()

// Handle a player right-click against a multiblock
InteractionResult handlePlayerInteraction(
    Level level, BlockPos hitPos, Player player,
    InteractionHand hand, ItemStack itemInHand
)
The manager listens to BlockEvent.BreakEvent, BlockEvent.EntityPlaceEvent, ServerTickEvent.Post, PlayerInteractEvent.RightClickBlock, and LevelEvent.Load automatically after init() is called.

MultiblockWorldData

MultiblockWorldData extends SavedData and persists active multiblock positions across server restarts. It is managed automatically by MultiblockManager.
// Retrieve (or create) the world data for a level
MultiblockWorldData.get(ServerLevel level)

// Inspect persisted multiblocks
Map<BlockPos, MultiblockInfo> getMultiblocks()
boolean hasMultiblock(BlockPos pos)
MultiblockInfo getMultiblock(BlockPos pos)

// Mutate persisted multiblocks (managed automatically by MultiblockManager)
void addMultiblock(BlockPos pos, MultiblockInfo info)
void removeMultiblock(BlockPos pos)
void clear()

RotationUtil

RotationUtil converts layout-space coordinates to world-space coordinates and back, handling all four horizontal facings and optional X-axis mirroring.
// Layout → world (used during validation and formation)
BlockPos rotateAndMirror(int x, int y, int z, Direction facing, boolean mirrorX)

// World → layout (used to check whether a world position is inside a multiblock)
BlockPos rotateAndMirrorInverse(int x, int y, int z, Direction facing, boolean mirrorX)
Rotation mapping (base facing is SOUTH):
FacingWorld XWorld Z
SOUTHxz
WESTz-x
NORTH-x-z
EAST-zx
Mirroring flips X before the rotation is applied: (x, z) → (-x, z).

Workflow

1

Define the layout

Use Layout.builder() to describe the 3D pattern with character layers and BlockPredicate keys.
2

Create a MultiblockDefinition

Wrap the layout in a MultiblockDefinition with a unique ResourceLocation ID. Optionally supply an objectFactory for behavior callbacks.
3

Register the definition

Register your definition in CoreRegistries.MULTIBLOCK_DEFINITIONS using a DeferredRegister.
4

Initialize MultiblockManager

Call MultiblockManager.init(modEventBus) in your mod constructor so event listeners are attached.
5

React to events

Listen for MultiblockFormedEvent and MultiblockDestroyedEvent on the NeoForge event bus to run game logic when a structure is assembled or broken.

Code example: defining a 3×3×3 smelter multiblock

import general.mechanics.api.multiblock.BlockPredicate;
import general.mechanics.api.multiblock.Layout;
import general.mechanics.api.multiblock.MultiblockDefinition;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.Blocks;

public class MyMultiblocks {

    public static final Layout SMELTER_LAYOUT = Layout.builder()
        // Y=0  (base)
        .layer(
            "BBB",
            "BAB",   // 'A' is the anchor / controller position
            "BBB"
        )
        // Y=1
        .layer(
            "B B",
            "B B",
            "B B"
        )
        // Y=2  (top)
        .layer(
            "BBB",
            "B B",
            "BBB"
        )
        .key('B', BlockPredicate.of(Blocks.BRICKS))
        .anchor('A')
        .build();

    public static final MultiblockDefinition SMELTER = new MultiblockDefinition(
        ResourceLocation.fromNamespaceAndPath("mymod", "smelter"),
        SMELTER_LAYOUT
    );
}
Register the definition so MultiblockManager can discover it:
// In your mod's DeferredRegister setup
DeferredRegister<MultiblockDefinition> MULTIBLOCK_REGISTER =
    DeferredRegister.create(CoreRegistries.MULTIBLOCKS, "mymod");

static final DeferredHolder<MultiblockDefinition, MultiblockDefinition> SMELTER =
    MULTIBLOCK_REGISTER.register("smelter", () -> MyMultiblocks.SMELTER);

Build docs developers (and LLMs) love