Views
Actions
Difference between revisions of "Block Entities"
(Update to 1.18) |
|||
Line 29: | Line 29: | ||
In order to save data, override the following two methods | In order to save data, override the following two methods | ||
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
− | BlockEntity# | + | BlockEntity#saveAdditional(CompoundTag tag) |
BlockEntity#load(CompoundTag tag) | BlockEntity#load(CompoundTag tag) | ||
Line 74: | Line 74: | ||
=== Synchronizing on block update === | === Synchronizing on block update === | ||
− | This method is a bit more complicated, but again you just need to override 2 methods. Here is a tiny example implementation of it: | + | This method is a bit more complicated, but again you just need to override 2 or 3 methods. Here is a tiny example implementation of it: |
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
@Override | @Override | ||
− | public | + | public CompoundTag getUpdateTag() { |
− | + | CompoundTag tag = new CompoundTag(); | |
− | + | //Write your data into the tag | |
− | + | return tag; | |
} | } | ||
@Override | @Override | ||
− | public | + | public Packet<ClientGamePacketListener> getUpdatePacket() { |
− | + | // Will get tag from #getUpdateTag | |
− | + | return ClientboundBlockEntityDataPacket.create(this); | |
} | } | ||
+ | |||
+ | // Can override IForgeBlockEntity#onDataPacket. By default, this will defer to the #load. | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | The | + | The static constructors <code>ClientboundBlockEntityDataPacket#create</code> takes: |
− | * The | + | * The <code>BlockEntity</code>. |
− | * An | + | * An optional function to get the <code>CompoundTag</code> from the <code>BlockEntity</code>. By default, this uses <code>BlockEntity#getUpdateTag</code>. |
− | |||
Now, to send the packet, an update notification must be given on the server. | Now, to send the packet, an update notification must be given on the server. | ||
Line 98: | Line 99: | ||
Level#sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags) | Level#sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags) | ||
</code> | </code> | ||
− | The <code>pos</code> should be your <code>BlockEntity</code>’s position. For <code>oldState</code> and <code>newState</code> you can pass the current <code>BlockState</code> at that position. The <code>flags</code> are a bitmask and should contain <code>2</code>, which will sync the changes to the client. See <code> | + | The <code>pos</code> should be your <code>BlockEntity</code>’s position. For <code>oldState</code> and <code>newState</code> you can pass the current <code>BlockState</code> at that position. The <code>flags</code> are a bitmask and should contain <code>2</code>, which will sync the changes to the client. See <code>Block</code> for more info as well as the rest of the flags. The flag <code>2</code> is equivalent to <code><nowiki>Block#UPDATE_CLIENTS</nowiki></code>. |
=== Synchronizing using a custom network message === | === Synchronizing using a custom network message === |
Revision as of 13:44, 12 January 2022
Block entities are like simplified entities that are bound to a Block
. They are used to store dynamic data, execute tick based tasks and for dynamic rendering. Some examples from vanilla Minecraft would be: handling of inventories (chests), smelting logic on furnaces, or area effects for beacons. More advanced examples exist in mods, such as quarries, sorting machines, pipes, and displays.
Tip
BlockEntity
isn’t a solution for everything, and they can cause lag when used wrongly. When possible, try to avoid them.Creating a BlockEntity
In order to create a BlockEntity
you need to extend the BlockEntity
class. To register it, listen for the appropriate registry event and create a BlockEntityType
:
@SubscribeEvent public static void registerTE(RegistryEvent.Register<BlockEntityType<?>> evt) { BlockEntityType<?> type = BlockEntityType.Builder.of(supplier, validBlocks).build(null); type.setRegistryName("examplemod", "mybe"); evt.getRegistry().register(type); }
You can also use a DeferredRegister
instead.
public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITIES = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITIES, "examplemod"); public static final RegistryObject<BlockEntityType<?>> MY_BE = BLOCK_ENTITIES.register("mybe", () -> BlockEntityType.Builder.of(supplier, validBlocks).build(null));
In this example, supplier
is a BlockEntityType$BlockEntitySupplier
that creates a new instance of your BlockEntity
. A method reference or a lambda is commonly used. The variable validBlocks
is one or more blocks (BlockEntityType$Builder::of
is varargs) that the block entity can exist for.
Attaching a BlockEntity
to a Block
To attach your new BlockEntity
to a Block
, the EntityBlock
interface must be implemented on your Block
subclass. The method EntityBlock#newBlockEntity(BlockPos, BlockState)
must be implemented and return a new instance of your BlockEntity
.
Storing Data within your BlockEntity
In order to save data, override the following two methods
BlockEntity#saveAdditional(CompoundTag tag) BlockEntity#load(CompoundTag tag)
These methods are called whenever the LevelChunk
containing the BlockEntity
gets loaded from/saved to a tag. Use them to read and write to the fields in your block entity class.
Tip
BlockEntity#setChanged()
, otherwise the LevelChunk
containing your BlockEntity
might be skipped while the level is saved.
Important
It is important that you call the super methods!
The tag namesid
, x
, y
, z
, ForgeData
and ForgeCaps
are reserved by the super methods.Ticking BlockEntity
If you need a ticking BlockEntity
, for example to keep track of the progress during a smelting process, another method must be implemented and overridden within EntityBlock
: EntityBlock#getTicker(Level, BlockState, BlockEntityType)
. This can implement different tickers depending on which logical side the user is on, or just implement one general ticker. In either case, a BlockEntityTicker
must be returned. Since this is a functional interface, it can just take in a method representing the ticker instead:
// Inside some Block subclass @Nullable @Override public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> type) { return type == MyBlockEntityTypes.MY_BE.get() ? MyBlockEntity::tick : null; } // Inside MyBlockEntity public static void tick(Level level, BlockPos pos, BlockState state, T blockEntity) { // Do stuff }
Tip
Synchronizing the Data to the Client
There are 3 (three) ways of syncing data to the client. Synchronizing on chunk load, synchronizing on block updates and synchronizing with a custom network message.
Synchronizing on LevelChunk load
For this you need to override
BlockEntity#getUpdateTag() IForgeBlockEntity#handleUpdateTag(CompoundTag tag)
Again, this is pretty simple, the first method collects the data that should be send to the client, while the second one processes that data. If your BlockEntity
doesn’t contain much data you might be able to use the methods out of the Storing Data section.
Important
AbstractContainerMenu
.)Synchronizing on block update
This method is a bit more complicated, but again you just need to override 2 or 3 methods. Here is a tiny example implementation of it:
@Override public CompoundTag getUpdateTag() { CompoundTag tag = new CompoundTag(); //Write your data into the tag return tag; } @Override public Packet<ClientGamePacketListener> getUpdatePacket() { // Will get tag from #getUpdateTag return ClientboundBlockEntityDataPacket.create(this); } // Can override IForgeBlockEntity#onDataPacket. By default, this will defer to the #load.
The static constructors ClientboundBlockEntityDataPacket#create
takes:
- The
BlockEntity
. - An optional function to get the
CompoundTag
from theBlockEntity
. By default, this usesBlockEntity#getUpdateTag
.
Now, to send the packet, an update notification must be given on the server.
Level#sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags)
The pos
should be your BlockEntity
’s position. For oldState
and newState
you can pass the current BlockState
at that position. The flags
are a bitmask and should contain 2
, which will sync the changes to the client. See Block
for more info as well as the rest of the flags. The flag 2
is equivalent to Block#UPDATE_CLIENTS
.
Synchronizing using a custom network message
This way of synchronizing is probably the most complicated one, but is usually also the most optimized one, as you can make sure that only the data you need to be synchronized is actually synchronized. You should first check out the Networking
section and especially SimpleImpl
before attempting this. Once you’ve created your custom network message, you can send it to all users that have the BlockEntity
loaded with:
SimpleChannel#send(PacketDistributor.TRACKING_CHUNK.with(() -> chunk), MSG);
Alert
BlockEntity
might already be destroyed/replaced when the message arrives at the player! You should also check if the area is loaded using (Level#hasChunkAt(BlockPos)
)