Skip to content

Controller

IController

Controller interface, defining multiblock detection and lifecycle behavior.

java
public interface IController {
    Block getBlock();           // The controller block
    Identifier getDefinitionId(); // The associated definition ID

    // Lifecycle callbacks
    default void onFormed(Level level, MultiblockState state) { }
    default void onUnformed(Level level, MultiblockState state) { }

    // Correct the controller position (multiblock component adjustment)
    default BlockPos correctPos(ServerLevel level, BlockPos pos, BlockState state) {
        return pos;
    }
}

correctPos Use Cases

When the controller detects that the block is not exactly at the ZERO position in the definition, you can correct it via this method. For example: a multiblock component acts as a "controller marker" but the actual controller block is at an adjacent position.

java
@Override
public BlockPos correctPos(ServerLevel level, BlockPos pos, BlockState state) {
    // The actual controller is 1 block above the detected position
    return pos.above();
}

SimpleController

A convenient abstract base class that stores Block and DefinitionId as final fields.

java
@Getter
public abstract class SimpleController implements IController {
    private final Block block;
    private final Identifier definitionId;

    protected SimpleController(Block block, Identifier definitionId) {
        this.block = block;
        this.definitionId = definitionId;
    }
}

Usage

java
public class MyMultiblockController extends SimpleController {
    public MyMultiblockController() {
        super(
            MyBlocks.CONTROLLER,
            ResourceLocation.fromNamespaceAndPath("mymod", "my_multiblock")
        );
    }

    @Override
    public void onFormed(Level level, MultiblockState state) {
        // When formed: update textures, start particles, play sounds, etc.
        if (level instanceof ServerLevel sl) {
            sl.sendParticles(ParticleTypes.END_ROD, ...);
        }
    }

    @Override
    public void onUnformed(Level level, MultiblockState state) {
        // When unformed: restore default state
    }

    @Override
    public BlockPos correctPos(ServerLevel level, BlockPos pos, BlockState state) {
        // Optional: correct the controller position
        return pos;
    }
}

Directly implementing IController on a block is also recognized:

java
public class ControllerBlock extends Block implements IController {
    @Override
    public Block getBlock() { return this; }

    @Override
    public Identifier getDefinitionId() {
        return ResourceLocation.fromNamespaceAndPath("mymod", "definition_id");
    }
}

ControllerRecord

A static registry that maintains a (Block, Identifier)IController mapping.

java
public class ControllerRecord {
    // Register a SimpleController
    public static void register(SimpleController controller);

    // Look up a controller (throws IllegalArgumentException if not found and block does not implement IController)
    public static IController get(Block block, Identifier definitionId);
}

Lookup Logic

  1. Search the CONTROLLERS map for an exact (Block, DefinitionId) match
  2. If not found, check whether Block is instanceof IController
  3. If so, auto-register it in the map (cache) and return
  4. If neither, throw IllegalArgumentException

Registration

java
// Register during mod construction (e.g., in @Mod constructor or common setup)
ControllerRecord.register(new MyMultiblockController());

ControllerInfo (Internal)

java
// package-private key record
record ControllerInfo(Block block, Identifier definitionId) { }

Lifecycle

Formed Flow (onFormed)

  1. Block placed → DynamicMultiblockManager.onPlace() synchronous detection
  2. All predicates match → updateFormed(level, state, true)
  3. ControllerRecord.get(block, definitionId) retrieves the controller
  4. Calls controller.onFormed(level, state)
  5. Broadcasts MultiblockFormPacket to all clients
  6. Clients receive and call local controller.onFormed(level, state)

Unformed Flow (onUnformed)

  1. Block broken or async detection fails → updateFormed(level, state, false)
  2. Calls controller.onUnformed(level, state)
  3. Broadcasts MultiblockUnformPacket to all clients
  4. Clients receive and call local controller.onUnformed(level, state)

Notes

  • onFormed/onUnformed are called on both server and client (synced to clients via network packets)
  • Controller callbacks verify that the controller block is still valid before execution (isController() check)
  • If the controller block is destroyed or replaced with a non-controller block, the callback will not execute

Released under the CC-BY-NC-SA 4.0 License.