Skip to content

← Back to Home

Block Entity Abstractions

Package: net.astronomy.multilib.api.blockentity

Optional base classes for structures whose blocks react to being part of a formed instance — a controller with state/menu/tick logic, and part blocks that need to reach their controller.

AbstractMultiblockControllerBE

Base class for the core's block entity (extends BlockEntity).

State

public MultiblockState getState();
public void setState(MultiblockState newState);
public boolean isFormed();               // state != UNFORMED
public UUID getInstanceId();
public ResourceLocation getActiveModelId(); // null unless .model(...) is set and formed
protected ServerLevel getServerLevel();
protected void markDirtyAndSync();

MultiblockState is registry-backed, not an enum — StandardMultiblockState provides UNFORMED/IDLE/RUNNING/ERROR; register your own via MultiLib.registerMultiblockState(...). setState(...) is a no-op if unchanged; otherwise it calls onStateChanged, records progression, posts MultiblockStateChangedEvent, and syncs (lifecycle).

getActiveModelId() is the block rendered in place of the hidden core — read every frame by MultiblockMasterModelRenderer.

Hooks you override

protected void onFormed(MultiblockFormedContext ctx) {}
protected void onBroken(MultiblockBrokenContext ctx) {}
protected void onStateChanged(MultiblockState prev, MultiblockState next) {}
protected void serverTick() {}   // every tick while isFormed()

onFormed/onBroken are called by the framework — you don't invoke them.

Framework-invoked (don't call these)

public final void onStructureFormed(MultiblockFormedContext ctx);
public final void onStructureBroken(MultiblockBrokenContext ctx);

onStructureFormed sets IDLE, applies model-hiding if .model(...) is set, then calls your onFormed. onStructureBroken reverses the hiding for remaining positions (skipping the broken one), resets to UNFORMED, then calls your onBroken.

Periodic (re-)validation

public void setValidationInterval(int ticks);
public void invalidateStructure();

Opt-in (> 0): while unformed, periodically attempts formation via triggerFormationAt(...) (discovers a structure built before the core, or after /reload); while formed, re-checks every position is non-air and treats an empty one as a break. invalidateStructure() forces the next check onto the next tick — call it when your code knows a relevant block changed. ExampleControllerBE uses 100 (5s).

Server ticker & NBT

public static <T extends AbstractMultiblockControllerBE> BlockEntityTicker<T> createServerTicker();
protected void saveController(CompoundTag tag, HolderLookup.Provider registries) {}
protected void loadController(CompoundTag tag, HolderLookup.Provider registries) {}

Return createServerTicker() from your block's getTicker(...) (ticks server-side only). Override saveController/loadController for your own data — state/instanceId/activeModelId are already persisted.

AbstractMultiblockControllerBlock

Base for the core's Block (extends AbstractMultiblockPartBlock).

protected abstract InteractionResult openMenu(Player player, Level level, BlockPos pos, BlockState state);

useWithoutItem(...) is implemented for you: it calls openMenu(...) only when isFormed(), so right-clicking an unformed core does nothing.

MultiblockPartMenus

public static boolean openPartMenu(ServerPlayer player, ServerLevel level, BlockPos partPos);
public static boolean openPartMenu(ServerPlayer player, ServerLevel level, MultiblockInstance instance, BlockPos partPos);

Lets a controller's menu redirect a player to a part's menu (e.g. a "configure this port" button). A thin wrapper over vanilla ServerPlayer#openMenu — MultiLib dictates no GUI framework. Returns false if no block entity at partPos or it isn't a MenuProvider. The instance overload also verifies partPos belongs to instance (guards against stale client-sent positions).

AbstractMultiblockPartBE

Base for non-core part block entities (extends BlockEntity, implements IMultiblockPart via MultiblockPartComponent).

protected void savePart(CompoundTag tag, HolderLookup.Provider registries) {}
protected void loadPart(CompoundTag tag, HolderLookup.Provider registries) {}

Membership persistence is automatic; override these only for your own data.

AbstractMultiblockPartBlock

Base Block enabling Master-Dummy model-hiding. Adds a MODEL_HIDDEN property; getRenderShape(...) returns INVISIBLE when set. Extend this for the core and any part block if the structure uses .model(...).

public static void setModelHidden(Level level, BlockPos pos, boolean hidden); // framework-internal

IMultiblockPart

Implemented by AbstractMultiblockPartBE. Query access to structure membership:

public interface IMultiblockPart {
    MultiblockPartComponent getMultiblockComponent();
    default Set<MultiblockAbility<?>> getAbilities() { return Set.of(); }
    default boolean isPartOfStructure();
    default UUID getInstanceId();
    default Optional<MultiblockInstance> getInstance(ServerLevel level);
    default Optional<AbstractMultiblockControllerBE> getController(ServerLevel level);
    default void onJoinedStructure(MultiblockInstance instance) {}
    default void onLeftStructure() {}
}

getController(level) is how a part (e.g. an IO port) reaches its controller for routing. getAbilities() declares the role(s) this part fulfills, looked up via MultiblockAbilities; empty by default. A structure can have any number of parts with the same ability.

MultiblockPartComponent

The composable backing for IMultiblockPart — embed it in a block entity that can't extend AbstractMultiblockPartBE. Handles instance-id tracking, model-hiding, and NBT (saveToTag/loadFromTag).

MultiblockMasterModelRenderer

Client-side counterpart to the model swap. A BlockEntityRenderer that reads the core's getActiveModelId() each frame and renders that block's model in place of the hidden core. Renders nothing if the id is null or unregistered.

public MultiblockMasterModelRenderer(BlockEntityRendererProvider.Context context);

Register it like any BE renderer — without it, a .model(...) structure hides its parts but shows nothing:

event.registerBlockEntityRenderer(MY_CONTROLLER_BE, MultiblockMasterModelRenderer::new);

Minimal example

public class MyControllerBE extends AbstractMultiblockControllerBE {
    public MyControllerBE(BlockEntityType<?> type, BlockPos pos, BlockState state) {
        super(type, pos, state);
        setValidationInterval(100);
    }
    @Override protected void onFormed(MultiblockFormedContext ctx) { }
    @Override protected void onBroken(MultiblockBrokenContext ctx) { }
    @Override protected void serverTick() { }   // every tick while formed
}

public class MyControllerBlock extends AbstractMultiblockControllerBlock implements EntityBlock {
    @Override protected InteractionResult openMenu(Player player, Level level, BlockPos pos, BlockState state) {
        return InteractionResult.SUCCESS;        // only reached while formed
    }
    @Override public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
        return new MyControllerBE(MyBlocks.CONTROLLER_BE_TYPE, pos, state);
    }
    @Override @SuppressWarnings("unchecked")
    public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> type) {
        return (BlockEntityTicker<T>) AbstractMultiblockControllerBE.createServerTicker();
    }
}

See also