User Tools

Site Tools


tutorial:screenhandler

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
tutorial:screenhandler [2021/09/01 00:28] – updated BlockEntityType.Builder to FabricBlockEntityTypeBuilder for 1.17 lvanderzandetutorial:screenhandler [2024/08/27 04:49] (current) – some minor changes solidblock
Line 6: Line 6:
 **Screenhandler:** **Screenhandler:**
 A ''ScreenHandler'' is a class responsible for synchronizing inventory contents between the client and the server. It can sync additional integer values like furnace progress as well, which will be showed in the next tutorial. A ''ScreenHandler'' is a class responsible for synchronizing inventory contents between the client and the server. It can sync additional integer values like furnace progress as well, which will be showed in the next tutorial.
 +
 Our subclass will have two constructors here: one will be used on the server side and will contain the real ''Inventory'' and another one will be used on the client side to hold the ''ItemStack''s and synchronize them. Our subclass will have two constructors here: one will be used on the server side and will contain the real ''Inventory'' and another one will be used on the client side to hold the ''ItemStack''s and synchronize them.
  
Line 12: Line 13:
  
 ===== Block and BlockEntity classes ===== ===== Block and BlockEntity classes =====
- 
  
 First we need to create the ''Block'' and its ''BlockEntity''. First we need to create the ''Block'' and its ''BlockEntity''.
Line 20: Line 20:
     protected BoxBlock(Settings settings) {     protected BoxBlock(Settings settings) {
         super(settings);         super(settings);
 +    }
 +
 +    // This method is required since 1.20.5.
 +    @Override
 +    protected MapCodec<? extends BoxBlock> getCodec() {
 +        return createCodec(BoxBlock::new);
     }     }
  
Line 29: Line 35:
     @Override     @Override
     public BlockRenderType getRenderType(BlockState state) {     public BlockRenderType getRenderType(BlockState state) {
-        //With inheriting from BlockWithEntity this defaults to INVISIBLE, so we need to change that!+        // With inheriting from BlockWithEntity this defaults to INVISIBLE, so we need to change that!
         return BlockRenderType.MODEL;         return BlockRenderType.MODEL;
     }     }
  
     @Override     @Override
-    public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {+    public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit) {
         if (!world.isClient) {         if (!world.isClient) {
-            //This will call the createScreenHandlerFactory method from BlockWithEntity, which will return our blockEntity casted to +            // This will call the createScreenHandlerFactory method from BlockWithEntity, which will return our blockEntity casted to 
-            //a namedScreenHandlerFactory. If your block class does not extend BlockWithEntity, it needs to implement createScreenHandlerFactory.+            // a namedScreenHandlerFactory. If your block class does not extend BlockWithEntity, it needs to implement createScreenHandlerFactory.
             NamedScreenHandlerFactory screenHandlerFactory = state.createScreenHandlerFactory(world, pos);             NamedScreenHandlerFactory screenHandlerFactory = state.createScreenHandlerFactory(world, pos);
  
             if (screenHandlerFactory != null) {             if (screenHandlerFactory != null) {
-                //With this call the server will request the client to open the appropriate Screenhandler+                // With this call the server will request the client to open the appropriate Screenhandler
                 player.openHandledScreen(screenHandlerFactory);                 player.openHandledScreen(screenHandlerFactory);
             }             }
Line 49: Line 55:
          
          
-    //This method will drop all items onto the ground when the block is broken+    // This method will drop all items onto the ground when the block is broken
     @Override     @Override
     public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {     public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
         if (state.getBlock() != newState.getBlock()) {         if (state.getBlock() != newState.getBlock()) {
             BlockEntity blockEntity = world.getBlockEntity(pos);             BlockEntity blockEntity = world.getBlockEntity(pos);
-            if (blockEntity instanceof BoxBlockEntity) { +            if (blockEntity instanceof BoxBlockEntity boxBlockEntity) { 
-                ItemScatterer.spawn(world, pos, (BoxBlockEntity)blockEntity);+                ItemScatterer.spawn(world, pos, boxblockEntity);
                 // update comparators                 // update comparators
                 world.updateComparators(pos,this);                 world.updateComparators(pos,this);
Line 86: Line 92:
  
  
-    //From the ImplementedInventory Interface+    // From the ImplementedInventory Interface
  
     @Override     @Override
     public DefaultedList<ItemStack> getItems() {     public DefaultedList<ItemStack> getItems() {
         return inventory;         return inventory;
- 
     }     }
  
-    //These Methods are from the NamedScreenHandlerFactory Interface +    // These Methods are from the NamedScreenHandlerFactory Interface 
-    //createMenu creates the ScreenHandler itself +    // createMenu creates the ScreenHandler itself 
-    //getDisplayName will Provide its name which is normally shown at the top+    // `getDisplayNamewill Provide its name which is normally shown at the top
  
     @Override     @Override
     public ScreenHandler createMenu(int syncId, PlayerInventory playerInventory, PlayerEntity player) {     public ScreenHandler createMenu(int syncId, PlayerInventory playerInventory, PlayerEntity player) {
-        //We provide *this* to the screenHandler as our class Implements Inventory +        // We provide *this* to the screenHandler as our class Implements Inventory 
-        //Only the Server has the Inventory at the start, this will be synced to the client in the ScreenHandler+        // Only the Server has the Inventory at the start, this will be synced to the client in the ScreenHandler
         return new BoxScreenHandler(syncId, playerInventory, this);         return new BoxScreenHandler(syncId, playerInventory, this);
     }     }
Line 107: Line 112:
     @Override     @Override
     public Text getDisplayName() {     public Text getDisplayName() {
-        return new TranslatableText(getCachedState().getBlock().getTranslationKey());+        // for 1.19+ 
 +        return Text.translatable(getCachedState().getBlock().getTranslationKey()); 
 +        // for earlier versions 
 +        // return new TranslatableText(getCachedState().getBlock().getTranslationKey());
     }     }
          
 +    // For the following two methods, for earlier versions, remove the parameter `registryLookup`.
     @Override     @Override
-    public void readNbt(NbtCompound nbt) { +    public void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) { 
-        super.readNbt(nbt); +        super.readNbt(nbt, registryLookup); 
-        Inventories.readNbt(nbt, this.inventory);+        Inventories.readNbt(nbt, this.inventory, registryLookup);
     }     }
  
     @Override     @Override
-    public NbtCompound writeNbt(NbtCompound nbt) { +    public NbtCompound writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) { 
-        super.writeNbt(nbt); +        super.writeNbt(nbt, registryLookup); 
-        Inventories.writeNbt(nbt, this.inventory);+        Inventories.writeNbt(nbt, this.inventory, registryLookup);
         return nbt;         return nbt;
     }     }
Line 127: Line 136:
  
 ===== Registering Block, BlockItem and BlockEntity ===== ===== Registering Block, BlockItem and BlockEntity =====
- +In this example, the block, block item and block entity are directly registered in ''ExampleMod'' class. You may also need to consider placing them into separate classes (see [[blocks]] and [[blockentity]]) when actually developing mods.
  
 <code java [enable_line_numbers="true"] ExampleMod.java> <code java [enable_line_numbers="true"] ExampleMod.java>
 public class ExampleMod implements ModInitializer { public class ExampleMod implements ModInitializer {
  
-    public static final Block BOX_BLOCK; +    // For versions before 1.21, replace `Identifier.of` with `new Identifier`. 
-    public static final BlockItem BOX_BLOCK_ITEM; +    public static final Block BOX_BLOCK = Registry.register(Registries.BLOCK, Identifier.of("tutorial""box_block"), 
-    public static final BlockEntityType<BoxBlockEntity> BOX_BLOCK_ENTITY; +        new BoxBlock(AbstractBlock.Settings.copyOf(Blocks.CHEST))); 
- +         
-    public static final String MOD_ID = "testmod"; +    public static final BlockItem BOX_BLOCK_ITEM = Registry.register(Registries.ITEM, Identifier.of("tutorial""block"), 
-    // a public identifier for multiple parts of our bigger chest +        new BlockItem(BOX_BLOCK, new Item.Settings())); 
-    public static final Identifier BOX = new Identifier(MOD_ID, "box_block"); +         
- +    public static final BlockEntityType<BoxBlockEntity> BOX_BLOCK_ENTITY = Registry.register(Registry.BLOCK_ENTITY_TYPE, Identifier.of("tutorial""box_block"),  
-    static +        BlockEntityType.Builder.create(BoxBlockEntity::new, BOX_BLOCK).build()); 
-        BOX_BLOCK = Registry.register(Registry.BLOCK, BOX, new BoxBlock(FabricBlockSettings.copyOf(Blocks.CHEST))); +    // In 1.17 use FabricBlockEntityTypeBuilder instead of BlockEntityType.Builder 
-        BOX_BLOCK_ITEM = Registry.register(Registry.ITEM, BOX, new BlockItem(BOX_BLOCK, new Item.Settings().group(ItemGroup.MISC))); +    // public static final BlockEntityType<BoxBlockEntity> BOX_BLOCK_ENTITY = Registry.register(Registry.BLOCK_ENTITY_TYPE, new Identifier("tutorial""box_block"), 
- +    //     FabricBlockEntityTypeBuilder.create(BoxBlockEntity::new, BOX_BLOCK).build(null));;
-        //The parameter of build at the very end is always null, do not worry about it +
-        // pre-1.17 +
-        BOX_BLOCK_ENTITY = Registry.register(Registry.BLOCK_ENTITY_TYPE, BOX, BlockEntityType.Builder.create(BoxBlockEntity::new, BOX_BLOCK).build(null)); +
-        // In 1.17 use FabricBlockEntityTypeBuilder instead of BlockEntityType.Builder +
-        BOX_BLOCK_ENTITY = Registry.register(Registry.BLOCK_ENTITY_TYPE, BOX, FabricBlockEntityTypeBuilder.create(BoxBlockEntity::new, BOX_BLOCK).build(null)); +
-    }+
  
     @Override     @Override
     public void onInitialize() {     public void onInitialize() {
- 
     }     }
 } }
- 
- 
 </code> </code>
  
Line 169: Line 168:
     private final Inventory inventory;     private final Inventory inventory;
  
-    //This constructor gets called on the client when the server wants it to open the screenHandler, +    // This constructor gets called on the client when the server wants it to open the screenHandler, 
-    //The client will call the other constructor with an empty Inventory and the screenHandler will automatically +    // The client will call the other constructor with an empty Inventory and the screenHandler will automatically 
-    //sync this empty inventory with the inventory on the server.+    // sync this empty inventory with the inventory on the server.
     public BoxScreenHandler(int syncId, PlayerInventory playerInventory) {     public BoxScreenHandler(int syncId, PlayerInventory playerInventory) {
         this(syncId, playerInventory, new SimpleInventory(9));         this(syncId, playerInventory, new SimpleInventory(9));
     }     }
  
-    //This constructor gets called from the BlockEntity on the server without calling the other constructor first, the server knows the inventory of the container +    // This constructor gets called from the BlockEntity on the server without calling the other constructor first, the server knows the inventory of the container 
-    //and can therefore directly provide it as an argument. This inventory will then be synced to the client.+    // and can therefore directly provide it as an argument. This inventory will then be synced to the client.
     public BoxScreenHandler(int syncId, PlayerInventory playerInventory, Inventory inventory) {     public BoxScreenHandler(int syncId, PlayerInventory playerInventory, Inventory inventory) {
         super(ExampleMod.BOX_SCREEN_HANDLER, syncId);         super(ExampleMod.BOX_SCREEN_HANDLER, syncId);
         checkSize(inventory, 9);         checkSize(inventory, 9);
         this.inventory = inventory;         this.inventory = inventory;
-        //some inventories do custom logic when a player opens it.+        // some inventories do custom logic when a player opens it.
         inventory.onOpen(playerInventory.player);         inventory.onOpen(playerInventory.player);
  
-        //This will place the slot in the correct locations for a 3x3 Grid. The slots exist on both server and client! +        // This will place the slot in the correct locations for a 3x3 Grid. The slots exist on both server and client! 
-        //This will not render the background of the slots however, this is the Screens job+        // This will not render the background of the slots however, this is the Screens job
         int m;         int m;
         int l;         int l;
-        //Our inventory+        // Our inventory
         for (m = 0; m < 3; ++m) {         for (m = 0; m < 3; ++m) {
             for (l = 0; l < 3; ++l) {             for (l = 0; l < 3; ++l) {
Line 195: Line 194:
             }             }
         }         }
-        //The player inventory+        // The player inventory
         for (m = 0; m < 3; ++m) {         for (m = 0; m < 3; ++m) {
             for (l = 0; l < 9; ++l) {             for (l = 0; l < 9; ++l) {
Line 201: Line 200:
             }             }
         }         }
-        //The player Hotbar+        // The player Hotbar
         for (m = 0; m < 9; ++m) {         for (m = 0; m < 9; ++m) {
             this.addSlot(new Slot(playerInventory, m, 8 + m * 18, 142));             this.addSlot(new Slot(playerInventory, m, 8 + m * 18, 142));
Line 215: Line 214:
     // Shift + Player Inv Slot     // Shift + Player Inv Slot
     @Override     @Override
-    public ItemStack transferSlot(PlayerEntity player, int invSlot) {+    public ItemStack quickMove(PlayerEntity player, int invSlot) {
         ItemStack newStack = ItemStack.EMPTY;         ItemStack newStack = ItemStack.EMPTY;
         Slot slot = this.slots.get(invSlot);         Slot slot = this.slots.get(invSlot);
Line 243: Line 242:
  
 <code java [enable_line_numbers="true"] BoxScreen.java> <code java [enable_line_numbers="true"] BoxScreen.java>
-public class BoxScreen extends HandledScreen<ScreenHandler> { +public class BoxScreen extends HandledScreen<BoxScreenHandler> { 
-    //A path to the gui texture. In this example we use the texture from the dispenser +    // A path to the gui texture. In this example we use the texture from the dispenser 
-    private static final Identifier TEXTURE = new Identifier("minecraft", "textures/gui/container/dispenser.png");+     
 +    private static final Identifier TEXTURE = Identifier.ofVanilla("textures/gui/container/dispenser.png"); 
 +    // For versions before 1.21: 
 +    // private static final Identifier TEXTURE = new Identifier("minecraft", "textures/gui/container/dispenser.png");
  
-    public BoxScreen(ScreenHandler handler, PlayerInventory inventory, Text title) {+    public BoxScreen(BoxScreenHandler handler, PlayerInventory inventory, Text title) {
         super(handler, inventory, title);         super(handler, inventory, title);
     }     }
  
     @Override     @Override
-    protected void drawBackground(MatrixStack matrices, float delta, int mouseX, int mouseY) { +    protected void drawBackground(DrawContext context, float delta, int mouseX, int mouseY) { 
-        RenderSystem.setShader(GameRenderer::getPositionTexShader);+        RenderSystem.setShader(GameRenderer::getPositionTexProgram);
         RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);         RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
         RenderSystem.setShaderTexture(0, TEXTURE);         RenderSystem.setShaderTexture(0, TEXTURE);
         int x = (width - backgroundWidth) / 2;         int x = (width - backgroundWidth) / 2;
         int y = (height - backgroundHeight) / 2;         int y = (height - backgroundHeight) / 2;
-        drawTexture(matrices, x, y, 0, 0, backgroundWidth, backgroundHeight);+        context.drawTexture(TEXTURE, x, y, 0, 0, backgroundWidth, backgroundHeight);
     }     }
  
     @Override     @Override
-    public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { +    public void render(DrawContext context, int mouseX, int mouseY, float delta) { 
-        renderBackground(matrices); +        renderBackground(context, mouseX, mouseY, delta); 
-        super.render(matrices, mouseX, mouseY, delta); +        super.render(context, mouseX, mouseY, delta); 
-        drawMouseoverTooltip(matrices, mouseX, mouseY);+        drawMouseoverTooltip(context, mouseX, mouseY);
     }     }
  
Line 278: Line 280:
  
 ===== Registering our Screen and ScreenHandler ===== ===== Registering our Screen and ScreenHandler =====
- 
  
 As screens are a client-only concept, we can only register them on the client. As screens are a client-only concept, we can only register them on the client.
  
 <code java [enable_line_numbers="true"] ExampleModClient.java> <code java [enable_line_numbers="true"] ExampleModClient.java>
- 
 @Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
 public class ExampleClientMod implements ClientModInitializer { public class ExampleClientMod implements ClientModInitializer {
     @Override     @Override
     public void onInitializeClient() {     public void onInitializeClient() {
-        ScreenRegistry.register(ExampleMod.BOX_SCREEN_HANDLER, BoxScreen::new);+        HandledScreens.register(ExampleMod.BOX_SCREEN_HANDLER, BoxScreen::new);
     }     }
 } }
Line 295: Line 295:
  
 Don't forget to register this entrypoint in ''fabric.mod.json'' if you haven't done it yet: Don't forget to register this entrypoint in ''fabric.mod.json'' if you haven't done it yet:
-<code json> +<code javascript src/main/resources/fabric.mod.json> 
-/* ... */+
 +   /* ... */
   "entrypoints": {   "entrypoints": {
     /* ... */     /* ... */
     "client": [     "client": [
-      "tutorial.path.to.ExampleModClient"+      "net.fabricmc.example.ExampleModClient"
     ]     ]
   },   },
 +  /* ... */
 +}
 </code> </code>
  
Line 310: Line 313:
 public class ExampleMod implements ModInitializer { public class ExampleMod implements ModInitializer {
     [...]     [...]
-    public static final ScreenHandlerType<BoxScreenHandler> BOX_SCREEN_HANDLER+    public static final ScreenHandlerType<BoxScreenHandler> BOX_SCREEN_HANDLER = Registry.register(Registries.SCREEN_HANDLER, Identifier.of("tutorial""box_block"), new ScreenHandlerType<>(BoxScreenHandler::new, FeatureSet.empty())); 
-    [...+    
-    static { +
-        [...] +
-        //We use registerSimple here because our Entity is not an ExtendedScreenHandlerFactory +
-        //but a NamedScreenHandlerFactory. +
-        //In a later Tutorial you will see what ExtendedScreenHandlerFactory can do! +
-        BOX_SCREEN_HANDLER = ScreenHandlerRegistry.registerSimple(BOX, BoxScreenHandler::new); +
-    +
     @Override     @Override
     public void onInitialize() {     public void onInitialize() {
 +        [...]
     }     }
 } }
Line 333: Line 328:
  
 ===== Further Reading ===== ===== Further Reading =====
-[[tutorial:extendedscreenhandler|Syncing Custom Data with Extended ScreenHandlers when screen is opened]] +  * [[tutorial:extendedscreenhandler|Syncing Custom Data with Extended ScreenHandlers when screen is opened]] 
- +  [[tutorial:propertydelegates|Syncing Integers continuously with PropertyDelegates]]
-[[tutorial:propertydelegates|Syncing Integers continuously with PropertyDelegates]]+
  
-An example mod using the ''ScreenHandler'' API: [[https://github.com/FabricMC/fabric/tree/1.16/fabric-screen-handler-api-v1/src/testmod|ExampleMod on Github]].+An example mod using the ''ScreenHandler'' API: [[https://github.com/FabricMC/fabric/tree/1.16/fabric-screen-handler-api-v1/src/testmod|ExampleMod on Github]].
  
tutorial/screenhandler.1630456103.txt.gz · Last modified: 2021/09/01 00:28 by lvanderzande