Jump to content

TileEntity with gui inventory change doesn't trigger writeToNBT automatically?


Frefreak

Recommended Posts

I implemented a block with tileentity which has a gui and inventory using capabilities and `IItemHandler`. After some struggling everything seems to work fine except I notice that when I put something into the only slot of that block (or TE), then exit and reconnect, that something is very likely to disappear. Here are the code of that tileentity and container. This happens when I don't override `detectAndSendChanges`

Spoiler

TileEntityREAnalyzer.java:


package nz.carso.reveng.common.block;

import net.minecraft.block.state.IBlockState;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.ITickable;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.ItemStackHandler;
import javax.annotation.Nullable;

import static com.google.common.primitives.Ints.min;

public class TileEntityREAnalyzer extends TileEntity implements ITickable {

    ItemStackHandler inv = new ItemStackHandler(1);
    public IItemHandler[] targetCap = {null, null, null, null, null, null, null};
    public int selectedFace = 0;
    public int selectedSlot = -1;
    public int[] selectedSlots = {0, 0, 0, 0, 0, 0, 0};

    @Override
    public void readFromNBT(NBTTagCompound compound) {
        super.readFromNBT(compound);
        System.out.println("read nbt");
        inv.deserializeNBT(compound.getCompoundTag("inv"));
        selectedFace = compound.getInteger("face");
        selectedSlot = compound.getInteger("slot");
        selectedSlots = compound.getIntArray("slots");
    }

    @Override
    public NBTTagCompound writeToNBT(NBTTagCompound compound) {
        if (world.isRemote)
            System.out.println("client writeNBT");
        else
            System.out.println("server writeNBT");
        compound.setTag("inv", inv.serializeNBT());
        compound.setInteger("face", selectedFace);
        compound.setInteger("slot", selectedSlot);
        compound.setIntArray("slots", selectedSlots);
        return super.writeToNBT(compound);
    }

    @Override
    public boolean hasCapability(Capability<?> capability, @Nullable EnumFacing facing) {
        return capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY || super.hasCapability(capability, facing);
    }

    @Nullable
    @Override
    public <T> T getCapability(Capability<T> capability, @Nullable EnumFacing facing) {
        return capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY? (T)inv : super.getCapability(capability, facing);
    }


    @Override
    public void update() {
        IBlockState state = world.getBlockState(pos);
        TileEntity te = world.getTileEntity(pos.offset(state.getValue(BlockREAnalyzer.FACING)));
        if (te != null) {
            for (int i = 0; i < 6; i++) {
                EnumFacing facing = EnumFacing.getFront(i);
                if (te.hasCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, facing)) {
                    targetCap[i] = te.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, facing);
                } else
                    targetCap[i] = null;
            }
            if (te.hasCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, null))
                targetCap[6] = te.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, null);
            else
                targetCap[6] = null;
        } else {
            for (int i = 0; i < 6; i++) {
                targetCap[i] = null;
            }
            targetCap[6] = null;
        }
        if (targetCap[selectedFace] != null) {
            selectedSlot = min(selectedSlots[selectedFace], targetCap[selectedFace].getSlots() - 1);
            selectedSlots[selectedFace] = selectedSlot;
        }
        else
            selectedSlot = -1;

    }

    public void setSelectedFace(int face) {
        if (targetCap[face] != null) {
            this.selectedFace = face;
            this.selectedSlot = this.selectedSlots[face];
        }
    }

    public void setSelectedSlot(int slot) {
        this.selectedSlot = slot;
        this.selectedSlots[this.selectedFace] = slot;
    }

    public void tryInsert() {
        if (this.targetCap[this.selectedFace] != null) {
            ItemStack rem = this.targetCap[this.selectedFace].insertItem(this.selectedSlot, inv.getStackInSlot(0), false);
            inv.setStackInSlot(0, rem);
        }
    }

    @Override
    public NBTTagCompound getUpdateTag() {
        return writeToNBT(super.getUpdateTag());
    }

    @Override
    public void handleUpdateTag(NBTTagCompound tag) {
        super.handleUpdateTag(tag);
        readFromNBT(tag);
    }
}

 

Spoiler

ContainerREAnalyzer.java


package nz.carso.reveng.common.block;

import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.inventory.Container;
import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.SlotItemHandler;

import javax.annotation.Nonnull;

public class ContainerREAnalyzer extends Container {

    TileEntityREAnalyzer ana;
    IItemHandler inv;
    ItemStack is = ItemStack.EMPTY;
    @Override
    public void detectAndSendChanges() {
        super.detectAndSendChanges();
        if (inv.getStackInSlot(0).equals(is)) {
            ana.markDirty();
        }
        is = inv.getStackInSlot(0);
    }

    public ContainerREAnalyzer(InventoryPlayer invPlayer, TileEntityREAnalyzer te) {
        this.ana = te;
        inv = te.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, EnumFacing.NORTH);
        addSlotToContainer(new SlotItemHandler(inv, 0, 44, 32));
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 9; j++) {
                addSlotToContainer(new Slot(invPlayer, j + i * 9 + 9, 8 + j * 18, 84 + i * 18));
            }
        }

        for (int k = 0; k < 9; k++) {
            addSlotToContainer(new Slot(invPlayer, k, 8 + k * 18, 142));
        }
    }



    @Override
    public boolean canInteractWith(EntityPlayer playerIn) {
        return true;
    }

    @Override
    public ItemStack transferStackInSlot(EntityPlayer player, int index) {
        ItemStack itemstack = ItemStack.EMPTY;
        Slot slot = inventorySlots.get(index);

        if (slot != null && slot.getHasStack()) {
            ItemStack itemstack1 = slot.getStack();
            itemstack = itemstack1.copy();

            int containerSlots = inventorySlots.size() - player.inventory.mainInventory.size();

            if (index < containerSlots) {
                if (!this.mergeItemStack(itemstack1, containerSlots, inventorySlots.size(), true)) {
                    return ItemStack.EMPTY;
                }
            } else if (!this.mergeItemStack(itemstack1, 0, containerSlots, false)) {
                return ItemStack.EMPTY;
            }

            if (itemstack1.getCount() == 0) {
                slot.putStack(ItemStack.EMPTY);
            } else {
                slot.onSlotChanged();
            }

            if (itemstack1.getCount() == itemstack.getCount()) {
                return ItemStack.EMPTY;
            }

            slot.onTake(player, itemstack1);
        }

        return itemstack;
    }

}

 

Using some print statements I can confirm that `ItemStackHandler` in tileentity is correctly updated as soon as new items are put in (or taken out), however when exit there's no call to writeToNBT. Only after I trigger something in the client and send a packet to server, writeToNBT get called, and the changes to inventory persist. Currently the only way I found is to override `detectAndSendChanges` as show in the code. However I'm not sure is this the correct way to do this. Doesn't this should be handled automatically because from all the tutorials I read, they all basically does the same thing as mine here, and none of them mentions the necessity to override detectAndSendChanges to make the inventory behave "correctly" in common sense.

I want to ask if its I'm missing something obviously or is this override expected?

Link to comment
Share on other sites

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Link to comment
Share on other sites

5 hours ago, Draco18s said:

After adding  override for `getUpdatePacket` and `onDataPacket` then remove `detectAndSendChanges`, its inventory still doesn't persist. The problem seems to be the inventory doesn't even stored (no writeToNBT called) on server side, not a server to client syncing problem. `getUpdatePacket` doesn't been called when I make changes to the inventory. I read it elsewhere that getUpdatePacket is called by `notifyBlockUpdate`, do I need to call it somewhere manually?

Link to comment
Share on other sites

1 minute ago, diesieben07 said:

ItemStackHandler implements INBTSerializable.

 

5 hours ago, diesieben07 said:

This has nothing to do with server-client syncing. You are simply not saving the inventory to NBT.

 

Am I wrong or isn't that what he does?

Quote

compound.setTag("inv", inv.serializeNBT());

 

Link to comment
Share on other sites

9 hours ago, diesieben07 said:

I guess I was blind when I wrote that.

 

Is your tile entity registered properly? Show your block class.

Sorry for the late reply, here you go:

 

BlockREAnalyzer:

Spoiler

package nz.carso.reveng.common.block;

import net.minecraft.block.*;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.BlockStateContainer;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.ISidedInventory;
import net.minecraft.inventory.InventoryHelper;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentString;
import net.minecraft.world.World;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.ItemHandlerHelper;
import nz.carso.reveng.GeneralProxy;
import nz.carso.reveng.RevEng;
import nz.carso.reveng.common.gui.REGuiHandler;

import javax.annotation.Nullable;

public class BlockREAnalyzer extends BlockDirectional {

    public BlockREAnalyzer() {
        super(Material.ROCK);
        setHardness(2.0F);
        setSoundType(SoundType.STONE);
        setRegistryName("re_integration:re_analyzer");
        setDefaultState(this.blockState.getBaseState().withProperty(FACING, EnumFacing.UP));
        setUnlocalizedName("re_integration:re_analyzer");
        setCreativeTab(GeneralProxy.reTab);
    }

    @Override
    protected BlockStateContainer createBlockState() {
        return new BlockStateContainer(this, FACING);
    }

    @Override
    public void onBlockPlacedBy(World worldIn, BlockPos pos, IBlockState state, EntityLivingBase placer, ItemStack stack) {
        worldIn.setBlockState(pos, state.withProperty(FACING, EnumFacing.getDirectionFromEntityLiving(pos, placer).getOpposite()), 2);
    }

    @Override
    public IBlockState getStateFromMeta(int meta) {
        return this.getDefaultState().withProperty(FACING, EnumFacing.getFront(meta));
    }

    @Override
    public int getMetaFromState(IBlockState state) {
        return state.getValue(FACING).getIndex();
    }

    @Override
    public boolean onBlockActivated(World worldIn, BlockPos pos, IBlockState state, EntityPlayer playerIn, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) {
        if (!worldIn.isRemote)
            playerIn.openGui(RevEng.instance, REGuiHandler.ANALYZER, worldIn, pos.getX(), pos.getY(), pos.getZ());
        return true;
    }

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

    @Nullable
    @Override
    public TileEntity createTileEntity(World world, IBlockState state) {
        return new TileEntityREAnalyzer();
    }

    @Override
    public void breakBlock(World worldIn, BlockPos pos, IBlockState state) {
        TileEntity te = worldIn.getTileEntity(pos);
        if (te != null && te instanceof TileEntityREAnalyzer) {
            IItemHandler inv = te.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, null);
            InventoryHelper.spawnItemStack(worldIn, pos.getX(), pos.getY(), pos.getZ(), inv.getStackInSlot(0));
        }
        super.breakBlock(worldIn, pos, state);
    }
}

 

 

 

and this is the registering part:

 

BlockRE:

Spoiler

package nz.carso.reveng.common.block;

import jline.internal.Preconditions;
import net.minecraft.block.Block;
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.item.Item;
import net.minecraft.item.ItemBlock;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import net.minecraftforge.registries.IForgeRegistry;
import nz.carso.reveng.GeneralProxy;
import nz.carso.reveng.RevEng;

@Mod.EventBusSubscriber
public final class BlockRE {

    public final static Block analyzer = new BlockREAnalyzer();

    public final static Block[] allBlocks = {
            analyzer
    };

    @SubscribeEvent
    public static void registerBlocks(RegistryEvent.Register<Block> event) {
        IForgeRegistry<Block> reg = event.getRegistry();
        for (Block blk : allBlocks) {
            reg.register(blk);
        }
    }

    @SubscribeEvent
    public static void registerItems(RegistryEvent.Register<Item> event) {
        IForgeRegistry<Item> reg = event.getRegistry();
        for (Block blk : allBlocks) {
            Item item = new ItemBlock(blk).setRegistryName(Preconditions.checkNotNull(blk.getRegistryName()));
            reg.register(item);
        }
    }

    @SubscribeEvent
    @SideOnly(Side.CLIENT)
    public static void registerModels(ModelRegistryEvent event) {
        for (Block blk : allBlocks) {
            ModelLoader.setCustomModelResourceLocation(Item.getItemFromBlock(blk), 0,
                    new ModelResourceLocation(blk.getRegistryName(), "facing=up"));
        }
    }
}

 

tileentity is registered in GeneralProxy, ClientProxy and ServerProxy are just inherited from it and calling super methods in 3 stages respectively.

 

Spoiler

package nz.carso.reveng;

import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import net.minecraftforge.fml.common.registry.GameRegistry;
import nz.carso.reveng.common.block.BlockRE;
import nz.carso.reveng.common.block.TileEntityREAnalyzer;
import nz.carso.reveng.common.gui.REGuiHandler;
import org.apache.logging.log4j.Logger;

public class GeneralProxy {
    public static Logger logger;
    public final static CreativeTabs reTab = new CreativeTabs("RE integration") {
        @Override
        public ItemStack getTabIconItem() {
            return new ItemStack(Items.DIAMOND, 1);
        }

        @Override
        public String getTranslatedTabLabel() {
            return getTabLabel();
        }
    };

    public void preInit(FMLPreInitializationEvent event)
    {
        logger = event.getModLog();
        GameRegistry.registerTileEntity(TileEntityREAnalyzer.class, "re_analyzer");
    }


    public void init(FMLInitializationEvent event)
    {
    }

    public void postInit(FMLPostInitializationEvent event)
    {
    }
}

 

 

I also setup a git repo here

Link to comment
Share on other sites

16 hours ago, diesieben07 said:
  • Problematic code, issue 10.
  • Code style, issue 1.
  • I can't see the issue right now, however your Git repository is incomplete, so I cannot actually debug your mod.

I have made some changes, but I'm not sure what's missing in the git repository. I tried clone it to new folder and gradle build works just fine.

 

EDIT: Is it the missing of gradlew.bat? ok I added that.

Edited by Frefreak
Link to comment
Share on other sites

2 minutes ago, diesieben07 said:

I can't reproduce it at all. It works just fine.

Have you remove that `detectAndSendChanges` in ContainerREAnalyzer.java? That issue only arise without it. And I was asking if it is really necessary or if I did something wrong elsewhere since all the tutorials I read didn't mention I also need `detectAndSendChanges`in order to make the changes detected.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...

Important Information

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