Minecraft Modding: TileEntity Quick Tips

Important: For versions 1.7.10 and older, TileEntity's worked somewhat differently. If you're working on such old version you should see my tips for older version tile entities.

Turning Off Updates For TileEntity When Possible


The reason why Minecraft uses single block instances per type and limited metadata per block location is to ensure perf is acceptable in a world with a million (literally) block locations.  However, sometimes you want a "smarter" block that contains more information than the 4 bits that metadata allows.  So there is ability to associate a TileEntity with certain blocks.  However, if you place too many of these, it will again hurt performance.

In some cases you may have a TileEntity that stores information or controls rendering but does not need to execute code.  In such cases, for versions 1.8+ just do not implement the ITickable interface.

Key Point: Tile Entities don't have to tick, they can just be used to store extra information. If you want them to tick implement the ITickable interface, and if you don't want them to tick don't implement that interface.

Warning: Even though the World.loadedTileEntityList is public, modifying it can cause co-modification error crashes.

Retaining Tile Entity When Block State Changes


Each time the block state changes, the default behavior is that the TileEntity is replaced. This is often not what you want, so to change this you need to override the shouldRefresh() method. However, you shouldn't just return false because you need to handle the case where the block is replaced with different block.

Important: You should probably always override the shouldRefresh() method unless you're sure that the default behavior is what you want.

So proper override is:

public boolean shouldRefresh(World world, BlockPos pos, IBlockState oldState, IBlockState newSate)
{
    return (oldState.getBlock() != newSate.getBlock());
}

Syncing Tile Entity Data Across All Clients


For efficiency reasons, tile entities normally only update other clients when the GUI is active. However it is possible that you want to communicate something else more continuously.  You can update the tile entities at any time by using the built-in SPacketUpdateTileEntity packet.

Note: The SPacketUpdateTileEntity takes an NBT for the payload, so assumes that the data you want to sync is in NBT. If not, you may want to use a custom packet.

To get the SPacketUpdateTileEntity working you need to do the following in your custom tile entity class:

  1. @Override the getUpdateTag() method to return the NBT information you would normally save (and presumably want to sync). This is also used during chunk loading so is important for ensuring your client gets synced when loading a game. 
  2. @Override the getUpdatePacket() method to return a new SPacketUpdateTileEntity constructed with a payload NBT from the getUpdateTag() method you created in Step 1.
  3. @Override the onDataPacket() method to take the NBT payload from the packet and update any local fields in your tile entity.


Warning: You do not want to sync every tick as that will create performance problems.  Instead you will want to only send a packet when there is a change worth sharing with the other clients.  In whatever code detects such a change you can then send a packet as needed using the following code:

worldObj.markBlockForUpdate(xCoord, yCoord, zCoord); // Makes the server call getDescriptionPacket for a full data sync
markDirty(); // Marks the chunk as dirty, so that it is saved properly on changes.

That's it!

3 comments:

  1. Oh wow thank you so much for this! I was trying so hard to figure out how to animate my model blocks in my mod, and this helped me figure it out!

    ReplyDelete
  2. I have a block with complex model that has 32 variations. This block supposed to be in big amounts in world so using TESR will probably cause a lot of lags. My solution was saving data in tile entity and loading it into block state when chunk is loaded from disk. I was trying to implement it all night but it didn't work properly. What is the right way to do that? And should I do that at all?

    ReplyDelete
  3. You can use Block#getActualState(...) to affect block state json using tile entity data. It works on versions with block properties. Just add some value of tile entity to block state in the Block#getActualState(...). Don't forget to declare property responsible for this value but don't save it in metadata. This way you can have more than 16 variants in block state json.

    ReplyDelete