Difference between revisions of "Block Entities"

From Forge Community Wiki
m (ChampionAsh5357 moved page Block Entities to Block Entities: Class rename)
(Update to 1.17)
Line 1: Line 1:
Tile entities are like simplified entities that are bound to a <code>Block</code>. 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.
+
Block entities are like simplified entities that are bound to a <code>Block</code>. 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.
  
{{Colored box|title=Tip|content=A <code>TileEntity</code> isn’t a solution for everything, and they can cause lag when used wrongly. When possible, try to avoid them.}}
+
{{Colored box|title=Tip|content=A <code>BlockEntity</code> isn’t a solution for everything, and they can cause lag when used wrongly. When possible, try to avoid them.}}
  
== Creating a <code>TileEntity</code> ==
+
== Creating a <code>BlockEntity</code> ==
  
In order to create a <code>TileEntity</code> you need to extend the <code>TileEntity</code> class. To register it, listen for the appropriate registry event and create a <code>TileEntityType</code>:  
+
In order to create a <code>BlockEntity</code> you need to extend the <code>BlockEntity</code> class. To register it, listen for the appropriate registry event and create a <code>BlockEntityType</code>:  
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
 
@SubscribeEvent
 
@SubscribeEvent
public static void registerTE(RegistryEvent.Register<TileEntityType<?>> evt) {
+
public static void registerTE(RegistryEvent.Register<BlockEntityType<?>> evt) {
   TileEntityType<?> type = TileEntityType.Builder.create(factory, validBlocks).build(null);
+
   BlockEntityType<?> type = BlockEntityType.Builder.of(supplier, validBlocks).build(null);
   type.setRegistryName("examplemod", "myte");
+
   type.setRegistryName("examplemod", "mybe");
 
   evt.getRegistry().register(type);
 
   evt.getRegistry().register(type);
 
}
 
}
Line 16: Line 16:
 
You can also use a <code>DeferredRegister</code> instead.
 
You can also use a <code>DeferredRegister</code> instead.
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
public static final DeferredRegister<TileEntityType<?>> TILE_ENTITIES = DeferredRegister.create(ForgeRegistries.TILE_ENTITIES, "examplemod");
+
public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITIES = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITIES, "examplemod");
  
public static final RegistryObject<TileEntityType<?>> MY_TE = TILE_ENTITIES.register("myte", () -> TileEntityType.Builder.create(factory, validBlocks).build(null));
+
public static final RegistryObject<BlockEntityType<?>> MY_BE = BLOCK_ENTITIES.register("mybe", () -> BlockEntityType.Builder.of(supplier, validBlocks).build(null));
 
</syntaxhighlight>
 
</syntaxhighlight>
  
In this example, <code>factory</code> is a function that creates a new instance of your <code>TileEntity</code>. A method reference or a lambda is commonly used. The variable <code>validBlocks</code> is one or more blocks (<code><nowiki>TileEntityType$Builder::create</nowiki></code> is varargs) that the tile entity can exist for.
+
In this example, <code>supplier</code> is a <code>BlockEntityType$BlockEntitySupplier</code> that creates a new instance of your <code>BlockEntity</code>. A method reference or a lambda is commonly used. The variable <code>validBlocks</code> is one or more blocks (<code><nowiki>BlockEntityType$Builder::of</nowiki></code> is varargs) that the block entity can exist for.
  
== Attaching a <code>TileEntity</code> to a <code>Block</code> ==
+
== Attaching a <code>BlockEntity</code> to a <code>Block</code> ==
To attach your new <code>TileEntity</code> to a <code>Block</code> you need to override 2 (two) methods within the <code>Block</code> class.
+
To attach your new <code>BlockEntity</code> to a <code>Block</code>, the <code>EntityBlock</code> interface must be implemented on your <code>Block</code> subclass. The method <code>EntityBlock#newBlockEntity(BlockPos, BlockState)</code> must be implemented and return a new isntance of your <code>BlockEntity</code>.
 +
 
 +
== Storing Data within your <code>BlockEntity</code> ==
 +
In order to save data, override the following two methods
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
IForgeBlock#hasTileEntity(BlockState state)
+
BlockEntity#save(CompoundTag tag)
  
IForgeBlock#createTileEntity(BlockState state, IBlockReader world)
+
BlockEntity#load(CompoundTag tag)
 
</syntaxhighlight>
 
</syntaxhighlight>
Using the parameters you can choose if the block should have a <code>TileEntity</code> or not. Usually you will return true in the first method and a new instance of your <code>TileEntity</code> in the second method.
+
These methods are called whenever the <code>LevelChunk</code> containing the <code>BlockEntity</code> gets loaded from/saved to a tag. Use them to read and write to the fields in your block entity class.
 
 
== Storing Data within your <code>TileEntity</code> ==
 
In order to save data, override the following two methods
 
<code>
 
TileEntity#write(CompoundNBT nbt)
 
  
TileEntity#read(BlockState state, CompoundNBT nbt)
+
{{Colored box|title=Tip|content=Whenever your data changes you need to call <code><nowiki>BlockEntity#setChanged()</nowiki></code>, otherwise the <code>LevelChunk</code> containing your <code>BlockEntity</code> might be skipped while the level is saved.}}
</code>
 
These methods are called whenever the <code>Chunk</code> containing the <code>TileEntity</code> gets loaded from/saved to NBT. Use them to read and write to the fields in your tile entity class.
 
 
 
{{Colored box|title=Tip|content=Whenever your data changes you need to call <code><nowiki>TileEntity#markDirty()</nowiki></code>, otherwise the <code>Chunk</code> containing your <code>TileEntity</code> might be skipped while the world is saved.}}
 
  
  
Line 48: Line 42:
 
The tag names <code>id</code>, <code>x</code>, <code>y</code>, <code>z</code>, <code>ForgeData</code> and <code>ForgeCaps</code> are reserved by the super methods.}}
 
The tag names <code>id</code>, <code>x</code>, <code>y</code>, <code>z</code>, <code>ForgeData</code> and <code>ForgeCaps</code> are reserved by the super methods.}}
  
== Ticking <code>TileEntity</code> ==
+
== Ticking <code>BlockEntity</code> ==
If you need a ticking <code>TileEntity</code>, for example to keep track of the progress during a smelting process, you need to add the <code><nowiki>ITickableTileEntity</nowiki></code> interface to your <code>TileEntity</code>. Now you can implement all your calculations within
+
If you need a ticking <code>BlockEntity</code>, for example to keep track of the progress during a smelting process, another method must be implemented and overridden within <code>EntityBlock</code>: <code>EntityBlock#getTicker(Level, BlockState, BlockEntityType)</code>. This can implement different tickers depending on which logical side the user is on, or just implement one general ticker. In either case, a <code>BlockEntityTicker</code> must be returned. Since this is a functional interface, it can just take in a method representing the ticker instead:
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
ITickableTileEntity#tick()
+
// 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
 +
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Line 58: Line 62:
 
== Synchronizing the Data to the Client ==
 
== 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.
 
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 chunk load  ===
+
=== Synchronizing on LevelChunk load  ===
 
For this you need to override  
 
For this you need to override  
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
TileEntity#getUpdateTag()
+
BlockEntity#getUpdateTag()
  
IForgeTileEntity#handleUpdateTag(BlockState state, CompoundNBT tag)
+
IForgeBlockEntity#handleUpdateTag(CompoundTag tag)
 
</syntaxhighlight>
 
</syntaxhighlight>
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 <code>TileEntity</code> doesn’t contain much data you might be able to use the methods out of the [[#storing_data|Storing Data]] section.
+
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 <code>BlockEntity</code> doesn’t contain much data you might be able to use the methods out of the [[#storing_data|Storing Data]] section.
  
{{Tip/Important|Synchronizing excessive/useless data for tile entities can lead to network congestion. You should optimize your network usage by sending only the information the client needs when the client needs it. For instance, it is more often than not unnecessary to send the inventory of a tile entity in the update tag, as this can be synchronized via its GUI.)}}
+
{{Tip/Important|Synchronizing excessive/useless data for block entities can lead to network congestion. You should optimize your network usage by sending only the information the client needs when the client needs it. For instance, it is more often than not unnecessary to send the inventory of a block entity in the update tag, as this can be synchronized via its <code>AbstractContainerMenu</code>.)}}
  
 
=== 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 methods. Here is a tiny example implementation of it:
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
 
@Override
 
@Override
public SUpdateTileEntityPacket getUpdatePacket(){
+
public ClientboundBlockEntityDataPacket getUpdatePacket(){
     CompoundNBT nbtTag = new CompoundNBT();
+
     CompoundTag tag = new CompoundTag();
     //Write your data into the nbtTag
+
     //Write your data into the tag
     return new SUpdateTileEntityPacket(getPos(), -1, nbtTag);
+
     return new ClientboundBlockEntityDataPacket(getBlockPos(), -1, tag);
 
}
 
}
  
 
@Override
 
@Override
public void onDataPacket(NetworkManager net, SUpdateTileEntityPacket pkt){
+
public void onDataPacket(Connection connection, ClientboundBlockEntityDataPacket pkt){
     CompoundNBT tag = pkt.getNbtCompound();
+
     CompoundTag tag = pkt.getTag();
 
     //Handle your Data
 
     //Handle your Data
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
The Constructor of <code>SUpdateTileEntityPacket</code> takes:
+
The Constructor of <code>ClientboundBlockEntityDataPacket</code> takes:
* The position of your <code>TileEntity</code>.
+
* The position of your <code>BlockEntity</code>.
 
* An ID, though it isn’t really used besides by Vanilla, therefore you can just put a -1 in there.
 
* An ID, though it isn’t really used besides by Vanilla, therefore you can just put a -1 in there.
* An <code>CompoundNBT</code> which should contain your data.
+
* A <code>CompoundTag</code> which should contain your data.
  
 
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.  
 
<code>
 
<code>
World#notifyBlockUpdate(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>TileEntity</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>Constants$BlockFlags</code> for more info as well as the rest of the flags. The flag <code>2</code> is equivalent to <code><nowiki>Constants$BlockFlags#BLOCK_UPDATE</nowiki></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>Constants$BlockFlags</code> for more info as well as the rest of the flags. The flag <code>2</code> is equivalent to <code><nowiki>Constants$BlockFlags#BLOCK_UPDATE</nowiki></code>.
  
 
=== Synchronizing using a custom network message ===
 
=== 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 <code>[[latest:advanced:networking:start|Networking]]</code> section and especially <code>[[latest:advanced:networking:simplechannel|SimpleImpl]]</code> before attempting this. Once you’ve created your custom network message, you can send it to all users that have the <code>TileEntity</code> loaded with:  
+
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 <code>[[Understanding Networking|Networking]]</code> section and especially <code>[[Using SimpleChannel|SimpleImpl]]</code> before attempting this. Once you’ve created your custom network message, you can send it to all users that have the <code>BlockEntity</code> loaded with:  
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
 
SimpleChannel#send(PacketDistributor.TRACKING_CHUNK.with(() -> chunk), MSG);
 
SimpleChannel#send(PacketDistributor.TRACKING_CHUNK.with(() -> chunk), MSG);
 
</syntaxhighlight>
 
</syntaxhighlight>
{{Colored box|title=Alert|content=It is important that you do safety checks, the TileEntity might already be destroyed/replaced when the message arrives at the player! You should also check if the area is loaded using (<code><nowiki>World#isAreaLoaded(BlockPos, int)</nowiki></code>)}}
+
{{Colored box|title=Alert|content=It is important that you do safety checks, the <code>BlockEntity</code> might already be destroyed/replaced when the message arrives at the player! You should also check if the area is loaded using (<code><nowiki>Level#hasChunkAt(BlockPos)</nowiki></code>)}}

Revision as of 23:39, 31 July 2021

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

A 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 isntance of your BlockEntity.

Storing Data within your BlockEntity

In order to save data, override the following two methods

BlockEntity#save(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

Whenever your data changes you need to call 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 names id, 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

This method is called each tick. Therefore, you should avoid having complicated calculations in here. If possible, you should make more complex calculations just every X ticks. (The amount of ticks in a second may be lower than 20 (twenty) but won’t be higher)

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

Synchronizing excessive/useless data for block entities can lead to network congestion. You should optimize your network usage by sending only the information the client needs when the client needs it. For instance, it is more often than not unnecessary to send the inventory of a block entity in the update tag, as this can be synchronized via its AbstractContainerMenu.)

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:

@Override
public ClientboundBlockEntityDataPacket getUpdatePacket(){
    CompoundTag tag = new CompoundTag();
    //Write your data into the tag
    return new ClientboundBlockEntityDataPacket(getBlockPos(), -1, tag);
}

@Override
public void onDataPacket(Connection connection, ClientboundBlockEntityDataPacket pkt){
    CompoundTag tag = pkt.getTag();
    //Handle your Data
}

The Constructor of ClientboundBlockEntityDataPacket takes:

  • The position of your BlockEntity.
  • An ID, though it isn’t really used besides by Vanilla, therefore you can just put a -1 in there.
  • A CompoundTag which should contain your data.

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 Constants$BlockFlags for more info as well as the rest of the flags. The flag 2 is equivalent to Constants$BlockFlags#BLOCK_UPDATE.

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

It is important that you do safety checks, the 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))