LibMultiPart's API.

This package contains everything needed to interact with libmultipart - you should never need to use anything in the package.

There are a few main concepts:

  1. Containers
  2. Individual Parts
  3. Part Shapes
  4. Accessing Parts
  5. Adding Parts
  6. Part Requirements
  7. Removing Parts
  8. Client <-> Server Networking
  9. Events
  10. Properties
  11. Rendering
  12. Converting normal blocks into parts


Every multipart is contained a BlockEntity, and is exposed through the MultipartContainer interface. This is exposed through a LibBlockAttributes Attribute in MultipartContainer.ATTRIBUTE

Individual Parts

Every part must extend from the base class AbstractPart, and be identified by a PartDefinition. These definitions must be (manually) added to the part definition map PartDefinition.PARTS.

The MultipartHolder interface acts as the bridge between the container implementation and the part, and contains a lot more information to simplify reading and writing individual parts.

Every part has a container-only unique ID (MultipartHolder.getUniqueId()) which can be used to store a reference to an exact part at a given position.

Part Shapes

Every part has 4 shape methods that they can override: getShape(), getCollisionShape(), getCullingShape(), and getDynamicShape(). The first method (getShape) is abstract, and is used for detecting if two parts occupy the same space inside a block. The second method (getCollisionShape) is used for nearly everything else - calculating solidity logic and collisions with entities. If this isn't overridden then it defaults to returning the main getShape(). The third method (getCullingShape) is used for calculating lighting and opaque sides. The final method (getDynamicShape()) is used for ray-tracing and client-side bounding boxes. It has a partialTicks parameter, which allows the bounding box to follow the motion of a part. (For example a buildcraft engine and the buildcraft pipe pulser move smoothly, and the boxes follow that motion).

Accessing Parts

Individual parts can be accessed via the various container methods all named similar to "getPart" or "getAllParts". For example MultipartContainer.getPart(long) will get the part with the given unique ID.

Adding Parts

New parts can be added in two ways:

  1. From nothing: MultipartUtil.offerNewPart(World, BlockPos, MultipartContainer.MultipartCreator)
  2. To an existing MultipartContainer: MultipartContainer.offerNewpart(MultipartContainer.MultipartCreator)
Both of these methods have the same basic behaviour: they will not affect the world/container unless PartOffer.apply() is called on the returned offer.

Part Requirements

If one part "requires" another then it will always be broken if the required part is broken. This works across multiple blocks. To make one part require another you should call MultipartHolder.addRequiredPart.

Removing Parts

Removing a single part is simple: just call MultipartHolder.remove() or MultipartContainer.removePart(AbstractPart). This will remove both the given part and every part that required it. This will not drop any of the items.

Client <-> Server Networking

Multipart's use LibNetworkStack for all networking operations, exposed through AbstractPart.NET_ID.


Unlike blocks or entities most events (like neighbour updates, ticks, entity collision, etc) are delivered through MultipartEvent objects through the MultipartEventBus. You can register listeners for these events in AbstractPart.onAdded(MultipartEventBus).

There are a few core events:

  • PartOfferedEvent, which is fired whenever a part is offered to the container.
  • PartAddedEvent, which is fired after the part is offered and has been added to the container.
  • PartContainerState.ChunkUnload, which is fired whenever the chunk containing the container is unloaded.
  • PartContainerState.Invalidate, which is fired whenever the BlockEntity containing the parts is invalidated.
  • PartContainerState.Validate, which is fired whenever the BlockEntity containing the parts is validated.
  • PartContainerState.Remove, which is fired whenever the BlockEntity containing the parts is removed.
  • PartTickEvent, which is fired once when the BlockEntity containing it is ticked.
  • PartListenerAdded, which is fired whenever a new listener is added to the event bus. This is only exposed to allow parts to optimise-out event calls on a per-part basis, and is not useful in normal scenarios.
  • PartListenerRemoved, which is fired whenever a listener is removed from the event bus. This is only exposed to allow parts to optimise-out event calls on a per-part basis, and is not useful in normal scenarios.


Properties are the way to change how the multipart block as a whole works - for example you can make the block emit light, redstone, etc. Usage is simple: each multipart has a MultipartPropertyContainer, which can be used to map a specific part to a value for any given property, via #setValue(Object,, Object) setValue. The default properties are static final fields in MultipartProperties. Changing the value of a property as a whole will fire a PartPropertyChangedEvent.


There are two types of renderers: static and dynamic. Both renderers are by adding a listener to the relevant event class. Static renderers are akin to normal block models, except you have to:

  1. Return a custom PartModelKey from #getModelKey() AbstractPart#getModelKey()
  2. Add a listener to PartStaticModelRegisterEvent.EVENT, which registers a handler for that class (or any of it's superclasses) with StaticModelRenderer#register(Class<? super P>, PartModelBaker<P>)
  3. Actually emit the quads in PartModelBaker#emitQuads()

Dynamic rendering is slightly simpler, as you do not have to go through a model key object in order to render your quads.

  1. Add a listener to PartDynamicModelRegisterEvent.EVENT, which registers a handler for that class (or any of it's superclasses) with DynamicModelRenderer#register(Class<? super P>, PartRenderer<P>)
  2. Emit the quads in PartRenderer#render().

Converting normal blocks into parts

If you wish a custom Block to be turned into a multipart block (when another multipart is placed) you need to implement NativeMultipart, and then return a non-null list.

You can initiate this change at any time by calling MultipartUtil#turnIntoMultipart().