Table of Contents

Syncing Custom Data with Extended ScreenHandlers

In this tutorial we will use the ExtendedScreenHandler to transfer arbitary data from the server to the client ScreenHandler when the ScreenHandler is opened.

In our example we will send the position of the block and render it as the container's title.

To understand this tutorial you need to read the first Screenhandler tutorial. Methods which have no code here were already shown in that tutorial.

BlockEntity

As the Block class does not need to be changed at all we leave it out here.

Our block entity now implements ExtendedScreenHandlerFactory, this interfaces provides us the writeScreenOpeningData method, which will be called on the server when it requests the client to open a ScreenHandler. The data you write into the PacketByteBuf will be transferred to the client over the network.

BoxBlockEntity.java
  1. public class BoxBlockEntity extends BlockEntity implements ExtendedScreenHandlerFactory, ImplementedInventory {
  2. private final DefaultedList<ItemStack> inventory = DefaultedList.ofSize(9, ItemStack.EMPTY);
  3.  
  4. public BoxBlockEntity() {
  5. super(Test.BOX_BLOCK_ENTITY);
  6. }
  7.  
  8.  
  9. //From the ImplementedInventory Interface
  10.  
  11. @Override
  12. public DefaultedList<ItemStack> getItems() {
  13. return inventory;
  14.  
  15. }
  16.  
  17. //These Methods are from the NamedScreenHandlerFactory Interface
  18.  
  19. @Override
  20. public @Nullable ScreenHandler createMenu(int syncId, PlayerInventory playerInventory, PlayerEntity player) {
  21. //We provide this to the screenHandler as our class Implements Inventory
  22. //Only the Server has the Inventory at the start, this will be synced to the client in the ScreenHandler
  23. return new BoxScreenHandler(syncId, playerInventory, this);
  24. }
  25.  
  26. @Override
  27. public Text getDisplayName() {
  28. // versions 1.18 and below
  29. return new TranslatableText(getCachedState().getBlock().getTranslationKey());
  30.  
  31. // versions 1.19 and later
  32. return Text.translatable(getCachedState().getBlock().getTranslationKey());
  33. }
  34.  
  35. //This Method is from the ExtendedScreenHandlerFactory
  36.  
  37. //This method gets called on the Server when it requests the client to open the screenHandler.
  38. //The contents you write into the packetByteBuf will automatically be transferred in a packet to the client
  39. //and the ScreenHandler Constructor with the packetByteBuf argument gets called on the client
  40. //
  41. //The order you insert things here is the same as you need to extract them. You do not need to reverse the order!
  42. @Override
  43. public void writeScreenOpeningData(ServerPlayerEntity serverPlayerEntity, PacketByteBuf packetByteBuf) {
  44. //The pos field is a public field from BlockEntity
  45. packetByteBuf.writeBlockPos(pos);
  46. }
  47. }

Our new ExtendedScreenHandler

BoxScreenHandler.java
  1. public class BoxScreenHandler extends ScreenHandler {
  2. //We save the blockPos we got from the Server and provide a getter for it so the BoxScreen can read that information
  3. private BlockPos pos;
  4. private final Inventory inventory;
  5.  
  6. //This constructor gets called on the client when the server wants it to open the screenHandler,
  7. //The client will call the super constructor with an empty Inventory and the screenHandler will automatically
  8. //sync this empty inventory with the inventory on the server
  9.  
  10. //NEW: The constructor of the client now gets the PacketByteBuf we filled in the BlockEntity
  11. public BoxScreenHandler(int syncId, PlayerInventory playerInventory, PacketByteBuf buf) {
  12. this(syncId, playerInventory, new SimpleInventory(9));
  13. pos = buf.readBlockPos();
  14. }
  15.  
  16. //This constructor gets called from the BlockEntity on the server, the server knows the inventory of the container
  17. //and can therefore directly provide it as an argument. This inventory will then be synced to the Client
  18. public BoxScreenHandler(int syncId, PlayerInventory playerInventory, Inventory inventory) {
  19. //[...]
  20. // See first Screenhandler Tutorial for the rest of the code
  21.  
  22. //Why do we use BlockPos.ORIGIN here?
  23. //This is because the packetByteBuf with our blockPosition is only availible on the Client, so we need a placeholder
  24. //value here. This is not a problem however, as the Server version of the ScreenHandler does not really need this
  25. //information.
  26. pos = BlockPos.ORIGIN;
  27.  
  28. [...]
  29. }
  30.  
  31. //this getter will be used by our Screen class
  32. public BlockPos getPos() {
  33. return pos;
  34. }
  35.  
  36. @Override
  37. public boolean canUse(PlayerEntity player) {
  38. return this.inventory.canPlayerUse(player);
  39. }
  40.  
  41. // See Screenhandler Tutorial
  42. // Shift + Player Inv Slot
  43. @Override
  44. public ItemStack transferSlot(PlayerEntity player, int invSlot);
  45. }

Using the Information of the ExtendedScreenHandler in our Screen

BoxScreen.java
  1. public class BoxScreen extends HandledScreen<ScreenHandler> {
  2. private static final Identifier TEXTURE = new Identifier("minecraft", "textures/gui/container/dispenser.png");
  3.  
  4. public BoxScreen(ScreenHandler handler, PlayerInventory inventory, Text title) {
  5. super(handler, inventory, getPositionText(handler).orElse(title));
  6. //We try to get the block position to use it as our title, if that fails for some reason we will use the default title
  7. }
  8.  
  9. //This method will try to get the Position from the ScreenHandler, as ScreenRendering only happens on the client we
  10. //get the ScreenHandler instance here which has the correct BlockPos in it!
  11. private static Optional<Text> getPositionText(ScreenHandler handler) {
  12. if (handler instanceof BoxScreenHandler) {
  13. BlockPos pos = ((BoxScreenHandler) handler).getPos();
  14. // for versions 1.18.2 and below, use `new LiteralText`
  15. return pos != null ? Optional.of(Text.literal("(" + pos.toShortString() + ")")) : Optional.empty();
  16. } else {
  17. return Optional.empty();
  18. }
  19. }
  20.  
  21.  
  22. @Override
  23. protected void drawBackground(MatrixStack matrices, float delta, int mouseX, int mouseY) { [...] }
  24.  
  25. @Override
  26. public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { [...] }
  27.  
  28. @Override
  29. protected void init() { [...] }
  30. }

Registering our ScreenHandler

ExampleMod.java
  1. public class ExampleMod implements ModInitializer {
  2.  
  3. [...]
  4. public static final ScreenHandlerType<BoxScreenHandler> BOX_SCREEN_HANDLER = new ExtendedScreenHandlerType<>(BoxScreenHandler::new);
  5.  
  6. static {
  7. [...]
  8.  
  9. //we now use registerExtended as our screenHandler now accepts a packetByteBuf in its Constructor
  10. BOX_SCREEN_HANDLER = Registry.register(Registries.SCREEN_HANDLER, new Identifier("mymod", "box"), BOX);
  11. }
  12.  
  13. @Override
  14. public void onInitialize() {
  15.  
  16. }
  17. }

Result

You have now seen how to transfer data when the ScreenHandler is opened. In the image you can see the result: The Block's title is now the block position. Do note that this is just a demonstration, there are easier ways of setting the position as the title.

You might be wondering: Can I transfer this data again even after the Screen was opened? This is possible by sending custom Packets (see: Networking Tutorial) after the Screen has been opened.
You might also want to have a look at the BlockEntityClientSerializable interface from the Fabric API.

If you only want to sync integer values you can use PropertyDelegates: propertydelegates.