Tile Entities/1.16
Tile 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
TileEntity
isn’t a solution for everything, and they can cause lag when used wrongly. When possible, try to avoid them.Creating a TileEntity
In order to create a TileEntity
you need to extend the TileEntity
class. To register it, listen for the appropriate registry event and create a TileEntityType
:
@SubscribeEvent public static void registerTE(RegistryEvent.Register<TileEntityType<?>> evt) { TileEntityType<?> type = TileEntityType.Builder.create(factory, validBlocks).build(null); type.setRegistryName("examplemod", "myte"); evt.getRegistry().register(type); }
You can also use a DeferredRegister
instead.
public static final DeferredRegister<TileEntityType<?>> TILE_ENTITIES = DeferredRegister.create(ForgeRegistries.TILE_ENTITIES, "examplemod"); public static final RegistryObject<TileEntityType<?>> MY_TE = TILE_ENTITIES.register("myte", () -> TileEntityType.Builder.create(factory, validBlocks).build(null));
In this example, factory
is a function that creates a new instance of your TileEntity
. A method reference or a lambda is commonly used. The variable validBlocks
is one or more blocks (TileEntityType$Builder::create
is varargs) that the tile entity can exist for.
Attaching a TileEntity
to a Block
To attach your new TileEntity
to a Block
you need to override 2 (two) methods within the Block
class.
IForgeBlock#hasTileEntity(BlockState state) IForgeBlock#createTileEntity(BlockState state, IBlockReader world)
Using the parameters you can choose if the block should have a TileEntity
or not. Usually you will return true in the first method and a new instance of your TileEntity
in the second method.
Storing Data within your TileEntity
In order to save data, override the following two methods
TileEntity#write(CompoundNBT nbt)
TileEntity#read(BlockState state, CompoundNBT nbt)
These methods are called whenever the Chunk
containing the TileEntity
gets loaded from/saved to NBT. Use them to read and write to the fields in your tile entity class.
Tip
TileEntity#markDirty()
, otherwise the Chunk
containing your TileEntity
might be skipped while the world 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 TileEntity
If you need a ticking TileEntity
, for example to keep track of the progress during a smelting process, you need to add the ITickableTileEntity
interface to your TileEntity
. Now you can implement all your calculations within
ITickableTileEntity#tick()
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 chunk load
For this you need to override
TileEntity#getUpdateTag() IForgeTileEntity#handleUpdateTag(BlockState state, CompoundNBT 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 TileEntity
doesn’t contain much data you might be able to use the methods out of the Storing Data/1.16 section.
Important
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 SUpdateTileEntityPacket getUpdatePacket(){ CompoundNBT nbtTag = new CompoundNBT(); //Write your data into the nbtTag return new SUpdateTileEntityPacket(getPos(), -1, nbtTag); } @Override public void onDataPacket(NetworkManager net, SUpdateTileEntityPacket pkt){ CompoundNBT tag = pkt.getNbtCompound(); //Handle your Data }
The Constructor of SUpdateTileEntityPacket
takes:
- The position of your
TileEntity
. - An ID, though it isn’t really used besides by Vanilla, therefore you can just put a -1 in there.
- An
CompoundNBT
which should contain your data.
Now, to send the packet, an update notification must be given on the server.
World#notifyBlockUpdate(BlockPos pos, BlockState oldState, BlockState newState, int flags)
The pos
should be your TileEntity
’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/1.16
section and especially SimpleImpl/1.16
before attempting this. Once you’ve created your custom network message, you can send it to all users that have the TileEntity
loaded with:
SimpleChannel#send(PacketDistributor.TRACKING_CHUNK.with(() -> chunk), MSG);
Alert
World#isAreaLoaded(BlockPos, int)
)