This is an old revision of the document!
Table of Contents
Creating a Container Block (DRAFT) (NEW)
In this tutorial we will create simple Storage Block similar to a dispenser. Explaining the ScreenHandler API from Fabric and Vanilla Minecraft along the way.
Let us first explain some vocabulary:
Screenhandler: A ScreenHandler is a class responsible for Synchronizing inventory contents between the Client and the Server. It can sync integer values like furnace progress aswell, which will be showed in the next Tutorial.
Screen: The Screen class only exists on the client and will render the background and other decorations for your ScreenHandler on the Screen
Tutorials based on this: - Syncing Custom Data with Extended ScreenHandlers when screen is opened
Further Reading
This and the following Tutorials are heavily inspired by the ScreenHandlerAPI ExampleMod. It can be found here: ExampleMod on Github
The documentation for the Fabric ScreenHandlerAPI can be found here: ##javadoc link here##
Block and BlockEntity classes
First we need to create the Block and its BlockEntity
- BoxBlock.java
- public class BoxBlock extends BlockWithEntity {
- protected BoxBlock(Settings settings) {
- super(settings);
- }
- @Override
- return new BoxBlockEntity();
- }
- @Override
- public BlockRenderType getRenderType(BlockState state) {
- //With inheriting from BlockWithEntity this defaults to INVISIBLE, so we need to change that!
- return BlockRenderType.MODEL;
- }
- @Override
- public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
- if (!world.isClient) {
- //This will call the createScreenHandlerFactory method from blockWithEntity, which will return our blockEntity casted
- //to a namedScreenHandlerFactory
- NamedScreenHandlerFactory screenHandlerFactory = state.createScreenHandlerFactory(world, pos);
- if (screenHandlerFactory != null) {
- //With this call the server will request the client to open the appropriate Screenhandler
- player.openHandledScreen(screenHandlerFactory);
- }
- }
- return ActionResult.SUCCESS;
- }
- //This method will drop all items onto the ground when the block is broken
- @Override
- public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
- if (state.getBlock() != newState.getBlock()) {
- BlockEntity blockEntity = world.getBlockEntity(pos);
- if (blockEntity instanceof BoxBlockEntity) {
- ItemScatterer.spawn(world, pos, (BoxBlockEntity)blockEntity);
- // update comparators
- world.updateComparators(pos,this);
- }
- super.onStateReplaced(state, world, pos, newState, moved);
- }
- }
- @Override
- public boolean hasComparatorOutput(BlockState state) {
- return true;
- }
- @Override
- public int getComparatorOutput(BlockState state, World world, BlockPos pos) {
- return ScreenHandler.calculateComparatorOutput(world.getBlockEntity(pos))
- }
- }
Now we will create our BlockEntity, it will use the ImplementedInventory Interface from the Inventory Tutorial
- BoxBlockEntity.java
- public class BoxBlockEntity extends BlockEntity implements NamedScreenHandlerFactory, ImplementedInventory {
- private final DefaultedList<ItemStack> inventory = DefaultedList.ofSize(9, ItemStack.EMPTY);
- public BoxBlockEntity() {
- super(ExampleMod.BOX_BLOCK_ENTITY);
- }
- //From the ImplementedInventory Interface
- @Override
- public DefaultedList<ItemStack> getItems() {
- return inventory;
- }
- //These Methods are from the NamedScreenHandlerFactory Interface
- //createMenu creates the ScreenHandler itself
- //getDisplayName will Provide its name which is normally shown at the top
- @Override
- public ScreenHandler createMenu(int syncId, PlayerInventory playerInventory, PlayerEntity player) {
- //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
- return new BoxScreenHandler(syncId, playerInventory, this);
- }
- @Override
- public Text getDisplayName() {
- return new TranslatableText(getCachedState().getBlock().getTranslationKey());
- }
- @Override
- public void fromTag(BlockState state, CompoundTag tag) {
- super.fromTag(state, tag);
- inventory = DefaultedList.ofSize(invsize, ItemStack.EMPTY);
- Inventories.fromTag(tag, this.inventory);
- }
- @Override
- public CompoundTag toTag(CompoundTag tag) {
- super.toTag(tag);
- Inventories.toTag(tag, this.inventory);
- return tag;
- }
- }
Registering Block, BlockItem and BlockEntity
- ExampleMod.java
- public class ExampleMod implements ModInitializer {
- public static final Block BOX_BLOCK;
- public static final BlockItem BOX_BLOCK_ITEM;
- public static final BlockEntityType<BoxBlockEntity> BOX_BLOCK_ENTITY;
- // a public identifier for multiple parts of our bigger chest
- public static final Identifier BOX = new Identifier(MOD_ID, "box_block");
- static {
- //The parameter of build at the very end is always null, do not worry about it
- }
- @Override
- public void onInitialize() {
- }
- }
ScreenHandler and Screen
We need a ScreenHandler Class and a HandledScreen Class to display and sync the GUI. ScreenHandler classes are used to synchronize GUI state between the server and the client. HandledScreen classes are fully client-sided and are responsible for drawing GUI elements.
- BoxScreenHandler.java
- public class BoxScreenHandler extends ScreenHandler {
- private final Inventory inventory;
- //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
- //sync this empty inventory with the inventory on the server
- public BoxScreenHandler(int syncId, PlayerInventory playerInventory) {
- 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
- //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) {
- super(ExampleMod.BOX_SCREEN_HANDLER, syncId);
- checkSize(inventory, 9);
- this.inventory = inventory;
- //some inventories do custom logic when a player opens it.
- 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 not render the background of the slots however, this is the Screens job
- int m;
- int l;
- //Our inventory
- for (m = 0; m < 3; ++m) {
- for (l = 0; l < 3; ++l) {
- this.addSlot(new Slot(inventory, l + m * 3, 62 + l * 18, 17 + m * 18));
- }
- }
- //The player inventory
- for (m = 0; m < 3; ++m) {
- for (l = 0; l < 9; ++l) {
- this.addSlot(new Slot(playerInventory, l + m * 9 + 9, 8 + l * 18, 84 + m * 18));
- }
- }
- //The player Hotbar
- for (m = 0; m < 9; ++m) {
- this.addSlot(new Slot(playerInventory, m, 8 + m * 18, 142));
- }
- }
- @Override
- public boolean canUse(PlayerEntity player) {
- return this.inventory.canPlayerUse(player);
- }
- // Shift + Player Inv Slot
- @Override
- public ItemStack transferSlot(PlayerEntity player, int invSlot) {
- ItemStack newStack = ItemStack.EMPTY;
- Slot slot = this.slots.get(invSlot);
- if (slot != null && slot.hasStack()) {
- ItemStack originalStack = slot.getStack();
- newStack = originalStack.copy();
- if (invSlot < this.inventory.size()) {
- if (!this.insertItem(originalStack, this.inventory.size(), this.slots.size(), true)) {
- return ItemStack.EMPTY;
- }
- } else if (!this.insertItem(originalStack, 0, this.inventory.size(), false)) {
- return ItemStack.EMPTY;
- }
- if (originalStack.isEmpty()) {
- slot.setStack(ItemStack.EMPTY);
- } else {
- slot.markDirty();
- }
- }
- return newStack;
- }
- }
- BoxScreen.java
- public class BoxScreen extends HandledScreen<ScreenHandler> {
- //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");
- public BoxScreen(ScreenHandler handler, PlayerInventory inventory, Text title) {
- super(handler, inventory, title);
- }
- @Override
- protected void drawBackground(MatrixStack matrices, float delta, int mouseX, int mouseY) {
- RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
- client.getTextureManager().bindTexture(TEXTURE);
- int x = (width - backgroundWidth) / 2;
- int y = (height - backgroundHeight) / 2;
- drawTexture(matrices, x, y, 0, 0, backgroundWidth, backgroundHeight);
- }
- @Override
- public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
- renderBackground(matrices);
- super.render(matrices, mouseX, mouseY, delta);
- drawMouseoverTooltip(matrices, mouseX, mouseY);
- }
- @Override
- protected void init() {
- super.init();
- // Center the title
- titleX = (backgroundWidth - textRenderer.getWidth(title)) / 2;
- }
- }
Registering our Screen and ScreenHandler
As Screens are are client only concept, we can only register them on the client
- BiggerChestBlock.java
- public class ExampleClientMod implements ClientModInitializer {
- @Override
- public void onInitializeClient() {
- ScreenRegistry.register(ExampleMod.BOX_SCREEN_HANDLER, BoxScreen::new);
- }
- }
ScreenHandlers exist both on Client and on the Server and therefore have to be registered on Both
- ExampleMod.java
- public class ExampleMod implements ModInitializer {
- [...]
- public static final ScreenHandlerType<BoxScreenHandler> BOX_SCREEN_HANDLER;
- [...]
- 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
- public void onInitialize() {
- }
- }
Result
You have now created your own container Block, you could easily change it to contain a smaller or bigger Inventory. Maybe even apply a texture