Jump to content

[1.16.5] [FORGE] Custom Screen - Output is only on client


Recommended Posts

Posted (edited)

Hello, i'm trying to create a simil stone cutter block.

I have done 99% of the mod, create a block, a tilentity, container and a screen.

The tileentity has 9 slots (which 6 are input and 3 are for output).

The screen shows some recipes that the player can craft, putting specific items in the input slots.

When the player clicks on a item that he can craft, the item is added but only on client, so when an update block is sent, everything is removed from the "inventory" (it is not even registered on server side)

It's 4 days that i'm trying to understand how to do this... I appreciate only a guide (maybe a commented guide) to solve this.

 

Here are my classes:

 

The Block:

Spoiler
import net.minecraft.block.*;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.inventory.container.Container;
import net.minecraft.inventory.container.INamedContainerProvider;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.pathfinding.PathType;
import net.minecraft.state.DirectionProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.Direction;
import net.minecraft.util.Hand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TranslationTextComponent;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import net.minecraftforge.fml.network.NetworkHooks;
import pokemold.pokemoldbast.container.BallCutterContainer;
import pokemold.pokemoldbast.tileentity.BallCutterTileEntity;
import pokemold.pokemoldbast.tileentity.TileEntities;

import javax.annotation.Nullable;

public class BallCutter extends Block {

    public static final DirectionProperty FACING = HorizontalBlock.HORIZONTAL_FACING;
    protected static final VoxelShape SHAPE = Block.makeCuboidShape(0.0D, 0.0D, 0.0D, 16.0D, 9.0D, 16.0D);

    public BallCutter(Properties properties) {
        super(properties);

        this.setDefaultState(this.stateContainer.getBaseState().with(FACING, Direction.NORTH));
    }

    @Override
    public ActionResultType onBlockActivated(BlockState state, World worldIn, BlockPos pos, PlayerEntity player, Hand handIn, BlockRayTraceResult hit) {
        if(worldIn.isRemote()) return ActionResultType.SUCCESS;
        TileEntity tileEntity = worldIn.getTileEntity(pos);
        if(!(tileEntity instanceof BallCutterTileEntity)) return ActionResultType.SUCCESS;

        INamedContainerProvider containerProvider = createContainerProvider(worldIn, pos);
        NetworkHooks.openGui((ServerPlayerEntity) player, containerProvider, tileEntity.getPos());
        return ActionResultType.SUCCESS;
    }

    public INamedContainerProvider createContainerProvider(World world, BlockPos pos){

        return new INamedContainerProvider() {
            @Override
            public ITextComponent getDisplayName() {
                return new TranslationTextComponent("screen.pokemold.ball_cutter");
            }

            @Nullable
            @Override
            public Container createMenu(int i, PlayerInventory playerInventory, PlayerEntity player) {
                return new BallCutterContainer(i, world, pos, playerInventory, player);
            }
        };

    }

    @Nullable
    @Override
    public TileEntity createTileEntity(BlockState state, IBlockReader world) {
        return TileEntities.BALL_CUTTER_TILE.get().create();
    }

    @Override
    public boolean hasTileEntity(BlockState state) {
        return true;
    }

    public BlockState getStateForPlacement(BlockItemUseContext context) {
        return this.getDefaultState().with(FACING, context.getPlacementHorizontalFacing().getOpposite());
    }

    public VoxelShape getShape(BlockState state, IBlockReader worldIn, BlockPos pos, ISelectionContext context) {
        return SHAPE;
    }

    protected void fillStateContainer(StateContainer.Builder<Block, BlockState> builder) {
        builder.add(FACING);
    }

    public boolean allowsMovement(BlockState state, IBlockReader worldIn, BlockPos pos, PathType type) {
        return false;
    }

}

 

The Tile Entity:

Spoiler
import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.play.server.SUpdateTileEntityPacket;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.util.Direction;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.ItemStackHandler;
import net.minecraftforge.registries.ForgeRegistries;
import pokemold.pokemoldbast.recipe.RecipeResult;
import pokemold.pokemoldbast.recipe.ToRemove;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;

public class BallCutterTileEntity extends TileEntity{

    private final ItemStackHandler itemStackHandler = createItemHandler();
    private final LazyOptional<ItemStackHandler> handler = LazyOptional.of(()-> itemStackHandler);

    private boolean markDirty = true;

    private static final List<Item> acceptedItems = new ArrayList<>();
    static {
        acceptedItems.add(ForgeRegistries.ITEMS.getValue(new ResourceLocation("pixelmon", "red_apricorn")));
        acceptedItems.add(ForgeRegistries.ITEMS.getValue(new ResourceLocation("pixelmon", "yellow_apricorn")));
        acceptedItems.add(ForgeRegistries.ITEMS.getValue(new ResourceLocation("pixelmon", "black_apricorn")));
        acceptedItems.add(ForgeRegistries.ITEMS.getValue(new ResourceLocation("pixelmon", "white_apricorn")));
        acceptedItems.add(ForgeRegistries.ITEMS.getValue(new ResourceLocation("pixelmon", "green_apricorn")));
        acceptedItems.add(ForgeRegistries.ITEMS.getValue(new ResourceLocation("pixelmon", "pink_apricorn")));
        acceptedItems.add(ForgeRegistries.ITEMS.getValue(new ResourceLocation("pixelmon", "blue_apricorn")));
        //acceptedItems.forEach(x->System.out.println("red_apricon: " + x == null + (x.getRegistryName().toString())));
    }

    protected BallCutterTileEntity(TileEntityType<?> typeIn) {
        super(typeIn);
    }

    public BallCutterTileEntity() {
        this(TileEntities.BALL_CUTTER_TILE.get());
    }


    @Nullable
    @Override
    public SUpdateTileEntityPacket getUpdatePacket() {
        return new SUpdateTileEntityPacket(getPos(), 0, getUpdateTag());
    }

    @Override
    public CompoundNBT getUpdateTag() {
        CompoundNBT compoundNBT = super.getUpdateTag();
        compoundNBT.put("inv", itemStackHandler.serializeNBT());
        write(compoundNBT);
        return compoundNBT;
    }

    @Override
    public void handleUpdateTag(BlockState state, CompoundNBT tag) {
        read(state, tag);
    }


    @Override
    public void onDataPacket(NetworkManager net, SUpdateTileEntityPacket pkt) {
        CompoundNBT nbtTagCompound = pkt.getNbtCompound();
        super.read(getBlockState(), nbtTagCompound);
        read(getBlockState(), nbtTagCompound);
    }

    public ItemStackHandler createItemHandler(){
        return new ItemStackHandler( 9){




            @Override
            protected void onContentsChanged(int slot) {
                //super.onContentsChanged(slot);
                //updateContainingBlockInfo();
                //Objects.requireNonNull(world).notifyBlockUpdate(pos, getBlockState(), getBlockState(), Constants.BlockFlags.BLOCK_UPDATE);
                if(markDirty) markDirty();

            }

            @Override
            public boolean isItemValid(int slot, @Nonnull ItemStack stack) {
                if(slot >= 6) return false;

                boolean result = acceptedItems.stream().anyMatch(x -> x.equals(stack.getItem()));

                return result;
            }

            @Override
            public int getSlotLimit(int slot) {
                return 64;
            }

            @Nonnull
            @Override
            public ItemStack insertItem(int slot, @Nonnull ItemStack stack, boolean simulate) {

                if(!isItemValid(slot, stack)) return stack;

                return super.insertItem(slot, stack, simulate);
            }
        };
    }






    @Nonnull
    @Override
    public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) {

        if(cap == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) return handler.cast();

        return super.getCapability(cap, side);
    }

    @Override
    public void read(BlockState state, CompoundNBT nbt) {
        itemStackHandler.deserializeNBT(nbt.getCompound("inv"));
        super.read(state, nbt);
    }

    @Override
    public CompoundNBT write(CompoundNBT compound) {
        compound.put("inv", itemStackHandler.serializeNBT());
        return super.write(compound);
    }


    public int[] findOutputSlot(ItemStack item){

        int left = item.getCount();

        int[] result = new int[3];


        for(int i = 0; i < 3; i++){

            System.out.println("***********");
            if(left <= 0 ) break;

            System.out.println("***********2");

            ItemStack slotItem = itemStackHandler.getStackInSlot(6+i);
            System.out.println(slotItem.getItem());
            if(!slotItem.getItem().equals(item.getItem()) && !slotItem.isEmpty()) continue;

            System.out.println("after 2 if");

            int canFit = 64 - slotItem.getCount();
            System.out.println(i + ": can fit: " + canFit);
            int addingThisSlot = Math.min(item.getCount(), canFit);
            System.out.println(i + ": adding this slot: " + addingThisSlot);
            left = Math.abs(left - addingThisSlot);
            System.out.println(i + ": left: " + left);
            result[i] = addingThisSlot;



        }

        return result;
    }


    public boolean hasSpace(Item item, int amount){


        int spaces = 0;
        for(int i = 0; i < 3; i++){

            ItemStack outputStack = itemStackHandler.getStackInSlot(6+i);
            if(outputStack.isEmpty() || (outputStack.getItem().equals(item) && outputStack.getCount() < 64-amount)) return true;
        }
        return false;



    }


    private void output(Item item, int howMany) {

        int[] slots = findOutputSlot(new ItemStack(item, howMany));

        for(int i = 0; i < slots.length; i++){

            if(slots[i] == 0) continue;

            ItemStack old = itemStackHandler.getStackInSlot(i+6);
            markDirty = false;
            itemStackHandler.insertItem(i+6, new ItemStack(item, old.getCount()+slots[i]), false);
            markDirty = true;
        }


    }

    public void craft(RecipeResult result, int howMany){

        if(!hasSpace(result.getRecipe().getItem(), howMany)) return;

        Map<Item, Integer> ingredient = result.getRecipe().getIngredient(howMany);

        Map<Item, Integer> removed = new HashMap<>(ingredient);

        ingredient.forEach((item, amount) ->{

            if(removed.get(item) <= 0) return;

            for(int i = 0; i < 6; i++){
                ItemStack itemStack = itemStackHandler.getStackInSlot(i);
                if(!itemStack.getItem().equals(item)) continue;
                int leftToRemove = removed.get(item);

                int removing = Math.min(itemStack.getCount(), leftToRemove);
                removed.put(item, leftToRemove-removing);
                itemStack.shrink(removing);
            }
            output(result.getRecipe().getItem(), howMany);
        });




    /*
    public void craft(RecipeResult recipe) {


        ToRemove[] toRemoves = recipe.getToRemoves();

        for(ToRemove toRemove : toRemoves){

            if(toRemove.getAmount() <= 0) continue;
            for(int i = 0; i < 6; i++){

                ItemStack itemStack = itemStackHandler.getStackInSlot(i);
                if(!itemStack.getItem().equals(toRemove.getIngredient())) continue;

                int amountToRemove = toRemove.remove(itemStack.getCount());
                itemStack.shrink(amountToRemove);
                toRemove.decrease(amountToRemove);
            }

            if(toRemove.getAmount() <= 0) itemStackHandler.insertItem(6, )

        }

     */









    }

}

 

The Screen:

Spoiler
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.gui.screen.inventory.ContainerScreen;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.ITextComponent;
import pokemold.pokemoldbast.ItemInit;
import pokemold.pokemoldbast.Pokemoldbast;
import pokemold.pokemoldbast.container.BallCutterContainer;
import pokemold.pokemoldbast.recipe.RecipeResult;

import java.util.Collection;

public class BallCutterScreen extends ContainerScreen<BallCutterContainer> {

    private final ResourceLocation GUI = new ResourceLocation(Pokemoldbast.MOD_ID, "textures/gui/ball_cutter_gui.png");
    public BallCutterScreen(BallCutterContainer screenContainer, PlayerInventory inv, ITextComponent titleIn) {
        super(screenContainer, inv, titleIn);
        this.ySize = getYSize()+15;
        this.playerInventoryTitleY = 88;
    }




    @Override
    protected void init() {
        super.init();
        final int[] index = new int[]{0};
        ItemInit.getBallDisc().forEach(item->{
            final int startX = 73 + this.guiLeft;
            final int startY = 19 + this.guiTop;

            int layer = index[0] / 6;

            int x = startX + ( Math.abs(index[0] - 6*layer)*16);
            int y = startY + layer * 16;
            this.addButton(new BlankButton(x, y, 16, 16, button ->
            {

                switch (((BlankButton) button).clickedMouseButton){

                    case 0:{


                        RecipeResult recipe = container.getRecipe(item);
                        if(recipe.getAmount() <= 0) return;

                        container.craft(recipe);


                    }break;
                    case 1:{
                        System.out.println("Right Click");
                    }break;

                }

            }));
            index[0]++;
        });
    }







    @Override
    public void render(MatrixStack matrixStack, int mouseX, int mouseY, float partialTicks) {
        this.renderBackground(matrixStack);
        super.render(matrixStack, mouseX, mouseY, partialTicks);
        this.renderHoveredTooltip(matrixStack, mouseX, mouseY);


        final int startX = 73 + this.guiLeft;
        final int startY = 19 + this.guiTop;


        final int[] index = new int[]{0};



        int i = this.guiLeft;
        int j = this.guiTop;

        ItemInit.getBallDisc().forEach(item->{

            int layer = index[0] / 6;

            int x = startX + ( Math.abs(index[0] - 6*layer)*16);
            int y = startY + layer * 16;

            this.itemRenderer.renderItemIntoGUI(new ItemStack(item), x,  y);
            index[0]++;
        });

    }



    @Override
    protected void drawGuiContainerBackgroundLayer(MatrixStack matrixStack, float partialTicks, int x, int y) {

        RenderSystem.color4f(1f,1f,1f,1f);
        this.minecraft.getTextureManager().bindTexture(GUI);
        int i = this.guiLeft;
        int j = this.guiTop;


        this.blit(matrixStack, i, j,  0, 0, this.xSize, this.ySize);

        final Collection<RecipeResult> validRecipes = container.getValidRecipes();

        final int startX = 73 + this.guiLeft;
        final int startY = 19 + this.guiTop;

        final int[] index = new int[]{0};


        ItemInit.getBallDisc().forEach(item-> {

            int layer = index[0] / 6;

            int x1 = startX + ( Math.abs(index[0] - 6*layer)*16);
            int y1 = startY + layer * 16;

            RenderSystem.color4f(1f,1f,1f,1f);
            if(validRecipes.stream().anyMatch(recipe->recipe.getRecipe().getItem().equals(item.getItem()))){
                this.blit(matrixStack, x1, y1,  176, 16, 16, 16);
            }else this.blit(matrixStack, x1, y1,  176, 0, 16, 16);



            index[0]++;

        });



    }
}

 

The Container:

Spoiler

import net.minecraft.block.FurnaceBlock;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.container.Container;
import net.minecraft.inventory.container.FurnaceContainer;
import net.minecraft.inventory.container.Slot;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.FurnaceTileEntity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.IWorldPosCallable;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.SlotItemHandler;
import net.minecraftforge.items.wrapper.InvWrapper;
import net.minecraftforge.registries.ForgeRegistries;
import pokemold.pokemoldbast.block.BlockInit;
import pokemold.pokemoldbast.recipe.Recipe;
import pokemold.pokemoldbast.recipe.RecipeResult;
import pokemold.pokemoldbast.recipe.RecipesUtil;
import pokemold.pokemoldbast.tileentity.BallCutterTileEntity;


import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class BallCutterContainer extends Container {

    private final TileEntity tileEntity;
    private final PlayerEntity player;
    private final IItemHandler playerInventory;

    public BallCutterContainer(int windowID, World world, BlockPos pos, PlayerInventory playerInventory, PlayerEntity playerEntity) {
        super(ContainersInit.BALL_CUTTER_CONTAINER.get(), windowID);
        this.tileEntity = world.getTileEntity(pos);
        this.player = playerEntity;
        this.playerInventory = new InvWrapper(playerInventory);



        layoutPlayerInventorySlots(8, 99);

        if(tileEntity == null) return;
        tileEntity.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY).ifPresent(x->{
            addSlot(new SlotItemHandler(x, 0, 8, 19));
            addSlot(new SlotItemHandler(x, 1, 27, 19));
            addSlot(new SlotItemHandler(x, 2, 46, 19));
            addSlot(new SlotItemHandler(x, 3, 8, 38));
            addSlot(new SlotItemHandler(x, 4, 27, 38));
            addSlot(new SlotItemHandler(x, 5, 46, 38));
            addSlot(new SlotItemHandler(x, 6, 8, 66));
            addSlot(new SlotItemHandler(x, 7, 27, 66));
            addSlot(new SlotItemHandler(x, 8, 46, 66));

        });
    }

    @Override
    public void detectAndSendChanges() {
        super.detectAndSendChanges();
    }


    public Collection<RecipeResult> getValidRecipes(){
        ItemStack[] input = new ItemStack[6];
        for(int i = 0; i < 6; i++){
            input[i] =  getInventory().get(i+36);
        }
        List<RecipeResult> valid = RecipesUtil.getInstance().getRecipeFrom(input);
        valid.removeIf(x->x.getAmount() <= 0);
        return valid;
    }


    public RecipeResult getRecipe(Item item){
        ItemStack[] input = new ItemStack[6];
        for(int i = 0; i < 6; i++){
            input[i] =  getInventory().get(i+36);
        }
        return RecipesUtil.getInstance().getRecipeFrom(item, input);
    }


    @Override
    public boolean canInteractWith(PlayerEntity playerIn) {
        return isWithinUsableDistance(IWorldPosCallable.of(tileEntity.getWorld(), tileEntity.getPos()),
                playerIn, BlockInit.BALL_CUTTER.get());
    }



    public void craft(RecipeResult recipe) {
        ((BallCutterTileEntity) tileEntity).craft(recipe, 1);
    }



    private int addSlotRange(IItemHandler handler, int index, int x, int y, int amount, int dx) {
        for (int i = 0; i < amount; i++) {
            addSlot(new SlotItemHandler(handler, index, x, y));
            x += dx;
            index++;
        }

        return index;
    }

    private int addSlotBox(IItemHandler handler, int index, int x, int y, int horAmount, int dx, int verAmount, int dy) {
        for (int j = 0; j < verAmount; j++) {
            index = addSlotRange(handler, index, x, y, horAmount, dx);
            y += dy;
        }

        return index;
    }

    private void layoutPlayerInventorySlots(int leftCol, int topRow) {
        addSlotBox(playerInventory, 9, leftCol, topRow, 9, 18, 3, 18);
        topRow += 58;
        addSlotRange(playerInventory, 0, leftCol, topRow, 9, 18);
    }



    private static final int HOTBAR_SLOT_COUNT = 9;
    private static final int PLAYER_INVENTORY_ROW_COUNT = 3;
    private static final int PLAYER_INVENTORY_COLUMN_COUNT = 9;
    private static final int PLAYER_INVENTORY_SLOT_COUNT = PLAYER_INVENTORY_COLUMN_COUNT * PLAYER_INVENTORY_ROW_COUNT;
    private static final int VANILLA_SLOT_COUNT = HOTBAR_SLOT_COUNT + PLAYER_INVENTORY_SLOT_COUNT;
    private static final int VANILLA_FIRST_SLOT_INDEX = 0;
    private static final int TE_INVENTORY_FIRST_SLOT_INDEX = VANILLA_FIRST_SLOT_INDEX + VANILLA_SLOT_COUNT;

    // THIS YOU HAVE TO DEFINE!
    private static final int TE_INVENTORY_SLOT_COUNT = 9;  // must match TileEntityInventoryBasic.NUMBER_OF_SLOTS

    @Override
    public ItemStack transferStackInSlot(PlayerEntity playerIn, int index) {
        Slot sourceSlot = inventorySlots.get(index);
        if (sourceSlot == null || !sourceSlot.getHasStack()) return ItemStack.EMPTY;  //EMPTY_ITEM
        ItemStack sourceStack = sourceSlot.getStack();
        ItemStack copyOfSourceStack = sourceStack.copy();

        // Check if the slot clicked is one of the vanilla container slots
        if (index < VANILLA_FIRST_SLOT_INDEX + VANILLA_SLOT_COUNT) {
            // This is a vanilla container slot so merge the stack into the tile inventory
            if (!mergeItemStack(sourceStack, TE_INVENTORY_FIRST_SLOT_INDEX, TE_INVENTORY_FIRST_SLOT_INDEX
                    + TE_INVENTORY_SLOT_COUNT, false)) {
                return ItemStack.EMPTY;  // EMPTY_ITEM
            }
        } else if (index < TE_INVENTORY_FIRST_SLOT_INDEX + TE_INVENTORY_SLOT_COUNT) {
            // This is a TE slot so merge the stack into the players inventory
            if (!mergeItemStack(sourceStack, VANILLA_FIRST_SLOT_INDEX, VANILLA_FIRST_SLOT_INDEX + VANILLA_SLOT_COUNT, false)) {
                return ItemStack.EMPTY;
            }
        } else {
            System.out.println("Invalid slotIndex:" + index);
            return ItemStack.EMPTY;
        }
        // If stack size == 0 (the entire stack was moved) set slot contents to null
        if (sourceStack.getCount() == 0) {
            sourceSlot.putStack(ItemStack.EMPTY);
        } else {
            sourceSlot.onSlotChanged();
        }
        sourceSlot.onTake(player, sourceStack);
        return copyOfSourceStack;
    }

}

 

Here's how the gui (screen) works:

 

SseTY3c.png

 

 

every tip is very appreciated

 

Edited by DioDogz
changing code to java format
Posted

1.16.5 is no longer supported here. Please upgrade to 1.18.x and 1.19.x to receive support.

I'm not good at modding, but at least I can read a crash report (well enough). That's something, right?

Guest
This topic is now closed to further replies.

Announcements



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.